> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paypal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Save PayPal with purchase with the JavaScript SDK

Use the PayPal JavaScript SDK v6 to save a buyer's PayPal information when they complete a one-time payment. PayPal stores the buyer's information in a secure data store called a vault. When a returning buyer selects PayPal at checkout, they can pay with their saved account without re-entering credentials or payment details.

With this integration, you can:

* Collect and securely store a buyer's PayPal account at checkout
* Retrieve a vault token after a successful capture
* Use `clientId` authentication (the standard path for saving to the vault)

## Prerequisites

To save PayPal accounts with a one-time purchase, you need:

* Complete the steps in [get started](/developer/how-to/api/get-started) to get:
  * An app in the PayPal Developer Dashboard with **Save payment methods** enabled
  * A client ID
  * A client secret
* A server-side environment that can make HTTPS requests to the PayPal API

## Key concepts

The following concepts are central to the vault-with-purchase flow.

### Use `clientId`

The PayPal JavaScript SDK v6 initializes with your `clientId`. Your `clientId` is safe to expose in the browser and is the standard authentication path for PayPal one-time payments, vaulting, and return-buyer flows. Only [Fastlane](https://developer.paypal.com/studio/checkout/fastlane) integrations require the alternate [`clientToken` authentication path](https://developer.paypal.com/studio/checkout/fastlane/integrate#generate-client-token).

### Vault attributes

Use the following vault attributes to support saving PayPal accounts with a purchase:

* `store_in_vault: "ON_SUCCESS"` saves the payment method when the order is successfully captured.
* `usage_type: "MERCHANT"` indicates that the merchant manages the vault.
* `customer_type: "CONSUMER"` identifies the buyer as a consumer to present a consumer vaulting experience.

### Vault token

After a successful capture, retrieve the vault token ID by calling `GET /v2/checkout/orders/:id`. Link the token at `payment_source.paypal.attributes.vault.id` to the customer ID and store it on your server side for future use.

## Integration flow

The following steps describe the end-to-end vault-with-purchase flow.

1. The page loads, and the SDK script fires [`onPayPalWebSdkLoaded()`](#load-the-sdk).
2. The SDK [initializes with `createInstance({ clientId })`](#initialize-the-sdk).
3. The buyer selects the PayPal button, which calls [`session.start()`](#create-a-payment-session).
4. The client calls `POST /api/orders` with [`paymentMethod: "paypal"`](#create-an-order-client).
5. The server [creates the order with `store_in_vault: "ON_SUCCESS"`](#create-an-order-with-vault-attributes) in the PayPal payment source.
6. The buyer authenticates in the PayPal window.
7. [`onApprove` fires](#create-a-payment-session), and the client calls [`POST /api/orders/:id/capture`](#capture-the-order).
8. The client calls [`GET /api/orders/:id`](#retrieve-the-vault-token) and retrieves the vault token.

## Set up your front end

To set up the front end:

* [Load the SDK](#load-the-sdk)
* [Add a PayPal button](#add-the-button-element-to-the-page) to your page
* [Initialize an SDK instance](#initialize-the-sdk) with your `clientId`
* [Create a payment session](#create-a-payment-session) that supports saving the buyer's PayPal information with the purchase
* [Create a client-side order helper](#create-an-order-client) that communicates with your [back end](#set-up-your-back-end)

### Load the SDK

Add the SDK script tag to your page. The `onload` callback fires when the script is ready.

```html lines theme={null}
<!-- Sandbox -->
<script
  async
  src="https://www.sandbox.paypal.com/web-sdk/v6/core"
  onload="onPayPalWebSdkLoaded()">
</script>
```

```html lines theme={null}
<!-- Production -->
<script
  async
  src="https://www.paypal.com/web-sdk/v6/core"
  onload="onPayPalWebSdkLoaded()">
</script>
```

### Add the button element to the page

Place the custom element where you want the button to render:

```html theme={null}
<paypal-button id="paypal-button" type="pay"></paypal-button>
```

### Initialize the SDK

After the SDK loads, use your `clientId` to create an instance and register the PayPal payments component.

```javascript lines theme={null}
async function onPayPalWebSdkLoaded() {
  const sdkInstance = await window.paypal.createInstance({
    clientId: "YOUR_CLIENT_ID",
    components: ["paypal-payments"],
    pageType: "checkout",
  });

  await setupPayPalButton(sdkInstance);
}
```

### Create a payment session

Set up a one-time payment session that includes vault support. The `onApprove` callback captures the order and retrieves the vault token.

The SDK uses `presentationMode: "auto"` to determine whether to use a popup or redirect to display the payment interface based on the buyer's device and browser.

```javascript lines expandable theme={null}
async function setupPayPalButton(sdkInstance) {
  const session = sdkInstance.createPayPalOneTimePaymentSession({
    onApprove: async ({ orderId }) => {
      // Capture the approved order
      await fetch(`/api/orders/${orderId}/capture`, { method: "POST" });

      // GET the order to surface the vault token
      const orderRes = await fetch(`/api/orders/${orderId}`);
      const { response: order } = await orderRes.json();

      const vaultToken = order?.payment_source?.paypal?.attributes?.vault?.id;
      console.log("Vault token:", vaultToken);
      // Store vaultToken server-side linked to the customer ID
    },
    onCancel: ({ orderId }) => {
      console.log("Buyer cancelled:", orderId);
    },
    onError: (err) => {
      console.error("PayPal error:", err.code, err.message);
    },
  });

  document.getElementById("paypal-button").addEventListener("click", async () => {
    await session.start(
      { presentationMode: "auto" },
      createOrder("paypal")
    );
  });
}
```

### Create an order (client)

This helper calls your server to create an order and returns the `orderId` for the SDK to start the checkout flow.

```javascript lines expandable theme={null}
async function createOrder(paymentMethod) {
  const res = await fetch("/api/orders", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ paymentMethod }),
  });
  const data = await res.json();
  if (!data.id) throw new Error("Order creation failed");
  return { orderId: data.id };
}
```

## Set up your back end

Your server handles authentication, order creation with [vault attributes](#vault-attributes), order capture, and vault token retrieval.

### Environment variables

Paste the following variables in an environment (`.env`) file in your project root. Replace `your_client_id` and `your_client_secret` with your app's client ID and client secret.

```bash lines theme={null}
PAYPAL_CLIENT_ID=your_client_id
PAYPAL_CLIENT_SECRET=your_client_secret
PORT=3000
```

### Server setup

Set up an [Express](https://expressjs.com/) server with JSON parsing and static file serving.

```javascript lines expandable theme={null}
const express = require("express");
const path = require("path");
require("dotenv").config();

const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, "public")));

// ... route handlers below ...

app.listen(process.env.PORT || 3000, () => {
  console.log(`Server running on port ${process.env.PORT || 3000}`);
});
```

### Get an access token

The order and capture endpoints need a server-side access token. This helper fetches one from the OAuth endpoint:

```javascript lines expandable theme={null}
async function getAccessToken() {
  const credentials = Buffer.from(
    `${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`
  ).toString("base64");

  const tokenRes = await fetch("https://api-m.sandbox.paypal.com/v1/oauth2/token", {
    method: "POST",
    headers: {
      Authorization: `Basic ${credentials}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: "grant_type=client_credentials",
  });

  const { access_token } = await tokenRes.json();
  return access_token;
}
```

### Create an order with vault attributes

This endpoint creates an order with `store_in_vault: "ON_SUCCESS"` in the PayPal payment source, which tells the system to save the buyer's payment method to the vault after a successful capture.

```javascript lines expandable theme={null}
app.post("/api/orders", async (req, res) => {
  const { paymentMethod } = req.body;
  const origin = req.headers.origin ?? `http://${req.headers.host}`;
  const accessToken = await getAccessToken();

  const orderRes = await fetch("https://api-m.sandbox.paypal.com/v2/checkout/orders", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      intent: "CAPTURE",
      payment_source: {
        paypal: {
          experience_context: {
            return_url: `${origin}/`,
            cancel_url: `${origin}/`,
            shipping_preference: "GET_FROM_FILE",
            user_action: "CONTINUE",
          },
          attributes: {
            vault: {
              store_in_vault: "ON_SUCCESS",
              usage_type: "MERCHANT",
              customer_type: "CONSUMER",
            },
          },
        },
      },
      purchase_units: [{
        amount: {
          currency_code: "USD",
          value: "100.00",
        },
      }],
    }),
  });

  const order = await orderRes.json();
  res.json({ id: order.id });
});
```

### Capture the order

After the buyer approves payment, call the Orders API to capture the funds.

```javascript lines expandable theme={null}
app.post("/api/orders/:orderID/capture", async (req, res) => {
  const accessToken = await getAccessToken();

  const captureRes = await fetch(
    `https://api-m.sandbox.paypal.com/v2/checkout/orders/${req.params.orderID}/capture`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
    }
  );

  res.json(await captureRes.json());
});
```

### Retrieve the vault token

Fetch the completed order to extract the vault token from the response. The token is at `payment_source.paypal.attributes.vault.id`. When you have the token, store it in your database, linked to the customer's account. After you do that, returning customers can check out without re-entering payment details.

```javascript lines expandable theme={null}
app.get("/api/orders/:orderID", async (req, res) => {
  const accessToken = await getAccessToken();

  const orderRes = await fetch(
    `https://api-m.sandbox.paypal.com/v2/checkout/orders/${req.params.orderID}`,
    { headers: { Authorization: `Bearer ${accessToken}` } }
  );

  const order = await orderRes.json();
  // Vault token at: order.payment_source.paypal.attributes.vault.id
  res.json({ response: order });
});
```

## Troubleshooting

Use this section to diagnose errors in the vault-with-purchase flow.

### `onPayPalWebSdkLoaded` doesn't fire

* **Cause:** The script tag doesn't include the `onload` attribute, or the script URL points to the wrong environment, for example, sandbox instead of production.
* **Fix:** Add `onload="onPayPalWebSdkLoaded()"` to the script tag. Use `sandbox.paypal.com` for testing. Remove the `sandbox.` subdomain for the production environment.

### `createInstance` throws or returns an error

* **Cause:** The `clientId` doesn't match the app, the app doesn't have **Save payment methods** enabled, or the `components` array doesn't include `"paypal-payments"`.
* **Fix:** Verify that the `clientId` matches the app in the PayPal Developer Dashboard. Verify the toggle is set to **Save payment methods** on under the app's features. Include `components: ["paypal-payments"]` in the `createInstance` call.

### The popup is blocked

* **Cause:** Your code calls `session.start()`, but the user has not selected a payment method. Browsers block popups that don't originate from a user action.
* **Fix:** Call `session.start()` inside the button's `click` event listener without any `await` calls before it. Move async work, such as order creation, into the `createOrder` callback that `session.start()` accepts as its second argument.

### `onApprove` fires with missing vault token

* **Cause:** Your server created the order without `store_in_vault: "ON_SUCCESS"` in the payment source, or the capture failed before vaulting could complete.
* **Fix:** Confirm that the server-side order body includes the full `attributes.vault` block. Check the capture response for errors before calling `GET /api/orders/:id`. The vault token appears after a successful capture.

### Order creation returns a 401 or 403

* **Cause:** The server-side access token expired, the credentials are wrong, or your server fetched the token from the wrong environment endpoint.
* **Fix:** Confirm `PAYPAL_CLIENT_ID` and `PAYPAL_CLIENT_SECRET` match the app in the Developer Dashboard. For testing, fetch the token from `api-m.sandbox.paypal.com`. Remove the `sandbox.` subdomain for the production environment.

## Related content

* [Orders v2 API](/api/payment-tokens/v3/customer-payment-tokens-get)
* [Payment Method Tokens v3 API](https://developer.paypal.com/docs/api/payment-tokens/v3/)
* [PayPal JavaScript SDK v6](/reference/sdk/js/v6/reference)
