Using Payment Intents on iOS

    Learn how to build an iOS application that uses the Payment Intents API to accept card payments.

    Accepting card payments with the Payment Intents API and the iOS SDK is a five-step process, with server-side and client-side actions:

    1. Create a PaymentIntent on the server
    2. Pass the PaymentIntent’s client secret to the client
    3. Collect card information and create a STPPaymentMethodCardParams object
    4. Confirm the PaymentIntent and authenticate if necessary
    5. Asynchronously fulfill the customer’s order

    A diagram showing the auto confirmation server to client to webhooks flow

    Step 1: Create a PaymentIntent on the server

    Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.

    Create a PaymentIntent on your server with an amount and currency. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d amount=1099 \
      -d currency=usd
    
    # 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: 1099,
        currency: 'usd',
    })
    
    # 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=1099,
      currency='usd',
    )
    
    // 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::create([
        'amount' => 1099,
        'currency' => 'usd',
    ]);
    
    // 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";
    
    import com.stripe.model.PaymentIntent;
    import com.stripe.param.PaymentIntentCreateParams;
    
    PaymentIntentCreateParams createParams = new PaymentIntentCreateParams.Builder()
            .setCurrency("usd").setAmount(new Long(1099))
            .build();
    
    PaymentIntent intent = PaymentIntent.create(createParams);
    
    // 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 () => {
      const paymentIntent = await stripe.paymentIntents.create({
        amount: 1099,
        currency: 'usd',
      });
    })();
    
    // 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(1099),
      Currency: stripe.String(string(stripe.CurrencyUSD)),
    }
    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 = 1099,
        Currency = "usd",
    };
    service.Create(options);
    

    Step 2: Pass the PaymentIntent’s client secret to the client

    The PaymentIntent object contains a client secret, a unique key that you pass to your app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint that you set up in step 1 to create a PaymentIntent.

    MyAPIClient.createPaymentIntent(amount: 100, currency: "usd") { result in
      switch (result) {
        case .success(let clientSecret):
          // Hold onto clientSecret for Step 4
        case .failure(let error):
          // Handle the error
      }
    }
    [MyAPIClient createPaymentIntentWithAmount:100
                      currency:@"usd"
                      completion:^(NSString *clientSecret, NSError *error) {
                        if (error != nil) {
                          // Handle the error
                        } else {
                          // Hold onto clientSecret for Step 4
                        }
                      }];

    Each PaymentIntent typically correlates with a single “cart” or customer session in your application. You can create the PaymentIntent during checkout and store its ID on the user’s cart in the data model of your application, retrieving it again as necessary.

    You can use the client secret to complete the payment process with the amount specified on the PaymentIntent. Do not log it, embed it in URLs, or expose it to anyone other than the customer.

    Step 3: Collect card information

    If you already have a PaymentMethod or Source that you would like to associate with the PaymentIntent, such as a STPPaymentMethod from an STPPaymentContext integration, skip this step.

    In your application, collect payment information through your own form and pass the collected information into new STPPaymentMethodCardParams and STPPaymentMethodBillingDetails instances.

    let cardParams = STPPaymentMethodCardParams()
    let billingDetails = STPPaymentMethodBillingDetails()
    // Fill in card, billing details
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil)
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    STPPaymentMethodBillingDetails *billingDetails = [STPPaymentMethodBillingDetails new];
    // Fill in card, billing details
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:billingDetails metadata:nil];

    Apple Pay is supported. Instead of collecting card details, create an STPPaymentMethod from the PKPayment.

    Step 4: Confirm the PaymentIntent

    To initiate payment collection, you must confirm that your customer intends to pay with the provided payment details.

    Add the desired payment method to an STPPaymentIntentParams object initialized with the PaymentIntent’s client secret. There are 3 ways to do this:

    The following code demonstrates how to create the STPPaymentIntentParams object from an STPPaymentMethodParams instance.

    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
    paymentIntentParams.paymentMethodParams = paymentMethodParams
    STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:client_secret];
    paymentIntentParams.paymentMethodParams = paymentMethodParams;

    If you’d like to send additional data, you can assign values to the relevant properties on the STPPaymentIntentParams object. Any extra parameters that you add are included in the request to Stripe. For example, to send an email to the customer on payment confirmation, use the receiptEmail property.

    To finish confirming the payment and automatically handle any additional required actions for authentication, pass the STPPaymentIntentParams object to the confirmPayment method on STPPaymentHandler sharedManager.

    If the customer must perform 3D Secure authentication to complete the payment, STPPaymentHandler presents view controllers using the STPAuthenticationContext passed in and walks them through that process. See Supporting 3D Secure Authentication on iOS to learn more.

    The following sample code assumes that MyCheckoutViewController should present any additional view controllers and implements STPAuthenticationContext.

    let paymentManager = STPPaymentHandler.shared()
    paymentManager.confirmPayment(withParams: paymentIntentParams, authenticationContext: self, completion: { (status, paymentIntent, error) in
      switch (status) {
        case .failed:
          // Handle error
        case .canceled:
          // Handle cancel
        case .succeeded:
          // Payment Intent is confirmed
      }
    })
    [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams
                            withAuthenticationContext:self
                                            returnURL:nil
                                           completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * paymentIntent, NSError * error) {
        switch (status) {
            case STPPaymentHandlerActionStatusFailed:
                // Handle the error by inspecting paymentIntent.status
                break;
            case STPPaymentHandlerActionStatusCanceled:
                // Handle cancel
                break;
            case STPPaymentHandlerActionStatusSucceeded:
                // Payment Intent is confirmed
                break;
        }
    }];

    Step 5: Asynchronously fulfill the customer’s order

    You can use the status returned by STPPaymentHandler to provide immediate feedback to your customers when the payment completes on the client. However, your integration should not attempt to handle order fulfillment on the client side because it is possible for customers to leave the app after payment is complete but before the fulfillment process initiates. Instead, you will need to handle asynchronous events in order to be notified and drive fulfillment when the payment succeeds.

    There are several ways you can fulfill an order:

    When you attempt to collect payment from a customer, the PaymentIntent creates a Charge. You can inspect the PaymentIntent’s charges.data property to obtain the latest charge:

    # 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.retrieve('{{PAYMENT_INTENT_ID}}')
    charges = intent.charges.data
    
    # 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.retrieve('{{PAYMENT_INTENT_ID}}')
    charges = intent.charges.data
    
    // 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('{{PAYMENT_INTENT_ID}}');
    $charges = $intent->charges->data;
    
    // 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("{{PAYMENT_INTENT_ID}}");
    List<Charge> charges = intent.getCharges().getData();
    
    // 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 () => {
      const intent = await stripe.paymentIntents.retrieve('{{PAYMENT_INTENT_ID}}');
      const charges = intent.charges.data;
    })();
    
    // 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.Get("{{PAYMENT_INTENT_ID}}", nil)
    charges := intent.Charges.Data
    
    // 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.Get("{{PAYMENT_INTENT_ID}}");
    var charges = intent.Charges.Data;
    

    A PaymentIntent’s Charge property only contains the most recent charge. To view all of the charges associated with a PaymentIntent, list all charges with the payment_intent​ parameter specified. Note that in addition to the final successful charge, the list includes any unsuccessful charges created during the payment process.

    Next, test your integration to make sure you’re correctly handling cards that require additional authentication.

    Test the integration

    It’s important to thoroughly test your integration to make sure you’re correctly handling cards that require additional authentication and cards that don’t. Use these card numbers in test mode with any expiration date in the future and any three digit CVC code to validate your integration when authentication is required and when it’s not required.

    Number Authentication Description
    4000002500003155 Required on setup or first transaction This test card requires authentication for one-time payments. However, if you set up this card using the Setup Intents API and use the saved card for subsequent payments, no further authentication is needed.
    4000002760003184 Required This test card requires authentication on all transactions.
    4000008260003178 Required This test card requires authentication, but payments will be declined with an insufficient_funds failure code after successful authentication.
    4000000000003055 Supported This test card supports authentication via 3D Secure 2, but does not require it. Payments using this card do not require additional authentication in test mode unless your test mode Radar rules request authentication.

    Use these cards in your application or the payments demo to see the different behavior.

    Next steps

    You now have an iOS integration that can accept card payments with the Payment Intents API, prompting customers for authentication if needed.

    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