Common iOS Workflows Invite Only

    Learn about common workflows with the Stripe Terminal iOS SDK.

    Handling reader input

    To use the collectPaymentMethod and readSource methods, you must provide an implementation of ReaderInputDelegate to handle reader input events.

    When reading a card begins, the didBeginWaitingForReaderInput method is called with a set of input options (e.g., Swipe, Insert, Tap). Your app should prompt the user to present a payment method using one of these input options.

    func terminal(_ terminal: Terminal, didBeginWaitingForReaderInput options: ReaderInputOptions) {
        // Placeholder for displaying the available input options
        inputOptionsLabel.text = Terminal.stringFromReaderInputOptions(options)
    }
    

    As reading a card proceeds, the SDK might request that additional prompts be displayed to your user via the didRequestReaderInputPrompt method (e.g., Retry Card. Your app should display these prompts.

    func terminal(terminal: Terminal, didRequestReaderInputPrompt prompt: ReaderInputPrompt) {
        // Placeholder for displaying the prompt
        promptLabel.text = Terminal.stringFromReaderInputPrompt(prompt)
    }
    

    To detect when a card is inserted or removed from the reader, you can use the didReportReaderEvent method on TerminalDelegate.

    func terminal(_ terminal: Terminal, didReportReaderEvent event: ReaderEvent, info: [AnyHashable : Any]?) {
        switch (event) {
        case .cardInserted:
            break;
        case .cardRemoved:
            break;
        }
    }
    

    Status changes

    Before starting a payment, you can use the paymentStatus property to determine whether the SDK is ready to begin a payment. While the payment is in progress, you can use the didChangePaymentStatus delegate method to update your UI as the payment proceeds.

    func terminal(_ terminal: Terminal, didChangePaymentStatus status: PaymentStatus) {
        // Placeholder for displaying the payment status
        label.text = Terminal.stringFromPaymentStatus(status)
    }
    

    To determine whether your app is connected to a reader, you can use the connectionStatus property, or the didChangeConnectionStatus delegate method. For more information about the connected reader (e.g., its battery level), use the connectedReader property.

    func terminal(_ terminal: Terminal, didChangeConnectionStatus status: ConnectionStatus) {
        // Placeholder for displaying the connection status
        label.text = Terminal.stringFromConnectionStatus(status)
    }
    

    To detect when your app unexpectedly disconnects from a reader, use the didDisconnectUnexpectedlyFromReader method. In your implementation of this method, notify your user that the reader disconnected.

    In these cases, you can also call discoverReaders to begin scanning for readers, and can attempt to automatically reconnect to the disconnected reader. Be sure to either set a timeout or make it possible to cancel calls to discoverReaders.

    func terminal(_ terminal: Terminal, didDisconnectUnexpectedlyFromReader reader: Reader) {
        self.reconnectSerialNumber = reader.serialNumber
        let config = DiscoveryConfiguration()
        // be sure to set a timeout, or make it possible to cancel discoverReaders
        config.timeout = 30
        terminal.discoverReaders(config, delegate: self) { error in
            if let error = error {
                print("failed to reconnect: \(error)")
            }
        }
    }
    

    Refunds

    You can cancel a PaymentIntent before it has been captured using the cancelPaymentIntent method. The iOS SDK supports canceling PaymentIntents directly, but you can also cancel the PaymentIntent on your server. Canceling a PaymentIntent releases all uncaptured funds. A canceled PaymentIntent can no longer be used to perform charges.

    terminal.cancel(paymentIntent) { intent, error in
        if let intent = intent {
            self.print("Canceled intent: \(intent)")
        }
        else if let error = error {
            self.print("Failed to cancel intent: \(error)")
        }
    }
    

    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.

    Server-side PaymentIntent creation

    If the information required to create a PaymentIntent isn’t readily available in your application, you can also create the PaymentIntent on your server, and retrieve it via the SDK.

    First, create a PaymentIntent object. A PaymentIntent represents your intent to collect payment from a customer, tracking the lifecycle of the payment process through each step.

    Each PaymentIntent typically correlates with a single cart or customer session in your application. When you create a PaymentIntent, specify the currency, the permitted source types, and the amount to collect from the customer.

    The following example shows how to create a PaymentIntent on your server:

    curl https://api.stripe.com/v1/payment_intents \
       -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
       -d amount=1000 \
       -d currency=usd \
       -d allowed_source_types[]=card_present \
       -d capture_method=manual
    
    
    require 'stripe'
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = Stripe::PaymentIntent.create({
      amount: 1000,
      currency: 'usd',
      allowed_source_types: ['card_present'],
      capture_method: 'manual',
    }, {
      idempotency_key: my_order_id,
    })
    
    import stripe
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    stripe.PaymentIntent.create(
      amount=1000,
      currency='usd',
      allowed_source_types: ['card_present'],
      capture_method: 'manual',
    )
    
    \Stripe\Stripe::setApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    \Stripe\PaymentIntent::create([
      "amount" => 1000,
      "currency" => "usd",
      "allowed_source_types" => ["card_present"],
      "capture_method" => "manual",
    ]);
    
    import stripe
    Stripe.apiKey = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    final List<String> allowedSourceTypes = new ArrayList<String>();
    allowedSourceTypes.add("card_present");
    final Map<String, Object> params = new HashMap<>();
    params.put("allowed_source_types", allowedSourceTypes);
    params.put("amount", 1000);
    params.put("currency", "usd");
    params.put("capture_method", "manual");
    
    final PaymentIntent paymentIntent = PaymentIntent.create(params);
    
    var stripe = require("stripe")("sk_test_4eC39HqLyjWDarjtT1zdp7dc")
    
    stripe.paymentIntents.create({
      amount: 1000,
      currency: 'usd',
      allowed_source_types: ['card_present'],
      capture_method: 'manual',
    }, function(err, paymentIntent) {
      // asynchronously called
    });
    
    import "github.com/stripe/stripe-go"
    
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1000),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      AllowedSourceTypes: []*string{
        stripe.String("card"),
      },
      CaptureMethod: stripe.String("manual"),
    }
    paymentIntent.New(params)
    

    The allowed_source_types parameter must include card_present for Stripe Terminal payments. To ensure that a card present payment is not unintentionally captured, only a capture_method of manual is allowed for card_present PaymentIntents.

    You can choose to cancel stale, uncaptured PaymentIntents. Canceling a PaymentIntent releases all uncaptured funds. A canceled PaymentIntent can no longer be used to perform charges.

    After creating a PaymentIntent on your server, you can interact with the PaymentIntent client-side by exposing its client_secret to your application.

    let paymentIntent = terminal.retrievePaymentIntent(clientSecret) { intent, error in
        if let intent = intent {
            terminal.collectPaymentMethod( ... )
        }
        else if let error = error {
            self.handleError(error)
        }
    }
    

    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. Like attachSource, this method requires a ReadCardDelegate to respond to events related to reading a card.

    If reading a source fails, the completion block is called with an error. If reading a source succeeds, the completion block is called with a CardPresentSource, which you can send to your backend for further processing.

    let params = ReadSourceParameters()
    let cancelable = terminal.readSource(params, delegate: self) { source, error in
        if let source = source {
            // Placeholder for sending the source to your server for processing
            self.apiClient.saveCardPresentSource(sourceId: source.stripeId)
        }
        else if let error = error {
            self.print("Failed to read source: \(error)")
        }
    }
    

    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 (defined in your app) to fetch a connection token, if it does not already have one. It 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.

    You should not attempt to switch accounts by destroying and re-creating the StripeTerminal object. Doing so could result in unexpected behavior.

    Receipts

    If you provide a receiptEmail when creating a PaymentIntent, a receipt is emailed to your customer when the PaymentIntent is captured. If you need to print a physical receipt, you can use the receiptData property on the confirmed PaymentIntent’s cardPresentSource to get a dictionary containing receipt fields for the transaction.

    Field Name Requirement
    applicationPreferredName Application Name Required
    dedicatedFileName AID Required
    authorizationResponseCode ARC Optional
    applicationCryptogram Application Cryptogram Optional
    terminalVerificationResults TVR Optional
    transactionStatusInformation TSI Optional

    Keyed-in payments

    To support manually entering your customer’s card information in your app, you can use STPPaymentCardTextField. This is a single-line text field available to collect a card number, expiration date, CVC, and ZIP code. To use this field, install the Stripe iOS SDK. Since you’ll only be using STPPaymentCardTextField, you don’t need to do any additional setup.

    After your user enters valid card details in STPPaymentCardTextField, use the createKeyedSource method (part of Terminal) to create a source using that text field. After a source is created, send the source ID to your backend so that you can use it to create a charge.

    let cardField = STPPaymentCardTextField()
    // ...
    
    func keyInButtonTapped() {
        self.terminal.createKeyedSource(cardField) { source, error in
            if let source = source {
                self.apiClient.processCardPresentSource(sourceId: source.stripeId)
            }
        }
    }
    

    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.

    Reader updates

    You can update a reader from your app using the SDK’s updateReaderSoftware workflow. Alternatively, you can use the included example app, which includes a workflow for updating readers. You can download the Example app directly by joining the TestFlight beta, or build it from source.

    To start an update via the SDK, call the updateReaderSoftware method. Your app must implement the UpdateReaderSoftwareDelegate protocol to notify the user as the update proceeds.

    When updateReaderSoftware is called, the SDK checks if an update is available for the connected reader. If an update is available, the readerSoftwareUpdateAvailable delegate method is called with a ReaderSoftwareUpdate object that contains details about the update. Your app should notify the user that an update is available, and should display a prompt to continue.

    After the user selects an option, use the installUpdate block to proceed. If the user chooses to proceed with the update, call installUpdate(true) begin the installation. If the user does not wish to proceed, call installUpdate(false). The SDK notifies your delegate when the update is complete, via the didCompleteReaderSoftwareUpdate: delegate method.

    func terminal(_ terminal: Terminal, readerUpdateAvailable readerUpdate: ReaderUpdate, installUpdate: @escaping BoolCompletionBlock) {
        // if the user wishes to install the update
        installUpdate(response)
    }
    
    func terminal(_ terminal: Terminal, didReportReaderUpdateProgress updateProgress: Float) {
        updateProgressView.value = updateProgress
    }
    
    func terminal(_ terminal: Terminal, didCompleteReaderUpdate error: Error?) {
        if let e = error {
            presentAlert(error: e)
        }
        else {
            presentAlert(title: "Update succeeded", message: "")
        }
        updateProgressView.value = 0
    }
    

    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.