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

partly integrated paypal create order

parent 6af8519a
No related branches found
No related tags found
No related merge requests found
......@@ -8,7 +8,7 @@ var logger = require("morgan");
var indexRouter = require("./routes/index");
var keyRouter = require("./routes/key");
var checkoutRouter = require("./routes/checkout/checkout");
//var checkoutRouter = require("./routes/checkout/checkout");
var redeemRouter = require("./routes/redeem.js");
var app = express();
......@@ -26,7 +26,7 @@ app.use(express.static(path.join(__dirname, "public")));
app.use("/", indexRouter);
app.use("/key", keyRouter);
app.use("/checkout", checkoutRouter);
//app.use("/checkout", checkoutRouter);
app.use("/redeem", redeemRouter);
// Browserified Javascript files
......
......@@ -52,10 +52,7 @@ class Order {
#order_id;
#expires_at;
#amount;
#unit_size;
#price_per_unit;
#encrypted_sales_receipts;
#signatures;
#price;
#payment_completed;
#payment_method_link; // Stores a link to an entry of the payment methods payment i.e. PayPal order id
......@@ -67,31 +64,15 @@ class Order {
#create_mode;
#redis_client;
constructor(
order_id,
expires_at,
amount,
unit_size,
price_per_unit,
encrypted_sales_receipts,
signatures
) {
this.#order_id = new String(order_id);
this.#expires_at = dayjs(expires_at);
constructor(order_id, amount, price) {
this.#order_id = order_id;
this.#expires_at = dayjs().add(6, "month");
this.#order_date = dayjs.unix(this.#order_id.substr(0, 10));
this.#order_path = Order.GET_ORDER_FILE_BASE_PATH(this.#order_date);
this.#amount = parseInt(amount);
this.#unit_size = parseInt(unit_size);
this.#price_per_unit = parseFloat(price_per_unit);
this.#encrypted_sales_receipts = encrypted_sales_receipts;
if (signatures) {
this.#signatures = signatures;
} else {
this.#signatures = [];
}
this.#price = parseInt(price);
this.#payment_completed = false;
this.#create_mode = true;
......@@ -110,22 +91,14 @@ class Order {
return this.#amount;
}
getPricePerUnit() {
return this.#price_per_unit;
getPrice() {
return this.#price;
}
getPaymentMethodLink() {
return this.#payment_method_link;
}
getEncryptedSalesReceipts() {
return this.#encrypted_sales_receipts;
}
getSignatures() {
return this.#signatures;
}
isPaymentComplete() {
return this.#payment_completed;
}
......@@ -134,35 +107,6 @@ class Order {
this.#payment_method_link = payment_method_link;
}
async signOrder() {
let mgcrypto = new Crypto();
if (this.#signatures.length > 0) {
return false;
}
let signed_encrypted_sales_receipts = mgcrypto.sign(
this.#encrypted_sales_receipts,
this.#order_date,
this.#expires_at
);
this.#signatures = await signed_encrypted_sales_receipts;
// Store amount of signed receipts for our records
// So we know how much searches we have given out
let fs = require("fs");
let generated_file = path.join(this.#order_path, "generated.json");
// Create directory if it does not exist
if (!fs.existsSync(path.dirname(generated_file))) {
fs.mkdirSync(path.dirname(generated_file), { recursive: true });
}
for (let i = 0; i < this.#signatures.length; i++) {
fs.appendFileSync(generated_file, this.#order_id + `_${i}\n`);
}
return true;
}
static async LOAD_ORDER_FROM_ID(order_id) {
return new Promise((resolve, reject) => {
let Redis = require("ioredis");
......@@ -191,10 +135,7 @@ class Order {
order_data.order_id,
order_data.expires_at,
order_data.amount,
order_data.unit_size,
order_data.price_per_unit,
JSON.parse(order_data.encrypted_sales_receipts),
JSON.parse(order_data.signatures)
order_data.price
);
if (order_data.payment_method_link) {
loaded_order.setPaymentMethodLink(
......@@ -225,10 +166,7 @@ class Order {
order_id: this.#order_id,
expires_at: this.#expires_at.format("YYYY-MM-DD"),
amount: this.#amount,
unit_size: this.#unit_size,
price_per_unit: this.#price_per_unit,
encrypted_sales_receipts: JSON.stringify(this.#encrypted_sales_receipts),
signatures: JSON.stringify(this.#signatures),
price: this.#price,
payment_completed: this.#payment_completed,
payment_method_link: JSON.stringify(this.#payment_method_link),
};
......@@ -274,6 +212,32 @@ class Order {
return false;
});
}
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 Redis = require("ioredis");
let redis_connection = new Redis({
host: config.get("redis.host"),
});
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
let order_lock = await redis_connection.setnx(
"" + order_base + order_mutex,
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++;
}
} while (order_id === null);
return order_id;
}
}
module.exports = Order;
{
"price": {
"per_300": 5
},
"redis": {
"host": "redis"
},
......
const paypal_client = require("@paypal/paypal-js");
console.log(paypal_client);
const initialize_paypal_payments = require("./checkout_paypal");
paypal_client
.loadScript({
"client-id": document.querySelector("input[name=paypal-client-id]").value,
components: ["buttons", "funding-eligibility"],
})
.then((paypal) => {
paypal.getFundingSources().forEach((fundingSource) => {
let button = paypal.Buttons({
style: {
color: "white",
height: 50,
},
fundingSource: fundingSource,
});
if (button.isEligible()) {
console.log("eligible");
let funding_source_element = document.createElement("div");
funding_source_element.classList.add("funding_source");
funding_source_element.id = fundingSource;
document
.getElementById("paypal-payments")
.appendChild(funding_source_element);
button.render("#" + fundingSource);
}
});
});
// ToDo only load paypal when paypal group is opened
initialize_paypal_payments();
const paypal_client = require("@paypal/paypal-js");
function execute_payment_paypal(encrypted_sales_receipts) {
let payment_method_buttons = document.querySelectorAll(
"#payment-providers > ul > li"
);
for (let i = 0; i < payment_method_buttons.length; i++) {
payment_method_buttons[i].dataset.active = false;
}
let paypal_payment_option_button = document.getElementById(
"payment_method_paypal"
);
paypal_payment_option_button.dataset.active = true;
let payment_container = document.getElementById("payment-information");
payment_container.textContent = "";
let client_id = paypal_payment_option_button.dataset.client_id;
function initialize_paypal_payments() {
paypal_client
.loadScript({
"client-id": client_id,
"client-id": document.querySelector("input[name=paypal-client-id]").value,
components: ["buttons", "funding-eligibility"],
currency: "EUR",
})
.then((paypal) => {
paypal
.Buttons({
createOrder: (data, actions) => {
return fetch("/checkout/payment/order/paypal", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify({
order_id: document.querySelector("input[name=order_id]").value,
expires_at: document.querySelector("input[name=expires_at]")
.value,
amount: document.querySelector("input[name=amount]").value,
unit_size: document.querySelector("input[name=unit_size]")
.value,
price_per_unit: document.querySelector(
"input[name=price_per_unit]"
).value,
public_key_n: document.querySelector("input[name=public_key_n]")
.value,
public_key_e: document.querySelector("input[name=public_key_e]")
.value,
integrity: document.querySelector("input[name=integrity]")
.value,
encrypted_sales_receipts: encrypted_sales_receipts,
}),
})
.then((response) => response.json())
.then((order) => order.id);
},
onCancel: () => cancelPayment(encrypted_sales_receipts),
onError: () => cancelPayment(encrypted_sales_receipts),
onApprove: (data, actions) => {
return fetch("/checkout/payment/order/paypal/capture", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify({
order_id: document.querySelector("input[name=order_id]").value,
expires_at: document.querySelector("input[name=expires_at]")
.value,
amount: document.querySelector("input[name=amount]").value,
unit_size: document.querySelector("input[name=unit_size]")
.value,
price_per_unit: document.querySelector(
"input[name=price_per_unit]"
).value,
public_key_n: document.querySelector("input[name=public_key_n]")
.value,
public_key_e: document.querySelector("input[name=public_key_e]")
.value,
integrity: document.querySelector("input[name=integrity]")
.value,
encrypted_sales_receipts: encrypted_sales_receipts,
}),
})
.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);
});
},
})
.render("#payment-information");
paypal.getFundingSources().forEach((fundingSource) => {
let button = paypal.Buttons(get_paypal_checkout_data(fundingSource));
if (button.isEligible()) {
console.log("eligible");
let funding_source_element = document.createElement("div");
funding_source_element.classList.add("funding_source");
funding_source_element.id = fundingSource;
document
.getElementById("paypal-payments")
.appendChild(funding_source_element);
button.render("#" + fundingSource);
}
});
})
.catch((err) => {
// ToDo Handle error
......@@ -104,6 +28,69 @@ function execute_payment_paypal(encrypted_sales_receipts) {
});
}
function get_paypal_checkout_data(funding_source) {
return {
style: {
color: "white",
height: 50,
},
fundingSource: funding_source,
createOrder: (data, actions) => {
let checkout_paypal_create_order_url =
document.URL.replace(/#.*$/, "").replace("//+$", "") +
"/paypal/create-order";
return fetch(checkout_paypal_create_order_url, {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify({}),
})
.then((response) => response.json())
.then((order) => order.id);
},
onCancel: () => cancelPayment(encrypted_sales_receipts),
onError: () => cancelPayment(encrypted_sales_receipts),
onApprove: (data, actions) => {
return fetch("/checkout/payment/order/paypal/capture", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify({
order_id: document.querySelector("input[name=order_id]").value,
expires_at: document.querySelector("input[name=expires_at]").value,
amount: document.querySelector("input[name=amount]").value,
unit_size: document.querySelector("input[name=unit_size]").value,
price_per_unit: document.querySelector("input[name=price_per_unit]")
.value,
public_key_n: document.querySelector("input[name=public_key_n]")
.value,
public_key_e: document.querySelector("input[name=public_key_e]")
.value,
integrity: document.querySelector("input[name=integrity]").value,
encrypted_sales_receipts: encrypted_sales_receipts,
}),
})
.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);
});
},
};
}
function cancelPayment(encrypted_sales_receipts) {
return fetch("/checkout/payment/order/paypal/cancel", {
method: "POST",
......@@ -125,4 +112,4 @@ function cancelPayment(encrypted_sales_receipts) {
});
}
module.exports = execute_payment_paypal;
module.exports = initialize_paypal_payments;
......@@ -184,29 +184,3 @@ var paypalRouter = require("./paypal.js");
router.use("/payment/order/paypal", paypalRouter);
module.exports = router;
async function 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 Redis = require("ioredis");
let redis_connection = new Redis({
host: config.get("redis.host"),
});
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
let order_lock = await redis_connection.setnx(
"" + order_base + order_mutex,
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++;
}
} while (order_id === null);
return order_id;
}
......@@ -10,17 +10,17 @@ const CLIENT_ID = config.get(
const APP_SECRET = config.get(`payments.paypal.${process.env.NODE_ENV}.secret`);
const base = config.get(`payments.paypal.${process.env.NODE_ENV}.base`);
/* Client initiates payment */
router.post("/", async (req, res, next) => {
router.post("/create-order", async (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
await Order.GENERATE_UNIQUE_ORDER_ID(),
req.params.amount,
(req.params.amount / 300) * config.get("price.per_300")
);
console.log(req.params);
res.status(200).json({ test: "test" });
return;
order
.save()
......@@ -84,16 +84,19 @@ module.exports = router;
// use the orders api to create an order
async function createOrder(loaded_order) {
console.log(loaded_order);
const accessToken = await generateAccessToken();
const url = `${base}/v2/checkout/orders`;
let unit_count = loaded_order.getAmount() / 300;
let price_per_unit = loaded_order.getPrice() / unit_count;
console.log(price_per_unit);
let tax_amount_per_unit = fixRounding(
loaded_order.getPricePerUnit() * Order.PURCHASE_TAX_AMOUNT,
price_per_unit * Order.PURCHASE_TAX_AMOUNT,
2
);
let item_amount_per_unit = fixRounding(
loaded_order.getPricePerUnit() - tax_amount_per_unit,
price_per_unit - tax_amount_per_unit,
2
);
......@@ -105,31 +108,25 @@ async function createOrder(loaded_order) {
amount: {
currency_code: "EUR",
value: fixRounding(
item_amount_per_unit * loaded_order.getAmount() +
tax_amount_per_unit * loaded_order.getAmount(),
item_amount_per_unit * unit_count +
tax_amount_per_unit * unit_count,
2
),
breakdown: {
item_total: {
currency_code: "EUR",
value: fixRounding(
item_amount_per_unit * loaded_order.getAmount(),
2
),
value: fixRounding(item_amount_per_unit * unit_count, 2),
},
tax_total: {
currency_code: "EUR",
value: fixRounding(
tax_amount_per_unit * loaded_order.getAmount(),
2
),
value: fixRounding(tax_amount_per_unit * unit_count, 2),
},
},
},
items: [
{
name: "MetaGer Pass: Suchanfragen (250x)",
quantity: loaded_order.getAmount(),
name: "MetaGer Pass: Suchanfragen (300x)",
quantity: unit_count,
unit_amount: {
currency_code: "EUR",
value: item_amount_per_unit,
......
......@@ -3,6 +3,8 @@ var router = express.Router();
const { param, validationResult } = require("express-validator");
const config = require("config");
var checkout_router_paypal = require("./checkout/paypal");
var Key = require("../app/Key");
router.get("/create", function (req, res, next) {
......@@ -89,4 +91,6 @@ router.get("/:key/checkout/:amount?", async (req, res) => {
});
});
router.use("/:key/checkout/:amount/paypal", checkout_router_paypal);
module.exports = router;
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