aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-05-17 23:27:01 -0700
committerAdam Soltys <[email protected]>2026-05-17 23:27:01 -0700
commit9fca61790af069dc150a6923c89d119c537e512c (patch)
tree887cadb91d1956ab945d2e2568cd6998457dc813
parentb52eee57bd9055d0f2b9044d6b87ce90ca5e48cc (diff)
downloadcoinos-server-9fca61790af069dc150a6923c89d119c537e512c.tar.gz
coinos-server-9fca61790af069dc150a6923c89d119c537e512c.zip
sendLightning / sendKeysend: only reverse when CLN reports the payment failed
The catch blocks for ln.xpay and ln.keysend reversed the user's debit whenever the RPC call threw, regardless of the actual payment state in CLN. If the RPC errored transiently while the payment was still pending, the user's balance got refunded — and then the lightning payment could still complete, leaving the user with both the refund and the outgoing sats. Both call sites now mirror the safe check() logic: const failed = !pays.length || pays.every((p) => p.status === "failed"); if (completed) finalize ... else if (failed) reverse ... // else (pending) leave the debit; check() reconciles when CLN settles. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
-rw-r--r--lib/payments.ts15
1 files changed, 13 insertions, 2 deletions
diff --git a/lib/payments.ts b/lib/payments.ts
index 3005e71b..6c78f40e 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -579,7 +579,14 @@ export const sendKeysend = async ({
extratlvs,
});
} catch (e) {
- try { await reverse(p); } catch (_) {}
+ try {
+ const { pays } = await ln.listpays({ payment_hash: hash });
+ const failed = !pays.length || pays.every((p) => p.status === "failed");
+ if (failed) await reverse(p);
+ // else: pending or completed — leave the debit; check() loop will
+ // reconcile. Reversing on pending would refund while CLN may still
+ // settle the keysend.
+ } catch (_) {}
throw e;
}
};
@@ -656,12 +663,16 @@ export const sendLightning = async ({
try {
const { pays } = await ln.listpays(pr);
const completed = pays.find((p) => p.status === "complete");
+ const failed = !pays.length || pays.every((p) => p.status === "failed");
if (completed) {
warn("payment completed despite error, finalizing", p.id);
try { await finalize(completed, p); } catch (_) {}
- } else {
+ } else if (failed) {
await reverse(p);
}
+ // else: pending — leave the debit in place; check() loop will reconcile
+ // once CLN confirms outcome. Reversing while still in flight would
+ // refund the user even though the payment may yet complete.
} catch (_) {}
throw e;
}