import { Payment } from '../../lib/models.js'; import logging from '../../lib/logging.js'; import { API, defer } from "../../lib/api.js"; import fetch from 'node-fetch'; import { btcpay_private } from '../../lib/config.js'; import { product_id, fake_payments, register_enabled } from '../../client/config.js'; import { Product } from "../../lib/models.js"; import assert from "assert"; const product = await Product.first({id: product_id}); const rules = { amount: "required|numeric|min:0|max:100", } const log = logging.create("api/payments/btcpay.js"); export const get = async (req, res) => { const api = new API(req, res); if(!register_enabled) { return api.error(401, {errors: { main: "Registration is disabled."}}); } try { assert(!fake_payments, "You have fake_payments set so use /api/user/payment APIs."); assert(!btcpay_private.disabled, "BTC Payments are disabled in secrets/config.json."); const form = api.validate(rules); assert(form.amount === product.price, `Form amount ${form.amount} doesn't match course price ${product.price}`); if(form._valid) { let internal_id = Payment.gen_internal_id(); const options = { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': btcpay_private.auth }, body: JSON.stringify({ "amount": form.amount, "currency": product.currency, "checkout": { "speedPolicy": "HighSpeed" }, "metadata": { "orderId": internal_id, "itemDesc": product.description, } }), } const response = await fetch(`${btcpay_private.url}/invoices`, options); const text = await response.text(); if(response.status != 200) { log.info(`BTCPAY error: ${response.status}, text: '${text}'`); return api.error(403, 'BTCPay server failure'); } else { const data = JSON.parse(text); const sys_primary_id = data.id; let payment = await Payment.insert({ system: 'btcpay', status: 'pending', user_id: api.user.id, internal_id, sys_primary_id, sys_created_on: new Date(data.createdTime) }); assert(payment, "Failed to store payment in the database. Email help@learnjsthehardway.com."); return api.reply(200, {sys_primary_id, internal_id}); } } else { log.error(form._errors, "btcpay validation failure"); return api.validation_error(res, form); } } catch(error) { log.error(error); return api.error(403, 'BTCPay server failure'); } } get.authenticated = true; export const post = async (req, res) => { const api = new API(req, res); const msg = req.body; try{ log.debug(`btcpay POST Recieved message ${JSON.stringify(msg)}`); const options = { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': btcpay_private.auth }, } let response = await fetch(`${btcpay_private.url}/invoices/${msg.sys_primary_id}`, options); let data = await response.json(); // TODO: what are the error conditions on this response? let waiter = defer("settled_waiting"); setTimeout(() => waiter.resolve(), 5000); await waiter; response = await fetch(`${btcpay_private.url}/invoices/${msg.sys_primary_id}`, options); data = await response.json(); // TODO: what are the error conditions on this response? console.log("BTCPAY COMPLETE WITH", data); const sys_primary_id = data.id; const status = data.status; const btc_due = data.btcDue; // BUG: this doesn't seem to actually mark it complete if(status == 'paid') { let count = await Payment.update({ sys_primary_id, internal_id: msg.internal_id }, {status: 'complete'}); assert(count === 1, "Failed to update payment database."); } api.reply(200, {sys_primary_id, status, btc_due}); } catch(error) { log.error(error); api.error(403, 'Failed to confirm payment'); } } post.authenticated = true;