@solvapay/react 1.0.8-preview.9 → 1.0.9

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,429 @@
1
+ import {
2
+ BalanceBadge,
3
+ CancelPlanButton,
4
+ Slot,
5
+ UsageMeter,
6
+ composeEventHandlers,
7
+ createHttpTransport,
8
+ createTransportCacheKey,
9
+ formatPrice,
10
+ interpolate,
11
+ useCopy,
12
+ usePurchase,
13
+ usePurchaseStatus,
14
+ useSolvaPay,
15
+ useTransport
16
+ } from "./chunk-37R5NZGF.js";
17
+
18
+ // src/hooks/usePaymentMethod.ts
19
+ import { useCallback, useEffect, useState } from "react";
20
+ var paymentMethodCache = /* @__PURE__ */ new Map();
21
+ var CACHE_DURATION = 5 * 60 * 1e3;
22
+ function cacheKeyFor(config) {
23
+ return createTransportCacheKey(
24
+ config,
25
+ config?.api?.getPaymentMethod || "/api/payment-method"
26
+ );
27
+ }
28
+ async function fetchPaymentMethod(config) {
29
+ const transport = config?.transport ?? createHttpTransport(config);
30
+ if (!transport.getPaymentMethod) return null;
31
+ return transport.getPaymentMethod();
32
+ }
33
+ function usePaymentMethod() {
34
+ const { _config } = useSolvaPay();
35
+ const key = cacheKeyFor(_config);
36
+ const [paymentMethod, setPaymentMethod] = useState(
37
+ () => paymentMethodCache.get(key)?.paymentMethod ?? null
38
+ );
39
+ const [loading, setLoading] = useState(() => {
40
+ const cached = paymentMethodCache.get(key);
41
+ return !cached || !cached.paymentMethod && !cached.promise;
42
+ });
43
+ const [error, setError] = useState(null);
44
+ const load = useCallback(
45
+ async (force = false) => {
46
+ const cached = paymentMethodCache.get(key);
47
+ const now = Date.now();
48
+ if (!force && cached?.paymentMethod && now - cached.timestamp < CACHE_DURATION) {
49
+ setPaymentMethod(cached.paymentMethod);
50
+ setLoading(false);
51
+ setError(null);
52
+ return;
53
+ }
54
+ if (!force && cached?.promise) {
55
+ try {
56
+ setLoading(true);
57
+ const pm = await cached.promise;
58
+ setPaymentMethod(pm);
59
+ setError(null);
60
+ } catch (err) {
61
+ setError(err instanceof Error ? err : new Error("Failed to load payment method"));
62
+ } finally {
63
+ setLoading(false);
64
+ }
65
+ return;
66
+ }
67
+ try {
68
+ setLoading(true);
69
+ setError(null);
70
+ const promise = fetchPaymentMethod(_config);
71
+ paymentMethodCache.set(key, {
72
+ paymentMethod: cached?.paymentMethod ?? null,
73
+ promise,
74
+ timestamp: now
75
+ });
76
+ const pm = await promise;
77
+ if (pm === null) {
78
+ paymentMethodCache.set(key, {
79
+ paymentMethod: cached?.paymentMethod ?? null,
80
+ promise: null,
81
+ timestamp: cached?.timestamp ?? now
82
+ });
83
+ setPaymentMethod(cached?.paymentMethod ?? null);
84
+ setLoading(false);
85
+ return;
86
+ }
87
+ paymentMethodCache.set(key, { paymentMethod: pm, promise: null, timestamp: now });
88
+ setPaymentMethod(pm);
89
+ } catch (err) {
90
+ paymentMethodCache.delete(key);
91
+ setError(err instanceof Error ? err : new Error("Failed to load payment method"));
92
+ setPaymentMethod(null);
93
+ } finally {
94
+ setLoading(false);
95
+ }
96
+ },
97
+ [_config, key]
98
+ );
99
+ useEffect(() => {
100
+ load();
101
+ }, [load]);
102
+ return {
103
+ paymentMethod,
104
+ loading,
105
+ error,
106
+ refetch: () => load(true)
107
+ };
108
+ }
109
+
110
+ // src/components/LaunchCustomerPortalButton.tsx
111
+ import { forwardRef, useEffect as useEffect2, useRef, useState as useState2 } from "react";
112
+
113
+ // src/components/ExternalLinkGlyph.tsx
114
+ import { jsx, jsxs } from "react/jsx-runtime";
115
+ var ExternalLinkGlyph = ({ className }) => {
116
+ return /* @__PURE__ */ jsxs(
117
+ "svg",
118
+ {
119
+ className: `solvapay-mcp-external-glyph${className ? ` ${className}` : ""}`,
120
+ width: "0.9em",
121
+ height: "0.9em",
122
+ viewBox: "0 0 24 24",
123
+ fill: "none",
124
+ stroke: "currentColor",
125
+ strokeWidth: "2",
126
+ strokeLinecap: "round",
127
+ strokeLinejoin: "round",
128
+ "aria-hidden": "true",
129
+ focusable: "false",
130
+ children: [
131
+ /* @__PURE__ */ jsx("path", { d: "M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6" }),
132
+ /* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
133
+ /* @__PURE__ */ jsx("path", { d: "m10 14 11-11" })
134
+ ]
135
+ }
136
+ );
137
+ };
138
+
139
+ // src/components/LaunchCustomerPortalButton.tsx
140
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
141
+ var LaunchCustomerPortalButton = forwardRef(function LaunchCustomerPortalButton2({
142
+ children,
143
+ onLaunch,
144
+ onError,
145
+ onClick,
146
+ loadingClassName,
147
+ errorClassName,
148
+ asChild,
149
+ ...rest
150
+ }, forwardedRef) {
151
+ const transport = useTransport();
152
+ const copy = useCopy();
153
+ const [state, setState] = useState2({ status: "loading" });
154
+ const onErrorRef = useRef(onError);
155
+ useEffect2(() => {
156
+ onErrorRef.current = onError;
157
+ }, [onError]);
158
+ useEffect2(() => {
159
+ let cancelled = false;
160
+ transport.createCustomerSession().then(({ customerUrl }) => {
161
+ if (cancelled) return;
162
+ setState({ status: "ready", href: customerUrl });
163
+ }).catch((err) => {
164
+ if (cancelled) return;
165
+ const error = err instanceof Error ? err : new Error(String(err));
166
+ setState({ status: "error", message: error.message });
167
+ onErrorRef.current?.(error);
168
+ });
169
+ return () => {
170
+ cancelled = true;
171
+ };
172
+ }, [transport]);
173
+ if (state.status === "ready") {
174
+ const label = children ?? copy.customerPortal.launchButton;
175
+ const labelText = typeof label === "string" ? label : copy.customerPortal.launchButton;
176
+ const readyProps = {
177
+ href: state.href,
178
+ target: "_blank",
179
+ rel: "noopener noreferrer",
180
+ "data-solvapay-launch-customer-portal": "",
181
+ "data-state": "ready",
182
+ "aria-label": `${labelText} (opens in a new tab)`,
183
+ onClick: composeEventHandlers(onClick, () => {
184
+ onLaunch?.(state.href);
185
+ }),
186
+ ...rest
187
+ };
188
+ if (asChild) {
189
+ return (
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
+ /* @__PURE__ */ jsx2(Slot, { ref: forwardedRef, ...readyProps, children: label })
192
+ );
193
+ }
194
+ return /* @__PURE__ */ jsxs2("a", { ref: forwardedRef, ...readyProps, children: [
195
+ label,
196
+ /* @__PURE__ */ jsx2(ExternalLinkGlyph, {})
197
+ ] });
198
+ }
199
+ if (state.status === "error") {
200
+ return /* @__PURE__ */ jsx2(
201
+ "button",
202
+ {
203
+ type: "button",
204
+ className: errorClassName,
205
+ "data-solvapay-launch-customer-portal": "",
206
+ "data-state": "error",
207
+ "aria-disabled": true,
208
+ disabled: true,
209
+ children: children ?? copy.customerPortal.launchButton
210
+ }
211
+ );
212
+ }
213
+ return /* @__PURE__ */ jsx2(
214
+ "button",
215
+ {
216
+ type: "button",
217
+ className: loadingClassName,
218
+ "data-solvapay-launch-customer-portal": "",
219
+ "data-state": "loading",
220
+ "aria-busy": true,
221
+ "aria-disabled": true,
222
+ disabled: true,
223
+ children: copy.customerPortal.loadingLabel
224
+ }
225
+ );
226
+ });
227
+
228
+ // src/components/UpdatePaymentMethodButton.tsx
229
+ import { forwardRef as forwardRef2 } from "react";
230
+ import { jsx as jsx3 } from "react/jsx-runtime";
231
+ var UpdatePaymentMethodButton = forwardRef2(function UpdatePaymentMethodButton2({ mode = "portal", children, ...rest }, forwardedRef) {
232
+ const copy = useCopy();
233
+ if (mode !== "portal") {
234
+ throw new Error(
235
+ `<UpdatePaymentMethodButton mode="${mode}"> is not implemented yet \u2014 use mode="portal" or omit the prop.`
236
+ );
237
+ }
238
+ return /* @__PURE__ */ jsx3(
239
+ LaunchCustomerPortalButton,
240
+ {
241
+ ref: forwardedRef,
242
+ "data-solvapay-update-payment-method": "",
243
+ ...rest,
244
+ children: children ?? copy.currentPlan.updatePaymentButton
245
+ }
246
+ );
247
+ });
248
+
249
+ // src/components/CurrentPlanCard.tsx
250
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
251
+ function PlanTypeLine({
252
+ purchase,
253
+ formatDate,
254
+ className
255
+ }) {
256
+ const copy = useCopy();
257
+ const planType = purchase.planSnapshot?.planType ?? "one-time";
258
+ if (planType === "recurring" || purchase.isRecurring) {
259
+ const date2 = formatDate(purchase.nextBillingDate);
260
+ if (!date2) return null;
261
+ return /* @__PURE__ */ jsx4(
262
+ "span",
263
+ {
264
+ className,
265
+ "data-solvapay-current-plan-next-billing": "",
266
+ children: interpolate(copy.currentPlan.nextBilling, { date: date2 })
267
+ }
268
+ );
269
+ }
270
+ if (planType === "usage-based") {
271
+ return null;
272
+ }
273
+ const date = formatDate(purchase.endDate);
274
+ return /* @__PURE__ */ jsx4("span", { className, "data-solvapay-current-plan-expires": "", children: date ? interpolate(copy.currentPlan.expiresOn, { date }) : copy.currentPlan.validIndefinitely });
275
+ }
276
+ function PaymentMethodLine({
277
+ paymentMethod,
278
+ className
279
+ }) {
280
+ const copy = useCopy();
281
+ if (paymentMethod.kind === "none") {
282
+ return /* @__PURE__ */ jsx4("span", { className, "data-solvapay-current-plan-payment-method": "none", children: copy.currentPlan.noPaymentMethod });
283
+ }
284
+ const brandDisplay = paymentMethod.brand.charAt(0).toUpperCase() + paymentMethod.brand.slice(1);
285
+ const label = interpolate(copy.currentPlan.paymentMethod, {
286
+ brand: brandDisplay,
287
+ last4: paymentMethod.last4
288
+ });
289
+ const expires = interpolate(copy.currentPlan.paymentMethodExpires, {
290
+ month: String(paymentMethod.expMonth).padStart(2, "0"),
291
+ year: paymentMethod.expYear
292
+ });
293
+ return /* @__PURE__ */ jsxs3("span", { className, "data-solvapay-current-plan-payment-method": "card", children: [
294
+ label,
295
+ ", ",
296
+ expires
297
+ ] });
298
+ }
299
+ var CurrentPlanCard = ({
300
+ hidePaymentMethod,
301
+ hideCancelButton,
302
+ hideUpdatePaymentButton,
303
+ hideUsageMeter,
304
+ classNames: overrides,
305
+ className
306
+ }) => {
307
+ const copy = useCopy();
308
+ const { activePurchase } = usePurchase();
309
+ const { formatDate } = usePurchaseStatus();
310
+ const { paymentMethod } = usePaymentMethod();
311
+ if (!activePurchase) return null;
312
+ const planType = activePurchase.planSnapshot?.planType ?? "one-time";
313
+ const isUsageBased = planType === "usage-based";
314
+ const amount = activePurchase.originalAmount ?? activePurchase.amount ?? 0;
315
+ const currency = activePurchase.currency ?? "usd";
316
+ const cycleKey = activePurchase.billingCycle;
317
+ const intervalLabel = cycleKey ? copy.currentPlan.cycleUnit[cycleKey] ?? cycleKey : void 0;
318
+ const priceLabel = formatPrice(amount, currency, {
319
+ interval: intervalLabel
320
+ });
321
+ const planName = activePurchase.planSnapshot?.name ?? activePurchase.productName;
322
+ const productContext = activePurchase.productName && activePurchase.productName !== planName ? activePurchase.productName : null;
323
+ const rootClass = [
324
+ "solvapay-current-plan-card",
325
+ overrides?.root,
326
+ className
327
+ ].filter(Boolean).join(" ");
328
+ const shouldShowPaymentMethod = !hidePaymentMethod && paymentMethod !== null;
329
+ return /* @__PURE__ */ jsxs3(
330
+ "div",
331
+ {
332
+ className: rootClass,
333
+ "data-solvapay-current-plan-card": "",
334
+ "data-plan-type": planType,
335
+ "data-solvapay-current-plan-ref": activePurchase.planRef ?? void 0,
336
+ children: [
337
+ /* @__PURE__ */ jsx4(
338
+ "h2",
339
+ {
340
+ className: overrides?.heading ?? "solvapay-current-plan-heading",
341
+ "data-solvapay-current-plan-heading": "",
342
+ children: copy.currentPlan.heading
343
+ }
344
+ ),
345
+ productContext && /* @__PURE__ */ jsx4(
346
+ "div",
347
+ {
348
+ className: overrides?.productContext ?? "solvapay-current-plan-product-context",
349
+ "data-solvapay-current-plan-product-context": "",
350
+ children: productContext
351
+ }
352
+ ),
353
+ /* @__PURE__ */ jsx4(
354
+ "div",
355
+ {
356
+ className: overrides?.planName ?? "solvapay-current-plan-name",
357
+ "data-solvapay-current-plan-name": "",
358
+ children: planName
359
+ }
360
+ ),
361
+ /* @__PURE__ */ jsx4(
362
+ "div",
363
+ {
364
+ className: overrides?.price ?? "solvapay-current-plan-price",
365
+ "data-solvapay-current-plan-price": "",
366
+ children: priceLabel
367
+ }
368
+ ),
369
+ /* @__PURE__ */ jsx4(
370
+ PlanTypeLine,
371
+ {
372
+ purchase: activePurchase,
373
+ formatDate,
374
+ className: overrides?.dateLine ?? "solvapay-current-plan-date-line"
375
+ }
376
+ ),
377
+ isUsageBased && !hideUsageMeter && /* @__PURE__ */ jsx4(
378
+ "div",
379
+ {
380
+ className: overrides?.usageMeter ?? "solvapay-current-plan-usage-meter",
381
+ "data-solvapay-current-plan-usage-meter": "",
382
+ children: /* @__PURE__ */ jsxs3(UsageMeter.Root, { children: [
383
+ /* @__PURE__ */ jsx4(UsageMeter.Label, {}),
384
+ /* @__PURE__ */ jsx4(UsageMeter.Bar, {}),
385
+ /* @__PURE__ */ jsx4(UsageMeter.Percentage, {}),
386
+ /* @__PURE__ */ jsx4(UsageMeter.ResetsIn, {}),
387
+ /* @__PURE__ */ jsx4(UsageMeter.Loading, {})
388
+ ] })
389
+ }
390
+ ),
391
+ isUsageBased && /* @__PURE__ */ jsx4(
392
+ "div",
393
+ {
394
+ className: overrides?.balanceLine ?? "solvapay-current-plan-balance-line",
395
+ "data-solvapay-current-plan-balance-line": "",
396
+ children: /* @__PURE__ */ jsx4(BalanceBadge, {})
397
+ }
398
+ ),
399
+ shouldShowPaymentMethod && paymentMethod && /* @__PURE__ */ jsx4(
400
+ PaymentMethodLine,
401
+ {
402
+ paymentMethod,
403
+ className: overrides?.paymentMethod ?? "solvapay-current-plan-payment-method"
404
+ }
405
+ ),
406
+ /* @__PURE__ */ jsxs3(
407
+ "div",
408
+ {
409
+ className: overrides?.actions ?? "solvapay-current-plan-actions",
410
+ "data-solvapay-current-plan-actions": "",
411
+ children: [
412
+ !hideUpdatePaymentButton && /* @__PURE__ */ jsx4(UpdatePaymentMethodButton, {}),
413
+ !hideCancelButton && /* @__PURE__ */ jsx4(CancelPlanButton, {})
414
+ ]
415
+ }
416
+ )
417
+ ]
418
+ }
419
+ );
420
+ };
421
+
422
+ export {
423
+ paymentMethodCache,
424
+ usePaymentMethod,
425
+ ExternalLinkGlyph,
426
+ LaunchCustomerPortalButton,
427
+ UpdatePaymentMethodButton,
428
+ CurrentPlanCard
429
+ };
@@ -0,0 +1,9 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ export {
8
+ __export
9
+ };