diff --git a/examples/hooks/11-Custom-Checkout.js b/examples/hooks/11-Custom-Checkout.js index 9f99533..2b3d027 100644 --- a/examples/hooks/11-Custom-Checkout.js +++ b/examples/hooks/11-Custom-Checkout.js @@ -3,27 +3,20 @@ import {loadStripe} from '@stripe/stripe-js'; import { PaymentElement, useStripe, - CustomCheckoutProvider, - useCustomCheckout, + CheckoutProvider, + useCheckout, AddressElement, } from '../../src'; import '../styles/common.css'; -const CustomerDetails = () => { - const { - phoneNumber, - updatePhoneNumber, - email, - updateEmail, - } = useCustomCheckout(); - +const CustomerDetails = ({phoneNumber, setPhoneNumber, email, setEmail}) => { const handlePhoneNumberChange = (event) => { - updatePhoneNumber(event.target.value); + setPhoneNumber(event.target.value); }; const handleEmailChange = (event) => { - updateEmail(event.target.value); + setEmail(event.target.value); }; return ( @@ -52,35 +45,36 @@ const CustomerDetails = () => { }; const CheckoutForm = () => { - const customCheckout = useCustomCheckout(); + const checkout = useCheckout(); const [status, setStatus] = React.useState(); const [loading, setLoading] = React.useState(false); const stripe = useStripe(); + const [phoneNumber, setPhoneNumber] = React.useState(''); + const [email, setEmail] = React.useState(''); React.useEffect(() => { - const {confirmationRequirements} = customCheckout || {}; + const {confirmationRequirements} = checkout || {}; setStatus( confirmationRequirements && confirmationRequirements.length > 0 ? `Missing: ${confirmationRequirements.join(', ')}` : '' ); - }, [customCheckout, setStatus]); + }, [checkout, setStatus]); const handleSubmit = async (event) => { event.preventDefault(); - if (!stripe || !customCheckout) { - return; - } - - const {canConfirm, confirm} = customCheckout; - if (!canConfirm) { + if (!stripe || !checkout) { return; } try { setLoading(true); - await confirm({return_url: window.location.href}); + await checkout.confirm({ + email, + phoneNumber, + returnUrl: window.location.href, + }); setLoading(false); } catch (err) { console.error(err); @@ -88,12 +82,16 @@ const CheckoutForm = () => { } }; - const buttonDisabled = - !stripe || !customCheckout || !customCheckout.canConfirm || loading; + const buttonDisabled = !stripe || !checkout || loading; return (
- +

Payment Details

Billing Details

@@ -125,7 +123,7 @@ const App = () => { e.preventDefault(); setStripePromise( loadStripe(pk, { - betas: ['custom_checkout_beta_1'], + betas: ['custom_checkout_beta_5'], }) ); }; @@ -171,12 +169,12 @@ const App = () => { {stripePromise && clientSecret && ( - - + )} ); diff --git a/package.json b/package.json index 01c9cbb..7b3de29 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-terser": "^0.4.4", "@storybook/react": "^6.5.0-beta.8", - "@stripe/stripe-js": "^4.9.0", + "@stripe/stripe-js": "^5.0.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.1.1", "@testing-library/react-hooks": "^8.0.0", @@ -108,7 +108,7 @@ "@types/react": "18.0.5" }, "peerDependencies": { - "@stripe/stripe-js": "^1.44.1 || ^2.0.0 || ^3.0.0 || ^4.0.0", + "@stripe/stripe-js": "^1.44.1 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } diff --git a/src/components/CustomCheckout.test.tsx b/src/components/CheckoutProvider.test.tsx similarity index 62% rename from src/components/CustomCheckout.test.tsx rename to src/components/CheckoutProvider.test.tsx index 7be6dbb..57b5fb6 100644 --- a/src/components/CustomCheckout.test.tsx +++ b/src/components/CheckoutProvider.test.tsx @@ -2,31 +2,31 @@ import React, {StrictMode} from 'react'; import {render, act, waitFor} from '@testing-library/react'; import {renderHook} from '@testing-library/react-hooks'; -import {CustomCheckoutProvider, useCustomCheckout} from './CustomCheckout'; +import {CheckoutProvider, useCheckout} from './CheckoutProvider'; import {Elements} from './Elements'; import {useStripe} from './useStripe'; import * as mocks from '../../test/mocks'; -describe('CustomCheckoutProvider', () => { +describe('CheckoutProvider', () => { let mockStripe: any; let mockStripePromise: any; - let mockCustomCheckoutSdk: any; + let mockCheckoutSdk: any; let mockSession: any; let consoleError: any; let consoleWarn: any; - let mockCustomCheckout: any; + let mockCheckout: any; beforeEach(() => { mockStripe = mocks.mockStripe(); mockStripePromise = Promise.resolve(mockStripe); - mockCustomCheckoutSdk = mocks.mockCustomCheckoutSdk(); - mockStripe.initCustomCheckout.mockResolvedValue(mockCustomCheckoutSdk); - mockSession = mocks.mockCustomCheckoutSession(); - mockCustomCheckoutSdk.session.mockReturnValue(mockSession); + mockCheckoutSdk = mocks.mockCheckoutSdk(); + mockStripe.initCheckout.mockResolvedValue(mockCheckoutSdk); + mockSession = mocks.mockCheckoutSession(); + mockCheckoutSdk.session.mockReturnValue(mockSession); - const {on: _on, session: _session, ...actions} = mockCustomCheckoutSdk; + const {on: _on, session: _session, ...actions} = mockCheckoutSdk; - mockCustomCheckout = {...actions, ...mockSession}; + mockCheckout = {...actions, ...mockSession}; jest.spyOn(console, 'error'); jest.spyOn(console, 'warn'); @@ -38,17 +38,14 @@ describe('CustomCheckoutProvider', () => { jest.restoreAllMocks(); }); - test('injects CustomCheckoutProvider with the useCustomCheckout hook', async () => { + test('injects CheckoutProvider with the useCheckout hook', async () => { const wrapper = ({children}: any) => ( - + {children} - + ); - const {result, waitForNextUpdate} = renderHook(() => useCustomCheckout(), { + const {result, waitForNextUpdate} = renderHook(() => useCheckout(), { wrapper, }); @@ -56,17 +53,14 @@ describe('CustomCheckoutProvider', () => { await waitForNextUpdate(); // wait for all (potentially multiple) updates to finish - await waitFor(() => expect(result.current).toEqual(mockCustomCheckout)); + await waitFor(() => expect(result.current).toEqual(mockCheckout)); }); - test('injects CustomCheckoutProvider with the useStripe hook', async () => { + test('injects CheckoutProvider with the useStripe hook', async () => { const wrapper = ({children}: any) => ( - + {children} - + ); const {result, waitForNextUpdate} = renderHook(() => useStripe(), { @@ -83,33 +77,30 @@ describe('CustomCheckoutProvider', () => { test('allows a transition from null to a valid Stripe object', async () => { let stripeProp: any = null; const wrapper = ({children}: any) => ( - + {children} - + ); - const {result, rerender} = renderHook(() => useCustomCheckout(), {wrapper}); + const {result, rerender} = renderHook(() => useCheckout(), {wrapper}); expect(result.current).toBe(undefined); stripeProp = mockStripe; act(() => rerender()); - await waitFor(() => expect(result.current).toEqual(mockCustomCheckout)); + await waitFor(() => expect(result.current).toEqual(mockCheckout)); }); test('works with a Promise resolving to a valid Stripe object', async () => { const wrapper = ({children}: any) => ( - {children} - + ); - const {result, waitForNextUpdate} = renderHook(() => useCustomCheckout(), { + const {result, waitForNextUpdate} = renderHook(() => useCheckout(), { wrapper, }); @@ -117,22 +108,19 @@ describe('CustomCheckoutProvider', () => { await waitForNextUpdate(); - await waitFor(() => expect(result.current).toEqual(mockCustomCheckout)); + await waitFor(() => expect(result.current).toEqual(mockCheckout)); }); test('allows a transition from null to a valid Promise', async () => { let stripeProp: any = null; const wrapper = ({children}: any) => ( - + {children} - + ); const {result, rerender, waitForNextUpdate} = renderHook( - () => useCustomCheckout(), + () => useCheckout(), {wrapper} ); expect(result.current).toBe(undefined); @@ -144,22 +132,22 @@ describe('CustomCheckoutProvider', () => { await waitForNextUpdate(); - await waitFor(() => expect(result.current).toEqual(mockCustomCheckout)); + await waitFor(() => expect(result.current).toEqual(mockCheckout)); }); - test('does not set context if Promise resolves after Elements is unmounted', async () => { + test('does not set context if Promise resolves after CheckoutProvider is unmounted', async () => { // Silence console output so test output is less noisy consoleError.mockImplementation(() => {}); let result: any; act(() => { result = render( - {null} - + ); }); @@ -172,19 +160,19 @@ describe('CustomCheckoutProvider', () => { test('works with a Promise resolving to null for SSR safety', async () => { const nullPromise = Promise.resolve(null); const TestComponent = () => { - const customCheckout = useCustomCheckout(); + const customCheckout = useCheckout(); return customCheckout ?
not empty
: null; }; let result: any; act(() => { result = render( - - + ); }); @@ -206,14 +194,14 @@ describe('CustomCheckoutProvider', () => { expect(() => render( -
- + ) - ).toThrow('Invalid prop `stripe` supplied to `CustomCheckoutProvider`.'); + ).toThrow('Invalid prop `stripe` supplied to `CheckoutProvider`.'); }); }); @@ -223,7 +211,7 @@ describe('CustomCheckoutProvider', () => { let result: any; act(() => { result = render( - @@ -233,7 +221,7 @@ describe('CustomCheckoutProvider', () => { const mockStripe2: any = mocks.mockStripe(); act(() => { result.rerender( - @@ -241,19 +229,19 @@ describe('CustomCheckoutProvider', () => { }); await waitFor(() => { - expect(mockStripe.initCustomCheckout).toHaveBeenCalledTimes(1); - expect(mockStripe2.initCustomCheckout).toHaveBeenCalledTimes(0); + expect(mockStripe.initCheckout).toHaveBeenCalledTimes(1); + expect(mockStripe2.initCheckout).toHaveBeenCalledTimes(0); expect(consoleWarn).toHaveBeenCalledWith( - 'Unsupported prop change on CustomCheckoutProvider: You cannot change the `stripe` prop after setting it.' + 'Unsupported prop change on CheckoutProvider: You cannot change the `stripe` prop after setting it.' ); }); }); - test('initCustomCheckout only called once and allows changes to elementsOptions appearance after setting the Stripe object', async () => { + test('initCheckout only called once and allows changes to elementsOptions appearance after setting the Stripe object', async () => { let result: any; act(() => { result = render( - { }); await waitFor(() => - expect(mockStripe.initCustomCheckout).toHaveBeenCalledWith({ + expect(mockStripe.initCheckout).toHaveBeenCalledWith({ clientSecret: 'cs_123', elementsOptions: { appearance: {theme: 'stripe'}, @@ -276,7 +264,7 @@ describe('CustomCheckoutProvider', () => { act(() => { result.rerender( - { }); await waitFor(() => { - expect(mockStripe.initCustomCheckout).toHaveBeenCalledTimes(1); - expect(mockCustomCheckoutSdk.changeAppearance).toHaveBeenCalledTimes(1); - expect(mockCustomCheckoutSdk.changeAppearance).toHaveBeenCalledWith({ + expect(mockStripe.initCheckout).toHaveBeenCalledTimes(1); + expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledTimes(1); + expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledWith({ theme: 'night', }); }); @@ -299,7 +287,7 @@ describe('CustomCheckoutProvider', () => { let result: any; act(() => { result = render( - { }); await waitFor(() => - expect(mockStripe.initCustomCheckout).toHaveBeenCalledTimes(0) + expect(mockStripe.initCheckout).toHaveBeenCalledTimes(0) ); act(() => { result.rerender( - { await waitFor(() => { expect(console.warn).not.toHaveBeenCalled(); - expect(mockStripe.initCustomCheckout).toHaveBeenCalledTimes(1); - expect(mockStripe.initCustomCheckout).toHaveBeenCalledWith({ + expect(mockStripe.initCheckout).toHaveBeenCalledTimes(1); + expect(mockStripe.initCheckout).toHaveBeenCalledWith({ clientSecret: 'cs_123', elementsOptions: { appearance: {theme: 'stripe'}, @@ -339,15 +327,15 @@ describe('CustomCheckoutProvider', () => { }); }); - test('throws when trying to call useCustomCheckout outside of CustomCheckoutProvider context', () => { - const {result} = renderHook(() => useCustomCheckout()); + test('throws when trying to call useCheckout outside of CheckoutProvider context', () => { + const {result} = renderHook(() => useCheckout()); expect(result.error && result.error.message).toBe( - 'Could not find CustomCheckoutProvider context; You need to wrap the part of your app that calls useCustomCheckout() in an provider.' + 'Could not find CheckoutProvider context; You need to wrap the part of your app that calls useCheckout() in an provider.' ); }); - test('throws when trying to call useStripe outside of CustomCheckoutProvider context', () => { + test('throws when trying to call useStripe outside of CheckoutProvider context', () => { const {result} = renderHook(() => useStripe()); expect(result.error && result.error.message).toBe( @@ -355,15 +343,15 @@ describe('CustomCheckoutProvider', () => { ); }); - test('throws when trying to call useStripe in Elements -> CustomCheckoutProvider nested context', async () => { + test('throws when trying to call useStripe in Elements -> CheckoutProvider nested context', async () => { const wrapper = ({children}: any) => ( - {children} - + ); @@ -374,18 +362,15 @@ describe('CustomCheckoutProvider', () => { await waitForNextUpdate(); expect(result.error && result.error.message).toBe( - 'You cannot wrap the part of your app that calls useStripe() in both and providers.' + 'You cannot wrap the part of your app that calls useStripe() in both and providers.' ); }); - test('throws when trying to call useStripe in CustomCheckoutProvider -> Elements nested context', async () => { + test('throws when trying to call useStripe in CheckoutProvider -> Elements nested context', async () => { const wrapper = ({children}: any) => ( - + {children} - + ); const {result, waitForNextUpdate} = renderHook(() => useStripe(), { @@ -395,65 +380,65 @@ describe('CustomCheckoutProvider', () => { await waitForNextUpdate(); expect(result.error && result.error.message).toBe( - 'You cannot wrap the part of your app that calls useStripe() in both and providers.' + 'You cannot wrap the part of your app that calls useStripe() in both and providers.' ); }); describe('React.StrictMode', () => { - test('initCustomCheckout once in StrictMode', async () => { + test('initCheckout once in StrictMode', async () => { const TestComponent = () => { - const _ = useCustomCheckout(); + const _ = useCheckout(); return
; }; act(() => { render( - - + ); }); await waitFor(() => - expect(mockStripe.initCustomCheckout).toHaveBeenCalledTimes(1) + expect(mockStripe.initCheckout).toHaveBeenCalledTimes(1) ); }); - test('initCustomCheckout once with stripePromise in StrictMode', async () => { + test('initCheckout once with stripePromise in StrictMode', async () => { const TestComponent = () => { - const _ = useCustomCheckout(); + const _ = useCheckout(); return
; }; act(() => { render( - - + ); }); await waitFor(() => - expect(mockStripe.initCustomCheckout).toHaveBeenCalledTimes(1) + expect(mockStripe.initCheckout).toHaveBeenCalledTimes(1) ); }); - test('allows changes to options via (mockCustomCheckoutSdk.changeAppearance after setting the Stripe object in StrictMode', async () => { + test('allows changes to options via (mockCheckoutSdk.changeAppearance after setting the Stripe object in StrictMode', async () => { let result: any; act(() => { result = render( - { }); await waitFor(() => { - expect(mockStripe.initCustomCheckout).toHaveBeenCalledTimes(1); - expect(mockStripe.initCustomCheckout).toHaveBeenCalledWith({ + expect(mockStripe.initCheckout).toHaveBeenCalledTimes(1); + expect(mockStripe.initCheckout).toHaveBeenCalledWith({ clientSecret: 'cs_123', elementsOptions: { appearance: {theme: 'stripe'}, @@ -479,7 +464,7 @@ describe('CustomCheckoutProvider', () => { act(() => { result.rerender( - { }); await waitFor(() => { - expect(mockCustomCheckoutSdk.changeAppearance).toHaveBeenCalledTimes(1); - expect(mockCustomCheckoutSdk.changeAppearance).toHaveBeenCalledWith({ + expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledTimes(1); + expect(mockCheckoutSdk.changeAppearance).toHaveBeenCalledWith({ theme: 'night', }); }); diff --git a/src/components/CheckoutProvider.tsx b/src/components/CheckoutProvider.tsx new file mode 100644 index 0000000..0041a63 --- /dev/null +++ b/src/components/CheckoutProvider.tsx @@ -0,0 +1,265 @@ +import {FunctionComponent, PropsWithChildren, ReactNode} from 'react'; +import * as stripeJs from '@stripe/stripe-js'; + +import React from 'react'; +import PropTypes from 'prop-types'; + +import {parseStripeProp} from '../utils/parseStripeProp'; +import {usePrevious} from '../utils/usePrevious'; +import {isUnknownObject} from '../utils/guards'; +import {isEqual} from '../utils/isEqual'; +import { + ElementsContext, + ElementsContextValue, + parseElementsContext, +} from './Elements'; +import {registerWithStripeJs} from '../utils/registerWithStripeJs'; + +interface CheckoutSdkContextValue { + checkoutSdk: stripeJs.StripeCheckout | null; + stripe: stripeJs.Stripe | null; +} + +const CheckoutSdkContext = React.createContext( + null +); +CheckoutSdkContext.displayName = 'CheckoutSdkContext'; + +export const parseCheckoutSdkContext = ( + ctx: CheckoutSdkContextValue | null, + useCase: string +): CheckoutSdkContextValue => { + if (!ctx) { + throw new Error( + `Could not find CheckoutProvider context; You need to wrap the part of your app that ${useCase} in an provider.` + ); + } + + return ctx; +}; + +type StripeCheckoutActions = Omit< + Omit, + 'on' +>; + +interface CheckoutContextValue + extends StripeCheckoutActions, + stripeJs.StripeCheckoutSession {} +const CheckoutContext = React.createContext(null); +CheckoutContext.displayName = 'CheckoutContext'; + +export const extractCheckoutContextValue = ( + checkoutSdk: stripeJs.StripeCheckout | null, + sessionState: stripeJs.StripeCheckoutSession | null +): CheckoutContextValue | null => { + if (!checkoutSdk) { + return null; + } + + const {on: _on, session: _session, ...actions} = checkoutSdk; + if (!sessionState) { + return {...actions, ...checkoutSdk.session()}; + } + + return {...actions, ...sessionState}; +}; + +interface CheckoutProviderProps { + /** + * A [Stripe object](https://stripe.com/docs/js/initializing) or a `Promise` resolving to a `Stripe` object. + * The easiest way to initialize a `Stripe` object is with the the [Stripe.js wrapper module](https://github.com/stripe/stripe-js/blob/master/README.md#readme). + * Once this prop has been set, it can not be changed. + * + * You can also pass in `null` or a `Promise` resolving to `null` if you are performing an initial server-side render or when generating a static site. + */ + stripe: PromiseLike | stripeJs.Stripe | null; + options: stripeJs.StripeCheckoutOptions; +} + +interface PrivateCheckoutProviderProps { + stripe: unknown; + options: stripeJs.StripeCheckoutOptions; + children?: ReactNode; +} +const INVALID_STRIPE_ERROR = + 'Invalid prop `stripe` supplied to `CheckoutProvider`. We recommend using the `loadStripe` utility from `@stripe/stripe-js`. See https://stripe.com/docs/stripe-js/react#elements-props-stripe for details.'; + +export const CheckoutProvider: FunctionComponent> = (({ + stripe: rawStripeProp, + options, + children, +}: PrivateCheckoutProviderProps) => { + const parsed = React.useMemo( + () => parseStripeProp(rawStripeProp, INVALID_STRIPE_ERROR), + [rawStripeProp] + ); + + // State used to trigger a re-render when sdk.session is updated + const [ + session, + setSession, + ] = React.useState(null); + + const [ctx, setContext] = React.useState(() => ({ + stripe: parsed.tag === 'sync' ? parsed.stripe : null, + checkoutSdk: null, + })); + + const safeSetContext = ( + stripe: stripeJs.Stripe, + checkoutSdk: stripeJs.StripeCheckout + ) => { + setContext((ctx) => { + if (ctx.stripe && ctx.checkoutSdk) { + return ctx; + } + + return {stripe, checkoutSdk}; + }); + }; + + // Ref used to avoid calling initCheckout multiple times when options changes + const initCheckoutCalledRef = React.useRef(false); + + React.useEffect(() => { + let isMounted = true; + + if (parsed.tag === 'async' && !ctx.stripe) { + parsed.stripePromise.then((stripe) => { + if (stripe && isMounted && !initCheckoutCalledRef.current) { + // Only update context if the component is still mounted + // and stripe is not null. We allow stripe to be null to make + // handling SSR easier. + initCheckoutCalledRef.current = true; + stripe.initCheckout(options).then((checkoutSdk) => { + if (checkoutSdk) { + safeSetContext(stripe, checkoutSdk); + checkoutSdk.on('change', setSession); + } + }); + } + }); + } else if ( + parsed.tag === 'sync' && + parsed.stripe && + !initCheckoutCalledRef.current + ) { + initCheckoutCalledRef.current = true; + parsed.stripe.initCheckout(options).then((checkoutSdk) => { + if (checkoutSdk) { + safeSetContext(parsed.stripe, checkoutSdk); + checkoutSdk.on('change', setSession); + } + }); + } + + return () => { + isMounted = false; + }; + }, [parsed, ctx, options, setSession]); + + // Warn on changes to stripe prop + const prevStripe = usePrevious(rawStripeProp); + React.useEffect(() => { + if (prevStripe !== null && prevStripe !== rawStripeProp) { + console.warn( + 'Unsupported prop change on CheckoutProvider: You cannot change the `stripe` prop after setting it.' + ); + } + }, [prevStripe, rawStripeProp]); + + // Apply updates to elements when options prop has relevant changes + const prevOptions = usePrevious(options); + React.useEffect(() => { + if (!ctx.checkoutSdk) { + return; + } + + if ( + options.clientSecret && + !isUnknownObject(prevOptions) && + !isEqual(options.clientSecret, prevOptions.clientSecret) + ) { + console.warn( + 'Unsupported prop change: options.clientSecret is not a mutable property.' + ); + } + + const previousAppearance = prevOptions?.elementsOptions?.appearance; + const currentAppearance = options?.elementsOptions?.appearance; + if (currentAppearance && !isEqual(currentAppearance, previousAppearance)) { + ctx.checkoutSdk.changeAppearance(currentAppearance); + } + }, [options, prevOptions, ctx.checkoutSdk]); + + // Attach react-stripe-js version to stripe.js instance + React.useEffect(() => { + registerWithStripeJs(ctx.stripe); + }, [ctx.stripe]); + + const checkoutContextValue = React.useMemo( + () => extractCheckoutContextValue(ctx.checkoutSdk, session), + [ctx.checkoutSdk, session] + ); + + if (!ctx.checkoutSdk) { + return null; + } + + return ( + + + {children} + + + ); +}) as FunctionComponent>; + +CheckoutProvider.propTypes = { + stripe: PropTypes.any, + options: PropTypes.shape({ + clientSecret: PropTypes.string.isRequired, + elementsOptions: PropTypes.object as any, + }).isRequired, +}; + +export const useCheckoutSdkContextWithUseCase = ( + useCaseString: string +): CheckoutSdkContextValue => { + const ctx = React.useContext(CheckoutSdkContext); + return parseCheckoutSdkContext(ctx, useCaseString); +}; + +export const useElementsOrCheckoutSdkContextWithUseCase = ( + useCaseString: string +): CheckoutSdkContextValue | ElementsContextValue => { + const checkoutSdkContext = React.useContext(CheckoutSdkContext); + const elementsContext = React.useContext(ElementsContext); + + if (checkoutSdkContext && elementsContext) { + throw new Error( + `You cannot wrap the part of your app that ${useCaseString} in both and providers.` + ); + } + + if (checkoutSdkContext) { + return parseCheckoutSdkContext(checkoutSdkContext, useCaseString); + } + + return parseElementsContext(elementsContext, useCaseString); +}; + +export const useCheckout = (): CheckoutContextValue => { + // ensure it's in CheckoutProvider + useCheckoutSdkContextWithUseCase('calls useCheckout()'); + const ctx = React.useContext(CheckoutContext); + if (!ctx) { + throw new Error( + 'Could not find Checkout Context; You need to wrap the part of your app that calls useCheckout() in an provider.' + ); + } + return ctx; +}; diff --git a/src/components/CustomCheckout.tsx b/src/components/CustomCheckout.tsx deleted file mode 100644 index f593236..0000000 --- a/src/components/CustomCheckout.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import {FunctionComponent, PropsWithChildren, ReactNode} from 'react'; -import * as stripeJs from '@stripe/stripe-js'; - -import React from 'react'; -import PropTypes from 'prop-types'; - -import {parseStripeProp} from '../utils/parseStripeProp'; -import {usePrevious} from '../utils/usePrevious'; -import {isUnknownObject} from '../utils/guards'; -import {isEqual} from '../utils/isEqual'; -import { - ElementsContext, - ElementsContextValue, - parseElementsContext, -} from './Elements'; -import {registerWithStripeJs} from '../utils/registerWithStripeJs'; - -interface CustomCheckoutSdkContextValue { - customCheckoutSdk: stripeJs.StripeCustomCheckout | null; - stripe: stripeJs.Stripe | null; -} - -const CustomCheckoutSdkContext = React.createContext( - null -); -CustomCheckoutSdkContext.displayName = 'CustomCheckoutSdkContext'; - -export const parseCustomCheckoutSdkContext = ( - ctx: CustomCheckoutSdkContextValue | null, - useCase: string -): CustomCheckoutSdkContextValue => { - if (!ctx) { - throw new Error( - `Could not find CustomCheckoutProvider context; You need to wrap the part of your app that ${useCase} in an provider.` - ); - } - - return ctx; -}; - -type StripeCustomCheckoutActions = Omit< - Omit, - 'on' ->; - -interface CustomCheckoutContextValue - extends StripeCustomCheckoutActions, - stripeJs.StripeCustomCheckoutSession {} -const CustomCheckoutContext = React.createContext( - null -); -CustomCheckoutContext.displayName = 'CustomCheckoutContext'; - -export const extractCustomCheckoutContextValue = ( - customCheckoutSdk: stripeJs.StripeCustomCheckout | null, - sessionState: stripeJs.StripeCustomCheckoutSession | null -): CustomCheckoutContextValue | null => { - if (!customCheckoutSdk) { - return null; - } - - const {on: _on, session: _session, ...actions} = customCheckoutSdk; - if (!sessionState) { - return {...actions, ...customCheckoutSdk.session()}; - } - - return {...actions, ...sessionState}; -}; - -interface CustomCheckoutProviderProps { - /** - * A [Stripe object](https://stripe.com/docs/js/initializing) or a `Promise` resolving to a `Stripe` object. - * The easiest way to initialize a `Stripe` object is with the the [Stripe.js wrapper module](https://github.com/stripe/stripe-js/blob/master/README.md#readme). - * Once this prop has been set, it can not be changed. - * - * You can also pass in `null` or a `Promise` resolving to `null` if you are performing an initial server-side render or when generating a static site. - */ - stripe: PromiseLike | stripeJs.Stripe | null; - options: stripeJs.StripeCustomCheckoutOptions; -} - -interface PrivateCustomCheckoutProviderProps { - stripe: unknown; - options: stripeJs.StripeCustomCheckoutOptions; - children?: ReactNode; -} -const INVALID_STRIPE_ERROR = - 'Invalid prop `stripe` supplied to `CustomCheckoutProvider`. We recommend using the `loadStripe` utility from `@stripe/stripe-js`. See https://stripe.com/docs/stripe-js/react#elements-props-stripe for details.'; - -export const CustomCheckoutProvider: FunctionComponent> = (({ - stripe: rawStripeProp, - options, - children, -}: PrivateCustomCheckoutProviderProps) => { - const parsed = React.useMemo( - () => parseStripeProp(rawStripeProp, INVALID_STRIPE_ERROR), - [rawStripeProp] - ); - - // State used to trigger a re-render when sdk.session is updated - const [ - session, - setSession, - ] = React.useState(null); - - const [ctx, setContext] = React.useState( - () => ({ - stripe: parsed.tag === 'sync' ? parsed.stripe : null, - customCheckoutSdk: null, - }) - ); - - const safeSetContext = ( - stripe: stripeJs.Stripe, - customCheckoutSdk: stripeJs.StripeCustomCheckout - ) => { - setContext((ctx) => { - if (ctx.stripe && ctx.customCheckoutSdk) { - return ctx; - } - - return {stripe, customCheckoutSdk}; - }); - }; - - // Ref used to avoid calling initCustomCheckout multiple times when options changes - const initCustomCheckoutCalledRef = React.useRef(false); - - React.useEffect(() => { - let isMounted = true; - - if (parsed.tag === 'async' && !ctx.stripe) { - parsed.stripePromise.then((stripe) => { - if (stripe && isMounted && !initCustomCheckoutCalledRef.current) { - // Only update context if the component is still mounted - // and stripe is not null. We allow stripe to be null to make - // handling SSR easier. - initCustomCheckoutCalledRef.current = true; - stripe.initCustomCheckout(options).then((customCheckoutSdk) => { - if (customCheckoutSdk) { - safeSetContext(stripe, customCheckoutSdk); - customCheckoutSdk.on('change', setSession); - } - }); - } - }); - } else if ( - parsed.tag === 'sync' && - parsed.stripe && - !initCustomCheckoutCalledRef.current - ) { - initCustomCheckoutCalledRef.current = true; - parsed.stripe.initCustomCheckout(options).then((customCheckoutSdk) => { - if (customCheckoutSdk) { - safeSetContext(parsed.stripe, customCheckoutSdk); - customCheckoutSdk.on('change', setSession); - } - }); - } - - return () => { - isMounted = false; - }; - }, [parsed, ctx, options, setSession]); - - // Warn on changes to stripe prop - const prevStripe = usePrevious(rawStripeProp); - React.useEffect(() => { - if (prevStripe !== null && prevStripe !== rawStripeProp) { - console.warn( - 'Unsupported prop change on CustomCheckoutProvider: You cannot change the `stripe` prop after setting it.' - ); - } - }, [prevStripe, rawStripeProp]); - - // Apply updates to elements when options prop has relevant changes - const prevOptions = usePrevious(options); - React.useEffect(() => { - if (!ctx.customCheckoutSdk) { - return; - } - - if ( - options.clientSecret && - !isUnknownObject(prevOptions) && - !isEqual(options.clientSecret, prevOptions.clientSecret) - ) { - console.warn( - 'Unsupported prop change: options.client_secret is not a mutable property.' - ); - } - - const previousAppearance = prevOptions?.elementsOptions?.appearance; - const currentAppearance = options?.elementsOptions?.appearance; - if (currentAppearance && !isEqual(currentAppearance, previousAppearance)) { - ctx.customCheckoutSdk.changeAppearance(currentAppearance); - } - }, [options, prevOptions, ctx.customCheckoutSdk]); - - // Attach react-stripe-js version to stripe.js instance - React.useEffect(() => { - registerWithStripeJs(ctx.stripe); - }, [ctx.stripe]); - - const customCheckoutContextValue = React.useMemo( - () => extractCustomCheckoutContextValue(ctx.customCheckoutSdk, session), - [ctx.customCheckoutSdk, session] - ); - - if (!ctx.customCheckoutSdk) { - return null; - } - - return ( - - - {children} - - - ); -}) as FunctionComponent>; - -CustomCheckoutProvider.propTypes = { - stripe: PropTypes.any, - options: PropTypes.shape({ - clientSecret: PropTypes.string.isRequired, - elementsOptions: PropTypes.object as any, - }).isRequired, -}; - -export const useCustomCheckoutSdkContextWithUseCase = ( - useCaseString: string -): CustomCheckoutSdkContextValue => { - const ctx = React.useContext(CustomCheckoutSdkContext); - return parseCustomCheckoutSdkContext(ctx, useCaseString); -}; - -export const useElementsOrCustomCheckoutSdkContextWithUseCase = ( - useCaseString: string -): CustomCheckoutSdkContextValue | ElementsContextValue => { - const customCheckoutSdkContext = React.useContext(CustomCheckoutSdkContext); - const elementsContext = React.useContext(ElementsContext); - - if (customCheckoutSdkContext && elementsContext) { - throw new Error( - `You cannot wrap the part of your app that ${useCaseString} in both and providers.` - ); - } - - if (customCheckoutSdkContext) { - return parseCustomCheckoutSdkContext( - customCheckoutSdkContext, - useCaseString - ); - } - - return parseElementsContext(elementsContext, useCaseString); -}; - -export const useCustomCheckout = (): CustomCheckoutContextValue => { - // ensure it's in CustomCheckoutProvider - useCustomCheckoutSdkContextWithUseCase('calls useCustomCheckout()'); - const ctx = React.useContext(CustomCheckoutContext); - if (!ctx) { - throw new Error( - 'Could not find CustomCheckout Context; You need to wrap the part of your app that calls useCustomCheckout() in an provider.' - ); - } - return ctx; -}; diff --git a/src/components/createElementComponent.test.tsx b/src/components/createElementComponent.test.tsx index 6e831f7..546d84e 100644 --- a/src/components/createElementComponent.test.tsx +++ b/src/components/createElementComponent.test.tsx @@ -2,7 +2,7 @@ import React, {StrictMode} from 'react'; import {render, act, waitFor} from '@testing-library/react'; import * as ElementsModule from './Elements'; -import * as CustomCheckoutModule from './CustomCheckout'; +import * as CheckoutModule from './CheckoutProvider'; import createElementComponent from './createElementComponent'; import * as mocks from '../../test/mocks'; import { @@ -13,13 +13,13 @@ import { } from '../types'; const {Elements} = ElementsModule; -const {CustomCheckoutProvider} = CustomCheckoutModule; +const {CheckoutProvider} = CheckoutModule; describe('createElementComponent', () => { let mockStripe: any; let mockElements: any; let mockElement: any; - let mockCustomCheckoutSdk: any; + let mockCheckoutSdk: any; let simulateElementsEvents: Record; let simulateOn: any; @@ -31,12 +31,12 @@ describe('createElementComponent', () => { beforeEach(() => { mockStripe = mocks.mockStripe(); mockElements = mocks.mockElements(); - mockCustomCheckoutSdk = mocks.mockCustomCheckoutSdk(); + mockCheckoutSdk = mocks.mockCheckoutSdk(); mockElement = mocks.mockElement(); mockStripe.elements.mockReturnValue(mockElements); mockElements.create.mockReturnValue(mockElement); - mockStripe.initCustomCheckout.mockResolvedValue(mockCustomCheckoutSdk); - mockCustomCheckoutSdk.createElement.mockReturnValue(mockElement); + mockStripe.initCheckout.mockResolvedValue(mockCheckoutSdk); + mockCheckoutSdk.createElement.mockReturnValue(mockElement); jest.spyOn(React, 'useLayoutEffect'); simulateElementsEvents = {}; @@ -85,14 +85,14 @@ describe('createElementComponent', () => { }); }); - describe('on the server - only for CustomCheckoutProvider', () => { + describe('on the server - only for CheckoutProvider', () => { const CardElement = createElementComponent('card', true); it('does not render anything', () => { const {container} = render( - + - + ); expect(container.firstChild).toBe(null); @@ -101,11 +101,7 @@ describe('createElementComponent', () => { describe.each([ ['Elements', Elements, {clientSecret: 'pi_123'}], - [ - 'CustomCheckoutProvider', - CustomCheckoutProvider, - {clientSecret: 'cs_123'}, - ], + ['CheckoutProvider', CheckoutProvider, {clientSecret: 'cs_123'}], ])( 'on the server with Provider - %s', (_providerName, Provider, providerOptions) => { @@ -317,10 +313,7 @@ describe('createElementComponent', () => { it('attaches event listeners once the element is created', () => { jest - .spyOn( - CustomCheckoutModule, - 'useElementsOrCustomCheckoutSdkContextWithUseCase' - ) + .spyOn(CheckoutModule, 'useElementsOrCheckoutSdkContextWithUseCase') .mockReturnValueOnce({elements: null, stripe: null}) .mockReturnValue({elements: mockElements, stripe: mockStripe}); @@ -842,7 +835,7 @@ describe('createElementComponent', () => { }); }); - describe('Within a CustomCheckoutProvider', () => { + describe('Within a CheckoutProvider', () => { let peMounted = false; let result: any; beforeEach(() => { @@ -862,12 +855,12 @@ describe('createElementComponent', () => { it('Can remove and add PaymentElement at the same time', async () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -876,12 +869,12 @@ describe('createElementComponent', () => { const rerender = result.rerender; act(() => { rerender( - - + ); }); @@ -892,12 +885,12 @@ describe('createElementComponent', () => { it('passes id to the wrapping DOM element', async () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -910,12 +903,12 @@ describe('createElementComponent', () => { it('passes className to the wrapping DOM element', async () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -929,16 +922,16 @@ describe('createElementComponent', () => { const options: any = {foo: 'foo'}; act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); - expect(mockCustomCheckoutSdk.createElement).toHaveBeenCalledWith( + expect(mockCheckoutSdk.createElement).toHaveBeenCalledWith( 'payment', options ); @@ -950,7 +943,7 @@ describe('createElementComponent', () => { let elementCreated = false; let elementMounted = false; - mockCustomCheckoutSdk.createElement.mockImplementation(() => { + mockCheckoutSdk.createElement.mockImplementation(() => { expect(elementCreated).toBe(false); elementCreated = true; @@ -969,18 +962,18 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(elementMounted).toBeTruthy()); - expect(mockCustomCheckoutSdk.createElement).toHaveBeenCalledTimes(2); + expect(mockCheckoutSdk.createElement).toHaveBeenCalledTimes(2); expect(mockElement.mount).toHaveBeenCalledTimes(2); expect(mockElement.destroy).toHaveBeenCalledTimes(1); }); @@ -988,12 +981,12 @@ describe('createElementComponent', () => { it('mounts the element', async () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1007,15 +1000,12 @@ describe('createElementComponent', () => { expect(simulateOff).not.toBeCalled(); }); - it('does not create and mount until CustomCheckoutSdk has been instantiated', async () => { + it('does not create and mount until CheckoutSdk has been instantiated', async () => { act(() => { result = render( - + - + ); }); @@ -1026,18 +1016,18 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); expect(mockElement.mount).toHaveBeenCalled(); - expect(mockCustomCheckoutSdk.createElement).toHaveBeenCalled(); + expect(mockCheckoutSdk.createElement).toHaveBeenCalled(); }); it('adds an event handlers to an Element', async () => { @@ -1045,12 +1035,12 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1063,35 +1053,32 @@ describe('createElementComponent', () => { it('attaches event listeners once the element is created', async () => { const mockHandler = jest.fn(); - // This won't create the element, since customCheckoutSdk is undefined on this render + // This won't create the element, since checkoutSdk is undefined on this render act(() => { result = render( - + - + ); }); - expect(mockCustomCheckoutSdk.createElement).not.toBeCalled(); + expect(mockCheckoutSdk.createElement).not.toBeCalled(); expect(simulateOn).not.toBeCalled(); - // This creates the element now that customCheckoutSdk is defined + // This creates the element now that checkoutSdk is defined act(() => { result.rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); - expect(mockCustomCheckoutSdk.createElement).toBeCalled(); + expect(mockCheckoutSdk.createElement).toBeCalled(); expect(simulateOn).toBeCalledWith('change', expect.any(Function)); expect(simulateOff).not.toBeCalled(); @@ -1105,12 +1092,12 @@ describe('createElementComponent', () => { const mockHandler = jest.fn(); act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1121,12 +1108,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1138,12 +1125,12 @@ describe('createElementComponent', () => { const mockHandler = jest.fn(); act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1154,12 +1141,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1173,12 +1160,12 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1188,12 +1175,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1208,24 +1195,24 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); const {rerender} = result; act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1242,12 +1229,12 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1255,12 +1242,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1276,12 +1263,12 @@ describe('createElementComponent', () => { const mockHandler2 = jest.fn(); act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1289,12 +1276,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1309,12 +1296,12 @@ describe('createElementComponent', () => { const mockHandler2 = jest.fn(); act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1322,12 +1309,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1342,12 +1329,12 @@ describe('createElementComponent', () => { const mockHandler2 = jest.fn(); act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1355,12 +1342,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1375,12 +1362,12 @@ describe('createElementComponent', () => { const mockHandler2 = jest.fn(); act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1388,12 +1375,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1408,12 +1395,12 @@ describe('createElementComponent', () => { const mockHandler2 = jest.fn(); act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1421,12 +1408,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1439,12 +1426,12 @@ describe('createElementComponent', () => { it('updates the Element when options change', async () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1452,12 +1439,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1470,12 +1457,12 @@ describe('createElementComponent', () => { it('does not trigger unnecessary updates', async () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1483,12 +1470,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1499,13 +1486,13 @@ describe('createElementComponent', () => { it('updates the Element when options change from null to non-null value', async () => { act(() => { result = render( - {/* @ts-expect-error */} - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1513,12 +1500,12 @@ describe('createElementComponent', () => { act(() => { rerender( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1531,12 +1518,9 @@ describe('createElementComponent', () => { it('destroys an existing Element when the component unmounts', async () => { act(() => { result = render( - + - + ); }); const {unmount} = result; @@ -1547,12 +1531,12 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1567,12 +1551,12 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); await waitFor(() => expect(peMounted).toBeTruthy()); @@ -1587,12 +1571,12 @@ describe('createElementComponent', () => { act(() => { result = render( - - + ); }); diff --git a/src/components/createElementComponent.tsx b/src/components/createElementComponent.tsx index bf1e3a4..3996dc9 100644 --- a/src/components/createElementComponent.tsx +++ b/src/components/createElementComponent.tsx @@ -13,7 +13,7 @@ import { extractAllowedOptionsUpdates, UnknownOptions, } from '../utils/extractAllowedOptionsUpdates'; -import {useElementsOrCustomCheckoutSdkContextWithUseCase} from './CustomCheckout'; +import {useElementsOrCheckoutSdkContextWithUseCase} from './CheckoutProvider'; type UnknownCallback = (...args: unknown[]) => any; @@ -62,12 +62,11 @@ const createElementComponent = ( onShippingAddressChange, onShippingRateChange, }) => { - const ctx = useElementsOrCustomCheckoutSdkContextWithUseCase( + const ctx = useElementsOrCheckoutSdkContextWithUseCase( `mounts <${displayName}>` ); const elements = 'elements' in ctx ? ctx.elements : null; - const customCheckoutSdk = - 'customCheckoutSdk' in ctx ? ctx.customCheckoutSdk : null; + const checkoutSdk = 'checkoutSdk' in ctx ? ctx.checkoutSdk : null; const [element, setElement] = React.useState( null ); @@ -109,11 +108,11 @@ const createElementComponent = ( if ( elementRef.current === null && domNode.current !== null && - (elements || customCheckoutSdk) + (elements || checkoutSdk) ) { let newElement: stripeJs.StripeElement | null = null; - if (customCheckoutSdk) { - newElement = customCheckoutSdk.createElement(type as any, options); + if (checkoutSdk) { + newElement = checkoutSdk.createElement(type as any, options); } else if (elements) { newElement = elements.create(type as any, options); } @@ -127,7 +126,7 @@ const createElementComponent = ( newElement.mount(domNode.current); } } - }, [elements, customCheckoutSdk, options]); + }, [elements, checkoutSdk, options]); const prevOptions = usePrevious(options); React.useEffect(() => { @@ -165,7 +164,7 @@ const createElementComponent = ( // Only render the Element wrapper in a server environment. const ServerElement: FunctionComponent = (props) => { - useElementsOrCustomCheckoutSdkContextWithUseCase(`mounts <${displayName}>`); + useElementsOrCheckoutSdkContextWithUseCase(`mounts <${displayName}>`); const {id, className} = props; return
; }; diff --git a/src/components/useStripe.tsx b/src/components/useStripe.tsx index 6cf3566..e246722 100644 --- a/src/components/useStripe.tsx +++ b/src/components/useStripe.tsx @@ -1,11 +1,11 @@ import * as stripeJs from '@stripe/stripe-js'; -import {useElementsOrCustomCheckoutSdkContextWithUseCase} from './CustomCheckout'; +import {useElementsOrCheckoutSdkContextWithUseCase} from './CheckoutProvider'; /** * @docs https://stripe.com/docs/stripe-js/react#usestripe-hook */ export const useStripe = (): stripeJs.Stripe | null => { - const {stripe} = useElementsOrCustomCheckoutSdkContextWithUseCase( + const {stripe} = useElementsOrCheckoutSdkContextWithUseCase( 'calls useStripe()' ); return stripe; diff --git a/src/index.ts b/src/index.ts index 4b93f32..5c855e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,10 +27,7 @@ export * from './types'; export {useElements, Elements, ElementsConsumer} from './components/Elements'; -export { - useCustomCheckout, - CustomCheckoutProvider, -} from './components/CustomCheckout'; +export {useCheckout, CheckoutProvider} from './components/CheckoutProvider'; export {EmbeddedCheckout} from './components/EmbeddedCheckout'; export {EmbeddedCheckoutProvider} from './components/EmbeddedCheckoutProvider'; export {useStripe} from './components/useStripe'; diff --git a/test/mocks.js b/test/mocks.js index 016aea7..95784fc 100644 --- a/test/mocks.js +++ b/test/mocks.js @@ -19,7 +19,7 @@ export const mockElements = () => { }; }; -export const mockCustomCheckoutSession = () => { +export const mockCheckoutSession = () => { return { lineItems: [], currency: 'usd', @@ -37,7 +37,7 @@ export const mockCustomCheckoutSession = () => { }; }; -export const mockCustomCheckoutSdk = () => { +export const mockCheckoutSdk = () => { const elements = {}; return { @@ -49,7 +49,7 @@ export const mockCustomCheckoutSdk = () => { getElement: jest.fn((type) => { return elements[type] || null; }), - session: jest.fn(() => mockCustomCheckoutSession()), + session: jest.fn(() => mockCheckoutSession()), applyPromotionCode: jest.fn(), removePromotionCode: jest.fn(), updateShippingAddress: jest.fn(), @@ -70,7 +70,7 @@ export const mockEmbeddedCheckout = () => ({ }); export const mockStripe = () => { - const customCheckoutSdk = mockCustomCheckoutSdk(); + const checkoutSdk = mockCheckoutSdk(); return { elements: jest.fn(() => mockElements()), createToken: jest.fn(), @@ -81,7 +81,7 @@ export const mockStripe = () => { paymentRequest: jest.fn(), registerAppInfo: jest.fn(), _registerWrapper: jest.fn(), - initCustomCheckout: jest.fn().mockResolvedValue(customCheckoutSdk), + initCheckout: jest.fn().mockResolvedValue(checkoutSdk), initEmbeddedCheckout: jest.fn(() => Promise.resolve(mockEmbeddedCheckout()) ), diff --git a/yarn.lock b/yarn.lock index 1ba4a0e..57dfff6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2350,10 +2350,10 @@ regenerator-runtime "^0.13.7" resolve-from "^5.0.0" -"@stripe/stripe-js@^4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-4.9.0.tgz#95dc000b2f90aeb4db2f2bab82a2fc574f26d1fd" - integrity sha512-tMPZQZZXGWyNX7hbgenq+1xEj2oigJ54XddbtSX36VedoKsPBq7dxwRXu4Xd5FdpT3JDyyDtnmvYkaSnH1yHTQ== +"@stripe/stripe-js@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-5.0.0.tgz#c5fe8e8fbd94080cfd7d55e35e51939ec5c57346" + integrity sha512-u/f/uq7oCgHGgnSxC/I68pHpMf1XQMNBeb8mbhbxFSCji86uz4HvC50vbUS1FBTkx+b4gkTIo/UCnYjwn1cseg== "@testing-library/dom@^8.5.0": version "8.13.0"