# Build a marketplace

Create connected accounts using Accounts v1, collect payments from customers, then pay out to sellers or service providers on your marketplace.

> #### 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 Marketplace guide](https://docs.stripe.com/connect/marketplace.md).

# Web

> This is a Web for when platform is web. View the full page at https://docs.stripe.com/connect/end-to-end-marketplace?platform=web.

This guide explains how to accept payments and move funds to the bank accounts of your service providers or sellers. For demonstration purposes, we’ll build a home-rental marketplace that connects homeowners to potential tenants. We’ll also show you how to accept payments from tenants (customers) and pay out homeowners (your platform’s users).

## Prerequisites

1. [Register your platform](https://dashboard.stripe.com/connect).
1. [Verify and add business details in Dashboard](https://dashboard.stripe.com/account/onboarding).
1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile).
1. [Customise your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand colour.

## Set up Stripe [Server-side]

Install Stripe’s official libraries to 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

#### Accounts v2

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/v2/core/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.

#### Accounts v1

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.

### Create a connected account with prefilled information

#### Accounts v2

Use the `/v2/core/accounts` API to [create](https://docs.stripe.com/api/v2/core/accounts/create.md) a connected account by specifying the [connected account dashboard and responsibilities](https://docs.stripe.com/connect/accounts-v2/connected-account-configuration.md).

```curl
curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-01-28.clover" \
  --json '{
    "contact_email": "furever_contact@example.com",
    "display_name": "Furever",
    "defaults": {
        "responsibilities": {
            "fees_collector": "application",
            "losses_collector": "application"
        }
    },
    "dashboard": "express",
    "identity": {
        "business_details": {
            "registered_name": "Furever"
        },
        "country": "us",
        "entity_type": "company"
    },
    "configuration": {
        "merchant": {
            "capabilities": {
                "card_payments": {
                    "requested": true
                }
            }
        },
        "recipient": {
            "capabilities": {
                "stripe_balance": {
                    "stripe_transfers": {
                        "requested": true
                    }
                }
            }
        }
    },
    "include": [
        "configuration.merchant",
        "configuration.recipient",
        "identity",
        "requirements"
    ]
  }'
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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/v2/core/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 -X POST https://api.stripe.com/v2/core/accounts/{{ACCOUNT_ID}}/persons \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-03-25.preview" \
  --json '{
    "given_name": "Jenny",
    "surname": "Rosen",
    "email": "jenny.rosen@example.com",
    "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 v2](https://docs.stripe.com/api/v2/core/account-links.md) API with the following parameters:

- `account`
- `refresh_url`
- `return_url`
- `type` = `account_onboarding`
- `configurations` = `recipient` and `merchant`

```curl
curl -X POST https://api.stripe.com/v2/core/account_links \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2025-08-27.preview" \
  --json '{
    "account": "{{CONNECTEDACCOUNT_ID}}",
    "use_case": {
        "type": "account_onboarding",
        "account_onboarding": {
            "configurations": [
                "recipient",
                "merchant"
            ],
            "refresh_url": "https://example.com/reauth",
            "return_url": "https://example.com/return"
        }
    }
  }'
```

### Redirect your user to the account link URL

The create Account Link response includes a `url` value. After authenticating the user in your application, re-direct them to this URL to send them into the flow. Account link URLs are temporary and single-use only, because they grant access to the connected account user’s personal information. If you want to prefill information, you must do so before generating the account link. After you create the account link, you can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

#### Item 3

### 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 account user is re-directed to your platform.

> You can use HTTP for your `return_url` and `refresh_url` while in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes the onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that the flow was entered and exited properly.

No state is passed through this URL. After a user is re-directed to your `return_url`, check the state of the requirements on their account by doing either of the following:

- Listen to `v2.core.account[requirements].updated` webhooks.
- Call the [Accounts v2](https://docs.stripe.com/api/v2/core/accounts/retrieve.md) API and inspect the returned object.

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v2](https://docs.stripe.com/api/v2/core/account-links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s redirected to your `return_url` might not have completed the onboarding process. Use the `/v2/core/accounts` endpoint to retrieve the user’s account and check if [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status) is `active`. If the status isn’t `active` and [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status_details.code](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status_details-code) is `requirements_past_due`, provide UI prompts to allow the user to continue onboarding through a new account link. For other codes, handle them as needed.

#### Accounts v1

Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account by specifying the [connected account properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type.

```curl
curl https://api.stripe.com/v1/accounts \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "controller[losses][payments]=application" \
  -d "controller[fees][payer]=application" \
  -d "controller[stripe_dashboard][type]=express"
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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 "<<YOUR_SECRET_KEY>>:" \
  -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 v1](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 "<<YOUR_SECRET_KEY>>:" \
  -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 create Account Link response includes a `url` value. After authenticating the user in your application, re-direct to this URL to send them into the flow. Account link URLs from the [Account Links v1](https://docs.stripe.com/api/account_links.md) API 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 re-directing 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 can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

### 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 account user is re-directed 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 in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that 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:

- Listen to `account.updated` webhooks
- Call the [Accounts v1](https://docs.stripe.com/api/accounts.md) API and inspect the returned object

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v1](https://docs.stripe.com/api/account_links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s re-directed 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 through a new account link. You can determine whether they’ve completed onboarding by checking the state of the `details_submitted` parameter on their account.

## Enable payment methods

View your [payment methods settings](https://dashboard.stripe.com/settings/payment_methods) and enable the payment methods you want to support. Card payments, Google Pay, and Apple Pay are enabled by default but you can enable and disable payment methods as needed.

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 prioritised. Lower priority payment methods are hidden in an overflow menu.

## Accept a payment

Use [Stripe Checkout](https://stripe.com/payments/checkout) to accept payments. Checkout supports multiple payment methods and automatically shows the most relevant ones to your customer. You can accept payments with Checkout using a Stripe-hosted page or add a pre-built embeddable payment form directly in your website. You can also create a custom flow (using Payment Element) to accept multiple payment methods with a single front-end integration.

#### Stripe-hosted page

### Create a Checkout Session (Client and Server)

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.

Add a checkout button to your website that calls a server-side endpoint to create a Checkout Session.

```html
<html>
  <head>
    <title>Checkout</title>
  </head>
  <body>
    <form action="/create-checkout-session" method="POST">
      <button type="submit">Checkout</button>
    </form>
  </body>
</html>
```

On your server, make the following call to Stripe’s 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 "<<YOUR_SECRET_KEY>>:" \
  -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 "payment_intent_data[transfer_data][destination]={{CONNECTEDACCOUNT_ID}}" \
  --data-urlencode "success_url=https://example.com/success"
```

- `line_items` - This argument represents the items the customer is purchasing. The items are displayed in the Stripe-hosted user interface.
- `success_url` - This argument redirects a user after they complete a payment.
- `payment_intent_data[application_fee_amount]` - This argument specifies the amount your platform plans to take from the transaction. The full charge amount is immediately transferred from the platform to the connected account that’s specified by `transfer_data[destination]` after the charge is captured. The `application_fee_amount` is then transferred back to the platform, and the Stripe fee is deducted from the platform’s amount.
- `payment_intent_data[transfer_data][destination]` – This argument indicates that this is a [destination charge](https://docs.stripe.com/connect/destination-charges.md). A destination charge means the charge is processed on the platform and then the funds are immediately and automatically transferred to the connected account’s pending balance. For our home-rental example, we want to build an experience where the customer pays through the platform and the homeowner gets paid by the platform.
![](https://b.stripecdn.com/docs-statics-srv/assets/application_fee_amount.837aa2339469b3c1a4319672971c1367.svg)

Checkout uses the brand settings of your platform account for destination charges. For more information, see [Customise branding](https://docs.stripe.com/connect/destination-charges.md?platform=web&ui=stripe-hosted#branding).

This Session creates a destination charge. If you need to control the timing of transfers or need to transfer funds from a single payment to multiple parties, use [separate charges and transfers](https://docs.stripe.com/connect/separate-charges-and-transfers.md) instead.

### 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, 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. 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 authorised 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 (Server-side)

A Checkout Session controls what your customer sees in the embedded payment form such as line items, the order amount and currency, and acceptable payment methods.

On your server, make the following call to Stripe’s API.

```curl
curl https://api.stripe.com/v1/checkout/sessions \
  -u "<<YOUR_SECRET_KEY>>:" \
  -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 "payment_intent_data[transfer_data][destination]={{CONNECTEDACCOUNT_ID}}" \
  -d ui_mode=embedded_page \
  --data-urlencode "return_url=https://example.com/checkout/return?session_id={CHECKOUT_SESSION_ID}"
```

- `line_items` - The items the customer is purchasing. Displayed in the Stripe-hosted user interface.
- `return_url` - This argument redirects a user after they complete a payment attempt.
- `payment_intent_data[application_fee_amount]` - This argument specifies the amount your platform plans to take from the transaction. The full charge amount is immediately transferred from the platform to the connected account that’s specified by `transfer_data[destination]` after the charge is captured. The `application_fee_amount` is then transferred back to the platform, and the Stripe fee is deducted from the platform’s amount.
- `payment_intent_data[transfer_data][destination]` – Indicates that this is a [destination charge](https://docs.stripe.com/connect/destination-charges.md), which means the charge is processed on the platform. Then the funds are immediately and automatically transferred to the connected account’s pending balance. In the home rental example, if a customer pays through the platform the homeowner gets paid by the platform.

### 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
<head>
  <script src="https://js.stripe.com/dahlia/stripe.js"></script>
</head>
<body>
  <div id="checkout">
    <!-- Checkout will insert the payment form here -->
  </div>
</body>
```

Initialise 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('<<YOUR_PUBLISHABLE_KEY>>');

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.createEmbeddedCheckoutPage({
    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 (
    <EmbeddedCheckoutProvider
      stripe={stripePromise}
      options={options}
    >
      <EmbeddedCheckout />
    </EmbeddedCheckoutProvider>
  )
}
```

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.
![](https://b.stripecdn.com/docs-statics-srv/assets/application_fee_amount.837aa2339469b3c1a4319672971c1367.svg)

Checkout uses the brand settings of your platform account for destination charges. For more information, see [Customise branding](https://docs.stripe.com/connect/destination-charges.md?platform=web&ui=stripe-hosted#branding).

This Session creates a destination charge. If you need to control the timing of transfers or need to transfer funds from a single payment to multiple parties, use [separate charges and transfers](https://docs.stripe.com/connect/separate-charges-and-transfers.md) instead.

### 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, 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. 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 authorised 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, 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 prioritised. Lower priority payment methods are hidden in an overflow menu.

#### Manage payment methods from the Dashboard

Create a PaymentIntent on your server with an amount and currency enabled. In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. 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 to support the payment methods that you configure in the Dashboard, as applicable. 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
curl https://api.stripe.com/v1/payment_intents \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d amount=1099 \
  -d currency=eur \
  -d "automatic_payment_methods[enabled]=true" \
  -d application_fee_amount=123 \
  -d "transfer_data[destination]={{CONNECTEDACCOUNT_ID}}"
```

#### List payment methods manually

Create a PaymentIntent on your server with an amount, currency, and a list of payment method types. 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
curl https://api.stripe.com/v1/payment_intents \
  -u "<<YOUR_SECRET_KEY>>:" \
  -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
```

Choose the currency based on the payment methods you want to offer. Some payment methods support multiple currencies and countries. This guide uses Bancontact, credit cards, EPS, iDEAL, Przelewy24, and SEPA Direct Debit, and Sofort.

> 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 front-end framework such as 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
<form id="payment-form" data-secret="<%= @intent.client_secret %>">
  <div id="payment-element">
    <!-- placeholder for Elements -->
  </div>
  <button id="submit">Submit</button>
</form>
```

```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](https://docs.stripe.com/payments/payment-element.md). The Payment Element is a pre-built 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.

If you choose to use an iframe and want to accept Apple Pay or Google Pay, the iframe must have the [allow](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allowpaymentrequest) attribute set to equal `"payment *"`.

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.

#### HTML + JS

### Set up Stripe.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
<head>
  <title>Checkout</title>
  <script src="https://js.stripe.com/dahlia/stripe.js"></script>
</head>
```

Create an instance of Stripe with the following JavaScript on your checkout page:

```javascript
// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = Stripe('<<YOUR_PUBLISHABLE_KEY>>');
```

### Add 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
<form id="payment-form">
  <div id="payment-element">
    <!-- Elements will create form elements here -->
  </div>
  <button id="submit">Submit</button>
  <div id="error-message">
    <!-- Display error message to your customers here -->
  </div>
</form>
```

When the previous form loads, create an instance of the Payment Element and mount it to the container DOM node. Pass the [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) from the previous step into `options` when you create the [Elements](https://docs.stripe.com/js/elements_object/create) instance:

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.

```javascript
const options = {
  clientSecret: '{{CLIENT_SECRET}}',
  // Fully customizable with appearance API.
  appearance: {/*...*/},
};

// Set up Stripe.js and Elements to use in checkout form, passing the client secret obtained in a previous stepconst elements = stripe.elements(options);

// Create and mount the Payment Element
const paymentElementOptions = { layout: 'accordion'};
const paymentElement = elements.create('payment', paymentElementOptions);
paymentElement.mount('#payment-element');

```

#### React

### Set up Stripe.js

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` to the `Elements` provider. Also pass the [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret) from the previous step as `options` to 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('<<YOUR_PUBLISHABLE_KEY>>');

function App() {
  const options = {
    // passing the client secret obtained in step 3
    clientSecret: '{{CLIENT_SECRET}}',
    // Fully customizable with appearance API.
    appearance: {/*...*/},
  };

  return (
    <Elements stripe={stripePromise} options={options}>
      <CheckoutForm />
    </Elements>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
```

### Add the Payment Element component

Use the `PaymentElement` component to build your form:

```jsx
import React from 'react';
import {PaymentElement} from '@stripe/react-stripe-js';

const CheckoutForm = () => {
  return (
    <form><PaymentElement />
      <button>Submit</button>
    </form>
  );
};

export default CheckoutForm;
```

Stripe Elements is a collection of drop-in UI components. To further customise your form or collect different customer information, browse the [Elements docs](https://docs.stripe.com/payments/elements.md).

The Payment Element renders a dynamic form that allows your customer to pick a payment method. For each payment method, the form automatically asks the customer to fill in all necessary payment details.

### Customise appearance

Customise the Payment Element to match the design of your site by passing the [appearance object](https://docs.stripe.com/js/elements_object/create#stripe_elements-options-appearance) into `options` when creating the `Elements` provider.

### Collect addresses

By default, the Payment Element only collects the necessary billing address details. Some behaviour, such as [calculating tax](https://docs.stripe.com/api/tax/calculations/create.md) or entering shipping details, requires your customer’s full address. You can:

- Use the [Address Element](https://docs.stripe.com/elements/address-element.md) to take advantage of autocomplete and localisation features to collect your customer’s full address. This helps ensure the most accurate tax calculation.
- Collect address details using your own custom form.

### Request Apple Pay merchant token

If you’ve configured your integration to [accept Apple Pay payments](https://docs.stripe.com/payments/accept-a-payment.md?payment-ui=elements&api-integration=checkout), we recommend configuring the Apple Pay interface to return a merchant token to enable merchant initiated transactions (MIT). [Request the relevant merchant token type](https://docs.stripe.com/apple-pay/merchant-tokens.md?pay-element=web-pe) in the Payment Element.

### 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, such as a bank authorisation 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 who 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 (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button disabled={!stripe}>Submit</button>
      {/* Show error message to your customers */}
      {errorMessage && <div>{errorMessage}</div>}
    </form>
  );
};

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('<<YOUR_PUBLISHABLE_KEY>>');

// 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).

#### Cards

| Card number         | Scenario                                                                                                                                                                                                                                                                                      | How to test                                                                                           |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| 4242424242424242    | The card payment succeeds and doesn’t require authentication.                                                                                                                                                                                                                                 | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000002500003155    | The card payment requires *authentication* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000000000009995    | The card is declined with a decline code like `insufficient_funds`.                                                                                                                                                                                                                           | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 6205500000000000004 | The UnionPay card has a variable length of 13-19 digits.                                                                                                                                                                                                                                      | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |

#### Wallets

| Payment method | Scenario                                                                                                                                                                     | How to test                                                                                                                                                  |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Alipay         | Your customer successfully pays with a redirect-based and [immediate notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment method. | Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click **Complete test payment** on the redirect page. |

#### Bank redirects

| Payment method                         | Scenario                                                                                                                                                                                              | How to test                                                                                                                                                                                             |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| BECS Direct Debit                      | Your customer successfully pays with BECS Direct Debit.                                                                                                                                               | Fill out the form using the account number `900123456` and BSB `000000`. The confirmed PaymentIntent initially transitions to `processing`, then transitions to the `succeeded` status 3 minutes later. |
| BECS Direct Debit                      | Your customer’s payment fails with an `account_closed` error code.                                                                                                                                    | Fill out the form using the account number `111111113` and BSB `000000`.                                                                                                                                |
| Bancontact, EPS, iDEAL, and Przelewy24 | Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method.                                                                              | Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page.                                                |
| Pay by Bank                            | Your customer successfully pays with a redirect-based and [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment method.                            | Choose the payment method, fill out the required details, and confirm the payment. Then click **Complete test payment** on the redirect page.                                                           |
| Pay by Bank                            | Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method.                                                                                | Choose the payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page.                                                               |
| BLIK                                   | BLIK payments fail in a variety of ways – immediate failures (for example, the code has expired or is invalid), delayed errors (the bank declines) or timeouts (the customer didn’t respond in time). | Use email patterns to [simulate the different failures.](https://docs.stripe.com/payments/blik/accept-a-payment.md#simulate-failures)                                                                   |

#### Bank debits

| Payment method    | Scenario                                                                                          | How to test                                                                                                                                                                                       |
| ----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SEPA Direct Debit | Your customer successfully pays with SEPA Direct Debit.                                           | Fill out the form using the account number `AT321904300235473204`. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. |
| SEPA Direct Debit | Your customer’s payment intent status transitions from `processing` to `requires_payment_method`. | Fill out the form using the account number `AT861904300235473202`.                                                                                                                                |

#### Vouchers

| Payment method | Scenario                                          | How to test                                                                                            |
| -------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Boleto, OXXO   | Your customer pays with a Boleto or OXXO voucher. | Select Boleto or OXXO as the payment method and submit the payment. Close the dialog after it appears. |

See [Testing](https://docs.stripe.com/testing.md) for additional information to test your integration.


# 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-marketplace?platform=ios&mobile-ui=payment-element.
![](https://b.stripecdn.com/docs-statics-srv/assets/ios-overview.9e0d68d009dc005f73a6f5df69e00458.png)

Integrate Stripe’s pre-built 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).

This guide demonstrates how to accept payments and move funds to the bank accounts of your sellers or service providers. For demonstration purposes, we’ll build a home-rental marketplace that connects homeowners to people looking for a place to rent. You can use the concepts covered in this guide in other applications as well.

## Prerequisites

1. [Register your platform](https://dashboard.stripe.com/connect).
1. [Verify and add business details in Dashboard](https://dashboard.stripe.com/account/onboarding).
1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile).
1. [Customise your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand colour.

## 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 = "<<YOUR_PUBLISHABLE_KEY>>"
        // 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

#### Accounts v2

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/v2/core/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.

#### Accounts v1

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.
![](https://b.stripecdn.com/docs-statics-srv/assets/express-ios.6789c3d9f8e327847abb218d75a29eec.png)

> This guide uses Express accounts, which have certain [restrictions](https://docs.stripe.com/connect/express-accounts.md#prerequisites-for-using-express). You can evaluate [Custom accounts](https://docs.stripe.com/connect/custom-accounts.md) as an alternative.

### Create a connected account with prefilled information

#### Accounts v2

Use the `/v2/core/accounts` API to [create](https://docs.stripe.com/api/v2/core/accounts/create.md) a connected account by specifying the [connected account dashboard and responsibilities](https://docs.stripe.com/connect/accounts-v2/connected-account-configuration.md).

```curl
curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-01-28.clover" \
  --json '{
    "contact_email": "furever_contact@example.com",
    "display_name": "Furever",
    "defaults": {
        "responsibilities": {
            "fees_collector": "application",
            "losses_collector": "application"
        }
    },
    "dashboard": "express",
    "identity": {
        "business_details": {
            "registered_name": "Furever"
        },
        "country": "us",
        "entity_type": "company"
    },
    "configuration": {
        "merchant": {
            "capabilities": {
                "card_payments": {
                    "requested": true
                }
            }
        },
        "recipient": {
            "capabilities": {
                "stripe_balance": {
                    "stripe_transfers": {
                        "requested": true
                    }
                }
            }
        }
    },
    "include": [
        "configuration.merchant",
        "configuration.recipient",
        "identity",
        "requirements"
    ]
  }'
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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/v2/core/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 -X POST https://api.stripe.com/v2/core/accounts/{{ACCOUNT_ID}}/persons \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-03-25.preview" \
  --json '{
    "given_name": "Jenny",
    "surname": "Rosen",
    "email": "jenny.rosen@example.com",
    "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 v2](https://docs.stripe.com/api/v2/core/account-links.md) API with the following parameters:

- `account`
- `refresh_url`
- `return_url`
- `type` = `account_onboarding`
- `configurations` = `recipient` and `merchant`

```curl
curl -X POST https://api.stripe.com/v2/core/account_links \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2025-08-27.preview" \
  --json '{
    "account": "{{CONNECTEDACCOUNT_ID}}",
    "use_case": {
        "type": "account_onboarding",
        "account_onboarding": {
            "configurations": [
                "recipient",
                "merchant"
            ],
            "refresh_url": "https://example.com/reauth",
            "return_url": "https://example.com/return"
        }
    }
  }'
```

### Redirect your user to the account link URL

The create Account Link response includes a `url` value. After authenticating the user in your application, re-direct them to this URL to send them into the flow. Account link URLs are temporary and single-use only, because they grant access to the connected account user’s personal information. If you want to prefill information, you must do so before generating the account link. After you create the account link, you can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

#### Item 3

### 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 account user is re-directed to your platform.

> You can use HTTP for your `return_url` and `refresh_url` while in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes the onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that the flow was entered and exited properly.

No state is passed through this URL. After a user is re-directed to your `return_url`, check the state of the requirements on their account by doing either of the following:

- Listen to `v2.core.account[requirements].updated` webhooks.
- Call the [Accounts v2](https://docs.stripe.com/api/v2/core/accounts/retrieve.md) API and inspect the returned object.

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v2](https://docs.stripe.com/api/v2/core/account-links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s redirected to your `return_url` might not have completed the onboarding process. Use the `/v2/core/accounts` endpoint to retrieve the user’s account and check if [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status) is `active`. If the status isn’t `active` and [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status_details.code](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status_details-code) is `requirements_past_due`, provide UI prompts to allow the user to continue onboarding through a new account link. For other codes, handle them as needed.

#### Accounts v1

Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account by specifying the [connected account properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type.

```curl
curl https://api.stripe.com/v1/accounts \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "controller[losses][payments]=application" \
  -d "controller[fees][payer]=application" \
  -d "controller[stripe_dashboard][type]=express"
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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 "<<YOUR_SECRET_KEY>>:" \
  -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 v1](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 "<<YOUR_SECRET_KEY>>:" \
  -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 create Account Link response includes a `url` value. After authenticating the user in your application, re-direct to this URL to send them into the flow. Account link URLs from the [Account Links v1](https://docs.stripe.com/api/account_links.md) API 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 re-directing 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 can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

### 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 account user is re-directed 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 in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that 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:

- Listen to `account.updated` webhooks
- Call the [Accounts v1](https://docs.stripe.com/api/accounts.md) API and inspect the returned object

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v1](https://docs.stripe.com/api/account_links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s re-directed 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 through a new account link. You can determine whether they’ve completed onboarding by checking 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, Google Pay, and Apple Pay are enabled by default but you can enable and disable payment methods as needed.

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 prioritised. Lower priority payment methods are hidden in an overflow menu.

## 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).

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) A [customer-configured Account](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-applied_configurations) or a [Customer](https://docs.stripe.com/api/customers.md) object: To set up a payment method for future payments, you must attach it to a customer. Create an object to represent your customer when they create an account with your business. If your customer makes a payment as a guest, you can create an `Account` or `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 object that represents your customer is sensitive, and can’t be retrieved directly from an app. A `CustomerSession` grants the SDK temporary scoped access to the `Account` or `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 for customers and don’t allow returning customers to reuse saved cards, you can omit the `Account` or `Customer` object and the `CustomerSession` object from your integration.

For security reasons, your app can’t create these objects. Instead, add an endpoint on your server that:

1. Retrieves the `Account` or `Customer`, or creates a new one.
1. Creates a [CustomerSession](https://docs.stripe.com/api/customer_sessions.md) for the `Account` or `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 either the [customer_account](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer_account) or the [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer).
1. Returns the `PaymentIntent`’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 ID of the `Account` or `Customer`, 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, note 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 prioritised.

#### 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.

#### Accounts v2

#### curl

```bash
# Create an Account with the customer configuration (use an existing Account ID if this is a returning customer)
curl https://api.stripe.com/v2/core/accounts \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "configuration[customer]"

# Create a CustomerSession for the Account
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \

  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Customers v1

#### curl

```bash
# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an CustomerSession for the Customer
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \

  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Listing payment methods manually

#### Accounts v2

#### curl

```bash
# Create an Account with the customer configuration (use an existing Account ID if this is a returning customer)
curl https://api.stripe.com/v2/core/accounts \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "configuration[customer]"

# Create a CustomerSession for the Account
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Customers v1

#### curl

```bash
# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an CustomerSession for the Customer
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -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 <<YOUR_SECRET_KEY>>: \
  -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" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

> 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 initialise the [PaymentSheet](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet.html).

#### 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 = publishableKey// 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 initialise the [PaymentSheet](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet.html).

```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 = publishableKey// 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 customise 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
      // 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 fulfil 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<UIOpenURLContext>) {
    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 in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000002500003155    | The card payment requires *authentication* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000000000009995    | The card is declined with a decline code like `insufficient_funds`.                                                                                                                                                                                                                           | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 6205500000000000004 | The UnionPay card has a variable length of 13-19 digits.                                                                                                                                                                                                                                      | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |

#### Bank redirects

| Payment method    | Scenario                                                                                                                                                                                              | How to test                                                                                                                                              |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bancontact, iDEAL | Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method.                                                                              | Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page. |
| Pay by Bank       | Your customer successfully pays with a redirect-based and [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment method.                            | Choose the payment method, fill out the required details, and confirm the payment. Then click **Complete test payment** on the redirect page.            |
| Pay by Bank       | Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method.                                                                                | Choose the payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page.                |
| BLIK              | BLIK payments fail in a variety of ways – immediate failures (for example, the code has expired or is invalid), delayed errors (the bank declines) or timeouts (the customer didn’t respond in time). | Use email patterns to [simulate the different failures.](https://docs.stripe.com/payments/blik/accept-a-payment.md#simulate-failures)                    |

#### Bank debits

| Payment method    | Scenario                                                                                          | How to test                                                                                                                                                                                       |
| ----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SEPA Direct Debit | Your customer successfully pays with SEPA Direct Debit.                                           | Fill out the form using the account number `AT321904300235473204`. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. |
| SEPA Direct Debit | Your customer’s payment intent status transitions from `processing` to `requires_payment_method`. | Fill out the form using the account number `AT861904300235473202`.                                                                                                                                |

See [Testing](https://docs.stripe.com/testing.md) for additional information to test your integration.

## 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 initialising `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 initialising `PaymentSheet.Configuration` with your Apple merchant ID and the [country code of your business](https://dashboard.stripe.com/settings/account).

As per [Apple’s guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay#Supporting-subscriptions) for recurring payments, you must also set additional attributes on the `PKPaymentRequest`. Add a handler in [ApplePayConfiguration.paymentRequestHandlers](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/applepayconfiguration/handlers/paymentrequesthandler) to configure the [PKPaymentRequest.paymentSummaryItems](https://developer.apple.com/documentation/passkit/pkpaymentrequest/1619231-paymentsummaryitems) with the amount you intend to charge (for example, 9.95 USD a month).

You can also adopt [merchant tokens](https://developer.apple.com/apple-pay/merchant-tokens/) by setting the `recurringPaymentRequest` or `automaticReloadPaymentRequest` properties on the `PKPaymentRequest`.

To learn more about how to use recurring payments with Apple Pay, see [Apple’s PassKit documentation](https://developer.apple.com/documentation/passkit/pkpaymentrequest).

#### iOS (Swift)

```swift
let customHandlers = 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: Customise the sheet

All customisation is configured through the [PaymentSheet.Configuration](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html) object.

### Appearance

Customise colours, fonts and so on to match the look and feel of your app by using the [appearance API](https://docs.stripe.com/elements/appearance-api/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 optimise 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, initialise [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, initialise [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 fulfil their order (for example, ship their product) when the payment is successful.


# 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-marketplace?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, expiry date, CVC, and postal code.
![](https://d37ugbyn3rpeym.cloudfront.net/docs/mobile/ios/card-field.mp4)
This guide demonstrates how to accept payments and move funds to the bank accounts of your sellers or service providers. For demonstration purposes, we’ll build a home-rental marketplace that connects homeowners to people looking for a place to rent. You can use the concepts covered in this guide in other applications as well.

## Prerequisites

1. [Register your platform](https://dashboard.stripe.com/connect).
1. [Verify and add business details in Dashboard](https://dashboard.stripe.com/account/onboarding).
1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile).
1. [Customise your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand colour.

## 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 = "<<YOUR_PUBLISHABLE_KEY>>"
        // 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

#### Accounts v2

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/v2/core/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.

#### Accounts v1

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.
![](https://b.stripecdn.com/docs-statics-srv/assets/express-ios.6789c3d9f8e327847abb218d75a29eec.png)

> This guide uses Express accounts, which have certain [restrictions](https://docs.stripe.com/connect/express-accounts.md#prerequisites-for-using-express). You can evaluate [Custom accounts](https://docs.stripe.com/connect/custom-accounts.md) as an alternative.

### Create a connected account with prefilled information

#### Accounts v2

Use the `/v2/core/accounts` API to [create](https://docs.stripe.com/api/v2/core/accounts/create.md) a connected account by specifying the [connected account dashboard and responsibilities](https://docs.stripe.com/connect/accounts-v2/connected-account-configuration.md).

```curl
curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-01-28.clover" \
  --json '{
    "contact_email": "furever_contact@example.com",
    "display_name": "Furever",
    "defaults": {
        "responsibilities": {
            "fees_collector": "application",
            "losses_collector": "application"
        }
    },
    "dashboard": "express",
    "identity": {
        "business_details": {
            "registered_name": "Furever"
        },
        "country": "us",
        "entity_type": "company"
    },
    "configuration": {
        "merchant": {
            "capabilities": {
                "card_payments": {
                    "requested": true
                }
            }
        },
        "recipient": {
            "capabilities": {
                "stripe_balance": {
                    "stripe_transfers": {
                        "requested": true
                    }
                }
            }
        }
    },
    "include": [
        "configuration.merchant",
        "configuration.recipient",
        "identity",
        "requirements"
    ]
  }'
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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/v2/core/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 -X POST https://api.stripe.com/v2/core/accounts/{{ACCOUNT_ID}}/persons \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-03-25.preview" \
  --json '{
    "given_name": "Jenny",
    "surname": "Rosen",
    "email": "jenny.rosen@example.com",
    "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 v2](https://docs.stripe.com/api/v2/core/account-links.md) API with the following parameters:

- `account`
- `refresh_url`
- `return_url`
- `type` = `account_onboarding`
- `configurations` = `recipient` and `merchant`

```curl
curl -X POST https://api.stripe.com/v2/core/account_links \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2025-08-27.preview" \
  --json '{
    "account": "{{CONNECTEDACCOUNT_ID}}",
    "use_case": {
        "type": "account_onboarding",
        "account_onboarding": {
            "configurations": [
                "recipient",
                "merchant"
            ],
            "refresh_url": "https://example.com/reauth",
            "return_url": "https://example.com/return"
        }
    }
  }'
```

### Redirect your user to the account link URL

The create Account Link response includes a `url` value. After authenticating the user in your application, re-direct them to this URL to send them into the flow. Account link URLs are temporary and single-use only, because they grant access to the connected account user’s personal information. If you want to prefill information, you must do so before generating the account link. After you create the account link, you can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

#### Item 3

### 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 account user is re-directed to your platform.

> You can use HTTP for your `return_url` and `refresh_url` while in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes the onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that the flow was entered and exited properly.

No state is passed through this URL. After a user is re-directed to your `return_url`, check the state of the requirements on their account by doing either of the following:

- Listen to `v2.core.account[requirements].updated` webhooks.
- Call the [Accounts v2](https://docs.stripe.com/api/v2/core/accounts/retrieve.md) API and inspect the returned object.

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v2](https://docs.stripe.com/api/v2/core/account-links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s redirected to your `return_url` might not have completed the onboarding process. Use the `/v2/core/accounts` endpoint to retrieve the user’s account and check if [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status) is `active`. If the status isn’t `active` and [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status_details.code](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status_details-code) is `requirements_past_due`, provide UI prompts to allow the user to continue onboarding through a new account link. For other codes, handle them as needed.

#### Accounts v1

Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account by specifying the [connected account properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type.

```curl
curl https://api.stripe.com/v1/accounts \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "controller[losses][payments]=application" \
  -d "controller[fees][payer]=application" \
  -d "controller[stripe_dashboard][type]=express"
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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 "<<YOUR_SECRET_KEY>>:" \
  -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 v1](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 "<<YOUR_SECRET_KEY>>:" \
  -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 create Account Link response includes a `url` value. After authenticating the user in your application, re-direct to this URL to send them into the flow. Account link URLs from the [Account Links v1](https://docs.stripe.com/api/account_links.md) API 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 re-directing 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 can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

### 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 account user is re-directed 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 in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that 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:

- Listen to `account.updated` webhooks
- Call the [Accounts v1](https://docs.stripe.com/api/accounts.md) API and inspect the returned object

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v1](https://docs.stripe.com/api/account_links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s re-directed 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 through a new account link. You can determine whether they’ve completed onboarding by checking the state of the `details_submitted` parameter on their account.

## 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, expiry 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 <<YOUR_SECRET_KEY>>: \
  -d amount=1000 \
  -d currency="usd" \
  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_STRIPE_ACCOUNT_ID}}"
```

In our home-rental example, we want to build a business where customers pay for rentals by using our platform, and where we pay homeowners for renting to customers. To set up this business:

- Indicate the rental is a destination charge with `transfer_data[destination]`.
- Specify how much of the rental amount goes to the platform with `application_fee_amount`.

When a rental charge occurs, Stripe transfers the entire amount to the connected account’s pending balance (`transfer_data[destination]`). Afterward, Stripe transfers the fee amount (`application_fee_amount`) to the platform’s account, which is the share of the revenue for facilitating the rental. Then, Stripe deducts the Stripe fees from the platform’s fee amount. An illustration of this funds flow is below:
![](https://b.stripecdn.com/docs-statics-srv/assets/application_fee_amount.837aa2339469b3c1a4319672971c1367.svg)

> This PaymentIntent creates a destination charge. If you need to control the timing of transfers or need to transfer funds from a single payment to multiple parties, use [separate charges and transfers](https://docs.stripe.com/connect/separate-charges-and-transfers.md) instead.

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

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 optimise 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 expiry 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: Fulfilment (Server-side)

After the payment is completed, you’ll need to handle any *fulfillment* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) necessary on your end. A home-rental company that requires payment upfront, for instance, would connect the homeowner with the renter after a successful payment.

Configure a *webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) endpoint (for events *from your account*) [in your dashboard](https://dashboard.stripe.com/account/webhooks).
![](https://b.stripecdn.com/docs-statics-srv/assets/account_webhooks.03b71cec87ef2093fe0caa92e5bfce44.png)

Then create an HTTP endpoint on your server to monitor for completed payments to enable your sellers or service providers to fulfill purchases. Make sure to replace the endpoint secret key (`whsec_...`) in the example with your key.

#### 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.
client = Stripe::StripeClient.new('<<YOUR_SECRET_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']
    handle_successful_payment_intent(payment_intent)
  end

  status 200
end

def handle_successful_payment_intent(payment_intent)
  # Fulfill the purchase
  puts payment_intent.to_s
end
```

Learn more in our [fulfilment 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-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.

### Step 3.6: Disputes

As the [settlement merchant](https://docs.stripe.com/connect/destination-charges.md#settlement-merchant) on charges, your platform is responsible for disputes. Make sure you understand the [best practices](https://docs.stripe.com/disputes/responding.md) for responding to disputes.

## 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). You can use [the available 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-marketplace?platform=android&mobile-ui=payment-element.
![](https://b.stripecdn.com/docs-statics-srv/assets/android-overview.471eaf89a760f5b6a757fd96b6bb9b60.png)

Integrate Stripe’s pre-built 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.

This guide demonstrates how to accept payments and move funds to the bank accounts of your sellers or service providers. For demonstration purposes, we’ll build a home-rental marketplace that connects homeowners to people looking for a place to rent. You can use the concepts covered in this guide in other applications as well.

## Prerequisites

1. [Register your platform](https://dashboard.stripe.com/connect).
1. [Verify and add business details in Dashboard](https://dashboard.stripe.com/account/onboarding).
1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile).
1. [Customise your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand colour.

## 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.3.0")
  // Include the financial connections SDK to support US bank account as a payment method
  implementation("com.stripe:financial-connections:23.3.0")
}
```

> 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,
            "<<YOUR_PUBLISHABLE_KEY>>"
        )
    }
}
```

> 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.

Stripe samples also use [OkHttp](https://github.com/square/okhttp) and [GSON](https://github.com/google/gson) to make HTTP requests to a server.

## Create a connected account
![](https://b.stripecdn.com/docs-statics-srv/assets/express-android.d13776fc7e474c2a0168a705f85b11cb.png)

> This guide uses Express accounts, which have certain [restrictions](https://docs.stripe.com/connect/express-accounts.md#prerequisites-for-using-express). You can evaluate [Custom accounts](https://docs.stripe.com/connect/custom-accounts.md) as an alternative.

### Create a connected account with prefilled information

#### Accounts v2

Use the `/v2/core/accounts` API to [create](https://docs.stripe.com/api/v2/core/accounts/create.md) a connected account by specifying the [connected account dashboard and responsibilities](https://docs.stripe.com/connect/accounts-v2/connected-account-configuration.md).

```curl
curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-01-28.clover" \
  --json '{
    "contact_email": "furever_contact@example.com",
    "display_name": "Furever",
    "defaults": {
        "responsibilities": {
            "fees_collector": "application",
            "losses_collector": "application"
        }
    },
    "dashboard": "express",
    "identity": {
        "business_details": {
            "registered_name": "Furever"
        },
        "country": "us",
        "entity_type": "company"
    },
    "configuration": {
        "merchant": {
            "capabilities": {
                "card_payments": {
                    "requested": true
                }
            }
        },
        "recipient": {
            "capabilities": {
                "stripe_balance": {
                    "stripe_transfers": {
                        "requested": true
                    }
                }
            }
        }
    },
    "include": [
        "configuration.merchant",
        "configuration.recipient",
        "identity",
        "requirements"
    ]
  }'
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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/v2/core/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 -X POST https://api.stripe.com/v2/core/accounts/{{ACCOUNT_ID}}/persons \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-03-25.preview" \
  --json '{
    "given_name": "Jenny",
    "surname": "Rosen",
    "email": "jenny.rosen@example.com",
    "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 v2](https://docs.stripe.com/api/v2/core/account-links.md) API with the following parameters:

- `account`
- `refresh_url`
- `return_url`
- `type` = `account_onboarding`
- `configurations` = `recipient` and `merchant`

```curl
curl -X POST https://api.stripe.com/v2/core/account_links \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2025-08-27.preview" \
  --json '{
    "account": "{{CONNECTEDACCOUNT_ID}}",
    "use_case": {
        "type": "account_onboarding",
        "account_onboarding": {
            "configurations": [
                "recipient",
                "merchant"
            ],
            "refresh_url": "https://example.com/reauth",
            "return_url": "https://example.com/return"
        }
    }
  }'
```

### Redirect your user to the account link URL

The create Account Link response includes a `url` value. After authenticating the user in your application, re-direct them to this URL to send them into the flow. Account link URLs are temporary and single-use only, because they grant access to the connected account user’s personal information. If you want to prefill information, you must do so before generating the account link. After you create the account link, you can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

#### Item 3

### 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 account user is re-directed to your platform.

> You can use HTTP for your `return_url` and `refresh_url` while in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes the onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that the flow was entered and exited properly.

No state is passed through this URL. After a user is re-directed to your `return_url`, check the state of the requirements on their account by doing either of the following:

- Listen to `v2.core.account[requirements].updated` webhooks.
- Call the [Accounts v2](https://docs.stripe.com/api/v2/core/accounts/retrieve.md) API and inspect the returned object.

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v2](https://docs.stripe.com/api/v2/core/account-links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s redirected to your `return_url` might not have completed the onboarding process. Use the `/v2/core/accounts` endpoint to retrieve the user’s account and check if [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status) is `active`. If the status isn’t `active` and [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status_details.code](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status_details-code) is `requirements_past_due`, provide UI prompts to allow the user to continue onboarding through a new account link. For other codes, handle them as needed.

#### Accounts v1

Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account by specifying the [connected account properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type.

```curl
curl https://api.stripe.com/v1/accounts \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "controller[losses][payments]=application" \
  -d "controller[fees][payer]=application" \
  -d "controller[stripe_dashboard][type]=express"
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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 "<<YOUR_SECRET_KEY>>:" \
  -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 v1](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 "<<YOUR_SECRET_KEY>>:" \
  -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 create Account Link response includes a `url` value. After authenticating the user in your application, re-direct to this URL to send them into the flow. Account link URLs from the [Account Links v1](https://docs.stripe.com/api/account_links.md) API 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 re-directing 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 can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

### 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 account user is re-directed 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 in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that 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:

- Listen to `account.updated` webhooks
- Call the [Accounts v1](https://docs.stripe.com/api/accounts.md) API and inspect the returned object

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v1](https://docs.stripe.com/api/account_links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s re-directed 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 through a new account link. You can determine whether they’ve completed onboarding by checking 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, Google Pay, and Apple Pay are enabled by default but you can enable and disable payment methods as needed.

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 prioritised. Lower priority payment methods are hidden in an overflow menu.

## 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).

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) A [customer-configured Account](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-applied_configurations) or a [Customer](https://docs.stripe.com/api/customers.md) object: To set up a payment method for future payments, you must attach it to a customer. Create an object to represent your customer when they create an account with your business. If your customer makes a payment as a guest, you can create an `Account` or `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 object that represents your customer is sensitive, and can’t be retrieved directly from an app. A `CustomerSession` grants the SDK temporary scoped access to the `Account` or `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 for customers and don’t allow returning customers to reuse saved cards, you can omit the `Account` or `Customer` object and the `CustomerSession` object from your integration.

For security reasons, your app can’t create these objects. Instead, add an endpoint on your server that:

1. Retrieves the `Account` or `Customer`, or creates a new one.
1. Creates a [CustomerSession](https://docs.stripe.com/api/customer_sessions.md) for the `Account` or `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 either the [customer_account](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer_account) or the [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer).
1. Returns the `PaymentIntent`’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 ID of the `Account` or `Customer`, 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, note 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 prioritised.

#### 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.

#### Accounts v2

#### curl

```bash
# Create an Account with the customer configuration (use an existing Account ID if this is a returning customer)
curl https://api.stripe.com/v2/core/accounts \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "configuration[customer]"

# Create a CustomerSession for the Account
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \

  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Customers v1

#### curl

```bash
# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an CustomerSession for the Customer
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \

  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Listing payment methods manually

#### Accounts v2

#### curl

```bash
# Create an Account with the customer configuration (use an existing Account ID if this is a returning customer)
curl https://api.stripe.com/v2/core/accounts \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "configuration[customer]"

# Create a CustomerSession for the Account
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Customers v1

#### curl

```bash
# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an CustomerSession for the Customer
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -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 <<YOUR_SECRET_KEY>>: \
  -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" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

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

Before displaying the mobile Payment Element, your checkout page should:

- Show the products being purchased and the total amount
- Collect any required shipping information using the [Address Element](https://docs.stripe.com/elements/address-element.md?platform=android)
- Include a checkout button to present Stripe’s UI

#### Jetpack Compose

[Initialise](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-builder/index.html) a `PaymentSheet` instance inside `onCreate` of your checkout Activity, passing a method to handle the result.

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult

@Composable
fun App() {
  val paymentSheet = remember { PaymentSheet.Builder(::onPaymentSheetResult) }.build()
}

private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
  // implemented in the next steps
}
```

Next, fetch the PaymentIntent client secret, Customer Session client secret, Customer ID, and publishable key from the endpoint you created in the previous step. Set the publishable key using `PaymentConfiguration` and store the others for use when you present the PaymentSheet.

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberimport androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult

@Composable
fun App() {
  val paymentSheet = remember { PaymentSheet.Builder(::onPaymentSheetResult) }.build()val context = LocalContext.current
  var customerConfig by remember { mutableStateOf<PaymentSheet.CustomerConfiguration?>(null) }
  varpaymentIntentClientSecret by remember { mutableStateOf<String?>(null) }

  LaunchedEffect(context) {
    // Make a request to your own server and retrieve payment configurations
    val networkResult = ...
    if (networkResult.isSuccess) {paymentIntentClientSecret = networkResult.paymentIntent
        customerConfig = PaymentSheet.CustomerConfiguration.createWithCustomerSession(
          id = networkResult.customer,
          clientSecret = networkResult.customerSessionClientSecret
        )PaymentConfiguration.init(context, networkResult.publishableKey)}
  }
}

private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
  // implemented in the next steps
}
```

When the customer taps your checkout button, call [presentWithPaymentIntent](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/index.html#1814490530%2FFunctions%2F2002900378) to present the payment sheet. After the customer completes the payment, the sheet dismisses and the [PaymentSheetResultCallback](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result-callback/index.html) is called with a [PaymentSheetResult](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result/index.html).

```kotlin
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult

@Composable
fun App() {
  val paymentSheet = remember { PaymentSheet.Builder(::onPaymentSheetResult) }.build()
  val context = LocalContext.current
  var customerConfig by remember { mutableStateOf<PaymentSheet.CustomerConfiguration?>(null) }
  var paymentIntentClientSecret by remember { mutableStateOf<String?>(null) }

  LaunchedEffect(context) {
    // Make a request to your own server and retrieve payment configurations
    val networkResult = ...
    if (networkResult.isSuccess) {
        paymentIntentClientSecret = networkResult.paymentIntent
        customerConfig = PaymentSheet.CustomerConfiguration.createWithCustomerSession(
          id = networkResult.customer,
          clientSecret = networkResult.customerSessionClientSecret
        )
        PaymentConfiguration.init(context, networkResult.publishableKey)
    }
  }Button(
    onClick = {
      val currentConfig = customerConfig
      val currentClientSecret =paymentIntentClientSecret

      if (currentConfig != null && currentClientSecret != null) {
        presentPaymentSheet(paymentSheet, currentConfig, currentClientSecret)
      }
    }
  ) {
    Text("Checkout")
  }
}private fun presentPaymentSheet(
  paymentSheet: PaymentSheet,
  customerConfig: PaymentSheet.CustomerConfiguration,paymentIntentClientSecret: String
) {
  paymentSheet.presentWithPaymentIntent(paymentIntentClientSecret,
    PaymentSheet.Configuration.Builder(merchantDisplayName = "My merchant name")
      .customer(customerConfig)
      // Set `allowsDelayedPaymentMethods` to true if your business handles
      // delayed notification payment methods like US bank accounts.
      .allowsDelayedPaymentMethods(true)
      .build()
  )
}
private fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {when(paymentSheetResult) {
    is PaymentSheetResult.Canceled -> {
      print("Canceled")
    }
    is PaymentSheetResult.Failed -> {
      print("Error: ${paymentSheetResult.error}")
    }
    is PaymentSheetResult.Completed -> {
      // Display for example, an order confirmation screen
      print("Completed")
    }
  }
}
```

#### Views (Classic)

[Initialise](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/index.html#-394860221%2FConstructors%2F2002900378) a `PaymentSheet` instance inside `onCreate` of your checkout Activity, passing a method to handle the result.

#### Kotlin

```kotlin
import com.stripe.android.paymentsheet.PaymentSheet

class CheckoutActivity : AppCompatActivity() {
  lateinit var paymentSheet: PaymentSheet

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    paymentSheet = PaymentSheet.Builder(::onPaymentSheetResult).build(this)
  }

  fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
    // implemented in the next steps
  }
}
```

Next, fetch the PaymentIntent client secret, Customer Session client secret, Customer ID, and publishable key from the endpoint you created in the previous step. Set the publishable key using `PaymentConfiguration` and store the others for use when you present the PaymentSheet.

#### Kotlin

```kotlin
import com.stripe.android.paymentsheet.PaymentSheet

class CheckoutActivity : AppCompatActivity() {
  lateinit var paymentSheet: PaymentSheetlateinit var customerConfig: PaymentSheet.CustomerConfiguration
  lateinit varpaymentIntentClientSecret: String

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    paymentSheet = PaymentSheet.Builder(::onPaymentSheetResult).build(this)lifecycleScope.launch {
      // Make a request to your own server and retrieve payment configurations
      val networkResult = MyBackend.getPaymentConfig()
      if (networkResult.isSuccess) {paymentIntentClientSecret = networkResult.paymentIntent
        customerConfig = PaymentSheet.CustomerConfiguration.createWithCustomerSession(
          id = networkResult.customer,
          clientSecret = networkResult.customerSessionClientSecret
        )PaymentConfiguration.init(context, networkResult.publishableKey)}
    }
  }

  fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
    // implemented in the next steps
  }
}
```

When the customer taps your checkout button, call [presentWithPaymentIntent](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/index.html#1814490530%2FFunctions%2F2002900378) to present the payment sheet. After the customer completes the payment, the sheet dismisses and the [PaymentSheetResultCallback](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result-callback/index.html) is called with a [PaymentSheetResult](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result/index.html).

#### Kotlin

```kotlin
// ...
class CheckoutActivity : AppCompatActivity() {
  lateinit var paymentSheet: PaymentSheet
  lateinit var customerConfig: PaymentSheet.CustomerConfiguration
  lateinit var paymentIntentClientSecret: String
  // ...fun presentPaymentSheet() {
    paymentSheet.presentWithPaymentIntent(paymentIntentClientSecret,
      PaymentSheet.Configuration.Builder(merchantDisplayName = "My merchant name")
        .customer(customerConfig)
        // Set `allowsDelayedPaymentMethods` to true if your business handles
        // delayed notification payment methods like US bank accounts.
        .allowsDelayedPaymentMethods(true)
        .build()
    )
  }

  fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {when(paymentSheetResult) {
      is PaymentSheetResult.Canceled -> {
        print("Canceled")
      }
      is PaymentSheetResult.Failed -> {
        print("Error: ${paymentSheetResult.error}")
      }
      is PaymentSheetResult.Completed -> {
        // Display for example, an order confirmation screen
        print("Completed")
      }
    }
  }
}
```

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 fulfil their order (for example, ship their product) when the payment is successful.

## 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 in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000002500003155    | The card payment requires *authentication* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000000000009995    | The card is declined with a decline code like `insufficient_funds`.                                                                                                                                                                                                                           | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 6205500000000000004 | The UnionPay card has a variable length of 13-19 digits.                                                                                                                                                                                                                                      | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |

#### Bank redirects

| Payment method    | Scenario                                                                                                                                                                                              | How to test                                                                                                                                              |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bancontact, iDEAL | Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method.                                                                              | Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page. |
| Pay by Bank       | Your customer successfully pays with a redirect-based and [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment method.                            | Choose the payment method, fill out the required details, and confirm the payment. Then click **Complete test payment** on the redirect page.            |
| Pay by Bank       | Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method.                                                                                | Choose the payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page.                |
| BLIK              | BLIK payments fail in a variety of ways – immediate failures (for example, the code has expired or is invalid), delayed errors (the bank declines) or timeouts (the customer didn’t respond in time). | Use email patterns to [simulate the different failures.](https://docs.stripe.com/payments/blik/accept-a-payment.md#simulate-failures)                    |

#### Bank debits

| Payment method    | Scenario                                                                                          | How to test                                                                                                                                                                                       |
| ----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SEPA Direct Debit | Your customer successfully pays with SEPA Direct Debit.                                           | Fill out the form using the account number `AT321904300235473204`. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. |
| SEPA Direct Debit | Your customer’s payment intent status transitions from `processing` to `requires_payment_method`. | Fill out the form using the account number `AT861904300235473202`.                                                                                                                                |

See [Testing](https://docs.stripe.com/testing.md) for additional information to test your integration.

## Optional: Enable Google Pay

### Set up your integration

To use Google Pay, first enable the Google Pay API by adding the following to the `<application>` tag of your **AndroidManifest.xml**:

```xml
<application>
  ...
  <meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />
</application>
```

For more details, see Google Pay’s [Set up Google Pay API](https://developers.google.com/pay/api/android/guides/setup) for Android.

### Add Google Pay

To add Google Pay to your integration, pass a [PaymentSheet.GooglePayConfiguration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-google-pay-configuration/index.html) with your Google Pay environment (production or test) and the [country code of your business](https://dashboard.stripe.com/settings/account) when initializing [PaymentSheet.Configuration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/index.html).

#### Kotlin

```kotlin
val googlePayConfiguration = PaymentSheet.GooglePayConfiguration(
  environment = PaymentSheet.GooglePayConfiguration.Environment.Test,
  countryCode = "US",
  currencyCode = "USD" // Required for Setup Intents, optional for Payment Intents
)
val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "My merchant name")
  .googlePay(googlePayConfiguration)
  .build()
```

### Test Google Pay

Google allows you to make test payments through their [Test card suite](https://developers.google.com/pay/api/android/guides/resources/test-card-suite). The test suite supports using Stripe [test cards](https://docs.stripe.com/testing.md).

You must test Google Pay using a physical Android device instead of a simulated device, in a country where Google Pay is supported. Log in to a Google account on your test device with a real card saved to Google Wallet.

## Optional: Customise the sheet

All customisation is configured using the [PaymentSheet.Configuration](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/index.html) object.

### Appearance

Customise colours, fonts and more to match the look and feel of your app by using the [appearance API](https://docs.stripe.com/elements/appearance-api/mobile.md?platform=android).

### Payment method layout

Configure the layout of payment methods in the sheet using [paymentMethodLayout](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/-builder/index.html#2123253356%2FFunctions%2F2002900378). You can display them horizontally, vertically, or let Stripe optimise the layout automatically.
![](https://b.stripecdn.com/docs-statics-srv/assets/android-mpe-payment-method-layouts.3bcfe828ceaad1a94e0572a22d91733f.png)

#### Kotlin

```kotlin
PaymentSheet.Configuration.Builder("Example, Inc.")
  .paymentMethodLayout(PaymentSheet.PaymentMethodLayout.Automatic)
  .build()
```

### 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=android).

### Business display name

Specify a customer-facing business name by setting [merchantDisplayName](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-configuration/index.html#-191101533%2FProperties%2F2002900378). By default, this is your app’s name.

#### Kotlin

```kotlin
PaymentSheet.Configuration.Builder(
  merchantDisplayName = "My app, Inc."
).build()
```

### Dark mode

By default, `PaymentSheet` automatically adapts to the user’s system-wide appearance settings (light and dark mode). You can change this by setting light or dark mode on your app:

#### Kotlin

```kotlin
// force dark
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
// force light
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
```

### 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.

#### Kotlin

```kotlin
val address = PaymentSheet.Address(country = "US")
val billingDetails = PaymentSheet.BillingDetails(
  address = address,
  email = "foo@bar.com"
)
val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Merchant, Inc.")
  .defaultBillingDetails(billingDetails)
  .build()
```

### Configure collection of billing details

Use `BillingDetailsCollectionConfiguration` to specify how you want to collect billing details in the PaymentSheet.

You can collect your customer’s name, email, phone number, and address.

If you want to attach default billing details to the PaymentMethod object even when those fields aren’t collected in the UI, set `billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod` to `true`.

#### Kotlin

```kotlin
val billingDetails = PaymentSheet.BillingDetails(
  email = "foo@bar.com"
)
val billingDetailsCollectionConfiguration = BillingDetailsCollectionConfiguration(
  attachDefaultsToPaymentMethod = true,
  name = BillingDetailsCollectionConfiguration.CollectionMode.Always,
  email = BillingDetailsCollectionConfiguration.CollectionMode.Never,
  address = BillingDetailsCollectionConfiguration.AddressCollectionMode.Full,
)
val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Merchant, Inc.")
  .defaultBillingDetails(billingDetails)
  .billingDetailsCollectionConfiguration(billingDetailsCollectionConfiguration)
  .build()
```

> 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 Payment Sheet to only collect payment method details and complete the payment back in your app’s UI. This is useful if you have a custom buy button or require additional steps after payment details are collected.
![](https://b.stripecdn.com/docs-statics-srv/assets/android-multi-step.84d8a0a44b1baa596bda491322b6d9fd.png)

> A sample integration is[available on our GitHub](https://github.com/stripe/stripe-android/blob/master/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/samples/ui/paymentsheet/custom_flow/CustomFlowActivity.kt).

1. First, initialise [PaymentSheet.FlowController](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-flow-controller/index.html) instead of `PaymentSheet` using one of the [Builder](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-flow-controller/-builder/index.html) methods.

#### Android (Kotlin)

```kotlin
class CheckoutActivity : AppCompatActivity() {
  private lateinit var flowController: PaymentSheet.FlowController

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val flowController = PaymentSheet.FlowController.Builder(
      resultCallback = ::onPaymentSheetResult,
      paymentOptionResultCallback = ::onPaymentOption,
    ).build(this)
  }
}
```

1. Next, call `configureWithPaymentIntent` with the Stripe object keys fetched from your back end and update your UI in the callback using [getPaymentOption()](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-flow-controller/index.html#-2091462043%2FFunctions%2F2002900378). This contains an image and label representing the customer’s currently selected payment method.

#### Android (Kotlin)

```kotlin
flowController.configureWithPaymentIntent(
  paymentIntentClientSecret = paymentIntentClientSecret,
  configuration = PaymentSheet.Configuration.Builder("Example, Inc.")
    .customer(PaymentSheet.CustomerConfiguration(
      id = customerId,
      ephemeralKeySecret = ephemeralKeySecret
    ))
    .build()
) { isReady, error ->
  if (isReady) {
    // Update your UI using `flowController.getPaymentOption()`
  } else {
    // handle FlowController configuration failure
  }
}
```

1. Next, call [presentPaymentOptions](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-flow-controller/index.html#449924733%2FFunctions%2F2002900378) to collect payment details. When the customer finishes, the sheet is dismissed and calls the [paymentOptionCallback](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-option-callback/index.html) passed earlier in `create`. Implement this method to update your UI with the returned `paymentOption`.

#### Android (Kotlin)

```kotlin
// ...
  flowController.presentPaymentOptions()
// ...
  private fun onPaymentOption(paymentOptionResult: PaymentOptionResult) {
    val paymentOption = paymentOptionResult.paymentOption
    if (paymentOption != null) {
      paymentMethodButton.text = paymentOption.label
      paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
        paymentOption.drawableResourceId,
        0,
        0,
        0
      )
    } else {
      paymentMethodButton.text = "Select"
      paymentMethodButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
        null,
        null,
        null,
        null
      )
    }
  }
```

1. Finally, call [confirm](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet/-flow-controller/index.html#-479056656%2FFunctions%2F2002900378) to complete the payment. When the customer finishes, the sheet is dismissed and calls the [paymentResultCallback](https://stripe.dev/stripe-android/paymentsheet/com.stripe.android.paymentsheet/-payment-sheet-result-callback/index.html#237248767%2FFunctions%2F2002900378) passed earlier in `create`.

#### Android (Kotlin)

```kotlin
  // ...
    flowController.confirmPayment()
  // ...

  private fun onPaymentSheetResult(
    paymentSheetResult: PaymentSheetResult
  ) {
    when (paymentSheetResult) {
      is PaymentSheetResult.Canceled -> {
        // Payment canceled
      }
      is PaymentSheetResult.Failed -> {
        // Payment Failed. See logcat for details or inspect paymentSheetResult.error
      }
      is PaymentSheetResult.Completed -> {
        // Payment Complete
      }
    }
  }
```

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 fulfil their order (for example, ship their product) when the payment is successful.


# Card element only

> This is a Card element only for when platform is android and mobile-ui is card-element. View the full page at https://docs.stripe.com/connect/end-to-end-marketplace?platform=android&mobile-ui=card-element.

Securely collect card information on the client with [CardInputWidget](https://stripe.dev/stripe-android/stripe/com.stripe.android.view/-card-input-widget/index.html), a drop-in UI component provided by the SDK that collects the card number, expiry date, CVC, and postal code.
![](https://d37ugbyn3rpeym.cloudfront.net/docs/mobile/android/android-card-input-widget-with-postal.mp4)
This guide demonstrates how to accept payments and move funds to the bank accounts of your sellers or service providers. For demonstration purposes, we’ll build a home-rental marketplace that connects homeowners to people looking for a place to rent. You can use the concepts covered in this guide in other applications as well.

## Prerequisites

1. [Register your platform](https://dashboard.stripe.com/connect).
1. [Verify and add business details in Dashboard](https://dashboard.stripe.com/account/onboarding).
1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile).
1. [Customise your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand colour.

## 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.3.0")
  // Include the financial connections SDK to support US bank account as a payment method
  implementation("com.stripe:financial-connections:23.3.0")
}
```

> 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,
            "<<YOUR_PUBLISHABLE_KEY>>"
        )
    }
}
```

> 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.

Stripe samples also use [OkHttp](https://github.com/square/okhttp) and [GSON](https://github.com/google/gson) to make HTTP requests to a server.

## Create a connected account

#### Accounts v2

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/v2/core/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.

#### Accounts v1

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.
![](https://b.stripecdn.com/docs-statics-srv/assets/express-android.d13776fc7e474c2a0168a705f85b11cb.png)

> This guide uses Express accounts, which have certain [restrictions](https://docs.stripe.com/connect/express-accounts.md#prerequisites-for-using-express). You can evaluate [Custom accounts](https://docs.stripe.com/connect/custom-accounts.md) as an alternative.

### Create a connected account with prefilled information (Server-side)

#### Accounts v2

Use the `/v2/core/accounts` API to [create](https://docs.stripe.com/api/v2/core/accounts/create.md) a connected account by specifying the [connected account dashboard and responsibilities](https://docs.stripe.com/connect/accounts-v2/connected-account-configuration.md).

```curl
curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-01-28.clover" \
  --json '{
    "contact_email": "furever_contact@example.com",
    "display_name": "Furever",
    "defaults": {
        "responsibilities": {
            "fees_collector": "application",
            "losses_collector": "application"
        }
    },
    "dashboard": "express",
    "identity": {
        "business_details": {
            "registered_name": "Furever"
        },
        "country": "us",
        "entity_type": "company"
    },
    "configuration": {
        "merchant": {
            "capabilities": {
                "card_payments": {
                    "requested": true
                }
            }
        },
        "recipient": {
            "capabilities": {
                "stripe_balance": {
                    "stripe_transfers": {
                        "requested": true
                    }
                }
            }
        }
    },
    "include": [
        "configuration.merchant",
        "configuration.recipient",
        "identity",
        "requirements"
    ]
  }'
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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/v2/core/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 -X POST https://api.stripe.com/v2/core/accounts/{{ACCOUNT_ID}}/persons \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-03-25.preview" \
  --json '{
    "given_name": "Jenny",
    "surname": "Rosen",
    "email": "jenny.rosen@example.com",
    "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 v2](https://docs.stripe.com/api/v2/core/account-links.md) API with the following parameters:

- `account`
- `refresh_url`
- `return_url`
- `type` = `account_onboarding`
- `configurations` = `recipient` and `merchant`

```curl
curl -X POST https://api.stripe.com/v2/core/account_links \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2025-08-27.preview" \
  --json '{
    "account": "{{CONNECTEDACCOUNT_ID}}",
    "use_case": {
        "type": "account_onboarding",
        "account_onboarding": {
            "configurations": [
                "recipient",
                "merchant"
            ],
            "refresh_url": "https://example.com/reauth",
            "return_url": "https://example.com/return"
        }
    }
  }'
```

### Redirect your user to the account link URL

The create Account Link response includes a `url` value. After authenticating the user in your application, re-direct them to this URL to send them into the flow. Account link URLs are temporary and single-use only, because they grant access to the connected account user’s personal information. If you want to prefill information, you must do so before generating the account link. After you create the account link, you can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

#### Item 3

### 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 account user is re-directed to your platform.

> You can use HTTP for your `return_url` and `refresh_url` while in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes the onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that the flow was entered and exited properly.

No state is passed through this URL. After a user is re-directed to your `return_url`, check the state of the requirements on their account by doing either of the following:

- Listen to `v2.core.account[requirements].updated` webhooks.
- Call the [Accounts v2](https://docs.stripe.com/api/v2/core/accounts/retrieve.md) API and inspect the returned object.

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v2](https://docs.stripe.com/api/v2/core/account-links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s redirected to your `return_url` might not have completed the onboarding process. Use the `/v2/core/accounts` endpoint to retrieve the user’s account and check if [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status) is `active`. If the status isn’t `active` and [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status_details.code](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status_details-code) is `requirements_past_due`, provide UI prompts to allow the user to continue onboarding through a new account link. For other codes, handle them as needed.

#### Accounts v1

Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account by specifying the [connected account properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type.

```curl
curl https://api.stripe.com/v1/accounts \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "controller[losses][payments]=application" \
  -d "controller[fees][payer]=application" \
  -d "controller[stripe_dashboard][type]=express"
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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 "<<YOUR_SECRET_KEY>>:" \
  -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 v1](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 "<<YOUR_SECRET_KEY>>:" \
  -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 create Account Link response includes a `url` value. After authenticating the user in your application, re-direct to this URL to send them into the flow. Account link URLs from the [Account Links v1](https://docs.stripe.com/api/account_links.md) API 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 re-directing 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 can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

### 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 account user is re-directed 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 in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that 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:

- Listen to `account.updated` webhooks
- Call the [Accounts v1](https://docs.stripe.com/api/accounts.md) API and inspect the returned object

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v1](https://docs.stripe.com/api/account_links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s re-directed 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 through a new account link. You can determine whether they’ve completed onboarding by checking the state of the `details_submitted` parameter on their account.

## Accept a payment

### Step 3.1: Create your checkout page (Client-side)

Securely collect card information on the client with [CardInputWidget](https://stripe.dev/stripe-android/payments-core/com.stripe.android.view/-card-input-widget/index.html), a drop-in UI component provided by the SDK that collects the card number, expiry date, CVC, and postal code.
![](https://d37ugbyn3rpeym.cloudfront.net/docs/mobile/android/android-card-input-widget-with-postal.mp4)
Create an instance of the card component and a **Pay** button by adding the following to your checkout page’s layout:

```xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="20dp"
    tools:context=".CheckoutActivityKotlin"
    tools:showIn="@layout/activity_checkout">

    <!--  ...  -->

    <com.stripe.android.view.CardInputWidget
        android:id="@+id/cardInputWidget"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:text="@string/pay"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/payButton"
        android:layout_marginTop="20dp"
        android:backgroundTint="@android:color/holo_green_light"/>

    <!--  ...  -->

</LinearLayout>
```

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 <<YOUR_SECRET_KEY>>: \
  -d amount=1000 \
  -d currency="usd" \
  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_STRIPE_ACCOUNT_ID}}"
```

In our home-rental example, we want to build a business where customers pay for rentals by using our platform, and where we pay homeowners for renting to customers. To set up this business:

- Indicate the rental is a destination charge with `transfer_data[destination]`.
- Specify how much of the rental amount goes to the platform with `application_fee_amount`.

When a rental charge occurs, Stripe transfers the entire amount to the connected account’s pending balance (`transfer_data[destination]`). Afterward, Stripe transfers the fee amount (`application_fee_amount`) to the platform’s account, which is the share of the revenue for facilitating the rental. Then, Stripe deducts the Stripe fees from the platform’s fee amount. An illustration of this funds flow is below:
![](https://b.stripecdn.com/docs-statics-srv/assets/application_fee_amount.837aa2339469b3c1a4319672971c1367.svg)

> This PaymentIntent creates a destination charge. If you need to control the timing of transfers or need to transfer funds from a single payment to multiple parties, use [separate charges and transfers](https://docs.stripe.com/connect/separate-charges-and-transfers.md) instead.

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

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)).

#### Kotlin

```kotlin
class CheckoutActivity : AppCompatActivity() {

  private lateinit var paymentIntentClientSecret: String

  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      // ...
      startCheckout()
  }

  private fun startCheckout() {
      // Request a PaymentIntent from your server and store its client secret in paymentIntentClientSecret
      // 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 [ConfirmPaymentIntentParams](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-confirm-payment-intent-params/index.html) object with:

1. The card component’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 [PaymentLauncher confirm](https://stripe.dev/stripe-android/payments-core/com.stripe.android.payments.paymentlauncher/-payment-launcher/index.html#74063765%2FFunctions%2F-1622557690) method.

#### Kotlin

```kotlin
class CheckoutActivity : AppCompatActivity() {
    // ...
    private lateinit var paymentIntentClientSecret: String
    private lateinit var paymentLauncher: PaymentLauncher

    private fun startCheckout() {
        // ...

        // Hook up the pay button to the card widget and stripe instance
        val payButton: Button = findViewById(R.id.payButton)
        payButton.setOnClickListener {
            val params = cardInputWidget.paymentMethodCreateParams
            if (params != null) {
                val confirmParams = ConfirmPaymentIntentParams
                    .createWithPaymentMethodCreateParams(params, paymentIntentClientSecret)
                val paymentConfiguration = PaymentConfiguration.getInstance(applicationContext)
                paymentLauncher = PaymentLauncher.Companion.create(
                    this,
                    paymentConfiguration.publishableKey,
                    paymentConfiguration.stripeAccountId,
                    ::onPaymentResult
                )
                paymentLauncher.confirm(confirmParams)
            }
        }
    }

    private fun onPaymentResult(paymentResult: PaymentResult) {
        val message = when (paymentResult) {
            is PaymentResult.Completed -> {
                "Completed!"
            }
            is PaymentResult.Canceled -> {
                "Canceled!"
            }
            is PaymentResult.Failed -> {
                // This string comes from the PaymentIntent's error message.
                // See here: https://stripe.com/docs/api/payment_intents/object#payment_intent_object-last_payment_error-message
                "Failed: " + paymentResult.throwable.message
            }
        }
        displayAlert(
            "PaymentResult: ",
            message,
            restartDemo = true
        )
    }
}
```

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 optimise 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, the SDK presents additional activities and walks the customer through that process. [Learn how to support 3D Secure Authentication on Android](https://docs.stripe.com/payments/3d-secure.md?platform=android).

When the payment completes, `onSuccess` is called and the value of the returned `PaymentIntent`’s `status` is `Succeeded`. Any other value indicates the payment wasn’t successful. Inspect [lastPaymentError](https://stripe.dev/stripe-android/payments-core/com.stripe.android.model/-payment-intent/index.html#com.stripe.android.model/PaymentIntent/lastPaymentError/#/PointingToDeclaration/) to determine the cause.

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 expiry 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: Fulfilment (Server-side)

After the payment is completed, you’ll need to handle any *fulfillment* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) necessary on your end. A home-rental company that requires payment upfront, for instance, would connect the homeowner with the renter after a successful payment.

Configure a *webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) endpoint (for events *from your account*) [in your dashboard](https://dashboard.stripe.com/account/webhooks).
![](https://b.stripecdn.com/docs-statics-srv/assets/account_webhooks.03b71cec87ef2093fe0caa92e5bfce44.png)

Then create an HTTP endpoint on your server to monitor for completed payments to enable your sellers or service providers to fulfill purchases. Make sure to replace the endpoint secret key (`whsec_...`) in the example with your key.

#### 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.
client = Stripe::StripeClient.new('<<YOUR_SECRET_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']
    handle_successful_payment_intent(payment_intent)
  end

  status 200
end

def handle_successful_payment_intent(payment_intent)
  # Fulfill the purchase
  puts payment_intent.to_s
end
```

Learn more in our [fulfilment 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-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.

### Step 3.6: Disputes

As the [settlement merchant](https://docs.stripe.com/connect/destination-charges.md#settlement-merchant) on charges, your platform is responsible for disputes. Make sure you understand the [best practices](https://docs.stripe.com/disputes/responding.md) for responding to disputes.

## 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). You can use [the available test cards](https://docs.stripe.com/testing.md) to test your payments flow and simulate various payment outcomes.


# React Native

> This is a React Native for when platform is react-native and mobile-ui is payment-element. View the full page at https://docs.stripe.com/connect/end-to-end-marketplace?platform=react-native&mobile-ui=payment-element.
![](https://b.stripecdn.com/docs-statics-srv/assets/ios-overview.9e0d68d009dc005f73a6f5df69e00458.png)

This integration combines all of the steps required to pay, including collecting payment details and confirming the payment, into a single sheet that displays on top of your app.

## Prerequisites

1. [Register your platform](https://dashboard.stripe.com/connect).
1. [Verify and add business details in Dashboard](https://dashboard.stripe.com/account/onboarding).
1. [Complete your platform profile](https://dashboard.stripe.com/connect/settings/profile).
1. [Customise your brand settings](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding). Add a business name, icon, and brand colour.

## 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 [React Native SDK](https://github.com/stripe/stripe-react-native) is open source and fully documented. Internally, it uses the [native iOS](https://github.com/stripe/stripe-ios) and [Android](https://github.com/stripe/stripe-android) SDKs. To install Stripe’s React Native SDK, run one of the following commands in your project’s directory (depending on which package manager you use):

#### yarn

```bash
yarn add @stripe/stripe-react-native
```

#### npm

```bash
npm install @stripe/stripe-react-native
```

Next, install some other necessary dependencies:

- For iOS, go to the **ios** directory and run `pod install` to ensure that you also install the required native dependencies.
- For Android, there are no more dependencies to install.

> We recommend following the [official TypeScript guide](https://reactnative.dev/docs/typescript#adding-typescript-to-an-existing-project) to add TypeScript support.

### Stripe initialisation

To initialise Stripe in your React Native app, either wrap your payment screen with the `StripeProvider` component, or use the `initStripe` initialisation method. Only the API [publishable key](https://docs.stripe.com/keys.md#obtain-api-keys) in `publishableKey` is required. The following example shows how to initialise Stripe using the `StripeProvider` component.

```jsx
import { useState, useEffect } from 'react';
import { StripeProvider } from '@stripe/stripe-react-native';

function App() {
  const [publishableKey, setPublishableKey] = useState('');

  const fetchPublishableKey = async () => {
    const key = await fetchKey(); // fetch key from your server here
    setPublishableKey(key);
  };

  useEffect(() => {
    fetchPublishableKey();
  }, []);

  return (
    <StripeProvider
      publishableKey={publishableKey}
      merchantIdentifier="merchant.identifier" // required for Apple Pay
      urlScheme="your-url-scheme" // required for 3D Secure and bank redirects
    >
      {/* Your app code here */}
    </StripeProvider>
  );
}
```

> Use your API [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

#### Accounts v2

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/v2/core/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.

#### Accounts v1

When a seller or service provider signs up on your platform, create a [connected account](https://docs.stripe.com/api/accounts.md) representing them. The connected account lets you collect their identifying information, accept payments for them and move funds to their bank account. In our home-rental example, the connected account represents the homeowner.

### Create a connected account with prefilled information

#### Accounts v2

Use the `/v2/core/accounts` API to [create](https://docs.stripe.com/api/v2/core/accounts/create.md) a connected account by specifying the [connected account dashboard and responsibilities](https://docs.stripe.com/connect/accounts-v2/connected-account-configuration.md).

```curl
curl -X POST https://api.stripe.com/v2/core/accounts \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-01-28.clover" \
  --json '{
    "contact_email": "furever_contact@example.com",
    "display_name": "Furever",
    "defaults": {
        "responsibilities": {
            "fees_collector": "application",
            "losses_collector": "application"
        }
    },
    "dashboard": "express",
    "identity": {
        "business_details": {
            "registered_name": "Furever"
        },
        "country": "us",
        "entity_type": "company"
    },
    "configuration": {
        "merchant": {
            "capabilities": {
                "card_payments": {
                    "requested": true
                }
            }
        },
        "recipient": {
            "capabilities": {
                "stripe_balance": {
                    "stripe_transfers": {
                        "requested": true
                    }
                }
            }
        }
    },
    "include": [
        "configuration.merchant",
        "configuration.recipient",
        "identity",
        "requirements"
    ]
  }'
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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/v2/core/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 -X POST https://api.stripe.com/v2/core/accounts/{{ACCOUNT_ID}}/persons \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2026-03-25.preview" \
  --json '{
    "given_name": "Jenny",
    "surname": "Rosen",
    "email": "jenny.rosen@example.com",
    "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 v2](https://docs.stripe.com/api/v2/core/account-links.md) API with the following parameters:

- `account`
- `refresh_url`
- `return_url`
- `type` = `account_onboarding`
- `configurations` = `recipient` and `merchant`

```curl
curl -X POST https://api.stripe.com/v2/core/account_links \
  -H "Authorization: Bearer <<YOUR_SECRET_KEY>>" \
  -H "Stripe-Version: 2025-08-27.preview" \
  --json '{
    "account": "{{CONNECTEDACCOUNT_ID}}",
    "use_case": {
        "type": "account_onboarding",
        "account_onboarding": {
            "configurations": [
                "recipient",
                "merchant"
            ],
            "refresh_url": "https://example.com/reauth",
            "return_url": "https://example.com/return"
        }
    }
  }'
```

### Redirect your user to the account link URL

The create Account Link response includes a `url` value. After authenticating the user in your application, re-direct them to this URL to send them into the flow. Account link URLs are temporary and single-use only, because they grant access to the connected account user’s personal information. If you want to prefill information, you must do so before generating the account link. After you create the account link, you can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

#### Item 3

### 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 account user is re-directed to your platform.

> You can use HTTP for your `return_url` and `refresh_url` while in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes the onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that the flow was entered and exited properly.

No state is passed through this URL. After a user is re-directed to your `return_url`, check the state of the requirements on their account by doing either of the following:

- Listen to `v2.core.account[requirements].updated` webhooks.
- Call the [Accounts v2](https://docs.stripe.com/api/v2/core/accounts/retrieve.md) API and inspect the returned object.

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v2](https://docs.stripe.com/api/v2/core/account-links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s redirected to your `return_url` might not have completed the onboarding process. Use the `/v2/core/accounts` endpoint to retrieve the user’s account and check if [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status) is `active`. If the status isn’t `active` and [configuration.recipient.capabilities.stripe_balance.stripe_transfers.status_details.code](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-configuration-recipient-capabilities-stripe_balance-stripe_transfers-status_details-code) is `requirements_past_due`, provide UI prompts to allow the user to continue onboarding through a new account link. For other codes, handle them as needed.

#### Accounts v1

Use the `/v1/accounts` API to [create](https://docs.stripe.com/api/accounts/create.md) a connected account by specifying the [connected account properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md), or by specifying the account type.

```curl
curl https://api.stripe.com/v1/accounts \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d "controller[losses][payments]=application" \
  -d "controller[fees][payer]=application" \
  -d "controller[stripe_dashboard][type]=express"
```

If you’ve already collected information for your connected accounts, you can pre-fill that information on the `Account` object. You can pre-fill 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 "<<YOUR_SECRET_KEY>>:" \
  -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 v1](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 "<<YOUR_SECRET_KEY>>:" \
  -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 create Account Link response includes a `url` value. After authenticating the user in your application, re-direct to this URL to send them into the flow. Account link URLs from the [Account Links v1](https://docs.stripe.com/api/account_links.md) API 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 re-directing 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 can’t read or write information for the connected account.

> Don’t email, text, or otherwise send account link URLs outside your platform application. Instead, provide them to the authenticated account holder within your application.

#### Item 1

#### 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
    }
}

```

#### Item 2

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ConnectWithStripeActivity">

    <Button
        android:id="@+id/connect_with_stripe"
        android:text="Connect with Stripe"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="?attr/materialButtonOutlinedStyle"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### Kotlin

```kotlin
class ConnectWithStripeActivity : AppCompatActivity() {

    private val viewBinding: ActivityConnectWithStripeViewBinding by lazy {
        ActivityConnectWithStripeViewBinding.inflate(layoutInflater)
    }
    private val httpClient = OkHttpClient()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(viewBinding.root)

        viewBinding.connectWithStripe.setOnClickListener {
            val weakActivity = WeakReference<Activity>(this)
            val request = Request.Builder()
                .url(BACKEND_URL + "onboard-user")
                .post("".toRequestBody())
                .build()
            httpClient.newCall(request)
                .enqueue(object: Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        // Request failed
                    }
                    override fun onResponse(call: Call, response: Response) {
                        if (!response.isSuccessful) {
                            // Request failed
                        } else {
                            val responseData = response.body?.string()
                            val responseJson =
                                responseData?.let { JSONObject(it) } ?: JSONObject()
                            val url = responseJson.getString("url")

                            weakActivity.get()?.let {
                                val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
                                val customTabsIntent = builder.build()
                                customTabsIntent.launchUrl(it, Uri.parse(url))
                            }
                        }
                    }
                })
        }
    }

    internal companion object {
        internal const val BACKEND_URL = "https://example-backend-url.com/"
    }
}
```

### 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 account user is re-directed 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 in you’re in a testing environment (for example, to test with localhost), but live mode only accepts HTTPS. You must update testing URLs to HTTPS URLs before you go live.

#### return_url

Stripe issues a re-direct to this URL when the connected account user completes onboarding flow. That doesn’t mean that all information has been collected or that there are no outstanding requirements on the account. It only means that 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:

- Listen to `account.updated` webhooks
- Call the [Accounts v1](https://docs.stripe.com/api/accounts.md) API and inspect the returned object

#### refresh_url

Stripe redirects your user to the `refresh_url` in these cases:

- The link has expired (a few minutes have passed since the link was created).
- The user already visited the URL (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.

Configure your `refresh_url` page to trigger a method on your server to call [Account Links v1](https://docs.stripe.com/api/account_links.md) again with the same parameters and re-direct the user to the new link.

### Handle users that haven’t completed onboarding

A user that’s re-directed 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 through a new account link. You can determine whether they’ve completed onboarding by checking 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, Google Pay, and Apple Pay are enabled by default but you can enable and disable payment methods as needed.

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 prioritised. Lower priority payment methods are hidden in an overflow menu.

## 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).

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) A [customer-configured Account](https://docs.stripe.com/api/v2/core/accounts/object.md#v2_account_object-applied_configurations) or a [Customer](https://docs.stripe.com/api/customers.md) object: To set up a payment method for future payments, you must attach it to a customer. Create an object to represent your customer when they create an account with your business. If your customer makes a payment as a guest, you can create an `Account` or `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 object that represents your customer is sensitive, and can’t be retrieved directly from an app. A `CustomerSession` grants the SDK temporary scoped access to the `Account` or `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 for customers and don’t allow returning customers to reuse saved cards, you can omit the `Account` or `Customer` object and the `CustomerSession` object from your integration.

For security reasons, your app can’t create these objects. Instead, add an endpoint on your server that:

1. Retrieves the `Account` or `Customer`, or creates a new one.
1. Creates a [CustomerSession](https://docs.stripe.com/api/customer_sessions.md) for the `Account` or `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 either the [customer_account](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer_account) or the [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer).
1. Returns the `PaymentIntent`’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 ID of the `Account` or `Customer`, 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, note 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 prioritised.

#### 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.

#### Accounts v2

#### curl

```bash
# Create an Account with the customer configuration (use an existing Account ID if this is a returning customer)
curl https://api.stripe.com/v2/core/accounts \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "configuration[customer]"

# Create a CustomerSession for the Account
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \

  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Customers v1

#### curl

```bash
# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an CustomerSession for the Customer
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer"="{{CUSTOMER_ID}}" \
  -d "amount"=1099 \
  -d "currency"="eur" \

  -d "automatic_payment_methods[enabled]"=true \
  -d application_fee_amount="123" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Listing payment methods manually

#### Accounts v2

#### curl

```bash
# Create an Account with the customer configuration (use an existing Account ID if this is a returning customer)
curl https://api.stripe.com/v2/core/accounts \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "configuration[customer]"

# Create a CustomerSession for the Account
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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 <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -d "customer_account"="{{CUSTOMER_ACCOUNT_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" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

#### Customers v1

#### curl

```bash
# Create a Customer (use an existing Customer ID if this is a returning customer)
curl https://api.stripe.com/v1/customers \
  -u <<YOUR_SECRET_KEY>>: \
  -X "POST" \
  -H "Stripe-Account: {{CONNECTED_ACCOUNT_ID}}"

# Create an CustomerSession for the Customer
curl https://api.stripe.com/v1/customer_sessions \
  -u <<YOUR_SECRET_KEY>>: \
  -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 <<YOUR_SECRET_KEY>>: \
  -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" \
  -d "transfer_data[destination]"="{{CONNECTED_ACCOUNT_ID}}" \
```

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

Before displaying the mobile Payment Element, your checkout page should:

- Show the products being purchased and the total amount
- Collect any required shipping information
- Include a checkout button to present Stripe’s UI

In the checkout of your app, make a network request to the backend endpoint you created in the previous step and call `initPaymentSheet` from the `useStripe` hook.

#### Accounts v2

```javascript
export default function CheckoutScreen() {
  const { initPaymentSheet, presentPaymentSheet } = useStripe();
  const [loading, setLoading] = useState(false);

  const fetchPaymentSheetParams = async () => {
    const response = await fetch(`${API_URL}/payment-sheet`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
    });
    const { paymentIntent, ephemeralKey, customer_account } = await response.json();

    return {
      paymentIntent,
      ephemeralKey,
      customer_account,
    };
  };

  const initializePaymentSheet = async () => {
    const {
      paymentIntent,
      ephemeralKey,
      customer_account,
    } = await fetchPaymentSheetParams();

    const { error } = await initPaymentSheet({
      merchantDisplayName: "Example, Inc.",
      customerAccountId: customer_account,
      customerEphemeralKeySecret: ephemeralKey,
      paymentIntentClientSecret: paymentIntent,
      // Set `allowsDelayedPaymentMethods` to true if your business accepts payment
      // methods that complete payment after a delay, like SEPA Debit and Sofort.
      allowsDelayedPaymentMethods: true,
      defaultBillingDetails: {
        name: 'Jane Doe',
      }
    });
    if (!error) {
      setLoading(true);
    }
  };

  const openPaymentSheet = async () => {
    // see below
  };

  useEffect(() => {
    initializePaymentSheet();
  }, []);

  return (
    <Screen>
      <Button
        variant="primary"
        disabled={!loading}
        title="Checkout"
        onPress={openPaymentSheet}
      />
    </Screen>
  );
}
```

#### Customers v1

```javascript

export default function CheckoutScreen() {
  const { initPaymentSheet, presentPaymentSheet } = useStripe();
  const [loading, setLoading] = useState(false);

  const fetchPaymentSheetParams = async () => {
    const response = await fetch(`${API_URL}/payment-sheet`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
    });
    const { paymentIntent, ephemeralKey, customer } = await response.json();

    return {
      paymentIntent,
      ephemeralKey,
      customer,
    };
  };

  const initializePaymentSheet = async () => {
    const {
      paymentIntent,
      ephemeralKey,
      customer,
    } = await fetchPaymentSheetParams();

    const { error } = await initPaymentSheet({
      merchantDisplayName: "Example, Inc.",
      customerId: customer,
      customerEphemeralKeySecret: ephemeralKey,
      paymentIntentClientSecret: paymentIntent,
      // Set `allowsDelayedPaymentMethods` to true if your business can handle payment
      //methods that complete payment after a delay, like SEPA Debit and Sofort.
      allowsDelayedPaymentMethods: true,
      defaultBillingDetails: {
        name: 'Jane Doe',
      }
    });
    if (!error) {
      setLoading(true);
    }
  };

  const openPaymentSheet = async () => {
    // see below
  };

  useEffect(() => {
    initializePaymentSheet();
  }, []);

  return (
    <Screen>
      <Button
        variant="primary"
        disabled={!loading}
        title="Checkout"
        onPress={openPaymentSheet}
      />
    </Screen>
  );
}
```

When your customer taps the **Checkout** button, call `presentPaymentSheet()` to open the sheet. After the customer completes the payment, the sheet is dismissed and the promise resolves with an optional `StripeError<PaymentSheetError>`.

```javascript
export default function CheckoutScreen() {
  // continued from above

  const openPaymentSheet = async () => {
    const { error } = await presentPaymentSheet();

    if (error) {
      Alert.alert(`Error code: ${error.code}`, error.message);
    } else {
      Alert.alert('Success', 'Your order is confirmed!');
    }
  };

  return (
    <Screen>
      <Button
        variant="primary"
        disabled={!loading}
        title="Checkout"
        onPress={openPaymentSheet}
      />
    </Screen>
  );
}
```

If there is no error, inform the user they’re done (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 fulfil their order (for example, ship their product) when the payment is successful.

## Set up a return URL (iOS only) [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<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else {
        return
    }
    let stripeHandled = StripeAPI.handleURLCallback(with: url)
    if (!stripeHandled) {
        // This was not a Stripe url – handle the URL normally as you would
    }
}

```

#### AppDelegate

#### Swift

```swift
// This method handles opening custom URL schemes (for example, "your-app://stripe-redirect")
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    let stripeHandled = StripeAPI.handleURLCallback(with: url)
    if (stripeHandled) {
        return true
    } else {
        // This was not a Stripe url – handle the URL normally as you would
    }
    return false
}
```

#### SwiftUI

#### Swift

```swift

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      Text("Hello, world!").onOpenURL { incomingURL in
          let stripeHandled = StripeAPI.handleURLCallback(with: incomingURL)
          if (!stripeHandled) {
            // This was not a Stripe url – handle the URL normally as you would
          }
        }
    }
  }
}
```

Additionally, set the [returnURL](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html#/s:6Stripe12PaymentSheetC13ConfigurationV9returnURLSSSgvp) on your [PaymentSheet.Configuration](https://stripe.dev/stripe-ios/stripe-paymentsheet/Classes/PaymentSheet/Configuration.html) object to the URL for your app.

```swift
var configuration = PaymentSheet.Configuration()
configuration.returnURL = "your-app://stripe-redirect"
```

## Handle post-payment events

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 in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000002500003155    | The card payment requires *authentication* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase). | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 4000000000009995    | The card is declined with a decline code like `insufficient_funds`.                                                                                                                                                                                                                           | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |
| 6205500000000000004 | The UnionPay card has a variable length of 13-19 digits.                                                                                                                                                                                                                                      | Fill in the credit card form using the credit card number with any expiry date, CVC, and postal code. |

#### Bank redirects

| Payment method    | Scenario                                                                                                                                                                                              | How to test                                                                                                                                              |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bancontact, iDEAL | Your customer fails to authenticate on the redirect page for a redirect-based and immediate notification payment method.                                                                              | Choose any redirect-based payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page. |
| Pay by Bank       | Your customer successfully pays with a redirect-based and [delayed notification](https://docs.stripe.com/payments/payment-methods.md#payment-notification) payment method.                            | Choose the payment method, fill out the required details, and confirm the payment. Then click **Complete test payment** on the redirect page.            |
| Pay by Bank       | Your customer fails to authenticate on the redirect page for a redirect-based and delayed notification payment method.                                                                                | Choose the payment method, fill out the required details, and confirm the payment. Then click **Fail test payment** on the redirect page.                |
| BLIK              | BLIK payments fail in a variety of ways – immediate failures (for example, the code has expired or is invalid), delayed errors (the bank declines) or timeouts (the customer didn’t respond in time). | Use email patterns to [simulate the different failures.](https://docs.stripe.com/payments/blik/accept-a-payment.md#simulate-failures)                    |

#### Bank debits

| Payment method    | Scenario                                                                                          | How to test                                                                                                                                                                                       |
| ----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SEPA Direct Debit | Your customer successfully pays with SEPA Direct Debit.                                           | Fill out the form using the account number `AT321904300235473204`. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later. |
| SEPA Direct Debit | Your customer’s payment intent status transitions from `processing` to `requires_payment_method`. | Fill out the form using the account number `AT861904300235473202`.                                                                                                                                |

See [Testing](https://docs.stripe.com/testing.md) for additional information to test your integration.

## Optional: Enable Apple Pay

### 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

Pass your merchant ID when you create `StripeProvider`:

```javascript
import { StripeProvider } from '@stripe/stripe-react-native';

function App() {
  return (
    <StripeProvider
      publishableKey="<<YOUR_PUBLISHABLE_KEY>>"
      merchantIdentifier="MERCHANT_ID"
    >
      {/* Your app code here */}
    </StripeProvider>
  );
}
```

When you call `initPaymentSheet`, pass in your [ApplePayParams](https://stripe.dev/stripe-react-native/api-reference/modules/PaymentSheet.html#ApplePayParams):

```javascript
await initPaymentSheet({
  // ...
  applePay: {
    merchantCountryCode: 'US',
  },
});
```

#### Recurring payments

When you call `initPaymentSheet`, pass in an [ApplePayParams](https://stripe.dev/stripe-react-native/api-reference/modules/PaymentSheet.html#ApplePayParams) with `merchantCountryCode` set to the country code of your business.

In accordance with [Apple’s guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay#Supporting-subscriptions) for recurring payments, you must also set a `cardItems` that includes a [RecurringCartSummaryItem](https://stripe.dev/stripe-react-native/api-reference/modules/ApplePay.html#RecurringCartSummaryItem) with the amount you intend to charge (for example, “US$59.95 a month”).

You can also adopt [merchant tokens](https://developer.apple.com/apple-pay/merchant-tokens/) by setting the `request` with its `type` set to `PaymentRequestType.Recurring`

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 (React Native)

```javascript
const initializePaymentSheet = async () => {
  const recurringSummaryItem = {
    label: 'My Subscription',
    amount: '59.99',
    paymentType: 'Recurring',
    intervalCount: 1,
    intervalUnit: 'month',
    // Payment starts today
    startDate: new Date().getTime() / 1000,

    // Payment ends in one year
    endDate: new Date().getTime() / 1000 + 60 * 60 * 24 * 365,
  };

  const {error} = await initPaymentSheet({
    // ...
    applePay: {
      merchantCountryCode: 'US',
      cartItems: [recurringSummaryItem],
      request: {
        type: PaymentRequestType.Recurring,
        description: 'Recurring',
        managementUrl: 'https://my-backend.example.com/customer-portal',
        billing: recurringSummaryItem,
        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.'",
      },
    },
  });
};
```

### 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 a `setOrderTracking` callback function. Stripe calls your implementation after the payment is complete, but before iOS dismisses the Apple Pay sheet.

In your implementation of `setOrderTracking` callback function, fetch the order details from your server for the completed order, and pass the details to the provided `completion` function.

To learn more about order tracking, see [Apple’s Wallet Orders documentation](https://developer.apple.com/documentation/walletorders).

#### iOS (React Native)

```javascript
await initPaymentSheet({
  // ...
  applePay: {
    // ...
    setOrderTracking: async complete => {
      const apiEndpoint =
        Platform.OS === 'ios'
          ? 'http://localhost:4242'
          : 'http://10.0.2.2:4567';
      const response = await fetch(
        `${apiEndpoint}/retrieve-order?orderId=${orderId}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        },
      );
      if (response.status === 200) {
        const orderDetails = await response.json();
        // orderDetails should include orderIdentifier, orderTypeIdentifier,
        // authenticationToken and webServiceUrl
        complete(orderDetails);
      }
    },
  },
});
```

## Optional: Enable Google Pay

### Set up your integration

To use Google Pay, first enable the Google Pay API by adding the following to the `<application>` tag of your **AndroidManifest.xml**:

```xml
<application>
  ...
  <meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />
</application>
```

For more details, see Google Pay’s [Set up Google Pay API](https://developers.google.com/pay/api/android/guides/setup) for Android.

### Add Google Pay

When you initialise `PaymentSheet`, set `merchantCountryCode` to the country code of your business and set `googlePay` to true.

You can also use the test environment by passing the `testEnv` parameter. You can only test Google Pay on a physical Android device. Follow the [React Native docs](https://reactnative.dev/docs/running-on-device) to test your application on a physical device.

```javascript
const { error, paymentOption } = await initPaymentSheet({
  // ...
  googlePay: {
    merchantCountryCode: 'US',
    testEnv: true, // use test environment
  },
});
```

## Enable card scanning [Client-side]

> Enabling card scanning is required for Apple’s iOS app review process. Card scanning is not required for Android’s app review process, but we recommend enabling it.

### iOS

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) Android

To enable card scanning support, [request production access](https://developers.google.com/pay/api/android/guides/test-and-deploy/request-prod-access) to the Google Pay API from the [Google Pay and Wallet Console](https://pay.google.com/business/console?utm_source=devsite&utm_medium=devsite&utm_campaign=devsite).

- If you’ve enabled Google Pay, the card scanning feature is automatically available in our UI on eligible devices. To learn more about eligible devices, see the [Google Pay API constraints](https://developers.google.com/pay/payment-card-recognition/debit-credit-card-recognition)
- **Important:** The card scanning feature only appears in builds signed with the same signing key registered in the [Google Pay & Wallet Console](https://pay.google.com/business/console). Test or debug builds using different signing keys (for example, builds distributed through Firebase App Tester) won’t show the **Scan card** option. To test card scanning in pre-release builds, you must either:
  - Sign your test builds with your production signing key
  - Add your test signing key fingerprint to the Google Pay and Wallet Console

## Optional: Customise the sheet

All customization is configured using `initPaymentSheet`.

### Appearance

Customise colours, fonts and so on to match the look and feel of your app by using the [appearance API](https://docs.stripe.com/elements/appearance-api/mobile.md?platform=react-native).

### Merchant display name

Specify a customer-facing business name by setting `merchantDisplayName`. By default, this is your app’s name.

```javascript
await initPaymentSheet({
  // ...
  merchantDisplayName: 'Example Inc.',
});
```

### Dark mode

By default, `PaymentSheet` automatically adapts to the user’s system-wide appearance settings (light and dark mode). You can change this by setting the `style` property to `alwaysLight` or `alwaysDark` mode on iOS.

```javascript
await initPaymentSheet({
  // ...
  style: 'alwaysDark',
});
```

On Android, set light or dark mode on your app:

```
// force dark
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
// force light
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
```

### Default billing details

To set default values for billing details collected in the PaymentSheet, configure the `defaultBillingDetails` property. The `PaymentSheet` pre-populates its fields with the values that you provide.

```javascript
await initPaymentSheet({
  // ...
  defaultBillingDetails: {
      email: 'foo@bar.com',
      address: {
        country: 'US',
      },
  },
});
```

### Collect billing details

Use `billingDetailsCollectionConfiguration` to specify how you want to collect billing details in the PaymentSheet.

You can collect your customer’s name, email, phone number, and address.

If you don’t intend to collect the values that the payment method requires, you must do the following:

1. Attach the values that aren’t collected by `PaymentSheet` to the `defaultBillingDetails` property.
1. Set `billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod` to `true`.

```javascript
await initPaymentSheet({
  // ...
  defaultBillingDetails: {
    email: 'foo@bar.com',
  },
  billingDetailsCollectionConfiguration: {
    name: PaymentSheet.CollectionMode.ALWAYS,
    email: PaymentSheet.CollectionMode.NEVER,
    address: PaymentSheet.AddressCollectionMode.FULL,
    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 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 payment details are collected.
![](https://b.stripecdn.com/docs-statics-srv/assets/react-native-multi-step.84d8a0a44b1baa596bda491322b6d9fd.png)

> A sample integration is [available on our GitHub](https://github.com/stripe/stripe-react-native/blob/master/example/src/screens/PaymentsUICustomScreen.tsx).

1. First, call `initPaymentSheet` and pass `customFlow: true`. `initPaymentSheet` resolves with an initial payment option containing an image and label representing the customer’s payment method. Update your UI with these details.

```javascript
const {
  initPaymentSheet,
  presentPaymentSheet,
  confirmPaymentSheetPayment,
} = useStripe()

const { error, paymentOption } = await initPaymentSheet({
  customerId: customer,
  customerEphemeralKeySecret: ephemeralKey,
  paymentIntentClientSecret: paymentIntent,
  customFlow: true,
  merchantDisplayName: 'Example Inc.',
});
// Update your UI with paymentOption
```

1. Use `presentPaymentSheet` to collect payment details. When the customer finishes, the sheet dismisses itself and resolves the promise. Update your UI with the selected payment method details.

```javascript
const { error, paymentOption } = await presentPaymentSheet();
```

1. Use `confirmPaymentSheetPayment` to confirm the payment. This resolves with the result of the payment.

```javascript
const { error } = await confirmPaymentSheetPayment();

if (error) {
  Alert.alert(`Error code: ${error.code}`, error.message);
} else {
  Alert.alert(
    'Success',
    'Your order is confirmed!'
  );
}
```

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 fulfil their order (for example, ship their product) when the payment is successful.


# No code

> This is a No code for when platform is no-code. View the full page at https://docs.stripe.com/connect/end-to-end-marketplace?platform=no-code.

This guide demonstrates how to accept payments from customers and move funds to the bank accounts of your sellers or service providers, without writing code. Use this guide if you:

- Want to accept payments from customers and pay out sellers or service providers without writing code.
- Want to rapidly test product-market fit without coding.
- Are a marketplace that is selling directly to end customers (as opposed to a SaaS platform who is selling software to help others operate their own business).

In this example, we’ll build a marketplace that allows T-shirt artists to sell customised T-shirts. You can use the concepts covered in this guide in other business applications as well.

## Prerequisites

1. [Register your platform](https://dashboard.stripe.com/connect).
1. [Verify and add business details in Dashboard](https://dashboard.stripe.com/account/onboarding).
1. Complete your [platform profile](https://dashboard.stripe.com/connect/settings/profile).
1. Customise your Connect brand settings on the [Stripe Dashboard settings page](https://dashboard.stripe.com/settings/connect/stripe-dashboard/branding) and customise your Payment Link brand settings on the Checkout [Branding settings page](https://dashboard.stripe.com/settings/branding).

## Create a connected account

When a seller or service provider signs up on your marketplace, you need to create a user account (referred to as a *connected account*) so you can move funds to their bank account. Connected accounts represent your sellers or service providers. In our T-shirt marketplace example, the connected account represents the artist making the shirts.

### Create a connected account onboarding link

#### Accounts v2

- Go to the [Connected accounts](https://dashboard.stripe.com/connect/accounts) page and click **+Create** to create a new connected account.
- Select **Merchant** and click **Next**
- Click **Edit** for **Account properties**
- Select **Express** for **Stripe dashboard access** and click **Save**
- Click **Create** to create the account and generate an onboarding link that you can then share with your seller or service provider over email, text message or other private means. Don’t share this link publicly.
![The create account modal displaying a generated Express link for onboarding a connected account](https://b.stripecdn.com/docs-statics-srv/assets/no-code-connect-v2-express-link.9b4b6bc618d0ecab2b0f2a146f600915.png)

Create an onboarding link

#### Accounts v1

- Go to the [Connected accounts](https://dashboard.stripe.com/connect/accounts) page and click **+Create** to create a new connected account.
- Select **Express** for account type and select the country where this connected account is located. Stripe allows platforms in supported countries to [create connected accounts in other Stripe-supported countries](https://docs.stripe.com/connect/express-accounts.md#onboarding-express-accounts-outside-of-your-platforms-country).
- Click **Continue** to generate the onboarding link that you can then share with your seller or service provider over email, SMS, or other private means. Don’t share this link publicly.
![The create account modal displaying a generated Express link for onboarding a connected account](https://b.stripecdn.com/docs-statics-srv/assets/no-code-connect-v1-express-link.0ce2e3df98419aae23f88c5cb77ca3f4.png)

Create an onboarding link

This link directs sellers and service providers to a sign-up form where they can provide their identity and bank account information to onboard to your marketplace. In our example of a T-shirt marketplace, share this link with the T-shirt artist to onboard them. This link expires after 90 days and is for use by a single seller or service provider. After they complete the onboarding flow, you can see their account details in your [Connected accounts](https://dashboard.stripe.com/connect/accounts) page. Repeat these steps any time you need to add additional sellers or service providers.
![A single account displayed in the connected accounts list view](https://b.stripecdn.com/docs-statics-srv/assets/no-code-connect-connected-accounts.a62a6dc14ef6c95ae5a1e36801090945.png)

## Create the Payment Link to accept payments

Now that you’ve created a connected account, create a Payment Link to accept payments from your customers – no coding required. When you send this link to your marketplace customer, they’ll land on a Stripe-hosted page where they can pay you. In the T-shirt marketplace example, customers buy T-shirts through your marketplace, and you pay T-shirt artists for designing and creating the T-shirts. To set this up, click **+Create link** on the [Payment Links page](https://dashboard.stripe.com/payment-links) and follow the steps.

### Step 2.1: Add the product

You configure Payment Links for a specific product and price so the first step is to [add the product](https://support.stripe.com/questions/how-to-create-products-and-prices) you want to sell. Click **+Add new product** to add a product.
![The first page of the payment link creation form](https://b.stripecdn.com/docs-statics-srv/assets/no-code-connect-empty-payment-link.9885cf82263d39d2be55c4fbdcbcbb9f.png)

Set your product name, description, and price, which are all visible to the customer on the Checkout page that you redirect them to. After entering the information for your new product click **+Add product** to add the product. In this example, we’re adding a medium-sized tree-patterned T-shirt product, so we’ll configure the Payment Link for this particular product. If we also wanted to sell small-sized T-shirts or T-shirts with other designs, we would follow the above steps to create a new Payment Link and a new product.
![The product creation modal found within the payment link creation form](https://b.stripecdn.com/docs-statics-srv/assets/no-code-connect-create-product.ce265a710c21ecb80ac7715dc240721a.png)

### Step 2.2: Customise the Payment Link

You can customise your Payment Link to allow customers to enter promotion codes, adjust the quantity of the product, or enter their shipping and billing address. For our example of a T-shirt marketplace, we want to allow customers to buy a variable number of T-shirts. We also need to collect the customer’s shipping address so we can ship the T-shirts to them. To enable both of these, select **Let customers adjust quantity** and **Collect customers’ addresses** then click **Next**.

### Step 2.3: Decide when to pay your connected account

You have two options for paying out your connected account.

- If you already know which connected account you want to pay using funds collected through this Payment Link, you can automatically pay out.
- However, if you don’t know the connected account ahead of time or need to pay multiple connected accounts, you can pay out later.

#### Automatically pay out

#### Select the connected account to pay

Select the tickbox that says **Split the payment with a connected account**. Selecting this tickbox allows a connected account to automatically get paid when a customer buys the product through this Payment Link. In our example, we want to automatically pay Jamie, the T-shirt artist, whenever a customer buys their T-shirt. To do that, we specify Jamie as the connected account.

#### Specify the application fee for the platform

To keep a portion of the payment for your marketplace, you can specify an application fee. This is a fixed amount for each payment that uses this payment link. It doesn’t change based on quantity, discounts, or taxes, and we cap it at the total purchase amount. In our example, our T-shirt marketplace takes a 1 USD application fee per sale and sends the remaining payment (9 USD) to the T-shirt artist. If the customer purchases two T-shirts, uses a promotion code, or needs to pay additional taxes on top of their 10 USD purchase, the T-shirt marketplace still receives a 1 USD application fee per sale. For products with recurring payments (subscriptions), you can specify an application fee as a percentage of the total transaction value.
![The second page of the payment link creation form. This page includes options for routing funds to connected accounts.](https://b.stripecdn.com/docs-statics-srv/assets/no-code-connect-create-payment-link-page-2.465b89cd12b53c1e3e623a0a3b03ffb0.png)

After configuring the Payment Link to split the payment with your connected account, click **+Create link** to generate the Payment Link URL.

After the customer pays, Stripe transfers the entire amount to the connected account’s pending balance and then transfers the application fee amount to the platform’s account as revenue for facilitating the sale. Then Stripe deducts the Stripe fees from the platform’s application fee. Under the hood, this funds flow is called a [destination charge](https://docs.stripe.com/connect/destination-charges.md).

#### Pay out later

#### Pay your connected account at a later time

In some cases you might prefer to manually transfer funds to a connected account at a later time instead of automatically at the time of purchase. This might happen if you don’t yet know which connected account should receive the funds, or if you need to pay multiple connected accounts for a single sale. For example:

- You sell a T-shirt that’s a collaboration between two artists who should each receive half of the funds.
- You sell a T-shirt that can be fulfilled by one of several artists, and you don’t yet know who will fulfil the order.

To create the Payment Link to accept payment from customers, make sure that you *don’t* check **Split the payment with a connected account**, and then click **Create link** to generate your Payment Link. When you’re ready to pay your connected account, go to the **Balance** section of the connected account’s details page and click **Send funds**. Under the hood, this funds flow is called a [Separate Charge and Transfer](https://docs.stripe.com/connect/separate-charges-and-transfers.md).

## Share the Payment Link with your customers

Now that you’ve created your Payment Link, copy the Payment Link URL and share it publicly on your website or through social media. When a customer clicks on your Payment Link URL, they see your customised Checkout page (example below) and can enter their payment information—they can pay from mobile or desktop with a card, Apple Pay, or Google Pay.
![](https://d37ugbyn3rpeym.cloudfront.net/docs/connect/video/no-code-connect-mobile-payment-link.mp4)
## After the payment

### Step 4.1 View your payments

Go to the [Payments page](https://dashboard.stripe.com/payments) to see the list of payments your business has accepted. You can click an individual payment to see more details about it, such as the shipping address if you chose to collect one. You can see the [application fees](https://dashboard.stripe.com/connect/application_fees) your business collected for each payment, or go to the [Balance page](https://dashboard.stripe.com/balance/overview) to see your total funds.

### Step 4.2 Fulfilment

After the payment completes, you need to handle *fulfilment* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) of the product. In the T-shirt marketplace example, this would entail shipping the T-shirts to the buyer after payment.

## Optional: Add more payment methods

Payment Links supports [more than 20 payment methods](https://docs.stripe.com/payments/payment-methods/payment-method-support.md#product-support). You can enable payment methods in your [Payment methods settings](https://dashboard.stripe.com/settings/payment_methods). After you enable payment methods, Stripe dynamically shows the payment methods most likely to increase conversion and most relevant to the currency and customer’s location.

> Stripe introduced support for more payment methods on 25 January 2022. Payment links created prior to this date only support card payments and wallets. Create a new link to enable more payment methods.

Some payment methods, such as bank debits or vouchers, take 2-14 days to confirm the payment. Use webhooks to notify you when payment clears so you can start fulfilment. See our [fulfilment guide](https://docs.stripe.com/checkout/fulfillment.md#create-payment-event-handler).


## Disputes

As the [settlement merchant](https://docs.stripe.com/connect/destination-charges.md#settlement-merchant) on charges, your platform is responsible for disputes. Make sure you understand the [best practices](https://docs.stripe.com/disputes/responding.md) for responding to disputes.

## Payouts

By default, any funds that you transfer to a connected account accumulate in the connected account’s [Stripe balance](https://docs.stripe.com/connect/account-balances.md) and are paid out on a daily rolling basis. You can change the *payout* (A payout is the transfer of funds to an external account, usually a bank account, in the form of a deposit) frequency by going into the connected account’s details page, clicking the right-most button in the **Balance** section, and selecting **Edit payout schedule**.

## Refunds

To issue refunds, go to the [Payments](https://dashboard.stripe.com/payments) page. Select individual payments by clicking the checkbox to the left of any payments you want to refund. After you select a payment, Stripe displays a **Refund** button in the upper-right corner of the page. Click the **Refund** button to issue a refund to customers for all payments you have selected.

> Connected accounts can’t initiate refunds for payments from the [Express Dashboard](https://docs.stripe.com/connect/express-dashboard.md) by default. If your connected accounts use the Express Dashboard, you can either process refunds for them, or enable them to process refunds by customising [Express dashboard features](https://docs.stripe.com/connect/express-dashboard.md#express-dashboard-features).

## See also

- [Manage connected accounts in the Dashboard](https://docs.stripe.com/connect/dashboard.md)
- [Issue refunds](https://docs.stripe.com/connect/direct-charges.md#issue-refunds)
- [Customise statement descriptors](https://docs.stripe.com/connect/statement-descriptors.md)
- [Work with multiple currencies](https://docs.stripe.com/connect/currencies.md)
- [Express dashboard](https://docs.stripe.com/connect/express-dashboard.md)
