aboutsummaryrefslogtreecommitdiffstats
path: root/lib/payments.ts
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-11-02 12:54:10 -0700
committerAdam Soltys <[email protected]>2026-11-02 12:54:10 -0700
commit8fbc6ea19c9f4959799f3d72b32e2b8661596ea8 (patch)
treeea26d3daa86be2ea647e9ef86a2090099b5629fa /lib/payments.ts
parent9c7d26e0312b7e133801fe9a512e31908c2b29da (diff)
downloadcoinos-server-8fbc6ea19c9f4959799f3d72b32e2b8661596ea8.tar.gz
coinos-server-8fbc6ea19c9f4959799f3d72b32e2b8661596ea8.zip
payments: credit each on-chain output once and require uuid invoice ids
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6VN9xvnX1cgvV6hrUV
Diffstat (limited to 'lib/payments.ts')
-rw-r--r--lib/payments.ts6
1 files changed, 6 insertions, 0 deletions
diff --git a/lib/payments.ts b/lib/payments.ts
index 20025cd2..46682670 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -469,6 +469,12 @@ export const credit = async ({
let balanceKey = "balance";
if ([PaymentType.bitcoin, PaymentType.liquid].includes(type)) {
const [txid, vout] = ref.split(":").slice(-2);
+ // Permanent first-credit claim per output. Unlike `payment:<txid>:<vout>`,
+ // nothing else writes or deletes this key.
+ if (!(await db.set(`credited:${txid}:${vout}`, id, { NX: true }))) {
+ warn("duplicate credit blocked", hash, ref);
+ return;
+ }
p.confirmed = false;
balanceKey = "pending";
await s(`payment:${txid}:${vout}`, id);