purchasekit 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80efbcba7ccd188e7f24d392bec5690bd1799d6aa3ad4c97045a6e7022242e64
4
- data.tar.gz: 1f6efa6565a3439bd10b36a0ec4aa484166201c4fdb372b87de73707b6fe8872
3
+ metadata.gz: 5d05f29f2c2248e33c8ee228dc191914f6aa66174505f5ddd3612987258c23a8
4
+ data.tar.gz: 1734e423d697dacab4cdfcf67bee287d43c48b6c5c40d38f229a1e2536ba5032
5
5
  SHA512:
6
- metadata.gz: f9ca56c119d365deadcff6b69826a5c5440e51d119bcbf560bab1cf3af1b7f228e7d5a8c331fcf13ef2affb6c7973c18e09a605ac5c6c36fa4761d282ac1e77c
7
- data.tar.gz: 1618b92df987fe7d7159391af48fa11de39b76993066239deb13b6a912b7c1dbba662ee998759adc30e0d392ba6024071b395f939fc86824c4eec064ebbec86e
6
+ metadata.gz: 6d1226d312912c774fef81504560f1bdbb8ec9be76d260588647494243e6319fa240a04f72e98048d6cd8ccf30f06754be95517c5ab79b2f174dbb839a7aaace
7
+ data.tar.gz: 88aa46c038eb5d35603df2a010fc4443949c8d25fbcdb7f17ecbbaa5e736422d8e8ea82652b0d8950d67245751f3717cd3ec5ba0fd6802756ef00e7b8a6e31e1
data/README.md CHANGED
@@ -113,6 +113,7 @@ end
113
113
  | `event.subscription_id` | Store's subscription ID |
114
114
  | `event.store` | `"apple"` or `"google"` |
115
115
  | `event.store_product_id` | e.g., `"com.example.pro.annual"` |
116
+ | `event.google_base_plan_id` | Google Play base plan ID (e.g., `"annual"`) when using umbrella subscriptions, nil otherwise |
116
117
  | `event.status` | `"active"`, `"canceled"`, `"expired"` |
117
118
  | `event.current_period_start` | Start of billing period |
118
119
  | `event.current_period_end` | End of billing period |
@@ -125,7 +126,36 @@ Webhooks may be delivered more than once. Write idempotent callbacks using `find
125
126
 
126
127
  ## Paywall helper
127
128
 
128
- Build a paywall using the included helper. Subscribe to the Turbo Stream for real-time redirects:
129
+ Build a paywall using the included helper. Subscribe to a Turbo Stream channel for real-time redirects after purchase. The `customer_id` you pass flows through the store and back to your webhook handler, so it must match what the handler expects.
130
+
131
+ ### With Pay
132
+
133
+ Pass the `Pay::Customer.id` (not your user ID), and subscribe to the Pay customer's `dom_id` channel:
134
+
135
+ ```erb
136
+ <% pay_customer = current_user.set_payment_processor(:purchasekit) %>
137
+
138
+ <%= turbo_stream_from dom_id(pay_customer) %>
139
+
140
+ <%= purchasekit_paywall customer_id: pay_customer.id, success_path: dashboard_path do |paywall| %>
141
+ <%= paywall.plan_option product: @annual, selected: true do %>
142
+ Annual - <%= paywall.price %>/year
143
+ <% end %>
144
+
145
+ <%= paywall.plan_option product: @monthly do %>
146
+ Monthly - <%= paywall.price %>/month
147
+ <% end %>
148
+
149
+ <%= paywall.submit "Subscribe" %>
150
+ <%= paywall.restore url: restore_purchases_path, class: "btn btn-link" %>
151
+ <% end %>
152
+ ```
153
+
154
+ `set_payment_processor(:purchasekit)` finds or creates the `Pay::Customer` row. Calling it on every paywall render guarantees the row exists before the webhook tries to look it up.
155
+
156
+ ### Without Pay
157
+
158
+ Use your own user ID for both the `customer_id` and the Turbo Stream channel:
129
159
 
130
160
  ```erb
131
161
  <%= turbo_stream_from "purchasekit_customer_#{current_user.id}" %>
@@ -1,6 +1,8 @@
1
1
  module PurchaseKit
2
2
  class PurchasesController < ApplicationController
3
3
  def create
