Skip to content
Open
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
19 changes: 19 additions & 0 deletions client/components/cardFields/oneTimePayment/html/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# One-Time Payments HTML Sample Integration

This HTML sample integration uses HTML, JavaScript, and CSS. It does not require a build process to transpile the source code. It's just static files that can be served up by any web server. [Vite](https://vite.dev/) is used for the local web server to provide the following functionality:

1. Serve up the static HTML and JavaScript files.
2. Proxy the API server so both the client and server are running on port 3000.

## How to Run Locally

```bash
npm install
npm start
```

### Sample Integrations

| Sample Integration | Description |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Recommended](src/recommended/index.html) | Start with this recommended sample integration. It displays all buttons supported by the v6 SDK and includes eligibility logic. It uses the "auto" presentation mode which attempts to launch a popup window and then automatically falls back to the modal presentation mode when the browser does not support popups. |
Comment on lines +17 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be updated?

19 changes: 19 additions & 0 deletions client/components/cardFields/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"
}
}
19 changes: 19 additions & 0 deletions client/components/cardFields/oneTimePayment/html/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>One-Time Payment Sample Integrations - PayPal Web SDK</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<h1>One-Time Payment Integrations</h1>

<ul>
<li>
<a href="/recommended/index.html"
>Recommended integration with PayPal Card Fields
</a>
</li>
</ul>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
async function onPayPalWebSdkLoaded() {
try {
const clientToken = await getBrowserSafeClientToken();
const sdkInstance = await window.paypal.createInstance({
clientToken,
components: ["card-fields"],
pageType: "checkout",
});

// const paymentMethods = await sdkInstance.findEligibleMethods({
// currencyCode: "USD",
// });

// NOTE: Eligibility does not currently return card
// if (paymentMethods.isEligible("card")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we enable this eligibility check?

Suggested change
// if (paymentMethods.isEligible("card")) {
if (paymentMethods.isEligible("card")) {

// setupCardFields(sdkInstance);
// }
setupCardFields(sdkInstance);
} catch (error) {
console.error(error);
}
}

async function setupCardFields(sdkInstance) {
const cardFieldsInstance =
sdkInstance.createCardFieldsOneTimePaymentSession();

const numberField = cardFieldsInstance.createCardFieldsComponent({
type: "number",
placeholder: "Enter a number:",
ariaDescription: "card-number-field",
ariaLabel: "some-values",
style: {
input: {
color: "green",
},
},
// SDK to take care of aria-invalid when the value is invalid on Change
});
const cvvField = cardFieldsInstance.createCardFieldsComponent({
type: "cvv",
placeholder: "Enter CVV:",
});
const expiryField = cardFieldsInstance.createCardFieldsComponent({
type: "expiry",
placeholder: "Enter Expiry:",
});

document.querySelector("#paypal-card-fields-number").appendChild(numberField);
document.querySelector("#paypal-card-fields-cvv").appendChild(cvvField);
document.querySelector("#paypal-card-fields-expiry").appendChild(expiryField);

const payButton = document.querySelector("#save-payment-method-button");
payButton.addEventListener("click", async () => {
try {
const orderId = await createOrder();
const { data, state } = await cardFieldsInstance.submit(orderId, {
billingAddress: {
postalCode: "95131",
},
});

switch (state) {
case "succeeded": {
const { orderId, ...liabilityShift } = data;
const orderData = await captureOrder({
orderId: data.orderId,
headers: { "X-CSRF-TOKEN": "<%= csrfToken %>" },
});
}
case "canceled": {
// specifically for if buyer cancels 3DS modal
const { orderId } = data;
}
case "failed": {
const { orderId, message } = data;
}
}
} catch (error) {
console.error(error);
}
});
}

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() {
const response = await fetch(
"/paypal-api/checkout/orders/create-with-sample-data",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
},
);
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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!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>
.card-fields-container {
display: flex;
flex-direction: column;
gap: 12px;
}
</style>
</head>
<body>
<h1>One-Time Payment Recommended Integration - Card Fields</h1>

<div class="card-fields-container">
<div class="card-field" id="paypal-card-fields-number"></div>
<div class="card-field" id="paypal-card-fields-cvv"></div>
<div class="card-field" id="paypal-card-fields-expiry"></div>
</div>
<button id="pay-button" class="pay-button">Pay</button>
<script src="app.js"></script>

<script
async
src="https://www.sandbox.paypal.com/web-sdk/v6/core"
onload="onPayPalWebSdkLoaded()"
></script>
</body>
</html>
17 changes: 17 additions & 0 deletions client/components/cardFields/oneTimePayment/html/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [],
root: "src",
server: {
port: 3000,
proxy: {
"/paypal-api": {
target: "http://localhost:8080",
changeOrigin: true,
secure: false,
},
},
},
});
19 changes: 19 additions & 0 deletions client/components/cardFields/savePayment/html/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "v6-web-sdk-sample-integration-client",
"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"
}
}
118 changes: 118 additions & 0 deletions client/components/cardFields/savePayment/html/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
async function onPayPalWebSdkLoaded() {
try {
const clientToken = await getBrowserSafeClientToken();
const sdkInstance = await window.paypal.createInstance({
clientToken,
components: ["card-fields"],
pageType: "checkout",
});

// const paymentMethods = await sdkInstance.findEligibleMethods({
// currencyCode: "USD",
// paymentFlow: "VAULT_WITHOUT_PAYMENT",
// });

// NOTE: Eligibility does not currently return card
// if (paymentMethods.isEligible("card")) {
// setupCardFields(sdkInstance);
// }
setupCardFields(sdkInstance);
} catch (error) {
console.error(error);
}
}
async function setupCardFields(sdkInstance) {
const cardFieldsInstance = sdkInstance.createCardFieldsSavePaymentSession();

const numberField = cardFieldsInstance.createCardFieldsComponent({
type: "number",
placeholder: "Enter a number:",
ariaDescription: "card-number-field",
ariaLabel: "some-values",
style: {
input: {
color: "green",
},
},
});
const cvvField = cardFieldsInstance.createCardFieldsComponent({
type: "cvv",
placeholder: "Enter CVV:",
});
const expiryField = cardFieldsInstance.createCardFieldsComponent({
type: "expiry",
placeholder: "Enter Expiry:",
});

document.querySelector("#paypal-card-fields-number").appendChild(numberField);
document.querySelector("#paypal-card-fields-cvv").appendChild(cvvField);
document.querySelector("#paypal-card-fields-expiry").appendChild(expiryField);

const payButton = document.querySelector("#pay-button");
payButton.addEventListener("click", async () => {
try {
const vaultSetupToken = await createVaultSetupToken();
const { data, state } = await cardFieldsInstance.submit(vaultSetupToken, {
billingAddress: {
postalCode: "95131",
},
});

switch (state) {
case "succeeded": {
const { vaultSetupToken, ...liabilityShift } = data;
const paymentToken = await createPaymentToken({
vaultSetupToken: data.vaultSetupToken,
headers: { "X-CSRF-TOKEN": "<%= csrfToken %>" },
});
}
case "canceled": {
// specifically for if buyer cancels 3DS modal
const { orderId } = data;
}
case "failed": {
const { orderId, message } = data;
}
}
} catch (error) {
console.error(error);
}
});
}

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 createVaultSetupToken() {
const response = await fetch("/paypal-api/vault/setup-token/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const { id } = await response.json();

return { vaultSetupToken: id };
}

async function createPaymentToken({ vaultSetupToken }) {
const response = await fetch(`/paypal-api/vault/payment-token/create`, {
body: JSON.stringify({ vaultSetupToken }),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();

return data;
}
Loading