|
|
|
import paypal from '@paypal/checkout-server-sdk';
|
|
|
|
import assert from "assert";
|
|
|
|
import { paypal_private } from "../lib/config.js";
|
|
|
|
import { Product, Payment } from "../lib/models.js";
|
|
|
|
import { product_id } from "../client/config.js";
|
|
|
|
import logging from "../lib/logging.js";
|
|
|
|
import { developer_admin } from "../lib/api.js";
|
|
|
|
|
|
|
|
const log = logging.create("queue/paypal.js");
|
|
|
|
|
|
|
|
|
|
|
|
const create_paypal = () => {
|
|
|
|
if(developer_admin) {
|
|
|
|
log.warn("Running in DANGER_ADMIN mode so using the Sandbox for Paypal.");
|
|
|
|
const environment = new paypal.core.SandboxEnvironment(paypal_private.client_id, paypal_private.secret);
|
|
|
|
|
|
|
|
const client = new paypal.core.PayPalHttpClient(environment);
|
|
|
|
|
|
|
|
return [environment, client];
|
|
|
|
} else {
|
|
|
|
const environment = new paypal.core.LiveEnvironment(paypal_private.client_id, paypal_private.secret);
|
|
|
|
|
|
|
|
const client = new paypal.core.PayPalHttpClient(environment);
|
|
|
|
|
|
|
|
return [environment, client];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const [environment, client] = create_paypal();
|
|
|
|
|
|
|
|
const order_is_valid = (course, response, payment) => {
|
|
|
|
let order = response.result.purchase_units[0];
|
|
|
|
|
|
|
|
assert(course.id, "Product is invalid in order_is_valid check.");
|
|
|
|
assert(payment.id, "Payment is invalid in order_is_valid check.");
|
|
|
|
|
|
|
|
if(!order) {
|
|
|
|
log.error(`Invalid response payment ${payment.id}, no order in the purchase_units.`);
|
|
|
|
return [false, "invalid_purchase_units"];
|
|
|
|
} else if(parseInt(order.amount.value, 10) !== course.price) {
|
|
|
|
// BUG: this will fail if you use a float for the price
|
|
|
|
log.error(`Wrong price ${order.amount.value}`);
|
|
|
|
return [false, "invalid_order_amount"];
|
|
|
|
} else if(order.payments.captures.length < 1) {
|
|
|
|
log.error("Not enough captures, only 0.");
|
|
|
|
return [false, "invalid_capture_count"];
|
|
|
|
} else if(order.payments.captures[0].status !== "COMPLETED") {
|
|
|
|
log.error(`Capture not completed: ${order.payments.captures.status}`);
|
|
|
|
return [false, "capture_not_completed"];
|
|
|
|
} else if(parseInt(order.payments.captures[0].amount.value, 10) !== course.price) {
|
|
|
|
console.error(`Capture amount ${order.payments.captures[0].amount.value} doesn't match course price ${course.price}`);
|
|
|
|
return [false, "capture_price_mismatch"];
|
|
|
|
} else {
|
|
|
|
return [true, "validated"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const validate_order = async (job) => {
|
|
|
|
try {
|
|
|
|
assert(job.data.payment_id, `Invalid order ID: ${JSON.stringify(job)}`);
|
|
|
|
|
|
|
|
const course = await Product.first({id: product_id});
|
|
|
|
assert(course, `No course for ID ${ product_id } from client/config.js:product_id`);
|
|
|
|
|
|
|
|
const payment = await Payment.first({id: job.data.payment_id});
|
|
|
|
assert(payment, `No payment for ID ${ job.payment_id }`);
|
|
|
|
|
|
|
|
let request = new paypal.orders.OrdersGetRequest(payment.sys_primary_id);
|
|
|
|
|
|
|
|
// Call API with your client and get a response for your call
|
|
|
|
let response = await client.execute(request);
|
|
|
|
|
|
|
|
log.debug(`Validating Paypal ID ${response.result.id}`);
|
|
|
|
|
|
|
|
const [valid, status_reason] = order_is_valid(course, response, payment);
|
|
|
|
|
|
|
|
if(valid) {
|
|
|
|
log.info(`Order for payment ${payment.id} is validated.`);
|
|
|
|
await Payment.update({id: payment.id}, {status: "complete", status_reason});
|
|
|
|
} else {
|
|
|
|
log.error(`Invalid Order for payment ${payment.id}.`);
|
|
|
|
await Payment.update({id: payment.id}, {status: "failed", status_reason});
|
|
|
|
}
|
|
|
|
} catch(error) {
|
|
|
|
log.error(error, `Processing payment paypal verification for ${job.data.payment_id}`);
|
|
|
|
|
|
|
|
try {
|
|
|
|
await Payment.update({id: job.data.payment_id}, {status: "failed", status_reason: "check_logs"});
|
|
|
|
} catch (e) {
|
|
|
|
log.error(error, `Failed to update the database with failure on payment id ${job.data.payment_id}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|