Skip to main content
Save payment methods to charge payers after a set amount of time. For example, you can offer a free trial and charge payers after the trial expires. Payers don’t need to be present when charged. No checkout required. Use the JavaScript SDK to save a payer’s card if you aren’t PCI Compliant - SAQ A but want to save credit or debit cards during checkout.

Availability

  • Australia
  • Austria
  • Belgium
  • Bulgaria
  • Canada
  • China
  • Cyprus
  • Czech Republic
  • Denmark
  • Estonia
  • Finland
  • France
  • Germany
  • Hong Kong
  • Hungary
  • Ireland
  • Italy
  • Japan
  • Latvia
  • Liechtenstein
  • Lithuania
  • Luxembourg
  • Malta
  • Netherlands
  • Norway
  • Poland
  • Portugal
  • Romania
  • Singapore
  • Slovakia
  • Slovenia
  • Spain
  • Sweden
  • United Kingdom
  • United States

Prerequisites

  • You are responsible for the front-end user experience. The JavaScript SDK provides back-end support.
  • To save payment methods, you must be able to identify payers uniquely. For example, payers create an account and log in to your site.
  • Complete the steps in Get started to get the following sandbox account information from the Developer Dashboard:
    • Your sandbox account login information
    • Your access token
  • This client-side integration uses the createCardFieldsSavePaymentSession() method to save a card without a transaction.
  • The JavaScript SDK saves the following card types for purchase later:
    • American Express
    • Discover
    • Mastercard
    • Visa You’ll need an existing advanced credit and debit integration. PayPal must approve your business account for advanced credit and debit card payments.

How it works

PayPal encrypts payment method information and stores it in a digital vault for that customer.
  1. The payer saves their payment method.
  2. For a first-time payer, PayPal creates a customer ID. Store this within your system for future use.
  3. When the customer returns to your website and is ready to check out, pass their PayPal-generated customer ID to your server to retrieve saved instruments.
  4. The payer completes any required verification (for example, 3DS) per your SCA policy.
  5. Your UI can display saved payment methods as a one-click button alongside other ways to pay.

1. Set up account to save payments

Set up your sandbox and live business accounts to save payment methods:
  1. Log in to the Developer Dashboard.
  2. Under REST API apps, select your app name.
  3. Under Sandbox App Settings > App Feature Options, check Accept payments.
  4. Expand Advanced options. Confirm that Vault is selected.

2. Add SDK to HTML page

Pass your client ID to the SDK to identify yourself. Replace CLIENT-ID with your app’s client ID in the following sample:
<script async src="https://sandbox.paypal.com/web-sdk/v6/core" onload="onPayPalWebSdkLoaded()"></script>

3. Create setup token

You request a setup token from your server. The setup token creation for v6 now supports optional 3D Secure verification per your SCA policy.

Create a vault setup token on your server

This example for v6 includes optional 3DS or SCA behavior:
curl -v -k -X POST 'https://api-m.sandbox.paypal.com/v3/vault/setup-tokens' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ACCESS-TOKEN" \
-H "PayPal-Request-Id: REQUEST-ID" \
-d '{
      "payment_source": {
        "card": {
          "verification_method": "SCA_WHEN_REQUIRED",
          "experience_context": {
            "return_url": "https://example.com/returnUrl",
            "cancel_url": "https://example.com/cancelUrl"
          }
        }
      }
  }'
Create a setup token for cards that have:
  • No verification - omit verification_method
  • 3D Secure verification - "SCA_ALWAYS" or "SCA_WHEN_REQUIRED"

Front-end sample

const sdk = await window.paypal.createInstance({ clientToken, components: ["card-fields"] });
const session = sdk.createCardFieldsSavePaymentSession();
// ... render number/cvv/expiry as usual

const { setupToken } = await createVaultSetupToken({ enable3DS, scaMethod });
const { state, data } = await session.submit(setupToken);

switch (state) {
  case "succeeded": {
    // Persist data.vaultSetupToken (or returned instrument id) on your server
    break;
  }
  case "canceled": {
    // Buyer dismissed challenge
    break;
  }
  case "failed": {
    // Show retry message, inspect data.message
    break;
  }
}

Back-end sample

Make this request from your server. This setup token is generated with an empty card in the payment_source object. PayPal hosted fields use this token to securely update the setup token with card details. Copy and modify the following code:
  1. Change ACCESS-TOKEN to your sandbox app’s access token.
  2. Change REQUEST-ID to a set of unique alphanumeric characters such as a time stamp.
  3. In the createVaultSetupToken, call the endpoint on your server to create a setup token with the Payment Method Tokens API. createVaultSetupToken returns the setup token as a string.
curl -v -k -X POST 'https://api-m.sandbox.paypal.com/v3/vault/setup-tokens' 
-H "Content-Type: application/json"
-H "Authorization: Bearer ACCESS-TOKEN" 
-H "PayPal-Request-Id: REQUEST-ID" 
-d '{
      "payment_source": {
        "card": {}
      }
  }'
