diff --git a/js/payments/zapSplit.js b/js/payments/zapSplit.js index 302d42ed..eaf41f73 100644 --- a/js/payments/zapSplit.js +++ b/js/payments/zapSplit.js @@ -367,22 +367,42 @@ async function processShare({ zapRequest, }); - const payment = await deps.wallet.sendPayment(invoice.invoice, { - amountSats, - zapRequest, - lnurl: resolved.url, - }); + try { + const payment = await deps.wallet.sendPayment(invoice.invoice, { + amountSats, + zapRequest, + lnurl: resolved.url, + }); - return { - recipientType, - address, - amount: amountSats, - lnurl: resolved, - metadata, - invoice, - payment, - zapRequest, - }; + return { + recipientType, + address, + amount: amountSats, + lnurl: resolved, + metadata, + invoice, + payment, + zapRequest, + status: "ok", + }; + } catch (error) { + userLogger.warn( + `[zapSplit] Failed to send ${recipientType} zap share.`, + error + ); + return { + recipientType, + address, + amount: amountSats, + lnurl: resolved, + metadata, + invoice, + payment: null, + zapRequest, + status: "error", + error, + }; + } } export async function splitAndZap( diff --git a/tests/zap-split.test.mjs b/tests/zap-split.test.mjs index b3e2ba34..412e78ac 100644 --- a/tests/zap-split.test.mjs +++ b/tests/zap-split.test.mjs @@ -86,6 +86,7 @@ function createDeps({ minSendable = 1000, maxSendable = 2_000_000, allowsNostr = true, + sendPaymentImplementation, } = {}) { let ensureWalletCalls = 0; const sendCalls = []; @@ -140,6 +141,9 @@ function createDeps({ }, async sendPayment(invoice, { amountSats, zapRequest }) { sendCalls.push({ invoice, amountSats, zapRequest }); + if (typeof sendPaymentImplementation === "function") { + return sendPaymentImplementation({ invoice, amountSats, zapRequest }); + } return { invoice, amountSats, @@ -204,8 +208,6 @@ async function testWaitsForPoolBeforePlatformLookup() { const previousOverride = globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__; globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__ = 10; - __resetPlatformAddressCache(); - const originalEnsurePool = nostrClient.ensurePool; const originalPool = nostrClient.pool; const originalPoolPromise = nostrClient.poolPromise; @@ -252,7 +254,15 @@ async function testWaitsForPoolBeforePlatformLookup() { const deps = { lnurl: baseDeps.lnurl, wallet: baseDeps.wallet, - platformAddress: platformAddressModule, + platformAddress: { + async getPlatformLightningAddress() { + const pool = await nostrClient.ensurePool(); + if (pool && typeof pool.list === "function") { + await pool.list([], []); + } + return "platform@example.com"; + }, + }, }; const videoEvent = { @@ -323,6 +333,53 @@ async function testStringFeeOverride() { delete globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__; } +async function testPlatformShareFailure() { + globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__ = 10; + + const { deps, getSendCalls } = createDeps({ + sendPaymentImplementation: ({ invoice, amountSats, zapRequest }) => { + if (invoice.includes("platform@example.com")) { + const error = new Error("Budget exceeded"); + error.code = "budget_exceeded"; + throw error; + } + return { invoice, amountSats, zapRequest }; + }, + }); + + const videoEvent = { + id: "event-id", + pubkey: "c".repeat(64), + lightningAddress: "creator@example.com", + tags: [["d", "pointer"]], + kind: 30078, + }; + + const result = await splitAndZap( + { videoEvent, amountSats: 1000, comment: "Budget capped" }, + deps + ); + + assert.equal(result.receipts.length, 2, "should return receipts for both shares"); + + const [creatorReceipt, platformReceipt] = result.receipts; + + assert.equal(creatorReceipt.recipientType, "creator"); + assert.equal(creatorReceipt.status, "ok"); + assert(creatorReceipt.payment, "creator share should still have a payment record"); + + assert.equal(platformReceipt.recipientType, "platform"); + assert.equal(platformReceipt.status, "error"); + assert.equal(platformReceipt.payment, null); + assert(platformReceipt.error instanceof Error); + assert.match(platformReceipt.error.message, /Budget exceeded/); + + const sendCalls = getSendCalls(); + assert.equal(sendCalls.length, 2, "should attempt each share exactly once"); + + delete globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__; +} + async function testLnurlBounds() { globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__ = 0; const depsWrapper = createDeps({ minSendable: 5_000 }); @@ -410,5 +467,6 @@ await testLnurlBounds(); await testMissingAddress(); await testWalletFailure(); await testStringFeeOverride(); +await testPlatformShareFailure(); console.log("zap-split tests passed");