@funnelsgrove/payments 0.1.12 → 0.1.14

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.
@@ -21,6 +21,7 @@ export type SharedStripeCheckoutV2DialogProps = {
21
21
  onCustomerEmailChange?: (value: string) => void;
22
22
  onCustomerEmailCommit?: (email: string) => Promise<string | null | void>;
23
23
  onError?: (message: string | null) => void;
24
+ onInactiveCheckoutSession?: () => void;
24
25
  onSuccess?: () => void;
25
26
  paymentMethodLabels?: readonly string[];
26
27
  paypalButtonLabel: string;
@@ -15,8 +15,29 @@ import { useEffect, useMemo, 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';
18
+ import { isInactiveCheckoutSessionError } from '../../providers/stripe/useStripeSubscriptionCheckoutSession.js';
18
19
  import { getStripeWalletPaymentMethodOrder, usePlatformWalletPaymentMethods, } from './walletPlatform.js';
19
20
  const defaultPaymentMethodLabels = ['Visa', 'Mastercard', 'Maestro', 'Discover'];
21
+ const stripeCheckoutV2Appearance = {
22
+ labels: 'above',
23
+ inputs: 'spaced',
24
+ variables: {
25
+ borderRadius: '0px',
26
+ colorBackground: '#fff',
27
+ colorText: '#303136',
28
+ colorTextPlaceholder: '#8c909a',
29
+ gridColumnSpacing: '12px',
30
+ gridRowSpacing: '14px',
31
+ spacingUnit: '5px',
32
+ },
33
+ rules: {
34
+ '.Label': {
35
+ paddingLeft: '14px',
36
+ paddingRight: '14px',
37
+ marginBottom: '6px',
38
+ },
39
+ },
40
+ };
20
41
  const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
21
42
  const formatCountdown = (remainingSeconds) => {
22
43
  const normalizedSeconds = Number.isFinite(remainingSeconds)
@@ -31,7 +52,7 @@ function getFriendlyCardErrorMessage(error) {
31
52
  ? error.message
32
53
  : 'Payment failed. Please review your card details and try again.';
33
54
  }
34
- function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, customerEmail, customerEmailEditable = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, themeColor, title, totalLabel, totalValue, walletButtonLabel, originalPriceLabel, originalPriceValue, }) {
55
+ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, customerEmail, customerEmailEditable = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, themeColor, title, totalLabel, totalValue, walletButtonLabel, originalPriceLabel, originalPriceValue, }) {
35
56
  const checkoutState = useCheckout();
36
57
  const walletPaymentMethods = usePlatformWalletPaymentMethods();
37
58
  const [selectedMethod, setSelectedMethod] = useState('wallet');
@@ -112,7 +133,15 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
112
133
  },
113
134
  };
114
135
  }, []);
136
+ const handleInactiveCheckoutSession = () => {
137
+ setError(null);
138
+ onInactiveCheckoutSession === null || onInactiveCheckoutSession === void 0 ? void 0 : onInactiveCheckoutSession();
139
+ };
115
140
  const setSharedError = (message) => {
141
+ if (isInactiveCheckoutSessionError(message)) {
142
+ handleInactiveCheckoutSession();
143
+ return;
144
+ }
116
145
  setError(message);
117
146
  onError === null || onError === void 0 ? void 0 : onError(message);
118
147
  };
@@ -128,11 +157,6 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
128
157
  setSelectedMethod('card');
129
158
  }
130
159
  };
131
- useEffect(() => {
132
- if (!customerEmailEditable) {
133
- setEmailDraft(initialCustomerEmail);
134
- }
135
- }, [customerEmailEditable, initialCustomerEmail]);
136
160
  useEffect(() => {
137
161
  if (effectiveSelectedMethod !== 'card' || typeof window === 'undefined') {
138
162
  return;
@@ -184,7 +208,8 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
184
208
  returnUrl,
185
209
  });
186
210
  if (result.type === 'error') {
187
- setSharedError(result.error.message || 'Payment failed.');
211
+ const message = result.error.message || 'Payment failed.';
212
+ setSharedError(message);
188
213
  setSubmitting(false);
189
214
  return;
190
215
  }
@@ -206,22 +231,17 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
206
231
  showPromo ? '' : 'is-plain',
207
232
  ]
