From 51d36caccf4361acb7c68230ab559c5705f8a356 Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@hebeler.club> Date: Fri, 24 Mar 2023 17:09:46 +0100 Subject: [PATCH] added base interface to process cash payments --- pass/app.js | 6 +++ pass/app/Exchangerates.js | 58 +++++++++++++++++++++++++++++ pass/bin/cron | 19 ++++++++++ pass/config/default.json | 15 +++++++- pass/lang/de/admin.json | 24 ++++++++++++ pass/public/styles/admin/base.css | 1 + pass/public/styles/admin/base.less | 28 ++++++++++++++ pass/public/styles/admin/index.css | 1 + pass/public/styles/admin/index.less | 23 ++++++++++++ pass/routes/admin/index.js | 16 ++++++++ pass/routes/index.js | 3 ++ pass/views/admin/index.ejs | 15 ++++++++ pass/views/admin/payments/cash.ejs | 32 ++++++++++++++++ 13 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 pass/app/Exchangerates.js create mode 100644 pass/lang/de/admin.json create mode 100644 pass/public/styles/admin/base.css create mode 100644 pass/public/styles/admin/base.less create mode 100644 pass/public/styles/admin/index.css create mode 100644 pass/public/styles/admin/index.less create mode 100644 pass/routes/admin/index.js create mode 100644 pass/views/admin/index.ejs create mode 100644 pass/views/admin/payments/cash.ejs diff --git a/pass/app.js b/pass/app.js index 24137de..9bb15e4 100644 --- a/pass/app.js +++ b/pass/app.js @@ -16,6 +16,12 @@ const mglangdetector = require("./app/Langdetector"); const langdetector = new i18nextmiddleware.LanguageDetector(); langdetector.addDetector(mglangdetector); let ns = []; +readdirSync(path.join(__dirname, 'lang', "de")).forEach((fileName) => { + const joinedPath = path.join(path.join(__dirname, 'lang', 'de'), fileName) + if (!lstatSync(joinedPath).isDirectory()) { + ns.push(fileName.replace(".json", "")); + } +}); i18next.use(langdetector).use(i18nextfsbackend).init({ debug: false, diff --git a/pass/app/Exchangerates.js b/pass/app/Exchangerates.js new file mode 100644 index 0000000..1497987 --- /dev/null +++ b/pass/app/Exchangerates.js @@ -0,0 +1,58 @@ +const { writeFileSync, readFileSync, existsSync } = require("fs"); +const dayjs = require("dayjs"); +const config = require("config"); + +class Exchangerates { + #data_path = "/data/exchangerates.json"; + #currencies = [ + "USD", + "CAD", + "GBP" + ]; + + #exchangerates = { + + }; + + constructor() { + if (existsSync(this.#data_path)) { + this.#exchangerates = JSON.parse(readFileSync(this.#data_path)); + } + } + + async update() { + let currentdate = dayjs(); + let date_formatted = currentdate.format("YYYY-MM-DD"); + if (!(date_formatted in this.#exchangerates)) { + let api_url = new URL(config.get("payments.exchange_api_apilayer.host")); + let exchangerate = {} + for (let i = 0; i < this.#currencies.length; i++) { + + let params = new URLSearchParams({ + symbols: "EUR", + base: this.#currencies[i] + }); + api_url.search = params.toString(); + await fetch(api_url).then(result => result.json()).then(result => { + exchangerate[this.#currencies[i]] = result.rates.EUR; + }).catch(reason => { + console.error("reason"); + exchangerate = null; + }) + if (exchangerate === null) { + break; + } + } + if (exchangerate !== null) { + this.#exchangerates[date_formatted] = exchangerate; + this.save(); + } + } + } + + save() { + writeFileSync(this.#data_path, JSON.stringify(this.#exchangerates)); + } +} + +module.exports = Exchangerates; \ No newline at end of file diff --git a/pass/bin/cron b/pass/bin/cron index 013cc31..b67da6c 100644 --- a/pass/bin/cron +++ b/pass/bin/cron @@ -5,6 +5,7 @@ const config = require("config"); const path = require("path"); const { writeFileSync } = require("fs"); const { exec } = require("child_process"); +const Exchangerates = require("../app/Exchangerates"); let redis_client; /** * Will call every cron script every minute. @@ -25,6 +26,12 @@ let cronjobs = async () => { "YYYY-MM-DD HH:mm:ss" )}] Written ${written_logs} key changes.` ); + await fetchExchangerates(); + console.log( + `[${now.format( + "YYYY-MM-DD HH:mm:ss" + )}] Fetched exchange rates.` + ); await accentSync(); await redis_client.quit(); console.log(`[${now.format("YYYY-MM-DD HH:mm:ss")}] Finish`); @@ -66,6 +73,18 @@ async function accentSync() { } +async function fetchExchangerates() { + let redis_lock_key = "cron:fetchExchangerates"; + let interval_seconds = 60 * 15; // Will execute every 15 minutes + let lock = await tryToGetLock(redis_lock_key, interval_seconds); + if (!lock) { + return 0; + } + + let exchagerates = new Exchangerates(); + await exchagerates.update(); +} + async function writeLogsToOrder() { let redis_lock_key = "cron:writeLogsToOrder"; let interval_seconds = 60; // Will execute every minute diff --git a/pass/config/default.json b/pass/config/default.json index e9dc337..f757894 100644 --- a/pass/config/default.json +++ b/pass/config/default.json @@ -36,7 +36,14 @@ "price": { "per_300": 10, "vat": 7, - "purchasable": [300, 600, 900, 1200, 1800, 3600] + "purchasable": [ + 300, + 600, + 900, + 1200, + 1800, + 3600 + ] }, "redis": { "host": "express_redis", @@ -80,6 +87,10 @@ "services": { "paysafecard": {} } + }, + "exchange_api_apilayer": { + "host": "https://api.apilayer.com/exchangerates_data/live", + "api_key": "<APILAYER_API_KEY>" } } -} +} \ No newline at end of file diff --git a/pass/lang/de/admin.json b/pass/lang/de/admin.json new file mode 100644 index 0000000..cfed163 --- /dev/null +++ b/pass/lang/de/admin.json @@ -0,0 +1,24 @@ +{ + "breadcrumps": { + "overview": "Übersicht", + "payments-cash": "Barzahlung erfassen" + }, + "index": { + "actions": { + "cash-payment": "Bargeldzahlung erfassen", + "heading": "Aktionen:" + }, + "heading": "MetaGer Schlüssel Admin" + }, + "cash-payment": { + "info": "Erfasse hier eingegangene Barzahlungen um die zugehörigen Bestellungen zu buchen", + "price": { + "placeholder": "10,00", + "label": "Eingegangener Betrag" + }, + "orderid": { + "label": "Bestellnummer:", + "placeholder": "123456789" + } + } +} diff --git a/pass/public/styles/admin/base.css b/pass/public/styles/admin/base.css new file mode 100644 index 0000000..bb4dcd4 --- /dev/null +++ b/pass/public/styles/admin/base.css @@ -0,0 +1 @@ +div#admin-container{max-width:980px;margin:0 auto}div#admin-container h1{border-bottom:1px solid #ef7700;text-align:center}div#admin-container h2{width:max-content}div#admin-container ul.breadcrumps{padding:0;display:flex;flex-wrap:wrap;gap:1.5rem;color:#6a6a6a}div#admin-container ul.breadcrumps>li:first-child{list-style-type:none} \ No newline at end of file diff --git a/pass/public/styles/admin/base.less b/pass/public/styles/admin/base.less new file mode 100644 index 0000000..f967aba --- /dev/null +++ b/pass/public/styles/admin/base.less @@ -0,0 +1,28 @@ +@import "../misc/vars.less"; + +div#admin-container { + max-width: @max-content-width; + margin: 0 auto; + + h1 { + border-bottom: 1px solid @color-main; + text-align: center; + } + + h2 { + width: max-content; + } + + ul.breadcrumps { + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + color: lighten(@font-color-on-white, 10%); + > li { + &:first-child { + list-style-type: none; + } + } + } +} diff --git a/pass/public/styles/admin/index.css b/pass/public/styles/admin/index.css new file mode 100644 index 0000000..28890d1 --- /dev/null +++ b/pass/public/styles/admin/index.css @@ -0,0 +1 @@ +div#admin-container div#action-container{display:flex;flex-wrap:wrap;margin-top:2rem}div#admin-container div#action-container>a{display:flex;gap:.5rem;align-items:center;border:1px solid #ef7700;padding:1rem;border-radius:10px;text-decoration:none;color:inherit;cursor:pointer}div#admin-container div#action-container>a>img{width:2em} \ No newline at end of file diff --git a/pass/public/styles/admin/index.less b/pass/public/styles/admin/index.less new file mode 100644 index 0000000..9c7074d --- /dev/null +++ b/pass/public/styles/admin/index.less @@ -0,0 +1,23 @@ +@import "../misc/vars.less"; + +div#admin-container { + div#action-container { + display: flex; + flex-wrap: wrap; + margin-top: 2rem; + > a { + display: flex; + gap: 0.5rem; + align-items: center; + border: 1px solid @color-main; + padding: 1rem; + border-radius: 10px; + text-decoration: none; + color: inherit; + cursor: pointer; + > img { + width: 2em; + } + } + } +} diff --git a/pass/routes/admin/index.js b/pass/routes/admin/index.js new file mode 100644 index 0000000..d32a3fa --- /dev/null +++ b/pass/routes/admin/index.js @@ -0,0 +1,16 @@ +var express = require("express"); +var router = express.Router(); +const config = require("config"); +const { auth, requiresAuth, claimCheck } = require("express-openid-connect"); + +router.use(requiresAuth()); + +router.get("/", (req, res) => { + res.render("admin/index"); +}); + +router.get("/payments/cash", (req, res) => { + res.render("admin/payments/cash"); +}) + +module.exports = router; \ No newline at end of file diff --git a/pass/routes/index.js b/pass/routes/index.js index 6019f8a..a9218b6 100644 --- a/pass/routes/index.js +++ b/pass/routes/index.js @@ -7,6 +7,7 @@ var browserify = require("browserify-middleware"); var paypalCheckoutRouter = require("../routes/checkout/paypal.js"); var micropaymentCheckoutRouter = require("../routes/checkout/micropayment"); var apiRouter = require("./api"); +var adminRouter = require("./admin/index"); var path = require("path"); const config = require("config"); @@ -19,6 +20,8 @@ router.use("/api/json", apiRouter); var authenticationRouter = require("../routes/authentication"); router.use("/", authenticationRouter); +router.use("/admin", adminRouter); + /* GET home page. */ router.get("/", function (req, res, next) { req.i18n.setDefaultNamespace("index"); // Default NS for localized Strings diff --git a/pass/views/admin/index.ejs b/pass/views/admin/index.ejs new file mode 100644 index 0000000..b7131bc --- /dev/null +++ b/pass/views/admin/index.ejs @@ -0,0 +1,15 @@ +<%- include('../templates/page_header', {css: [`${baseDir}/styles/admin/base.css`, `${baseDir}/styles/admin/index.css`], js: []}); %> +<div id="admin-container"> + <h1><%= req.t("index.heading", {ns: "admin"}) _%></h1> + <ul class="breadcrumps"> + <li><%= req.t("breadcrumps.overview", {ns: "admin"}) _%></li> + </ul> + <h2><%= req.t("index.actions.heading", {ns: "admin"}) _%></h2> + <div id="action-container"> + <a id="action-cash-payment" href="<%= baseDir %>/admin/payments/cash"> + <img src="<%= baseDir %>/images/money.svg" alt="Geldschein"> + <div><%= req.t("index.actions.cash-payment", {ns: "admin"}) _%></div> + </a> + </div> +</div> +<%- include('../templates/page_footer'); -%> \ No newline at end of file diff --git a/pass/views/admin/payments/cash.ejs b/pass/views/admin/payments/cash.ejs new file mode 100644 index 0000000..914e602 --- /dev/null +++ b/pass/views/admin/payments/cash.ejs @@ -0,0 +1,32 @@ +<%- include('../../templates/page_header', {css: [`${baseDir}/styles/admin/base.css`, `${baseDir}/styles/admin/payments/cash.css`], js: []}); %> +<div id="admin-container"> + <h1><%= req.t("index.heading", {ns: "admin"}) _%></h1> + <ul class="breadcrumps"> + <li> + <a href="<%= baseDir _%>/admin/"> + <%= req.t("breadcrumps.overview", {ns: "admin"}) _%> + </a> + </li> + <li><%= req.t("breadcrumps.payments-cash", {ns: "admin"}) _%></li> + </ul> + + <p><%= req.t("cash-payment.info", {ns: "admin"}) _%></p> + <form method="POST"> + <div class="input-group"> + <label for="orderid"><%= req.t("cash-payment.orderid.label", {ns: "admin"}) _%></label> + <input type=" text" name="orderid" id="orderid" placeholder="<%= req.t("cash-payment.orderid.placeholder", {ns: "admin"}) _%>"> + </div> + <div class="input-group"> + <label for="price"><%= req.t("cash-payment.price.label", {ns: "admin"}) _%></label> + <input type="number" name="price" id="price" placeholder="<%= req.t("cash-payment.price.placeholder", {ns: "admin"}) _%>" step="0.01"> + <select name="currency" id="currency"> + <option value="EUR" selected>EUR</option> + <option value="USD">USD</option> + <option value="CAD">CAD</option> + <option value="GBP">GBP</option> + </select> + </div> + </form> + +</div> +<%- include('../../templates/page_footer'); -%> \ No newline at end of file -- GitLab