Deploy a mobile checkout using Vercel
Get a step-by-step overview of how to deploy a checkout to Vercel to add an external purchase flow for your iOS app, letting you go direct to customers while remaining compliant.
With recent developments in legislation around the App Store, you can link users in the United States to an external checkout for purchases in iOS apps.
You can use the Paddle in-app checkout starter kit to quickly create and deploy a Next.js app that includes Paddle Checkout, letting users securely make purchases outside your app. Customers tap a button in your app to open a checkout that you host on Vercel, then they're redirected to your app when they complete their purchase.
What are we building?
In this tutorial, we'll deploy the Paddle in-app checkout starter kit to Vercel to build an external purchase flow for in-app purchases in iOS apps.
We'll walk through handling fulfilment using the RevenueCat x Paddle integration or webhooks.
What's not covered
This tutorial doesn't cover:
Handling authentication
We assume you already have a way to identify your users, like Firebase or Sign in with Apple.
Native in-app purchases
We'll launch Paddle Checkout in Safari then redirect users back to your app. Like the App Store, Paddle Checkout supports Apple Pay with no additional setup, plus other popular payment options.
Subscription lifecycle management
You can use Paddle to handle all parts of the subscription lifecycle, including updating payment methods and canceling subscriptions using the prebuilt customer portal. We cover that elsewhere in our docs.
Before you begin
Sign up for Paddle
You'll need a Paddle account to get started. You can sign up for two kinds of account:
- Sandbox — for testing and evaluation
- Live — for selling to customers
For this tutorial, we recommend signing up for a sandbox account. You can transition to a live account later when you've built your integration and you're ready to start selling.
If you sign up for a live account, you'll need to complete account verification. This is where we ask for some information from you to make sure that we can work together. While we're verifying your account, you can't launch a checkout or sell on the Paddle platform.
Sign up for Vercel and a Git provider
As part of our tutorial, we're going to deploy our app to Vercel. Vercel is a developer platform that you can use to host and deploy web apps using serverless technology, designed for Next.js. We'll deploy our app to Vercel.
If you don't have a Vercel account, you'll need to sign up — it's free to get started.
You'll also need a Git provider to store the code that powers your app. When deploying to Vercel, the deployment screen walks you through setting up an account with GitHub (recommended), GitLab, or Bitbucket, if you don't already have one.
Prep your iOS development environment
As part of our tutorial, we're going to update our app to include a link to a hosted checkout for purchases. You'll need:
- Some knowledge of iOS development, access to your iOS project, and Xcode on macOS.
- A correctly configured URL scheme so you can redirect users back to your app.
You don't need to make changes to your iOS app to create a hosted checkout in Paddle, so you can come back to this later if you're working with a developer.
Overview
Deploy a checkout and add it to your app to link out for in-app purchases in six steps:
Create products and prices in Paddle that match your in-app purchase options.
Clone the Git repo, configure environment variables, and deploy.
Add your website to Paddle, then test your checkout to see how it works.
Add a checkout button to your app
Create a button that opens the checkout you deployed to Vercel when tapped.
Handle fulfillment and provisioning
Use RevenueCat or process webhooks to fulfill purchases after a customer completes a checkout.
Make a test purchase to make sure your purchase flow works correctly.
1. Map your product catalog
Before we add a hosted checkout to our app, we need to set up our product catalog in Paddle to match the in-app purchases we offer.
Model your pricing
A complete product in Paddle is made up of two parts:
- A product entity that describes the item, like its name, description, and an image.
- At least one related price entity that describes how much and how often a product is billed.
You can create as many prices for a product as you want to describe all the ways they're billed.
In this example, we'll create a single product and single price for a one-time item called Lifetime Access
.
Create products and prices
You can create products and prices using the Paddle dashboard or the API.
Go to Paddle > Catalog > Products.
Click New product.
Enter details for your new product, then click Save when you're done.
Under the Prices section on the page for your product, click New price.
Enter details for your new price. Set the type to One-time to create a one-time price.
Click Save when you're done.
2. Start deploy to Vercel
To create a Vercel project ready for us to set up, click the button to get started:
Create Git repo
First, create a clone of our starter kit repo. This creates a copy of the code in a repo in your Git provider account, so you can build your app on top of our project.
Click the Continue with GitHub, Continue with GitLab, or Continue with Bitbucket buttons to connect your Git provider to Vercel, if you haven't already. Then, enter a name for your repo.
The repo name becomes the name of your project in Vercel, and it's also used for deploy preview URLs. If the name is taken, Vercel appends some characters to your project name when creating a deploy preview URL.
Configure environment variables
We need to supply some variables so that our checkout can work properly.
We need six variables:
APPLE_TEAM_ID | Your Apple Developer team ID, required for Universal Links so you can link back to your app. |
NEXT_PUBLIC_BUNDLE_IDENTIFIER | Your iOS app's bundle ID, for example com.example.myapp . |
NEXT_PUBLIC_APP_REDIRECT_URL | Custom URL scheme that bounces users back to your app when their purchase is completed, for example myapp://example-redirect . |
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN | A client-side key, used for securely opening Paddle Checkout. |
NEXT_PUBLIC_PADDLE_ENV | Environment for our Paddle account. sandbox for sandbox accounts; production for live accounts. |
NEXT_PUBLIC_PADDLE_PRICE_ID | Default item to open a checkout for. Used when a price ID is passed to the checkout link. |
Get Apple team ID and bundle ID
You can find your Apple Developer team ID and your bundle ID in your Apple Developer account.
- To get your team ID, sign in to your Apple Developer account, then go to membership details.
- To get your bundle identifier, open your app in Xcode, then go to the target summary pane.
Copy and paste the values as APPLE_TEAM_ID
and NEXT_PUBLIC_BUNDLE_IDENTIFIER
.
Get a client-side token
Client-side tokens are for authenticating with Paddle in your frontend. We need one to securely open Paddle Checkout.
Go Paddle > Developer tools > Authentication
Click the Client-side tokens tab, then click New client-side token.
Give your client-side token a name and description, then click Save.
From the list of client-side tokens, click the … action menu next to the client-side token you just created, then choose Copy token from the menu.
Paste your token as
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN
in the Deploy to Vercel screen.
Set your environment
For NEXT_PUBLIC_PADDLE_ENV
, enter:
sandbox
if you're working with a sandbox accountproduction
if you're working with a live account
We recommend working with a sandbox account for this tutorial. Sandbox accounts are designed for testing and evaluation. Live accounts must be approved by Paddle before you can open checkouts for them.
Get your price ID
You can get the Paddle ID for your price using the Paddle dashboard:
Go to Paddle > Catalog > Products, then click the product you want to get a price ID for in the list.
Click the … action menu next to a price in the list, then choose Copy price ID from the menu.
Paste the ID as the value for
NEXT_PUBLIC_PADDLE_PRICE_ID
.
Review and deploy
At this point, we've done everything that we need to deploy. Review your settings, then click Deploy.
Wait for Vercel to build. If everything went well, our build should complete successfully.
However, if we open our deploy link then our integration isn't ready yet — there's an error message saying something went wrong. We'll fix this in the next step.
3. Add your website to Paddle and test
To keep the Paddle platform safe for everyone, you must add your Vercel deploy link to Paddle before you can launch a checkout. This protects you as a seller, making sure that only you are able to sell your products.
Get your website approved
If you're using a sandbox account, website approval is instant. You'll need to add your domain, but our verification team don't check your website.
Website approval makes sure you own the domains where you use Paddle Checkout, and that the products sold meet the Paddle acceptable use policy.
Get your website approved using the Paddle dashboard:
Go to Paddle > Checkout > Website approval.
Click Add a new domain, enter your Vercel deploy link, then click Submit for Approval.
Wait for approval.
If you're using a sandbox account, your website is automatically approved right away. You should see a green status symbol that says "Approved."
For live accounts, website approval may take a few days while the Paddle verification team check your website to make sure you're able to sell with Paddle. For more information, see: Website verification FAQs
Set your default payment link
Your default payment link is a quick way to open Paddle Checkout for a transaction. It's also used in emails from Paddle that let customers manage purchases that are recurring. You need to set a default payment link before you can launch a checkout.
We'll set our default payment link to the checkout page we just deployed.
Go to Paddle > Checkout > Checkout settings.
Enter your Vercel deploy link under the Default payment link heading.
Click Save when you're done.
4. Add a checkout button to your app
Now, update your iOS app to add a button that:
Checks to see if in-app purchases are allowed on the device.
Checks to see if a user already purchased the item.
Constructs a URL using your Vercel deploy URL, and a
price_id
query parameter with the price ID you copied previously as the value.
Here's an example using SwiftUI:
12345678910111213141516171819201import SwiftUI
2import StoreKit // required for checking device payment capabilities using SKPaymentQueue
3
4struct PurchaseView: View {
5 let checkoutBaseURL = "https://paddle-in-app-checkout-starter.vercel.app/?price_id=pri_01h1vjg3sqjj1y9tvazkdqe5vt" // replace with your checkout launch URL
6 let priceId = "pri_01h1vjg3sqjj1y9tvazkdqe5vt" // replace with a price ID
7
8 var body: some View {
9 VStack {
10 // Check if the device can make payments
11 if SKPaymentQueue.canMakePayments() {
12 // Create a purchase button with styling
13 Button("Buy now") {
14 openCheckout()
15 }
16 .padding()
17 .background(Color.blue)
18 .foregroundColor(.white)
19 .cornerRadius(10)
20 } else {
Prefill information — recommended
To make for a more seamless user experience, you can use URL parameters to pass additional information to the checkout.
Unique identifier for this customer in RevenueCat. Used for fulfilment using entitlements in RevenueCat.
Email for this customer. You can't use if you're passing paddle_customer_id
.
Two-letter ISO 3166 country code for this customer.
ZIP or postal code of this address. Paddle Checkout only asks for this in countries with postal codes.
For a full list, see Hosted checkout URL query parameters
In this updated example, we pass customer email address and a unique identifier for the customer in RevenueCat.
12345678910111213141516171819201import SwiftUI
2import StoreKit // required for checking device payment capabilities using SKPaymentQueue
3
4struct PurchaseView: View {
5 let checkoutBaseURL = "https://paddle-in-app-checkout-starter.vercel.app/?price_id=pri_01h1vjg3sqjj1y9tvazkdqe5vt" // replace with your checkout launch URL
6 let priceId = "pri_01h1vjg3sqjj1y9tvazkdqe5vt" // replace with a price ID
7
8 // Additional information
9 // In a real app, this would come from your user authentication platform
10 let appUserId = "85886aac-eef6-41df-8133-743cbb1daa4b"
11 let userEmail = "sam@example.com"
12 let countryCode = "US"
13 let postalCode = "10021"
14
15 var body: some View {
16 VStack {
17 // Check if the device can make payments
18 if SKPaymentQueue.canMakePayments() {
19 // Create a purchase button with styling
20 Button("Buy now") {
5. Handle fulfillment and provisioning
When a customer completes a purchase, they'll be redirected back to your app. At this point, you need to handle fulfilment and unlock the features they bought.
If you use the RevenueCat x Paddle integration to handle entitlements, you're all set!
Here's how it works:
Paddle automatically sends data to RevenueCat about the completed checkout.
RevenueCat grants the user an entitlement based on your product configuration.
Use the RevenueCat SDK to check entitlement status in your iOS app.
You can use webhooks to build your own fulfilment workflow. In this example, we'll grant users access when they've purchased our Lifetime Access
product.
Build a webhook handler
When a customer creates or completes a transaction, Paddle can send a webhook to an endpoint you set up. You can store details of the transaction in your database and associate it with the user's account.
Add a new endpoint to the existing server-side code as set up in Set up the endpoint.
12345678910111213141516171819201app.post("/paddle/webhooks", express.raw({ type: 'application/json' }), async (req, res) => {
2 try {
3 // You can verify the webhook signature here
4 // We don't cover this in the tutorial but it's best practice to do so
5 // https://developer.paddle.com/webhooks/signature-verification
6
7 const payload = JSON.parse(req.body.toString());
8 const { data, event_type } = payload;
9 const occurredAt = payload.occurred_at;
10
11 // Listen for vital events from Paddle
12 switch (event_type) {
13 // 1. Record transactions in the database
14
15 // Handle a new transaction
16 // You can create a Transaction database to store records and associate them to a user
17 case 'transaction.created':
18 // Find the user associated with this transaction
19 const userForTransaction = await User.findOne({ where: { paddleCustomerId: data.customer_id } });
20
Unlock user access
When you receive the transaction.completed
webhook, you can use the details to handle order fulfilment and provisioning.
The example below updates a user's access permissions in your database. After this, your iOS app can check for the lifetimeAccess
permission to unlock premium features.
444546474849505152535455565758596061626344 await completedTransaction.update({
45 status: data.status,
46 subscriptionId: data.subscription_id,
47 invoiceId: data.invoice_id,
48 invoiceNumber: data.invoice_number,
49 billedAt: data.billed_at,
50 updatedAt: data.updated_at
51 });
52
53 // 2. Provision access to your app
54 // Fetch the user associated with this transaction
55 const user = await User.findOne({ where: { id: completedTransaction.userId } });
56
57 if (user) {
58 // Fetch the items from the transaction
59 const purchasedItems = data.items || [];
60
61 // Add what access the user has based on the items they purchased
62 // For this example, we're using access permissions and storing them in the user model on an accessPermissions field
63 // We also map the Paddle product IDs to the access permissions
Create a notification destination
To start receiving webhooks, create a notification destination. This is where you can tell Paddle which events you want to receive and where to deliver them to.
Go to Paddle > Developer Tools > Notifications.
Click New destination.
Give your destination a name.
Make sure notification type is set to webhook — this is the default.
Enter the URL for your webhook handler, then check the transaction.completed box. You can always edit events later.
Click Save destination when you're done.
6. Test the complete flow
We're now ready to test the complete purchase flow end-to-end! If you're using a sandbox account, you can take a test payment using our test card details:
Email address | An email address you own |
Country | Any valid country supported by Paddle |
ZIP code (if required) | Any valid ZIP or postal code |
Card number | 4242 4242 4242 4242 |
Name on card | Any name |
Expiration date | Any valid date in the future. |
Security code | 100 |
Before you go live, extend your Apple Pay integration by verifying your domain for Apple Pay. This lets you launch the Apple Pay modal directly from your checkout.
Next steps
That's it! Now you've built a purchase workflow that links out to Paddle Checkout, you might like to hook into other features of the Paddle platform.
Learn more about Paddle
When you use Paddle, we take care of payments, tax, subscriptions, and metrics with one unified platform. Customers can self-serve with the portal, and Paddle handles any order inquiries for you.
Build a web checkout
Our tutorial launches a simple checkout using Paddle.js. You can also Paddle.js to build pricing pages and signup flows on the web, then redirect people to your app.
Build advanced subscription functionality
Paddle Billing is designed for subscriptions as well as one-time items. You can use Paddle to build workflows to pause and resume subscriptions, flexibly change billing dates, and offer trials.
- Deploy a mobile checkout using Vercel
- What are we building?
- What's not covered
- Before you begin
- Sign up for Paddle
- Sign up for Vercel and a Git provider
- Prep your iOS development environment
- Overview
- 1. Map your product catalog
- Model your pricing
- Create products and prices
- 2. Start deploy to Vercel
- Create Git repo
- Configure environment variables
- Review and deploy
- 3. Add your website to Paddle and test
- Get your website approved
- Set your default payment link
- 4. Add a checkout button to your app
- Prefill information — recommended
- 5. Handle fulfillment and provisioning
- 6. Test the complete flow
- Next steps
- Learn more about Paddle
- Build a web checkout
- Build advanced subscription functionality