Skip to main content
Save ACH (Automated Clearing House) as a payment method for future purchases without an initial transaction. Use PayPal’s JavaScript SDK to tokenize and save bank account details for later use.

Prerequisites

  1. Ensure you have PayPal business account approved for Expanded Checkout.
  2. Ensure you set up developer, sandbox, and production environment accounts.

Load PayPal JS SDK

PayPal’s JavaScript SDK provides the necessary pre-built tools to render the 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. For production environment:
<script src="https://www.paypal.com/web-sdk/v6/core"></script>
For sandbox environment:
<script src="https://www.sandbox.paypal.com/web-sdk/v6/core"></script>

Create browser-safe client token

The browser-safe token is a client-side access token that authorizes the app to use the JavaScript SDK resources. Configure your server to call PayPal’s OAuth API and parse the access_token from the response for your client-side application. In the server-side call to the /v1/oauth2/token endpoint:
  1. Encode app credentials Client ID : Secret in Base64 format and use it in the Authorization header.
  2. Include the following data parameters:
    • 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 for a client-side access token.
    • intent: Set the parameter value to sdk_init to specify that the purpose for the request is SDK initialization.
Response: Contains the browser-safe client token in the access_token response parameter.
// 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;
  }
}

Initialize JS SDK and create SDK instance

Use the window.paypal.createInstance() method to create a PayPal SDK instance and include the following parameters:
  • clientToken: The browser-safe client token.
  • components: Set to ["bank-ach-payments"] to load and render ACH components.
Response: Contains createBankAchSavePaymentSession() for creating ACH payment sessions and findEligibleMethods() for checking payment method availability.
const clientToken = await getClientToken();

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

Verify eligibility

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 - Boolean method that determines if ACH is an eligible payment method.
Call findEligibleMethods() with an options object containing the following:
  • apiCode: Set to restApi to specify the API methodology.
  • countryCode: Set to the two-letter country code. Example: US.
  • currencyCode: Set to the three-letter currency code. Example: USD.
  • paymentFlow: Set to VAULT_WITHOUT_PAYMENT to vault without an initial payment.
  • paymentMethods: Set to ["ACH"] to check ACH payment eligibility.
Response: Contains a paymentMethods object with isEligible() method to check if ACH is eligible.
// Check if ACH is available for vaulting
const options = {
  apiCode: "restApi",
  countryCode: "US",
  currencyCode: "USD",
  paymentFlow: "VAULT_WITHOUT_PAYMENT",
  paymentMethods: ["ACH"]
};

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

// Check ACH eligibility
const isAchEligible = paymentMethods.isEligible("ach");

Render Bank ACH button

After confirming eligibility, render the Bank ACH button on your webpage. To render the Bank ACH button:
  1. Define the container for the Bank ACH button: In the HTML file corresponding to the webpage where you want to render the button, include a container element as shown in the sample code:
<section class="payment-method-container" id="bank-ach-section">
  <h2>Bank ACH Button</h2>
  <p>
    This example uses the <code>BankAchButton</code> web component to
    launch the popup flow
  </p>
  {/* Bank ACH Button will be added dynamically if eligible */}
</section>
  1. Add the Bank ACH button element: Dynamically insert the Bank ACH button HTML element into the container. The <bank-ach-button> web component supports id, a unique identifier for the button element.
  2. Create vault setup token and payment session: After rendering the button, call createVaultSetupToken() to get the vault setup token, then use sdkInstance.createBankAchSavePaymentSession() to create the payment session with the event handlers. See Create vault setup token function for the complete client and server implementation code.
  3. Attach the event handler: Use addEventListener() to register the onClick callback handler that executes when customers select the Bank ACH button.
// isAchEligible returns true if ACH payments are available for your configuration.

if (isAchEligible) {
  // Step 2: Render Bank ACH button component
  const achSection = document.querySelector("#bank-ach-section");
  const achButton = document.createElement("bank-ach-button");
  const achBtnId = "paypal-bank-ach-button";
  achButton.id = achBtnId;
  achSection.appendChild(achButton);

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

  // Step 4: Set up event listener for the Bank ACH button
  const bankButton = document.querySelector(`#${achBtnId}`);
  bankButton.addEventListener("click", onClick);
} else {
  console.log("ACH is not eligible");
}
Result: Successful button rendering and session setup:
  • Sets up the Bank ACH button on your webpage.
  • Creates the vault setup token and payment session.
  • Defines the on-click flow for your button.

