On this page
No Headings
Last updated: June 12, 2026
With PayPal's JavaScript SDK v6, you can support Strong Customer Authentication (SCA) and 3D Secure (3DS) in two payment flows:
The 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.
On your server, create an order and define SCA and 3DS settings. Use payment_source.card.attributes.verification.method and supply return and cancel URLs in experience_context.
// POST /paypal-api/checkout/orders
// Body (example — adapt amounts, currency, and URLs)
{
"intent": "CAPTURE",
"purchase_units": [
{ "amount": { "currency_code": "USD", "value": "100.00" } }
],
"payment_source": {
"card": {
"attributes": {
"verification": { "method": "SCA_ALWAYS" } // or SCA_WHEN_REQUIRED
},
"experience_context": {
"return_url": "https://example.com/returnUrl",
"cancel_url": "https://example.com/cancelUrl"
}
}
}
}SCA_ALWAYS to attempt 3DS for eligible cards and regions.SCA_WHEN_REQUIRED to rely on network or regulatory mandates.On the client side, initialize the SDK and card fields. Submit the order and handle each possible outcome.
const clientToken = await getClientToken();
const sdk = await window.paypal.createInstance({
clientToken,
components: ["card-fields"],
});
const session = sdk.createCardFieldsOneTimePaymentSession();
// Render fields
const numberField = session.createCardFieldsComponent({
type: "number",
placeholder: "Card number",
});
const cvvField = session.createCardFieldsComponent({
type: "cvv",
placeholder: "CVV",
});
const expiryField = session.createCardFieldsComponent({
type: "expiry",
placeholder: "MM/YY",
});
[
"#paypal-card-fields-number",
"#paypal-card-fields-cvv",
"#paypal-card-fields-expiry",
].map((sel, i) =>
document
.querySelector(sel)
.appendChild([numberField, cvvField, expiryField][i]),
);
// Pay click
document.querySelector("#pay-button").addEventListener("click", async () => {
try {
const orderId = await createOrder({
enable3DS: true,
scaMethod: getScaMethodFromUI(),
});
const { state, data } = await session.submit(orderId, {
billingAddress: { postalCode: "95131" },
});
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 result = await captureOrder(data.orderId);
// success UI
break;
}
case "canceled": {
// buyer closed 3DS modal
break;
}
case "failed": {
// inspect data.message
break;
}
}
} catch (e) {
console.error(e);
}
});Use this Express handler to create a PayPal order and optionally include 3DS parameters based on your request body.
app.post("/paypal-api/checkout/orders", async (req, res) => {
const { enable3DS, scaMethod = "SCA_WHEN_REQUIRED" } = req.body || {};
const body = {
intent: "CAPTURE",
purchase_units: [{ amount: { currency_code: "USD", value: "100.00" } }],
};
if (enable3DS) {
body.payment_source = {
card: {
attributes: { verification: { method: scaMethod } },
experience_context: {
return_url: "https://example.com/returnUrl",
cancel_url: "https://example.com/cancelUrl",
},
},
};
}
// call PayPal Orders API with body and return { id }
});On the server, create a vault setup token to save a payment method and require 3DS. Specify a verification method in the request.
// POST /paypal-api/vault/setup-token
// Body (example)
{
"payment_source": {
"card": {
"experience_context": {
"return_url": "https://example.com/returnUrl",
"cancel_url": "https://example.com/cancelUrl"
},
"verification_method": "SCA_ALWAYS" // or SCA_WHEN_REQUIRED
}
}
}On the client side, initialize the SDK and card fields. Submit the order and handle each possible outcome.
const clientToken = await getClientToken();
const sdk = await window.paypal.createInstance({
clientToken,
components: ["card-fields"],
});
const session = sdk.createCardFieldsSavePaymentSession();
// Render 3 fields as shown above...
const { setupToken } = await createVaultSetupToken({
enable3DS: true,
scaMethod: getScaMethodFromUI(),
});
const { state, data } = await session.submit(setupToken);
switch (state) {
case "succeeded": {
// Persist data.vaultSetupToken on your server (maps to a vaulted instrument)
break;
}
case "canceled": {
// user dismissed 3DS
break;
}
case "failed": {
// show retry message
break;
}
}return_url and cancel_url in all 3DS requests.break; in each switch case.data to manage risk.