@burdenoff/microfe-billing 2026.609.3 → 2026.611.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.
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js +119 -115
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useNativeCheckout.js +57 -53
- package/dist/billing/modules/checkout/hooks/useNativeCheckout.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Capacitor as e } from "../../../../node_modules/@capacitor/core/dist/index.js";
|
|
2
|
+
import { useEffect as t, useMemo as n, useState as r } from "react";
|
|
3
|
+
import { ArrowLeft as i, Blocks as a, CalendarDays as o, CheckCircle as s, ChevronRight as c, Coins as l, Info as u, Loader2 as d, Package as f, RotateCcw as p, ShoppingBag as m, Smartphone as h, XCircle as g } from "lucide-react";
|
|
4
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
4
5
|
//#region src/billing/modules/checkout/components/NativeCheckoutView.tsx
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
function b() {
|
|
7
|
+
return e.getPlatform() === "ios" ? "App Store" : "Google Play";
|
|
8
|
+
}
|
|
9
|
+
function x(e) {
|
|
7
10
|
switch (e) {
|
|
8
11
|
case "CREDITS": return "Credits";
|
|
9
12
|
case "ADDON": return "Add-on";
|
|
@@ -11,15 +14,15 @@ function y(e) {
|
|
|
11
14
|
default: return "Subscription";
|
|
12
15
|
}
|
|
13
16
|
}
|
|
14
|
-
function
|
|
17
|
+
function S(e) {
|
|
15
18
|
switch (e) {
|
|
16
|
-
case "CREDITS": return /* @__PURE__ */
|
|
17
|
-
case "ADDON": return /* @__PURE__ */
|
|
18
|
-
case "ORDER": return /* @__PURE__ */
|
|
19
|
-
default: return /* @__PURE__ */
|
|
19
|
+
case "CREDITS": return /* @__PURE__ */ v(l, { className: "size-6 text-[--color-primary]" });
|
|
20
|
+
case "ADDON": return /* @__PURE__ */ v(a, { className: "size-6 text-[--color-primary]" });
|
|
21
|
+
case "ORDER": return /* @__PURE__ */ v(m, { className: "size-6 text-[--color-primary]" });
|
|
22
|
+
default: return /* @__PURE__ */ v(f, { className: "size-6 text-[--color-primary]" });
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
|
-
function
|
|
25
|
+
function C(e) {
|
|
23
26
|
if (!e) return null;
|
|
24
27
|
switch (e.unit) {
|
|
25
28
|
case 0: return e.numberOfUnits === 1 ? "Billed daily" : `Billed every ${e.numberOfUnits} days`;
|
|
@@ -29,7 +32,7 @@ function x(e) {
|
|
|
29
32
|
default: return null;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
function
|
|
35
|
+
function w(e) {
|
|
33
36
|
switch (e) {
|
|
34
37
|
case "CREDITS": return "Your credits will be added shortly. It may take a few seconds to reflect.";
|
|
35
38
|
case "ADDON": return "Your add-on will be activated shortly. It may take a few seconds to reflect.";
|
|
@@ -37,92 +40,92 @@ function S(e) {
|
|
|
37
40
|
default: return "Your subscription will be activated shortly. It may take a few seconds to reflect.";
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
var
|
|
41
|
-
let a =
|
|
42
|
-
return /* @__PURE__ */
|
|
43
|
+
var T = ({ product: e, purchaseType: t, storeName: n, onPay: r, isProcessing: i }) => {
|
|
44
|
+
let a = C(e.subscriptionPeriod), s = !!e.introductoryPrice;
|
|
45
|
+
return /* @__PURE__ */ y("div", {
|
|
43
46
|
className: "flex flex-col gap-4 p-4",
|
|
44
47
|
children: [
|
|
45
|
-
/* @__PURE__ */
|
|
48
|
+
/* @__PURE__ */ y("div", {
|
|
46
49
|
className: "rounded-2xl border border-[--color-border] bg-[--color-surface] overflow-hidden",
|
|
47
50
|
children: [
|
|
48
|
-
/* @__PURE__ */
|
|
51
|
+
/* @__PURE__ */ y("div", {
|
|
49
52
|
className: "flex items-start gap-4 p-4",
|
|
50
|
-
children: [/* @__PURE__ */
|
|
53
|
+
children: [/* @__PURE__ */ v("div", {
|
|
51
54
|
className: "flex size-11 shrink-0 items-center justify-center rounded-xl bg-[--color-primary-subtle]",
|
|
52
|
-
children:
|
|
53
|
-
}), /* @__PURE__ */
|
|
55
|
+
children: S(t)
|
|
56
|
+
}), /* @__PURE__ */ y("div", {
|
|
54
57
|
className: "flex-1 min-w-0",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
58
|
+
children: [/* @__PURE__ */ v("p", {
|
|
56
59
|
className: "text-sm font-semibold text-[--color-text-primary] leading-snug",
|
|
57
60
|
children: e.title
|
|
58
|
-
}), e.description ? /* @__PURE__ */
|
|
61
|
+
}), e.description ? /* @__PURE__ */ v("p", {
|
|
59
62
|
className: "mt-1 text-xs text-[--color-text-secondary] leading-relaxed",
|
|
60
63
|
children: e.description
|
|
61
64
|
}) : null]
|
|
62
65
|
})]
|
|
63
66
|
}),
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
/* @__PURE__ */
|
|
67
|
+
/* @__PURE__ */ v("div", { className: "h-px bg-[--color-border]" }),
|
|
68
|
+
/* @__PURE__ */ y("div", {
|
|
66
69
|
className: "flex items-center justify-between px-4 py-3",
|
|
67
|
-
children: [/* @__PURE__ */
|
|
70
|
+
children: [/* @__PURE__ */ y("div", {
|
|
68
71
|
className: "flex flex-col",
|
|
69
|
-
children: [s && e.introductoryPrice ? /* @__PURE__ */ _
|
|
72
|
+
children: [s && e.introductoryPrice ? /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("span", {
|
|
70
73
|
className: "text-xs text-[--color-text-secondary] line-through",
|
|
71
74
|
children: e.priceString
|
|
72
|
-
}), /* @__PURE__ */
|
|
75
|
+
}), /* @__PURE__ */ y("span", {
|
|
73
76
|
className: "text-lg font-bold text-[--color-primary]",
|
|
74
|
-
children: [e.introductoryPrice.priceString, /* @__PURE__ */
|
|
77
|
+
children: [e.introductoryPrice.priceString, /* @__PURE__ */ v("span", {
|
|
75
78
|
className: "ml-1 text-xs font-normal text-[--color-text-secondary]",
|
|
76
79
|
children: "intro offer"
|
|
77
80
|
})]
|
|
78
|
-
})] }) : /* @__PURE__ */
|
|
81
|
+
})] }) : /* @__PURE__ */ v("span", {
|
|
79
82
|
className: "text-lg font-bold text-[--color-primary]",
|
|
80
83
|
children: e.priceString
|
|
81
|
-
}), a ? /* @__PURE__ */
|
|
84
|
+
}), a ? /* @__PURE__ */ y("div", {
|
|
82
85
|
className: "mt-0.5 flex items-center gap-1",
|
|
83
|
-
children: [/* @__PURE__ */
|
|
86
|
+
children: [/* @__PURE__ */ v(o, { className: "size-3 text-[--color-text-tertiary]" }), /* @__PURE__ */ v("span", {
|
|
84
87
|
className: "text-xs text-[--color-text-secondary]",
|
|
85
88
|
children: a
|
|
86
89
|
})]
|
|
87
90
|
}) : null]
|
|
88
|
-
}), /* @__PURE__ */
|
|
91
|
+
}), /* @__PURE__ */ v("div", {
|
|
89
92
|
className: "flex items-center gap-1 rounded-full bg-[--color-surface-subtle] px-2.5 py-1",
|
|
90
|
-
children: /* @__PURE__ */
|
|
93
|
+
children: /* @__PURE__ */ v("span", {
|
|
91
94
|
className: "text-xs font-medium text-[--color-text-secondary]",
|
|
92
|
-
children:
|
|
95
|
+
children: x(t)
|
|
93
96
|
})
|
|
94
97
|
})]
|
|
95
98
|
})
|
|
96
99
|
]
|
|
97
100
|
}),
|
|
98
|
-
/* @__PURE__ */
|
|
101
|
+
/* @__PURE__ */ y("div", {
|
|
99
102
|
className: "flex items-start gap-2 rounded-xl bg-[--color-surface-subtle] px-3 py-2.5",
|
|
100
|
-
children: [/* @__PURE__ */
|
|
103
|
+
children: [/* @__PURE__ */ v(u, { className: "mt-0.5 size-3.5 shrink-0 text-[--color-text-tertiary]" }), /* @__PURE__ */ y("p", {
|
|
101
104
|
className: "text-xs text-[--color-text-secondary] leading-relaxed",
|
|
102
105
|
children: [
|
|
103
106
|
"Price includes all applicable taxes as determined by ",
|
|
104
|
-
|
|
107
|
+
n,
|
|
105
108
|
". No additional charges."
|
|
106
109
|
]
|
|
107
110
|
})]
|
|
108
111
|
}),
|
|
109
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ v("button", {
|
|
110
113
|
type: "button",
|
|
111
|
-
disabled:
|
|
112
|
-
onClick:
|
|
114
|
+
disabled: i,
|
|
115
|
+
onClick: r,
|
|
113
116
|
className: "flex w-full items-center justify-center gap-2 rounded-xl bg-[--color-primary] px-6 py-4 text-sm font-semibold text-[--color-primary-foreground] transition-opacity disabled:opacity-60 active:opacity-80",
|
|
114
|
-
children:
|
|
115
|
-
/* @__PURE__ */
|
|
117
|
+
children: i ? /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(d, { className: "size-4 animate-spin" }), "Processing…"] }) : /* @__PURE__ */ y(_, { children: [
|
|
118
|
+
/* @__PURE__ */ v(h, { className: "size-4" }),
|
|
116
119
|
"Pay with ",
|
|
117
|
-
|
|
118
|
-
/* @__PURE__ */
|
|
120
|
+
n,
|
|
121
|
+
/* @__PURE__ */ v(c, { className: "size-4 opacity-70" })
|
|
119
122
|
] })
|
|
120
123
|
}),
|
|
121
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ y("p", {
|
|
122
125
|
className: "text-center text-[10px] leading-relaxed text-[--color-text-tertiary]",
|
|
123
126
|
children: [
|
|
124
127
|
"By continuing, you agree to the ",
|
|
125
|
-
|
|
128
|
+
n,
|
|
126
129
|
" terms.",
|
|
127
130
|
" ",
|
|
128
131
|
t === "PLAN" ? "Subscription renews automatically. Cancel anytime." : "This is a one-time purchase."
|
|
@@ -130,151 +133,152 @@ var C = ({ product: e, purchaseType: t, onPay: n, isProcessing: r }) => {
|
|
|
130
133
|
})
|
|
131
134
|
]
|
|
132
135
|
});
|
|
133
|
-
},
|
|
136
|
+
}, E = ({ products: e, purchaseType: t, onSelect: n, isProcessing: r }) => /* @__PURE__ */ y("div", {
|
|
134
137
|
className: "flex flex-col gap-3 p-4",
|
|
135
|
-
children: [/* @__PURE__ */
|
|
138
|
+
children: [/* @__PURE__ */ y("p", {
|
|
136
139
|
className: "text-xs font-medium uppercase tracking-wide text-[--color-text-tertiary]",
|
|
137
|
-
children: ["Select a ",
|
|
140
|
+
children: ["Select a ", x(t)]
|
|
138
141
|
}), e.map((e) => {
|
|
139
|
-
let i =
|
|
140
|
-
return /* @__PURE__ */
|
|
142
|
+
let i = C(e.subscriptionPeriod);
|
|
143
|
+
return /* @__PURE__ */ y("button", {
|
|
141
144
|
type: "button",
|
|
142
145
|
disabled: r,
|
|
143
146
|
onClick: () => n(e),
|
|
144
147
|
className: "flex items-center justify-between rounded-xl border border-[--color-border] bg-[--color-surface] p-4 text-left transition-colors hover:border-[--color-primary] hover:bg-[--color-surface-hover] disabled:opacity-60",
|
|
145
|
-
children: [/* @__PURE__ */
|
|
148
|
+
children: [/* @__PURE__ */ y("div", {
|
|
146
149
|
className: "flex items-center gap-3",
|
|
147
|
-
children: [/* @__PURE__ */
|
|
150
|
+
children: [/* @__PURE__ */ v("div", {
|
|
148
151
|
className: "flex size-9 shrink-0 items-center justify-center rounded-lg bg-[--color-primary-subtle]",
|
|
149
|
-
children:
|
|
150
|
-
}), /* @__PURE__ */
|
|
152
|
+
children: S(t)
|
|
153
|
+
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
151
154
|
className: "text-sm font-semibold text-[--color-text-primary]",
|
|
152
155
|
children: e.title
|
|
153
|
-
}), i ? /* @__PURE__ */
|
|
156
|
+
}), i ? /* @__PURE__ */ v("p", {
|
|
154
157
|
className: "mt-0.5 text-xs text-[--color-text-secondary]",
|
|
155
158
|
children: i
|
|
156
159
|
}) : null] })]
|
|
157
|
-
}), /* @__PURE__ */
|
|
160
|
+
}), /* @__PURE__ */ y("div", {
|
|
158
161
|
className: "ml-4 shrink-0 text-right",
|
|
159
|
-
children: [/* @__PURE__ */
|
|
162
|
+
children: [/* @__PURE__ */ v("p", {
|
|
160
163
|
className: "text-base font-bold text-[--color-primary]",
|
|
161
164
|
children: e.priceString
|
|
162
|
-
}), r && /* @__PURE__ */
|
|
165
|
+
}), r && /* @__PURE__ */ v(d, { className: "ml-auto mt-1 size-4 animate-spin text-[--color-text-tertiary]" })]
|
|
163
166
|
})]
|
|
164
167
|
}, e.identifier);
|
|
165
168
|
})]
|
|
166
|
-
}),
|
|
167
|
-
let { products:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}, [
|
|
171
|
-
|
|
172
|
-
}, [
|
|
173
|
-
let
|
|
174
|
-
|
|
169
|
+
}), D = ({ purchaseType: e, productId: a, nativeCheckout: o, onBack: c }) => {
|
|
170
|
+
let l = n(() => b(), []), { products: u, checkoutState: f, error: m, isLoading: _, loadProducts: S, purchaseProduct: C, restorePurchases: D } = o, [O, k] = r(null);
|
|
171
|
+
t(() => {
|
|
172
|
+
S(a ? [a] : void 0);
|
|
173
|
+
}, [S, a]), t(() => {
|
|
174
|
+
u.length === 1 && u[0] && !O && k(u[0]);
|
|
175
|
+
}, [u, O]);
|
|
176
|
+
let A = (e) => {
|
|
177
|
+
k(e), C(e);
|
|
175
178
|
};
|
|
176
|
-
if (
|
|
179
|
+
if (f === "success") return /* @__PURE__ */ y("div", {
|
|
177
180
|
className: "flex flex-col items-center justify-center gap-6 py-16 px-6 text-center",
|
|
178
181
|
children: [
|
|
179
|
-
/* @__PURE__ */
|
|
180
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ v(s, { className: "size-16 text-[--color-success]" }),
|
|
183
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
181
184
|
className: "text-xl font-semibold text-[--color-text-primary]",
|
|
182
185
|
children: "Purchase Successful"
|
|
183
|
-
}), /* @__PURE__ */
|
|
186
|
+
}), /* @__PURE__ */ v("p", {
|
|
184
187
|
className: "mt-2 text-sm text-[--color-text-secondary]",
|
|
185
|
-
children:
|
|
188
|
+
children: w(e)
|
|
186
189
|
})] }),
|
|
187
|
-
/* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ v("button", {
|
|
188
191
|
type: "button",
|
|
189
|
-
onClick:
|
|
192
|
+
onClick: c,
|
|
190
193
|
className: "rounded-xl bg-[--color-primary] px-8 py-3 text-sm font-semibold text-[--color-primary-foreground]",
|
|
191
194
|
children: "Done"
|
|
192
195
|
})
|
|
193
196
|
]
|
|
194
197
|
});
|
|
195
|
-
if (
|
|
198
|
+
if (f === "error") return /* @__PURE__ */ y("div", {
|
|
196
199
|
className: "flex flex-col items-center justify-center gap-6 py-16 px-6 text-center",
|
|
197
200
|
children: [
|
|
198
|
-
/* @__PURE__ */ g
|
|
199
|
-
/* @__PURE__ */
|
|
201
|
+
/* @__PURE__ */ v(g, { className: "size-16 text-[--color-destructive]" }),
|
|
202
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
200
203
|
className: "text-xl font-semibold text-[--color-text-primary]",
|
|
201
204
|
children: "Purchase Failed"
|
|
202
|
-
}), /* @__PURE__ */
|
|
205
|
+
}), /* @__PURE__ */ v("p", {
|
|
203
206
|
className: "mt-2 text-sm text-[--color-text-secondary]",
|
|
204
|
-
children:
|
|
207
|
+
children: m
|
|
205
208
|
})] }),
|
|
206
|
-
/* @__PURE__ */
|
|
209
|
+
/* @__PURE__ */ y("div", {
|
|
207
210
|
className: "flex gap-3",
|
|
208
|
-
children: [/* @__PURE__ */
|
|
211
|
+
children: [/* @__PURE__ */ v("button", {
|
|
209
212
|
type: "button",
|
|
210
|
-
onClick: () => void
|
|
213
|
+
onClick: () => void S(a ? [a] : void 0),
|
|
211
214
|
className: "rounded-xl border border-[--color-border] px-6 py-3 text-sm font-medium text-[--color-text-primary]",
|
|
212
215
|
children: "Try Again"
|
|
213
|
-
}), /* @__PURE__ */
|
|
216
|
+
}), /* @__PURE__ */ v("button", {
|
|
214
217
|
type: "button",
|
|
215
|
-
onClick:
|
|
218
|
+
onClick: c,
|
|
216
219
|
className: "rounded-xl bg-[--color-primary] px-6 py-3 text-sm font-medium text-[--color-primary-foreground]",
|
|
217
220
|
children: "Cancel"
|
|
218
221
|
})]
|
|
219
222
|
})
|
|
220
223
|
]
|
|
221
224
|
});
|
|
222
|
-
let
|
|
223
|
-
return /* @__PURE__ */
|
|
225
|
+
let j = f === "processing";
|
|
226
|
+
return /* @__PURE__ */ y("div", {
|
|
224
227
|
className: "flex flex-col gap-0",
|
|
225
228
|
children: [
|
|
226
|
-
/* @__PURE__ */
|
|
229
|
+
/* @__PURE__ */ y("div", {
|
|
227
230
|
className: "flex items-center gap-3 border-b border-[--color-border] px-4 py-3",
|
|
228
|
-
children: [/* @__PURE__ */
|
|
231
|
+
children: [/* @__PURE__ */ v("button", {
|
|
229
232
|
type: "button",
|
|
230
|
-
onClick:
|
|
233
|
+
onClick: c,
|
|
231
234
|
className: "rounded-lg p-2 text-[--color-text-secondary] hover:bg-[--color-surface-hover]",
|
|
232
|
-
children: /* @__PURE__ */
|
|
233
|
-
}), /* @__PURE__ */
|
|
235
|
+
children: /* @__PURE__ */ v(i, { className: "size-5" })
|
|
236
|
+
}), /* @__PURE__ */ v("div", { children: /* @__PURE__ */ v("p", {
|
|
234
237
|
className: "text-base font-semibold text-[--color-text-primary]",
|
|
235
|
-
children:
|
|
238
|
+
children: O ?? u.length === 1 ? "Order Summary" : `Choose a ${x(e)}`
|
|
236
239
|
}) })]
|
|
237
240
|
}),
|
|
238
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ y("div", {
|
|
239
242
|
className: "flex items-center gap-2 border-b border-[--color-border] bg-[--color-surface-subtle] px-4 py-2",
|
|
240
|
-
children: [/* @__PURE__ */
|
|
243
|
+
children: [/* @__PURE__ */ v(h, { className: "size-3.5 text-[--color-text-tertiary]" }), /* @__PURE__ */ y("span", {
|
|
241
244
|
className: "text-xs text-[--color-text-secondary]",
|
|
242
|
-
children: ["Payments processed securely by ", /* @__PURE__ */
|
|
245
|
+
children: ["Payments processed securely by ", /* @__PURE__ */ v("span", {
|
|
243
246
|
className: "font-medium",
|
|
244
|
-
children:
|
|
247
|
+
children: l
|
|
245
248
|
})]
|
|
246
249
|
})]
|
|
247
250
|
}),
|
|
248
|
-
|
|
251
|
+
_ && u.length === 0 ? /* @__PURE__ */ v("div", {
|
|
249
252
|
className: "flex items-center justify-center py-20",
|
|
250
|
-
children: /* @__PURE__ */
|
|
251
|
-
}) :
|
|
253
|
+
children: /* @__PURE__ */ v(d, { className: "size-6 animate-spin text-[--color-text-tertiary]" })
|
|
254
|
+
}) : u.length === 0 ? /* @__PURE__ */ v("div", {
|
|
252
255
|
className: "py-16 text-center text-sm text-[--color-text-secondary] px-6",
|
|
253
256
|
children: "No products available for this platform. Please try again later."
|
|
254
|
-
}) :
|
|
255
|
-
product:
|
|
256
|
-
purchaseType:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
257
|
+
}) : O ?? u.length === 1 ? /* @__PURE__ */ v(T, {
|
|
258
|
+
product: O ?? u[0],
|
|
259
|
+
purchaseType: e,
|
|
260
|
+
storeName: l,
|
|
261
|
+
onPay: () => A(O ?? u[0]),
|
|
262
|
+
isProcessing: j
|
|
263
|
+
}) : /* @__PURE__ */ v(E, {
|
|
264
|
+
products: u,
|
|
265
|
+
purchaseType: e,
|
|
266
|
+
onSelect: (e) => A(e),
|
|
267
|
+
isProcessing: j
|
|
264
268
|
}),
|
|
265
|
-
!
|
|
269
|
+
!j && /* @__PURE__ */ v("div", {
|
|
266
270
|
className: "px-4 pb-6 pt-2",
|
|
267
|
-
children: /* @__PURE__ */
|
|
271
|
+
children: /* @__PURE__ */ y("button", {
|
|
268
272
|
type: "button",
|
|
269
|
-
onClick: () => void
|
|
273
|
+
onClick: () => void D(),
|
|
270
274
|
className: "flex w-full items-center justify-center gap-1.5 text-xs text-[--color-text-tertiary] underline-offset-2 hover:underline",
|
|
271
|
-
children: [/* @__PURE__ */
|
|
275
|
+
children: [/* @__PURE__ */ v(p, { className: "size-3" }), "Restore previous purchases"]
|
|
272
276
|
})
|
|
273
277
|
})
|
|
274
278
|
]
|
|
275
279
|
});
|
|
276
280
|
};
|
|
277
281
|
//#endregion
|
|
278
|
-
export {
|
|
282
|
+
export { D as NativeCheckoutView };
|
|
279
283
|
|
|
280
284
|
//# sourceMappingURL=NativeCheckoutView.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeCheckoutView.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/NativeCheckoutView.tsx"],"sourcesContent":["/**\n * Native IAP Checkout View\n *\n * Replaces the Stripe/Razorpay checkout UI when running inside a Capacitor\n * native app (iOS / Android). Shows an order summary then triggers the native\n * payment sheet on confirmation.\n *\n * After a successful purchase the backend validates the receipt/token with\n * Apple/Google servers and activates the subscription, credits, or add-on.\n */\n\nimport { type FC, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n Loader2,\n CheckCircle,\n XCircle,\n RotateCcw,\n Smartphone,\n Package,\n Coins,\n Blocks,\n ShoppingBag,\n CalendarDays,\n Info,\n ChevronRight,\n} from 'lucide-react';\nimport type { useNativeCheckout } from '../hooks/useNativeCheckout';\nimport type { Product } from '@capgo/native-purchases';\nimport type { PurchaseType } from '../types';\n\ninterface NativeCheckoutViewProps {\n purchaseType: PurchaseType;\n productId?: string;\n billingAccountId?: string;\n nativeCheckout: ReturnType<typeof useNativeCheckout>;\n onBack: () => void;\n}\n\nconst isIOS =\n typeof navigator !== 'undefined' &&\n (/iPad|iPhone|iPod/.test(navigator.userAgent) ||\n (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1));\n\nconst storeName = isIOS ? 'App Store' : 'Google Play';\n\nfunction purchaseTypeLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Credits';\n case 'ADDON':\n return 'Add-on';\n case 'ORDER':\n return 'Order';\n default:\n return 'Subscription';\n }\n}\n\nfunction purchaseTypeIcon(type: PurchaseType) {\n switch (type) {\n case 'CREDITS':\n return <Coins className=\"size-6 text-[--color-primary]\" />;\n case 'ADDON':\n return <Blocks className=\"size-6 text-[--color-primary]\" />;\n case 'ORDER':\n return <ShoppingBag className=\"size-6 text-[--color-primary]\" />;\n default:\n return <Package className=\"size-6 text-[--color-primary]\" />;\n }\n}\n\ninterface SubscriptionPeriod {\n numberOfUnits: number;\n unit: number;\n}\n\n// unit: 0=day, 1=week, 2=month, 3=year (Apple/Google convention)\nfunction billingPeriodLabel(period: SubscriptionPeriod | null | undefined): string | null {\n if (!period) return null;\n switch (period.unit) {\n case 0:\n return period.numberOfUnits === 1\n ? 'Billed daily'\n : `Billed every ${period.numberOfUnits} days`;\n case 1:\n return period.numberOfUnits === 1\n ? 'Billed weekly'\n : `Billed every ${period.numberOfUnits} weeks`;\n case 2:\n return period.numberOfUnits === 1\n ? 'Billed monthly'\n : `Billed every ${period.numberOfUnits} months`;\n case 3:\n return period.numberOfUnits === 1\n ? 'Billed yearly'\n : `Billed every ${period.numberOfUnits} years`;\n default:\n return null;\n }\n}\n\nfunction successLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Your credits will be added shortly. It may take a few seconds to reflect.';\n case 'ADDON':\n return 'Your add-on will be activated shortly. It may take a few seconds to reflect.';\n case 'ORDER':\n return 'Your order has been placed. It may take a few seconds to reflect.';\n default:\n return 'Your subscription will be activated shortly. It may take a few seconds to reflect.';\n }\n}\n\n// ─── Single-product summary card ──────────────────────────────────────────────\n\ninterface ProductSummaryProps {\n product: Product;\n purchaseType: PurchaseType;\n onPay: () => void;\n isProcessing: boolean;\n}\n\nconst ProductSummary: FC<ProductSummaryProps> = ({\n product,\n purchaseType,\n onPay,\n isProcessing,\n}) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n const hasIntro = !!product.introductoryPrice;\n\n return (\n <div className=\"flex flex-col gap-4 p-4\">\n {/* What you're getting */}\n <div className=\"rounded-2xl border border-[--color-border] bg-[--color-surface] overflow-hidden\">\n {/* Item row */}\n <div className=\"flex items-start gap-4 p-4\">\n <div className=\"flex size-11 shrink-0 items-center justify-center rounded-xl bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-semibold text-[--color-text-primary] leading-snug\">\n {product.title}\n </p>\n {product.description ? (\n <p className=\"mt-1 text-xs text-[--color-text-secondary] leading-relaxed\">\n {product.description}\n </p>\n ) : null}\n </div>\n </div>\n\n {/* Divider */}\n <div className=\"h-px bg-[--color-border]\" />\n\n {/* Pricing row */}\n <div className=\"flex items-center justify-between px-4 py-3\">\n <div className=\"flex flex-col\">\n {hasIntro && product.introductoryPrice ? (\n <>\n <span className=\"text-xs text-[--color-text-secondary] line-through\">\n {product.priceString}\n </span>\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.introductoryPrice.priceString}\n <span className=\"ml-1 text-xs font-normal text-[--color-text-secondary]\">\n intro offer\n </span>\n </span>\n </>\n ) : (\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.priceString}\n </span>\n )}\n {period ? (\n <div className=\"mt-0.5 flex items-center gap-1\">\n <CalendarDays className=\"size-3 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">{period}</span>\n </div>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1 rounded-full bg-[--color-surface-subtle] px-2.5 py-1\">\n <span className=\"text-xs font-medium text-[--color-text-secondary]\">\n {purchaseTypeLabel(purchaseType)}\n </span>\n </div>\n </div>\n </div>\n\n {/* Tax note */}\n <div className=\"flex items-start gap-2 rounded-xl bg-[--color-surface-subtle] px-3 py-2.5\">\n <Info className=\"mt-0.5 size-3.5 shrink-0 text-[--color-text-tertiary]\" />\n <p className=\"text-xs text-[--color-text-secondary] leading-relaxed\">\n Price includes all applicable taxes as determined by {storeName}. No additional charges.\n </p>\n </div>\n\n {/* Pay CTA */}\n <button\n type=\"button\"\n disabled={isProcessing}\n onClick={onPay}\n className=\"flex w-full items-center justify-center gap-2 rounded-xl bg-[--color-primary] px-6 py-4 text-sm font-semibold text-[--color-primary-foreground] transition-opacity disabled:opacity-60 active:opacity-80\"\n >\n {isProcessing ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Processing…\n </>\n ) : (\n <>\n <Smartphone className=\"size-4\" />\n Pay with {storeName}\n <ChevronRight className=\"size-4 opacity-70\" />\n </>\n )}\n </button>\n\n {/* Legal note */}\n <p className=\"text-center text-[10px] leading-relaxed text-[--color-text-tertiary]\">\n By continuing, you agree to the {storeName} terms.{' '}\n {purchaseType === 'PLAN'\n ? 'Subscription renews automatically. Cancel anytime.'\n : 'This is a one-time purchase.'}\n </p>\n </div>\n );\n};\n\n// ─── Multi-product list ───────────────────────────────────────────────────────\n\ninterface ProductListProps {\n products: Product[];\n purchaseType: PurchaseType;\n onSelect: (product: Product) => void;\n isProcessing: boolean;\n}\n\nconst ProductList: FC<ProductListProps> = ({ products, purchaseType, onSelect, isProcessing }) => (\n <div className=\"flex flex-col gap-3 p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-[--color-text-tertiary]\">\n Select a {purchaseTypeLabel(purchaseType)}\n </p>\n {products.map((product) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n return (\n <button\n type=\"button\"\n key={product.identifier}\n disabled={isProcessing}\n onClick={() => onSelect(product)}\n className=\"flex items-center justify-between rounded-xl border border-[--color-border] bg-[--color-surface] p-4 text-left transition-colors hover:border-[--color-primary] hover:bg-[--color-surface-hover] disabled:opacity-60\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div>\n <p className=\"text-sm font-semibold text-[--color-text-primary]\">{product.title}</p>\n {period ? (\n <p className=\"mt-0.5 text-xs text-[--color-text-secondary]\">{period}</p>\n ) : null}\n </div>\n </div>\n <div className=\"ml-4 shrink-0 text-right\">\n <p className=\"text-base font-bold text-[--color-primary]\">{product.priceString}</p>\n {isProcessing && (\n <Loader2 className=\"ml-auto mt-1 size-4 animate-spin text-[--color-text-tertiary]\" />\n )}\n </div>\n </button>\n );\n })}\n </div>\n);\n\n// ─── Main component ───────────────────────────────────────────────────────────\n\nexport const NativeCheckoutView: FC<NativeCheckoutViewProps> = ({\n purchaseType,\n productId,\n nativeCheckout,\n onBack,\n}) => {\n const {\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n } = nativeCheckout;\n\n // When user already selected a specific product, track it for the summary view\n const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);\n\n useEffect(() => {\n void loadProducts(productId ? [productId] : undefined);\n }, [loadProducts, productId]);\n\n // Auto-select when only one product loaded (specific product was pre-selected)\n useEffect(() => {\n if (products.length === 1 && products[0] && !selectedProduct) {\n setSelectedProduct(products[0]);\n }\n }, [products, selectedProduct]);\n\n const handlePay = (product: Product) => {\n setSelectedProduct(product);\n void purchaseProduct(product);\n };\n\n // ── Success ────────────────────────────────────────────────────────────────\n if (checkoutState === 'success') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <CheckCircle className=\"size-16 text-[--color-success]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Successful</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{successLabel(purchaseType)}</p>\n </div>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-8 py-3 text-sm font-semibold text-[--color-primary-foreground]\"\n >\n Done\n </button>\n </div>\n );\n }\n\n // ── Error ──────────────────────────────────────────────────────────────────\n if (checkoutState === 'error') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <XCircle className=\"size-16 text-[--color-destructive]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Failed</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{error}</p>\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={() => void loadProducts(productId ? [productId] : undefined)}\n className=\"rounded-xl border border-[--color-border] px-6 py-3 text-sm font-medium text-[--color-text-primary]\"\n >\n Try Again\n </button>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-6 py-3 text-sm font-medium text-[--color-primary-foreground]\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n }\n\n const isProcessing = checkoutState === 'processing';\n\n return (\n <div className=\"flex flex-col gap-0\">\n {/* Header */}\n <div className=\"flex items-center gap-3 border-b border-[--color-border] px-4 py-3\">\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-lg p-2 text-[--color-text-secondary] hover:bg-[--color-surface-hover]\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <p className=\"text-base font-semibold text-[--color-text-primary]\">\n {(selectedProduct ?? products.length === 1)\n ? 'Order Summary'\n : `Choose a ${purchaseTypeLabel(purchaseType)}`}\n </p>\n </div>\n </div>\n\n {/* Store badge */}\n <div className=\"flex items-center gap-2 border-b border-[--color-border] bg-[--color-surface-subtle] px-4 py-2\">\n <Smartphone className=\"size-3.5 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">\n Payments processed securely by <span className=\"font-medium\">{storeName}</span>\n </span>\n </div>\n\n {/* Content */}\n {isLoading && products.length === 0 ? (\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"size-6 animate-spin text-[--color-text-tertiary]\" />\n </div>\n ) : products.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-[--color-text-secondary] px-6\">\n No products available for this platform. Please try again later.\n </div>\n ) : (selectedProduct ?? products.length === 1) ? (\n // Single product — show order summary + pay CTA\n <ProductSummary\n product={(selectedProduct ?? products[0])!}\n purchaseType={purchaseType}\n onPay={() => handlePay((selectedProduct ?? products[0])!)}\n isProcessing={isProcessing}\n />\n ) : (\n // Multiple products — show list to pick from\n <ProductList\n products={products}\n purchaseType={purchaseType}\n onSelect={(p) => handlePay(p)}\n isProcessing={isProcessing}\n />\n )}\n\n {/* Restore purchases */}\n {!isProcessing && (\n <div className=\"px-4 pb-6 pt-2\">\n <button\n type=\"button\"\n onClick={() => void restorePurchases()}\n className=\"flex w-full items-center justify-center gap-1.5 text-xs text-[--color-text-tertiary] underline-offset-2 hover:underline\"\n >\n <RotateCcw className=\"size-3\" />\n Restore previous purchases\n </button>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;AA4CA,IAAM,IAJJ,OAAO,YAAc,QACpB,mBAAmB,KAAK,UAAU,UAAU,IAC1C,UAAU,aAAa,cAAc,UAAU,iBAAiB,KAE3C,cAAc;AAExC,SAAS,EAAkB,GAA4B;AACrD,SAAQ,GAAR;EACE,KAAK,UACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,EAAiB,GAAoB;AAC5C,SAAQ,GAAR;EACE,KAAK,UACH,QAAO,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA;EAC5D,KAAK,QACH,QAAO,kBAAC,GAAD,EAAQ,WAAU,iCAAkC,CAAA;EAC7D,KAAK,QACH,QAAO,kBAAC,GAAD,EAAa,WAAU,iCAAkC,CAAA;EAClE,QACE,QAAO,kBAAC,GAAD,EAAS,WAAU,iCAAkC,CAAA;;;AAUlE,SAAS,EAAmB,GAA8D;AACxF,KAAI,CAAC,EAAQ,QAAO;AACpB,SAAQ,EAAO,MAAf;EACE,KAAK,EACH,QAAO,EAAO,kBAAkB,IAC5B,iBACA,gBAAgB,EAAO,cAAc;EAC3C,KAAK,EACH,QAAO,EAAO,kBAAkB,IAC5B,kBACA,gBAAgB,EAAO,cAAc;EAC3C,KAAK,EACH,QAAO,EAAO,kBAAkB,IAC5B,mBACA,gBAAgB,EAAO,cAAc;EAC3C,KAAK,EACH,QAAO,EAAO,kBAAkB,IAC5B,kBACA,gBAAgB,EAAO,cAAc;EAC3C,QACE,QAAO;;;AAIb,SAAS,EAAa,GAA4B;AAChD,SAAQ,GAAR;EACE,KAAK,UACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,QACE,QAAO;;;AAab,IAAM,KAA2C,EAC/C,YACA,iBACA,UACA,sBACI;CACJ,IAAM,IAAS,EACb,EAAQ,mBACT,EACK,IAAW,CAAC,CAAC,EAAQ;AAE3B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAiB,EAAa;OAC3B,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAQ;QACP,CAAA,EACH,EAAQ,cACP,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAQ;QACP,CAAA,GACF,KACA;SACF;;KAGN,kBAAC,OAAD,EAAK,WAAU,4BAA6B,CAAA;KAG5C,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,KAAY,EAAQ,oBACnB,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAQ;QACJ,CAAA,EACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACG,EAAQ,kBAAkB,aAC3B,kBAAC,QAAD;SAAM,WAAU;mBAAyD;SAElE,CAAA,CACF;UACN,EAAA,CAAA,GAEH,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAQ;QACJ,CAAA,EAER,IACC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAc,WAAU,uCAAwC,CAAA,EAChE,kBAAC,QAAD;SAAM,WAAU;mBAAyC;SAAc,CAAA,CACnE;YACJ,KACA;UACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAkB,EAAa;QAC3B,CAAA;OACH,CAAA,CACF;;KACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAM,WAAU,yDAA0D,CAAA,EAC1E,kBAAC,KAAD;KAAG,WAAU;eAAb;MAAqE;MACb;MAAU;MAC9D;OACA;;GAGN,kBAAC,UAAD;IACE,MAAK;IACL,UAAU;IACV,SAAS;IACT,WAAU;cAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,EAAA,cAE1C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;;KACvB;KACV,kBAAC,GAAD,EAAc,WAAU,qBAAsB,CAAA;KAC7C,EAAA,CAAA;IAEE,CAAA;GAGT,kBAAC,KAAD;IAAG,WAAU;cAAb;KAAoF;KACjD;KAAU;KAAQ;KAClD,MAAiB,SACd,uDACA;KACF;;GACA;;GAaJ,KAAqC,EAAE,aAAU,iBAAc,aAAU,sBAC7E,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,KAAD;EAAG,WAAU;YAAb,CAAwF,aAC5E,EAAkB,EAAa,CACvC;KACH,EAAS,KAAK,MAAY;EACzB,IAAM,IAAS,EACb,EAAQ,mBACT;AACD,SACE,kBAAC,UAAD;GACE,MAAK;GAEL,UAAU;GACV,eAAe,EAAS,EAAQ;GAChC,WAAU;aALZ,CAOE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAiB,EAAa;KAC3B,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAAqD,EAAQ;KAAU,CAAA,EACnF,IACC,kBAAC,KAAD;KAAG,WAAU;eAAgD;KAAW,CAAA,GACtE,KACA,EAAA,CAAA,CACF;OACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAA8C,EAAQ;KAAgB,CAAA,EAClF,KACC,kBAAC,GAAD,EAAS,WAAU,iEAAkE,CAAA,CAEnF;MACC;KAtBF,EAAQ,WAsBN;GAEX,CACE;IAKK,KAAmD,EAC9D,iBACA,cACA,mBACA,gBACI;CACJ,IAAM,EACJ,aACA,kBACA,UACA,cACA,iBACA,oBACA,wBACE,GAGE,CAAC,GAAiB,KAAsB,EAAyB,KAAK;AAO5E,CALA,QAAgB;AACT,IAAa,IAAY,CAAC,EAAU,GAAG,KAAA,EAAU;IACrD,CAAC,GAAc,EAAU,CAAC,EAG7B,QAAgB;AACd,EAAI,EAAS,WAAW,KAAK,EAAS,MAAM,CAAC,KAC3C,EAAmB,EAAS,GAAG;IAEhC,CAAC,GAAU,EAAgB,CAAC;CAE/B,IAAM,KAAa,MAAqB;AAEjC,EADL,EAAmB,EAAQ,EACtB,EAAgB,EAAQ;;AAI/B,KAAI,MAAkB,UACpB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAa,WAAU,kCAAmC,CAAA;GAC1D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAAoD;IAAuB,CAAA,EACxF,kBAAC,KAAD;IAAG,WAAU;cAA8C,EAAa,EAAa;IAAK,CAAA,CACtF,EAAA,CAAA;GACN,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cACX;IAEQ,CAAA;GACL;;AAKV,KAAI,MAAkB,QACpB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAS,WAAU,sCAAuC,CAAA;GAC1D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAAoD;IAAmB,CAAA,EACpF,kBAAC,KAAD;IAAG,WAAU;cAA8C;IAAU,CAAA,CACjE,EAAA,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,KAAK,EAAa,IAAY,CAAC,EAAU,GAAG,KAAA,EAAU;KACrE,WAAU;eACX;KAEQ,CAAA,EACT,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eACX;KAEQ,CAAA,CACL;;GACF;;CAIV,IAAM,IAAe,MAAkB;AAEvC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;KACzB,CAAA,EACT,kBAAC,OAAD,EAAA,UACE,kBAAC,KAAD;KAAG,WAAU;eACT,KAAmB,EAAS,WAAW,IACrC,kBACA,YAAY,EAAkB,EAAa;KAC7C,CAAA,EACA,CAAA,CACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAY,WAAU,yCAA0C,CAAA,EAChE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAwD,mCACvB,kBAAC,QAAD;MAAM,WAAU;gBAAe;MAAiB,CAAA,CAC1E;OACH;;GAGL,KAAa,EAAS,WAAW,IAChC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAS,WAAU,oDAAqD,CAAA;IACpE,CAAA,GACJ,EAAS,WAAW,IACtB,kBAAC,OAAD;IAAK,WAAU;cAA+D;IAExE,CAAA,GACH,KAAmB,EAAS,WAAW,IAE1C,kBAAC,GAAD;IACE,SAAU,KAAmB,EAAS;IACxB;IACd,aAAa,EAAW,KAAmB,EAAS,GAAK;IAC3C;IACd,CAAA,GAGF,kBAAC,GAAD;IACY;IACI;IACd,WAAW,MAAM,EAAU,EAAE;IACf;IACd,CAAA;GAIH,CAAC,KACA,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,KAAK,GAAkB;KACtC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,6BAEzB;;IACL,CAAA;GAEJ"}
|
|
1
|
+
{"version":3,"file":"NativeCheckoutView.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/NativeCheckoutView.tsx"],"sourcesContent":["/**\n * Native IAP Checkout View\n *\n * Replaces the Stripe/Razorpay checkout UI when running inside a Capacitor\n * native app (iOS / Android). Shows an order summary then triggers the native\n * payment sheet on confirmation.\n *\n * After a successful purchase the backend validates the receipt/token with\n * Apple/Google servers and activates the subscription, credits, or add-on.\n */\n\nimport { type FC, useEffect, useState, useMemo } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n ArrowLeft,\n Loader2,\n CheckCircle,\n XCircle,\n RotateCcw,\n Smartphone,\n Package,\n Coins,\n Blocks,\n ShoppingBag,\n CalendarDays,\n Info,\n ChevronRight,\n} from 'lucide-react';\nimport type { useNativeCheckout } from '../hooks/useNativeCheckout';\nimport type { Product } from '@capgo/native-purchases';\nimport type { PurchaseType } from '../types';\n\ninterface NativeCheckoutViewProps {\n purchaseType: PurchaseType;\n productId?: string;\n billingAccountId?: string;\n nativeCheckout: ReturnType<typeof useNativeCheckout>;\n onBack: () => void;\n}\n\nfunction getStoreName(): string {\n return Capacitor.getPlatform() === 'ios' ? 'App Store' : 'Google Play';\n}\n\nfunction purchaseTypeLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Credits';\n case 'ADDON':\n return 'Add-on';\n case 'ORDER':\n return 'Order';\n default:\n return 'Subscription';\n }\n}\n\nfunction purchaseTypeIcon(type: PurchaseType) {\n switch (type) {\n case 'CREDITS':\n return <Coins className=\"size-6 text-[--color-primary]\" />;\n case 'ADDON':\n return <Blocks className=\"size-6 text-[--color-primary]\" />;\n case 'ORDER':\n return <ShoppingBag className=\"size-6 text-[--color-primary]\" />;\n default:\n return <Package className=\"size-6 text-[--color-primary]\" />;\n }\n}\n\ninterface SubscriptionPeriod {\n numberOfUnits: number;\n unit: number;\n}\n\n// unit: 0=day, 1=week, 2=month, 3=year (Apple/Google convention)\nfunction billingPeriodLabel(period: SubscriptionPeriod | null | undefined): string | null {\n if (!period) return null;\n switch (period.unit) {\n case 0:\n return period.numberOfUnits === 1\n ? 'Billed daily'\n : `Billed every ${period.numberOfUnits} days`;\n case 1:\n return period.numberOfUnits === 1\n ? 'Billed weekly'\n : `Billed every ${period.numberOfUnits} weeks`;\n case 2:\n return period.numberOfUnits === 1\n ? 'Billed monthly'\n : `Billed every ${period.numberOfUnits} months`;\n case 3:\n return period.numberOfUnits === 1\n ? 'Billed yearly'\n : `Billed every ${period.numberOfUnits} years`;\n default:\n return null;\n }\n}\n\nfunction successLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Your credits will be added shortly. It may take a few seconds to reflect.';\n case 'ADDON':\n return 'Your add-on will be activated shortly. It may take a few seconds to reflect.';\n case 'ORDER':\n return 'Your order has been placed. It may take a few seconds to reflect.';\n default:\n return 'Your subscription will be activated shortly. It may take a few seconds to reflect.';\n }\n}\n\n// ─── Single-product summary card ──────────────────────────────────────────────\n\ninterface ProductSummaryProps {\n product: Product;\n purchaseType: PurchaseType;\n storeName: string;\n onPay: () => void;\n isProcessing: boolean;\n}\n\nconst ProductSummary: FC<ProductSummaryProps> = ({\n product,\n purchaseType,\n storeName,\n onPay,\n isProcessing,\n}) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n const hasIntro = !!product.introductoryPrice;\n\n return (\n <div className=\"flex flex-col gap-4 p-4\">\n {/* What you're getting */}\n <div className=\"rounded-2xl border border-[--color-border] bg-[--color-surface] overflow-hidden\">\n {/* Item row */}\n <div className=\"flex items-start gap-4 p-4\">\n <div className=\"flex size-11 shrink-0 items-center justify-center rounded-xl bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-semibold text-[--color-text-primary] leading-snug\">\n {product.title}\n </p>\n {product.description ? (\n <p className=\"mt-1 text-xs text-[--color-text-secondary] leading-relaxed\">\n {product.description}\n </p>\n ) : null}\n </div>\n </div>\n\n {/* Divider */}\n <div className=\"h-px bg-[--color-border]\" />\n\n {/* Pricing row */}\n <div className=\"flex items-center justify-between px-4 py-3\">\n <div className=\"flex flex-col\">\n {hasIntro && product.introductoryPrice ? (\n <>\n <span className=\"text-xs text-[--color-text-secondary] line-through\">\n {product.priceString}\n </span>\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.introductoryPrice.priceString}\n <span className=\"ml-1 text-xs font-normal text-[--color-text-secondary]\">\n intro offer\n </span>\n </span>\n </>\n ) : (\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.priceString}\n </span>\n )}\n {period ? (\n <div className=\"mt-0.5 flex items-center gap-1\">\n <CalendarDays className=\"size-3 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">{period}</span>\n </div>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1 rounded-full bg-[--color-surface-subtle] px-2.5 py-1\">\n <span className=\"text-xs font-medium text-[--color-text-secondary]\">\n {purchaseTypeLabel(purchaseType)}\n </span>\n </div>\n </div>\n </div>\n\n {/* Tax note */}\n <div className=\"flex items-start gap-2 rounded-xl bg-[--color-surface-subtle] px-3 py-2.5\">\n <Info className=\"mt-0.5 size-3.5 shrink-0 text-[--color-text-tertiary]\" />\n <p className=\"text-xs text-[--color-text-secondary] leading-relaxed\">\n Price includes all applicable taxes as determined by {storeName}. No additional charges.\n </p>\n </div>\n\n {/* Pay CTA */}\n <button\n type=\"button\"\n disabled={isProcessing}\n onClick={onPay}\n className=\"flex w-full items-center justify-center gap-2 rounded-xl bg-[--color-primary] px-6 py-4 text-sm font-semibold text-[--color-primary-foreground] transition-opacity disabled:opacity-60 active:opacity-80\"\n >\n {isProcessing ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Processing…\n </>\n ) : (\n <>\n <Smartphone className=\"size-4\" />\n Pay with {storeName}\n <ChevronRight className=\"size-4 opacity-70\" />\n </>\n )}\n </button>\n\n {/* Legal note */}\n <p className=\"text-center text-[10px] leading-relaxed text-[--color-text-tertiary]\">\n By continuing, you agree to the {storeName} terms.{' '}\n {purchaseType === 'PLAN'\n ? 'Subscription renews automatically. Cancel anytime.'\n : 'This is a one-time purchase.'}\n </p>\n </div>\n );\n};\n\n// ─── Multi-product list ───────────────────────────────────────────────────────\n\ninterface ProductListProps {\n products: Product[];\n purchaseType: PurchaseType;\n onSelect: (product: Product) => void;\n isProcessing: boolean;\n}\n\nconst ProductList: FC<ProductListProps> = ({ products, purchaseType, onSelect, isProcessing }) => (\n <div className=\"flex flex-col gap-3 p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-[--color-text-tertiary]\">\n Select a {purchaseTypeLabel(purchaseType)}\n </p>\n {products.map((product) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n return (\n <button\n type=\"button\"\n key={product.identifier}\n disabled={isProcessing}\n onClick={() => onSelect(product)}\n className=\"flex items-center justify-between rounded-xl border border-[--color-border] bg-[--color-surface] p-4 text-left transition-colors hover:border-[--color-primary] hover:bg-[--color-surface-hover] disabled:opacity-60\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div>\n <p className=\"text-sm font-semibold text-[--color-text-primary]\">{product.title}</p>\n {period ? (\n <p className=\"mt-0.5 text-xs text-[--color-text-secondary]\">{period}</p>\n ) : null}\n </div>\n </div>\n <div className=\"ml-4 shrink-0 text-right\">\n <p className=\"text-base font-bold text-[--color-primary]\">{product.priceString}</p>\n {isProcessing && (\n <Loader2 className=\"ml-auto mt-1 size-4 animate-spin text-[--color-text-tertiary]\" />\n )}\n </div>\n </button>\n );\n })}\n </div>\n);\n\n// ─── Main component ───────────────────────────────────────────────────────────\n\nexport const NativeCheckoutView: FC<NativeCheckoutViewProps> = ({\n purchaseType,\n productId,\n nativeCheckout,\n onBack,\n}) => {\n const storeName = useMemo(() => getStoreName(), []);\n const {\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n } = nativeCheckout;\n\n // When user already selected a specific product, track it for the summary view\n const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);\n\n useEffect(() => {\n void loadProducts(productId ? [productId] : undefined);\n }, [loadProducts, productId]);\n\n // Auto-select when only one product loaded (specific product was pre-selected)\n useEffect(() => {\n if (products.length === 1 && products[0] && !selectedProduct) {\n setSelectedProduct(products[0]);\n }\n }, [products, selectedProduct]);\n\n const handlePay = (product: Product) => {\n setSelectedProduct(product);\n void purchaseProduct(product);\n };\n\n // ── Success ────────────────────────────────────────────────────────────────\n if (checkoutState === 'success') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <CheckCircle className=\"size-16 text-[--color-success]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Successful</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{successLabel(purchaseType)}</p>\n </div>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-8 py-3 text-sm font-semibold text-[--color-primary-foreground]\"\n >\n Done\n </button>\n </div>\n );\n }\n\n // ── Error ──────────────────────────────────────────────────────────────────\n if (checkoutState === 'error') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <XCircle className=\"size-16 text-[--color-destructive]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Failed</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{error}</p>\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={() => void loadProducts(productId ? [productId] : undefined)}\n className=\"rounded-xl border border-[--color-border] px-6 py-3 text-sm font-medium text-[--color-text-primary]\"\n >\n Try Again\n </button>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-6 py-3 text-sm font-medium text-[--color-primary-foreground]\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n }\n\n const isProcessing = checkoutState === 'processing';\n\n return (\n <div className=\"flex flex-col gap-0\">\n {/* Header */}\n <div className=\"flex items-center gap-3 border-b border-[--color-border] px-4 py-3\">\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-lg p-2 text-[--color-text-secondary] hover:bg-[--color-surface-hover]\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <p className=\"text-base font-semibold text-[--color-text-primary]\">\n {(selectedProduct ?? products.length === 1)\n ? 'Order Summary'\n : `Choose a ${purchaseTypeLabel(purchaseType)}`}\n </p>\n </div>\n </div>\n\n {/* Store badge */}\n <div className=\"flex items-center gap-2 border-b border-[--color-border] bg-[--color-surface-subtle] px-4 py-2\">\n <Smartphone className=\"size-3.5 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">\n Payments processed securely by <span className=\"font-medium\">{storeName}</span>\n </span>\n </div>\n\n {/* Content */}\n {isLoading && products.length === 0 ? (\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"size-6 animate-spin text-[--color-text-tertiary]\" />\n </div>\n ) : products.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-[--color-text-secondary] px-6\">\n No products available for this platform. Please try again later.\n </div>\n ) : (selectedProduct ?? products.length === 1) ? (\n // Single product — show order summary + pay CTA\n <ProductSummary\n product={(selectedProduct ?? products[0])!}\n purchaseType={purchaseType}\n storeName={storeName}\n onPay={() => handlePay((selectedProduct ?? products[0])!)}\n isProcessing={isProcessing}\n />\n ) : (\n // Multiple products — show list to pick from\n <ProductList\n products={products}\n purchaseType={purchaseType}\n onSelect={(p) => handlePay(p)}\n isProcessing={isProcessing}\n />\n )}\n\n {/* Restore purchases */}\n {!isProcessing && (\n <div className=\"px-4 pb-6 pt-2\">\n <button\n type=\"button\"\n onClick={() => void restorePurchases()}\n className=\"flex w-full items-center justify-center gap-1.5 text-xs text-[--color-text-tertiary] underline-offset-2 hover:underline\"\n >\n <RotateCcw className=\"size-3\" />\n Restore previous purchases\n </button>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,88 +1,92 @@
|
|
|
1
1
|
import { useBilling as e } from "../../../providers/BillingProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { normalizeGlobalGatewayBaseUrl as t } from "../../../shared/utils/gatewayGraphql.js";
|
|
3
|
+
import { Capacitor as n } from "../../../../node_modules/@capacitor/core/dist/index.js";
|
|
4
|
+
import { acknowledgeNativePurchase as r, getNativeProducts as i, isNativeIAPAvailable as a, purchaseNativeProduct as o, restoreNativePurchases as s } from "../../../lib/native-purchases.js";
|
|
5
|
+
import { useCallback as c, useState as l } from "react";
|
|
5
6
|
//#region src/billing/modules/checkout/hooks/useNativeCheckout.ts
|
|
6
|
-
function
|
|
7
|
-
let { apiGatewayUrl:
|
|
8
|
-
if (!
|
|
9
|
-
let t = e ?? (
|
|
7
|
+
function u({ billingAccountId: u, purchaseType: d, productId: f } = {}) {
|
|
8
|
+
let { apiGatewayUrl: p, authToken: m } = e(), [h, g] = l([]), [_, v] = l("idle"), [y, b] = l(null), [x, S] = l(!1), C = c(async (e) => {
|
|
9
|
+
if (!a()) return;
|
|
10
|
+
let t = e ?? (f ? [f] : []);
|
|
10
11
|
if (t.length !== 0) {
|
|
11
|
-
|
|
12
|
+
S(!0), b(null);
|
|
12
13
|
try {
|
|
13
|
-
|
|
14
|
+
g(await i(t));
|
|
14
15
|
} catch (e) {
|
|
15
|
-
|
|
16
|
+
b(e instanceof Error ? e.message : "Failed to load products");
|
|
16
17
|
} finally {
|
|
17
|
-
|
|
18
|
+
S(!1);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
}, [
|
|
21
|
-
if (!
|
|
22
|
-
|
|
23
|
-
let
|
|
21
|
+
}, [f]), w = c(async (e) => {
|
|
22
|
+
if (!u) return b("No billing account selected"), { success: !1 };
|
|
23
|
+
v("processing"), b(null);
|
|
24
|
+
let i;
|
|
24
25
|
try {
|
|
25
|
-
let
|
|
26
|
-
|
|
26
|
+
let a = d === "CREDITS", s = crypto.randomUUID();
|
|
27
|
+
i = await o(e.identifier, {
|
|
27
28
|
autoAcknowledgePurchases: !1,
|
|
28
|
-
isConsumable:
|
|
29
|
-
planIdentifier:
|
|
30
|
-
appAccountToken:
|
|
29
|
+
isConsumable: a,
|
|
30
|
+
planIdentifier: a ? void 0 : e.planIdentifier ?? void 0,
|
|
31
|
+
appAccountToken: s
|
|
31
32
|
});
|
|
32
|
-
let
|
|
33
|
+
let c = `${t(p) ?? ""}/global/rest/billing/iap/validate`, l = n.getPlatform(), f = a ? "INAPP" : "SUBS", h = await fetch(c, {
|
|
33
34
|
method: "POST",
|
|
34
35
|
headers: {
|
|
35
36
|
"Content-Type": "application/json",
|
|
36
|
-
...
|
|
37
|
+
...m ? { Authorization: `Bearer ${m}` } : {}
|
|
37
38
|
},
|
|
38
39
|
body: JSON.stringify({
|
|
39
|
-
platform:
|
|
40
|
-
transactionId:
|
|
41
|
-
productIdentifier:
|
|
42
|
-
receipt:
|
|
43
|
-
jwsRepresentation:
|
|
44
|
-
purchaseToken:
|
|
45
|
-
appAccountToken:
|
|
46
|
-
billingAccountId:
|
|
47
|
-
purchaseType:
|
|
40
|
+
platform: l,
|
|
41
|
+
transactionId: i.transactionId,
|
|
42
|
+
productIdentifier: i.productIdentifier,
|
|
43
|
+
receipt: i.receipt,
|
|
44
|
+
jwsRepresentation: i.jwsRepresentation,
|
|
45
|
+
purchaseToken: i.purchaseToken,
|
|
46
|
+
appAccountToken: s,
|
|
47
|
+
billingAccountId: u,
|
|
48
|
+
purchaseType: d,
|
|
49
|
+
nativePurchaseType: f,
|
|
50
|
+
price: e.price,
|
|
51
|
+
currency: e.currencyCode
|
|
48
52
|
})
|
|
49
53
|
});
|
|
50
|
-
if (!
|
|
51
|
-
let e = await
|
|
52
|
-
throw Error(e.message ?? `Validation failed (${
|
|
54
|
+
if (!h.ok) {
|
|
55
|
+
let e = await h.json().catch(() => ({}));
|
|
56
|
+
throw Error(e.message ?? `Validation failed (${h.status})`);
|
|
53
57
|
}
|
|
54
|
-
return await
|
|
58
|
+
return await r(i.purchaseToken ?? i.transactionId), v("success"), { success: !0 };
|
|
55
59
|
} catch (e) {
|
|
56
60
|
let t = e instanceof Error ? e.message : "Purchase failed";
|
|
57
|
-
return t.toLowerCase().includes("cancel") || t.toLowerCase().includes("dismiss") || t.toLowerCase().includes("abort") ?
|
|
61
|
+
return t.toLowerCase().includes("cancel") || t.toLowerCase().includes("dismiss") || t.toLowerCase().includes("abort") ? v("idle") : (b(t), v("error")), { success: !1 };
|
|
58
62
|
}
|
|
59
63
|
}, [
|
|
60
|
-
|
|
61
|
-
f,
|
|
64
|
+
u,
|
|
62
65
|
p,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
m,
|
|
67
|
+
d
|
|
68
|
+
]), T = c(async () => {
|
|
69
|
+
S(!0), b(null);
|
|
66
70
|
try {
|
|
67
|
-
return await
|
|
71
|
+
return await s(), { success: !0 };
|
|
68
72
|
} catch (e) {
|
|
69
|
-
return
|
|
73
|
+
return b(e instanceof Error ? e.message : "Restore failed"), { success: !1 };
|
|
70
74
|
} finally {
|
|
71
|
-
|
|
75
|
+
S(!1);
|
|
72
76
|
}
|
|
73
77
|
}, []);
|
|
74
78
|
return {
|
|
75
|
-
isAvailable:
|
|
76
|
-
products:
|
|
77
|
-
checkoutState:
|
|
78
|
-
error:
|
|
79
|
-
isLoading:
|
|
80
|
-
loadProducts:
|
|
81
|
-
purchaseProduct:
|
|
82
|
-
restorePurchases:
|
|
79
|
+
isAvailable: a(),
|
|
80
|
+
products: h,
|
|
81
|
+
checkoutState: _,
|
|
82
|
+
error: y,
|
|
83
|
+
isLoading: x,
|
|
84
|
+
loadProducts: C,
|
|
85
|
+
purchaseProduct: w,
|
|
86
|
+
restorePurchases: T
|
|
83
87
|
};
|
|
84
88
|
}
|
|
85
89
|
//#endregion
|
|
86
|
-
export {
|
|
90
|
+
export { u as useNativeCheckout };
|
|
87
91
|
|
|
88
92
|
//# sourceMappingURL=useNativeCheckout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNativeCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useNativeCheckout.ts"],"sourcesContent":["/**\n * Native IAP checkout hook — Apple StoreKit / Google Play Billing.\n *\n * Manages the full native purchase lifecycle:\n * 1. Load product info from the App Store / Play Store\n * 2. Trigger the native payment sheet\n * 3. Send the receipt / JWS / purchase token to the backend for validation\n * 4. Acknowledge the transaction after the backend confirms it\n *\n * The backend endpoint POST /rest/billing/iap/validate validates with Apple\n * or Google servers and then activates the subscription or credits — no\n * RevenueCat or third-party account required.\n */\n\nimport { useState, useCallback } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n getNativeProducts,\n purchaseNativeProduct,\n acknowledgeNativePurchase,\n restoreNativePurchases,\n isNativeIAPAvailable,\n} from '../../../lib/native-purchases';\nimport type { Product, Transaction, PURCHASE_TYPE } from '@capgo/native-purchases';\nimport type { CheckoutState, PurchaseType } from '../types';\nimport { useBilling } from '../../../providers/BillingProvider';\n\nexport type { Product, Transaction };\n\ninterface UseNativeCheckoutOptions {\n billingAccountId?: string;\n purchaseType?: PurchaseType;\n /** Store product identifier — passed to getProducts to load the specific product. */\n productId?: string;\n}\n\ninterface UseNativeCheckoutResult {\n isAvailable: boolean;\n products: Product[];\n checkoutState: CheckoutState;\n error: string | null;\n isLoading: boolean;\n loadProducts: (productIdentifiers?: string[]) => Promise<void>;\n purchaseProduct: (product: Product) => Promise<{ success: boolean }>;\n restorePurchases: () => Promise<{ success: boolean }>;\n}\n\nexport function useNativeCheckout({\n billingAccountId,\n purchaseType,\n productId,\n}: UseNativeCheckoutOptions = {}): UseNativeCheckoutResult {\n const { apiGatewayUrl, authToken } = useBilling();\n const [products, setProducts] = useState<Product[]>([]);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const loadProducts = useCallback(\n async (productIdentifiers?: string[]) => {\n if (!isNativeIAPAvailable()) return;\n const ids = productIdentifiers ?? (productId ? [productId] : []);\n if (ids.length === 0) return;\n setIsLoading(true);\n setError(null);\n try {\n const result = await getNativeProducts(ids);\n setProducts(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setIsLoading(false);\n }\n },\n [productId]\n );\n\n const purchaseProduct = useCallback(\n async (product: Product): Promise<{ success: boolean }> => {\n if (!billingAccountId) {\n setError('No billing account selected');\n return { success: false };\n }\n\n setCheckoutState('processing');\n setError(null);\n\n let transaction: Transaction | undefined;\n try {\n // Trigger native payment sheet — deferred ack so we validate server-side first.\n // planIdentifier is required for Android subscriptions (base plan ID from Play Console).\n // isConsumable allows credits to be re-purchased on Android.\n const isCredits = purchaseType === 'CREDITS';\n // appAccountToken: UUID passed to Apple/Google so they echo it in server notifications,\n // allowing us to link renewals/cancellations to this billing account.\n // iOS requires UUID format; Android (obfuscatedExternalAccountId) accepts any string ≤ 64 chars.\n const appAccountToken = crypto.randomUUID();\n transaction = await purchaseNativeProduct(product.identifier, {\n autoAcknowledgePurchases: false,\n isConsumable: isCredits,\n planIdentifier: !isCredits ? (product.planIdentifier ?? undefined) : undefined,\n appAccountToken,\n });\n\n // Send receipt / token to backend for server-side validation and entitlement activation
|
|
1
|
+
{"version":3,"file":"useNativeCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useNativeCheckout.ts"],"sourcesContent":["/**\n * Native IAP checkout hook — Apple StoreKit / Google Play Billing.\n *\n * Manages the full native purchase lifecycle:\n * 1. Load product info from the App Store / Play Store\n * 2. Trigger the native payment sheet\n * 3. Send the receipt / JWS / purchase token to the backend for validation\n * 4. Acknowledge the transaction after the backend confirms it\n *\n * The backend endpoint POST /rest/billing/iap/validate validates with Apple\n * or Google servers and then activates the subscription or credits — no\n * RevenueCat or third-party account required.\n */\n\nimport { useState, useCallback } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n getNativeProducts,\n purchaseNativeProduct,\n acknowledgeNativePurchase,\n restoreNativePurchases,\n isNativeIAPAvailable,\n} from '../../../lib/native-purchases';\nimport type { Product, Transaction, PURCHASE_TYPE } from '@capgo/native-purchases';\nimport type { CheckoutState, PurchaseType } from '../types';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { normalizeGlobalGatewayBaseUrl } from '../../../shared/utils/gatewayGraphql';\n\nexport type { Product, Transaction };\n\ninterface UseNativeCheckoutOptions {\n billingAccountId?: string;\n purchaseType?: PurchaseType;\n /** Store product identifier — passed to getProducts to load the specific product. */\n productId?: string;\n}\n\ninterface UseNativeCheckoutResult {\n isAvailable: boolean;\n products: Product[];\n checkoutState: CheckoutState;\n error: string | null;\n isLoading: boolean;\n loadProducts: (productIdentifiers?: string[]) => Promise<void>;\n purchaseProduct: (product: Product) => Promise<{ success: boolean }>;\n restorePurchases: () => Promise<{ success: boolean }>;\n}\n\nexport function useNativeCheckout({\n billingAccountId,\n purchaseType,\n productId,\n}: UseNativeCheckoutOptions = {}): UseNativeCheckoutResult {\n const { apiGatewayUrl, authToken } = useBilling();\n const [products, setProducts] = useState<Product[]>([]);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n\n const loadProducts = useCallback(\n async (productIdentifiers?: string[]) => {\n if (!isNativeIAPAvailable()) return;\n const ids = productIdentifiers ?? (productId ? [productId] : []);\n if (ids.length === 0) return;\n setIsLoading(true);\n setError(null);\n try {\n const result = await getNativeProducts(ids);\n setProducts(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setIsLoading(false);\n }\n },\n [productId]\n );\n\n const purchaseProduct = useCallback(\n async (product: Product): Promise<{ success: boolean }> => {\n if (!billingAccountId) {\n setError('No billing account selected');\n return { success: false };\n }\n\n setCheckoutState('processing');\n setError(null);\n\n let transaction: Transaction | undefined;\n try {\n // Trigger native payment sheet — deferred ack so we validate server-side first.\n // planIdentifier is required for Android subscriptions (base plan ID from Play Console).\n // isConsumable allows credits to be re-purchased on Android.\n const isCredits = purchaseType === 'CREDITS';\n // appAccountToken: UUID passed to Apple/Google so they echo it in server notifications,\n // allowing us to link renewals/cancellations to this billing account.\n // iOS requires UUID format; Android (obfuscatedExternalAccountId) accepts any string ≤ 64 chars.\n const appAccountToken = crypto.randomUUID();\n transaction = await purchaseNativeProduct(product.identifier, {\n autoAcknowledgePurchases: false,\n isConsumable: isCredits,\n planIdentifier: !isCredits ? (product.planIdentifier ?? undefined) : undefined,\n appAccountToken,\n });\n\n // Send receipt / token to backend for server-side validation and entitlement activation.\n // apiGatewayUrl is the GraphQL endpoint (e.g. https://graphql.burdenoff.com/global/graphql);\n // strip the /global/graphql suffix to get the base URL, then use the REST path.\n const gatewayBase = normalizeGlobalGatewayBaseUrl(apiGatewayUrl) ?? '';\n const iapValidateUrl = `${gatewayBase}/global/rest/billing/iap/validate`;\n const platform = Capacitor.getPlatform() as 'ios' | 'android';\n // nativePurchaseType: credits are consumable (INAPP); plans and addons are subscriptions (SUBS).\n const nativePurchaseType = isCredits ? 'INAPP' : 'SUBS';\n const response = await fetch(iapValidateUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n },\n body: JSON.stringify({\n platform,\n transactionId: transaction.transactionId,\n productIdentifier: transaction.productIdentifier,\n receipt: transaction.receipt,\n jwsRepresentation: transaction.jwsRepresentation,\n purchaseToken: transaction.purchaseToken,\n appAccountToken,\n billingAccountId,\n purchaseType,\n nativePurchaseType,\n price: product.price,\n currency: product.currencyCode,\n }),\n });\n\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { message?: string };\n throw new Error(body.message ?? `Validation failed (${response.status})`);\n }\n\n // Acknowledge only after backend confirms the purchase is valid\n await acknowledgeNativePurchase(transaction.purchaseToken ?? transaction.transactionId);\n\n setCheckoutState('success');\n return { success: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Purchase failed';\n // User cancellation is not an error worth surfacing as \"error\" state\n if (\n message.toLowerCase().includes('cancel') ||\n message.toLowerCase().includes('dismiss') ||\n message.toLowerCase().includes('abort')\n ) {\n setCheckoutState('idle');\n } else {\n setError(message);\n setCheckoutState('error');\n }\n return { success: false };\n }\n },\n [billingAccountId, apiGatewayUrl, authToken, purchaseType]\n );\n\n const restorePurchases = useCallback(async (): Promise<{ success: boolean }> => {\n setIsLoading(true);\n setError(null);\n try {\n await restoreNativePurchases();\n return { success: true };\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Restore failed');\n return { success: false };\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n return {\n isAvailable: isNativeIAPAvailable(),\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n };\n}\n"],"mappings":";;;;;;AAgDA,SAAgB,EAAkB,EAChC,qBACA,iBACA,iBAC4B,EAAE,EAA2B;CACzD,IAAM,EAAE,kBAAe,iBAAc,GAAY,EAC3C,CAAC,GAAU,KAAe,EAAoB,EAAE,CAAC,EACjD,CAAC,GAAe,KAAoB,EAAwB,OAAO,EACnE,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAW,KAAgB,EAAS,GAAM,EAE3C,IAAe,EACnB,OAAO,MAAkC;AACvC,MAAI,CAAC,GAAsB,CAAE;EAC7B,IAAM,IAAM,MAAuB,IAAY,CAAC,EAAU,GAAG,EAAE;AAC3D,QAAI,WAAW,GAEnB;GADA,EAAa,GAAK,EAClB,EAAS,KAAK;AACd,OAAI;AAEF,MADe,MAAM,EAAkB,EAAI,CACxB;YACZ,GAAK;AACZ,MAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;aAChE;AACR,MAAa,GAAM;;;IAGvB,CAAC,EAAU,CACZ,EAEK,IAAkB,EACtB,OAAO,MAAoD;AACzD,MAAI,CAAC,EAEH,QADA,EAAS,8BAA8B,EAChC,EAAE,SAAS,IAAO;AAI3B,EADA,EAAiB,aAAa,EAC9B,EAAS,KAAK;EAEd,IAAI;AACJ,MAAI;GAIF,IAAM,IAAY,MAAiB,WAI7B,IAAkB,OAAO,YAAY;AAC3C,OAAc,MAAM,EAAsB,EAAQ,YAAY;IAC5D,0BAA0B;IAC1B,cAAc;IACd,gBAAiB,IAAoD,KAAA,IAAvC,EAAQ,kBAAkB,KAAA;IACxD;IACD,CAAC;GAMF,IAAM,IAAiB,GADH,EAA8B,EAAc,IAAI,GAC9B,oCAChC,IAAW,EAAU,aAAa,EAElC,IAAqB,IAAY,UAAU,QAC3C,IAAW,MAAM,MAAM,GAAgB;IAC3C,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,GAAI,IAAY,EAAE,eAAe,UAAU,KAAa,GAAG,EAAE;KAC9D;IACD,MAAM,KAAK,UAAU;KACnB;KACA,eAAe,EAAY;KAC3B,mBAAmB,EAAY;KAC/B,SAAS,EAAY;KACrB,mBAAmB,EAAY;KAC/B,eAAe,EAAY;KAC3B;KACA;KACA;KACA;KACA,OAAO,EAAQ;KACf,UAAU,EAAQ;KACnB,CAAC;IACH,CAAC;AAEF,OAAI,CAAC,EAAS,IAAI;IAChB,IAAM,IAAQ,MAAM,EAAS,MAAM,CAAC,aAAa,EAAE,EAAE;AACrD,UAAU,MAAM,EAAK,WAAW,sBAAsB,EAAS,OAAO,GAAG;;AAO3E,UAHA,MAAM,EAA0B,EAAY,iBAAiB,EAAY,cAAc,EAEvF,EAAiB,UAAU,EACpB,EAAE,SAAS,IAAM;WACjB,GAAK;GACZ,IAAM,IAAU,aAAe,QAAQ,EAAI,UAAU;AAYrD,UATE,EAAQ,aAAa,CAAC,SAAS,SAAS,IACxC,EAAQ,aAAa,CAAC,SAAS,UAAU,IACzC,EAAQ,aAAa,CAAC,SAAS,QAAQ,GAEvC,EAAiB,OAAO,IAExB,EAAS,EAAQ,EACjB,EAAiB,QAAQ,GAEpB,EAAE,SAAS,IAAO;;IAG7B;EAAC;EAAkB;EAAe;EAAW;EAAa,CAC3D,EAEK,IAAmB,EAAY,YAA2C;AAE9E,EADA,EAAa,GAAK,EAClB,EAAS,KAAK;AACd,MAAI;AAEF,UADA,MAAM,GAAwB,EACvB,EAAE,SAAS,IAAM;WACjB,GAAK;AAEZ,UADA,EAAS,aAAe,QAAQ,EAAI,UAAU,iBAAiB,EACxD,EAAE,SAAS,IAAO;YACjB;AACR,KAAa,GAAM;;IAEpB,EAAE,CAAC;AAEN,QAAO;EACL,aAAa,GAAsB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|