Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Developer tools
Get started
Payments
Finance automation
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseManaged Payments
Use Payment Links
Build a checkout page
Build an advanced integration
Build an in-app integration
    Overview
    Payment Sheet
    Embedded Payment Element
    Link out for in-app purchases
      Use a prebuilt payment page
      Use Payment Links (low-code)
      Build a custom flow
    Collect addresses
    US and Canadian cards
Payment methods
Add payment methods
Manage payment methods
Faster checkout with Link
Payment interfaces
Payment Links
Checkout
Web Elements
In-app Elements
Payment scenarios
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Other Stripe products
Financial Connections
Crypto
Climate
HomePaymentsBuild an in-app integrationLink out for in-app purchases

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

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

Copy page

In some countries, you can link to an external website to accept payments on iOS. As an example, this guide describes how to sell credits for consumption in your app. You use Stripe Checkout to redirect your customers to a Stripe-hosted payment page. If you have a limited number of products and prices, you can also use low-code Payment Links to accept payments for digital goods on iOS.

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’re selling:

  • Physical items
  • Goods and services intended for consumption outside your app
  • Real-time person-to-person services between two individuals

Use the native iOS payment guide instead.

This guide shows you how to:

  • Collect payment information with Checkout.
  • Model your credit packages 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 currency balance.

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
Ruby
# Available as a gem sudo gem install stripe
Gemfile
Ruby
# 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.

Command Line
homebrew
# Install Homebrew to run this command: https://brew.sh/ brew install stripe/stripe-cli/stripe # Connect the CLI to your dashboard stripe login

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. 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 object for your user if one doesn’t already exist.

Node
// Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')(
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
); // This assumes your app has an existing user database, which we'll call `myUserDB`. const user = myUserDB.getUser("jennyrosen"); if (!user.stripeCustomerID) { const customer = await stripe.customers.create({ name: user.name, email: user.email, }); // Set the user's Stripe Customer ID for later retrieval. user.stripeCustomerID = customer.id; }

Warning

Make sure to 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 your app doesn’t have an existing authentication provider, you can use Sign in with Apple.

Use the customer argument to pass their Customer ID when creating a Checkout Session. This ensures that all objects created during the session associate with the correct Customer object.

Note

If the customer changes their email on the Checkout page, the Customer object updates with the new email.

In payment mode, we use the customer’s most recent card payment method to prefill the email, name, card details, and billing address on the Checkout page. Checkout requires a valid billing address to prefill the customer’s card details.

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

Set up universal links
Client-side
Server-side

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

Warning

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

Command Line
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

  1. Open the Signing & Capabilities pane of your app’s target.
  2. Click + Capability, then select Associated Domains.
  3. Add an entry for applinks:yourdomain.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 and cancel URLs.

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. Configure it with:

  • The customer ID
  • The product ID (either a one time payment or subscription)
  • A success_url, a universal link to redirect your customer to your app after they complete the payment.
  • A cancel_url, a universal link to return your customer to your app if they click on your logo in Checkout.

Note

Checkout Sessions expire 24 hours after creation.

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

Node
// This example sets up an endpoint using the Express framework. // Watch this video to get started: https://youtu.be/rPR2aJ6XnAc. const express = require('express'); const app = express(); const stripe = require('stripe')(
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
) app.post('/create-checkout-session', async (req, res) => { // Fetch the Stripe customer ID for the customer associated with this request. // This assumes your app has an existing user database, which we'll call `myUserDB`. const user = myUserDB.getUserFromToken(req.query.token); const customerId = user.stripeCustomerID; // The price ID from the previous step const priceId = '{{PRICE_ID}}'; const session = await stripe.checkout.sessions.create({ line_items: [ { price: priceId, quantity: 1, }, ], mode: 'payment', customer: customerId, success_url: 'https://example.com/checkout_redirect/success', cancel_url: 'https://example.com/checkout_redirect/cancel', }); res.json({url: session.url}); }); app.post('/login', async (req, res) => { // This assumes your app has an existing user database, which we'll call `myUserDB`. const token = myUserDB.login(req.body.login_details) res.json({token: token}) }); app.listen(4242, () => console.log(`Listening on port ${4242}!`));

Note

Apple Pay is enabled by default and automatically appears in Checkout when a customer uses a supported device and has saved at least one card in the Wallet app. 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 STRIPE_WEBHOOK_SECRET key, click the webhook in the Dashboard to view it.

server.js
Node
// Set your secret key. Remember to switch to your live secret key in production. // See your keys here: 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

You should now have a working 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.

OptionalAdditional testing resources

OptionalIn-app purchases with Lemon Squeezy

See also

  • Add discounts
  • Collect taxes
  • Collect tax IDs
  • Customize your branding
  • Customize your success page
Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc