Collecting payments

    Prepare your application and backend to collect payments using the Stripe Terminal SDK.

    Collecting payments with Stripe Terminal requires writing a payment flow in your application. Use the Stripe Terminal SDK to create and update a PaymentIntent, an object representing a single payment session.

    Designed to be robust to failures, the Terminal integration splits the payment process into several steps, each of which can be retried safely:

    1. Create a PaymentIntent
    2. Collect a payment method
    3. Process the payment
    4. Capture the PaymentIntent

    Authorization on the customer’s card takes place in Step 3, when the SDK processes the payment.

    Step 1: Create a Payment Intent Client-side Server-side

    The first step in collecting payments is starting the payment flow. When a customer begins checking out, your application must create a PaymentIntent object. This represents a new payment session on Stripe.

    With the iOS and Android SDKs, you can create a PaymentIntent on the client or server. The JavaScript SDK only supports server-side creation.

    Use test amounts to try producing different results. An amount ending in 00 results in an approved payment.

    Client-side

    Create a PaymentIntent from your client using the iOS or Android SDK:

    import UIKit
    import StripeTerminal
    
    class PaymentViewController: UIViewController {
    
        // ...
    
        // Action for a "Checkout" button
        func checkoutAction() {
            let params = PaymentIntentParameters(amount: 1000, currency: "usd")
            Terminal.shared.createPaymentIntent(params) { createResult, createError in
                if let error = createError {
                    print("createPaymentIntent failed: \(error)")
                }
                else if let paymentIntent = createResult {
                    print("createPaymentIntent succeeded")
                    // ...
                }
    
            }
        }
    
        // ...
    }
    
    #import "APPPaymentViewController.h"
    #import <StripeTerminal/StripeTerminal.h>
    
    // ...
    
    @implementation APPPaymentViewController
    
    // Action for a "Checkout" button
    - (void)checkoutAction {
        SCPPaymentIntentParameters *params = [[SCPPaymentIntentParameters alloc] initWithAmount:1000
                                                                                       currency:@"usd"];
        [[SCPTerminal shared] createPaymentIntent:params completion:^(SCPPaymentIntent *createResult, NSError *createError) {
            if (createError) {
                NSLog(@"createPaymentIntent failed: %@", createError);
            }
            else {
                NSLog(@"createPaymentIntent succeeded");
                // ...
            }
    }
    
    // ...
    
    @end
    
    PaymentIntentParameters params = new PaymentIntentParameters.Builder()
      .setAmount(100)
      .setCurrency("usd")
      .build();
    Terminal.getInstance().createPaymentIntent(params, new PaymentIntentCallback() {
        @Override
        public void onSuccess(PaymentIntent paymentIntent) {
          // Placeholder for collecting a payment method with paymentIntent
        }
    
        @Override
        public void onFailure(TerminalException exception) {
          // Placeholder for handling exception
        }
      }
    })
    val params = PaymentIntentParameters.Builder()
      .setAmount(100)
      .setCurrency("usd")
      .build();
    Terminal.getInstance().createPaymentIntent(params, object: PaymentIntentCallback {
      override fun onSuccess(paymentIntent: PaymentIntent) {
        // Placeholder for collecting a payment method with paymentIntent
      }
    
      override fun onFailure(exception: TerminalException) {
        // Placeholder for handling exception
      }
    })

    Server-side

    The JavaScript SDK requires you to create the PaymentIntent on your server. For iOS or Android, you might choose to create the PaymentIntent on your server if the information required to start a payment isn’t readily available in your app.

    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 "payment_method_types[]"=card_present \
      -d capture_method=manual
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = Stripe::PaymentIntent.create({
      amount: 1000,
      currency: 'usd',
      payment_method_types: ['card_present'],
      capture_method: 'manual',
    }, {
      idempotency_key: my_order_id,
    })
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    stripe.PaymentIntent.create(
      amount=1000,
      currency='usd',
      payment_method_types=['card_present'],
      capture_method='manual'
    )
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    StripePaymentIntent::create([
      "amount" => 1000,
      "currency" => "usd",
      "payment_method_types" => ["card_present"],
      "capture_method" => "manual",
    ]);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    ArrayList paymentMethodTypes = new ArrayList();
    paymentMethodTypes.add("card_present");
    final Map<String, Object> params = new HashMap<String, Object>();
    params.put("payment_method_types", paymentMethodTypes);
    params.put("amount", 1000);
    params.put("currency", "usd");
    params.put("capture_method", "manual");
    
    PaymentIntent.create(params);
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async function() {
      const intent = await stripe.paymentIntents.create({
        amount: 1000,
        currency: 'usd',
        payment_method_types: ['card_present'],
        capture_method: 'manual',
      });
    })();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.PaymentIntentParams{
      Amount: stripe.Int64(1000),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
      PaymentMethodTypes: stripe.StringSlice([]string{
        "card_present",
      }),
      CaptureMethod: stripe.String("manual"),
    }
    paymentintent.New(params)
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentCreateOptions
    {
        Amount = 1000,
        Currency = "usd",
        PaymentMethodTypes = new List<string> { "card_present" },
        CaptureMethod = "manual",
    };
    service.Create(options);
    

    For Terminal payments, the payment_method_types parameter must include card_present. The capture_method must be set to manual for all card_present PaymentIntents to precisely control the payment flow.

    The PaymentIntent contains a client secret, a key that is unique to the individual PaymentIntent. To use the client secret, you must obtain it from the PaymentIntent on your server and pass it to the client side.

    For JavaScript, use the client secret as a parameter when calling collectPaymentMethod. For iOS and Android, first use the client secret to call retrievePaymentIntent, and then use the retrieved PaymentIntent to call collectPaymentMethod.

    func checkoutButtonAction() {
        // ... Fetch the client secret from your backend
        Terminal.shared.retrievePaymentIntent(clientSecret: clientSecret) { retrieveResult, retrieveError in
            if let error = retrieveError {
                print("retrievePaymentIntent failed: \(error)")
            }
            else if let paymentIntent = retrieveResult {
                print("retrievePaymentIntent succeeded: \(paymentIntent)")
                // ...
            }
        }
    }
    
    // Action for a "Checkout" button
    - (void)checkoutAction {
        // ... Fetch the client secret from your backend
        [[SCPTerminal shared] retrievePaymentIntent:clientSecret completion:^(SCPPaymentIntent *retrieveResult, NSError *retrieveError) {
            if (retrieveError) {
                NSLog(@"retrievePaymentIntent failed: %@", retrieveError);
            }
            else {
                NSLog(@"retrievePaymentIntent succeeded");
                // ...
            }
        }];
    }
    
    Terminal.getInstance().retrievePaymentIntent(clientSecret,
      new PaymentIntentCallback() {
        @Override
        public void onSuccess(PaymentIntent paymentIntent) {
          // Placeholder for collecting a payment method with paymentIntent
        }
    
        @Override
        public void onFailure(TerminalException exception) {
          // Placeholder for handling exception
        }
      }
    })
    Terminal.getInstance().retrievePaymentIntent(clientSecret, object: PaymentIntentCallback {
      override fun onSuccess(paymentIntent: PaymentIntent) {
        // Placeholder for collecting a payment method with paymentIntent
      }
    
      override fun onFailure(exception: TerminalException) {
        // Placeholder for handling exception
      }
    })

    Step 2: Collect a payment method Client-side

    After you’ve created a PaymentIntent, the next step is to collect a payment method with the SDK.

    When your app is ready to begin collecting an in-person card payment, call collectPaymentMethod, and the connected reader begins waiting for a card.

    function checkout() {
      // clientSecret is the client_secret from the PaymentIntent you created in Step 1.
      terminal.collectPaymentMethod(clientSecret).then(function(result) {
        if (result.error) {
          // Placeholder for handling result.error
        } else {
          // Placeholder for processing result.paymentIntent
        }
      });
    }
    async () => {
      // clientSecret is the client_secret from the PaymentIntent you created in Step 1.
      const result = await terminal.collectPaymentMethod(clientSecret);
      if (result.error) {
        // Placeholder for handling result.error
      } else {
        // Placeholder for processing result.paymentIntent
      }
    }
    import UIKit
    import StripeTerminal
    
    class PaymentViewController: UIViewController, ReaderDisplayDelegate {
    
        // Label for displaying messages from the card reader
        let readerMessageLabel = UILabel(frame: .zero)
        var collectCancelable: Cancelable? = nil
    
        // ...
    
        // Action for a "Checkout" button
        func checkoutAction() {
            let params = PaymentIntentParameters(amount: 1000, currency: "usd")
            Terminal.shared.createPaymentIntent(params) { createResult, createError in
                if let error = createError {
                    print("createPaymentIntent failed: \(error)")
                }
                else if let paymentIntent = createResult {
                    print("createPaymentIntent succeeded")
                    self.collectCancelable = Terminal.shared.collectPaymentMethod(paymentIntent, delegate: self) { collectResult, collectError in
                        if let error = collectError {
                            print("collectPaymentMethod failed: \(error)")
                        }
                        else if let paymentIntent = collectResult {
                            print("collectPaymentMethod succeeded")
                            // ... Process the payment
                        }
                    }
                }
    
            }
        }
    
        // 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)
        }
    }
    
    #import "APPPaymentViewController.h"
    #import <StripeTerminal/StripeTerminal.h>
    
    @interface APPPaymentViewController () <SCPReaderDisplayDelegate>
    
    // Label for displaying messages from the card reader
    @property (nonatomic, nullable, strong) UILabel *readerMessageLabel;
    @property (nonatomic, nullable, strong) SCPCancelable *collectCancelable;
    
    @end
    
    @implementation APPPaymentViewController
    
    // ...
    
    // Action for a "Checkout" button
    - (void)checkoutAction {
        SCPPaymentIntentParameters *params = [[SCPPaymentIntentParameters alloc] initWithAmount:1000
                                                                                       currency:@"usd"];
        [[SCPTerminal shared] createPaymentIntent:params completion:^(SCPPaymentIntent *createResult, NSError *createError) {
            if (createError) {
                NSLog(@"createPaymentIntent failed: %@", createError);
            }
            else {
                NSLog(@"createPaymentIntent succeeded");
                self.collectCancelable = [[SCPTerminal shared] collectPaymentMethod:createResult delegate:self completion:^(SCPPaymentIntent *collectResult, NSError *collectError) {
                    if (collectError) {
                        NSLog(@"collectPaymentMethod failed: %@", collectError);
                    }
                    else {
                        NSLog(@"collectPaymentMethod succeeded");
                        // ... Process the payment
                    }
                }];
            }
        }];
    }
    
    #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];
    }
    
    Cancelable cancelable = Terminal.getInstance().collectPaymentMethod(paymentIntent,
      new MyReaderListener(),
      new PaymentIntentCallback() {
        @Override
        public void onSuccess(PaymentIntent paymentIntent) {
          // Placeholder for processing paymentIntent
        }
    
        @Override
        public void onFailure(TerminalException exception) {
          // Placeholder for handling exception
        }
      });
    val cancelable = Terminal.getInstance().collectPaymentMethod(paymentIntent, MyReaderListener(),
      object : PaymentIntentCallback {
      override fun onSuccess(paymentIntent: PaymentIntent) {
        // Placeholder for processing paymentIntent
      }
    
      override fun onFailure(exception: TerminalException) {
        // Placeholder for handling exception
      }
    })

    This method collects encrypted payment method data using the connected card reader, and associates the encrypted data with the local PaymentIntent.

    You can cancel collecting a payment method using the Cancelable object returned by the iOS or Android SDK, or calling cancelCollectPaymentMethod in the JavaScript SDK.

    Handling events

    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 reader display handler, 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 to your app’s reader display handler. 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()
    }

    Step 3: Process the payment Client-side

    After successfully collecting a payment method from the customer, the next step is to process the payment with the SDK. You can either process automatically or display a confirmation screen, where the customer can choose to proceed with the payment or cancel (e.g., to pay with cash, or use a different payment method).

    When you’re ready to proceed with the payment, call processPayment with the updated PaymentIntent from Step 2.

    terminal.processPayment(paymentIntent).then(function(result) {
      if (result.error) {
        // Placeholder for handling result.error
      } else if (result.paymentIntent) {
        // Placeholder for notifying your backend to capture result.paymentIntent.id
      }
    });
    async () => {
      const result = await terminal.processPayment(paymentIntent);
      if (result.error) {
        // Placeholder for handling result.error
      } else if (result.paymentIntent) {
        // Placeholder for notifying your backend to capture result.paymentIntent.id
      }
    }
    // Action for a "Checkout" button
    func checkoutAction() {
        let params = PaymentIntentParameters(amount: 1000, currency: "usd")
        Terminal.shared.createPaymentIntent(params) { createResult, createError in
            if let error = createError {
                print("createPaymentIntent failed: \(error)")
            }
            else if let paymentIntent = createResult {
                print("createPaymentIntent succeeded")
                self.collectCancelable = Terminal.shared.collectPaymentMethod(paymentIntent, delegate: self) { collectResult, collectError in
                    if let error = collectError {
                        print("collectPaymentMethod failed: \(error)")
                    }
                    else if let paymentIntent = collectResult {
                        print("collectPaymentMethod succeeded")
                        Terminal.shared.processPayment(paymentIntent) { processResult, processError in
                            if let error = processError {
                                print("processPayment failed: \(error)")
                            }
                            else if let paymentIntent = processResult {
                                print("processPayment succeeded")
                                // Notify your backend to capture the PaymentIntent
                                APIClient.shared.capturePaymentIntent(paymentIntent.stripeId) { captureError in
                                    if let error = captureError {
                                        print("capture failed: \(error)")
                                    }
                                    else {
                                        print("capture succeeded")
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
     // Action for a "Checkout" button
    - (void)checkoutAction {
        SCPPaymentIntentParameters *params = [[SCPPaymentIntentParameters alloc] initWithAmount:1000
                                                                                       currency:@"usd"];
        [[SCPTerminal shared] createPaymentIntent:params completion:^(SCPPaymentIntent *createResult, NSError *createError) {
            if (createError) {
                NSLog(@"createPaymentIntent failed: %@", createError);
            }
            else {
                NSLog(@"createPaymentIntent succeeded");
                self.collectCancelable = [[SCPTerminal shared] collectPaymentMethod:createResult delegate:self completion:^(SCPPaymentIntent *collectResult, NSError *collectError) {
                    if (collectError) {
                        NSLog(@"collectPaymentMethod failed: %@", collectError);
                    }
                    else {
                        NSLog(@"collectPaymentMethod succeeded");
                        [[SCPTerminal shared] processPayment:createResult completion:^(SCPPaymentIntent *processResult, SCPProcessPaymentError *processError) {
                            if (processError) {
                                NSLog(@"processPayment failed: %@", processError);
                            }
                            else {
                                NSLog(@"processPayment succeeded");
                                // Notify your backend to capture the PaymentIntent
                                [[APPAPIClient shared] capturePaymentIntent:processResult.stripeId completion:^(NSError *captureError) {
                                    if (captureError) {
                                        NSLog(@"capture failed: %@", captureError);
                                    }
                                    else {
                                        NSLog(@"capture succeeded");
                                    }
                                }];
                            }
                        }];
                    }
                }];
            }
        }];
    }
    
    Terminal.getInstance().processPayment(paymentIntent,
      new PaymentIntentCallback() {
        @Override
        public void onSuccess(PaymentIntent paymentIntent) {
          // Placeholder for notifying your backend to capture paymentIntent.id
        }
    
        @Override
        public void onFailure(TerminalException exception) {
          // Placeholder for handling the exception
        }
    });
    Terminal.getInstance().processPayment(paymentIntent, object : PaymentIntentCallback {
      override fun onSuccess(paymentIntent: PaymentIntent) {
        // Placeholder for notifying your backend to capture paymentIntent.id
      }
    
      override fun onFailure(exception: TerminalException) {
        // Placeholder for handling the exception
      }
    })

    Handling processing failures

    When processing a PaymentIntent fails, the SDK returns an error that includes the updated PaymentIntent. Your application should inspect the PaymentIntent to decide how to deal with the error.

    PaymentIntent Status Meaning Resolution
    requires_payment_method Payment method declined Try collecting a different payment method by calling collectPaymentMethod again with the same PaymentIntent.
    requires_confirmation Temporary connectivity problem Call processPayment again with the same PaymentIntent to retry the request.
    PaymentIntent is nil Request to Stripe timed out, unknown PaymentIntent status Retry processing the original PaymentIntent. Do not create a new one, as that could result in multiple authorizations for the cardholder.

    If you encounter multiple, consecutive timeouts, there might be a problem with your connectivity. Make sure that your app is able to communicate with the internet.

    Step 4: Capture the Payment Intent Server-side

    Stripe Terminal uses a two-step process to prevent unintended and duplicate payments. When the SDK returns a processed PaymentIntent to your app, the payment is authorized but not captured. Read the auth and capture documentation for more information about the difference.

    When your app receives a processed PaymentIntent from the SDK, make sure it notifies your backend to capture the PaymentIntent. Create an endpoint on your backend that accepts a PaymentIntent ID and sends a request to the Stripe API to capture it:

    curl https://api.stripe.com/v1/payment_intents/pi_H85ICVf0OCxPLobviOok/capture \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc:
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = Stripe::PaymentIntent.capture(
      'pi_H85ICVf0OCxPLobviOok'
    )
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    intent = stripe.PaymentIntent.capture(
      'pi_H85ICVf0OCxPLobviOok'
    )
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    $intent = \Stripe\PaymentIntent::retrieve('pi_H85ICVf0OCxPLobviOok');
    $intent->capture();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    PaymentIntent intent = PaymentIntent.retrieve("pi_H85ICVf0OCxPLobviOok");
    intent.capture();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    (async () => {
      await stripe.paymentIntents.capture('pi_H85ICVf0OCxPLobviOok')
    })();
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    intent, err := paymentintent.Capture("pi_H85ICVf0OCxPLobviOok")
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    var service = new PaymentIntentService();
    var intent = service.Capture("pi_H85ICVf0OCxPLobviOok");
    

    Reconciling payments

    To monitor the payments activity of your business, you may want to reconcile PaymentIntents with your internal orders system on your server at the end of a day’s activity.

    A PaymentIntent that retains a requires_capture status may represent two things:

    Unnecessary authorization on your customer’s card statement

    • Cause: User abandons your app’s checkout flow in the middle of a transaction
    • Solution: If the uncaptured PaymentIntent is not associated with a completed order on your server, you can cancel it. A canceled PaymentIntent can no longer be used to perform charges.

    Incomplete collection of funds from a customer

    • Cause: Failure of the request from your app notifying your backend to capture the PaymentIntent
    • Solution: If the uncaptured PaymentIntent is associated with a completed order on your server, and no other payment has been taken for the order (e.g., a cash payment), you can capture it.

    Next steps

    Congratulations! Your Terminal integration is now set up to collect in-person payments. Next, configure the customer-facing checkout experience in your app, or test your current integration with a physical reader.

    Was this page helpful?

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

    On this page