Build an inline checkout
Get a step-by-step overview of how to build a complete inline checkout — including initializing Paddle.js, passing settings and items, updating on-page information, and next steps.
The checkout is where customers make purchases. For SaaS businesses, it's the process where customers enter their details and payment information, and confirm that they'd like to sign up for a subscription with you.
You can use Paddle.js to integrate an inline checkout into your app. Inline checkout lets you embed a checkout and display information about items and totals in your own interface, creating checkout experiences that are fully integrated with your app.
Grab the code and test using CodePen
CodePen is a platform for building and sharing frontend code. Explore the code for this tutorial and test right away using our overlay checkout pen.
What are we building?
In this tutorial, we'll build a page that embeds a multi-page inline checkout for three items in our product catalog. We'll display information about items and totals in a table on the page, add a way for customers to switch to annual plan during checkout, then we'll extend it by passing customer information.
We'll learn how to:
- Include and set up Paddle.js using a client-side token
- Pass settings to Paddle.js to embed an inline checkout in our page
- Pass items to inline checkout using
Paddle.Checkout.open()
- Display and update information about items and totals using checkout events
- Take a test payment
- Update items for an opened checkout using
Paddle.Checkout.updateCheckout()
If you like, you can copy-paste the sample code in your editor or view on CodePen and follow along.
12345678910111213141516171819201<!DOCTYPE html>
2<html lang="en" color-mode="user">
3<head>
4 <title>Inline checkout demo</title>
5 <meta charset="utf-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
8 <style>
9 .page-container {
10 max-width: 1000px;
11 margin: 0 auto 2em auto;
12 padding-left: 1em;
13 padding-right: 1em;
14 text-align: center;
15 }
16 .grid {
17 display: block;
18 }
19 .grid > * {
20 padding: 1rem;
Before you begin
Choose a type of checkout
This tutorial walks through creating an inline checkout. You can also create overlay checkouts, which let you launch a checkout in just a few lines of code.
We recommend building an overlay checkout if you're new to Paddle. Inline checkouts use the same JavaScript methods as overlay checkouts, so you can switch to an inline checkout later.
Overlay checkout
Integrate Paddle in just a few lines of code. Launches an overlay to capture payment.
Inline checkout
Get complete control of the checkout experience. Captures payment directly in your app.
Learn more about the differences between overlay and inline checkouts: Paddle Checkout
Create products and prices
Paddle Checkout works with products and prices to say what you're billing for, so you'll need to create a product and at least one related price to pass to your checkout.
Set your default payment link
You'll also need to:
- Set your default payment link under Paddle > Checkout > Checkout settings > Default payment link.
- Get your default payment link domain approved, if you're working with the live environment.
We recommend starting the domain approval early in your integration process, so your domains are approved for when you're ready to go-live.
Get started
Add an inline checkout to your website or app in five steps:
Include and initialize Paddle.js
Add Paddle.js to your app or website, so you can securely capture payment information and build subscription billing experiences.
Embed and pass checkout settings and items
Pass settings to determine how your checkout opens and how it works, then pass items to say what your checkout is for.
Show and update on-page information
Inline checkout handles capturing payment securely. Display an items list and totals on your page, and update using event callbacks.
Make sure that your checkout loads successfully, then take a test payment.
Update your checkout — optional
Dynamically update items and other information for your opened checkout.
1. Include and initialize Paddle.js
Paddle.js is a lightweight JavaScript library that lets you build rich, integrated subscription billing experiences using Paddle. We can use Paddle.js to securely work with products and prices in our Paddle system, as well as opening checkouts and capturing payment information.
Include Paddle.js script
Start with a blank webpage, or an existing page on your website. Then, include Paddle.js by adding this script to the <head>
:
<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
Set environment (optional)
We recommend signing up for a sandbox account to test and build your integration, then switching to a live account later when you're ready to go live.
If you're testing with the sandbox, call Paddle.Environment.set()
and set your environment to sandbox
:
12341<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
2<script type="text/javascript">
3 Paddle.Environment.set("sandbox");
4</script>
Pass a client-side token
Next, go to Paddle > Developer tools > Authentication and generate a client-side token. Client-side tokens let you interact with the Paddle platform in frontend code, like webpages or mobile apps. They have limited access to the data in your system, so they're safe to publish.
In your page, call Paddle.Initialize()
and pass your client-side token as token
. For best performance, do this just after calling Paddle.Environment.set()
, like this:
12345671<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
2<script type="text/javascript">
3 Paddle.Environment.set("sandbox");
4 Paddle.Initialize({
5 token: "test_7d279f61a3499fed520f7cd8c08" // replace with a client-side token
6 });
7</script>
Client-side tokens are separate for your sandbox and live accounts. You'll need to generate a new client-side token for your live account. Sandbox tokens start with
test_
to make them easy to distinguish.
2. Embed and pass checkout settings
Next, we'll set an element on our page as a container for Paddle Checkout and set up Paddle.js for inline checkout.
Inline checkout works by embedding a frame that contains Paddle Checkout into your website or app. The Paddle Checkout frame handles securely capturing payment information, letting you display information about items and totals elsewhere on the page.
Create checkout container
Create an empty <div>
for the Paddle Checkout frame and give it a unique class
, for example checkout-container
:
11<div class="checkout-container"></div>
Pass settings
Now, we'll pass the class of this empty <div>
to tell Paddle.js where to embed the checkout frame. We'll also pass checkout settings to tell Paddle.js to load an inline checkout and say how our inline checkout should work.
There are two ways we can do this:
Paddle.Initialize() method
- Pass settings to
Paddle.Initialize()
when initializing Paddle.js. - Settings apply to all checkouts opened on this page.
- Recommended in most cases.
Paddle.Checkout.open() method
- Pass settings to
Paddle.Checkout.open()
when opening a checkout. - Settings only apply to the opened checkout.
- Recommended where you have multiple checkouts on a page with different options.
If all the checkouts you use have the same settings, we recommend using the Paddle.Initialize()
method. This means you don't need to pass the same settings for every checkout that you want to open.
Update your Paddle.Initialize()
method call so that it includes checkout.settings
. These settings are applied to all checkouts opened on this page.
In our sample, we pass these settings to checkout.settings
:
displayMode | Determines whether Paddle.js should open an inline or overlay checkout. | We set to inline . |
frameTarget | Sets the element where Paddle Checkout should be loaded. | We passed our checkout-container class name. |
frameInitialHeight | Sets the initial height of the dev element where Paddle Checkout is loaded. | We set this to 450 , which is our recommendation. |
frameStyle | CSS properties to apply to the checkout container. | We passed some simple CSS styles here. |
12345678910111213141<script type="text/javascript">
2 Paddle.Environment.set("sandbox");
3 Paddle.Initialize({
4 token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
5 checkout: {
6 settings: {
7 displayMode: "inline",
8 frameTarget: "checkout-container",
9 frameInitialHeight: "450",
10 frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
11 }
12 }
13 });
14</script>
We covered the required settings for an inline checkout, but you may also pass
locale
,theme
, and other settings that control how Paddle Checkout works. See: Pass checkout settings
Pass items
Checkouts must be for one or more items. If we were to try to open a checkout so far, Paddle.js would throw an error.
To pass items, we can use the Paddle.Checkout.open()
method.
In our sample, we've created a function called openCheckout()
to open a checkout. Here's how it works:
We create a variable called
monthItemsList
and pass an array of objects, where each object contains apriceId
andquantity
. In our case, there are two prices that recur monthly and a single one-time price.We create a function called
openCheckout()
that takes a parameter calleditems
.In our
openCheckout()
function, we callPaddle.Checkout.open()
, passing the value ofitems
as the items list for the checkout.
If you already used the Paddle.Checkout.open()
method in the previous step to pass settings
, work this into your existing openCheckout()
function.
Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually.
12345678910111213141516171819201<script type="text/javascript">
2 Paddle.Environment.set("sandbox");
3 Paddle.Initialize({
4 token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
5 checkout: {
6 settings: {
7 displayMode: "inline",
8 frameTarget: "checkout-container",
9 frameInitialHeight: "450",
10 frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;"
11 }
12 }
13 });
14
15 // define items
16 let monthItemsList = [
17 {
18 priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke',
19 quantity: 10
20 },
Set openCheckout() to run on page load
Right now, we've written a function to open a checkout, but we haven't set it to run.
We can add onLoad
to our <body>
tag to run our openCheckout()
function immediately after the page has loaded, passing in our monthItemsList
variable as a parameter:
11<body onLoad="openCheckout(monthItemsList)">
Test your work
Save your page, then open it in your browser. Paddle Checkout should load in place of the checkout container <div>
element we created earlier.
You'll notice that the Paddle Checkout frame doesn't include any information about what the checkout is for. That's our next step.
3. Show and update on-page information
The inline checkout frame doesn't include a breakdown of items or totals. It's designed to handle capturing customer and payment information, giving you the flexibility to show items and totals in your frontend in a way that fits with our design.
To do this, we can use an event callback function. Paddle.js emits events throughout the checkout process when key things happen. An event callback function is some code that we run when a specific event occurs.
For example, when checkout is first loaded, Paddle.js emits a checkout.loaded
event that contains information about the items and totals on a checkout. We can build an event callback function to update our items and totals table with data contained in the event.
It's important that customers know who they're buying from, what they're buying, and how much they're paying.
To build an inline checkout that's compliant and optimized for conversion, your implementation must include:
If recurring, how often it recurs and the total to pay on renewal. If a trial, how long the trial lasts.
A description of what's being purchased.
Transaction totals, including subtotal, total tax, and grand total. Be sure to include the currency too.
The full inline checkout frame, including the checkout footer that has information about Paddle, our terms of sale, and our privacy policy.
A link to your refund policy, if it differs from the Paddle.com standard refund policy.
Add tables to hold items and totals
First, we need to add some HTML for a couple of tables to hold items and totals information. You might use something more visual when building an app or website, but tables work for our tutorial.
Add this to the <body>
of your page.
In this sample, we have two tables for our items and our totals:
- Our items table has a header row and a body row that's got some zero values in. We'll add a row for each item on our checkout later.
- Our totals table has a header column for the totals we'd like to show to our customer. There are
id
s set on the<td>
elements that should contain totals. We'll use these IDs to replace the contents of these elements with totals later.
12345678910111213141516171819201<div class="page-container">
2 <div class="grid">
3 <div class="checkout-container">
4 </div>
5 <div>
6 <h3>Items</h3>
7 <table class="items-table">
8 <thead>
9 <tr>
10 <th>Product name</th>
11 <th>Price name</th>
12 <th>Quantity</th>
13 <th>Total</th>
14 </tr>
15 </thead>
16 <tbody>
17 <tr>
18 <td></td>
19 <td></td>
20 <td>0</td>
Update items table
Next, we'll build an event callback function to take data about our items from events and display it in our items table.
In our sample, we created a function called updateTable()
that takes a parameter called event
. Then, we pass the event payload to our function as event
. Here's how it works:
First, we exclude events that don't return a
name
field and print the data payload of events to the console. This is useful for us to see which events are emitted and how they look while we're testing.We create a variable called
items
and set this toevent.data.items
in our event payload. We'll use this variable to populate our items table.We call another function as part of this event callback function:
updateItemsTable()
, where we passitems
as a parameter.We create the
updateItemsTable()
function that we called in our event callback, setting it up to accept a parameter calleditems
. It finds and selects our items table body (.items-table tbody
), clears out any rows, then iterates through each item in theitems
array we passed.When iterating through each item, we call another function called
createTableRow()
. We define this underneath, and it acceptsproductName
,priceName
,quantity
, andtotal
— a parameter for each of the columns in our items table.createTableRow()
returns an HTML table row element with our product name, price name, quantity, and total. This newly created row is appended to our table body in ourupdateItemsTable()
function.We update our
Paddle.Initialize()
method, passingupdateTable
as theeventCallback
. This means this function is run every time an event is emitted by Paddle.js.
12345678910111213141516171819201<script type="text/javascript">
2 function updateTable(event) {
3 if (!event.name) {
4 return;
5 }
6
7 console.log(event);
8
9 let items = event.data.items;
10
11 updateItemsTable(items);
12 }
13
14 function updateItemsTable(items) {
15 const itemsTableBody = document.querySelector('.items-table tbody');
16 itemsTableBody.innerHTML = '';
17
18 items.forEach(item => {
19 const newRow = createTableRow(item.product.name, item.price_name, item.quantity, item.totals.subtotal);
20 itemsTableBody.appendChild(newRow);
Update totals table
Let's update our event callback function so that it displays totals from our events in our totals table.
In our sample, we updated our updateTable()
function so that it calls another function, updateSummaryTable()
, to update our totals table. Here's how it works:
We create some additional variables called
totals
andrecurringTotals
, setting these to values in our event payload.We add a call to another function as part of this event callback function:
updateSummaryTable()
, where we passtotals
andrecurringTotals
as parameters.We create the
updateSummaryTable()
function that we called in our event callback, setting it up to accept parameters calledtotals
andrecurringTotals
.updateSummaryTable()
gets cells in our totals table using the IDs that we gave them earlier, then replaces the contents with values from thetotals
andrecurringTotals
arrays that we passed in as parameters. We calculate the one-time total by subtracting the subtotal of recurring items from the overall subtotal.
For simplicity, we use the built-in
.toFixed()
JavaScript method to format values to two decimal places in our sample. Paddle supports 30 currencies, some of which use a different number of decimal places. Consider using a currency library like currency.js to format currencies correctly.
293031323334353637383940414243444546474829 newRow.innerHTML = `
30 <td>${productName}</td>
31 <td>${priceName}</td>
32 <td>${quantity}</td>
33 <td>${total.toFixed(2)}</td>
34 `;
35 return newRow;
36 }
37
38 function updateSummaryTable(totals, recurringTotals) {
39 document.getElementById('oneTimeTotal').textContent = (totals.subtotal - recurringTotals.subtotal).toFixed(2);
40 document.getElementById('recurringTotal').textContent = recurringTotals.subtotal.toFixed(2);
41 document.getElementById('discountTotal').textContent = totals.discount.toFixed(2);
42 document.getElementById('taxTotal').textContent = totals.tax.toFixed(2);
43 document.getElementById('totalToday').textContent = totals.total.toFixed(2);
44 }
45
46 Paddle.Environment.set("sandbox");
47 Paddle.Initialize({
48 token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token
4. Take a test payment
We're now ready to test! Save your page, then open it in your browser. Paddle.js should open an inline checkout for the items that we passed. You should see items and totals in the tables we created.
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 |
If the checkout doesn't appear, or you get a message saying "Something went wrong," you can open your browser console to see any specific error messages from Paddle.js to help you troubleshoot.
Use
⌘ Command
+⌥ Option
+J
(Mac) orCtrl
+⇧ Shift
+J
(Windows) to quickly open your browser console in Google Chrome.
Common problems
Check that:
- You added a default payment link to your checkout under Paddle > Checkout > Checkout settings > Default payment link, and that this matches the domain where you're testing. You can use
localhost
if you're testing locally on sandbox. - You included Paddle.js correctly. If you're moving from Paddle Classic, the CDN URL has changed.
- Your client-side token is correct and passed to
Paddle.Initialize()
. - You set the correct environment.
- The Paddle IDs for price entities that you passed are correct. Sandbox and live systems are separate, so make sure you're passing price IDs for the environment that you're working in.
- There are no problems in your event callback function.
5. Update items on the checkout (optional)
What if we want to update our checkout now it's opened? For example, we might want to let customers adjust the quantity of items or upsell them addons. Paddle.js includes the Paddle.Checkout.updateCheckout()
method to let us dynamically update items, customer information, and discount on a checkout.
For this tutorial, we'll add a button that we can click to switch to annual plan. When customers click this button, we'll swap monthly items on the checkout for annual plan items.
Define list of prices
Paddle.Checkout.updateCheckout()
has an items
parameter. When we pass an items list, Paddle.js replaces the items on the checkout with the new items list we passed.
First, we'll define a new variable called yearItemsList
. Like monthItemsList
, we'll pass an array of objects, where each object contains a priceId
and quantity
. In this case, there are two prices that recur yearly and a single one-time price.
Keep in mind that the entire items list is replaced — any omitted items are removed from the checkout entirely.
Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually.
666768697071727374757677787980818283848566 {
67 priceId: 'pri_01gsz95g2zrkagg294kpstx54r',
68 quantity: 1
69 },
70 {
71 priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
72 quantity: 1
73 }
74 ];
75 let yearItemsList = [
76 {
77 priceId: 'pri_01gsz8z1q1n00f12qt82y31smh',
78 quantity: 10
79 },
80 {
81 priceId: 'pri_01gsz96z29d88jrmsf2ztbfgjg',
82 quantity: 1
83 },
84 {
85 priceId: 'pri_01gsz98e27ak2tyhexptwc58yk',
Update items
Next, we'll build a function to replace items on our checkout
In our sample, we created a function called switchPlan()
. Here's how it works:
We create a variable called
isMonthly
and set it totrue
. This variable tracks whether the current plan is monthly or yearly, and it's set totrue
initially because we passmonthItemsList
to our checkout when the page loads.We create a function called
switchPlan()
, then set a variable calledupdatedItems
to ouryearItemList
array ifisMonthly
istrue
, andmonthItemList
ifisMonthly
isfalse
.We call the
Paddle.Checkout.updateCheckout()
method, passingupdatedItems
as theitems
parameter. This is our yearly items list when the plan is monthly, and our monthly items list when the plan is yearly.We toggle the value of
isMonthly
. If it weretrue
, it's set tofalse
; if it werefalse
, it's set totrue
. This means that next timeswitchPlan()
is called, it'll switch plans correctly.
88899091929394959697989910010110210310410510610788 ];
89
90 // open checkout
91 function openCheckout(items){
92 Paddle.Checkout.open({
93 items: items
94 });
95 }
96
97 // switch plan
98 let isMonthly = true;
99
100 function switchPlan() {
101 let updatedItems = isMonthly ? yearItemsList : monthItemsList;
102 Paddle.Checkout.updateCheckout({
103 items: updatedItems
104 });
105 isMonthly = !isMonthly;
106 }
107</script>
Add a button to swap plan
Finally, add a button to our HTML to call our switchPlan()
function.
11<a href="#" onclick="switchPlan()"><b>Switch plan</b></a>
Test your work
Save your page, then open it in your browser. Paddle Checkout should load as before. When you click the switch plan button, Paddle.js swaps items on your checkout from monthly to annual, and vice versa.
Next steps
That's it! Now you've built a checkout, you might like to extend Paddle Checkout by presenting other fields to your checkout, automatically applying a discount, passing optional checkout settings, or building a success workflow.
Add other fields to your checkout
Events emitted by Paddle.js contain information about the items and totals on a checkout. We present fields in data.items[]
, data.product
, data.totals
, and data.recurring_totals
in our sample.
You might like to include other data from the event on your page.
Here are some fields in Paddle.js events that you might like to use on your page:
data.items[].product.name | Product name for itemized breakdowns. |
data.items[].price_name | Price name. Typically describes how often this item bills. |
data.items[].trial_period | Details about the trial period for an item, if any. |
data.items[].billing_cycle | How often an item is billed, if recurring. |
data.items[].totals.subtotal | Total for an item, excluding tax and discount. |
data.totals.total | Grand total for a transaction, including tax and discount. |
data.recurring_totals.total | Recurring total for a transaction, including tax and discount. |
(data.totals.total - data.recurring_totals.total ) | Where transactions contain a mix of one-time charges and recurring items, subtract a value in data.recurring_totals from the corresponding value in data.totals to calculate one-time charge totals. |
For compliance and the best customer experience, inline checkout implementations must include a description of what's being purchased, transaction totals, billing frequency (if recurring), and information about any trial periods (if applicable).
For a full list of fields, see: Paddle.js events
Automatically apply a discount
Extend your checkout by passing a discount. When our checkout is launched, Paddle automatically applies the discount (where it's valid).
Pass checkout settings
We covered passing the required settings for inline checkout, but there are a bunch of other settings you can pass that give you more control over how opened checkouts work. For example, you can set the language that Paddle Checkout uses, hide the option to add a discount, or restrict payment methods shown to customers.
Build a success workflow
When customers complete checkout, Paddle Checkout has a final screen that lets customers know that their purchase was successful. If you like, you can redirect customers to your own page or use JavaScript event callbacks to build a more advanced success workflow.
- Build an inline checkout
- What are we building?
- Before you begin
- Choose a type of checkout
- Create products and prices
- Set your default payment link
- Get started
- 1. Include and initialize Paddle.js
- Include Paddle.js script
- Set environment (optional)
- Pass a client-side token
- 2. Embed and pass checkout settings
- Create checkout container
- Pass settings
- Pass items
- Set openCheckout() to run on page load
- Test your work
- 3. Show and update on-page information
- Add tables to hold items and totals
- Update items table
- Update totals table
- 4. Take a test payment
- 5. Update items on the checkout (optional)
- Define list of prices
- Update items
- Add a button to swap plan
- Test your work
- Next steps
- Add other fields to your checkout
- Automatically apply a discount
- Pass checkout settings
- Build a success workflow