From 0f79b544e559e0702b56e5d7741db326f8dc643d Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@suma-ev.de> Date: Wed, 7 Dec 2022 17:06:12 +0100 Subject: [PATCH] partly integrated paypal create order --- pass/app.js | 4 +- pass/app/Order.js | 106 ++++++---------- pass/config/default.json | 3 + pass/resources/js/checkout.js | 31 +---- pass/resources/js/checkout_paypal.js | 173 +++++++++++++-------------- pass/routes/checkout/checkout.js | 26 ---- pass/routes/checkout/paypal.js | 43 ++++--- pass/routes/key.js | 4 + 8 files changed, 147 insertions(+), 243 deletions(-) diff --git a/pass/app.js b/pass/app.js index c02a9ab..10c9513 100644 --- a/pass/app.js +++ b/pass/app.js @@ -8,7 +8,7 @@ var logger = require("morgan"); var indexRouter = require("./routes/index"); var keyRouter = require("./routes/key"); -var checkoutRouter = require("./routes/checkout/checkout"); +//var checkoutRouter = require("./routes/checkout/checkout"); var redeemRouter = require("./routes/redeem.js"); var app = express(); @@ -26,7 +26,7 @@ app.use(express.static(path.join(__dirname, "public"))); app.use("/", indexRouter); app.use("/key", keyRouter); -app.use("/checkout", checkoutRouter); +//app.use("/checkout", checkoutRouter); app.use("/redeem", redeemRouter); // Browserified Javascript files diff --git a/pass/app/Order.js b/pass/app/Order.js index fcca840..afb462a 100644 --- a/pass/app/Order.js +++ b/pass/app/Order.js @@ -52,10 +52,7 @@ class Order { #order_id; #expires_at; #amount; - #unit_size; - #price_per_unit; - #encrypted_sales_receipts; - #signatures; + #price; #payment_completed; #payment_method_link; // Stores a link to an entry of the payment methods payment i.e. PayPal order id @@ -67,31 +64,15 @@ class Order { #create_mode; #redis_client; - constructor( - order_id, - expires_at, - amount, - unit_size, - price_per_unit, - encrypted_sales_receipts, - signatures - ) { - this.#order_id = new String(order_id); - this.#expires_at = dayjs(expires_at); + constructor(order_id, amount, price) { + this.#order_id = order_id; + this.#expires_at = dayjs().add(6, "month"); this.#order_date = dayjs.unix(this.#order_id.substr(0, 10)); this.#order_path = Order.GET_ORDER_FILE_BASE_PATH(this.#order_date); this.#amount = parseInt(amount); - this.#unit_size = parseInt(unit_size); - this.#price_per_unit = parseFloat(price_per_unit); - this.#encrypted_sales_receipts = encrypted_sales_receipts; - - if (signatures) { - this.#signatures = signatures; - } else { - this.#signatures = []; - } + this.#price = parseInt(price); this.#payment_completed = false; this.#create_mode = true; @@ -110,22 +91,14 @@ class Order { return this.#amount; } - getPricePerUnit() { - return this.#price_per_unit; + getPrice() { + return this.#price; } getPaymentMethodLink() { return this.#payment_method_link; } - getEncryptedSalesReceipts() { - return this.#encrypted_sales_receipts; - } - - getSignatures() { - return this.#signatures; - } - isPaymentComplete() { return this.#payment_completed; } @@ -134,35 +107,6 @@ class Order { this.#payment_method_link = payment_method_link; } - async signOrder() { - let mgcrypto = new Crypto(); - - if (this.#signatures.length > 0) { - return false; - } - - let signed_encrypted_sales_receipts = mgcrypto.sign( - this.#encrypted_sales_receipts, - this.#order_date, - this.#expires_at - ); - this.#signatures = await signed_encrypted_sales_receipts; - - // Store amount of signed receipts for our records - // So we know how much searches we have given out - let fs = require("fs"); - let generated_file = path.join(this.#order_path, "generated.json"); - // Create directory if it does not exist - if (!fs.existsSync(path.dirname(generated_file))) { - fs.mkdirSync(path.dirname(generated_file), { recursive: true }); - } - for (let i = 0; i < this.#signatures.length; i++) { - fs.appendFileSync(generated_file, this.#order_id + `_${i}\n`); - } - - return true; - } - static async LOAD_ORDER_FROM_ID(order_id) { return new Promise((resolve, reject) => { let Redis = require("ioredis"); @@ -191,10 +135,7 @@ class Order { order_data.order_id, order_data.expires_at, order_data.amount, - order_data.unit_size, - order_data.price_per_unit, - JSON.parse(order_data.encrypted_sales_receipts), - JSON.parse(order_data.signatures) + order_data.price ); if (order_data.payment_method_link) { loaded_order.setPaymentMethodLink( @@ -225,10 +166,7 @@ class Order { order_id: this.#order_id, expires_at: this.#expires_at.format("YYYY-MM-DD"), amount: this.#amount, - unit_size: this.#unit_size, - price_per_unit: this.#price_per_unit, - encrypted_sales_receipts: JSON.stringify(this.#encrypted_sales_receipts), - signatures: JSON.stringify(this.#signatures), + price: this.#price, payment_completed: this.#payment_completed, payment_method_link: JSON.stringify(this.#payment_method_link), }; @@ -274,6 +212,32 @@ class Order { return false; }); } + + static async GENERATE_UNIQUE_ORDER_ID() { + // Generate Order ID => time in seconds since 1.1.1970 and and add a mutex to it to allow multiple order ids per second + let Redis = require("ioredis"); + let redis_connection = new Redis({ + host: config.get("redis.host"), + }); + let order_id = null; + let order_base = Math.round(new Date().getTime() / 1000); + let order_mutex = Math.floor(Math.random() * 10000); + do { + // make sure this order_id is not already registered + let order_lock = await redis_connection.setnx( + "" + order_base + order_mutex, + true + ); + if (order_lock === 1) { + await redis_connection.expire(order_id, 5); + order_id = "" + order_base + order_mutex; + } else { + console.log("Couldn't acquire lock"); + order_mutex++; + } + } while (order_id === null); + return order_id; + } } module.exports = Order; diff --git a/pass/config/default.json b/pass/config/default.json index 7de7091..ecaad1b 100644 --- a/pass/config/default.json +++ b/pass/config/default.json @@ -1,4 +1,7 @@ { + "price": { + "per_300": 5 + }, "redis": { "host": "redis" }, diff --git a/pass/resources/js/checkout.js b/pass/resources/js/checkout.js index fad6fc3..fc07a5e 100644 --- a/pass/resources/js/checkout.js +++ b/pass/resources/js/checkout.js @@ -1,29 +1,4 @@ -const paypal_client = require("@paypal/paypal-js"); -console.log(paypal_client); +const initialize_paypal_payments = require("./checkout_paypal"); -paypal_client - .loadScript({ - "client-id": document.querySelector("input[name=paypal-client-id]").value, - components: ["buttons", "funding-eligibility"], - }) - .then((paypal) => { - paypal.getFundingSources().forEach((fundingSource) => { - let button = paypal.Buttons({ - style: { - color: "white", - height: 50, - }, - fundingSource: fundingSource, - }); - if (button.isEligible()) { - console.log("eligible"); - let funding_source_element = document.createElement("div"); - funding_source_element.classList.add("funding_source"); - funding_source_element.id = fundingSource; - document - .getElementById("paypal-payments") - .appendChild(funding_source_element); - button.render("#" + fundingSource); - } - }); - }); +// ToDo only load paypal when paypal group is opened +initialize_paypal_payments(); diff --git a/pass/resources/js/checkout_paypal.js b/pass/resources/js/checkout_paypal.js index 19a5be6..8ac5d08 100644 --- a/pass/resources/js/checkout_paypal.js +++ b/pass/resources/js/checkout_paypal.js @@ -1,102 +1,26 @@ const paypal_client = require("@paypal/paypal-js"); -function execute_payment_paypal(encrypted_sales_receipts) { - let payment_method_buttons = document.querySelectorAll( - "#payment-providers > ul > li" - ); - for (let i = 0; i < payment_method_buttons.length; i++) { - payment_method_buttons[i].dataset.active = false; - } - - let paypal_payment_option_button = document.getElementById( - "payment_method_paypal" - ); - paypal_payment_option_button.dataset.active = true; - - let payment_container = document.getElementById("payment-information"); - payment_container.textContent = ""; - - let client_id = paypal_payment_option_button.dataset.client_id; +function initialize_paypal_payments() { paypal_client .loadScript({ - "client-id": client_id, + "client-id": document.querySelector("input[name=paypal-client-id]").value, + components: ["buttons", "funding-eligibility"], currency: "EUR", }) .then((paypal) => { - paypal - .Buttons({ - createOrder: (data, actions) => { - return fetch("/checkout/payment/order/paypal", { - method: "POST", - headers: { - "Content-Type": "application/json;charset=utf-8", - }, - body: JSON.stringify({ - order_id: document.querySelector("input[name=order_id]").value, - expires_at: document.querySelector("input[name=expires_at]") - .value, - amount: document.querySelector("input[name=amount]").value, - unit_size: document.querySelector("input[name=unit_size]") - .value, - price_per_unit: document.querySelector( - "input[name=price_per_unit]" - ).value, - public_key_n: document.querySelector("input[name=public_key_n]") - .value, - public_key_e: document.querySelector("input[name=public_key_e]") - .value, - integrity: document.querySelector("input[name=integrity]") - .value, - encrypted_sales_receipts: encrypted_sales_receipts, - }), - }) - .then((response) => response.json()) - .then((order) => order.id); - }, - onCancel: () => cancelPayment(encrypted_sales_receipts), - onError: () => cancelPayment(encrypted_sales_receipts), - onApprove: (data, actions) => { - return fetch("/checkout/payment/order/paypal/capture", { - method: "POST", - headers: { - "Content-Type": "application/json;charset=utf-8", - }, - body: JSON.stringify({ - order_id: document.querySelector("input[name=order_id]").value, - expires_at: document.querySelector("input[name=expires_at]") - .value, - amount: document.querySelector("input[name=amount]").value, - unit_size: document.querySelector("input[name=unit_size]") - .value, - price_per_unit: document.querySelector( - "input[name=price_per_unit]" - ).value, - public_key_n: document.querySelector("input[name=public_key_n]") - .value, - public_key_e: document.querySelector("input[name=public_key_e]") - .value, - integrity: document.querySelector("input[name=integrity]") - .value, - encrypted_sales_receipts: encrypted_sales_receipts, - }), - }) - .then((response) => response.json()) - .then((orderData) => { - let paymentEvent = new CustomEvent("payment-complete", { - detail: { - order_id: orderData.order_id, - expires_at: data.expires_at, - signatures: orderData.signatures, - }, - bubbles: true, - cancelable: true, - composed: false, - }); - paypal_payment_option_button.dispatchEvent(paymentEvent); - }); - }, - }) - .render("#payment-information"); + paypal.getFundingSources().forEach((fundingSource) => { + let button = paypal.Buttons(get_paypal_checkout_data(fundingSource)); + if (button.isEligible()) { + console.log("eligible"); + let funding_source_element = document.createElement("div"); + funding_source_element.classList.add("funding_source"); + funding_source_element.id = fundingSource; + document + .getElementById("paypal-payments") + .appendChild(funding_source_element); + button.render("#" + fundingSource); + } + }); }) .catch((err) => { // ToDo Handle error @@ -104,6 +28,69 @@ function execute_payment_paypal(encrypted_sales_receipts) { }); } +function get_paypal_checkout_data(funding_source) { + return { + style: { + color: "white", + height: 50, + }, + fundingSource: funding_source, + createOrder: (data, actions) => { + let checkout_paypal_create_order_url = + document.URL.replace(/#.*$/, "").replace("//+$", "") + + "/paypal/create-order"; + + return fetch(checkout_paypal_create_order_url, { + method: "POST", + headers: { + "Content-Type": "application/json;charset=utf-8", + }, + body: JSON.stringify({}), + }) + .then((response) => response.json()) + .then((order) => order.id); + }, + onCancel: () => cancelPayment(encrypted_sales_receipts), + onError: () => cancelPayment(encrypted_sales_receipts), + onApprove: (data, actions) => { + return fetch("/checkout/payment/order/paypal/capture", { + method: "POST", + headers: { + "Content-Type": "application/json;charset=utf-8", + }, + body: JSON.stringify({ + order_id: document.querySelector("input[name=order_id]").value, + expires_at: document.querySelector("input[name=expires_at]").value, + amount: document.querySelector("input[name=amount]").value, + unit_size: document.querySelector("input[name=unit_size]").value, + price_per_unit: document.querySelector("input[name=price_per_unit]") + .value, + public_key_n: document.querySelector("input[name=public_key_n]") + .value, + public_key_e: document.querySelector("input[name=public_key_e]") + .value, + integrity: document.querySelector("input[name=integrity]").value, + encrypted_sales_receipts: encrypted_sales_receipts, + }), + }) + .then((response) => response.json()) + .then((orderData) => { + let paymentEvent = new CustomEvent("payment-complete", { + detail: { + order_id: orderData.order_id, + expires_at: data.expires_at, + signatures: orderData.signatures, + }, + bubbles: true, + cancelable: true, + composed: false, + }); + paypal_payment_option_button.dispatchEvent(paymentEvent); + }); + }, + }; +} + function cancelPayment(encrypted_sales_receipts) { return fetch("/checkout/payment/order/paypal/cancel", { method: "POST", @@ -125,4 +112,4 @@ function cancelPayment(encrypted_sales_receipts) { }); } -module.exports = execute_payment_paypal; +module.exports = initialize_paypal_payments; diff --git a/pass/routes/checkout/checkout.js b/pass/routes/checkout/checkout.js index 8dfb3b7..8e4a6c4 100644 --- a/pass/routes/checkout/checkout.js +++ b/pass/routes/checkout/checkout.js @@ -184,29 +184,3 @@ var paypalRouter = require("./paypal.js"); router.use("/payment/order/paypal", paypalRouter); module.exports = router; - -async function generate_unique_order_id() { - // Generate Order ID => time in seconds since 1.1.1970 and and add a mutex to it to allow multiple order ids per second - let Redis = require("ioredis"); - let redis_connection = new Redis({ - host: config.get("redis.host"), - }); - let order_id = null; - let order_base = Math.round(new Date().getTime() / 1000); - let order_mutex = Math.floor(Math.random() * 10000); - do { - // make sure this order_id is not already registered - let order_lock = await redis_connection.setnx( - "" + order_base + order_mutex, - true - ); - if (order_lock === 1) { - await redis_connection.expire(order_id, 5); - order_id = "" + order_base + order_mutex; - } else { - console.log("Couldn't acquire lock"); - order_mutex++; - } - } while (order_id === null); - return order_id; -} diff --git a/pass/routes/checkout/paypal.js b/pass/routes/checkout/paypal.js index ecd44e7..d2bbe25 100644 --- a/pass/routes/checkout/paypal.js +++ b/pass/routes/checkout/paypal.js @@ -10,17 +10,17 @@ const CLIENT_ID = config.get( const APP_SECRET = config.get(`payments.paypal.${process.env.NODE_ENV}.secret`); const base = config.get(`payments.paypal.${process.env.NODE_ENV}.base`); -/* Client initiates payment */ -router.post("/", async (req, res, next) => { +router.post("/create-order", async (req, res) => { // Order data is validated: Create and store the order in the redis database + let order = new Order( - req.body.order_id, - req.body.expires_at, - req.body.amount, - req.body.unit_size, - req.body.price_per_unit, - req.body.encrypted_sales_receipts + await Order.GENERATE_UNIQUE_ORDER_ID(), + req.params.amount, + (req.params.amount / 300) * config.get("price.per_300") ); + console.log(req.params); + res.status(200).json({ test: "test" }); + return; order .save() @@ -84,16 +84,19 @@ module.exports = router; // use the orders api to create an order async function createOrder(loaded_order) { - console.log(loaded_order); const accessToken = await generateAccessToken(); const url = `${base}/v2/checkout/orders`; + + let unit_count = loaded_order.getAmount() / 300; + let price_per_unit = loaded_order.getPrice() / unit_count; + console.log(price_per_unit); let tax_amount_per_unit = fixRounding( - loaded_order.getPricePerUnit() * Order.PURCHASE_TAX_AMOUNT, + price_per_unit * Order.PURCHASE_TAX_AMOUNT, 2 ); let item_amount_per_unit = fixRounding( - loaded_order.getPricePerUnit() - tax_amount_per_unit, + price_per_unit - tax_amount_per_unit, 2 ); @@ -105,31 +108,25 @@ async function createOrder(loaded_order) { amount: { currency_code: "EUR", value: fixRounding( - item_amount_per_unit * loaded_order.getAmount() + - tax_amount_per_unit * loaded_order.getAmount(), + item_amount_per_unit * unit_count + + tax_amount_per_unit * unit_count, 2 ), breakdown: { item_total: { currency_code: "EUR", - value: fixRounding( - item_amount_per_unit * loaded_order.getAmount(), - 2 - ), + value: fixRounding(item_amount_per_unit * unit_count, 2), }, tax_total: { currency_code: "EUR", - value: fixRounding( - tax_amount_per_unit * loaded_order.getAmount(), - 2 - ), + value: fixRounding(tax_amount_per_unit * unit_count, 2), }, }, }, items: [ { - name: "MetaGer Pass: Suchanfragen (250x)", - quantity: loaded_order.getAmount(), + name: "MetaGer Pass: Suchanfragen (300x)", + quantity: unit_count, unit_amount: { currency_code: "EUR", value: item_amount_per_unit, diff --git a/pass/routes/key.js b/pass/routes/key.js index 7f3938b..cdda198 100644 --- a/pass/routes/key.js +++ b/pass/routes/key.js @@ -3,6 +3,8 @@ var router = express.Router(); const { param, validationResult } = require("express-validator"); const config = require("config"); +var checkout_router_paypal = require("./checkout/paypal"); + var Key = require("../app/Key"); router.get("/create", function (req, res, next) { @@ -89,4 +91,6 @@ router.get("/:key/checkout/:amount?", async (req, res) => { }); }); +router.use("/:key/checkout/:amount/paypal", checkout_router_paypal); + module.exports = router; -- GitLab