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 re-use the payment method online.

    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 several approaches for creating charges on behalf of a connected account. One approach is a direct charge, where the connected account is responsible for the cost of the Stripe fees, refunds, and chargebacks. To perform a direct charge, provide the connected account’s ID while creating the PaymentIntent:

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1099 \
      -d currency=usd \
      -d payment_method_types[]=card_present \
      -d capture_method=manual \
      -H "Stripe-Account: {CONNECTED_ACCOUNT_ID}"
    

    Another approach is a destination charge, in which the payment creates a transfer to a connected account automatically and sets a connected account as the business of record for the payment.

    The transfer_data[destination] is the ID of the connected account that should receive the transfer.

    The on_behalf_of parameter is the ID of the connected account to use as the business of record for the payment. When on_behalf_of is set, Stripe automatically:

    • Settles charges in the country of the specified account, thereby minimizing declines and avoiding currency conversions
    • Uses the fee structure for the connected account’s country
    • Lists the connected account’s address and phone number on the customer’s credit card statement, as opposed to the platform’s address and phone number. This only occurs if the account is in a different country than the platform.

    Finally, you may withhold an application fee by providing the application_fee_amount parameter:

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1099 \
      -d currency=usd \
      -d payment_method_types[]=card_present \
      -d capture_method=manual \
      -d application_fee_amount=200 \
      -d on_behalf_of="{CONNECTED_ACCOUNT_ID}" \
      -d transfer_data[destination]="{CONNECTED_ACCOUNT_ID}"
    

    Grouping Terminal Objects

    When using destination charges, objects belonging to your connected accounts will be created on your platform account. To group Terminal API objects on your platform account by connected account, set the operator_account parameter to the connected account. This parameter can be used on all Terminal API endpoints.

    Locations

    To create a Location on your platform account that is also associated with a connected account, use the optional operator_account parameter.

    curl https://api.stripe.com/v1/terminal/locations \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d display_name=HQ \
      -d operator_account="{CONNECTED_ACCOUNT_ID}"
    

    Readers

    When registering a reader, you can use the optional location parameter to assign it to a specific location. If the location was created with an operator_account, you must specify the same operator_account when registering the reader.

    curl https://api.stripe.com/v1/terminal/readers \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d registration_code="{REGISTRATION_CODE" \
      -d label="Test Reader" \
      -d location="{LOCATION_ID}" \
      -d operator_account="{CONNECTED_ACCOUNT_ID}"
    

    Note that if you don’t specify a location when registering a reader it will be assigned to your account’s default location. If you don’t specify a location and you do specify an operator_account, the reader will be assigned to the connected account’s default location.

    Connection Tokens

    When creating a connection token for the Terminal SDK, set operator_account to be the connected account operating your application.

    curl https://api.stripe.com/v1/terminal/connection_tokens \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d operator_account="{CONNECTED_ACCOUNT_ID}"
    

    The SDK will inherit the operator account that was specified when creating the connection token. You do not need to specify an operator account in any client-side calls to the SDK.

    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.