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