From 63aac564602bb65065fee5fdb8f29abdacd388d4 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 23 Feb 2024 12:19:36 -0800 Subject: payment memos --- lib/invoices.js | 5 +++- lib/lightning.js | 2 +- lib/payments.js | 36 ++++++++++++++++++++++----- lib/utils.js | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/whitelist.js | 1 + 5 files changed, 112 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/invoices.js b/lib/invoices.js index 85b03224..6962898a 100644 --- a/lib/invoices.js +++ b/lib/invoices.js @@ -10,7 +10,7 @@ import lq from "$lib/liquid"; import bc from "$lib/bitcoin"; import ln from "$lib/ln"; -export let generate = async ({ invoice, user, sender, memo }) => { +export let generate = async ({ invoice, user, sender, }) => { let { currency, expiry, @@ -20,6 +20,8 @@ export let generate = async ({ invoice, user, sender, memo }) => { items = [], rate, request_id, + memo, + memoPrompt, prompt, type, webhook, @@ -77,6 +79,7 @@ export let generate = async ({ invoice, user, sender, memo }) => { pending: 0, received: 0, request_id, + memoPrompt, prompt, secret, text, diff --git a/lib/lightning.js b/lib/lightning.js index c32e4151..4c2a830b 100644 --- a/lib/lightning.js +++ b/lib/lightning.js @@ -34,7 +34,7 @@ export async function listenForLightning() { try { if (JSON.parse(description).kind === 9734) await handleZap(inv); } catch (e) { - warn("failed to handle zap", e.message); + if (!e.message.startsWith("Unexpected")) warn("failed to handle zap", e.message); } } diff --git a/lib/payments.js b/lib/payments.js index ff7052ca..54c901e6 100644 --- a/lib/payments.js +++ b/lib/payments.js @@ -6,7 +6,17 @@ 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 { btc, fail, getInvoice, sleep, wait, SATS, sats } from "$lib/utils"; +import { + f, + btc, + fail, + getInvoice, + sleep, + wait, + SATS, + sats, + formatReceipt, +} from "$lib/utils"; import { callWebhook } from "$lib/webhooks"; import got from "got"; import { mqtt1, mqtt2 } from "$lib/mqtt"; @@ -36,9 +46,10 @@ export let debit = async ({ type = types.internal, rate, }) => { - if (type !== types.internal && await g("freeze")) fail("Problem sending payment"); - if (!user.unlimited && amount > 1000000) - fail(`⚡️${amount} exceeds max withdrawal of ⚡️1,000,000`); + if (type !== types.internal && (await g("freeze"))) + fail("Problem sending payment"); + // if (!user.unlimited && amount > 1000000) + // fail(`⚡️${amount} exceeds max withdrawal of ⚡️1,000,000`); let ref; let { id: uid, currency, username } = user; if (!rate) rate = store.rates[currency]; @@ -144,6 +155,7 @@ export let credit = async (hash, amount, memo, ref, type = types.internal) => { let id = v4(); let p = { id, + iid: inv.id, hash, amount: parseInt(amount - tip), uid, @@ -186,17 +198,29 @@ export let credit = async (hash, amount, memo, ref, type = types.internal) => { callWebhook(inv, p); l(username, "received", type, amount); + // let items = ""; + // for (let i of inv.items) { + // items += `${i.quantity} ${i.name} ${f( + // i.price * i.quantity, + // inv.currency, + // )}\n`; + // } + // + // console.log(items); + + let items = formatReceipt(inv.items, inv.currency); + if (config.mqtt1) { mqtt1.publish( username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, ); } if (config.mqtt2) { mqtt2.publish( username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, ); } diff --git a/lib/utils.js b/lib/utils.js index c68d222b..389f8a57 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -75,4 +75,80 @@ export let getInvoice = async (hash) => { if (iid && iid.id) iid = iid.id; else if (iid && iid.hash) iid = iid.hash; return await g(`invoice:${iid}`); +}; + +export let f = (s, currency) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency, + }) + .format(s) + .replace("CA", ""); + +export function formatReceipt(items, currency) { +function wrapText(text, maxWidth) { + const words = text.split(" "); + const lines = []; + let currentLine = ""; + words.forEach((word) => { + if (currentLine.length + word.length + 1 > maxWidth) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine += (currentLine.length > 0 ? " " : "") + word; + } + }); + if (currentLine) lines.push(currentLine); + return lines; } + +function calculateColumnWidths(items) { + let maxQuantityLength = 0; + let maxPriceLength = 0; + + items.forEach((item) => { + const quantityLength = String(item.quantity).length; + if (quantityLength > maxQuantityLength) { + maxQuantityLength = quantityLength; + } + + const priceLength = f(item.price * item.quantity, currency).length; + if (priceLength > maxPriceLength) { + maxPriceLength = priceLength; + } + }); + + // Add padding to the widths for aesthetic spacing + return { + quantityColumnWidth: maxQuantityLength + 1, // Space after quantity + priceColumnWidth: maxPriceLength + 1, // Space before price + }; +} + const maxLineWidth = 32; + const { quantityColumnWidth, priceColumnWidth } = calculateColumnWidths( + items, + ); + const nameColumnWidth = maxLineWidth - quantityColumnWidth - priceColumnWidth; + + return items + .map((item) => { + const quantityStr = String(item.quantity).padEnd(quantityColumnWidth); + const priceStr = f(item.price * item.quantity, currency).padStart( + priceColumnWidth, + ); + const nameLines = wrapText(item.name, nameColumnWidth); + + // Construct the full line(s) with the first line including the price + let fullLines = [ + `${quantityStr}${nameLines[0].padEnd(nameColumnWidth)}${priceStr}`, + ]; + // Add any additional name lines, properly indented + for (let i = 1; i < nameLines.length; i++) { + fullLines.push(" ".repeat(quantityColumnWidth) + nameLines[i]); + } + + return fullLines.join("\n"); + }) + .join("\n\n"); +} + diff --git a/lib/whitelist.js b/lib/whitelist.js index 97caf221..e709d53e 100644 --- a/lib/whitelist.js +++ b/lib/whitelist.js @@ -22,6 +22,7 @@ export default [ "language", "linked", "locktime", + "memoPrompt", "nip5", "notify", "nsec", -- cgit v1.2.3