Using Payment Intents on iOS

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

    There are two ways to accept card payments with the Payment Intents APIThe Payment Intents API is a new way to build dynamic payment flows. It tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods. depending on how you want to complete the payment: automatic confirmation and manual confirmation. See the Payments Intents API Overview for a detailed comparison.

    • The automatic confirmation flow confirms the payment on the client and uses webhooks to inform you when the payment is successful.
    • The manual confirmation flow confirms the payment on the server and lets you fulfill on your server immediately after the payment succeeds.

    Apple Pay is supported in both flows. Use the STPToken object if you’re using the Custom UI Components in Step 2 of the automatic confirmation flow or Step 1 of the manual confirmation flow. If you’re using the Standard UI Components, you can use Apple Pay like any other STPPaymentResult source and skip these steps.

    Automatic confirmation

    Accepting a card payment with the Payment Intents API using automatic confirmation and the iOS SDK is a four-step process, with server-side and client-side actions:

    1. Create a PaymentIntent and make its client secret accessible to your application
    2. Collect card information and create a STPPaymentMethodCardParams object
    3. Confirm the PaymentIntent
    4. Redirect and authenticate the payment if necessary

    Step 1: Create a PaymentIntent and make its client secret accessible to your application

    When a customer begins the checkout process, create a PaymentIntent on your server. To use the PaymentIntent, you need to make its client secretThe client secret is a unique key associated with a single PaymentIntent. It is passed to the client side where it can be used in Stripe.js to complete the payment process. accessible to your mobile application. Typically, the best approach is to serve it from an HTTP endpoint on your server and then retrieve it on the client side.

    Step 2: Collect card information

    If you already have a payment method that you would like to associate with the PaymentIntent, such as a source from an STPPaymentContext integration, you can skip this step.

    In your application, collect card details from the customer. There are two ways to do this:

    Create the STPPaymentMethodParams object from STPPaymentMethodCardParams and STPPaymentMethodBillingDetails.

    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];

    Set the STPToken id as the token of the STPPaymentMethodCardParams object.

    let cardParams = STPPaymentMethodCardParams()
    cardParams.token = myApplePayToken.tokenId
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil)
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    cardParams.token = myApplePayToken.tokenId;
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:nil metadata:nil];

    Step 3: Confirm the PaymentIntent

    To initiate payment collection, you must confirmConfirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment. 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:

    If you are collecting details of a payment method that involves redirection as part of the authentication process, include a return_url to indicate where the PaymentIntent should return when authentication is complete. The next step has more information about handling redirection to authenticate payment methods.

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

    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
    paymentIntentParams.paymentMethodParams = paymentMethodParams
    paymentIntentParams.returnURL = "your-app://stripe-redirect"
    STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:client_secret];
    paymentIntentParams.paymentMethodParams = paymentMethodParams;
    paymentIntentParams.returnURL = "your-app://stripe-redirect";

    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 e-mail to the customer on payment confirmation, use the receiptEmail property.

    To finish confirming the payment, pass the STPPaymentIntentParams object to the confirmPaymentIntentWithParams method on a STAPIClient sharedClient:

    let client = STPAPIClient.shared()
    client.confirmPaymentIntent(with: paymentIntentParams, completion: { (paymentIntent, error) in
      if let error = error {
          // handle error
      } else if let paymentIntent = paymentIntent {
          // see below to handle the confirmed PaymentIntent
      }
    })
    [[STPAPIClient sharedClient] confirmPaymentIntentWithParams:paymentIntentParams
        completion:^(STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable error) {
        if (error != nil) {
            // handle error
        } else {
            // see below to handle the confirmed PaymentIntent
        }
    }];

    Step 4: Redirect and authenticate the payment if necessary

    Some payment methods may require additional authentication steps in order to complete a payment. Pass the PaymentIntent to STPRedirectContext and invoke startRedirectFlowFromViewController: if a redirect is required. The STPRedirectContext completion block is called after your customer returns to the application. At this point, the user may or may not have completed the authentication process. You can refetch the PaymentIntent and check its status for success.

    if paymentIntent.status == .requiresAction {
        guard let redirectContext = STPRedirectContext(paymentIntent: paymentIntent, completion: { clientSecret, redirectError in
            // Fetch the latest status of the Payment Intent if necessary
            STPAPIClient.shared().retrievePaymentIntent(withClientSecret: clientSecret) { paymentIntent, error in
                // Check paymentIntent.status
            }
        }) else {
            // This PaymentIntent action is not yet supported by the SDK.
            return;
        }
        // Note you must retain this for the duration of the redirect flow - it dismisses any presented view controller upon deallocation.
        self.redirectContext = redirectContext
    
        // opens SFSafariViewController to the necessary URL
        redirectContext.startRedirectFlow(from: self)
    } else {
        // Show success message
    }
    if (paymentIntent.status == STPPaymentIntentStatusRequiresAction) {
        // Note you must retain this for the duration of the redirect flow - it dismisses any presented view controller upon deallocation.
        self.redirectContext = [[STPRedirectContext alloc] initWithPaymentIntent:paymentIntent completion:^(NSString *clientSecret, NSError *redirectError) {
            // Fetch the latest status of the Payment Intent if necessary
            [[STPAPIClient sharedClient] retrievePaymentIntentWithClientSecret:clientSecret completion:^(STPPaymentIntent *paymentIntent, NSError *error) {
                // Check paymentIntent.status
            }];
        }];
        if (self.redirectContext) {
            // opens SFSafariViewController to the necessary URL
            [self.redirectContext startRedirectFlowFromViewController:self];
        } else {
            // This PaymentIntent action is not yet supported by the SDK.
        }
    } else {
        // Show success message
    }

    You’ll also need to set up your app delegate to forward the return_url you provided in Step 3 to the Stripe SDK:

    // This method handles opening native URLs (e.g., "your-app://")
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        let stripeHandled = Stripe.handleURLCallback(with: url)
        if (stripeHandled) {
            return true
        } else {
            // This was not a stripe url – do whatever url handling your app
            // normally does, if any.
        }
        return false
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
            if let url = userActivity.webpageURL {
                let stripeHandled = Stripe.handleURLCallback(with: url)
                if (stripeHandled) {
                    return true
                } else {
                    // This was not a stripe url – do whatever url handling your app
                    // normally does, if any.
                }
            }
        }
        return false
    }
    // This method handles opening native URLs (e.g., "your-app://")
    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
        BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url];
        if (stripeHandled) {
            return YES;
        } else {
            // This was not a stripe url – do whatever url handling your app
            // normally does, if any.
        }
        return NO;
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
        if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
            if (userActivity.webpageURL) {
                BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url];
                if (stripeHandled) {
                    return YES;
                } else {
                    // This was not a stripe url – do whatever url handling your app
                    // normally does, if any.
                }
                return NO;
            }
        }
    }


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

    Manual confirmation

    Accepting a card payment with the Payment Intents API using manual confirmation and the iOS SDK is a four-step process, with server-side and client-side actions:

    1. Collect card information and create a PaymentMethod
    2. Create and confirm a PaymentIntent on the server
    3. Redirect and authenticate the payment if necessary
    4. Confirm the PaymentIntent again on the server

    Step 1: Collect card information

    If you already have a payment method that you would like to associate with the PaymentIntent, such as a source from an STPPaymentContext integration, you can skip this step.

    In your application, collect card details from the customer and create a PaymentMethod. There are two ways to do this:

    Create a PaymentMethod object from STPPaymentMethodCardParams and STPPaymentMethodBillingDetails.

    let cardParams = STPPaymentMethodCardParams()
    let billingDetails = STPPaymentMethodBillingDetails()
    // Fill in card, billing details
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil)
    STPAPIClient.shared().createPaymentMethod(with: paymentMethodParams, completion: completionClosure)
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    STPPaymentMethodBillingDetails *billingDetails = [STPPaymentMethodBillingDetails new];
    // Fill in card, billing details
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:billingDetails metadata:nil];
    [[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams completion:completionBlock];

    Create a PaymentMethod object by passing the STPToken id as the token of the STPPaymentMethodCardParams object.

    let cardParams = STPPaymentMethodCardParams()
    cardParams.token = myApplePayToken.tokenId
    let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil)
    STPAPIClient.shared().createPaymentMethod(with: paymentMethodParams, completion: completionClosure)
    STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new];
    cardParams.token = myApplePayToken.tokenId;
    STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:nil metadata:nil];
    [[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams completion:completionBlock];

    Step 2: Create and confirm a PaymentIntent on the server

    Set up an endpoint on your server to receive the request. This endpoint will also be used in Step 4 to handle cards that require an extra step of authentication. It returns a PaymentIntent back to the client.

    Set your endpoint to additionally receive a return_url from the client.

    If you are migrating an existing STPPaymentContext integration, your endpoint creates the PaymentIntent by passing the STPaymentResult source id received from the client as the source parameter.

    Step 3: Redirect and authenticate the payment if necessary

    Some payment methods may require additional authentication steps in order to complete a payment. Pass the PaymentIntent to STPRedirectContext and invoke startRedirectFlowFromViewController if a redirect is required. The STPRedirectContext completion block is called after your customer returns to the application. At this point, the user may or may not have completed the authentication process. You can refetch the PaymentIntent and check its status for success.

    if paymentIntent.status == .requiresAction {
        guard let redirectContext = STPRedirectContext(paymentIntent: paymentIntent, completion: { clientSecret, redirectError in
            // Fetch the latest status of the Payment Intent if necessary
            STPAPIClient.shared().retrievePaymentIntent(withClientSecret: clientSecret) { paymentIntent, error in
                // Check paymentIntent.status
            }
        }) else {
            // This PaymentIntent action is not yet supported by the SDK.
            return;
        }
        // Note you must retain this for the duration of the redirect flow - it dismisses any presented view controller upon deallocation.
        self.redirectContext = redirectContext
    
        // opens SFSafariViewController to the necessary URL
        redirectContext.startRedirectFlow(from: self)
    } else {
        // Show success message
    }
    if (paymentIntent.status == STPPaymentIntentStatusRequiresAction) {
        // Note you must retain this for the duration of the redirect flow - it dismisses any presented view controller upon deallocation.
        self.redirectContext = [[STPRedirectContext alloc] initWithPaymentIntent:paymentIntent completion:^(NSString *clientSecret, NSError *redirectError) {
            // Fetch the latest status of the Payment Intent if necessary
            [[STPAPIClient sharedClient] retrievePaymentIntentWithClientSecret:clientSecret completion:^(STPPaymentIntent *paymentIntent, NSError *error) {
                // Check paymentIntent.status
            }];
        }];
        if (self.redirectContext) {
            // opens SFSafariViewController to the necessary URL
            [self.redirectContext startRedirectFlowFromViewController:self];
        } else {
            // This PaymentIntent action is not yet supported by the SDK.
        }
    } else {
        // Show success message
    }

    You’ll also need to set up your app delegate to forward the return_url to the Stripe SDK:

    // This method handles opening native URLs (e.g., "your-app://")
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        let stripeHandled = Stripe.handleURLCallback(with: url)
        if (stripeHandled) {
            return true
        } else {
            // This was not a stripe url – do whatever url handling your app
            // normally does, if any.
        }
        return false
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
            if let url = userActivity.webpageURL {
                let stripeHandled = Stripe.handleURLCallback(with: url)
                if (stripeHandled) {
                    return true
                } else {
                    // This was not a stripe url – do whatever url handling your app
                    // normally does, if any.
                }
            }
        }
        return false
    }
    // This method handles opening native URLs (e.g., "your-app://")
    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
        BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url];
        if (stripeHandled) {
            return YES;
        } else {
            // This was not a stripe url – do whatever url handling your app
            // normally does, if any.
        }
        return NO;
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
        if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
            if (userActivity.webpageURL) {
                BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url];
                if (stripeHandled) {
                    return YES;
                } else {
                    // This was not a stripe url – do whatever url handling your app
                    // normally does, if any.
                }
                return NO;
            }
        }
    }

    Step 4: Confirm the PaymentIntent again on the server

    Using the same endpoint you set up in Step 2, confirm the PaymentIntent again to finalize the payment and fulfill the order.

    Test the integration

    It’s important to thoroughly test your Payment Intents API integration to make sure you’re correctly handling cards that require additional authentication and cards that don’t to ensure the smoothest checkout experience for your users. We have test cards you can use in test mode to simulate different types of cards.

    The two test cards you should use to validate your integration are 3D Secure required and 3D Secure not required cards. You can use these card numbers with any expiration date in the future and any three digit CVC code.

    Card Number 3D Secure usage Description
    4000000000003220 Required This test card requires 3D Secure 2 on all transactions and will trigger 3D Secure 2 in test mode.
    4000000000003055 Supported This test card supports but does not require 3D Secure 2 and will not require additional authentication steps in test mode.

    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.

    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