@solvapay/react 2.1.1 → 2.2.0-preview-e3e546693c002c32f128d83bf6c5fe76ac182a79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,579 +0,0 @@
1
- import {
2
- BalanceBadge,
3
- CancelPlanButton,
4
- CancelledPlanNotice,
5
- Slot,
6
- UsageMeter,
7
- composeEventHandlers,
8
- createHttpTransport,
9
- createTransportCacheKey,
10
- formatPrice,
11
- interpolate,
12
- useCopy,
13
- usePurchase,
14
- usePurchaseStatus,
15
- useSolvaPay,
16
- useTransport
17
- } from "./chunk-LL6AYSTC.js";
18
-
19
- // src/components/CancelledPlanNotice.tsx
20
- import { jsx, jsxs } from "react/jsx-runtime";
21
- var CancelledPlanNotice2 = ({
22
- onReactivated,
23
- onError,
24
- className
25
- }) => {
26
- const rootClass = ["solvapay-cancelled-notice", className].filter(Boolean).join(" ");
27
- return /* @__PURE__ */ jsxs(
28
- CancelledPlanNotice.Root,
29
- {
30
- onReactivated,
31
- onError,
32
- className: rootClass,
33
- children: [
34
- /* @__PURE__ */ jsx(CancelledPlanNotice.Heading, { className: "solvapay-cancelled-notice-heading" }),
35
- /* @__PURE__ */ jsxs("div", { className: "solvapay-cancelled-notice-details", children: [
36
- /* @__PURE__ */ jsx(CancelledPlanNotice.Expires, { className: "solvapay-cancelled-notice-expires" }),
37
- /* @__PURE__ */ jsx(CancelledPlanNotice.DaysRemaining, { className: "solvapay-cancelled-notice-days-remaining" }),
38
- /* @__PURE__ */ jsx(CancelledPlanNotice.AccessUntil, { className: "solvapay-cancelled-notice-access-until" })
39
- ] }),
40
- /* @__PURE__ */ jsxs("div", { className: "solvapay-cancelled-notice-footer", children: [
41
- /* @__PURE__ */ jsx(CancelledPlanNotice.CancelledOn, { className: "solvapay-cancelled-notice-cancelled-on" }),
42
- /* @__PURE__ */ jsx(CancelledPlanNotice.Reason, { className: "solvapay-cancelled-notice-reason" })
43
- ] }),
44
- /* @__PURE__ */ jsx(CancelledPlanNotice.ReactivateButton, { className: "solvapay-cancelled-notice-reactivate" })
45
- ]
46
- }
47
- );
48
- };
49
-
50
- // src/hooks/usePaymentMethod.ts
51
- import { useCallback, useEffect, useState } from "react";
52
- var paymentMethodCache = /* @__PURE__ */ new Map();
53
- var CACHE_DURATION = 5 * 60 * 1e3;
54
- function cacheKeyFor(config) {
55
- return createTransportCacheKey(
56
- config,
57
- config?.api?.getPaymentMethod || "/api/payment-method"
58
- );
59
- }
60
- async function fetchPaymentMethod(config) {
61
- const transport = config?.transport ?? createHttpTransport(config);
62
- if (!transport.getPaymentMethod) return null;
63
- return transport.getPaymentMethod();
64
- }
65
- function usePaymentMethod() {
66
- const { _config } = useSolvaPay();
67
- const key = cacheKeyFor(_config);
68
- const [paymentMethod, setPaymentMethod] = useState(
69
- () => paymentMethodCache.get(key)?.paymentMethod ?? null
70
- );
71
- const [loading, setLoading] = useState(() => {
72
- const cached = paymentMethodCache.get(key);
73
- return !cached || !cached.paymentMethod && !cached.promise;
74
- });
75
- const [error, setError] = useState(null);
76
- const load = useCallback(
77
- async (force = false) => {
78
- const cached = paymentMethodCache.get(key);
79
- const now = Date.now();
80
- if (!force && cached?.paymentMethod && now - cached.timestamp < CACHE_DURATION) {
81
- setPaymentMethod(cached.paymentMethod);
82
- setLoading(false);
83
- setError(null);
84
- return;
85
- }
86
- if (!force && cached?.promise) {
87
- try {
88
- setLoading(true);
89
- const pm = await cached.promise;
90
- setPaymentMethod(pm);
91
- setError(null);
92
- } catch (err) {
93
- setError(err instanceof Error ? err : new Error("Failed to load payment method"));
94
- } finally {
95
- setLoading(false);
96
- }
97
- return;
98
- }
99
- try {
100
- setLoading(true);
101
- setError(null);
102
- const promise = fetchPaymentMethod(_config);
103
- paymentMethodCache.set(key, {
104
- paymentMethod: cached?.paymentMethod ?? null,
105
- promise,
106
- timestamp: now
107
- });
108
- const pm = await promise;
109
- if (pm === null) {
110
- paymentMethodCache.set(key, {
111
- paymentMethod: cached?.paymentMethod ?? null,
112
- promise: null,
113
- timestamp: cached?.timestamp ?? now
114
- });
115
- setPaymentMethod(cached?.paymentMethod ?? null);
116
- setLoading(false);
117
- return;
118
- }
119
- paymentMethodCache.set(key, { paymentMethod: pm, promise: null, timestamp: now });
120
- setPaymentMethod(pm);
121
- } catch (err) {
122
- paymentMethodCache.delete(key);
123
- setError(err instanceof Error ? err : new Error("Failed to load payment method"));
124
- setPaymentMethod(null);
125
- } finally {
126
- setLoading(false);
127
- }
128
- },
129
- [_config, key]
130
- );
131
- useEffect(() => {
132
- load();
133
- }, [load]);
134
- return {
135
- paymentMethod,
136
- loading,
137
- error,
138
- refetch: () => load(true)
139
- };
140
- }
141
-
142
- // src/components/LaunchCustomerPortalButton.tsx
143
- import { forwardRef, useState as useState2 } from "react";
144
-
145
- // src/hooks/useCustomerSessionUrl.ts
146
- import { useCallback as useCallback2, useEffect as useEffect2, useSyncExternalStore } from "react";
147
- var SessionStore = class {
148
- constructor(transport) {
149
- this.transport = transport;
150
- }
151
- // Snapshot is replaced (not mutated) on every transition so
152
- // `useSyncExternalStore` sees a fresh reference and schedules
153
- // a re-render for every subscriber.
154
- state = { status: "idle" };
155
- inflight = null;
156
- listeners = /* @__PURE__ */ new Set();
157
- subscribe = (listener) => {
158
- this.listeners.add(listener);
159
- return () => {
160
- this.listeners.delete(listener);
161
- };
162
- };
163
- getSnapshot = () => this.state;
164
- ensure = () => {
165
- if (this.state.status === "ready" && this.state.url) {
166
- return Promise.resolve(this.state.url);
167
- }
168
- return this.start();
169
- };
170
- refresh = () => {
171
- this.inflight = null;
172
- this.state = { status: "idle" };
173
- this.notify();
174
- return this.start();
175
- };
176
- start() {
177
- if (this.inflight) return this.inflight;
178
- this.state = { status: "loading" };
179
- this.notify();
180
- const promise = this.transport.createCustomerSession().then(({ customerUrl }) => {
181
- this.state = { status: "ready", url: customerUrl };
182
- this.inflight = null;
183
- this.notify();
184
- return customerUrl;
185
- }).catch((err) => {
186
- const error = err instanceof Error ? err : new Error(String(err));
187
- this.state = { status: "error", error };
188
- this.inflight = null;
189
- this.notify();
190
- throw error;
191
- });
192
- this.inflight = promise;
193
- return promise;
194
- }
195
- notify() {
196
- this.listeners.forEach((listener) => listener());
197
- }
198
- };
199
- var stores = /* @__PURE__ */ new WeakMap();
200
- function getStore(transport) {
201
- let store = stores.get(transport);
202
- if (!store) {
203
- store = new SessionStore(transport);
204
- stores.set(transport, store);
205
- }
206
- return store;
207
- }
208
- function useCustomerSessionUrl() {
209
- const transport = useTransport();
210
- const store = getStore(transport);
211
- const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
212
- useEffect2(() => {
213
- const current = getStore(transport);
214
- if (current.state.status === "idle") {
215
- current.ensure().catch(() => {
216
- });
217
- }
218
- }, [transport]);
219
- const ensure = useCallback2(() => getStore(transport).ensure(), [transport]);
220
- const refresh = useCallback2(() => getStore(transport).refresh(), [transport]);
221
- return {
222
- status: snapshot.status,
223
- url: snapshot.url,
224
- error: snapshot.error,
225
- ensure,
226
- refresh
227
- };
228
- }
229
-
230
- // src/components/ExternalLinkGlyph.tsx
231
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
232
- var ExternalLinkGlyph = ({ className }) => {
233
- return /* @__PURE__ */ jsxs2(
234
- "svg",
235
- {
236
- className: `solvapay-mcp-external-glyph${className ? ` ${className}` : ""}`,
237
- width: "0.9em",
238
- height: "0.9em",
239
- viewBox: "0 0 24 24",
240
- fill: "none",
241
- stroke: "currentColor",
242
- strokeWidth: "2",
243
- strokeLinecap: "round",
244
- strokeLinejoin: "round",
245
- "aria-hidden": "true",
246
- focusable: "false",
247
- children: [
248
- /* @__PURE__ */ jsx2("path", { d: "M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6" }),
249
- /* @__PURE__ */ jsx2("path", { d: "M15 3h6v6" }),
250
- /* @__PURE__ */ jsx2("path", { d: "m10 14 11-11" })
251
- ]
252
- }
253
- );
254
- };
255
-
256
- // src/components/LaunchCustomerPortalButton.tsx
257
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
258
- var LaunchCustomerPortalButton = forwardRef(function LaunchCustomerPortalButton2({
259
- children,
260
- onLaunch,
261
- onError,
262
- onClick,
263
- loadingClassName,
264
- errorClassName,
265
- asChild,
266
- className,
267
- ...rest
268
- }, forwardedRef) {
269
- const { status, url, ensure } = useCustomerSessionUrl();
270
- const copy = useCopy();
271
- const [clickState, setClickState] = useState2("idle");
272
- const isReady = status === "ready" && typeof url === "string";
273
- const label = children ?? copy.customerPortal.launchButton;
274
- const labelText = typeof label === "string" ? label : copy.customerPortal.launchButton;
275
- const composedClassName = [
276
- className,
277
- clickState === "pending" ? loadingClassName : null,
278
- clickState === "error" ? errorClassName : null
279
- ].filter(Boolean).join(" ") || void 0;
280
- const handleReadyClick = () => {
281
- if (!isReady || !url) return;
282
- onLaunch?.(url);
283
- };
284
- const handlePendingClick = async (event) => {
285
- event.preventDefault();
286
- setClickState("pending");
287
- try {
288
- const resolved = await ensure();
289
- setClickState("idle");
290
- if (typeof window !== "undefined") {
291
- window.open(resolved, "_blank", "noopener,noreferrer");
292
- }
293
- onLaunch?.(resolved);
294
- } catch (err) {
295
- const error = err instanceof Error ? err : new Error(String(err));
296
- setClickState("error");
297
- onError?.(error);
298
- }
299
- };
300
- const sharedProps = {
301
- "data-solvapay-launch-customer-portal": "",
302
- "data-state": clickState === "pending" ? "pending" : isReady ? "ready" : "idle",
303
- "aria-label": `${labelText} (opens in a new tab)`,
304
- className: composedClassName,
305
- ...rest
306
- };
307
- if (isReady && url) {
308
- const readyProps = {
309
- ...sharedProps,
310
- href: url,
311
- target: "_blank",
312
- rel: "noopener noreferrer",
313
- onClick: composeEventHandlers(onClick, handleReadyClick)
314
- };
315
- if (asChild) {
316
- return (
317
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
- /* @__PURE__ */ jsx3(Slot, { ref: forwardedRef, ...readyProps, children: label })
319
- );
320
- }
321
- return /* @__PURE__ */ jsxs3("a", { ref: forwardedRef, ...readyProps, children: [
322
- label,
323
- /* @__PURE__ */ jsx3(ExternalLinkGlyph, {})
324
- ] });
325
- }
326
- const pendingProps = {
327
- ...sharedProps,
328
- role: "link",
329
- onClick: composeEventHandlers(onClick, handlePendingClick)
330
- };
331
- if (asChild) {
332
- return (
333
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
334
- /* @__PURE__ */ jsx3(Slot, { ref: forwardedRef, ...pendingProps, children: label })
335
- );
336
- }
337
- return /* @__PURE__ */ jsxs3("a", { ref: forwardedRef, ...pendingProps, children: [
338
- label,
339
- /* @__PURE__ */ jsx3(ExternalLinkGlyph, {})
340
- ] });
341
- });
342
-
343
- // src/components/UpdatePaymentMethodButton.tsx
344
- import { forwardRef as forwardRef2 } from "react";
345
- import { jsx as jsx4 } from "react/jsx-runtime";
346
- var UpdatePaymentMethodButton = forwardRef2(function UpdatePaymentMethodButton2({ mode = "portal", children, ...rest }, forwardedRef) {
347
- const copy = useCopy();
348
- if (mode !== "portal") {
349
- throw new Error(
350
- `<UpdatePaymentMethodButton mode="${mode}"> is not implemented yet \u2014 use mode="portal" or omit the prop.`
351
- );
352
- }
353
- return /* @__PURE__ */ jsx4(
354
- LaunchCustomerPortalButton,
355
- {
356
- ref: forwardedRef,
357
- "data-solvapay-update-payment-method": "",
358
- ...rest,
359
- children: children ?? copy.currentPlan.updatePaymentButton
360
- }
361
- );
362
- });
363
-
364
- // src/components/CurrentPlanCard.tsx
365
- import { countsUsage } from "@solvapay/core";
366
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
367
- function PlanTypeLine({
368
- purchase,
369
- formatDate,
370
- className
371
- }) {
372
- const copy = useCopy();
373
- const isMetered = countsUsage(purchase.planSnapshot) || purchase.planSnapshot?.isMetered === true;
374
- if (purchase.isRecurring) {
375
- const date2 = formatDate(purchase.nextBillingDate);
376
- if (!date2) return null;
377
- return /* @__PURE__ */ jsx5(
378
- "span",
379
- {
380
- className,
381
- "data-solvapay-current-plan-next-billing": "",
382
- children: interpolate(copy.currentPlan.nextBilling, { date: date2 })
383
- }
384
- );
385
- }
386
- if (isMetered) {
387
- return null;
388
- }
389
- const date = formatDate(purchase.endDate);
390
- return /* @__PURE__ */ jsx5("span", { className, "data-solvapay-current-plan-expires": "", children: date ? interpolate(copy.currentPlan.expiresOn, { date }) : copy.currentPlan.validIndefinitely });
391
- }
392
- function PaymentMethodLine({
393
- paymentMethod,
394
- className
395
- }) {
396
- const copy = useCopy();
397
- if (paymentMethod.kind === "none") {
398
- return /* @__PURE__ */ jsx5("span", { className, "data-solvapay-current-plan-payment-method": "none", children: copy.currentPlan.noPaymentMethod });
399
- }
400
- const brandDisplay = paymentMethod.brand.charAt(0).toUpperCase() + paymentMethod.brand.slice(1);
401
- const label = interpolate(copy.currentPlan.paymentMethod, {
402
- brand: brandDisplay,
403
- last4: paymentMethod.last4
404
- });
405
- const expires = interpolate(copy.currentPlan.paymentMethodExpires, {
406
- month: String(paymentMethod.expMonth).padStart(2, "0"),
407
- year: paymentMethod.expYear
408
- });
409
- return /* @__PURE__ */ jsxs4("span", { className, "data-solvapay-current-plan-payment-method": "card", children: [
410
- label,
411
- ", ",
412
- expires
413
- ] });
414
- }
415
- var CurrentPlanCard = ({
416
- hidePaymentMethod,
417
- hideCancelButton,
418
- hideCancelledNotice,
419
- hideUpdatePaymentButton,
420
- hideUsageMeter,
421
- hideHeading,
422
- hideProductContext,
423
- showStartDate,
424
- showReference,
425
- classNames: overrides,
426
- className
427
- }) => {
428
- const copy = useCopy();
429
- const { activePurchase } = usePurchase();
430
- const { formatDate, shouldShowCancelledNotice } = usePurchaseStatus();
431
- const { paymentMethod } = usePaymentMethod();
432
- if (!activePurchase) return null;
433
- const isUsageBased = countsUsage(activePurchase.planSnapshot) || activePurchase.planSnapshot?.isMetered === true;
434
- const planType = activePurchase.isRecurring ? "recurring" : isUsageBased ? "usage-based" : "one-time";
435
- const amount = activePurchase.originalAmount ?? activePurchase.amount ?? 0;
436
- const currency = activePurchase.currency ?? "usd";
437
- const rawCycle = activePurchase.billingCycle;
438
- const cycleKey = rawCycle && rawCycle in copy.currentPlan.cycleUnit ? rawCycle : void 0;
439
- const intervalLabel = cycleKey ? copy.currentPlan.cycleUnit[cycleKey] ?? rawCycle : rawCycle;
440
- const priceLabel = formatPrice(amount, currency, {
441
- interval: intervalLabel
442
- });
443
- const planName = activePurchase.planSnapshot?.name ?? activePurchase.productName;
444
- const productContext = activePurchase.productName && activePurchase.productName !== planName ? activePurchase.productName : null;
445
- const rootClass = [
446
- "solvapay-current-plan-card",
447
- overrides?.root,
448
- className
449
- ].filter(Boolean).join(" ");
450
- const shouldShowPaymentMethod = !hidePaymentMethod && paymentMethod !== null;
451
- const showCancelButton = !hideCancelButton && !activePurchase.cancelledAt && !shouldShowCancelledNotice;
452
- return /* @__PURE__ */ jsxs4(
453
- "section",
454
- {
455
- className: rootClass,
456
- "data-solvapay-current-plan-card": "",
457
- "data-plan-type": planType,
458
- "data-solvapay-current-plan-ref": activePurchase.planRef ?? void 0,
459
- children: [
460
- !hideHeading && /* @__PURE__ */ jsx5(
461
- "h2",
462
- {
463
- className: overrides?.heading ?? "solvapay-current-plan-heading",
464
- "data-solvapay-current-plan-heading": "",
465
- children: copy.currentPlan.heading
466
- }
467
- ),
468
- productContext && !hideProductContext && /* @__PURE__ */ jsx5(
469
- "p",
470
- {
471
- className: overrides?.productContext ?? "solvapay-current-plan-product-context",
472
- "data-solvapay-current-plan-product-context": "",
473
- children: productContext
474
- }
475
- ),
476
- /* @__PURE__ */ jsx5(
477
- "h3",
478
- {
479
- className: overrides?.planName ?? "solvapay-current-plan-name",
480
- "data-solvapay-current-plan-name": "",
481
- children: planName
482
- }
483
- ),
484
- /* @__PURE__ */ jsx5(
485
- "p",
486
- {
487
- className: overrides?.price ?? "solvapay-current-plan-price",
488
- "data-solvapay-current-plan-price": "",
489
- children: priceLabel
490
- }
491
- ),
492
- /* @__PURE__ */ jsx5(
493
- PlanTypeLine,
494
- {
495
- purchase: activePurchase,
496
- formatDate,
497
- className: overrides?.dateLine ?? "solvapay-current-plan-date-line"
498
- }
499
- ),
500
- showStartDate && (() => {
501
- const started = formatDate(activePurchase.startDate);
502
- if (!started) return null;
503
- return /* @__PURE__ */ jsx5(
504
- "span",
505
- {
506
- className: overrides?.startedLine ?? "solvapay-current-plan-started-line",
507
- "data-solvapay-current-plan-started-line": "",
508
- children: interpolate(copy.currentPlan.startedOn, { date: started })
509
- }
510
- );
511
- })(),
512
- showReference && activePurchase.reference && /* @__PURE__ */ jsx5(
513
- "span",
514
- {
515
- className: overrides?.reference ?? "solvapay-current-plan-reference",
516
- "data-solvapay-current-plan-reference": "",
517
- children: activePurchase.reference
518
- }
519
- ),
520
- isUsageBased && activePurchase.isRecurring && !hideUsageMeter && /* @__PURE__ */ jsx5(
521
- "section",
522
- {
523
- className: overrides?.usageMeter ?? "solvapay-current-plan-usage-meter",
524
- "data-solvapay-current-plan-usage-meter": "",
525
- children: /* @__PURE__ */ jsxs4(UsageMeter.Root, { children: [
526
- /* @__PURE__ */ jsx5(UsageMeter.Label, {}),
527
- /* @__PURE__ */ jsx5(UsageMeter.Bar, {}),
528
- /* @__PURE__ */ jsx5(UsageMeter.Percentage, {}),
529
- /* @__PURE__ */ jsx5(UsageMeter.ResetsIn, {}),
530
- /* @__PURE__ */ jsx5(UsageMeter.Loading, {})
531
- ] })
532
- }
533
- ),
534
- isUsageBased && /* @__PURE__ */ jsx5(
535
- "p",
536
- {
537
- className: overrides?.balanceLine ?? "solvapay-current-plan-balance-line",
538
- "data-solvapay-current-plan-balance-line": "",
539
- children: /* @__PURE__ */ jsx5(BalanceBadge, {})
540
- }
541
- ),
542
- shouldShowPaymentMethod && paymentMethod && /* @__PURE__ */ jsx5(
543
- PaymentMethodLine,
544
- {
545
- paymentMethod,
546
- className: overrides?.paymentMethod ?? "solvapay-current-plan-payment-method"
547
- }
548
- ),
549
- /* @__PURE__ */ jsxs4(
550
- "section",
551
- {
552
- className: overrides?.actions ?? "solvapay-current-plan-actions",
553
- "data-solvapay-current-plan-actions": "",
554
- children: [
555
- !hideUpdatePaymentButton && /* @__PURE__ */ jsx5(UpdatePaymentMethodButton, {}),
556
- showCancelButton && /* @__PURE__ */ jsx5(CancelPlanButton, {})
557
- ]
558
- }
559
- ),
560
- shouldShowCancelledNotice && !hideCancelledNotice && /* @__PURE__ */ jsx5(
561
- CancelledPlanNotice2,
562
- {
563
- className: overrides?.cancelledNotice ?? "solvapay-current-plan-cancelled-notice"
564
- }
565
- )
566
- ]
567
- }
568
- );
569
- };
570
-
571
- export {
572
- CancelledPlanNotice2 as CancelledPlanNotice,
573
- paymentMethodCache,
574
- usePaymentMethod,
575
- ExternalLinkGlyph,
576
- LaunchCustomerPortalButton,
577
- UpdatePaymentMethodButton,
578
- CurrentPlanCard
579
- };