From 1434e0fbdc1f81aa81e5d73dc88cd9a05ac8bf5f Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@suma-ev.de>
Date: Mon, 14 Nov 2022 16:25:21 +0100
Subject: [PATCH] moved paypal client payment to its own file

---
 pass/app/Order.js                    |   4 ++
 pass/resources/js/checkout.js        |  99 +++++---------------------
 pass/resources/js/checkout_paypal.js | 101 +++++++++++++++++++++++++++
 pass/routes/checkout/paypal.js       |  51 +++++++++++---
 4 files changed, 163 insertions(+), 92 deletions(-)
 create mode 100644 pass/resources/js/checkout_paypal.js

diff --git a/pass/app/Order.js b/pass/app/Order.js
index cff4bb0..052720e 100644
--- a/pass/app/Order.js
+++ b/pass/app/Order.js
@@ -77,6 +77,10 @@ class Order {
     return this.#price_per_unit;
   }
 
+  getPaymentMethodLink() {
+    return this.#payment_method_link;
+  }
+
   setPaymentMethodLink(payment_method_link) {
     this.#payment_method_link = payment_method_link;
   }
diff --git a/pass/resources/js/checkout.js b/pass/resources/js/checkout.js
index 532680f..835f5e1 100644
--- a/pass/resources/js/checkout.js
+++ b/pass/resources/js/checkout.js
@@ -1,10 +1,9 @@
 const uuid_generator = require("uuid");
 const BlindSignature = require("blind-signatures");
 const BigInteger = require("jsbn").BigInteger;
-const paypal_client = require("@paypal/paypal-js");
 
-var encrypted_sales_receipt = [];
-var encrypted_sales_receipt_r = [];
+var encrypted_sales_receipts = []; // Stores the encrypted sales receipts that get signed by our server after successful purchase
+var encrypted_sales_receipts_r = []; // Those are the secrets for decrypting the signed sales_receipts; Should never leave the clients computer
 
 one_generate_encrypted_sales_receipt();
 
@@ -26,91 +25,29 @@ function one_generate_encrypted_sales_receipt() {
       N: N,
       E: E,
     });
-    encrypted_sales_receipt.push(blinded.toString());
-    encrypted_sales_receipt_r.push(r.toString());
+    encrypted_sales_receipts.push(blinded.toString());
+    encrypted_sales_receipts_r.push(r.toString());
   }
 
   current_step_container.classList.remove("current");
   current_step_container.classList.add("finished");
   next_step_container.classList.add("current");
-}
 
-function two_execute_payment(provider) {}
+  two_execute_payment();
+}
 
-function execute_payment_paypal(e) {
-  let paypal_payment_option_button = document.getElementById(
-    "payment_method_paypal"
-  );
+function two_execute_payment() {
+  let current_step_container = document.getElementById("execute-payment");
 
-  paypal_payment_option_button.dataset.active = true;
-  let client_id = paypal_payment_option_button.dataset.client_id;
-  paypal_client
-    .loadScript({
-      "client-id": client_id,
-      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,
-                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_receipt,
-              }),
-            })
-              .then((response) => response.json())
-              .then((order) => order.id);
-          },
-          onCancel: (data) => {
-            return fetch("/checkout/payment/order/paypal/cancel", {
-              method: "POST",
-              headers: {
-                "Content-Type": "application/json;charset=utf-8",
-              },
-              body: JSON.stringify({
-                order_id: document.querySelector("input[name=order_id]").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_receipt,
-              }),
-            });
-          },
-        })
-        .render("#payment-information");
-    })
-    .catch((err) => {
-      // ToDo Handle error
-      console.error("failed to load the PayPal JS SDK script", err);
+  let paypal_handler = require("./checkout_paypal"); // Add handler for PayPal Checkout
+  document
+    .getElementById("payment_method_paypal")
+    .addEventListener("click", () => {
+      paypal_handler(encrypted_sales_receipts);
     });
-}
 
-document
-  .getElementById("payment_method_paypal")
-  .addEventListener("click", execute_payment_paypal);
+  current_step_container.addEventListener("payment-complete", (e) => {
+    current_step_container.classList.remove("current");
+    current_step_container.classList.add("finished");
+  });
+}
diff --git a/pass/resources/js/checkout_paypal.js b/pass/resources/js/checkout_paypal.js
new file mode 100644
index 0000000..00fa75c
--- /dev/null
+++ b/pass/resources/js/checkout_paypal.js
@@ -0,0 +1,101 @@
+const paypal_client = require("@paypal/paypal-js");
+
+function execute_payment_paypal(encrypted_sales_receipts) {
+  let paypal_payment_option_button = document.getElementById(
+    "payment_method_paypal"
+  );
+
+  paypal_payment_option_button.dataset.active = true;
+  let client_id = paypal_payment_option_button.dataset.client_id;
+  paypal_client
+    .loadScript({
+      "client-id": client_id,
+      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,
+                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,
+                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) => {});
+          },
+        })
+        .render("#payment-information");
+    })
+    .catch((err) => {
+      // ToDo Handle error
+      console.error("failed to load the PayPal JS SDK script", err);
+    });
+}
+
+function cancelPayment(encrypted_sales_receipts) {
+  return fetch("/checkout/payment/order/paypal/cancel", {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json;charset=utf-8",
+    },
+    body: JSON.stringify({
+      order_id: document.querySelector("input[name=order_id]").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,
+    }),
+  });
+}
+
+module.exports = execute_payment_paypal;
diff --git a/pass/routes/checkout/paypal.js b/pass/routes/checkout/paypal.js
index 044e64d..41e5fa6 100644
--- a/pass/routes/checkout/paypal.js
+++ b/pass/routes/checkout/paypal.js
@@ -35,6 +35,17 @@ router.post("/", async (req, res, next) => {
     });
 });
 
+// capture payment & store order information or fullfill order
+router.post("/capture", async (req, res) => {
+  Order.LOAD_ORDER_FROM_ID(req.body.order_id).then((loaded_order) => {
+    let paypal_order_id = loaded_order.getPaymentMethodLink().id;
+    console.log(paypal_order_id);
+    //const captureData = await capturePayment(orderID);
+    //res.json(captureData);
+    // TODO: store payment information such as the transaction ID
+  });
+});
+
 module.exports = router;
 
 //////////////////////
@@ -50,34 +61,39 @@ async function createOrder(loaded_order) {
   const accessToken = await generateAccessToken();
 
   const url = `${base}/v2/checkout/orders`;
-  let tax_amount_per_unit = (
-    loaded_order.getPricePerUnit().toFixed(2) * Order.PURCHASE_TAX_AMOUNT
-  ).toFixed(2);
-  let item_amount_per_unit = (
-    loaded_order.getPricePerUnit().toFixed(2) - tax_amount_per_unit
-  ).toFixed(2);
+  let tax_amount_per_unit = fixRounding(
+    loaded_order.getPricePerUnit() * Order.PURCHASE_TAX_AMOUNT,
+    2
+  );
+  let item_amount_per_unit = fixRounding(
+    loaded_order.getPricePerUnit() - tax_amount_per_unit,
+    2
+  );
 
   let order = {
     intent: "CAPTURE",
-
     purchase_units: [
       {
         description: "MetaGer Pass Einkauf",
         amount: {
           currency_code: "EUR",
-          value:
+          value: fixRounding(
             item_amount_per_unit * loaded_order.getAmount() +
-            tax_amount_per_unit * loaded_order.getAmount(),
+              tax_amount_per_unit * loaded_order.getAmount(),
+            2
+          ),
           breakdown: {
             item_total: {
               currency_code: "EUR",
-              value: (item_amount_per_unit * loaded_order.getAmount()).toFixed(
+              value: fixRounding(
+                item_amount_per_unit * loaded_order.getAmount(),
                 2
               ),
             },
             tax_total: {
               currency_code: "EUR",
-              value: (tax_amount_per_unit * loaded_order.getAmount()).toFixed(
+              value: fixRounding(
+                tax_amount_per_unit * loaded_order.getAmount(),
                 2
               ),
             },
@@ -167,3 +183,16 @@ async function generateAccessToken() {
 
   return data.access_token;
 }
+
+/**
+ *
+ * Javascripts calculations with floating points produce weird artifacts. This method is to fix that issue
+ *
+ * @param {Float} value
+ * @param {Int} precision
+ * @returns
+ */
+function fixRounding(value, precision) {
+  var power = Math.pow(10, precision || 0);
+  return Math.round(value * power) / power;
+}
-- 
GitLab