Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

4466 articles

Accept payments for digital goods on iOS with a prebuilt payment page


Accept payments for digital goods on iOS with a prebuilt payment page

Open Stripe Checkout in a browser to sell in-app digital goods or subscriptions.

Note

Learn how to build a similar integration that uses Managed Payments, Stripe’s merchant of record solution.

You can also configure and preview your app to web checkout through Checkout studio, which provides a centralized Dashboard page for managing your Checkout integrations.

For iOS apps selling digital products, content, and subscriptions in the US, you can redirect customers to an external payment page to accept payments using Stripe Checkout. Use StoreKit’s Storefront property to detect which storefront your app was downloaded from. This guide describes how to accept payments for purchasing credits in your iOS app by redirecting customers to a Stripe-hosted payment page.

For Android developers in the US, you can process payments directly in-app with a third party payment processor. To accept payments directly in-app with Stripe, see In-app payments.

This guide only describes the process for iOS developers selling in-app digital goods. Use the native app payment guide if you sell:

  • 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:

Use the Accounts v2 API to represent customers

The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a preview version in your code.

To join the Accounts v2 preview, go to Account previews and features in your Dashboard and enable Reusable payment methods for Global Payouts.

For most use cases, we recommend modeling your customers as customer-configured Account objects instead of using Customer objects.

Link out of app for one-time payments

Link out of app for recurring or subscription payments

How it works

The following diagram shows the full app-to-web payment flow at a high level:

What isn’t covered

This guide demonstrates how to add Stripe Checkout alongside your existing in-app purchase system. It doesn’t cover:

  • User authentication. If you don’t have an existing authentication provider, you can use a third-party provider, such as Sign in with Apple or Firebase Authentication .
  • Native in-app purchases. To implement in-app purchases using StoreKit, visit Apple’s in-app purchase guide .

Set up Stripe Server-side

First, register for a Stripe account.

Then add the Stripe API library to your backend:

Command Line

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

# Available as a gem
sudo gem install stripe

Gemfile

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

Next, install the Stripe CLI. The CLI provides the webhook testing you’ll need, and you can run it to create your products and prices.

Install the Stripe CLI with npm:

Command Line

npm install -g @stripe/cli@latest

After installation, log in to your Stripe account:

Command Line

stripe login

After you install the CLI, you can also install agent tooling, or set up autocompletion.

Note

For more installation options for Windows, macOS, Linux, and Docker, see the Stripe CLI readme on GitHub.

Create products and prices

Create your products and their prices in the Dashboard or with the Stripe CLI. You can model digital goods using one-off prices and subscriptions using recurring prices. You can also let your customer pay what they want (for example, to decide how many credits to buy), by selecting Customers choose what to pay.

This example uses a single product and price to represent a 100 coin bundle.

Navigate to the Add a product page and create the coin bundle. Add a one-time price of 10 USD.

  • 100 coins: Bundle of 100 in-app coins
  • Price: Standard model | 10 USD | One time

After you create the price, record the price ID so you can use it in subsequent steps. Price IDs look like this: price_G0FvDp6vZvdwRZ.

When you’re ready, use the Copy to live mode button at the top right of the page to clone your product from a testing environment to live mode.

Create customers Server-side

Each time you create a Checkout Session, create a customer-configured Account object representing your customer, if one doesn’t already exist.

Node.js

// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
// Find your keys at https://dashboard.stripe.com/apikeys.
const stripe = require('stripe')('sk_test_BQokikJOvBiI2HlWgH4olfQ2');

// This assumes your app has an existing customer database, which we'll call `myUserDB`.
const user = myUserDB.getUser("jennyrosen");

if (!user.stripeCustomerAccountID) {
 const customer_account = await stripe.v2.core.accounts.create({
 display_name: user.name,
 contact_email: user.email,
 });

 // Set the user's Stripe customer Account ID for later retrieval. Associating the user with the Stripe object ID lets your customers recover their purchases.
 user.stripeCustomerAccountID = customer_account.id;
}

If your app doesn’t have an existing authentication provider, you can use Sign in with Apple.

You can save payment method details to have Checkout automatically attach the payment method to the Account for future reuse.

Universal links allow Checkout to deep link into your app. To configure a universal link:

  1. Add an apple-app-site-association file to your domain.
  2. Add an Associated Domains entitlement to your app.
  3. Add a fallback page for your Checkout redirect URLs.

Define the associated domains

Add a file to your domain at .well-known/apple-app-site-association to define the URLs your app handles. Prepend your App ID with your Team ID, which you can find on the Membership page of the Apple Developer Portal.

.well-known/apple-app-site-association

{
 "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"
 }
 ]
 }
 ]
 }
}

You must serve the file with the related setting type application/json. Use curl -I to confirm the content type.

Command Line

See Apple’s page on supporting associated domains for more details.

Add an Associated Domains entitlement to your app

  1. Open the Signing & Capabilities pane of your app’s target.
  2. Click + Capability , then select Associated Domains .
  3. Add an entry for applinks:example. com to the Associated Domains list.

For more information on universal links, see Apple’s Universal Links for Developers page.

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.

Make sure to create a fallback page at your success_url. For example, you can define a custom URL scheme for your app and use it to link back in case the universal link fails.

