Add honest accounting and automatic refund mechanism for completed jobs
Implement honest accounting post-job completion, calculating actual costs, adding margin, and enabling automatic refunds for overpayments via a new endpoint. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: c6386de2-d5f4-47cc-a557-73416f09e118 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8 Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -26,6 +26,8 @@ export class LNbitsService {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Inbound invoices ─────────────────────────────────────────────────────
|
||||
|
||||
async createInvoice(amountSats: number, memo: string): Promise<LNbitsInvoice> {
|
||||
if (this.stubMode) {
|
||||
const paymentHash = randomBytes(32).toString("hex");
|
||||
@@ -34,7 +36,7 @@ export class LNbitsService {
|
||||
return { paymentHash, paymentRequest };
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.url.replace(/\/$/, "")}/api/v1/payments`, {
|
||||
const response = await fetch(`${this.base()}/api/v1/payments`, {
|
||||
method: "POST",
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ out: false, amount: amountSats, memo }),
|
||||
@@ -58,7 +60,7 @@ export class LNbitsService {
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${this.url.replace(/\/$/, "")}/api/v1/payments/${paymentHash}`,
|
||||
`${this.base()}/api/v1/payments/${paymentHash}`,
|
||||
{ method: "GET", headers: this.headers() },
|
||||
);
|
||||
|
||||
@@ -71,7 +73,68 @@ export class LNbitsService {
|
||||
return data.paid;
|
||||
}
|
||||
|
||||
/** Stub-only helper: mark an invoice as paid for testing/dev flows. */
|
||||
// ── Outbound payments (refunds) ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Decode a BOLT11 invoice and return its amount in satoshis.
|
||||
* Stub mode: parses the embedded amount from our known stub format (lnbcrtXu1stub_...),
|
||||
* or returns null for externally-generated invoices.
|
||||
*/
|
||||
async decodeInvoice(bolt11: string): Promise<{ amountSats: number } | null> {
|
||||
if (this.stubMode) {
|
||||
// Our stub format: lnbcrtNNNu1stub_... where NNN is the amount in sats
|
||||
const m = bolt11.match(/^lnbcrt(\d+)u1stub_/i);
|
||||
if (m) return { amountSats: parseInt(m[1], 10) };
|
||||
// Unknown format in stub mode — accept blindly (simulated environment)
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.base()}/api/v1/payments/decode`, {
|
||||
method: "POST",
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ data: bolt11 }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`LNbits decodeInvoice failed (${response.status}): ${body}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { amount_msat?: number };
|
||||
if (!data.amount_msat) return null;
|
||||
return { amountSats: Math.floor(data.amount_msat / 1000) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Pay an outgoing BOLT11 invoice (e.g. to return a refund to a user).
|
||||
* Returns the payment hash.
|
||||
* Stub mode: simulates a successful payment.
|
||||
*/
|
||||
async payInvoice(bolt11: string): Promise<string> {
|
||||
if (this.stubMode) {
|
||||
const paymentHash = randomBytes(32).toString("hex");
|
||||
console.log(`[stub] Paid outgoing invoice — fake hash=${paymentHash}`);
|
||||
return paymentHash;
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.base()}/api/v1/payments`, {
|
||||
method: "POST",
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ out: true, bolt11 }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`LNbits payInvoice failed (${response.status}): ${body}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { payment_hash: string };
|
||||
return data.payment_hash;
|
||||
}
|
||||
|
||||
// ── Stub helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Stub-only: mark an inbound invoice as paid for testing/dev flows. */
|
||||
stubMarkPaid(paymentHash: string): void {
|
||||
if (!this.stubMode) {
|
||||
throw new Error("stubMarkPaid called on a real LNbitsService instance");
|
||||
@@ -80,6 +143,12 @@ export class LNbitsService {
|
||||
console.log(`[stub] Marked invoice paid: hash=${paymentHash}`);
|
||||
}
|
||||
|
||||
// ── Private helpers ──────────────────────────────────────────────────────
|
||||
|
||||
private base(): string {
|
||||
return this.url.replace(/\/$/, "");
|
||||
}
|
||||
|
||||
private headers(): Record<string, string> {
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
Reference in New Issue
Block a user