# Accept in-app payments Build a customised payments integration in your iOS Android or React Native app using the Payment Element. The Payment Element is a customisable component that renders a list of payment methods that you can add into any screen in your app. When customers interact with payment methods in the list, the component opens individual bottom sheets to collect payment details. > #### Accounts v2 API support > > The Payment Sheet doesn’t support *customer-configured Accounts* (Account configurations represent role-based functionality that you can enable for accounts, such as merchant, customer, or recipient). It only supports `Customer` objects. # Collect and save a payment method A SetupIntent flow allows you to save payment methods for future payments without creating a charge. In this integration, you render the Payment Element, create a *SetupIntent* (The Setup Intents API lets you build dynamic flows for collecting payment method details for future payments. It tracks the lifecycle of a payment setup flow and can trigger additional authentication steps if required by law or by the payment method) and save the payment method in your app. ## Set up Stripe [Server-side] [Client-side] ### Server-side This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server: #### Ruby ```bash # Available as a gem sudo gem install stripe ``` ```ruby # If you use bundler, you can add this line to your Gemfile gem 'stripe' ``` ### Client-side The [Stripe iOS SDK](https://github.com/stripe/stripe-ios) is open source, [fully documented](https://stripe.dev/stripe-ios/index.html), and compatible with apps supporting iOS 13 or above. #### Swift Package Manager To install the SDK, follow these steps: 1. In Xcode, select **File** > **Add Package Dependencies…** and enter `https://github.com/stripe/stripe-ios-spm` as the repository URL. 1. Select the latest version number from our [releases page](https://github.com/stripe/stripe-ios/releases). 1. Add the **StripePaymentSheet** product to the [target of your app](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app). #### CocoaPods 1. If you haven’t already, install the latest version of [CocoaPods](https://guides.cocoapods.org/using/getting-started.html). 1. If you don’t have an existing [Podfile](https://guides.cocoapods.org/syntax/podfile.html), run the following command to create one: ```bash pod init ``` 1. Add this line to your `Podfile`: ```podfile pod 'StripePaymentSheet' ``` 1. Run the following command: ```bash pod install ``` 1. Don’t forget to use the `.xcworkspace` file to open your project in Xcode, instead of the `.xcodeproj` file, from here on out. 1. In the future, to update to the latest version of the SDK, run: ```bash pod update StripePaymentSheet ``` #### Carthage 1. If you haven’t already, install the latest version of [Carthage](https://github.com/Carthage/Carthage#installing-carthage). 1. Add this line to your `Cartfile`: ```cartfile github "stripe/stripe-ios" ``` 1. Follow the [Carthage installation instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos). Make sure to embed all of the required frameworks listed [here](https://github.com/stripe/stripe-ios/tree/master/StripePaymentSheet/README.md#manual-linking). 1. In the future, to update to the latest version of the SDK, run the following command: ```bash carthage update stripe-ios --platform ios ``` #### Manual Framework 1. Head to our [GitHub releases page](https://github.com/stripe/stripe-ios/releases/latest) and download and unzip **Stripe.xcframework.zip**. 1. Drag **StripePaymentSheet.xcframework** to the **Embedded Binaries** section of the **General** settings in your Xcode project. Make sure to select **Copy items if needed**. 1. Repeat step 2 for all required frameworks listed [here](https://github.com/stripe/stripe-ios/tree/master/StripePaymentSheet/README.md#manual-linking). 1. In the future, to update to the latest version of our SDK, repeat steps 1–3. > For details on the latest SDK release and past versions, see the [Releases](https://github.com/stripe/stripe-ios/releases) page on GitHub. To receive notifications when a new release is published, [watch releases](https://help.github.com/en/articles/watching-and-unwatching-releases-for-a-repository#watching-releases-for-a-repository) for the repository. You also need to set your [publishable key](https://dashboard.stripe.com/apikeys) so that the SDK can make API calls to Stripe. To get started, you can hard-code the publishable key on the client while you’re integrating, but fetch the publishable key from your server in production. ```swift // Set your publishable key: remember to change this to your live publishable key in production // See your keys here: https://dashboard.stripe.com/apikeys STPAPIClient.shared.publishableKey = "<>" ``` ## Enable payment methods View your [payment methods settings](https://dashboard.stripe.com/settings/payment_methods) and enable the payment methods you want to support. You need at least one payment method enabled to create a *SetupIntent* (The Setup Intents API lets you build dynamic flows for collecting payment method details for future payments. It tracks the lifecycle of a payment setup flow and can trigger additional authentication steps if required by law or by the payment method). By default, Stripe enables cards and other prevalent payment methods that can help you reach more customers, but we recommend turning on additional payment methods that are relevant for your business and customers. See [Payment method support](https://docs.stripe.com/payments/payment-methods/payment-method-support.md) for product and payment method support, and our [pricing page](https://stripe.com/pricing/local-payment-methods) for fees. ## Create a customer [Server-side] To set up a payment method for future payments, you must attach it to an object that represents your customer. When your customer creates an account or has their first transaction with your business, create a [Customer](https://docs.stripe.com/api/customers/create.md) object. ```curl curl -X POST https://api.stripe.com/v1/customers \ -u "<>:" ``` ## Collect payment details [Client-side] The Embedded Mobile Payment Element is designed to be placed on the checkout page of your native mobile app. The element displays a list of payment methods and you can customise it to match your app’s look and feel. When the customer taps the **Card** row, it opens a sheet where they can enter their payment method details. The button in the sheet says **Continue** by default and dismisses the sheet when tapped, which lets your customer finish the setup in your checkout. ![Payment Element](https://b.stripecdn.com/docs-statics-srv/assets/ios-embedded.b5867c116d537ffcb920dd80ebdfc741.png) You can configure the button to immediately complete the setup instead of continuing. To do so, complete [this step](https://docs.stripe.com/payments/mobile/accept-payment-embedded.md#embedded-let-customer-pay-immediately) after following the guide. #### UIKit ### Initialise the Payment Element Call `create` to instantiate EmbeddedPaymentElement with a `EmbeddedPaymentElement.Configuration` and a [PaymentSheet.IntentConfiguration](https://github.com/stripe/stripe-ios/blob/master/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetIntentConfiguration.swift). The Configuration object contains general-purpose configuration options for EmbeddedPaymentElement that don’t change between payments, like `returnURL`. The `IntentConfiguration` object contains details about the specific payment like the amount and currency, as well as a `confirmationTokenConfirmHandler` callback. For now, leave its implementation empty. After it successfully initialises, set its `presentingViewController` and `delegate` properties. ```swift import StripePaymentSheet class MyCheckoutVC: UIViewController { func createEmbeddedPaymentElement() async throws -> EmbeddedPaymentElement { let intentConfig = PaymentSheet.IntentConfiguration( mode: .setup(currency: "USD") ) { [weak self] confirmationToken in return await self?.handleConfirmationToken(confirmationToken) } var configuration = EmbeddedPaymentElement.Configuration() configuration.returnURL = "your-app://stripe-redirect" // Use the return url you set up in the previous step let embeddedPaymentElement = try await EmbeddedPaymentElement.create(intentConfiguration: intentConfig, configuration: configuration) embeddedPaymentElement.presentingViewController = self embeddedPaymentElement.delegate = self return embeddedPaymentElement } func handleConfirmationToken(_ confirmationToken: STPConfirmationToken) async throws -> String { // You'll implement this in the "Confirm the payment" section below } } ``` ### Add the Payment Element view After EmbeddedPaymentElement successfully initialises, put its view in your checkout UI. > The view must be contained within a scrollable view such as UIScrollView because it doesn’t have a fixed size and can change height after it initially renders. ```swift class MyCheckoutVC: UIViewController { // ... private(set) var embeddedPaymentElement: EmbeddedPaymentElement? private lazy var checkoutButton: UIButton = { let checkoutButton = UIButton(type: .system) checkoutButton.backgroundColor = .systemBlue checkoutButton.layer.cornerRadius = 5.0 checkoutButton.clipsToBounds = true checkoutButton.setTitle("Checkout", for: .normal) checkoutButton.setTitleColor(.white, for: .normal) checkoutButton.translatesAutoresizingMaskIntoConstraints = false checkoutButton.isEnabled = embeddedPaymentElement?.paymentOption != nil checkoutButton.addTarget(self, action: #selector(didTapConfirmButton), for: .touchUpInside) return checkoutButton }() // ... @objc func didTapConfirmButton() { // You'll implement this in the "Confirm the payment" section below } override func viewDidLoad() { super.viewDidLoad() Task { @MainActor in do { // Create a UIScrollView let scrollView = UIScrollView() scrollView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(scrollView) // Create the Payment Element let embeddedPaymentElement = try await createEmbeddedPaymentElement() embeddedPaymentElement.delegate = self embeddedPaymentElement.presentingViewController = self self.embeddedPaymentElement = embeddedPaymentElement // Add its view to the scroll view scrollView.addSubview(embeddedPaymentElement.view) // Add your own checkout button to the scroll view scrollView.addSubview(checkoutButton) // Set up layout constraints embeddedPaymentElement.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), embeddedPaymentElement.view.topAnchor.constraint(equalTo: scrollView.topAnchor), embeddedPaymentElement.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), embeddedPaymentElement.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), checkoutButton.topAnchor.constraint(equalTo: embeddedPaymentElement.view.bottomAnchor, constant: 4.0), checkoutButton.leadingAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.leadingAnchor, constant: 4.0), checkoutButton.trailingAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.trailingAnchor, constant: -4.0), ]) } catch { // Handle view not being added to view } } } } ``` At this point you can run your app and see the Embedded Mobile Payment Element. ### Handle height changes The EmbeddedPaymentElement’s view might grow or shrink in size, which can impact the layout of the view. Handle height changes by implementing the `embeddedPaymentElementDidUpdateHeight` delegate method. EmbeddedPaymentElement’s view calls this method inside an animation block that updates its height. Your implementation is expected to call `setNeedsLayout()` and `layoutIfNeeded()` on the scroll view that contains the EmbeddedPaymentElement’s view to enable a smooth animation of the height change. ```swift extension MyCheckoutVC: EmbeddedPaymentElementDelegate { func embeddedPaymentElementDidUpdateHeight(embeddedPaymentElement: StripePaymentSheet.EmbeddedPaymentElement) { // Handle layout appropriately self.view.setNeedsLayout() self.view.layoutIfNeeded() } } ``` We recommend that you test that your view properly responds to changes in height. To do this, call `testHeightChange()` on EmbeddedPaymentElement to simulate showing and hiding a mandate within the element. Make sure that after calling `testHeightChange()`, your scroll view adjusts smoothly. ```swift class MyCheckoutVC: UIViewController { override func viewDidLoad() { // This is only for testing purposes: #if DEBUG Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in Task { @MainActor in self?.embeddedPaymentElement?.testHeightChange() } } #endif } } ``` ### (Optional) Display the selected payment option If you need to access details about the customer’s selected payment option like a label (for example, “····4242”), image (for example, a VISA logo), or billing details to display in your UI, use the EmbeddedPaymentElement’s `paymentOption` property. To be notified when the `paymentOption` changes, implement the `embeddedPaymentElementDidUpdatePaymentOption` delegate method. ```swift extension MyCheckoutVC: EmbeddedPaymentElementDelegate { func embeddedPaymentElementDidUpdatePaymentOption(embeddedPaymentElement: EmbeddedPaymentElement) { print("The payment option changed: \(embeddedPaymentElement.paymentOption)") checkoutButton.isEnabled = embeddedPaymentElement.paymentOption != nil } } ``` ### (Optional) Update payment details As the customer performs actions that change the payment details (for example, applying a discount code), update the `EmbeddedPaymentElement` instance to reflect the new values by calling the `update` method. Some payment methods, like Apple Pay and Google Pay, show the amount in the UI, so make sure it’s always accurate and up to date. When the update call completes, update your UI. The update call might change the customer’s currently selected payment option. ```swift extension MyCheckoutVC { func update() { Task { @MainActor in var updatedIntentConfig = oldIntentConfig // Update the currency updatedIntentConfig.mode = PaymentSheet.IntentConfiguration.Mode.setup(currency: "USD") let result = await embeddedPaymentElement?.update(intentConfiguration: updatedIntentConfig) switch result { case .canceled, nil: // Do nothing; this happens when a subsequent `update` call cancels this one break case .failed(let error): // Display error to user in an alert, let users retry case .succeeded: // Update your UI in case the payment option changed } } } } ``` ### Confirm the setup When the customer taps the checkout button, call `embeddedPaymentElement.confirm()` to complete the setup. Be sure to disable user interaction during confirmation. ```swift extension MyCheckoutVC { @objc func didTapConfirmButton() { Task { @MainActor in guard let embeddedPaymentElement else { return } self.view.isUserInteractionEnabled = false // Disable user interaction, show a spinner, and so on before calling confirm. let result = await embeddedPaymentElement.confirm() switch result { case .completed: // Setup completed - show a confirmation screen. case .failed(let error): self.view.isUserInteractionEnabled = true // Encountered an unrecoverable error. You can display the error to the user, log it, and so on. case .canceled: self.view.isUserInteractionEnabled = true // Customer canceled - you should probably do nothing. break } } } } ``` Next, implement the `confirmationTokenConfirmHandler` callback you passed to `PaymentSheet.IntentConfiguration` earlier to send a request to your server. Your server creates a SetupIntent and returns its client secret. For information about this process, see [Create a SetupIntent](https://docs.stripe.com/payments/mobile/accept-payment-embedded.md#submit-setup). When the request returns, return your server response’s client secret or throw an error. The EmbeddedPaymentElement confirms the PaymentIntent using the client secret or displays the localised error message in its UI (either [errorDescription](https://developer.apple.com/documentation/foundation/localizederror/2946895-errordescription) or [localizedDescription](https://developer.apple.com/documentation/foundation/nserror/1414418-localizeddescription)). After confirmation completes, EmbeddedPaymentElement isn’t usable. Instead, direct the user to a receipt screen or something similar. ```swift extension MyCheckoutVC { func handleConfirmationToken(_ confirmationToken: STPConfirmationToken) async throws -> String { // Make a request to your own server. Pass `confirmationToken.stripeId` if using server-side confirmation, and return the client secret or throw an error. let myServerClientSecret = try await fetchIntentClientSecret(...) } } ``` #### SwiftUI ### Initialise the Payment Element Call `load` to load EmbeddedPaymentElementViewModel with a `EmbeddedPaymentElement.Configuration` and a [PaymentSheet.IntentConfiguration](https://github.com/stripe/stripe-ios/blob/master/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetIntentConfiguration.swift). The Configuration object contains general-purpose configuration options for EmbeddedPaymentElement that don’t change between payments, like `returnURL`. The `IntentConfiguration` object contains details about the specific payment like the amount and currency, as well as a `confirmationTokenConfirmHandler` callback. For now, leave its implementation empty. ```swift import SwiftUI import StripePaymentSheet struct MyEmbeddedCheckoutView: View { // Store an `EmbeddedPaymentElementViewModel` as a `@StateObject` @StateObject var embeddedViewModel = EmbeddedPaymentElementViewModel() var body: some View { ScrollView { // Empty scroll view for now } .task { do { if !embeddedViewModel.isLoaded { // Load the view model with your configuration try await loadEmbeddedViewModel() } } catch { // On load failure, implement retry logic (automatic retry or user-triggered through UI). } } } private func loadEmbeddedViewModel() async throws { let intentConfiguration = PaymentSheet.IntentConfiguration( mode: .setup(currency: "USD") ) { confirmationToken in try await self.handleConfirmationToken(confirmationToken) } var configuration = EmbeddedPaymentElement.Configuration() configuration.returnURL = "your-app://stripe-redirect" // Use the return url you set up in the previous step try await embeddedViewModel.load(intentConfiguration: intentConfiguration, configuration: configuration) } func handleConfirmationToken(_ confirmationToken: STPConfirmationToken) async throws -> String { // You'll implement this in the "Confirm the payment" section below } } ``` ### Add the Payment Element view After EmbeddedPaymentElementViewModel successfully loads, add EmbeddedPaymentElementView in your checkout UI. > The view must be contained within a scrollable view such as ScrollView because it doesn’t have a fixed size and can change height after it initially renders. ```swift struct MyEmbeddedCheckoutView: View { var body: some View { ScrollView { // isLoaded becomes true only after a successful call to `embeddedViewModel.load()` if embeddedViewModel.isLoaded { EmbeddedPaymentElementView(viewModel: embeddedViewModel) } } } } ``` At this point you can run your app and see the Embedded Mobile Payment Element. ### Handle height changes The EmbeddedPaymentElementView might grow or shrink in size, which might impact the layout of the view. The EmbeddedPaymentElementView handles this automatically when inside a ScrollView. We recommend that you test that your view properly responds to changes in height. To do this, call `testHeightChange()` on EmbeddedPaymentElementViewModel to simulate showing and hiding a mandate within the element. Make sure that after calling `testHeightChange()`, your scroll view adjusts smoothly. ```swift struct MyEmbeddedCheckoutView: View { var body: some View { ScrollView { if embeddedViewModel.isLoaded { EmbeddedPaymentElementView(viewModel: embeddedViewModel) // For testing only #if DEBUG Button("Test height change") { embeddedViewModel.testHeightChange() } #endif } } } } ``` ### (Optional) Display the selected payment option If you need to access details about the customer’s selected payment option like a label (for example, “····4242”), image (for example, a VISA logo), or billing details to display in your UI, use the EmbeddedPaymentElementViewModel’s `paymentOption` property. When the customer selects a payment method that opens a form sheet, the payment option updates after they tap **Continue** in the sheet. ```swift struct MyEmbeddedCheckoutView: View { var body: some View { ScrollView { if embeddedViewModel.isLoaded { // ... // A real integration probably wouldn't show the selected payment option on the same screen as the embedded payment element. We display it as an example. if let paymentOption = embeddedViewModel.paymentOption { HStack { Image(uiImage: paymentOption.image) .resizable() .aspectRatio(contentMode: .fit) .frame(height: 30) Text(paymentOption.label) Spacer() } .padding() } } } } } ``` ### (Optional) Update payment details As the customer performs actions that change the payment details (for example, applying a discount code), update the `EmbeddedPaymentElementViewModel` instance to reflect the new values by calling the `update` method. Some payment methods, like Apple Pay and Google Pay, show the amount in the UI, so make sure it’s always accurate and up to date. When the update completes, the update might have changed the customer’s currently selected payment option. ```swift extension MyEmbeddedCheckoutView { func update() { Task { @MainActor in var updatedIntentConfig = oldIntentConfig updatedIntentConfig.mode = PaymentSheet.IntentConfiguration.Mode.setup(currency: "USD") let result = await embeddedViewModel.update(intentConfiguration: updatedIntentConfig) switch result { case .canceled: // Do nothing; this happens when a subsequent `update` call cancels this one break case .failed(let error): // Display error to user in an alert, let users retry case .succeeded: // Update your UI in case the payment option changed } } } } ``` ### Confirm the setup When the customer taps the checkout button, call `embeddedViewModel.confirm()` to complete the setup. Be sure to disable user interaction during confirmation. ```swift extension MyEmbeddedCheckoutView { func didTapConfirmButton() { Task { @MainActor in // Be sure to disable user interaction during confirm (not shown in this example) let result = await embeddedViewModel.confirm() switch result { case .completed: // Setup completed - show a confirmation screen. case .failed(let error): // Encountered an unrecoverable error. Re-enable user interaction, display the error to the user, log it, and so on. case .canceled: // Customer canceled - re-enable user interaction and let them try again. break } } } } ``` Next, implement the `confirmationTokenConfirmHandler` callback you passed to `PaymentSheet.IntentConfiguration` earlier to send a request to your server. Your server creates a SetupIntent and returns its client secret. For information about this process, see [Create a SetupIntent](https://docs.stripe.com/payments/mobile/accept-payment-embedded.md#submit-setup). Return your server response’s client secret or throw an error. The EmbeddedPaymentElement confirms the PaymentIntent using the client secret or displays the localised error message in its UI (either [errorDescription](https://developer.apple.com/documentation/foundation/localizederror/2946895-errordescription) or [localizedDescription](https://developer.apple.com/documentation/foundation/nserror/1414418-localizeddescription)). After confirmation completes, EmbeddedPaymentElement isn’t usable. Instead, direct the user to a receipt screen or something similar. ```swift extension MyEmbeddedCheckoutView { func handleConfirmationToken(_ confirmationToken: STPConfirmationToken) async throws -> String { // Make a request to your own server. Pass confirmationToken.stripeId if using server-side confirmation. // Return the client secret or throw an error return try await MyAPIClient.shared.createIntent(confirmationTokenId: confirmationToken.stripeId) } } ``` ## Optional: Clear the selected payment option If you have payment options external to EmbeddedPaymentElement, you might need to clear the selected payment option. To do this, use the `clearPaymentOption` API to deselect the selected payment option. #### UIKit ```swift extension MyCheckoutVC: UIViewController { func deselectPaymentMethod() { embeddedPaymentElement?.clearPaymentOption() } } ``` #### SwiftUI ```swift @available(iOS 15.0, *) extension MyEmbeddedCheckoutView { func deselectPaymentMethod() { embeddedViewModel.clearPaymentOption() } } ``` ## Optional: Display the mandate yourself By default, the Embedded Mobile Payment Element displays mandates and legal disclaimers to ensure regulatory compliance. This text must be located close to your buy button. If necessary, disable its display in the view and display it yourself instead. > Your integration must display the mandate text to be compliant. Make sure URLs in the text can be opened by using a UITextView or something similar. #### UIKit ```swift var configuration = EmbeddedPaymentElement.Configuration(...) configuration.embeddedViewDisplaysMandateText = true ... let mandateTextView = UITextView() mandateTextView.attributedText = embeddedPaymentElement.paymentOption.mandateText ``` #### SwiftUI ```swift var configuration = EmbeddedPaymentElement.Configuration(...) configuration.embeddedViewDisplaysMandateText = true ... if let attributedText = embeddedViewModel.paymentOption?.mandateText { Text(AttributedString(attributedText)) } ``` ## Optional: Let the customer confirm setup immediately in the sheet To configure the button in the form sheet to immediately confirm setup, set `formSheetAction` on your `EmbeddedPaymentElement.Configuration` object. The completion block executes with the result of the setup after the sheet dismisses. The embedded UI isn’t usable after setup completes, so we recommend that your implementation directs the user to a different screen, such as a receipt screen. ```swift var configuration = EmbeddedPaymentElement.Configuration() configuration.formSheetAction = .confirm(completion: { result in switch result { case .completed: // Setup completed. You can for example, show a confirmation screen. print("Completed") case .failed(let error): // Encountered an unrecoverable error. You can display the error to the user, log it, etc. print(error) case .canceled: // Customer canceled - you should probably do nothing. break } }) ``` ## Create a SetupIntent [Server-side] On your server, create a *SetupIntent* (The Setup Intents API lets you build dynamic flows for collecting payment method details for future payments. It tracks the lifecycle of a payment setup flow and can trigger additional authentication steps if required by law or by the payment method). You can manage payment methods from the [Dashboard](https://dashboard.stripe.com/settings/payment_methods). Stripe evaluates payment method restrictions and other parameters to determine the list of supported payment methods. If the call succeeds, return the SetupIntent *client secret* (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer)). If the call fails, [handle the error](https://docs.stripe.com/error-handling.md) and return an error message with a brief explanation for your customer. > Verify that all IntentConfiguration properties match your SetupIntent (for example, [usage](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-usage)). #### Ruby ```ruby require 'stripe' # Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. client = Stripe::StripeClient.new('<>') post '/create-intent' do data = JSON.parse request.body.read params = { customer: ..., # The Customer ID you previously created automatic_payment_methods: {enabled: true}, } begin intent = client.v1.setup_intents.create(params) {client_secret: intent.client_secret}.to_json rescue Stripe::StripeError => e {error: e.error.message}.to_json end end ``` ## Set up a return URL [Client-side] The customer might navigate away from your app to authenticate (for example, in Safari or their banking app). To allow them to automatically return to your app after authenticating, [configure a custom URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) and set up your app delegate to forward the URL to the SDK. Stripe doesn’t support [universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content). #### SceneDelegate #### Swift ```swift // This method handles opening custom URL schemes (for example, "your-app://stripe-redirect") func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { guard let url = URLContexts.first?.url else { return } let stripeHandled = StripeAPI.handleURLCallback(with: url) if (!stripeHandled) { // This was not a Stripe url – handle the URL normally as you would } } ``` #### AppDelegate #### Swift ```swift // This method handles opening custom URL schemes (for example, "your-app://stripe-redirect") func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { let stripeHandled = StripeAPI.handleURLCallback(with: url) if (stripeHandled) { return true } else { // This was not a Stripe url – handle the URL normally as you would } return false } ``` #### SwiftUI #### Swift ```swift @main struct MyApp: App { var body: some Scene { WindowGroup { Text("Hello, world!").onOpenURL { incomingURL in let stripeHandled = StripeAPI.handleURLCallback(with: incomingURL) if (!stripeHandled) { // This was not a Stripe url – handle the URL normally as you would } } } } } ``` Additionally, set the [returnURL](https://github.com/stripe/stripe-ios/blob/aa3234a7fafde98c9203b6ed77e0278c04310eb0/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift#L70) on your [EmbeddedPaymentElement.Configuration](https://github.com/stripe/stripe-ios/blob/aa3234a7fafde98c9203b6ed77e0278c04310eb0/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift#L12) object to the URL for your app. ```swift var configuration = EmbeddedPaymentElement.Configuration() configuration.returnURL = "your-app://stripe-redirect" ``` ## Charge the saved payment method later [Server-side] > `bancontact` and `ideal` are one-time payment methods by default. When set up for future usage, they generate a `sepa_debit` reusable payment method type so you need to use `sepa_debit` to query for saved payment methods. To find a payment method to charge, list the payment methods associated with your customer. This example lists cards but you can list any supported [type](https://docs.stripe.com/api/payment_methods/object.md#payment_method_object-type). > #### Use the Accounts v2 API to represent customers > > The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a [specify a preview version](https://docs.stripe.com/api-v2-overview.md#sdk-and-api-versioning) in your code. > > To request access to the Accounts v2 preview, > > For most use cases, we recommend [modelling your customers as customer-configured Account objects](https://docs.stripe.com/accounts-v2/use-accounts-as-customers.md) instead of using [Customer](https://docs.stripe.com/api/customers.md) objects. #### Accounts v2 ```curl curl -G https://api.stripe.com/v1/payment_methods \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d type=card ``` #### Customers v1 ```curl curl -G https://api.stripe.com/v1/payment_methods \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d type=card ``` When you’re ready to charge your customer *off-session* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information), use the ID of the `Customer` or customer-configured `Account` and the `PaymentMethod` ID to create a `PaymentIntent` with the amount and currency of the payment. Set a few other parameters to make the off-session payment: - Set [off_session](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-off_session) to true to indicate that the customer isn’t in your checkout flow to respond to any authentication requests. If, during your checkout flow, a partner (such as a card issuer or bank) requests authentication, Stripe requests exemptions using customer information from a previous *on-session* (A payment is described as on-session if it occurs while the customer is actively in your checkout flow and able to authenticate the payment method) transaction. If the conditions for exemption aren’t met, the `PaymentIntent` might throw an error. - Set [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) to true to trigger confirmation immediately when the `PaymentIntent` is created. - Set [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the `PaymentMethod`’s ID. - Depending on how you represent customers in your integration, set either [customer_account](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer_account) to the ID of the customer-configured `Account` or [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) to the ID of the `Customer`. #### Accounts v2 ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1099 \ -d currency=usd \ -d "automatic_payment_methods[enabled]=true" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d payment_method={{PAYMENT_METHOD_ID}} \ --data-urlencode "return_url=https://example.com/order/123/complete" \ -d off_session=true \ -d confirm=true ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1099 \ -d currency=usd \ -d "automatic_payment_methods[enabled]=true" \ -d "customer={{CUSTOMER_ID}}" \ -d payment_method={{PAYMENT_METHOD_ID}} \ --data-urlencode "return_url=https://example.com/order/123/complete" \ -d off_session=true \ -d confirm=true ``` ## Test the integration #### Cards | Card number | Scenario | How to test | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | 4242424242424242 | The card payment succeeds and doesn’t require authentication. | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. | | 4000002500003155 | The card payment requires *authentication* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. | | 4000000000009995 | The card is declined with a decline code like `insufficient_funds`. | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. | | 6205500000000000004 | The UnionPay card has a variable length of 13-19 digits. | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. | #### Bank redirects | Payment method | Scenario | How to test | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Bancontact, iDEAL | Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method. | Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page. | | Pay by Bank | Your customer successfully pays with a redirect-based and [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment method. | Choose the payment method, fill out the required details, and confirm the payment. Then click **Complete test payment** on the redirect page. | | Pay by Bank | Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method. | Choose the payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page. | | BLIK | BLIK payments fail in a variety of ways – immediate failures (for example, the code has expired or is invalid), delayed errors (the bank declines) or timeouts (the customer didn’t respond in time). | Use email patterns to [simulate the different failures.](https://docs.stripe.com/payments/blik/accept-a-payment.md#simulate-failures) | #### Bank debits | Payment method | Scenario | How to test | | ----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SEPA Direct Debit | Your customer successfully pays with SEPA Direct Debit. | Fill out the form using the account number `AT321904300235473204`. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. | | SEPA Direct Debit | Your customer’s payment intent status transitions from `processing` to `requires_payment_method`. | Fill out the form using the account number `AT861904300235473202`. | See [Testing](https://docs.stripe.com/testing.md) for additional information to test your integration. ## Enable card scanning To enable card scanning support for iOS, set the `NSCameraUsageDescription` (**Privacy - Camera Usage Description**) in the `Info.plist` of your application, and provide a reason for accessing the camera (for example, “To scan cards”). ## Optional: Enable saved cards [Server-side] [Client-side] `EmbeddedPaymentElement` can allow the customer to save their card and can include the customer’s saved cards in available payment methods. The customer must have an associated customer-configured [Account](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer) object or a [Customer](https://docs.stripe.com/api/customers/create.md) object on your server. To enable a checkbox that allows the customer to save their card, create a [CustomerSession](https://docs.stripe.com/api/customer_sessions.md), with `payment_method_save` set to `enabled`. #### Accounts v2 ```javascript const stripe = require('stripe')('sk_test_your_secret_key'); app.post('/mobile-payment-element', async (req, res) => { // Use an existing Account ID if this is a returning customer. const customer_account = await stripe.v2.core.accounts.create(); const customerSession = await stripe.customerSessions.create({ customer_account: customer_account.id, components: { mobile_payment_element: { enabled: true, features: { payment_method_save: 'enabled', payment_method_redisplay: 'enabled', payment_method_remove: 'enabled' } }, }, }); res.json({ customerSessionClientSecret: customerSession.client_secret, customer_account: customer_account.id, }); }); ``` Next, configure EmbeddedPaymentElement with the customer’s ID and the CustomerSession client secret. #### UIKit ```swift @_spi(CustomerSessionBetaAccess) import StripePaymentSheet var configuration = EmbeddedPaymentElement.Configuration() configuration.customer = .init(id: customerAccountId, customerSessionClientSecret: customerSessionClientSecret) self.embeddedPaymentElement = try await EmbeddedPaymentElement.create(..., configuration: configuration) ``` #### SwiftUI ```swift @_spi(CustomerSessionBetaAccess) import StripePaymentSheet var configuration = EmbeddedPaymentElement.Configuration() configuration.customer = .init(id: customerAccountId, customerSessionClientSecret: customerSessionClientSecret) try await embeddedViewModel.load(..., configuration: configuration) ``` #### Customers v1 ```javascript const stripe = require('stripe')('sk_test_your_secret_key'); app.post('/mobile-payment-element', async (req, res) => { // Use an existing Customer ID if this is a returning customer. const customer = await stripe.customers.create(); const customerSession = await stripe.customerSessions.create({ customer: customer.id, components: { mobile_payment_element: { enabled: true, features: { payment_method_save: 'enabled', payment_method_redisplay: 'enabled', payment_method_remove: 'enabled' } }, }, }); res.json({ customerSessionClientSecret: customerSession.client_secret, customer: customer.id, }); }); ``` Next, configure EmbeddedPaymentElement with the customer’s ID and the CustomerSession client secret. #### UIKit ```swift @_spi(CustomerSessionBetaAccess) import StripePaymentSheet var configuration = EmbeddedPaymentElement.Configuration() configuration.customer = .init(id: customerId, customerSessionClientSecret: customerSessionClientSecret) self.embeddedPaymentElement = try await EmbeddedPaymentElement.create(..., configuration: configuration) ``` #### SwiftUI ```swift @_spi(CustomerSessionBetaAccess) import StripePaymentSheet var configuration = EmbeddedPaymentElement.Configuration() configuration.customer = .init(id: customerId, customerSessionClientSecret: customerSessionClientSecret) try await embeddedViewModel.load(..., configuration: configuration) ``` ## Optional: Allow delayed payment methods [Client-side] *Delayed payment methods* (A payment method that can't immediately return payment status when a customer attempts a transaction (for example, ACH debits). Businesses commonly hold an order in a pending state until payment is successful with these payment methods) don’t guarantee that you’ll receive funds from your customer at the end of the checkout either because they take time to settle (for example, US Bank Accounts, SEPA Debit, iDEAL, and Bancontact) or because they require customer action to complete (for example, OXXO, Konbini, and Boleto). By default, EmbeddedPaymentElement doesn’t display delayed payment methods. To opt in, set `allowsDelayedPaymentMethods` to true in your `EmbeddedPaymentElement.Configuration`. This step alone doesn’t activate any specific payment methods; rather, it indicates that your app is able to handle them. For example, although OXXO is currently not supported by EmbeddedPaymentElement, if it becomes supported and you’ve updated to the latest SDK version, your app will be able to display OXXO as a payment option without additional integration changes. #### UIKit ```swift var configuration = EmbeddedPaymentElement.Configuration() configuration.allowsDelayedPaymentMethods = true self.embeddedPaymentElement = try await EmbeddedPaymentElement.create(..., configuration: configuration) ``` #### SwiftUI ```swift var configuration = EmbeddedPaymentElement.Configuration() configuration.allowsDelayedPaymentMethods = true try await embeddedViewModel.load(..., configuration: configuration) ``` If the customer successfully uses one of these delayed payment methods in EmbeddedPaymentElement, the payment result returned is `.completed`. ## Optional: Enable Apple Pay > If your checkout screen has a dedicated **Apple Pay** button, follow the [Apple Pay guide](https://docs.stripe.com/apple-pay.md#present-payment-sheet) and use `ApplePayContext` to collect payment from your **Apple Pay** button. You can use `EmbeddedPaymentElement` to handle other payment method types. ### Register for an Apple Merchant ID Obtain an Apple Merchant ID by [registering for a new identifier](https://developer.apple.com/account/resources/identifiers/add/merchant) on the Apple Developer website. Fill out the form with a description and identifier. Your description is for your own records and you can modify it in the future. Stripe recommends using the name of your app as the identifier (for example, `merchant.com.{{YOUR_APP_NAME}}`). ### Create a new Apple Pay certificate Create a certificate for your app to encrypt payment data. Go to the [iOS Certificate Settings](https://dashboard.stripe.com/settings/ios_certificates) in the Dashboard, click **Add new application**, and follow the guide. Download a Certificate Signing Request (CSR) file to get a secure certificate from Apple that allows you to use Apple Pay. One CSR file must be used to issue exactly one certificate. If you switch your Apple Merchant ID, you must go to the [iOS Certificate Settings](https://dashboard.stripe.com/settings/ios_certificates) in the Dashboard to obtain a new CSR and certificate. ### Integrate with Xcode Add the Apple Pay capability to your app. In Xcode, open your project settings, click the **Signing & Capabilities** tab, and add the **Apple Pay** capability. You might be prompted to log in to your developer account at this point. Select the merchant ID you created earlier, and your app is ready to accept Apple Pay. ![](https://b.stripecdn.com/docs-statics-srv/assets/xcode.a701d4c1922d19985e9c614a6f105bf1.png) Enable the Apple Pay capability in Xcode ### Add Apple Pay #### One-time payment To add Apple Pay to EmbeddedPaymentElement, set [applePay](https://github.com/stripe/stripe-ios/blob/8be959abc141e360080efa88980afb325737a935/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift#L28) after initialising `EmbeddedPaymentElement.Configuration` with your Apple merchant ID and your business’s [country code](https://dashboard.stripe.com/settings/account). #### iOS (Swift) ```swift var configuration = EmbeddedPaymentElement.Configuration() configuration.applePay = .init( merchantId: "merchant.com.your_app_name", merchantCountryCode: "US" ) ``` #### Recurring payments To add Apple Pay to EmbeddedPaymentElement, set [applePay](https://github.com/stripe/stripe-ios/blob/8be959abc141e360080efa88980afb325737a935/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift#L28) after initialising `EmbeddedPaymentElement.Configuration` with your Apple merchant ID and your business’s [country code](https://dashboard.stripe.com/settings/account). As per [Apple’s guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay#Supporting-subscriptions) for recurring payments, you must also set additional attributes on the `PKPaymentRequest`. Add a handler in [ApplePayConfiguration.paymentRequestHandlers](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/applepayconfiguration/handlers/paymentrequesthandler) to configure the [PKPaymentRequest.paymentSummaryItems](https://developer.apple.com/documentation/passkit/pkpaymentrequest/1619231-paymentsummaryitems) with the amount you intend to charge (for example, 9.95 USD a month). You can also adopt [merchant tokens](https://developer.apple.com/apple-pay/merchant-tokens/) by setting the `recurringPaymentRequest` or `automaticReloadPaymentRequest` properties on the `PKPaymentRequest`. To learn more about how to use recurring payments with Apple Pay, see [Apple’s PassKit documentation](https://developer.apple.com/documentation/passkit/pkpaymentrequest). #### iOS (Swift) ```swift let customHandlers = EmbeddedPaymentElement.ApplePayConfiguration.Handlers( paymentRequestHandler: { request in // PKRecurringPaymentSummaryItem is available on iOS 15 or later if #available(iOS 15.0, *) { let billing = PKRecurringPaymentSummaryItem(label: "My Subscription", amount: NSDecimalNumber(string: "59.99")) // Payment starts today billing.startDate = Date() // Payment ends in one year billing.endDate = Date().addingTimeInterval(60 * 60 * 24 * 365) // Pay once a month. billing.intervalUnit = .month billing.intervalCount = 1 // recurringPaymentRequest is only available on iOS 16 or later if #available(iOS 16.0, *) { request.recurringPaymentRequest = PKRecurringPaymentRequest(paymentDescription: "Recurring", regularBilling: billing, managementURL: URL(string: "https://my-backend.example.com/customer-portal")!) request.recurringPaymentRequest?.billingAgreement = "You'll be billed $59.99 every month for the next 12 months. To cancel at any time, go to Account and click 'Cancel Membership.'" } request.paymentSummaryItems = [billing] request.currencyCode = "USD" } else { // On older iOS versions, set alternative summary items. request.paymentSummaryItems = [PKPaymentSummaryItem(label: "Monthly plan starting July 1, 2022", amount: NSDecimalNumber(string: "59.99"), type: .final)] } return request } ) var configuration = EmbeddedPaymentElement.Configuration() configuration.applePay = .init(merchantId: "merchant.com.your_app_name", merchantCountryCode: "US", customHandlers: customHandlers) ``` ### Order tracking To add [order tracking](https://developer.apple.com/design/human-interface-guidelines/technologies/wallet/designing-order-tracking) information in iOS 16 or later, configure an [authorizationResultHandler](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/applepayconfiguration/handlers/authorizationresulthandler) in your `PaymentSheet.ApplePayConfiguration.Handlers`. Stripe calls your implementation after the payment is complete, but before iOS dismisses the Apple Pay sheet. In your `authorizationResultHandler` implementation, fetch the order details from your server for the completed order. Add the details to the provided [PKPaymentAuthorizationResult](https://developer.apple.com/documentation/passkit/pkpaymentauthorizationresult) and return the modified result. To learn more about order tracking, see [Apple’s Wallet Orders documentation](https://developer.apple.com/documentation/walletorders). #### iOS (Swift) ```swift let customHandlers = PaymentSheet.ApplePayConfiguration.Handlers( authorizationResultHandler: { result in do { // Fetch the order details from your service let myOrderDetails = try await MyAPIClient.shared.fetchOrderDetails(orderID: orderID) result.orderDetails = PKPaymentOrderDetails( orderTypeIdentifier: myOrderDetails.orderTypeIdentifier, // "com.myapp.order" orderIdentifier: myOrderDetails.orderIdentifier, // "ABC123-AAAA-1111" webServiceURL: myOrderDetails.webServiceURL, // "https://my-backend.example.com/apple-order-tracking-backend" authenticationToken: myOrderDetails.authenticationToken) // "abc123" // Return your modified PKPaymentAuthorizationResult return result } catch { return PKPaymentAuthorizationResult(status: .failure, errors: [error]) } } ) var configuration = EmbeddedPaymentElement.Configuration() configuration.applePay = .init(merchantId: "merchant.com.your_app_name", merchantCountryCode: "US", customHandlers: customHandlers) ``` ## Optional: Customise the element All customisation is configured through the [EmbeddedPaymentElement.Configuration](https://github.com/stripe/stripe-ios/blob/8be959abc141e360080efa88980afb325737a935/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift#L12) object. ### Appearance Customise colours, fonts, and so on to match the look and feel of your app by using the [appearance API](https://docs.stripe.com/elements/appearance-api/embedded-mobile.md?platform=ios). ### Collect users addresses Collect local and international shipping or billing addresses from your customers using the [Address Element](https://docs.stripe.com/elements/address-element.md?platform=ios). ### Merchant display name Specify a customer-facing business name by setting [merchantDisplayName](https://github.com/stripe/stripe-ios/blob/8be959abc141e360080efa88980afb325737a935/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift#L66). By default, this is your app’s name. #### Swift ```swift var configuration = EmbeddedPaymentElement.Configuration() configuration.merchantDisplayName = "My app, Inc." ``` ### Dark mode `EmbeddedPaymentElement` automatically adapts to the user’s system-wide appearance settings (light and dark mode). If your app doesn’t support dark mode, you can set [style](https://github.com/stripe/stripe-ios/blob/8be959abc141e360080efa88980afb325737a935/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift#L51) to `alwaysLight` or `alwaysDark` mode. ```swift var configuration = EmbeddedPaymentElement.Configuration() configuration.style = .alwaysLight ```