Skip to content

feat(core,react): add the payment link pay flow contract - #201

Open
TaprootFreak wants to merge 2 commits into
developfrom
feat/payment-link-pay-contract
Open

feat(core,react): add the payment link pay flow contract#201
TaprootFreak wants to merge 2 commits into
developfrom
feat/payment-link-pay-contract

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Step 3 of #198: move the payment link pay-flow contract into the packages. CRUD was already
covered; this adds the part a paying terminal or wallet needs.

Changes

@dfx.swiss/core (definitions/route.ts, PaymentLinksApi)

  • URLs: paymentLink/standard, paymentLink/standard/:id, paymentLink/payment/wait,
    paymentLink/payment/confirm, paymentLink/history
  • Types: PaymentStandard, C2BPaymentMethod, TransferMethod, TransferAmount, PaymentAmount,
    PaymentQuote, PaymentLinkRequestBase, PaymentLinkPayRequest, PaymentLinkPayTerminal,
    PaymentLinkPayResponse, PaymentLinkHistory, PaymentLinkHistoryPayment, plus the
    PaymentLinkPaymentQuery / PaymentLinkHistoryQuery parameter shapes
  • hasPaymentQuote() type guard
  • Client: getStandards, getStandard, waitForPayment, confirmPayment, getHistory

@dfx.swiss/react re-exports all of it.

Tests cover every new client method, the query building (comma-separated statuses, ISO dates,
omitted parameters) and both branches of the type guard.

What the API actually says

The issue asked to verify the endpoints before freezing them. Doing that turned up four places
where a locally modelled version of this contract does not match the API:

  1. A history entry is not a pay request. paymentLink/history returns payment links (id,
    routeId, url, lnurl, status, recipient, config …) each with their payments[] and
    totalCompletedAmount. It has no tag, callback, quote, minSendable/maxSendable and no
    requestedAmount. PaymentLinkHistory is modelled on the payment link, with payment omitted —
    the history carries the plural payments instead, never the singular one.
  2. A history payment has a numeric id and carries more than id/status/amount/currency/date: also
    note, mode, expiryDate, txCount, isConfirmed, url and lnurl.
  3. paymentLink/payment/wait returns the whole payment link, not a bare { status }. The status
    lives at payment.status. (The bare status shape belongs to the LNURL wait endpoint, which is a
    different call.)
  4. route and currency are optional on a pay request, and available is always present on a
    transfer amount.

paymentLink/standard does exist — it is served by its own controller rather than the payment link
controller, which is why it is easy to miss.

Scope

  • recipient reuses the existing PaymentLinkRecipient instead of introducing a second recipient
    type. It is the same object; a separate one would only differ by declaring the address fields as
    required, which the API does not guarantee.
  • No React hook. The pay flow is a stateful polling flow whose loop, timers and merchant session
    handling belong to the consumer. The parts worth sharing are the wire types and the URL and query
    building, and those are now here.
  • Not moved: POS UI orchestration, wallet/MetaMask handling, polling timers, merchant session
    storage, and the UI-only "no payment" pseudo status.
  • The existing PaymentLink.id / routeId are typed as string while the API returns numbers.
    That predates this change and is left alone; PaymentLinkHistory inherits it rather than
    contradicting its own base type.

Version fields, changelogs and lockfile pins are untouched, per CONTRIBUTING.


Corrections from review

Reviewing the frozen contract against the API turned up seven more differences, all fixed here:

  • PaymentLinkHistoryQuery.linkId is gone. The history endpoint never reads it — a caller who passed
    it would silently get the account's whole history instead of one link's.
  • PaymentLinkPaymentQuery.route is gone. Wait and confirm do not read it; only create and cancel do,
    and those are not wrapped here.
  • PaymentLinkHistoryPayment and PaymentLink gained frontendUrl, which the API always sends;
    PaymentLink also gained the webhookUrl it returns. This reaches waitForPayment,
    confirmPayment and every history entry.
  • PaymentLinkRecipient gained the five merchant fields the API carries in these responses
    (registrationNumber, storeType, merchantCategory, goodsType, goodsCategory).
  • TransferMethod now covers the manually settled methods via ManualPaymentMethod. The API appends
    TaprootAsset to every transfer amount list, and that value is in no blockchain enum — the old
    union rejected a value that arrives on every single response.
  • PaymentLinkPayTerminal.error, .message and .statusCode are required. They always arrive
    together; declaring them optional invited a null check that can never fire.

PaymentLink gaining a required frontendUrl is worth calling out for downstream consumers: reading
code is unaffected, but code that builds a PaymentLink literal (test fixtures, mocks) has to add the
field.

@TaprootFreak

Copy link
Copy Markdown
Contributor Author

Took 2 review passes to reach a clean run.

The second pass found seven places where the contract, as first written here, did not match what the
API actually serves — all fixed in fix(core): correct the payment link pay flow contract:

  • linkId on the history query is never read by that endpoint; a caller passing it would have got
    the account's whole history instead of one link's. Same for route on the wait/confirm query.
  • frontendUrl (always sent) and webhookUrl were missing from the payment link, and frontendUrl
    was missing from a history payment.
  • The recipient carries five merchant fields that were unreachable through these types.
  • TransferMethod did not cover TaprootAsset, which the API appends to every transfer amount list
    and which is in no blockchain enum.
  • The terminal response's error, message and statusCode always arrive together; they were
    declared optional.

Note for downstream consumers: PaymentLink gained a required frontendUrl. Reading code is
unaffected; code that builds a PaymentLink literal (fixtures, mocks) needs the field.

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.

1 participant