Create a Checkout session Server-side

A Checkout Session is the programmatic representation of what your customer sees when they’re redirected to the payment form. Checkout Sessions expire 24 hours after creation. Configure it with:

  • The customer-configured Account ID
  • The Product ID (either a one-time payment or a subscription)
  • An origin _ context set to mobile _ app (to use a UI optimized for app-to-web purchases)
  • A success _ url (a universal link to redirect your customer to your app after they complete the payment)

After creating a Checkout Session, return the URL from the response to your app.

Node.js

Note

Apple Pay is enabled by default and automatically appears in Checkout when a customer uses a supported device. You can accept additional payment methods by using dynamic payment methods.

Open Checkout in Safari Client-side

Add a checkout button to your app. This button:

  1. Calls your server-side endpoint to create a Checkout session.
  2. Returns the Checkout session to the client.
  3. Opens the session URL in Safari.

CheckoutView.swift

import Foundation
import SwiftUI
import StoreKit

struct BuyCoinsView: View {
 @EnvironmentObject var myBackend: MyServer
 @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 {
 myBackend.createCheckoutSession { url in
 UIApplication.shared.open(url, options: [:], completionHandler: nil)
 }
 } label: {
 Text("Buy 100 coins")
 }.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
 }
 }
 }
 }
 }
}

Fetch the Checkout URL on the client

Use your server endpoint to fetch the checkout session.

CheckoutView.swift

class MyServer: ObservableObject {
 // The cached login token
 var token: String?

 func createCheckoutSession(completion: @escaping (URL) -> Void) {
 // Send the login token to the `/create_checkout_session` endpoint
 let request = URLRequest(url: URL(string: "https://example.com/create-checkout-session?token=\(self.token)")!)

 let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
 guard let unwrappedData = data,
 let json = try? JSONSerialization.jsonObject(with: unwrappedData, options: []) as? [String : Any],
 let urlString = json["url"] as? String,
 let url = URL(string: urlString) else {
 // Handle error
 return
 }

 DispatchQueue.main.async {
 // Call the completion block with the Checkout session URL returned from the backend
 completion(url)
 }
 })
 task.resume()
 }

 func login() {
 // Login using the server and set the login token.
 let request = URLRequest(url: URL(string: "https://example.com/login")!)

 let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
 guard let unwrappedData = data,
 let json = try? JSONSerialization.jsonObject(with: unwrappedData, options: []) as? [String : Any],
 let token = json["token"] as? String else {
 // Handle error
 return
 }
 self.token = token
 })
 task.resume()
 }
}

Handle order fulfillment Server-side

After the purchase succeeds, Stripe sends you a checkout.session.completed webhook. When you receive this event, you can add the coins to the customer on your server.

Checkout redirects your customer to the success_url when you acknowledge you received the event. In scenarios where your endpoint is down or the event isn’t acknowledged properly, Checkout redirects the customer to the success_url 10 seconds after a successful payment.

For testing purposes, you can monitor events in the Dashboard or using the Stripe CLI. For production, set up a webhook endpoint and subscribe to appropriate event types. If you don’t know your the related setting key, click the webhook in the Dashboard to view it.

server.js

Node.js

// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
// Find your keys at https://dashboard.stripe.com/apikeys.
const stripe = require('stripe')('sk_test_BQokikJOvBiI2HlWgH4olfQ2');

app.post("/webhook", async (req, res) => {
 let data;
 let eventType;
 // Check if webhook signing is configured.
 const webhookSecret = "{{STRIPE_WEBHOOK_SECRET}}"
 if (webhookSecret) {
 // Retrieve the event by verifying the signature using the raw body and secret.
 let event;
 let signature = req.headers["stripe-signature"];

 try {
 event = stripe.webhooks.constructEvent(
 req.body,
 signature,
 webhookSecret
 );
 } catch (err) {
 console.log(`⚠️ Webhook signature verification failed.`);
 return res.sendStatus(400);
 }
 // Extract the object from the event.
 data = event.data;
 eventType = event.type;
 } else {
 // Webhook signing is recommended, but if the secret is not configured in `config.js`,
 // retrieve the event data directly from the request body.
 data = req.body.data;
 eventType = req.body.type;
 }

 switch (eventType) {
 case 'checkout.session.completed':
 const session = event.data.object;
 // Payment is successful.
 // Update the customer in your database to reflect this purchase.
 const user = myUserDB.userForStripeCustomerID(session.customer);
 user.addCoinsTransaction(100, session.id);
 break;
 default:
 // Unhandled event type
 }

 res.sendStatus(200);
});

Testing

Test your checkout button that redirects your customer to Stripe Checkout.

  1. Click the checkout button, which redirects you to the Stripe Checkout payment form.
  2. Enter the test number , a three-digit CVC, a future expiration date, and any valid postal code.
  3. Tap Pay .
  4. The checkout. session. completed webhook fires, and Stripe notifies your server about the transaction.
  5. You’re redirected back to your app.

If your integration isn’t working, see the additional testing resources section below.

Optional Additional testing resources

Optional In-app purchases with Managed Payments

Optional Price display configuration

See also

Last verified 2026-09-24

Is this helpful?