Checkout "Remember Me" with SMS verification using Stripe and Twilio

thorwebdev profile image Thor 雷神 Originally published at twilio.com ・5 min read

Stripe and Twilio have teamed up to build a sample application that shows you how to securely collect and store payment details from your customers and use Twilio Verify to send returning customers an authentication code before charging their saved card details.

Demo and resources

If you prefer to watch this tutorial, you can find a recording of how to set up the sample application on the Stripe Developers YouTube channel:

Running the sample on your local machine

The sample application comes with two backend implementations one in JavaScript (Node) and one in Python (Flask). In this tutorial we outline how to set up the Node.js backend. You can find instructions for running the Python Flask server here.

Creating the sample with the Stripe CLI

The most convenient way to set up a Stripe Sample is using the Stripe CLI. Follow these instructions to install the CLI.

Next, you will need to be logged in to your Stripe account. If you don't have one you can register here. When logged in, run:

stripe login

Follow the steps to authenticate the CLI with your account.

Next, create the sample and when prompted select the node backend:

stripe samples create checkout-remember-me-with-twilio-verify

Screenshot of options

Next, navigate into the newly created sample folder and install the dependencies:

cd checkout-remember-me-with-twilio-verify/server
npm install

Setting up the environment variables

NOTE: All values in the .env file except for the STRIPE_PUBLISHABLE_KEY must be kept secret. Therefore always double check that you're not committing the .env file to git!

The Stripe CLI has automatically set up a .env file for you, including your test API keys. Open up the .env file and add the following Twilio identifiers:

Variable Meaning
ACCOUNT_SID Find in the Twilio console
AUTH_TOKEN Find in the Twilio console
VERIFY_SERVICE_SID Create a Verify Service

Create a Twilio account if you don't have one. If you use this link you'll get $10 when you upgrade. You will need to create a new Twilio Verify service on the Twilio Console.

Twilio console screenshot

You can find your Verify SID in the general settings:

Twilio console screenshot

With all environment variables set, you can now start up the local server:

npm start

That's it, the appication is now running on http://localhost:4242/ 🎉

A look at the source code

Setting up an international phone number input field

To offer a country selector and client-side phone number formatting and validation we're using the open-source library intl-tel-input.

// client/script.js

// Format phone number input field
const phoneInputField = document.querySelector('#phone');
const phoneInput = window.intlTelInput(phoneInputField, {
  separateDialCode: true,
  utilsScript: 'build/js/utils.js',
// Validate input
// Format input

Validating the input client-side allows us to minimise requests to our server and provide faster feedback to our users, resulting in a better user experience.

Look up the phone number on the server

While client-side validation is crucial, it is not sufficient, therefore we can make an API request to look up the phone number on our server also.

// server/server.js

try {
  // Validate the phone number input
  const number = await client.lookups.phoneNumbers(phone).fetch();
  number.phoneNumber; // Is valid number
} catch (error) {
  // Not a valid number

Collect and store the customer's payment details

To securely collect the customer's payment details we're using Stripe Checkout in setup mode:

// server/server.js

// Create a new customer object
const customer = await stripe.customers.create({
  phone: number.phoneNumber,

// Create a CheckoutSession to set up our payment methods recurring usage
const checkoutSession = await stripe.checkout.sessions.create({
  payment_method_types: ['card'],
  mode: 'setup',
  customer: customer.id,
  success_url: `${req.headers.origin}?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url: `${req.headers.origin}/`,

Using the resulting checkout session ID, we redirect the customer to the Stripe hosted payment form:

// client/script.js

// Recirect to Checkout
await stripe.redirectToCheckout({ sessionId: checkoutSession.id });

Stripe Checkout will validate the inputted card details and attach them to the customer object if valid. Afterwards, the customer is redirected back to our application where we can now make use of their stored card details.

Send the verification code

When charging stored payment details we need to make sure that our customer is authenticated and therefore allowed to charge their card. In this example we're using Twilio Verify to send them a verification code to their phone number to authenticate them.

Twilio Verify does all of the heavy lifting here for us, namely:

  • Send from a globally recognised short code or alpha sender ID
  • Use a message template that is white-listed by carriers globally for better deliverability
  • Generate a random verification code and manage its expiry

This snippet generates and sends the code to our customer:

// server/server.js

// Start Twilio verify
const verification = await client.verify
  .verifications.create({ to: customer.phone, channel: 'sms' });

Check the verification code

Next, we take the code the customer inputted in our application and check it with Twilio:

// server/server.js

try {
  // Check Twilio verify code
  const verificationCheck = await client.verify
    .verificationChecks.create({ to: customer.phone, code });

  // Check the status
  if (verificationCheck.status === 'approved') {
    // Verification code is valid
} catch (error) {
  // Incorrect code

Charge the customer

Now that we've authenticated the customer, we can charge their stored card details:

// server/server.js

// Charge the stored method
const paymentIntent = await stripe.paymentIntents.create({
  customer: customerId,
  payment_method: paymentMethods.data[0].id,
  off_session: false, // Customer is on-session during checkout
  confirm: true, // Confirm and charge payment immediately

Listen to Stripe webhook events

To help you test webhooks the Stripe CLI can forward webhook events to your server running locally. In a separate terminal window run:

stripe listen --forward-to localhost:4242/webhook

You will now see events logged in the terminal where the CLI is running and webhook events are forward to the /webhook route of your local server.

Questions ?

We hope you found this tutorial helpful ❤️ If you have any feedback or questions you can reach the authors on Twitter:


Editor guide