purchasekit 0.2.4 → 0.3.1
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 +4 -4
- data/LICENSE +9 -19
- data/README.md +27 -3
- data/app/controllers/purchase_kit/application_controller.rb +26 -0
- data/app/controllers/purchase_kit/webhooks_controller.rb +3 -0
- data/app/helpers/purchase_kit/pay/paywall_helper.rb +6 -13
- data/app/helpers/purchase_kit/paywall_helper.rb +0 -7
- data/app/javascript/controllers/purchasekit/paywall_controller.js +22 -10
- data/app/views/purchase_kit/pay/purchases/create.turbo_stream.erb +1 -1
- data/app/views/purchase_kit/purchases/_error.html.erb +3 -0
- data/lib/purchasekit/api_client.rb +1 -1
- data/lib/purchasekit/events.rb +26 -0
- data/lib/purchasekit/version.rb +1 -1
- data/lib/purchasekit.rb +2 -1
- metadata +4 -4
- data/app/javascript/controllers/purchasekit_pay/paywall_controller.js +0 -112
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad42037cf5c3b0f675db003d7a1565d755e51f74620f115ec9c25253843bb634
|
|
4
|
+
data.tar.gz: e53162e6e3abcdbfecfac4fd9049414e4a045c46f9c4155c8e3e91b69fe24465
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f97ddc9b714181a0e9c0ac24b5bfe8c9e57e59219e1d4fcb2b835bfc0bae47b4dbc68f43b14703f11073a5ac8077410fb4104934c74fc245e8b16aa08643273b
|
|
7
|
+
data.tar.gz: 92bc3584cafb0df48435e9d6e6c5ddb900c3eeff31755a464f92d0c663375f1a5def9942e245e2e8b6f65fd3e4ba9bb7954bec631e24988cc1e52c81ad644a4c
|
data/LICENSE
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 Joe Masilotti
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to
|
|
7
|
-
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
(https://purchasekit.dev or successor URLs). Use of the Software requires
|
|
12
|
-
a valid PurchaseKit account and active data transmission to PurchaseKit.
|
|
13
|
-
|
|
14
|
-
2. The Software may not be used:
|
|
15
|
-
a. In any application that does not integrate with PurchaseKit, including
|
|
16
|
-
internal tools, personal projects, or proprietary systems.
|
|
17
|
-
b. To build, operate, or integrate with any competing in-app purchase
|
|
18
|
-
management service.
|
|
19
|
-
c. To create a self-hosted, forked, or alternative version of PurchaseKit.
|
|
20
|
-
d. As a standalone library independent of PurchaseKit.
|
|
21
|
-
|
|
22
|
-
3. The above copyright notice and this permission notice shall be included
|
|
23
|
-
in all copies or substantial portions of the Software.
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
24
14
|
|
|
25
15
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
data/README.md
CHANGED
|
@@ -108,6 +108,7 @@ end
|
|
|
108
108
|
|
|
109
109
|
| Method | Description |
|
|
110
110
|
|--------|-------------|
|
|
111
|
+
| `event.event_id` | Unique event identifier (for idempotency) |
|
|
111
112
|
| `event.customer_id` | Your user ID |
|
|
112
113
|
| `event.subscription_id` | Store's subscription ID |
|
|
113
114
|
| `event.store` | `"apple"` or `"google"` |
|
|
@@ -118,11 +119,17 @@ end
|
|
|
118
119
|
| `event.ends_at` | When subscription will end |
|
|
119
120
|
| `event.success_path` | Redirect path after purchase |
|
|
120
121
|
|
|
122
|
+
### Idempotency
|
|
123
|
+
|
|
124
|
+
Webhooks may be delivered more than once. Write idempotent callbacks using `find_or_create_by` or check `event.event_id` to avoid duplicate side effects.
|
|
125
|
+
|
|
121
126
|
## Paywall helper
|
|
122
127
|
|
|
123
|
-
Build a paywall using the included helper:
|
|
128
|
+
Build a paywall using the included helper. Subscribe to the Turbo Stream for real-time redirects:
|
|
124
129
|
|
|
125
130
|
```erb
|
|
131
|
+
<%= turbo_stream_from "purchasekit_customer_#{current_user.id}" %>
|
|
132
|
+
|
|
126
133
|
<%= purchasekit_paywall customer_id: current_user.id, success_path: dashboard_path do |paywall| %>
|
|
127
134
|
<%= paywall.plan_option product: @annual, selected: true do %>
|
|
128
135
|
Annual - <%= paywall.price %>/year
|
|
@@ -133,8 +140,25 @@ Build a paywall using the included helper:
|
|
|
133
140
|
<% end %>
|
|
134
141
|
|
|
135
142
|
<%= paywall.submit "Subscribe" %>
|
|
136
|
-
<%= paywall.restore_link %>
|
|
137
143
|
<% end %>
|
|
144
|
+
|
|
145
|
+
<%= button_to "Restore purchases", restore_purchases_path %>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The restore link checks your server for an active subscription. Implement the endpoint in your app:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
# routes.rb
|
|
152
|
+
post "restore_purchases", to: "subscriptions#restore"
|
|
153
|
+
|
|
154
|
+
# subscriptions_controller.rb
|
|
155
|
+
def restore
|
|
156
|
+
if current_user.subscribed?
|
|
157
|
+
redirect_to dashboard_path, notice: "Your subscription is active."
|
|
158
|
+
else
|
|
159
|
+
redirect_to paywall_path, alert: "No active subscription found."
|
|
160
|
+
end
|
|
161
|
+
end
|
|
138
162
|
```
|
|
139
163
|
|
|
140
164
|
Products are fetched from the PurchaseKit API:
|
|
@@ -162,4 +186,4 @@ Works with Xcode's StoreKit local testing.
|
|
|
162
186
|
|
|
163
187
|
## License
|
|
164
188
|
|
|
165
|
-
|
|
189
|
+
MIT License. See LICENSE for details.
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
module PurchaseKit
|
|
2
2
|
class ApplicationController < ActionController::Base
|
|
3
3
|
protect_from_forgery with: :exception
|
|
4
|
+
|
|
5
|
+
rescue_from PurchaseKit::NotFoundError, with: :handle_not_found
|
|
6
|
+
rescue_from PurchaseKit::SubscriptionRequiredError, with: :handle_subscription_required
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def handle_not_found(exception)
|
|
11
|
+
handle_error("Product not found", :not_found)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def handle_subscription_required(exception)
|
|
15
|
+
handle_error("PurchaseKit subscription required", :payment_required)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def handle_error(message, status)
|
|
19
|
+
respond_to do |format|
|
|
20
|
+
format.turbo_stream do
|
|
21
|
+
render turbo_stream: turbo_stream.append(
|
|
22
|
+
"purchasekit_paywall",
|
|
23
|
+
partial: "purchase_kit/purchases/error",
|
|
24
|
+
locals: {message: message}
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
format.json { render json: {error: message}, status: status }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
4
30
|
end
|
|
5
31
|
end
|
|
@@ -13,6 +13,9 @@ module PurchaseKit
|
|
|
13
13
|
PurchaseKit.queue_pay_webhook(event) if PurchaseKit.pay_enabled?
|
|
14
14
|
|
|
15
15
|
head :ok
|
|
16
|
+
rescue JSON::ParserError => e
|
|
17
|
+
Rails.logger.error "[PurchaseKit] Invalid JSON in webhook: #{e.message}"
|
|
18
|
+
head :bad_request
|
|
16
19
|
rescue PurchaseKit::SignatureVerificationError => e
|
|
17
20
|
Rails.logger.error "[PurchaseKit] Webhook signature error: #{e.message}"
|
|
18
21
|
head :bad_request
|
|
@@ -9,14 +9,14 @@ module PurchaseKit::Pay::PaywallHelper
|
|
|
9
9
|
builder = PurchaseKit::Pay::PaywallBuilder.new(self, customer)
|
|
10
10
|
|
|
11
11
|
form_data = (options.delete(:data) || {}).merge(
|
|
12
|
-
controller: "purchasekit
|
|
13
|
-
|
|
12
|
+
controller: "purchasekit--paywall",
|
|
13
|
+
purchasekit__paywall_customer_id_value: customer.id
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
form_with(url: purchasekit_pay.purchases_path, id: "purchasekit_paywall", data: form_data, **options) do |form|
|
|
17
17
|
hidden = hidden_field_tag(:customer_id, customer.id)
|
|
18
18
|
hidden += hidden_field_tag(:success_path, success_path)
|
|
19
|
-
hidden += hidden_field_tag(:environment, "sandbox", data: {
|
|
19
|
+
hidden += hidden_field_tag(:environment, "sandbox", data: {purchasekit__paywall_target: "environment"})
|
|
20
20
|
hidden + capture { yield builder }
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -40,7 +40,7 @@ class PurchaseKit::Pay::PaywallBuilder
|
|
|
40
40
|
class: input_class,
|
|
41
41
|
autocomplete: "off",
|
|
42
42
|
data: {
|
|
43
|
-
|
|
43
|
+
purchasekit__paywall_target: "planRadio",
|
|
44
44
|
apple_store_product_id: product.apple_product_id,
|
|
45
45
|
google_store_product_id: product.google_product_id
|
|
46
46
|
}
|
|
@@ -57,7 +57,7 @@ class PurchaseKit::Pay::PaywallBuilder
|
|
|
57
57
|
raise "price must be called within a plan_option block" unless @current_product
|
|
58
58
|
|
|
59
59
|
data = (options.delete(:data) || {}).merge(
|
|
60
|
-
|
|
60
|
+
purchasekit__paywall_target: "price",
|
|
61
61
|
apple_store_product_id: @current_product.apple_product_id,
|
|
62
62
|
google_store_product_id: @current_product.google_product_id
|
|
63
63
|
)
|
|
@@ -68,18 +68,11 @@ class PurchaseKit::Pay::PaywallBuilder
|
|
|
68
68
|
|
|
69
69
|
def submit(text = "Subscribe", **options)
|
|
70
70
|
data = (options.delete(:data) || {}).merge(
|
|
71
|
-
|
|
71
|
+
purchasekit__paywall_target: "submitButton",
|
|
72
72
|
turbo_submits_with: text
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
@template.submit_tag(text, disabled: true, data: data, **options)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def restore_link(text: "Restore purchases", **options)
|
|
79
|
-
data = (options.delete(:data) || {}).merge(
|
|
80
|
-
action: "click->purchasekit-pay--paywall#restore"
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
@template.link_to(text, "#", data: data, **options)
|
|
84
|
-
end
|
|
85
78
|
end
|
|
@@ -85,12 +85,5 @@ module PurchaseKit
|
|
|
85
85
|
@template.submit_tag(text, disabled: true, data: data, **options)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def restore_link(text: "Restore purchases", **options)
|
|
89
|
-
data = (options.delete(:data) || {}).merge(
|
|
90
|
-
action: "click->purchasekit--paywall#restore"
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
@template.link_to(text, "#", data: data, **options)
|
|
94
|
-
end
|
|
95
88
|
end
|
|
96
89
|
end
|
|
@@ -9,7 +9,22 @@ export default class extends BridgeComponent {
|
|
|
9
9
|
this.#fetchPrices()
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
disconnect() {
|
|
13
|
+
if (this.#fallbackTimeoutId) {
|
|
14
|
+
clearTimeout(this.#fallbackTimeoutId)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
responseTargetConnected(element) {
|
|
19
|
+
const error = element.dataset.error
|
|
20
|
+
|
|
21
|
+
if (error) {
|
|
22
|
+
element.remove()
|
|
23
|
+
alert(error)
|
|
24
|
+
this.#enableForm()
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
const correlationId = element.dataset.correlationId
|
|
14
29
|
const productIds = this.#productIds(element)
|
|
15
30
|
const relativeUrl = element.dataset.xcodeCompletionUrl
|
|
@@ -21,11 +36,6 @@ export default class extends BridgeComponent {
|
|
|
21
36
|
this.#triggerNativePurchase(productIds, correlationId, xcodeCompletionUrl, successPath)
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
restore(event) {
|
|
25
|
-
event.preventDefault()
|
|
26
|
-
this.send("restore")
|
|
27
|
-
}
|
|
28
|
-
|
|
29
39
|
#triggerNativePurchase(productIds, correlationId, xcodeCompletionUrl, successPath) {
|
|
30
40
|
this.send("purchase", { ...productIds, correlationId, xcodeCompletionUrl }, message => {
|
|
31
41
|
const { status, error } = message.data
|
|
@@ -42,16 +52,18 @@ export default class extends BridgeComponent {
|
|
|
42
52
|
return
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
// On success,
|
|
46
|
-
//
|
|
47
|
-
if (
|
|
48
|
-
setTimeout(() => {
|
|
55
|
+
// On success, Turbo Stream will broadcast redirect when webhook completes.
|
|
56
|
+
// Fallback: redirect after 30 seconds in case ActionCable isn't connected.
|
|
57
|
+
if (successPath) {
|
|
58
|
+
this.#fallbackTimeoutId = setTimeout(() => {
|
|
49
59
|
window.Turbo.visit(successPath)
|
|
50
|
-
},
|
|
60
|
+
}, 30000)
|
|
51
61
|
}
|
|
52
62
|
})
|
|
53
63
|
}
|
|
54
64
|
|
|
65
|
+
#fallbackTimeoutId = null
|
|
66
|
+
|
|
55
67
|
#fetchPrices() {
|
|
56
68
|
const products = this.priceTargets.map(el => this.#productIds(el))
|
|
57
69
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%= turbo_stream.append "purchasekit_paywall" do %>
|
|
2
|
-
<div data-purchasekit
|
|
2
|
+
<div data-purchasekit--paywall-target="response"
|
|
3
3
|
data-correlation-id="<%= @intent.uuid %>"
|
|
4
4
|
data-apple-store-product-id="<%= @intent.product.apple_product_id %>"
|
|
5
5
|
data-google-store-product-id="<%= @intent.product.google_product_id %>"
|
data/lib/purchasekit/events.rb
CHANGED
|
@@ -8,9 +8,12 @@ module PurchaseKit
|
|
|
8
8
|
].freeze
|
|
9
9
|
|
|
10
10
|
class << self
|
|
11
|
+
include ActionView::Helpers::TagHelper
|
|
12
|
+
|
|
11
13
|
# Publish an event to all registered handlers.
|
|
12
14
|
#
|
|
13
15
|
# Also publishes via ActiveSupport::Notifications for additional flexibility.
|
|
16
|
+
# Broadcasts redirect for subscription_created when Pay is not handling it.
|
|
14
17
|
#
|
|
15
18
|
# @param type [Symbol] Event type (e.g., :subscription_created)
|
|
16
19
|
# @param payload [Hash] Event payload from the webhook
|
|
@@ -27,8 +30,25 @@ module PurchaseKit
|
|
|
27
30
|
# Also publish via ActiveSupport::Notifications for subscribers
|
|
28
31
|
ActiveSupport::Notifications.instrument("purchasekit.#{type}", event: event)
|
|
29
32
|
|
|
33
|
+
# Broadcast redirect for new subscriptions (Pay handles its own broadcasts)
|
|
34
|
+
if type == :subscription_created && !PurchaseKit.pay_enabled?
|
|
35
|
+
broadcast_redirect(event)
|
|
36
|
+
end
|
|
37
|
+
|
|
30
38
|
event
|
|
31
39
|
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def broadcast_redirect(event)
|
|
44
|
+
return if event.success_path.blank?
|
|
45
|
+
return unless defined?(Turbo::StreamsChannel)
|
|
46
|
+
|
|
47
|
+
Turbo::StreamsChannel.broadcast_stream_to(
|
|
48
|
+
"purchasekit_customer_#{event.customer_id}",
|
|
49
|
+
content: tag.turbo_stream(action: :redirect, url: event.success_path)
|
|
50
|
+
)
|
|
51
|
+
end
|
|
32
52
|
end
|
|
33
53
|
|
|
34
54
|
# Represents a subscription event from Apple or Google.
|
|
@@ -45,6 +65,12 @@ module PurchaseKit
|
|
|
45
65
|
@payload = payload.is_a?(Hash) ? payload.with_indifferent_access : payload
|
|
46
66
|
end
|
|
47
67
|
|
|
68
|
+
# Unique identifier for this event. Use for idempotency checks.
|
|
69
|
+
# Store processed event_ids to prevent duplicate processing.
|
|
70
|
+
def event_id
|
|
71
|
+
payload[:event_id]
|
|
72
|
+
end
|
|
73
|
+
|
|
48
74
|
# The customer ID you passed when creating the purchase intent.
|
|
49
75
|
# Use this to look up the user in your database.
|
|
50
76
|
def customer_id
|
data/lib/purchasekit/version.rb
CHANGED
data/lib/purchasekit.rb
CHANGED
|
@@ -43,10 +43,11 @@ module PurchaseKit
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def pay_enabled?
|
|
46
|
-
defined?(::Pay)
|
|
46
|
+
defined?(::Pay) && defined?(::Pay::VERSION)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def queue_pay_webhook(event)
|
|
50
|
+
return unless defined?(PurchaseKit::Pay::Webhook)
|
|
50
51
|
PurchaseKit::Pay::Webhook.queue(event)
|
|
51
52
|
end
|
|
52
53
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: purchasekit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joe Masilotti
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -122,7 +122,6 @@ files:
|
|
|
122
122
|
- app/helpers/purchase_kit/pay/paywall_helper.rb
|
|
123
123
|
- app/helpers/purchase_kit/paywall_helper.rb
|
|
124
124
|
- app/javascript/controllers/purchasekit/paywall_controller.js
|
|
125
|
-
- app/javascript/controllers/purchasekit_pay/paywall_controller.js
|
|
126
125
|
- app/javascript/purchasekit/turbo_actions.js
|
|
127
126
|
- app/javascript/purchasekit_pay/turbo_actions.js
|
|
128
127
|
- app/models/pay/purchasekit/charge.rb
|
|
@@ -130,6 +129,7 @@ files:
|
|
|
130
129
|
- app/models/pay/purchasekit/subscription.rb
|
|
131
130
|
- app/views/purchase_kit/pay/purchases/_subscription_required.html.erb
|
|
132
131
|
- app/views/purchase_kit/pay/purchases/create.turbo_stream.erb
|
|
132
|
+
- app/views/purchase_kit/purchases/_error.html.erb
|
|
133
133
|
- app/views/purchase_kit/purchases/_intent.html.erb
|
|
134
134
|
- config/importmap.rb
|
|
135
135
|
- config/routes.rb
|
|
@@ -162,7 +162,7 @@ files:
|
|
|
162
162
|
- lib/purchasekit/webhook_signature.rb
|
|
163
163
|
homepage: https://purchasekit.dev
|
|
164
164
|
licenses:
|
|
165
|
-
-
|
|
165
|
+
- MIT
|
|
166
166
|
metadata:
|
|
167
167
|
homepage_uri: https://purchasekit.dev
|
|
168
168
|
source_code_uri: https://github.com/purchasekit/purchasekit
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
|
|
2
|
-
|
|
3
|
-
export default class extends BridgeComponent {
|
|
4
|
-
static component = "paywall"
|
|
5
|
-
static targets = ["planRadio", "price", "submitButton", "response", "environment"]
|
|
6
|
-
|
|
7
|
-
connect() {
|
|
8
|
-
super.connect()
|
|
9
|
-
this.#fetchPrices()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
responseTargetConnected(element) {
|
|
13
|
-
const correlationId = element.dataset.correlationId
|
|
14
|
-
const productIds = this.#productIds(element)
|
|
15
|
-
const xcodeCompletionUrl = element.dataset.xcodeCompletionUrl
|
|
16
|
-
|
|
17
|
-
element.remove()
|
|
18
|
-
this.#disableForm()
|
|
19
|
-
this.#triggerNativePurchase(productIds, correlationId, xcodeCompletionUrl)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
restore(event) {
|
|
23
|
-
event.preventDefault()
|
|
24
|
-
this.send("restore")
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
#triggerNativePurchase(productIds, correlationId, xcodeCompletionUrl) {
|
|
28
|
-
this.send("purchase", { ...productIds, correlationId, xcodeCompletionUrl }, message => {
|
|
29
|
-
const { status, error } = message.data
|
|
30
|
-
|
|
31
|
-
if (error) {
|
|
32
|
-
console.error(error)
|
|
33
|
-
alert(`Purchase error: ${error}`)
|
|
34
|
-
this.#enableForm()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (status == "cancelled") {
|
|
38
|
-
this.#enableForm()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// On success, keep showing processing state.
|
|
42
|
-
// Turbo Stream will update the UI when webhook completes.
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
#fetchPrices() {
|
|
47
|
-
const products = this.priceTargets.map(el => this.#productIds(el))
|
|
48
|
-
|
|
49
|
-
this.send("prices", { products }, message => {
|
|
50
|
-
const { prices, environment, error } = message.data
|
|
51
|
-
|
|
52
|
-
if (error) {
|
|
53
|
-
console.error(error)
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (prices) {
|
|
58
|
-
this.#setPrices(prices)
|
|
59
|
-
this.#setEnvironment(environment)
|
|
60
|
-
this.#enableForm()
|
|
61
|
-
}
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
#setEnvironment(environment) {
|
|
66
|
-
if (this.hasEnvironmentTarget && environment) {
|
|
67
|
-
this.environmentTarget.value = environment
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
#setPrices(prices) {
|
|
72
|
-
this.priceTargets.forEach(el => {
|
|
73
|
-
const { appleStoreProductId, googleStoreProductId } = this.#productIds(el)
|
|
74
|
-
const price = prices[appleStoreProductId] || prices[googleStoreProductId]
|
|
75
|
-
|
|
76
|
-
if (price) {
|
|
77
|
-
el.textContent = price
|
|
78
|
-
} else {
|
|
79
|
-
console.error(`No price found for product.`)
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
#productIds(element) {
|
|
85
|
-
return {
|
|
86
|
-
appleStoreProductId: element.dataset.appleStoreProductId,
|
|
87
|
-
googleStoreProductId: element.dataset.googleStoreProductId
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
#enableForm() {
|
|
92
|
-
this.planRadioTargets.forEach(radio => radio.disabled = false)
|
|
93
|
-
if (this.hasSubmitButtonTarget) {
|
|
94
|
-
this.submitButtonTarget.disabled = false
|
|
95
|
-
if (this.#originalButtonText) {
|
|
96
|
-
this.submitButtonTarget.innerHTML = this.#originalButtonText
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#disableForm() {
|
|
102
|
-
this.planRadioTargets.forEach(radio => radio.disabled = true)
|
|
103
|
-
if (this.hasSubmitButtonTarget) {
|
|
104
|
-
this.#originalButtonText = this.submitButtonTarget.innerHTML
|
|
105
|
-
this.submitButtonTarget.disabled = true
|
|
106
|
-
const processingText = this.submitButtonTarget.dataset.processingText || "Processing..."
|
|
107
|
-
this.submitButtonTarget.innerHTML = processingText
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
#originalButtonText = null
|
|
112
|
-
}
|