Initial clean V5 repository
This commit is contained in:
9
sl/.env
Normal file
9
sl/.env
Normal 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
21
sl/LICENSE
Normal 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
43
sl/README.md
Normal file
@ -0,0 +1,43 @@
|
||||

|
||||
|
||||
<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> - </span>
|
||||
<a href="https://www.paypal.com/us/home" target="_blank">PayPal</a>
|
||||
<span> - </span>
|
||||
<a href="https://developer.paypal.com/home" target="_blank">Docs</a>
|
||||
<span> - </span>
|
||||
<a href="https://github.com/paypaldev" target="_blank">Code Samples</a>
|
||||
<span> - </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. You’ll 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
60
sl/index.html
Normal 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
219
sl/index.js
Normal file
File diff suppressed because one or more lines are too long
15
sl/package.json
Normal file
15
sl/package.json
Normal 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
234
sl/script.js
Normal 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¤cy=" + 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
44
sl/style.css
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user