Last updated: October 1, 2026
After customers save their credit or debit card, they can select it for faster checkout. Customers won't have to enter payment details for future transactions.
Use the JavaScript SDK to save a payer's card if you aren't PCI compliant under SAQ A but want to save credit or debit cards during checkout.
This integration is available in the following countries and territories.
Americas (2)
+
Asia (4)
+
Europe (28)
+
Oceania (1)
+
PayPal encrypts payment method information and stores it in a digital vault for that customer.
The checkout process is now shorter because it uses saved payment information.
Businesses save payment methods if they want customers to:
You can charge now and save the card in a single flow. Keep your one-time payment session on the client and add a vault directive on your server when creating the order.
Set up your sandbox business account to save payment methods:
Add a checkbox element grouped with your card collection fields to give payers the option to save their card.
Add the <input> elements for cardholder name and billing address referenced in the following code samples. The inputs are standard HTML form elements that you manage on the client side, not PayPal-hosted fields.
<div class="card-fields-container">
<input id="card-name-field-container" type="text" placeholder="Name on card" />
<div id="card-number-field-container"></div>
<div id="card-expiry-field-container"></div>
<div id="card-cvv-field-container"></div>
<input id="billing-street-address" type="text" placeholder="Street address" />
<input id="billing-city" type="text" placeholder="City" />
<input id="billing-state" type="text" placeholder="State / Province" />
<input id="billing-postal-code" type="text" placeholder="ZIP / Postal code" />
<input id="billing-country-code" type="text" placeholder="Country code (for example, US)" maxlength="2" />
<input type="checkbox" id="save" name="save">
<label for="save">Save your card</label>
</div>Pass document.getElementById("save").checked to your server with the following details in the createOrder() method:
// Initialize the v6 SDK with card-fields component
const sdk = await window.paypal.createInstance({
clientId: "YOUR_CLIENT_ID",
components: ["card-fields"]
});
// Check eligibility for advanced card payments
const eligibility = sdk.findEligibleMethods({
currencyCode: "USD",
paymentFlow: "VAULT_WITH_PAYMENT",
});
if (eligibility.isEligible("advanced_cards")) {
// Create a one-time payment session using v6 SDK
const cardSession = sdk.createCardFieldsOneTimePaymentSession();
// Render each hosted card field. Cardholder name is a standard input you own,
// so it isn't rendered here.
const numberField = cardSession.createCardFieldsComponent({ type: "number" });
numberField.appendChild(document.getElementById("card-number-field-container"));
const expiryField = cardSession.createCardFieldsComponent({ type: "expiry" });
expiryField.appendChild(document.getElementById("card-expiry-field-container"));
const cvvField = cardSession.createCardFieldsComponent({ type: "cvv" });
cvvField.appendChild(document.getElementById("card-cvv-field-container"));
// Submit button event handler
const submitButton = document.getElementById("submit-button");
submitButton.addEventListener("click", async () => {
try {
// Create order - server determines verification settings
const saveCheckbox = document.getElementById("save");
const orderResponse = await fetch("/api/paypal/order/create/", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
saveCard: saveCheckbox.checked,
}),
});
const orderData = await orderResponse.json();
const orderId = orderData.id;
// Submit the session with cardholder name and full billing address
const { state, data } = await cardSession.submit(orderId, {
name: document.getElementById("card-name-field-container").value,
billingAddress: {
streetAddress: document.getElementById("billing-street-address").value,
city: document.getElementById("billing-city").value,
state: document.getElementById("billing-state").value,
postalCode: document.getElementById("billing-postal-code").value,
countryCode: document.getElementById("billing-country-code").value,
},
});
// Handle successful transaction
if (state === "succeeded") {
// Capture the order
const captureResponse = await fetch(`/api/paypal/orders/${data.orderId}/capture/`, {
method: "POST"
});
const captureData = await captureResponse.json();
// Handle vault details if card was saved
const vault = captureData?.payment_source?.card?.attributes?.vault;
if (vault?.status === "VAULTED") {
// Save the vault.id and vault.customer.id for future use
console.log('Vault ID:', vault.id);
console.log('Customer ID:', vault.customer.id);
} else if (vault?.status === "APPROVED") {
// vault.id isn't available yet; save it once the VAULT.PAYMENT-TOKEN.CREATED webhook arrives
console.log('Vault token pending, awaiting webhook');
}
// Handle successful transaction completion
console.log('Payment completed successfully');
}
} catch (error) {
// Handle any error that may occur
console.error('Payment error:', error);
}
});
} else {
// Handle the workflow when advanced credit and debit cards are not available
}Set up your server to call the Orders API. The button that the payer selects determines the payment_source sent in the following sample.
This SDK uses the Orders v2 API to save payment methods in the background. Use the following request to add the attributes needed to save a card.
payment_source.card.attributes.vault to the request when the client-sent saveCard value is true. If the payer didn't select the save checkbox, omit attributes.vault from the request so the card isn't saved to the vault.This request is for payers who:
document.getElementById("save").checked value is true.To run 3D Secure on the card, set the payment_source.card.attributes.verification.method to SCA_ALWAYS or SCA_WHEN_REQUIRED.
SCA_ALWAYS triggers an authentication for every transaction, while SCA_WHEN_REQUIRED triggers an authentication only when a regional compliance mandate such as PSD2 is required. 3D Secure is supported only in countries with a PSD2 compliance mandate.
payment_source.attributes.vault.store_in_vault with the value ON_SUCCESS means the card is saved with a successful authorization or capture.curl -v -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ACCESS-TOKEN" \
-d '{
"intent": "CAPTURE",
"purchase_units": [{
"reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
"amount": {
"currency_code": "USD",
"value": "100.00"
}
}],
"payment_source": {
"card": {
"name": "Firstname Lastname",
"billing_address": {
"address_line_1": "123 Main St.",
"address_line_2": "Unit B",
"admin_area_2": "Anytown",
"admin_area_1": "CA",
"postal_code": "12345",
"country_code": "US"
},
"attributes": {
"vault": {
"store_in_vault": "ON_SUCCESS"
}
}
}
}
}'Pass the order id to the JavaScript SDK. The SDK updates the order with the new card details. PayPal handles any PCI compliance issues.
After the SDK is updated, it triggers the onApprove() method, which receives an object containing the orderID. You can authorize or capture the order when you have the orderID.
payment_source.attributes.vault.store_in_vault. Vault details are available only after an order is authorized or captured. {
"id": "5O190127TN364715T",
"status": "CREATED",
"intent": "CAPTURE",
"payment_source": {
"card": {
"brand": "VISA",
"last_digits": "1881",
"billing_address": {
"address_line_1": "123 Main St.",
"address_line_2": "Unit B",
"admin_area_2": "Anytown",
"admin_area_1": "CA",
"postal_code": "12345",
"country_code": "US"
}
}
},
"purchase_units": [{
"reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
"amount": {
"currency_code": "USD",
"value": "100.00"
}
}],
"create_time": "2021-10-28T21:18:49Z",
"links": [{
"href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
"rel": "self",
"method": "GET"
},
{
"href": "https://www.sandbox.paypal.com/checkoutnow?token=5O190127TN364715T",
"rel": "approve",
"method": "GET"
},
{
"href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
"rel": "update",
"method": "PATCH"
},
{
"href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T/capture",
"rel": "capture",
"method": "POST"
}
]
}Set up your server to call the Orders v2 API:
intent passed was AUTHORIZE.intent passed was CAPTURE.Add a payment_source.card instruction for vault. Use your desired amounts, URLs, and business policy. For 3D Secure, add attributes.verification (see your 3D Secure guide for attributes.verification and experience_context). Set attributes.vault.store_in_vault to "ON_SUCCESS" or "ALWAYS" depending on your business policy.
The following example shows a capture request. If the intent passed at order creation was AUTHORIZE, send the same payment_source.card.attributes to the authorize order endpoint instead.
curl -v -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders/ORDER-ID/capture \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ACCESS-TOKEN" \
-d \
'{
"payment_source": {
"card": {
"attributes": {
"vault": {
"store_in_vault": "ON_SUCCESS"
}
}
}
}
}' {
"id": "5O190127TN364715T",
"status": "COMPLETED",
"payment_source": {
"card": {
"brand": "VISA",
"last_digits": "4949",
"attributes": {
"vault": {
"id": "nkq2y9g",
"customer": {
"id": "695922590"
},
"status": "VAULTED",
"links": [{
"href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/nkq2y9g",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/nkq2y9g",
"rel": "delete",
"method": "DELETE"
},
{
"href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
"rel": "up",
"method": "GET"
}
]
}
}
}
},
"purchase_units": [{
"reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
"payments": {
"captures": [{
"id": "3C679366HH908993F",
"status": "COMPLETED",
"amount": {
"currency_code": "USD",
"value": "100.00"
},
"seller_protection": {
"status": "NOT_ELIGIBLE"
},
"final_capture": true,
"seller_receivable_breakdown": {
"gross_amount": {
"currency_code": "USD",
"value": "100.00"
},
"paypal_fee": {
"currency_code": "USD",
"value": "3.00"
},
"net_amount": {
"currency_code": "USD",
"value": "97.00"
}
},
"create_time": "2022-01-01T21:20:49Z",
"update_time": "2022-01-01T21:20:49Z",
"processor_response": {
"avs_code": "Y",
"cvv_code": "M",
"response_code": "0000"
},
"links": [{
"href": "https://api-m.sandbox.paypal.com/v2/payments/captures/3C679366HH908993F",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.sandbox.paypal.com/v2/payments/captures/3C679366HH908993F/refund",
"rel": "refund",
"method": "POST"
}
]
}]
}
}],
"links": [{
"href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
"rel": "self",
"method": "GET"
}]
}In the response from the Authorize or Capture request, the Orders v2 API interacts with the Payment Method Tokens v3 API to save the card.
The payment_source.card.attributes.vault stores the card information as the vault.id, which can be used for future payments when the vault.status is VAULTED.
If the payment has been authorized or captured, the payer doesn't need to be present to save a payment_source. To keep checkout times as short as possible, the Orders API responds as soon as payment is captured.
If the attributes.vault.status returned after payment is APPROVED, you won't have a vault.id yet. An example of the attributes object from this scenario is in the following sample:
"attributes": {
"vault": {
"status": "APPROVED",
"links": [
{
"href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
"rel": "up",
"method": "GET"
}
]
}
}The Payment Method Tokens API still saves the payment source even after the Orders API returns its response and sends a webhook after the payment source is saved.
In order to retrieve a vault_id when an APPROVED status is returned, you'll need to subscribe to the VAULT.PAYMENT-TOKEN.CREATED webhook.
The Payment Method Tokens API sends a webhook after the payment source is saved. An example of the VAULT.PAYMENT-TOKEN.CREATED webhook payload is shown in the following sample:
{
"id":"WH-1KN88282901968003-82E75604WM969463F",
"event_version":"1.0",
"create_time":"2022-08-15T14:13:48.978Z",
"resource_type":"payment_token",
"resource_version":"3.0",
"event_type":"VAULT.PAYMENT-TOKEN.CREATED",
"summary":"A payment token has been created.",
"resource":{
"time_created":"2022-08-15T07:13:48.964PDT",
"links":[
{
"href":"https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/9n6724m",
"rel":"self",
"method":"GET",
"encType":"application/json"
},
{
"href":"https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/9n6724m",
"rel":"delete",
"method":"DELETE",
"encType":"application/json"
}
],
"id":"nkq2y9g",
"payment_source":{
"card":{
"last_digits":"1111",
"brand":"VISA",
"expiry":"2027-02",
"billing_address":{
"address_line_1":"123 Main St.",
"address_line_2":"Unit B",
"admin_area_2":"Anytown",
"admin_area_1":"CA",
"postal_code":"12345",
"country_code":"US"
}
}
},
"customer":{
"id":"695922590"
}
},
"links":[
{
"href":"https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/WH-1KN88282901968003-82E75604WM969463F",
"rel":"self",
"method":"GET"
},
{
"href":"https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/WH-1KN88282901968003-82E75604WM969463F/resend",
"rel":"resend",
"method":"POST"
}
]
}In this example, the resource.id field is the vault ID, and resource.customer.id is the PayPal-generated customer ID.
You can now style your card fields and test a purchase.
Payment processors return the following codes when they receive a transaction request. For advanced card payments, the code shows in the authorization object under the response_code field.
The following sample shows the processor response codes returned in an authorization (avs_code) and capture call (cvv_code) response:
"processor_response": {
"avs_code": "Y",
"cvv_code": "S",
"response_code": "0000"
}See the Orders API response_code object for the processor response codes for non-PayPal payment processor errors.
When a payer returns to your site, you can show the payer's saved payment methods with the Payment Method Tokens API.
Make the server-side list all payment tokens API call to retrieve payment methods saved to a payer's PayPal-generated customer ID. Based on this list, you can show all saved payment methods to a payer to select during checkout.
Show the saved card to the payer and use the Orders API to make another transaction. Use the vault ID the payer selects as an input to the Orders API.
Use supported CSS properties to style the card fields.
Test your vault integration in the PayPal sandbox.
ACCESS-TOKEN to your access token.Use the following card numbers to test transactions in the sandbox:
| Test number | Card type |
|---|---|
371449635398431 | American Express |
376680816376961 | American Express |
36259600000004 | Diners Club |
6304000000000000 | Maestro |
5063516945005047 | Maestro |
2223000048400011 | Mastercard |
4005519200000004 | Visa |
4012000033330026 | Visa |
4012000077777777 | Visa |
4012888888881881 | Visa |
4217651111111119 | Visa |
4500600000000061 | Visa |
4772129056533503 | Visa |
4915805038587737 | Visa |
Test the following scenarios:
ON_SUCCESS.The following sample shows how a full script to save cards might appear in HTML. This is a sample sandbox front-end implementation:
<head>
<!-- Add meta tags for mobile and IE -->
<meta charset="utf-8" />
</head>
<body>
<script async src="https://www.sandbox.paypal.com/web-sdk/v6/core" onload="onPayPalWebSdkLoaded()"></script>
<!-- Advanced credit and debit card payments form -->
<div class="card_container">
<input id="card-holder-name" type="text" placeholder="Name on card" />
<div id="card-number"></div>
<div id="expiration-date"></div>
<div id="cvv"></div>
<input id="billing-street-address" type="text" placeholder="Street address" />
<input id="billing-city" type="text" placeholder="City" />
<input id="billing-state" type="text" placeholder="State / Province" />
<input id="billing-postal-code" type="text" placeholder="ZIP / Postal code" />
<input id="billing-country-code" type="text" placeholder="Country code (e.g. US)" maxlength="2" />
<label>
<input type="checkbox" id="vault" name="vault" /> Vault
</label>
<br /><br />
<button value="submit" id="submit" class="btn">Pay</button>
</div>
<!-- Implementation -->
<script>
async function onPayPalWebSdkLoaded() {
// Initialize the v6 SDK with card-fields component
const sdk = await window.paypal.createInstance({
clientId: "YOUR_CLIENT_ID",
components: ["card-fields"],
});
// Check eligibility for advanced card payments
const eligibility = sdk.findEligibleMethods({
currencyCode: "USD",
paymentFlow: "VAULT_WITH_PAYMENT",
});
if (!eligibility.isEligible("advanced_cards")) {
// Handle workflow when credit and debit cards are not available
return;
}
const cardSession = sdk.createCardFieldsOneTimePaymentSession();
// Render each hosted card field. Cardholder name and billing address
// are standard inputs you own, so they aren't rendered here.
const numberField = cardSession.createCardFieldsComponent({ type: "number" });
numberField.appendChild(document.getElementById("card-number"));
const expiryField = cardSession.createCardFieldsComponent({ type: "expiry" });
expiryField.appendChild(document.getElementById("expiration-date"));
const cvvField = cardSession.createCardFieldsComponent({ type: "cvv" });
cvvField.appendChild(document.getElementById("cvv"));
const submitButton = document.getElementById("submit");
submitButton.addEventListener("click", async () => {
try {
const vaultCheckbox = document.getElementById("vault");
const orderResponse = await fetch("https://api-m.sandbox.paypal.com/v2/checkout/orders", {
method: "POST",
body: JSON.stringify({
intent: "CAPTURE",
purchase_units: [{
amount: {
currency_code: "USD",
value: "100.00",
},
}],
payment_source: {
card: {
attributes: {
verification: {
method: "SCA_ALWAYS",
},
vault: vaultCheckbox.checked
? {
store_in_vault: "ON_SUCCESS",
usage_type: "PLATFORM",
customer_type: "CONSUMER",
permit_multiple_payment_tokens: true,
}
: undefined,
},
},
experience_context: {
shipping_preference: "NO_SHIPPING",
return_url: "https://example.com/returnUrl",
cancel_url: "https://example.com/cancelUrl",
},
},
}),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer ACCESS_TOKEN",
"PayPal-Request-Id": "UNIQUE_ALPHANUMERIC_KEY",
},
});
const { id: orderId } = await orderResponse.json();
// Submit the session with cardholder name and full billing address
const { state, data } = await cardSession.submit(orderId, {
name: document.getElementById("card-holder-name").value,
billingAddress: {
streetAddress: document.getElementById("billing-street-address").value,
city: document.getElementById("billing-city").value,
state: document.getElementById("billing-state").value,
postalCode: document.getElementById("billing-postal-code").value,
countryCode: document.getElementById("billing-country-code").value,
},
});
if (state === "succeeded") {
const captureResponse = await fetch(
`https://api-m.sandbox.paypal.com/v2/checkout/orders/${data.orderId}/capture/`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer ACCESS_TOKEN",
"PayPal-Request-Id": "UNIQUE_ALPHANUMERIC_KEY",
},
}
);
const captureData = await captureResponse.json();
// Retrieve vault details from the response and
// save vault.id and customer.id for the buyer's return experience
const vault = captureData?.payment_source?.card?.attributes?.vault;
if (vault?.status === "VAULTED") {
console.log("Vault ID:", vault.id);
console.log("Customer ID:", vault.customer.id);
} else if (vault?.status === "APPROVED") {
// vault.id isn't available yet; save it once the VAULT.PAYMENT-TOKEN.CREATED webhook arrives
console.log("Vault token pending, awaiting webhook");
}
console.log("submit was successful");
}
} catch (error) {
console.error("submit erred:", error);
}
});
}
</script>
</body>payment_source.card.attributes during order creation.break; statements in client switch handling of state.