Initial clean V5 repository

This commit is contained in:
2026-05-13 09:43:32 -04:00
commit 5842222283
7264 changed files with 1322128 additions and 0 deletions

9
sl/.env Normal file
View File

@ -0,0 +1,9 @@
PORT=3000
#ENVIRONMENT=production
#CLIENT_ID=
#CLIENT_SECRET=
ENVIRONMENT=sandbox
CLIENT_ID=ASX6ldixTmfKnk1w7IKhfTyjTKmNwf8CBYD_vLdEz2-K0msADQmnp9MevIgIvjiSFzwGg3R4bZnvr1FV
CLIENT_SECRET=ELuQIBgqgd1mk_ChzOJivkYBbxFy2nBe1d4ED6fIfYFQ7r6-0oBqVBHF5G0WaywNxlGgI8LiyN5yhISP

21
sl/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 PayPal Developer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

43
sl/README.md Normal file
View File

@ -0,0 +1,43 @@
![PayPal Developer Cover](https://github.com/paypaldev/.github/blob/main/pp-cover.png)
<div align="center">
<a href="https://twitter.com/paypaldev" target="_blank">
<img alt="Twitter: PayPal Developer" src="https://img.shields.io/twitter/follow/paypaldev?style=social" />
</a>
<br />
<a href="https://twitter.com/paypaldev" target="_blank">Twitter</a>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<a href="https://www.paypal.com/us/home" target="_blank">PayPal</a>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<a href="https://developer.paypal.com/home" target="_blank">Docs</a>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<a href="https://github.com/paypaldev" target="_blank">Code Samples</a>
<span>&nbsp;&nbsp;-&nbsp;&nbsp;</span>
<a href="https://dev.to/paypaldeveloper" target="_blank">Blog</a>
<br />
<hr />
</div>
# PayPal Advanced Checkout Tutorial
This is the example code for the YouTube video, [How to Integrate PayPal Advanced Checkout](https://www.youtube.com/watch?v=pHbeILqVjOk)
## Getting Started
Grab your PayPal CLIENT ID and SECRET and fill them in the following files:
- [.env](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/.env)
- [script.js](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/script.js)
- If you want to use a port other than 3000, make sure you adjust the [.env file here](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/.env#L1) and also adjust that in the main javascript file in these locations:
- [script.js#L30](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/script.js#L30), [script.js#L97](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/script.js#L97), [script.js#L108](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/script.js#L108), [script.js#L140](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/script.js#L140), [script.js#L206](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/script.js#L206)
- If you want to use SendGrid for sending emails, add your SendGrid API Key in the [index.js file here](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/index.js#L160) and you can edit the email HTML content in the [email_content_html variable here](https://github.com/rauljr7/ppcp_advanced_acdc_tutorial/blob/main/index.js#L161)
Once you have configured your credentials, you can run `npm i` to install the packages and start your app with `node index`
## PayPal Developer Community
The PayPal Developer community helps you build your career, while also improving PayPal products and the developer experience. Youll be able to contribute code and documentation, meet new people and learn from the open source community.
- Website: [developer.paypal.com](https://developer.paypal.com)
- Twitter: [@paypaldev](https://twitter.com/paypaldev)
- Github: [@paypal](https://github.com/paypal)

60
sl/index.html Normal file
View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PPCP Advanced</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/minstyle.io@2.0.1/dist/css/minstyle.io.min.css">
<link rel="stylesheet" type="text/css" href="style.css">
<script src="script.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm"></div>
<div class="col-sm">
<h2 class="ms-text-center">ai-generated NFT Bored Ape</h2>
<div class="ms-text-center pb-2">
<div class="ms-label ms-large ms-action2 ms-light">$100.00 USD</div>
</div>
<div id="alerts" class="ms-text-center"></div>
<div id="loading" class="spinner-container ms-div-center">
<div class="spinner"></div>
</div>
<div id="content" class="hide">
<div class="ms-card ms-fill">
<div class="ms-card-content">
</div>
</div>
<div id="payment_options">
<form class="row ms-form-group" id="card-form">
<div>
<label for="card-number">Card Number</label>
<div class="div_input" type="text" id="card-number"></div>
</div>
<div class="col-md mb-2">
<label for="expiration-date">Expiration Date</label>
<div id="expiration-date" class="div_input"></div>
</div>
<div class="col-md mb-2">
<label for="cvv">Security Code</label>
<div id="cvv" class="div_input"></div>
</div>
<div>
<label for="email">Email</label>
<input value placeholder="username@email.com" type="email" id="email" class="div_input" required>
</div>
<div><input class="ms-fullwidth mt-2 ms-medium" type="submit" value="Purchase"></div>
</form>
<hr><hr>
</div>
</div>
</div>
<div class="col-sm"></div>
<footer style="margin-top:50px" class="ms-footer"> Footer Intentionally left empty :) </footer>
</div>
</div>
</body>
</html>

219
sl/index.js Normal file

File diff suppressed because one or more lines are too long

15
sl/package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "ppcp-starter-node",
"version": "1.0.0",
"description": "Starter code for a Node.js app for PayPal Complete Payments - Advanced Checkout: Advanced Credit and Debit Cards",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"dotenv": "^10.0.0",
"express": "^4.17.1",
"node-fetch": "^3.1.0"
}
}

234
sl/script.js Normal file
View File

@ -0,0 +1,234 @@
// Helper / Utility functions
let current_customer_id;
let order_id;
let script_to_head = (attributes_object) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
for (const name of Object.keys(attributes_object)) {
script.setAttribute(name, attributes_object[name]);
}
document.head.appendChild(script);
script.addEventListener('load', resolve);
script.addEventListener('error', reject);
});
}
let reset_purchase_button = () => {
document.querySelector("#card-form").querySelector("input[type='submit']").removeAttribute("disabled");
document.querySelector("#card-form").querySelector("input[type='submit']").value = "Purchase";
}
const is_user_logged_in = () => {
return new Promise((resolve) => {
customer_id = localStorage.getItem("logged_in_user_id") || "";
resolve();
});
}
const get_client_token = () => {
return new Promise(async (resolve, reject) => {
try {
const response = await fetch("http://localhost:3000/get_client_token", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ "customer_id": current_customer_id }),
});
const client_token = await response.text();
resolve(client_token);
} catch (error) {
reject(error);
}
});
}
let handle_close = (event) => {
event.target.closest(".ms-alert").remove();
}
let handle_click = (event) => {
if (event.target.classList.contains("ms-close")) {
handle_close(event);
}
}
document.addEventListener("click", handle_click);
const paypal_sdk_url = "https://www.paypal.com/sdk/js";
const client_id = "REPLACE_WITH_YOUR_CLIENT_ID";
const currency = "USD";
const intent = "capture";
let display_error_alert = () => {
document.getElementById("alerts").innerHTML = `<div class="ms-alert ms-action2 ms-small"><span class="ms-close"></span><p>An Error Ocurred! (View console for more info)</p> </div>`;
}
let display_success_message = (object) => {
order_details = object.order_details;
paypal_buttons = object.paypal_buttons;
console.log(order_details); //https://developer.paypal.com/docs/api/orders/v2/#orders_capture!c=201&path=create_time&t=response
let intent_object = intent === "authorize" ? "authorizations" : "captures";
//Custom Successful Message
document.getElementById("alerts").innerHTML = `<div class=\'ms-alert ms-action\'>Thank you ` + (order_details?.payer?.name?.given_name || ``) + ` ` + (order_details?.payer?.name?.surname || ``) + ` for your payment of ` + order_details.purchase_units[0].payments[intent_object][0].amount.value + ` ` + order_details.purchase_units[0].payments[intent_object][0].amount.currency_code + `!</div>`;
//Close out the PayPal buttons that were rendered
paypal_buttons.close();
document.getElementById("card-form").classList.add("hide");
}
//PayPal Code
is_user_logged_in()
.then(() => {
return get_client_token();
})
.then((client_token) => {
//https://developer.paypal.com/sdk/js/configuration/#link-queryparameters
return script_to_head({"src": paypal_sdk_url + "?client-id=" + client_id + "&enable-funding=venmo&currency=" + currency + "&intent=" + intent + "&components=buttons,hosted-fields", "data-client-token": client_token}) //https://developer.paypal.com/sdk/js/configuration/#link-configureandcustomizeyourintegration
})
.then(() => {
//Handle loading spinner
document.getElementById("loading").classList.add("hide");
document.getElementById("content").classList.remove("hide");
let paypal_buttons = paypal.Buttons({ // https://developer.paypal.com/sdk/js/reference
onClick: (data) => { // https://developer.paypal.com/sdk/js/reference/#link-oninitonclick
//Custom JS here
},
style: { //https://developer.paypal.com/sdk/js/reference/#link-style
shape: 'rect',
color: 'gold',
layout: 'vertical',
label: 'paypal'
},
createOrder: function(data, actions) { //https://developer.paypal.com/docs/api/orders/v2/#orders_create
return fetch("http://localhost:3000/create_order", {
method: "post", headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({ "intent": intent })
})
.then((response) => response.json())
.then((order) => { return order.id; });
},
onApprove: function(data, actions) {
order_id = data.orderID;
console.log(data);
return fetch("http://localhost:3000/complete_order", {
method: "post", headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
"intent": intent,
"order_id": order_id
})
})
.then((response) => response.json())
.then((order_details) => {
display_success_message({"order_details": order_details, "paypal_buttons": paypal_buttons});
})
.catch((error) => {
console.log(error);
display_error_alert()
});
},
onCancel: function (data) {
document.getElementById("alerts").innerHTML = `<div class="ms-alert ms-action2 ms-small"><span class="ms-close"></span><p>Order cancelled!</p> </div>`;
},
onError: function(err) {
console.log(err);
}
});
paypal_buttons.render('#payment_options');
//Hosted Fields
if (paypal.HostedFields.isEligible()) {
// Renders card fields
paypal_hosted_fields = paypal.HostedFields.render({
// Call your server to set up the transaction
createOrder: () => {
return fetch("http://localhost:3000/create_order", {
method: "post", headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({ "intent": intent })
})
.then((response) => response.json())
.then((order) => { order_id = order.id; return order.id; });
},
styles: {
'.valid': {
color: 'green'
},
'.invalid': {
color: 'red'
},
'input': {
'font-size': '16pt',
'color': '#ffffff'
},
},
fields: {
number: {
selector: "#card-number",
placeholder: "4111 1111 1111 1111"
},
cvv: {
selector: "#cvv",
placeholder: "123"
},
expirationDate: {
selector: "#expiration-date",
placeholder: "MM/YY"
}
}
}).then((card_fields) => {
document.querySelector("#card-form").addEventListener("submit", (event) => {
event.preventDefault();
document.querySelector("#card-form").querySelector("input[type='submit']").setAttribute("disabled", "");
document.querySelector("#card-form").querySelector("input[type='submit']").value = "Loading...";
card_fields
.submit(
//Customer Data BEGIN
//This wasn't part of the video guide originally, but I've included it here
//So you can reference how you could send customer data, which may
//be a requirement of your project to pass this info to card issuers
{
// Cardholder's first and last name
cardholderName: "Raúl Uriarte, Jr.",
// Billing Address
billingAddress: {
// Street address, line 1
streetAddress: "123 Springfield Rd",
// Street address, line 2 (Ex: Unit, Apartment, etc.)
extendedAddress: "",
// State
region: "AZ",
// City
locality: "CHANDLER",
// Postal Code
postalCode: "85224",
// Country Code
countryCodeAlpha2: "US",
},
}
//Customer Data END
)
.then(() => {
return fetch("http://localhost:3000/complete_order", {
method: "post", headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify({
"intent": intent,
"order_id": order_id,
"email": document.getElementById("email").value
})
})
.then((response) => response.json())
.then((order_details) => {
display_success_message({"order_details": order_details, "paypal_buttons": paypal_buttons});
})
.catch((error) => {
console.log(error);
display_error_alert();
});
})
.catch((err) => {
console.log(err);
reset_purchase_button();
display_error_alert();
});
});
});
}
})
.catch((error) => {
reset_purchase_button();
});

44
sl/style.css Normal file
View File

@ -0,0 +1,44 @@
.hide {
display:none !important;
}
.spinner-container {
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
}
.div_input {
display: inline-block;
height: 40px;
width: 100%;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: none;
box-shadow: none;
font-size: 0.98rem;
background-color: rgba(var(--main-bg), 1);
border: 2px solid rgba(var(--default-border-color), 1);
border-radius: var(--default-border-radius);
margin: 0;
padding: 0 0.8rem;
}
.spinner {
border: 8px solid rgba(0, 0, 0, 0.1);
border-top-color: lightblue;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}