`}

4. Initialize card fields to save data

When you want to vault a card without taking a payment, use the Save Payment Session on the client: sdk.createCardFieldsSavePaymentSession().

Front-end sample

  • No verification
  • 3D Secure
// Initialize the v6 SDK
const sdk = await window.paypal.createInstance({ 
  clientToken, 
  components: ["card-fields"] 
});

// Create the session
const session = sdk.createCardFieldsSavePaymentSession();

// Render the card fields (you still need this part)
session.render({
  fields: {
    number: { selector: '#card-number' },
    cvv: { selector: '#card-cvv' },
    expirationDate: { selector: '#card-expiry' }
  }
});

// When user submits the form (replace the automatic callbacks)
async function handleSubmit() {
  try {
    // This replaces your createVaultSetupToken callback
    const result = await fetch("example.com/create/setup/token");
    const setupToken = result.token;

    // Submit with the session (this is the new way)
    const { state, data } = await session.submit(setupToken);

    switch (state) {
      case "succeeded": {
        // This replaces your onApprove callback
        await fetch("example.com/create/payment/token", { 
          body: JSON.stringify({ vaultSetupToken: data.vaultSetupToken })
        });
        break;
      }
      case "canceled": {
        // Handle when user cancels
        break;
      }
      case "failed": {
        // Handle errors
        console.error(data.message);
        break;
      }
    }
  } catch (error) {
    console.error("Error:", error);
  }
}

Back-end sample

Make this request from your server. Copy and modify the following code.
  1. Pass the vaultSetupToken returned by onApprove to your server.
  2. Change ACCESS-TOKEN to your sandbox app’s access token.
  3. Change REQUEST-ID to a set of unique alphanumeric characters such as a time stamp.
  4. Change VAULT-SETUP-TOKEN to the value passed from the client.
  5. Save the resulting payment token returned from the API to use in future transactions.
curl -v -k -X POST 'https://api-m.sandbox.paypal.com/v3/vault/payment-tokens' 
-H "Content-Type: application/json" 
-H "Authorization: Bearer ACCESS-TOKEN" 
-H "PayPal-Request-Id: REQUEST-ID"
-d '{
      "payment_source": {
          "token": {
              "id": "VAULT-SETUP-TOKEN",
              "type": "SETUP_TOKEN"
          }
      }
  }'

Avoid validation errors

sdk.createCardFieldsSavePaymentSession() cannot be used alongside sdk.createCardFieldsPaymentSession() on the same page. Create separate sessions for different use cases:
// Initialize the v6 SDK
const sdk = await window.paypal.createInstance({ 
  clientToken, 
  components: ["card-fields"] 
});

// Option 1: For vaulting (saving cards)
const vaultSession = sdk.createCardFieldsSavePaymentSession();

// Option 2: For immediate payments
const paymentSession = sdk.createCardFieldsPaymentSession();

// Render the same card fields (they can be reused)
const cardFieldsConfig = {
  fields: {
    number: { selector: '#card-number' },
    cvv: { selector: '#card-cvv' },
    expirationDate: { selector: '#card-expiry' }
  }
};

// You can render with either session
vaultSession.render(cardFieldsConfig);
// OR
// paymentSession.render(cardFieldsConfig);

// Then use the appropriate session based on user intent:

// For vaulting:
async function handleVault() {
  const result = await fetch("example.com/create/setup/token");
  const setupToken = result.token;
  const { state, data } = await vaultSession.submit(setupToken);
  // Handle vault result...
}

// For immediate payment:
async function handlePayment() {
  const result = await fetch("example.com/create/order");
  const orderId = result.id;
  const { state, data } = await paymentSession.submit(orderId);
  // Handle payment result...

5. Show error page

If an error prevents checkout, alert the payer that an error has occurred using the console.error() method call.
Note: This script doesn’t handle specific errors. It shows a specified error page for all errors.
// Initialize the v6 SDK
const sdk = await window.paypal.createInstance({ 
  clientToken, 
  components: ["card-fields"] 
});

// Create a session (choose based on your use case)
const session = sdk.createCardFieldsSavePaymentSession();
// OR
// const session = sdk.createCardFieldsPaymentSession();

// Render the card fields
session.render({
  fields: {
    number: { selector: '#card-number' },
    cvv: { selector: '#card-cvv' },
    expirationDate: { selector: '#card-expiry' }
  }
});

// Error handling is now done in your submit function
async function handleSubmit() {
  try {
    // Your token/order creation logic here
    const setupToken = "your-setup-token"; // or orderId for payments
    
    const { state, data } = await session.submit(setupToken);

    switch (state) {
      case "succeeded": {
        // Handle success
        break;
      }
      case "canceled": {
        // Handle cancellation
        break;
      }
      case "failed": {
        // This replaces your onError callback
        console.error('Something went wrong:', data.message);
        break;
      }
    }
  } catch (error) {
    // This also handles errors (like the onError callback)
    console.error('Something went wrong:', error);
  }
}

6. Show saved payment methods to returning payers

When a payer returns to your site, you can show the payer’s saved payment methods with the Payment Method Tokens API.

List all saved payment methods

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.
Important: Don’t expose payment method token IDs on the client side. To protect your payers, create separate IDs for each token and use your server to correlate them.

Sample request: List all saved payment methods

Copy and modify the following code.
  • Change CUSTOMER-ID to a PayPal-generated customer ID.
  • Change ACCESS-TOKEN to your sandbox app’s access token.
  • Change REQUEST-ID to a set of unique alphanumeric characters such as a time stamp.
curl -L -X GET "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens?customer_id=CUSTOMER-ID"
-H "Content-Type: application/json" 
-H "Accept-Language: en_US" 
-H "Authorization: Bearer ACCESS-TOKEN"
-H "PayPal-Request-Id: REQUEST-ID" \

Show saved card to payer

Display 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 theOrders API to capture the payment. Use supported CSS properties to style the card fields. We recommend showing the card brand and last 4 digits.
Visa ending in 1234

7. Integrate back end

The following sample shows a complete back-end integration to save cards for purchase later:
import "dotenv/config";
import express from "express";
const {
  PORT = 8888
} = process.env;
const app = express();
app.set("view engine", "ejs");
app.use(express.static("public"));
// Create setup token
app.post("/api/vault/token", async (req, res) => {
  try {
    // Use your access token to securely generate a setup token
    // with an empty payment_source
    const vaultResponse = await fetch("https://api-m.sandbox.paypal.com/v3/vault/setup-tokens", {
      method: "POST",
      body: JSON.stringify({
        payment_source: {
          card: {}
        }
      }),
      headers: {
        Authorization: 'Bearer \${ACCESS-TOKEN}',
        "PayPal-Request-Id": Date.now()
      }
    })
    // Return the reponse to the client
    res.json(vaultResponse);
  } catch (err) {
    res.status(500).send(err.message);
  }
})
// Create payment token from a setup token
app.post("/api/vault/payment-token", async (req, res) => {
  try {
    const paymentTokenResult = await fetch("https://api-m.sandbox.paypal.com/v3/vault/payment-tokens", {
      method: "POST",
      body: {
        payment_source: {
          token: {
            id: req.body.vaultSetupToken,
            type: "SETUP_TOKEN"
          }
        }
      },
      headers: {
        Authorization: 'Bearer \${ACCESS-TOKEN}',
        "PayPal-Request-Id": Date.now()
      }
    })
    const paymentMethodToken = paymentTokenResult.id
    const customerId = paymentTokenResult.customer.id
    await save(paymentMethodToken, customerId)
    res.json(captureData);
  } catch (err) {
    res.status(500).send(err.message);
  }
})
const save = async function(paymentMethodToken, customerId) {
  // Specify where to save the payment method token
}
app.listen(PORT, () => {
  console.log('Server listening at http://localhost:\${PORT}/');
})

8. Test saving cards

Use the following card numbers to test transactions in the sandbox:
Test numberCard type
371449635398431American Express
376680816376961American Express
36259600000004Diners Club
6304000000000000Maestro
5063516945005047Maestro
2223000048400011Mastercard
4005519200000004Visa
4012000033330026Visa
4012000077777777Visa
4012888888881881Visa
4217651111111119Visa
4500600000000061Visa
4772129056533503Visa
4915805038587737Visa
Test your integration to see if it saves credit and debit cards as expected. Any errors that occur appear in the console.error() method call provided to the CardFields component. Test 3DS success, cancel, and fail cases behave as expected.
  1. Render the card fields.
  2. Create a save button in your UI.
  3. When the save button is selected, session.submit(setupToken) returns succeeded and provides a reference you can store. Update the setup token with card details.
  4. On your server, use a server-side call to swap your setup token for a payment token from the Payment Method Tokens API.
    1. For a first-time payer, save the PayPal-generated customer.id.
    2. For a returning payer, use the PayPal-generated customer.id to swap the setup-token for a payment-token.
  5. Save the payment-token for future use. Test a purchase with the payment token.
  6. Show saved payment methods:
    1. Make a server-side call to the list all payment tokens endpoint. Include the PayPal-generated customer.id.
    2. Style the card fields.

Troubleshooting

IssueResolution
Vault setup is failingCheck session type: Verify you’re using createCardFieldsSavePaymentSession() for vault-only flows, not createCardFieldsOneTimePaymentSession(). One-time sessions are for transactions, not vaulting.
3D Secure verification is not completing properlyCheck URLs: Ensure you’ve included return_url and cancel_url in your experience_context when requesting SCA verification. These are required for the 3DS challenge flow to redirect users back to your application.
Cannot use vaulted payment methods for transactionsCheck vault reference storage: Verify that your server is capturing and persisting the payment token returned in the API response after vaulting. Without storing this reference, you won’t be able to use the vaulted card later.

Optional: Show saved payment methods

We recommend creating a page on your site where payers can see their saved payment methods as in the following example:
A website showing a payment methods page. The page shows the payer saved a PayPal Wallet and a credit card. The card option is highlighted.

Next step

Go live with your integration.
I