From a4196b910add12893a446dde2602f568c34de979 Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@hebeler.club> Date: Mon, 17 Apr 2023 21:48:22 +0200 Subject: [PATCH] removed old order class --- pass/app/Order.js | 787 ------------------ pass/app/payment_processor/Cash.js | 4 +- pass/app/payment_processor/Manual.js | 10 +- pass/app/payment_processor/Micropayment.js | 36 +- .../app/payment_processor/PaymentProcessor.js | 20 +- pass/app/payment_processor/Paypal.js | 8 +- pass/app/pdf/OrderReceipt.js | 16 +- pass/bin/cron | 1 - pass/routes/admin/index.js | 82 +- pass/routes/api.js | 75 +- pass/routes/checkout/cash.js | 6 - pass/routes/checkout/checkout.js | 184 ---- pass/routes/checkout/development.js | 56 -- pass/routes/checkout/manual.js | 24 +- pass/routes/checkout/micropayment.js | 92 +- pass/routes/checkout/paypal.js | 25 +- pass/routes/index.js | 3 +- pass/routes/orders/orders.js | 51 +- pass/routes/redeem.js | 247 ------ 19 files changed, 215 insertions(+), 1512 deletions(-) delete mode 100644 pass/app/Order.js delete mode 100644 pass/routes/checkout/checkout.js delete mode 100644 pass/routes/checkout/development.js delete mode 100644 pass/routes/redeem.js diff --git a/pass/app/Order.js b/pass/app/Order.js deleted file mode 100644 index 49b9c99..0000000 --- a/pass/app/Order.js +++ /dev/null @@ -1,787 +0,0 @@ -const config = require("config"); -const dayjs = require("dayjs"); -const path = require("path"); -const Key = require("./Key"); -const RedisClient = require("./RedisClient"); -const PaymentProcessor = require("./payment_processor/PaymentProcessor"); -const { CLIENT } = require("./RedisClient"); -const i18next = require("i18next"); - -class Order { - static get STORAGE_MUTEX_KEY_PREFIX() { - return "order_mutex:"; - } - - static get STORAGE_KEY_PREFIX() { - return "order:"; - } - - static get PURCHASE_TAX_AMOUNT() { - return 0.07; - } - - // How long is a link between order <=> key stored - static get PURCHASE_LINK_TIME_DAYS() { - return 31; - } - static get PURCHASE_LINK_ORDER_TO_KEY_PREFIX() { - return "order_link_order_key:"; - } - - // How many minutes is a user allowed to take for finishing the payment - static get PURCHASE_STORAGE_TIME_UNCOMPLETED_HOURS() { - return 6; - } - - static GET_ORDER_FILE_BASE_PATH(order_date) { - let order_path = config.get("storage.data_path"); - order_path = path.join(order_path, process.env.NODE_ENV, "orders"); - order_path = path.join( - order_path, - order_date.format("YYYY"), - order_date.format("MM") - ); - - return order_path; - } - /** - * Data stored in Redis Database - */ - #order_id; - #order_date; - #expires_at; - #amount; - #amount_refund_requested = 0; - #amount_refunded = 0; - #price; - #foreign_currency_price; - #foreign_currency; - #order_key_charged = false; - #payment_captured = false; - #order_key_changes = []; - - #receipt_created = false; - #civicrm_contribution_id; - /** @type {PaymentProcessor} */ - #payment_processor; - - /** - * Data populated by context - */ - #order_path; - - constructor( - order_id, - order_date = null, - amount, - price, - foreign_currency_price, - foreign_currency, - payment_processor, - amount_refund_requested = 0, - order_key_charged = false, - payment_captured = false, - amount_refunded = 0, - receipt_created = false, - civicrm_contribution_id, - order_key_changes = [] - ) { - this.#order_id = order_id; - if (order_date !== null) { - this.#order_date = order_date; - } else { - this.#order_date = dayjs.unix(this.#order_id.substr(0, 10)); - } - this.#expires_at = dayjs().add(config.get("keys.expiration_days"), "days"); - - - this.#order_path = Order.GET_ORDER_FILE_BASE_PATH(this.#order_date); - - this.#amount = parseInt(amount); - this.#price = parseFloat(price); - if (foreign_currency_price !== undefined) { - this.#foreign_currency_price = parseFloat(foreign_currency_price); - } - if (foreign_currency !== undefined) { - this.#foreign_currency = foreign_currency; - } - - this.#payment_processor = payment_processor; - - this.#amount_refund_requested = amount_refund_requested; - this.#order_key_charged = order_key_charged; - this.#payment_captured = payment_captured; - this.#amount_refunded = amount_refunded; - - this.#receipt_created = receipt_created; - - this.#civicrm_contribution_id = civicrm_contribution_id; - this.#order_key_changes = order_key_changes; - } - - getOrderID() { - return this.#order_id; - } - - getAmount() { - return this.#amount; - } - - getNettoPrice() { - let amount = this.getPrice(); - let vat = config.get("price.vat") / 100; // Vat is configured in % - return amount / (1 + vat); - } - getVat() { - return config.get("price.vat"); - } - getVatAmount() { - let vat = config.get("price.vat") / 100; // Vat is configured in % - let amount = this.getPrice(); - return (amount / (1 + vat)) * vat; - } - getPrice() { - return this.#price; - } - getForeignCurrencyPrice() { - return this.#foreign_currency_price; - } - getForeignCurrency() { - return this.#foreign_currency; - } - - /** - * - * @returns {PaymentProcessor} - */ - getPaymentProcessor() { - return this.#payment_processor; - } - - /** - * - * @param {dayjs.Dayjs} expiration - */ - setExpiration(expiration) { - this.#expires_at = expiration; - } - - async captureOrder() { - if (this.isPaymentCaptured()) { - throw new Error("Order already captured"); - } - return this.getWriteLock() - .then(() => this.getPaymentProcessor().captureOrder()) - .then(() => this.createCiviCRMOrder()) - .then(() => { - this.#payment_captured = true; - this.#order_date = dayjs(); - return this.save(); - }) - .then(() => this.releaseWriteLock()); - } - - async chargeKey() { - if (this.isOrderKeyCharged()) { - throw new Error("Key already charged"); - } - return this.getWriteLock() - .then(() => this.getKeyFromOrderLink()) - .then((key) => Key.GET_KEY(key, true)) - .then((key) => { - key.charge_key_order( - this.getAmount(), - this.getOrderID(), - this.#expires_at - ); - return key.save(); - }) - .then(() => { - this.#order_key_charged = true; - return this.save(); - }) - .then(() => this.releaseWriteLock()); - } - - async requestRefund(amount) { - if (this.#amount_refund_requested > 0) { - throw new Error("Refund is already requested"); - } - let new_key = null; - return this.getWriteLock() - .then(() => this.getKeyFromOrderLink()) - .then((key) => Key.GET_KEY(key, true)) - .then((key) => { - key.discharge_key(amount, this.getOrderID()); - return key.save(); - }) - .then((key) => { - new_key = key; - this.#amount_refund_requested = amount; - return this.save(); - }) - .then(() => this.releaseWriteLock()) - .then(() => new_key); - } - - /** - * Processes a refund which was previously issued by the customer. - * Will issue a refund from the payment processor - * - * @returns - */ - async refund() { - if (this.#amount_refunded > 0 || this.#amount_refund_requested === 0) { - throw "No refund requested or Refund already processed"; - } - - let refund_price = parseFloat( - ( - Math.ceil( - (this.#amount_refund_requested / this.#amount) * this.#price * 100 - ) / 100 - ).toFixed(2) - ); - - return this.getWriteLock() - .then(() => this.getPaymentProcessor().refundOrder(refund_price)) - .then(() => { - this.#amount_refunded = this.#amount_refund_requested; - return this.save(); - }) - .then(() => this.releaseWriteLock()); - } - - /** - * Processes an external refund. I.e. when the refund was issued through Payment Gateway directly - * - * @param {float} price price that was refunded externally - */ - async external_refund(price) { - // Create amount from refunded price - price = Math.min(this.#price, price); - let amount = Math.floor((price / this.#price) * this.#amount); - - return this.getWriteLock() - .then(() => this.getKeyFromOrderLink()) - .then((key) => Key.GET_KEY(key, true)) - .then((key) => { - try { - key.discharge_key(amount, this.getOrderID()); - return key.save(); - } catch (err) { - throw err; - } - }) - .then(() => { - this.#amount_refunded = amount; - return this.save(); - }) - .then(() => this.releaseWriteLock()); - } - - async denyRefund() { - if (this.#amount_refunded > 0 || this.#amount_refund_requested === 0) { - return false; - } - return this.getWriteLock() - .then(() => this.getKeyFromOrderLink()) - .then((key) => Key.GET_KEY(key, true)) - .then((key) => { - key.charge_key_order( - this.#amount_refund_requested, - this.getOrderID(), - this.#expires_at - ); - return key.save(); - }) - .then(() => { - this.#amount_refund_requested = 0; - return this.save(); - }) - .then(() => this.releaseWriteLock()) - .then(() => { - return true; - }) - .catch((reason) => { - console.debug(reason); - return false; - }); - } - - isReceiptCreated() { - return this.#receipt_created; - } - - getAmountRefundRequested() { - return this.#amount_refund_requested; - } - - isOrderKeyCharged() { - return this.#order_key_charged; - } - isPaymentCaptured() { - return this.#payment_captured; - } - getAmountRefunded() { - return this.#amount_refunded; - } - - /** - * - * @param {int} amount - * @param {string} key - * @param {PaymentProcessor} payment_processor - * @param {i18next.TFunction} t - * @returns - */ - static async CREATE_NEW_ORDER(amount, key, payment_processor, t) { - // Calculate price from amount - let price = (amount / 300) * config.get("price.per_300"); - price = price.toFixed(2); - - // Validate that Key can be charged with another order - /** - * @type {Key} - */ - let loaded_key = null; - return Key.GET_KEY(key) - .then((key) => { - if (!key.isChargable()) { - throw "Key cannot be charged"; - } else { - loaded_key = key; - return true; - } - }) - .then(() => Order.GENERATE_UNIQUE_ORDER_ID()) - .then((order_id) => { - let new_order = new Order( - order_id, - null, - amount, - price, - null, - null, - payment_processor - ); - return new_order - .getWriteLock() - .then(() => new_order.getPaymentProcessor().createOrder(new_order, t)) // Create Order on PaymentProcessor side - .then(() => new_order.createOrderLink(loaded_key.get_key())) - .then(() => new_order.save()) - .then(() => new_order.releaseWriteLock()) - .then(() => { - return new_order; - }); - }); - } - - /** - * - * @param {Number} order_id - * @returns {Promise<Order>} - */ - static async LOAD_ORDER_FROM_ID(order_id) { - return new Promise((resolve, reject) => { - __redis_client - .get(Order.STORAGE_KEY_PREFIX + order_id) - .then((order_data) => { - let order_date = dayjs.unix(order_id.substr(0, 10)); - if (order_data === null) { - let order_file = path.join( - Order.GET_ORDER_FILE_BASE_PATH(order_date), - order_id.toString() + ".json" - ); - let fs = require("fs"); - if (fs.existsSync(order_file)) { - order_data = JSON.parse(fs.readFileSync(order_file)); - if ("order_date" in order_data && order_data.order_date) { - order_date = dayjs(order_data.order_date); - } - } else { - throw "Could not find Order in our database!"; - } - } else { - order_data = JSON.parse(order_data); - } - let loaded_order = new Order( - order_data.order_id, - order_date, - order_data.amount, - order_data.price, - order_data.foreign_currency_price, - order_data.foreign_currency, - PaymentProcessor.LOAD_PAYMENT_PROCESSOR( - order_data.payment_processor - ), - order_data.amount_refund_requested, - order_data.order_key_charged, - order_data.payment_captured, - order_data.amount_refunded, - order_data.receipt_created, - order_data.civicrm_contribution_id, - order_data.order_key_changes - ); - resolve(loaded_order); - }) - .catch((reason) => { - reject(reason); - }); - }); - } - - async save(redis_expiration = null) { - let stored_data = { - order_id: this.#order_id, - order_date: this.#order_date.unix(), - expires_at: this.#expires_at, - amount: this.#amount, - price: this.#price, - foreign_currency_price: this.#foreign_currency_price, - foreign_currency: this.#foreign_currency, - amount_refund_requested: this.#amount_refund_requested, - order_key_charged: this.#order_key_charged, - payment_captured: this.#payment_captured, - amount_refunded: this.#amount_refunded, - receipt_created: this.#receipt_created, - payment_processor: this.#payment_processor.serialize(), - civicrm_contribution_id: this.#civicrm_contribution_id, - order_key_changes: this.#order_key_changes, - }; - /** - * Completed Orders will be stored in Filesystem - * Uncompleted Orders will be stored in Redis - */ - if (redis_expiration === null) { - if (this.isPaymentCaptured()) { - redis_expiration = dayjs().add(Order.PURCHASE_LINK_TIME_DAYS, "day"); - } else { - redis_expiration = dayjs().add( - Order.PURCHASE_STORAGE_TIME_UNCOMPLETED_HOURS, - "hour" - ); - } - } else { - let min_expiration = dayjs().add( - Order.PURCHASE_STORAGE_TIME_UNCOMPLETED_HOURS, - "hour" - ); - if (this.isPaymentCaptured()) { - min_expiration = dayjs().add(Order.PURCHASE_LINK_TIME_DAYS, "day"); - } - if (min_expiration.isAfter(redis_expiration)) { - redis_expiration = min_expiration; - } - } - - let redis_key = Order.STORAGE_KEY_PREFIX + this.#order_id; - if (this.isPaymentCaptured()) { - let fs = require("fs"); - let order_file = path.join( - this.#order_path, - this.#order_id.toString() + ".json" - ); - // Create directory if it does not exist - if (!fs.existsSync(path.dirname(order_file))) { - fs.mkdirSync(path.dirname(order_file), { recursive: true }); - } - return __redis_client - .del(redis_key) - .then(() => this.updateOrderLinkTTL(redis_expiration)) - .then(() => - fs.writeFileSync(order_file, JSON.stringify(stored_data, null, 4)) - ); - } else { - // Store Order in Redis - return __redis_client - .pipeline() - .set(redis_key, JSON.stringify(stored_data)) - .expireat(redis_key, redis_expiration.unix()) - .exec() - .then(() => this.updateOrderLinkTTL(redis_expiration)); - } - } - - async delete() { - let redis_key = Order.STORAGE_KEY_PREFIX + this.#order_id; - if (!this.isPaymentCaptured()) { - return this.deleteOrderLink() - .then(() => __redis_client.del(redis_key)) - .then(async (deleted_keys) => { - if (deleted_keys > 0) { - return true; - } else { - return false; - } - }) - .catch(async () => { - return false; - }); - } else { - return false; - } - } - - async getWriteLock() { - let write_lock_key = Order.STORAGE_MUTEX_KEY_PREFIX + this.#order_id; - - return new Promise(async (resolve, reject) => { - let timestart = dayjs(); - let write_lock = 0; - do { - write_lock = await __redis_client - .pipeline() - .setnx(write_lock_key, 1) - .expiretime(write_lock_key) - .expire(write_lock_key, 15) - .exec(); - let expire_at = write_lock[1][1]; - write_lock = write_lock[0][1]; - if (write_lock === 1) { - resolve(true); - break; - } else if (dayjs().diff(timestart, "second") >= 15) { - if (expire_at >= 0) { - await __redis_client.expireat(write_lock_key, expire_at); - } - reject("Timed out waiting for write lock for Order"); - break; - } else { - if (expire_at >= 0) { - await __redis_client.expireat(write_lock_key, expire_at); - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - } while (true); - }); - } - - async releaseWriteLock() { - let write_lock_key = Order.STORAGE_MUTEX_KEY_PREFIX + this.#order_id; - return __redis_client.del(write_lock_key); - } - - addOrderKeyChanges(changes) { - this.#order_key_changes.push(changes); - } - - /** - * Creates a link between Order and Key that will automatically expire - * @param {string} key - */ - async createOrderLink(key) { - let expiration = dayjs().add(Order.PURCHASE_LINK_TIME_DAYS, "day"); - let redis_key_order_key = - Order.PURCHASE_LINK_ORDER_TO_KEY_PREFIX + this.#order_id; - return __redis_client - .pipeline() - .set(redis_key_order_key, key) - .expireat(redis_key_order_key, expiration.unix()) - .exec(); - } - - async deleteOrderLink() { - let redis_key_order_key = - Order.PURCHASE_LINK_ORDER_TO_KEY_PREFIX + this.#order_id; - return this.getKeyFromOrderLink().then((key) => { - return __redis_client.pipeline().del(redis_key_order_key).exec(); - }); - } - - async createCiviCRMOrder() { - if (this.getPaymentProcessor().serialize().processor_name === "Manual") { - // Manual Orders will not get stored in CiviCRM - return; - } - - let civicrm_data = config.get("app.civicrm"); - if (!civicrm_data.enabled) { - return; - } - return fetch(civicrm_data.url + "/Contribution/create", { - method: "post", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "X-Civi-Auth": `Bearer ${civicrm_data.api_key}`, - }, - body: - "params=" + - encodeURIComponent( - JSON.stringify({ - values: { - contact_id: civicrm_data.user_id, - financial_type_id: 5, - receive_date: this.#order_date.format("YYYY-MM-DD HH:mm:ss"), - total_amount: this.getPrice(), - is_test: process.env.NODE_ENV === "development" ? true : false, - is_pay_later: false, - tax_amount: this.getVatAmount(), - is_template: false, - }, - }) - ), - }) - .then((response) => { - if (response.status !== 200) { - throw "Error Creating Civicrm Contribution"; - } else { - return response.json(); - } - }) - .then((response_json) => { - this.#civicrm_contribution_id = response_json.values[0].id; - }); - } - - getOrderDate() { - return this.#order_date; - } - - /** - * - * @param {number} price - * @param {number} foreign_price - * @param {string} foreign_currency - */ - setPrice(price, foreign_price = 0, foreign_currency = "EUR") { - if ( - config.get("price.allowed_currencies").indexOf(foreign_currency) === -1 - ) { - throw new Error(`Currency ${foreign_currency} not allowed`); - } - if (this.isPaymentCaptured()) { - throw new Error("Cannot change details of processed Order"); - } - this.#price = price; - this.#foreign_currency_price = foreign_price; - this.#foreign_currency = foreign_currency; - - // Calculate new amount from price - this.#amount = parseInt( - Math.ceil((price / config.get("price.per_300")) * 300) - ); - } - - async attachReceipt(data, invoice_id) { - let civicrm_data = config.get("app.civicrm"); - if (!civicrm_data.enabled) { - return; - } - - return this.getWriteLock() - .then(() => - fetch(civicrm_data.url + "/Contribution/addReceipt", { - method: "post", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "X-Civi-Auth": `Bearer ${civicrm_data.api_key}`, - }, - body: - "params=" + - encodeURIComponent( - JSON.stringify({ - receipt: data, - invoice_id, - id: this.#civicrm_contribution_id, - }) - ), - }) - ) - .then((response) => { - if (response.status !== 200) { - throw "Error Creating Civicrm Contribution"; - } else { - return response.json(); - } - }) - .then((response_json) => { - if (response_json["count"] !== 1) { - throw JSON.stringify(response_json, null, 4); - } else { - this.#receipt_created = true; - return response_json; - } - }) - .then(() => this.save()) - .then(() => this.releaseWriteLock()); - } - - async getReceipt() { - let civicrm_data = config.get("app.civicrm"); - if (!civicrm_data.enabled) { - return Promise.reject("No Civicrm Config supplied"); - } - return fetch(civicrm_data.url + "/Contribution/getReceipt", { - method: "post", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "X-Civi-Auth": `Bearer ${civicrm_data.api_key}`, - }, - body: - "params=" + - encodeURIComponent( - JSON.stringify({ - id: this.#civicrm_contribution_id, - }) - ), - }) - .then((result) => result.json()) - .then((result_json) => { - if ( - typeof result_json.error_code !== "undefined" && - result_json.error_code !== 0 - ) { - throw result_json; - } else { - return result_json; - } - }); - } - - /** - * - * @param {dayjs.Dayjs} redis_expiration - * @returns - */ - async updateOrderLinkTTL(redis_expiration) { - let redis_key = Order.PURCHASE_LINK_ORDER_TO_KEY_PREFIX + this.#order_id; - - return __redis_client.expireat(redis_key, redis_expiration.unix()); - } - - /** - * Tries to get a key for this order. If the order link already expired this function rejects the promise - */ - async getKeyFromOrderLink() { - let redis_key = Order.PURCHASE_LINK_ORDER_TO_KEY_PREFIX + this.#order_id; - return __redis_client.get(redis_key).then(async (result) => { - if (result) { - return result; - } else { - throw "Order Link does not exist"; - } - }); - } - - 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 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 - order_id = "" + order_base + order_mutex; - let order_lock = await __redis_client.setnx(order_id, true); - if (order_lock === 1) { - await __redis_client.expire(order_id, 5); - } else { - order_mutex++; - order_id = null; - } - } while (order_id === null); - return order_id; - } -} - -module.exports = Order; diff --git a/pass/app/payment_processor/Cash.js b/pass/app/payment_processor/Cash.js index 1bd7c5f..6a730ff 100644 --- a/pass/app/payment_processor/Cash.js +++ b/pass/app/payment_processor/Cash.js @@ -16,10 +16,10 @@ class Cash extends PaymentProcessor { /** * - * @param {Order} order + * @param {PaymentReference} payment_reference * @param {i18next.TFunction} t */ - async createOrder(order, t) { + async createOrder(payment_reference, t) { return Promise.resolve(); } diff --git a/pass/app/payment_processor/Manual.js b/pass/app/payment_processor/Manual.js index f9f1b0a..780b58a 100644 --- a/pass/app/payment_processor/Manual.js +++ b/pass/app/payment_processor/Manual.js @@ -20,10 +20,10 @@ class Manual extends PaymentProcessor { /** * - * @param {Order} order + * @param {PaymentReference} payment_reference * @param {i18next.TFunction} t */ - async createOrder(order, t) { + async createOrder(payment_reference, t) { return Promise.resolve(); } @@ -31,9 +31,9 @@ class Manual extends PaymentProcessor { return Promise.resolve(); } /** - * - * @returns {boolean} - */ + * + * @returns {boolean} + */ isRefundSupported() { return false; } diff --git a/pass/app/payment_processor/Micropayment.js b/pass/app/payment_processor/Micropayment.js index b2f057b..eccfa43 100644 --- a/pass/app/payment_processor/Micropayment.js +++ b/pass/app/payment_processor/Micropayment.js @@ -1,10 +1,12 @@ -const Order = require("../Order"); const PaymentProcessor = require("./PaymentProcessor"); const i18next = require("i18next"); const config = require("config"); +const PaymentReference = require("../PaymentReference"); class Micropayment extends PaymentProcessor { - static get NAME() { return "Micropayment" }; + static get NAME() { + return "Micropayment"; + } /** * @var {string[]} */ @@ -38,29 +40,13 @@ class Micropayment extends PaymentProcessor { * Generates a URL for the customer where he is redirected to * to complete Payment. * - * @param {Order} order + * @param {PaymentReference} payment_reference * @param {i18next.TFunction} t * * @returns {Promise<URL>} */ - async createOrder(order, t) { - let payment_window_url = new URL( - `https://${this.#service}.micropayment.de/${this.#service}/event` - ); - let searchParams = new URLSearchParams({ - project: config.get("payments.micropayment.project"), - amount: order.getPrice() * 100, - title: t("product.name", { ns: "order" }), - mp_user_id: order.getOrderID(), - producttype: "quantity", - paytext: t("product.name", { ns: "order", count: order.getAmount() }), - testmode: process.env.NODE_ENV === "development" ? "1" : "0", - orderid: order.getOrderID(), - vatinfo: "1", - }); - payment_window_url.search = searchParams.toString(); - this.#capture_url = payment_window_url; - return Promise.resolve(this.#capture_url); + async createOrder(payment_reference, t) { + return Promise.resolve(); } /** @@ -73,9 +59,9 @@ class Micropayment extends PaymentProcessor { } /** - * - * @returns {boolean} - */ + * + * @returns {boolean} + */ isRefundSupported() { return true; } @@ -99,7 +85,7 @@ class Micropayment extends PaymentProcessor { return Promise.reject("Testmode Payment not accepted in production"); } if (!("currency" in query) || query.currency !== "EUR") { - return Promise.reject("Can only receive Payments in EUR") + return Promise.reject("Can only receive Payments in EUR"); } return Promise.resolve(); } diff --git a/pass/app/payment_processor/PaymentProcessor.js b/pass/app/payment_processor/PaymentProcessor.js index aa5594f..704ed09 100644 --- a/pass/app/payment_processor/PaymentProcessor.js +++ b/pass/app/payment_processor/PaymentProcessor.js @@ -1,5 +1,5 @@ -const Order = require("../Order"); const i18next = require("i18next"); +const PaymentReference = require("../PaymentReference"); class PaymentProcessor { constructor() { @@ -10,10 +10,10 @@ class PaymentProcessor { /** * - * @param {Order} order + * @param {PaymentReference} payment_reference * @param {i18next.TFunction} t */ - async createOrder(order, t) { + async createOrder(payment_reference, t) { throw new Error("Function createOrder() must be implemented"); } @@ -21,9 +21,9 @@ class PaymentProcessor { throw new Error("Function captureOrder() must be implemented"); } /** - * - * @returns {boolean} - */ + * + * @returns {boolean} + */ isRefundSupported() { throw new Error("Function isRefundPossible() must be implemented"); } @@ -66,13 +66,9 @@ class PaymentProcessor { }; if (!(processor_name in processors)) { - throw new Error( - `Cannot find Payment Processor ${processor_name}` - ); + throw new Error(`Cannot find Payment Processor ${processor_name}`); } - return processors[processor_name].DESERIALIZE( - serialized_data - ); + return processors[processor_name].DESERIALIZE(serialized_data); } } diff --git a/pass/app/payment_processor/Paypal.js b/pass/app/payment_processor/Paypal.js index c8d1341..7dbad29 100644 --- a/pass/app/payment_processor/Paypal.js +++ b/pass/app/payment_processor/Paypal.js @@ -1,11 +1,8 @@ -const Order = require("../Order"); const PaymentReference = require("../PaymentReference"); const Payment = require("../Payment"); const dayjs = require("dayjs"); const config = require("config"); const PaymentProcessor = require("./PaymentProcessor"); -const Key = require("../Key"); -const RedisClient = require("../RedisClient"); const webhook_redis_key = "checkout_paypal_webhook_id"; const base = config.get(`payments.paypal.base`); const i18next = require("i18next"); @@ -156,7 +153,7 @@ class Paypal extends PaymentProcessor { if ( response_data.status !== "COMPLETED" || response_data.purchase_units[0].payments.captures[0].status !== - "COMPLETED" + "COMPLETED" ) { console.error(JSON.stringify(response_data)); throw "PAYMENT_NOT_COMPLETED_ERROR"; @@ -290,7 +287,8 @@ class Paypal extends PaymentProcessor { ); } } - }); + } + ); } else { console.log(req.body); throw "Webhook not implemented"; diff --git a/pass/app/pdf/OrderReceipt.js b/pass/app/pdf/OrderReceipt.js index d0c0583..a549175 100644 --- a/pass/app/pdf/OrderReceipt.js +++ b/pass/app/pdf/OrderReceipt.js @@ -1,4 +1,3 @@ -const Order = require("../Order"); const dayjs = require("dayjs"); const i18next = require("i18next"); const config = require("config"); @@ -15,7 +14,12 @@ class OrderReceipt { * @param {Object} invoice * @param {i18next.TFunction} t */ - static async CREATE_ORDER_RECEIPT(payment_reference, payment, receipt = undefined, t) { + static async CREATE_ORDER_RECEIPT( + payment_reference, + payment, + receipt = undefined, + t + ) { let letter_left_margin = OrderReceipt.CM_TO_POINTS(2.5, false); let letter_right_margin = OrderReceipt.CM_TO_POINTS(2, false); @@ -256,10 +260,10 @@ class OrderReceipt { .fontSize(8) .text( " 1 " + - payment.converted_currency + - " = " + - (payment.price / payment.converted_price).toFixed(6) + - "€", + payment.converted_currency + + " = " + + (payment.price / payment.converted_price).toFixed(6) + + "€", { width: OrderReceipt.CM_TO_POINTS(3.25, false), align: "right", diff --git a/pass/bin/cron b/pass/bin/cron index efc8a84..be05d09 100644 --- a/pass/bin/cron +++ b/pass/bin/cron @@ -1,5 +1,4 @@ const dayjs = require("dayjs"); -const Order = require("../app/Order"); const RedisClient = require("../app/RedisClient"); const config = require("config"); const path = require("path"); diff --git a/pass/routes/admin/index.js b/pass/routes/admin/index.js index 9465b4b..5573953 100644 --- a/pass/routes/admin/index.js +++ b/pass/routes/admin/index.js @@ -2,8 +2,7 @@ var express = require("express"); var router = express.Router(); const config = require("config"); const dayjs = require("dayjs"); -const Order = require("../../app/Order"); -const { auth, requiresAuth, claimCheck } = require("express-openid-connect"); +const { auth } = require("express-openid-connect"); const { validationResult, matchedData, @@ -87,18 +86,21 @@ router.get( if (reqData.order && reqData.order.receipt_id !== null) { // There is already a receipt: Send it back - return Receipt.LOAD_RECEIPT_FROM_INTERNAL_ID(reqData.order.receipt_id).then(receipt => { - let receipt_data = Buffer.from(receipt.receipt, 'base64'); - res.header({ - "Content-Type": "application/pdf", - "Content-Disposition": `inline; filename=${receipt.public_id}.pdf` - }).send(receipt_data); + return Receipt.LOAD_RECEIPT_FROM_INTERNAL_ID( + reqData.order.receipt_id + ).then((receipt) => { + let receipt_data = Buffer.from(receipt.receipt, "base64"); + res + .header({ + "Content-Type": "application/pdf", + "Content-Disposition": `inline; filename=${receipt.public_id}.pdf`, + }) + .send(receipt_data); }); } if (data_complete) { // Create Invoice for preview - let receiptid = "xxxxxxxx"; return OrderReceipt.CREATE_ORDER_RECEIPT( res.locals.payment_reference, reqData.order, @@ -109,7 +111,7 @@ router.get( email: res.locals.email, address: res.locals.address, created_at: dayjs().format("YYYY-MM-DD HH:mm:ss"), - payment_id: reqData.order.id + payment_id: reqData.order.id, }), req.t ) @@ -119,9 +121,9 @@ router.get( let hasher = crypto.createHash("sha256"); hasher.update( reqData.company + - res.locals.name + - res.locals.email + - res.locals.address + res.locals.name + + res.locals.email + + res.locals.address ); res.locals.datahash = hasher.digest("hex"); res.render("admin/payments/receipt"); @@ -198,29 +200,35 @@ router.post( email: reqData.email, address: reqData.address, payment_id: reqData.order.id, - }).then((receipt) => { - reqData.id = receipt.public_id; - let receipt_data; - return OrderReceipt.CREATE_ORDER_RECEIPT( - payment_reference, - reqData.order, - receipt, - req.t - ) - .then((data) => { - receipt_data = Buffer.concat(data); - return receipt.attachReceipt(receipt_data.toString("base64")); - }) - .then((receipt) => reqData.order.setReceipt(receipt.id)).then(() => { - res.type("pdf").header({ - "Content-Disposition": `inline; filename=${receipt.public_id}.pdf` - }).send(receipt_data); - }) - }).catch(reason => { - console.error(reason); - res.redirect(url.toString()); - return; - }); + }) + .then((receipt) => { + reqData.id = receipt.public_id; + let receipt_data; + return OrderReceipt.CREATE_ORDER_RECEIPT( + payment_reference, + reqData.order, + receipt, + req.t + ) + .then((data) => { + receipt_data = Buffer.concat(data); + return receipt.attachReceipt(receipt_data.toString("base64")); + }) + .then((receipt) => reqData.order.setReceipt(receipt.id)) + .then(() => { + res + .type("pdf") + .header({ + "Content-Disposition": `inline; filename=${receipt.public_id}.pdf`, + }) + .send(receipt_data); + }); + }) + .catch((reason) => { + console.error(reason); + res.redirect(url.toString()); + return; + }); reqData.id = await OrderReceipt.CREATE_UNIQUE_RECEIPT_ID(); OrderReceipt.CREATE_ORDER_RECEIPT(reqData.order, reqData, req.t) .then((data) => { @@ -297,7 +305,7 @@ router.post( converted_currency: price_data.converted_currency, payment_processor: Cash.NAME, }) - .then((payment) => { + .then(() => { res.locals.orderid = payment_reference.public_id; res.render("admin/payments/cash_success"); }) diff --git a/pass/routes/api.js b/pass/routes/api.js index 4ec5e6f..36cfb85 100644 --- a/pass/routes/api.js +++ b/pass/routes/api.js @@ -5,13 +5,10 @@ const { body, validationResult } = require("express-validator"); const config = require("config"); const Key = require("../app/Key"); -const Order = require("../app/Order"); -const Manual = require("../app/payment_processor/Manual"); const Crypto = require("../app/Crypto"); const dayjs = require("dayjs"); const NodeRSA = require("node-rsa"); const PaymentReference = require("../app/PaymentReference"); -const RedisClient = require("../app/RedisClient"); router.use("/key", authorizedOnly); @@ -32,23 +29,29 @@ router.post("/key/create", (req, res) => { } } - return Key.GET_NEW_KEY().then(key => { - return PaymentReference.CREATE_NEW_REQUEST(amount, key.get_key(), dayjs().add("10", "years")).then(payment_reference => { - return payment_reference.chargeKey().then(() => { - return res.status(201).json({ - key: key.get_key(), - payment_reference: payment_reference.public_id, - charged: payment_reference.amount, + return Key.GET_NEW_KEY() + .then((key) => { + return PaymentReference.CREATE_NEW_REQUEST( + amount, + key.get_key(), + dayjs().add("10", "years") + ).then((payment_reference) => { + return payment_reference.chargeKey().then(() => { + return res.status(201).json({ + key: key.get_key(), + payment_reference: payment_reference.public_id, + charged: payment_reference.amount, + }); }); - }) - }); - }).catch((reason) => { - res.status(423).json({ - code: 423, - error: reason, - charged: 0, + }); + }) + .catch((reason) => { + res.status(423).json({ + code: 423, + error: reason, + charged: 0, + }); }); - }); }); router.get("/key/:key", (req, res) => { @@ -111,23 +114,29 @@ router.post("/key/:key/charge", (req, res) => { return; } } - return Key.GET_KEY(req.params.key).then(key => { - return PaymentReference.CREATE_NEW_REQUEST(amount, key.get_key(), dayjs().add("10", "years")).then(payment_reference => { - return payment_reference.chargeKey().then(() => { - return res.status(201).json({ - key: key.get_key(), - payment_reference: payment_reference.public_id, - charged: payment_reference.amount, + return Key.GET_KEY(req.params.key) + .then((key) => { + return PaymentReference.CREATE_NEW_REQUEST( + amount, + key.get_key(), + dayjs().add("10", "years") + ).then((payment_reference) => { + return payment_reference.chargeKey().then(() => { + return res.status(201).json({ + key: key.get_key(), + payment_reference: payment_reference.public_id, + charged: payment_reference.amount, + }); }); - }) - }); - }).catch((reason) => { - res.status(423).json({ - code: 423, - error: reason, - charged: 0, + }); + }) + .catch((reason) => { + res.status(423).json({ + code: 423, + error: reason, + charged: 0, + }); }); - }); }); router.get("/token/pubkey", async (req, res) => { diff --git a/pass/routes/checkout/cash.js b/pass/routes/checkout/cash.js index 6e35901..909ba58 100644 --- a/pass/routes/checkout/cash.js +++ b/pass/routes/checkout/cash.js @@ -1,13 +1,7 @@ var express = require("express"); -const Order = require("../../app/Order"); -const Manual = require("../../app/payment_processor/Manual"); -const config = require("config"); var router = express.Router({ mergeParams: true }); const { query, validationResult, matchedData } = require("express-validator"); const dayjs = require("dayjs"); -const crypto = require("crypto"); -const RedisClient = require("../../app/RedisClient"); -const Cash = require("../../app/payment_processor/Cash"); const PaymentReference = require("../../app/PaymentReference"); router.use("/", (req, res, next) => { diff --git a/pass/routes/checkout/checkout.js b/pass/routes/checkout/checkout.js deleted file mode 100644 index c035186..0000000 --- a/pass/routes/checkout/checkout.js +++ /dev/null @@ -1,184 +0,0 @@ -const Crypto = require("../../app/Crypto.js"); -const Order = require("../../app/Order.js"); -const config = require("config"); -const dayjs = require("dayjs"); - -var express = require("express"); -var router = express.Router(); -const { query, body, validationResult } = require("express-validator"); - -/* GET home page. */ -router.get( - "/", - query("amount") - .isInt({ min: -1, max: 12 }) - .withMessage("Amount needs to be between 0 and 4.") - .toInt(), - async function (req, res, next) { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - /** - * The user interface allows either 100 searches or steps of 250 searches (up to 12 * 250 = 3000) - * 100 searches are a little bit more expensive than 250 - * - * an amount of 0 corresponds to 100 searches - * an amount of 1-12 corresponds to 1-12 * 250 searches - */ - let params = { - amount: req.query.amount === 0 ? 1 : req.query.amount, - unit_size: req.query.amount === 0 ? 100 : 250, - price_per_unit: - req.query.amount === 0 ? Order.PRICE_FOR_100 : Order.PRICE_FOR_250, - order_id: await generate_unique_order_id(), - payments: { - paypal: { - client_id: config.get(`payments.paypal.client_id`), - }, - }, - }; - - let crypto = new Crypto(); - let order_date = dayjs(); - let expiration_date = order_date.add( - Order.PURCHASE_STORAGE_TIME_MONTHS, - "month" - ); - let private_key = await crypto.private_key_get(order_date, expiration_date); - params.crypto = { - N: private_key.keyPair.n, - E: private_key.keyPair.e, - }; - params.expires_at = expiration_date.format("YYYY-MM-DD"); - - // Generate hmac hash of the payment data so we are able to verify them when the client submits them again - // Generate hmac hash of the payment data so we are able to verify them when the client submits them again - params.integrity = crypto.createIntegrityHash( - params.order_id, - params.expires_at, - params.amount, - params.unit_size, - params.price_per_unit, - params.crypto.N, - params.crypto.E - ); - - res.render("checkout/checkout", params); - } -); - -router.use( - "/payment/order", - body("amount") - .isInt({ min: 1, max: 12 }) - .withMessage("Invalid amount submitted") - .toInt(), - body("unit_size").isIn(["100", "250"]).toInt(), - body("price_per_unit") - .isCurrency({ symbol: "", allow_negatives: false, thousands_separator: "" }) - .withMessage("Invalid Price Value.") - .toFloat(), - body("order_id").isInt({ min: 0 }).withMessage("Invalid `order_id` value"), - body("expires_at") - .isDate() - .matches(/^\d{4}-\d{2}-\d{2}$/) - .withMessage("Expiration date is not correct"), - body("public_key_e") - .isNumeric({ no_symbols: true }) - .withMessage("Invalid Public Key"), - body("public_key_n") - .isNumeric({ no_symbols: true }) - .withMessage("Invalid Public Key"), - body("integrity") - .isHash("sha256") - .custom((value, { req }) => { - let crypto = new Crypto(); - if ( - !crypto.validateIntegrityHash( - value, - req.body.order_id, - req.body.expires_at, - req.body.amount, - req.body.unit_size, - req.body.price_per_unit, - req.body.public_key_n, - req.body.public_key_e - ) - ) { - console.log("Invalid integrity"); - return Promise.reject("Integrity is not matching"); - } - return true; - }) - .withMessage( - "Value for `integrity` must be a SHA256 Hash and validate against the purchase data." - ), - body("encrypted_sales_receipts") - .custom((value, { req }) => { - if (!Array.isArray(value)) { - return Promise.reject("Parameter is not an array"); - } - for (let i = 0; i < value.length; i++) { - if (!/^\d+$/.test(value[i])) { - return Promise.reject( - "Encrypted Sales Receipt contains invalid data" - ); - } - } - let expected_ticket_count = (req.body.amount * req.body.unit_size) / 50; - if (expected_ticket_count % 1 !== 0) { - return Promise.reject("Expected ticket count is not an integer."); - } - if (value.length !== expected_ticket_count) { - return Promise.reject("Two many receipts compared to the order."); - } - return true; - }) - .withMessage("Invalid Sales Receipt"), - (req, res, next) => { - // This route gets called when the user initiates a payment: Validate the submitted data here - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - next("route"); - } -); - -/** Cancel is the same for all payment gateways */ -router.post("/payment/order/*/cancel", (req, res) => { - Order.LOAD_ORDER_FROM_ID(req.body.order_id).then((loaded_order) => { - if (loaded_order.isPaymentComplete()) { - res.status(400).json({ - msg: "Cannot delete a completed order", - }); - return; - } - loaded_order.delete().then((success) => { - if (success) { - res.status(200).json({ - msg: "Order deleted", - }); - } else { - res.status(400).json({ - errors: [ - { - msg: "Could not delete specified order", - }, - ], - }); - } - }); - }); -}); - -var developmentRouter = require("./development.js"); -router.use("/payment/order/development", developmentRouter); - -var paypalRouter = require("./paypal.js"); -router.use("/payment/order/paypal", paypalRouter); - -module.exports = router; diff --git a/pass/routes/checkout/development.js b/pass/routes/checkout/development.js deleted file mode 100644 index d373885..0000000 --- a/pass/routes/checkout/development.js +++ /dev/null @@ -1,56 +0,0 @@ -var express = require("express"); -var router = express.Router(); - -const config = require("config"); -const Order = require("../../app/Order.js"); - -router.post( - "/", - (req, res, next) => { - if (process.env.NODE_ENV !== "development") { - res.status(400).json({ - errors: [ - { msg: "Payment method only allowed in development environment." }, - ], - }); - } else { - next(); - } - }, - (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 - ); - - order - .save() - .then(() => { - order.signOrder().then(() => { - order.save().then(() => { - res.json({ - order_id: req.body.order_id, - expires_at: req.body.expires_at, - signatures: order.getSignatures(), - }); - }); - }); - }) - .catch((reason) => { - return res.status(400).json({ - errors: [ - { - msg: reason, - }, - ], - }); - }); - } -); - -module.exports = router; diff --git a/pass/routes/checkout/manual.js b/pass/routes/checkout/manual.js index b03b72e..b5ac5ce 100644 --- a/pass/routes/checkout/manual.js +++ b/pass/routes/checkout/manual.js @@ -1,7 +1,6 @@ var express = require("express"); const Order = require("../../app/Order"); -const Manual = require("../../app/payment_processor/Manual"); -const config = require("config"); +const PaymentReference = require("../../app/PaymentReference"); var router = express.Router({ mergeParams: true }); router.use("/", (req, res, next) => { @@ -24,27 +23,14 @@ router.get("/", (req, res) => { }); router.post("/", (req, res) => { - /** - * @type {Order} - */ - let new_order = null; - Order.CREATE_NEW_ORDER( + return PaymentReference.CREATE_NEW_REQUEST( req.params.amount, - req.data.key.key.get_key(), - new Manual(req.body.note), - req.t + req.data.key.key.get_key() ) - .then((order) => { - new_order = order; - return order.captureOrder(); - }) - .then(() => new_order.chargeKey()) + .then((payment_reference) => payment_reference.chargeKey()) .then(() => { let redirect_url = - `${res.locals.baseDir}/key/` + - req.data.key.key.get_key() + - "/orders/" + - new_order.getOrderID(); + `${res.locals.baseDir}/key/` + req.data.key.key.get_key(); res.redirect(redirect_url); }); }); diff --git a/pass/routes/checkout/micropayment.js b/pass/routes/checkout/micropayment.js index abebc4e..f3a5e4b 100644 --- a/pass/routes/checkout/micropayment.js +++ b/pass/routes/checkout/micropayment.js @@ -2,31 +2,39 @@ var express = require("express"); var router = express.Router({ mergeParams: true }); const config = require("config"); -const Order = require("../../app/Order"); const PaymentReference = require("../../app/PaymentReference"); const Micropayment = require("../../app/payment_processor/Micropayment"); const crypto = require("crypto"); router.get("/event", (req, res) => { Micropayment.VERIFY_WEBHOOK(req.query) - .then(() => PaymentReference.LOAD_FROM_PUBLIC_ID(req.query.paymentreference)) + .then(() => + PaymentReference.LOAD_FROM_PUBLIC_ID(req.query.paymentreference) + ) .then((payment_reference) => { let price = (req.query.amount / 100).toFixed(2); if (req.query.function === "refund") { price *= -1; } - let redirect_url = new URL(`${res.locals.baseDir}/key/` + payment_reference.key.get_key() + "/orders/" + payment_reference.public_id); + let redirect_url = new URL( + `${res.locals.baseDir}/key/` + + payment_reference.key.get_key() + + "/orders/" + + payment_reference.public_id + ); if (req.query.function !== "billing" && req.query.function !== "refund") { return redirect_url; } - return payment_reference.createPayment({ - price: (req.query.amount / 100).toFixed(2), - converted_currency: req.query.currency, - converted_price: (req.query.amount / 100).toFixed(2), - payment_processor: Micropayment.NAME, - payment_processor_id: req.query.auth, - payment_processor_data: req.query - }).then(payment => redirect_url); + return payment_reference + .createPayment({ + price: (req.query.amount / 100).toFixed(2), + converted_currency: req.query.currency, + converted_price: (req.query.amount / 100).toFixed(2), + payment_processor: Micropayment.NAME, + payment_processor_id: req.query.auth, + payment_processor_data: req.query, + }) + .then(() => redirect_url); }) .then((redirect_url) => { let response = { @@ -82,39 +90,41 @@ router.get("/:service", (req, res) => { }); router.post("/:service", async (req, res) => { - return PaymentReference.CREATE_NEW_REQUEST(req.data.checkout.amount, - req.data.key.key.get_key()).then(payment_reference => { - /** - * @type {URL} - */ - let payment_window_url = - req.data.checkout.payment.micropayment.payment_window_url; - let searchParams = { - project: config.get("payments.micropayment.project"), - amount: payment_reference.price * 100, - title: "MetaGer Schlüssel", - mp_user_id: payment_reference.public_id, - producttype: "quantity", - paytext: `${payment_reference.amount} MetaGer Token`, - testmode: process.env.NODE_ENV === "development" ? "1" : "0", - paymentreference: payment_reference.public_id, - vatinfo: "1", - }; - let seal = ""; - for (let key in searchParams) { - seal += `${key}=${searchParams[key]}&`; - } - seal = seal.replace(/&$/, ""); - seal += config.get("payments.micropayment.access_key"); + return PaymentReference.CREATE_NEW_REQUEST( + req.data.checkout.amount, + req.data.key.key.get_key() + ).then((payment_reference) => { + /** + * @type {URL} + */ + let payment_window_url = + req.data.checkout.payment.micropayment.payment_window_url; + let searchParams = { + project: config.get("payments.micropayment.project"), + amount: payment_reference.price * 100, + title: "MetaGer Schlüssel", + mp_user_id: payment_reference.public_id, + producttype: "quantity", + paytext: `${payment_reference.amount} MetaGer Token`, + testmode: process.env.NODE_ENV === "development" ? "1" : "0", + paymentreference: payment_reference.public_id, + vatinfo: "1", + }; + let seal = ""; + for (let key in searchParams) { + seal += `${key}=${searchParams[key]}&`; + } + seal = seal.replace(/&$/, ""); + seal += config.get("payments.micropayment.access_key"); - let hasher = crypto.createHash("md5"); - seal = hasher.update(seal).digest("hex"); - searchParams.seal = seal; + let hasher = crypto.createHash("md5"); + seal = hasher.update(seal).digest("hex"); + searchParams.seal = seal; - payment_window_url.search = (new URLSearchParams(searchParams)).toString(); + payment_window_url.search = new URLSearchParams(searchParams).toString(); - res.redirect(payment_window_url.toString()); - }); + res.redirect(payment_window_url.toString()); + }); }); module.exports = router; diff --git a/pass/routes/checkout/paypal.js b/pass/routes/checkout/paypal.js index c92397d..a337609 100644 --- a/pass/routes/checkout/paypal.js +++ b/pass/routes/checkout/paypal.js @@ -2,10 +2,7 @@ var express = require("express"); var router = express.Router({ mergeParams: true }); const config = require("config"); -const Order = require("../../app/Order.js"); const PaymentReference = require("../../app/PaymentReference.js"); -const Payment = require("../../app/Payment"); -const Key = require("../../app/Key.js"); const Paypal = require("../../app/payment_processor/Paypal.js"); const { body } = require("express-validator"); @@ -73,7 +70,7 @@ router.post("/:funding_source/order/create", async (req, res) => { 600, paypal.getOrderId() ) - .then((result) => { + .then(() => { return res.status(200).json({ payment_reference: payment_reference.public_id, paypal_order_id: paypal.getOrderId(), @@ -224,24 +221,4 @@ module.exports = router; ////////////////////// -async function refundPayment(capture_ids) { - const accessToken = await generateAccessToken(); - let promises = []; - capture_ids.forEach((capture_id) => { - promises.push( - fetch(`${base}/v2/payments/captures/${capture_id}/refund`, { - method: "post", - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - note_to_payer: "Something went wrong when processing your payment.", - }), - }) - ); - }); - return Promise.all(promises); -} - // generate an access token using client id and app secret diff --git a/pass/routes/index.js b/pass/routes/index.js index a377290..7740df2 100644 --- a/pass/routes/index.js +++ b/pass/routes/index.js @@ -1,5 +1,4 @@ var express = require("express"); -const Order = require("../app/Order.js"); var router = express.Router({ mergeParams: true }); var lessMiddleware = require("less-middleware"); @@ -23,7 +22,7 @@ router.use("/admin", adminRouter); router.use("/help", helpRouter); /* GET home page. */ -router.get("/", function (req, res, next) { +router.get("/", function (req, res) { req.i18n.setDefaultNamespace("index"); // Default NS for localized Strings res.render("index"); }); diff --git a/pass/routes/orders/orders.js b/pass/routes/orders/orders.js index 8365b07..028cd90 100644 --- a/pass/routes/orders/orders.js +++ b/pass/routes/orders/orders.js @@ -7,7 +7,6 @@ const { validationResult, matchedData, } = require("express-validator"); -const Order = require("../../app/Order"); const OrderReceipt = require("../../app/pdf/OrderReceipt"); const PaymentReference = require("../../app/PaymentReference"); const Payment = require("../../app/Payment"); @@ -22,7 +21,7 @@ router.use("/", (req, res, next) => { } next(); }); -router.get("/", (req, res, next) => { +router.get("/", (req, res) => { req.data.page = "order"; req.data.css.push(`${res.locals.baseDir}/styles/orders/orders.css`); res.render("key", req.data); @@ -31,7 +30,7 @@ router.get("/", (req, res, next) => { router.post( "/", body("payment_reference").matches(/^(Z)?(\d+)$/), - (req, res, next) => { + (req, res) => { let errors = validationResult(req); if (!errors.isEmpty()) { req.data.page = "order"; @@ -60,7 +59,9 @@ router.post( res.render("key", req.data); } else { res.redirect( - `${res.locals.baseDir}/key/${req.data.key.key.get_key()}/orders/${queryData.payment_reference}#order` + `${res.locals.baseDir}/key/${req.data.key.key.get_key()}/orders/${ + queryData.payment_reference + }#order` ); } }); @@ -118,21 +119,31 @@ router.use( req.data.order.payments = payments; req.data.css.push(`${res.locals.baseDir}/styles/orders/order.css`); - req.data.links.order_url = `${res.locals.baseDir - }/key/${req.data.key.key.get_key()}/orders/${queryData.payment_reference.public_id - }#order`; - req.data.links.order_actions_base = `${res.locals.baseDir - }/key/${req.data.key.key.get_key()}/orders/${queryData.payment_reference.public_id - }`; - req.data.links.receipt_url = `${res.locals.baseDir - }/key/${req.data.key.key.get_key()}/orders/${queryData.payment_reference.public_id - }/pdf`; - req.data.links.invoice_url = `${res.locals.baseDir - }/key/${req.data.key.key.get_key()}/orders/${queryData.payment_reference.public_id - }/invoice#invoice-form`; - req.data.links.refund_url = `${res.locals.baseDir - }/key/${req.data.key.key.get_key()}/orders/${queryData.payment_reference.public_id - }/refund#refund-form`; + req.data.links.order_url = `${ + res.locals.baseDir + }/key/${req.data.key.key.get_key()}/orders/${ + queryData.payment_reference.public_id + }#order`; + req.data.links.order_actions_base = `${ + res.locals.baseDir + }/key/${req.data.key.key.get_key()}/orders/${ + queryData.payment_reference.public_id + }`; + req.data.links.receipt_url = `${ + res.locals.baseDir + }/key/${req.data.key.key.get_key()}/orders/${ + queryData.payment_reference.public_id + }/pdf`; + req.data.links.invoice_url = `${ + res.locals.baseDir + }/key/${req.data.key.key.get_key()}/orders/${ + queryData.payment_reference.public_id + }/invoice#invoice-form`; + req.data.links.refund_url = `${ + res.locals.baseDir + }/key/${req.data.key.key.get_key()}/orders/${ + queryData.payment_reference.public_id + }/refund#refund-form`; /*if (req.data.order.order.isReceiptCreated()) { req.data.order.download_invoice_url = `${res.locals.baseDir}/key/${req.data.key.key.get_key() }/orders/${req.data.order.order.getOrderID()}/invoice/download`; @@ -177,7 +188,7 @@ router.get("/:payment_reference/:order_id/pdf", (req, res) => { .status(200) .header({ "Content-Type": "application/pdf", - "Content-Disposition": `inline, filename=${req.params.order_id}.pdf` + "Content-Disposition": `inline, filename=${req.params.order_id}.pdf`, }) .send(Buffer.concat(data)); }) diff --git a/pass/routes/redeem.js b/pass/routes/redeem.js deleted file mode 100644 index b11108d..0000000 --- a/pass/routes/redeem.js +++ /dev/null @@ -1,247 +0,0 @@ -var express = require("express"); -var router = express.Router(); -const { query, body, validationResult } = require("express-validator"); - -const config = require("config"); -const dayjs = require("dayjs"); -const Crypto = require("../app/Crypto"); -var customParseFormat = require("dayjs/plugin/customParseFormat"); -const Key = require("../app/Key"); -const Order = require("../app/Order"); -const path = require('path'); -const fs = require('fs'); -const readline = require('readline'); -dayjs.extend(customParseFormat); - -/** - * This routes are called after a purchase and intended to - * exchange a purchase receipt against a MetaGer-Pass Key - * - * This middleware validates the purchase receipt for all of the other routes - */ -router.use( - "/", - body("expiration_month") - .matches(/^\d{4}-\d{2}$/) - .custom((expiration_month, { req }) => { - dayjs(expiration_month + "-01"); - return true; - }) - .customSanitizer(expiration_month => { - return dayjs(expiration_month); - }) - .withMessage("Invalid Expiration Month supplied"), - body("generation_month") - .matches(/^\d{4}-\d{2}$/) - .custom((generation_month, { req }) => { - return dayjs(generation_month + "-01"); - }) - .customSanitizer(generation_month => { - return dayjs(generation_month); - }) - .withMessage("Invalid Generation Month supplied"), - body("receipts").custom(async (receipts, { req }) => { - return new Promise((resolve, reject) => { - new Crypto() - .validateMetaGerPassCode( - req.body.generation_month, - req.body.expiration_month, - req.body.metager_pass_codes - ) - .then(() => { - resolve(true); - }) - .catch((reason) => { - reject(reason); - }); - }); - }), - (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - next("route"); - } -); - -/* Recharge a MetaGer-Pass Key */ -router.post("/", body("metager_pass_key").isWhitelisted(Key.KEY_CHARSET).isLength({ min: 6, max: 20 }), async (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - let max_recharge_tries = 10; - let max_recharge_tries_hours = 1; // Defines in which time frame the recharge tries are counted; recharge tries get reset after this amount of hours without recharge try have passed - - let charge_amount = 0; - - // Redis Client - let Redis = require("ioredis"); - let redis_client = new Redis({ - host: config.get("redis.host"), - }); - // Dayjs - let dayjs = require("dayjs"); - - let key_recharge_cache_keys = []; - let key_recharge_cache_prefix = "recharge_key_cache"; - for (let i = 0; i < req.body.metager_pass_codes.length; i++) { - let code = req.body.metager_pass_codes[i].code; - key_recharge_cache_keys.push(key_recharge_cache_prefix + "_" + code); - charge_amount += Order.PACKET_SIZE; - } - - // Check one or more of the codes was already used to redeem a MetaGer-Pass Key - let order_month = dayjs(req.body.generation_month); - let redeem_file_path = path.join( - config.get("storage.data_path"), - process.env.NODE_ENV, - order_month.format("YYYY"), - order_month.format("MM"), - "redeemed.json"); - if (!fs.existsSync(path.dirname(redeem_file_path))) { - fs.mkdirSync(path.dirname(redeem_file_path), { recursive: true }); - } - - if (fs.existsSync(redeem_file_path)) { - let rl = readline.createInterface({ - input: fs.createReadStream(redeem_file_path), - output: process.stdout, - terminal: false - }); - for await (const line of rl) { - for (let i = 0; i < req.body.metager_pass_codes.length; i++) { - if (req.body.metager_pass_codes[i].code === line.trim()) { - return res.status(400).json({ - status: "FAILED", - msg: ["One or more of the provided MetaGer-Pass Codes are already redeemed."] - }); - } - } - } - } - - - let recharge_tries = 0; - redis_client.mget(key_recharge_cache_keys).then(async response => { - for (let i = 0; i < response.length; i++) { - let tries = response[i]; - if (tries && tries > recharge_tries) { - recharge_tries = tries; - } - } - - if (recharge_tries >= max_recharge_tries) { - res.status(400).json({ - status: "FAILED", - msg: "Too many failed attempts against non existing MetaGer-Pass Keys. Please try again later." - }); - } else { - recharge_tries++; - - let redis_pipeline = redis_client.pipeline(); - let expiration = dayjs().add(max_recharge_tries_hours, "hour"); - for (let i = 0; i < key_recharge_cache_keys.length; i++) { - redis_pipeline.set(key_recharge_cache_keys[i], recharge_tries); - redis_pipeline.expireat(key_recharge_cache_keys[i], expiration.unix()); - } - await redis_pipeline.exec(); - - Key.CHARGE_EXISTING_KEY(req.body.metager_pass_key, charge_amount).then(result => { - // Key is charged store the redeem codes into filesystem so they can only be used once - for (let i = 0; i < req.body.metager_pass_codes.length; i++) { - fs.appendFileSync(redeem_file_path, req.body.metager_pass_codes[i].code + "\n"); - } - - res.json(result); - }).catch(reason => { - res.status(400).json({ - status: "FAILED", - msg: [reason] - }); - }); - } - }); -}); - -/* Create a new MetaGer-Pass Key from purchase receipt */ -router.post("/create", async (req, res, next) => { - let key = ""; - - // Redis Client - let Redis = require("ioredis"); - let redis_client = new Redis({ - host: config.get("redis.host"), - }); - // Dayjs - let dayjs = require("dayjs"); - - let key_cache_redis_keys = []; - let key_redis_cache_prefix = "create_key_cache"; - // Check if the user already created a key which is in redis - for (let i = 0; i < req.body.metager_pass_codes.length; i++) { - let code = req.body.metager_pass_codes[i].code; - key_cache_redis_keys.push(key_redis_cache_prefix + "_" + code); - } - redis_client.mget(key_cache_redis_keys).then(async response => { - let existing_key = null; - let existing_key_expiration = null; - // Check if there is at least one existing key associated with the pass codes - for (let i = 0; i < response.length; i++) { - if (response[i]) { - existing_key = response[i]; - existing_key_expiration = await redis_client.expiretime(key_cache_redis_keys[i]); - break; - } - } - if (existing_key) { - // If there is we will return it and store this key to all the empty keys - for (let i = 0; i < response.length; i++) { - if (!response[i]) { - await redis_client.set(key_cache_redis_keys[i], existing_key); - await redis_client.expireat(key_cache_redis_keys[i], existing_key_expiration); - } - } - res.json({ - status: "SUCCESS", - metager_pass_key: { - key: existing_key, - valid_until: dayjs.unix(existing_key_expiration).format() - } - }); - } else { - // There is no Key yet. Let's generate one - let expiration = (new dayjs()).add(6, 'hour').unix(); - - Key.CREATE_NEW_KEY(expiration).then(async key => { - for (let i = 0; i < key_cache_redis_keys.length; i++) { - await redis_client.set(key_cache_redis_keys[i], key); - await redis_client.expireat(key_cache_redis_keys[i], expiration); - } - res.json({ - status: "SUCCESS", - metager_pass_key: { - key: key, - valid_until: dayjs.unix(expiration).format() - } - }); - }).catch(reason => { - res.status(400).json({ - status: "FAILURE", - msg: reason - }); - }); - } - }).catch(reason => { - res.status(400).json({ - status: "FAILURE", - msg: reason - }); - }); - - -}); - -module.exports = router; -- GitLab