From 6fded599c4d218188d3d1daf91c8ce359ceaef08 Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@hebeler.club>
Date: Wed, 12 Apr 2023 11:45:47 +0200
Subject: [PATCH] calculating price per token

---
 pass/app/PaymentReference.js   |  4 +-
 pass/config/default.json       | 20 ++++++--
 pass/lang/de/cost.json         | 40 ++++++++++++++++
 pass/public/styles/cost.less   | 51 ++++++++++++++++----
 pass/routes/api.js             |  6 +--
 pass/views/checkout/charge.ejs |  4 +-
 pass/views/cost.ejs            | 86 ++++++++--------------------------
 7 files changed, 124 insertions(+), 87 deletions(-)
 create mode 100644 pass/lang/de/cost.json

diff --git a/pass/app/PaymentReference.js b/pass/app/PaymentReference.js
index 027ee63..39cc563 100644
--- a/pass/app/PaymentReference.js
+++ b/pass/app/PaymentReference.js
@@ -67,7 +67,7 @@ class PaymentReference {
     expiration = dayjs().add(PaymentReference.DEFAULT_EXPIRATION_HOURS, "hours")
   ) {
     // Calculate price from amount
-    let price = (amount / 300) * config.get("price.per_300");
+    let price = amount * config.get("price.per_token");
     price = price.toFixed(2);
 
     // Validate that Key can be charged with another order
@@ -197,7 +197,7 @@ class PaymentReference {
       }
       resolve(
         parseInt(matcher[2]) -
-          config.get("price.number_range.payment_reference")
+        config.get("price.number_range.payment_reference")
       );
     });
   }
