Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions client/components/bank/oneTimePayment/html/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Bank ACH One-time Payment Sample Integration

This is a demo of how to integration Bank ACH One-time payment via PayPal Web SDK v6. Paypal SDK lets merchants provide Bank ACH as a payment method via plain HTML and javascript.

## 🏗️ Architecture Overview

This sample demonstrates a complete Bank ACH integration flow:

1. Initialize PayPal Web SDK with Bank ACH component
2. Create an order and authenticate payer's bank account
3. Handle bank ACH validation and order completion

### Prerequisites
Before running this demo, you'll need to set up accounts and configure your development environmnet

1. **PayPal Developer Account**
- Visit [developer.paypal.com](https://developer.paypal.com)
- Sign up for a developer account or log in with existing credentials
- Navigate to the **Apps & Credentials** section in your dashboard

2. **Create a PayPal Application** (or configure the default application)
- Click **Create App**
- Name your app
- Select **Merchant** under **Type**
- Choose the **Sandbox** account for testing
- Click **Create App** at the bottom of the modal
- Enable **Features** -> **Accept payments** -> **Bank ACH** (be sure to click **Save Changes** below)
- Note your **Client ID** and **Secret key** under **API credentials** for later configuration of the `.env` file


### Server Setup

1. **Navigate to the server directory:**

```bash
cd server/node
```

2. **Install dependencies:**

```bash
npm install
```

3. **Configure environment variables:**
Create a `.env` file in the root directory:

```env
PAYPAL_SANDBOX_CLIENT_ID=your_paypal_sandbox_client_id
PAYPAL_SANDBOX_CLIENT_SECRET=your_paypal_sandbox_client_secret
```

4. **Start the server:**
```bash
npm start
```
The server will run on `https://localhost:8080`

### Client Setup

1. **Navigate to the Apple Pay demo directory:**

```bash
cd client/components/applePay/html
```

2. **Install dependencies:**

```bash
npm install
```

3. **Start the development server:**
```bash
npm start
```
The demo will be available at `http://localhost:3000`


## 🧪 Testing the Integration

1. **Visit http://localhost:3000**
- Enter your Merchant ID
- Click the Bank Payment button
- The bank login popup will be displayed

3. **Complete bank login and account selection**
- Enter your credentials to get authenticated
- Select the bank and account you would like to use for testing

4. **Verify Results**
- Check the browser console logs for order capture details
- Check Event Logs -> API Calls at [developer.paypal.com](https://developer.paypal.com)
19 changes: 19 additions & 0 deletions client/components/bank/oneTimePayment/html/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "v6-web-sdk-sample-integration-client-html",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"preview": "vite preview",
"start": "vite",
"format": "prettier . --write",
"format:check": "prettier . --check",
"lint": "echo \"eslint is not set up\""
},
"license": "Apache-2.0",
"devDependencies": {
"prettier": "^3.6.2",
"vite": "^7.0.4"
}
}
130 changes: 130 additions & 0 deletions client/components/bank/oneTimePayment/html/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const getMerchantId = () => {
return document.getElementById('merchant-id-input').value
}

async function onPayPalWebSdkLoaded() {
try {
const clientToken = await getBrowserSafeClientToken();
const sdkInstance = await window.paypal.createInstance({
clientToken,
components: ["bank-ach-payments"],
pageType: "checkout",
});

const paymentMethods = await sdkInstance.findEligibleMethods({
apiCode: "restApi",
countryCode: "US",
currencyCode: "USD",
paymentFlow: "ONE_TIME_PAYMENT",
paymentMethods: ["ACH"],
merchantId: getMerchantId(),
});

if (paymentMethods.isEligible("ach")) {
setupAchButton(sdkInstance);
}
console.log('sdk loaded')
} catch (error) {
console.error(error);
}
}

const paymentSessionOptions = {
async onApprove(data) {
console.log("onApprove", data);
const orderData = await captureOrder({
orderId: data.orderId,
});
console.log("Capture order", orderData);
},
onCancel(data) {
console.log("onCancel", data);
},
onError(error) {
console.log("onError", error);
},
};


async function setupAchButton(sdkInstance) {
const achPaymentSession = sdkInstance.createBankAchOneTimePaymentSession(
paymentSessionOptions,
);
document.getElementById('ach-button-container').innerHTML = '<bank-ach-button id="ach-button">';
const onClick = async () => {
const startOptions = {
presentationMode: "popup",
}

const orderPayload ={
intent: 'CAPTURE',
purchaseUnits: [
{
amount: {
currencyCode: "USD",
value: "100.00",
},
payee: {
merchantId: getMerchantId()
}
},
]
};
const checkoutOptionsPromise = createOrder(orderPayload).then((orderId) => {
console.log("Created order", orderId);

return orderId
}
)
try {
await achPaymentSession.start(startOptions, checkoutOptionsPromise)
} catch(e) {
console.error(e)
}
}
const achButton = document.querySelector("#ach-button");

achButton.removeAttribute("hidden");
achButton.addEventListener("click", onClick);
}

async function getBrowserSafeClientToken() {
const response = await fetch("/paypal-api/auth/browser-safe-client-token", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const { accessToken } = await response.json();

return accessToken;
}

async function createOrder(orderPayload) {
const response = await fetch("/paypal-api/checkout/orders/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(orderPayload),
});
const { id } = await response.json();

return {orderId: id};
}


async function captureOrder({ orderId }) {
const response = await fetch(
`/paypal-api/checkout/orders/${orderId}/capture`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
},
);
const data = await response.json();

return data;
}
41 changes: 41 additions & 0 deletions client/components/bank/oneTimePayment/html/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>One-Time Payment - Recommended Integration - PayPal Web SDK</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
#ach-button-container {
display: flex;
flex-direction: column;
gap: 12px;
}
#merchant-id-input {
margin-top: 12px;
padding: 8px;
font-size: 16px;
width: 300px;
}
</style>
</head>
<body>
<h1>One-Time Payment Recommended Integration</h1>

<div id="ach-button-container">
<button id="ach-button" hidden>Pay with Bank Account</button>
</div>

<label for="merchant-id-input">Merchant ID:</label>
<input type="text" id="merchant-id-input" placeholder="Enter your merchant ID" />

<script src="app.js"></script>

<script type="module">
const script = document.createElement('script');
script.async = true;
script.src = 'https://www.sandbox.paypal.com/web-sdk/v6/core';
script.onload = () => window.onPayPalWebSdkLoaded();
document.head.appendChild(script);
</script>
</body>
</html>
19 changes: 19 additions & 0 deletions client/components/bank/oneTimePayment/html/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig(() => {
return {
plugins: [],
root: "src",
server: {
port: 3000,
proxy: {
"/paypal-api": {
target: "http://localhost:8080",
changeOrigin: true,
secure: false,
},
},
},
};
});