Skip to content
Snippets Groups Projects
Commit efc6640a authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

started integration for webhooks

parent fe21f91a
No related branches found
No related tags found
No related merge requests found
......@@ -4,6 +4,10 @@ const dayjs = require("dayjs");
const path = require("path");
class Order {
static get STORAGE_MUTEX_KEY_PREFIX() {
return "order_mutex";
}
static get STORAGE_KEY_PREFIX() {
return "order_";
}
......@@ -100,47 +104,62 @@ class Order {
}
static async LOAD_ORDER_FROM_ID(order_id) {
let Redis = require("ioredis");
let redis_client = new Redis({
host: config.get("redis.host"),
});
return new Promise((resolve, reject) => {
let Redis = require("ioredis");
let redis_client = new Redis({
host: config.get("redis.host"),
});
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),
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);
redis_client
.setnx(Order.STORAGE_MUTEX_KEY_PREFIX + order_id, 1)
.then((mutex) => {
console.log(mutex);
if (mutex !== 1) {
// Could not acquire lock. Try again
throw "LOCK_NOT_ACQUIRED";
} else {
return reject("Could not find Order in our database! Checking FS");
return redis_client.expire(Order.STORAGE_MUTEX_KEY_PREFIX, 15);
}
}
let loaded_order = new Order(
order_data.order_id,
order_data.amount,
order_data.price
);
if (order_data.payment_method_link) {
loaded_order.setPaymentMethodLink(
JSON.parse(order_data.payment_method_link)
})
.then(() => redis_client.hgetall(Order.STORAGE_KEY_PREFIX + order_id))
.then((order_data) => {
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),
order_id.toString() + ".json"
);
let fs = require("fs");
if (fs.existsSync(order_file)) {
order_data = JSON.parse(fs.readFileSync(order_file));
} else {
throw "Could not find Order in our database! Checking FS";
}
}
console.log(order_data);
let loaded_order = new Order(
order_data.order_id,
order_data.amount,
order_data.price
);
}
if (order_data.payment_completed) {
loaded_order.setPaymentCompleted(true);
}
resolve(loaded_order);
});
if (order_data.payment_method_link) {
loaded_order.setPaymentMethodLink(
JSON.parse(order_data.payment_method_link)
);
}
if (order_data.payment_completed) {
loaded_order.setPaymentCompleted(true);
}
resolve(loaded_order);
})
.catch((reason) => {
if (reason === "LOCK_NOT_ACQUIRED") {
console.log("lock not acquired");
setTimeout(Order.LOAD_ORDER_FROM_ID(order_id), 5000);
} else {
reject(reason);
}
});
});
}
......@@ -168,12 +187,14 @@ class Order {
if (!fs.existsSync(path.dirname(order_file))) {
fs.mkdirSync(path.dirname(order_file), { recursive: true });
}
this.#redis_client.del(redis_key).then(() => {
return fs.writeFileSync(
order_file,
JSON.stringify(stored_data, null, 4)
);
});
let redis_client = this.#redis_client;
let mutex_key = Order.STORAGE_MUTEX_KEY_PREFIX + this.#order_id;
return this.#redis_client
.del(redis_key)
.then(() =>
fs.writeFileSync(order_file, JSON.stringify(stored_data, null, 4))
)
.then(() => redis_client.del(mutex_key));
} else {
// Store Order in Redis
let expiration = new dayjs();
......@@ -185,6 +206,7 @@ class Order {
.pipeline()
.hmset(redis_key, stored_data)
.expireat(redis_key, expiration.unix())
.del(Order.STORAGE_MUTEX_KEY_PREFIX + this.#order_id)
.exec();
}
}
......@@ -216,13 +238,10 @@ class Order {
let order_mutex = Math.floor(Math.random() * 10000);
do {
// make sure this order_id is not already registered
let order_lock = await redis_connection.setnx(
"" + order_base + order_mutex,
true
);
order_id = "" + order_base + order_mutex;
let order_lock = await redis_connection.setnx(order_id, true);
if (order_lock === 1) {
await redis_connection.expire(order_id, 5);
order_id = "" + order_base + order_mutex;
} else {
console.log("Couldn't acquire lock");
order_mutex++;
......
{
"app": {
"url": "http://localhost:8080"
},
"price": {
"per_300": 5
},
......
......@@ -121,47 +121,31 @@ router.post("/:funding_source/order/cancel", async (req, res) => {
// capture payment & store order information or fullfill order
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 = 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) => {
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() }] });
});
});
verifyWebhook()
.then(() => {
return Order.LOAD_ORDER_FROM_ID(req.body.order_id);
})
.then((loaded_order) => {
let paypal_order = loaded_order.getPaymentMethodLink();
capturePayment(paypal_order.order_id)
.then((captureData) => {
if (captureData.status === "COMPLETED") {
captureData.redirect_url = "/key/" + req.data.key.key;
res.json(captureData);
} else {
throw "Couldn't capture the payment";
}
})
.then(() => {
loaded_order.setPaymentCompleted(true);
return loaded_order.save();
})
.then(() => Key.CHARGE_KEY(req.data.key.key, loaded_order.getAmount()))
.catch((reason) => {
console.error(reason);
res.status(400).json({ errors: [{ msg: reason.toString() }] });
});
});
});
module.exports = router;
......@@ -176,7 +160,6 @@ module.exports = router;
async function createOrder(loaded_order) {
const accessToken = await generateAccessToken();
console.log(accessToken);
const url = `${base}/v2/checkout/orders`;
......@@ -280,6 +263,71 @@ async function capturePayment(orderId) {
return data;
}
async function verifyWebhook() {
let domain = config.get("app.url").replace(/\/+$/, "");
if (!domain.startsWith("https://")) {
// Do not attempt to register a webhook for localhost etc.
return;
}
let Redis = require("ioredis");
let redis_client = new Redis({
host: config.get("redis.host"),
});
let webhook_already_registered_key = "paypal_webhook_registered";
if (await redis_client.get(webhook_already_registered_key)) {
return;
}
const accessToken = await generateAccessToken();
return fetch(`${base}/v1/notifications/webhooks`, {
method: "get",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => response.json())
.then((webhooks) => {
webhooks.webhooks.forEach((webhook) => {
if (webhook.url.startsWith(domain)) {
throw "WEBHOOK_ALREADY_REGISTERED"; // Webhook already registered
}
});
})
.then(async () => {
// Webhook does not exist yet
return fetch(`${base}/v1/notifications/webhooks`, {
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
event_types: [
{ name: "PAYMENT.CAPTURE.COMPLETED" },
{ name: "PAYMENT.CAPTURE.REFUNDED" },
{ name: "PAYMENT.CAPTURE.REVERSED" },
],
url: domain + "/webhooks/paypal",
}),
}).then((response) => {
if (response.status !== 201) {
return Promise.reject(response);
}
redis_client.set(webhook_already_registered_key, true);
});
})
.catch((reason) => {
if (reason !== "WEBHOOK_ALREADY_REGISTERED") {
console.warning("Could not register Webhook for PayPal.");
console.warning(reason);
} else {
redis_client.set(webhook_already_registered_key, true);
}
});
}
async function refundPayment(capture_ids) {
const accessToken = await generateAccessToken();
let promises = [];
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment