> ## 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 bank details for later use (JavaScript SDK integration)

You can use PayPal's JavaScript SDK v6 to save Automated Clearing House (ACH) Direct Debit as a payment method for future purchases without an initial transaction, with fully integrated instant bank account verification.

## Prerequisites

1. Ensure you have a PayPal business account approved for Expanded Checkout.
2. Ensure to <a href="/limited-release/apm/set-up-accounts" target="_blank">set up developer, sandbox, and production environment accounts</a>.
3. Ensure to complete the <a href="/limited-release/apm/ach/overview#account-provisioning" target="_blank">account provisioning process</a> to enable ACH payments.

## Load PayPal JavaScript SDK

PayPal's JavaScript SDK provides the necessary pre-built tools to render the Pay with Bank (ACH) button on your webpage and handle payment authorization. Include the JavaScript SDK as a `<script>` tag in the HTML file that renders your webpage.

<CodeGroup>
  ```html HTML - Production environment theme={null}
  <script src="https://www.paypal.com/web-sdk/v6/core"></script>
  ```

  ```html HTML - Sandbox environment theme={null}
  <script src="https://www.sandbox.paypal.com/web-sdk/v6/core"></script>
  ```
</CodeGroup>

## Create browser-safe client token

The browser-safe token is a client-side access token that authorizes an app to use the JavaScript SDK resources.

<Note> A browser-safe token is not the same as a server-side access token. The server-side access token helps PayPal authenticate an app when the app accesses PayPal REST API resources.</Note>

To get the browser-safe client token from your server-side code, make a POST call to the `/v1/oauth2/token` endpoint and include the following:

* Encoded app credentials Client ID : Secret in Base64 format, in the Authorization header.
* Data parameters:

| Parameter       | Action                                                                                                                                      |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `grant_type`    | Set the parameter value to `client_credentials` to specify that the app is requesting to exchange client ID and secret for an access token. |
| `response_type` | Set the parameter value to `client_token` to request a client-side access token.                                                            |

