purchasekit 0.4.5 → 0.5.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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 262fd99889485c18c0d9d668d930fc0cec3b258620396ef960b8d8828a040e29
|
|
4
|
+
data.tar.gz: 2b07a7abf7c580e72f98896e8e0a87a2cd7008cccbddbac8ba5d481e12c7308d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66ac5a6194010cfaa573d54eaa11df03a6000778e3d828b35476d12d6ba4474ce233018043adcd166187ab309db3dd009d65b9759d36019c6f48391b5672fd21
|
|
7
|
+
data.tar.gz: a2385ee1ef72d178fe0354f0631a6cc4d258ebcedb2f3ae1a779974b6b9698079bd68854286bf30288ef1cb4284187abfc9000228a18569678a4466a706354e1
|
data/README.md
CHANGED
|
@@ -140,12 +140,23 @@ Build a paywall using the included helper. Subscribe to the Turbo Stream for rea
|
|
|
140
140
|
<% end %>
|
|
141
141
|
|
|
142
142
|
<%= paywall.submit "Subscribe" %>
|
|
143
|
+
<%= paywall.restore url: restore_purchases_path, class: "btn btn-link" %>
|
|
143
144
|
<% end %>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Restore purchases
|
|
148
|
+
|
|
149
|
+
Apple requires apps with in-app purchases to include a "Restore purchases" button. This handles users who switch devices or reinstall the app.
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
The `paywall.restore` helper renders a button that reads active subscriptions directly from StoreKit (iOS) or Play Billing (Android) via the native bridge. Pass a `url:` to automatically POST the subscription IDs to your server:
|
|
152
|
+
|
|
153
|
+
```erb
|
|
154
|
+
<%= paywall.restore url: restore_purchases_path, class: "btn btn-link" %>
|
|
146
155
|
```
|
|
147
156
|
|
|
148
|
-
|
|
157
|
+
When the user taps restore, the JS controller sends a bridge message to the native app, receives the active subscription IDs, and POSTs them as JSON to your URL. If the server responds with a redirect, the page navigates automatically.
|
|
158
|
+
|
|
159
|
+
On the server, match the IDs against your stored subscriptions. The `subscription_ids` match the `subscription_id` field in PurchaseKit webhook payloads (Apple's `originalTransactionId`, Google's order ID):
|
|
149
160
|
|
|
150
161
|
```ruby
|
|
151
162
|
# routes.rb
|
|
@@ -153,7 +164,9 @@ post "restore_purchases", to: "subscriptions#restore"
|
|
|
153
164
|
|
|
154
165
|
# subscriptions_controller.rb
|
|
155
166
|
def restore
|
|
156
|
-
|
|
167
|
+
ids = params[:subscription_ids] || []
|
|
168
|
+
|
|
169
|
+
if ids.any? && current_user.subscriptions.where(processor_id: ids).active.any?
|
|
157
170
|
redirect_to dashboard_path, notice: "Your subscription is active."
|
|
158
171
|
else
|
|
159
172
|
redirect_to paywall_path, alert: "No active subscription found."
|
|
@@ -161,6 +174,15 @@ def restore
|
|
|
161
174
|
end
|
|
162
175
|
```
|
|
163
176
|
|
|
177
|
+
If you need custom behavior, omit the `url:` and listen for the DOM event instead:
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
document.addEventListener("purchasekit--paywall:restore", (event) => {
|
|
181
|
+
const { subscriptionIds, error } = event.detail
|
|
182
|
+
// Handle as needed
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
|
|
164
186
|
Products are fetched from the PurchaseKit API:
|
|
165
187
|
|
|
166
188
|
```ruby
|
|
@@ -85,5 +85,15 @@ module PurchaseKit
|
|
|
85
85
|
@template.submit_tag(text, disabled: true, data: data, **options)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
def restore(text = "Restore purchases", url: nil, **options)
|
|
89
|
+
data = (options.delete(:data) || {}).merge(
|
|
90
|
+
purchasekit__paywall_target: "restoreButton",
|
|
91
|
+
action: "purchasekit--paywall#restore"
|
|
92
|
+
)
|
|
93
|
+
data[:restore_url] = url if url
|
|
94
|
+
|
|
95
|
+
@template.content_tag(:button, text, type: "button", data: data, **options)
|
|
96
|
+
end
|
|
97
|
+
|
|
88
98
|
end
|
|
89
99
|
end
|
|
@@ -2,7 +2,7 @@ import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
|
|
|
2
2
|
|
|
3
3
|
export default class extends BridgeComponent {
|
|
4
4
|
static component = "paywall"
|
|
5
|
-
static targets = ["planRadio", "price", "submitButton", "response", "environment"]
|
|
5
|
+
static targets = ["planRadio", "price", "submitButton", "response", "environment", "restoreButton"]
|
|
6
6
|
|
|
7
7
|
connect() {
|
|
8
8
|
super.connect()
|
|
@@ -15,6 +15,26 @@ export default class extends BridgeComponent {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
restore() {
|
|
19
|
+
if (this.hasRestoreButtonTarget) {
|
|
20
|
+
this.restoreButtonTarget.disabled = true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.send("restore", {}, message => {
|
|
24
|
+
if (this.hasRestoreButtonTarget) {
|
|
25
|
+
this.restoreButtonTarget.disabled = false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { subscriptionIds, error } = message.data
|
|
29
|
+
this.dispatch("restore", { detail: { subscriptionIds, error } })
|
|
30
|
+
|
|
31
|
+
const restoreUrl = this.hasRestoreButtonTarget && this.restoreButtonTarget.dataset.restoreUrl
|
|
32
|
+
if (!error && restoreUrl) {
|
|
33
|
+
this.#submitRestore(restoreUrl, subscriptionIds || [])
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
responseTargetConnected(element) {
|
|
19
39
|
const error = element.dataset.error
|
|
20
40
|
|
|
@@ -62,6 +82,26 @@ export default class extends BridgeComponent {
|
|
|
62
82
|
})
|
|
63
83
|
}
|
|
64
84
|
|
|
85
|
+
#submitRestore(url, subscriptionIds) {
|
|
86
|
+
const csrfToken = document.querySelector("meta[name=csrf-token]")?.content
|
|
87
|
+
|
|
88
|
+
fetch(url, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
...(csrfToken && { "X-CSRF-Token": csrfToken })
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({ subscription_ids: subscriptionIds })
|
|
95
|
+
}).then(response => {
|
|
96
|
+
if (response.redirected) {
|
|
97
|
+
window.Turbo.visit(response.url)
|
|
98
|
+
}
|
|
99
|
+
}).catch(error => {
|
|
100
|
+
console.error("Restore request failed:", error)
|
|
101
|
+
alert("Something went wrong restoring purchases. Please try again.")
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
65
105
|
#fallbackTimeoutId = null
|
|
66
106
|
|
|
67
107
|
#fetchPrices() {
|
data/lib/purchasekit/version.rb
CHANGED
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.5.0
|
|
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-02-
|
|
11
|
+
date: 2026-02-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|