@solvapay/react 1.3.0 → 1.4.0-preview-ecd61384a4e849717e13d47ab2daa086cdcd91c5

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.
@@ -1,1527 +0,0 @@
1
- import {
2
- AmountPicker,
3
- MandateText,
4
- MissingProductRefError,
5
- MissingProviderError,
6
- PaymentForm,
7
- PlanSelector,
8
- Slot,
9
- SolvaPayContext,
10
- TopupForm,
11
- buildDefaultCheckoutPlanFilter,
12
- composeEventHandlers,
13
- formatContinueLabel,
14
- formatPrice,
15
- getMinorUnitsPerMajor,
16
- interpolate,
17
- isPaygPlan,
18
- planSortByPaygFirstThenAsc,
19
- shortCycle,
20
- useActivation,
21
- useAmountPicker,
22
- useAmountPickerCopy,
23
- useBalance,
24
- useCheckoutFlow,
25
- useCopy,
26
- useLocale,
27
- usePlan,
28
- usePlanSelection,
29
- usePlans,
30
- useProduct,
31
- usePurchase,
32
- useTopupAmountSelector
33
- } from "./chunk-DW5FJUUG.js";
34
-
35
- // src/TopupForm.tsx
36
- import { jsx, jsxs } from "react/jsx-runtime";
37
- var TopupForm2 = (props) => {
38
- const { className, buttonClassName, submitButtonText, ...rootProps } = props;
39
- const rootClass = ["solvapay-topup-form", className].filter(Boolean).join(" ");
40
- const buttonClass = ["solvapay-topup-form-submit", buttonClassName].filter(Boolean).join(" ");
41
- return /* @__PURE__ */ jsxs(TopupForm.Root, { ...rootProps, className: rootClass, children: [
42
- /* @__PURE__ */ jsx(TopupForm.PaymentElement, {}),
43
- /* @__PURE__ */ jsx(TopupForm.Error, { className: "solvapay-topup-form-error" }),
44
- /* @__PURE__ */ jsx(TopupForm.Loading, { className: "solvapay-topup-form-loading" }),
45
- /* @__PURE__ */ jsx(TopupForm.SubmitButton, { className: buttonClass, children: submitButtonText })
46
- ] });
47
- };
48
-
49
- // src/primitives/ProductBadge.tsx
50
- import { forwardRef, useContext, useEffect, useState } from "react";
51
- import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
52
- var ProductBadgeImpl = forwardRef(function ProductBadge({ asChild, children, ...rest }, forwardedRef) {
53
- const solva = useContext(SolvaPayContext);
54
- if (!solva) throw new MissingProviderError("ProductBadge");
55
- const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
56
- const copy = useCopy();
57
- const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
58
- useEffect(() => {
59
- if (!loading) setHasLoadedOnce(true);
60
- }, [loading]);
61
- const planToDisplay = activePurchase?.productName ?? null;
62
- const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
63
- if (!shouldShow) return null;
64
- const commonProps = {
65
- "data-solvapay-product-badge": "",
66
- "data-loading": loading ? "" : void 0,
67
- "data-has-purchase": activePurchase ? "" : void 0,
68
- "data-has-paid-purchase": hasPaidPurchase ? "" : void 0,
69
- role: "status",
70
- "aria-live": "polite",
71
- "aria-busy": loading,
72
- "aria-label": interpolate(copy.product.currentProductLabel, { name: planToDisplay }),
73
- ...rest
74
- };
75
- if (asChild) {
76
- return (
77
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
- /* @__PURE__ */ jsx2(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx2(Fragment, { children: planToDisplay }) })
79
- );
80
- }
81
- void purchases;
82
- return (
83
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
- /* @__PURE__ */ jsx2("div", { ref: forwardedRef, ...commonProps, children: children ?? planToDisplay })
85
- );
86
- });
87
- var ProductBadge2 = ProductBadgeImpl;
88
- var PlanBadge = ProductBadgeImpl;
89
-
90
- // src/primitives/PurchaseGate.tsx
91
- import {
92
- createContext,
93
- forwardRef as forwardRef2,
94
- useContext as useContext2,
95
- useMemo
96
- } from "react";
97
- import { jsx as jsx3 } from "react/jsx-runtime";
98
- var PurchaseGateContext = createContext(null);
99
- function useGateCtx(part) {
100
- const ctx = useContext2(PurchaseGateContext);
101
- if (!ctx) {
102
- throw new Error(`PurchaseGate.${part} must be rendered inside <PurchaseGate.Root>.`);
103
- }
104
- return ctx;
105
- }
106
- var Root = forwardRef2(function PurchaseGateRoot({ requirePlan, requireProduct, asChild, children, ...rest }, forwardedRef) {
107
- const solva = useContext2(SolvaPayContext);
108
- if (!solva) throw new MissingProviderError("PurchaseGate");
109
- const { purchases, loading, hasProduct, error } = usePurchase();
110
- const productToCheck = requireProduct || requirePlan;
111
- const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
112
- const state = loading ? "loading" : hasAccess ? "allowed" : "blocked";
113
- const ctx = useMemo(
114
- () => ({ state, loading, hasAccess, error }),
115
- [state, loading, hasAccess, error]
116
- );
117
- const Comp = asChild ? Slot : "div";
118
- return /* @__PURE__ */ jsx3(PurchaseGateContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx3(
119
- Comp,
120
- {
121
- ref: forwardedRef,
122
- "data-solvapay-purchase-gate": "",
123
- "data-state": state,
124
- ...rest,
125
- children
126
- }
127
- ) });
128
- });
129
- var Allowed = forwardRef2(function PurchaseGateAllowed({ asChild, children, ...rest }, forwardedRef) {
130
- const ctx = useGateCtx("Allowed");
131
- if (ctx.state !== "allowed") return null;
132
- const Comp = asChild ? Slot : "div";
133
- return /* @__PURE__ */ jsx3(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-allowed": "", ...rest, children });
134
- });
135
- var Blocked = forwardRef2(function PurchaseGateBlocked({ asChild, children, ...rest }, forwardedRef) {
136
- const ctx = useGateCtx("Blocked");
137
- if (ctx.state !== "blocked") return null;
138
- const Comp = asChild ? Slot : "div";
139
- return /* @__PURE__ */ jsx3(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-blocked": "", ...rest, children });
140
- });
141
- var Loading = forwardRef2(function PurchaseGateLoading({ asChild, children, ...rest }, forwardedRef) {
142
- const ctx = useGateCtx("Loading");
143
- if (ctx.state !== "loading") return null;
144
- const Comp = asChild ? Slot : "div";
145
- return /* @__PURE__ */ jsx3(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-loading": "", ...rest, children });
146
- });
147
- var ErrorSlot = forwardRef2(function PurchaseGateError({ asChild, children, ...rest }, forwardedRef) {
148
- const ctx = useGateCtx("Error");
149
- if (!ctx.error) return null;
150
- const Comp = asChild ? Slot : "div";
151
- return /* @__PURE__ */ jsx3(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-error": "", role: "alert", ...rest, children: children ?? ctx.error.message });
152
- });
153
- var PurchaseGateRoot2 = Root;
154
- var PurchaseGateAllowed2 = Allowed;
155
- var PurchaseGateBlocked2 = Blocked;
156
- var PurchaseGateLoading2 = Loading;
157
- var PurchaseGateError2 = ErrorSlot;
158
- var PurchaseGate = {
159
- Root,
160
- Allowed,
161
- Blocked,
162
- Loading,
163
- Error: ErrorSlot
164
- };
165
- function usePurchaseGate() {
166
- return useGateCtx("usePurchaseGate");
167
- }
168
-
169
- // src/primitives/ActivationFlow.tsx
170
- import {
171
- createContext as createContext2,
172
- forwardRef as forwardRef3,
173
- useCallback,
174
- useContext as useContext3,
175
- useEffect as useEffect2,
176
- useMemo as useMemo2,
177
- useRef,
178
- useState as useState2
179
- } from "react";
180
- import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
181
- var ActivationFlowContext = createContext2(null);
182
- function useFlowCtx(part) {
183
- const ctx = useContext3(ActivationFlowContext);
184
- if (!ctx) {
185
- throw new Error(`ActivationFlow.${part} must be rendered inside <ActivationFlow.Root>.`);
186
- }
187
- return ctx;
188
- }
189
- var Root2 = forwardRef3(function ActivationFlowRoot({
190
- productRef,
191
- planRef: planRefProp,
192
- onSuccess,
193
- onError,
194
- retryDelayMs = 1500,
195
- retryBackoffMs = 2e3,
196
- asChild,
197
- children,
198
- ...rest
199
- }, forwardedRef) {
200
- const solva = useContext3(SolvaPayContext);
201
- if (!solva) throw new MissingProviderError("ActivationFlow");
202
- if (!productRef) throw new MissingProductRefError("ActivationFlow");
203
- const planSelection = usePlanSelection();
204
- const resolvedPlanRef = planRefProp ?? planSelection?.selectedPlanRef ?? void 0;
205
- const { plan } = usePlan({ planRef: resolvedPlanRef, productRef });
206
- const { activate, state, error, result, reset } = useActivation();
207
- const currency = plan?.currency ?? "USD";
208
- const amountSelector = useTopupAmountSelector({ currency });
209
- const { adjustBalance, creditsPerMinorUnit, displayExchangeRate } = useBalance();
210
- const [step, setStep] = useState2("summary");
211
- const calledSuccessRef = useRef(false);
212
- const retryTimeoutRef = useRef(null);
213
- useEffect2(() => {
214
- if (state === "activated" && !calledSuccessRef.current) {
215
- calledSuccessRef.current = true;
216
- setStep("activated");
217
- if (result) {
218
- const activationResult = { kind: "activated", result };
219
- onSuccess?.(activationResult);
220
- }
221
- }
222
- }, [state, result, onSuccess]);
223
- useEffect2(() => {
224
- if (state === "topup_required" && (step === "summary" || step === "activating")) {
225
- setStep("selectAmount");
226
- }
227
- }, [state, step]);
228
- useEffect2(() => {
229
- if ((state === "error" || state === "payment_required") && error) {
230
- setStep("error");
231
- if (state === "error") onError?.(new Error(error));
232
- }
233
- }, [state, error, onError]);
234
- const handleActivate = useCallback(async () => {
235
- if (!resolvedPlanRef) return;
236
- setStep("activating");
237
- await activate({ productRef, planRef: resolvedPlanRef });
238
- }, [activate, productRef, resolvedPlanRef]);
239
- const goToTopupPayment = useCallback(() => {
240
- if (amountSelector.validate()) setStep("topupPayment");
241
- }, [amountSelector]);
242
- const backToSelectAmount = useCallback(() => {
243
- setStep("selectAmount");
244
- }, []);
245
- const retryActivation = useCallback(async () => {
246
- if (!resolvedPlanRef) return;
247
- setStep("retrying");
248
- const amountMinor2 = Math.round(
249
- (amountSelector.resolvedAmount || 0) * getMinorUnitsPerMajor(currency)
250
- );
251
- const rate = displayExchangeRate ?? 1;
252
- if (creditsPerMinorUnit) {
253
- adjustBalance(Math.floor(amountMinor2 / rate * creditsPerMinorUnit));
254
- }
255
- await new Promise((r) => setTimeout(r, retryDelayMs));
256
- await activate({ productRef, planRef: resolvedPlanRef });
257
- }, [
258
- resolvedPlanRef,
259
- amountSelector.resolvedAmount,
260
- currency,
261
- displayExchangeRate,
262
- creditsPerMinorUnit,
263
- adjustBalance,
264
- retryDelayMs,
265
- activate,
266
- productRef
267
- ]);
268
- const handleTopupSuccess = useCallback(async () => {
269
- await retryActivation();
270
- }, [retryActivation]);
271
- useEffect2(() => {
272
- if (step !== "retrying") return;
273
- if (state !== "topup_required") return;
274
- if (!resolvedPlanRef) return;
275
- retryTimeoutRef.current = setTimeout(async () => {
276
- await activate({ productRef, planRef: resolvedPlanRef });
277
- }, retryBackoffMs);
278
- return () => {
279
- if (retryTimeoutRef.current) clearTimeout(retryTimeoutRef.current);
280
- };
281
- }, [step, state, resolvedPlanRef, retryBackoffMs, activate, productRef]);
282
- const resetFlow = useCallback(() => {
283
- reset();
284
- calledSuccessRef.current = false;
285
- setStep("summary");
286
- }, [reset]);
287
- const amountMinor = Math.round(
288
- (amountSelector.resolvedAmount || 0) * getMinorUnitsPerMajor(currency)
289
- );
290
- const ctx = useMemo2(
291
- () => ({
292
- step,
293
- plan,
294
- productRef,
295
- planRef: resolvedPlanRef ?? "",
296
- amountSelector,
297
- amountMinor,
298
- // Deprecated alias — same value as amountMinor so legacy consumers
299
- // keep working after the zero-decimal fix (previously this was
300
- // `resolvedAmount * 100`, which over-billed JPY/KRW by 100x).
301
- amountCents: amountMinor,
302
- currency,
303
- activate: handleActivate,
304
- reset: resetFlow,
305
- goToTopupPayment,
306
- backToSelectAmount,
307
- onTopupSuccess: handleTopupSuccess,
308
- error,
309
- onError
310
- }),
311
- [
312
- step,
313
- plan,
314
- productRef,
315
- resolvedPlanRef,
316
- amountSelector,
317
- amountMinor,
318
- currency,
319
- handleActivate,
320
- resetFlow,
321
- goToTopupPayment,
322
- backToSelectAmount,
323
- handleTopupSuccess,
324
- error,
325
- onError
326
- ]
327
- );
328
- const Comp = asChild ? Slot : "div";
329
- return /* @__PURE__ */ jsx4(ActivationFlowContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx4(
330
- Comp,
331
- {
332
- ref: forwardedRef,
333
- "data-solvapay-activation-flow": "",
334
- "data-state": step,
335
- ...rest,
336
- children
337
- }
338
- ) });
339
- });
340
- function matchStep(step, allowed) {
341
- return allowed.includes(step);
342
- }
343
- var Summary = forwardRef3(function ActivationFlowSummary({ asChild, children, ...rest }, forwardedRef) {
344
- const ctx = useFlowCtx("Summary");
345
- if (!matchStep(ctx.step, ["summary", "activating"])) return null;
346
- const Comp = asChild ? Slot : "div";
347
- return /* @__PURE__ */ jsx4(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-summary": "", ...rest, children });
348
- });
349
- var ActivateButton = forwardRef3(
350
- function ActivationFlowActivateButton({ asChild, onClick, children, ...rest }, forwardedRef) {
351
- const ctx = useFlowCtx("ActivateButton");
352
- const copy = useCopy();
353
- if (!matchStep(ctx.step, ["summary", "activating"])) return null;
354
- const isActivating = ctx.step === "activating";
355
- const disabled = isActivating;
356
- const commonProps = {
357
- "data-solvapay-activation-flow-activate": "",
358
- "data-state": isActivating ? "activating" : "idle",
359
- "aria-busy": isActivating,
360
- disabled,
361
- onClick: composeEventHandlers(onClick, () => {
362
- void ctx.activate();
363
- }),
364
- ...rest
365
- };
366
- const label = isActivating ? copy.activationFlow.activatingLabel : copy.activationFlow.activateButton;
367
- if (asChild) {
368
- return (
369
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
370
- /* @__PURE__ */ jsx4(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx4(Fragment2, { children: label }) })
371
- );
372
- }
373
- return /* @__PURE__ */ jsx4("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? label });
374
- }
375
- );
376
- var AmountPickerMount = ({ children }) => {
377
- const ctx = useFlowCtx("AmountPicker");
378
- if (ctx.step !== "selectAmount") return null;
379
- return /* @__PURE__ */ jsx4("div", { "data-solvapay-activation-flow-amount-picker": "", children: /* @__PURE__ */ jsx4(AmountPicker.Root, { currency: ctx.currency, selector: ctx.amountSelector, children }) });
380
- };
381
- var ContinueButton = forwardRef3(
382
- function ActivationFlowContinueButton({ asChild, onClick, children, ...rest }, forwardedRef) {
383
- const ctx = useFlowCtx("ContinueButton");
384
- const copy = useCopy();
385
- if (ctx.step !== "selectAmount") return null;
386
- const disabled = !ctx.amountSelector.resolvedAmount;
387
- const commonProps = {
388
- "data-solvapay-activation-flow-continue": "",
389
- "data-state": disabled ? "disabled" : "idle",
390
- disabled,
391
- "aria-disabled": disabled || void 0,
392
- onClick: composeEventHandlers(onClick, () => {
393
- ctx.goToTopupPayment();
394
- }),
395
- ...rest
396
- };
397
- if (asChild) {
398
- return (
399
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
400
- /* @__PURE__ */ jsx4(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ jsx4(Fragment2, { children: copy.activationFlow.continueToPayment }) })
401
- );
402
- }
403
- return /* @__PURE__ */ jsx4("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? copy.activationFlow.continueToPayment });
404
- }
405
- );
406
- var Retrying = forwardRef3(function ActivationFlowRetrying({ asChild, children, ...rest }, forwardedRef) {
407
- const ctx = useFlowCtx("Retrying");
408
- if (ctx.step !== "retrying") return null;
409
- const Comp = asChild ? Slot : "div";
410
- return /* @__PURE__ */ jsx4(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-retrying": "", ...rest, children });
411
- });
412
- var Activated = forwardRef3(function ActivationFlowActivated({ asChild, children, ...rest }, forwardedRef) {
413
- const ctx = useFlowCtx("Activated");
414
- if (ctx.step !== "activated") return null;
415
- const Comp = asChild ? Slot : "div";
416
- return /* @__PURE__ */ jsx4(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-activated": "", ...rest, children });
417
- });
418
- var Loading2 = forwardRef3(function ActivationFlowLoading({ asChild, children, ...rest }, forwardedRef) {
419
- const ctx = useFlowCtx("Loading");
420
- if (ctx.plan) return null;
421
- const Comp = asChild ? Slot : "div";
422
- return /* @__PURE__ */ jsx4(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-loading": "", ...rest, children });
423
- });
424
- var ErrorSlot2 = forwardRef3(function ActivationFlowError({ asChild, children, ...rest }, forwardedRef) {
425
- const ctx = useFlowCtx("Error");
426
- if (ctx.step !== "error") return null;
427
- const Comp = asChild ? Slot : "div";
428
- return /* @__PURE__ */ jsx4(
429
- Comp,
430
- {
431
- ref: forwardedRef,
432
- role: "alert",
433
- "data-solvapay-activation-flow-error": "",
434
- ...rest,
435
- children: children ?? ctx.error
436
- }
437
- );
438
- });
439
- var ActivationFlowRoot2 = Root2;
440
- var ActivationFlowSummary2 = Summary;
441
- var ActivationFlowActivateButton2 = ActivateButton;
442
- var ActivationFlowAmountPicker = AmountPickerMount;
443
- var ActivationFlowContinueButton2 = ContinueButton;
444
- var ActivationFlowRetrying2 = Retrying;
445
- var ActivationFlowActivated2 = Activated;
446
- var ActivationFlowLoading2 = Loading2;
447
- var ActivationFlowError2 = ErrorSlot2;
448
- var ActivationFlow = {
449
- Root: Root2,
450
- Summary,
451
- ActivateButton,
452
- AmountPicker: AmountPickerMount,
453
- ContinueButton,
454
- Retrying,
455
- Activated,
456
- Loading: Loading2,
457
- Error: ErrorSlot2
458
- };
459
- function useActivationFlow() {
460
- return useFlowCtx("useActivationFlow");
461
- }
462
-
463
- // src/primitives/CreditGate.tsx
464
- import {
465
- createContext as createContext3,
466
- forwardRef as forwardRef4,
467
- useContext as useContext4,
468
- useMemo as useMemo3
469
- } from "react";
470
- import { jsx as jsx5 } from "react/jsx-runtime";
471
- var CreditGateContext = createContext3(null);
472
- function useGateCtx2(part) {
473
- const ctx = useContext4(CreditGateContext);
474
- if (!ctx) {
475
- throw new Error(`CreditGate.${part} must be rendered inside <CreditGate.Root>.`);
476
- }
477
- return ctx;
478
- }
479
- var Root3 = forwardRef4(function CreditGateRoot({
480
- minCredits = 1,
481
- productRef,
482
- topupAmount = 1e3,
483
- topupCurrency = "usd",
484
- asChild,
485
- children,
486
- ...rest
487
- }, forwardedRef) {
488
- const solva = useContext4(SolvaPayContext);
489
- if (!solva) throw new MissingProviderError("CreditGate");
490
- const { credits, loading } = useBalance();
491
- const { product } = useProduct(productRef);
492
- const hasCredits = credits != null && credits >= minCredits;
493
- const state = loading && credits == null ? "loading" : hasCredits ? "allowed" : "blocked";
494
- const ctx = useMemo3(
495
- () => ({
496
- state,
497
- loading,
498
- hasCredits,
499
- balance: credits,
500
- productName: product?.name ?? null,
501
- topupAmount,
502
- topupCurrency
503
- }),
504
- [state, loading, hasCredits, credits, product?.name, topupAmount, topupCurrency]
505
- );
506
- const Comp = asChild ? Slot : "div";
507
- return /* @__PURE__ */ jsx5(CreditGateContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx5(
508
- Comp,
509
- {
510
- ref: forwardedRef,
511
- "data-solvapay-credit-gate": "",
512
- "data-state": state,
513
- ...rest,
514
- children
515
- }
516
- ) });
517
- });
518
- var Heading = forwardRef4(function CreditGateHeading({ asChild, children, ...rest }, forwardedRef) {
519
- const ctx = useGateCtx2("Heading");
520
- const copy = useCopy();
521
- if (ctx.state !== "blocked") return null;
522
- const Comp = asChild ? Slot : "h3";
523
- return /* @__PURE__ */ jsx5(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-heading": "", ...rest, children: children ?? copy.creditGate.lowBalanceHeading });
524
- });
525
- var Subheading = forwardRef4(function CreditGateSubheading({ asChild, children, ...rest }, forwardedRef) {
526
- const ctx = useGateCtx2("Subheading");
527
- const copy = useCopy();
528
- if (ctx.state !== "blocked") return null;
529
- const text = interpolate(copy.creditGate.lowBalanceSubheading, {
530
- product: ctx.productName ?? "this product"
531
- });
532
- const Comp = asChild ? Slot : "p";
533
- return /* @__PURE__ */ jsx5(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-subheading": "", ...rest, children: children ?? text });
534
- });
535
- var Topup = ({ amount, currency }) => {
536
- const ctx = useGateCtx2("Topup");
537
- if (ctx.state !== "blocked") return null;
538
- return /* @__PURE__ */ jsx5(
539
- TopupForm2,
540
- {
541
- amount: amount ?? ctx.topupAmount,
542
- currency: currency ?? ctx.topupCurrency
543
- }
544
- );
545
- };
546
- var Loading3 = forwardRef4(function CreditGateLoading({ asChild, children, ...rest }, forwardedRef) {
547
- const ctx = useGateCtx2("Loading");
548
- if (ctx.state !== "loading") return null;
549
- const Comp = asChild ? Slot : "div";
550
- return (
551
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
552
- /* @__PURE__ */ jsx5(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-loading": "", ...rest, children })
553
- );
554
- });
555
- var ErrorSlot3 = forwardRef4(function CreditGateError({ asChild, children, ...rest }, forwardedRef) {
556
- if (!children) return null;
557
- const Comp = asChild ? Slot : "div";
558
- return (
559
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
560
- /* @__PURE__ */ jsx5(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-error": "", role: "alert", ...rest, children })
561
- );
562
- });
563
- var CreditGateRoot2 = Root3;
564
- var CreditGateHeading2 = Heading;
565
- var CreditGateSubheading2 = Subheading;
566
- var CreditGateTopup = Topup;
567
- var CreditGateLoading2 = Loading3;
568
- var CreditGateError2 = ErrorSlot3;
569
- var CreditGate = {
570
- Root: Root3,
571
- Heading,
572
- Subheading,
573
- Topup,
574
- Loading: Loading3,
575
- Error: ErrorSlot3
576
- };
577
- function useCreditGate() {
578
- return useGateCtx2("useCreditGate");
579
- }
580
-
581
- // src/hooks/usePaywallResolver.ts
582
- import { useCallback as useCallback2, useMemo as useMemo4 } from "react";
583
- function balanceCoversNextUnit(balance, credits) {
584
- if (!balance) return false;
585
- if (typeof balance.remainingUnits === "number" && balance.remainingUnits > 0) {
586
- return true;
587
- }
588
- if (credits != null && balance.creditsPerUnit && credits >= balance.creditsPerUnit) {
589
- return true;
590
- }
591
- return false;
592
- }
593
- function usePaywallResolver(content) {
594
- const { activePurchase, hasPaidPurchase, refetch: refetchPurchase } = usePurchase();
595
- const { credits, refetch: refetchBalance } = useBalance();
596
- const resolved = useMemo4(() => {
597
- if (!content) return false;
598
- const productMatches = !content.product || activePurchase?.productRef === content.product || activePurchase?.productName === content.product;
599
- if (content.kind === "payment_required") {
600
- if (hasPaidPurchase && productMatches) return true;
601
- return balanceCoversNextUnit(content.balance, credits);
602
- }
603
- if (balanceCoversNextUnit(content.balance, credits)) return true;
604
- return Boolean(productMatches && activePurchase?.status === "active");
605
- }, [content, hasPaidPurchase, activePurchase, credits]);
606
- const refetch = useCallback2(async () => {
607
- await Promise.all([
608
- refetchPurchase().catch(() => void 0),
609
- refetchBalance().catch(() => void 0)
610
- ]);
611
- }, [refetchPurchase, refetchBalance]);
612
- return { resolved, refetch };
613
- }
614
-
615
- // src/primitives/PaywallNotice.tsx
616
- import {
617
- createContext as createContext5,
618
- forwardRef as forwardRef6,
619
- useCallback as useCallback3,
620
- useContext as useContext6,
621
- useEffect as useEffect3,
622
- useMemo as useMemo6,
623
- useRef as useRef2
624
- } from "react";
625
-
626
- // src/primitives/checkout/index.tsx
627
- import { createContext as createContext4, forwardRef as forwardRef5, useContext as useContext5, useMemo as useMemo5 } from "react";
628
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
629
- var CheckoutContext = createContext4(null);
630
- function useCheckoutContext(part) {
631
- const ctx = useContext5(CheckoutContext);
632
- if (!ctx) {
633
- throw new Error(`CheckoutSteps.${part} must be rendered inside <CheckoutSteps.Root>.`);
634
- }
635
- return ctx;
636
- }
637
- function useCheckoutSteps() {
638
- return useCheckoutContext("useCheckoutSteps");
639
- }
640
- var Root4 = forwardRef5(function CheckoutStepsRoot(props, forwardedRef) {
641
- const {
642
- productRef,
643
- returnUrl: _returnUrl,
644
- flow: externalFlow,
645
- initialStep,
646
- initialPlanRef,
647
- initialAmountMinor,
648
- autoSelectFirstPaid = false,
649
- topupCurrency,
650
- filter,
651
- sortBy = planSortByPaygFirstThenAsc,
652
- popularPlanRef,
653
- currentPlanRef,
654
- onPlanSelect,
655
- onAmountSelect,
656
- onPurchaseSuccess,
657
- onError,
658
- className,
659
- children
660
- } = props;
661
- if (externalFlow) {
662
- return /* @__PURE__ */ jsx6("div", { ref: forwardedRef, "data-solvapay-checkout": "", className, children: /* @__PURE__ */ jsx6(CheckoutContext.Provider, { value: externalFlow, children }) });
663
- }
664
- return /* @__PURE__ */ jsx6(
665
- RootWithPlanSelector,
666
- {
667
- productRef,
668
- filter,
669
- sortBy,
670
- popularPlanRef,
671
- currentPlanRef,
672
- autoSelectFirstPaid,
673
- initialPlanRef,
674
- forwardedRef,
675
- className,
676
- initialStep,
677
- initialAmountMinor,
678
- topupCurrency,
679
- onPlanSelect,
680
- onAmountSelect,
681
- onPurchaseSuccess,
682
- onError,
683
- children
684
- }
685
- );
686
- });
687
- function RootWithPlanSelector({
688
- productRef,
689
- filter,
690
- sortBy,
691
- popularPlanRef,
692
- currentPlanRef,
693
- autoSelectFirstPaid,
694
- initialPlanRef,
695
- forwardedRef,
696
- className,
697
- initialStep,
698
- initialAmountMinor,
699
- topupCurrency,
700
- onPlanSelect,
701
- onAmountSelect,
702
- onPurchaseSuccess,
703
- onError,
704
- children
705
- }) {
706
- const { plans } = usePlans({ productRef });
707
- const resolvedFilter = useMemo5(
708
- () => filter ?? buildDefaultCheckoutPlanFilter(plans),
709
- [filter, plans]
710
- );
711
- return /* @__PURE__ */ jsx6(
712
- PlanSelector.Root,
713
- {
714
- productRef,
715
- filter: resolvedFilter,
716
- sortBy,
717
- popularPlanRef,
718
- currentPlanRef,
719
- autoSelectFirstPaid,
720
- initialPlanRef,
721
- children: /* @__PURE__ */ jsx6(
722
- FlowProvider,
723
- {
724
- productRef,
725
- forwardedRef,
726
- className,
727
- initialStep,
728
- initialAmountMinor,
729
- topupCurrency,
730
- onPlanSelect,
731
- onAmountSelect,
732
- onPurchaseSuccess,
733
- onError,
734
- children
735
- }
736
- )
737
- }
738
- );
739
- }
740
- function FlowProvider({
741
- productRef,
742
- initialStep,
743
- initialAmountMinor,
744
- topupCurrency,
745
- onPlanSelect,
746
- onAmountSelect,
747
- onPurchaseSuccess,
748
- onError,
749
- className,
750
- forwardedRef,
751
- children
752
- }) {
753
- const flow = useCheckoutFlow({
754
- productRef,
755
- initialStep,
756
- initialAmountMinor,
757
- topupCurrency,
758
- onPlanSelect,
759
- onAmountSelect,
760
- onPurchaseSuccess,
761
- onError
762
- });
763
- return /* @__PURE__ */ jsx6("div", { ref: forwardedRef, "data-solvapay-checkout": "", "data-step": flow.step, className, children: /* @__PURE__ */ jsx6(CheckoutContext.Provider, { value: flow, children }) });
764
- }
765
- function IfStep({ step, children }) {
766
- const flow = useCheckoutContext("IfStep");
767
- const matches = Array.isArray(step) ? step.includes(flow.step) : flow.step === step;
768
- if (!matches) return null;
769
- return /* @__PURE__ */ jsx6(Fragment3, { children });
770
- }
771
- var StepHeading = forwardRef5(function CheckoutStepsStepHeading({ asChild, children, className, ...rest }, forwardedRef) {
772
- const flow = useCheckoutContext("StepHeading");
773
- const copy = useCopy();
774
- const paywallCtx = usePaywallNoticeOptional();
775
- if (flow.step === "success") return null;
776
- const defaultText = resolveStepHeading(flow.step, copy, paywallCtx?.content ?? null);
777
- const Comp = asChild ? Slot : "h3";
778
- return /* @__PURE__ */ jsx6(
779
- Comp,
780
- {
781
- ref: forwardedRef,
782
- "data-solvapay-checkout-step-heading": "",
783
- "data-step": flow.step,
784
- className: className ?? "solvapay-checkout-step-heading",
785
- ...rest,
786
- children: children ?? defaultText
787
- }
788
- );
789
- });
790
- var StepMessage = forwardRef5(function CheckoutStepsStepMessage({ asChild, children, className, ...rest }, forwardedRef) {
791
- const flow = useCheckoutContext("StepMessage");
792
- const copy = useCopy();
793
- const paywallCtx = usePaywallNoticeOptional();
794
- if (flow.step === "success") return null;
795
- const defaultText = resolveStepMessage(flow, copy, paywallCtx?.content ?? null);
796
- if (!defaultText && children == null) return null;
797
- const Comp = asChild ? Slot : "p";
798
- return /* @__PURE__ */ jsx6(
799
- Comp,
800
- {
801
- ref: forwardedRef,
802
- "data-solvapay-checkout-step-message": "",
803
- "data-step": flow.step,
804
- className: className ?? "solvapay-checkout-step-message",
805
- ...rest,
806
- children: children ?? defaultText
807
- }
808
- );
809
- });
810
- function resolveStepHeading(step, copy, content) {
811
- if (step === "amount") return copy.checkout.stepHeading.amount;
812
- if (step === "payment") return copy.checkout.stepHeading.payment;
813
- if (step === "plan") {
814
- if (content) {
815
- if (content.kind === "payment_required") return copy.paywall.paymentRequiredHeading;
816
- if (content.kind === "activation_required") {
817
- return isTopupGate(content) ? copy.paywall.topupRequiredHeading : copy.paywall.activationRequiredHeading;
818
- }
819
- }
820
- return copy.checkout.stepHeading.plan;
821
- }
822
- return "";
823
- }
824
- function resolveStepMessage(flow, copy, content) {
825
- if (flow.step === "plan") {
826
- if (content) return resolvePaywallMessage(content, copy.paywall);
827
- return copy.checkout.stepMessage.plan;
828
- }
829
- if (flow.step === "amount") return copy.checkout.stepMessage.amount;
830
- if (flow.step === "payment") {
831
- if (flow.branch === "payg") return copy.checkout.stepMessage.paymentPayg;
832
- const plan = flow.selectedPlan;
833
- if (flow.branch === "recurring" && plan) {
834
- const planName = plan.name ?? "your";
835
- if (plan.billingCycle) {
836
- return interpolate(copy.checkout.stepMessage.paymentRecurring, { planName });
837
- }
838
- return copy.checkout.stepMessage.paymentOneTime;
839
- }
840
- return copy.checkout.stepMessage.paymentOneTime;
841
- }
842
- return "";
843
- }
844
- function PlanGrid({ className, children }) {
845
- return /* @__PURE__ */ jsx6(PlanSelector.Grid, { className: className ?? "solvapay-checkout-plan-grid", children: children ?? /* @__PURE__ */ jsxs2(PlanSelector.Card, { className: "solvapay-checkout-plan-card", children: [
846
- /* @__PURE__ */ jsx6(PlanSelector.CardBadge, { className: "solvapay-checkout-plan-card-badge" }),
847
- /* @__PURE__ */ jsx6(PlanSelector.CardName, { className: "solvapay-checkout-plan-card-name" }),
848
- /* @__PURE__ */ jsx6(PlanSelector.CardPrice, { className: "solvapay-checkout-plan-card-price" }),
849
- /* @__PURE__ */ jsx6(PlanSelector.CardInterval, { className: "solvapay-checkout-plan-card-interval" })
850
- ] }) });
851
- }
852
- var PlanContinueButton = forwardRef5(
853
- function PlanContinueButton2({ className, children, onClick, disabled, ...rest }, ref) {
854
- const flow = useCheckoutContext("PlanContinueButton");
855
- const locale = useLocale();
856
- const selectedPlanShape = flow.selectedPlan;
857
- const isDisabled = disabled || !flow.selectedPlanRef || flow.status === "activating";
858
- const label = children ?? formatContinueLabel(selectedPlanShape, locale);
859
- return /* @__PURE__ */ jsx6(
860
- "button",
861
- {
862
- ref,
863
- type: "button",
864
- className: className ?? "solvapay-checkout-continue-button",
865
- disabled: isDisabled,
866
- "aria-disabled": isDisabled || void 0,
867
- "data-solvapay-checkout-continue": "",
868
- onClick: (e) => {
869
- onClick?.(e);
870
- if (e.defaultPrevented) return;
871
- void flow.advance();
872
- },
873
- ...rest,
874
- children: label
875
- }
876
- );
877
- }
878
- );
879
- function AmountPicker2({ className, children }) {
880
- const flow = useCheckoutContext("AmountPicker");
881
- if (!flow.topupCurrencyReady || flow.topupCurrency == null) {
882
- return /* @__PURE__ */ jsx6(
883
- "div",
884
- {
885
- className: className ?? "solvapay-amount-picker",
886
- "data-state": "loading",
887
- "aria-busy": "true",
888
- children: /* @__PURE__ */ jsx6("div", { className: "solvapay-amount-picker-pills", children: [0, 1, 2, 3].map((i) => /* @__PURE__ */ jsx6(
889
- "span",
890
- {
891
- className: "solvapay-amount-picker-pill",
892
- "data-state": "disabled",
893
- "aria-hidden": "true"
894
- },
895
- i
896
- )) })
897
- }
898
- );
899
- }
900
- return /* @__PURE__ */ jsx6(
901
- AmountPicker.Root,
902
- {
903
- currency: flow.topupCurrency,
904
- emit: "minor",
905
- className: className ?? "solvapay-amount-picker",
906
- onChange: (value) => {
907
- if (typeof value === "number") {
908
- flow.selectAmount(value);
909
- }
910
- },
911
- children: children ?? /* @__PURE__ */ jsx6(DefaultAmountTree, {})
912
- }
913
- );
914
- }
915
- function DefaultAmountTree() {
916
- const ctx = useAmountPicker();
917
- const { selectAmountLabel, customAmountLabel, creditEstimate } = useAmountPickerCopy();
918
- return /* @__PURE__ */ jsxs2(Fragment3, { children: [
919
- /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
920
- /* @__PURE__ */ jsx6("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx6(
921
- AmountPicker.Option,
922
- {
923
- amount,
924
- className: "solvapay-amount-picker-pill"
925
- },
926
- amount
927
- )) }),
928
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
929
- /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
930
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-amount-picker-custom-row", children: [
931
- /* @__PURE__ */ jsx6("span", { className: "solvapay-amount-picker-currency-symbol", children: ctx.currencySymbol }),
932
- /* @__PURE__ */ jsx6(
933
- AmountPicker.Custom,
934
- {
935
- className: "solvapay-amount-picker-custom-input",
936
- placeholder: "0.00"
937
- }
938
- )
939
- ] })
940
- ] }),
941
- /* @__PURE__ */ jsx6(
942
- "p",
943
- {
944
- className: "solvapay-amount-picker-credit-estimate",
945
- "aria-hidden": ctx.estimatedCredits == null || void 0,
946
- children: ctx.estimatedCredits != null ? creditEstimate(ctx.estimatedCredits) : "\xA0"
947
- }
948
- )
949
- ] });
950
- }
951
- var AmountContinueButton = forwardRef5(
952
- function AmountContinueButton2({ className, children, onClick, disabled, ...rest }, ref) {
953
- const flow = useCheckoutContext("AmountContinueButton");
954
- const locale = useLocale();
955
- const currency = flow.topupCurrency;
956
- const amountMinor = flow.selectedAmountMinor;
957
- const ready = flow.topupCurrencyReady && currency != null;
958
- const isDisabled = disabled || !ready || amountMinor == null;
959
- const label = children ?? (ready && currency != null && amountMinor != null ? `Continue \u2014 ${formatPrice(amountMinor, currency, { locale })}` : "Continue");
960
- return /* @__PURE__ */ jsx6(
961
- "button",
962
- {
963
- ref,
964
- type: "button",
965
- className: className ?? "solvapay-checkout-continue-button",
966
- disabled: isDisabled,
967
- "aria-disabled": isDisabled || void 0,
968
- "data-solvapay-checkout-continue": "",
969
- onClick: (e) => {
970
- onClick?.(e);
971
- if (e.defaultPrevented) return;
972
- void flow.advance();
973
- },
974
- ...rest,
975
- children: label
976
- }
977
- );
978
- }
979
- );
980
- function Payment({ className }) {
981
- const flow = useCheckoutContext("Payment");
982
- if (flow.branch === "payg") {
983
- return /* @__PURE__ */ jsx6(PaygPayment, { className });
984
- }
985
- if (flow.branch === "recurring") {
986
- return /* @__PURE__ */ jsx6(RecurringPayment, { className });
987
- }
988
- return null;
989
- }
990
- function PaygPayment({ className }) {
991
- const flow = useCheckoutContext("Payment");
992
- const locale = useLocale();
993
- const { creditsPerMinorUnit, displayExchangeRate } = useBalance();
994
- const returnUrl = useReturnUrl();
995
- const selectedPlanShape = flow.selectedPlan;
996
- const amountMinor = flow.selectedAmountMinor;
997
- const currency = flow.topupCurrency;
998
- if (!selectedPlanShape || amountMinor == null || currency == null) return null;
999
- const creditsAdded = creditsPerMinorUnit != null && creditsPerMinorUnit > 0 ? Math.floor(amountMinor / (displayExchangeRate ?? 1) * creditsPerMinorUnit) : null;
1000
- return /* @__PURE__ */ jsxs2("div", { className: className ?? "solvapay-checkout-payment", "data-branch": "payg", children: [
1001
- /* @__PURE__ */ jsx6("div", { className: "solvapay-checkout-order-summary", "data-variant": "payg", children: /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-order-summary-row", children: [
1002
- /* @__PURE__ */ jsx6("span", { children: creditsAdded != null ? `${creditsAdded.toLocaleString(locale)} credits` : formatPrice(amountMinor, currency, { locale }) }),
1003
- creditsAdded != null ? /* @__PURE__ */ jsx6("span", { children: formatPrice(amountMinor, currency, { locale }) }) : null
1004
- ] }) }),
1005
- /* @__PURE__ */ jsxs2(
1006
- TopupForm.Root,
1007
- {
1008
- amount: amountMinor,
1009
- currency,
1010
- returnUrl,
1011
- className: "solvapay-checkout-topup-form",
1012
- onSuccess: (intent, extras) => flow.notifyPaymentSuccess(intent, extras),
1013
- children: [
1014
- /* @__PURE__ */ jsx6(TopupForm.Loading, {}),
1015
- /* @__PURE__ */ jsx6(TopupForm.PaymentElement, {}),
1016
- /* @__PURE__ */ jsx6(TopupForm.Error, { className: "solvapay-checkout-error" }),
1017
- /* @__PURE__ */ jsx6(MandateText, { mode: "topup", amountMinor, currency }),
1018
- /* @__PURE__ */ jsxs2(TopupForm.SubmitButton, { className: "solvapay-checkout-pay-button", children: [
1019
- "Pay ",
1020
- formatPrice(amountMinor, currency, { locale })
1021
- ] })
1022
- ]
1023
- }
1024
- )
1025
- ] });
1026
- }
1027
- function RecurringPayment({ className }) {
1028
- const flow = useCheckoutContext("Payment");
1029
- const locale = useLocale();
1030
- const returnUrl = useReturnUrl();
1031
- const productRef = useProductRef();
1032
- const selectedPlanShape = flow.selectedPlan;
1033
- if (!selectedPlanShape || !flow.selectedPlanRef) return null;
1034
- const currency = (selectedPlanShape.currency ?? "USD").toUpperCase();
1035
- const amountMinor = selectedPlanShape.price ?? 0;
1036
- const cycle = selectedPlanShape.billingCycle;
1037
- const planName = selectedPlanShape.name ?? "Plan";
1038
- const isRecurring = !!cycle;
1039
- const formattedAmount = formatPrice(amountMinor, currency, { locale });
1040
- const priceLine = isRecurring ? `${formattedAmount}/${shortCycle(cycle)}` : formattedAmount;
1041
- return /* @__PURE__ */ jsxs2("div", { className: className ?? "solvapay-checkout-payment", "data-branch": "recurring", children: [
1042
- /* @__PURE__ */ jsx6("div", { className: "solvapay-checkout-order-summary", "data-variant": "recurring", children: /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-order-summary-row", children: [
1043
- /* @__PURE__ */ jsx6("span", { children: planName }),
1044
- /* @__PURE__ */ jsx6("span", { children: priceLine })
1045
- ] }) }),
1046
- /* @__PURE__ */ jsxs2(
1047
- PaymentForm.Root,
1048
- {
1049
- planRef: flow.selectedPlanRef,
1050
- productRef,
1051
- returnUrl,
1052
- requireTermsAcceptance: false,
1053
- onSuccess: (intent) => flow.notifyPaymentSuccess(intent),
1054
- children: [
1055
- /* @__PURE__ */ jsx6(PaymentForm.Loading, {}),
1056
- /* @__PURE__ */ jsx6(PaymentForm.PaymentElement, {}),
1057
- /* @__PURE__ */ jsx6(PaymentForm.Error, { className: "solvapay-checkout-error" }),
1058
- /* @__PURE__ */ jsx6(PaymentForm.MandateText, {}),
1059
- /* @__PURE__ */ jsx6(PaymentForm.SubmitButton, { className: "solvapay-checkout-pay-button", children: isRecurring ? `Subscribe \u2014 ${priceLine}` : `Pay ${formattedAmount}` })
1060
- ]
1061
- }
1062
- )
1063
- ] });
1064
- }
1065
- var CheckoutEnvContext = createContext4(null);
1066
- function useReturnUrl() {
1067
- return useContext5(CheckoutEnvContext)?.returnUrl ?? "";
1068
- }
1069
- function useProductRef() {
1070
- return useContext5(CheckoutEnvContext)?.productRef ?? "";
1071
- }
1072
- var BackLink = forwardRef5(function CheckoutStepsBackLink({ label, glyph = "\u2190", className, onClick, ...rest }, ref) {
1073
- const flow = useCheckoutContext("BackLink");
1074
- if (!flow.canGoBack) return null;
1075
- const resolvedLabel = label ?? (flow.step === "payment" && flow.branch === "payg" ? "Change amount" : flow.step === "payment" && flow.branch === "recurring" ? "Change plan" : "Back");
1076
- return /* @__PURE__ */ jsxs2(
1077
- "button",
1078
- {
1079
- ref,
1080
- type: "button",
1081
- className: ["solvapay-checkout-back-link", className].filter(Boolean).join(" "),
1082
- "data-solvapay-checkout-back": "",
1083
- onClick: (e) => {
1084
- onClick?.(e);
1085
- if (e.defaultPrevented) return;
1086
- flow.back();
1087
- },
1088
- ...rest,
1089
- children: [
1090
- /* @__PURE__ */ jsx6("span", { className: "solvapay-checkout-back-link-glyph", "aria-hidden": "true", children: glyph }),
1091
- /* @__PURE__ */ jsx6("span", { className: "solvapay-checkout-back-link-label", children: resolvedLabel })
1092
- ]
1093
- }
1094
- );
1095
- });
1096
- function Success({ className, children }) {
1097
- const flow = useCheckoutContext("Success");
1098
- const locale = useLocale();
1099
- if (flow.step !== "success" || !flow.successMeta) return null;
1100
- if (children) {
1101
- return /* @__PURE__ */ jsx6("div", { className: className ?? "solvapay-checkout-success", children });
1102
- }
1103
- const meta = flow.successMeta;
1104
- if (meta.branch === "payg") {
1105
- return /* @__PURE__ */ jsxs2("div", { className: className ?? "solvapay-checkout-success", "data-branch": "payg", children: [
1106
- /* @__PURE__ */ jsx6("div", { className: "solvapay-checkout-success-check", "aria-hidden": "true", children: "\u2713" }),
1107
- /* @__PURE__ */ jsx6("h2", { className: "solvapay-checkout-success-heading", children: "Credits added" }),
1108
- /* @__PURE__ */ jsx6("p", { className: "solvapay-checkout-success-subheading", children: "Pay as you go plan is active." }),
1109
- /* @__PURE__ */ jsxs2("dl", { className: "solvapay-checkout-receipt", "data-variant": "payg", children: [
1110
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1111
- /* @__PURE__ */ jsx6("dt", { children: "Amount" }),
1112
- /* @__PURE__ */ jsx6("dd", { children: formatPrice(meta.amountMinor, meta.currency, { locale }) })
1113
- ] }),
1114
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1115
- /* @__PURE__ */ jsx6("dt", { children: "Credits" }),
1116
- /* @__PURE__ */ jsxs2("dd", { children: [
1117
- "+",
1118
- meta.creditsAdded.toLocaleString(locale)
1119
- ] })
1120
- ] }),
1121
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1122
- /* @__PURE__ */ jsx6("dt", { children: "Plan" }),
1123
- /* @__PURE__ */ jsx6("dd", { children: meta.plan.name ?? "Pay as you go" })
1124
- ] }),
1125
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1126
- /* @__PURE__ */ jsx6("dt", { children: "Rate" }),
1127
- /* @__PURE__ */ jsx6("dd", { children: meta.rateLabel })
1128
- ] })
1129
- ] })
1130
- ] });
1131
- }
1132
- return /* @__PURE__ */ jsxs2("div", { className: className ?? "solvapay-checkout-success", "data-branch": "recurring", children: [
1133
- /* @__PURE__ */ jsx6("div", { className: "solvapay-checkout-success-check", "aria-hidden": "true", children: "\u2713" }),
1134
- /* @__PURE__ */ jsxs2("h2", { className: "solvapay-checkout-success-heading", children: [
1135
- meta.plan.name ?? "Plan",
1136
- " active"
1137
- ] }),
1138
- /* @__PURE__ */ jsx6("p", { className: "solvapay-checkout-success-subheading", children: "Subscription is live and credits are ready." }),
1139
- /* @__PURE__ */ jsxs2("dl", { className: "solvapay-checkout-receipt", "data-variant": "recurring", children: [
1140
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1141
- /* @__PURE__ */ jsx6("dt", { children: "Plan" }),
1142
- /* @__PURE__ */ jsx6("dd", { children: meta.plan.name ?? "Plan" })
1143
- ] }),
1144
- meta.creditsIncluded > 0 ? /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1145
- /* @__PURE__ */ jsx6("dt", { children: "Credits" }),
1146
- /* @__PURE__ */ jsxs2("dd", { children: [
1147
- "+",
1148
- meta.creditsIncluded.toLocaleString(locale)
1149
- ] })
1150
- ] }) : null,
1151
- /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1152
- /* @__PURE__ */ jsx6("dt", { children: "Charged today" }),
1153
- /* @__PURE__ */ jsx6("dd", { children: formatPrice(meta.chargedTodayMinor, meta.currency, { locale }) })
1154
- ] }),
1155
- meta.nextRenewalLabel ? /* @__PURE__ */ jsxs2("div", { className: "solvapay-checkout-receipt-row", children: [
1156
- /* @__PURE__ */ jsx6("dt", { children: "Next renewal" }),
1157
- /* @__PURE__ */ jsx6("dd", { children: meta.nextRenewalLabel })
1158
- ] }) : null
1159
- ] })
1160
- ] });
1161
- }
1162
- var RootWithEnv = forwardRef5(
1163
- function CheckoutStepsRootWithEnv(props, forwardedRef) {
1164
- const env = useMemo5(
1165
- () => ({ returnUrl: props.returnUrl, productRef: props.productRef }),
1166
- [props.returnUrl, props.productRef]
1167
- );
1168
- return /* @__PURE__ */ jsx6(CheckoutEnvContext.Provider, { value: env, children: /* @__PURE__ */ jsx6(Root4, { ...props, ref: forwardedRef }) });
1169
- }
1170
- );
1171
- var CheckoutSteps = {
1172
- Root: RootWithEnv,
1173
- IfStep,
1174
- StepHeading,
1175
- StepMessage,
1176
- PlanGrid,
1177
- PlanContinueButton,
1178
- AmountPicker: AmountPicker2,
1179
- AmountContinueButton,
1180
- Payment,
1181
- BackLink,
1182
- Success
1183
- };
1184
- var CheckoutStepsRoot2 = RootWithEnv;
1185
- var CheckoutStepsIfStep = IfStep;
1186
- var CheckoutStepsStepHeading2 = StepHeading;
1187
- var CheckoutStepsStepMessage2 = StepMessage;
1188
- var CheckoutStepsPlanGrid = PlanGrid;
1189
- var CheckoutStepsPlanContinueButton = PlanContinueButton;
1190
- var CheckoutStepsAmountPicker = AmountPicker2;
1191
- var CheckoutStepsAmountContinueButton = AmountContinueButton;
1192
- var CheckoutStepsPayment = Payment;
1193
- var CheckoutStepsBackLink2 = BackLink;
1194
- var CheckoutStepsSuccess = Success;
1195
-
1196
- // src/primitives/PaywallNotice.tsx
1197
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1198
- var PaywallNoticeContext = createContext5(null);
1199
- function usePaywallNoticeCtx(part) {
1200
- const ctx = useContext6(PaywallNoticeContext);
1201
- if (!ctx) {
1202
- throw new Error(`PaywallNotice.${part} must be rendered inside <PaywallNotice.Root>.`);
1203
- }
1204
- return ctx;
1205
- }
1206
- var Root5 = forwardRef6(function PaywallNoticeRoot({ content, onResolved, classNames, asChild, children, ...rest }, forwardedRef) {
1207
- const { resolved, refetch } = usePaywallResolver(content);
1208
- const classNamesResolved = useMemo6(() => classNames ?? {}, [classNames]);
1209
- const onResolvedRef = useRef2(onResolved);
1210
- useEffect3(() => {
1211
- onResolvedRef.current = onResolved;
1212
- }, [onResolved]);
1213
- const hasResolvedRef = useRef2(false);
1214
- const signalResolved = useCallback3(() => {
1215
- if (hasResolvedRef.current) return;
1216
- hasResolvedRef.current = true;
1217
- onResolvedRef.current?.();
1218
- }, []);
1219
- useEffect3(() => {
1220
- if (resolved) signalResolved();
1221
- }, [resolved, signalResolved]);
1222
- const ctx = useMemo6(
1223
- () => ({
1224
- content,
1225
- resolved,
1226
- refetch,
1227
- onResolved: signalResolved,
1228
- classNames: classNamesResolved
1229
- }),
1230
- [content, resolved, refetch, signalResolved, classNamesResolved]
1231
- );
1232
- const Comp = asChild ? Slot : "div";
1233
- return /* @__PURE__ */ jsx7(PaywallNoticeContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx7(
1234
- Comp,
1235
- {
1236
- ref: forwardedRef,
1237
- "data-solvapay-paywall-notice": "",
1238
- "data-kind": content.kind,
1239
- "data-state": resolved ? "resolved" : "pending",
1240
- className: classNamesResolved.root,
1241
- ...rest,
1242
- children
1243
- }
1244
- ) });
1245
- });
1246
- var Heading2 = forwardRef6(function PaywallNoticeHeading({ asChild, children, className, ...rest }, forwardedRef) {
1247
- const ctx = usePaywallNoticeCtx("Heading");
1248
- const copy = useCopy();
1249
- const defaultText = ctx.resolved ? copy.paywall.resolvedHeading : ctx.content.kind === "payment_required" ? copy.paywall.paymentRequiredHeading : isTopupGate(ctx.content) ? copy.paywall.topupRequiredHeading : copy.paywall.activationRequiredHeading;
1250
- const Comp = asChild ? Slot : "h2";
1251
- return /* @__PURE__ */ jsx7(
1252
- Comp,
1253
- {
1254
- ref: forwardedRef,
1255
- "data-solvapay-paywall-heading": "",
1256
- className: className ?? ctx.classNames.heading,
1257
- ...rest,
1258
- children: children ?? defaultText
1259
- }
1260
- );
1261
- });
1262
- var Message = forwardRef6(function PaywallNoticeMessage({ asChild, children, className, ...rest }, forwardedRef) {
1263
- const ctx = usePaywallNoticeCtx("Message");
1264
- const copy = useCopy();
1265
- const Comp = asChild ? Slot : "p";
1266
- const defaultText = resolvePaywallMessage(ctx.content, copy.paywall);
1267
- return /* @__PURE__ */ jsx7(
1268
- Comp,
1269
- {
1270
- ref: forwardedRef,
1271
- "data-solvapay-paywall-message": "",
1272
- className: className ?? ctx.classNames.message,
1273
- ...rest,
1274
- children: children ?? defaultText
1275
- }
1276
- );
1277
- });
1278
- function isTopupGate(content) {
1279
- if (content.kind !== "activation_required") return false;
1280
- const plans = content.plans;
1281
- if (!plans || plans.length === 0) return false;
1282
- return plans.every((p) => isPaygPlan(p));
1283
- }
1284
- function resolvePaywallMessage(content, paywallCopy) {
1285
- const productName = content.kind === "payment_required" || content.kind === "activation_required" ? content.productDetails?.name : void 0;
1286
- const forProduct = productName ? interpolate(paywallCopy.paymentRequiredProductSuffix, { product: productName }) : "";
1287
- if (content.kind === "payment_required") {
1288
- const balance = content.balance;
1289
- if (!balance) {
1290
- return interpolate(paywallCopy.paymentRequiredMessageNoBalance, { forProduct });
1291
- }
1292
- const remaining = balance.remainingUnits ?? 0;
1293
- if (remaining <= 0) {
1294
- return interpolate(paywallCopy.paymentRequiredMessage, { forProduct });
1295
- }
1296
- return interpolate(paywallCopy.paymentRequiredMessageRemaining, {
1297
- remaining: String(remaining),
1298
- pluralSuffix: remaining === 1 ? "" : "s",
1299
- forProduct
1300
- });
1301
- }
1302
- if (content.kind === "activation_required") {
1303
- if (isTopupGate(content)) {
1304
- return interpolate(paywallCopy.topupRequiredMessage, { forProduct });
1305
- }
1306
- return interpolate(paywallCopy.activationRequiredMessage, { forProduct });
1307
- }
1308
- return content.message ?? "";
1309
- }
1310
- var ProductContext = forwardRef6(function PaywallNoticeProductContext({ asChild, children, className, ...rest }, forwardedRef) {
1311
- const ctx = usePaywallNoticeCtx("ProductContext");
1312
- const copy = useCopy();
1313
- const product = ctx.content.kind === "activation_required" ? ctx.content.productDetails : void 0;
1314
- if (!product?.name) return null;
1315
- const label = interpolate(copy.paywall.productContext, { product: product.name });
1316
- const Comp = asChild ? Slot : "div";
1317
- return /* @__PURE__ */ jsx7(
1318
- Comp,
1319
- {
1320
- ref: forwardedRef,
1321
- "data-solvapay-paywall-product-context": "",
1322
- className: className ?? ctx.classNames.productContext,
1323
- ...rest,
1324
- children: children ?? label
1325
- }
1326
- );
1327
- });
1328
- var Balance = forwardRef6(function PaywallNoticeBalance({ asChild, children, className, ...rest }, forwardedRef) {
1329
- const ctx = usePaywallNoticeCtx("Balance");
1330
- const copy = useCopy();
1331
- const balance = ctx.content.balance;
1332
- if (ctx.content.kind !== "activation_required" || !balance) return null;
1333
- const label = interpolate(copy.paywall.balanceLine, {
1334
- available: String(balance.remainingUnits ?? 0),
1335
- required: String(balance.creditsPerUnit ?? 1)
1336
- });
1337
- const Comp = asChild ? Slot : "div";
1338
- return /* @__PURE__ */ jsx7(
1339
- Comp,
1340
- {
1341
- ref: forwardedRef,
1342
- "data-solvapay-paywall-balance": "",
1343
- className: className ?? ctx.classNames.balance,
1344
- ...rest,
1345
- children: children ?? label
1346
- }
1347
- );
1348
- });
1349
- function Plans({ className, children }) {
1350
- const ctx = usePaywallNoticeCtx("Plans");
1351
- if (ctx.content.kind !== "activation_required") return null;
1352
- if (!ctx.content.product) return null;
1353
- const defaultClass = className ?? ctx.classNames.plans ?? "solvapay-paywall-plans";
1354
- return /* @__PURE__ */ jsx7("div", { "data-solvapay-paywall-plans": "", className: defaultClass, children: /* @__PURE__ */ jsx7(PlanSelector.Root, { productRef: ctx.content.product, filter: hidesFreePlan, children: children ?? /* @__PURE__ */ jsxs3(Fragment4, { children: [
1355
- /* @__PURE__ */ jsx7(PlanSelector.Grid, { children: /* @__PURE__ */ jsxs3(PlanSelector.Card, { children: [
1356
- /* @__PURE__ */ jsx7(PlanSelector.CardBadge, {}),
1357
- /* @__PURE__ */ jsx7(PlanSelector.CardName, {}),
1358
- /* @__PURE__ */ jsx7(PlanSelector.CardPrice, {}),
1359
- /* @__PURE__ */ jsx7(PlanSelector.CardInterval, {})
1360
- ] }) }),
1361
- /* @__PURE__ */ jsx7(PlanSelector.Loading, {}),
1362
- /* @__PURE__ */ jsx7(PlanSelector.Error, {})
1363
- ] }) }) });
1364
- }
1365
- function hidesFreePlan(plan) {
1366
- return plan.requiresPayment !== false;
1367
- }
1368
- var HostedCheckoutLink = forwardRef6(function PaywallNoticeHostedCheckoutLink({ asChild, children, className, ...rest }, forwardedRef) {
1369
- const ctx = usePaywallNoticeCtx("HostedCheckoutLink");
1370
- const copy = useCopy();
1371
- const href = ctx.content.checkoutUrl;
1372
- if (!href) return null;
1373
- const Comp = asChild ? Slot : "a";
1374
- return /* @__PURE__ */ jsx7(
1375
- Comp,
1376
- {
1377
- ref: forwardedRef,
1378
- href,
1379
- target: "_blank",
1380
- rel: "noopener noreferrer",
1381
- "data-solvapay-paywall-hosted-link": "",
1382
- className: className ?? ctx.classNames.hostedLink,
1383
- ...rest,
1384
- children: children ?? copy.paywall.hostedCheckoutButton
1385
- }
1386
- );
1387
- });
1388
- function EmbeddedCheckout({ returnUrl, topupCurrency, className }) {
1389
- const ctx = usePaywallNoticeCtx("EmbeddedCheckout");
1390
- const productRef = ctx.content.product;
1391
- const { plans } = usePlans({ productRef: productRef ?? void 0 });
1392
- const filter = useMemo6(() => buildDefaultCheckoutPlanFilter(plans), [plans]);
1393
- if (!productRef) return null;
1394
- const resolvedClassName = className ?? ctx.classNames.embeddedCheckout ?? "solvapay-paywall-embedded-checkout";
1395
- const headingClassName = ctx.classNames.heading ?? "solvapay-paywall-step-heading";
1396
- const messageClassName = ctx.classNames.message ?? "solvapay-paywall-step-message";
1397
- return /* @__PURE__ */ jsxs3(
1398
- CheckoutSteps.Root,
1399
- {
1400
- productRef,
1401
- returnUrl,
1402
- filter,
1403
- topupCurrency,
1404
- onPurchaseSuccess: () => {
1405
- void ctx.refetch();
1406
- ctx.onResolved?.();
1407
- },
1408
- className: resolvedClassName,
1409
- children: [
1410
- /* @__PURE__ */ jsx7(CheckoutSteps.StepHeading, { className: headingClassName }),
1411
- /* @__PURE__ */ jsx7(CheckoutSteps.StepMessage, { className: messageClassName }),
1412
- /* @__PURE__ */ jsxs3(CheckoutSteps.IfStep, { step: "plan", children: [
1413
- /* @__PURE__ */ jsx7(CheckoutSteps.PlanGrid, {}),
1414
- /* @__PURE__ */ jsx7(PlanSelector.Loading, {}),
1415
- /* @__PURE__ */ jsx7(PlanSelector.Error, {}),
1416
- /* @__PURE__ */ jsx7(CheckoutSteps.PlanContinueButton, {})
1417
- ] }),
1418
- /* @__PURE__ */ jsxs3(CheckoutSteps.IfStep, { step: "amount", children: [
1419
- /* @__PURE__ */ jsx7(CheckoutSteps.BackLink, {}),
1420
- /* @__PURE__ */ jsx7(CheckoutSteps.AmountPicker, {}),
1421
- /* @__PURE__ */ jsx7(CheckoutSteps.AmountContinueButton, {})
1422
- ] }),
1423
- /* @__PURE__ */ jsxs3(CheckoutSteps.IfStep, { step: "payment", children: [
1424
- /* @__PURE__ */ jsx7(CheckoutSteps.BackLink, {}),
1425
- /* @__PURE__ */ jsx7(CheckoutSteps.Payment, {})
1426
- ] }),
1427
- /* @__PURE__ */ jsx7(CheckoutSteps.IfStep, { step: "success", children: /* @__PURE__ */ jsx7(CheckoutSteps.Success, {}) })
1428
- ]
1429
- }
1430
- );
1431
- }
1432
- var Retry = forwardRef6(function PaywallNoticeRetry({ asChild, children, className, onClick, ...rest }, forwardedRef) {
1433
- const ctx = usePaywallNoticeCtx("Retry");
1434
- const copy = useCopy();
1435
- const Comp = asChild ? Slot : "button";
1436
- return /* @__PURE__ */ jsx7(
1437
- Comp,
1438
- {
1439
- ref: forwardedRef,
1440
- type: "button",
1441
- disabled: !ctx.resolved,
1442
- "data-solvapay-paywall-retry": "",
1443
- "data-state": ctx.resolved ? "ready" : "waiting",
1444
- className: className ?? ctx.classNames.retryButton,
1445
- onClick: (e) => {
1446
- onClick?.(e);
1447
- if (!e.defaultPrevented && ctx.resolved) ctx.onResolved?.();
1448
- },
1449
- ...rest,
1450
- children: children ?? copy.paywall.retryButton
1451
- }
1452
- );
1453
- });
1454
- var PaywallNotice = Object.assign(Root5, {
1455
- Root: Root5,
1456
- Heading: Heading2,
1457
- Message,
1458
- ProductContext,
1459
- Plans,
1460
- Balance,
1461
- HostedCheckoutLink,
1462
- EmbeddedCheckout,
1463
- Retry
1464
- });
1465
- function usePaywallNotice() {
1466
- return usePaywallNoticeCtx("usePaywallNotice");
1467
- }
1468
- function usePaywallNoticeOptional() {
1469
- return useContext6(PaywallNoticeContext);
1470
- }
1471
-
1472
- export {
1473
- TopupForm2 as TopupForm,
1474
- ProductBadge2 as ProductBadge,
1475
- PlanBadge,
1476
- PurchaseGateRoot2 as PurchaseGateRoot,
1477
- PurchaseGateAllowed2 as PurchaseGateAllowed,
1478
- PurchaseGateBlocked2 as PurchaseGateBlocked,
1479
- PurchaseGateLoading2 as PurchaseGateLoading,
1480
- PurchaseGateError2 as PurchaseGateError,
1481
- PurchaseGate,
1482
- usePurchaseGate,
1483
- ActivationFlowRoot2 as ActivationFlowRoot,
1484
- ActivationFlowSummary2 as ActivationFlowSummary,
1485
- ActivationFlowActivateButton2 as ActivationFlowActivateButton,
1486
- ActivationFlowAmountPicker,
1487
- ActivationFlowContinueButton2 as ActivationFlowContinueButton,
1488
- ActivationFlowRetrying2 as ActivationFlowRetrying,
1489
- ActivationFlowActivated2 as ActivationFlowActivated,
1490
- ActivationFlowLoading2 as ActivationFlowLoading,
1491
- ActivationFlowError2 as ActivationFlowError,
1492
- ActivationFlow,
1493
- useActivationFlow,
1494
- CreditGateRoot2 as CreditGateRoot,
1495
- CreditGateHeading2 as CreditGateHeading,
1496
- CreditGateSubheading2 as CreditGateSubheading,
1497
- CreditGateTopup,
1498
- CreditGateLoading2 as CreditGateLoading,
1499
- CreditGateError2 as CreditGateError,
1500
- CreditGate,
1501
- useCreditGate,
1502
- usePaywallResolver,
1503
- Root5 as Root,
1504
- Heading2 as Heading,
1505
- Message,
1506
- ProductContext,
1507
- Balance,
1508
- Plans,
1509
- HostedCheckoutLink,
1510
- EmbeddedCheckout,
1511
- Retry,
1512
- PaywallNotice,
1513
- usePaywallNotice,
1514
- useCheckoutSteps,
1515
- CheckoutSteps,
1516
- CheckoutStepsRoot2 as CheckoutStepsRoot,
1517
- CheckoutStepsIfStep,
1518
- CheckoutStepsStepHeading2 as CheckoutStepsStepHeading,
1519
- CheckoutStepsStepMessage2 as CheckoutStepsStepMessage,
1520
- CheckoutStepsPlanGrid,
1521
- CheckoutStepsPlanContinueButton,
1522
- CheckoutStepsAmountPicker,
1523
- CheckoutStepsAmountContinueButton,
1524
- CheckoutStepsPayment,
1525
- CheckoutStepsBackLink2 as CheckoutStepsBackLink,
1526
- CheckoutStepsSuccess
1527
- };