On this page
No Headings
Last updated: June 11, 2026
This guide shows how to:
You'll wire up three backend routes:
GET /paypal-api/auth/browser-safe-client-token: returns a browser-safe tokenPOST /paypal-api/checkout/orders/create-with-sample-data: returns a new orderIdPOST /paypal-api/checkout/orders/{orderId}/capture captures the orderThe code examples on this page use USD and US-based scenarios. PayPal supports multiple currencies and countries. See currency codes and country codes for the full list, and payment methods for country-specific options.
You'll need the following:
Include the core script and call the onPayPalWebSdkLoaded() handler when it's ready.
<script async src="https://sandbox.paypal.com/web-sdk/v6/core" onload="onPayPalWebSdkLoaded()"></script>Keep async to avoid blocking the render.
Provide containers for each card field and a pay button.
The provided containers must have a defined height and width. The card field fills the entire space of the parent container.
The button id in the sample is pay-button. Make sure your JavaScript uses the same selector.
<style>
.card-field {
height: 3rem;
margin-bottom: 1rem;
}
</style>
<div class="card-fields-container">
<div class="card-field" id="paypal-card-fields-number"></div>
<div class="card-field" id="paypal-card-fields-expiry"></div>
<div class="card-field" id="paypal-card-fields-cvv"></div>
</div>
<button id="pay-button" class="pay-button">Pay</button>Create an SDK instance with your browser‑safe client token, then create a one‑time payment session for credit and debit card fields and mount the components.
To pass billing fields, use the submit(orderId, { billingAddress: { ... } }) options object. Include at least the fields you require for your risk and SCA strategy, for example, postalCode.
async function onPayPalWebSdkLoaded() {
try {
const clientToken = await getBrowserSafeClientToken();
const sdk = await window.paypal.createInstance({
clientToken,
components: ["card-fields"],
});
const paymentMethods = await sdk.findEligibleMethods();
const isCardFieldsEligible = paymentMethods.isEligible("advanced_cards");
if (isCardFieldsEligible) {
await setupCardFields(sdk);
}
} catch (err) {
console.error("SDK init failed", err);
}
}
async function setupCardFields(sdk) {
const cardSession = sdk.createCardFieldsOneTimePaymentSession();
const numberField = cardSession.createCardFieldsComponent({
type: "number",
placeholder: "Card number",
});
const expiryField = cardSession.createCardFieldsComponent({
type: "expiry",
placeholder: "MM/YY",
});
const cvvField = cardSession.createCardFieldsComponent({
type: "cvv",
placeholder: "CVV",
});
document.querySelector("#paypal-card-fields-number").appendChild(numberField);
document.querySelector("#paypal-card-fields-expiry").appendChild(expiryField);
document.querySelector("#paypal-card-fields-cvv").appendChild(cvvField);
const payButton = document.querySelector("#pay-button");
payButton.addEventListener("click", () => onPayClick(cardSession));
}orderId.cardSession.submit(orderId, options) to handle validations and 3D Secure.state === "succeeded", capture the order on your server.async function onPayClick(cardSession) {
try {
const orderId = await createOrder(); // returns a string id
const {
data,
state
} = await cardSession.submit(orderId, {
billingAddress: {
postalCode: "95131"
}, // supply what your business needs
});
switch (state) {
case "succeeded": {
const {
orderId,
liabilityShift
} = data
// 3DS may or may not have occurred; Use liabilityShift
// to determine if the payment should be captured
const capture = await captureOrder(orderId);
// TODO: show success UI, redirect, etc.
break;
}
case "canceled": {
// Buyer dismissed 3DS modal or canceled the flow
// TODO: show non-blocking message & allow retry
break;
}
case "failed": {
// Validation or processing failure. data.message may be present
console.error("Card submission failed", data);
// TODO: surface error to buyer, allow retry
break;
}
default: {
// Future-proof for other states (e.g., pending)
console.warn("Unhandled submit state", state, data);
}
}
} catch (err) {
console.error("Payment flow error", err);
// TODO: Show generic error and allow retry
}
}Use these client-side helper functions to fetch a browser-safe client token and create an order before submitting the payment.
async function getBrowserSafeClientToken() {
const res = await fetch("/paypal-api/auth/browser-safe-client-token", {
method: "GET",
headers: {
"Content-Type": "application/json"
},
});
const {
accessToken
} = await res.json();
return accessToken; // pass as clientToken above
}
async function createOrder() {
const res = await fetch("/paypal-api/checkout/orders/create-with-sample-data", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
});
const {
id
} = await res.json();
return id; // return the string orderId
}
async function captureOrder(orderId) {
const res = await fetch(`/paypal-api/checkout/orders/${orderId}/capture`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
});
return res.json();
}The following is a minimal example to support three routes. It uses the PayPal Orders v2 API. Replace placeholders and secure secrets appropriately.
import express from "express";
import fetch from "node-fetch";
const app = express();
app.use(express.json());
const PAYPAL_BASE = process.env.PAYPAL_BASE || "https://api-m.sandbox.paypal.com"; // use api-m.paypal.com in prod
const CLIENT_ID = process.env.PAYPAL_CLIENT_ID;
const CLIENT_SECRET = process.env.PAYPAL_CLIENT_SECRET;
async function appAccessToken(formBody) {
const creds = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
const res = await fetch(`${PAYPAL_BASE}/v1/oauth2/token`, {
method: "POST",
headers: {
"Authorization": `Basic ${creds}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: formBody,
});
const json = await res.json();
return json.access_token;
}
// 1) Return browser-safe client token
app.get("/paypal-api/auth/browser-safe-client-token", async (_req, res) => {
try {
const sdkClientTokenFormBody = "grant_type=client_credentials&response_type=client_token&intent=sdk_init"
const accessToken = await appAccessToken(sdkClientTokenFormBody);
res.json({
accessToken
});
} catch (e) {
console.error(e);
res.status(500).json({
error: "client_token_error"
});
}
});
// 2) Create order with sample purchase units
app.post("/paypal-api/checkout/orders/create-with-sample-data", async (_req, res) => {
try {
const fullScopeAccessTokenFormBody = "grant_type=client_credentials&response_type=token"
const accessToken = await appAccessToken(fullScopeAccessTokenFormBody);
const r = await fetch(`${PAYPAL_BASE}/v2/checkout/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
intent: "CAPTURE",
purchase_units: [{
amount: {
currency_code: "USD",
value: "10.00"
},
}, ],
}),
});
const data = await r.json();
res.json({
id: data.id
});
} catch (e) {
console.error(e);
res.status(500).json({
error: "order_create_error"
});
}
});
// 3) Capture order
app.post("/paypal-api/checkout/orders/:orderId/capture", async (req, res) => {
try {
const fullScopeAccessTokenFormBody = "grant_type=client_credentials&response_type=token"
const accessToken = await appAccessToken(fullScopeAccessTokenFormBody);
const r = await fetch(`${PAYPAL_BASE}/v2/checkout/orders/${req.params.orderId}/capture`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
});
const data = await r.json();
res.json(data);
} catch (e) {
console.error(e);
res.status(500).json({
error: "order_capture_error"
});
}
});
app.listen(3000, () => console.log("Server listening on 3000"));Use the style option to control input styling such as font size and color. Keep contrast AA or better. Avoid setting styles that imply validation, such as red and green, unless synchronized with real validation states.
Allowed CSS selectors:
appearancebackgroundborderborderRadiusboxShadowcolordirectionfontfontFamilyfontSizefontSizeAdjustfontStretchfontStylefontVariantfontVariantAlternatesfontVariantCapsfontVariantEastAsianfontVariantLigaturesfontVariantNumericfontWeightheightletterSpacinglineHeightopacityoutlinepaddingpaddingBottompaddingLeftpaddingRightpaddingToptextShadowtransitionconst numberField = cardSession.createCardFieldsComponent({
type: "number",
style: {
input: {
fontSize: "16px",
lineHeight: "24px",
},
".invalid": {
color: "orange"
},
},
});There are 3 possible outcomes from checkout:
state === "succeeded": Proceed to capture on server and show success.state === "canceled": Buyer closed 3DS modal. Allow retry.state === "failed": Inspect data.message and field errors if provided. Keep logs server‑side with correlation IDs from API responses.Common troubleshooting issues:
#pay-button and not #save-payment-method-button.orderId to submit(). Do not pass an object such as { orderId }.break; in each case to avoid fall‑through.The following example shows a JavaScript and HTML page that renders card fields for one-time checkout. You can localize placeholder strings when creating each component.
When available, use sdk.findEligibleMethods({ currencyCode: "USD" }) and gate Card Fields rendering accordingly. The card may not appear in the eligibility response yet. Integrate defensively.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>One‑Time Payment — Card Fields</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
.card-fields-container { display: flex; flex-direction: column; gap: 12px; }
.pay-button { padding: 12px 16px; font-size: 16px; }
.is-loading { opacity: 0.6; pointer-events: none; }
</style>
</head>
<body>
<h1>Card Fields — One‑Time Payment</h1>
<div class="card-fields-container">
<div class="card-field" id="paypal-card-fields-number"></div>
<div class="card-field" id="paypal-card-fields-expiry"></div>
<div class="card-field" id="paypal-card-fields-cvv"></div>
</div>
<button id="pay-button" class="pay-button">Pay</button>
<script>
async function onPayPalWebSdkLoaded() {
try {
const clientToken = await getBrowserSafeClientToken();
const sdk = await window.paypal.createInstance({
clientToken,
components: ["card-fields"],
pageType: "checkout",
});
const paymentMethods = await sdk.findEligibleMethods();
const isCardFieldsEligible = paymentMethods.isEligible("advanced_cards");
if(isCardFieldsEligible){
await setupCardFields(sdk);
}
} catch (err) {
console.error(err);
}
}
async function setupCardFields(sdk) {
const session = sdk.createCardFieldsOneTimePaymentSession();
const numberField = session.createCardFieldsComponent({ type: "number", placeholder: "Card number" });
const expiryField = session.createCardFieldsComponent({ type: "expiry", placeholder: "MM/YY" });
const cvvField = session.createCardFieldsComponent({ type: "cvv", placeholder: "CVV" });
document.querySelector("#paypal-card-fields-number").appendChild(numberField);
document.querySelector("#paypal-card-fields-expiry").appendChild(expiryField);
document.querySelector("#paypal-card-fields-cvv").appendChild(cvvField);
const button = document.querySelector("#pay-button");
button.addEventListener("click", async () => {
try {
button.classList.add("is-loading");
const orderId = await createOrder();
const { data, state } = await session.submit(orderId, {
billingAddress: { postalCode: "95131" },
});
switch (state) {
case "succeeded": {
const capture = await captureOrder(data.orderId);
alert("Payment captured");
break;
}
case "canceled": {
alert("Authentication canceled. Please try again.");
break;
}
case "failed": {
alert(data?.message || "Payment failed. Check your details and try again.");
break;
}
default: {
console.warn("Unhandled state", state);
}
}
} catch (err) {
console.error(err);
alert("Unexpected error. Please try again.");
} finally {
button.classList.remove("is-loading");
}
});
}
async function getBrowserSafeClientToken() {
const r = await fetch("/paypal-api/auth/browser-safe-client-token");
const { accessToken } = await r.json();
return accessToken;
}
async function createOrder() {
const r = await fetch("/paypal-api/checkout/orders/create-with-sample-data", { method: "POST" });
const { id } = await r.json();
return id;
}
async function captureOrder(orderId) {
const r = await fetch(`/paypal-api/checkout/orders/${orderId}/capture`, { method: "POST" });
return r.json();
}
</script>
<script async src="https://www.sandbox.paypal.com/web-sdk/v6/core" onload="onPayPalWebSdkLoaded()"></script>
</body>
</html>