aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-01-19 16:00:38 -0800
committerAdam Soltys <[email protected]>2023-01-19 16:00:38 -0800
commit5264f702a2217fe8815cb5275aca14a672f6d4bb (patch)
tree1c92a5258cfb92749c4250923f73797284c5d9c7
parentef3679051214a9b3047b68f236eac656edcf8d38 (diff)
downloadcoinos-server-5264f702a2217fe8815cb5275aca14a672f6d4bb.tar.gz
coinos-server-5264f702a2217fe8815cb5275aca14a672f6d4bb.zip
refactor payment types
-rw-r--r--index.js9
-rw-r--r--lib/lightning.js33
-rw-r--r--lib/payments.js132
-rw-r--r--lib/utils.js2
-rw-r--r--routes/invoices.js16
-rw-r--r--routes/payments.js220
6 files changed, 166 insertions, 246 deletions
diff --git a/index.js b/index.js
index 6a34f543..7f97a20d 100644
--- a/index.js
+++ b/index.js
@@ -44,17 +44,14 @@ app.get("/locations", locations.list);
app.get("/invoice", invoices.get);
app.post("/invoice", optional, invoices.create);
-app.post("/send", auth, payments.send);
+app.post("/payments", auth, payments.create);
app.get("/payments", auth, payments.list);
app.get("/payments/:hash", auth, payments.get);
-app.post("/lightning/send", auth, payments.sendLightning);
+app.post("/query", auth, payments.query);
app.get("/pot/:name", payments.pot);
-app.post("/withdraw", auth, payments.withdraw);
+app.post("/take", auth, payments.take);
app.post("/bitcoin", payments.bitcoin);
-app.get("/voucher/:id", payments.voucher);
-app.post("/redeem", optional, payments.redeem);
-
app.get("/me", auth, users.me);
app.get("/users/:key", users.get);
app.post("/register", users.create);
diff --git a/lib/lightning.js b/lib/lightning.js
index 86dfdb2b..c8daf8c8 100644
--- a/lib/lightning.js
+++ b/lib/lightning.js
@@ -5,6 +5,7 @@ import { callWebhook } from "$lib/webhooks";
import ln from "$lib/ln";
import { l, err, warn } from "$lib/logging";
import { g, s, t, db } from "$lib/db";
+import { credit, types } from "$lib/payments";
export async function listenForLightning() {
let {
@@ -33,37 +34,7 @@ export async function listenForLightning() {
let amount = parseInt(received) - tip;
if (amount < 0) throw new Error("amount out of range");
- let user = await g(`user:${uid}`);
-
- let payment = {
- amount,
- created: Date.now(),
- currency,
- fee: 0,
- hash,
- memo,
- preimage,
- rate,
- tip,
- type: "lightning",
- uid
- };
-
- invoice.status = "paid";
- invoice.received += received;
-
- await s(`invoice:${hash}`, invoice);
- await db.incrBy(`balance:${uid}`, received);
- payment.invoice = invoice;
- await s(`payment:${hash}`, payment);
- await db.lPush(`${uid}:payments`, hash);
-
- callWebhook(invoice, payment);
-
- emit(uid, "payment", payment);
- notify(user, `Received ${received} SAT`);
-
- l("lightning payment received", user.username, payment.amount, payment.tip);
+ await credit(hash, amount, memo, preimage, types.lightning);
} catch (e) {
console.log(e);
err("problem receiving lightning payment", e.message);
diff --git a/lib/payments.js b/lib/payments.js
new file mode 100644
index 00000000..e12f7858
--- /dev/null
+++ b/lib/payments.js
@@ -0,0 +1,132 @@
+import config from "$config";
+import store from "$lib/store";
+import { emit } from "$lib/sockets";
+import { v4 } from "uuid";
+import { notify } from "$lib/notifications";
+import { db, g, s, t } from "$lib/db";
+import { l, err } from "$lib/logging";
+import { fail } from "$lib/utils";
+import { callWebhook } from "$lib/webhooks";
+
+export const types = {
+ internal: "internal",
+ bitcoin: "bitcoin",
+ lightning: "lightning",
+ pot: "pot"
+};
+
+export let debit = async (
+ user,
+ amount,
+ memo,
+ to,
+ type = types.internal,
+ id = v4()
+) => {
+ let { id: uid, currency, username } = user;
+ to = to && to.id;
+
+ amount = parseInt(amount);
+
+ if (!amount || amount < 0) fail("Amount must be greater than zero");
+
+ await t(`balance:${uid}`, async balance => {
+ await new Promise(r => setTimeout(r, 100));
+ if (balance < amount) fail("Insufficient funds");
+ return balance - amount;
+ });
+
+ let fee = 0;
+
+ let p = {
+ id,
+ amount: -amount,
+ memo,
+ uid,
+ rate: store.rates[currency],
+ currency,
+ type,
+ to,
+ created: Date.now()
+ };
+
+ if (!to) delete p.to;
+
+ await s(`payment:${hash}`, p);
+ await db.lPush(`${uid}:payments`, hash);
+
+ l("sent internal", user.username, amount);
+ emit(user.id, "payment", p);
+
+ return p;
+};
+
+export let credit = async (hash, amount, memo, from, type = "internal") => {
+ amount = parseInt(amount);
+ let invoice = await g(`invoice:${hash}`);
+ if (!invoice) return;
+
+ let user = await g(`user:${invoice.uid}`);
+
+ let { id: uid, currency, username } = user;
+
+ let p = {
+ hash,
+ amount,
+ uid,
+ rate: store.rates[currency],
+ currency,
+ memo,
+ from,
+ type,
+ confirmed: true,
+ created: Date.now()
+ };
+
+ let { tip } = invoice;
+ if (tip) {
+ p.tip = tip;
+ p.amount -= tip;
+ }
+
+ invoice.received += amount;
+
+ let balance = "balance";
+ if (type === types.bitcoin) {
+ let [txid, vout] = from.split(":").slice(-2);
+ p.id = `${txid}:${vout}`;
+ p.confirmed = false;
+ balance = "pending";
+ } else {
+ p.id = hash;
+ }
+
+ await db
+ .multi()
+ .set(`invoice:${hash}`, JSON.stringify(invoice))
+ .set(`payment:${p.id}`, JSON.stringify(p))
+ .lPush(`${uid}:payments`, p.id)
+ .incrBy(`${balance}:${uid}`, amount)
+ .exec();
+
+ l("received internal", username, amount);
+ emit(uid, "payment", p);
+ notify(user, `Received ${amount} sats`);
+ callWebhook(invoice, p);
+
+ return p;
+};
+
+export let confirm = async (address, txid, vout) => {
+ let id = `payment:${txid}:${vout}`;
+ let p = await g(id);
+ p.confirmed = true;
+ console.log("P", p);
+
+ await db
+ .multi()
+ .set(id, JSON.stringify(p))
+ .decrBy(`pending:${p.uid}`, p.amount)
+ .incrBy(`balance:${p.uid}`, p.amount)
+ .exec();
+};
diff --git a/lib/utils.js b/lib/utils.js
index e653f235..b65f179a 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -23,7 +23,7 @@ export const fail = msg => {
};
export const SATS = 100000000;
-export const toSats = n => Math.round(n * SATS);
+export const sats = n => Math.round(n * SATS);
export const uniq = (a, k) => [...new Map(a.map(x => [k(x), x])).values()];
export const pick = (O, K) =>
diff --git a/routes/invoices.js b/routes/invoices.js
index 1287de88..62106f3a 100644
--- a/routes/invoices.js
+++ b/routes/invoices.js
@@ -3,6 +3,8 @@ import { l } from "$lib/logging";
import { emit } from "$lib/sockets";
import { db, g, s } from "$lib/db";
import { bip21, fail } from "$lib/utils";
+import { types } from "$lib/payments";
+
import bc from "$lib/bitcoin";
import ln from "$lib/ln";
@@ -31,13 +33,13 @@ export default {
if (amount < 0) fail("invalid amount");
let hash, text;
- if (type === "lightning") {
+ if (type === types.lightning) {
let amt = amount ? `${amount + tip}sat` : "any";
let r = await ln.invoice(amt, new Date(), "", 3600);
hash = r.payment_hash;
text = r.bolt11;
- } else if (type === "bitcoin") {
+ } else if (type === types.bitcoin) {
hash = await bc.getNewAddress();
text = bip21(hash, invoice);
} else {
@@ -57,15 +59,7 @@ export default {
created: Date.now()
};
-
- l(
- "creating invoice",
- user.username,
- amount,
- tip,
- currency,
- invoice.hash
- );
+ l("creating invoice", user.username, amount, tip, currency, invoice.hash);
await s(`invoice:${hash}`, invoice);
await db.lPush(`${uid}:invoices`, hash);
diff --git a/routes/payments.js b/routes/payments.js
index fc506685..d044f1e8 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -2,97 +2,19 @@ import config from "$config";
import store from "$lib/store";
import { emit } from "$lib/sockets";
import { v4 } from "uuid";
-import { notify } from "$lib/notifications";
import { db, g, s, t } from "$lib/db";
-import { l, err, warn } from "$lib/logging";
-import { fail } from "$lib/utils";
-import ln from "$lib/ln";
+import { l, err } from "$lib/logging";
+import { fail, sats } from "$lib/utils";
import { requirePin } from "$lib/auth";
-import { callWebhook } from "$lib/webhooks";
-
-const { HOSTNAME: hostname } = process.env;
-
-let debit = async (user, amount, memo, to) => {
- let { id: uid, currency, username } = user;
- to = to && to.id;
-
- amount = parseInt(amount);
-
- if (!amount || amount < 0) fail("Amount must be greater than zero");
-
- await t(`balance:${uid}`, async balance => {
- await new Promise(r => setTimeout(r, 100));
- if (balance < amount) fail("Insufficient funds");
- return balance - amount;
- });
-
- let fee = 0;
-
- let hash = v4();
-
- let p = {
- hash,
- amount: -amount,
- memo,
- uid,
- rate: store.rates[currency],
- currency,
- type: "internal",
- to,
- created: Date.now()
- };
-
- if (!to) delete p.to;
-
- await s(`payment:${hash}`, p);
- await db.lPush(`${uid}:payments`, hash);
-
- l("sent internal", user.username, amount);
- emit(user.id, "payment", p);
+import { debit, credit, confirm, types } from "$lib/payments";
- return p;
-};
-
-let credit = async (hash, amount, memo, from) => {
- let invoice = await g(`invoice:${hash}`);
- let user = await g(`user:${invoice.uid}`);
-
- let { id: uid, currency, username } = user;
- from = from.id;
-
- let p = {
- hash,
- amount,
- uid,
- rate: store.rates[currency],
- currency,
- memo,
- type: memo === "pot" ? "pot" : "internal",
- from,
- created: Date.now()
- };
-
- let { tip } = invoice;
- if (tip) {
- p.tip = tip;
- p.amount -= tip;
- }
-
- invoice.received += amount;
- await s(`invoice:${hash}`, invoice);
- await s(`payment:${hash}`, p);
- await db.lPush(`${uid}:payments`, hash);
-
- l("received internal", username, amount);
- emit(username, "payment", p);
- notify(user, `Received ${amount} sats`);
- callWebhook(invoice, p);
+import bc from "$lib/bitcoin";
+import ln from "$lib/ln";
- return p;
-};
+const { HOSTNAME: hostname } = process.env;
export default {
- async send({ body, user }, res) {
+ async create({ body, user }, res) {
let { amount, hash, name, memo, tip } = body;
await requirePin({ body, user });
@@ -101,7 +23,7 @@ export default {
let p = await debit(user, amount, memo, recipient);
if (hash) {
- await credit(hash, amount, memo, user);
+ await credit(hash, amount, memo, user.id);
} else {
let pot = name || v4();
await db.incrBy(`pot:${pot}`, amount);
@@ -126,116 +48,11 @@ export default {
res.send({ payments, total: payments.length });
},
- async voucher(req, res) {
- try {
- const { redeemcode } = req.params;
- let payment = await g(`voucher:${id}`);
-
- payment = payment.get({ plain: true });
- payment.redeemer = payment["with"];
-
- if (!payment) fail("invalid code");
-
- res.send(payment);
- } catch (e) {
- res.code(500).send(e.message);
- }
- },
-
async get({ params: { hash } }, res) {
res.send(await g(`payment:${hash}`));
},
- async redeem({ body: { redeemcode } }, res) {
- try {
- await db.transaction(async transaction => {
- if (!redeemcode) fail("no code provided");
-
- let { user } = req;
-
- const source = await db.Payment.findOne({
- where: {
- redeemcode: req.body.redeemcode
- },
- include: {
- model: db.Account,
- as: "account"
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- l("redeeming", redeemcode);
-
- if (!source) fail("Invalid code");
- if (source.redeemed) fail("Voucher has already been redeemed");
- let { amount } = source;
- amount = -amount;
-
- if (!user) {
- const ip =
- req.headers["x-forwarded-for"] || req.connection.remoteAddress;
-
- user = await register(
- {
- username: redeemcode.substr(0, 8),
- password: ""
- },
- ip,
- false
- );
-
- let payload = { username: user.username };
- let token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, {
- expires: new Date(Date.now() + 432000000)
- });
-
- return res.send({ user });
- }
-
- let account = await getAccount(source.account.asset, user, transaction);
- let { hash, memo, confirmed, fee, network } = source;
-
- source.redeemed = true;
- (source.with_id = user.id), await source.save({ transaction });
-
- let payment = await db.Payment.create(
- {
- amount,
- account_id: account.id,
- user_id: user.id,
- hash: "Voucher " + redeemcode,
- memo,
- rate: store.rates[user.currency],
- currency: user.currency,
- confirmed,
- network,
- received: true,
- fee,
- with_id: source.user_id
- },
- { transaction }
- );
-
- await account.increment({ balance: amount }, { transaction });
- await account.reload({ transaction });
-
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
- emit(user.username, "payment", payment);
- emit(user.username, "account", account);
-
- res.send({ payment });
- });
- } catch (e) {
- console.log(e);
- err("problem redeeming", e.message);
- return res.code(500).send("There was a problem redeeming the voucher");
- }
- },
-
- async sendLightning({ body: { payreq } }, res) {
+ async query({ body: { payreq } }, res) {
let hour = 1000 * 60 * 60;
let { last } = store.nodes;
let { nodes } = store;
@@ -264,7 +81,7 @@ export default {
res.send({ amount, payments });
},
- async withdraw({ body: { name, amount }, user }, res) {
+ async take({ body: { name, amount }, user }, res) {
amount = parseInt(amount);
await t(`pot:${name}`, async balance => {
await new Promise(r => setTimeout(r, 100));
@@ -278,14 +95,23 @@ export default {
received: 0
});
- let payment = await credit(hash, amount, "pot", { id: name });
- await db.lPush(`pot:${name}:payments`, hash);
+ let payment = await credit(hash, amount, "", name, types.pot);
+ await db.lPush(`${name}:payments`, hash);
res.send({ payment });
},
- async bitcoin({ body }, res) {
- console.log(body);
+ async bitcoin({ body: { txid, wallet } }, res) {
+ if (wallet === config.bitcoin.wallet) {
+ let { confirmations, details } = await bc.getTransaction(txid);
+ for (let { address, amount, vout } of details) {
+ if (confirmations > 0) {
+ await confirm(address, txid, vout);
+ } else {
+ await credit(address, sats(amount), "", `${txid}:${vout}`, types.bitcoin);
+ }
+ }
+ }
res.send({});
}
};