diff --git a/js/payments/lnurl.js b/js/payments/lnurl.js index 14f1f503..36cb8fb5 100644 --- a/js/payments/lnurl.js +++ b/js/payments/lnurl.js @@ -331,7 +331,7 @@ function truncateComment(comment, limit) { export async function requestInvoice( metadata, - { amountSats, amountMsats, comment, zapRequest, fetcher } = {} + { amountSats, amountMsats, comment, zapRequest, lnurl, fetcher } = {} ) { if (!metadata || typeof metadata !== "object") { throw new Error("LNURL metadata is required to request an invoice."); @@ -356,6 +356,11 @@ export async function requestInvoice( callbackUrl.searchParams.set("nostr", zapRequest); } + const originalLnurl = typeof lnurl === "string" ? lnurl.trim() : ""; + if (originalLnurl) { + callbackUrl.searchParams.set("lnurl", originalLnurl); + } + const response = await fetchFn(callbackUrl.toString(), { method: "GET", headers: { Accept: "application/json" }, diff --git a/js/payments/zapSplit.js b/js/payments/zapSplit.js index 71cb4a44..2a1f9be4 100644 --- a/js/payments/zapSplit.js +++ b/js/payments/zapSplit.js @@ -433,6 +433,7 @@ async function processShare({ amountMsats, comment, zapRequest, + lnurl: lnurlTag, }); try { diff --git a/tests/zap-split.test.mjs b/tests/zap-split.test.mjs index 7af0f706..814cad95 100644 --- a/tests/zap-split.test.mjs +++ b/tests/zap-split.test.mjs @@ -82,6 +82,7 @@ const lnurlModule = await import("../js/payments/lnurl.js"); const { encodeLnurlBech32, resolveLightningAddress: baseResolveLightningAddress, + requestInvoice, } = lnurlModule; const { decodeLnurlBech32 } = lnurlModule.__TESTING__; const { nostrClient } = await import("../js/nostr.js"); @@ -134,11 +135,11 @@ function createDeps({ } return { amountMsats: amount * 1000 }; }, - async requestInvoice(metadata, { amountMsats, comment, zapRequest }) { + async requestInvoice(metadata, { amountMsats, comment, zapRequest, lnurl }) { return { invoice: `bolt11-${metadata.callback}-${amountMsats}-${comment}-${Boolean( zapRequest - )}`, + )}-${lnurl || ""}`, raw: {}, }; }, @@ -154,15 +155,21 @@ function createDeps({ relays: walletRelays.slice(), }; }, - async sendPayment(invoice, { amountSats, zapRequest }) { - sendCalls.push({ invoice, amountSats, zapRequest }); + async sendPayment(invoice, { amountSats, zapRequest, lnurl }) { + sendCalls.push({ invoice, amountSats, zapRequest, lnurl }); if (typeof sendPaymentImplementation === "function") { - return sendPaymentImplementation({ invoice, amountSats, zapRequest }); + return sendPaymentImplementation({ + invoice, + amountSats, + zapRequest, + lnurl, + }); } return { invoice, amountSats, zapRequest, + lnurl, }; }, }, @@ -334,6 +341,45 @@ async function testUrlLightningAddressZapTag() { } } +async function testRequestInvoiceIncludesLnurlParam() { + const callback = "https://callback-lnurl.example/api/pay"; + const lnurlValue = encodeLnurlBech32(callback); + const zapRequest = JSON.stringify({ kind: 9734, content: "zap" }); + const requestedUrls = []; + + const metadata = { + callback, + commentAllowed: 0, + }; + + const fetcher = async (url) => { + requestedUrls.push(url); + return { + ok: true, + async json() { + return { pr: "bolt11-invoice" }; + }, + }; + }; + + const result = await requestInvoice(metadata, { + amountMsats: 123_000, + zapRequest, + lnurl: lnurlValue, + fetcher, + }); + + assert.equal(result.invoice, "bolt11-invoice"); + assert.equal(requestedUrls.length, 1, "should call fetcher once"); + + const [url] = requestedUrls; + const parsed = new URL(url); + + assert.equal(parsed.searchParams.get("amount"), "123000"); + assert.equal(parsed.searchParams.get("nostr"), zapRequest); + assert.equal(parsed.searchParams.get("lnurl"), lnurlValue); +} + async function testWaitsForPoolBeforePlatformLookup() { const previousOverride = globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__; globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__ = 10; @@ -598,6 +644,7 @@ async function testWalletFailure() { await testSplitMath(); await testBech32LightningAddressZapTag(); await testUrlLightningAddressZapTag(); +await testRequestInvoiceIncludesLnurlParam(); await testWaitsForPoolBeforePlatformLookup(); await testLnurlBounds(); await testMissingAddress();