@funnelsgrove/payments 0.13.0 → 0.15.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.
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
14
- import { useCallback, useEffect, useMemo, useState, } from 'react';
14
+ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
15
15
  import { CheckoutProvider, PaymentElement, useCheckout, } from '@stripe/react-stripe-js/checkout';
16
16
  import { PaymentBrandMark, SecurityLockIcon } from './CheckoutBrandAssets.js';
17
17
  import { StripeCheckoutExpressCheckoutButton } from './StripeCheckoutExpressCheckoutButton.js';
@@ -70,6 +70,7 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
70
70
  ? true
71
71
  : walletAvailable;
72
72
  const [submitting, setSubmitting] = useState(false);
73
+ const submitInFlightRef = useRef(false);
73
74
  const [paymentElementReady, setPaymentElementReady] = useState(false);
74
75
  const [error, setError] = useState(null);
75
76
  const [billingCountry, setBillingCountry] = useState(null);
@@ -250,7 +251,7 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
250
251
  };
251
252
  const handleSubmit = async (event) => {
252
253
  event.preventDefault();
253
- if (submitting) {
254
+ if (submitInFlightRef.current) {
254
255
  return;
255
256
  }
256
257
  const billingCountryError = getBillingCountryError(billingCountry !== null && billingCountry !== void 0 ? billingCountry : fixedBillingCountry);
@@ -268,12 +269,17 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
268
269
  setSharedError('Payment form is not ready yet.', true);
269
270
  return;
270
271
  }
272
+ submitInFlightRef.current = true;
271
273
  setSubmitting(true);
272
274
  setSharedError(null);
275
+ const finishSubmitting = () => {
276
+ submitInFlightRef.current = false;
277
+ setSubmitting(false);
278
+ };
273
279
  try {
274
280
  const syncedEmail = await ensureCustomerEmail();
275
281
  if (!syncedEmail) {
276
- setSubmitting(false);
282
+ finishSubmitting();
277
283
  return;
278
284
  }
279
285
  await trackPaymentInfoSubmitted();
@@ -283,7 +289,7 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
283
289
  if (result.type === 'error') {
284
290
  const message = result.error.message || 'Payment failed.';
285
291
  setSharedError(message, false, result.error);
286
- setSubmitting(false);
292
+ finishSubmitting();
287
293
  return;
288
294
  }
289
295
  if (typeof window !== 'undefined') {
@@ -291,11 +297,11 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
291
297
  window.location.assign(returnUrl);
292
298
  return;
293
299
  }
294
- setSubmitting(false);
300
+ finishSubmitting();
295
301
  }
296
302
  catch (submitError) {
297
303
  setSharedError(getFriendlyCardErrorMessage(submitError));
298
- setSubmitting(false);
304
+ finishSubmitting();
299
305
  }
300
306
  };
301
307
  const visibleError = error;
@@ -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
  }
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",