@funnelsgrove/payments 0.12.0 → 0.14.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.
@@ -12,4 +12,4 @@ export type StripeOneTimeWalletSurfaceProps = Omit<StripeWalletSurfaceProps, 'ap
12
12
  googlePayOptions?: StripeWalletSurfaceProps['googlePayOptions'];
13
13
  googlePayPlaceholderLabel?: string;
14
14
  };
15
- export declare function StripeOneTimeWalletSurface({ applePayOptions, applePayPlaceholderLabel, checkout, googlePayOptions, googlePayPlaceholderLabel, surfaceId, ...surfaceProps }: StripeOneTimeWalletSurfaceProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function StripeOneTimeWalletSurface({ applePayOptions, applePayPlaceholderLabel, checkout, checkoutAnalytics, googlePayOptions, googlePayPlaceholderLabel, surfaceId, ...surfaceProps }: StripeOneTimeWalletSurfaceProps): import("react/jsx-runtime").JSX.Element;
@@ -16,13 +16,13 @@ import { useStripeOneTimeCheckoutSession, } from '../hooks/useStripeOneTimeCheck
16
16
  import { StripeWalletSurface, buildStripeWalletSurfaceAnalyticsMetadata, } from './StripeWalletSurface.internal.js';
17
17
  export function StripeOneTimeWalletSurface(_a) {
18
18
  var _b, _c;
19
- var { applePayOptions, applePayPlaceholderLabel = 'Buy with Apple Pay', checkout, googlePayOptions, googlePayPlaceholderLabel = 'Buy with Google Pay', surfaceId } = _a, surfaceProps = __rest(_a, ["applePayOptions", "applePayPlaceholderLabel", "checkout", "googlePayOptions", "googlePayPlaceholderLabel", "surfaceId"]);
19
+ var { applePayOptions, applePayPlaceholderLabel = 'Buy with Apple Pay', checkout, checkoutAnalytics, googlePayOptions, googlePayPlaceholderLabel = 'Buy with Google Pay', surfaceId } = _a, surfaceProps = __rest(_a, ["applePayOptions", "applePayPlaceholderLabel", "checkout", "checkoutAnalytics", "googlePayOptions", "googlePayPlaceholderLabel", "surfaceId"]);
20
20
  const normalizedSurfaceId = surfaceId.trim();
21
21
  const { customerName } = checkout, checkoutSessionInput = __rest(checkout, ["customerName"]);
22
22
  const surfaceAnalyticsMetadata = useMemo(() => {
23
23
  var _a;
24
24
  return buildStripeWalletSurfaceAnalyticsMetadata(Object.assign(Object.assign({}, ((_a = checkout.analyticsMetadata) !== null && _a !== void 0 ? _a : {})), { paymentMode: 'one_time' }), normalizedSurfaceId);
25
25
  }, [checkout.analyticsMetadata, normalizedSurfaceId]);
26
- const session = useStripeOneTimeCheckoutSession(Object.assign(Object.assign({}, checkoutSessionInput), { analyticsMetadata: surfaceAnalyticsMetadata }));
27
- return (_jsx(StripeWalletSurface, Object.assign({}, surfaceProps, { applePayOptions: Object.assign(Object.assign({}, applePayOptions), { buttonType: Object.assign({ applePay: 'buy' }, ((_b = applePayOptions === null || applePayOptions === void 0 ? void 0 : applePayOptions.buttonType) !== null && _b !== void 0 ? _b : {})) }), applePayPlaceholderLabel: applePayPlaceholderLabel, customerEmail: checkout.customerEmail, customerName: customerName, googlePayOptions: Object.assign(Object.assign({}, googlePayOptions), { buttonType: Object.assign({ googlePay: 'buy' }, ((_c = googlePayOptions === null || googlePayOptions === void 0 ? void 0 : googlePayOptions.buttonType) !== null && _c !== void 0 ? _c : {})) }), googlePayPlaceholderLabel: googlePayPlaceholderLabel, returnUrl: checkout.returnUrl, session: session, surfaceId: normalizedSurfaceId })));
26
+ const session = useStripeOneTimeCheckoutSession(Object.assign(Object.assign({}, checkoutSessionInput), { analyticsMetadata: surfaceAnalyticsMetadata, checkoutAnalytics }));
27
+ return (_jsx(StripeWalletSurface, Object.assign({}, surfaceProps, { checkoutAnalytics: checkoutAnalytics, applePayOptions: Object.assign(Object.assign({}, applePayOptions), { buttonType: Object.assign({ applePay: 'buy' }, ((_b = applePayOptions === null || applePayOptions === void 0 ? void 0 : applePayOptions.buttonType) !== null && _b !== void 0 ? _b : {})) }), applePayPlaceholderLabel: applePayPlaceholderLabel, customerEmail: checkout.customerEmail, customerName: customerName, googlePayOptions: Object.assign(Object.assign({}, googlePayOptions), { buttonType: Object.assign({ googlePay: 'buy' }, ((_c = googlePayOptions === null || googlePayOptions === void 0 ? void 0 : googlePayOptions.buttonType) !== null && _c !== void 0 ? _c : {})) }), googlePayPlaceholderLabel: googlePayPlaceholderLabel, returnUrl: checkout.returnUrl, session: session, surfaceId: normalizedSurfaceId })));
28
28
  }
@@ -8,7 +8,7 @@ export declare const resolveStripeCheckoutAttempt: (input: {
8
8
  current: StripeCheckoutAttempt | null;
9
9
  forceNew: boolean;
10
10
  intentKey: string;
11
- paymentsApiVersion?: "v1" | "v2" | null;
11
+ paymentsApiVersion?: "v2" | null;
12
12
  createId: () => string;
13
13
  nowMs?: number;
14
14
  }) => StripeCheckoutAttempt | null;
@@ -1,7 +1,7 @@
1
1
  import { type CheckoutIdentityStatus } from '@funnelsgrove/runtime';
2
2
  export declare const isStripeCheckoutIdentityReady: (input: {
3
3
  runtimeConfig?: {
4
- paymentsApiVersion?: "v1" | "v2" | null;
4
+ paymentsApiVersion?: "v2" | null;
5
5
  } | null;
6
6
  userId?: string | null;
7
7
  }, getIdentityStatus?: (userId: string | null | undefined) => CheckoutIdentityStatus) => boolean;
@@ -2,6 +2,7 @@ import type { Stripe } 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';
5
+ import { type CheckoutObservabilityAnalyticsContext } from '../../../services/checkoutObservability.service.js';
5
6
  export { isStripeCheckoutIdentityReady as isStripeOneTimeCheckoutIdentityReady, } from './checkoutIdentity.js';
6
7
  type StripeOneTimeCheckoutPlanInput = Pick<PaywallPlan, 'amountCents' | 'analyticsPurchaseValue' | 'description' | 'id' | 'providerPlanId' | 'title' | 'funnelPlanKey' | 'offerSetId' | 'offerSetKey'>;
7
8
  export type StripeOneTimeCheckoutSessionInput = {
@@ -9,6 +10,7 @@ export type StripeOneTimeCheckoutSessionInput = {
9
10
  paymentProfileId?: string | null;
10
11
  provider?: 'stripe' | null;
11
12
  analyticsMetadata?: Record<string, unknown> | null;
13
+ checkoutAnalytics?: CheckoutObservabilityAnalyticsContext | null;
12
14
  checkoutMode: RuntimeMode;
13
15
  couponId?: string | null;
14
16
  customerEmail?: string | null;
@@ -49,4 +51,4 @@ export type StripeOneTimeCheckoutIntentKeyInput = Pick<StripeOneTimeCheckoutSess
49
51
  };
50
52
  export declare function buildStripeOneTimeCheckoutIntentKey({ runtimeConfigRevisionId, paymentProfileId, provider, checkoutMode, couponId, customerEmail, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutIntentKeyInput): string;
51
53
  export declare function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
52
- export declare function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutMode, couponId, customerEmail, enabled, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
54
+ export declare function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutAnalytics, checkoutMode, couponId, customerEmail, enabled, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
@@ -13,8 +13,10 @@ var __rest = (this && this.__rest) || function (s, e) {
13
13
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
14
14
  import { createEmptyCheckoutPreparationLoading, } from '../../paymentProvider.types.js';
15
15
  import { createStripeOneTimeCheckout, getStripePromise, isStripeConfigured, } from '../services/stripe.service.js';
16
- import { isInactiveCheckoutSessionError } from './useStripeSubscriptionCheckoutSession.js';
16
+ import { describeStripeCheckoutFailure } from '../services/stripeCheckoutFailure.js';
17
+ import { isInactiveCheckoutSessionError, shouldReportStripeCheckoutFailureShown, } from './useStripeSubscriptionCheckoutSession.js';
17
18
  import { toAnalyticsPaymentMethod } from '../../../services/paymentMethodAnalytics.service.js';
19
+ import { resolveCheckoutObservabilityAnalyticsContext, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
18
20
  import { resolveStripeCheckoutAttempt, } from './checkoutAttempt.js';
19
21
  import { isStripeCheckoutIdentityReady } from './checkoutIdentity.js';
20
22
  export { isStripeCheckoutIdentityReady as isStripeOneTimeCheckoutIdentityReady, } from './checkoutIdentity.js';
@@ -24,12 +26,6 @@ export function resolveStripeOneTimeCheckoutPreparationOptions(source, options =
24
26
  const paymentMethod = (_b = options.paymentMethod) !== null && _b !== void 0 ? _b : (source === 'card' && checkoutStartSource !== 'checkout_render' ? 'card' : undefined);
25
27
  return Object.assign(Object.assign(Object.assign({}, options), { checkoutStartSource }), (paymentMethod !== undefined ? { paymentMethod } : {}));
26
28
  }
27
- const getFriendlyStripeCheckoutError = (error) => {
28
- const message = error instanceof Error ? error.message : 'Unable to initialize checkout';
29
- return message === 'Internal server error'
30
- ? 'Unable to initialize checkout. Stripe settings may be missing for this funnel.'
31
- : message;
32
- };
33
29
  const normalizeRuntimeConfigValue = (value) => (value === null || value === void 0 ? void 0 : value.trim()) || '';
34
30
  const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
35
31
  export const buildStripeOneTimeCheckoutPreparationAnalyticsMetadata = (analyticsMetadata, options = {}) => {
@@ -75,7 +71,7 @@ export function buildStripeOneTimeCheckoutIntentKey({ runtimeConfigRevisionId, p
75
71
  export function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey, nextIntentKey) {
76
72
  return previousIntentKey !== null && previousIntentKey !== nextIntentKey;
77
73
  }
78
- export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, 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, userId, }) {
79
75
  var _a, _b, _c;
80
76
  const [clientSecret, setClientSecret] = useState(null);
81
77
  const [clientSecretIntentKey, setClientSecretIntentKey] = useState(null);
@@ -226,7 +222,26 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
226
222
  catch (checkoutError) {
227
223
  if (((_e = creatingIntentRef.current) === null || _e === void 0 ? void 0 : _e.key) === requestKey &&
228
224
  ((_f = creatingIntentRef.current) === null || _f === void 0 ? void 0 : _f.id) === requestId) {
229
- setError(getFriendlyStripeCheckoutError(checkoutError));
225
+ const failure = describeStripeCheckoutFailure(checkoutError);
226
+ const message = failure.message;
227
+ const observabilityAnalytics = resolveCheckoutObservabilityAnalyticsContext({
228
+ analytics: checkoutAnalytics,
229
+ environment: checkoutMode,
230
+ metadata: analyticsMetadataRef.current,
231
+ runtimeConfig,
232
+ });
233
+ if (observabilityAnalytics && shouldReportStripeCheckoutFailureShown(message)) {
234
+ trackCheckoutFailureShown({
235
+ analytics: observabilityAnalytics,
236
+ errorCode: failure.errorCode,
237
+ errorMessage: message,
238
+ failureStage: 'checkout_session_creation',
239
+ paymentMethod: options.paymentMethod,
240
+ provider: 'stripe',
241
+ providerErrorCode: failure.providerErrorCode,
242
+ });
243
+ }
244
+ setError(message);
230
245
  }
231
246
  return null;
232
247
  }
@@ -249,6 +264,7 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
249
264
  paymentProfileId,
250
265
  provider,
251
266
  checkoutMode,
267
+ checkoutAnalytics,
252
268
  clearActiveCheckoutSession,
253
269
  configured,
254
270
  couponId,
@@ -13,6 +13,7 @@ var __rest = (this && this.__rest) || function (s, e) {
13
13
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
14
14
  import { createEmptyCheckoutPreparationLoading, } from '../../paymentProvider.types.js';
15
15
  import { createStripeSubscriptionCheckout, getStripePromise, isStripeConfigured, updateStripeSubscriptionCheckoutPlan, } from '../services/stripe.service.js';
16
+ import { describeStripeCheckoutFailure } from '../services/stripeCheckoutFailure.js';
16
17
  import { toAnalyticsPaymentMethod } from '../../../services/paymentMethodAnalytics.service.js';
17
18
  import { resolveCheckoutObservabilityAnalyticsContext, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
18
19
  import { resolveStripeCheckoutAttempt, } from './checkoutAttempt.js';
@@ -24,12 +25,6 @@ export function resolveStripeSubscriptionCheckoutPreparationOptions(source, opti
24
25
  const paymentMethod = (_b = options.paymentMethod) !== null && _b !== void 0 ? _b : (source === 'card' && checkoutStartSource !== 'checkout_render' ? 'card' : undefined);
25
26
  return Object.assign(Object.assign(Object.assign({}, options), { checkoutStartSource }), (paymentMethod !== undefined ? { paymentMethod } : {}));
26
27
  }
27
- const getFriendlyStripeCheckoutError = (error) => {
28
- const message = error instanceof Error ? error.message : 'Unable to initialize checkout';
29
- return message === 'Internal server error'
30
- ? 'Unable to initialize checkout. Stripe settings may be missing for this funnel.'
31
- : message;
32
- };
33
28
  const normalizeRuntimeConfigValue = (value) => (value === null || value === void 0 ? void 0 : value.trim()) || '';
34
29
  const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
35
30
  export const buildStripeSubscriptionCheckoutPreparationAnalyticsMetadata = (analyticsMetadata, options = {}) => {
@@ -246,7 +241,8 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
246
241
  catch (subscriptionError) {
247
242
  if (((_f = creatingIntentRef.current) === null || _f === void 0 ? void 0 : _f.key) === requestKey &&
248
243
  ((_g = creatingIntentRef.current) === null || _g === void 0 ? void 0 : _g.id) === requestId) {
249
- const message = getFriendlyStripeCheckoutError(subscriptionError);
244
+ const failure = describeStripeCheckoutFailure(subscriptionError);
245
+ const message = failure.message;
250
246
  const observabilityAnalytics = resolveCheckoutObservabilityAnalyticsContext({
251
247
  analytics: checkoutAnalytics,
252
248
  environment: checkoutMode,
@@ -257,10 +253,12 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
257
253
  && shouldReportStripeCheckoutFailureShown(message)) {
258
254
  trackCheckoutFailureShown({
259
255
  analytics: observabilityAnalytics,
256
+ errorCode: failure.errorCode,
260
257
  errorMessage: message,
261
258
  failureStage: 'checkout_session_creation',
262
259
  paymentMethod: options.paymentMethod,
263
260
  provider: 'stripe',
261
+ providerErrorCode: failure.providerErrorCode,
264
262
  });
265
263
  }
266
264
  setError(message);
@@ -364,7 +362,8 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
364
362
  return true;
365
363
  }
366
364
  catch (subscriptionError) {
367
- const message = getFriendlyStripeCheckoutError(subscriptionError);
365
+ const failure = describeStripeCheckoutFailure(subscriptionError);
366
+ const message = failure.message;
368
367
  const observabilityAnalytics = resolveCheckoutObservabilityAnalyticsContext({
369
368
  analytics: checkoutAnalytics,
370
369
  environment: checkoutMode,
@@ -376,9 +375,11 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
376
375
  trackCheckoutFailureShown({
377
376
  analytics: observabilityAnalytics,
378
377
  checkoutSessionId: activeCheckoutSessionId,
378
+ errorCode: failure.errorCode,
379
379
  errorMessage: message,
380
380
  failureStage: 'checkout_session_update',
381
381
  provider: 'stripe',
382
+ providerErrorCode: failure.providerErrorCode,
382
383
  });
383
384
  }
384
385
  setError(message);
@@ -0,0 +1,6 @@
1
+ export type StripeCheckoutFailure = {
2
+ errorCode?: string;
3
+ message: string;
4
+ providerErrorCode?: string;
5
+ };
6
+ export declare const describeStripeCheckoutFailure: (error: unknown) => StripeCheckoutFailure;
@@ -0,0 +1,22 @@
1
+ import { FunnelSdkHttpError } from '@funnelsgrove/runtime';
2
+ export const describeStripeCheckoutFailure = (error) => {
3
+ const errorCode = error instanceof FunnelSdkHttpError
4
+ ? error.code || undefined
5
+ : undefined;
6
+ const providerErrorCode = (errorCode === null || errorCode === void 0 ? void 0 : errorCode.startsWith('stripe_'))
7
+ ? errorCode.slice('stripe_'.length)
8
+ : undefined;
9
+ const rawMessage = error instanceof Error
10
+ ? error.message
11
+ : 'Unable to initialize checkout';
12
+ const message = errorCode === 'stripe_email_invalid'
13
+ ? 'Enter a valid email address to continue.'
14
+ : rawMessage === 'Internal server error'
15
+ ? 'Unable to initialize checkout. Stripe settings may be missing for this funnel.'
16
+ : rawMessage;
17
+ return {
18
+ errorCode,
19
+ message,
20
+ providerErrorCode,
21
+ };
22
+ };
@@ -39,14 +39,19 @@ const STRIPE_PAYMENT_REJECTION_CODES = new Set([
39
39
  'payment_intent_authentication_failure',
40
40
  'paymentFailed',
41
41
  ]);
42
+ const STRIPE_VALIDATION_ERROR_CODES = new Set(['email_invalid']);
42
43
  export const classifyCheckoutFailure = (input) => {
43
44
  const providerDeclineCode = asNonEmptyString(input.providerDeclineCode);
44
45
  const providerErrorCode = asNonEmptyString(input.providerErrorCode);
45
46
  const providerErrorType = asNonEmptyString(input.providerErrorType);
47
+ if (input.provider === 'stripe' && providerErrorCode
48
+ && STRIPE_VALIDATION_ERROR_CODES.has(providerErrorCode)) {
49
+ return 'validation_failure';
50
+ }
46
51
  if (input.provider === 'stripe' && (Boolean(providerDeclineCode)
47
52
  || providerErrorType === 'card_error'
48
53
  || (providerErrorCode ? STRIPE_PAYMENT_REJECTION_CODES.has(providerErrorCode) : false)
49
- || /card has insufficient funds|unable to authenticate your payment method/i.test(input.errorMessage))) {
54
+ || /card has insufficient funds|unable to authenticate your payment method|card(?: was| is)? declined|security code is incorrect/i.test(input.errorMessage))) {
50
55
  return 'payment_rejection';
51
56
  }
52
57
  const message = input.errorMessage.trim();
@@ -57,8 +62,8 @@ export const classifyCheckoutFailure = (input) => {
57
62
  if (/failed to fetch|load failed|network|connection to (?:the )?provider|too many requests/i.test(message)) {
58
63
  return 'network_failure';
59
64
  }
60
- if (/(?:email|card number|expiration|expiry|cvc|zip|postal code|billing address|country).*(?:required|incomplete|incorrect|invalid|unsupported)/i.test(message)
61
- || /(?:required|incomplete|incorrect|invalid|unsupported).*(?:email|card number|expiration|expiry|cvc|zip|postal code|billing address|country)/i.test(message)
65
+ if (/(?:email|card number|expiration|expiry|cvc|security code|zip|postal code|billing address|country).*(?:required|incomplete|incorrect|invalid|unsupported)/i.test(message)
66
+ || /(?:required|incomplete|incorrect|invalid|unsupported).*(?:email|card number|expiration|expiry|cvc|security code|zip|postal code|billing address|country)/i.test(message)
62
67
  || /(?:enter|provide|use) a valid email(?: address)?/i.test(message)) {
63
68
  return 'validation_failure';
64
69
  }
@@ -4,7 +4,7 @@ import { type PaywallPlan } from './paywallPlans.service.js';
4
4
  export type PublishedBillingPlan = BillingFallbackPlanConfig & OfferSetRuntimePlanOverrides;
5
5
  export type ResolvedPublishedBillingRuntime = {
6
6
  revisionId: string | null;
7
- paymentsApiVersion: 'v1' | 'v2';
7
+ paymentsApiVersion: 'v2';
8
8
  mode: RuntimeMode;
9
9
  offerSetId: string;
10
10
  paymentProfileId: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.12.0",
3
+ "version": "0.14.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.97",
36
- "@funnelsgrove/runtime": "^0.12.0",
36
+ "@funnelsgrove/runtime": "^0.13.0",
37
37
  "@solidgate/react-sdk": "1.34.0",
38
38
  "@stripe/react-stripe-js": "^5.6.0",
39
39
  "@stripe/stripe-js": "^8.7.0",