Saving Cards for Online Payments Beta

    Learn how to use your Stripe Terminal integration to create online payments.

    Stripe Terminal lets you save payment methods for online use. Use an in-person card to initiate an online subscription using Billing, save payment details to a customer’s online account, or defer payment.

    There are two ways to collect reusable card details with Terminal:

    Saving a card from a PaymentIntent

    Save card details for online reuse from a customer’s in-person transaction. When you successfully process a PaymentIntent, the returned object contains a list of attempted charges in reverse chronological order—so the first is the successful one. This charge contains a generated_card ID.

    Create a PaymentIntent with the generated card for one-time use, or attach it to a customer for future reuse, like recurring payments.

    The initial, in-person payment benefits from the liability shift and lower pricing given to standard Terminal payments, but subsequent payments happen online and are treated as card-not-present. For example, a gym customer pays for an initial session in person and begins a membership in the same transaction. Or a clothing store collects a customer’s email address and payment method at the checkout counter, and the customer can log in later and easily use it again.

    Reading a reusable card

    Alternatively, collect payment details in person and defer any payment until later. Use the readReusableCard method instead of collectPaymentMethod and processPayment. For example, a customer orders flowers in your store, but you only want to charge them when the order ships.

    With this method, all payments are treated as card-not-present. Even though you’re presenting a card in person, the transactions happen later and don’t benefit from the lower pricing and liability shift given to standard Terminal payments.

    async () => {
      var params = {}
      // (Optional) Automatically save the payment method to a customer
      params['customer'] = 'cus_4fdAW5ftNQow1a'
      const result = await terminal.readReusableCard(params)
      if (result.error) {
        // Placeholder for handling result.error
      } else {
        // Placeholder for sending result.paymentMethod to your backend.
      }
    }
    var params = {}
    // (Optional) Automatically save the payment method to a customer
    params['customer'] = 'cus_4fdAW5ftNQow1a'
    terminal.readReusableCard(params).then(function(result) {
      if (result.error) {
        // Placeholder for handling result.error
      } else {
        // Placeholder for sending result.paymentMethod.id to your backend.
      }
    });
    var readCancelable: Cancelable? = nil
    
    // Action for a "Subscribe" button
    func subscribeAction() {
        let params = ReadReusableCardParameters()
        self.readCancelable = Terminal.shared.readReusableCard(params, delegate: self) { readResult, readError in
            if let error = readError {
                print("readReusableCard failed: \(error)")
            }
            else if let paymentMethod = readResult {
                print("readReusableCard succeeded")
    
                // Notify your backend to attach the PaymentMethod to a Customer
                APIClient.shared.attachPaymentMethod(paymentMethod.stripeId) { attachError in
                    if let error = attachError {
                        print("attach failed: \(error)")
                    }
                    else {
                        print("attach succeeded")
                    }
                }
            }
    
        }
    }
    
    // Action for a "Subscribe" button
    - (void)subscribeAction {
        SCPReadReusableCardParameters *params = [SCPReadReusableCardParameters new];
        self.readCancelable = [[SCPTerminal shared] readReusableCard:params delegate:self completion:^(SCPPaymentMethod *readResult, NSError *readError) {
            if (readError) {
                NSLog(@"readReusableCard failed: %@", readError);
            }
            else {
                NSLog(@"readReusableCard succeeded");
    
                // Notify your backend to attach the PaymentMethod to a Customer
                [[APPAPIClient shared] attachPaymentMethod:readResult.stripeId completion:^(NSError *attachError) {
                    if (attachError) {
                        NSLog(@"attach failed: %@", attachError);
                    }
                    else {
                        NSLog(@"attach succeeded");
                    }
                }];
            }
        }];
    }
    
    ReadReusableCardParameters params = new ReadReusableCardParameters.Builder()
      .build();
    Cancelable cancelable = Terminal.getInstance().readReusableCard(params,
    new MyReaderDisplayListener(),
    new PaymentMethodCallback() {
      @Override
      public void onSuccess(PaymentMethod paymentMethod) {
        // Placeholder for sending paymentMethod.id to your backend.
      }
    
      @Override
      public void onFailure(TerminalException exception) {
        // Placeholder for handling the exception
      }
    });
    val params = ReadReusableCardParameters.Builder()
      .build()
    val cancelable = Terminal.getInstance().readReusableCard(params,
      MyReaderDisplayListener(),
      object : PaymentMethodCallback {
        override fun onSuccess(paymentMethod: PaymentMethod) {
          // Placeholder for sending paymentMethod.id to your backend.
        }
    
        override fun onFailure(exception: TerminalException) {
          // Placeholder for handling the exception
        }
      })

    The connected reader waits for a card to be presented. When the customer presents a card to the reader, readReusableCard collects encrypted card data and tokenizes it as a PaymentMethod but does not create any payments.

    To charge the customer, create a PaymentIntent and attach the PaymentMethod. It can only be used once, unless you first attach it to a Customer. Avoid calling processPayment with the PaymentMethod. For more information, see Saving Payment Methods.

    Handling events in your app

    When collecting a payment method using a reader like the BBPOS Chipper 2X BT, without a built-in display, your app must be able to display events from the payment method collection process to users. These events help users successfully collect payments (e.g., retrying a card, trying a different card, or using a different read method).

    When a transaction begins, the SDK passes a ReaderInputOptions value to your app’s listener, denoting the acceptable types of input (e.g., Swipe, Insert, Tap). In your app’s checkout UI, prompt the user to present a card using one of these options.

    During the transaction, the SDK might request your app to display additional prompts (e.g., Retry Card) to your user by passing a ReaderDisplayMessage value. Make sure your checkout UI displays these messages to the user.

    // MARK: ReaderDisplayDelegate
    
    func terminal(_ terminal: Terminal, didRequestReaderInput inputOptions: ReaderInputOptions = []) {
        readerMessageLabel.text = Terminal.stringFromReaderInputOptions(inputOptions)
    }
    
    func terminal(_ terminal: Terminal, didRequestReaderDisplayMessage displayMessage: ReaderDisplayMessage) {
        readerMessageLabel.text = Terminal.stringFromReaderDisplayMessage(displayMessage)
    }
    
    #pragma mark - SCPReaderDisplayDelegate
    
    - (void)terminal:(SCPTerminal *)terminal didRequestReaderInput:(SCPReaderInputOptions)inputOptions {
        self.readerMessageLabel.text = [SCPTerminal stringFromReaderInputOptions:inputOptions];
    }
    
    - (void)terminal:(SCPTerminal *)terminal didRequestReaderDisplayMessage:(SCPReaderDisplayMessage)displayMessage {
        self.readerMessageLabel.text = [SCPTerminal stringFromReaderDisplayMessage:displayMessage];
    }
    
    @Override
    public void onRequestReaderInput(ReaderInputOptions options) {
        // Placeholder for updating your app's checkout UI
        Toast.makeText(getActivity(), options.toString(), Toast.LENGTH_SHORT).show();
    }
    
    @Override
    public void onRequestReaderDisplayMessage(ReaderDisplayMessage message) {
        Toast.makeText(getActivity(), message.toString(), Toast.LENGTH_SHORT).show();
    }
    override fun onRequestReaderInput(options: ReaderInputOptions) {
      // Placeholder for updating your app's checkout UI
      Toast.makeText(activity, options.toString(), Toast.LENGTH_SHORT).show()
    }
    
    override fun onRequestReaderDisplayMessage(message: ReaderDisplayMessage) {
      Toast.makeText(activity, message.toString(), Toast.LENGTH_SHORT).show()
    }

    Tracking customer behavior with card fingerprints

    The Stripe API makes it easy to recognize repeat customers across online and retail channels by correlating transactions by the same card. Like card payment methods, each card_present payment method 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 PaymentMethods 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.

    On this page