@funnelsgrove/payments 0.20.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/components/ManageSubscriptionScreen.d.ts +30 -4
- package/dist/components/ManageSubscriptionScreen.js +45 -30
- package/dist/index.d.ts +1 -1
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Dialog.d.ts +3 -0
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Form.internal.d.ts +1 -1
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Form.internal.js +15 -17
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Summary.internal.d.ts +2 -1
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Summary.internal.js +2 -2
- package/dist/providers/stripe/components/checkout-v2.content.d.ts +14 -0
- package/dist/providers/stripe/components/checkout-v2.content.js +13 -0
- package/dist/providers/stripe/hooks/useStripeOneTimeCheckoutSession.d.ts +3 -2
- package/dist/providers/stripe/hooks/useStripeOneTimeCheckoutSession.js +3 -3
- package/dist/providers/stripe/hooks/useStripeSubscriptionCheckoutSession.d.ts +3 -2
- package/dist/providers/stripe/hooks/useStripeSubscriptionCheckoutSession.js +2 -2
- package/dist/providers/stripe/services/stripeClient.d.ts +2 -2
- package/dist/providers/stripe/services/stripeClient.js +5 -4
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -47,6 +47,25 @@ mounts one official Solidgate form per attempt, destroys the remote form on repl
|
|
|
47
47
|
and never treats iframe `success` as funnel completion. Its `onSuccess` callback runs only after the
|
|
48
48
|
provider-neutral status endpoint finds canonical shared-ledger payment evidence.
|
|
49
49
|
|
|
50
|
+
## Localized funnel copy
|
|
51
|
+
|
|
52
|
+
Funnel content files own translated strings. Pass a complete `SharedStripeCheckoutV2Copy`
|
|
53
|
+
as the dialog's `copy` prop to translate its method tabs, country label and payment
|
|
54
|
+
status/fallback messages. Existing callers keep the English defaults. Stripe-owned
|
|
55
|
+
fields and provider errors use the `stripeLocale` option on both checkout-session
|
|
56
|
+
hooks; wallet surfaces accept it through their `checkout` input. Stripe clients are
|
|
57
|
+
cached by publishable key and locale so changing language cannot reuse another
|
|
58
|
+
language's client. Locale does not change a plan, currency or billing interval.
|
|
59
|
+
|
|
60
|
+
`ManageSubscriptionContent` exported by this package extends the runtime content
|
|
61
|
+
with optional `labels: ManageSubscriptionLabels`. A localized funnel should make
|
|
62
|
+
`labels` required in its own content type and provide the complete English and
|
|
63
|
+
translated objects. The labels own section/status text, date locale and interval
|
|
64
|
+
templates (`{count}`), while subscription data and cancellation behavior remain
|
|
65
|
+
unchanged. Set `errorMessagePolicy: 'content'` to render load/cancel failures from
|
|
66
|
+
the content's `errorMessages`. The default `'server'` policy preserves existing
|
|
67
|
+
error rendering independently of the selected labels.
|
|
68
|
+
|
|
50
69
|
## Advanced Wallet Primitives
|
|
51
70
|
|
|
52
71
|
`ApplePaySubscriptionCheckoutSlot`, `GooglePaySubscriptionCheckoutSlot`, and `WalletSubscriptionCheckoutSlot` stay exported and runtime-compatible. They are backward-compatible advanced low-level primitives: reach for them only when a placement needs wiring the shared wallet surfaces do not cover. Never share one checkout hook result across placements; give each visible placement its own session.
|
|
@@ -1,6 +1,31 @@
|
|
|
1
|
-
import { type ManageSubscriptionContent, type ManageSubscriptionsResponse } from '@funnelsgrove/runtime';
|
|
1
|
+
import { type ManageSubscriptionContent as RuntimeManageSubscriptionContent, type ManageSubscriptionsResponse } from '@funnelsgrove/runtime';
|
|
2
2
|
import type { BillingFallbackPlanConfig, BillingPlanCatalog } from '../config/billing.config.js';
|
|
3
|
-
export type
|
|
3
|
+
export type ManageSubscriptionLabels = {
|
|
4
|
+
locale: string;
|
|
5
|
+
unavailableDate: string;
|
|
6
|
+
activeTitle: string;
|
|
7
|
+
cancelledTitle: string;
|
|
8
|
+
pastTitle: string;
|
|
9
|
+
pastNote: string;
|
|
10
|
+
cancelledNote: string;
|
|
11
|
+
subscriptionNumber: string;
|
|
12
|
+
subscriptionFallback: string;
|
|
13
|
+
activeUntil: string;
|
|
14
|
+
endedOn: string;
|
|
15
|
+
historicalEnd: string;
|
|
16
|
+
nextCharge: string;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
cancelledStatus: string;
|
|
19
|
+
unknownStatus: string;
|
|
20
|
+
statuses: Readonly<Record<string, string>>;
|
|
21
|
+
intervals: Readonly<Record<string, string>>;
|
|
22
|
+
};
|
|
23
|
+
export type ManageSubscriptionContent = RuntimeManageSubscriptionContent & {
|
|
24
|
+
/** Complete localized presentation; omitted by existing English funnels. */
|
|
25
|
+
labels?: ManageSubscriptionLabels;
|
|
26
|
+
/** Use authored errorMessages instead of raw server exception text. */
|
|
27
|
+
errorMessagePolicy?: 'content' | 'server';
|
|
28
|
+
};
|
|
4
29
|
export type ManageSubscriptionScreenProps = {
|
|
5
30
|
stepId: string;
|
|
6
31
|
content: ManageSubscriptionContent;
|
|
@@ -24,6 +49,7 @@ export declare const __manageSubscriptionScreenTestables: {
|
|
|
24
49
|
resolveBillingDateLabel: (subscription: Pick<ManageSubscription, "cancelAtPeriodEnd" | "currentPeriodEnd" | "status">, options?: {
|
|
25
50
|
cancelled?: boolean;
|
|
26
51
|
past?: boolean;
|
|
27
|
-
}) => string;
|
|
28
|
-
resolveSubscriptionStatusLabel: (subscription: ManageSubscription) => string;
|
|
52
|
+
}, labels?: ManageSubscriptionLabels) => string;
|
|
53
|
+
resolveSubscriptionStatusLabel: (subscription: ManageSubscription, labels?: ManageSubscriptionLabels) => string;
|
|
29
54
|
};
|
|
55
|
+
export {};
|
|
@@ -13,15 +13,16 @@ const isCancelledActiveSubscription = (subscription) => {
|
|
|
13
13
|
const isCancellableSubscription = (subscription) => {
|
|
14
14
|
return getNormalizedStatus(subscription) !== 'canceled' && !subscription.cancelAtPeriodEnd;
|
|
15
15
|
};
|
|
16
|
-
const formatDate = (value) => {
|
|
16
|
+
const formatDate = (value, labels) => {
|
|
17
|
+
var _a, _b, _c;
|
|
17
18
|
if (!value) {
|
|
18
|
-
return 'n/a';
|
|
19
|
+
return (_a = labels === null || labels === void 0 ? void 0 : labels.unavailableDate) !== null && _a !== void 0 ? _a : 'n/a';
|
|
19
20
|
}
|
|
20
21
|
const timestamp = Date.parse(value);
|
|
21
22
|
if (!Number.isFinite(timestamp)) {
|
|
22
|
-
return 'n/a';
|
|
23
|
+
return (_b = labels === null || labels === void 0 ? void 0 : labels.unavailableDate) !== null && _b !== void 0 ? _b : 'n/a';
|
|
23
24
|
}
|
|
24
|
-
return new Intl.DateTimeFormat('en-US', {
|
|
25
|
+
return new Intl.DateTimeFormat((_c = labels === null || labels === void 0 ? void 0 : labels.locale) !== null && _c !== void 0 ? _c : 'en-US', {
|
|
25
26
|
month: 'numeric',
|
|
26
27
|
day: 'numeric',
|
|
27
28
|
year: 'numeric',
|
|
@@ -32,9 +33,12 @@ const formatSubscriptionNumber = (subscription) => {
|
|
|
32
33
|
const normalized = source.replace(/[^a-zA-Z0-9]/g, '');
|
|
33
34
|
return normalized.slice(-7) || source.slice(-7) || subscription.id;
|
|
34
35
|
};
|
|
35
|
-
const formatInterval = (interval, count) => {
|
|
36
|
+
const formatInterval = (interval, count, labels) => {
|
|
36
37
|
const normalizedCount = count && Number.isFinite(count) && count > 0 ? count : 1;
|
|
37
38
|
const normalizedInterval = (interval === null || interval === void 0 ? void 0 : interval.trim()) || 'month';
|
|
39
|
+
const translated = labels === null || labels === void 0 ? void 0 : labels.intervals[normalizedInterval];
|
|
40
|
+
if (translated)
|
|
41
|
+
return translated.replace('{count}', String(normalizedCount));
|
|
38
42
|
const pluralSuffix = normalizedCount === 1 ? '' : 's';
|
|
39
43
|
return `${normalizedCount} ${normalizedInterval}${pluralSuffix}`;
|
|
40
44
|
};
|
|
@@ -42,46 +46,54 @@ const formatMoney = (amountCents, currency) => {
|
|
|
42
46
|
const normalizedCurrency = (currency === null || currency === void 0 ? void 0 : currency.trim().toLowerCase()) || 'usd';
|
|
43
47
|
return `${(amountCents / 100).toFixed(2)} ${normalizedCurrency}`;
|
|
44
48
|
};
|
|
45
|
-
const resolvePlanLine = (subscription, plans) => {
|
|
49
|
+
const resolvePlanLine = (subscription, plans, labels) => {
|
|
46
50
|
const plan = plans.find((item) => item.providerPlanId === subscription.providerPlanId);
|
|
47
51
|
if (plan) {
|
|
48
|
-
return `${formatMoney(plan.amountCents, 'usd')} / ${formatInterval(plan.billingInterval, 1)}`;
|
|
52
|
+
return `${formatMoney(plan.amountCents, 'usd')} / ${formatInterval(plan.billingInterval, 1, labels)}`;
|
|
49
53
|
}
|
|
50
54
|
if (typeof subscription.amountCents === 'number' && subscription.amountCents > 0) {
|
|
51
|
-
return `${formatMoney(subscription.amountCents, subscription.currency)} / ${formatInterval(subscription.billingInterval, subscription.billingIntervalCount)}`;
|
|
55
|
+
return `${formatMoney(subscription.amountCents, subscription.currency)} / ${formatInterval(subscription.billingInterval, subscription.billingIntervalCount, labels)}`;
|
|
52
56
|
}
|
|
53
|
-
return
|
|
57
|
+
return labels
|
|
58
|
+
? labels.subscriptionFallback.replace('{environment}', subscription.environment.toUpperCase())
|
|
59
|
+
: `${subscription.environment.toUpperCase()} subscription`;
|
|
54
60
|
};
|
|
55
|
-
const resolveCreatedDate = (subscription) => {
|
|
56
|
-
return formatDate(subscription.createdAt || subscription.currentPeriodStart);
|
|
61
|
+
const resolveCreatedDate = (subscription, labels) => {
|
|
62
|
+
return formatDate(subscription.createdAt || subscription.currentPeriodStart, labels);
|
|
57
63
|
};
|
|
58
|
-
const resolveBillingDateLabel = (subscription, options = {}) => {
|
|
64
|
+
const resolveBillingDateLabel = (subscription, options = {}, labels) => {
|
|
65
|
+
var _a, _b, _c, _d;
|
|
59
66
|
const normalizedStatus = getNormalizedStatus(subscription);
|
|
60
67
|
if (options.cancelled || subscription.cancelAtPeriodEnd) {
|
|
61
|
-
return
|
|
68
|
+
return `${(_a = labels === null || labels === void 0 ? void 0 : labels.activeUntil) !== null && _a !== void 0 ? _a : 'Active until:'} ${formatDate(subscription.currentPeriodEnd, labels)}`;
|
|
62
69
|
}
|
|
63
70
|
if (options.past && normalizedStatus === 'canceled') {
|
|
64
|
-
return
|
|
71
|
+
return `${(_b = labels === null || labels === void 0 ? void 0 : labels.endedOn) !== null && _b !== void 0 ? _b : 'Ended on:'} ${formatDate(subscription.currentPeriodEnd, labels)}`;
|
|
65
72
|
}
|
|
66
73
|
if (options.past) {
|
|
67
|
-
return
|
|
74
|
+
return `${(_c = labels === null || labels === void 0 ? void 0 : labels.historicalEnd) !== null && _c !== void 0 ? _c : 'Historical period ended:'} ${formatDate(subscription.currentPeriodEnd, labels)}`;
|
|
68
75
|
}
|
|
69
|
-
return
|
|
76
|
+
return `${(_d = labels === null || labels === void 0 ? void 0 : labels.nextCharge) !== null && _d !== void 0 ? _d : 'Next charge:'} ${formatDate(subscription.currentPeriodEnd, labels)}`;
|
|
70
77
|
};
|
|
71
78
|
const toManageSubscriptions = (subscriptions) => subscriptions;
|
|
72
|
-
const resolveSubscriptionStatusLabel = (subscription) => {
|
|
79
|
+
const resolveSubscriptionStatusLabel = (subscription, labels) => {
|
|
80
|
+
var _a, _b, _c;
|
|
73
81
|
if (isCancelledActiveSubscription(subscription)) {
|
|
74
|
-
return 'Cancelled';
|
|
82
|
+
return (_a = labels === null || labels === void 0 ? void 0 : labels.cancelledStatus) !== null && _a !== void 0 ? _a : 'Cancelled';
|
|
75
83
|
}
|
|
76
84
|
const normalizedStatus = getNormalizedStatus(subscription);
|
|
77
85
|
if (!normalizedStatus) {
|
|
78
|
-
return 'Active';
|
|
86
|
+
return labels ? (_b = labels.statuses.active) !== null && _b !== void 0 ? _b : labels.unknownStatus : 'Active';
|
|
79
87
|
}
|
|
88
|
+
if (labels)
|
|
89
|
+
return (_c = labels.statuses[normalizedStatus]) !== null && _c !== void 0 ? _c : labels.unknownStatus;
|
|
80
90
|
return `${normalizedStatus.charAt(0).toUpperCase()}${normalizedStatus.slice(1)}`;
|
|
81
91
|
};
|
|
82
92
|
export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId, plans, }) {
|
|
83
|
-
var _a, _b;
|
|
93
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
84
94
|
const { attributes, goToStep, setAnswer, setUser, user } = useFunnel();
|
|
95
|
+
const labels = content.labels;
|
|
96
|
+
const useContentErrors = content.errorMessagePolicy === 'content';
|
|
85
97
|
const { config: publishedRuntimeConfig } = useFunnelRuntimeConfig();
|
|
86
98
|
const paymentsApiVersion = publishedRuntimeConfig === null || publishedRuntimeConfig === void 0 ? void 0 : publishedRuntimeConfig.paymentsApiVersion;
|
|
87
99
|
const supportEmail = runtimePublicConfig.supportEmail;
|
|
@@ -115,7 +127,7 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
|
|
|
115
127
|
}
|
|
116
128
|
catch (nextError) {
|
|
117
129
|
if (active) {
|
|
118
|
-
setError(nextError instanceof Error ? nextError.message : content.errorMessages.load);
|
|
130
|
+
setError(!useContentErrors && nextError instanceof Error ? nextError.message : content.errorMessages.load);
|
|
119
131
|
}
|
|
120
132
|
}
|
|
121
133
|
finally {
|
|
@@ -128,7 +140,7 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
|
|
|
128
140
|
return () => {
|
|
129
141
|
active = false;
|
|
130
142
|
};
|
|
131
|
-
}, [content.errorMessages.load, paymentsApiVersion]);
|
|
143
|
+
}, [content.errorMessages.load, useContentErrors, paymentsApiVersion]);
|
|
132
144
|
const subscriptions = useMemo(() => { var _a; return toManageSubscriptions((_a = data === null || data === void 0 ? void 0 : data.subscriptions) !== null && _a !== void 0 ? _a : []); }, [data]);
|
|
133
145
|
const activeSubscriptions = useMemo(() => subscriptions.filter(isCancellableSubscription), [subscriptions]);
|
|
134
146
|
const cancelledSubscriptions = useMemo(() => subscriptions.filter(isCancelledActiveSubscription), [subscriptions]);
|
|
@@ -188,7 +200,7 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
|
|
|
188
200
|
});
|
|
189
201
|
setData(payload);
|
|
190
202
|
const refreshedSubscription = toManageSubscriptions(payload.subscriptions).find((subscription) => subscription.id === selectedSubscription.id);
|
|
191
|
-
setCancelledActiveUntilDate(formatDate((refreshedSubscription === null || refreshedSubscription === void 0 ? void 0 : refreshedSubscription.currentPeriodEnd) || selectedSubscription.currentPeriodEnd));
|
|
203
|
+
setCancelledActiveUntilDate(formatDate((refreshedSubscription === null || refreshedSubscription === void 0 ? void 0 : refreshedSubscription.currentPeriodEnd) || selectedSubscription.currentPeriodEnd, labels));
|
|
192
204
|
await persistCancellationAnswers({
|
|
193
205
|
cancelled: true,
|
|
194
206
|
reasonId: selectedReason.id,
|
|
@@ -198,17 +210,17 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
|
|
|
198
210
|
setStage('done');
|
|
199
211
|
}
|
|
200
212
|
catch (nextError) {
|
|
201
|
-
setError(nextError instanceof Error ? nextError.message : content.errorMessages.cancel);
|
|
213
|
+
setError(!useContentErrors && nextError instanceof Error ? nextError.message : content.errorMessages.cancel);
|
|
202
214
|
}
|
|
203
215
|
finally {
|
|
204
216
|
setCancelInFlight(false);
|
|
205
217
|
}
|
|
206
218
|
};
|
|
207
|
-
return (_jsxs(_Fragment, { children: [_jsxs("section", { className: 'manage-subscription', "data-node-id": nodeId || stepId, children: [stage === 'subscriptions' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.subscriptionsStage.title }), loading ? (_jsx("p", { className: 'manage-subscription-muted', children: content.subscriptionsStage.loadingLabel })) : null, error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, !loading && !error && subscriptions.length === 0 ? (_jsx("p", { className: 'manage-subscription-empty', children: content.subscriptionsStage.emptyLabel })) : null, !loading && activeSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Active Subscriptions', subscriptions: activeSubscriptions, plans: planList, onSelect: handleSubscriptionSelected })) : null, !loading && cancelledSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Cancelled Subscriptions', subscriptions: cancelledSubscriptions, plans: planList, onSelect: handleSubscriptionSelected, description: cancelledSubscriptionsNote, cancelled: true })) : null, !loading && pastSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: 'Past Subscriptions', subscriptions: pastSubscriptions, plans: planList, onSelect: handleSubscriptionSelected, description: pastSubscriptionsNote, past: true })) : null, _jsxs("p", { className: 'manage-subscription-support', children: [content.subscriptionsStage.supportPrefix, ' ', _jsx("a", { href: `mailto:${supportEmail}`, children: supportEmail })] })] })) : null, stage === 'why' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title is-reason', children: content.whyStage.title }), _jsx("ul", { className: 'manage-subscription-reasons', role: 'list', "aria-label": content.whyStage.reasonsAriaLabel, children: content.whyStage.reasons.map((reason) => (_jsx("li", { children: _jsxs("button", { type: 'button', className: 'manage-subscription-reason', onClick: () => handleReasonSelected(reason.id), children: [_jsx("span", { className: 'manage-subscription-reason-icon', "aria-hidden": true, children: reason.icon }), _jsx("span", { children: reason.label }), _jsx("span", { className: 'manage-subscription-reason-arrow', "aria-hidden": true, children: ">" })] }) }, reason.id))) })] })) : null, stage === 'confirm' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.confirmStage.title }), error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', disabled: !selectedSubscription || !selectedReason || cancelInFlight, onClick: () => {
|
|
219
|
+
return (_jsxs(_Fragment, { children: [_jsxs("section", { className: 'manage-subscription', "data-node-id": nodeId || stepId, children: [stage === 'subscriptions' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.subscriptionsStage.title }), loading ? (_jsx("p", { className: 'manage-subscription-muted', children: content.subscriptionsStage.loadingLabel })) : null, error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, !loading && !error && subscriptions.length === 0 ? (_jsx("p", { className: 'manage-subscription-empty', children: content.subscriptionsStage.emptyLabel })) : null, !loading && activeSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: (_c = labels === null || labels === void 0 ? void 0 : labels.activeTitle) !== null && _c !== void 0 ? _c : 'Active Subscriptions', subscriptions: activeSubscriptions, plans: planList, labels: labels, onSelect: handleSubscriptionSelected })) : null, !loading && cancelledSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: (_d = labels === null || labels === void 0 ? void 0 : labels.cancelledTitle) !== null && _d !== void 0 ? _d : 'Cancelled Subscriptions', subscriptions: cancelledSubscriptions, plans: planList, labels: labels, onSelect: handleSubscriptionSelected, description: (_e = labels === null || labels === void 0 ? void 0 : labels.cancelledNote) !== null && _e !== void 0 ? _e : cancelledSubscriptionsNote, cancelled: true })) : null, !loading && pastSubscriptions.length > 0 ? (_jsx(SubscriptionSection, { title: (_f = labels === null || labels === void 0 ? void 0 : labels.pastTitle) !== null && _f !== void 0 ? _f : 'Past Subscriptions', subscriptions: pastSubscriptions, plans: planList, labels: labels, onSelect: handleSubscriptionSelected, description: (_g = labels === null || labels === void 0 ? void 0 : labels.pastNote) !== null && _g !== void 0 ? _g : pastSubscriptionsNote, past: true })) : null, _jsxs("p", { className: 'manage-subscription-support', children: [content.subscriptionsStage.supportPrefix, ' ', _jsx("a", { href: `mailto:${supportEmail}`, children: supportEmail })] })] })) : null, stage === 'why' ? (_jsxs("div", { className: 'manage-subscription-inner', children: [_jsx("h1", { className: 'manage-subscription-title is-reason', children: content.whyStage.title }), _jsx("ul", { className: 'manage-subscription-reasons', role: 'list', "aria-label": content.whyStage.reasonsAriaLabel, children: content.whyStage.reasons.map((reason) => (_jsx("li", { children: _jsxs("button", { type: 'button', className: 'manage-subscription-reason', onClick: () => handleReasonSelected(reason.id), children: [_jsx("span", { className: 'manage-subscription-reason-icon', "aria-hidden": true, children: reason.icon }), _jsx("span", { children: reason.label }), _jsx("span", { className: 'manage-subscription-reason-arrow', "aria-hidden": true, children: ">" })] }) }, reason.id))) })] })) : null, stage === 'confirm' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.confirmStage.title }), error ? _jsx("p", { className: 'manage-subscription-error', children: error }) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', disabled: !selectedSubscription || !selectedReason || cancelInFlight, onClick: () => {
|
|
208
220
|
void handleCancelSubscription();
|
|
209
221
|
}, children: cancelInFlight
|
|
210
222
|
? content.confirmStage.cancellingLabel
|
|
211
|
-
: content.confirmStage.cancelLabel })] })) : null, stage === 'done' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.doneStage.title }), _jsx("p", { className: 'manage-subscription-done-status', children:
|
|
223
|
+
: content.confirmStage.cancelLabel })] })) : null, stage === 'done' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.doneStage.title }), _jsx("p", { className: 'manage-subscription-done-status', children: (_h = labels === null || labels === void 0 ? void 0 : labels.cancelledStatus) !== null && _h !== void 0 ? _h : 'Cancelled' }), _jsx("p", { className: 'manage-subscription-done-copy', children: content.doneStage.cancelledMessage }), cancelledActiveUntilDate ? (_jsxs("p", { className: 'manage-subscription-done-copy is-active-until', children: [(_j = labels === null || labels === void 0 ? void 0 : labels.activeUntil) !== null && _j !== void 0 ? _j : 'Active until:', " ", cancelledActiveUntilDate] })) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', onClick: () => {
|
|
212
224
|
if (homeStepId) {
|
|
213
225
|
goToStep(homeStepId, { type: 'exit', reason: 'cancel' });
|
|
214
226
|
return;
|
|
@@ -216,16 +228,19 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
|
|
|
216
228
|
setStage('subscriptions');
|
|
217
229
|
}, children: content.doneStage.returnHomeLabel })] })) : null] }), _jsx("style", { children: manageSubscriptionStyles })] }));
|
|
218
230
|
}
|
|
219
|
-
function SubscriptionSection({ title, subscriptions, plans, onSelect, description, cancelled = false, past = false, }) {
|
|
231
|
+
function SubscriptionSection({ title, subscriptions, plans, onSelect, description, labels, cancelled = false, past = false, }) {
|
|
220
232
|
const isDisabled = cancelled || past;
|
|
221
233
|
const rowClassName = cancelled
|
|
222
234
|
? 'manage-subscription-row is-cancelled'
|
|
223
235
|
: past
|
|
224
236
|
? 'manage-subscription-row is-past'
|
|
225
237
|
: 'manage-subscription-row';
|
|
226
|
-
return (_jsxs("section", { className: 'manage-subscription-section', "aria-label": title, children: [_jsx("h2", { children: title }), description ? _jsx("p", { className: 'manage-subscription-section-note', children: description }) : null, _jsx("ul", { className: 'manage-subscription-list', role: 'list', children: subscriptions.map((subscription) =>
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
return (_jsxs("section", { className: 'manage-subscription-section', "aria-label": title, children: [_jsx("h2", { children: title }), description ? _jsx("p", { className: 'manage-subscription-section-note', children: description }) : null, _jsx("ul", { className: 'manage-subscription-list', role: 'list', children: subscriptions.map((subscription) => {
|
|
239
|
+
var _a, _b;
|
|
240
|
+
return (_jsx("li", { children: _jsxs("button", { type: 'button', className: rowClassName, disabled: isDisabled, onClick: () => onSelect(subscription), children: [_jsx("span", { className: 'manage-subscription-radio', "aria-hidden": true }), _jsxs("span", { className: 'manage-subscription-row-heading', children: [_jsx("span", { className: 'manage-subscription-row-title', children: ((_a = labels === null || labels === void 0 ? void 0 : labels.subscriptionNumber) !== null && _a !== void 0 ? _a : 'Subscription #{number}').replace('{number}', formatSubscriptionNumber(subscription)) }), _jsx("span", { className: 'manage-subscription-row-status', children: resolveSubscriptionStatusLabel(subscription, labels) })] }), _jsx("span", { className: 'manage-subscription-row-plan', children: resolvePlanLine(subscription, plans, labels) }), _jsxs("span", { className: 'manage-subscription-row-meta', children: [_jsx("span", { className: cancelled ? 'is-active-until' : undefined, children: cancelled
|
|
241
|
+
? resolveBillingDateLabel(subscription, { cancelled }, labels)
|
|
242
|
+
: resolveBillingDateLabel(subscription, { past }, labels) }), _jsxs("span", { children: [(_b = labels === null || labels === void 0 ? void 0 : labels.createdAt) !== null && _b !== void 0 ? _b : 'Created at:', " ", resolveCreatedDate(subscription, labels)] })] })] }) }, subscription.id));
|
|
243
|
+
}) })] }));
|
|
229
244
|
}
|
|
230
245
|
export const __manageSubscriptionScreenTestables = {
|
|
231
246
|
resolveBillingDateLabel,
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,6 @@ export * from './services/checkoutObservability.service.js';
|
|
|
11
11
|
export * from './providers/paymentProvider.types.js';
|
|
12
12
|
export * from './providers/stripe/index.js';
|
|
13
13
|
export * from './providers/solidgate/index.js';
|
|
14
|
-
export { ManageSubscriptionScreen, type ManageSubscriptionContent, type ManageSubscriptionScreenProps, } from './components/ManageSubscriptionScreen.js';
|
|
14
|
+
export { ManageSubscriptionScreen, type ManageSubscriptionContent, type ManageSubscriptionScreenProps, type ManageSubscriptionLabels, } from './components/ManageSubscriptionScreen.js';
|
|
15
15
|
export { isCheckoutEmailError, isValidCheckoutEmail } from './services/checkoutEmail.js';
|
|
16
16
|
export * from './components/CheckoutEmailDialog.js';
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type CSSProperties } from 'react';
|
|
2
2
|
import type { Stripe } from '@stripe/stripe-js';
|
|
3
3
|
import { type StripeSubscriptionCheckoutAnalyticsContext } from '../services/checkoutCompletionAnalytics.service.js';
|
|
4
|
+
import type { SharedStripeCheckoutV2Copy } from './checkout-v2.content.js';
|
|
5
|
+
export type { SharedStripeCheckoutV2Copy } from './checkout-v2.content.js';
|
|
4
6
|
export type SharedCheckoutV2CssVars = CSSProperties & {
|
|
5
7
|
'--shared-checkout-v2-theme-color': string;
|
|
6
8
|
'--shared-checkout-v2-button-color': string;
|
|
7
9
|
};
|
|
8
10
|
export type SharedStripeCheckoutV2DialogProps = {
|
|
11
|
+
copy?: SharedStripeCheckoutV2Copy;
|
|
9
12
|
amountCents: number;
|
|
10
13
|
buttonColor: string;
|
|
11
14
|
cardSubmitLabel: string;
|
|
@@ -3,5 +3,5 @@ export declare const getFixedSupportedBillingCountry: (supportedCountries: reado
|
|
|
3
3
|
type SharedStripeCheckoutV2FormProps = Omit<SharedStripeCheckoutV2DialogProps, 'stripePromise' | 'onClose'> & {
|
|
4
4
|
onClose: () => void;
|
|
5
5
|
};
|
|
6
|
-
export declare function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing, customerEmailLabel, customerEmailPlaceholder, customerEmailInvalidMessage, customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, variant, }: SharedStripeCheckoutV2FormProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function SharedStripeCheckoutV2CheckoutSessionForm({ copy, amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing, customerEmailLabel, customerEmailPlaceholder, customerEmailInvalidMessage, customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, variant, }: SharedStripeCheckoutV2FormProps): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export {};
|
|
@@ -13,18 +13,16 @@ import { trackPaidStripeSubscriptionCheckoutCompleted, trackStripeSubscriptionPa
|
|
|
13
13
|
import { trackCheckoutFailureShown } from '../../../services/checkoutObservability.service.js';
|
|
14
14
|
import { SharedStripeCheckoutV2Summary } from './SharedStripeCheckoutV2Summary.internal.js';
|
|
15
15
|
import { useCheckoutEmail } from './useCheckoutEmail.internal.js';
|
|
16
|
+
import { defaultCheckoutV2Copy } from './checkout-v2.content.js';
|
|
16
17
|
const defaultPaymentMethodLabels = ['Visa', 'Mastercard', 'Maestro', 'Discover'];
|
|
17
18
|
export const getFixedSupportedBillingCountry = (supportedCountries) => supportedCountries.length === 1 ? supportedCountries[0] : null;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
};
|
|
21
|
-
const getCheckoutCountryLabel = (countryCode) => { var _a; return (_a = checkoutCountryLabels[countryCode]) !== null && _a !== void 0 ? _a : countryCode; };
|
|
22
|
-
function getFriendlyCardErrorMessage(error) {
|
|
19
|
+
const getCheckoutCountryLabel = (countryCode, copy) => { var _a; return (_a = copy.countryNames[countryCode]) !== null && _a !== void 0 ? _a : countryCode; };
|
|
20
|
+
function getFriendlyCardErrorMessage(error, fallback) {
|
|
23
21
|
return error instanceof Error && error.message
|
|
24
22
|
? error.message
|
|
25
|
-
:
|
|
23
|
+
: fallback;
|
|
26
24
|
}
|
|
27
|
-
export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, variant = 'standard', }) {
|
|
25
|
+
export function SharedStripeCheckoutV2CheckoutSessionForm({ copy = defaultCheckoutV2Copy, amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, variant = 'standard', }) {
|
|
28
26
|
const checkoutState = useCheckout();
|
|
29
27
|
const walletPaymentMethods = usePlatformWalletPaymentMethods();
|
|
30
28
|
const [selectedMethod, setSelectedMethod] = useState('wallet');
|
|
@@ -47,7 +45,7 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
47
45
|
const normalizedSupportedCountries = useMemo(() => normalizeSupportedCheckoutCountries(supportedCountries), [supportedCountries]);
|
|
48
46
|
const fixedBillingCountry = getFixedSupportedBillingCountry(normalizedSupportedCountries);
|
|
49
47
|
const fixedBillingCountryLabel = fixedBillingCountry
|
|
50
|
-
? getCheckoutCountryLabel(fixedBillingCountry)
|
|
48
|
+
? getCheckoutCountryLabel(fixedBillingCountry, copy)
|
|
51
49
|
: null;
|
|
52
50
|
const cardBrandLabels = useMemo(() => paymentMethodLabels.length > 0 ? paymentMethodLabels : defaultPaymentMethodLabels, [paymentMethodLabels]);
|
|
53
51
|
const normalizedPromoCode = promoCode.trim();
|
|
@@ -203,11 +201,11 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
203
201
|
if (checkoutState.type !== 'success') {
|
|
204
202
|
setSharedError(checkoutState.type === 'error'
|
|
205
203
|
? checkoutState.error.message
|
|
206
|
-
:
|
|
204
|
+
: copy.formNotReady);
|
|
207
205
|
return;
|
|
208
206
|
}
|
|
209
207
|
if (!paymentElementReady) {
|
|
210
|
-
setSharedError(
|
|
208
|
+
setSharedError(copy.formNotReady, true);
|
|
211
209
|
return;
|
|
212
210
|
}
|
|
213
211
|
}
|
|
@@ -233,7 +231,7 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
233
231
|
redirect: 'if_required',
|
|
234
232
|
});
|
|
235
233
|
if (result.type === 'error') {
|
|
236
|
-
const message = result.error.message ||
|
|
234
|
+
const message = result.error.message || copy.paymentFailed;
|
|
237
235
|
setSharedError(message, false, result.error);
|
|
238
236
|
finishSubmitting();
|
|
239
237
|
return;
|
|
@@ -255,16 +253,16 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
255
253
|
finishSubmitting();
|
|
256
254
|
}
|
|
257
255
|
catch (submitError) {
|
|
258
|
-
setSharedError(getFriendlyCardErrorMessage(submitError), confirmed);
|
|
256
|
+
setSharedError(getFriendlyCardErrorMessage(submitError, copy.cardError), confirmed);
|
|
259
257
|
finishSubmitting();
|
|
260
258
|
}
|
|
261
259
|
};
|
|
262
260
|
const visibleError = error;
|
|
263
261
|
if (providerConfirmed && !completed)
|
|
264
|
-
return _jsxs("form", { onSubmit: handleSubmit, className: 'bg-background text-foreground p-6', children: [_jsx("p", { role: error ? 'alert' : 'status', children: error ||
|
|
262
|
+
return _jsxs("form", { onSubmit: handleSubmit, className: 'bg-background text-foreground p-6', children: [_jsx("p", { role: error ? 'alert' : 'status', children: error || copy.verifyingPayment }), _jsx("button", { type: 'submit', disabled: submitting, className: 'bg-accent text-foreground border-border rounded border p-3', children: copy.checkPaymentStatus })] });
|
|
265
263
|
if (completed)
|
|
266
|
-
return _jsx("div", { role: 'status', className: 'bg-background text-foreground p-6', children:
|
|
267
|
-
return (_jsxs("div", { className: 'shared-checkout-v2-shell', "data-variant": variant, style: checkoutStyle, children: [_jsxs("header", { className: 'shared-checkout-v2-header', children: [_jsx("button", { type: 'button', className: 'shared-checkout-v2-close', onClick: onClose, "aria-label": closeAriaLabel, children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: 'shared-checkout-v2-title', children: title })] }), _jsx(SharedStripeCheckoutV2Summary, { discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo }), _jsxs("form", { className: 'shared-checkout-v2-payment', onSubmit: (event) => void handleSubmit(event), children: [showWalletMethod ? (_jsxs("div", { className: 'shared-checkout-v2-methods', role: 'tablist', "aria-label":
|
|
264
|
+
return _jsx("div", { role: 'status', className: 'bg-background text-foreground p-6', children: copy.paymentConfirmed });
|
|
265
|
+
return (_jsxs("div", { className: 'shared-checkout-v2-shell', "data-variant": variant, style: checkoutStyle, children: [_jsxs("header", { className: 'shared-checkout-v2-header', children: [_jsx("button", { type: 'button', className: 'shared-checkout-v2-close', onClick: onClose, "aria-label": closeAriaLabel, children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: 'shared-checkout-v2-title', children: title })] }), _jsx(SharedStripeCheckoutV2Summary, { countdownUnit: copy.countdownUnit, discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo }), _jsxs("form", { className: 'shared-checkout-v2-payment', onSubmit: (event) => void handleSubmit(event), children: [showWalletMethod ? (_jsxs("div", { className: 'shared-checkout-v2-methods', role: 'tablist', "aria-label": copy.paymentMethodLabel, children: [_jsxs("button", { type: 'button', className: [
|
|
268
266
|
'shared-checkout-v2-method',
|
|
269
267
|
effectiveSelectedMethod === 'wallet' ? 'is-selected' : '',
|
|
270
268
|
]
|
|
@@ -274,7 +272,7 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
274
272
|
effectiveSelectedMethod === 'card' ? 'is-selected' : '',
|
|
275
273
|
]
|
|
276
274
|
.filter(Boolean)
|
|
277
|
-
.join(' '), onClick: selectCardMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'card', children: [_jsx("strong", { children:
|
|
275
|
+
.join(' '), onClick: selectCardMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'card', children: [_jsx("strong", { children: copy.cardLabel }), _jsx("span", { className: 'shared-checkout-v2-tab-brands', "aria-hidden": 'true', children: cardBrandLabels.slice(0, 4).map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-tab-brand' }, label))) })] })] })) : null, _jsxs("div", { hidden: effectiveSelectedMethod !== 'wallet', className: [
|
|
278
276
|
'shared-checkout-v2-wallet-panel',
|
|
279
277
|
effectiveSelectedMethod === 'wallet' ? 'is-visible' : '',
|
|
280
278
|
]
|
|
@@ -289,5 +287,5 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
289
287
|
return;
|
|
290
288
|
}
|
|
291
289
|
setEmailDraft(event.target.value);
|
|
292
|
-
} }) })] }), _jsx(PaymentElement, { options: paymentElementOptions, onChange: handlePaymentElementChange, onReady: () => setPaymentElementReady(true) }), fixedBillingCountry && fixedBillingCountryLabel ? (_jsxs("label", { className: 'shared-checkout-v2-country-field', children: [_jsx("span", { className: 'shared-checkout-v2-country-label', children:
|
|
290
|
+
} }) })] }), _jsx(PaymentElement, { options: paymentElementOptions, onChange: handlePaymentElementChange, onReady: () => setPaymentElementReady(true) }), fixedBillingCountry && fixedBillingCountryLabel ? (_jsxs("label", { className: 'shared-checkout-v2-country-field', children: [_jsx("span", { className: 'shared-checkout-v2-country-label', children: copy.billingCountryLabel }), _jsx("span", { className: 'shared-checkout-v2-country-control', children: _jsx("select", { "aria-label": copy.billingCountryLabel, className: 'shared-checkout-v2-country-select', value: fixedBillingCountry, onChange: () => setBillingCountry(fixedBillingCountry), children: _jsx("option", { value: fixedBillingCountry, children: fixedBillingCountryLabel }) }) })] })) : null] }), visibleError ? _jsx("p", { className: 'shared-checkout-v2-error', children: visibleError }) : null, _jsxs("button", { type: 'submit', className: 'shared-checkout-v2-card-submit', disabled: submitting || emailSaving || emailSessionChanged || !paymentElementReady, children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-card-submit-icon' }), submitting || emailSaving || emailSessionChanged ? processingLabel : cardSubmitLabel] }), _jsx("div", { className: 'shared-checkout-v2-card-brands', "aria-hidden": 'true', children: cardBrandLabels.map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-card-brand' }, label))) }), _jsxs("p", { className: 'shared-checkout-v2-secure-pill shared-checkout-v2-secure-pill--card', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] })] })] })] }));
|
|
293
291
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SharedStripeCheckoutV2DialogProps } from './SharedStripeCheckoutV2Dialog.js';
|
|
2
2
|
type SummaryProps = Pick<SharedStripeCheckoutV2DialogProps, 'discountPercent' | 'countdownLabel' | 'remainingSeconds' | 'variant' | 'satisfactionPercent' | 'satisfactionLabel' | 'originalPriceLabel' | 'originalPriceValue' | 'discountLabel' | 'discountAmountLabel' | 'promoCodeLabel' | 'promoCode' | 'totalLabel' | 'totalValue' | 'savedLabel'> & {
|
|
3
3
|
showPromo: boolean;
|
|
4
|
+
countdownUnit: string;
|
|
4
5
|
};
|
|
5
|
-
export declare function SharedStripeCheckoutV2Summary({ discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo }: SummaryProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function SharedStripeCheckoutV2Summary({ discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo, countdownUnit }: SummaryProps): import("react/jsx-runtime").JSX.Element;
|
|
6
7
|
export {};
|
|
@@ -7,10 +7,10 @@ const formatCountdown = (remainingSeconds) => {
|
|
|
7
7
|
const seconds = normalizedSeconds % 60;
|
|
8
8
|
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
9
9
|
};
|
|
10
|
-
export function SharedStripeCheckoutV2Summary({ discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo }) {
|
|
10
|
+
export function SharedStripeCheckoutV2Summary({ discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo, countdownUnit }) {
|
|
11
11
|
const countdownValue = formatCountdown(remainingSeconds);
|
|
12
12
|
const normalizedPromoCode = promoCode.trim();
|
|
13
|
-
return (_jsxs("section", { className: 'shared-checkout-v2-summary', children: [showPromo ? (_jsxs("p", { className: 'shared-checkout-v2-countdown', children: [discountPercent, "% ", countdownLabel, " ", countdownValue, "
|
|
13
|
+
return (_jsxs("section", { className: 'shared-checkout-v2-summary', children: [showPromo ? (_jsxs("p", { className: 'shared-checkout-v2-countdown', children: [discountPercent, "% ", countdownLabel, " ", countdownValue, " ", countdownUnit] })) : null, variant === 'standard' ? (_jsxs("p", { className: 'shared-checkout-v2-satisfaction', children: [_jsx("strong", { children: satisfactionPercent }), " ", satisfactionLabel] })) : null, showPromo ? (_jsxs("div", { className: 'shared-checkout-v2-price-stack', children: [_jsxs("div", { className: 'shared-checkout-v2-price-row is-muted', children: [_jsx("span", { children: originalPriceLabel }), _jsx("span", { className: 'shared-checkout-v2-strike', children: originalPriceValue })] }), _jsxs("div", { className: 'shared-checkout-v2-price-row is-discount', children: [_jsx("strong", { children: discountLabel }), _jsx("strong", { children: discountAmountLabel })] }), _jsxs("div", { className: 'shared-checkout-v2-promo', children: [_jsx("span", { children: promoCodeLabel }), _jsx("strong", { children: normalizedPromoCode })] })] })) : null, _jsxs("div", { className: [
|
|
14
14
|
'shared-checkout-v2-total',
|
|
15
15
|
showPromo ? '' : 'is-plain',
|
|
16
16
|
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SharedStripeCheckoutV2Copy = {
|
|
2
|
+
paymentMethodLabel: string;
|
|
3
|
+
countdownUnit: string;
|
|
4
|
+
cardLabel: string;
|
|
5
|
+
formNotReady: string;
|
|
6
|
+
paymentFailed: string;
|
|
7
|
+
cardError: string;
|
|
8
|
+
verifyingPayment: string;
|
|
9
|
+
checkPaymentStatus: string;
|
|
10
|
+
paymentConfirmed: string;
|
|
11
|
+
billingCountryLabel: string;
|
|
12
|
+
countryNames: Readonly<Record<string, string>>;
|
|
13
|
+
};
|
|
14
|
+
export declare const defaultCheckoutV2Copy: SharedStripeCheckoutV2Copy;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const defaultCheckoutV2Copy = {
|
|
2
|
+
paymentMethodLabel: 'Payment method',
|
|
3
|
+
countdownUnit: 'min',
|
|
4
|
+
cardLabel: 'Credit card',
|
|
5
|
+
formNotReady: 'Payment form is not ready yet.',
|
|
6
|
+
paymentFailed: 'Payment failed.',
|
|
7
|
+
cardError: 'Payment failed. Please review your card details and try again.',
|
|
8
|
+
verifyingPayment: 'Verifying payment…',
|
|
9
|
+
checkPaymentStatus: 'Check payment status',
|
|
10
|
+
paymentConfirmed: 'Payment confirmed. You can return to the app.',
|
|
11
|
+
billingCountryLabel: 'Country',
|
|
12
|
+
countryNames: { US: 'United States' },
|
|
13
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Stripe } from '@stripe/stripe-js';
|
|
1
|
+
import type { Stripe, StripeConstructorOptions } from '@stripe/stripe-js';
|
|
2
2
|
import type { RuntimeMode } from '@funnelsgrove/runtime';
|
|
3
3
|
import { type CheckoutPreparationLoading, type CheckoutPreparationSource } from '../../paymentProvider.types.js';
|
|
4
4
|
import { type PaywallPlan, type StripeRuntimeConfigOverrides } from '../services/stripe.service.js';
|
|
@@ -20,6 +20,7 @@ export type StripeOneTimeCheckoutSessionInput = {
|
|
|
20
20
|
returnUrl: string;
|
|
21
21
|
runtimeConfig?: StripeRuntimeConfigOverrides;
|
|
22
22
|
stripePublishableKey?: string | null;
|
|
23
|
+
stripeLocale?: StripeConstructorOptions['locale'];
|
|
23
24
|
userId?: string | null;
|
|
24
25
|
};
|
|
25
26
|
export type StripeOneTimeCheckoutPreparationOptions = {
|
|
@@ -51,4 +52,4 @@ export type StripeOneTimeCheckoutIntentKeyInput = Pick<StripeOneTimeCheckoutSess
|
|
|
51
52
|
};
|
|
52
53
|
export declare function buildStripeOneTimeCheckoutIntentKey({ runtimeConfigRevisionId, paymentProfileId, provider, checkoutMode, couponId, customerEmail, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutIntentKeyInput): string;
|
|
53
54
|
export declare function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
|
|
54
|
-
export declare function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutAnalytics, checkoutMode, couponId, customerEmail, enabled, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
|
|
55
|
+
export declare function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutAnalytics, checkoutMode, couponId, customerEmail, enabled, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, stripeLocale, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
|
|
@@ -71,7 +71,7 @@ export function buildStripeOneTimeCheckoutIntentKey({ runtimeConfigRevisionId, p
|
|
|
71
71
|
export function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey, nextIntentKey) {
|
|
72
72
|
return previousIntentKey !== null && previousIntentKey !== nextIntentKey;
|
|
73
73
|
}
|
|
74
|
-
export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutAnalytics, checkoutMode, couponId, customerEmail, enabled = true, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }) {
|
|
74
|
+
export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutAnalytics, checkoutMode, couponId, customerEmail, enabled = true, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, stripeLocale, userId, }) {
|
|
75
75
|
var _a, _b, _c;
|
|
76
76
|
const [clientSecret, setClientSecret] = useState(null);
|
|
77
77
|
const [clientSecretIntentKey, setClientSecretIntentKey] = useState(null);
|
|
@@ -114,8 +114,8 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
|
|
|
114
114
|
const activeCheckoutSessionId = clientSecretIntentKey === intentKey ? checkoutSessionId : null;
|
|
115
115
|
const stripePromise = useMemo(() => {
|
|
116
116
|
var _a;
|
|
117
|
-
return getStripePromise(checkoutMode, (_a = runtimeStripePublishableKey !== null && runtimeStripePublishableKey !== void 0 ? runtimeStripePublishableKey : stripePublishableKey) !== null && _a !== void 0 ? _a : runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey);
|
|
118
|
-
}, [checkoutMode, runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey, runtimeStripePublishableKey, stripePublishableKey]);
|
|
117
|
+
return getStripePromise(checkoutMode, (_a = runtimeStripePublishableKey !== null && runtimeStripePublishableKey !== void 0 ? runtimeStripePublishableKey : stripePublishableKey) !== null && _a !== void 0 ? _a : runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey, stripeLocale);
|
|
118
|
+
}, [checkoutMode, runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey, runtimeStripePublishableKey, stripePublishableKey, stripeLocale]);
|
|
119
119
|
const previousIntentKeyRef = useRef(intentKey);
|
|
120
120
|
const analyticsMetadataRef = useRef(analyticsMetadata);
|
|
121
121
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Stripe } from '@stripe/stripe-js';
|
|
1
|
+
import type { Stripe, StripeConstructorOptions } from '@stripe/stripe-js';
|
|
2
2
|
import type { RuntimeMode } from '@funnelsgrove/runtime';
|
|
3
3
|
import { type CheckoutPreparationLoading, type CheckoutPreparationSource } from '../../paymentProvider.types.js';
|
|
4
4
|
import type { PaywallDisplayPlan } from '../../../services/paywallOffer.service.js';
|
|
@@ -21,6 +21,7 @@ export type StripeSubscriptionCheckoutSessionInput = {
|
|
|
21
21
|
paymentProfileId?: string | null;
|
|
22
22
|
provider?: 'stripe' | null;
|
|
23
23
|
stripePublishableKey?: string | null;
|
|
24
|
+
stripeLocale?: StripeConstructorOptions['locale'];
|
|
24
25
|
userId?: string | null;
|
|
25
26
|
};
|
|
26
27
|
export type StripeSubscriptionCheckoutPreparationOptions = {
|
|
@@ -58,4 +59,4 @@ export declare function shouldResetStripeSubscriptionCheckoutSessionForIntentKey
|
|
|
58
59
|
export declare function isInactiveCheckoutSessionError(message?: string | null): boolean;
|
|
59
60
|
export declare function shouldAutoRecoverStripeSubscriptionCheckoutSession(message: string | null | undefined, alreadyRecovered: boolean): boolean;
|
|
60
61
|
export declare const shouldReportStripeCheckoutFailureShown: (message: string) => boolean;
|
|
61
|
-
export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
|
|
62
|
+
export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, stripeLocale, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
|
|
@@ -71,7 +71,7 @@ export function shouldAutoRecoverStripeSubscriptionCheckoutSession(message, alre
|
|
|
71
71
|
return !alreadyRecovered && isInactiveCheckoutSessionError(message);
|
|
72
72
|
}
|
|
73
73
|
export const shouldReportStripeCheckoutFailureShown = (message) => !isInactiveCheckoutSessionError(message);
|
|
74
|
-
export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }) {
|
|
74
|
+
export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, stripeLocale, userId, }) {
|
|
75
75
|
var _a, _b, _c, _d, _e;
|
|
76
76
|
const [clientSecret, setClientSecret] = useState(null);
|
|
77
77
|
const [clientSecretIntentKey, setClientSecretIntentKey] = useState(null);
|
|
@@ -119,7 +119,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
|
|
|
119
119
|
const activeClientSecret = clientSecretIntentKey === intentKey ? clientSecret : null;
|
|
120
120
|
const activeCheckoutSessionId = clientSecretIntentKey === intentKey ? checkoutSessionId : null;
|
|
121
121
|
const activePlanKey = clientSecretIntentKey === intentKey ? clientSecretPlanKey : null;
|
|
122
|
-
const stripePromise = useMemo(() => getStripePromise(checkoutMode, runtimeStripePublishableKey !== null && runtimeStripePublishableKey !== void 0 ? runtimeStripePublishableKey : stripePublishableKey), [checkoutMode, runtimeStripePublishableKey, stripePublishableKey]);
|
|
122
|
+
const stripePromise = useMemo(() => getStripePromise(checkoutMode, runtimeStripePublishableKey !== null && runtimeStripePublishableKey !== void 0 ? runtimeStripePublishableKey : stripePublishableKey, stripeLocale), [checkoutMode, runtimeStripePublishableKey, stripePublishableKey, stripeLocale]);
|
|
123
123
|
const previousIntentKeyRef = useRef(intentKey);
|
|
124
124
|
const analyticsMetadataRef = useRef(analyticsMetadata);
|
|
125
125
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Stripe } from '@stripe/stripe-js';
|
|
1
|
+
import type { Stripe, StripeConstructorOptions } from '@stripe/stripe-js';
|
|
2
2
|
import type { RuntimeMode } from '@funnelsgrove/runtime';
|
|
3
3
|
export declare function isStripeConfigured(mode: RuntimeMode): boolean;
|
|
4
|
-
export declare function getStripePromise(_mode: RuntimeMode, runtimePublishableKey?: string | null): Promise<Stripe | null> | null;
|
|
4
|
+
export declare function getStripePromise(_mode: RuntimeMode, runtimePublishableKey?: string | null, locale?: StripeConstructorOptions['locale']): Promise<Stripe | null> | null;
|
|
@@ -5,15 +5,16 @@ export function isStripeConfigured(mode) {
|
|
|
5
5
|
void mode;
|
|
6
6
|
return FUNNEL_SDK_PUBLISHABLE_KEY.length > 0;
|
|
7
7
|
}
|
|
8
|
-
export function getStripePromise(_mode, runtimePublishableKey) {
|
|
8
|
+
export function getStripePromise(_mode, runtimePublishableKey, locale) {
|
|
9
9
|
const resolvedPublishableKey = (runtimePublishableKey === null || runtimePublishableKey === void 0 ? void 0 : runtimePublishableKey.trim()) || '';
|
|
10
10
|
if (!resolvedPublishableKey) {
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
const cacheKey = JSON.stringify([resolvedPublishableKey, locale !== null && locale !== void 0 ? locale : 'auto']);
|
|
14
|
+
let stripePromise = stripePromiseByKey.get(cacheKey) || null;
|
|
14
15
|
if (!stripePromise) {
|
|
15
|
-
stripePromise = loadStripe(resolvedPublishableKey);
|
|
16
|
-
stripePromiseByKey.set(
|
|
16
|
+
stripePromise = locale ? loadStripe(resolvedPublishableKey, { locale }) : loadStripe(resolvedPublishableKey);
|
|
17
|
+
stripePromiseByKey.set(cacheKey, stripePromise);
|
|
17
18
|
}
|
|
18
19
|
return stripePromise;
|
|
19
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/payments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
|
|
@@ -32,8 +32,6 @@
|
|
|
32
32
|
"test:run": "vitest run --passWithNoTests"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@funnelsgrove/analytics": "0.1.110",
|
|
36
|
-
"@funnelsgrove/runtime": "0.19.0",
|
|
37
35
|
"@solidgate/react-sdk": "1.34.0",
|
|
38
36
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
39
37
|
"@stripe/stripe-js": "^8.7.0",
|
|
@@ -47,6 +45,12 @@
|
|
|
47
45
|
"@types/react-dom": "^19",
|
|
48
46
|
"jsdom": "20.0.3",
|
|
49
47
|
"typescript": "^5",
|
|
50
|
-
"vitest": "^3.2.4"
|
|
48
|
+
"vitest": "^3.2.4",
|
|
49
|
+
"@funnelsgrove/runtime": "0.19.1",
|
|
50
|
+
"@funnelsgrove/analytics": "0.2.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@funnelsgrove/runtime": "^0.19.1",
|
|
54
|
+
"@funnelsgrove/analytics": "^0.1.111 || ^0.2.0"
|
|
51
55
|
}
|
|
52
56
|
}
|