> ## 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.

# Accept one-time payments through ACH (JavaScript SDK integration)

You can use PayPal's JavaScript SDK v6 to accept Automated Clearing House (ACH) Direct Debit payments 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>

### Load and use JavaScript SDK v6 along with v5

If you have an existing JavaScript SDK v5 integration and want to integrate JavaScript SDK v6 on the same page, add the `data-namespace` attribute to your existing v5 `<script>` tag.

<CodeGroup>
  ```html HTML theme={null}
  <script
      src="https://www.paypal.com/sdk/js?client-id=test"
      data-namespace="paypal_v5">
  </script>
  ```
</CodeGroup>

This ensures that:

* You access the v5 SDK via `window.paypal_v5` instead of `window.paypal`.
* JavaScript SDK v6 can attach to `window.paypal` without naming collisions.

The following example shows how to use both SDKs on the same page:

<CodeGroup>
  ```html HTML lines expandable theme={null}
  <!-- JavaScript SDK v5 -->
  <script
      src="https://www.paypal.com/sdk/js?client-id=test"
      data-namespace="paypal_v5">
  </script>

  <!-- JavaScript SDK v6 -->
  <script
      src="https://www.paypal.com/web-sdk/v6/core">
  </script>

  <script>
      // Access v5 SDK via the namespace "window.paypal_v5"
      window.paypal_v5.Buttons().render('#v5-button-container');

      // Access v6 SDK via default namespace "window.paypal"
      window.paypal.createInstance({ clientToken })
  </script>
  ```
</CodeGroup>

## Initialize JavaScript SDK and create SDK instance

[Get a client token](/developer/how-to/sdk/js/v6/configuration#get-client-token) and create an instance with the following code sample:

```javascript client-side - app.js lines expandable theme={null}
const clientToken = await getClientToken();
const sdkInstance = await window.paypal.createInstance({
  clientToken,
  // this merchantId value is automatically passed through to the eligibility call
  merchantId: "ABC123",
  components: ["bank-ach-payments"],
  pageType: "checkout",
});
```

## 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.

<Warning>
  PayPal performs a risk assessment on each transaction. The eligibility to render the Pay with Bank - ACH button depends on the risk profile.
</Warning>

<Tabs>
  <Tab title="Merchants">
    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 `ONE_TIME_PAYMENT`. JavaScript SDK retrieves all payment methods through which the merchant is eligible to accept one-time payments.                      |
    | `amount`       | Set to the total order amount that includes item cost, applicable taxes, shipping costs, handling cost, insurance cost, and discounts if any. Example: `100.00`. |

    **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}
      const options = {
        currencyCode: "USD",
        paymentFlow: "ONE_TIME_PAYMENT",
        amount: "100.00"
      };

      // Query for available payment methods that support one-time payments
      const paymentMethods = await sdkInstance.findEligibleMethods(options);

      // Check if ACH is one of the available payment methods
      const isAchEligible = paymentMethods.isEligible("ach");
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Partners">
    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 `ONE_TIME_PAYMENT` to process a single payment. JavaScript SDK retrieves all payment methods through which the sub-merchants are eligible to accept one-time payments. |
    | `amount`       | Set to the total order amount that includes item cost, applicable taxes, shipping costs, handling cost, insurance cost, and discounts if any. Example: `100.00`.              |

    **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}
      const options = {
        currencyCode: "USD",
        paymentFlow: "ONE_TIME_PAYMENT",
        amount: "100.00",
        merchantId: "YOUR_MERCHANT_ID" // Required if PayPal-Auth-Assertion header is not used when creating browser-safe client token
      };

      // Query for available payment methods that support one-time payments
      const paymentMethods = await sdkInstance.findEligibleMethods(options);

      // Check if ACH is one of the available payment methods
      const isAchEligible = paymentMethods.isEligible("ach");
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## 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 payment session**: Use `sdkInstance.createBankAchOneTimePaymentSession()` to create a payment session and register the `onApprove()`, `onCancel()`, and `onError()` event handlers.

