Modern browser APIs, like the Payment Request API, allow you to access payment credentials stored in your customer’s digital wallets like Apple Pay, Google Pay, or even Chrome.
Stripe provides a Payment Request Button Element that securely tokenizes these stored credentials for a convenient checkout experience on mobile devices.
In this tutorial, we’ll learn how to add support for the Payment Request API to your site quickly using Stripe and React!
- Try the demo to see the Payment Request API in action
- View the source code on GitHub
- Watch the Learn With Jason episode
Set up a new React project
To scaffold a new React project we use create-react-app
:
npx create-react-app my-app
cd my-app
You can of course use other toolchains like Gatsby or Next.js as well. See the React docs for a list of recommendations.
Install additional dependencies
To make use of Stripe Elements in React, install both @stripe/stripe-js
and @stripe/react-stripe-js
:
npm install --save @stripe/react-stripe-js @stripe/stripe-js
To communicate with the Stripe API from our Netlify function, install the stripe
module as well:
npm install --save stripe
In our demo application we’re additionally using the use-shopping-cart
library, which isn’t a required dependency, but makes managing your shopping cart logic a lot easier by handling the client-side logic for managing what’s in the cart, subtotals, and Stripe Checkout session creation.
Create a new Netlify site using the command line
The “create-react-app” tool has already initialized a git repository for us. Let’s commit the recent changes and push it to a new GitHub repository.
# HEADS UP! don’t forget to change this to use your own username and repo!
git remote add origin git@github.com:username/repo.git
git commit -a -m "Add Stripe dependencies."
git push origin master
Now that our project is on GitHub, we can set up Netlify to automatically build the and publish the site whenever changes are pushed to GitHub.
# initialize this site as a Netlify site
ntl init
Follow the prompts to create a new site in your Netlify account that will auto-deploy when changes are pushed to the GitHub repo.
For your “create-react-app” use the following build command and directory:
Your build command:
npm run build
Directory to deploy:
build
Use Netlify environment variables for local development
You can find detailed instructions on how to set up environment variables in this tutorial.
Add REACT_APP_STRIPE_PUBLISHABLE_KEY
and STRIPE_SECRET_KEY
from your Stripe Dashboard.
After saving, go to the command line and run ntl dev
to see the environment variables injected locally. They’ll be printed in the CLI output near the top.
Add Stripe.js and Elements to your page
To use Element components, wrap the root of your React app in an Elements provider. Call loadStripe
with your publishable key and pass the returned Promise
to the Elements
provider.
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY);
ReactDOM.render(
<Elements stripe={stripePromise}>
<App />
</Elements>,
document.getElementById('root')
);
Create a Payment Request object
The Payment Request object is used to communicate the payment details to the digital wallet. We can only create the payment request once Stripe.js has loaded. Therefore we need to wrap it into a useEffect
hook with stripe
as a dependency.
import React, { useState, useEffect } from 'react';
import {
PaymentRequestButtonElement,
useStripe,
} from '@stripe/react-stripe-js';
const App = () => {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
useEffect(() => {
if (stripe) {
const pr = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Demo total',
amount: 1350,
},
requestPayerName: true,
requestPayerEmail: true,
requestShipping: true,
shippingOptions: [
{
id: 'standard-global',
label: 'Global shipping',
detail: 'Arrives in 5 to 7 days',
amount: 350,
},
],
});
}
}, [stripe]);
// Use a traditional checkout form.
return 'Insert your form or button component here.';
};
The parameters requestPayerName
, requestPayerEmail
, requestShipping
allow you to collect stored billing and shipping details which can speed up the checkout process significantly.
Check if a digital wallet is available and render the button
The payment request object provides a canMakePayment
method that allows you check whether the customer has a digital wallet set up. Use this to decide whether to show the fast checkout option or if none available show a “proceed to checkout” button.
import React, { useState, useEffect } from 'react';
import {
PaymentRequestButtonElement,
useStripe,
} from '@stripe/react-stripe-js';
const App = () => {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
useEffect(() => {
if (stripe) {
const pr = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Demo total',
amount: 1350,
},
requestPayerName: true,
requestPayerEmail: true,
requestShipping: true,
shippingOptions: [
{
id: 'standard-global',
label: 'Global shipping',
detail: 'Arrives in 5 to 7 days',
amount: 350,
},
],
});
+ // Check the availability of the Payment Request API first.
+ pr.canMakePayment().then((result) => {
+ if (result) {
+ pr.on('paymentmethod', handlePaymentMethodReceived);
+ setPaymentRequest(pr);
+ }
+ });
}
}, [stripe]);
+ if (paymentRequest) {
+ return <PaymentRequestButtonElement options={% raw %}{{ paymentRequest }}{% endraw %} />;
+ }
// Use a traditional checkout form.
return 'Insert your form or button component here.';
};
Heads up! The Payment Request API requires pages to be served securely, meaning the button will only be visible when the site is served over HTTPS. SSL is enabled automatically for Netlify sites, so for this tutorial things will Just Work™.
Process the payment details from your customer’s wallet
When the customer clicks the wallet button, a native payment screen is shown where they can select their stored payment credentials as well as contact and shipping information. Their sensitive payment information is securely send to Stripe and exchanged for a paymentmethod token.
Once this process is finished our handlePaymentMethodReceived
will be called that we’ve subscribed via the on-paymentmethod
event handler.
const handlePaymentMethodReceived = async (event) => {
// Send the payment details to our function.
const paymentDetails = {
payment_method: event.paymentMethod.id,
shipping: {
name: event.shippingAddress.recipient,
phone: event.shippingAddress.phone,
address: {
line1: event.shippingAddress.addressLine[0],
city: event.shippingAddress.city,
postal_code: event.shippingAddress.postalCode,
state: event.shippingAddress.region,
country: event.shippingAddress.country,
},
},
};
const response = await fetch('/.netlify/functions/create-payment-intent', {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ paymentDetails }),
}).then((res) => {
return res.json();
});
if (response.error) {
// Report to the browser that the payment failed.
console.log(response.error);
event.complete('fail');
} else {
// Report to the browser that the confirmation was successful, prompting
// it to close the browser payment method collection interface.
event.complete('success');
// Let Stripe.js handle the rest of the payment flow, including 3D Secure if needed.
const { error, paymentIntent } = await stripe.confirmCardPayment(
response.paymentIntent.client_secret
);
if (error) {
console.log(error);
return;
}
if (paymentIntent.status === 'succeeded') {
history.push('/success');
} else {
console.warn(
`Unexpected status: ${paymentIntent.status} for ${paymentIntent}`
);
}
}
};
Charge the payment details in a Netlify function
To charge the customers payment method token, we need to create a payment intent server-side. In your project root, create a new folder called functions
and in it create a new file called create-payment-intent.js
.
In our handlePaymentMethodReceived
method from above we send a POST request to this function with the payment details from the customer’s wallet.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
exports.handler = async (event) => {
try {
const { paymentDetails } = JSON.parse(event.body);
const paymentIntent = await stripe.paymentIntents.create({
amount: 1350,
currency: 'usd',
...paymentDetails,
confirm: true,
});
return {
statusCode: 200,
body: JSON.stringify({ paymentIntent }),
};
} catch (error) {
console.log({ error });
return {
statusCode: 400,
body: JSON.stringify({ error }),
};
}
};
Tell Netlify where functions are stored
For Netlify to deploy our serverless functions, we need to tell it where they’re located. To do this, we need to modify netlify.toml
with a functions setting and add our functions directory as the value:
[build]
command = "yarn build"
publish = "build"
functions = "functions"
Once we’ve saved our updated config, we can start the server with ntl dev
, which will automatically serve both our React app and our functions.
Automate fulfillment with webhook events
Stripe sends webhook events for successful payments which we can use to automate our fulfillment process. See this detailed guide on how to do this. The demo source code also includes a webhook handler function for your reference.
What to do next
Want to take your Jamstack and Stripe sites even further? Check out all the articles we’ve written about e-commerce on the Jamstack!