diff --git a/README.md b/README.md index 2b7e3edb..bc2c7f2c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ version, we recommend using legacy ## Getting started -- [Build a custom payment form using the Checkout Sessions API](https://docs.stripe.com/payments/accept-a-payment?payment-ui=elements&api-integration=checkout) +- [Build a custom checkout page using the Checkout Sessions API](https://docs.stripe.com/payments/accept-a-payment?payment-ui=elements&api-integration=checkout) - [Add React Stripe.js to your React app](https://stripe.com/docs/stripe-js/react#setup) - [Try it out using CodeSandbox](https://codesandbox.io/s/react-stripe-official-q1loc?fontsize=14&hidenavigation=1&theme=dark) @@ -25,7 +25,16 @@ version, we recommend using legacy - [Legacy `react-stripe-elements` docs](https://github.com/stripe/react-stripe-elements/#react-stripe-elements) - [Examples](examples) -### Minimal example +## Build a custom checkout page + +For a new custom checkout page, we recommend the +[Checkout Sessions API](https://docs.stripe.com/payments/accept-a-payment?payment-ui=elements&api-integration=checkout) +with `ui_mode: 'elements'`. This lets you combine Stripe Elements with your own +React layout while Checkout Sessions manages the checkout state. If you want to +own every part of your checkout, the lower-level +[Payment Intents API](https://docs.stripe.com/payments/accept-a-payment?payment-ui=elements&api-integration=payment-intents) +provides more fine-grained control, but requires significantly more code and +ongoing maintenance. First, install React Stripe.js and [Stripe.js](https://github.com/stripe/stripe-js). @@ -34,16 +43,8 @@ First, install React Stripe.js and npm install @stripe/react-stripe-js @stripe/stripe-js ``` -#### Using hooks - -> **Building a custom payment form?** Use the -> [Checkout Sessions API](https://docs.stripe.com/payments/accept-a-payment?payment-ui=elements&api-integration=checkout) -> integration shown below — the recommended approach for most integrations. -> Create a Checkout Session on your server with `ui_mode: 'elements'` and pass -> its `clientSecret` to `CheckoutElementsProvider`. - -Your server endpoint should create a Checkout Session and return its client -secret: +Create a Checkout Session on your server using trusted product and pricing data, +then return its client secret: ```js // POST /create-checkout-session @@ -63,6 +64,10 @@ const session = await stripe.checkout.sessions.create({ ], }); +if (!session.client_secret) { + throw new Error('Checkout Session is missing a client secret.'); +} + res.json({clientSecret: session.client_secret}); ``` @@ -81,151 +86,94 @@ import { const CheckoutForm = () => { const result = useCheckoutElements(); const [errorMessage, setErrorMessage] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); const handleSubmit = async (event) => { event.preventDefault(); - if (result.type !== 'success') { + if (result.type !== 'success' || !result.checkout.canConfirm) { return; } + setIsSubmitting(true); + setErrorMessage(null); + try { - await result.checkout.confirm({ + const confirmResult = await result.checkout.confirm({ returnUrl: 'https://example.com/order/123/complete', }); + + if (confirmResult.type === 'error') { + setErrorMessage(confirmResult.error.message); + } } catch (error) { - setErrorMessage(error.message); + setErrorMessage( + error instanceof Error ? error.message : 'An unexpected error occurred.' + ); + } finally { + setIsSubmitting(false); } }; + if (result.type === 'loading') { + return
Total: {checkout.total.total.amount}
+ + > ); }; // Use the publishable key for the same account that created the Checkout Session. const stripePromise = loadStripe('pk_test_...'); -const App = () => { - // Fetch clientSecret from your server when the page loads. - // e.g. POST /create-checkout-session → { clientSecret } - const clientSecret = '...'; +const clientSecretPromise = fetch('/create-checkout-session', { + method: 'POST', +}).then(async (response) => { + const body = await response.json(); - return ( -