Accept payments for digital goods on iOS with a custom checkout page
Open your own custom checkout to sell in-app digital goods and subscriptions using Payment Element.
This guide describes how to sell a subscription in your app by redirecting to your own custom checkout page using Elements.
Note
Contact our sales team if your business is new to Stripe, processes a high volume of payments, and has advanced integration needs.
What you’ll build 
Note
This guide only describes the process for selling in-app digital goods. If you sell any of the following use the native iOS payment guide instead:
- Physical items
- Goods and services intended for consumption outside your app
- Real-time person-to-person services between two individuals
This guide shows you how to:
- Collect payment information with your own custom checkout page using Elements.
- Model your subscriptions with Products, Prices, and Customers.
- Use universal links to redirect directly to your app from Checkout.
- Monitor webhooks to update your customer’s in-app subscriptions.
Set up StripeServer-side
First, register for a Stripe account.
Then add the Stripe API library to your backend:
Next, install the Stripe CLI. The CLI provides the required webhook testing, and you can run it to create your products and prices.
For additional install options, see Get started with the Stripe CLI.
Create products and prices
Create your products and their prices in the Dashboard or with the Stripe CLI.
This example uses a single product and price to represent a subscription product with a $9.99 USD monthly price.
Navigate to the Add a product page and create a subscription product with a 9.99 USD monthly price.
After you create the price, record the price ID so you can use it in subsequent steps. Price IDs look like this: price_
.
Next, click Copy to live mode to clone your product from a testing environment to live mode.
Create customersServer-side
Each time your customer goes to your checkout page, create a Customer object for your customer if one doesn’t already exist.
Your server needs to handle:
- Customer creation (if a matching Stripe Customer doesn’t exist yet).
- Subscription creation in an
incomplete
state. - Returning the PaymentIntent client secret to the front end.
- Webhook handling so you can update your customer’s subscription status in your own database.
Warning
Store an association on your server between the user account and the Stripe customer ID. Without a way to associate a customer with a purchase, your customers can’t recover their purchases.
If the customer changes their email on the checkout page, the Customer object updates with the new email.
Create a SubscriptionServer-side
When creating a subscription to use the Payment Element, you typically pass payment_
. This tells Stripe to create a subscription in incomplete
status and generate a Payment Intent for the initial payment.
Note
Store the subscription.
in your database to manage future subscription events such as cancellation, upgrades, and downgrades.
Note
Apple Pay is enabled by default and automatically appears in the Payment Element when a customer uses a supported device and has saved at least one card in the Wallet app. You can accept additional payment methods using the payment_
property. See payment methods overview for more details.
Set up universal linksClient-sideServer-side
Universal links allow your checkout page to deeply link into your app. To configure a universal link:
- Add an
apple-app-site-association
file to your domain. - Add an Associated Domains entitlement to your app.
- Add a fallback page for your checkout redirect URLs.
Define the associated domains
Add a file to your domain at .
to define the URLs that your app handles. Prepend your App ID with your Team ID, which you can find on the Membership page of the Apple Developer Portal.
{ "applinks": { "apps": [], "details": [ { "appIDs": [ "A28BC3DEF9.com.example.MyApp1", "A28BC3DEF9.com.example.MyApp1-Debug" ], "components": [ { "/": "/checkout_redirect*", "comment": "Matches any URL whose path starts with /checkout_redirect" } ] } ] } }
Warning
You must serve the file with MIME type application/json
. Use curl -I
to confirm the content type.
curl -I https://example.com/.well-known/apple-app-site-association
See Apple’s page on supporting associated domains for more details.
Add an Associated Domains entitlement to your app
- Open the Signing & Capabilities pane of your app’s target.
- Click + Capability, then select Associated Domains.
- Add an entry for
applinks:yourdomain.
to the Associated Domains list.com
For more information on universal links, see Apple’s universal links documentation.
Although iOS intercepts links to the URLs defined in your apple-app-site-association
file, you might encounter situations where the redirect fails to open your app.
Create a fallback page at your success
and cancel
URLs. For example, you can have a /checkout_
page and a /checkout_
page.
Open Checkout in SafariClient-side
Add a checkout button to your app. This button opens your custom checkout page in Safari.
import Foundation import SwiftUI import StoreKit struct BuySubscriptionsView: View { @EnvironmentObject var myBackend: MyBackend @State var paymentComplete = false var body: some View { // Check if payments are blocked by Parental Controls on this device. if !SKPaymentQueue.canMakePayments() { Text("Payments are disabled on this device.") } else { if paymentComplete { Text("Payment complete!") } else { Button { UIApplication.shared.open("https://example.com/checkout", options: [:], completionHandler: nil) } label: { Text("Subscribe") }.onOpenURL { url in // Handle the universal link from Checkout. if url.absoluteString.contains("success") { // The payment was completed. Show a success // page and fetch the latest customer entitlements // from your server. paymentComplete = true } } } } } }
Redirect back to your appServer-side
With Elements, make sure you redirect users back to your app (using the registered universal link) on a successful payment confirmation.
Handle order fulfillmentServer-side
When the user completes the initial payment or when subsequent recurring payments occur, Stripe sends events such as:
invoice.
payment_ succeeded customer.
subscription. updated invoice.
payment_ failed
Listen for these events in your webhook endpoint. For example:
To test your integration, you can monitor events in the Dashboard or using the Stripe CLI. When developing in production, set up a webhook endpoint and subscribe to appropriate event types. If you don’t know your STRIPE_
key, click the webhook in the Dashboard to view it.
Testing 
To test your that your checkout button works, do the following:
- Click the checkout button, which redirects you to your checkout with Stripe’s Payment Element.
- Enter the test number , a three-digit CVC, a future expiration date, and any valid postal code.
- Tap Pay.
- The
invoice.
webhook fires, and Stripe notifies your server about the transaction.payment_ succeeded - You’re redirected back to your app.
If your integration isn’t working, see additional testing resources.