diff --git a/pass/config/default.json b/pass/config/default.json
index 4f915c1..dad5b3a 100644
--- a/pass/config/default.json
+++ b/pass/config/default.json
@@ -43,10 +43,22 @@
     }
   },
   "price": {
-    "per_300": 10,
+    "per_token": 0.01,
     "vat": 7,
-    "purchasable": [300, 600, 900, 1200, 1800, 3600],
-    "allowed_currencies": ["EUR", "USD", "CAD", "GBP"],
+    "purchasable": [
+      1000,
+      2000,
+      3000,
+      4000,
+      6000,
+      12000
+    ],
+    "allowed_currencies": [
+      "EUR",
+      "USD",
+      "CAD",
+      "GBP"
+    ],
     "number_range": {
       "payment_reference": 0,
       "invoices": 0,
@@ -98,4 +110,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/pass/lang/de/cost.json b/pass/lang/de/cost.json
new file mode 100644
index 0000000..dd9e062
--- /dev/null
+++ b/pass/lang/de/cost.json
@@ -0,0 +1,40 @@
+{
+    "headings": [
+        "Das kostet Ihr MetaGer Schlüssel",
+        "Das Wichtigste zusammengefasst"
+    ],
+    "texts": [
+        "Für jede werbefreie Websuche auf MetaGer werden <b>3 Token</b> berechnet. Sie haben aber auch die Möglichkeit weitere/andere kostenpflichtige Suchmaschinen zu Ihrer Suche hinzuzufügen. Die Kosten pro Suche können Sie dann jeweils der Einstellungsseite entnehmen.",
+        "Ihren Schlüssel können Sie jederzeit mit einem der folgenden Pakete aufladen.",
+        "Die angegebenen Zeiträume sind Schätzungen, die auf unseren Erfahrungswerten basieren und sollen einen Anhaltspunkt geben, wie viele Token benötigt werden."
+    ],
+    "months_one": "Monat",
+    "months_other": "Monate",
+    "short-info": [
+        {
+            "heading": "Gekaufte Suchen bleiben 1 Jahr lang gültig",
+            "text": "Ihre gekauften Tokens sind darauf ausgelegt so lange gültig zu bleiben, bis sie verbraucht wurden. Es gibt kein Abo."
+        },
+        {
+            "heading": "30 Tage Geld Zurück Garantie",
+            "text": "Sollten Sie mit Ihrem Schlüssel unzufrieden sein, haben Sie nach dem Kauf 30 Tage Zeit das nicht verbrauchte Guthaben wieder zurück zu geben."
+        },
+        {
+            "heading": "Schlüssel wird automatisch im Browser eingerichtet und verwendet",
+            "text": "Um Ihren MetaGer Schlüssel bei der Suche zu verwenden brauchen Sie nichts weiter tun. Nach dem Auffüllen ist er automatisch in Ihrem Browser eingerichtet und Sie erhalten Informationen zur einfachen Einrichtung auf weiteren Geräten."
+        },
+        {
+            "heading": "Genauso anonym wie eine Suche ohne Schlüssel",
+            "text": "Verwenden Sie unsere <a href=\"#\">Android App</a>, oder unsere Extension für <a href=\"#\">Firefox</a> und <a href=\"#\">Chrome</a> und seien Sie unter Verwendung von <a href=\"https://en.wikipedia.org/wiki/Blind_signature\">   blinden Signaturen </a> beweisbar genauso anonym untewegs, wie ohne Schlüssel."
+        }
+    ],
+    "payment-methods": {
+        "heading": "Zahlungsmethoden",
+        "texts": [
+            "MetaGer Schlüssel wurden von uns so konzipiert, dass Sie per Design ohne personenbeziehbare Daten auskommen. Nichtsdestotrotz fallen spätestens bei der Durchführung einer Zahlung meist welche an. Sei es nun die IBAN des zahlenden Kontos, oder die E-Mail Adresse des verwendeten PayPal Kontos. Der SUMA-EV verarbeitet diese Daten nicht selbst und speichert sie auch nicht ab. Allerdings tut es je nach Zahlungsmethode der Zahlungsdienstleister. Dadurch verbleibt natürlich auf unseren Kontoauszügen eine entsprechende Buchung. Wir sind verpflichtet diese Kontoauszüge 10 Jahre lang aufzuheben.",
+            "Deshalb bieten wir zusätzlich zu den Standard-Zahlungsmethoden auch zwei an, bei denen Sie anonym bezahlen können."
+        ],
+        "anonymous": "Anonyme Zahlungsmethoden",
+        "more": "Weitere Zahlungsmethoden"
+    }
+}
\ No newline at end of file
diff --git a/pass/public/styles/cost.less b/pass/public/styles/cost.less
index 52fd058..43f0747 100644
--- a/pass/public/styles/cost.less
+++ b/pass/public/styles/cost.less
@@ -2,7 +2,27 @@
 #content {
   max-width: @max-content-width;
   margin: 0 auto;
-  padding: 1rem;
+
+  h1 {
+    font-size: clamp(1rem, 5vw, 2rem);
+  }
+
+  h2 {
+    font-size: clamp(1rem, 4vw, 1.5rem);
+  }
+
+  h3 {
+    font-size: clamp(1rem, 3vw, 1.2rem);
+  }
+
+  .hint {
+    position: relative;
+    &::before {
+      content: "*";
+      position: absolute;
+      left: -1em;
+    }
+  }
   #price-tiers {
     margin-top: 2rem;
     display: grid;
@@ -12,20 +32,29 @@
     gap: 1rem;
     color: @font-color-on-white;
 
-    @media (max-width: 530px) {
+    @media (max-width: 640px) {
       grid-template-rows: 1fr 1fr 1fr;
       grid-template-columns: 1fr 1fr;
     }
 
+    @media (max-width: 375px) {
+      grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
+      grid-template-columns: 1fr;
+    }
+
     > .price-tier {
       display: grid;
       grid-template-rows: 1fr auto;
-      grid-template-columns: 1fr 3.5em;
-      max-width: 150px;
+      grid-template-columns: 6em 4em;
       place-items: stretch;
       border: 1px solid @color-main;
       border-radius: 10px;
 
+      @media (max-width: 375px) {
+        width: 100%;
+        grid-template-columns: 1fr 4em;
+      }
+
       > div {
         display: grid;
         place-items: center;
@@ -93,14 +122,15 @@
   }
 
   .payment-methods-container {
-    &#more-payment-methods {
-      justify-content: space-between;
-    }
-    display: flex;
+    display: grid;
+    grid-template-columns: repeat(auto-fill, 10em);
+    place-content: center;
     flex-wrap: wrap;
     gap: 1rem;
-    @media (max-width: 350px) {
-      justify-content: center;
+
+    @media (max-width: 368px) {
+      grid-template-columns: 10em;
+      width: 100%;
     }
     > .payment-method {
       display: grid;
@@ -116,6 +146,7 @@
         place-content: center;
         > img {
           max-width: 100%;
+          max-height: 44px;
         }
       }
     }
diff --git a/pass/routes/api.js b/pass/routes/api.js
index f270389..4ec5e6f 100644
--- a/pass/routes/api.js
+++ b/pass/routes/api.js
@@ -22,7 +22,7 @@ router.post("/key/create", (req, res) => {
     // Calculate amount from price
     let price = req.body.price;
     if (price) {
-      amount = Math.ceil((price / config.get("price.per_300")) * 300);
+      amount = Math.ceil(price / config.get("price.per_token"));
     } else {
       res.status(403).json({
         code: 403,
@@ -69,7 +69,7 @@ router.post("/key/:key/discharge", (req, res) => {
     // Calculate amount from price
     let price = req.body.price;
     if (price) {
-      amount = Math.ceil((price / config.get("price.per_300")) * 300);
+      amount = Math.ceil(price / config.get("price.per_token"));
     } else {
       res.status(403).json({
         code: 403,
@@ -102,7 +102,7 @@ router.post("/key/:key/charge", (req, res) => {
     // Calculate amount from price
     let price = req.body.price;
     if (price) {
-      amount = Math.ceil((price / config.get("price.per_300")) * 300);
+      amount = Math.ceil(price / config.get("price.per_token"));
     } else {
       res.status(403).json({
         code: 403,
diff --git a/pass/views/checkout/charge.ejs b/pass/views/checkout/charge.ejs
index 1253686..d156101 100644
--- a/pass/views/checkout/charge.ejs
+++ b/pass/views/checkout/charge.ejs
@@ -3,7 +3,7 @@
   <div class="checkout-amounts">
     <a href="<%= change_url.amount %>" class="checkout-amount" title="Menge ändern">
       <span class="checkout-amount"><%= checkout.amount %></span>
-      <span class="checkout-cost"><%= checkout.amount / 300 * price.per_300%>€</span>
+      <span class="checkout-cost"><%= (checkout.amount * price.per_token).toFixed(2) %>€</span>
       <span class="checkout-duration"><%= req.t("charge.includes-vat", {ns: "checkout"}) _%></span>
     </a>
   </div>
@@ -35,7 +35,7 @@
     <%_ for(let i = 0; i < price.purchasable.length; i++) { _%>
     <a href="<%= baseDir _%>/key/<%= key.key.get_key() %>/checkout/<%= price.purchasable[i] _%>#payment" class="checkout-amount">
       <span class="checkout-amount"><%= price.purchasable[i] _%></span>
-      <span class="checkout-cost"><%= (price.purchasable[i] / 300) * price.per_300 _%>€</span>
+      <span class="checkout-cost"><%= (price.purchasable[i] * price.per_token).toFixed(2) _%>€</span>
       <span class="checkout-duration">> <%= req.t("charge.month", {ns: "checkout", count: price.purchasable[i] / 300}) _%>*</span>
     </a>
     <%_ } _%>
diff --git a/pass/views/cost.ejs b/pass/views/cost.ejs
index 6d15fe8..0b46837 100644
--- a/pass/views/cost.ejs
+++ b/pass/views/cost.ejs
@@ -1,94 +1,48 @@
 <%- include('templates/page_header', {css: [`${baseDir}/styles/cost.css`],
 js:[]}); %>
 <div id="content">
-  <h1>Das kostet Ihr MetaGer Schlüssel</h1>
-  <p>
-    Für jede werbefreie Suche auf MetaGer wird <b>1 Token</b> berechnet. Sie
-    haben aber auch die Möglichkeit weitere kostenpflichtige Suchmaschinen zu
-    Ihrer Suche hinzuzufügen. Die Kosten pro Suche können Sie dann jeweils der
-    Einstellungsseite entnehmen.
-  </p>
-  <p>
-    Ihren Schlüssel können Sie jederzeit mit einem der folgenden Pakete
-    aufladen.
-  </p>
+  <h1><%= req.t("headings.0", {ns: "cost"}) _%></h1>
+  <p><%- req.t("texts.0", {ns: "cost"}) _%></p>
+  <p><%= req.t("texts.1", {ns: "cost"}) _%></p>
   <div id="price-tiers">
     <%_ for(let i = 0; i < price.purchasable.length; i++) { _%>
     <div class="price-tier">
       <div class="amount"><%= price.purchasable[i] _%></div>
       <div class="price">
-        <%= (price.purchasable[i] / 300) * price.per_300 _%>€
-      </div>
-      <div class="lasts-for">
-        > <%= price.purchasable[i] / 300 %> Monat<%_ if(price.purchasable[i] >
-        300) { _%>e<%_ } _%>*
+        <%= (price.purchasable[i] * price.per_token).toFixed(0) _%>€
       </div>
+      <div class="lasts-for">> <%= req.t("months", {ns: "cost", count: Math.floor(price.purchasable[i] / 900)}) %> *</div>
     </div>
     <%_ } _%>
   </div>
-  <p>
-    * Die angegebenen Zeiträume sind Schätzungen, die auf unseren
-    Erfahrungswerten basieren und sollen einen Anhaltspunkt geben, wie viele
-    Token benötigt werden.
-  </p>
-  <h2>Das Wichtigste zusammengefasst:</h2>
+  <p class="hint"><%= req.t("texts.2", {ns: "cost"}) _%></p>
+  <h2><%= req.t("headings.1", {ns: "cost"}) _%>:</h2>
   <ol id="short-info">
     <li>
       <img src="<%= baseDir _%>/images/calendar-365.svg" alt="" />
-      <h3>Gekaufte Suchen bleiben 1 Jahr lang gültig</h3>
-      <div>
-        Ihre gekauften Tokens sind darauf ausgelegt so lange gültig zu bleiben,
-        bis sie verbraucht wurden. Es gibt kein Abo.
-      </div>
+      <h3><%= req.t("short-info.0.heading", {ns: "cost"}) _%></h3>
+      <div><%= req.t("short-info.0.text", {ns: "cost"}) _%></div>
     </li>
     <li>
       <img src="<%= baseDir _%>/images/money.svg" alt="" />
-      <h3>30 Tage Geld Zurück Garantie</h3>
-      <div>
-        Sollten Sie mit Ihrem Schlüssel unzufrieden sein, haben Sie nach dem
-        Kauf 30 Tage Zeit das nicht verbrauchte Guthaben wieder zurück zu geben.
-      </div>
+      <h3><%= req.t("short-info.1.heading", {ns: "cost"}) _%></h3>
+      <div><%= req.t("short-info.1.text", {ns: "cost"}) _%></div>
     </li>
     <li>
       <img src="<%= baseDir _%>/images/cogwheel.svg" alt="" />
-      <h3>Schlüssel wird automatisch im Browser eingerichtet und verwendet</h3>
-      <div>
-        Um Ihren MetaGer Schlüssel bei der Suche zu verwenden brauchen Sie
-        nichts weiter tun. Nach dem Auffüllen ist er automatisch in Ihrem
-        Browser eingerichtet und Sie erhalten Informationen zur einfachen
-        Einrichtung auf weiteren Geräten.
-      </div>
+      <h3><%= req.t("short-info.2.heading", {ns: "cost"}) _%></h3>
+      <div><%= req.t("short-info.2.text", {ns: "cost"}) _%></div>
     </li>
     <li>
       <img src="<%= baseDir _%>/images/metager-schloss-orange.svg" alt="" />
-      <h3>Genauso anonym wie eine Suche ohne Schlüssel</h3>
-      <div>
-        Verwenden Sie unsere <a href="#">Android App</a>, oder unsere Extension
-        für <a href="#">Firefox</a> und <a href="#">Chrome</a> und seien Sie
-        unter Verwendung von
-        <a href="https://en.wikipedia.org/wiki/Blind_signature">
-          blinden Signaturen
-        </a>
-        beweisbar genauso anonym untewegs, wie ohne Schlüssel.
-      </div>
+      <h3><%= req.t("short-info.3.heading", {ns: "cost"}) _%></h3>
+      <div><%- req.t("short-info.3.text", {ns: "cost"}) _%></div>
     </li>
   </ol>
-  <h2 id="payment-methods">Zahlungsmethoden</h2>
-  <p>
-    MetaGer Schlüssel wurden von uns so konzipiert, dass Sie per Design ohne
-    personenbeziehbare Daten auskommen. Nichtsdestotrotz fallen spätestens bei
-    der Durchführung einer Zahlung meist welche an. Sei es nun die IBAN des
-    zahlenden Kontos, oder die E-Mail Adresse des verwendeten PayPal Kontos. Der
-    SUMA-EV verarbeitet diese Daten nicht selbst und speichert sie auch nicht
-    ab. Allerdings tut es je nach Zahlungsmethode der Zahlungsdienstleister.
-    Dadurch verbleibt natürlich auf unseren Kontoauszügen eine entsprechende
-    Buchung. Wir sind verpflichtet diese Kontoauszüge 10 Jahre lang aufzuheben.
-  </p>
-  <p>
-    Deshalb bieten wir zusätzlich zu den Standard-Zahlungsmethoden auch zwei an,
-    bei denen Sie anonym bezahlen können.
-  </p>
-  <h3>Anonyme Zahlungsmethoden</h3>
+  <h2 id="payment-methods"><%= req.t("payment-methods.heading", {ns: "cost"}) _%></h2>
+  <p><%= req.t("payment-methods.texts.0", {ns: "cost"}) _%></p>
+  <p><%= req.t("payment-methods.texts.1", {ns: "cost"}) _%></p>
+  <h3><%= req.t("payment-methods.anonymous", {ns: "cost"}) _%></h3>
   <div class="payment-methods-container">
     <div class="payment-method">
       <div>
@@ -101,7 +55,7 @@ js:[]}); %>
       </div>
     </div>
   </div>
-  <h3>Weitere Zahlungsmethoden</h3>
+  <h3><%= req.t("payment-methods.more", {ns: "cost"}) _%></h3>
   <div id="more-payment-methods" class="payment-methods-container">
     <div class="payment-method">
       <div>
-- 
GitLab