Skip to content

feat(payment-receipt-reconciler): reconcile payment ops with account effects - #492

Merged
egekoca merged 3 commits into
RevenantLabs:mainfrom
chainsmt:feat/payment-receipt-reconciler
Sep 30, 2026
Merged

egekoca merged 3 commits into
RevenantLabs:mainfrom
chainsmt:feat/payment-receipt-reconciler

Conversation

@chainsmt

@chainsmt chainsmt commented Sep 29, 2026 •

Copy link
Copy Markdown

Closes #466

Summary

Adds Payment Receipt Reconciler under features/payment-receipt-reconciler/. Given one settled classic payment or path-payment transaction, it builds a public receipt that ties supported operations to observed Horizon debit/credit effects, shows exact per-asset totals, and keeps the charged fee separate — without pretending to be a full accounting export.

Motivation

/transactions/{hash} plus operations and effects already contain the pieces of a receipt, but operators still have to manually correlate operation TOIDs with effect IDs and decide what belongs “inside” the receipt. Silent drops of unrelated ops/effects hide audit trails. This tool keeps outside items visible and links supported transfers exactly.

What this PR does

  • Validates a 64-character transaction hash locally before networking (empty_input / invalid_hash).
  • Fetches Horizon GET /transactions/{hash}, /operations, and /effects on the selected network via lib/receipt-fetch.ts.
  • Rejects absent transactions (transaction_not_found) and failed transactions (transaction_failed) with specific outcomes — a failed payment is not treated as a durable receipt.
  • Keeps payment, path_payment_strict_send, and path_payment_strict_receive as supported operations; everything else is listed under Outside this receipt (not silently dropped).
  • Links each supported operation to account_debited / account_credited effects using the operation TOID embedded in the effect id (lib/effect-links.ts). Missing either side yields incomplete_effects.
  • Sums exact amounts by asset code + issuer with BigInt stroops (lib/receipt-amounts.ts). Charged fee is displayed separately and never folded into debit totals.
  • Produces a copyable public receipt: transaction hash, selected network, ledger number, with operation/effect ids still visible for audit.
  • Full feature contract: schema, copy, hook, panel/form/result/empty, MSW, fixtures (including mixed-effects), unit/a11y/e2e (including mixed-effects journey).

Architecture notes

  • Slice-only: only features/payment-receipt-reconciler/.
  • Effect linking prefers Horizon effect id shape over guessing from accounts/amounts so multi-op transactions stay exact.
  • Transport/decode problems map to request_failed; domain outcomes keep dedicated codes for actionable UI copy.

Acceptance criteria coverage

  • Validate hash; reject absent or failed transactions specifically
  • Link supported payment/path-payment ops to visible debit/credit effects
  • Show exact amounts by asset code + issuer; charged fee separate
  • Mark unrelated op/effect types as outside the receipt
  • Copyable public receipt with hash, network, ledger

Out of scope

  • No payment initiation
  • No tax / accounting classification
  • No secret keys

Test plan

  • npm run verify:features -- payment-receipt-reconciler (35 files)
  • npm test -- features/payment-receipt-reconciler (52 tests)
  • Manual: successful payment hash on testnet → ops, effects, totals, outside items
  • Manual: failed / missing hash → specific error copy
  • Manual: mixed-effects fixture path → outside section populated

@drips-wave

drips-wave Bot commented Sep 29, 2026

Copy link
Copy Markdown

@chainsmt Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

egekoca and others added 2 commits September 30, 2026 17:54
HttpResponse.json<T extends JsonBodyType>() cannot infer a type
parameter from a bare 'unknown' argument, since 'unknown' is not
assignable to JsonBodyType's Record<string, any> | primitive union.
Typing the fixture parameters as JsonBodyType directly (msw's own
exported type) fixes the type error without loosening what the
fixtures can actually be.
@egekoca
egekoca merged commit 631ff0a into RevenantLabs:main Sep 30, 2026
1 check passed
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.

[Payments] Payment Receipt Reconciler

2 participants