@solvapay/react 1.0.6 → 1.0.8-preview.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.
@@ -0,0 +1,4254 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/primitives/index.ts
31
+ var primitives_exports = {};
32
+ __export(primitives_exports, {
33
+ ActivationFlow: () => ActivationFlow,
34
+ ActivationFlowActivateButton: () => ActivationFlowActivateButton2,
35
+ ActivationFlowActivated: () => ActivationFlowActivated2,
36
+ ActivationFlowAmountPicker: () => ActivationFlowAmountPicker,
37
+ ActivationFlowContinueButton: () => ActivationFlowContinueButton2,
38
+ ActivationFlowError: () => ActivationFlowError2,
39
+ ActivationFlowLoading: () => ActivationFlowLoading2,
40
+ ActivationFlowRetrying: () => ActivationFlowRetrying2,
41
+ ActivationFlowRoot: () => ActivationFlowRoot2,
42
+ ActivationFlowSummary: () => ActivationFlowSummary2,
43
+ AmountPicker: () => AmountPicker,
44
+ AmountPickerConfirm: () => AmountPickerConfirm2,
45
+ AmountPickerCustom: () => AmountPickerCustom2,
46
+ AmountPickerOption: () => AmountPickerOption2,
47
+ AmountPickerRoot: () => AmountPickerRoot2,
48
+ BalanceBadge: () => BalanceBadge,
49
+ CancelPlanButton: () => CancelPlanButton,
50
+ CancelledPlanNotice: () => CancelledPlanNotice,
51
+ CancelledPlanNoticeAccessUntil: () => CancelledPlanNoticeAccessUntil2,
52
+ CancelledPlanNoticeCancelledOn: () => CancelledPlanNoticeCancelledOn2,
53
+ CancelledPlanNoticeDaysRemaining: () => CancelledPlanNoticeDaysRemaining2,
54
+ CancelledPlanNoticeExpires: () => CancelledPlanNoticeExpires2,
55
+ CancelledPlanNoticeHeading: () => CancelledPlanNoticeHeading2,
56
+ CancelledPlanNoticeReactivateButton: () => CancelledPlanNoticeReactivateButton2,
57
+ CancelledPlanNoticeReason: () => CancelledPlanNoticeReason2,
58
+ CancelledPlanNoticeRoot: () => CancelledPlanNoticeRoot2,
59
+ CheckoutSummary: () => CheckoutSummary,
60
+ CheckoutSummaryPlan: () => CheckoutSummaryPlan2,
61
+ CheckoutSummaryPrice: () => CheckoutSummaryPrice2,
62
+ CheckoutSummaryProduct: () => CheckoutSummaryProduct2,
63
+ CheckoutSummaryRoot: () => CheckoutSummaryRoot2,
64
+ CheckoutSummaryTaxNote: () => CheckoutSummaryTaxNote2,
65
+ CheckoutSummaryTrial: () => CheckoutSummaryTrial2,
66
+ CreditGate: () => CreditGate,
67
+ CreditGateError: () => CreditGateError2,
68
+ CreditGateHeading: () => CreditGateHeading2,
69
+ CreditGateLoading: () => CreditGateLoading2,
70
+ CreditGateRoot: () => CreditGateRoot2,
71
+ CreditGateSubheading: () => CreditGateSubheading2,
72
+ CreditGateTopup: () => CreditGateTopup,
73
+ MandateText: () => MandateText,
74
+ PaymentForm: () => PaymentForm,
75
+ PaymentFormCardElement: () => PaymentFormCardElement,
76
+ PaymentFormCustomerFields: () => PaymentFormCustomerFields2,
77
+ PaymentFormError: () => PaymentFormError2,
78
+ PaymentFormLoading: () => PaymentFormLoading2,
79
+ PaymentFormMandateText: () => PaymentFormMandateText,
80
+ PaymentFormPaymentElement: () => PaymentFormPaymentElement,
81
+ PaymentFormRoot: () => PaymentFormRoot2,
82
+ PaymentFormSubmitButton: () => PaymentFormSubmitButton2,
83
+ PaymentFormSummary: () => PaymentFormSummary,
84
+ PaymentFormTermsCheckbox: () => PaymentFormTermsCheckbox2,
85
+ PlanBadge: () => PlanBadge,
86
+ PlanSelector: () => PlanSelector,
87
+ PlanSelectorCard: () => PlanSelectorCard2,
88
+ PlanSelectorCardBadge: () => PlanSelectorCardBadge2,
89
+ PlanSelectorCardInterval: () => PlanSelectorCardInterval2,
90
+ PlanSelectorCardName: () => PlanSelectorCardName2,
91
+ PlanSelectorCardPrice: () => PlanSelectorCardPrice2,
92
+ PlanSelectorError: () => PlanSelectorError2,
93
+ PlanSelectorGrid: () => PlanSelectorGrid2,
94
+ PlanSelectorHeading: () => PlanSelectorHeading2,
95
+ PlanSelectorLoading: () => PlanSelectorLoading2,
96
+ PlanSelectorRoot: () => PlanSelectorRoot2,
97
+ ProductBadge: () => ProductBadge2,
98
+ PurchaseGate: () => PurchaseGate,
99
+ PurchaseGateAllowed: () => PurchaseGateAllowed2,
100
+ PurchaseGateBlocked: () => PurchaseGateBlocked2,
101
+ PurchaseGateError: () => PurchaseGateError2,
102
+ PurchaseGateLoading: () => PurchaseGateLoading2,
103
+ PurchaseGateRoot: () => PurchaseGateRoot2,
104
+ Slot: () => Slot,
105
+ Slottable: () => Slottable,
106
+ TopupForm: () => TopupForm,
107
+ TopupFormError: () => TopupFormError2,
108
+ TopupFormLoading: () => TopupFormLoading2,
109
+ TopupFormPaymentElement: () => TopupFormPaymentElement,
110
+ TopupFormRoot: () => TopupFormRoot2,
111
+ TopupFormSubmitButton: () => TopupFormSubmitButton2,
112
+ composeEventHandlers: () => composeEventHandlers,
113
+ composeRefs: () => composeRefs,
114
+ setRef: () => setRef,
115
+ useActivationFlow: () => useActivationFlow,
116
+ useAmountPicker: () => useAmountPicker,
117
+ useAmountPickerCopy: () => useAmountPickerCopy,
118
+ useCancelledPlanNotice: () => useCancelledPlanNotice,
119
+ useCheckoutSummary: () => useCheckoutSummary,
120
+ useCreditGate: () => useCreditGate,
121
+ usePlanSelector: () => usePlanSelector,
122
+ usePurchaseGate: () => usePurchaseGate,
123
+ useTopupForm: () => useTopupForm
124
+ });
125
+ module.exports = __toCommonJS(primitives_exports);
126
+
127
+ // src/primitives/slot.tsx
128
+ var import_react = __toESM(require("react"), 1);
129
+
130
+ // src/primitives/composeRefs.ts
131
+ function setRef(ref, value) {
132
+ if (typeof ref === "function") {
133
+ ref(value);
134
+ } else if (ref !== null && ref !== void 0) {
135
+ ;
136
+ ref.current = value;
137
+ }
138
+ }
139
+ function composeRefs(...refs) {
140
+ return (node) => {
141
+ for (const ref of refs) {
142
+ setRef(ref, node);
143
+ }
144
+ };
145
+ }
146
+
147
+ // src/primitives/slot.tsx
148
+ var import_jsx_runtime = require("react/jsx-runtime");
149
+ var Slot = import_react.default.forwardRef((props, forwardedRef) => {
150
+ const { children, ...slotProps } = props;
151
+ const childrenArray = import_react.default.Children.toArray(children);
152
+ const slottable = childrenArray.find(isSlottable);
153
+ if (slottable) {
154
+ const newElement = slottable.props.children;
155
+ const newChildren = childrenArray.map((child) => {
156
+ if (child === slottable) {
157
+ if (import_react.default.Children.count(newElement) > 1) return import_react.default.Children.only(null);
158
+ return import_react.default.isValidElement(newElement) ? newElement.props.children : null;
159
+ }
160
+ return child;
161
+ });
162
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: import_react.default.isValidElement(newElement) ? import_react.default.cloneElement(newElement, void 0, newChildren) : null });
163
+ }
164
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children });
165
+ });
166
+ Slot.displayName = "Slot";
167
+ var SlotClone = import_react.default.forwardRef(
168
+ (props, forwardedRef) => {
169
+ const { children, ...slotProps } = props;
170
+ if (import_react.default.isValidElement(children)) {
171
+ const childProps = children.props ?? {};
172
+ const childRef = getElementRef(children);
173
+ const props2 = mergeProps(slotProps, childProps);
174
+ const cloneProps = { ...props2 };
175
+ if (forwardedRef) {
176
+ cloneProps.ref = childRef ? composeRefs(forwardedRef, childRef) : forwardedRef;
177
+ } else {
178
+ cloneProps.ref = childRef;
179
+ }
180
+ return import_react.default.cloneElement(children, cloneProps);
181
+ }
182
+ return import_react.default.Children.count(children) > 1 ? import_react.default.Children.only(null) : null;
183
+ }
184
+ );
185
+ SlotClone.displayName = "SlotClone";
186
+ var Slottable = ({ children }) => {
187
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
188
+ };
189
+ function isSlottable(child) {
190
+ return import_react.default.isValidElement(child) && child.type === Slottable;
191
+ }
192
+ function mergeProps(slotProps, childProps) {
193
+ const overrideProps = { ...childProps };
194
+ for (const propName in slotProps) {
195
+ const slotPropValue = slotProps[propName];
196
+ const childPropValue = childProps[propName];
197
+ const isHandler = /^on[A-Z]/.test(propName);
198
+ if (isHandler) {
199
+ if (slotPropValue && childPropValue) {
200
+ overrideProps[propName] = (...args) => {
201
+ ;
202
+ childPropValue(...args);
203
+ const first = args[0];
204
+ if (!first?.defaultPrevented) {
205
+ ;
206
+ slotPropValue(...args);
207
+ }
208
+ };
209
+ } else if (slotPropValue) {
210
+ overrideProps[propName] = slotPropValue;
211
+ }
212
+ } else if (propName === "style") {
213
+ overrideProps[propName] = {
214
+ ...slotPropValue,
215
+ ...childPropValue
216
+ };
217
+ } else if (propName === "className") {
218
+ overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
219
+ } else if (propName === "ref") {
220
+ } else if (childPropValue === void 0) {
221
+ overrideProps[propName] = slotPropValue;
222
+ }
223
+ }
224
+ return overrideProps;
225
+ }
226
+ function getElementRef(element) {
227
+ const el = element;
228
+ return el.props?.ref ?? el.ref ?? null;
229
+ }
230
+
231
+ // src/primitives/composeEventHandlers.ts
232
+ function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
233
+ return function handleEvent(event) {
234
+ originalEventHandler?.(event);
235
+ if (checkForDefaultPrevented === false || !event.defaultPrevented) {
236
+ ourEventHandler?.(event);
237
+ }
238
+ };
239
+ }
240
+
241
+ // src/primitives/CheckoutSummary.tsx
242
+ var import_react9 = require("react");
243
+
244
+ // src/hooks/usePlan.ts
245
+ var import_react6 = require("react");
246
+
247
+ // src/hooks/useSolvaPay.ts
248
+ var import_react4 = require("react");
249
+
250
+ // src/SolvaPayProvider.tsx
251
+ var import_react3 = require("react");
252
+
253
+ // src/adapters/auth.ts
254
+ var defaultAuthAdapter = {
255
+ async getToken() {
256
+ if (typeof window === "undefined") return null;
257
+ const token = localStorage.getItem("auth_token");
258
+ return token || null;
259
+ },
260
+ async getUserId() {
261
+ const token = await this.getToken();
262
+ if (!token) return null;
263
+ try {
264
+ const parts = token.split(".");
265
+ if (parts.length === 3) {
266
+ const payload = JSON.parse(atob(parts[1]));
267
+ return payload.sub || payload.user_id || null;
268
+ }
269
+ } catch {
270
+ }
271
+ return null;
272
+ }
273
+ };
274
+
275
+ // src/utils/headers.ts
276
+ var CUSTOMER_REF_KEY = "solvapay_customerRef";
277
+ var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
278
+ var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
279
+ function getAuthAdapter(config) {
280
+ if (config?.auth?.adapter) {
281
+ return config.auth.adapter;
282
+ }
283
+ if (config?.auth?.getToken || config?.auth?.getUserId) {
284
+ return {
285
+ async getToken() {
286
+ return config?.auth?.getToken?.() || null;
287
+ },
288
+ async getUserId() {
289
+ return config?.auth?.getUserId?.() || null;
290
+ }
291
+ };
292
+ }
293
+ return defaultAuthAdapter;
294
+ }
295
+ function getCachedCustomerRef(userId) {
296
+ if (typeof window === "undefined") return null;
297
+ const cached = localStorage.getItem(CUSTOMER_REF_KEY);
298
+ const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
299
+ const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
300
+ if (!cached || !expiry) return null;
301
+ if (Date.now() > parseInt(expiry)) {
302
+ localStorage.removeItem(CUSTOMER_REF_KEY);
303
+ localStorage.removeItem(CUSTOMER_REF_EXPIRY);
304
+ localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
305
+ return null;
306
+ }
307
+ if (userId !== void 0 && userId !== null) {
308
+ if (cachedUserId !== userId) {
309
+ clearCachedCustomerRef();
310
+ return null;
311
+ }
312
+ }
313
+ return cached;
314
+ }
315
+ var CACHE_DURATION = 24 * 60 * 60 * 1e3;
316
+ function clearCachedCustomerRef() {
317
+ if (typeof window === "undefined") return;
318
+ localStorage.removeItem(CUSTOMER_REF_KEY);
319
+ localStorage.removeItem(CUSTOMER_REF_EXPIRY);
320
+ localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
321
+ }
322
+ async function buildRequestHeaders(config) {
323
+ const adapter = getAuthAdapter(config);
324
+ const token = await adapter.getToken();
325
+ const userId = await adapter.getUserId();
326
+ const cachedRef = getCachedCustomerRef(userId);
327
+ const headers = {
328
+ "Content-Type": "application/json"
329
+ };
330
+ if (token) {
331
+ headers["Authorization"] = `Bearer ${token}`;
332
+ }
333
+ if (cachedRef) {
334
+ headers["x-solvapay-customer-ref"] = cachedRef;
335
+ }
336
+ if (config?.headers) {
337
+ const custom = typeof config.headers === "function" ? await config.headers() : config.headers;
338
+ Object.assign(headers, custom);
339
+ }
340
+ return { headers, userId };
341
+ }
342
+
343
+ // src/i18n/context.tsx
344
+ var import_react2 = require("react");
345
+
346
+ // src/i18n/en.ts
347
+ function intervalPhrase(ctx) {
348
+ const interval = ctx.plan?.interval;
349
+ const count = ctx.plan?.intervalCount ?? 1;
350
+ if (!interval) return "";
351
+ return count > 1 ? `${count} ${interval}s` : interval;
352
+ }
353
+ function trialPhrase(ctx) {
354
+ const trialDays = ctx.plan?.trialDays ?? ctx.trialDays;
355
+ return trialDays ? ` after your ${trialDays}-day free trial` : "";
356
+ }
357
+ function termsSentence(ctx) {
358
+ const { termsUrl, privacyUrl } = ctx.merchant;
359
+ if (termsUrl && privacyUrl) {
360
+ return ` See ${termsUrl} and ${privacyUrl}.`;
361
+ }
362
+ if (termsUrl) return ` See ${termsUrl}.`;
363
+ if (privacyUrl) return ` See ${privacyUrl}.`;
364
+ return "";
365
+ }
366
+ var enCopy = {
367
+ mandate: {
368
+ recurring: (ctx) => {
369
+ const period = intervalPhrase(ctx);
370
+ const trial = trialPhrase(ctx);
371
+ const every = period ? ` every ${period}` : "";
372
+ return `By subscribing, you authorize ${ctx.merchant.legalName} to charge ${ctx.amountFormatted}${every}${trial} until you cancel. You can cancel any time. Payments are processed by SolvaPay.${termsSentence(ctx)}`;
373
+ },
374
+ oneTime: (ctx) => {
375
+ const product = ctx.product?.name ? ` for ${ctx.product.name}` : "";
376
+ return `By confirming, you authorize ${ctx.merchant.legalName} to charge ${ctx.amountFormatted}${product}. Payments are processed by SolvaPay.${termsSentence(ctx)}`;
377
+ },
378
+ topup: (ctx) => {
379
+ const product = ctx.product?.name ? ` to add credits to your ${ctx.product.name} balance` : " to add credits to your balance";
380
+ return `By confirming, you authorize ${ctx.merchant.legalName} to charge ${ctx.amountFormatted}${product}. Credits are non-refundable once used. Payments are processed by SolvaPay.${termsSentence(ctx)}`;
381
+ },
382
+ usageMetered: (ctx) => {
383
+ const measures = ctx.plan?.measures ?? "unit";
384
+ const cycle = ctx.plan?.billingCycle ?? "monthly";
385
+ const product = ctx.product?.name ?? "the service";
386
+ return `By confirming, you authorize ${ctx.merchant.legalName} to charge your payment method for metered usage of ${product} at ${ctx.amountFormatted} per ${measures}, billed ${cycle}. You can cancel any time. Payments are processed by SolvaPay.${termsSentence(ctx)}`;
387
+ },
388
+ freeTier: (ctx) => {
389
+ const product = ctx.product?.name ?? "this plan";
390
+ const planPhrase = ctx.plan?.name ? ` on ${ctx.plan.name}` : "";
391
+ return `By confirming, you activate ${product}${planPhrase}. Payments are processed by SolvaPay.${termsSentence(ctx)}`;
392
+ }
393
+ },
394
+ cta: {
395
+ payNow: "Pay Now",
396
+ topUp: "Top Up",
397
+ subscribe: "Subscribe",
398
+ trialStart: "Start {trialDays}-day free trial",
399
+ payAmount: "Pay {amount}",
400
+ addAmount: "Add {amount}",
401
+ startUsing: "Start using {product}",
402
+ processing: "Processing..."
403
+ },
404
+ interval: {
405
+ day: "day",
406
+ week: "week",
407
+ month: "month",
408
+ year: "year",
409
+ every: "every {n} {unit}",
410
+ free: "Free",
411
+ trial: "{trialDays}-day free trial"
412
+ },
413
+ terms: {
414
+ checkboxLabel: "I agree to the terms and privacy policy"
415
+ },
416
+ customer: {
417
+ chargingTo: "Charging to {email}",
418
+ emailLabel: "Email",
419
+ nameLabel: "Name"
420
+ },
421
+ balance: {
422
+ credits: " credits",
423
+ currencyEquivalent: " (~{amount})"
424
+ },
425
+ product: {
426
+ currentProductLabel: "Current product: {name}"
427
+ },
428
+ topup: {
429
+ selectOrEnterAmount: "Please select or enter an amount",
430
+ minAmount: "Minimum amount is {amount}",
431
+ maxAmount: "Maximum amount is {amount}"
432
+ },
433
+ activation: {
434
+ paymentRequired: "This plan requires payment. Please select a different plan.",
435
+ invalidConfiguration: "Invalid plan configuration.",
436
+ unexpectedResponse: "Unexpected response from server.",
437
+ failed: "Activation failed"
438
+ },
439
+ planSelector: {
440
+ heading: "Choose your pricing",
441
+ currentBadge: "Current",
442
+ popularBadge: "Popular",
443
+ freeBadge: "Free",
444
+ perIntervalShort: "/{interval}",
445
+ continueButton: "Continue",
446
+ backButton: "\u2190 Back to plans",
447
+ trialBadge: "{trialDays}-day free trial"
448
+ },
449
+ amountPicker: {
450
+ selectAmountLabel: "Select an amount",
451
+ customAmountLabel: "Or enter a custom amount",
452
+ creditEstimateExact: "= {credits} credits",
453
+ creditEstimateApprox: "~ {credits} credits"
454
+ },
455
+ activationFlow: {
456
+ heading: "Confirm your plan",
457
+ activateButton: "Activate",
458
+ activatingLabel: "Activating...",
459
+ topupHeading: "Add credits",
460
+ topupSubheading: "Top up your credits to activate this plan.",
461
+ continueToPayment: "Continue to payment",
462
+ changeAmountButton: "Change amount",
463
+ retryingHeading: "Activating your plan...",
464
+ retryingSubheading: "Payment received. Setting up your plan.",
465
+ activatedHeading: "Plan selected",
466
+ activatedSubheading: "Your plan is now active.",
467
+ tryAgainButton: "Try Again",
468
+ backButton: "\u2190 Back to plan selection"
469
+ },
470
+ cancelPlan: {
471
+ button: "Cancel plan",
472
+ buttonLoading: "Cancelling...",
473
+ confirmRecurring: "Are you sure you want to cancel your subscription?",
474
+ confirmUsageBased: "Are you sure you want to deactivate your plan? This will take effect immediately."
475
+ },
476
+ cancelledNotice: {
477
+ heading: "Your purchase has been cancelled",
478
+ expiresLabel: "Purchase Expires: {date}",
479
+ daysRemaining: "{days} days remaining",
480
+ dayRemaining: "1 day remaining",
481
+ accessUntil: "You'll continue to have access to {product} features until this date",
482
+ accessEnded: "Your purchase access has ended",
483
+ cancelledOn: "Cancelled on {date}",
484
+ reactivateButton: "Undo Cancellation",
485
+ reactivateButtonLoading: "Reactivating..."
486
+ },
487
+ creditGate: {
488
+ lowBalanceHeading: "You're out of credits",
489
+ lowBalanceSubheading: "Top up to continue using {product}",
490
+ topUpCta: "Top up now"
491
+ },
492
+ errors: {
493
+ paymentInitFailed: "Payment initialization failed",
494
+ topupInitFailed: "Top-up initialization failed",
495
+ configMissingPlanOrProduct: "PaymentForm: either planRef or productRef is required",
496
+ configMissingAmount: "TopupForm: amount must be a positive number",
497
+ unknownError: "Unknown error",
498
+ stripeUnavailable: "Stripe is not available. Please refresh the page.",
499
+ paymentIntentUnavailable: "Payment intent not available. Please refresh the page.",
500
+ cardElementMissing: "Card element not found",
501
+ paymentUnexpected: "An unexpected error occurred.",
502
+ paymentProcessingFailed: "Payment processing failed. Please try again or contact support.",
503
+ paymentRequires3ds: "Payment requires additional authentication. Please complete the verification.",
504
+ paymentProcessingTimeout: "Payment processing timed out \u2014 webhooks may not be configured",
505
+ paymentStatusPrefix: "Payment status: {status}"
506
+ }
507
+ };
508
+
509
+ // src/i18n/context.tsx
510
+ var import_jsx_runtime2 = require("react/jsx-runtime");
511
+ var CopyContext = (0, import_react2.createContext)({
512
+ locale: void 0,
513
+ copy: enCopy
514
+ });
515
+ function useCopyContext() {
516
+ return (0, import_react2.useContext)(CopyContext);
517
+ }
518
+
519
+ // src/SolvaPayProvider.tsx
520
+ var import_jsx_runtime3 = require("react/jsx-runtime");
521
+ var SolvaPayContext = (0, import_react3.createContext)(null);
522
+
523
+ // src/hooks/useSolvaPay.ts
524
+ function useSolvaPay() {
525
+ const context = (0, import_react4.useContext)(SolvaPayContext);
526
+ if (!context) {
527
+ throw new Error(
528
+ "useSolvaPay must be used within a SolvaPayProvider. Wrap your component tree with <SolvaPayProvider> to use this hook."
529
+ );
530
+ }
531
+ return context;
532
+ }
533
+
534
+ // src/hooks/usePlans.ts
535
+ var import_react5 = require("react");
536
+ var plansCache = /* @__PURE__ */ new Map();
537
+ var CACHE_DURATION2 = 5 * 60 * 1e3;
538
+ function processPlans(raw, filter, sortBy) {
539
+ let result = sortBy ? [...raw].sort(sortBy) : raw;
540
+ if (filter) result = result.filter(filter);
541
+ return result;
542
+ }
543
+ function computeInitialIndex(plans, initialPlanRef, autoSelectFirstPaid) {
544
+ if (plans.length === 0) return 0;
545
+ if (initialPlanRef) {
546
+ const idx = plans.findIndex((p) => p.reference === initialPlanRef);
547
+ if (idx >= 0) return idx;
548
+ }
549
+ if (autoSelectFirstPaid) {
550
+ const idx = plans.findIndex((p) => p.requiresPayment !== false);
551
+ return idx >= 0 ? idx : 0;
552
+ }
553
+ return 0;
554
+ }
555
+ function usePlans(options) {
556
+ const {
557
+ fetcher,
558
+ productRef,
559
+ filter,
560
+ sortBy,
561
+ autoSelectFirstPaid = false,
562
+ initialPlanRef,
563
+ selectionReady = true
564
+ } = options;
565
+ const fetcherRef = (0, import_react5.useRef)(fetcher);
566
+ const filterRef = (0, import_react5.useRef)(filter);
567
+ const sortByRef = (0, import_react5.useRef)(sortBy);
568
+ const autoSelectFirstPaidRef = (0, import_react5.useRef)(autoSelectFirstPaid);
569
+ const initialPlanRefRef = (0, import_react5.useRef)(initialPlanRef);
570
+ const selectionReadyRef = (0, import_react5.useRef)(selectionReady);
571
+ const hasAppliedInitialRef = (0, import_react5.useRef)(false);
572
+ const userHasSelectedRef = (0, import_react5.useRef)(false);
573
+ const [selectedPlanIndex, setSelectedPlanIndexState] = (0, import_react5.useState)(() => {
574
+ if (!selectionReady || !productRef) return 0;
575
+ const cached = plansCache.get(productRef);
576
+ if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
577
+ return 0;
578
+ }
579
+ const processed = processPlans(cached.plans, filter, sortBy);
580
+ if (processed.length === 0) return 0;
581
+ const idx = computeInitialIndex(processed, initialPlanRef, autoSelectFirstPaid);
582
+ hasAppliedInitialRef.current = true;
583
+ return idx;
584
+ });
585
+ const [plans, setPlans] = (0, import_react5.useState)(() => {
586
+ if (!productRef) return [];
587
+ const cached = plansCache.get(productRef);
588
+ if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
589
+ return [];
590
+ }
591
+ return processPlans(cached.plans, filter, sortBy);
592
+ });
593
+ const [loading, setLoading] = (0, import_react5.useState)(() => plans.length === 0);
594
+ const [error, setError] = (0, import_react5.useState)(null);
595
+ (0, import_react5.useEffect)(() => {
596
+ fetcherRef.current = fetcher;
597
+ }, [fetcher]);
598
+ (0, import_react5.useEffect)(() => {
599
+ filterRef.current = filter;
600
+ }, [filter]);
601
+ (0, import_react5.useEffect)(() => {
602
+ sortByRef.current = sortBy;
603
+ }, [sortBy]);
604
+ (0, import_react5.useEffect)(() => {
605
+ autoSelectFirstPaidRef.current = autoSelectFirstPaid;
606
+ }, [autoSelectFirstPaid]);
607
+ (0, import_react5.useEffect)(() => {
608
+ initialPlanRefRef.current = initialPlanRef;
609
+ }, [initialPlanRef]);
610
+ (0, import_react5.useEffect)(() => {
611
+ selectionReadyRef.current = selectionReady;
612
+ }, [selectionReady]);
613
+ const setSelectedPlanIndex = (0, import_react5.useCallback)((index) => {
614
+ userHasSelectedRef.current = true;
615
+ setSelectedPlanIndexState(index);
616
+ }, []);
617
+ const applyInitialSelection = (0, import_react5.useCallback)((processedPlans) => {
618
+ if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
619
+ if (!selectionReadyRef.current || processedPlans.length === 0) return;
620
+ hasAppliedInitialRef.current = true;
621
+ const idx = computeInitialIndex(
622
+ processedPlans,
623
+ initialPlanRefRef.current,
624
+ autoSelectFirstPaidRef.current
625
+ );
626
+ setSelectedPlanIndexState(idx);
627
+ }, []);
628
+ const fetchPlans = (0, import_react5.useCallback)(
629
+ async (force = false) => {
630
+ if (!productRef) {
631
+ setError(new Error("Product reference not configured"));
632
+ setLoading(false);
633
+ return;
634
+ }
635
+ const cached = plansCache.get(productRef);
636
+ const now = Date.now();
637
+ if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
638
+ const processedPlans = processPlans(
639
+ cached.plans,
640
+ filterRef.current,
641
+ sortByRef.current
642
+ );
643
+ setPlans(processedPlans);
644
+ setLoading(false);
645
+ setError(null);
646
+ applyInitialSelection(processedPlans);
647
+ return;
648
+ }
649
+ if (cached?.promise) {
650
+ try {
651
+ setLoading(true);
652
+ const fetchedPlans = await cached.promise;
653
+ const processedPlans = processPlans(
654
+ fetchedPlans,
655
+ filterRef.current,
656
+ sortByRef.current
657
+ );
658
+ setPlans(processedPlans);
659
+ setError(null);
660
+ applyInitialSelection(processedPlans);
661
+ } catch (err) {
662
+ setError(err instanceof Error ? err : new Error("Failed to load plans"));
663
+ } finally {
664
+ setLoading(false);
665
+ }
666
+ return;
667
+ }
668
+ try {
669
+ setLoading(true);
670
+ setError(null);
671
+ const fetchPromise = fetcherRef.current(productRef);
672
+ plansCache.set(productRef, { plans: [], timestamp: now, promise: fetchPromise });
673
+ const fetchedPlans = await fetchPromise;
674
+ plansCache.set(productRef, { plans: fetchedPlans, timestamp: now, promise: null });
675
+ const processedPlans = processPlans(
676
+ fetchedPlans,
677
+ filterRef.current,
678
+ sortByRef.current
679
+ );
680
+ setPlans(processedPlans);
681
+ applyInitialSelection(processedPlans);
682
+ } catch (err) {
683
+ plansCache.delete(productRef);
684
+ setError(err instanceof Error ? err : new Error("Failed to load plans"));
685
+ } finally {
686
+ setLoading(false);
687
+ }
688
+ },
689
+ [productRef, applyInitialSelection]
690
+ );
691
+ (0, import_react5.useEffect)(() => {
692
+ fetchPlans();
693
+ }, [fetchPlans]);
694
+ (0, import_react5.useEffect)(() => {
695
+ if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
696
+ if (!selectionReady || plans.length === 0) return;
697
+ applyInitialSelection(plans);
698
+ }, [selectionReady, plans, applyInitialSelection]);
699
+ const selectedPlan = (0, import_react5.useMemo)(() => plans[selectedPlanIndex] || null, [plans, selectedPlanIndex]);
700
+ const selectPlan = (0, import_react5.useCallback)(
701
+ (planRef) => {
702
+ const index = plans.findIndex((p) => p.reference === planRef);
703
+ if (index >= 0) {
704
+ setSelectedPlanIndex(index);
705
+ }
706
+ },
707
+ [plans, setSelectedPlanIndex]
708
+ );
709
+ return {
710
+ plans,
711
+ loading,
712
+ error,
713
+ selectedPlanIndex,
714
+ selectedPlan,
715
+ setSelectedPlanIndex,
716
+ selectPlan,
717
+ refetch: () => fetchPlans(true),
718
+ isSelectionReady: hasAppliedInitialRef.current
719
+ };
720
+ }
721
+
722
+ // src/hooks/usePlan.ts
723
+ async function listPlans(productRef, config) {
724
+ const base = config?.api?.listPlans || "/api/list-plans";
725
+ const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
726
+ const fetchFn = config?.fetch || fetch;
727
+ const { headers } = await buildRequestHeaders(config);
728
+ const res = await fetchFn(url, { method: "GET", headers });
729
+ if (!res.ok) {
730
+ const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
731
+ config?.onError?.(error, "listPlans");
732
+ throw error;
733
+ }
734
+ const data = await res.json();
735
+ return data.plans ?? [];
736
+ }
737
+ function usePlan(options) {
738
+ const { planRef, productRef } = options;
739
+ const { _config } = useSolvaPay();
740
+ const findPlan = (0, import_react6.useCallback)(
741
+ (plans) => {
742
+ if (!planRef) return null;
743
+ return plans.find((p) => p.reference === planRef) || null;
744
+ },
745
+ [planRef]
746
+ );
747
+ const [plan, setPlan] = (0, import_react6.useState)(() => {
748
+ if (!planRef || !productRef) return null;
749
+ const cached = plansCache.get(productRef);
750
+ if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2) return null;
751
+ return findPlan(cached.plans);
752
+ });
753
+ const [loading, setLoading] = (0, import_react6.useState)(() => !!(planRef && !plan));
754
+ const [error, setError] = (0, import_react6.useState)(null);
755
+ const load = (0, import_react6.useCallback)(
756
+ async (force = false) => {
757
+ if (!planRef) {
758
+ setPlan(null);
759
+ setLoading(false);
760
+ setError(null);
761
+ return;
762
+ }
763
+ if (!productRef) {
764
+ setLoading(false);
765
+ setError(
766
+ new Error("usePlan: productRef is required to resolve a plan reference")
767
+ );
768
+ return;
769
+ }
770
+ const cached = plansCache.get(productRef);
771
+ const now = Date.now();
772
+ if (!force && cached?.plans.length && now - cached.timestamp < CACHE_DURATION2) {
773
+ const p = findPlan(cached.plans);
774
+ setPlan(p);
775
+ setLoading(false);
776
+ setError(
777
+ p ? null : new Error(
778
+ `Plan "${planRef}" not found in product "${productRef}"`
779
+ )
780
+ );
781
+ return;
782
+ }
783
+ if (!force && cached?.promise) {
784
+ try {
785
+ setLoading(true);
786
+ const plans = await cached.promise;
787
+ const p = findPlan(plans);
788
+ setPlan(p);
789
+ setError(
790
+ p ? null : new Error(
791
+ `Plan "${planRef}" not found in product "${productRef}"`
792
+ )
793
+ );
794
+ } catch (err) {
795
+ setError(err instanceof Error ? err : new Error("Failed to load plan"));
796
+ } finally {
797
+ setLoading(false);
798
+ }
799
+ return;
800
+ }
801
+ try {
802
+ setLoading(true);
803
+ setError(null);
804
+ const promise = listPlans(productRef, _config);
805
+ plansCache.set(productRef, { plans: [], timestamp: now, promise });
806
+ const plans = await promise;
807
+ plansCache.set(productRef, { plans, timestamp: now, promise: null });
808
+ const p = findPlan(plans);
809
+ setPlan(p);
810
+ if (!p) {
811
+ setError(new Error(`Plan "${planRef}" not found in product "${productRef}"`));
812
+ }
813
+ } catch (err) {
814
+ plansCache.delete(productRef);
815
+ setError(err instanceof Error ? err : new Error("Failed to load plan"));
816
+ } finally {
817
+ setLoading(false);
818
+ }
819
+ },
820
+ [planRef, productRef, _config, findPlan]
821
+ );
822
+ (0, import_react6.useEffect)(() => {
823
+ load();
824
+ }, [load]);
825
+ return {
826
+ plan,
827
+ loading,
828
+ error,
829
+ refetch: () => load(true)
830
+ };
831
+ }
832
+
833
+ // src/hooks/useProduct.ts
834
+ var import_react7 = require("react");
835
+ var productCache = /* @__PURE__ */ new Map();
836
+ var CACHE_DURATION3 = 5 * 60 * 1e3;
837
+ function routeFor(config) {
838
+ return config?.api?.getProduct || "/api/get-product";
839
+ }
840
+ async function fetchProduct(productRef, config) {
841
+ const base = routeFor(config);
842
+ const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
843
+ const fetchFn = config?.fetch || fetch;
844
+ const { headers } = await buildRequestHeaders(config);
845
+ const res = await fetchFn(url, { method: "GET", headers });
846
+ if (!res.ok) {
847
+ const error = new Error(`Failed to fetch product: ${res.statusText || res.status}`);
848
+ config?.onError?.(error, "getProduct");
849
+ throw error;
850
+ }
851
+ return await res.json();
852
+ }
853
+ function useProduct(productRef) {
854
+ const { _config } = useSolvaPay();
855
+ const cacheKey = productRef || "";
856
+ const [product, setProduct] = (0, import_react7.useState)(
857
+ () => productRef ? productCache.get(cacheKey)?.product ?? null : null
858
+ );
859
+ const [loading, setLoading] = (0, import_react7.useState)(() => {
860
+ if (!productRef) return false;
861
+ const cached = productCache.get(cacheKey);
862
+ return !cached || !cached.product && !cached.promise;
863
+ });
864
+ const [error, setError] = (0, import_react7.useState)(null);
865
+ const load = (0, import_react7.useCallback)(
866
+ async (force = false) => {
867
+ if (!productRef) {
868
+ setProduct(null);
869
+ setLoading(false);
870
+ setError(null);
871
+ return;
872
+ }
873
+ const cached = productCache.get(productRef);
874
+ const now = Date.now();
875
+ if (!force && cached?.product && now - cached.timestamp < CACHE_DURATION3) {
876
+ setProduct(cached.product);
877
+ setLoading(false);
878
+ setError(null);
879
+ return;
880
+ }
881
+ if (!force && cached?.promise) {
882
+ try {
883
+ setLoading(true);
884
+ const p = await cached.promise;
885
+ setProduct(p);
886
+ setError(null);
887
+ } catch (err) {
888
+ setError(err instanceof Error ? err : new Error("Failed to load product"));
889
+ } finally {
890
+ setLoading(false);
891
+ }
892
+ return;
893
+ }
894
+ try {
895
+ setLoading(true);
896
+ setError(null);
897
+ const promise = fetchProduct(productRef, _config);
898
+ productCache.set(productRef, { product: null, promise, timestamp: now });
899
+ const p = await promise;
900
+ productCache.set(productRef, { product: p, promise: null, timestamp: now });
901
+ setProduct(p);
902
+ } catch (err) {
903
+ productCache.delete(productRef);
904
+ setError(err instanceof Error ? err : new Error("Failed to load product"));
905
+ } finally {
906
+ setLoading(false);
907
+ }
908
+ },
909
+ [productRef, _config]
910
+ );
911
+ (0, import_react7.useEffect)(() => {
912
+ load();
913
+ }, [load]);
914
+ return {
915
+ product,
916
+ loading,
917
+ error,
918
+ refetch: () => load(true)
919
+ };
920
+ }
921
+
922
+ // src/hooks/useCopy.ts
923
+ function useCopy() {
924
+ return useCopyContext().copy;
925
+ }
926
+ function useLocale() {
927
+ return useCopyContext().locale;
928
+ }
929
+
930
+ // src/utils/format.ts
931
+ var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
932
+ "bif",
933
+ "clp",
934
+ "djf",
935
+ "gnf",
936
+ "jpy",
937
+ "kmf",
938
+ "krw",
939
+ "mga",
940
+ "pyg",
941
+ "rwf",
942
+ "ugx",
943
+ "vnd",
944
+ "vuv",
945
+ "xaf",
946
+ "xof",
947
+ "xpf"
948
+ ]);
949
+ function getFractionDigits(currency) {
950
+ return ZERO_DECIMAL_CURRENCIES.has(currency.toLowerCase()) ? 0 : 2;
951
+ }
952
+ function toMajorUnits(amountMinor, currency) {
953
+ const fractionDigits = getFractionDigits(currency);
954
+ return fractionDigits === 0 ? amountMinor : amountMinor / 100;
955
+ }
956
+ function formatPrice(amountMinor, currency, opts = {}) {
957
+ const { locale, interval, intervalCount = 1, free = "Free" } = opts;
958
+ if (amountMinor === 0 && free !== "") {
959
+ return free;
960
+ }
961
+ const fractionDigits = getFractionDigits(currency);
962
+ const major = toMajorUnits(amountMinor, currency);
963
+ const formatter = new Intl.NumberFormat(locale, {
964
+ style: "currency",
965
+ currency: currency.toUpperCase(),
966
+ minimumFractionDigits: fractionDigits,
967
+ maximumFractionDigits: fractionDigits
968
+ });
969
+ const formatted = formatter.format(major);
970
+ if (!interval) return formatted;
971
+ const suffix = intervalCount > 1 ? `${intervalCount} ${interval}s` : interval;
972
+ return `${formatted} / ${suffix}`;
973
+ }
974
+
975
+ // src/i18n/interpolate.ts
976
+ function interpolate(template, vars) {
977
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
978
+ const value = vars[key];
979
+ if (value === void 0 || value === null) return match;
980
+ return String(value);
981
+ });
982
+ }
983
+
984
+ // src/components/PlanSelectionContext.tsx
985
+ var import_react8 = require("react");
986
+ var import_jsx_runtime4 = require("react/jsx-runtime");
987
+ var PlanSelectionContext = (0, import_react8.createContext)(null);
988
+ var PlanSelectionProvider = ({
989
+ value,
990
+ children
991
+ }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PlanSelectionContext.Provider, { value, children });
992
+ function usePlanSelection() {
993
+ return (0, import_react8.useContext)(PlanSelectionContext);
994
+ }
995
+
996
+ // src/utils/errors.ts
997
+ var DOCS_BASE_URL = "https://solvapay.com/docs";
998
+ var SolvaPayError = class extends Error {
999
+ code;
1000
+ docsUrl;
1001
+ constructor(code, message, docsUrl) {
1002
+ super(message);
1003
+ this.name = "SolvaPayError";
1004
+ this.code = code;
1005
+ this.docsUrl = docsUrl;
1006
+ }
1007
+ };
1008
+ var MissingProviderError = class extends SolvaPayError {
1009
+ constructor(primitiveName) {
1010
+ const docsUrl = `${DOCS_BASE_URL}/troubleshooting/missing-provider`;
1011
+ super(
1012
+ "MISSING_PROVIDER",
1013
+ `${primitiveName} must be rendered inside <SolvaPayProvider>. Wrap your app (or the section using ${primitiveName}) with <SolvaPayProvider config={{ ... }}>. See ${docsUrl}`,
1014
+ docsUrl
1015
+ );
1016
+ this.name = "MissingProviderError";
1017
+ }
1018
+ };
1019
+ var MissingProductRefError = class extends SolvaPayError {
1020
+ constructor(primitiveName) {
1021
+ const docsUrl = `${DOCS_BASE_URL}/primitives/product-ref`;
1022
+ super(
1023
+ "MISSING_PRODUCT_REF",
1024
+ `${primitiveName} requires a productRef. Pass the \`productRef\` prop (or set NEXT_PUBLIC_SOLVAPAY_PRODUCT_REF). See ${docsUrl}`,
1025
+ docsUrl
1026
+ );
1027
+ this.name = "MissingProductRefError";
1028
+ }
1029
+ };
1030
+
1031
+ // src/primitives/CheckoutSummary.tsx
1032
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1033
+ var CheckoutSummaryContext = (0, import_react9.createContext)(null);
1034
+ function useCheckoutSummaryContext(part) {
1035
+ const ctx = (0, import_react9.useContext)(CheckoutSummaryContext);
1036
+ if (!ctx) {
1037
+ throw new Error(
1038
+ `CheckoutSummary.${part} must be rendered inside <CheckoutSummary.Root>.`
1039
+ );
1040
+ }
1041
+ return ctx;
1042
+ }
1043
+ var Root = (0, import_react9.forwardRef)(function CheckoutSummaryRoot({ planRef, productRef, asChild, children, ...rest }, forwardedRef) {
1044
+ const solva = (0, import_react9.useContext)(SolvaPayContext);
1045
+ if (!solva) throw new MissingProviderError("CheckoutSummary");
1046
+ const locale = useLocale();
1047
+ const copy = useCopy();
1048
+ const planSelection = usePlanSelection();
1049
+ const resolvedPlanRef = planRef ?? planSelection?.selectedPlanRef ?? void 0;
1050
+ const resolvedProductRef = productRef ?? planSelection?.productRef;
1051
+ const { plan, loading: planLoading } = usePlan({
1052
+ planRef: resolvedPlanRef,
1053
+ productRef: resolvedProductRef
1054
+ });
1055
+ const { product, loading: productLoading } = useProduct(resolvedProductRef);
1056
+ const loading = planLoading || productLoading;
1057
+ const priceFormatted = (0, import_react9.useMemo)(() => {
1058
+ const price = plan?.price ?? 0;
1059
+ const currency = plan?.currency ?? "usd";
1060
+ return formatPrice(price, currency, {
1061
+ locale,
1062
+ interval: plan?.interval,
1063
+ intervalCount: 1,
1064
+ free: copy.interval.free
1065
+ });
1066
+ }, [plan, locale, copy.interval.free]);
1067
+ const trialBanner = (0, import_react9.useMemo)(() => {
1068
+ const trialDays = plan?.trialDays;
1069
+ if (!trialDays || trialDays <= 0) return null;
1070
+ return interpolate(copy.interval.trial, { trialDays });
1071
+ }, [plan?.trialDays, copy.interval.trial]);
1072
+ const ctx = (0, import_react9.useMemo)(
1073
+ () => ({ plan, product, priceFormatted, trialBanner, loading }),
1074
+ [plan, product, priceFormatted, trialBanner, loading]
1075
+ );
1076
+ const Comp = asChild ? Slot : "div";
1077
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Comp, { ref: forwardedRef, "data-solvapay-checkout-summary": "", ...rest, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckoutSummaryContext.Provider, { value: ctx, children }) });
1078
+ });
1079
+ var ProductSlot = (0, import_react9.forwardRef)(function CheckoutSummaryProduct({ asChild, children, ...rest }, forwardedRef) {
1080
+ const ctx = useCheckoutSummaryContext("Product");
1081
+ const name = ctx.product?.name;
1082
+ if (!name) return null;
1083
+ const Comp = asChild ? Slot : "span";
1084
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Comp, { ref: forwardedRef, "data-solvapay-checkout-summary-product": "", ...rest, children: children ?? name });
1085
+ });
1086
+ var PlanSlot = (0, import_react9.forwardRef)(function CheckoutSummaryPlan({ asChild, children, ...rest }, forwardedRef) {
1087
+ const ctx = useCheckoutSummaryContext("Plan");
1088
+ const name = ctx.plan?.name;
1089
+ if (!name) return null;
1090
+ const Comp = asChild ? Slot : "span";
1091
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Comp, { ref: forwardedRef, "data-solvapay-checkout-summary-plan": "", ...rest, children: children ?? name });
1092
+ });
1093
+ var PriceSlot = (0, import_react9.forwardRef)(function CheckoutSummaryPrice({ asChild, children, ...rest }, forwardedRef) {
1094
+ const ctx = useCheckoutSummaryContext("Price");
1095
+ const Comp = asChild ? Slot : "span";
1096
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Comp, { ref: forwardedRef, "data-solvapay-checkout-summary-price": "", ...rest, children: children ?? ctx.priceFormatted });
1097
+ });
1098
+ var TrialSlot = (0, import_react9.forwardRef)(function CheckoutSummaryTrial({ asChild, children, ...rest }, forwardedRef) {
1099
+ const ctx = useCheckoutSummaryContext("Trial");
1100
+ if (!ctx.trialBanner) return null;
1101
+ const Comp = asChild ? Slot : "span";
1102
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Comp, { ref: forwardedRef, "data-solvapay-checkout-summary-trial": "", ...rest, children: children ?? ctx.trialBanner });
1103
+ });
1104
+ var TaxNoteSlot = (0, import_react9.forwardRef)(function CheckoutSummaryTaxNote({ asChild, children, ...rest }, forwardedRef) {
1105
+ useCheckoutSummaryContext("TaxNote");
1106
+ const Comp = asChild ? Slot : "span";
1107
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Comp, { ref: forwardedRef, "data-solvapay-checkout-summary-tax-note": "", ...rest, children: children ?? "Taxes calculated at checkout" });
1108
+ });
1109
+ var CheckoutSummaryRoot2 = Root;
1110
+ var CheckoutSummaryProduct2 = ProductSlot;
1111
+ var CheckoutSummaryPlan2 = PlanSlot;
1112
+ var CheckoutSummaryPrice2 = PriceSlot;
1113
+ var CheckoutSummaryTrial2 = TrialSlot;
1114
+ var CheckoutSummaryTaxNote2 = TaxNoteSlot;
1115
+ var CheckoutSummary = {
1116
+ Root,
1117
+ Product: ProductSlot,
1118
+ Plan: PlanSlot,
1119
+ Price: PriceSlot,
1120
+ Trial: TrialSlot,
1121
+ TaxNote: TaxNoteSlot
1122
+ };
1123
+ function useCheckoutSummary() {
1124
+ return useCheckoutSummaryContext("useCheckoutSummary");
1125
+ }
1126
+
1127
+ // src/primitives/PlanSelector.tsx
1128
+ var import_react10 = require("react");
1129
+
1130
+ // src/hooks/usePurchase.ts
1131
+ function usePurchase() {
1132
+ const { purchase, refetchPurchase } = useSolvaPay();
1133
+ return {
1134
+ ...purchase,
1135
+ refetch: refetchPurchase
1136
+ };
1137
+ }
1138
+
1139
+ // src/primitives/PlanSelector.tsx
1140
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1141
+ var PlanSelectorContext = (0, import_react10.createContext)(null);
1142
+ function usePlanSelectorContext(part) {
1143
+ const ctx = (0, import_react10.useContext)(PlanSelectorContext);
1144
+ if (!ctx) {
1145
+ throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Root>.`);
1146
+ }
1147
+ return ctx;
1148
+ }
1149
+ var CardContext = (0, import_react10.createContext)(null);
1150
+ function useCardContext(part) {
1151
+ const ctx = (0, import_react10.useContext)(CardContext);
1152
+ if (!ctx) {
1153
+ throw new Error(`PlanSelector.${part} must be rendered inside <PlanSelector.Card>.`);
1154
+ }
1155
+ return ctx;
1156
+ }
1157
+ async function defaultListPlans(productRef, config) {
1158
+ const base = config?.api?.listPlans || "/api/list-plans";
1159
+ const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
1160
+ const fetchFn = config?.fetch || fetch;
1161
+ const { headers } = await buildRequestHeaders(config);
1162
+ const res = await fetchFn(url, { method: "GET", headers });
1163
+ if (!res.ok) {
1164
+ const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
1165
+ config?.onError?.(error, "listPlans");
1166
+ throw error;
1167
+ }
1168
+ const data = await res.json();
1169
+ return data.plans ?? [];
1170
+ }
1171
+ var Root2 = (0, import_react10.forwardRef)(function PlanSelectorRoot(props, forwardedRef) {
1172
+ const {
1173
+ productRef,
1174
+ fetcher,
1175
+ filter,
1176
+ sortBy,
1177
+ autoSelectFirstPaid = true,
1178
+ initialPlanRef,
1179
+ currentPlanRef,
1180
+ popularPlanRef,
1181
+ onSelect,
1182
+ children,
1183
+ ...rest
1184
+ } = props;
1185
+ const solva = (0, import_react10.useContext)(SolvaPayContext);
1186
+ if (!solva) throw new MissingProviderError("PlanSelector");
1187
+ if (!productRef) throw new MissingProductRefError("PlanSelector");
1188
+ const { _config } = solva;
1189
+ const { purchases } = usePurchase();
1190
+ const effectiveFetcher = (0, import_react10.useMemo)(
1191
+ () => fetcher ?? ((ref) => defaultListPlans(ref, _config)),
1192
+ [fetcher, _config]
1193
+ );
1194
+ const { plans, selectedPlan, selectPlan, loading, error } = usePlans({
1195
+ productRef,
1196
+ fetcher: effectiveFetcher,
1197
+ filter,
1198
+ sortBy,
1199
+ autoSelectFirstPaid,
1200
+ initialPlanRef
1201
+ });
1202
+ const autoCurrentPlanRef = (0, import_react10.useMemo)(() => {
1203
+ const active = purchases.filter((p) => p.status === "active").sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())[0];
1204
+ return active?.planSnapshot?.reference ?? null;
1205
+ }, [purchases]);
1206
+ const resolvedCurrentPlanRef = currentPlanRef === null ? null : currentPlanRef ?? autoCurrentPlanRef;
1207
+ const isCurrent = (0, import_react10.useCallback)(
1208
+ (ref) => resolvedCurrentPlanRef === ref,
1209
+ [resolvedCurrentPlanRef]
1210
+ );
1211
+ const isFree = (0, import_react10.useCallback)(
1212
+ (ref) => plans.find((p) => p.reference === ref)?.requiresPayment === false,
1213
+ [plans]
1214
+ );
1215
+ const isPopular = (0, import_react10.useCallback)(
1216
+ (ref) => popularPlanRef === ref,
1217
+ [popularPlanRef]
1218
+ );
1219
+ const select = (0, import_react10.useCallback)(
1220
+ (ref) => {
1221
+ const plan = plans.find((p) => p.reference === ref);
1222
+ if (!plan) return;
1223
+ if (plan.requiresPayment === false) return;
1224
+ selectPlan(ref);
1225
+ onSelect?.(ref, plan);
1226
+ },
1227
+ [plans, selectPlan, onSelect]
1228
+ );
1229
+ const selectedPlanRef = selectedPlan?.reference ?? null;
1230
+ const ctx = (0, import_react10.useMemo)(
1231
+ () => ({
1232
+ plans,
1233
+ loading,
1234
+ error,
1235
+ selectedPlan: selectedPlan ?? null,
1236
+ selectedPlanRef,
1237
+ popularPlanRef,
1238
+ currentPlanRef: resolvedCurrentPlanRef,
1239
+ isCurrent,
1240
+ isFree,
1241
+ isPopular,
1242
+ select
1243
+ }),
1244
+ [
1245
+ plans,
1246
+ loading,
1247
+ error,
1248
+ selectedPlan,
1249
+ selectedPlanRef,
1250
+ popularPlanRef,
1251
+ resolvedCurrentPlanRef,
1252
+ isCurrent,
1253
+ isFree,
1254
+ isPopular,
1255
+ select
1256
+ ]
1257
+ );
1258
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1259
+ PlanSelectionProvider,
1260
+ {
1261
+ value: {
1262
+ productRef,
1263
+ selectedPlanRef,
1264
+ setSelectedPlanRef: (ref) => {
1265
+ if (ref) select(ref);
1266
+ },
1267
+ plans,
1268
+ loading,
1269
+ error
1270
+ },
1271
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: forwardedRef, "data-solvapay-plan-selector": "", ...rest, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PlanSelectorContext.Provider, { value: ctx, children }) })
1272
+ }
1273
+ );
1274
+ });
1275
+ var Heading = (0, import_react10.forwardRef)(function PlanSelectorHeading({ asChild, children, ...rest }, forwardedRef) {
1276
+ usePlanSelectorContext("Heading");
1277
+ const copy = useCopy();
1278
+ const Comp = asChild ? Slot : "h3";
1279
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-heading": "", ...rest, children: children ?? copy.planSelector.heading });
1280
+ });
1281
+ var Grid = (0, import_react10.forwardRef)(function PlanSelectorGrid({ children, ...rest }, forwardedRef) {
1282
+ const ctx = usePlanSelectorContext("Grid");
1283
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: forwardedRef, "data-solvapay-plan-selector-grid": "", ...rest, children: ctx.plans.map((plan) => {
1284
+ const isCurrent = ctx.isCurrent(plan.reference);
1285
+ const isFree = ctx.isFree(plan.reference);
1286
+ const isPopular = ctx.isPopular(plan.reference);
1287
+ const selected = ctx.selectedPlanRef === plan.reference;
1288
+ const disabled = isCurrent || isFree;
1289
+ const state = isCurrent ? "current" : isFree ? "disabled" : selected ? "selected" : "idle";
1290
+ const cardCtx = {
1291
+ plan,
1292
+ state,
1293
+ isCurrent,
1294
+ isFree,
1295
+ isPopular,
1296
+ disabled,
1297
+ select: () => ctx.select(plan.reference)
1298
+ };
1299
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardContext.Provider, { value: cardCtx, children }, plan.reference);
1300
+ }) });
1301
+ });
1302
+ var Card = (0, import_react10.forwardRef)(function PlanSelectorCard({ asChild, onClick, children, ...rest }, forwardedRef) {
1303
+ const card = useCardContext("Card");
1304
+ const dataTrial = !!(card.plan.trialDays && card.plan.trialDays > 0);
1305
+ const commonProps = {
1306
+ "data-solvapay-plan-selector-card": "",
1307
+ "data-state": card.state,
1308
+ "data-free": card.isFree ? "" : void 0,
1309
+ "data-popular": card.isPopular ? "" : void 0,
1310
+ "data-trial": dataTrial ? "" : void 0,
1311
+ "aria-disabled": card.disabled || void 0,
1312
+ onClick: composeEventHandlers(onClick, () => {
1313
+ if (!card.disabled) card.select();
1314
+ }),
1315
+ ...rest
1316
+ };
1317
+ if (asChild) {
1318
+ return (
1319
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1320
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Slot, { ref: forwardedRef, ...commonProps, children })
1321
+ );
1322
+ }
1323
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1324
+ "button",
1325
+ {
1326
+ ref: forwardedRef,
1327
+ type: "button",
1328
+ disabled: card.disabled,
1329
+ ...commonProps,
1330
+ children
1331
+ }
1332
+ );
1333
+ });
1334
+ var CardName = (0, import_react10.forwardRef)(function PlanSelectorCardName({ asChild, children, ...rest }, forwardedRef) {
1335
+ const card = useCardContext("CardName");
1336
+ if (!card.plan.name) return null;
1337
+ const Comp = asChild ? Slot : "span";
1338
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-name": "", ...rest, children: children ?? card.plan.name });
1339
+ });
1340
+ var CardPrice = (0, import_react10.forwardRef)(function PlanSelectorCardPrice({ asChild, children, ...rest }, forwardedRef) {
1341
+ const card = useCardContext("CardPrice");
1342
+ const locale = useLocale();
1343
+ const copy = useCopy();
1344
+ const formatted = (0, import_react10.useMemo)(() => {
1345
+ if (card.isFree) return copy.planSelector.freeBadge;
1346
+ return formatPrice(card.plan.price ?? 0, card.plan.currency ?? "usd", {
1347
+ locale,
1348
+ free: copy.interval.free
1349
+ });
1350
+ }, [
1351
+ card.isFree,
1352
+ card.plan.price,
1353
+ card.plan.currency,
1354
+ locale,
1355
+ copy.planSelector.freeBadge,
1356
+ copy.interval.free
1357
+ ]);
1358
+ const Comp = asChild ? Slot : "span";
1359
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-price": "", ...rest, children: children ?? formatted });
1360
+ });
1361
+ var CardInterval = (0, import_react10.forwardRef)(function PlanSelectorCardInterval({ asChild, children, ...rest }, forwardedRef) {
1362
+ const card = useCardContext("CardInterval");
1363
+ const copy = useCopy();
1364
+ if (card.isFree || !card.plan.interval) return null;
1365
+ const text = interpolate(copy.planSelector.perIntervalShort, { interval: card.plan.interval });
1366
+ const Comp = asChild ? Slot : "span";
1367
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-card-interval": "", ...rest, children: children ?? text });
1368
+ });
1369
+ var CardBadge = (0, import_react10.forwardRef)(function PlanSelectorCardBadge({ asChild, children, ...rest }, forwardedRef) {
1370
+ const card = useCardContext("CardBadge");
1371
+ const copy = useCopy();
1372
+ let variant = null;
1373
+ let label = "";
1374
+ if (card.isCurrent) {
1375
+ variant = "current";
1376
+ label = copy.planSelector.currentBadge;
1377
+ } else if (card.isPopular && !card.isFree) {
1378
+ variant = "popular";
1379
+ label = copy.planSelector.popularBadge;
1380
+ }
1381
+ if (!variant) return null;
1382
+ const Comp = asChild ? Slot : "span";
1383
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1384
+ Comp,
1385
+ {
1386
+ ref: forwardedRef,
1387
+ "data-solvapay-plan-selector-card-badge": "",
1388
+ "data-variant": variant,
1389
+ ...rest,
1390
+ children: children ?? label
1391
+ }
1392
+ );
1393
+ });
1394
+ var Loading = (0, import_react10.forwardRef)(function PlanSelectorLoading({ asChild, children, ...rest }, forwardedRef) {
1395
+ const ctx = usePlanSelectorContext("Loading");
1396
+ if (!ctx.loading || ctx.plans.length > 0) return null;
1397
+ const Comp = asChild ? Slot : "div";
1398
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-loading": "", ...rest, children });
1399
+ });
1400
+ var ErrorSlot = (0, import_react10.forwardRef)(function PlanSelectorError({ asChild, children, ...rest }, forwardedRef) {
1401
+ const ctx = usePlanSelectorContext("Error");
1402
+ if (!ctx.error) return null;
1403
+ const Comp = asChild ? Slot : "div";
1404
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Comp, { ref: forwardedRef, "data-solvapay-plan-selector-error": "", role: "alert", ...rest, children: children ?? ctx.error.message });
1405
+ });
1406
+ var PlanSelectorRoot2 = Root2;
1407
+ var PlanSelectorHeading2 = Heading;
1408
+ var PlanSelectorGrid2 = Grid;
1409
+ var PlanSelectorCard2 = Card;
1410
+ var PlanSelectorCardName2 = CardName;
1411
+ var PlanSelectorCardPrice2 = CardPrice;
1412
+ var PlanSelectorCardInterval2 = CardInterval;
1413
+ var PlanSelectorCardBadge2 = CardBadge;
1414
+ var PlanSelectorLoading2 = Loading;
1415
+ var PlanSelectorError2 = ErrorSlot;
1416
+ var PlanSelector = {
1417
+ Root: Root2,
1418
+ Heading,
1419
+ Grid,
1420
+ Card,
1421
+ CardName,
1422
+ CardPrice,
1423
+ CardInterval,
1424
+ CardBadge,
1425
+ Loading,
1426
+ Error: ErrorSlot
1427
+ };
1428
+ function usePlanSelector() {
1429
+ return usePlanSelectorContext("usePlanSelector");
1430
+ }
1431
+
1432
+ // src/primitives/PaymentForm.tsx
1433
+ var import_react16 = require("react");
1434
+ var import_react_stripe_js = require("@stripe/react-stripe-js");
1435
+
1436
+ // src/hooks/useCheckout.ts
1437
+ var import_react11 = require("react");
1438
+ var import_stripe_js = require("@stripe/stripe-js");
1439
+ var stripePromiseCache = /* @__PURE__ */ new Map();
1440
+ function getStripeCacheKey(publishableKey, accountId) {
1441
+ return accountId ? `${publishableKey}:${accountId}` : publishableKey;
1442
+ }
1443
+ async function resolvePlanRef(productRef, fetchFn, headers, listPlansRoute) {
1444
+ const url = `${listPlansRoute}?productRef=${encodeURIComponent(productRef)}`;
1445
+ const res = await fetchFn(url, { method: "GET", headers });
1446
+ if (!res.ok) {
1447
+ throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
1448
+ }
1449
+ const data = await res.json();
1450
+ const allPlans = data.plans ?? [];
1451
+ const activePlans = allPlans.filter((p) => p.isActive !== false && p.status !== "inactive");
1452
+ if (activePlans.length === 0) {
1453
+ throw new Error(
1454
+ `No active plans found for product "${productRef}". Configure at least one plan in the SolvaPay Console.`
1455
+ );
1456
+ }
1457
+ if (activePlans.length === 1) {
1458
+ return activePlans[0].reference;
1459
+ }
1460
+ const defaultPlan = activePlans.find((p) => p.default === true);
1461
+ if (defaultPlan) {
1462
+ return defaultPlan.reference;
1463
+ }
1464
+ throw new Error(
1465
+ `Product "${productRef}" has ${activePlans.length} active plans but none is marked as default. Either pass planRef explicitly, use <PricingSelector> for user selection, or mark one plan as default in the SolvaPay Console.`
1466
+ );
1467
+ }
1468
+ function useCheckout(options) {
1469
+ const { planRef, productRef, customer } = options;
1470
+ const { createPayment, customerRef, updateCustomerRef, _config } = useSolvaPay();
1471
+ const [loading, setLoading] = (0, import_react11.useState)(false);
1472
+ const [error, setError] = (0, import_react11.useState)(null);
1473
+ const [stripePromise, setStripePromise] = (0, import_react11.useState)(null);
1474
+ const [clientSecret, setClientSecret] = (0, import_react11.useState)(null);
1475
+ const [resolvedPlanRef, setResolvedPlanRef] = (0, import_react11.useState)(planRef || null);
1476
+ const isStartingRef = (0, import_react11.useRef)(false);
1477
+ const startCheckout = (0, import_react11.useCallback)(async () => {
1478
+ if (isStartingRef.current || loading) {
1479
+ return;
1480
+ }
1481
+ if (!planRef && !productRef) {
1482
+ setError(
1483
+ new Error(
1484
+ "useCheckout: either planRef or productRef is required. Pass planRef directly, or pass productRef to auto-resolve the plan."
1485
+ )
1486
+ );
1487
+ return;
1488
+ }
1489
+ isStartingRef.current = true;
1490
+ setLoading(true);
1491
+ setError(null);
1492
+ try {
1493
+ let effectivePlanRef = planRef;
1494
+ if (!effectivePlanRef && productRef) {
1495
+ const listPlansRoute = _config?.api?.listPlans || "/api/list-plans";
1496
+ effectivePlanRef = await resolvePlanRef(productRef, fetch, {}, listPlansRoute);
1497
+ setResolvedPlanRef(effectivePlanRef);
1498
+ }
1499
+ if (!effectivePlanRef) {
1500
+ throw new Error("Could not determine plan reference for checkout");
1501
+ }
1502
+ const result = await createPayment({
1503
+ planRef: effectivePlanRef,
1504
+ productRef,
1505
+ customer
1506
+ });
1507
+ if (!result || typeof result !== "object") {
1508
+ throw new Error("Invalid payment intent response from server");
1509
+ }
1510
+ if (!result.clientSecret || typeof result.clientSecret !== "string") {
1511
+ throw new Error("Invalid client secret in payment intent response");
1512
+ }
1513
+ if (!result.publishableKey || typeof result.publishableKey !== "string") {
1514
+ throw new Error("Invalid publishable key in payment intent response");
1515
+ }
1516
+ if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
1517
+ updateCustomerRef(result.customerRef);
1518
+ }
1519
+ const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
1520
+ const cacheKey = getStripeCacheKey(result.publishableKey, result.accountId);
1521
+ let stripe = stripePromiseCache.get(cacheKey);
1522
+ if (!stripe) {
1523
+ stripe = (0, import_stripe_js.loadStripe)(result.publishableKey, stripeOptions);
1524
+ stripePromiseCache.set(cacheKey, stripe);
1525
+ }
1526
+ setStripePromise(stripe);
1527
+ setClientSecret(result.clientSecret);
1528
+ } catch (err) {
1529
+ const error2 = err instanceof Error ? err : new Error("Failed to start checkout");
1530
+ setError(error2);
1531
+ } finally {
1532
+ setLoading(false);
1533
+ isStartingRef.current = false;
1534
+ }
1535
+ }, [planRef, productRef, customer, createPayment, updateCustomerRef, loading, _config]);
1536
+ const reset = (0, import_react11.useCallback)(() => {
1537
+ isStartingRef.current = false;
1538
+ setLoading(false);
1539
+ setError(null);
1540
+ setStripePromise(null);
1541
+ setClientSecret(null);
1542
+ setResolvedPlanRef(planRef || null);
1543
+ }, [planRef]);
1544
+ return {
1545
+ loading,
1546
+ error,
1547
+ stripePromise,
1548
+ clientSecret,
1549
+ resolvedPlanRef,
1550
+ startCheckout,
1551
+ reset
1552
+ };
1553
+ }
1554
+
1555
+ // src/hooks/useCustomer.ts
1556
+ function useCustomer() {
1557
+ const { purchase, customerRef } = useSolvaPay();
1558
+ return {
1559
+ customerRef: purchase.customerRef || customerRef,
1560
+ email: purchase.email,
1561
+ name: purchase.name,
1562
+ loading: purchase.loading
1563
+ };
1564
+ }
1565
+
1566
+ // src/hooks/useActivation.ts
1567
+ var import_react12 = require("react");
1568
+ function useActivation() {
1569
+ const { activatePlan } = useSolvaPay();
1570
+ const copy = useCopy();
1571
+ const [state, setState] = (0, import_react12.useState)("idle");
1572
+ const [error, setError] = (0, import_react12.useState)(null);
1573
+ const [result, setResult] = (0, import_react12.useState)(null);
1574
+ const activate = (0, import_react12.useCallback)(
1575
+ async (params) => {
1576
+ setState("activating");
1577
+ setError(null);
1578
+ setResult(null);
1579
+ try {
1580
+ const data = await activatePlan(params);
1581
+ setResult(data);
1582
+ switch (data.status) {
1583
+ case "activated":
1584
+ case "already_active":
1585
+ setState("activated");
1586
+ break;
1587
+ case "topup_required":
1588
+ setState("topup_required");
1589
+ break;
1590
+ case "payment_required":
1591
+ setError(copy.activation.paymentRequired);
1592
+ setState("payment_required");
1593
+ break;
1594
+ case "invalid":
1595
+ setError(data.message || copy.activation.invalidConfiguration);
1596
+ setState("error");
1597
+ break;
1598
+ default:
1599
+ setError(copy.activation.unexpectedResponse);
1600
+ setState("error");
1601
+ }
1602
+ } catch (err) {
1603
+ setError(err instanceof Error ? err.message : copy.activation.failed);
1604
+ setState("error");
1605
+ }
1606
+ },
1607
+ [activatePlan, copy]
1608
+ );
1609
+ const reset = (0, import_react12.useCallback)(() => {
1610
+ setState("idle");
1611
+ setError(null);
1612
+ setResult(null);
1613
+ }, []);
1614
+ return { activate, state, error, result, reset };
1615
+ }
1616
+
1617
+ // src/components/PaymentFormContext.tsx
1618
+ var import_react13 = require("react");
1619
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1620
+ var PaymentFormContext = (0, import_react13.createContext)(null);
1621
+ function usePaymentForm() {
1622
+ const ctx = (0, import_react13.useContext)(PaymentFormContext);
1623
+ if (!ctx) {
1624
+ throw new Error(
1625
+ "PaymentForm subcomponents must be used inside a <PaymentForm>. Wrap the slots with <PaymentForm planRef=... productRef=...>."
1626
+ );
1627
+ }
1628
+ return ctx;
1629
+ }
1630
+ var PaymentFormProvider = ({ value, children }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaymentFormContext.Provider, { value, children });
1631
+
1632
+ // src/components/CheckoutSummary.tsx
1633
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1634
+ var CheckoutSummary2 = ({
1635
+ planRef,
1636
+ productRef,
1637
+ showTrial = true,
1638
+ showTaxNote = false,
1639
+ className
1640
+ }) => {
1641
+ const rootClass = ["solvapay-checkout-summary", className].filter(Boolean).join(" ");
1642
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CheckoutSummary.Root, { planRef, productRef, className: rootClass, children: [
1643
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckoutSummary.Product, { className: "solvapay-checkout-summary-product" }),
1644
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "solvapay-checkout-summary-row", children: [
1645
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckoutSummary.Plan, { className: "solvapay-checkout-summary-plan" }),
1646
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckoutSummary.Price, { className: "solvapay-checkout-summary-price" })
1647
+ ] }),
1648
+ showTrial && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckoutSummary.Trial, { className: "solvapay-checkout-summary-trial" }),
1649
+ showTaxNote && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckoutSummary.TaxNote, { className: "solvapay-checkout-summary-tax-note" })
1650
+ ] });
1651
+ };
1652
+
1653
+ // src/primitives/MandateText.tsx
1654
+ var import_react15 = require("react");
1655
+
1656
+ // src/hooks/useMerchant.ts
1657
+ var import_react14 = require("react");
1658
+ var merchantCache = /* @__PURE__ */ new Map();
1659
+ var CACHE_DURATION4 = 5 * 60 * 1e3;
1660
+ function cacheKeyFor(config) {
1661
+ return config?.api?.getMerchant || "/api/merchant";
1662
+ }
1663
+ async function fetchMerchant(config) {
1664
+ const route = cacheKeyFor(config);
1665
+ const fetchFn = config?.fetch || fetch;
1666
+ const { headers } = await buildRequestHeaders(config);
1667
+ const res = await fetchFn(route, { method: "GET", headers });
1668
+ if (!res.ok) {
1669
+ const error = new Error(`Failed to fetch merchant: ${res.statusText || res.status}`);
1670
+ config?.onError?.(error, "getMerchant");
1671
+ throw error;
1672
+ }
1673
+ return await res.json();
1674
+ }
1675
+ function useMerchant() {
1676
+ const { _config } = useSolvaPay();
1677
+ const key = cacheKeyFor(_config);
1678
+ const [merchant, setMerchant] = (0, import_react14.useState)(
1679
+ () => merchantCache.get(key)?.merchant ?? null
1680
+ );
1681
+ const [loading, setLoading] = (0, import_react14.useState)(() => {
1682
+ const cached = merchantCache.get(key);
1683
+ return !cached || !cached.merchant && !cached.promise;
1684
+ });
1685
+ const [error, setError] = (0, import_react14.useState)(null);
1686
+ const load = (0, import_react14.useCallback)(
1687
+ async (force = false) => {
1688
+ const cached = merchantCache.get(key);
1689
+ const now = Date.now();
1690
+ if (!force && cached?.merchant && now - cached.timestamp < CACHE_DURATION4) {
1691
+ setMerchant(cached.merchant);
1692
+ setLoading(false);
1693
+ setError(null);
1694
+ return;
1695
+ }
1696
+ if (!force && cached?.promise) {
1697
+ try {
1698
+ setLoading(true);
1699
+ const m = await cached.promise;
1700
+ setMerchant(m);
1701
+ setError(null);
1702
+ } catch (err) {
1703
+ setError(err instanceof Error ? err : new Error("Failed to load merchant"));
1704
+ } finally {
1705
+ setLoading(false);
1706
+ }
1707
+ return;
1708
+ }
1709
+ try {
1710
+ setLoading(true);
1711
+ setError(null);
1712
+ const promise = fetchMerchant(_config);
1713
+ merchantCache.set(key, { merchant: null, promise, timestamp: now });
1714
+ const m = await promise;
1715
+ merchantCache.set(key, { merchant: m, promise: null, timestamp: now });
1716
+ setMerchant(m);
1717
+ } catch (err) {
1718
+ merchantCache.delete(key);
1719
+ setError(err instanceof Error ? err : new Error("Failed to load merchant"));
1720
+ } finally {
1721
+ setLoading(false);
1722
+ }
1723
+ },
1724
+ [_config, key]
1725
+ );
1726
+ (0, import_react14.useEffect)(() => {
1727
+ load();
1728
+ }, [load]);
1729
+ return {
1730
+ merchant,
1731
+ loading,
1732
+ error,
1733
+ refetch: () => load(true)
1734
+ };
1735
+ }
1736
+
1737
+ // src/utils/checkoutVariant.ts
1738
+ function deriveVariant(plan, mode) {
1739
+ if (mode === "topup") return "topup";
1740
+ if (!plan?.type) return "oneTime";
1741
+ if (plan.type === "usage-based") {
1742
+ return plan.billingModel === "post-paid" ? "usageMetered" : "topup";
1743
+ }
1744
+ if (plan.requiresPayment === false) return "freeTier";
1745
+ switch (plan.type) {
1746
+ case "recurring":
1747
+ return "recurring";
1748
+ case "one-time":
1749
+ default:
1750
+ return "oneTime";
1751
+ }
1752
+ }
1753
+
1754
+ // src/primitives/MandateText.tsx
1755
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1756
+ var MandateText = (0, import_react15.forwardRef)(
1757
+ function MandateText2({
1758
+ planRef,
1759
+ productRef,
1760
+ variant,
1761
+ mode,
1762
+ amountMinor,
1763
+ currency,
1764
+ asChild,
1765
+ children,
1766
+ ...rest
1767
+ }, forwardedRef) {
1768
+ const solva = (0, import_react15.useContext)(SolvaPayContext);
1769
+ if (!solva) throw new MissingProviderError("MandateText");
1770
+ const locale = useLocale();
1771
+ const copy = useCopy();
1772
+ const planSelection = usePlanSelection();
1773
+ const resolvedPlanRef = planRef ?? planSelection?.selectedPlanRef ?? void 0;
1774
+ const resolvedProductRef = productRef ?? planSelection?.productRef;
1775
+ const { plan } = usePlan({ planRef: resolvedPlanRef, productRef: resolvedProductRef });
1776
+ const { product } = useProduct(resolvedProductRef);
1777
+ const { merchant } = useMerchant();
1778
+ const resolvedVariant = variant || deriveVariant(plan, mode);
1779
+ const effectiveAmount = amountMinor ?? plan?.price ?? 0;
1780
+ const effectiveCurrency = currency ?? plan?.currency ?? merchant?.defaultCurrency ?? "usd";
1781
+ const amountFormatted = formatPrice(effectiveAmount, effectiveCurrency, {
1782
+ locale,
1783
+ free: copy.interval.free
1784
+ });
1785
+ const ctx = (0, import_react15.useMemo)(
1786
+ () => ({
1787
+ merchant: {
1788
+ legalName: merchant?.legalName ?? merchant?.displayName ?? "",
1789
+ displayName: merchant?.displayName,
1790
+ supportEmail: merchant?.supportEmail,
1791
+ termsUrl: merchant?.termsUrl,
1792
+ privacyUrl: merchant?.privacyUrl
1793
+ },
1794
+ plan: plan ? {
1795
+ name: plan.name,
1796
+ interval: plan.interval,
1797
+ intervalCount: 1,
1798
+ trialDays: plan.trialDays,
1799
+ measures: plan.measures,
1800
+ billingCycle: plan.billingCycle
1801
+ } : void 0,
1802
+ product: product ? { name: product.name } : void 0,
1803
+ amountFormatted,
1804
+ trialDays: plan?.trialDays
1805
+ }),
1806
+ [merchant, plan, product, amountFormatted]
1807
+ );
1808
+ const template = copy.mandate[resolvedVariant];
1809
+ const text = typeof template === "function" ? template(ctx) : template;
1810
+ if (!text) return null;
1811
+ const Comp = asChild ? Slot : "p";
1812
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1813
+ Comp,
1814
+ {
1815
+ ref: forwardedRef,
1816
+ "data-solvapay-mandate-text": "",
1817
+ "data-variant": resolvedVariant,
1818
+ ...rest,
1819
+ children: children ?? text
1820
+ }
1821
+ );
1822
+ }
1823
+ );
1824
+
1825
+ // src/components/Spinner.tsx
1826
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1827
+ var Spinner = ({
1828
+ className = "",
1829
+ size = "md"
1830
+ }) => {
1831
+ const sizeClasses = {
1832
+ sm: "h-4 w-4",
1833
+ md: "h-5 w-5",
1834
+ lg: "h-6 w-6"
1835
+ };
1836
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1837
+ "svg",
1838
+ {
1839
+ className: `animate-spin ${sizeClasses[size]} ${className}`,
1840
+ xmlns: "http://www.w3.org/2000/svg",
1841
+ fill: "none",
1842
+ viewBox: "0 0 24 24",
1843
+ role: "status",
1844
+ "aria-busy": "true",
1845
+ children: [
1846
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
1847
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1848
+ "path",
1849
+ {
1850
+ className: "opacity-75",
1851
+ fill: "currentColor",
1852
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
1853
+ }
1854
+ )
1855
+ ]
1856
+ }
1857
+ );
1858
+ };
1859
+
1860
+ // src/utils/confirmPayment.ts
1861
+ async function confirmPayment(input) {
1862
+ const { stripe, elements, clientSecret, mode, returnUrl, billingDetails, copy } = input;
1863
+ try {
1864
+ if (mode === "payment-element") {
1865
+ const paymentElement = elements.getElement("payment");
1866
+ if (!paymentElement) {
1867
+ return { status: "error", message: copy.errors.cardElementMissing };
1868
+ }
1869
+ const { error: error2, paymentIntent: paymentIntent2 } = await stripe.confirmPayment({
1870
+ elements,
1871
+ clientSecret,
1872
+ confirmParams: {
1873
+ return_url: returnUrl,
1874
+ payment_method_data: billingDetails ? { billing_details: billingDetails } : void 0
1875
+ },
1876
+ redirect: "if_required"
1877
+ });
1878
+ if (error2) {
1879
+ return { status: "error", message: error2.message || copy.errors.paymentUnexpected };
1880
+ }
1881
+ return mapIntent(paymentIntent2, copy);
1882
+ }
1883
+ const cardElement = elements.getElement("card");
1884
+ if (!cardElement) {
1885
+ return { status: "error", message: copy.errors.cardElementMissing };
1886
+ }
1887
+ const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
1888
+ payment_method: {
1889
+ card: cardElement,
1890
+ billing_details: billingDetails
1891
+ }
1892
+ });
1893
+ if (error) {
1894
+ return { status: "error", message: error.message || copy.errors.paymentUnexpected };
1895
+ }
1896
+ return mapIntent(paymentIntent, copy);
1897
+ } catch (err) {
1898
+ return {
1899
+ status: "error",
1900
+ message: err instanceof Error ? err.message : copy.errors.paymentUnexpected
1901
+ };
1902
+ }
1903
+ }
1904
+ function mapIntent(paymentIntent, copy) {
1905
+ if (!paymentIntent) {
1906
+ return { status: "error", message: copy.errors.paymentUnexpected };
1907
+ }
1908
+ if (paymentIntent.status === "succeeded") {
1909
+ return { status: "succeeded", paymentIntent };
1910
+ }
1911
+ if (paymentIntent.status === "requires_action") {
1912
+ return { status: "requires_action", message: copy.errors.paymentRequires3ds };
1913
+ }
1914
+ return {
1915
+ status: "other",
1916
+ message: interpolate(copy.errors.paymentStatusPrefix, {
1917
+ status: paymentIntent.status || "processing"
1918
+ }),
1919
+ paymentIntent
1920
+ };
1921
+ }
1922
+
1923
+ // src/utils/processPaymentResult.ts
1924
+ async function reconcilePayment(input) {
1925
+ const { paymentIntentId, productRef, planRef, processPayment, refetchPurchase, copy } = input;
1926
+ if (!processPayment || !productRef) {
1927
+ try {
1928
+ await refetchPurchase();
1929
+ return { status: "success" };
1930
+ } catch (err) {
1931
+ return {
1932
+ status: "error",
1933
+ error: err instanceof Error ? err : new Error(String(err))
1934
+ };
1935
+ }
1936
+ }
1937
+ try {
1938
+ const result = await processPayment({ paymentIntentId, productRef, planRef });
1939
+ const isTimeout = result?.status === "timeout";
1940
+ if (isTimeout) {
1941
+ for (let attempt = 1; attempt <= 5; attempt++) {
1942
+ await new Promise((resolve) => setTimeout(resolve, attempt * 1e3));
1943
+ await refetchPurchase();
1944
+ }
1945
+ return {
1946
+ status: "timeout",
1947
+ error: new Error(copy.errors.paymentProcessingTimeout)
1948
+ };
1949
+ }
1950
+ await refetchPurchase();
1951
+ return { status: "success" };
1952
+ } catch (err) {
1953
+ return {
1954
+ status: "error",
1955
+ error: err instanceof Error ? err : new Error(String(err))
1956
+ };
1957
+ }
1958
+ }
1959
+
1960
+ // src/utils/checkoutCta.ts
1961
+ function resolveCta(input) {
1962
+ if (input.override) return input.override;
1963
+ const { variant, plan, product, amountFormatted, copy } = input;
1964
+ if (variant === "recurring") {
1965
+ if (plan?.trialDays && plan.trialDays > 0) {
1966
+ return interpolate(copy.cta.trialStart, { trialDays: plan.trialDays });
1967
+ }
1968
+ return copy.cta.subscribe;
1969
+ }
1970
+ if (variant === "oneTime") {
1971
+ return interpolate(copy.cta.payAmount, { amount: amountFormatted });
1972
+ }
1973
+ if (variant === "topup") {
1974
+ return interpolate(copy.cta.addAmount, { amount: amountFormatted });
1975
+ }
1976
+ if (variant === "usageMetered" || variant === "freeTier") {
1977
+ const productName = product?.name ?? (typeof plan?.metadata?.productName === "string" ? plan.metadata.productName : plan?.name ?? "service");
1978
+ return interpolate(copy.cta.startUsing, { product: productName });
1979
+ }
1980
+ return copy.cta.payNow;
1981
+ }
1982
+
1983
+ // src/primitives/PaymentForm.tsx
1984
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1985
+ function toSubmitVariant(variant) {
1986
+ switch (variant) {
1987
+ case "freeTier":
1988
+ return "free";
1989
+ case "topup":
1990
+ return "topup";
1991
+ case "usageMetered":
1992
+ return "activate";
1993
+ default:
1994
+ return "paid";
1995
+ }
1996
+ }
1997
+ var Root3 = (0, import_react16.forwardRef)(function PaymentFormRoot(props, forwardedRef) {
1998
+ const {
1999
+ planRef,
2000
+ productRef,
2001
+ onSuccess,
2002
+ onResult,
2003
+ onFreePlan,
2004
+ onError,
2005
+ returnUrl,
2006
+ submitButtonText,
2007
+ className,
2008
+ buttonClassName,
2009
+ prefillCustomer,
2010
+ requireTermsAcceptance = false,
2011
+ children
2012
+ } = props;
2013
+ const solva = (0, import_react16.useContext)(SolvaPayContext);
2014
+ if (!solva) throw new MissingProviderError("PaymentForm");
2015
+ const copy = useCopy();
2016
+ const locale = useLocale();
2017
+ const planSelection = usePlanSelection();
2018
+ const effectivePlanRef = planRef ?? planSelection?.selectedPlanRef ?? void 0;
2019
+ const effectiveProductRef = productRef ?? planSelection?.productRef;
2020
+ const { plan: resolvedPlan } = usePlan({
2021
+ planRef: effectivePlanRef,
2022
+ productRef: effectiveProductRef
2023
+ });
2024
+ const isFreePlan = resolvedPlan?.requiresPayment === false;
2025
+ const {
2026
+ loading: checkoutLoading,
2027
+ error: checkoutError,
2028
+ clientSecret,
2029
+ startCheckout,
2030
+ stripePromise,
2031
+ resolvedPlanRef
2032
+ } = useCheckout({
2033
+ planRef: effectivePlanRef,
2034
+ productRef: effectiveProductRef,
2035
+ customer: prefillCustomer
2036
+ });
2037
+ const hasInitializedRef = (0, import_react16.useRef)(false);
2038
+ const hasPlanOrProduct = !!(effectivePlanRef || effectiveProductRef);
2039
+ (0, import_react16.useEffect)(() => {
2040
+ if (isFreePlan) return;
2041
+ if (!hasInitializedRef.current && hasPlanOrProduct && !checkoutLoading && !checkoutError && !clientSecret) {
2042
+ hasInitializedRef.current = true;
2043
+ startCheckout().catch(() => {
2044
+ hasInitializedRef.current = false;
2045
+ });
2046
+ }
2047
+ if (hasPlanOrProduct && clientSecret) {
2048
+ hasInitializedRef.current = true;
2049
+ }
2050
+ }, [
2051
+ hasPlanOrProduct,
2052
+ checkoutLoading,
2053
+ checkoutError,
2054
+ clientSecret,
2055
+ startCheckout,
2056
+ isFreePlan
2057
+ ]);
2058
+ const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
2059
+ const elementsOptions = (0, import_react16.useMemo)(() => {
2060
+ if (!clientSecret) return void 0;
2061
+ return { clientSecret, locale };
2062
+ }, [clientSecret, locale]);
2063
+ const shouldRenderElements = !!(stripePromise && clientSecret);
2064
+ if (!hasPlanOrProduct) {
2065
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2066
+ "div",
2067
+ {
2068
+ ref: forwardedRef,
2069
+ className,
2070
+ "data-solvapay-payment-form": "",
2071
+ "data-state": "error",
2072
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-solvapay-payment-form-error": "", children: copy.errors.configMissingPlanOrProduct })
2073
+ }
2074
+ );
2075
+ }
2076
+ if (checkoutError) {
2077
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2078
+ "div",
2079
+ {
2080
+ ref: forwardedRef,
2081
+ className,
2082
+ "data-solvapay-payment-form": "",
2083
+ "data-state": "error",
2084
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-solvapay-payment-form-error": "", children: [
2085
+ copy.errors.paymentInitFailed,
2086
+ " ",
2087
+ checkoutError.message || copy.errors.unknownError
2088
+ ] })
2089
+ }
2090
+ );
2091
+ }
2092
+ if (isFreePlan && resolvedPlan) {
2093
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2094
+ "div",
2095
+ {
2096
+ ref: forwardedRef,
2097
+ className,
2098
+ "data-solvapay-payment-form": "",
2099
+ "data-state": "ready",
2100
+ "data-variant": "free",
2101
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2102
+ FreeInner,
2103
+ {
2104
+ planRef: effectivePlanRef,
2105
+ productRef: effectiveProductRef,
2106
+ plan: resolvedPlan,
2107
+ resolvedPlanRef,
2108
+ requireTermsAcceptance,
2109
+ submitButtonText,
2110
+ buttonClassName,
2111
+ onFreePlan,
2112
+ onResult,
2113
+ onError,
2114
+ children
2115
+ }
2116
+ )
2117
+ }
2118
+ );
2119
+ }
2120
+ if (shouldRenderElements && elementsOptions) {
2121
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2122
+ "div",
2123
+ {
2124
+ ref: forwardedRef,
2125
+ className,
2126
+ "data-solvapay-payment-form": "",
2127
+ "data-state": "ready",
2128
+ "data-variant": "paid",
2129
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_stripe_js.Elements, { stripe: stripePromise, options: elementsOptions, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2130
+ PaidInner,
2131
+ {
2132
+ planRef: effectivePlanRef,
2133
+ productRef: effectiveProductRef,
2134
+ prefillCustomer,
2135
+ resolvedPlanRef,
2136
+ plan: resolvedPlan ?? null,
2137
+ clientSecret,
2138
+ returnUrl: finalReturnUrl,
2139
+ submitButtonText,
2140
+ buttonClassName,
2141
+ requireTermsAcceptance,
2142
+ onSuccess,
2143
+ onResult,
2144
+ onError,
2145
+ children
2146
+ }
2147
+ ) }, clientSecret)
2148
+ }
2149
+ );
2150
+ }
2151
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2152
+ "div",
2153
+ {
2154
+ ref: forwardedRef,
2155
+ className,
2156
+ "data-solvapay-payment-form": "",
2157
+ "data-state": "loading",
2158
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-solvapay-payment-form-loading": "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, { size: "md" }) })
2159
+ }
2160
+ );
2161
+ });
2162
+ var PaidInner = ({
2163
+ planRef,
2164
+ productRef,
2165
+ prefillCustomer,
2166
+ resolvedPlanRef,
2167
+ plan,
2168
+ clientSecret,
2169
+ returnUrl,
2170
+ submitButtonText,
2171
+ buttonClassName,
2172
+ requireTermsAcceptance,
2173
+ onSuccess,
2174
+ onResult,
2175
+ onError,
2176
+ children
2177
+ }) => {
2178
+ const stripe = (0, import_react_stripe_js.useStripe)();
2179
+ const elements = (0, import_react_stripe_js.useElements)();
2180
+ const copy = useCopy();
2181
+ const customer = useCustomer();
2182
+ const { processPayment } = useSolvaPay();
2183
+ const { refetch } = usePurchase();
2184
+ const [elementKind, setElementKind] = (0, import_react16.useState)(
2185
+ children ? null : "payment-element"
2186
+ );
2187
+ const [paymentInputComplete, setPaymentInputComplete] = (0, import_react16.useState)(false);
2188
+ const [termsAccepted, setTermsAccepted] = (0, import_react16.useState)(false);
2189
+ const [isProcessing, setIsProcessing] = (0, import_react16.useState)(false);
2190
+ const [error, setError] = (0, import_react16.useState)(null);
2191
+ const isReady = !!(stripe && elements);
2192
+ const canSubmit = isReady && !!clientSecret && !!elementKind && paymentInputComplete && (!requireTermsAcceptance || termsAccepted) && !isProcessing;
2193
+ const submit = (0, import_react16.useCallback)(async () => {
2194
+ if (!stripe || !elements || !clientSecret || !elementKind) {
2195
+ const msg2 = !stripe || !elements ? copy.errors.stripeUnavailable : copy.errors.paymentIntentUnavailable;
2196
+ setError(msg2);
2197
+ onError?.(new Error(msg2));
2198
+ return;
2199
+ }
2200
+ setError(null);
2201
+ setIsProcessing(true);
2202
+ const result = await confirmPayment({
2203
+ stripe,
2204
+ elements,
2205
+ clientSecret,
2206
+ mode: elementKind,
2207
+ returnUrl,
2208
+ billingDetails: {
2209
+ name: customer.name ?? prefillCustomer?.name,
2210
+ email: customer.email ?? prefillCustomer?.email
2211
+ },
2212
+ copy
2213
+ });
2214
+ if (result.status === "error") {
2215
+ setError(result.message);
2216
+ setIsProcessing(false);
2217
+ onError?.(new Error(result.message));
2218
+ return;
2219
+ }
2220
+ if (result.status === "requires_action" || result.status === "other") {
2221
+ setError(result.message);
2222
+ setIsProcessing(false);
2223
+ return;
2224
+ }
2225
+ const paymentIntent = result.paymentIntent;
2226
+ const reconcileResult = await reconcilePayment({
2227
+ paymentIntentId: paymentIntent.id,
2228
+ productRef,
2229
+ planRef: planRef || resolvedPlanRef || void 0,
2230
+ processPayment,
2231
+ refetchPurchase: refetch,
2232
+ copy
2233
+ });
2234
+ setIsProcessing(false);
2235
+ if (reconcileResult.status === "success") {
2236
+ const pi = paymentIntent;
2237
+ onSuccess?.(pi);
2238
+ const paid = { kind: "paid", paymentIntent: pi };
2239
+ onResult?.(paid);
2240
+ return;
2241
+ }
2242
+ const msg = reconcileResult.status === "timeout" ? reconcileResult.error.message : copy.errors.paymentProcessingFailed;
2243
+ setError(msg);
2244
+ onError?.(reconcileResult.error);
2245
+ }, [
2246
+ stripe,
2247
+ elements,
2248
+ clientSecret,
2249
+ elementKind,
2250
+ returnUrl,
2251
+ customer,
2252
+ prefillCustomer,
2253
+ copy,
2254
+ processPayment,
2255
+ productRef,
2256
+ planRef,
2257
+ resolvedPlanRef,
2258
+ refetch,
2259
+ onSuccess,
2260
+ onResult,
2261
+ onError
2262
+ ]);
2263
+ const contextValue = (0, import_react16.useMemo)(
2264
+ () => ({
2265
+ planRef,
2266
+ productRef,
2267
+ prefillCustomer,
2268
+ resolvedPlanRef,
2269
+ plan,
2270
+ clientSecret,
2271
+ stripe: stripe ?? null,
2272
+ elements: elements ?? null,
2273
+ isProcessing,
2274
+ isReady,
2275
+ paymentInputComplete,
2276
+ termsAccepted,
2277
+ requireTermsAcceptance,
2278
+ canSubmit,
2279
+ error,
2280
+ elementKind,
2281
+ returnUrl,
2282
+ submitButtonText,
2283
+ buttonClassName,
2284
+ setElementKind,
2285
+ setPaymentInputComplete,
2286
+ setTermsAccepted,
2287
+ submit
2288
+ }),
2289
+ [
2290
+ planRef,
2291
+ productRef,
2292
+ prefillCustomer,
2293
+ resolvedPlanRef,
2294
+ plan,
2295
+ clientSecret,
2296
+ stripe,
2297
+ elements,
2298
+ isProcessing,
2299
+ isReady,
2300
+ paymentInputComplete,
2301
+ termsAccepted,
2302
+ requireTermsAcceptance,
2303
+ canSubmit,
2304
+ error,
2305
+ elementKind,
2306
+ returnUrl,
2307
+ submitButtonText,
2308
+ buttonClassName,
2309
+ submit
2310
+ ]
2311
+ );
2312
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PaymentFormProvider, { value: contextValue, children });
2313
+ };
2314
+ var FreeInner = ({
2315
+ planRef,
2316
+ productRef,
2317
+ plan,
2318
+ resolvedPlanRef,
2319
+ requireTermsAcceptance,
2320
+ submitButtonText,
2321
+ buttonClassName,
2322
+ onFreePlan,
2323
+ onResult,
2324
+ onError,
2325
+ children
2326
+ }) => {
2327
+ const copy = useCopy();
2328
+ const { refetch } = usePurchase();
2329
+ const { activate, state, error: activationError, result: activationResult } = useActivation();
2330
+ const [termsAccepted, setTermsAccepted] = (0, import_react16.useState)(false);
2331
+ const [localError, setLocalError] = (0, import_react16.useState)(null);
2332
+ const resultFiredRef = (0, import_react16.useRef)(false);
2333
+ (0, import_react16.useEffect)(() => {
2334
+ if (state === "activated" && activationResult && !resultFiredRef.current) {
2335
+ resultFiredRef.current = true;
2336
+ const res = { kind: "activated", result: activationResult };
2337
+ onResult?.(res);
2338
+ refetch().catch(() => {
2339
+ });
2340
+ }
2341
+ }, [state, activationResult, onResult, refetch]);
2342
+ const isProcessing = state === "activating";
2343
+ const canSubmit = !isProcessing && (!requireTermsAcceptance || termsAccepted) && !!productRef;
2344
+ const submit = (0, import_react16.useCallback)(async () => {
2345
+ if (!productRef) {
2346
+ const msg = copy.errors.configMissingPlanOrProduct;
2347
+ setLocalError(msg);
2348
+ onError?.(new Error(msg));
2349
+ return;
2350
+ }
2351
+ setLocalError(null);
2352
+ try {
2353
+ if (onFreePlan) {
2354
+ await onFreePlan(plan);
2355
+ onResult?.({
2356
+ kind: "activated",
2357
+ result: {
2358
+ status: "activated",
2359
+ productRef,
2360
+ planRef: plan.reference
2361
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2362
+ }
2363
+ });
2364
+ return;
2365
+ }
2366
+ await activate({ productRef, planRef: plan.reference });
2367
+ } catch (err) {
2368
+ const msg = err instanceof Error ? err.message : copy.activation.failed;
2369
+ setLocalError(msg);
2370
+ onError?.(err instanceof Error ? err : new Error(msg));
2371
+ }
2372
+ }, [productRef, plan, onFreePlan, activate, onResult, onError, copy]);
2373
+ const error = localError ?? activationError;
2374
+ const contextValue = (0, import_react16.useMemo)(
2375
+ () => ({
2376
+ planRef,
2377
+ productRef,
2378
+ prefillCustomer: void 0,
2379
+ resolvedPlanRef,
2380
+ plan,
2381
+ clientSecret: null,
2382
+ stripe: null,
2383
+ elements: null,
2384
+ isProcessing,
2385
+ isReady: true,
2386
+ paymentInputComplete: true,
2387
+ termsAccepted,
2388
+ requireTermsAcceptance,
2389
+ canSubmit,
2390
+ error,
2391
+ elementKind: null,
2392
+ returnUrl: "",
2393
+ submitButtonText,
2394
+ buttonClassName,
2395
+ setElementKind: () => {
2396
+ },
2397
+ setPaymentInputComplete: () => {
2398
+ },
2399
+ setTermsAccepted,
2400
+ submit
2401
+ }),
2402
+ [
2403
+ planRef,
2404
+ productRef,
2405
+ resolvedPlanRef,
2406
+ plan,
2407
+ isProcessing,
2408
+ termsAccepted,
2409
+ requireTermsAcceptance,
2410
+ canSubmit,
2411
+ error,
2412
+ submitButtonText,
2413
+ buttonClassName,
2414
+ submit
2415
+ ]
2416
+ );
2417
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PaymentFormProvider, { value: contextValue, children });
2418
+ };
2419
+ var Summary = (props) => {
2420
+ const ctx = usePaymentForm();
2421
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2422
+ CheckoutSummary2,
2423
+ {
2424
+ ...props,
2425
+ planRef: ctx.planRef || ctx.resolvedPlanRef || void 0,
2426
+ productRef: ctx.productRef
2427
+ }
2428
+ );
2429
+ };
2430
+ var MandateTextPrimitive = (props) => {
2431
+ const ctx = usePaymentForm();
2432
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2433
+ MandateText,
2434
+ {
2435
+ ...props,
2436
+ planRef: ctx.planRef || ctx.resolvedPlanRef || void 0,
2437
+ productRef: ctx.productRef
2438
+ }
2439
+ );
2440
+ };
2441
+ var CustomerFields = (0, import_react16.forwardRef)(
2442
+ function PaymentFormCustomerFields({ asChild, readOnly: _readOnly = true, children, ...rest }, ref) {
2443
+ const copy = useCopy();
2444
+ const customer = useCustomer();
2445
+ const { prefillCustomer } = usePaymentForm();
2446
+ const email = customer.email ?? prefillCustomer?.email;
2447
+ const name = customer.name ?? prefillCustomer?.name;
2448
+ if (!email && !name) return null;
2449
+ const Comp = asChild ? Slot : "div";
2450
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Comp, { ref, "data-solvapay-payment-form-customer-fields": "", ...rest, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2451
+ email && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-solvapay-payment-form-customer-email": "", children: [
2452
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
2453
+ copy.customer.emailLabel,
2454
+ ": "
2455
+ ] }),
2456
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: email })
2457
+ ] }),
2458
+ name && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-solvapay-payment-form-customer-name": "", children: [
2459
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
2460
+ copy.customer.nameLabel,
2461
+ ": "
2462
+ ] }),
2463
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: name })
2464
+ ] })
2465
+ ] }) });
2466
+ }
2467
+ );
2468
+ var PaymentElementSlot = ({ options }) => {
2469
+ const { setElementKind, setPaymentInputComplete, isReady, stripe, elements } = usePaymentForm();
2470
+ const locale = useLocale();
2471
+ (0, import_react16.useEffect)(() => {
2472
+ if (stripe && elements) setElementKind("payment-element");
2473
+ }, [setElementKind, stripe, elements]);
2474
+ if (!stripe || !elements) return null;
2475
+ if (!isReady) {
2476
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-solvapay-payment-form-loading": "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, { size: "sm" }) });
2477
+ }
2478
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-solvapay-payment-form-payment-element": "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2479
+ import_react_stripe_js.PaymentElement,
2480
+ {
2481
+ options,
2482
+ onChange: (e) => setPaymentInputComplete(e.complete)
2483
+ },
2484
+ locale || "default"
2485
+ ) });
2486
+ };
2487
+ var CardElementSlot = ({ options }) => {
2488
+ const { setElementKind, setPaymentInputComplete, isReady, stripe, elements } = usePaymentForm();
2489
+ (0, import_react16.useEffect)(() => {
2490
+ if (stripe && elements) setElementKind("card-element");
2491
+ }, [setElementKind, stripe, elements]);
2492
+ if (!stripe || !elements) return null;
2493
+ if (!isReady) {
2494
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-solvapay-payment-form-loading": "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, { size: "sm" }) });
2495
+ }
2496
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-solvapay-payment-form-card-element": "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2497
+ import_react_stripe_js.CardElement,
2498
+ {
2499
+ options,
2500
+ onChange: (e) => setPaymentInputComplete(e.complete)
2501
+ }
2502
+ ) });
2503
+ };
2504
+ var TermsCheckbox = (0, import_react16.forwardRef)(
2505
+ function PaymentFormTermsCheckbox({ asChild, label, children, ...rest }, ref) {
2506
+ const { termsAccepted, setTermsAccepted } = usePaymentForm();
2507
+ const copy = useCopy();
2508
+ const id = "solvapay-terms-checkbox";
2509
+ if (asChild) {
2510
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Slot, { ref, "data-solvapay-payment-form-terms": "", ...rest, children });
2511
+ }
2512
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2513
+ "label",
2514
+ {
2515
+ ref,
2516
+ htmlFor: id,
2517
+ "data-solvapay-payment-form-terms": "",
2518
+ ...rest,
2519
+ children: [
2520
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2521
+ "input",
2522
+ {
2523
+ id,
2524
+ type: "checkbox",
2525
+ checked: termsAccepted,
2526
+ onChange: (e) => setTermsAccepted(e.target.checked)
2527
+ }
2528
+ ),
2529
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: label ?? copy.terms.checkboxLabel })
2530
+ ]
2531
+ }
2532
+ );
2533
+ }
2534
+ );
2535
+ var SubmitButton = (0, import_react16.forwardRef)(
2536
+ function PaymentFormSubmitButton({ asChild, onClick, children, ...rest }, ref) {
2537
+ const ctx = usePaymentForm();
2538
+ const copy = useCopy();
2539
+ const locale = useLocale();
2540
+ const { plan } = usePlan({
2541
+ planRef: ctx.planRef || ctx.resolvedPlanRef || void 0,
2542
+ productRef: ctx.productRef
2543
+ });
2544
+ const { product } = useProduct(ctx.productRef);
2545
+ const variant = deriveVariant(plan ?? ctx.plan ?? void 0);
2546
+ const dataVariant = toSubmitVariant(variant);
2547
+ const dataState = ctx.isProcessing ? "processing" : !ctx.canSubmit ? "disabled" : "idle";
2548
+ const amountFormatted = formatPrice(
2549
+ plan?.price ?? ctx.plan?.price ?? 0,
2550
+ plan?.currency ?? ctx.plan?.currency ?? "usd",
2551
+ { locale, free: copy.interval.free }
2552
+ );
2553
+ const label = resolveCta({
2554
+ variant,
2555
+ plan: plan ?? ctx.plan,
2556
+ product,
2557
+ amountFormatted,
2558
+ copy,
2559
+ override: typeof children === "string" ? children : ctx.submitButtonText
2560
+ });
2561
+ const content = ctx.isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2562
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, { size: "sm" }),
2563
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: copy.cta.processing })
2564
+ ] }) : children && typeof children !== "string" ? children : label;
2565
+ const buttonProps = {
2566
+ "data-solvapay-payment-form-submit": "",
2567
+ "data-state": dataState,
2568
+ "data-variant": dataVariant,
2569
+ "aria-busy": ctx.isProcessing,
2570
+ "aria-disabled": !ctx.canSubmit,
2571
+ "aria-label": label,
2572
+ disabled: !ctx.canSubmit,
2573
+ className: rest.className ?? ctx.buttonClassName,
2574
+ onClick: composeEventHandlers(onClick, (e) => {
2575
+ e.preventDefault();
2576
+ ctx.submit();
2577
+ }),
2578
+ ...rest
2579
+ };
2580
+ if (asChild) {
2581
+ return (
2582
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2583
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Slot, { ref, ...buttonProps, children: content })
2584
+ );
2585
+ }
2586
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { ref, type: "submit", ...buttonProps, children: content });
2587
+ }
2588
+ );
2589
+ var Loading2 = (0, import_react16.forwardRef)(function PaymentFormLoading({ asChild, children, ...rest }, ref) {
2590
+ const ctx = usePaymentForm();
2591
+ if (ctx.isReady && ctx.clientSecret) return null;
2592
+ const Comp = asChild ? Slot : "div";
2593
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Comp, { ref, "data-solvapay-payment-form-loading": "", ...rest, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, { size: "sm" }) });
2594
+ });
2595
+ var ErrorSlot2 = (0, import_react16.forwardRef)(function PaymentFormError({ asChild, children, ...rest }, ref) {
2596
+ const ctx = usePaymentForm();
2597
+ if (!ctx.error) return null;
2598
+ const Comp = asChild ? Slot : "div";
2599
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2600
+ Comp,
2601
+ {
2602
+ ref,
2603
+ role: "alert",
2604
+ "aria-live": "assertive",
2605
+ "aria-atomic": "true",
2606
+ "data-solvapay-payment-form-error": "",
2607
+ ...rest,
2608
+ children: children ?? ctx.error
2609
+ }
2610
+ );
2611
+ });
2612
+ var PaymentFormRoot2 = Root3;
2613
+ var PaymentFormSummary = Summary;
2614
+ var PaymentFormCustomerFields2 = CustomerFields;
2615
+ var PaymentFormPaymentElement = PaymentElementSlot;
2616
+ var PaymentFormCardElement = CardElementSlot;
2617
+ var PaymentFormMandateText = MandateTextPrimitive;
2618
+ var PaymentFormTermsCheckbox2 = TermsCheckbox;
2619
+ var PaymentFormSubmitButton2 = SubmitButton;
2620
+ var PaymentFormLoading2 = Loading2;
2621
+ var PaymentFormError2 = ErrorSlot2;
2622
+ var PaymentForm = {
2623
+ Root: Root3,
2624
+ Summary,
2625
+ CustomerFields,
2626
+ PaymentElement: PaymentElementSlot,
2627
+ CardElement: CardElementSlot,
2628
+ MandateText: MandateTextPrimitive,
2629
+ TermsCheckbox,
2630
+ SubmitButton,
2631
+ Loading: Loading2,
2632
+ Error: ErrorSlot2
2633
+ };
2634
+
2635
+ // src/primitives/ActivationFlow.tsx
2636
+ var import_react20 = require("react");
2637
+
2638
+ // src/primitives/AmountPicker.tsx
2639
+ var import_react19 = require("react");
2640
+
2641
+ // src/hooks/useTopupAmountSelector.ts
2642
+ var import_react17 = require("react");
2643
+ function getQuickAmounts(currency) {
2644
+ switch (currency.toUpperCase()) {
2645
+ case "SEK":
2646
+ case "NOK":
2647
+ case "DKK":
2648
+ return [100, 500, 1e3, 5e3];
2649
+ case "JPY":
2650
+ return [1e3, 5e3, 1e4, 5e4];
2651
+ case "KRW":
2652
+ return [1e4, 5e4, 1e5, 5e5];
2653
+ case "ISK":
2654
+ case "HUF":
2655
+ return [1e3, 5e3, 1e4, 5e4];
2656
+ default:
2657
+ return [10, 50, 100, 500];
2658
+ }
2659
+ }
2660
+ function getCurrencySymbol(currency) {
2661
+ try {
2662
+ const parts = new Intl.NumberFormat("en", { style: "currency", currency }).formatToParts(0);
2663
+ const sym = parts.find((p) => p.type === "currency");
2664
+ return sym?.value || currency;
2665
+ } catch {
2666
+ return currency;
2667
+ }
2668
+ }
2669
+ var DEFAULT_MIN = 1;
2670
+ var DEFAULT_MAX = 1e5;
2671
+ function useTopupAmountSelector(options) {
2672
+ const { currency, minAmount = DEFAULT_MIN, maxAmount = DEFAULT_MAX } = options;
2673
+ const copy = useCopy();
2674
+ const [selectedAmount, setSelectedAmount] = (0, import_react17.useState)(null);
2675
+ const [customAmount, setCustomAmountRaw] = (0, import_react17.useState)("");
2676
+ const [error, setError] = (0, import_react17.useState)(null);
2677
+ const quickAmounts = (0, import_react17.useMemo)(() => getQuickAmounts(currency), [currency]);
2678
+ const currencySymbol = (0, import_react17.useMemo)(() => getCurrencySymbol(currency), [currency]);
2679
+ const resolvedAmount = (0, import_react17.useMemo)(() => {
2680
+ if (customAmount) {
2681
+ const parsed = parseFloat(customAmount);
2682
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
2683
+ }
2684
+ return selectedAmount;
2685
+ }, [selectedAmount, customAmount]);
2686
+ const selectQuickAmount = (0, import_react17.useCallback)(
2687
+ (amount) => {
2688
+ setSelectedAmount(amount);
2689
+ setCustomAmountRaw("");
2690
+ setError(null);
2691
+ },
2692
+ []
2693
+ );
2694
+ const setCustomAmount = (0, import_react17.useCallback)(
2695
+ (value) => {
2696
+ const sanitized = value.replace(/[^0-9.]/g, "");
2697
+ const dotIndex = sanitized.indexOf(".");
2698
+ const cleaned = dotIndex === -1 ? sanitized : sanitized.slice(0, dotIndex + 1) + sanitized.slice(dotIndex + 1).replace(/\./g, "");
2699
+ setCustomAmountRaw(cleaned);
2700
+ setSelectedAmount(null);
2701
+ setError(null);
2702
+ },
2703
+ []
2704
+ );
2705
+ const validate = (0, import_react17.useCallback)(() => {
2706
+ if (resolvedAmount == null) {
2707
+ setError(copy.topup.selectOrEnterAmount);
2708
+ return false;
2709
+ }
2710
+ if (resolvedAmount < minAmount) {
2711
+ setError(
2712
+ interpolate(copy.topup.minAmount, {
2713
+ amount: `${currencySymbol}${minAmount}`
2714
+ })
2715
+ );
2716
+ return false;
2717
+ }
2718
+ if (resolvedAmount > maxAmount) {
2719
+ setError(
2720
+ interpolate(copy.topup.maxAmount, {
2721
+ amount: `${currencySymbol}${maxAmount.toLocaleString()}`
2722
+ })
2723
+ );
2724
+ return false;
2725
+ }
2726
+ setError(null);
2727
+ return true;
2728
+ }, [resolvedAmount, minAmount, maxAmount, currencySymbol, copy]);
2729
+ const reset = (0, import_react17.useCallback)(() => {
2730
+ setSelectedAmount(null);
2731
+ setCustomAmountRaw("");
2732
+ setError(null);
2733
+ }, []);
2734
+ return {
2735
+ quickAmounts,
2736
+ selectedAmount,
2737
+ customAmount,
2738
+ resolvedAmount,
2739
+ selectQuickAmount,
2740
+ setCustomAmount,
2741
+ error,
2742
+ validate,
2743
+ reset,
2744
+ currencySymbol
2745
+ };
2746
+ }
2747
+
2748
+ // src/hooks/useBalance.ts
2749
+ var import_react18 = require("react");
2750
+ function useBalance() {
2751
+ const { balance } = useSolvaPay();
2752
+ (0, import_react18.useEffect)(() => {
2753
+ balance.refetch();
2754
+ }, [balance.refetch]);
2755
+ return balance;
2756
+ }
2757
+
2758
+ // src/primitives/AmountPicker.tsx
2759
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2760
+ var AmountPickerContext = (0, import_react19.createContext)(null);
2761
+ function usePickerCtx(part) {
2762
+ const ctx = (0, import_react19.useContext)(AmountPickerContext);
2763
+ if (!ctx) {
2764
+ throw new Error(`AmountPicker.${part} must be rendered inside <AmountPicker.Root>.`);
2765
+ }
2766
+ return ctx;
2767
+ }
2768
+ var Root4 = (0, import_react19.forwardRef)(function AmountPickerRoot({ currency, minAmount, maxAmount, onChange, asChild, children, ...rest }, forwardedRef) {
2769
+ const solva = (0, import_react19.useContext)(SolvaPayContext);
2770
+ if (!solva) throw new MissingProviderError("AmountPicker");
2771
+ const selector = useTopupAmountSelector({ currency, minAmount, maxAmount });
2772
+ const { creditsPerMinorUnit, displayExchangeRate } = useBalance();
2773
+ (0, import_react19.useEffect)(() => {
2774
+ onChange?.(selector.resolvedAmount);
2775
+ }, [selector.resolvedAmount, onChange]);
2776
+ const resolvedAmountMinor = selector.resolvedAmount != null && selector.resolvedAmount > 0 ? Math.round(selector.resolvedAmount * 100) : null;
2777
+ const rate = displayExchangeRate ?? 1;
2778
+ const estimatedCredits = creditsPerMinorUnit != null && creditsPerMinorUnit > 0 && resolvedAmountMinor != null ? Math.floor(resolvedAmountMinor / rate * creditsPerMinorUnit) : null;
2779
+ const isApproximate = rate !== 1;
2780
+ const ctx = (0, import_react19.useMemo)(
2781
+ () => ({
2782
+ ...selector,
2783
+ currency,
2784
+ creditsPerMinorUnit,
2785
+ displayExchangeRate,
2786
+ estimatedCredits,
2787
+ isApproximate
2788
+ }),
2789
+ [selector, currency, creditsPerMinorUnit, displayExchangeRate, estimatedCredits, isApproximate]
2790
+ );
2791
+ const Comp = asChild ? Slot : "div";
2792
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AmountPickerContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Comp, { ref: forwardedRef, "data-solvapay-amount-picker": "", ...rest, children }) });
2793
+ });
2794
+ var Option = (0, import_react19.forwardRef)(function AmountPickerOption({ asChild, amount, onClick, children, ...rest }, forwardedRef) {
2795
+ const ctx = usePickerCtx("Option");
2796
+ const isActive = ctx.selectedAmount === amount && !ctx.customAmount;
2797
+ const state = rest.disabled ? "disabled" : isActive ? "selected" : "idle";
2798
+ const commonProps = {
2799
+ "data-solvapay-amount-picker-option": "",
2800
+ "data-state": state,
2801
+ "data-amount": String(amount),
2802
+ "aria-pressed": isActive,
2803
+ onClick: composeEventHandlers(onClick, () => {
2804
+ ctx.selectQuickAmount(amount);
2805
+ }),
2806
+ ...rest
2807
+ };
2808
+ if (asChild) {
2809
+ return (
2810
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2811
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2812
+ ctx.currencySymbol,
2813
+ amount.toLocaleString()
2814
+ ] }) })
2815
+ );
2816
+ }
2817
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2818
+ ctx.currencySymbol,
2819
+ amount.toLocaleString()
2820
+ ] }) });
2821
+ });
2822
+ var Custom = (0, import_react19.forwardRef)(function AmountPickerCustom({ asChild, onChange, onFocus, ...rest }, forwardedRef) {
2823
+ const ctx = usePickerCtx("Custom");
2824
+ const state = ctx.customAmount ? "active" : "dormant";
2825
+ const commonProps = {
2826
+ "data-solvapay-amount-picker-custom": "",
2827
+ "data-state": state,
2828
+ type: "text",
2829
+ inputMode: "decimal",
2830
+ placeholder: "0.00",
2831
+ value: ctx.customAmount,
2832
+ onChange: composeEventHandlers(onChange, (e) => {
2833
+ ctx.setCustomAmount(e.target.value);
2834
+ }),
2835
+ onFocus: composeEventHandlers(onFocus, () => {
2836
+ if (ctx.selectedAmount != null) ctx.selectQuickAmount(0);
2837
+ }),
2838
+ ...rest
2839
+ };
2840
+ if (asChild) {
2841
+ return (
2842
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2843
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Slot, { ref: forwardedRef, ...commonProps })
2844
+ );
2845
+ }
2846
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { ref: forwardedRef, ...commonProps });
2847
+ });
2848
+ var Confirm = (0, import_react19.forwardRef)(function AmountPickerConfirm({ asChild, onClick, onConfirm, disabled, children, ...rest }, forwardedRef) {
2849
+ const ctx = usePickerCtx("Confirm");
2850
+ const isDisabled = disabled || !ctx.resolvedAmount;
2851
+ const handleConfirm = (0, import_react19.useCallback)(() => {
2852
+ if (ctx.validate() && ctx.resolvedAmount != null) {
2853
+ onConfirm?.(ctx.resolvedAmount);
2854
+ }
2855
+ }, [ctx, onConfirm]);
2856
+ const commonProps = {
2857
+ "data-solvapay-amount-picker-confirm": "",
2858
+ "data-state": isDisabled ? "disabled" : "idle",
2859
+ disabled: isDisabled,
2860
+ "aria-disabled": isDisabled || void 0,
2861
+ onClick: composeEventHandlers(onClick, handleConfirm),
2862
+ ...rest
2863
+ };
2864
+ if (asChild) {
2865
+ return (
2866
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2867
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Slot, { ref: forwardedRef, ...commonProps, children })
2868
+ );
2869
+ }
2870
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children });
2871
+ });
2872
+ var AmountPickerRoot2 = Root4;
2873
+ var AmountPickerOption2 = Option;
2874
+ var AmountPickerCustom2 = Custom;
2875
+ var AmountPickerConfirm2 = Confirm;
2876
+ var AmountPicker = {
2877
+ Root: Root4,
2878
+ Option,
2879
+ Custom,
2880
+ Confirm
2881
+ };
2882
+ function useAmountPicker() {
2883
+ return usePickerCtx("useAmountPicker");
2884
+ }
2885
+ function useAmountPickerCopy() {
2886
+ const copy = useCopy();
2887
+ const ctx = usePickerCtx("useAmountPickerCopy");
2888
+ return {
2889
+ selectAmountLabel: copy.amountPicker.selectAmountLabel,
2890
+ customAmountLabel: copy.amountPicker.customAmountLabel,
2891
+ creditEstimate: (credits) => interpolate(
2892
+ ctx.isApproximate ? copy.amountPicker.creditEstimateApprox : copy.amountPicker.creditEstimateExact,
2893
+ { credits: new Intl.NumberFormat().format(credits) }
2894
+ )
2895
+ };
2896
+ }
2897
+
2898
+ // src/primitives/ActivationFlow.tsx
2899
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2900
+ var ActivationFlowContext = (0, import_react20.createContext)(null);
2901
+ function useFlowCtx(part) {
2902
+ const ctx = (0, import_react20.useContext)(ActivationFlowContext);
2903
+ if (!ctx) {
2904
+ throw new Error(`ActivationFlow.${part} must be rendered inside <ActivationFlow.Root>.`);
2905
+ }
2906
+ return ctx;
2907
+ }
2908
+ var Root5 = (0, import_react20.forwardRef)(function ActivationFlowRoot({
2909
+ productRef,
2910
+ planRef: planRefProp,
2911
+ onSuccess,
2912
+ onError,
2913
+ retryDelayMs = 1500,
2914
+ retryBackoffMs = 2e3,
2915
+ asChild,
2916
+ children,
2917
+ ...rest
2918
+ }, forwardedRef) {
2919
+ const solva = (0, import_react20.useContext)(SolvaPayContext);
2920
+ if (!solva) throw new MissingProviderError("ActivationFlow");
2921
+ if (!productRef) throw new MissingProductRefError("ActivationFlow");
2922
+ const planSelection = usePlanSelection();
2923
+ const resolvedPlanRef = planRefProp ?? planSelection?.selectedPlanRef ?? void 0;
2924
+ const { plan } = usePlan({ planRef: resolvedPlanRef, productRef });
2925
+ const { activate, state, error, result, reset } = useActivation();
2926
+ const currency = plan?.currency ?? "USD";
2927
+ const amountSelector = useTopupAmountSelector({ currency });
2928
+ const { adjustBalance, creditsPerMinorUnit, displayExchangeRate } = useBalance();
2929
+ const [step, setStep] = (0, import_react20.useState)("summary");
2930
+ const calledSuccessRef = (0, import_react20.useRef)(false);
2931
+ const retryTimeoutRef = (0, import_react20.useRef)(null);
2932
+ (0, import_react20.useEffect)(() => {
2933
+ if (state === "activated" && !calledSuccessRef.current) {
2934
+ calledSuccessRef.current = true;
2935
+ setStep("activated");
2936
+ if (result) {
2937
+ const activationResult = { kind: "activated", result };
2938
+ onSuccess?.(activationResult);
2939
+ }
2940
+ }
2941
+ }, [state, result, onSuccess]);
2942
+ (0, import_react20.useEffect)(() => {
2943
+ if (state === "topup_required" && (step === "summary" || step === "activating")) {
2944
+ setStep("selectAmount");
2945
+ }
2946
+ }, [state, step]);
2947
+ (0, import_react20.useEffect)(() => {
2948
+ if ((state === "error" || state === "payment_required") && error) {
2949
+ setStep("error");
2950
+ if (state === "error") onError?.(new Error(error));
2951
+ }
2952
+ }, [state, error, onError]);
2953
+ const handleActivate = (0, import_react20.useCallback)(async () => {
2954
+ if (!resolvedPlanRef) return;
2955
+ setStep("activating");
2956
+ await activate({ productRef, planRef: resolvedPlanRef });
2957
+ }, [activate, productRef, resolvedPlanRef]);
2958
+ const goToTopupPayment = (0, import_react20.useCallback)(() => {
2959
+ if (amountSelector.validate()) setStep("topupPayment");
2960
+ }, [amountSelector]);
2961
+ const backToSelectAmount = (0, import_react20.useCallback)(() => {
2962
+ setStep("selectAmount");
2963
+ }, []);
2964
+ const retryActivation = (0, import_react20.useCallback)(async () => {
2965
+ if (!resolvedPlanRef) return;
2966
+ setStep("retrying");
2967
+ const amountMinor = Math.round((amountSelector.resolvedAmount || 0) * 100);
2968
+ const rate = displayExchangeRate ?? 1;
2969
+ if (creditsPerMinorUnit) {
2970
+ adjustBalance(Math.floor(amountMinor / rate * creditsPerMinorUnit));
2971
+ }
2972
+ await new Promise((r) => setTimeout(r, retryDelayMs));
2973
+ await activate({ productRef, planRef: resolvedPlanRef });
2974
+ }, [
2975
+ resolvedPlanRef,
2976
+ amountSelector.resolvedAmount,
2977
+ displayExchangeRate,
2978
+ creditsPerMinorUnit,
2979
+ adjustBalance,
2980
+ retryDelayMs,
2981
+ activate,
2982
+ productRef
2983
+ ]);
2984
+ const handleTopupSuccess = (0, import_react20.useCallback)(async () => {
2985
+ await retryActivation();
2986
+ }, [retryActivation]);
2987
+ (0, import_react20.useEffect)(() => {
2988
+ if (step !== "retrying") return;
2989
+ if (state !== "topup_required") return;
2990
+ if (!resolvedPlanRef) return;
2991
+ retryTimeoutRef.current = setTimeout(async () => {
2992
+ await activate({ productRef, planRef: resolvedPlanRef });
2993
+ }, retryBackoffMs);
2994
+ return () => {
2995
+ if (retryTimeoutRef.current) clearTimeout(retryTimeoutRef.current);
2996
+ };
2997
+ }, [step, state, resolvedPlanRef, retryBackoffMs, activate, productRef]);
2998
+ const resetFlow = (0, import_react20.useCallback)(() => {
2999
+ reset();
3000
+ calledSuccessRef.current = false;
3001
+ setStep("summary");
3002
+ }, [reset]);
3003
+ const amountCents = Math.round((amountSelector.resolvedAmount || 0) * 100);
3004
+ const ctx = (0, import_react20.useMemo)(
3005
+ () => ({
3006
+ step,
3007
+ plan,
3008
+ productRef,
3009
+ planRef: resolvedPlanRef ?? "",
3010
+ amountSelector,
3011
+ amountCents,
3012
+ currency,
3013
+ activate: handleActivate,
3014
+ reset: resetFlow,
3015
+ goToTopupPayment,
3016
+ backToSelectAmount,
3017
+ onTopupSuccess: handleTopupSuccess,
3018
+ error,
3019
+ onError
3020
+ }),
3021
+ [
3022
+ step,
3023
+ plan,
3024
+ productRef,
3025
+ resolvedPlanRef,
3026
+ amountSelector,
3027
+ amountCents,
3028
+ currency,
3029
+ handleActivate,
3030
+ resetFlow,
3031
+ goToTopupPayment,
3032
+ backToSelectAmount,
3033
+ handleTopupSuccess,
3034
+ error,
3035
+ onError
3036
+ ]
3037
+ );
3038
+ const Comp = asChild ? Slot : "div";
3039
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ActivationFlowContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3040
+ Comp,
3041
+ {
3042
+ ref: forwardedRef,
3043
+ "data-solvapay-activation-flow": "",
3044
+ "data-state": step,
3045
+ ...rest,
3046
+ children
3047
+ }
3048
+ ) });
3049
+ });
3050
+ function matchStep(step, allowed) {
3051
+ return allowed.includes(step);
3052
+ }
3053
+ var Summary2 = (0, import_react20.forwardRef)(function ActivationFlowSummary({ asChild, children, ...rest }, forwardedRef) {
3054
+ const ctx = useFlowCtx("Summary");
3055
+ if (!matchStep(ctx.step, ["summary", "activating"])) return null;
3056
+ const Comp = asChild ? Slot : "div";
3057
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-summary": "", ...rest, children });
3058
+ });
3059
+ var ActivateButton = (0, import_react20.forwardRef)(
3060
+ function ActivationFlowActivateButton({ asChild, onClick, children, ...rest }, forwardedRef) {
3061
+ const ctx = useFlowCtx("ActivateButton");
3062
+ const copy = useCopy();
3063
+ if (!matchStep(ctx.step, ["summary", "activating"])) return null;
3064
+ const isActivating = ctx.step === "activating";
3065
+ const disabled = isActivating;
3066
+ const commonProps = {
3067
+ "data-solvapay-activation-flow-activate": "",
3068
+ "data-state": isActivating ? "activating" : "idle",
3069
+ "aria-busy": isActivating,
3070
+ disabled,
3071
+ onClick: composeEventHandlers(onClick, () => {
3072
+ void ctx.activate();
3073
+ }),
3074
+ ...rest
3075
+ };
3076
+ const label = isActivating ? copy.activationFlow.activatingLabel : copy.activationFlow.activateButton;
3077
+ if (asChild) {
3078
+ return (
3079
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3080
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: label }) })
3081
+ );
3082
+ }
3083
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? label });
3084
+ }
3085
+ );
3086
+ var AmountPickerMount = ({ children }) => {
3087
+ const ctx = useFlowCtx("AmountPicker");
3088
+ if (ctx.step !== "selectAmount") return null;
3089
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { "data-solvapay-activation-flow-amount-picker": "", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AmountPicker.Root, { currency: ctx.currency, children }) });
3090
+ };
3091
+ var ContinueButton = (0, import_react20.forwardRef)(
3092
+ function ActivationFlowContinueButton({ asChild, onClick, children, ...rest }, forwardedRef) {
3093
+ const ctx = useFlowCtx("ContinueButton");
3094
+ const copy = useCopy();
3095
+ if (ctx.step !== "selectAmount") return null;
3096
+ const disabled = !ctx.amountSelector.resolvedAmount;
3097
+ const commonProps = {
3098
+ "data-solvapay-activation-flow-continue": "",
3099
+ "data-state": disabled ? "disabled" : "idle",
3100
+ disabled,
3101
+ "aria-disabled": disabled || void 0,
3102
+ onClick: composeEventHandlers(onClick, () => {
3103
+ ctx.goToTopupPayment();
3104
+ }),
3105
+ ...rest
3106
+ };
3107
+ if (asChild) {
3108
+ return (
3109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3110
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: copy.activationFlow.continueToPayment }) })
3111
+ );
3112
+ }
3113
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? copy.activationFlow.continueToPayment });
3114
+ }
3115
+ );
3116
+ var Retrying = (0, import_react20.forwardRef)(function ActivationFlowRetrying({ asChild, children, ...rest }, forwardedRef) {
3117
+ const ctx = useFlowCtx("Retrying");
3118
+ if (ctx.step !== "retrying") return null;
3119
+ const Comp = asChild ? Slot : "div";
3120
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-retrying": "", ...rest, children });
3121
+ });
3122
+ var Activated = (0, import_react20.forwardRef)(function ActivationFlowActivated({ asChild, children, ...rest }, forwardedRef) {
3123
+ const ctx = useFlowCtx("Activated");
3124
+ if (ctx.step !== "activated") return null;
3125
+ const Comp = asChild ? Slot : "div";
3126
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-activated": "", ...rest, children });
3127
+ });
3128
+ var Loading3 = (0, import_react20.forwardRef)(function ActivationFlowLoading({ asChild, children, ...rest }, forwardedRef) {
3129
+ const ctx = useFlowCtx("Loading");
3130
+ if (ctx.plan) return null;
3131
+ const Comp = asChild ? Slot : "div";
3132
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Comp, { ref: forwardedRef, "data-solvapay-activation-flow-loading": "", ...rest, children });
3133
+ });
3134
+ var ErrorSlot3 = (0, import_react20.forwardRef)(function ActivationFlowError({ asChild, children, ...rest }, forwardedRef) {
3135
+ const ctx = useFlowCtx("Error");
3136
+ if (ctx.step !== "error") return null;
3137
+ const Comp = asChild ? Slot : "div";
3138
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3139
+ Comp,
3140
+ {
3141
+ ref: forwardedRef,
3142
+ role: "alert",
3143
+ "data-solvapay-activation-flow-error": "",
3144
+ ...rest,
3145
+ children: children ?? ctx.error
3146
+ }
3147
+ );
3148
+ });
3149
+ var ActivationFlowRoot2 = Root5;
3150
+ var ActivationFlowSummary2 = Summary2;
3151
+ var ActivationFlowActivateButton2 = ActivateButton;
3152
+ var ActivationFlowAmountPicker = AmountPickerMount;
3153
+ var ActivationFlowContinueButton2 = ContinueButton;
3154
+ var ActivationFlowRetrying2 = Retrying;
3155
+ var ActivationFlowActivated2 = Activated;
3156
+ var ActivationFlowLoading2 = Loading3;
3157
+ var ActivationFlowError2 = ErrorSlot3;
3158
+ var ActivationFlow = {
3159
+ Root: Root5,
3160
+ Summary: Summary2,
3161
+ ActivateButton,
3162
+ AmountPicker: AmountPickerMount,
3163
+ ContinueButton,
3164
+ Retrying,
3165
+ Activated,
3166
+ Loading: Loading3,
3167
+ Error: ErrorSlot3
3168
+ };
3169
+ function useActivationFlow() {
3170
+ return useFlowCtx("useActivationFlow");
3171
+ }
3172
+
3173
+ // src/primitives/BalanceBadge.tsx
3174
+ var import_react21 = require("react");
3175
+ var import_jsx_runtime14 = require("react/jsx-runtime");
3176
+ var BalanceBadge = (0, import_react21.forwardRef)(function BalanceBadge2({ asChild, numberOnly, lowThreshold = 10, children, ...rest }, forwardedRef) {
3177
+ const solva = (0, import_react21.useContext)(SolvaPayContext);
3178
+ if (!solva) throw new MissingProviderError("BalanceBadge");
3179
+ const { credits, displayCurrency, creditsPerMinorUnit, displayExchangeRate, loading } = useBalance();
3180
+ const copy = useCopy();
3181
+ const locale = useLocale();
3182
+ const state = loading ? "loading" : credits == null || credits === 0 ? "zero" : credits <= lowThreshold ? "low" : "ok";
3183
+ const commonProps = {
3184
+ "data-solvapay-balance-badge": "",
3185
+ "data-state": state,
3186
+ "data-credits": credits == null ? void 0 : String(credits),
3187
+ "aria-busy": loading,
3188
+ ...rest
3189
+ };
3190
+ if (state === "loading") {
3191
+ if (asChild) {
3192
+ return (
3193
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3194
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, {}) })
3195
+ );
3196
+ }
3197
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { ref: forwardedRef, ...commonProps });
3198
+ }
3199
+ if (credits == null) return null;
3200
+ const formattedCredits = new Intl.NumberFormat(locale).format(credits);
3201
+ let currencyEquivalent = "";
3202
+ if (!numberOnly && displayCurrency && creditsPerMinorUnit) {
3203
+ const usdCents = credits / creditsPerMinorUnit;
3204
+ const displayMajorUnits = usdCents * (displayExchangeRate ?? 1) / 100;
3205
+ const formatted = new Intl.NumberFormat(locale, {
3206
+ style: "currency",
3207
+ currency: displayCurrency,
3208
+ minimumFractionDigits: 2
3209
+ }).format(displayMajorUnits);
3210
+ currencyEquivalent = interpolate(copy.balance.currencyEquivalent, { amount: formatted });
3211
+ }
3212
+ const content = children ?? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
3213
+ formattedCredits,
3214
+ !numberOnly && copy.balance.credits,
3215
+ !numberOnly && currencyEquivalent
3216
+ ] });
3217
+ if (asChild) {
3218
+ return (
3219
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3220
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: content })
3221
+ );
3222
+ }
3223
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { ref: forwardedRef, ...commonProps, children: content });
3224
+ });
3225
+
3226
+ // src/primitives/CancelPlanButton.tsx
3227
+ var import_react23 = require("react");
3228
+
3229
+ // src/hooks/usePurchaseActions.ts
3230
+ var import_react22 = require("react");
3231
+ function usePurchaseActions() {
3232
+ const ctx = useSolvaPay();
3233
+ const [isCancelling, setIsCancelling] = (0, import_react22.useState)(false);
3234
+ const [isReactivating, setIsReactivating] = (0, import_react22.useState)(false);
3235
+ const [isActivating, setIsActivating] = (0, import_react22.useState)(false);
3236
+ const cancelRenewal = (0, import_react22.useCallback)(
3237
+ async (params) => {
3238
+ setIsCancelling(true);
3239
+ try {
3240
+ return await ctx.cancelRenewal(params);
3241
+ } finally {
3242
+ setIsCancelling(false);
3243
+ }
3244
+ },
3245
+ [ctx.cancelRenewal]
3246
+ );
3247
+ const reactivateRenewal = (0, import_react22.useCallback)(
3248
+ async (params) => {
3249
+ setIsReactivating(true);
3250
+ try {
3251
+ return await ctx.reactivateRenewal(params);
3252
+ } finally {
3253
+ setIsReactivating(false);
3254
+ }
3255
+ },
3256
+ [ctx.reactivateRenewal]
3257
+ );
3258
+ const activatePlan = (0, import_react22.useCallback)(
3259
+ async (params) => {
3260
+ setIsActivating(true);
3261
+ try {
3262
+ return await ctx.activatePlan(params);
3263
+ } finally {
3264
+ setIsActivating(false);
3265
+ }
3266
+ },
3267
+ [ctx.activatePlan]
3268
+ );
3269
+ return {
3270
+ cancelRenewal,
3271
+ reactivateRenewal,
3272
+ activatePlan,
3273
+ isCancelling,
3274
+ isReactivating,
3275
+ isActivating
3276
+ };
3277
+ }
3278
+
3279
+ // src/primitives/CancelPlanButton.tsx
3280
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3281
+ function resolveConfirmText(confirm, purchase, defaults) {
3282
+ if (confirm === false) return null;
3283
+ if (typeof confirm === "string") return confirm;
3284
+ const planType = purchase?.planSnapshot?.planType;
3285
+ if (planType === "usage-based") return defaults.usageBased;
3286
+ return defaults.recurring;
3287
+ }
3288
+ var CancelPlanButton = (0, import_react23.forwardRef)(
3289
+ function CancelPlanButton2({
3290
+ asChild,
3291
+ purchaseRef,
3292
+ reason,
3293
+ confirm = true,
3294
+ onCancelled,
3295
+ onError,
3296
+ onClick,
3297
+ children,
3298
+ ...rest
3299
+ }, forwardedRef) {
3300
+ const solva = (0, import_react23.useContext)(SolvaPayContext);
3301
+ if (!solva) throw new MissingProviderError("CancelPlanButton");
3302
+ const copy = useCopy();
3303
+ const { activePurchase } = usePurchase();
3304
+ const { cancelRenewal, isCancelling } = usePurchaseActions();
3305
+ const effectivePurchase = purchaseRef ? activePurchase && activePurchase.reference === purchaseRef ? activePurchase : { reference: purchaseRef } : activePurchase;
3306
+ const effectiveRef = purchaseRef ?? effectivePurchase?.reference;
3307
+ const cancel = (0, import_react23.useCallback)(async () => {
3308
+ if (!effectiveRef) return;
3309
+ const confirmText = resolveConfirmText(confirm, effectivePurchase, {
3310
+ recurring: copy.cancelPlan.confirmRecurring,
3311
+ usageBased: copy.cancelPlan.confirmUsageBased
3312
+ });
3313
+ if (confirmText && typeof window !== "undefined" && !window.confirm(confirmText)) {
3314
+ return;
3315
+ }
3316
+ try {
3317
+ await cancelRenewal({ purchaseRef: effectiveRef, reason });
3318
+ onCancelled?.();
3319
+ } catch (err) {
3320
+ onError?.(err instanceof Error ? err : new Error(String(err)));
3321
+ }
3322
+ }, [effectiveRef, effectivePurchase, confirm, copy, cancelRenewal, reason, onCancelled, onError]);
3323
+ const disabled = isCancelling || !effectiveRef;
3324
+ const state = isCancelling ? "cancelling" : "idle";
3325
+ const commonProps = {
3326
+ "data-solvapay-cancel-plan": "",
3327
+ "data-state": state,
3328
+ "aria-busy": isCancelling,
3329
+ "aria-disabled": disabled || void 0,
3330
+ disabled,
3331
+ onClick: composeEventHandlers(onClick, () => {
3332
+ void cancel();
3333
+ }),
3334
+ ...rest
3335
+ };
3336
+ const label = isCancelling ? copy.cancelPlan.buttonLoading : copy.cancelPlan.button;
3337
+ if (asChild) {
3338
+ return (
3339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3340
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: label }) })
3341
+ );
3342
+ }
3343
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? label });
3344
+ }
3345
+ );
3346
+
3347
+ // src/primitives/CancelledPlanNotice.tsx
3348
+ var import_react25 = require("react");
3349
+
3350
+ // src/hooks/usePurchaseStatus.ts
3351
+ var import_react24 = require("react");
3352
+ function usePurchaseStatus() {
3353
+ const { purchases } = usePurchase();
3354
+ const locale = useLocale();
3355
+ const isPaidPurchase = (0, import_react24.useCallback)((p) => {
3356
+ return (p.amount ?? 0) > 0;
3357
+ }, []);
3358
+ const purchaseData = (0, import_react24.useMemo)(() => {
3359
+ const cancelledPaidPurchases = purchases.filter((p) => {
3360
+ return p.status === "active" && p.cancelledAt && isPaidPurchase(p);
3361
+ });
3362
+ const cancelledPurchase = cancelledPaidPurchases.sort(
3363
+ (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
3364
+ )[0] || null;
3365
+ const shouldShowCancelledNotice = !!cancelledPurchase;
3366
+ return {
3367
+ cancelledPurchase,
3368
+ shouldShowCancelledNotice
3369
+ };
3370
+ }, [purchases, isPaidPurchase]);
3371
+ const formatDate = (0, import_react24.useCallback)(
3372
+ (dateString) => {
3373
+ if (!dateString) return null;
3374
+ return new Date(dateString).toLocaleDateString(locale || "en-US", {
3375
+ year: "numeric",
3376
+ month: "long",
3377
+ day: "numeric"
3378
+ });
3379
+ },
3380
+ [locale]
3381
+ );
3382
+ const getDaysUntilExpiration = (0, import_react24.useCallback)((endDate) => {
3383
+ if (!endDate) return null;
3384
+ const now = /* @__PURE__ */ new Date();
3385
+ const expiration = new Date(endDate);
3386
+ const diffTime = expiration.getTime() - now.getTime();
3387
+ const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
3388
+ return diffDays > 0 ? diffDays : 0;
3389
+ }, []);
3390
+ return {
3391
+ cancelledPurchase: purchaseData.cancelledPurchase,
3392
+ shouldShowCancelledNotice: purchaseData.shouldShowCancelledNotice,
3393
+ formatDate,
3394
+ getDaysUntilExpiration
3395
+ };
3396
+ }
3397
+
3398
+ // src/primitives/CancelledPlanNotice.tsx
3399
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3400
+ var CancelledPlanNoticeContext = (0, import_react25.createContext)(null);
3401
+ function useNoticeCtx(part) {
3402
+ const ctx = (0, import_react25.useContext)(CancelledPlanNoticeContext);
3403
+ if (!ctx) {
3404
+ throw new Error(
3405
+ `CancelledPlanNotice.${part} must be rendered inside <CancelledPlanNotice.Root>.`
3406
+ );
3407
+ }
3408
+ return ctx;
3409
+ }
3410
+ var Root6 = (0, import_react25.forwardRef)(function CancelledPlanNoticeRoot({ onReactivated, onError, asChild, children, ...rest }, forwardedRef) {
3411
+ const solva = (0, import_react25.useContext)(SolvaPayContext);
3412
+ if (!solva) throw new MissingProviderError("CancelledPlanNotice");
3413
+ const {
3414
+ cancelledPurchase,
3415
+ shouldShowCancelledNotice,
3416
+ formatDate,
3417
+ getDaysUntilExpiration
3418
+ } = usePurchaseStatus();
3419
+ const { reactivateRenewal, isReactivating } = usePurchaseActions();
3420
+ const reactivate = (0, import_react25.useCallback)(async () => {
3421
+ if (!cancelledPurchase) return;
3422
+ try {
3423
+ await reactivateRenewal({ purchaseRef: cancelledPurchase.reference });
3424
+ onReactivated?.();
3425
+ } catch (err) {
3426
+ onError?.(err instanceof Error ? err : new Error(String(err)));
3427
+ }
3428
+ }, [cancelledPurchase, reactivateRenewal, onReactivated, onError]);
3429
+ const daysRemaining = (0, import_react25.useMemo)(
3430
+ () => cancelledPurchase ? getDaysUntilExpiration(cancelledPurchase.endDate) : null,
3431
+ [cancelledPurchase, getDaysUntilExpiration]
3432
+ );
3433
+ const ctx = (0, import_react25.useMemo)(() => {
3434
+ if (!cancelledPurchase) return null;
3435
+ const state = daysRemaining != null && daysRemaining > 0 ? "active" : "expired";
3436
+ return {
3437
+ purchase: cancelledPurchase,
3438
+ state,
3439
+ daysRemaining,
3440
+ hasReason: !!cancelledPurchase.cancellationReason,
3441
+ formatDate,
3442
+ reactivate,
3443
+ isReactivating
3444
+ };
3445
+ }, [cancelledPurchase, daysRemaining, formatDate, reactivate, isReactivating]);
3446
+ if (!shouldShowCancelledNotice || !ctx) return null;
3447
+ const Comp = asChild ? Slot : "div";
3448
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CancelledPlanNoticeContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3449
+ Comp,
3450
+ {
3451
+ ref: forwardedRef,
3452
+ "data-solvapay-cancelled-notice": "",
3453
+ "data-state": ctx.state,
3454
+ "data-has-reason": ctx.hasReason ? "" : void 0,
3455
+ ...rest,
3456
+ children
3457
+ }
3458
+ ) });
3459
+ });
3460
+ var Heading2 = (0, import_react25.forwardRef)(function CancelledPlanNoticeHeading({ asChild, children, ...rest }, forwardedRef) {
3461
+ useNoticeCtx("Heading");
3462
+ const copy = useCopy();
3463
+ const Comp = asChild ? Slot : "p";
3464
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-heading": "", ...rest, children: children ?? copy.cancelledNotice.heading });
3465
+ });
3466
+ var Expires = (0, import_react25.forwardRef)(function CancelledPlanNoticeExpires({ asChild, children, ...rest }, forwardedRef) {
3467
+ const ctx = useNoticeCtx("Expires");
3468
+ const copy = useCopy();
3469
+ const date = ctx.formatDate(ctx.purchase.endDate);
3470
+ if (!ctx.purchase.endDate) {
3471
+ const Comp2 = asChild ? Slot : "p";
3472
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Comp2, { ref: forwardedRef, "data-solvapay-cancelled-notice-expires": "", ...rest, children: children ?? copy.cancelledNotice.accessEnded });
3473
+ }
3474
+ const Comp = asChild ? Slot : "p";
3475
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-expires": "", ...rest, children: children ?? interpolate(copy.cancelledNotice.expiresLabel, { date: date ?? "" }) });
3476
+ });
3477
+ var DaysRemaining = (0, import_react25.forwardRef)(
3478
+ function CancelledPlanNoticeDaysRemaining({ asChild, children, ...rest }, forwardedRef) {
3479
+ const ctx = useNoticeCtx("DaysRemaining");
3480
+ const copy = useCopy();
3481
+ if (ctx.daysRemaining == null || ctx.daysRemaining <= 0) return null;
3482
+ const template = ctx.daysRemaining === 1 ? copy.cancelledNotice.dayRemaining : copy.cancelledNotice.daysRemaining;
3483
+ const Comp = asChild ? Slot : "p";
3484
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-days-remaining": "", ...rest, children: children ?? interpolate(template, { days: ctx.daysRemaining }) });
3485
+ }
3486
+ );
3487
+ var AccessUntil = (0, import_react25.forwardRef)(
3488
+ function CancelledPlanNoticeAccessUntil({ asChild, children, ...rest }, forwardedRef) {
3489
+ const ctx = useNoticeCtx("AccessUntil");
3490
+ const copy = useCopy();
3491
+ if (!ctx.purchase.endDate) return null;
3492
+ const Comp = asChild ? Slot : "p";
3493
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-access-until": "", ...rest, children: children ?? interpolate(copy.cancelledNotice.accessUntil, {
3494
+ product: ctx.purchase.productName
3495
+ }) });
3496
+ }
3497
+ );
3498
+ var CancelledOn = (0, import_react25.forwardRef)(
3499
+ function CancelledPlanNoticeCancelledOn({ asChild, children, ...rest }, forwardedRef) {
3500
+ const ctx = useNoticeCtx("CancelledOn");
3501
+ const copy = useCopy();
3502
+ if (!ctx.purchase.cancelledAt) return null;
3503
+ const date = ctx.formatDate(ctx.purchase.cancelledAt);
3504
+ const Comp = asChild ? Slot : "p";
3505
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-cancelled-on": "", ...rest, children: children ?? interpolate(copy.cancelledNotice.cancelledOn, { date: date ?? "" }) });
3506
+ }
3507
+ );
3508
+ var Reason = (0, import_react25.forwardRef)(function CancelledPlanNoticeReason({ asChild, children, ...rest }, forwardedRef) {
3509
+ const ctx = useNoticeCtx("Reason");
3510
+ if (!ctx.hasReason) return null;
3511
+ const Comp = asChild ? Slot : "span";
3512
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Comp, { ref: forwardedRef, "data-solvapay-cancelled-notice-reason": "", ...rest, children: children ?? ctx.purchase.cancellationReason });
3513
+ });
3514
+ var ReactivateButton = (0, import_react25.forwardRef)(
3515
+ function CancelledPlanNoticeReactivateButton({ asChild, onClick, children, ...rest }, forwardedRef) {
3516
+ const ctx = useNoticeCtx("ReactivateButton");
3517
+ const copy = useCopy();
3518
+ const disabled = ctx.isReactivating;
3519
+ const commonProps = {
3520
+ "data-solvapay-cancelled-notice-reactivate": "",
3521
+ "data-state": ctx.isReactivating ? "reactivating" : "idle",
3522
+ "aria-busy": ctx.isReactivating,
3523
+ "aria-disabled": disabled || void 0,
3524
+ disabled,
3525
+ onClick: composeEventHandlers(onClick, () => {
3526
+ void ctx.reactivate();
3527
+ }),
3528
+ ...rest
3529
+ };
3530
+ const label = ctx.isReactivating ? copy.cancelledNotice.reactivateButtonLoading : copy.cancelledNotice.reactivateButton;
3531
+ if (asChild) {
3532
+ return (
3533
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3534
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: label }) })
3535
+ );
3536
+ }
3537
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { ref: forwardedRef, type: "button", ...commonProps, children: children ?? label });
3538
+ }
3539
+ );
3540
+ var CancelledPlanNoticeRoot2 = Root6;
3541
+ var CancelledPlanNoticeHeading2 = Heading2;
3542
+ var CancelledPlanNoticeExpires2 = Expires;
3543
+ var CancelledPlanNoticeDaysRemaining2 = DaysRemaining;
3544
+ var CancelledPlanNoticeAccessUntil2 = AccessUntil;
3545
+ var CancelledPlanNoticeCancelledOn2 = CancelledOn;
3546
+ var CancelledPlanNoticeReason2 = Reason;
3547
+ var CancelledPlanNoticeReactivateButton2 = ReactivateButton;
3548
+ var CancelledPlanNotice = {
3549
+ Root: Root6,
3550
+ Heading: Heading2,
3551
+ Expires,
3552
+ DaysRemaining,
3553
+ AccessUntil,
3554
+ CancelledOn,
3555
+ Reason,
3556
+ ReactivateButton
3557
+ };
3558
+ function useCancelledPlanNotice() {
3559
+ return useNoticeCtx("useCancelledPlanNotice");
3560
+ }
3561
+
3562
+ // src/primitives/CreditGate.tsx
3563
+ var import_react28 = require("react");
3564
+
3565
+ // src/primitives/TopupForm.tsx
3566
+ var import_react27 = require("react");
3567
+ var import_react_stripe_js2 = require("@stripe/react-stripe-js");
3568
+
3569
+ // src/hooks/useTopup.ts
3570
+ var import_react26 = require("react");
3571
+ var import_stripe_js2 = require("@stripe/stripe-js");
3572
+ var stripePromiseCache2 = /* @__PURE__ */ new Map();
3573
+ function getStripeCacheKey2(publishableKey, accountId) {
3574
+ return accountId ? `${publishableKey}:${accountId}` : publishableKey;
3575
+ }
3576
+ function useTopup(options) {
3577
+ const { amount, currency } = options;
3578
+ const { createTopupPayment, customerRef, updateCustomerRef } = useSolvaPay();
3579
+ const [loading, setLoading] = (0, import_react26.useState)(false);
3580
+ const [error, setError] = (0, import_react26.useState)(null);
3581
+ const [stripePromise, setStripePromise] = (0, import_react26.useState)(null);
3582
+ const [clientSecret, setClientSecret] = (0, import_react26.useState)(null);
3583
+ const isStartingRef = (0, import_react26.useRef)(false);
3584
+ const startTopup = (0, import_react26.useCallback)(async () => {
3585
+ if (isStartingRef.current || loading) {
3586
+ return;
3587
+ }
3588
+ if (!amount || amount <= 0) {
3589
+ setError(new Error("useTopup: amount must be a positive number"));
3590
+ return;
3591
+ }
3592
+ isStartingRef.current = true;
3593
+ setLoading(true);
3594
+ setError(null);
3595
+ try {
3596
+ const result = await createTopupPayment({ amount, currency });
3597
+ if (!result || typeof result !== "object") {
3598
+ throw new Error("Invalid topup payment intent response from server");
3599
+ }
3600
+ if (!result.clientSecret || typeof result.clientSecret !== "string") {
3601
+ throw new Error("Invalid client secret in topup payment intent response");
3602
+ }
3603
+ if (!result.publishableKey || typeof result.publishableKey !== "string") {
3604
+ throw new Error("Invalid publishable key in topup payment intent response");
3605
+ }
3606
+ if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
3607
+ updateCustomerRef(result.customerRef);
3608
+ }
3609
+ const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
3610
+ const cacheKey = getStripeCacheKey2(result.publishableKey, result.accountId);
3611
+ let stripe = stripePromiseCache2.get(cacheKey);
3612
+ if (!stripe) {
3613
+ stripe = (0, import_stripe_js2.loadStripe)(result.publishableKey, stripeOptions);
3614
+ stripePromiseCache2.set(cacheKey, stripe);
3615
+ }
3616
+ setStripePromise(stripe);
3617
+ setClientSecret(result.clientSecret);
3618
+ } catch (err) {
3619
+ const error2 = err instanceof Error ? err : new Error("Failed to start topup");
3620
+ setError(error2);
3621
+ } finally {
3622
+ setLoading(false);
3623
+ isStartingRef.current = false;
3624
+ }
3625
+ }, [amount, currency, createTopupPayment, customerRef, updateCustomerRef, loading]);
3626
+ const reset = (0, import_react26.useCallback)(() => {
3627
+ isStartingRef.current = false;
3628
+ setLoading(false);
3629
+ setError(null);
3630
+ setStripePromise(null);
3631
+ setClientSecret(null);
3632
+ }, []);
3633
+ return {
3634
+ loading,
3635
+ error,
3636
+ stripePromise,
3637
+ clientSecret,
3638
+ startTopup,
3639
+ reset
3640
+ };
3641
+ }
3642
+
3643
+ // src/primitives/TopupForm.tsx
3644
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3645
+ var TopupFormContext = (0, import_react27.createContext)(null);
3646
+ function useTopupCtx(part) {
3647
+ const ctx = (0, import_react27.useContext)(TopupFormContext);
3648
+ if (!ctx) {
3649
+ throw new Error(`TopupForm.${part} must be rendered inside <TopupForm.Root>.`);
3650
+ }
3651
+ return ctx;
3652
+ }
3653
+ var Root7 = (0, import_react27.forwardRef)(function TopupFormRoot(props, forwardedRef) {
3654
+ const {
3655
+ amount,
3656
+ currency,
3657
+ onSuccess,
3658
+ onError,
3659
+ returnUrl,
3660
+ submitButtonText: _submitButtonText,
3661
+ buttonClassName: _buttonClassName,
3662
+ className,
3663
+ asChild,
3664
+ children,
3665
+ ...rest
3666
+ } = props;
3667
+ const solva = (0, import_react27.useContext)(SolvaPayContext);
3668
+ if (!solva) throw new MissingProviderError("TopupForm");
3669
+ const copy = useCopy();
3670
+ const locale = useLocale();
3671
+ const { loading, error: topupError, clientSecret, startTopup, stripePromise } = useTopup({
3672
+ amount,
3673
+ currency
3674
+ });
3675
+ const hasInitializedRef = (0, import_react27.useRef)(false);
3676
+ const hasAmount = amount > 0;
3677
+ (0, import_react27.useEffect)(() => {
3678
+ if (!hasInitializedRef.current && hasAmount && !loading && !topupError && !clientSecret) {
3679
+ hasInitializedRef.current = true;
3680
+ startTopup().catch(() => {
3681
+ hasInitializedRef.current = false;
3682
+ });
3683
+ }
3684
+ if (hasAmount && clientSecret) hasInitializedRef.current = true;
3685
+ }, [hasAmount, loading, topupError, clientSecret, startTopup]);
3686
+ const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
3687
+ const elementsOptions = (0, import_react27.useMemo)(() => {
3688
+ if (!clientSecret) return void 0;
3689
+ return { clientSecret, locale };
3690
+ }, [clientSecret, locale]);
3691
+ const Comp = asChild ? Slot : "div";
3692
+ const outerError = !hasAmount ? copy.errors.configMissingAmount : topupError ? `${copy.errors.topupInitFailed} ${topupError.message || copy.errors.unknownError}` : null;
3693
+ const dataState = outerError ? "error" : clientSecret && stripePromise ? "ready" : "loading";
3694
+ const canMountElements = !!(stripePromise && clientSecret && elementsOptions);
3695
+ const innerCommon = {
3696
+ amount,
3697
+ currency,
3698
+ clientSecret,
3699
+ returnUrl: finalReturnUrl,
3700
+ outerError,
3701
+ state: dataState,
3702
+ onSuccess,
3703
+ onError
3704
+ };
3705
+ const shell = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3706
+ Comp,
3707
+ {
3708
+ ref: forwardedRef,
3709
+ className,
3710
+ "data-solvapay-topup-form": "",
3711
+ "data-state": dataState,
3712
+ ...rest,
3713
+ children
3714
+ }
3715
+ );
3716
+ if (canMountElements) {
3717
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_stripe_js2.Elements, { stripe: stripePromise, options: elementsOptions, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Inner, { ...innerCommon, children: shell }) }, clientSecret);
3718
+ }
3719
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(OfflineInner, { ...innerCommon, children: shell });
3720
+ });
3721
+ var Inner = ({
3722
+ amount,
3723
+ currency,
3724
+ clientSecret,
3725
+ returnUrl,
3726
+ outerError,
3727
+ state,
3728
+ onSuccess,
3729
+ onError,
3730
+ children
3731
+ }) => {
3732
+ const stripe = (0, import_react_stripe_js2.useStripe)();
3733
+ const elements = (0, import_react_stripe_js2.useElements)();
3734
+ const copy = useCopy();
3735
+ const [paymentInputComplete, setPaymentInputComplete] = (0, import_react27.useState)(false);
3736
+ const [isProcessing, setIsProcessing] = (0, import_react27.useState)(false);
3737
+ const [error, setError] = (0, import_react27.useState)(null);
3738
+ const isReady = !!(stripe && elements);
3739
+ const canSubmit = isReady && paymentInputComplete && !isProcessing && !!clientSecret;
3740
+ const submit = (0, import_react27.useCallback)(async () => {
3741
+ if (!stripe || !elements || !clientSecret) {
3742
+ const msg = copy.errors.stripeUnavailable;
3743
+ setError(msg);
3744
+ onError?.(new Error(msg));
3745
+ return;
3746
+ }
3747
+ setError(null);
3748
+ setIsProcessing(true);
3749
+ const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
3750
+ elements,
3751
+ clientSecret,
3752
+ confirmParams: { return_url: returnUrl },
3753
+ redirect: "if_required"
3754
+ });
3755
+ if (confirmError) {
3756
+ const msg = confirmError.message || copy.errors.paymentUnexpected;
3757
+ setError(msg);
3758
+ setIsProcessing(false);
3759
+ onError?.(new Error(msg));
3760
+ return;
3761
+ }
3762
+ setIsProcessing(false);
3763
+ if (paymentIntent) {
3764
+ await onSuccess?.(paymentIntent);
3765
+ }
3766
+ }, [stripe, elements, clientSecret, returnUrl, copy, onSuccess, onError]);
3767
+ const effectiveError = error ?? outerError;
3768
+ const ctx = (0, import_react27.useMemo)(
3769
+ () => ({
3770
+ amount,
3771
+ currency,
3772
+ state,
3773
+ clientSecret,
3774
+ stripe: stripe ?? null,
3775
+ elements: elements ?? null,
3776
+ isReady,
3777
+ isProcessing,
3778
+ paymentInputComplete,
3779
+ canSubmit,
3780
+ error: effectiveError,
3781
+ returnUrl,
3782
+ setPaymentInputComplete,
3783
+ submit
3784
+ }),
3785
+ [
3786
+ amount,
3787
+ currency,
3788
+ state,
3789
+ clientSecret,
3790
+ stripe,
3791
+ elements,
3792
+ isReady,
3793
+ isProcessing,
3794
+ paymentInputComplete,
3795
+ canSubmit,
3796
+ effectiveError,
3797
+ returnUrl,
3798
+ submit
3799
+ ]
3800
+ );
3801
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TopupFormContext.Provider, { value: ctx, children });
3802
+ };
3803
+ var OfflineInner = ({
3804
+ amount,
3805
+ currency,
3806
+ clientSecret,
3807
+ returnUrl,
3808
+ outerError,
3809
+ state,
3810
+ children
3811
+ }) => {
3812
+ const noopSubmit = (0, import_react27.useCallback)(async () => {
3813
+ }, []);
3814
+ const noopSet = (0, import_react27.useCallback)(() => {
3815
+ }, []);
3816
+ const ctx = (0, import_react27.useMemo)(
3817
+ () => ({
3818
+ amount,
3819
+ currency,
3820
+ state,
3821
+ clientSecret,
3822
+ stripe: null,
3823
+ elements: null,
3824
+ isReady: false,
3825
+ isProcessing: false,
3826
+ paymentInputComplete: false,
3827
+ canSubmit: false,
3828
+ error: outerError,
3829
+ returnUrl,
3830
+ setPaymentInputComplete: noopSet,
3831
+ submit: noopSubmit
3832
+ }),
3833
+ [amount, currency, state, clientSecret, outerError, returnUrl, noopSet, noopSubmit]
3834
+ );
3835
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TopupFormContext.Provider, { value: ctx, children });
3836
+ };
3837
+ var PaymentElementSlot2 = ({ options }) => {
3838
+ const { setPaymentInputComplete, isReady, stripe, elements } = useTopupCtx("PaymentElement");
3839
+ const locale = useLocale();
3840
+ if (!stripe || !elements) return null;
3841
+ if (!isReady) {
3842
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { "data-solvapay-topup-form-loading": "", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, { size: "sm" }) });
3843
+ }
3844
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { "data-solvapay-topup-form-payment-element": "", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3845
+ import_react_stripe_js2.PaymentElement,
3846
+ {
3847
+ options,
3848
+ onChange: (e) => setPaymentInputComplete(e.complete)
3849
+ },
3850
+ locale || "default"
3851
+ ) });
3852
+ };
3853
+ var SubmitButton2 = (0, import_react27.forwardRef)(function TopupFormSubmitButton({ asChild, onClick, children, ...rest }, forwardedRef) {
3854
+ const ctx = useTopupCtx("SubmitButton");
3855
+ const copy = useCopy();
3856
+ const dataState = ctx.isProcessing ? "processing" : !ctx.canSubmit ? "disabled" : "idle";
3857
+ const content = ctx.isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3858
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, { size: "sm" }),
3859
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: copy.cta.processing })
3860
+ ] }) : children ? children : copy.cta.topUp;
3861
+ const commonProps = {
3862
+ "data-solvapay-topup-form-submit": "",
3863
+ "data-state": dataState,
3864
+ "aria-busy": ctx.isProcessing,
3865
+ "aria-disabled": !ctx.canSubmit,
3866
+ disabled: !ctx.canSubmit,
3867
+ onClick: composeEventHandlers(onClick, (e) => {
3868
+ e.preventDefault();
3869
+ void ctx.submit();
3870
+ }),
3871
+ ...rest
3872
+ };
3873
+ if (asChild) {
3874
+ return (
3875
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3876
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: content })
3877
+ );
3878
+ }
3879
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { ref: forwardedRef, type: "submit", ...commonProps, children: content });
3880
+ });
3881
+ var Loading4 = (0, import_react27.forwardRef)(function TopupFormLoading({ asChild, children, ...rest }, forwardedRef) {
3882
+ const ctx = useTopupCtx("Loading");
3883
+ if (ctx.state !== "loading") return null;
3884
+ const Comp = asChild ? Slot : "div";
3885
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Comp, { ref: forwardedRef, "data-solvapay-topup-form-loading": "", ...rest, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, { size: "sm" }) });
3886
+ });
3887
+ var ErrorSlot4 = (0, import_react27.forwardRef)(function TopupFormError({ asChild, children, ...rest }, forwardedRef) {
3888
+ const ctx = useTopupCtx("Error");
3889
+ if (!ctx.error) return null;
3890
+ const Comp = asChild ? Slot : "div";
3891
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3892
+ Comp,
3893
+ {
3894
+ ref: forwardedRef,
3895
+ role: "alert",
3896
+ "aria-live": "assertive",
3897
+ "aria-atomic": "true",
3898
+ "data-solvapay-topup-form-error": "",
3899
+ ...rest,
3900
+ children: children ?? ctx.error
3901
+ }
3902
+ );
3903
+ });
3904
+ var TopupFormRoot2 = Root7;
3905
+ var TopupFormPaymentElement = PaymentElementSlot2;
3906
+ var TopupFormSubmitButton2 = SubmitButton2;
3907
+ var TopupFormLoading2 = Loading4;
3908
+ var TopupFormError2 = ErrorSlot4;
3909
+ var TopupForm = {
3910
+ Root: Root7,
3911
+ AmountPicker: AmountPicker.Root,
3912
+ PaymentElement: PaymentElementSlot2,
3913
+ SubmitButton: SubmitButton2,
3914
+ Loading: Loading4,
3915
+ Error: ErrorSlot4
3916
+ };
3917
+ function useTopupForm() {
3918
+ return useTopupCtx("useTopupForm");
3919
+ }
3920
+
3921
+ // src/TopupForm.tsx
3922
+ var import_jsx_runtime18 = require("react/jsx-runtime");
3923
+ var TopupForm2 = (props) => {
3924
+ const { className, buttonClassName, submitButtonText, ...rootProps } = props;
3925
+ const rootClass = ["solvapay-topup-form", className].filter(Boolean).join(" ");
3926
+ const buttonClass = ["solvapay-topup-form-submit", buttonClassName].filter(Boolean).join(" ");
3927
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(TopupForm.Root, { ...rootProps, className: rootClass, children: [
3928
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TopupForm.PaymentElement, {}),
3929
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TopupForm.Error, { className: "solvapay-topup-form-error" }),
3930
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TopupForm.Loading, { className: "solvapay-topup-form-loading" }),
3931
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TopupForm.SubmitButton, { className: buttonClass, children: submitButtonText })
3932
+ ] });
3933
+ };
3934
+
3935
+ // src/primitives/CreditGate.tsx
3936
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3937
+ var CreditGateContext = (0, import_react28.createContext)(null);
3938
+ function useGateCtx(part) {
3939
+ const ctx = (0, import_react28.useContext)(CreditGateContext);
3940
+ if (!ctx) {
3941
+ throw new Error(`CreditGate.${part} must be rendered inside <CreditGate.Root>.`);
3942
+ }
3943
+ return ctx;
3944
+ }
3945
+ var Root8 = (0, import_react28.forwardRef)(function CreditGateRoot({
3946
+ minCredits = 1,
3947
+ productRef,
3948
+ topupAmount = 1e3,
3949
+ topupCurrency = "usd",
3950
+ asChild,
3951
+ children,
3952
+ ...rest
3953
+ }, forwardedRef) {
3954
+ const solva = (0, import_react28.useContext)(SolvaPayContext);
3955
+ if (!solva) throw new MissingProviderError("CreditGate");
3956
+ const { credits, loading } = useBalance();
3957
+ const { product } = useProduct(productRef);
3958
+ const hasCredits = credits != null && credits >= minCredits;
3959
+ const state = loading && credits == null ? "loading" : hasCredits ? "allowed" : "blocked";
3960
+ const ctx = (0, import_react28.useMemo)(
3961
+ () => ({
3962
+ state,
3963
+ loading,
3964
+ hasCredits,
3965
+ balance: credits,
3966
+ productName: product?.name ?? null,
3967
+ topupAmount,
3968
+ topupCurrency
3969
+ }),
3970
+ [state, loading, hasCredits, credits, product?.name, topupAmount, topupCurrency]
3971
+ );
3972
+ const Comp = asChild ? Slot : "div";
3973
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CreditGateContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3974
+ Comp,
3975
+ {
3976
+ ref: forwardedRef,
3977
+ "data-solvapay-credit-gate": "",
3978
+ "data-state": state,
3979
+ ...rest,
3980
+ children
3981
+ }
3982
+ ) });
3983
+ });
3984
+ var Heading3 = (0, import_react28.forwardRef)(function CreditGateHeading({ asChild, children, ...rest }, forwardedRef) {
3985
+ const ctx = useGateCtx("Heading");
3986
+ const copy = useCopy();
3987
+ if (ctx.state !== "blocked") return null;
3988
+ const Comp = asChild ? Slot : "h3";
3989
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-heading": "", ...rest, children: children ?? copy.creditGate.lowBalanceHeading });
3990
+ });
3991
+ var Subheading = (0, import_react28.forwardRef)(function CreditGateSubheading({ asChild, children, ...rest }, forwardedRef) {
3992
+ const ctx = useGateCtx("Subheading");
3993
+ const copy = useCopy();
3994
+ if (ctx.state !== "blocked") return null;
3995
+ const text = interpolate(copy.creditGate.lowBalanceSubheading, {
3996
+ product: ctx.productName ?? "this product"
3997
+ });
3998
+ const Comp = asChild ? Slot : "p";
3999
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-subheading": "", ...rest, children: children ?? text });
4000
+ });
4001
+ var Topup = ({ amount, currency }) => {
4002
+ const ctx = useGateCtx("Topup");
4003
+ if (ctx.state !== "blocked") return null;
4004
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4005
+ TopupForm2,
4006
+ {
4007
+ amount: amount ?? ctx.topupAmount,
4008
+ currency: currency ?? ctx.topupCurrency
4009
+ }
4010
+ );
4011
+ };
4012
+ var Loading5 = (0, import_react28.forwardRef)(function CreditGateLoading({ asChild, children, ...rest }, forwardedRef) {
4013
+ const ctx = useGateCtx("Loading");
4014
+ if (ctx.state !== "loading") return null;
4015
+ const Comp = asChild ? Slot : "div";
4016
+ return (
4017
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4018
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-loading": "", ...rest, children })
4019
+ );
4020
+ });
4021
+ var ErrorSlot5 = (0, import_react28.forwardRef)(function CreditGateError({ asChild, children, ...rest }, forwardedRef) {
4022
+ if (!children) return null;
4023
+ const Comp = asChild ? Slot : "div";
4024
+ return (
4025
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4026
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Comp, { ref: forwardedRef, "data-solvapay-credit-gate-error": "", role: "alert", ...rest, children })
4027
+ );
4028
+ });
4029
+ var CreditGateRoot2 = Root8;
4030
+ var CreditGateHeading2 = Heading3;
4031
+ var CreditGateSubheading2 = Subheading;
4032
+ var CreditGateTopup = Topup;
4033
+ var CreditGateLoading2 = Loading5;
4034
+ var CreditGateError2 = ErrorSlot5;
4035
+ var CreditGate = {
4036
+ Root: Root8,
4037
+ Heading: Heading3,
4038
+ Subheading,
4039
+ Topup,
4040
+ Loading: Loading5,
4041
+ Error: ErrorSlot5
4042
+ };
4043
+ function useCreditGate() {
4044
+ return useGateCtx("useCreditGate");
4045
+ }
4046
+
4047
+ // src/primitives/PurchaseGate.tsx
4048
+ var import_react29 = require("react");
4049
+ var import_jsx_runtime20 = require("react/jsx-runtime");
4050
+ var PurchaseGateContext = (0, import_react29.createContext)(null);
4051
+ function useGateCtx2(part) {
4052
+ const ctx = (0, import_react29.useContext)(PurchaseGateContext);
4053
+ if (!ctx) {
4054
+ throw new Error(`PurchaseGate.${part} must be rendered inside <PurchaseGate.Root>.`);
4055
+ }
4056
+ return ctx;
4057
+ }
4058
+ var Root9 = (0, import_react29.forwardRef)(function PurchaseGateRoot({ requirePlan, requireProduct, asChild, children, ...rest }, forwardedRef) {
4059
+ const solva = (0, import_react29.useContext)(SolvaPayContext);
4060
+ if (!solva) throw new MissingProviderError("PurchaseGate");
4061
+ const { purchases, loading, hasProduct, error } = usePurchase();
4062
+ const productToCheck = requireProduct || requirePlan;
4063
+ const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
4064
+ const state = loading ? "loading" : hasAccess ? "allowed" : "blocked";
4065
+ const ctx = (0, import_react29.useMemo)(
4066
+ () => ({ state, loading, hasAccess, error }),
4067
+ [state, loading, hasAccess, error]
4068
+ );
4069
+ const Comp = asChild ? Slot : "div";
4070
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PurchaseGateContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4071
+ Comp,
4072
+ {
4073
+ ref: forwardedRef,
4074
+ "data-solvapay-purchase-gate": "",
4075
+ "data-state": state,
4076
+ ...rest,
4077
+ children
4078
+ }
4079
+ ) });
4080
+ });
4081
+ var Allowed = (0, import_react29.forwardRef)(function PurchaseGateAllowed({ asChild, children, ...rest }, forwardedRef) {
4082
+ const ctx = useGateCtx2("Allowed");
4083
+ if (ctx.state !== "allowed") return null;
4084
+ const Comp = asChild ? Slot : "div";
4085
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-allowed": "", ...rest, children });
4086
+ });
4087
+ var Blocked = (0, import_react29.forwardRef)(function PurchaseGateBlocked({ asChild, children, ...rest }, forwardedRef) {
4088
+ const ctx = useGateCtx2("Blocked");
4089
+ if (ctx.state !== "blocked") return null;
4090
+ const Comp = asChild ? Slot : "div";
4091
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-blocked": "", ...rest, children });
4092
+ });
4093
+ var Loading6 = (0, import_react29.forwardRef)(function PurchaseGateLoading({ asChild, children, ...rest }, forwardedRef) {
4094
+ const ctx = useGateCtx2("Loading");
4095
+ if (ctx.state !== "loading") return null;
4096
+ const Comp = asChild ? Slot : "div";
4097
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-loading": "", ...rest, children });
4098
+ });
4099
+ var ErrorSlot6 = (0, import_react29.forwardRef)(function PurchaseGateError({ asChild, children, ...rest }, forwardedRef) {
4100
+ const ctx = useGateCtx2("Error");
4101
+ if (!ctx.error) return null;
4102
+ const Comp = asChild ? Slot : "div";
4103
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Comp, { ref: forwardedRef, "data-solvapay-purchase-gate-error": "", role: "alert", ...rest, children: children ?? ctx.error.message });
4104
+ });
4105
+ var PurchaseGateRoot2 = Root9;
4106
+ var PurchaseGateAllowed2 = Allowed;
4107
+ var PurchaseGateBlocked2 = Blocked;
4108
+ var PurchaseGateLoading2 = Loading6;
4109
+ var PurchaseGateError2 = ErrorSlot6;
4110
+ var PurchaseGate = {
4111
+ Root: Root9,
4112
+ Allowed,
4113
+ Blocked,
4114
+ Loading: Loading6,
4115
+ Error: ErrorSlot6
4116
+ };
4117
+ function usePurchaseGate() {
4118
+ return useGateCtx2("usePurchaseGate");
4119
+ }
4120
+
4121
+ // src/primitives/ProductBadge.tsx
4122
+ var import_react30 = require("react");
4123
+ var import_jsx_runtime21 = require("react/jsx-runtime");
4124
+ var ProductBadgeImpl = (0, import_react30.forwardRef)(function ProductBadge({ asChild, children, ...rest }, forwardedRef) {
4125
+ const solva = (0, import_react30.useContext)(SolvaPayContext);
4126
+ if (!solva) throw new MissingProviderError("ProductBadge");
4127
+ const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
4128
+ const copy = useCopy();
4129
+ const [hasLoadedOnce, setHasLoadedOnce] = (0, import_react30.useState)(false);
4130
+ (0, import_react30.useEffect)(() => {
4131
+ if (!loading) setHasLoadedOnce(true);
4132
+ }, [loading]);
4133
+ const planToDisplay = activePurchase?.productName ?? null;
4134
+ const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
4135
+ if (!shouldShow) return null;
4136
+ const commonProps = {
4137
+ "data-solvapay-product-badge": "",
4138
+ "data-loading": loading ? "" : void 0,
4139
+ "data-has-purchase": activePurchase ? "" : void 0,
4140
+ "data-has-paid-purchase": hasPaidPurchase ? "" : void 0,
4141
+ role: "status",
4142
+ "aria-live": "polite",
4143
+ "aria-busy": loading,
4144
+ "aria-label": interpolate(copy.product.currentProductLabel, { name: planToDisplay }),
4145
+ ...rest
4146
+ };
4147
+ if (asChild) {
4148
+ return (
4149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4150
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Slot, { ref: forwardedRef, ...commonProps, children: children ?? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_jsx_runtime21.Fragment, { children: planToDisplay }) })
4151
+ );
4152
+ }
4153
+ void purchases;
4154
+ return (
4155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4156
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { ref: forwardedRef, ...commonProps, children: children ?? planToDisplay })
4157
+ );
4158
+ });
4159
+ var ProductBadge2 = ProductBadgeImpl;
4160
+ var PlanBadge = ProductBadgeImpl;
4161
+ // Annotate the CommonJS export names for ESM import in node:
4162
+ 0 && (module.exports = {
4163
+ ActivationFlow,
4164
+ ActivationFlowActivateButton,
4165
+ ActivationFlowActivated,
4166
+ ActivationFlowAmountPicker,
4167
+ ActivationFlowContinueButton,
4168
+ ActivationFlowError,
4169
+ ActivationFlowLoading,
4170
+ ActivationFlowRetrying,
4171
+ ActivationFlowRoot,
4172
+ ActivationFlowSummary,
4173
+ AmountPicker,
4174
+ AmountPickerConfirm,
4175
+ AmountPickerCustom,
4176
+ AmountPickerOption,
4177
+ AmountPickerRoot,
4178
+ BalanceBadge,
4179
+ CancelPlanButton,
4180
+ CancelledPlanNotice,
4181
+ CancelledPlanNoticeAccessUntil,
4182
+ CancelledPlanNoticeCancelledOn,
4183
+ CancelledPlanNoticeDaysRemaining,
4184
+ CancelledPlanNoticeExpires,
4185
+ CancelledPlanNoticeHeading,
4186
+ CancelledPlanNoticeReactivateButton,
4187
+ CancelledPlanNoticeReason,
4188
+ CancelledPlanNoticeRoot,
4189
+ CheckoutSummary,
4190
+ CheckoutSummaryPlan,
4191
+ CheckoutSummaryPrice,
4192
+ CheckoutSummaryProduct,
4193
+ CheckoutSummaryRoot,
4194
+ CheckoutSummaryTaxNote,
4195
+ CheckoutSummaryTrial,
4196
+ CreditGate,
4197
+ CreditGateError,
4198
+ CreditGateHeading,
4199
+ CreditGateLoading,
4200
+ CreditGateRoot,
4201
+ CreditGateSubheading,
4202
+ CreditGateTopup,
4203
+ MandateText,
4204
+ PaymentForm,
4205
+ PaymentFormCardElement,
4206
+ PaymentFormCustomerFields,
4207
+ PaymentFormError,
4208
+ PaymentFormLoading,
4209
+ PaymentFormMandateText,
4210
+ PaymentFormPaymentElement,
4211
+ PaymentFormRoot,
4212
+ PaymentFormSubmitButton,
4213
+ PaymentFormSummary,
4214
+ PaymentFormTermsCheckbox,
4215
+ PlanBadge,
4216
+ PlanSelector,
4217
+ PlanSelectorCard,
4218
+ PlanSelectorCardBadge,
4219
+ PlanSelectorCardInterval,
4220
+ PlanSelectorCardName,
4221
+ PlanSelectorCardPrice,
4222
+ PlanSelectorError,
4223
+ PlanSelectorGrid,
4224
+ PlanSelectorHeading,
4225
+ PlanSelectorLoading,
4226
+ PlanSelectorRoot,
4227
+ ProductBadge,
4228
+ PurchaseGate,
4229
+ PurchaseGateAllowed,
4230
+ PurchaseGateBlocked,
4231
+ PurchaseGateError,
4232
+ PurchaseGateLoading,
4233
+ PurchaseGateRoot,
4234
+ Slot,
4235
+ Slottable,
4236
+ TopupForm,
4237
+ TopupFormError,
4238
+ TopupFormLoading,
4239
+ TopupFormPaymentElement,
4240
+ TopupFormRoot,
4241
+ TopupFormSubmitButton,
4242
+ composeEventHandlers,
4243
+ composeRefs,
4244
+ setRef,
4245
+ useActivationFlow,
4246
+ useAmountPicker,
4247
+ useAmountPickerCopy,
4248
+ useCancelledPlanNotice,
4249
+ useCheckoutSummary,
4250
+ useCreditGate,
4251
+ usePlanSelector,
4252
+ usePurchaseGate,
4253
+ useTopupForm
4254
+ });