From 5fb6af408388dcd147755f66e888769adbafbc02 Mon Sep 17 00:00:00 2001 From: amackillop Date: Tue, 13 Jan 2026 13:14:58 -0800 Subject: [PATCH] Add callback-based payment receiving for instant confirmation Introduce receivePaymentsWithCallback method that fires a callback immediately when each payment is received, enabling instant customer confirmation while the node continues running. Updated webhook handler to use callback approach instead of batch processing, marking payments received and notifying the backend asynchronously as they arrive. WIP: Known limitations to be addressed in follow-up work. --- packages/core/src/handlers/webhooks.ts | 45 +++++++++++++------------- packages/core/src/lightning-node.ts | 23 +++++++++++++ 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/packages/core/src/handlers/webhooks.ts b/packages/core/src/handlers/webhooks.ts index e0f503d7..e16bc729 100644 --- a/packages/core/src/handlers/webhooks.ts +++ b/packages/core/src/handlers/webhooks.ts @@ -12,31 +12,32 @@ const webhookSchema = z.object({ async function handleIncomingPayment() { const node = createMoneyDevKitNode(); const client = createMoneyDevKitClient(); - const payments = node.receivePayments(); - if (payments.length === 0) { - return; - } - - payments.forEach((payment) => { + // Use callback-based receive to process payments IMMEDIATELY as they arrive + // This allows instant customer confirmation while node continues running + node.receivePaymentsWithCallback((payment) => { + // Mark payment received in local state IMMEDIATELY markPaymentReceived(payment.paymentHash); - }); - try { - await client.checkouts.paymentReceived({ - payments: payments.map((payment) => ({ - paymentHash: payment.paymentHash, - // amount comes in msat from the node, convert to sats - amountSats: payment.amount / 1000, - sandbox: false, - })), - }); - } catch (error) { - warn( - "Failed to notify MoneyDevKit checkout about received payments. Will rely on local state and retry on next webhook.", - error, - ); - } + // Notify backend asynchronously (fire and forget for speed) + // Note: payment.amount is in msat, convert to sats + client.checkouts + .paymentReceived({ + payments: [ + { + paymentHash: payment.paymentHash, + amountSats: payment.amount / 1000, + sandbox: false, + }, + ], + }) + .catch((error) => { + warn( + "Failed to notify MoneyDevKit checkout about received payment. Will rely on local state and retry on next webhook.", + error, + ); + }); + }); } export async function handleMdkWebhook(request: Request): Promise { diff --git a/packages/core/src/lightning-node.ts b/packages/core/src/lightning-node.ts index 83fe735e..7c2a2628 100644 --- a/packages/core/src/lightning-node.ts +++ b/packages/core/src/lightning-node.ts @@ -128,6 +128,29 @@ export class MoneyDevKitNode { ) } + /** + * Receive payments with an immediate callback for each payment received. + * + * This method behaves like `receivePayments` but fires a callback immediately + * when each payment is received, allowing for instant customer confirmation + * while the node continues to run for the full timeout period. + */ + receivePaymentsWithCallback( + onPaymentReceived: (payment: { paymentHash: string; amount: number }) => void + ) { + return this.node.receivePaymentWithCallback( + RECEIVE_PAYMENTS_MIN_THRESHOLD_MS, + RECEIVE_PAYMENTS_QUIET_THRESHOLD_MS, + (err: unknown, payment: { paymentHash: string; amount: number }) => { + if (err) { + console.error('[MoneyDevKitNode] Payment callback error:', err) + return + } + onPaymentReceived(payment) + } + ) + } + payBolt12Offer(bolt12: string, amountMsat: number): string { return this.node.payBolt12Offer(bolt12, amountMsat, 30) }