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}`); } } }