208
233
  .filter(Boolean)
209
- .join(' '), children: [_jsx("span", { children: totalLabel }), _jsxs("strong", { children: [_jsx("svg", { viewBox: '0 0 24 24', "aria-hidden": 'true', children: _jsx("path", { d: 'M20.6 13.4 12.4 21.6a2.2 2.2 0 0 1-3.1 0L2.4 14.7a2.2 2.2 0 0 1-.6-1.6V5.4a2 2 0 0 1 2-2h7.7a2.2 2.2 0 0 1 1.6.6l7.5 7.5a1.4 1.4 0 0 1 0 1.9ZM7.2 8.1a1.2 1.2 0 1 0 0-2.4 1.2 1.2 0 0 0 0 2.4Z' }) }), totalValue] })] }), showPromo ? (_jsxs("p", { className: 'shared-checkout-v2-saved', children: [_jsx("span", { "aria-hidden": 'true', children: "\uD83D\uDD25" }), savedLabel] })) : null] }), _jsxs("form", { className: 'shared-checkout-v2-payment', onSubmit: (event) => void handleSubmit(event), children: [_jsxs("div", { className: [
210
- 'shared-checkout-v2-methods',
211
- showWalletMethod ? '' : 'has-card-only',
212
- ]
213
- .filter(Boolean)
214
- .join(' '), role: 'tablist', "aria-label": 'Payment method', children: [showWalletMethod ? (_jsxs("button", { type: 'button', className: [
234
+ .join(' '), children: [_jsx("span", { children: totalLabel }), _jsxs("strong", { children: [_jsx("svg", { viewBox: '0 0 24 24', "aria-hidden": 'true', children: _jsx("path", { d: 'M20.6 13.4 12.4 21.6a2.2 2.2 0 0 1-3.1 0L2.4 14.7a2.2 2.2 0 0 1-.6-1.6V5.4a2 2 0 0 1 2-2h7.7a2.2 2.2 0 0 1 1.6.6l7.5 7.5a1.4 1.4 0 0 1 0 1.9ZM7.2 8.1a1.2 1.2 0 1 0 0-2.4 1.2 1.2 0 0 0 0 2.4Z' }) }), totalValue] })] }), showPromo ? (_jsxs("p", { className: 'shared-checkout-v2-saved', children: [_jsx("span", { "aria-hidden": 'true', children: "\uD83D\uDD25" }), savedLabel] })) : null] }), _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": 'Payment method', children: [_jsxs("button", { type: 'button', className: [
215
235
  'shared-checkout-v2-method',
216
236
  effectiveSelectedMethod === 'wallet' ? 'is-selected' : '',
217
237
  ]
218
238
  .filter(Boolean)
219
- .join(' '), onClick: selectWalletMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'wallet', children: [_jsxs("span", { className: 'shared-checkout-v2-tab-wallets', "aria-hidden": 'true', children: [walletPaymentMethods.includes('applePay') ? (_jsx(PaymentBrandMark, { label: 'Apple Pay', className: 'shared-checkout-v2-tab-wallet' })) : null, walletPaymentMethods.includes('googlePay') ? (_jsx(PaymentBrandMark, { label: 'Google Pay', className: 'shared-checkout-v2-tab-wallet' })) : null] }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] })) : null, _jsxs("button", { type: 'button', className: [
239
+ .join(' '), onClick: selectWalletMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'wallet', children: [_jsxs("span", { className: 'shared-checkout-v2-tab-wallets', "aria-hidden": 'true', children: [walletPaymentMethods.includes('applePay') ? (_jsx(PaymentBrandMark, { label: 'Apple Pay', className: 'shared-checkout-v2-tab-wallet' })) : null, walletPaymentMethods.includes('googlePay') ? (_jsx(PaymentBrandMark, { label: 'Google Pay', className: 'shared-checkout-v2-tab-wallet' })) : null] }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] }), _jsxs("button", { type: 'button', className: [
220
240
  'shared-checkout-v2-method',
221
241
  effectiveSelectedMethod === 'card' ? 'is-selected' : '',
222
242
  ]
223
243
  .filter(Boolean)
224
- .join(' '), onClick: selectCardMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'card', children: [_jsx("strong", { children: "Credit card" }), _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))) })] })] }), _jsxs("div", { hidden: effectiveSelectedMethod !== 'wallet', className: [
244
+ .join(' '), onClick: selectCardMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'card', children: [_jsx("strong", { children: "Credit card" }), _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: [
225
245
  'shared-checkout-v2-wallet-panel',
226
246
  effectiveSelectedMethod === 'wallet' ? 'is-visible' : '',
227
247
  ]
@@ -231,16 +251,22 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
231
251
  effectiveSelectedMethod === 'card' ? 'is-visible' : '',
232
252
  ]
233
253
  .filter(Boolean)
234
- .join(' '), children: [_jsxs("div", { className: 'shared-checkout-v2-card-box', children: [_jsxs("label", { className: emailFieldClassName, children: [_jsx("span", { children: customerEmailLabel }), _jsx("input", { type: 'email', autoComplete: 'email', className: 'shared-checkout-v2-email-input', disabled: !customerEmailEditable, inputMode: 'email', readOnly: !customerEmailEditable, value: emailDraft, placeholder: customerEmailPlaceholder, onChange: (event) => {
235
- if (!customerEmailEditable) {
236
- return;
237
- }
238
- setEmailDraft(event.target.value);
239
- onCustomerEmailChange === null || onCustomerEmailChange === void 0 ? void 0 : onCustomerEmailChange(event.target.value);
240
- } })] }), _jsx(PaymentElement, { options: paymentElementOptions })] }), visibleError ? _jsx("p", { className: 'shared-checkout-v2-error', children: visibleError }) : null, _jsxs("button", { type: 'submit', className: 'shared-checkout-v2-card-submit', disabled: submitting, children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-card-submit-icon' }), submitting ? 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] })] })] })] }));
254
+ .join(' '), children: [_jsxs("div", { className: 'shared-checkout-v2-card-box', children: [_jsxs("label", { className: emailFieldClassName, children: [_jsx("span", { className: 'shared-checkout-v2-email-label', children: customerEmailLabel }), _jsx("span", { className: 'shared-checkout-v2-email-control', children: _jsx("input", { type: 'email', autoComplete: 'email', className: 'shared-checkout-v2-email-input', disabled: !customerEmailEditable, inputMode: 'email', readOnly: !customerEmailEditable, value: customerEmailEditable ? emailDraft : initialCustomerEmail, placeholder: customerEmailPlaceholder, onChange: (event) => {
255
+ if (!customerEmailEditable) {
256
+ return;
257
+ }
258
+ setEmailDraft(event.target.value);
259
+ onCustomerEmailChange === null || onCustomerEmailChange === void 0 ? void 0 : onCustomerEmailChange(event.target.value);
260
+ } }) })] }), _jsx(PaymentElement, { options: paymentElementOptions })] }), visibleError ? _jsx("p", { className: 'shared-checkout-v2-error', children: visibleError }) : null, _jsxs("button", { type: 'submit', className: 'shared-checkout-v2-card-submit', disabled: submitting, children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-card-submit-icon' }), submitting ? 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] })] })] })] }));
241
261
  }
242
262
  export function SharedStripeCheckoutV2Dialog(_a) {
243
263
  var { clientSecret, onClose, stripePromise } = _a, props = __rest(_a, ["clientSecret", "onClose", "stripePromise"]);
264
+ const checkoutProviderOptions = useMemo(() => ({
265
+ clientSecret,
266
+ elementsOptions: {
267
+ appearance: stripeCheckoutV2Appearance,
268
+ },
269
+ }), [clientSecret]);
244
270
  useEffect(() => {
245
271
  const handleKeyDown = (event) => {
246
272
  if (event.key === 'Escape') {
@@ -250,9 +276,7 @@ export function SharedStripeCheckoutV2Dialog(_a) {
250
276
  window.addEventListener('keydown', handleKeyDown);
251
277
  return () => window.removeEventListener('keydown', handleKeyDown);
252
278
  }, [onClose]);
253
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutProvider, { stripe: stripePromise, options: {
254
- clientSecret,
255
- }, children: _jsx(SharedStripeCheckoutV2Form, Object.assign({}, props, { clientSecret: clientSecret, onClose: onClose })) }) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
279
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutProvider, { stripe: stripePromise, options: checkoutProviderOptions, children: _jsx(SharedStripeCheckoutV2Form, Object.assign({}, props, { clientSecret: clientSecret, onClose: onClose })) }, clientSecret) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
256
280
  }
257
281
  function SharedCheckoutSpecialOfferGift({ discountLabel, imageAlt, imageSrc, }) {
258
282
  if (imageSrc) {
@@ -522,11 +546,6 @@ const sharedStripeCheckoutV2Styles = `
522
546
  gap: 42px;
523
547
  }
524
548
 
525
- .shared-checkout-v2-methods.has-card-only {
526
- grid-template-columns: minmax(0, 1fr);
527
- gap: 0;
528
- }
529
-
530
549
  .shared-checkout-v2-method {
531
550
  min-width: 0;
532
551
  min-height: 74px;
@@ -648,9 +667,9 @@ const sharedStripeCheckoutV2Styles = `
648
667
  .shared-checkout-v2-card-panel {
649
668
  position: relative;
650
669
  gap: 10px;
651
- margin-bottom: 42px;
670
+ margin-inline: -17px;
652
671
  background: #f2f2f3;
653
- padding: 16px 15px 58px;
672
+ padding: 16px 32px 92px;
654
673
  }
655
674
 
656
675
  .shared-checkout-v2-wallet-panel.is-visible,
@@ -668,36 +687,54 @@ const sharedStripeCheckoutV2Styles = `
668
687
  }
669
688
 
670
689
  .shared-checkout-v2-email-field {
671
- min-height: 64px;
672
690
  box-sizing: border-box;
673
691
  display: flex;
674
692
  flex-direction: column;
675
- justify-content: center;
693
+ gap: 7px;
676
694
  border: 0;
677
- border-bottom: 1px solid #dadde4;
678
695
  border-radius: 0;
679
- background: #fff;
680
- padding: 8px 14px;
681
- color: #647087;
696
+ background: transparent;
697
+ margin-bottom: 14px;
698
+ padding: 0;
699
+ color: #303136;
682
700
  font-size: 14px;
683
- font-weight: 500;
684
- line-height: 1;
701
+ font-weight: 400;
702
+ line-height: 1.2;
685
703
  }
686
704
 
687
705
  .shared-checkout-v2-email-field.is-disabled {
688
706
  cursor: default;
689
707
  }
690
708
 
709
+ .shared-checkout-v2-email-label {
710
+ padding: 0 14px;
711
+ color: #303136;
712
+ font-size: 14px;
713
+ font-weight: 400;
714
+ line-height: 1.2;
715
+ }
716
+
717
+ .shared-checkout-v2-email-control {
718
+ min-height: 50px;
719
+ box-sizing: border-box;
720
+ display: flex;
721
+ align-items: center;
722
+ border: 1px solid #dedede;
723
+ background: #fff;
724
+ box-shadow: 0 1px 2px rgb(16 24 40 / 8%);
725
+ padding: 0 14px;
726
+ }
727
+
691
728
  .shared-checkout-v2-email-input {
692
729
  width: 100%;
693
- min-height: 30px;
730
+ min-height: 48px;
694
731
  box-sizing: border-box;
695
732
  border: 0;
696
733
  background: transparent;
697
734
  padding: 0;
698
735
  color: #303136;
699
736
  font: inherit;
700
- font-size: 20px;
737
+ font-size: 18px;
701
738
  font-weight: 400;
702
739
  line-height: 1.2;
703
740
  outline: none;
@@ -714,11 +751,11 @@ const sharedStripeCheckoutV2Styles = `
714
751
  }
715
752
 
716
753
  .shared-checkout-v2-card-box {
717
- overflow: hidden;
718
- border: 1px solid #dadde4;
754
+ overflow: visible;
755
+ border: 0;
719
756
  border-radius: 6px;
720
- background: #fff;
721
- box-shadow: 0 1px 2px rgb(16 24 40 / 10%);
757
+ background: transparent;
758
+ box-shadow: none;
722
759
  }
723
760
 
724
761
  .shared-checkout-v2-error {
@@ -773,7 +810,7 @@ const sharedStripeCheckoutV2Styles = `
773
810
  .shared-checkout-v2-secure-pill--card {
774
811
  position: absolute;
775
812
  left: 50%;
776
- bottom: -33px;
813
+ bottom: 16px;
777
814
  transform: translateX(-50%);
778
815
  white-space: nowrap;
779
816
  }
@@ -908,7 +945,7 @@ const sharedStripeCheckoutV2Styles = `
908
945
  }
909
946
 
910
947
  .shared-checkout-v2-card-panel {
911
- padding-inline: 15px;
948
+ padding-inline: 32px;
912
949
  }
913
950
  }
914
951
  `;
@@ -12,10 +12,8 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  };
13
13
  import { jsx as _jsx } from "react/jsx-runtime";
14
14
  import { ApplePaySubscribeButton } from '../../components/shared/ApplePaySubscribeButton.js';
15
- import { useApplePayExpressCheckoutAllowed } from '../../components/shared/SharedStripeCheckoutDialog.js';
16
15
  import { WalletSubscriptionCheckoutLoadingPlaceholder, WalletSubscriptionCheckoutSlot, } from './WalletSubscriptionCheckoutSlot.js';
17
16
  export function ApplePaySubscriptionCheckoutSlot(_a) {
18
17
  var { placeholderLabel = 'Subscribe with Apple Pay' } = _a, slotProps = __rest(_a, ["placeholderLabel"]);
19
- const allowApplePayExpressCheckout = useApplePayExpressCheckoutAllowed();
20
- return (_jsx(WalletSubscriptionCheckoutSlot, Object.assign({}, slotProps, { availabilityPaymentMethod: 'applePay', expressCheckoutAllowed: allowApplePayExpressCheckout, placeholderLabel: placeholderLabel, renderPreparingPlaceholder: (placeholderProps) => (_jsx(WalletSubscriptionCheckoutLoadingPlaceholder, Object.assign({}, placeholderProps, { label: 'Loading Apple Pay' }))), renderPlaceholder: (placeholderProps) => (_jsx(ApplePaySubscribeButton, Object.assign({}, placeholderProps))) })));
18
+ return (_jsx(WalletSubscriptionCheckoutSlot, Object.assign({}, slotProps, { availabilityPaymentMethod: 'applePay', placeholderLabel: placeholderLabel, renderPreparingPlaceholder: (placeholderProps) => (_jsx(WalletSubscriptionCheckoutLoadingPlaceholder, Object.assign({}, placeholderProps, { label: 'Loading Apple Pay' }))), renderPlaceholder: (placeholderProps) => (_jsx(ApplePaySubscribeButton, Object.assign({}, placeholderProps))) })));
21
19
  }
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { AvailablePaymentMethods, StripeCheckoutExpressCheckoutElementOptions } from '@stripe/stripe-js';
3
- import type { StripeSubscriptionCheckoutSession } from './useStripeSubscriptionCheckoutSession.js';
3
+ import { type StripeSubscriptionCheckoutSession } from './useStripeSubscriptionCheckoutSession.js';
4
4
  type WalletPlaceholderButtonProps = {
5
5
  className?: string;
6
6
  disabled: boolean;
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useRef, useState } from 'react';
4
4
  import { StripeExpressCheckoutElement } from '../../components/shared/StripeExpressCheckoutElement.js';
5
+ import { isInactiveCheckoutSessionError, } from './useStripeSubscriptionCheckoutSession.js';
5
6
  export function WalletSubscriptionCheckoutLoadingPlaceholder({ className, label, }) {
6
7
  return (_jsxs("div", { className: [
7
8
  'wallet-subscription-checkout-slot__loading',
@@ -65,6 +66,14 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymen
65
66
  walletAvailable,
66
67
  ]);
67
68
  const handleError = (message) => {
69
+ if (isInactiveCheckoutSessionError(message)) {
70
+ setWalletAvailability({
71
+ available: null,
72
+ intentKey: session.intentKey,
73
+ });
74
+ void session.restartWalletCheckout();
75
+ return;
76
+ }
68
77
  session.setError(message);
69
78
  onError === null || onError === void 0 ? void 0 : onError(message);
70
79
  };
@@ -96,7 +105,7 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymen
96
105
  .filter(Boolean)
97
106
  .join(' '), "aria-busy": placeholderIsPreparing || undefined, children: [shouldRenderExpressCheckout &&
98
107
  session.activeClientSecret &&
99
- session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, availabilityPaymentMethods: [availabilityPaymentMethod], beforeConfirm: beforeConfirm, className: className, clientSecret: session.activeClientSecret, customerEmail: customerEmail, customerName: customerName, onAvailabilityChange: handleAvailabilityChange, onError: handleError, onSuccess: onSuccess, options: options, returnUrl: returnUrl, stripePromise: session.stripePromise, summaryLabel: summaryLabel })) : null, shouldRenderPlaceholder ? (_jsx("div", { className: 'wallet-subscription-checkout-slot__placeholder', children: (renderPreparingPlaceholder !== null && renderPreparingPlaceholder !== void 0 ? renderPreparingPlaceholder : renderPlaceholder)({
108
+ session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, availabilityPaymentMethods: [availabilityPaymentMethod], beforeConfirm: beforeConfirm, className: className, clientSecret: session.activeClientSecret, customerEmail: customerEmail, customerName: customerName, onAvailabilityChange: handleAvailabilityChange, onError: handleError, onSuccess: onSuccess, options: options, returnUrl: returnUrl, stripePromise: session.stripePromise, summaryLabel: summaryLabel }, session.activeClientSecret)) : null, shouldRenderPlaceholder ? (_jsx("div", { className: 'wallet-subscription-checkout-slot__placeholder', children: (renderPreparingPlaceholder !== null && renderPreparingPlaceholder !== void 0 ? renderPreparingPlaceholder : renderPlaceholder)({
100
109
  className,
101
110
  disabled: slotDisabled,
102
111
  label: placeholderLabel,
@@ -25,7 +25,10 @@ export type StripeSubscriptionCheckoutSession = {
25
25
  prepareWalletCheckout: () => Promise<string | null>;
26
26
  provider: 'stripe';
27
27
  reset: () => void;
28
+ restartCardCheckout: () => Promise<string | null>;
29
+ restartWalletCheckout: () => Promise<string | null>;
28
30
  setError: (message: string | null) => void;
29
31
  stripePromise: Promise<Stripe | null> | null;
30
32
  };
33
+ export declare function isInactiveCheckoutSessionError(message?: string | null): boolean;
31
34
  export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
@@ -9,6 +9,10 @@ const getFriendlyStripeCheckoutError = (error) => {
9
9
  : message;
10
10
  };
11
11
  const normalizeRuntimeConfigValue = (value) => (value === null || value === void 0 ? void 0 : value.trim()) || '';
12
+ export function isInactiveCheckoutSessionError(message) {
13
+ var _a;
14
+ return /\bcheckout session\b.*\bno longer active\b/i.test((_a = message === null || message === void 0 ? void 0 : message.trim()) !== null && _a !== void 0 ? _a : '');
15
+ }
12
16
  export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, userId, }) {
13
17
  var _a, _b, _c, _d;
14
18
  const [clientSecret, setClientSecret] = useState(null);
@@ -17,6 +21,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
17
21
  const [loading, setLoading] = useState(createEmptyCheckoutPreparationLoading);
18
22
  const [runtimeStripePublishableKey, setRuntimeStripePublishableKey] = useState(null);
19
23
  const creatingIntentRef = useRef(null);
24
+ const requestIdRef = useRef(0);
20
25
  const runtimeConfigSignature = [
21
26
  normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.apiBaseUrl),
22
27
  normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelId),
@@ -39,18 +44,28 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
39
44
  ].join('|');
40
45
  const activeClientSecret = clientSecretIntentKey === intentKey ? clientSecret : null;
41
46
  const stripePromise = useMemo(() => getStripePromise(checkoutMode, runtimeStripePublishableKey), [checkoutMode, runtimeStripePublishableKey]);
47
+ const clearActiveCheckoutSession = useCallback(() => {
48
+ requestIdRef.current += 1;
49
+ creatingIntentRef.current = null;
50
+ setClientSecret(null);
51
+ setClientSecretIntentKey(null);
52
+ }, []);
42
53
  const setError = useCallback((message) => {
54
+ if (isInactiveCheckoutSessionError(message)) {
55
+ clearActiveCheckoutSession();
56
+ setErrorState(null);
57
+ onError === null || onError === void 0 ? void 0 : onError(null);
58
+ return;
59
+ }
43
60
  setErrorState(message);
44
61
  onError === null || onError === void 0 ? void 0 : onError(message);
45
- }, [onError]);
62
+ }, [clearActiveCheckoutSession, onError]);
46
63
  const reset = useCallback(() => {
47
- creatingIntentRef.current = null;
48
- setClientSecret(null);
49
- setClientSecretIntentKey(null);
64
+ clearActiveCheckoutSession();
50
65
  setLoading(createEmptyCheckoutPreparationLoading());
51
66
  setRuntimeStripePublishableKey(null);
52
67
  setError(null);
53
- }, [setError]);
68
+ }, [clearActiveCheckoutSession, setError]);
54
69
  useEffect(() => {
55
70
  reset();
56
71
  }, [intentKey, reset]);
@@ -59,20 +74,25 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
59
74
  ? current
60
75
  : Object.assign(Object.assign({}, current), { [source]: value }));
61
76
  }, []);
62
- const createCheckoutSubscription = useCallback(async () => {
77
+ const createCheckoutSubscription = useCallback(async (forceNew = false) => {
63
78
  var _a;
64
79
  if (!configured || !plan || !displayPlan) {
65
80
  return null;
66
81
  }
67
- if (activeClientSecret) {
82
+ if (!forceNew && activeClientSecret) {
68
83
  return activeClientSecret;
69
84
  }
70
- if (((_a = creatingIntentRef.current) === null || _a === void 0 ? void 0 : _a.key) === intentKey) {
85
+ if (!forceNew && ((_a = creatingIntentRef.current) === null || _a === void 0 ? void 0 : _a.key) === intentKey) {
71
86
  return creatingIntentRef.current.promise;
72
87
  }
88
+ if (forceNew) {
89
+ clearActiveCheckoutSession();
90
+ }
73
91
  const requestKey = intentKey;
92
+ const requestId = requestIdRef.current + 1;
93
+ requestIdRef.current = requestId;
74
94
  const subscriptionRequest = (async () => {
75
- var _a, _b, _c, _d;
95
+ var _a, _b, _c, _d, _e, _f, _g;
76
96
  try {
77
97
  const response = await createStripeSubscriptionCheckout({
78
98
  plan,
@@ -83,10 +103,11 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
83
103
  environment: checkoutMode,
84
104
  runtimeConfig,
85
105
  });
86
- if (((_a = creatingIntentRef.current) === null || _a === void 0 ? void 0 : _a.key) !== requestKey) {
106
+ if (((_a = creatingIntentRef.current) === null || _a === void 0 ? void 0 : _a.key) !== requestKey ||
107
+ ((_b = creatingIntentRef.current) === null || _b === void 0 ? void 0 : _b.id) !== requestId) {
87
108
  return response.clientSecret;
88
109
  }
89
- const publishableKey = (_b = response.stripePublishableKey) === null || _b === void 0 ? void 0 : _b.trim();
110
+ const publishableKey = (_c = response.stripePublishableKey) === null || _c === void 0 ? void 0 : _c.trim();
90
111
  if (publishableKey) {
91
112
  setRuntimeStripePublishableKey(publishableKey);
92
113
  }
@@ -95,18 +116,21 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
95
116
  return response.clientSecret;
96
117
  }
97
118
  catch (subscriptionError) {
98
- if (((_c = creatingIntentRef.current) === null || _c === void 0 ? void 0 : _c.key) === requestKey) {
119
+ if (((_d = creatingIntentRef.current) === null || _d === void 0 ? void 0 : _d.key) === requestKey &&
120
+ ((_e = creatingIntentRef.current) === null || _e === void 0 ? void 0 : _e.id) === requestId) {
99
121
  setError(getFriendlyStripeCheckoutError(subscriptionError));
100
122
  }
101
123
  return null;
102
124
  }
103
125
  finally {
104
- if (((_d = creatingIntentRef.current) === null || _d === void 0 ? void 0 : _d.key) === requestKey) {
126
+ if (((_f = creatingIntentRef.current) === null || _f === void 0 ? void 0 : _f.key) === requestKey &&
127
+ ((_g = creatingIntentRef.current) === null || _g === void 0 ? void 0 : _g.id) === requestId) {
105
128
  creatingIntentRef.current = null;
106
129
  }
107
130
  }
108
131
  })();
109
132
  creatingIntentRef.current = {
133
+ id: requestId,
110
134
  key: requestKey,
111
135
  promise: subscriptionRequest,
112
136
  };
@@ -114,6 +138,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
114
138
  }, [
115
139
  activeClientSecret,
116
140
  checkoutMode,
141
+ clearActiveCheckoutSession,
117
142
  configured,
118
143
  couponId,
119
144
  customerEmail,
@@ -125,17 +150,17 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
125
150
  setError,
126
151
  userId,
127
152
  ]);
128
- const prepareCheckout = useCallback(async (source) => {
153
+ const prepareCheckout = useCallback(async (source, options = {}) => {
129
154
  if (!configured || !plan || !displayPlan) {
130
155
  return null;
131
156
  }
132
- if (activeClientSecret) {
157
+ if (!options.forceNew && activeClientSecret) {
133
158
  return activeClientSecret;
134
159
  }
135
160
  setSourceLoading(source, true);
136
161
  setError(null);
137
162
  try {
138
- return await createCheckoutSubscription();
163
+ return await createCheckoutSubscription(options.forceNew === true);
139
164
  }
140
165
  finally {
141
166
  setSourceLoading(source, false);
@@ -151,6 +176,8 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
151
176
  ]);
152
177
  const prepareCardCheckout = useCallback(() => prepareCheckout('card'), [prepareCheckout]);
153
178
  const prepareWalletCheckout = useCallback(() => prepareCheckout('wallet'), [prepareCheckout]);
179
+ const restartCardCheckout = useCallback(() => prepareCheckout('card', { forceNew: true }), [prepareCheckout]);
180
+ const restartWalletCheckout = useCallback(() => prepareCheckout('wallet', { forceNew: true }), [prepareCheckout]);
154
181
  return {
155
182
  activeClientSecret,
156
183
  configured,
@@ -161,6 +188,8 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, couponId, c
161
188
  prepareWalletCheckout,
162
189
  provider: 'stripe',
163
190
  reset,
191
+ restartCardCheckout,
192
+ restartWalletCheckout,
164
193
  setError,
165
194
  stripePromise,
166
195
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",