From fe21f91ad1f8eacfd645b4e6ff7a30f58a0f19a5 Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@hebeler.club> Date: Fri, 9 Dec 2022 22:43:22 +0100 Subject: [PATCH] charging the key will work now --- pass/app/Key.js | 51 ++++++----------- pass/app/Order.js | 50 +++++------------ pass/resources/js/checkout_paypal.js | 14 +---- pass/routes/checkout/paypal.js | 83 ++++++++++++++++++++++------ pass/routes/key.js | 2 +- pass/views/key.ejs | 2 +- 6 files changed, 104 insertions(+), 98 deletions(-) diff --git a/pass/app/Key.js b/pass/app/Key.js index 9dbcdfd..82dd1ae 100644 --- a/pass/app/Key.js +++ b/pass/app/Key.js @@ -55,41 +55,26 @@ class Key { }); } - static async CHARGE_EXISTING_KEY(key, amount) { - return new Promise(async (resolve, reject) => { - let redis_client = Key.REDIS_CLIENT; - - // Check if key exists and is eligable for recharge - redis_client.get(Key.DATABASE_PREFIX + key).then(async (key_charge) => { - if (!key_charge || key_charge >= 3000) { - reject( - "Der angegebene Schlüssel ist ungültig, oder beinhaltet bereits mehr als 3.000 Suchanfragen" - ); - } else { - let dayjs = require("dayjs"); - let expiration = dayjs().add(Key.EXPIRATION_AFTER_CHARGE_DAYS, "day"); - redis_client - .pipeline() - .incrby(Key.DATABASE_PREFIX + key, amount) - .expireat(Key.DATABASE_PREFIX + key, expiration.unix()) - .exec() - .then((result) => { - resolve({ - status: "SUCCESS", - metager_pass_key: { - key: key, - searches: result[0][1], - expire_at: expiration.format(), - }, - }); - }) - .catch((reason) => { - reject(reason); - }); - } - }); + static async GET_KEY_CHARGE(key) { + let redis_client = Key.REDIS_CLIENT; + return redis_client.get(Key.DATABASE_PREFIX + key).then((key_charge) => { + return key_charge | 0; }); } + + static async CHARGE_KEY(key, amount) { + let redis_client = Key.REDIS_CLIENT; + let expiration = require("dayjs")().add( + Key.EXPIRATION_AFTER_CHARGE_DAYS, + "day" + ); + // Check if key exists and is eligable for recharge + return redis_client + .pipeline() + .incrby(Key.DATABASE_PREFIX + key, amount) + .expireat(Key.DATABASE_PREFIX + key, expiration.unix()) + .exec(); + } } module.exports = Key; diff --git a/pass/app/Order.js b/pass/app/Order.js index 848fc34..70ff322 100644 --- a/pass/app/Order.js +++ b/pass/app/Order.js @@ -109,23 +109,25 @@ class Order { let redis_key = Order.STORAGE_KEY_PREFIX + order_id; redis_client.hgetall(redis_key).then((order_data) => { + console.log(Object.keys(order_data).length); if (Object.keys(order_data).length === 0) { // Checking FS for order let order_date = dayjs.unix(order_id.substr(0, 10)); let order_file = path.join( Order.GET_ORDER_FILE_BASE_PATH(order_date), - this.#order_id.toString() + ".json" + order_id.toString() + ".json" ); let fs = require("fs"); + console.log("Loading from fs: " + order_file); if (fs.existsSync(order_file)) { order_data = JSON.parse(fs.readFileSync(order_file)); + console.log(order_data); } else { return reject("Could not find Order in our database! Checking FS"); } } let loaded_order = new Order( order_data.order_id, - order_data.expires_at, order_data.amount, order_data.price ); @@ -134,6 +136,9 @@ class Order { JSON.parse(order_data.payment_method_link) ); } + if (order_data.payment_completed) { + loaded_order.setPaymentCompleted(true); + } resolve(loaded_order); }); }); @@ -152,21 +157,25 @@ class Order { * Completed Orders will be stored in Filesystem * Uncompleted Orders will be stored in Redis */ + let redis_key = Order.STORAGE_KEY_PREFIX + this.#order_id; if (this.#payment_completed) { let fs = require("fs"); let order_file = path.join( this.#order_path, - "orders", 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 fs.writeFileSync(order_file, JSON.stringify(stored_data, null, 4)); + this.#redis_client.del(redis_key).then(() => { + return fs.writeFileSync( + order_file, + JSON.stringify(stored_data, null, 4) + ); + }); } else { // Store Order in Redis - let redis_key = Order.STORAGE_KEY_PREFIX + this.#order_id; let expiration = new dayjs(); expiration = expiration.add( Order.PURCHASE_STORAGE_TIME_UNCOMPLETED_HOURS, @@ -178,37 +187,6 @@ class Order { .expireat(redis_key, expiration.unix()) .exec(); } - /* - expiration = Math.round(expiration.diff() / 1000); - - let storage_promise = this.#redis_client - .hmset(redis_key, stored_data) - .then((result) => { - this.#create_mode = false; - }) - .then((result) => { - this.#redis_client.expire(redis_key, expiration); - }); - - await storage_promise; - - // If the Order payment is completed store the order additionally directly to harddisk - let fs = require("fs"); - let order_file = path.join( - this.#order_path, - "orders", - 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 }); - } - storage_promise = fs.writeFileSync( - order_file, - JSON.stringify(stored_data, null, 4) - ); - - await storage_promise;*/ } async delete() { diff --git a/pass/resources/js/checkout_paypal.js b/pass/resources/js/checkout_paypal.js index 3ef98fd..cb6c839 100644 --- a/pass/resources/js/checkout_paypal.js +++ b/pass/resources/js/checkout_paypal.js @@ -209,17 +209,9 @@ function get_paypal_checkout_data(funding_source) { }) .then((response) => response.json()) .then((orderData) => { - let paymentEvent = new CustomEvent("payment-complete", { - detail: { - order_id: orderData.order_id, - expires_at: data.expires_at, - signatures: orderData.signatures, - }, - bubbles: true, - cancelable: true, - composed: false, - }); - paypal_payment_option_button.dispatchEvent(paymentEvent); + if (typeof orderData.redirect_url !== "undefined") { + document.location.href = orderData.redirect_url; + } }); }, onInit: () => { diff --git a/pass/routes/checkout/paypal.js b/pass/routes/checkout/paypal.js index 12498b3..ada0070 100644 --- a/pass/routes/checkout/paypal.js +++ b/pass/routes/checkout/paypal.js @@ -7,6 +7,7 @@ var clm = require("country-locale-map"); const config = require("config"); const Order = require("../../app/Order.js"); +const Key = require("../../app/Key.js"); const CLIENT_ID = config.get( `payments.paypal.${process.env.NODE_ENV}.client_id` @@ -96,6 +97,14 @@ router.post("/:funding_source/order/create", async (req, res) => { router.post("/:funding_source/order/cancel", async (req, res) => { Order.LOAD_ORDER_FROM_ID(req.body.order_id) .then((order) => { + if (order.isPaymentComplete()) { + // Not so good. Something went wrong after we captured the Payment + // Refund it back + let paypal_order_data = order.getPaymentMethodLink(); + if (paypal_order_data.name === "paypal") { + console.log(paypal_order_data); + } + } return order.delete(); }) .then((success) => { @@ -106,7 +115,7 @@ router.post("/:funding_source/order/cancel", async (req, res) => { } }) .catch((reason) => { - res.status(400).json({ msg: reason }); + res.status(400).json({ msg: reason.toString() }); }); // Deletes a order but only if the payment is not yet completed }); @@ -114,23 +123,44 @@ router.post("/:funding_source/order/cancel", async (req, res) => { router.post("/:funding_source/order/capture", async (req, res) => { Order.LOAD_ORDER_FROM_ID(req.body.order_id).then((loaded_order) => { loaded_order.setPaymentCompleted(true); - let paypal_order_id = loaded_order.getPaymentMethodLink().id; - capturePayment(paypal_order_id) + let paypal_order = loaded_order.getPaymentMethodLink(); + if (paypal_order.name !== "paypal") { + res + .status(400) + .json({ errors: [{ msg: "This order is not a PayPal Payment" }] }); + return; + } + let key_filled = false; + loaded_order + .save() + .then(() => { + return Key.CHARGE_KEY(req.data.key.key, req.data.checkout.amount); // ToDo verify amount + }) + .then((key_charge) => { + key_filled = true; + return key_charge; + }) + .then((key_charge) => { + return capturePayment(paypal_order.order_id); + }) .then((captureData) => { - loaded_order - .save() - .then(() => { - res.json(captureData); - }) - .catch((error) => { - res.status(400).json({ errors: [{ msg: error.toString() }] }); - }); + if (captureData.status === "COMPLETED") { + captureData.redirect_url = "/key/" + req.data.key.key; + res.json(captureData); + } else { + res + .status(400) + .json({ errors: [{ msg: "Couldn't capture the payment" }] }); + } }) .catch((error) => { + // We captured a payment but did not successfully update the order + if (key_filled) { + // Capture failed... Remove searches from key again + console.log("Removing searches from key again"); + } res.status(400).json({ errors: [{ msg: error.toString() }] }); }); - //res.json(captureData); - // TODO: store payment information such as the transaction ID }); }); @@ -146,6 +176,7 @@ module.exports = router; async function createOrder(loaded_order) { const accessToken = await generateAccessToken(); + console.log(accessToken); const url = `${base}/v2/checkout/orders`; @@ -185,7 +216,7 @@ async function createOrder(loaded_order) { }, items: [ { - name: "MetaGer Pass: Suchanfragen (300x)", + name: "MetaGer Schlüssel: Suchanfragen (300x)", quantity: unit_count, unit_amount: { currency_code: "EUR", @@ -197,7 +228,7 @@ async function createOrder(loaded_order) { }, category: "DIGITAL_GOODS", description: - "MetaGer Pass Suchanfragen zur Nutzung der Suchmaschine MetaGer", + "MetaGer Schlüssel zur werbefreien Nutzung der Suchmaschine MetaGer", }, ], }, @@ -220,7 +251,7 @@ async function createOrder(loaded_order) { }) .then((response) => response.json()) .then((data) => { - loaded_order.setPaymentMethodLink({ id: data.id }); + loaded_order.setPaymentMethodLink({ name: "paypal", order_id: data.id }); return loaded_order.save().then(() => { return { id: data.id, order_id: loaded_order.getOrderID() }; }); @@ -249,6 +280,26 @@ async function capturePayment(orderId) { return data; } +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); +} + // Client Token for handling credit card payments async function generateClientToken() { const accessToken = await generateAccessToken(); diff --git a/pass/routes/key.js b/pass/routes/key.js index a30ebc6..f090b91 100644 --- a/pass/routes/key.js +++ b/pass/routes/key.js @@ -31,6 +31,7 @@ router.use("/:key", param("key").isUUID(4), async (req, res, next) => { created_new: req.query.new === "true" ? true : false, key: { key: req.params.key, + charge: await Key.GET_KEY_CHARGE(req.params.key), settings_url: metager_url, qr: qr_data_uri, }, @@ -61,7 +62,6 @@ router.use( return res.status(400).json({ errors: errors.array() }); } let amount = req.params.amount; - console.log(parseInt(amount)); if (typeof amount !== "undefined" && parseInt(req.params.amount) === 0) { amount = undefined; } else { diff --git a/pass/views/key.ejs b/pass/views/key.ejs index 825349d..318089d 100644 --- a/pass/views/key.ejs +++ b/pass/views/key.ejs @@ -21,7 +21,7 @@ <div id="amount"> <h3>Gültig für</h3> - <div class="amount">0</div> + <div class="amount"><%= key.charge %></div> <div>Suchanfragen</div> </div> <div id="charge"> -- GitLab