DEV Community

Cover image for Create an omnichannel shopping experience with Stripe Terminal in Node.js
Charlie Gerard for Stripe

Posted on

Create an omnichannel shopping experience with Stripe Terminal in Node.js

Recently, my colleague Charles Watkins wrote a 4-part intro series to Stripe Terminal, our in-person payment solution. Charles' articles will teach you how to set up and register a Terminal device and build an app to accept or cancel payments. Let's take it a step further and look into an additional feature: saving card details via the Terminal device for future online reuse.
This feature can improve your customers’ overall experience by simplifying the checkout process.

For example, when checking into a hotel, you are often asked to pre-authorize your card in case the hotel needs to charge you for any damage done to the room or get any room service directly charged to your account. Your card details are usually saved via a terminal device and re-used later on without needing additional authorization.

Creating or retrieving a customer

With Stripe, in order to charge a payment method more than once, it must be attached to a customer object, so the first step in this process is to create or retrieve your customer.

Creating a new customer

Creating a customer can be done with the following code:

// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('your-API-key');
const customer = await stripe.customers.create();
Enter fullscreen mode Exit fullscreen mode

This will create a standard customer object; however, if you’d like to assign specific properties, you can do so by passing them to the create method.

// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('your-API-key');
const customer = await stripe.customers.create({
     name: "Hedy Lamarr",
     email: "hedy@lamarr.com",
     description:
       "Patented radio frequency hopping, the basis for Wi-Fi, GPS and Bluetooth",
});
Enter fullscreen mode Exit fullscreen mode

Retrieving a customer

If your customer already exists, you can retrieve it using the retrieve method and pass it the customer ID starting with cus_:

// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('your-API-key');
const customer = await stripe.customers.retrieve("cus_xxx");
Enter fullscreen mode Exit fullscreen mode

If you do not know the customer ID, you can search for it by using the search method, for example:

const customers = await stripe.customers.search({
    query: 'name:\'Hedy Lamarr\'', 'email:\'hedy@lamarr.com\''
});
Enter fullscreen mode Exit fullscreen mode

This will return an object containing an array of customers that match the query. From there, you can loop through the array, find your customer and their ID.
Once you have your customer object, you can move on to saving their card details.

Attaching card details to a customer

First, you need to create a SetupIntent that will collect and record the customer’s consent to have their card details saved. This will authorize the payment method with the customer’s bank so future authentication will not be needed.

const intent = await stripe.setupIntents.create({
   payment_method_types: ["card_present"],
   customer: customer.id,
});
Enter fullscreen mode Exit fullscreen mode

Then, you need to call the processSetupIntent method on the reader to collect the payment method and attach it to the customer. You need to pass it the reader ID, as well as an object containing the setup intent ID and the property customer_consent_collected set to a boolean value.
When testing, you can set this value to true, however, when implementing a production-level solution, make sure to adapt your checkout flow so you can obtain consent from your customer. For example, if you have a custom UI, you can update it to collect consent and send the value true or false back to your server before continuing.

const reader = await stripe.terminal.readers.processSetupIntent(
     "tmr_xxx",
     {
       setup_intent: intent.id,
       customer_consent_collected: true,
     }
);
Enter fullscreen mode Exit fullscreen mode

If you do not know your terminal ID, you can find it using the following code:

const readers = await stripe.terminal.readers.list();
Enter fullscreen mode Exit fullscreen mode

This will return an object containing an array of terminals you registered.

{
 object: 'list',
 data: [
   {
     id: 'tmr_xxx',
     object: 'terminal.reader',
     action: null,
     device_sw_version: '2.6.2.0',
     device_type: 'bbpos_wisepos_e',
     ip_address: '192.000.0.00',
     label: 'my-terminal',
     livemode: false,
     location: 'tml_xxx',
     metadata: {},
     serial_number: 'WSCxxx',
     status: 'online'
   }
 ],
 has_more: false,
 url: '/v1/terminal/readers'
}
Enter fullscreen mode Exit fullscreen mode

Each terminal is associated with a location. Via the Stripe dashboard, you can add metadata to the location so it becomes easier to filter through the list of devices. For example, you could add a metadata object with the key-value pair {“reception”: 1} and filter the list of terminal objects to return only the one that contains {“reception”: 1} in its metadata. From there, you can find the device’s ID, starting with “tmr_”.

Screenshot of the Stripe dashboard showing a location with a terminal instance and a metadata object with key "reception" and value "1".

When the processSetupIntent method is called, the terminal display updates, waiting for the customer to insert, tap or swipe their card.

GIF showing the terminal display waiting for a customer to insert, tap or swipe their card.

Finally, if you want to verify that the payment method was successfully attached to the customer, you can call listPaymentMethods.

const paymentMethods = await stripe.customers.listPaymentMethods(
     customer.id,
     { type: "card" }
);
Enter fullscreen mode Exit fullscreen mode

This will return an object containing an array of payment methods attached to that customer.
Voila! Your customer’s card details are now saved for online reuse, without charging them!

Saving a card after payment

When collecting payments with Terminal, you need to create a PaymentIntent, using paymentIntents.create. This does not save the card details by default. You can change the default behavior by passing the additional properties paymentMethod and setup_future_usage.

const paymentIntent = await stripe.paymentIntents.create({
 payment_method_types: ['card'],
 amount: 1099,
 currency: 'usd',
 customer: customer.id,
 payment_method: paymentMethods[0].id,
 setup_future_usage: 'off_session',
});
Enter fullscreen mode Exit fullscreen mode

Now, you should be able to update your checkout flow so you can save card details without charging them, or after payment, so your customers can reuse them online easily.

Let us know if you’re implementing this, and stay up to date with Stripe developer updates on the following platforms:

📣 Follow @StripeDev and our team on Twitter
📺 Subscribe to our Youtube channel
💬 Join the official Discord server
📧 Sign up for the Dev Digest

About the author

Charlie's profile picture. She is a caucasian woman with brown hair and brown eyes. She is wearing silver glasses. She is standing in front of a plain white wall with purple lights.

Charlie Gerard is a Developer Advocate at Stripe, a creative technologist and Google Developer Expert. She loves researching and experimenting with technologies. When she’s not coding, she enjoys spending time outdoors, trying new beers and reading.

Top comments (0)