This is the template project that's checked out and configured when you run the bando-up command from ljsthw-bandolier. This is where the code really lives.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bandolier-template/api/payments/btcpay.js

141 lines
4.1 KiB

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;