Skip to content

feat: Report why Payjoin v2 is unavailable instead of a boolean - #110

Open
ValeraFinebits wants to merge 2 commits into
masterfrom
feat/Report-why-Payjoin-v2-is-unavailable-instead-of-a-boolean
Open

feat: Report why Payjoin v2 is unavailable instead of a boolean#110
ValeraFinebits wants to merge 2 commits into
masterfrom
feat/Report-why-Payjoin-v2-is-unavailable-instead-of-a-boolean

Conversation

@ValeraFinebits

@ValeraFinebits ValeraFinebits commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Data foundation for the five checkout availability states in #35: the payment-url
response now says why Payjoin v2 is unavailable, not just that it is.

What changed

  • GetBip21Response: payjoinEnabled (bool) -> status (PayjoinAvailabilityStatus,
    serialized as its name) + unavailableReason.
  • BuildAsync returns PayjoinUriResult instead of a string. Two factories enforce the
    status/reason pairing, and the Active path verifies the built URI under the session
    build lock before claiming Active.
  • The anonymous checkout endpoint returns a separate GetCheckoutBip21Response with a
    deliberately coarse Active | Unavailable status and no reason. Greenfield, which is
    behind btcpay.store.canviewinvoices, returns the full five states plus the reason.
  • Swagger updated; the checkout script reads status instead of payjoinEnabled.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR evolves the PayJoin v2 “payment-url” response from a boolean (payjoinEnabled) to an explicit availability model (status + unavailableReason) to better diagnose why PayJoin is not active, aligning with the multi-state availability work in #35.

Changes:

  • Replace payjoinEnabled with PayjoinAvailabilityStatus + unavailableReason in the invoice payment-url response, with Swagger updates and accompanying tests.
  • Refactor PayJoin URI building to return a PayjoinUriResult, enforcing consistent status/reason pairing and validating the built URI before reporting Active.
  • Split anonymous checkout vs Greenfield output: checkout collapses to Active | Unavailable (no reason), while Greenfield retains full status + reason.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
BTCPayServer.Plugins.Payjoin/Views/Shared/PayJoinBitcoinCheckoutEnd.cshtml Checkout script now consumes status and validates fetched PayJoin BIP21 before applying it.
BTCPayServer.Plugins.Payjoin/Services/PayjoinUriSessionService.cs URI builder now returns structured result with status/reason and validates advertised PayJoin support.
BTCPayServer.Plugins.Payjoin/Services/PayjoinUriResult.cs New value object enforcing valid Active vs Unavailable state invariants.
BTCPayServer.Plugins.Payjoin/Services/PayjoinSwaggerProvider.cs Swagger documentation/schema updated to describe status + unavailableReason.
BTCPayServer.Plugins.Payjoin/Services/PayjoinInvoicePaymentUrlService.cs Service now maps builder results into GetBip21Response with status/reason.
BTCPayServer.Plugins.Payjoin/Services/PayjoinBitcoinCheckoutModelExtension.cs Removes server-side PayJoin URL application path (now handled by checkout script).
BTCPayServer.Plugins.Payjoin/Models/PayjoinCheckoutAvailabilityStatus.cs New coarse enum for anonymous checkout `Active
BTCPayServer.Plugins.Payjoin/Models/PayjoinAvailabilityStatus.cs New detailed availability enum for Greenfield/API consumers.
BTCPayServer.Plugins.Payjoin/Models/GetCheckoutBip21Response.cs New anonymous-checkout response model exposing only coarse status.
BTCPayServer.Plugins.Payjoin/Models/GetBip21Response.cs API response model now exposes Status and UnavailableReason.
BTCPayServer.Plugins.Payjoin/Controllers/UIPayJoinController.cs Anonymous endpoint now returns collapsed checkout response; test endpoint respects status/reason.
BTCPayServer.Plugins.Payjoin.Tests/UIPayJoinControllerTests.cs Adds coverage for collapsing detailed statuses into checkout coarse statuses.
BTCPayServer.Plugins.Payjoin.Tests/PayjoinUriSessionServiceStatusTests.cs Adds unit tests for early-return status/reason mapping in session builder.
BTCPayServer.Plugins.Payjoin.Tests/PayjoinSwaggerProviderTests.cs Ensures documented status enum values stay in sync with code enum.
BTCPayServer.Plugins.Payjoin.Tests/PayjoinInvoicePaymentUrlServiceTests.cs Adds unit coverage for mapping PayjoinUriResult into API response.
BTCPayServer.Plugins.Payjoin.Tests/PayjoinBitcoinCheckoutModelExtensionTests.cs Removes tests tied to deleted server-side ApplyPayjoinPaymentUrl flow.
BTCPayServer.Plugins.Payjoin.Tests/GreenfieldPayjoinControllerTests.cs Updates tests to assert Status == Active instead of PayjoinEnabled == true.
BTCPayServer.Plugins.Payjoin.IntegrationTests/TestUtils/PayjoinIntegrationTestSupport.cs Integration assertions updated for status/reason instead of boolean flag.
BTCPayServer.Plugins.Payjoin.IntegrationTests/PayjoinPluginIntegrationTests.cs Adds HTTP-level integration checks for collapsed status and camelCase payload.
BTCPayServer.Plugins.Payjoin.IntegrationTests/PayjoinPluginConcurrencyIntegrationTests.cs Updates concurrency tests to assert Active via new status field.
Comments suppressed due to low confidence (1)

BTCPayServer.Plugins.Payjoin/Views/Shared/PayJoinBitcoinCheckoutEnd.cshtml:212

  • paymentUrlFetchInvoiceId is set before the fetch but never cleared, so if the request fails or returns a non-Active status (leaving model.payjoinPaymentUrl unset), subsequent ticks will never retry fetching the PayJoin payment URL for that invoice. This makes transient unavailability effectively permanent for the session and also causes the bootstrap retry loop to spin without doing new work.
        paymentUrlFetchInvoiceId = model.invoiceId;
        fetch(model.payjoinPaymentUrlEndpoint, {
            method: 'GET',
            credentials: 'same-origin',
            headers: { 'Accept': 'application/json' }
        })
            .then(response => response.ok ? response.json() : null)
            .then(response => applyFetchedPaymentUrl(model, normalizePaymentUrlResponse(response)))
            .catch(error => console.error('PayJoin payment URL fetch failed', error))
            .finally(function () {
                scheduleTick();
            });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ValeraFinebits
ValeraFinebits marked this pull request as ready for review July 27, 2026 20:58
@ValeraFinebits
ValeraFinebits requested a review from chavic July 27, 2026 20:58
@ValeraFinebits
ValeraFinebits marked this pull request as draft July 29, 2026 09:43
@ValeraFinebits
ValeraFinebits force-pushed the feat/Report-why-Payjoin-v2-is-unavailable-instead-of-a-boolean branch from 9f5e028 to 3e57c18 Compare July 29, 2026 10:07
@ValeraFinebits
ValeraFinebits marked this pull request as ready for review July 29, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants