From 11ad69fb8db9a1e28a4c02d0a8e674a57d4a36a0 Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@hebeler.club>
Date: Wed, 21 Dec 2022 16:11:20 +0100
Subject: [PATCH] refund form now creates a ticket

---
 pass/app/Key.js                      | 27 ++++------
 pass/routes/checkout/paypal.js       | 18 ++++---
 pass/routes/orders/invoice.js        |  2 +-
 pass/routes/orders/refund.js         | 74 +++++++++++++++++++++++++++-
 pass/views/orders/refund.ejs         | 13 ++++-
 pass/views/orders/refund_message.ejs | 22 +++++++++
 6 files changed, 127 insertions(+), 29 deletions(-)
 create mode 100644 pass/views/orders/refund_message.ejs

diff --git a/pass/app/Key.js b/pass/app/Key.js
index f4743bf..fc6c802 100644
--- a/pass/app/Key.js
+++ b/pass/app/Key.js
@@ -94,26 +94,19 @@ class Key {
   static async DISCHARGE_KEY(key, amount) {
     let redis_client = Key.REDIS_CLIENT;
 
-    // Check if key exists and get charge
-    redis_client.get(Key.DATABASE_PREFIX + key).then((current_amount) => {
-      if (current_amount && current_amount > 0) {
-        if (current_amount > amount) {
-          let expiration = require("dayjs")().add(
-            Key.EXPIRATION_AFTER_CHARGE_DAYS,
-            "day"
+    // Dicharge key and make sure it can't fall below 0
+    return redis_client
+      .decrby(Key.DATABASE_PREFIX + key, amount)
+      .then((new_charge) => {
+        if (new_charge < 0) {
+          return redis_client.incrby(
+            Key.DATABASE_PREFIX + key,
+            Math.abs(new_charge)
           );
-          return redis_client
-            .pipeline()
-            .set(Key.DATABASE_PREFIX + key, current_amount - amount)
-            .expireat(Key.DATABASE_PREFIX + key, expiration.unix())
-            .exec();
         } else {
-          return redis_client.del(Key.DATABASE_PREFIX + key);
+          return new_charge;
         }
-      } else {
-        throw "Key Does not exist or is not charged";
-      }
-    });
+      });
   }
 }
 
diff --git a/pass/routes/checkout/paypal.js b/pass/routes/checkout/paypal.js
index c18dcd5..1d63362 100644
--- a/pass/routes/checkout/paypal.js
+++ b/pass/routes/checkout/paypal.js
@@ -260,16 +260,20 @@ router.post("/webhook", async (req, res) => {
           }
           return Order.LOAD_ORDER_FROM_ID(order_id).then(
             /** @param {Order} order */ (order) => {
-              if (order.isPaymentComplete()) {
+              if (!order.isPaymentComplete()) {
                 // Update Payment status
                 let payment_link = order.getPaymentMethodLink();
-                payment_link.payment_status = "REFUNDED";
-                return order
-                  .setPaymentMethodLink(payment_link)
-                  .then(() => order.getKeyFromOrderLink())
-                  .then((key) => Key.DISCHARGE_KEY(key, order.getAmount()));
+                if (payment_link.payment_status !== "REFUNDED") {
+                  payment_link.payment_status = "REFUNDED";
+                  return order
+                    .setPaymentMethodLink(payment_link)
+                    .then(() => order.getKeyFromOrderLink())
+                    .then((key) => Key.DISCHARGE_KEY(key, order.getAmount()));
+                } else {
+                  throw "Order is already refunded";
+                }
               } else {
-                throw "Order is already completed";
+                throw "Order is already refunded";
               }
             }
           );
diff --git a/pass/routes/orders/invoice.js b/pass/routes/orders/invoice.js
index 822cda7..3a699f7 100644
--- a/pass/routes/orders/invoice.js
+++ b/pass/routes/orders/invoice.js
@@ -82,7 +82,7 @@ router.post(
     let ejs = require("ejs"),
       fs = require("fs"),
       template = fs.readFileSync(
-        `${__dirname}/../views/orders/invoice_message.ejs`,
+        `${__dirname}/../../views/orders/invoice_message.ejs`,
         "utf-8"
       );
 
diff --git a/pass/routes/orders/refund.js b/pass/routes/orders/refund.js
index 13b7a44..8dac122 100644
--- a/pass/routes/orders/refund.js
+++ b/pass/routes/orders/refund.js
@@ -1,12 +1,13 @@
 var express = require("express");
 var router = express.Router({ mergeParams: true });
 
+const config = require("config");
+
 // Base URLÖ: /key/:key/orders/:order/refund
 router.use("/", (req, res, next) => {
   let refund_count = Math.min(
     req.data.key.charge,
-    req.data.order.order.getAmount(),
-    408
+    req.data.order.order.getAmount()
   );
   req.data.order.refund = {
     count: refund_count,
@@ -20,5 +21,74 @@ router.use("/", (req, res, next) => {
 router.get("/", (req, res) => {
   res.render("key", req.data);
 });
+router.post("/", (req, res, next) => {
+  req.data.order.refund.message = req.body.message;
+  // Refund values as sent back to us by the user.
+  // Used only to verify the amount we are refunding corresponds to what we showed the user
+  let user_amount = req.body.amount; // Amount of money refunded back to the user
+  let user_count = parseInt(req.body.count); // Amount of search request discharged from key
+  console.log(req.data.order.refund);
+  if (
+    req.data.order.refund.amount.toFixed(2) !== user_amount ||
+    req.data.order.refund.count !== user_count
+  ) {
+    // Something changed abort here
+    req.data.order.refund.error = "invalid_data";
+    res.render("key", req.data);
+  } else {
+    let request_data = {
+      moderation: true,
+      amount: req.data.order.refund.amount,
+      count: req.data.order.refund.count,
+    };
+    req.data.order.refund.moderation_url = `/key/${
+      req.data.key.key
+    }/orders/${req.data.order.order.getOrderID()}/refund${new URLSearchParams(
+      request_data
+    ).toString()}`;
+    // Render the message
+    let ejs = require("ejs"),
+      fs = require("fs"),
+      template = fs.readFileSync(
+        `${__dirname}/../../views/orders/refund_message.ejs`,
+        "utf-8"
+      );
+
+    let message = ejs.render(template, req.data);
+
+    // No validation errors. Try to create a new Ticket
+    fetch(`${config.get("app.osticket.url")}/api/tickets.json`, {
+      method: "post",
+      headers: {
+        "X-API-Key": config.get("app.osticket.api_key"),
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify({
+        alert: true,
+        autorespond: false,
+        source: "API",
+        name: "Metzgermeister Unbekannt",
+        email: "no-reply@metager.de",
+        subject: `MetaGer Schlüssel: Erstattung (${req.data.order.order.getOrderID()})`,
+        message: `data:text/html;charset=utf-8,${message}`,
+        topicId: 12, // ToDo change topic for english autoresponder
+      }),
+    })
+      .then(async (response) => {
+        if (response.status != 201) {
+          console.error(await response.text());
+          throw "Fehler beim Erstellen der Benachrichtigung,";
+        } else {
+          req.data.order.refund.success = true;
+          res.render("key", req.data);
+        }
+      })
+      .catch((reason) => {
+        console.log(reason);
+        req.data.order.refund.error = "send_email";
+        res.render("key", req.data);
+      });
+  }
+});
 
 module.exports = router;
diff --git a/pass/views/orders/refund.ejs b/pass/views/orders/refund.ejs
index 7d370ac..52ca0b4 100644
--- a/pass/views/orders/refund.ejs
+++ b/pass/views/orders/refund.ejs
@@ -6,16 +6,25 @@
         <li>Erstattung</li>
     </ul>
     <%- include("order_details") %>
-    <form id="refund-form">
-        <p>Sind Sie unzufrieden mit Ihrem Schlüssel? Das bedauern wir sehr! Selbstverständlich erstatten wir Ihnen in diesem Fall den Rechnungsbetrag. Gerne nehmen wir auch Ihre Kritik entgegen.</p>
+    <%_ if (typeof order.refund.success !== "undefined" && order.refund.success === true) { _%>
+    <p>Ihre Anfrage wurde uns erfolgreich zugestellt. Wir bearbeiten diese so schnell wie möglich. Je nach Zahlungsmethode kann es einige Tage dauern, bis eine Erstattung in Ihren Umsätzen sichtbar wird.</p>
+    <%_ } else { _%>
+    <form id="refund-form" method="post">
+        <input type="hidden" name="amount" value="<%= order.refund.amount.toFixed(2) %>">
+        <input type="hidden" name="count" value="<%= order.refund.count %>">
+        <p>Sind Sie unzufrieden mit Ihrem Schlüssel? Das bedauern wir sehr! Selbstverständlich erstatten wir Ihnen in diesem Fall den Rechnungsbetrag. Eine Erstatung erfolgt stets auf das Gleiche Konto, welches bei der ursprünglichen Zahlung verwendet wurde. Gerne nehmen wir auch Ihre Kritik entgegen.</p>
         <%_ if(order.refund.count < order.order.getAmount()) { _%>
         <p>Hinweis: Ein Teil Ihres gekauften Guthabens wurde bereits verbraucht. Wir können Ihnen deshalb lediglich <span class="bold"><%= order.refund.count %>/<%= order.order.getAmount() %></span> Suchanfragen erstatten.</p>
         <%_ } _%>
         <h3>Ihre Erstattung</h3>
+        <%_ if(typeof order.refund.error !== "undefined") { _%>
+        <p class="error">Fehler beim Senden Ihrer Nachricht. Bitte versuchen Sie es später erneut.</p>
+        <%_ } _%>
         <textarea name="message" id="message" cols="1" rows="10" placeholder="Ihre Nachricht (optional)" size="1" autofocus></textarea>
         <button class="button">
             <img src="/images/money.svg" alt="" aria-hidden="true">
             <span><%= order.refund.amount.toFixed(2) %>€ Erstattung anfragen</span>
         </button>
     </form>
+    <%_ } _%>
 </div>
\ No newline at end of file
diff --git a/pass/views/orders/refund_message.ejs b/pass/views/orders/refund_message.ejs
new file mode 100644
index 0000000..9d69456
--- /dev/null
+++ b/pass/views/orders/refund_message.ejs
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <p>Eine Erstattung für die Bestellung mit der ID <span style="font-weight: bold"><%= order.order.getOrderID() %></span> wurde angefragt.</p>
+    <div style="display: grid; grid-template-columns: auto 1fr; gap: 1rem; align-items: center; justify-content: center;">
+        <label for="count">Suchanfragen</label>
+        <input type="text" name="count" id="count" value="<%= order.refund.count %>" disabled>
+        <label for="amount">Erstattungsbetrag</label>
+        <input type="amount" name="amount" id="amount" value="<%= order.refund.amount.toFixed(2) %>" disabled>
+        <label for="message">Nachricht</label>
+        <textarea name="message" id="message" cols="30" rows="3" disabled><%= order.refund.message %></textarea>
+        <a href="<%= order.refund.moderation_url %>" target="_blank" style="display: block;text-decoration: none;border: 1px solid rgb(168, 84, 0);border-radius: 5px;padding: .2rem .5rem;background-color: rgb(185, 92, 0);color: white!important;font-weight: bold;grid-column: span 2;text-align: center;max-width: 15em;justify-self: center;">Erstattung prüfen</a>
+    </div>
+    <p style="margin-top: 1rem">Bitte Erstattungsdaten überprüfen und die Erstattung ggf. freigeben.</p>
+</body>
+</html>
\ No newline at end of file
-- 
GitLab