**Response**: Contains the browser-safe client token in the `access_token` response parameter.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  // Get OAuth token using client credentials
  async function getClientToken() {
    try {
      const response = await fetch("/your-server/api/paypal/browser-safe-client-token");
      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || "Failed to fetch client token");
      }

      return data.access_token;
    } catch (error) {
      console.error("Client token error:", error);
      throw error;
    }
  }
  ```

  ```javascript server-side - Node.js theme={null}
  // Sample config object
  const config = {
    clientID: process.env.PAYPAL_CLIENT_ID,
    clientSecret: process.env.PAYPAL_CLIENT_SECRET,
    apiBaseUrl: process.env.PAYPAL_API_BASE_URL
  }
  // Helper function to get OAuth token
  async function getAuthToken(formBody) {
    const encodedClientCredentials = Buffer.from(
      `${config.clientID}:${config.clientSecret}`,
    ).toString("base64");

    const response = await fetch(`${config.apiBaseUrl}/v1/oauth2/token`, {
      body: formBody,
      headers: {
        Accept: "application/json",
        "Accept-Language": "en_US",
        Authorization: `Basic ${encodedClientCredentials}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      method: "POST",
    });

    const data = await response.json();

    if (response.status !== 200 && response.status !== 201) {
      throw new Error(data.error_description || "Failed to generate OAuth token");
    }

    return data;
  }

  // Function to get browser-safe client token
  function getBrowserSafeClientToken() {
    return getAuthToken(
      "grant_type=client_credentials&response_type=client_token"
    );
  }

  // Route handler for browser-safe client token endpoint
  router.get(
    "/your-server/api/paypal/browser-safe-client-token",
    async (request, response, next) => {
      try {
        const clientTokenResponse = await getBrowserSafeClientToken();
        return response.send({
          access_token: clientTokenResponse.access_token,
        });
      } catch (error) {
        next(error);
      }
    },
  );
  ```

  ```bash server-side - cURL theme={null}
  curl -v -X POST 'https://api-m.sandbox.paypal.com/v1/oauth2/token' \
    -u 'CLIENT-ID:CLIENT-SECRET' \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -H 'Accept: application/json' \
    -H 'Accept-Language: en_US' \
    -d 'grant_type=client_credentials' \
    -d 'response_type=client_token'
  ```
</CodeGroup>

## Initialize JavaScript SDK and create SDK instance

In your client-side code, use the `window.paypal.createInstance()` method with the following parameters to create a PayPal SDK instance:

| Parameter     | Action                                                            |
| ------------- | ----------------------------------------------------------------- |
| `clientToken` | Pass the browser-safe client token.                               |
| `components`  | Set to `["bank-ach-payments"]` to load and render ACH components. |

**Response**: Contains an SDK instance object with the `createBankAchSavePaymentSession()` method for creating ACH payment sessions and the `findEligibleMethods()` method for checking payment method availability.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  const clientToken = await getClientToken();

  const sdkInstance = await window.paypal.createInstance({
    clientToken,
    components: ["bank-ach-payments"],
  });
  window.sdkInstance = sdkInstance;
  ```
</CodeGroup>

## Verify eligibility

In your client-side code, use the following methods to determine if you are eligible to offer ACH as a payment method:

* `findEligibleMethods()`: Returns all the eligible payment methods.
* `isEligible()`: Indicates if ACH is an eligible payment method.

Call `findEligibleMethods()` with an input `options` object containing the following parameters:

| Parameter      | Action                                                              |
| -------------- | ------------------------------------------------------------------- |
| `currencyCode` | Set to the three-letter currency code. Example: `USD`.              |
| `paymentFlow`  | Set to `VAULT_WITHOUT_PAYMENT` to vault without an initial payment. |

**Response**: Contains an object with the `isEligible()` method. Use the method to verify if the eligible payment methods include ACH.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  // Check if ACH is available for vaulting without payment
  const options = {
    currencyCode: "USD",
    paymentFlow: "VAULT_WITHOUT_PAYMENT"
  };

  // Query for available payment methods
  const paymentMethods = await sdkInstance.findEligibleMethods(options);

  // Check ACH eligibility
  const isAchEligible = paymentMethods.isEligible("ach");
  ```
</CodeGroup>

## Render Pay with Bank button

After confirming eligibility, render the Pay with Bank - ACH button on your webpage:

1. **Define the container for the ACH button**: In the HTML file corresponding to the webpage where you want to render the button, include a container element.

<CodeGroup>
  ```html index.html theme={null}
  <section class="payment-method-container" id="bank-ach-section">
    <h2>Pay with Bank</h2>
    <!-- BankAchButton web component launches the popup flow -->
    <bank-ach-button id="paypal-bank-ach-button" hidden></bank-ach-button>
  </section>
  ```
</CodeGroup>

2. **Create vault setup token**: Call `createVaultSetupToken()` to get the [vault setup token](#define-createvaultsetuptoken-function). A vault setup token is a temporary token that is later exchanged for a payment method token.

3. **Create payment session**: Use `sdkInstance.createBankAchSavePaymentSession()` to create a payment session and register the `onApprove()`, `onCancel()`, `onComplete()`, and `onError()` event handlers.

4. **Attach the onClick event handler and display the button**: Use `addEventListener()` to attach the [`onClick` event handler](#define-onclick-event-handler-function) that triggers the `onClick` function when customers select the Pay with Bank - ACH button.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  // isAchEligible returns true if ACH payments are eligible for your account.

  if (isAchEligible) {
    // Step 2: Create vault setup token
    const { vaultSetupToken } = await createVaultSetupToken();

    // Step 3: Create payment session
    const bankAchSession = sdkInstance.createBankAchSavePaymentSession({
      onApprove,
      onCancel,
      onComplete,
      onError,
      vaultSetupToken,
    });

    // Step 4: Attach the onClick event handler and display the button
    const bankButton = document.querySelector("#paypal-bank-ach-button");
    bankButton.addEventListener("click", onClick);
    bankButton.removeAttribute("hidden");
  } else {
    console.log("ACH is not eligible");
  }
  ```
</CodeGroup>

## Define createVaultSetupToken function

The `createVaultSetupToken()` function creates a vault setup token for saving payment methods without an initial payment.

In your client-side code, include the `createVaultSetupToken()` function that calls the server-side code to create a vault setup token.

In your server-side code, include the code to make a POST call to the `/v3/vault/setup-tokens` endpoint:

1. Use a Bearer token with full-scope access token in the `Authorization` header.
2. Include the following parameters:

| Parameter                                                     | Action                                                                                                 |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `payment_source.bank.ach_debit.billing_address.country_code`  | Set to the two-letter country code. Example: `US`.                                                     |
| `payment_source.bank.ach_debit.experience_context.cancel_url` | Set to the URL where customers are redirected if they cancel. Example: `https://example.com/cancel`.   |
| `payment_source.bank.ach_debit.experience_context.locale`     | Set to the locale code. Example: `en-US`.                                                              |
| `payment_source.bank.ach_debit.experience_context.return_url` | Set to the URL where customers are redirected after completion. Example: `https://example.com/return`. |
| `payment_source.bank.ach_debit.verification.paypal.method`    | Set to `INSTANT_ACCOUNT_VERIFICATION` for the verification method.                                     |

**Response**: Contains the vault setup token in the `id` response parameter.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  async function createVaultSetupToken() {
    console.log("Creating vault setup token");

    const defaultHeaders = {
      "Content-Type": "application/json",
    };

    try {
      const response = await fetch(
        "/your-server/api/paypal/vault/setup-token",
        {
          headers: {
            ...defaultHeaders,
          },
          method: "POST",
        },
      );

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.message || "Failed to create setup token");
      }

      return data;
    } catch (error) {
      throw new Error(error);
    }
  }
  ```

  ```javascript server-side - Node.js theme={null}
  // Function to get full scope access token for vault operations
  function getFullScopeAccessToken() {
    return getAuthToken(
      "grant_type=client_credentials&response_type=token"
    );
  }

  // Express route to create setup token for ACH
  router.post(
    "/your-server/api/paypal/vault/setup-token",
    async (request, response, next) => {
      try {
        const { access_token: accessToken } = await getFullScopeAccessToken();

        const requestHeaders = {
          Authorization: `Bearer ${accessToken}`,
          "Content-Type": "application/json",
        };

        const apiResponse = await fetch(
          `${config.apiBaseUrl}/v3/vault/setup-tokens`,
          {
            body: JSON.stringify({
              payment_source: {
                bank: {
                  ach_debit: {
                    billing_address: {
                      country_code: "US",
                    },
                    experience_context: {
                      cancel_url: "https://example.com/cancel",
                      locale: "en-US",
                      return_url: "https://example.com/return",
                    },
                    verification: {
                      paypal: {
                        method: "INSTANT_ACCOUNT_VERIFICATION",
                      },
                    },
                  },
                },
              },
            }),
            headers: requestHeaders,
            method: "POST",
          },
        );

        const data = await apiResponse.json();

        if (apiResponse.ok) {
          return response.send({
            vaultSetupToken: data.id,
          });
        } else {
          return response.status(apiResponse.status).send(data);
        }
      } catch (error) {
        next(error);
      }
    },
  );
  ```

  ```bash server-side - cURL theme={null}
  curl -v -X POST 'https://api-m.sandbox.paypal.com/v3/vault/setup-tokens' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer ACCESS-TOKEN' \
    -d '{
      "payment_source": {
        "bank": {
          "ach_debit": {
            "billing_address": {
              "country_code": "US"
            },
            "experience_context": {
              "cancel_url": "https://example.com/cancel",
              "locale": "en-US",
              "return_url": "https://example.com/return"
            },
            "verification": {
              "paypal": {
                "method": "INSTANT_ACCOUNT_VERIFICATION"
              }
            }
          }
        }
      }
    }'
  ```
</CodeGroup>

## Define onClick event handler function

Create an `onClick` event-handler function that starts the authentication flow.

In your client-side code, include an event-handler function that is triggered when the user clicks the Pay with Bank - ACH button and starts the authentication (account verification) flow.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  async function onClick() {
    try {
      await bankAchSession.start({
        presentationMode: "popup",
      });
    } catch (error) {
      console.error("Error starting authentication flow:", error);
    }
  }
  ```
</CodeGroup>

## Handle events

Event handlers manage the different outcomes when your customers attempt to save ACH payment methods. In your code, create event-handler functions that handle customer approval, payment cancellation, error scenarios, and completion.

### a. Handle approval and create payment token

In your client-side code, include the `onApprove()` event-handler function that receives the vault setup token after the customer successfully verifies their bank account, calls the `createPaymentToken()` function, and passes the vault setup token.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  async function onApprove(data) {
    try {
      console.log("Processing vault PaymentToken for ACH...");

      // Create payment token using the vault setup token
      const createPaymentTokenResponse = await createPaymentToken(
        data.vaultSetupToken,
      );

      console.log(
        "✅Payment Token created successfully: " + createPaymentTokenResponse.paymentToken,
      );

      console.log("Create payment token response: ", createPaymentTokenResponse);
    } catch (error) {
      console.error("❌Error creating payment token: " + error.message);
    }
  }
  ```
</CodeGroup>

In your server-side code, include the code to make a POST call to the `/v3/vault/payment-tokens` endpoint:

1. Use a valid full-scope `Bearer` access token and make a POST call to the `/v3/vault/payment-tokens` endpoint with the vault setup token received from client-side.
2. Receive the payment token details from the PayPal server and pass them to the client-side code.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  async function createPaymentToken(setupToken) {
    console.log("Creating vault payment token");

    const defaultHeaders = {
      "Content-Type": "application/json",
    };

    try {
      const response = await fetch(
        "/your-server/api/paypal/vault/payment-token",
        {
          headers: {
            ...defaultHeaders,
          },
          method: "POST",
          body: JSON.stringify({ setupToken }),
        },
      );

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.message || "Failed to create payment token");
      }

      return data;
    } catch (error) {
      throw new Error(error);
    }
  }
  ```

  ```javascript server-side - Node.js theme={null}
  // Express route to create payment token for ACH
  router.post(
    "/your-server/api/paypal/vault/payment-token",
    async (request, response, next) => {
      const setupToken = request.body.setupToken;

      try {
        const { access_token: accessToken } = await getFullScopeAccessToken();

        const requestHeaders = {
          Authorization: `Bearer ${accessToken}`,
          "Content-Type": "application/json",
        };

        const apiResponse = await fetch(
          `${config.apiBaseUrl}/v3/vault/payment-tokens`,
          {
            body: JSON.stringify({
              payment_source: {
                token: {
                  id: setupToken,
                  type: "SETUP_TOKEN",
                },
              },
            }),
            headers: requestHeaders,
            method: "POST",
          },
        );

        const data = await apiResponse.json();

        if (apiResponse.ok) {
          return response.send({
            paymentToken: data.id,
          });
        } else {
          return response.status(apiResponse.status).send(data);
        }
      } catch (error) {
        next(error);
      }
    },
  );
  ```

  ```bash server-side - cURL theme={null}
  curl -v -X POST 'https://api-m.sandbox.paypal.com/v3/vault/payment-tokens' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer ACCESS-TOKEN' \
    -d '{
      "payment_source": {
        "token": {
          "id": "VAULT-SETUP-TOKEN",
          "type": "SETUP_TOKEN"
        }
      }
    }'
  ```
</CodeGroup>

### b. Handle cancellation

In your app code, include the `onCancel()` event-handler function that handles payment cancellation. Payment cancellation can occur when the customer cancels account verification or the saving process. JavaScript SDK passes the cancellation details to the function's `data` parameter.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  function onCancel(data) {
    console.log("ACH Save Cancelled!", data);
    console.log("Customer cancelled the bank account linking process");
  }
  ```
</CodeGroup>

### c. Handle errors

In your app code, include the `onError(data)` event-handler function to process errors that occur during bank account verification or the saving process. JavaScript SDK passes error message and error details to the function's `data` parameter.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  function onError(data) {
    console.log("ACH Save Error:", data);
    console.error(`Error occurred during bank account linking: ${data.message || 'Unknown error'}`);
  }
  ```
</CodeGroup>

### d. \[Optional] Handle completion

In your app code, you can include the `onComplete(data)` event-handler function to perform necessary clean-up operations.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  function onComplete(data) {
    console.log("ACH Save Complete:", data);
    console.log("Bank account authentication flow completed");
  }
  ```
</CodeGroup>

## Test and go live

**Test the end-to-end integration flow**

1. Load your integration page and verify ACH eligibility checking works correctly.

2. Click the Pay with Bank button to start the bank account authentication process.

3. Verify the vault setup token is created successfully when the authentication flow begins as documented in the [Define createVaultSetupToken function](#define-createvaultsetuptoken-function) section.

4. Complete the authentication flow and verify the `onApprove` callback receives the vault setup token as documented in the [Handle approval and create payment token](#a-handle-approval-and-create-payment-token) section. When the bank selection popup appears, use the following sandbox test credentials:
   * **Bank selection**: Select **Demo Bank** from the list of available banks.
   * **Username**: `pyplopenbankingubsb.site16441.2credential`
   * **Password**: `site16441.2`

5. Confirm your server successfully creates a payment method token using the vault setup token as documented in the [Handle approval and create payment token](#a-handle-approval-and-create-payment-token) section.

6. Verify the authentication flow starts correctly when clicking the button as documented in the [Define onClick event handler function](#define-onclick-event-handler-function) section.

**Test error handling**

1. Cancel the authentication process and verify the `onCancel` callback executes as documented in the [Handle cancellation](#b-handle-cancellation) section.
2. Test error scenarios and verify the `onError` callback handles failures appropriately as documented in the [Handle errors](#c-handle-errors) section.
3. Confirm the `onComplete` callback executes when the flow finishes as documented in the [Handle completion](#d-\[optional]-handle-completion) section.

**Go live**

1. Switch from sandbox to production API endpoints:
   * Change SDK URL from `https://www.sandbox.paypal.com/web-sdk/v6/core` to `https://www.paypal.com/web-sdk/v6/core`.
   * Update API base URL from `https://api-m.sandbox.paypal.com` to `https://api-m.paypal.com`.

2. Replace sandbox credentials with production credentials:
   * Use your production client ID and secret.
   * Ensure your production PayPal business account is approved for Expanded Checkout and ACH payments.

3. Deploy and test your production integration to confirm everything works correctly.
