@funnelsgrove/payments 0.1.58 → 0.2.0
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 +3 -2
- package/dist/components/shared/SharedStripeCheckoutDialog.js +6 -3
- package/dist/components/shared/SharedStripeCheckoutV2Dialog.js +6 -3
- package/dist/components/shared/StripeCheckoutExpressCheckoutButton.js +7 -4
- package/dist/components/shared/StripeExpressCheckoutButton.js +7 -4
- package/dist/providers/stripe/WalletSubscriptionCheckoutSlot.js +1 -1
- package/dist/services/checkoutCompletionAnalytics.service.d.ts +1 -0
- package/dist/services/checkoutCompletionAnalytics.service.js +6 -1
- package/dist/services/stripe.service.js +3 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -87,8 +87,9 @@ When a live funnel still uses one of these, migrate the funnel deliberately with
|
|
|
87
87
|
- Keep provider-specific implementation under `src/providers/<provider>`.
|
|
88
88
|
- Stripe owns real card fields and wallet controls. Local wallet buttons are placeholders until Stripe confirms availability.
|
|
89
89
|
- Wallet unavailable state must fall back to card/manual checkout or disappear.
|
|
90
|
-
-
|
|
91
|
-
- Wallet slots may create render-only Checkout Sessions on mount so Apple Pay and Google Pay render before the visitor taps.
|
|
90
|
+
- Count `checkout_started` only at shared UI boundaries: when a shared card dialog opens or when Stripe reports an Apple Pay / Google Pay button click.
|
|
91
|
+
- Wallet slots may create render-only Checkout Sessions on mount so Apple Pay and Google Pay render before the visitor taps. Session creation is technical preparation and must never emit `checkout_started`.
|
|
92
|
+
- Shared checkout analytics deduplicates `checkout_started` by Checkout Session id. Card submit and wallet confirmation must not emit it again.
|
|
92
93
|
- Funnel paywalls should style wallet buttons through the shared slot API: `className` for the Stripe button/placeholder, `slotClassName` for the outer slot, `appearance` for Stripe Elements appearance, and `options` for Stripe Express Checkout options.
|
|
93
94
|
- Keep discount math reusable here, but keep offer activation timing in funnel code.
|
|
94
95
|
- Checkout metadata must avoid sensitive raw query params and use `@funnelsgrove/analytics` metadata helpers.
|
|
@@ -191,9 +191,6 @@ function SharedStripeCheckoutForm({ amountCents, checkoutAnalytics, checkoutSess
|
|
|
191
191
|
setSubmitting(false);
|
|
192
192
|
return;
|
|
193
193
|
}
|
|
194
|
-
if (checkoutAnalytics) {
|
|
195
|
-
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, paymentMethod: 'card' }));
|
|
196
|
-
}
|
|
197
194
|
const result = await stripe.confirmCardPayment(clientSecret, {
|
|
198
195
|
payment_method: {
|
|
199
196
|
card: cardNumberElement,
|
|
@@ -249,6 +246,12 @@ function SharedStripeCheckoutForm({ amountCents, checkoutAnalytics, checkoutSess
|
|
|
249
246
|
}
|
|
250
247
|
export function SharedStripeCheckoutDialog(_a) {
|
|
251
248
|
var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise"]);
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (!checkoutAnalytics) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, checkoutStartSource: 'card_form_open', paymentMethod: 'card' }));
|
|
254
|
+
}, [checkoutAnalytics, checkoutSessionId]);
|
|
252
255
|
useEffect(() => {
|
|
253
256
|
const handleKeyDown = (event) => {
|
|
254
257
|
if (event.key === 'Escape') {
|
|
@@ -250,9 +250,6 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
|
|
|
250
250
|
setSubmitting(false);
|
|
251
251
|
return;
|
|
252
252
|
}
|
|
253
|
-
if (checkoutAnalytics) {
|
|
254
|
-
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, paymentMethod: 'card' }));
|
|
255
|
-
}
|
|
256
253
|
await (onPaymentInfoSubmitted === null || onPaymentInfoSubmitted === void 0 ? void 0 : onPaymentInfoSubmitted());
|
|
257
254
|
const result = await checkoutState.checkout.confirm({
|
|
258
255
|
redirect: 'if_required',
|
|
@@ -330,6 +327,12 @@ export function SharedStripeCheckoutV2Dialog(_a) {
|
|
|
330
327
|
var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise, supportedCountries } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise", "supportedCountries"]);
|
|
331
328
|
const normalizedSupportedCountries = useMemo(() => normalizeSupportedCheckoutCountries(supportedCountries), [supportedCountries]);
|
|
332
329
|
const fixedBillingCountry = getFixedSupportedBillingCountry(normalizedSupportedCountries);
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (!checkoutAnalytics) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, checkoutStartSource: 'card_form_open', paymentMethod: 'card' }));
|
|
335
|
+
}, [checkoutAnalytics, checkoutSessionId]);
|
|
333
336
|
useEffect(() => {
|
|
334
337
|
const handleKeyDown = (event) => {
|
|
335
338
|
if (event.key === 'Escape') {
|
|
@@ -123,9 +123,6 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
123
123
|
event.paymentFailed({ reason: 'fail', message });
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
|
-
if (checkoutAnalytics) {
|
|
127
|
-
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, paymentMethod: event.expressPaymentType }));
|
|
128
|
-
}
|
|
129
126
|
let preparedCustomerEmail = (customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || null;
|
|
130
127
|
if (beforeConfirm) {
|
|
131
128
|
try {
|
|
@@ -162,13 +159,19 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
162
159
|
window.location.assign(returnUrl);
|
|
163
160
|
}
|
|
164
161
|
};
|
|
162
|
+
const handleClick = (event) => {
|
|
163
|
+
if (checkoutAnalytics) {
|
|
164
|
+
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, checkoutStartSource: 'wallet_click', paymentMethod: event.expressPaymentType }));
|
|
165
|
+
}
|
|
166
|
+
event.resolve();
|
|
167
|
+
};
|
|
165
168
|
return (_jsxs(_Fragment, { children: [_jsx("div", { className: [
|
|
166
169
|
'stripe-express-checkout-button',
|
|
167
170
|
className !== null && className !== void 0 ? className : '',
|
|
168
171
|
effectiveWalletAvailable === false ? 'is-hidden' : '',
|
|
169
172
|
]
|
|
170
173
|
.filter(Boolean)
|
|
171
|
-
.join(' '), children: _jsx(ExpressCheckoutElementWithCancel, { options: resolvedOptions, onCancel: onCancel, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
|
|
174
|
+
.join(' '), children: _jsx(ExpressCheckoutElementWithCancel, { options: resolvedOptions, onCancel: onCancel, onClick: handleClick, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
|
|
172
175
|
setWalletAvailable(false);
|
|
173
176
|
onAvailabilityChange === null || onAvailabilityChange === void 0 ? void 0 : onAvailabilityChange(false);
|
|
174
177
|
onError === null || onError === void 0 ? void 0 : onError(error.message || 'Unable to load wallet checkout.');
|
|
@@ -78,9 +78,6 @@ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, checko
|
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
onError === null || onError === void 0 ? void 0 : onError(null);
|
|
81
|
-
if (checkoutAnalytics) {
|
|
82
|
-
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, paymentMethod: event.expressPaymentType }));
|
|
83
|
-
}
|
|
84
81
|
let preparedCustomerEmail = (customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || null;
|
|
85
82
|
if (beforeConfirm) {
|
|
86
83
|
try {
|
|
@@ -138,13 +135,19 @@ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, checko
|
|
|
138
135
|
window.location.assign(returnUrl);
|
|
139
136
|
}
|
|
140
137
|
};
|
|
138
|
+
const handleClick = (event) => {
|
|
139
|
+
if (checkoutAnalytics) {
|
|
140
|
+
queueStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId, checkoutStartSource: 'wallet_click', paymentMethod: event.expressPaymentType }));
|
|
141
|
+
}
|
|
142
|
+
event.resolve();
|
|
143
|
+
};
|
|
141
144
|
return (_jsxs(_Fragment, { children: [_jsx("div", { className: [
|
|
142
145
|
'stripe-express-checkout-button',
|
|
143
146
|
className !== null && className !== void 0 ? className : '',
|
|
144
147
|
effectiveWalletAvailable === false ? 'is-hidden' : '',
|
|
145
148
|
]
|
|
146
149
|
.filter(Boolean)
|
|
147
|
-
.join(' '), children: _jsx(ExpressCheckoutElement, { options: resolvedOptions, onCancel: onCancel, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
|
|
150
|
+
.join(' '), children: _jsx(ExpressCheckoutElement, { options: resolvedOptions, onCancel: onCancel, onClick: handleClick, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
|
|
148
151
|
setWalletAvailable(false);
|
|
149
152
|
onAvailabilityChange === null || onAvailabilityChange === void 0 ? void 0 : onAvailabilityChange(false);
|
|
150
153
|
onError === null || onError === void 0 ? void 0 : onError(error.message || 'Unable to load wallet checkout.');
|
|
@@ -121,7 +121,7 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
|
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
walletCheckoutStartedAfterClickSessionIdRef.current = session.checkoutSessionId;
|
|
124
|
-
void trackStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId: session.checkoutSessionId, paymentMethod: analyticsPaymentMethod }));
|
|
124
|
+
void trackStripeSubscriptionCheckoutStarted(Object.assign(Object.assign({}, checkoutAnalytics), { checkoutSessionId: session.checkoutSessionId, checkoutStartSource: 'wallet_click', paymentMethod: analyticsPaymentMethod }));
|
|
125
125
|
}, [
|
|
126
126
|
analyticsPaymentMethod,
|
|
127
127
|
checkoutAnalytics,
|
|
@@ -12,6 +12,7 @@ export type StripeSubscriptionCheckoutAnalyticsContext = {
|
|
|
12
12
|
};
|
|
13
13
|
export type TrackStripeSubscriptionCheckoutStartedInput = StripeSubscriptionCheckoutAnalyticsContext & {
|
|
14
14
|
checkoutSessionId?: string | null;
|
|
15
|
+
checkoutStartSource?: 'card_form_open' | 'wallet_click';
|
|
15
16
|
paymentMethod?: unknown;
|
|
16
17
|
};
|
|
17
18
|
export type TrackPaidStripeSubscriptionCheckoutCompletedInput = StripeSubscriptionCheckoutAnalyticsContext & {
|
|
@@ -70,7 +70,12 @@ export async function trackStripeSubscriptionCheckoutStarted(input) {
|
|
|
70
70
|
stepName: input.stepName,
|
|
71
71
|
stepType: input.stepType,
|
|
72
72
|
stepContractVersion: input.stepContractVersion,
|
|
73
|
-
metadata: Object.assign(Object.assign(Object.assign({}, metadata), { checkoutSessionId, checkout_session_id: checkoutSessionId
|
|
73
|
+
metadata: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, metadata), { checkoutSessionId, checkout_session_id: checkoutSessionId }), (input.checkoutStartSource
|
|
74
|
+
? {
|
|
75
|
+
checkoutStartSource: input.checkoutStartSource,
|
|
76
|
+
checkout_start_source: input.checkoutStartSource,
|
|
77
|
+
}
|
|
78
|
+
: {})), { environment: input.environment || metadata.environment, payment_provider: 'stripe' }), (paymentMethod ? { payment_method: paymentMethod } : {})),
|
|
74
79
|
};
|
|
75
80
|
const eventId = publicAnalyticsSdk.trackCheckoutStarted(conversionInput);
|
|
76
81
|
if (!eventId) {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
var _a, _b, _c, _d;
|
|
2
1
|
import { loadStripe } from '@stripe/stripe-js';
|
|
3
2
|
import { billingPlanList, } from '../config/billing.config.js';
|
|
4
|
-
import { FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, funnelSdkService,
|
|
3
|
+
import { FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, funnelSdkService, } from '@funnelsgrove/runtime';
|
|
5
4
|
import { isBillingPlanMappingCatalog, resolveMappedProjectPlans, } from './planCatalog.service.js';
|
|
6
|
-
const STRIPE_TEST_PUBLISHABLE_KEY = ((_b = (_a = runtimeEnvConfig.stripeTestPublishableKey) !== null && _a !== void 0 ? _a : runtimeEnvConfig.stripePublishableKey) !== null && _b !== void 0 ? _b : '').trim();
|
|
7
|
-
const STRIPE_LIVE_PUBLISHABLE_KEY = ((_d = (_c = runtimeEnvConfig.stripeLivePublishableKey) !== null && _c !== void 0 ? _c : runtimeEnvConfig.stripePublishableKey) !== null && _d !== void 0 ? _d : '').trim();
|
|
8
5
|
const stripePromiseByKey = new Map();
|
|
9
6
|
const syncedPlansPromiseByMode = new Map();
|
|
10
7
|
export const buildPaywallPlanResolutionSignature = (options) => {
|
|
@@ -292,12 +289,6 @@ export function buildConfigPaywallPlans(planCatalog, orderedPlanIds = []) {
|
|
|
292
289
|
: Object.entries(planCatalog !== null && planCatalog !== void 0 ? planCatalog : {}).map(([key, plan]) => toConfigPaywallPlan(key, plan));
|
|
293
290
|
return getOrderedPaywallPlans(plans, orderedPlanIds);
|
|
294
291
|
}
|
|
295
|
-
const getStripePublishableKeyFromEnv = (mode) => {
|
|
296
|
-
if (mode === 'live') {
|
|
297
|
-
return STRIPE_LIVE_PUBLISHABLE_KEY || STRIPE_TEST_PUBLISHABLE_KEY;
|
|
298
|
-
}
|
|
299
|
-
return STRIPE_TEST_PUBLISHABLE_KEY || STRIPE_LIVE_PUBLISHABLE_KEY;
|
|
300
|
-
};
|
|
301
292
|
const toFallbackPaywallPlans = (planCatalog, options) => {
|
|
302
293
|
const sourcePlans = buildConfigPaywallPlans(planCatalog);
|
|
303
294
|
return reorderPlans(sourcePlans.length > 0 ? sourcePlans : fallbackPaywallPlans, options === null || options === void 0 ? void 0 : options.pricing);
|
|
@@ -430,7 +421,8 @@ export async function getPaywallPlans(mode, planCatalog, options) {
|
|
|
430
421
|
return getFallbackPlans(planCatalog, options);
|
|
431
422
|
}
|
|
432
423
|
export function isStripeConfigured(mode) {
|
|
433
|
-
|
|
424
|
+
void mode;
|
|
425
|
+
return FUNNEL_SDK_PUBLISHABLE_KEY.length > 0;
|
|
434
426
|
}
|
|
435
427
|
export function getStripePromise(_mode, runtimePublishableKey) {
|
|
436
428
|
const resolvedPublishableKey = (runtimePublishableKey === null || runtimePublishableKey === void 0 ? void 0 : runtimePublishableKey.trim()) || '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/payments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@funnelsgrove/analytics": "^0.1.28",
|
|
36
|
-
"@funnelsgrove/runtime": "^0.
|
|
36
|
+
"@funnelsgrove/runtime": "^0.2.0",
|
|
37
37
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
38
38
|
"@stripe/stripe-js": "^8.7.0",
|
|
39
39
|
"react": "19.2.3",
|