Common JavaScript WorkflowsInvite Only

    Learn about common workflows with the Stripe Terminal JavaScript SDK.

    Customize the display during a payment

    To update the line items and totals displayed on the reader during a payment, use the setReaderDisplay method.

    terminal.setReaderDisplay({
      type: 'cart',
      cart: {
        lineItems: [
          {
            description: "Blue Shirt",
            amount: 1000,
            quantity: 2,
          },
          {
            description: "Jeans",
            amount: 3000,
            quantity: 1,
          },
        ],
        tax: 100,
        total: 5100,
        currency: 'usd',
      },
    });

    These amounts are for display only, and do not determine the amount that gets charged.

    Control the display at the end of a payment

    By default, the customer sees the result of a payment attempt as soon as the payment request to Stripe completes. In some cases, you might want to wait until after some post-processing has completed before signaling the end of the checkout.

    For example, you might need to update a booking, or update information about the customer, before signaling the end of the checkout experience. Or, for checkouts that involve collecting multiple payment methods, you might not want to signal the completion of checkout until all the payment method collections succeeds.

    By default, the Verifone P400 reader shows a Thank you or Results screen after a payment attempt. To clear the display, call the clearReaderDisplay method.

    terminal.clearReaderDisplay();
    

    Refunds

    You can cancel a PaymentIntent before it has been captured using the cancel API on your server. Canceling a PaymentIntent releases all uncaptured funds. A canceled PaymentIntent can no longer be used to perform charges.

    To refund a PaymentIntent that has already been captured, you must refund the charge created by the PaymentIntent, using the refunds API on your server.

    Reading a card without charging

    You can read a card without charging it using the readSource method. This can be useful if you want to charge the customer later, e.g. when their order has been fulfilled.

    If reading a source fails, the promise is resolved with an object that contains an error field. If reading a source succeeds, the promise is resolved with a CardPresentSource, which you can send to your backend for further processing.

    terminal.readSource().then(function(readSourceResult) {
      if (readSourceResult.error) {
        alert("Read source failed: " + readSourceResult.error.message);
      }
      else {
        processCardPresentSource(readSourceResult.source)
      }
    });
    async () => {
      // clientSecret is the client_secret from the PaymentIntent
      // created in Step 1 of the payment guide.
      const readSourceResult = await terminal.readSource()
      if (readSourceResult.error) {
        alert(`Read source failed: ${readSourceResult.error.message}`);
      }
      else {
        processCardPresentSource(readSourceResult.source)
      }
    }
    

    Saving a card present source

    You cannot charge or reuse a card_present source created by the Stripe Terminal SDK. To save a card_present source for reuse online (e.g., for a subscription), you must convert it to a card source on your backend.

    curl https://api.stripe.com/v1/sources \
       -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
       -d type=card \
       -d card[card_present_source]="{SOURCE ID}"
    
    

    Switching Stripe accounts

    To connect to a reader, the SDK uses the fetchConnectionToken method (provided to StripeTerminal.create) to fetch a connection token, if it does not already have one. The SDK then uses the connection token and reader information to create a reader session.

    To switch Stripe accounts in your app, you’ll need to:

    1. Disconnect from the currently connected reader, if one is connected.
    2. Call clearConnectionToken to clear the current connection token.
    3. Configure your app to use the new account.
    4. Connect to a reader.

    Receipts

    If you provide a receipt_email when creating a PaymentIntent, a receipt is emailed to your customer when the PaymentIntent is captured.

    If you need to print a physical receipt, the confirmed PaymentIntent’s source has a card_present property whose data you can use to get an object containing all required receipt fields for the transaction.

    Field Name Requirement
    application_preferred_name Application Name Required
    dedicated_file_name AID Required
    authorization_response_code ARC Optional
    application_cryptogram Application Cryptogram Optional
    terminal_verification_results TVR Optional
    transaction_status_information TSI Optional

    Automatically connecting to a reader

    For many UI flows, you might want to automatically connect to a previously connected device, or store a preferred reader for a specific user. In these cases, store the ID of the reader you want to remember, and then automatically connect to that reader if it is discovered, instead of prompting the cashier.

    First, update your discovery code to check for a preferred reader:

    terminal.discoverReaders().then(function(discoverResult) {
      if (discoverResult.error) {
        console.log('Failed to discover: ', discoverResult.error);
      } else if (discoverResult.discoveredReaders.length === 0) {
        console.log('No available readers.');
      } else {
        let rememberedReaderId = localStorage.getItem('last-reader');
        let preferredReader = discoverResult.discoveredReaders.find(function(reader) { return reader.id === rememberedReaderId })
        if (preferredReader) {
          connectReader(preferredReader);
        } else {
          // Show cashier a modal with the discovered readers so they can select which to connect to then call your `connectReader` function
        }
      }
    });
    async () => {
      const discoverResult = await terminal.discoverReaders();
      if (discoverResult.error) {
        console.log('Failed to discover: ', discoverResult.error);
      } else if (discoverResult.discoveredReaders.length === 0) {
        console.log('No available readers.');
      } else {
        let rememberedReaderId = localStorage.getItem('last-reader');
        let preferredReader = discoverResult.discoveredReaders.find(reader => reader.id === rememberedReaderId)
        if (preferredReader) {
          connectReader(preferredReader);
        } else {
          // Show cashier a modal with the discovered readers so they can select which to connect to then call your `connectReader` function
        }
      }
    }

    Second, update your code to store the preferred device in local storage, or in other storage, like your database.

    function connectReader(reader) {
      terminal.connectReader(selectedReader).then(function(connectResult) {
        if (connectResult.error) {
          console.log('Failed to connect: ', connectResult.error);
        } else {
          localStorage.setItem('last-reader', connectResult.connection.reader.id)
          console.log('Connected to reader: ', connectResult.connection.reader.label);
        }
      });
    }
    async function connectReader(reader) {
      const connectResult = await terminal.connectReader(reader);
      if (connectResult.error) {
        console.log('Failed to connect:', connectResult.error);
      } else {
        localStorage.setItem('last-reader', connectResult.connection.reader.id)
        console.log('Connected to reader:', connectResult.connection.reader.label);
      }
    }

    Using Connect

    Stripe Terminal supports all Connect fund flows.

    Direct charges

    • When creating a PaymentIntent, set the optional stripeAccount property to the connected account’s ID.
    • When capturing the PaymentIntent on your server, set the charge’s application_fee.
    • For more information, see our documentation on direct charges.

    Destination charges

    • When creating a PaymentIntent, set the optional transfer_data[destination] parameter to the connected account’s ID.
    • When capturing the PaymentIntent on your server, set the transfer_data[amount] parameter.
    • For more information, see our documentation on destination charges.

    Grouping transactions

    • When creating a PaymentIntent, set the optional transferGroup parameter to a string that identifies the payment as part of a group.
    • For more information, see our documentation on grouping transactions.

    Using locations to filter the discovered readers list

    If you have multiple locations with hundreds of readers, the default result set of the discoverReaders call will quickly become too large to be useful. Luckily, Stripe provides a Location object as a way to segment the readers and filter discovered readers.

    To use locations, first create a location:

    curl https://api.stripe.com/v1/terminal/locations \
    -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
    -d display_name="HQ" \
    -d "address[line1]"="123 Main St" \
    -d "address[city]"="San Francisco" \
    -d "address[state]"="CA" \
    -d "address[country]"="US" \
    -d "address[postal_code]"="94103"

    Next, when registering a reader, specify that this reader belongs to a specific location:

    curl https://api.stripe.com/v1/terminal/readers \
    -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
    -d registration_code="{{READER_REGISTRATION_CODE}}" \
    -d location="{{location ID}}" \
    -d label="Bob's Reader"

    Then, when calling discoverReaders with the JS SDK, pass in a location ID to filter the result list:

    terminal.discoverReaders({ location: <location_id>}).then(function(discoverResult) {
      // Then Normal Discovery Handling Code
    });
    async () => {
      const discoverResult = await terminal.discoverReaders({ location: <location_id>});
      // Then Normal Discovery Handling Code
    }

    Pre-dipping a card

    The Verifone P400 supports the ability to present a card to the reader before the transaction amount is finalized. This option—known as “pre-dip,” “pre-tap,” or “pre-swipe”—can help speed up transaction times by allowing a customer to present a payment method before the end of the transaction.

    Pre-dipping is enabled by default on the Verifone P400 reader. When you use the setReaderDisplay method, this also prepares the reader for pre-dipping. The customer can present a payment method at any point after this method is called.

    Even if a customer pre-dips their card, your application must still complete the full payment flow.

    Card fingerprints

    The Stripe API makes it easy to track customers across online and retail channels. Like card sources, each card_present source has a fingerprint attribute that uniquely identifies a particular card number. Note that cards from mobile wallets like Apple Pay or Google Pay do not share a fingerprint with cards used online.

    Starting with API version 2018-01-23, Connect platforms see a fingerprint on card_present and card sources that is uniform across all connected accounts. You can use this fingerprint to look up charges across your platform from a particular card.

    Questions?

    We're always happy to help with code or other questions you might have! Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.