Create vault setup token

The createVaultSetupToken() function creates a vault setup token for saving payment methods without an initial payment. This token is different from the browser-safe client token and is used to configure the payment session. In the server-side 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 in the request body:
    • 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.
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);
  }
}

Define on-click event handler function

Create an event handler to process Bank ACH button clicks and start the authentication session. Since the vault setup token and payment session are already created, this function only needs to start the authentication flow.
async function onClick() {
  try {
    // Start the pre-configured authentication flow
    console.log("Starting bank account authentication...");
    await bankAchSession.start({
      presentationMode: "popup",
    });
  } catch (error) {
    console.error("Error starting ACH flow:", error);
  }
}

Set up event handlers

Event handlers manage the different outcomes when your customers attempt to save ACH payment methods. Configure event handlers for the Bank ACH session lifecycle.

a. Handle approval

In your app code, include the logic to handle when the customer successfully authenticates their bank account. Use the createPaymentToken() method to convert the vault setup token to a reusable payment token and include setupToken, the vault setup token received from the approval event (from data.vaultSetupToken). Response: Contains paymentToken - the reusable payment token ID for future transactions.
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);
  }
}
Create payment method token
Convert the setup token to a reusable payment token for future transactions. This process is triggered when the customer successfully authenticates their bank account. In the server-side call to the /v3/vault/payment-tokens endpoint:
  1. Use a Bearer token with full-scope access token in the Authorization header.
  2. Include the following in the request body:
    • payment_source.token.id: Set to the vaultSetupToken received from the client.
    • payment_source.token.type: Set to SETUP_TOKEN to specify the token type.
Response: Contains the reusable payment token in the id response parameter, which is the permanent token for future transactions.
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);
  }
}

b. Handle cancellation

In your app code, include the logic to handle when the customer cancels the bank account linking process. Use the onCancel() callback function to handle the cancellation event. This function accepts the optional data parameter containing cancellation details. Response: Handles cancellation events when the customer exits the bank account linking process without completing it.
function onCancel(data) {
  console.log("ACH Save Cancelled!", data);
  console.log("Customer cancelled the bank account linking process");
}

c. Handle errors

In your app code, include the logic to handle errors that occur during the bank account authentication process. Use the onError() callback function to process error events and include the data parameter containing error message and details. Response: Processes error events and provides error details for troubleshooting authentication failures.
function onError(data) {
  console.log("ACH Save Error:", data);
  console.error(`Error occurred during bank account linking: ${data.message || 'Unknown error'}`);
}

d. Handle completion

In your app code, include the logic to handle when the authentication flow finishes. Use the onComplete() callback function to perform cleanup operations and finalize the ACH flow. This function accepts the optional data parameter containing completion status and details. Response: Performs any necessary cleanup and resets the payment flow state.
function onComplete(data) {
  console.log("ACH Save Complete:", data);
  console.log("Bank account authentication flow completed");
}

Test and go live

Test

Test your Save ACH integration in the PayPal sandbox environment. Test the integration flow:
  1. Load your integration page and verify ACH eligibility checking works correctly.
  2. Click the Bank ACH button to start the bank account authentication process.
  3. Complete the authentication flow and verify the onApprove callback receives the vault setup token as documented in the Set up event handlers section.
  4. Confirm your server successfully creates a payment method token using the vault setup token as documented in the Create payment method token section.
  5. Verify the authentication flow starts correctly when clicking the button as documented in the Define on-click event handler function section.
Test error handling:
  1. Cancel the authentication process and verify the onCancel callback executes as documented in the Set up event handlers section.
  2. Test error scenarios and verify the onError callback handles failures appropriately as documented in the Set up event handlers section.
  3. Confirm the onComplete callback executes when the flow finishes as documented in the Set up event handlers section.

Go live

Update to production environment:
  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 Client 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.
I