# Build a SaaS platform with Accounts v1 Create connected accounts using Accounts v1, then facilitate direct payments to them on your SaaS platform. > #### Accounts v2 API integrations > > This guide only applies to existing Connect platforms that use the Accounts v1 API. If you’re a new Connect user, or if you use the Accounts v2 API, see the [v2 SaaS platform guide](https://docs.stripe.com/connect/saas.md). This guide covers letting your users accept payments, moving a portion of your users’ earnings into your balance, and paying out the remainder to your users’ bank accounts. To illustrate these concepts, we’ll use an example platform that lets businesses build their own online stores. # Web > This is a Web for when platform is web. View the full page at https://docs.stripe.com/connect/end-to-end-saas-platform?platform=web. ## Prerequisites 1. [Register your platform](https://dashboard.stripe.com/connect). 1. Add business details to [activate your account](https://dashboard.stripe.com/account/onboarding). 1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile). 1. [Customize your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand color. ## Set up Stripe [Server-side] Install Stripe’s official libraries so you can access the API from your application: #### 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' ``` ## Create a connected account When a user (seller or service provider) signs up on your platform, create a user [Account](https://docs.stripe.com/api/accounts.md) (referred to as a *connected account*) so you can accept payments and move funds to their bank account. Connected accounts represent your user in Stripe’s API and help facilitate the collection of onboarding requirements so Stripe can verify the user’s identity. In our store builder example, the connected account represents the business setting up their Internet store. ![Screenshot of Connect Onboarding form](https://b.stripecdn.com/docs-statics-srv/assets/Kavholm-Seamless-Standard.78b64d90c0bf87130c8b6ba1ef53df7f.png) ### Create a connected account and prefill information Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account. You can create the connected account by using the [default connected account parameters](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type. #### With default properties ```curl curl -X POST https://api.stripe.com/v1/accounts \ -u "<>:" ``` #### With account type ```curl curl https://api.stripe.com/v1/accounts \ -u "<>:" \ -d type=standard ``` If you’ve already collected information for your connected accounts, you can prefill that information on the `Account` object. You can prefill any account information, including personal and business information, external account information, and so on. After creating the `Account`, create a [Person](https://docs.stripe.com/api/persons/create.md) to represent the person responsible for opening the account, with `relationship.representative` set to true and any account information you want to prefill (for example, their first and last name). ```curl curl https://api.stripe.com/v1/accounts/{{ACCOUNT_ID}}/persons \ -u "<>:" \ -d first_name=Jenny \ -d last_name=Rosen \ -d "relationship[representative]"=true ``` Connect Onboarding doesn’t ask for the prefilled information. However, it does ask the account holder to confirm the prefilled information before accepting the [Connect service agreement](https://docs.stripe.com/connect/service-agreement-types.md). When testing your integration, prefill account information using [test data](https://docs.stripe.com/connect/testing.md). ### Create an account link You can create an account link by calling the [Account Links](https://docs.stripe.com/api/account_links.md) API with the following parameters: - `account` - `refresh_url` - `return_url` - `type` = `account_onboarding` ```curl curl https://api.stripe.com/v1/account_links \ -u "<>:" \ -d account="{{CONNECTEDACCOUNT_ID}}" \ --data-urlencode refresh_url="https://example.com/reauth" \ --data-urlencode return_url="https://example.com/return" \ -d type=account_onboarding ``` ### Redirect your user to the account link URL The response to your [Account Links](https://docs.stripe.com/api/account_links.md) request includes a value for the key `url`. Redirect to this link to send your user into the flow. Account Links are temporary and are single-use only because they grant access to the connected account user’s personal information. Authenticate the user in your application before redirecting them to this URL. If you want to prefill information, you must do so before generating the account link. After you create the account link, you won’t be able to read or write information for the account. > Don’t email, text, or otherwise send account link URLs outside of your platform application. Instead, provide them to the authenticated account holder within your application. ### Handle the user returning to your platform Connect Onboarding requires you to pass both a `return_url` and `refresh_url` to handle all cases where the user is redirected to your platform. It’s important that you implement these correctly to provide the best experience for your user. > You can use HTTP for your `return_url` and `refresh_url` while you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. Be sure to swap testing URLs for HTTPS URLs before going live. #### return_url Stripe issues a redirect to this URL when the user completes the Connect Onboarding flow. This doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. This only means the flow was entered and exited properly. No state is passed through this URL. After a user is redirected to your `return_url`, check the state of the `details_submitted` parameter on their account by doing either of the following: - Listening to `account.updated` webhooks - Calling the [Accounts](https://docs.stripe.com/api/accounts.md) API and inspecting the returned object #### refresh_url Your user is redirected to the `refresh_url` in these cases: - The link is expired (a few minutes went by since the link was created) - The user already visited the link (the user refreshed the page or clicked back or forward in the browser) - Your platform is no longer able to access the account - The account has been rejected Your `refresh_url` should trigger a method on your server to call [Account Links](https://docs.stripe.com/api/account_links.md) again with the same parameters, and redirect the user to the Connect Onboarding flow to create a seamless experience. ### Handle users that haven’t completed onboarding If a user is redirected to your `return_url`, they might not have completed the onboarding process. Use the `/v1/accounts` endpoint to retrieve the user’s account and check for `charges_enabled`. If the account isn’t fully onboarded, provide UI prompts to allow the user to continue onboarding later. The user can complete their account activation through a new account link (generated by your integration). To see if they’ve completed the onboarding process, check the state of the `details_submitted` parameter on their account. ## Enable payment methods View your [payment methods settings](https://dashboard.stripe.com/settings/connect/payment_methods) and enable the payment methods you want to support. Card payments are enabled by default but you can enable and disable payment methods as needed. This guide assumes Bancontact, credit cards, EPS, iDEAL, Przelewy24, SEPA Direct Debit, and Sofort are enabled. Before the payment form is displayed, Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Payment methods that increase conversion and that are most relevant to the currency and customer’s location are prioritized. Lower priority payment methods are hidden in an overflow menu. > The **embedded payment method settings component** allows connected accounts to configure the payment methods they offer at checkout without the need to access the Stripe Dashboard. Learn more about the [embedded payment method settings component](https://docs.stripe.com/connect/supported-embedded-components/payment-method-settings.md) and get notified once it becomes available. ## Accept a payment Embed [Stripe Checkout](https://stripe.com/payments/checkout) as a payment form directly on your website or redirect users to a Stripe-hosted page to accept payments. Checkout supports multiple payment methods and automatically shows the most relevant ones to your customer. You can also use the Payment Element, a prebuilt UI component that is embedded as an iframe in your payment form, to accept multiple payment methods with a single frontend integration. #### Stripe-hosted page ### Create a Checkout Session (Client-side) (Server-side) A Checkout Session controls what your customer sees in the embeddable payment form such as line items, the order amount and currency, and acceptable payment methods. When performing direct charges, Checkout uses the connected account’s branding settings. See the [Customize branding](https://docs.stripe.com/connect/direct-charges.md?platform=web&ui=stripe-hosted#branding) section for more information. Unlike destination charges and separate charges and transfers, users (connected accounts) are responsible for handling disputes on direct charges—it’s not the responsibility of the platform. On your server, make the following call to the Stripe API. After creating a Checkout Session, redirect your customer to the [URL](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-url) returned in the response. ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -H "Stripe-Account: {{CONNECTEDACCOUNT_ID}}" \ -d mode=payment \ -d "line_items[0][price]"="{{PRICE_ID}}" \ -d "line_items[0][quantity]"=1 \ -d "payment_intent_data[application_fee_amount]"=123 \ --data-urlencode success_url="https://example.com/success" ``` - `line_items` - This argument represents items that your customer is purchasing and that will show up in the hosted user interface. - `success_url` - This argument redirects a user after they complete a payment. - `Stripe-Account` - This header indicates a [direct charge](https://docs.stripe.com/connect/direct-charges.md) for your connected account. With direct charges, the connected account is responsible for Stripe fees, refunds, and chargebacks. The connected account’s branding is used in Checkout, which allows their customers to feel like they’re interacting directly with the business instead of your platform. - (Optional) `payment_intent_data[application_fee_amount]` - This argument specifies the amount your platform plans to take from the transaction. After the payment is processed on the connected account, the `application_fee_amount` is transferred to the platform and the Stripe fee is deducted from the connected account’s balance. ![Account creation flow](https://b.stripecdn.com/docs-statics-srv/assets/direct_charges.a2a8b68037ac95fe22140d6dde9740d3.svg) ### Handle post-payment events (Server-side) Stripe sends a [checkout.session.completed](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.completed) event when the payment completes. [Use a webhook to receive these events](https://docs.stripe.com/webhooks/quickstart.md) and run actions, like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow. Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes. Some payment methods also take 2-14 days for payment confirmation. Setting up your integration to listen for asynchronous events enables you to accept multiple [payment methods](https://stripe.com/payments/payment-methods-guide) with a single integration. In addition to handling the `checkout.session.completed` event, we recommend handling two other events when collecting payments with Checkout: | Event | Description | Next steps | | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | [checkout.session.completed](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.completed) | The customer has successfully authorized the payment by submitting the Checkout form. | Wait for the payment to succeed or fail. | | [checkout.session.async_payment_succeeded](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.async_payment_succeeded) | The customer’s payment succeeded. | Fulfill the purchased goods or services. | | [checkout.session.async_payment_failed](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.async_payment_failed) | The payment was declined, or failed for some other reason. | Contact the customer through email and request that they place a new order. | These events all include the [Checkout Session](https://docs.stripe.com/api/checkout/sessions.md) object. After the payment succeeds, the underlying *PaymentIntent* (The Payment Intents API 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) status changes from `processing` to `succeeded`. #### Embedded form ### Create a Checkout Session (Client-side) (Server-side) A Checkout Session controls what your customer sees in the Stripe-hosted payment page such as line items, the order amount and currency, and acceptable payment methods. When performing direct charges, Checkout uses the connected account’s branding settings. See the [Customize branding](https://docs.stripe.com/connect/direct-charges.md?platform=web&ui=stripe-hosted#branding) section for more information. Unlike destination charges and separate charges and transfers, users (connected accounts) are responsible for handling disputes on direct charges—it’s not the responsibility of the platform. On your server, make the following call to the Stripe API. The Checkout Session creation response includes a `client_secret`, which you’ll use in the next step to mount Checkout. ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -H "Stripe-Account: {{CONNECTEDACCOUNT_ID}}" \ -d mode=payment \ -d "line_items[0][price]"="{{PRICE_ID}}" \ -d "line_items[0][quantity]"=1 \ -d "payment_intent_data[application_fee_amount]"=123 \ -d ui_mode=embedded \ --data-urlencode return_url="https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}" ``` - `line_items` - This argument represents the items your customer is purchasing, which will appear in the hosted user interface. - `return_url` - This argument redirects a user after they complete a payment attempt. - `Stripe-Account` - This header indicates a [direct charge](https://docs.stripe.com/connect/direct-charges.md) for your connected account. With direct charges, the connected account is responsible for Stripe fees, refunds, and chargebacks. The connected account’s branding is used in Checkout, which allows their customers to feel like they’re interacting directly with the merchant instead of your platform. - (Optional) `payment_intent_data[application_fee_amount]` - This argument specifies the amount your platform plans to take from the transaction. If you’re using Stripe’s [Platform Pricing Tool](https://docs.stripe.com/connect/platform-pricing-tools.md) to manage application fee pricing from the [Dashboard](https://dashboard.stripe.com/test/settings/connect/platform_pricing/payments), don’t include this argument as it’ll override any pricing logic set by the tool. After processing the payment on the connected account, the `application_fee_amount` transfers to the platform, and the Stripe fee is deducted from the connected account’s balance. Make sure the `return_url` corresponds to a page on your website that provides the status of the payment. When Stripe redirects the customer to the `return_url`, we replace the `{CHECKOUT_SESSION_ID}` string with the Checkout Session ID. Use this to retrieve the Checkout Session and inspect the status to decide what to show your customers. You can also append your own query parameters when providing the `return_url`, which persist through the redirect process. ![Account creation flow](https://b.stripecdn.com/docs-statics-srv/assets/direct_charges.a2a8b68037ac95fe22140d6dde9740d3.svg) ### Mount Checkout (Client-side) #### HTML + JS Checkout is available as part of [Stripe.js](https://docs.stripe.com/js.md). Include the Stripe.js script on your page by adding it to the head of your HTML file. Next, create an empty DOM node (container) to use for mounting. ```html
``` Initialize Stripe.js with your publishable API key. Create an asynchronous `fetchClientSecret` function that makes a request to your server to create the Checkout Session and retrieve the client secret. Pass this function into `options` when you create the Checkout instance: ```javascript // Initialize Stripe.js const stripe = Stripe('<>', { stripeAccount: '{{CONNECTED_ACCOUNT_ID}}', }); initialize(); // Fetch Checkout Session and retrieve the client secret async function initialize() { const fetchClientSecret = async () => { const response = await fetch("/create-checkout-session", { method: "POST", }); const { clientSecret } = await response.json(); return clientSecret; }; // Initialize Checkout const checkout = await stripe.initEmbeddedCheckout({ fetchClientSecret, }); // Mount Checkout checkout.mount('#checkout'); } ``` #### React Install Connect.js and the React Connect.js libraries from the [npm public registry](https://www.npmjs.com/package/@stripe/react-connect-js). ```bash npm install --save @stripe/connect-js @stripe/react-connect-js ``` To use the Embedded Checkout component, create an `EmbeddedCheckoutProvider`. Call `loadStripe` with your publishable API key and pass the returned `Promise` to the provider. Create an asynchronous `fetchClientSecret` function that makes a request to your server to create the Checkout Session and retrieve the client secret. Pass this function into the `options` prop accepted by the provider. ```jsx import * as React from 'react'; import {loadStripe} from '@stripe/stripe-js'; import { EmbeddedCheckoutProvider, EmbeddedCheckout } from '@stripe/react-stripe-js'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. const stripePromise = loadStripe('pk_test_123'); const App = ({fetchClientSecret}) => { const options = {fetchClientSecret}; return ( ) } ``` Checkout is rendered in an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing Checkout within another iframe because some payment methods require redirecting to another page for payment confirmation. ### Customize appearance Customize Checkout to match the design of your site by setting the background color, button color, border radius, and fonts in your account’s [branding settings](https://dashboard.stripe.com/settings/branding). By default, Checkout renders with no external padding or margin. We recommend using a container element such as a div to apply your desired margin (for example, 16px on all sides). ### Handle post-payment events (Server-side) Stripe sends a [checkout.session.completed](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.completed) event when the payment completes. [Use a webhook to receive these events](https://docs.stripe.com/webhooks/quickstart.md) and run actions, like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow. Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes. Some payment methods also take 2-14 days for payment confirmation. Setting up your integration to listen for asynchronous events enables you to accept multiple [payment methods](https://stripe.com/payments/payment-methods-guide) with a single integration. In addition to handling the `checkout.session.completed` event, we recommend handling two other events when collecting payments with Checkout: | Event | Description | Next steps | | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | [checkout.session.completed](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.completed) | The customer has successfully authorized the payment by submitting the Checkout form. | Wait for the payment to succeed or fail. | | [checkout.session.async_payment_succeeded](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.async_payment_succeeded) | The customer’s payment succeeded. | Fulfill the purchased goods or services. | | [checkout.session.async_payment_failed](https://docs.stripe.com/api/events/types.md#event_types-checkout.session.async_payment_failed) | The payment was declined, or failed for some other reason. | Contact the customer through email and request that they place a new order. | These events all include the [Checkout Session](https://docs.stripe.com/api/checkout/sessions.md) object. After the payment succeeds, the underlying *PaymentIntent* (The Payment Intents API 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) status changes from `processing` to `succeeded`. #### Custom flow ### Create a PaymentIntent (Server-side) Stripe uses a [PaymentIntent](https://docs.stripe.com/api/payment_intents.md) object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process. > If you want to render the Payment Element without first creating a PaymentIntent, see [Collect payment details before creating an Intent](https://docs.stripe.com/payments/accept-a-payment-deferred.md?type=payment). ![An overview diagram of the entire payment flow](https://b.stripecdn.com/docs-statics-srv/assets/accept-a-payment-payment-element.5cf6795a02f864923f9953611493d735.svg) The payment methods shown to customers during the checkout process are also included on the PaymentIntent. You can let Stripe automatically pull payment methods from your Dashboard settings or you can list them manually. Unless your integration requires a code-based option for offering payment methods, don’t list payment methods manually. Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Stripe prioritizes payment methods that help increase conversion and are most relevant to the currency and the customer’s location. We hide lower priority payment methods in an overflow menu. #### Manage payment methods from the Dashboard ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -H "Stripe-Account: {{CONNECTEDACCOUNT_ID}}" \ -d amount=1099 \ -d currency=eur \ -d "automatic_payment_methods[enabled]"=true \ -d application_fee_amount=123 ``` When creating a PaymentIntent, you need to specify certain parameters: - `amount` and `currency` - Create a PaymentIntent on your server with an amount and currency. - (Optional) `automatic_payment_methods` - In the latest version of the API, you don’t need to specify this parameter because Stripe enables its functionality by default. You can manage payment methods from the [Dashboard](https://dashboard.stripe.com/settings/payment_methods). Stripe automatically returns eligible payment methods based on factors such as the transaction’s amount, currency, and payment flow. - (Optional) `payment_intent_data[application_fee_amount]` - This argument specifies the amount your platform plans to take from the transaction. If you’re using Stripe’s [Platform Pricing Tool](https://docs.stripe.com/connect/platform-pricing-tools.md) to manage application fee pricing from the [Dashboard](https://dashboard.stripe.com/test/settings/connect/platform_pricing/payments), don’t include this argument as it’ll override any pricing logic set by the tool. After processing the payment on the connected account, the `application_fee_amount` transfers to the platform, and the Stripe fee is deducted from the connected account’s balance. #### List payment methods manually ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -H "Stripe-Account: {{CONNECTEDACCOUNT_ID}}" \ -d amount=1099 \ -d currency=eur \ -d "payment_method_types[]"=bancontact \ -d "payment_method_types[]"=card \ -d "payment_method_types[]"=eps \ -d "payment_method_types[]"=ideal \ -d "payment_method_types[]"=p24 \ -d "payment_method_types[]"=sepa_debit \ -d "payment_method_types[]"=sofort \ -d application_fee_amount=123 ``` When creating a PaymentIntent, you need to specify certain parameters: - `amount` - Create a PaymentIntent on your server with a specified amount. Always determine how much to charge on the server side, as this is a trusted environment. This approach prevents malicious customers from choosing their own prices. - `currency` - The currency you include in the PaymentIntent filters the payment methods shown to the customer, so choose it based on the payment methods you want to offer. For example, if you pass `eur` and have OXXO enabled in the Dashboard, OXXO won’t appear to the customer because it doesn’t support `eur` payments. Some payment methods support multiple currencies and countries. This guide uses Bancontact, credit cards, EPS, iDEAL, Przelewy24, SEPA Direct Debit, and Sofort in the example code. - `"payment_method_types[]"` - Manually list all the payment methods you want to support. - (Optional) `payment_intent_data[application_fee_amount]` - This argument specifies the amount your platform plans to take from the transaction. If you’re using Stripe’s [Platform Pricing Tool](https://docs.stripe.com/connect/platform-pricing-tools.md) to manage application fee pricing from the [Dashboard](https://dashboard.stripe.com/test/settings/connect/platform_pricing/payments), don’t include this argument as it’ll override any pricing logic set by the tool. After processing the payment on the connected account, the `application_fee_amount` transfers to the platform, and the Stripe fee is deducted from the connected account’s balance. > Each payment method needs to support the currency passed in the PaymentIntent and your business needs to be based in one of the countries each payment method supports. See [Payment method integration options](https://docs.stripe.com/payments/payment-methods/integration-options.md) for more details about what’s supported. ### Retrieve the client secret The PaymentIntent includes a *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)) that the client side uses to securely complete the payment process. You can use different approaches to pass the client secret to the client side. #### Single-page application Retrieve the client secret from an endpoint on your server, using the browser’s `fetch` function. This approach is best if your client side is a single-page application, particularly one built with a modern frontend framework like React. Create the server endpoint that serves the client secret: #### Ruby ```ruby get '/secret' do intent = # ... Create or retrieve the PaymentIntent {client_secret: intent.client_secret}.to_json end ``` And then fetch the client secret with JavaScript on the client side: ```javascript (async () => { const response = await fetch('/secret'); const {client_secret: clientSecret} = await response.json(); // Render the form using the clientSecret })(); ``` #### Server-side rendering Pass the client secret to the client from your server. This approach works best if your application generates static content on the server before sending it to the browser. Add the [client_secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) in your checkout form. In your server-side code, retrieve the client secret from the PaymentIntent: #### Ruby ```erb
``` ```ruby get '/checkout' do @intent = # ... Fetch or create the PaymentIntent erb :checkout end ``` ### Collect payment details (Client-side) Collect payment details on the client with the Payment Element. The Payment Element is a prebuilt UI component that simplifies collecting payment details for a variety of payment methods. The Payment Element contains an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing the Payment Element within another iframe because some payment methods require redirecting to another page for payment confirmation. The checkout page address must start with `https://` rather than `http://` for your integration to work. You can test your integration without using HTTPS, but remember to [enable it](https://docs.stripe.com/security/guide.md#tls) when you’re ready to accept live payments. #### Set up Stripe.js #### HTML + JS The Payment Element is automatically available as a feature of Stripe.js. Include the Stripe.js script on your checkout page by adding it to the `head` of your HTML file. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself. ```html Checkout ``` Create an instance of `Stripe` with the following JavaScript on your checkout page: ```javascript // Initialize Stripe.js with the same connected account ID used when creating // the PaymentIntent. const stripe = Stripe('<>', { stripeAccount: '{{CONNECTED_ACCOUNT_ID}}' }); ``` #### Add Stripe Elements and the Payment Element to your payment page The Payment Element needs a place to live on your payment page. Create an empty DOM node (container) with a unique ID in your payment form. ```html
``` When the form has loaded, create an instance of the Payment Element and mount it to the container DOM node along with the [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) from the previous step. Pass this value as an option when creating the [Elements](https://docs.stripe.com/js/elements_object/create) instance. The client secret must be handled carefully because it can complete the charge. Don’t log it, embed it in URLs, or expose it to anyone but the customer. ```javascript const options = { clientSecret: '{{CLIENT_SECRET}}', // Fully customizable with the Appearance API appearance: {/*...*/}, }; // Set up Stripe.js and Elements to use in checkout form using the client secret const elements = stripe.elements(options); // Create and mount the Payment Element const paymentElement = elements.create("payment"); paymentElement.mount("#payment-element"); ``` The Payment Element renders a dynamic form that allows your customer to pick a payment method. The form automatically collects all necessary payments details for the payment method selected by the customer. You can [customize the appearance of the Payment Element](https://docs.stripe.com/elements/appearance-api.md) to match the design of your site when you set up the `Elements` object. #### React Install [React Stripe.js](https://www.npmjs.com/package/@stripe/react-stripe-js) and the [Stripe.js loader](https://www.npmjs.com/package/@stripe/stripe-js) from the npm public registry: ```bash npm install --save @stripe/react-stripe-js @stripe/stripe-js ``` ### Add and configure the Elements provider to your payment page To use the Payment Element component, wrap your checkout page component in an [Elements provider](https://docs.stripe.com/sdks/stripejs-react.md#elements-provider). Call `loadStripe` with your publishable key, and pass the returned `Promise` along with the [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) from the previous step as `options` in the `Elements` provider. ```jsx import React from 'react'; import ReactDOM from 'react-dom'; import {Elements} from '@stripe/react-stripe-js'; import {loadStripe} from '@stripe/stripe-js'; import CheckoutForm from './CheckoutForm'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. const stripePromise = loadStripe("<>", { stripeAccount: '{{CONNECTED_ACCOUNT_ID}}' }); function App() { const options = { // pass the client secret from the previous step clientSecret: '{{CLIENT_SECRET}}', // Fully customizable with the Appearance API appearance: {/*...*/}, }; return ( ); }; ReactDOM.render(, document.getElementById('root')); ``` #### Add the PaymentElement component Use the `PaymentElement` component to build your form. ```jsx import React from 'react'; import {PaymentElement} from '@stripe/react-stripe-js'; const CheckoutForm = () => { return (
); }; export default CheckoutForm; ``` The Payment Element renders a dynamic form that allows your customer to pick a payment method type. The form automatically collects all necessary payments details for the payment method selected by the customer. You can [customize the appearance of the Payment Element](https://docs.stripe.com/elements/appearance-api.md) to match the design of your site when you configure the `Elements` provider. ### Submit the payment to Stripe (Client-side) Use [stripe.confirmPayment](https://docs.stripe.com/js/payment_intents/confirm_payment) to complete the payment using details from the Payment Element. Provide a [return_url](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-return_url) to this function to indicate where Stripe should redirect the user after they complete the payment. Your user may be first redirected to an intermediate site, like a bank authorization page, before being redirected to the `return_url`. Card payments immediately redirect to the `return_url` when a payment is successful. If you don’t want to redirect for card payments after payment completion, you can set [redirect](https://docs.stripe.com/js/payment_intents/confirm_payment#confirm_payment_intent-options-redirect) to `if_required`. This only redirects customers that check out with redirect-based payment methods. #### HTML + JS ```javascript const form = document.getElementById('payment-form'); form.addEventListener('submit', async (event) => { event.preventDefault(); const {error} = await stripe.confirmPayment({ //`Elements` instance that was used to create the Payment Element elements, confirmParams: { return_url: 'https://example.com/order/123/complete', }, }); if (error) { // This point will only be reached if there is an immediate error when // confirming the payment. Show error to your customer (for example, payment // details incomplete) const messageContainer = document.querySelector('#error-message'); messageContainer.textContent = error.message; } else { // Your customer will be redirected to your `return_url`. For some payment // methods like iDEAL, your customer will be redirected to an intermediate // site first to authorize the payment, then redirected to the `return_url`. } }); ``` #### React To call [stripe.confirmPayment](https://docs.stripe.com/js/payment_intents/confirm_payment) from your payment form component, use the [useStripe](https://docs.stripe.com/sdks/stripejs-react.md#usestripe-hook) and [useElements](https://docs.stripe.com/sdks/stripejs-react.md#useelements-hook) hooks. If you prefer traditional class components over hooks, you can instead use an [ElementsConsumer](https://docs.stripe.com/sdks/stripejs-react.md#elements-consumer). ```jsx import React, {useState} from 'react'; import {useStripe, useElements, PaymentElement} from '@stripe/react-stripe-js'; const CheckoutForm = () => { const stripe = useStripe(); const elements = useElements(); const [errorMessage, setErrorMessage] = useState(null); const handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); if (!stripe || !elements) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } const {error} = await stripe.confirmPayment({ //`Elements` instance that was used to create the Payment Element elements, confirmParams: { return_url: 'https://example.com/order/123/complete', }, }); if (error) { // This point will only be reached if there is an immediate error when // confirming the payment. Show error to your customer (for example, payment // details incomplete) setErrorMessage(error.message); } else { // Your customer will be redirected to your `return_url`. For some payment // methods like iDEAL, your customer will be redirected to an intermediate // site first to authorize the payment, then redirected to the `return_url`. } }; return (
{/* Show error message to your customers */} {errorMessage &&
{errorMessage}
} ); }; export default CheckoutForm; ``` Make sure the `return_url` corresponds to a page on your website that provides the status of the payment. When Stripe redirects the customer to the `return_url`, we provide the following URL query parameters: | Parameter | Description | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `payment_intent` | The unique identifier for the `PaymentIntent`. | | `payment_intent_client_secret` | The [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) of the `PaymentIntent` object. | > If you have tooling that tracks the customer’s browser session, you might need to add the `stripe.com` domain to the referrer exclude list. Redirects cause some tools to create new sessions, which prevents you from tracking the complete session. Use one of the query parameters to retrieve the PaymentIntent. Inspect the [status of the PaymentIntent](https://docs.stripe.com/payments/paymentintents/lifecycle.md) to decide what to show your customers. You can also append your own query parameters when providing the `return_url`, which persist through the redirect process. #### HTML + JS ```javascript // Initialize Stripe.js using your publishable key const stripe = Stripe('<>'); // Retrieve the "payment_intent_client_secret" query parameter appended to // your return_url by Stripe.js const clientSecret = new URLSearchParams(window.location.search).get( 'payment_intent_client_secret' ); // Retrieve the PaymentIntent stripe.retrievePaymentIntent(clientSecret).then(({paymentIntent}) => { const message = document.querySelector('#message') // Inspect the PaymentIntent `status` to indicate the status of the payment // to your customer. // // Some payment methods will [immediately succeed or fail][0] upon // confirmation, while others will first enter a `processing` state. // // [0]: https://stripe.com/docs/payments/payment-methods#payment-notification switch (paymentIntent.status) { case 'succeeded': message.innerText = 'Success! Payment received.'; break; case 'processing': message.innerText = "Payment processing. We'll update you when payment is received."; break; case 'requires_payment_method': message.innerText = 'Payment failed. Please try another payment method.'; // Redirect your user back to your payment page to attempt collecting // payment again break; default: message.innerText = 'Something went wrong.'; break; } }); ``` #### React ```jsx import React, {useState, useEffect} from 'react'; import {useStripe} from '@stripe/react-stripe-js'; const PaymentStatus = () => { const stripe = useStripe(); const [message, setMessage] = useState(null); useEffect(() => { if (!stripe) { return; } // Retrieve the "payment_intent_client_secret" query parameter appended to // your return_url by Stripe.js const clientSecret = new URLSearchParams(window.location.search).get( 'payment_intent_client_secret' ); // Retrieve the PaymentIntent stripe .retrievePaymentIntent(clientSecret) .then(({paymentIntent}) => { // Inspect the PaymentIntent `status` to indicate the status of the payment // to your customer. // // Some payment methods will [immediately succeed or fail][0] upon // confirmation, while others will first enter a `processing` state. // // [0]: https://stripe.com/docs/payments/payment-methods#payment-notification switch (paymentIntent.status) { case 'succeeded': setMessage('Success! Payment received.'); break; case 'processing': setMessage("Payment processing. We'll update you when payment is received."); break; case 'requires_payment_method': // Redirect your user back to your payment page to attempt collecting // payment again setMessage('Payment failed. Please try another payment method.'); break; default: setMessage('Something went wrong.'); break; } }); }, [stripe]); return message; }; export default PaymentStatus; ``` ### Handle post-payment events (Server-side) Stripe sends a [payment_intent.succeeded](https://docs.stripe.com/api/events/types.md#event_types-payment_intent.succeeded) event when the payment completes. Use the [Dashboard webhook tool](https://dashboard.stripe.com/webhooks) or follow the [webhook guide](https://docs.stripe.com/webhooks/quickstart.md) to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow. Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes, and malicious clients could manipulate the response. Setting up your integration to listen for asynchronous events is what enables you to accept [different types of payment methods](https://stripe.com/payments/payment-methods-guide) with a single integration. In addition to handling the `payment_intent.succeeded` event, we recommend handling these other events when collecting payments with the Payment Element: | Event | Description | Action | | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [payment_intent.succeeded](https://docs.stripe.com/api/events/types.md?lang=php#event_types-payment_intent.succeeded) | Sent when a customer successfully completes a payment. | Send the customer an order confirmation and *fulfill* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order. | | [payment_intent.processing](https://docs.stripe.com/api/events/types.md?lang=php#event_types-payment_intent.processing) | Sent when a customer successfully initiates a payment, but the payment has yet to complete. This event is most commonly sent when the customer initiates a bank debit. It’s followed by either a `payment_intent.succeeded` or `payment_intent.payment_failed` event in the future. | Send the customer an order confirmation that indicates their payment is pending. For digital goods, you might want to fulfill the order before waiting for payment to complete. | | [payment_intent.payment_failed](https://docs.stripe.com/api/events/types.md?lang=php#event_types-payment_intent.payment_failed) | Sent when a customer attempts a payment, but the payment fails. | If a payment transitions from `processing` to `payment_failed`, offer the customer another attempt to pay. | ## Testing Test your account creation flow by [creating accounts](https://docs.stripe.com/connect/testing.md#creating-accounts) and [using OAuth](https://docs.stripe.com/connect/testing.md#using-oauth). Test your **Payment methods** settings for your connected accounts by logging into one of your test accounts and navigating to the [Payment methods settings](https://dashboard.stripe.com/settings/payment_methods). Test your checkout flow with your test keys and a test account. You can use our [test cards](https://docs.stripe.com/testing.md) to test your payments flow and simulate various payment outcomes. # Payment sheet > This is a Payment sheet for when platform is ios and mobile-ui is payment-element. View the full page at https://docs.stripe.com/connect/end-to-end-saas-platform?platform=ios&mobile-ui=payment-element. ![](https://b.stripecdn.com/docs-statics-srv/assets/ios-overview.9e0d68d009dc005f73a6f5df69e00458.png) Integrate Stripe’s prebuilt payment UI into the checkout of your iOS app with the [PaymentSheet](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet.html) class. See our sample integration [on GitHub](https://github.com/stripe/stripe-ios/tree/master/Example/PaymentSheet%20Example). ## Prerequisites 1. [Register your platform](https://dashboard.stripe.com/connect). 1. Add business details to [activate your account](https://dashboard.stripe.com/account/onboarding). 1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile). 1. [Customize your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand color. ## Set up Stripe [Server-side] [Client-side] First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register). ### 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. Configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/test/apikeys) on app start. This enables your app to make requests to the Stripe API. #### Swift ```swift import UIKitimportStripePaymentSheet @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {StripeAPI.defaultPublishableKey = "<>" // do any other necessary launch configuration return true } } ``` > Use your [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) keys when you publish your app. ## Create a connected account When a user (seller or service provider) signs up on your platform, create a user [Account](https://docs.stripe.com/api/accounts.md) (referred to as a *connected account*) so you can accept payments and move funds to their bank account. Connected accounts represent your user in Stripe’s API and help facilitate the collection of onboarding requirements so Stripe can verify the user’s identity. In our store builder example, the connected account represents the business setting up their Internet store. ![Account creation flow](https://b.stripecdn.com/docs-statics-srv/assets/standard-ios.10c6b24cef1d683d36f2264c726beb1d.png) ### Step 2.1: Create a connected account and prefill information (Server-side) Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account. You can create the connected account by using the [default connected account parameters](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type. #### With default properties ```curl curl -X POST https://api.stripe.com/v1/accounts \ -u "<>:" ``` #### With account type ```curl curl https://api.stripe.com/v1/accounts \ -u "<>:" \ -d type=standard ``` If you’ve already collected information for your connected accounts, you can prefill that information on the `Account` object. You can prefill any account information, including personal and business information, external account information, and so on. After creating the `Account`, create a [Person](https://docs.stripe.com/api/persons/create.md) to represent the person responsible for opening the account, with `relationship.representative` set to true and any account information you want to prefill (for example, their first and last name). ```curl curl https://api.stripe.com/v1/accounts/{{ACCOUNT_ID}}/persons \ -u "<>:" \ -d first_name=Jenny \ -d last_name=Rosen \ -d "relationship[representative]"=true ``` Connect Onboarding doesn’t ask for the prefilled information. However, it does ask the account holder to confirm the prefilled information before accepting the [Connect service agreement](https://docs.stripe.com/connect/service-agreement-types.md). When testing your integration, prefill account information using [test data](https://docs.stripe.com/connect/testing.md). ### Step 2.2: Create an account link (Server-side) You can create an account link by calling the [Account Links](https://docs.stripe.com/api/account_links.md) API with the following parameters: - `account` - `refresh_url` - `return_url` - `type` = `account_onboarding` ```curl curl https://api.stripe.com/v1/account_links \ -u "<>:" \ -d account="{{CONNECTEDACCOUNT_ID}}" \ --data-urlencode refresh_url="https://example.com/reauth" \ --data-urlencode return_url="https://example.com/return" \ -d type=account_onboarding ``` ### Step 2.3: Redirect your user to the account link URL (Client-side) The response to your [Account Links](https://docs.stripe.com/api/account_links.md) request includes a value for the key `url`. Redirect to this link to send your user into the flow. Account Links are temporary and are single-use only because they grant access to the connected account user’s personal information. Authenticate the user in your application before redirecting them to this URL. If you want to prefill information, you must do so before generating the account link. After you create the account link for a Standard account, you won’t be able to read or write information for the account. > Don’t email, text, or otherwise send account link URLs outside of your platform application. Instead, provide them to the authenticated account holder within your application. #### Swift ```swift import UIKit import SafariServices let BackendAPIBaseURL: String = "" // Set to the URL of your backend server class ConnectOnboardViewController: UIViewController { // ... override func viewDidLoad() { super.viewDidLoad() let connectWithStripeButton = UIButton(type: .system) connectWithStripeButton.setTitle("Connect with Stripe", for: .normal) connectWithStripeButton.addTarget(self, action: #selector(didSelectConnectWithStripe), for: .touchUpInside) view.addSubview(connectWithStripeButton) // ... } @objc func didSelectConnectWithStripe() { if let url = URL(string: BackendAPIBaseURL)?.appendingPathComponent("onboard-user") { var request = URLRequest(url: url) request.httpMethod = "POST" let task = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any], let accountURLString = json["url"] as? String, let accountURL = URL(string: accountURLString) else { // handle error } let safariViewController = SFSafariViewController(url: accountURL) safariViewController.delegate = self DispatchQueue.main.async { self.present(safariViewController, animated: true, completion: nil) } } } } // ... } extension ConnectOnboardViewController: SFSafariViewControllerDelegate { func safariViewControllerDidFinish(_ controller: SFSafariViewController) { // the user may have closed the SFSafariViewController instance before a redirect // occurred. Sync with your backend to confirm the correct state } } ``` ### Step 2.4: Handle the user returning to your platform (Client-side) *Connect* (Connect is Stripe's solution for multi-party businesses, such as marketplace or software platforms, to route payments between sellers, customers, and other recipients) Onboarding requires you to pass both a `return_url` and `refresh_url` to handle all cases where the user is redirected to your platform. It’s important that you implement these correctly to provide the best experience for your user. You can set up a [universal link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) to enable iOS to redirect to your app automatically. #### return_url Stripe issues a redirect to this URL when the user completes the Connect Onboarding flow. This doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. This only means the flow was entered and exited properly. No state is passed through this URL. After a user is redirected to your `return_url`, check the state of the `details_submitted` parameter on their account by doing either of the following: - Listening to `account.updated` *webhooks* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) - Calling the [Accounts](https://docs.stripe.com/api/accounts.md) API and inspecting the returned object #### refresh_url Your user is redirected to the `refresh_url` in these cases: - The link is expired (a few minutes went by since the link was created) - The user already visited the link (the user refreshed the page or clicked back or forward in the browser) - Your platform is no longer able to access the account - The account has been rejected Your `refresh_url` should trigger a method on your server to call [Account Links](https://docs.stripe.com/api/account_links.md) again with the same parameters, and redirect the user to the Connect Onboarding flow to create a seamless experience. ### Step 2.5: Handle users that haven’t completed onboarding A user that is redirected to your `return_url` might not have completed the onboarding process. Use the `/v1/accounts` endpoint to retrieve the user’s account and check for `charges_enabled`. If the account isn’t fully onboarded, provide UI prompts to allow the user to continue onboarding later. The user can complete their account activation through a new account link (generated by your integration). You can check the state of the `details_submitted` parameter on their account to see if they’ve completed the onboarding process. ## Enable payment methods View your [payment methods settings](https://dashboard.stripe.com/settings/connect/payment_methods) and enable the payment methods you want to support. Card payments are enabled by default but you can enable and disable payment methods as needed. ## Add an endpoint [Server-side] > #### Note > > To display the PaymentSheet before you create a PaymentIntent, see [Collect payment details before creating an Intent](https://docs.stripe.com/payments/accept-a-payment-deferred.md?type=payment). If your Connect platform uses [customer-configured Accounts](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer), use our [guide](https://docs.stripe.com/connect/use-accounts-as-customers.md) to replace `Customer` and event references in your code with the equivalent Accounts v2 API references. This integration uses three Stripe API objects: 1. [PaymentIntent](https://docs.stripe.com/api/payment_intents.md): Stripe uses this to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process. 1. (Optional) [Customer](https://docs.stripe.com/api/customers.md): To set up a payment method for future payments, you must attach it to a *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments). Create a Customer object when your customer creates an account with your business. If your customer is making a payment as a guest, you can create a Customer object before payment and associate it with your own internal representation of the customer’s account later. 1. (Optional) [CustomerSession](https://docs.stripe.com/api/customer_sessions.md): Information on the Customer object is sensitive, and can’t be retrieved directly from an app. A CustomerSession grants the SDK temporary scoped access to the Customer and provides additional configuration options. See a complete list of [configuration options](https://docs.stripe.com/api/customer_sessions/create.md#create_customer_session-components). > If you never save cards to a Customer and don’t allow returning Customers to reuse saved cards, you can omit the Customer and CustomerSession objects from your integration. For security reasons, your app can’t create these objects. Instead, add an endpoint on your server that: 1. Retrieves the Customer, or creates a new one. 1. Creates a [CustomerSession](https://docs.stripe.com/api/customer_sessions.md) for the Customer. 1. Creates a PaymentIntent with the [amount](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-amount), [currency](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-currency), and [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer). 1. Returns the Payment Intent’s *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)), the CustomerSession’s `client_secret`, the Customer’s [id](https://docs.stripe.com/api/customers/object.md#customer_object-id), and your [publishable key](https://dashboard.stripe.com/apikeys) to your app. The payment methods shown to customers during the checkout process are also included on the PaymentIntent. You can let Stripe pull payment methods from your Dashboard settings or you can list them manually. Regardless of the option you choose, know that the currency passed in the PaymentIntent filters the payment methods shown to the customer. For example, if you pass `eur` on the PaymentIntent and have OXXO enabled in the Dashboard, OXXO won’t be shown to the customer because OXXO doesn’t support `eur` payments. Unless your integration requires a code-based option for offering payment methods, Stripe recommends the automated option. This is because Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Payment methods that increase conversion and that are most relevant to the currency and customer’s location are prioritized. #### Manage payment methods from the Dashboard You can manage payment methods from the [Dashboard](https://dashboard.stripe.com/settings/payment_methods). Stripe handles the return of eligible payment methods based on factors such as the transaction’s amount, currency, and payment flow. The PaymentIntent is created using the payment methods you configured in the Dashboard. If you don’t want to use the Dashboard or if you want to specify payment methods manually, you can list them using the `payment_method_types` attribute. #### curl ```bash # Create a Customer (use an existing Customer ID if this is a returning customer) curl https://api.stripe.com/v1/customers \ -u <>: \ -X "POST" \ -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}" # Create an CustomerSession for the Customer curl https://api.stripe.com/v1/customer_sessions \ -u <>: \ -X "POST" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "components[mobile_payment_element][enabled]"=true \ -d "components[mobile_payment_element][features][payment_method_save]"=enabled \ -d "components[mobile_payment_element][features][payment_method_redisplay]"=enabled \ -d "components[mobile_payment_element][features][payment_method_remove]"=enabled # Create a PaymentIntent curl https://api.stripe.com/v1/payment_intents \ -u <>: \ -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}" -X "POST" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "amount"=1099 \ -d "currency"="eur" \ # In the latest version of the API, specifying the `automatic_payment_methods` parameter # is optional because Stripe enables its functionality by default. -d "automatic_payment_methods[enabled]"=true \ -d application_fee_amount="123" \ ``` #### Listing payment methods manually #### curl ```bash # Create a Customer (use an existing Customer ID if this is a returning customer) curl https://api.stripe.com/v1/customers \ -u <>: \ -X "POST" \ -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}" # Create an CustomerSession for the Customer curl https://api.stripe.com/v1/customer_sessions \ -u <>: \ -X "POST" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "components[mobile_payment_element][enabled]"=true \ -d "components[mobile_payment_element][features][payment_method_save]"=enabled \ -d "components[mobile_payment_element][features][payment_method_redisplay]"=enabled \ -d "components[mobile_payment_element][features][payment_method_remove]"=enabled # Create a PaymentIntent curl https://api.stripe.com/v1/payment_intents \ -u <>: \ -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}" -X "POST" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "amount"=1099 \ -d "currency"="eur" \ -d "payment_method_types[]"="bancontact" \ -d "payment_method_types[]"="card" \ -d "payment_method_types[]"="ideal" \ -d "payment_method_types[]"="klarna" \ -d "payment_method_types[]"="sepa_debit" \ -d application_fee_amount="123" \ ``` > Each payment method needs to support the currency passed in the PaymentIntent and your business needs to be based in one of the countries each payment method supports. See the [Payment method integration options](https://docs.stripe.com/payments/payment-methods/integration-options.md) page for more details about what’s supported. ## Integrate the payment sheet [Client-side] To display the mobile Payment Element on your checkout screen, make sure you: - Display the products the customer is purchasing along with the total amount - Use the [Address Element](https://docs.stripe.com/elements/address-element.md?platform=ios) to collect any required shipping information from the customer - Add a checkout button to display Stripe’s UI #### UIKit In your app’s checkout screen, fetch the PaymentIntent client secret, CustomerSession client secret, Customer ID, and publishable key from the endpoint you created in the previous step. Use `STPAPIClient.shared` to set your publishable key and initialize the [PaymentSheet](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet.html). Then, set `STPAPIClient.shared.stripeAccount` to the connected account ID. #### iOS (Swift) ```swift import UIKit@_spi(CustomerSessionBetaAccess) import StripePaymentSheet class CheckoutViewController: UIViewController { @IBOutlet weak var checkoutButton: UIButton! var paymentSheet: PaymentSheet? let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint override func viewDidLoad() { super.viewDidLoad() checkoutButton.addTarget(self, action: #selector(didTapCheckoutButton), for: .touchUpInside) checkoutButton.isEnabled = false // MARK: Fetch the PaymentIntent client secret, CustomerSession client secret, Customer ID, and publishable key var request = URLRequest(url: backendCheckoutUrl) request.httpMethod = "POST" let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any], let customerId = json["customer"] as? String, let customerSessionClientSecret = json["customerSessionClientSecret"] as? String, let paymentIntentClientSecret = json["paymentIntent"] as? String, let publishableKey = json["publishableKey"] as? String, let self = self else { // Handle error return } STPAPIClient.shared.publishableKey = publishableKeySTPAPIClient.shared.stripeAccount = ""{{CONNECTED_ACCOUNT_ID}}""// MARK: Create a PaymentSheet instance var configuration = PaymentSheet.Configuration() configuration.merchantDisplayName = "Example, Inc." configuration.customer = .init(id: customerId, customerSessionClientSecret: customerSessionClientSecret) // Set `allowsDelayedPaymentMethods` to true if your business handles // delayed notification payment methods like US bank accounts. configuration.allowsDelayedPaymentMethods = true self.paymentSheet = PaymentSheet(paymentIntentClientSecret:paymentIntentClientSecret, configuration: configuration) DispatchQueue.main.async { self.checkoutButton.isEnabled = true } }) task.resume() } } ``` When the customer taps the **Checkout** button, call `present` to present the PaymentSheet. After the customer completes the payment, Stripe dismisses the PaymentSheet and calls the completion block with [PaymentSheetResult](https://stripe.dev/stripe-ios/stripe-paymentsheet/Enums/PaymentSheetResult.html). #### iOS (Swift) ```swift @objc func didTapCheckoutButton() { // MARK: Start the checkout process paymentSheet?.present(from: self) { paymentResult in // MARK: Handle the payment result switch paymentResult { case .completed: print("Your order is confirmed") case .canceled: print("Canceled!") case .failed(let error): print("Payment failed: \(error)") } } } ``` #### SwiftUI Create an `ObservableObject` model for your checkout screen. This model publishes a [PaymentSheet](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet.html) and a [PaymentSheetResult](https://stripe.dev/stripe-ios/stripe-paymentsheet/Enums/PaymentSheetResult.html). ```swift import StripePaymentSheet import SwiftUI class CheckoutViewModel: ObservableObject { let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint @Published var paymentSheet: PaymentSheet? @Published var paymentResult: PaymentSheetResult? } ``` Fetch the PaymentIntent client secret, CustomerSession client secret, Customer ID, and publishable key from the endpoint you created in the previous step. Use `STPAPIClient.shared` to set your publishable key and initialize the [PaymentSheet](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet.html). Then, set `STPAPIClient.shared.stripeAccount` to the connected account ID. ```swift @_spi(CustomerSessionBetaAccess) import StripePaymentSheet import SwiftUI class CheckoutViewModel: ObservableObject { let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint @Published var paymentSheet: PaymentSheet? @Published var paymentResult: PaymentSheetResult? func preparePaymentSheet() { // MARK: Fetch thePaymentIntent and Customer information from the backend var request = URLRequest(url: backendCheckoutUrl) request.httpMethod = "POST" let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any], let customerId = json["customer"] as? String, let customerSessionClientSecret = json["customerSessionClientSecret"] as? String, letpaymentIntentClientSecret = json["paymentIntent"] as? String, let publishableKey = json["publishableKey"] as? String, let self = self else { // Handle error return } STPAPIClient.shared.publishableKey = publishableKeySTPAPIClient.shared.stripeAccount = ""{{CONNECTED_ACCOUNT_ID}}""// MARK: Create a PaymentSheet instance var configuration = PaymentSheet.Configuration() configuration.merchantDisplayName = "Example, Inc." configuration.customer = .init(id: customerId, customerSessionClientSecret: customerSessionClientSecret) // Set `allowsDelayedPaymentMethods` to true if your business handles // delayed notification payment methods like US bank accounts. configuration.allowsDelayedPaymentMethods = true DispatchQueue.main.async { self.paymentSheet = PaymentSheet(paymentIntentClientSecret:paymentIntentClientSecret, configuration: configuration) } }) task.resume() } } struct CheckoutView: View { @ObservedObject var model = CheckoutViewModel() var body: some View { VStack { if model.paymentSheet != nil { Text("Ready to pay.") } else { Text("Loading…") } }.onAppear { model.preparePaymentSheet() } } } ``` Add a [PaymentSheet.PaymentButton](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/PaymentButton.html) to your `View`. This behaves similarly to a SwiftUI `Button`, which allows you to customize it by adding a `View`. When you tap the button, it displays the PaymentSheet. After you complete the payment, Stripe dismisses the PaymentSheet and calls the `onCompletion` handler with a [PaymentSheetResult](https://stripe.dev/stripe-ios/stripe-paymentsheet/Enums/PaymentSheetResult.html) object. ```swift @_spi(CustomerSessionBetaAccess) import StripePaymentSheet import SwiftUI class CheckoutViewModel: ObservableObject { let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")! // Your backend endpoint @Published var paymentSheet: PaymentSheet? @Published var paymentResult: PaymentSheetResult? func preparePaymentSheet() { // MARK: Fetch the PaymentIntent and Customer information from the backend var request = URLRequest(url: backendCheckoutUrl) request.httpMethod = "POST" let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any], let customerId = json["customer"] as? String, let customerSessionClientSecret = json["customerSessionClientSecret"] as? String, let paymentIntentClientSecret = json["paymentIntent"] as? String, let publishableKey = json["publishableKey"] as? String, let self = self else { // Handle error return } STPAPIClient.shared.publishableKey = publishableKey STPAPIClient.shared.stripeAccount = ""{{CONNECTED_ACCOUNT_ID}}"" // MARK: Create a PaymentSheet instance var configuration = PaymentSheet.Configuration() configuration.merchantDisplayName = "Example, Inc." configuration.customer = .init(id: customerId, customerSessionClientSecret: customerSessionClientSecret) // Set `allowsDelayedPaymentMethods` to true if your business can handle payment methods // that complete payment after a delay, like SEPA Debit and Sofort. configuration.allowsDelayedPaymentMethods = true DispatchQueue.main.async { self.paymentSheet = PaymentSheet(paymentIntentClientSecret: paymentIntentClientSecret, configuration: configuration) } }) task.resume() } func onPaymentCompletion(result: PaymentSheetResult) { self.paymentResult = result } } struct CheckoutView: View { @ObservedObject var model = CheckoutViewModel() var body: some View { VStack {if let paymentSheet = model.paymentSheet { PaymentSheet.PaymentButton( paymentSheet: paymentSheet, onCompletion: model.onPaymentCompletion ) { Text("Buy") } } else { Text("Loading…") }if let result = model.paymentResult { switch result { case .completed: Text("Payment complete") case .failed(let error): Text("Payment failed: \(error.localizedDescription)") case .canceled: Text("Payment canceled.") } } }.onAppear { model.preparePaymentSheet() } } } ``` If `PaymentSheetResult` is `.completed`, inform the user (for example, by displaying an order confirmation screen). Setting `allowsDelayedPaymentMethods` to true allows [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment methods like US bank accounts. For these payment methods, the final payment status isn’t known when the `PaymentSheet` completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful. ## 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 } } } } } ``` ## Handle post-payment events [Server-side] Stripe sends a [payment_intent.succeeded](https://docs.stripe.com/api/events/types.md#event_types-payment_intent.succeeded) event when the payment completes. Use the [Dashboard webhook tool](https://dashboard.stripe.com/webhooks) or follow the [webhook guide](https://docs.stripe.com/webhooks/quickstart.md) to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow. Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes, and malicious clients could manipulate the response. Setting up your integration to listen for asynchronous events is what enables you to accept [different types of payment methods](https://stripe.com/payments/payment-methods-guide) with a single integration. In addition to handling the `payment_intent.succeeded` event, we recommend handling these other events when collecting payments with the Payment Element: | Event | Description | Action | | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [payment_intent.succeeded](https://docs.stripe.com/api/events/types.md?lang=php#event_types-payment_intent.succeeded) | Sent when a customer successfully completes a payment. | Send the customer an order confirmation and *fulfill* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) their order. | | [payment_intent.processing](https://docs.stripe.com/api/events/types.md?lang=php#event_types-payment_intent.processing) | Sent when a customer successfully initiates a payment, but the payment has yet to complete. This event is most commonly sent when the customer initiates a bank debit. It’s followed by either a `payment_intent.succeeded` or `payment_intent.payment_failed` event in the future. | Send the customer an order confirmation that indicates their payment is pending. For digital goods, you might want to fulfill the order before waiting for payment to complete. | | [payment_intent.payment_failed](https://docs.stripe.com/api/events/types.md?lang=php#event_types-payment_intent.payment_failed) | Sent when a customer attempts a payment, but the payment fails. | If a payment transitions from `processing` to `payment_failed`, offer the customer another attempt to pay. | ## Test the integration #### Cards | Card number | Scenario | How to test | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | 4242424242424242 | The card payment succeeds and doesn’t require authentication. | Fill out the credit card form using the credit card number with any expiration, 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 out the credit card form using the credit card number with any expiration, CVC, and postal code. | | 4000000000009995 | The card is declined with a decline code like `insufficient_funds`. | Fill out the credit card form using the credit card number with any expiration, CVC, and postal code. | | 6205500000000000004 | The UnionPay card has a variable length of 13-19 digits. | Fill out the credit card form using the credit card number with any expiration, 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 is expired or 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. ## 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 `PaymentSheet` 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 PaymentSheet, set [applePay](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html#/s:6Stripe12PaymentSheetC13ConfigurationV8applePayAC05ApplefD0VSgvp) after initializing `PaymentSheet.Configuration` with your Apple merchant ID and the [country code of your business](https://dashboard.stripe.com/settings/account). #### iOS (Swift) ```swift var configuration = PaymentSheet.Configuration() configuration.applePay = .init( merchantId: "merchant.com.your_app_name", merchantCountryCode: "US" ) ``` #### Recurring payments To add Apple Pay to PaymentSheet, set [applePay](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html#/s:6Stripe12PaymentSheetC13ConfigurationV8applePayAC05ApplefD0VSgvp) after initializing `PaymentSheet.Configuration` with your Apple merchant ID and the [country code of your business](https://dashboard.stripe.com/settings/account). 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 = PaymentSheet.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 = PaymentSheet.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 = PaymentSheet.Configuration() configuration.applePay = .init(merchantId: "merchant.com.your_app_name", merchantCountryCode: "US", customHandlers: customHandlers) ``` ## 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: Customize the sheet All customization is configured through the [PaymentSheet.Configuration](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html) object. ### Appearance Customize colors, 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/mobile.md?platform=ios). ### Payment method layout Configure the layout of payment methods in the sheet using [paymentMethodLayout](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/configuration-swift.struct/paymentmethodlayout). You can display them horizontally, vertically, or let Stripe optimize the layout automatically. ![](https://b.stripecdn.com/docs-statics-srv/assets/ios-mpe-payment-method-layouts.9d0513e2fcec5660378ba1824d952054.png) #### Swift ```swift var configuration = PaymentSheet.Configuration() configuration.paymentMethodLayout = .automatic ``` ### 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://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html#/s:18StripePaymentSheet0bC0C13ConfigurationV19merchantDisplayNameSSvp). By default, this is your app’s name. #### Swift ```swift var configuration = PaymentSheet.Configuration() configuration.merchantDisplayName = "My app, Inc." ``` ### Dark mode `PaymentSheet` 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://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html#/s:18StripePaymentSheet0bC0C13ConfigurationV5styleAC18UserInterfaceStyleOvp) to `alwaysLight` or `alwaysDark` mode. ```swift var configuration = PaymentSheet.Configuration() configuration.style = .alwaysLight ``` ### Default billing details To set default values for billing details collected in the payment sheet, configure the `defaultBillingDetails` property. The `PaymentSheet` pre-populates its fields with the values that you provide. ```swift var configuration = PaymentSheet.Configuration() configuration.defaultBillingDetails.address.country = "US" configuration.defaultBillingDetails.email = "foo@bar.com" ``` ### Billing details collection Use `billingDetailsCollectionConfiguration` to specify how you want to collect billing details in the payment sheet. You can collect your customer’s name, email, phone number, and address. If you only want to billing details required by the payment method, set `billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod` to true. In that case, the `PaymentSheet.Configuration.defaultBillingDetails` are set as the payment method’s [billing details](https://docs.stripe.com/api/payment_methods/object.md?lang=node#payment_method_object-billing_details). If you want to collect additional billing details that aren’t necessarily required by the payment method, set `billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod` to false. In that case, the billing details collected through the `PaymentSheet` are set as the payment method’s billing details. ```swift var configuration = PaymentSheet.Configuration() configuration.defaultBillingDetails.email = "foo@bar.com" configuration.billingDetailsCollectionConfiguration.name = .always configuration.billingDetailsCollectionConfiguration.email = .never configuration.billingDetailsCollectionConfiguration.address = .full configuration.billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod = true ``` > Consult with your legal counsel regarding laws that apply to collecting information. Only collect phone numbers if you need them for the transaction. ## Optional: Complete payment in your UI You can present the Payment Sheet to only collect payment method details and then later call a `confirm` method to complete payment in your app’s UI. This is useful if you have a custom buy button or require additional steps after you collect payment details. ![](https://b.stripecdn.com/docs-statics-srv/assets/ios-multi-step.cd631ea4f1cd8cf3f39b6b9e1e92b6c5.png) Complete the payment in your app’s UI #### UIKit The following steps walk you through how to complete payment in your app’s UI. See our sample integration out on [GitHub](https://github.com/stripe/stripe-ios/blob/master/Example/PaymentSheet%20Example/PaymentSheet%20Example/ExampleCustomCheckoutViewController.swift). 1. First, initialize [PaymentSheet.FlowController](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/flowcontroller) instead of `PaymentSheet` and update your UI with its `paymentOption` property. This property contains an image and label representing the customer’s initially selected, default payment method. ```swift PaymentSheet.FlowController.create(paymentIntentClientSecret: paymentIntentClientSecret, configuration: configuration) { [weak self] result in switch result { case .failure(let error): print(error) case .success(let paymentSheetFlowController): self?.paymentSheetFlowController = paymentSheetFlowController // Update your UI using paymentSheetFlowController.paymentOption } } ``` 1. Next, call `presentPaymentOptions` to collect payment details. When completed, update your UI again with the `paymentOption` property. ```swift paymentSheetFlowController.presentPaymentOptions(from: self) { // Update your UI using paymentSheetFlowController.paymentOption } ``` 1. Finally, call `confirm`. ```swift paymentSheetFlowController.confirm(from: self) { paymentResult in // MARK: Handle the payment result switch paymentResult { case .completed: print("Payment complete!") case .canceled: print("Canceled!") case .failed(let error): print(error) } } ``` #### SwiftUI The following steps walk you through how to complete payment in your app’s UI. See our sample integration out on [GitHub](https://github.com/stripe/stripe-ios/blob/master/Example/PaymentSheet%20Example/PaymentSheet%20Example/ExampleSwiftUICustomPaymentFlow.swift). 1. First, initialize [PaymentSheet.FlowController](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/flowcontroller) instead of `PaymentSheet`. Its `paymentOption` property contains an image and label representing the customer’s currently selected payment method, which you can use in your UI. ```swift PaymentSheet.FlowController.create(paymentIntentClientSecret: paymentIntentClientSecret, configuration: configuration) { [weak self] result in switch result { case .failure(let error): print(error) case .success(let paymentSheetFlowController): self?.paymentSheetFlowController = paymentSheetFlowController // Use the paymentSheetFlowController.paymentOption properties in your UI myPaymentMethodLabel = paymentSheetFlowController.paymentOption?.label ?? "Select a payment method" myPaymentMethodImage = paymentSheetFlowController.paymentOption?.image ?? UIImage(systemName: "square.and.pencil")! } } ``` 1. Use [PaymentSheet.FlowController.PaymentOptionsButton](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/flowcontroller/paymentoptionsbutton) to wrap the button that presents the sheet to collect payment details. When `PaymentSheet.FlowController` calls the `onSheetDismissed` argument, the `paymentOption` for the `PaymentSheet.FlowController` instance reflects the currently selected payment method. ```swift PaymentSheet.FlowController.PaymentOptionsButton( paymentSheetFlowController: paymentSheetFlowController, onSheetDismissed: { myPaymentMethodLabel = paymentSheetFlowController.paymentOption?.label ?? "Select a payment method" myPaymentMethodImage = paymentSheetFlowController.paymentOption?.image ?? UIImage(systemName: "square.and.pencil")! }, content: { /* An example button */ HStack { Text(myPaymentMethodLabel) Image(uiImage: myPaymentMethodImage) } } ) ``` 1. Use [PaymentSheet.FlowController.PaymentOptionsButton](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/flowcontroller/paymentoptionsbutton) to wrap the button that confirms the payment. ```swift PaymentSheet.FlowController.ConfirmButton( paymentSheetFlowController: paymentSheetFlowController, onCompletion: { result in // MARK: Handle the payment result switch result { case .completed: print("Payment complete!") case .canceled: print("Canceled!") case .failed(let error): print(error) } }, content: { /* An example button */ Text("Pay") } ) ``` If `PaymentSheetResult` is `.completed`, inform the user (for example, by displaying an order confirmation screen). Setting `allowsDelayedPaymentMethods` to true allows [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment methods like US bank accounts. For these payment methods, the final payment status isn’t known when the `PaymentSheet` completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful. ## Testing Test your account creation flow by [creating accounts](https://docs.stripe.com/connect/testing.md#creating-accounts) and [using OAuth](https://docs.stripe.com/connect/testing.md#using-oauth). Test your **Payment methods** settings for your connected accounts by logging into one of your test accounts and navigating to the [Payment methods settings](https://dashboard.stripe.com/settings/payment_methods). Test your checkout flow with your test keys and a test account. You can use our [test cards](https://docs.stripe.com/testing.md) to test your payments flow and simulate various payment outcomes. # Card element only > This is a Card element only for when platform is ios and mobile-ui is card-element. View the full page at https://docs.stripe.com/connect/end-to-end-saas-platform?platform=ios&mobile-ui=card-element. Securely collect card information on the client with [STPPaymentCardTextField](https://stripe.dev/stripe-ios/stripe-payments-ui/Classes/STPPaymentCardTextField.html), a drop-in UI component provided by the SDK that collects the card number, expiration date, CVC, and postal code. ![](https://d37ugbyn3rpeym.cloudfront.net/docs/mobile/ios/card-field.mp4) ## Prerequisites 1. [Register your platform](https://dashboard.stripe.com/connect). 1. Add business details to [activate your account](https://dashboard.stripe.com/account/onboarding). 1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile). 1. [Customize your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand color. ## Set up Stripe [Server-side] [Client-side] First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register). ### Server-side This integration requires endpoints on your server that talk to the Stripe API. Use the 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 **StripePaymentsUI** 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 'StripePaymentsUI' ``` 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 StripePaymentsUI ``` #### 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/StripePaymentsUI/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 **StripePaymentsUI.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/StripePaymentsUI/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. Configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/test/apikeys) on app start. This enables your app to make requests to the Stripe API. #### Swift ```swift import UIKitimportStripePaymentsUI @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {StripeAPI.defaultPublishableKey = "<>" // do any other necessary launch configuration return true } } ``` > Use your [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) keys when you publish your app. ## Create a connected account Use this guide to learn how to use code to create a connected account. If you’re not ready to integrate yet, you can start by creating a connected account [through the dashboard](https://docs.stripe.com/connect/dashboard/managing-individual-accounts.md). When a user (seller or service provider) signs up on your platform, create a user [Account](https://docs.stripe.com/api/accounts.md) (referred to as a *connected account*) so you can accept payments and move funds to their bank account. Connected accounts represent your user in Stripe’s API and help facilitate the collection of onboarding requirements so Stripe can verify the user’s identity. In our store builder example, the connected account represents the business setting up their Internet store. ![](https://b.stripecdn.com/docs-statics-srv/assets/standard-ios.10c6b24cef1d683d36f2264c726beb1d.png) ### Step 2.1: Create a connected account and prefill information (Server-side) Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account. You can create the connected account by using the [default connected account parameters](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type. #### With default properties ```curl curl -X POST https://api.stripe.com/v1/accounts \ -u "<>:" ``` #### With account type ```curl curl https://api.stripe.com/v1/accounts \ -u "<>:" \ -d type=standard ``` If you’ve already collected information for your connected accounts, you can prefill that information on the `Account` object. You can prefill any account information, including personal and business information, external account information, and so on. After creating the `Account`, create a [Person](https://docs.stripe.com/api/persons/create.md) to represent the person responsible for opening the account, with `relationship.representative` set to true and any account information you want to prefill (for example, their first and last name). ```curl curl https://api.stripe.com/v1/accounts/{{ACCOUNT_ID}}/persons \ -u "<>:" \ -d first_name=Jenny \ -d last_name=Rosen \ -d "relationship[representative]"=true ``` Connect Onboarding doesn’t ask for the prefilled information. However, it does ask the account holder to confirm the prefilled information before accepting the [Connect service agreement](https://docs.stripe.com/connect/service-agreement-types.md). When testing your integration, prefill account information using [test data](https://docs.stripe.com/connect/testing.md). ### Step 2.2: Create an account link (Server-side) You can create an account link by calling the [Account Links](https://docs.stripe.com/api/account_links.md) API with the following parameters: - `account` - `refresh_url` - `return_url` - `type` = `account_onboarding` ```curl curl https://api.stripe.com/v1/account_links \ -u "<>:" \ -d account="{{CONNECTEDACCOUNT_ID}}" \ --data-urlencode refresh_url="https://example.com/reauth" \ --data-urlencode return_url="https://example.com/return" \ -d type=account_onboarding ``` ### Step 2.3: Redirect your user to the account link URL (Client-side) The response to your [Account Links](https://docs.stripe.com/api/account_links.md) request includes a value for the key `url`. Redirect to this link to send your user into the flow. URLs from the [Account Links](https://docs.stripe.com/api/account_links.md) API are temporary and can be used only once because they grant access to the account holder’s personal information. Authenticate the user in your application before redirecting them to this URL. If you want to prefill information, you must do so before generating the account link. After you create the account link for a Standard account, you won’t be able to read or write information for the account. > Don’t email, text, or otherwise send account link URLs outside of your platform application. Instead, provide them to the authenticated account holder within your application. #### Swift ```swift import UIKit import SafariServices let BackendAPIBaseURL: String = "" // Set to the URL of your backend server class ConnectOnboardViewController: UIViewController { // ... override func viewDidLoad() { super.viewDidLoad() let connectWithStripeButton = UIButton(type: .system) connectWithStripeButton.setTitle("Connect with Stripe", for: .normal) connectWithStripeButton.addTarget(self, action: #selector(didSelectConnectWithStripe), for: .touchUpInside) view.addSubview(connectWithStripeButton) // ... } @objc func didSelectConnectWithStripe() { if let url = URL(string: BackendAPIBaseURL)?.appendingPathComponent("onboard-user") { var request = URLRequest(url: url) request.httpMethod = "POST" let task = URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any], let accountURLString = json["url"] as? String, let accountURL = URL(string: accountURLString) else { // handle error } let safariViewController = SFSafariViewController(url: accountURL) safariViewController.delegate = self DispatchQueue.main.async { self.present(safariViewController, animated: true, completion: nil) } } } } // ... } extension ConnectOnboardViewController: SFSafariViewControllerDelegate { func safariViewControllerDidFinish(_ controller: SFSafariViewController) { // the user may have closed the SFSafariViewController instance before a redirect // occurred. Sync with your backend to confirm the correct state } } ``` ### Step 2.4: Handle the user returning to your platform (Client-side) *Connect* (Connect is Stripe's solution for multi-party businesses, such as marketplace or software platforms, to route payments between sellers, customers, and other recipients) Onboarding requires you to pass both a `return_url` and `refresh_url` to handle all cases where the user will be redirected to your platform. It’s important that you implement these correctly to provide the best experience for your user. You can set up a [universal link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) to enable iOS to redirect to your app automatically. #### return_url Stripe issues a redirect to this URL when the user completes the Connect onboarding flow. This doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. This only means the flow was entered and exited properly. No state is passed through this URL. After a user is redirected to your `return_url`, check the state of the `details_submitted` parameter on their account by doing either of the following: - Listening to `account.updated` *webhooks* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) - Calling the [Accounts](https://docs.stripe.com/api/accounts.md) API and inspecting the returned object #### refresh_url Your user will be redirected to the `refresh_url` in these cases: - The link is expired (a few minutes went by since the link was created) - The link was already visited (the user refreshed the page or clicked back or forward in the browser) - The link was shared in a third-party application such as a messaging client that attempts to access the URL to preview it. Many clients automatically visit links which cause them to become expired - Your platform is no longer able to access the account - The account has been rejected Your `refresh_url` should trigger a method on your server to call [Account Links](https://docs.stripe.com/api/account_links.md) again with the same parameters, and redirect the user to the Connect Onboarding flow to create a seamless experience. ### Step 2.5: Handle users that have not completed onboarding A user that is redirected to your `return_url` might not have completed the onboarding process. Use the `/v1/accounts` endpoint to retrieve the user’s account and check for `charges_enabled`. If the account isn’t fully onboarded, provide UI prompts to allow the user to continue onboarding later. The user can complete their account activation through a new account link (generated by your integration). You can check the state of the `details_submitted` parameter on their account to see if they’ve completed the onboarding process. ## Accept a payment ### Step 3.1: Create your checkout page (Client-side) Securely collect card information on the client with [STPPaymentCardTextField](https://stripe.dev/stripe-ios/stripe-payments-ui/Classes/STPPaymentCardTextField.html), a drop-in UI component provided by the SDK that collects the card number, expiration date, CVC, and postal code. ![](https://d37ugbyn3rpeym.cloudfront.net/docs/mobile/ios/card-field.mp4) Create an instance of the card component and a **Pay** button with the following code: #### Swift ```swift import UIKit import StripePaymentsUI class CheckoutViewController: UIViewController { lazy var cardTextField: STPPaymentCardTextField = { let cardTextField = STPPaymentCardTextField() return cardTextField }() lazy var payButton: UIButton = { let button = UIButton(type: .custom) button.layer.cornerRadius = 5 button.backgroundColor = .systemBlue button.titleLabel?.font = UIFont.systemFont(ofSize: 22) button.setTitle("Pay", for: .normal) button.addTarget(self, action: #selector(pay), for: .touchUpInside) return button }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white let stackView = UIStackView(arrangedSubviews: [cardTextField, payButton]) stackView.axis = .vertical stackView.spacing = 20 stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView) NSLayoutConstraint.activate([ stackView.leftAnchor.constraint(equalToSystemSpacingAfter: view.leftAnchor, multiplier: 2), view.rightAnchor.constraint(equalToSystemSpacingAfter: stackView.rightAnchor, multiplier: 2), stackView.topAnchor.constraint(equalToSystemSpacingBelow: view.topAnchor, multiplier: 2), ]) } @objc func pay() { // ... } } ``` Run your app, and make sure your checkout page shows the card component and pay button. ### Step 3.2: Create a PaymentIntent (Server-side) (Client-side) Stripe uses a [PaymentIntent](https://docs.stripe.com/api/payment_intents.md) object to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process. ### Server-side On your server, make an endpoint that creates a PaymentIntent with an [amount](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-amount) and [currency](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-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 ```bash curl https://api.stripe.com/v1/payment_intents \ -u <>: \ -d amount=1000 \ -d currency="usd" \ -d "automatic_payment_methods[enabled]"=true \ -d application_fee_amount="123" \ -H "Stripe-Account: {{CONNECTED_STRIPE_ACCOUNT_ID}}" ``` In our store builder example, we want to build a business where customers pay businesses directly. To set up this business: - Indicate a purchase from the business is a direct charge with the `Stripe-Account` header. - Specify how much of the purchase from the business goes to the platform with `application_fee_amount`. When a sale occurs, Stripe transfers the `application_fee_amount` from the connected account to the platform and deducts the Stripe fee from the connected account’s share. An illustration of this funds flow is below: ![](https://b.stripecdn.com/docs-statics-srv/assets/direct_charges.a2a8b68037ac95fe22140d6dde9740d3.svg) Instead of passing the entire PaymentIntent object to your app, just return its *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)). The PaymentIntent’s client secret is a unique key that lets you confirm the payment and update card details on the client, without allowing manipulation of sensitive information, like payment amount. ### Client-side Set the connected account id as an argument to the client application in the client-side libraries. #### Swift ```swift import UIKit import StripePayments @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { StripeAPI.defaultPublishableKey = "<>" STPAPIClient.shared.stripeAccount = ""{{CONNECTED_ACCOUNT_ID}}"" return true } } ``` On the client, request a PaymentIntent from your server and store its *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)). #### Swift ```swift class CheckoutViewController: UIViewController { var paymentIntentClientSecret: String? // ...continued from previous step override func viewDidLoad() { // ...continued from previous step startCheckout() } func startCheckout() { // Request a PaymentIntent from your server and store its client secret // Click View full sample to see a complete implementation } } ``` ### Step 3.3: Submit the payment to Stripe (Client-side) When the customer taps the **Pay** button, *confirm* (Confirming 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) the `PaymentIntent` to complete the payment. First, assemble a [STPPaymentIntentParams](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentIntentParams.html) object with: 1. The card text field’s payment method details 1. The `PaymentIntent` client secret from your server Rather than sending the entire PaymentIntent object to the client, use its *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)). This is different from your API keys that authenticate Stripe API requests. The client secret is a string that lets your app access important fields from the PaymentIntent (for example, `status`) while hiding sensitive ones (for example, `customer`). Handle the client secret carefully, because it can complete the charge. Don’t log it, embed it in URLs, or expose it to anyone but the customer. Next, complete the payment by calling the [STPPaymentHandler confirmPayment](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentHandler.html#/c:@M@StripePayments@objc\(cs\)STPPaymentHandler\(im\)confirmPayment:withAuthenticationContext:completion:) method. #### Swift ```swift class CheckoutViewController: UIViewController { // ... @objc func pay() { guard let paymentIntentClientSecret = paymentIntentClientSecret else { return } // Collect card details let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) paymentIntentParams.paymentMethodParams = cardTextField.paymentMethodParams // Submit the payment let paymentHandler = STPPaymentHandler.shared() paymentHandler.confirmPayment(paymentIntentParams, with: self) { (status, paymentIntent, error) in switch (status) { case .failed: self.displayAlert(title: "Payment failed", message: error?.localizedDescription ?? "") break case .canceled: self.displayAlert(title: "Payment canceled", message: error?.localizedDescription ?? "") break case .succeeded: self.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "", restartDemo: true) break @unknown default: fatalError() break } } } } extension CheckoutViewController: STPAuthenticationContext { func authenticationPresentingViewController() -> UIViewController { return self } } ``` You can [save a customer’s payment card details](https://docs.stripe.com/payments/payment-intents.md#future-usage) on payment confirmation by providing both `setupFutureUsage` and a `customer` on the `PaymentIntent`. You can also supply these parameters when creating the `PaymentIntent` on your server. Supplying an appropriate `setupFutureUsage` value for your application might require your customer to complete additional authentication steps, but reduces the chance of banks rejecting future payments. [Learn how to optimize cards for future payments](https://docs.stripe.com/payments/payment-intents.md#future-usage) and determine which value to use for your application. | How you intend to use the card | `setup_future_usage` enum value | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | | *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) payments only | `on_session` | | *Off-session* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information) payments only | `off_session` | | Both on- and off-session payments | `off_session` | You can use a card that’s set up for on-session payments to make off-session payments, but the bank is more likely to reject the off-session payment and require authentication from the cardholder. If regulation such as [Strong Customer Authentication](https://docs.stripe.com/strong-customer-authentication.md) requires authentication, `STPPaymentHandler` presents view controllers using the [STPAuthenticationContext](https://stripe.dev/stripe-ios/stripe-payments/Protocols/STPAuthenticationContext.html) passed in and walks the customer through that process. [Learn how to support 3D Secure Authentication on iOS](https://docs.stripe.com/payments/3d-secure.md?platform=ios). If the payment succeeds, the completion handler is called with a status of `.succeeded`. If it fails, the status is `.failed` and you can display the `error.localizedDescription` to the user. You can also check the status of a `PaymentIntent` in the [Dashboard](https://dashboard.stripe.com/test/payments) or by inspecting the `status` property on the object. ### Step 3.4: Test the integration (Client-side) ​​Several test cards are available for you to use in a sandbox to make sure this integration is ready. Use them with any CVC and an expiration date in the future. | Number | Description | | ---------------- | ----------------------------------------------------------------------------------------- | | 4242424242424242 | Succeeds and immediately processes the payment. | | 4000002500003155 | Requires authentication. Stripe triggers a modal asking for the customer to authenticate. | | 4000000000009995 | Always fails with a decline code of `insufficient_funds`. | For the full list of test cards see our guide on [testing](https://docs.stripe.com/testing.md). ### Step 3.5: Fulfillment (Server-side) After payment completes, you must handle any necessary *fulfillment* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected). For example, a store builder must alert the business to send the purchased item to the customer. Configure a *webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) endpoint [in your dashboard](https://dashboard.stripe.com/account/webhooks) (for events *from your Connect applications*). ![](https://b.stripecdn.com/docs-statics-srv/assets/connect_webhooks.4de92f78dcd94b36838010c85c8a051f.png) Then create an HTTP endpoint on your server to monitor for completed payments to then enable your users (connected accounts) to fulfill purchases. #### Ruby ```ruby # Using Sinatra. require 'sinatra' require 'stripe' set :port, 4242 # Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. # Find your keys at https://dashboard.stripe.com/apikeys. Stripe.api_key = '<>' # If you are testing your webhook locally with the Stripe CLI you # can find the endpoint's secret by running `stripe listen` # Otherwise, find your endpoint's secret in your webhook settings in # the Developer Dashboard endpoint_secret = 'whsec_...' post '/webhook' do payload = request.body.read sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil # Verify webhook signature and extract the event. # See https://stripe.com/docs/webhooks#verify-events for more information. begin event = Stripe::Webhook.construct_event( payload, sig_header, endpoint_secret ) rescue JSON::ParserError => e # Invalid payload. status 400 return rescue Stripe::SignatureVerificationError => e # Invalid Signature. status 400 return end if event['type'] == 'payment_intent.succeeded' payment_intent = event['data']['object'] connected_account_id = event['account'] handle_successful_payment_intent(connected_account_id, payment_intent) end status 200 end def handle_successful_payment_intent(connected_account_id, payment_intent) # Fulfill the purchase puts 'Connected account ID: ' + connected_account_id puts payment_intent.to_s end ``` Learn more in our [fulfillment guide for payments](https://docs.stripe.com/webhooks/handling-payment-events.md). ### Testing webhooks locally Use the Stripe CLI to test webhooks locally. 1. First, [install the Stripe CLI](https://docs.stripe.com/stripe-cli/install.md) on your machine if you haven’t already. 1. Then, to log in run `stripe login` in the command line, and follow the instructions. 1. Finally, to allow your local host to receive a simulated event on your connected account run `stripe listen --forward-connect-to localhost:{PORT}/webhook` in one terminal window, and run `stripe trigger --stripe-account={{CONNECTED_STRIPE_ACCOUNT_ID}} payment_intent.succeeded` (or trigger any other [supported event](https://github.com/stripe/stripe-cli/wiki/trigger-command#supported-events)) in another. ## Testing Test your account creation flow by [creating accounts](https://docs.stripe.com/connect/testing.md#creating-accounts) and [using OAuth](https://docs.stripe.com/connect/testing.md#using-oauth). Test your **Payment methods** settings for your connected accounts by logging into one of your test accounts and navigating to the [Payment methods settings](https://dashboard.stripe.com/settings/payment_methods). Test your checkout flow with your test keys and a test account. You can use our [test cards](https://docs.stripe.com/testing.md) to test your payments flow and simulate various payment outcomes. # Payment sheet > This is a Payment sheet for when platform is android and mobile-ui is payment-element. View the full page at https://docs.stripe.com/connect/end-to-end-saas-platform?platform=android&mobile-ui=payment-element. ![](https://b.stripecdn.com/docs-statics-srv/assets/android-overview.471eaf89a760f5b6a757fd96b6bb9b60.png) Integrate Stripe’s prebuilt payment UI into the checkout of your Android app with the [PaymentSheet](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/index.html) class. ## Prerequisites 1. [Register your platform](https://dashboard.stripe.com/connect). 1. Add business details to [activate your account](https://dashboard.stripe.com/account/onboarding). 1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile). 1. [Customize your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand color. ## Set up Stripe [Server-side] [Client-side] First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register). ### Server-side This integration requires endpoints on your server that talk to the Stripe API. Use the 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 Android SDK](https://github.com/stripe/stripe-android) is open source and [fully documented](https://stripe.dev/stripe-android/). To install the SDK, add `stripe-android` to the `dependencies` block of your [app/build.gradle](https://developer.android.com/studio/build/dependencies) file: #### Kotlin ```kotlin plugins { id("com.android.application") } android { ... } dependencies { // ... // Stripe Android SDK implementation("com.stripe:stripe-android:23.0.2") // Include the financial connections SDK to support US bank account as a payment method implementation("com.stripe:financial-connections:23.0.2") } ``` > For details on the latest SDK release and past versions, see the [Releases](https://github.com/stripe/stripe-android/releases) page on GitHub. To receive notifications when a new release is published, [watch releases for the repository](https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/configuring-notifications#configuring-your-watch-settings-for-an-individual-repository). Configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/apikeys) so that it can make requests to the Stripe API, such as in your `Application` subclass: #### Kotlin ```kotlin import com.stripe.android.PaymentConfiguration class MyApp : Application() { override fun onCreate() { super.onCreate() PaymentConfiguration.init( applicationContext, "<>" ) } } ``` > Use your [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) keys when you publish your app. ## Create a connected account When a user (seller or service provider) signs up on your platform, create a user [Account](https://docs.stripe.com/api/accounts.md) (referred to as a *connected account*) so you can accept payments and move funds to their bank account. Connected accounts represent your user in Stripe’s API and help facilitate the collection of onboarding requirements so Stripe can verify the user’s identity. In our store builder example, the connected account represents the business setting up their Internet store. ![Account creation flow](https://b.stripecdn.com/docs-statics-srv/assets/standard-android.04900ae101e927a74a6f2b0afb53bf23.png) ### Step 2.1: Create a connected account and prefill information (Server-side) Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account. You can create the connected account by using the [default connected account parameters](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type. #### With default properties ```curl curl -X POST https://api.stripe.com/v1/accounts \ -u "<>:" ``` #### With account type ```curl curl https://api.stripe.com/v1/accounts \ -u "<>:" \ -d type=standard ``` If you’ve already collected information for your connected accounts, you can prefill that information on the `Account` object. You can prefill any account information, including personal and business information, external account information, and so on. After creating the `Account`, create a [Person](https://docs.stripe.com/api/persons/create.md) to represent the person responsible for opening the account, with `relationship.representative` set to true and any account information you want to prefill (for example, their first and last name). ```curl curl https://api.stripe.com/v1/accounts/{{ACCOUNT_ID}}/persons \ -u "<>:" \ -d first_name=Jenny \ -d last_name=Rosen \ -d "relationship[representative]"=true ``` Connect Onboarding doesn’t ask for the prefilled information. However, it does ask the account holder to confirm the prefilled information before accepting the [Connect service agreement](https://docs.stripe.com/connect/service-agreement-types.md). When testing your integration, prefill account information using [test data](https://docs.stripe.com/connect/testing.md). ### Step 2.2: Create an account link (Server-side) You can create an account link by calling the [Account Links](https://docs.stripe.com/api/account_links.md) API with the following parameters: - `account` - `refresh_url` - `return_url` - `type` = `account_onboarding` ```curl curl https://api.stripe.com/v1/account_links \ -u "<>:" \ -d account="{{CONNECTEDACCOUNT_ID}}" \ --data-urlencode refresh_url="https://example.com/reauth" \ --data-urlencode return_url="https://example.com/return" \ -d type=account_onboarding ``` ### Step 2.3: Redirect your user to the account link URL (Client-side) The response to your [Account Links](https://docs.stripe.com/api/account_links.md) request includes a value for the key `url`. Redirect to this link to send your user into the flow. Account Links are temporary and are single-use only because they grant access to the connected account user’s personal information. Authenticate the user in your application before redirecting them to this URL. If you want to prefill information, you must do so before generating the account link. After you create the account link for a Standard account, you won’t be able to read or write information for the account. > Don’t email, text, or otherwise send account link URLs outside of your platform application. Instead, provide them to the authenticated account holder within your application. ```xml