4
+ raise PurchaseKit::NotFoundError, "No product selected" if params[:product_id].blank?
5
+
4
6
  intent = PurchaseKit::Purchase::Intent.create(
5
7
  product_id: params[:product_id],
6
8
  customer_id: params[:customer_id],
@@ -2,12 +2,15 @@ module PurchaseKit
2
2
  module PaywallHelper
3
3
  # Renders a paywall form that triggers native in-app purchases
4
4
  #
5
- # @param customer_id [String, Integer] Your user/customer identifier
5
+ # @param customer_id [String, Integer] The identifier passed back in webhook
6
+ # events. With Pay, this must be `Pay::Customer.id` (the webhook handler
7
+ # does `Pay::Customer.find(customer_id)`). Without Pay, use your own user ID.
6
8
  # @param success_path [String] Where to redirect after successful purchase
7
9
  # @yield [PaywallBuilder] Builder for plan options and buttons
8
10
  #
9
- # Example:
10
- # <%= purchasekit_paywall customer_id: current_user.id, success_path: dashboard_path do |paywall| %>
11
+ # Example (with Pay):
12
+ # <% pay_customer = current_user.set_payment_processor(:purchasekit) %>
13
+ # <%= purchasekit_paywall customer_id: pay_customer.id, success_path: dashboard_path do |paywall| %>
11
14
  # <%= paywall.plan_option product: @annual, selected: true do %>
12
15
  # Annual - <%= paywall.price %>
13
16
  # <% end %>
@@ -48,6 +51,7 @@ module PurchaseKit
48
51
  selected,
49
52
  id: input_id,
50
53
  class: input_class,
54
+ required: true,
51
55
  autocomplete: "off",
52
56
  data: {
53
57
  purchasekit__paywall_target: "planRadio",
@@ -94,6 +94,13 @@ module PurchaseKit
94
94
  payload[:store_product_id]
95
95
  end
96
96
 
97
+ # The Google Play base plan ID (e.g., "annual", "monthly").
98
+ # Only present when the purchase used Google's umbrella subscription
99
+ # with multiple base plans. Nil for Apple and for flat Google products.
100
+ def google_base_plan_id
101
+ payload[:google_base_plan_id]
102
+ end
103
+
97
104
  # The subscription name you configured in PurchaseKit
98
105
  def subscription_name
99
106
  payload[:subscription_name]
@@ -23,7 +23,10 @@ module PurchaseKit
23
23
  current_period_end: parse_time(event["current_period_end"]),
24
24
  trial_ends_at: parse_time(event["trial_ends_at"]),
25
25
  ends_at: parse_time(event["ends_at"]),
26
- data: (subscription.data || {}).merge("store" => event["store"])
26
+ data: (subscription.data || {}).merge(
27
+ "store" => event["store"],
28
+ "google_base_plan_id" => event["google_base_plan_id"]
29
+ ).compact
27
30
  )
28
31
 
29
32
  return unless is_new
@@ -6,21 +6,34 @@ module PurchaseKit
6
6
  include Turbo::Streams::ActionHelper
7
7
 
8
8
  def call(event)
9
- update_subscription(event,
9
+ customer = ::Pay::Customer.find(event["customer_id"])
10
+
11
+ subscription = ::Pay::Purchasekit::Subscription.find_or_initialize_by(
12
+ customer: customer,
13
+ processor_id: event["subscription_id"]
14
+ )
15
+ subscription.name ||= event["subscription_name"] || ::Pay.default_product_name
16
+ subscription.quantity ||= 1
17
+
18
+ subscription.update!(
10
19
  processor_plan: event["store_product_id"],
11
20
  status: event["status"],
12
21
  current_period_start: parse_time(event["current_period_start"]),
13
22
  current_period_end: parse_time(event["current_period_end"]),
14
23
  trial_ends_at: parse_time(event["trial_ends_at"]),
15
- ends_at: parse_time(event["ends_at"]))
24
+ ends_at: parse_time(event["ends_at"]),
25
+ data: (subscription.data || {}).merge(
26
+ "store" => event["store"],
27
+ "google_base_plan_id" => event["google_base_plan_id"]
28
+ ).compact
29
+ )
16
30
 
17
- broadcast_redirect(event) if event["success_path"].present?
31
+ broadcast_redirect(customer, event) if event["success_path"].present?
18
32
  end
19
33
 
20
34
  private
21
35
 
22
- def broadcast_redirect(event)
23
- customer = ::Pay::Customer.find(event["customer_id"])
36
+ def broadcast_redirect(customer, event)
24
37
  Turbo::StreamsChannel.broadcast_stream_to(
25
38
  dom_id(customer),
26
39
  content: turbo_stream_action_tag(:redirect, url: event["success_path"])
@@ -1,3 +1,3 @@
1
1
  module PurchaseKit
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: purchasekit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Masilotti