3. **Attach the onClick event handler and display the button**: Use `addEventListener()` to attach the [`onClick` event handler](#create-order-and-start-authentication-flow) 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 payment session
    const bankAchSession = sdkInstance.createBankAchOneTimePaymentSession({
      onApprove,
      onCancel,
      onError,
    });

    // Step 3: 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>

## Create order and start authentication flow

<Tabs>
  <Tab title="Merchants">
    In your client-side code:

    1. Include an event-handler function that is triggered when the user clicks the Pay with Bank - ACH button. The function calls the `createOrder()` function, receives the order ID from the server-side code, passes it to the JavaScript SDK, and starts the authentication (account verification) flow.

    <Note>
      JavaScript SDK uses the order ID to confirm the payment source as `bank.ach_debit` to PayPal servers.
    </Note>

    2. Include the `createOrder()` function that constructs a payload, calls the server-side code to create an order, and passes the payload. Include the following parameters in the payload:

    | Parameter                               | Action                                                                                                                                                                                                                                                                                                                                                                                              |
    | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `intent`                                | Set to `CAPTURE` to capture payment immediately after approval.                                                                                                                                                                                                                                                                                                                                     |
    | `purchase_units[].amount.currency_code` | Set to the three-letter currency code. Example: `USD`.                                                                                                                                                                                                                                                                                                                                              |
    | `purchase_units[].amount.value`         | Set to the total order amount that includes item cost, applicable taxes, shipping costs, handling cost, insurance cost, and discounts if any. Example: `100.00`.<br /><br /><Note>If there is a mismatch between `purchase_units[].amount.value` and the amount specified in the `findEligibleMethods()` method, PayPal considers `purchase_units[].amount.value` as the total order amount.</Note> |

    In your server-side code, include the code to:

    1. Use a valid full-scope `Bearer` access token, make a POST call to the `/v2/checkout/orders` endpoint, and pass the payload received from client-side.
    2. Receive the response to the Create Order call. The response contains the order ID.
    3. Pass the order ID to the client-side code.

    <CodeGroup>
      ```javascript client-side - app.js theme={null}
      async function onClick() {
        try {
          console.log("Starting bank account authentication...");
          const startOptions = {
                presentationMode: "popup",
          };

          const checkoutOptionsPromise = createOrder().then((id) => ({
                orderId: id,
          }));

          // Start the pre-configured authentication flow
          await bankAchSession.start(startOptions, checkoutOptionsPromise);
        } catch (error) {
          console.error("Error starting ACH flow:", error);
        }
      }

      async function createOrder() {
        console.log("Creating order...");
        const orderPayload = {
          cart: [
            {
              id: "TSHIRT_GREEN_001",
              quantity: "1",
            },
          ],
        };
        try {
          const response = await fetch("/your-server/api/paypal/order", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(orderPayload),
          });
          const data = await response.json();
          if (!response.ok) {
            throw new Error(data.message || "Failed to create order");
          }
          console.log(`Order created: ${data.id}`);
          return data.id;
        } catch (error) {
          console.error("Error creating order:", error);
          throw error;
        }
      }
      ```

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

      // Express route to create order for ACH
      app.post(
        "/your-server/api/paypal/order",
        async (request, response, next) => {
          try {
            console.log("Creating PayPal order...");
            const { access_token: accessToken } = await getFullScopeAccessToken();
            const requestHeaders = {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
              "PayPal-Request-Id": randomUUID(), // For idempotency
              Prefer: "return=minimal",
            };
            const { cart } = request.body;
            function getProductDetails(productId) {
              return {
                category: "PHYSICAL_GOODS",
                description: "Super Fresh Shirt",
                image_url:
                  "https://example.com/static/images/items/1/tshirt_green.jpg",
                name: "T-Shirt",
                unit_amount: {
                  currency_code: "USD",
                  value: "220.00",
                },
                url: "https://example.com/product/tshirt-green",
                upc: {
                  code: "123456789012",
                  type: "UPC-A"
                }
              };
            }
            const productId = cart?.[0]?.id;
            const productQuantity = cart?.[0]?.quantity;
            const productDetails = getProductDetails(productId);
            const orderRequestBody = {
              intent: "CAPTURE",
              purchase_units: [
                {
                  amount: {
                    breakdown: {
                      item_total: {
                        currency_code: "USD",
                        value: productDetails.unit_amount.value,
                      },
                      shipping: {
                        currency_code: "USD",
                        value: "10.00",
                      },
                    },
                    currency_code: "USD",
                    value: "230.00",
                  },
                  items: [
                    {
                      ...productDetails,
                      quantity: productQuantity,
                      sku: productId,
                    }
                  ],
                },
              ],
            }
            const apiResponse = await fetch(
              `${config.apiBaseUrl}/v2/checkout/orders`,
              {
                body: JSON.stringify(orderRequestBody),
                headers: requestHeaders,
                method: "POST",
              },
            );
            const data = await apiResponse.json();
            if (apiResponse.ok) {
              console.log(`Order created successfully: ${data.id}`);
              return response.send(data);
            } else {
              console.error("Order creation failed:", data);
              return response.status(apiResponse.status).send(data);
            }
          } catch (error) {
            console.error("Error creating order:", error);
            return response.status(500).send({
              error: "Failed to create order",
              details: error.message
            });
          }
        }
      );
      ```

      ```bash server-side - cURL lines expandable theme={null}
      curl -s -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders \
        -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -H "PayPal-Request-Id: REQUEST-ID" \
        -H "Prefer: return=minimal" \
        -d '{
          "intent": "CAPTURE",
          "purchase_units": [
              {
                  "amount": {
                      "breakdown": {
                          "item_total": {
                              "currency_code": "USD",
                              "value": "220.00"
                          },
                          "shipping": {
                              "currency_code": "USD",
                              "value": "10.00"
                          }
                      },
                      "currency_code": "USD",
                      "value": "230.00"
                  },
                  "items": [
                      {
                          "category": "PHYSICAL_GOODS",
                          "description": "Super Fresh Shirt",
                          "image_url": "https://example.com/static/images/items/1/tshirt_green.jpg",
                          "name": "T-Shirt",
                          "unit_amount": {
                              "currency_code": "USD",
                              "value": "220.00"
                          },
                          "url": "https://example.com/product/tshirt-green",
                          "upc": {
                              "code": "123456789012",
                              "type": "UPC-A"
                          },
                          "quantity": "1",
                          "sku": "TSHIRT_GREEN_001"
                      }
                  ]
              }
          ]
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Partners">
    In your client-side code:

    1. Include an event-handler function that is triggered when the user clicks the Pay with Bank - ACH button. The function calls the `createOrder()` function, receives the order ID from the server-side code, passes it to the JavaScript SDK, and starts the authentication (account verification) flow.

    <Note>
      JavaScript SDK uses the order ID to confirm the payment source as `bank.ach_debit` to PayPal servers.
    </Note>

    2. Include the `createOrder()` function that constructs a payload, calls the server-side code to create an order, and passes the payload. Pass the cart context as a part of the payload.

    In your server-side code:

    1. Use a valid full scope `Bearer` access token.
    2. Make a `POST` call to `/v2/checkout/orders` with [`PayPal-Auth-Assertion`](/developer/how-to/api/make-api-requests#paypal-auth-assertion) and [`PayPal-Partner-Attribution-Id`](/developer/how-to/api/make-api-requests#paypal-partner-attribution-id) headers.
    3. Include the following parameters in the payload:

    | Parameter                                                                                                                                                                | Action                                                                                                                                                                                                                                                                                                                                                                                              |
    | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `intent`                                                                                                                                                                 | Set to `CAPTURE` to capture payment immediately after approval.                                                                                                                                                                                                                                                                                                                                     |
    | `purchase_units[].amount.currency_code`                                                                                                                                  | Set to the three-letter currency code. Example: `USD`.                                                                                                                                                                                                                                                                                                                                              |
    | `purchase_units[].amount.value`                                                                                                                                          | Set to the total order amount that includes item cost, applicable taxes, shipping costs, handling cost, insurance cost, and discounts if any. Example: `100.00`.<br /><br /><Note>If there is a mismatch between `purchase_units[].amount.value` and the amount specified in the `findEligibleMethods()` method, PayPal considers `purchase_units[].amount.value` as the total order amount.</Note> |
    | `purchase_units[].payee.merchant_id` \[<span style={{color: 'red', fontSize: 'smaller'}}>Required to identify specific merchants in a multi-seller purchase flow</span>] | Set to your merchant's encrypted PayPal account ID.                                                                                                                                                                                                                                                                                                                                                 |

    4. Recieve the response to the create order call. The response contains the order ID.
    5. Pass the order ID to the client-side code.

    <CodeGroup>
      ```javascript client-side - app.js lines expandable theme={null}
      async function onClick() {
        try {
          console.log("Starting bank account authentication...");
          const startOptions = {
                presentationMode: "popup",
          };

          const checkoutOptionsPromise = createOrder().then((id) => ({
                orderId: id,
          }));

          // Start the pre-configured authentication flow
          await bankAchSession.start(startOptions, checkoutOptionsPromise);
        } catch (error) {
          console.error("Error starting ACH flow:", error);
        }
      }

      async function createOrder() {
        console.log("Creating order...");
        const orderPayload = {
          cart: [
            {
              id: "TSHIRT_GREEN_001",
              quantity: "1",
            },
          ],
        };
        try {
          const response = await fetch("/your-server/api/paypal/order", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(orderPayload),
          });
          const data = await response.json();
          if (!response.ok) {
            throw new Error(data.message || "Failed to create order");
          }
          console.log(`Order created: ${data.id}`);
          return data.id;
        } catch (error) {
          console.error("Error creating order:", error);
          throw error;
        }
      }
      ```

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

      // Express route to create order for ACH
      app.post(
        "/your-server/api/paypal/order",
        async (request, response, next) => {
          try {
            console.log("Creating PayPal order...");
            const { access_token: accessToken } = await getFullScopeAccessToken();
            const requestHeaders = {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
              "PayPal-Auth-Assertion": "PAYPAL-AUTH-ASSERTION", // Replace with generated assertion
              "PayPal-Partner-Attribution-Id": "YOUR-BN-CODE",
              "PayPal-Request-Id": randomUUID(), // For idempotency
              Prefer: "return=minimal",
            };
            const { cart } = request.body;
            function getProductDetails(productId) {
              return {
                category: "PHYSICAL_GOODS",
                description: "Super Fresh Shirt",
                image_url:
                  "https://example.com/static/images/items/1/tshirt_green.jpg",
                name: "T-Shirt",
                unit_amount: {
                  currency_code: "USD",
                  value: "220.00",
                },
                url: "https://example.com/product/tshirt-green",
                upc: {
                  code: "123456789012",
                  type: "UPC-A"
                }
              };
            }
            const productId = cart?.[0]?.id;
            const productQuantity = cart?.[0]?.quantity;
            const productDetails = getProductDetails(productId);
            const orderRequestBody = {
              intent: "CAPTURE",
              purchase_units: [
                {
                  amount: {
                    breakdown: {
                      item_total: {
                        currency_code: "USD",
                        value: productDetails.unit_amount.value,
                      },
                      shipping: {
                        currency_code: "USD",
                        value: "10.00",
                      },
                    },
                    currency_code: "USD",
                    value: "230.00",
                  },
                  items: [
                    {
                      ...productDetails,
                      quantity: productQuantity,
                      sku: productId,
                    }
                  ],
                },
              ],
            }
            const apiResponse = await fetch(
              `${config.apiBaseUrl}/v2/checkout/orders`,
              {
                body: JSON.stringify(orderRequestBody),
                headers: requestHeaders,
                method: "POST",
              },
            );
            const data = await apiResponse.json();
            if (apiResponse.ok) {
              console.log(`Order created successfully: ${data.id}`);
              return response.send(data);
            } else {
              console.error("Order creation failed:", data);
              return response.status(apiResponse.status).send(data);
            }
          } catch (error) {
            console.error("Error creating order:", error);
            return response.status(500).send({
              error: "Failed to create order",
              details: error.message
            });
          }
        }
      );
      ```

      ```bash server-side - cURL lines expandable theme={null}
      curl -s -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders \
        -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -H "PayPal-Auth-Assertion: PAYPAL-AUTH-ASSERTION" \
        -H "PayPal-Partner-Attribution-Id: YOUR-BN-CODE" \
        -H "PayPal-Request-Id: REQUEST-ID" \
        -H "Prefer: return=minimal" \
        -d '{
          "intent": "CAPTURE",
          "purchase_units": [
              {
                  "amount": {
                      "breakdown": {
                          "item_total": {
                              "currency_code": "USD",
                              "value": "220.00"
                          },
                          "shipping": {
                              "currency_code": "USD",
                              "value": "10.00"
                          }
                      },
                      "currency_code": "USD",
                      "value": "230.00"
                  },
                  "items": [
                      {
                          "category": "PHYSICAL_GOODS",
                          "description": "Super Fresh Shirt",
                          "image_url": "https://example.com/static/images/items/1/tshirt_green.jpg",
                          "name": "T-Shirt",
                          "unit_amount": {
                              "currency_code": "USD",
                              "value": "220.00"
                          },
                          "url": "https://example.com/product/tshirt-green",
                          "upc": {
                              "code": "123456789012",
                              "type": "UPC-A"
                          },
                          "quantity": "1",
                          "sku": "TSHIRT_GREEN_001"
                      }
                  ]
              }
          ]
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Handle events

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

### a. Handle approval, get buyer's authorization, and capture payment

In your client-side code:

1. Include the `onApprove()` event-handler function that receives the order ID after the customer successfully verifies their bank account, calls the `getOrderDetails()` function, and passes the order ID.
2. Include the `getOrderDetails()` function that receives the order ID from the `onApprove()` event and calls the server-side code to get order details.

In your server-side code, include the code to:

1. Use a valid full-scope `Bearer` access token and make a GET call to `/v2/checkout/orders/{order_id}/`.
2. Receive the order details and render a debit authorization screen to get consent from the buyer.

<Warning>
  For all ACH transactions, you are required to collect an authorization from the customer indicating that you have their explicit permission to debit their bank account. To remain in compliance with NACHA regulations, store the full text of the authorization with the amount, bank details, and timestamp of authorization. You may be required to provide proof of authorization when requested by PayPal, the customer's bank, or NACHA.
</Warning>

**Sample authorization text (one-time payment)**

> "I authorize \[Merchant business name] to initiate a one-time ACH/electronic debit to my account as follows: Amount: \[insert amount], Authorization Date: \[insert date], Account holder: \[insert full name], Bank: \[insert bank name], \[Checking / Savings] Account Number: \[insert account number], Routing Number: \[insert routing number]. I agree that ACH transactions I authorize comply with all applicable laws." \[CTA: Agree and Continue]

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  async function onApprove(data) {
    try {
      console.log(`Order approved: ${data.orderId} — fetching details for authorization`);
      const orderDetails = await getOrderDetails(data.orderId);
      showDebitAuthorization(orderDetails, data.orderId);
    } catch (error) {
      console.error("Error loading authorization:", error.message);
    }
  }

  async function getOrderDetails(orderId) {
    const response = await fetch(`/your-server/api/paypal/order/${orderId}`);
    const data = await response.json();
    if (!response.ok) {
      throw new Error(data.error || "Failed to fetch order details");
    }
    return data;
  }

  function showDebitAuthorization(orderDetails, orderId) {
    // Debit authorization content and storage to be hosted by Merchant
  }
  ```

  ```javascript server-side - Node.js theme={null}
  app.get(
    "/your-server/api/paypal/order/:orderId",
    async (request, response) => {
      const { orderId } = request.params;
      try {
        const { access_token: accessToken } = await getFullScopeAccessToken();

        const apiResponse = await fetch(
          `${config.apiBaseUrl}/v2/checkout/orders/${orderId}`,
          {
            headers: {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
            },
            method: "GET",
          }
        );

        const data = await apiResponse.json();

        if (!apiResponse.ok) {
          return response.status(apiResponse.status).send({ error: "Failed to fetch order" });
        }

        const achDebit = data.payment_source?.bank?.ach_debit;
        const bankName = achDebit?.bank_name;
        const lastDigits = achDebit?.last_digits;
        const accountHolderName = achDebit?.account_holder_name;

        const purchaseUnit = data.purchase_units?.[0];
        const amount = purchaseUnit?.amount?.value;
        const currencyCode = purchaseUnit?.amount?.currency_code;

        // Use the above data to receive debit authorization from the buyer
        // (using the sample consent content suggested above)
      } catch (error) {
        console.error("Error fetching order:", error);
        return response.status(500).send({ error: "Failed to fetch order details" });
      }
    }
  );
  ```

  ```bash server-side - cURL theme={null}
  curl -v -X GET 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{orderId}' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer ACCESS-TOKEN'
  ```
</CodeGroup>

After the buyer authorizes the direct debit, capture the payment. Before capturing an order using ACH direct debit, the order status must be `APPROVED`.

In your client-side code:

1. Include an event-handler function (for example, `handleCapture`) that receives the order ID after the customer authorizes the debit, calls the `captureOrder()` function, and passes the order ID.
2. Include the `captureOrder()` function that calls the server-side code to capture the payment and displays the transaction results to the customer.

In your server-side code, include the code to make a POST call to `/v2/checkout/orders/{order_id}/capture` and pass the captured order details to the client-side code.

<Tabs>
  <Tab title="Merchants">
    <CodeGroup>
      ```javascript client-side - app.js theme={null}
      async function handleCapture(orderId) {
        try {
          console.log("Processing ACH payment capture...");
          console.log(`Capturing order ${orderId}...`);

          const orderData = await captureOrder({ orderId });

          console.log("✅ Payment captured successfully");
          console.log(`Status: ${orderData.status}`);
        } catch (error) {
          console.error("❌ Error capturing payment:", error.message);
        }
      }

      async function captureOrder({ orderId }) {
        console.log(`Capturing order ${orderId}`);

        try {
          const response = await fetch(
            `/your-server/api/paypal/order/${orderId}`,
            {
              method: "PUT",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({}),
            },
          );

          const data = await response.json();

          if (!response.ok) {
            throw new Error(data.message || "Failed to capture order");
          }

          return data;
        } catch (error) {
          console.error("Error capturing order:", error);
          throw error;
        }
      }
      ```

      ```javascript server-side - Node.js theme={null}
      // Express route to capture order for ACH
      router.put(
        "/your-server/api/paypal/order/:orderId",
        async (request, response, next) => {
          const { orderId } = request.params;
          const uniqueRequestId = randomUUID();

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

            const requestHeaders = {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
              "PayPal-Request-Id": uniqueRequestId,
              Prefer: "return=representation",
            };

            const apiResponse = await fetch(
              `${config.apiBaseUrl}/v2/checkout/orders/${orderId}/capture`,
              {
                body: JSON.stringify({}),
                headers: requestHeaders,
                method: "POST",
              },
            );

            const data = await apiResponse.json();

            // For 5xx errors, implement idempotent retry
            if (apiResponse.status >= 500) {
              // Wait 2 seconds
              await new Promise((resolve) => setTimeout(resolve, 2000));

              // Retry with same request ID
              const retryResponse = await fetch(
                `${config.apiBaseUrl}/v2/checkout/orders/${orderId}/capture`,
                {
                  body: JSON.stringify({}),
                  headers: requestHeaders,
                  method: "POST",
                },
              );

              const retryData = await retryResponse.json();
              return response.status(retryResponse.status).send(retryData);
            }

            if (apiResponse.ok) {
              return response.send({
                id: data.id,
                status: data.status,
                transactionId: data.purchase_units?.[0]?.payments?.captures?.[0]?.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/v2/checkout/orders/{orderId}/capture' \
        -H 'Content-Type: application/json' \
        -H 'Authorization: Bearer ACCESS-TOKEN' \
        -H 'PayPal-Request-Id: REQUEST-ID' \
        -H 'Prefer: return=representation' \
        -d '{}'
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Partners">
    Use a valid full-scope `Bearer` access token and include `PayPal-Auth-Assertion` and `PayPal-Partner-Attribution-Id` headers. See <a href="https://developer.paypal.com/docs/multiparty/checkout/standard/integrate/#generate-paypal-auth-assertion-header" target="_blank">Generate PayPal-Auth-Assertion</a>.

    <CodeGroup>
      ```javascript client-side - app.js theme={null}
      async function handleCapture(orderId) {
        try {
          console.log("Processing ACH payment capture...");
          console.log(`Capturing order ${orderId}...`);

          const orderData = await captureOrder({ orderId });

          console.log("✅ Payment captured successfully");
          console.log(`Status: ${orderData.status}`);
        } catch (error) {
          console.error("❌ Error capturing payment:", error.message);
        }
      }

      async function captureOrder({ orderId }) {
        console.log(`Capturing order ${orderId}`);

        try {
          const response = await fetch(
            `/your-server/api/paypal/order/${orderId}`,
            {
              method: "PUT",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({}),
            },
          );

          const data = await response.json();

          if (!response.ok) {
            throw new Error(data.message || "Failed to capture order");
          }

          return data;
        } catch (error) {
          console.error("Error capturing order:", error);
          throw error;
        }
      }
      ```

      ```javascript server-side - Node.js theme={null}
      // Express route to capture order for ACH
      router.put(
        "/your-server/api/paypal/order/:orderId",
        async (request, response, next) => {
          const { orderId } = request.params;
          const uniqueRequestId = randomUUID();

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

            const requestHeaders = {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
              "PayPal-Auth-Assertion": "PAYPAL-AUTH-ASSERTION", // Replace with generated assertion
              "PayPal-Partner-Attribution-Id": "YOUR-BN-CODE",
              "PayPal-Request-Id": uniqueRequestId,
              Prefer: "return=representation",
            };

            const apiResponse = await fetch(
              `${config.apiBaseUrl}/v2/checkout/orders/${orderId}/capture`,
              {
                body: JSON.stringify({}),
                headers: requestHeaders,
                method: "POST",
              },
            );

            const data = await apiResponse.json();

            // For 5xx errors, implement idempotent retry
            if (apiResponse.status >= 500) {
              // Wait 2 seconds
              await new Promise((resolve) => setTimeout(resolve, 2000));

              // Retry with same request ID
              const retryResponse = await fetch(
                `${config.apiBaseUrl}/v2/checkout/orders/${orderId}/capture`,
                {
                  body: JSON.stringify({}),
                  headers: requestHeaders,
                  method: "POST",
                },
              );

              const retryData = await retryResponse.json();
              return response.status(retryResponse.status).send(retryData);
            }

            if (apiResponse.ok) {
              return response.send({
                id: data.id,
                status: data.status,
                transactionId: data.purchase_units?.[0]?.payments?.captures?.[0]?.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/v2/checkout/orders/{orderId}/capture' \
        -H 'Content-Type: application/json' \
        -H 'Authorization: Bearer ACCESS-TOKEN' \
        -H 'PayPal-Auth-Assertion: PAYPAL-AUTH-ASSERTION' \
        -H 'PayPal-Partner-Attribution-Id: YOUR-BN-CODE' \
        -H 'PayPal-Request-Id: REQUEST-ID' \
        -H 'Prefer: return=representation' \
        -d '{}'
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### 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 payment processing. JavaScript SDK passes the cancellation details including the order ID to the function's `data` parameter.

<CodeGroup>
  ```javascript client-side - app.js theme={null}
  function onCancel(data) {
    console.log("ACH Payment Cancelled!", data);
    let message = "Customer cancelled the bank account payment process";
    if (data && data.orderId) {
      message += ` for order ${data.orderId}`;
    }
    console.log(message);
  }
  ```
</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 payment 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 Payment Error:", data);
    console.error(`Error occurred during bank account payment: ${data.message || data || 'Unknown error'}`);
  }
  ```
</CodeGroup>

## Test and go live

Test the end-to-end flow, test error handling, and then switch to production environment by changing the API endpoints and credentials.

### 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 order is created successfully when the authentication flow begins as documented in the [Create order and start authentication flow](#create-order-and-start-authentication-flow) section.

4. Complete the authentication flow and verify the `onApprove` callback receives the order ID as documented in the [Handle approval, get buyer's authorization, and capture payment](#a-handle-approval-get-buyers-authorization-and-capture-payment) section. When the bank selection popup appears, use the following sandbox test credentials:
   * **Bank selection**: Select **Open Finance Bank OAUTH** from the list of available banks.
   * **Username**: `john_surname`
   * **Password**: `ob_user`

5. Confirm your server successfully captures the payment using the order ID as documented in the [Handle approval, get buyer's authorization, and capture payment](#a-handle-approval-get-buyers-authorization-and-capture-payment) section.

6. Verify the authentication flow starts correctly with order creation as documented in the [Create order and start authentication flow](#create-order-and-start-authentication-flow) section.

### Test error handling

1. Cancel the authentication process and verify the `onCancel` callback executes with the order ID 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. Test idempotent retry logic for 5xx errors during order capture as documented in the [Handle approval, get buyer's authorization, and capture payment](#a-handle-approval-get-buyers-authorization-and-capture-payment) 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.
