You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Blocked by: Issue #24 (Core Soroban Indexer Infrastructure). This issue registers a handler against the dispatch registry, cursor, and dedupe pipeline that Issue #24 — it cannot start until that skeleton exists.
Background
Issue #24 builds the generic decode → dedupe → dispatch pipeline with an empty handler registry. This issue plugs in the first real handler: InvoicePaidEvent, following the same decode pattern proven in the earlier proof of concept (topic decoded via scValToNative) — but this time the Prisma writes live in a service function the handler calls, not inline in the handler itself, so the state-mutation logic is testable on its own and independent of the indexer plumbing.
Proposed Steps
Confirm the real topic symbol and decoded field casing for InvoicePaidEvent against testnet — this should already exist from Issue Core Soroban Indexer Infrastructure #24's verification step; don't re-guess it here, and don't proceed with a placeholder topic string.
Add a service function — applyInvoicePayment(event: InvoicePaidEventData, txHash: string) in src/services/invoice.services.ts, alongside the invoice model's other mutations — responsible for:
Looking up Invoice by invoiceId (log and skip if not found — don't throw; ordering gaps between DB and chain are possible and not an error condition)
Looking up Merchant by merchantId
Computing amountPaid = invoice.amountPaid + event.amount and the resulting status: PAID if amountPaid >= invoice.amount, else PARTIALLY_PAID
Inside one prisma.$transaction: updating the Invoice (status, payer, amountPaid, datePaid) and creating a Transaction row (transactionType: INVOICE_PAYMENT, refId: invoiceId, amount, token, merchantId, date)
Add src/indexer/handlers/invoicePaid.ts — thin glue only: receives the already-decoded event from the poller and calls applyInvoicePayment. No Prisma calls in this file directly.
Register it: registerEventHandler(<confirmed topic string>, handleInvoicePaid) at whatever bootstrap point Issue Core Soroban Indexer Infrastructure #24's registry expects (e.g. imported by run.ts before startPolling() is called).
Idempotency is already handled upstream by Issue Core Soroban Indexer Infrastructure #24's IndexerEvent dedupe table, before dispatch ever reaches this handler — do not add a second replay guard here (e.g. no Transaction.eventId-style uniqueness needed).
Once this handler is verified working end-to-end on testnet, deprecate POST /pay/:slug/confirm for the wallet-payment path: stop treating its input as authoritative for invoice status — either remove the route, or downgrade it to a non-authoritative log-only call that no longer mutates Invoice.status/amountPaid. Do not remove it before this handler is proven working; there would be no confirmation path left in the interim.
Do not implement deposit-based detection here. Leave a clear note (code comment or follow-up ticket reference) that it's a known gap pending a future issue.
Acceptance Criteria
Handler is registered using the topic string confirmed against a real testnet event, not a placeholder
All Prisma writes live in applyInvoicePayment (invoice.services.ts); handlers/invoicePaid.ts contains no direct Prisma calls
Triggering pay_invoice on testnet results in the matching Invoice.status / amountPaid / datePaid being updated and a Transaction row created — with no client-submitted confirmation involved
invoiceId not found in the DB → logged and skipped, not thrown
A partial payment (event amount less than the remaining balance) results in status = PARTIALLY_PAID
A payment that completes the invoice results in status = PAID and datePaid set
No second idempotency/dedupe mechanism is introduced in this issue — replay protection is entirely Issue Core Soroban Indexer Infrastructure #24's IndexerEvent pipeline's responsibility
POST /pay/:slug/confirm is deprecated/de-authoritative for wallet payments once this handler is verified working — only after, not before
Deposit-based confirmation is explicitly not implemented here
Blocked by: Issue #24 (Core Soroban Indexer Infrastructure). This issue registers a handler against the dispatch registry, cursor, and dedupe pipeline that Issue #24 — it cannot start until that skeleton exists.
Background
Issue #24 builds the generic decode → dedupe → dispatch pipeline with an empty handler registry. This issue plugs in the first real handler:
InvoicePaidEvent, following the same decode pattern proven in the earlier proof of concept (topic decoded viascValToNative) — but this time the Prisma writes live in a service function the handler calls, not inline in the handler itself, so the state-mutation logic is testable on its own and independent of the indexer plumbing.Proposed Steps
InvoicePaidEventagainst testnet — this should already exist from Issue Core Soroban Indexer Infrastructure #24's verification step; don't re-guess it here, and don't proceed with a placeholder topic string.src/indexer/types.ts(if Issue Core Soroban Indexer Infrastructure #24 didn't already scaffold it):InvoicePaidEventData { invoiceId, merchantId, payer, amount, fee, merchantAmount, token, timestamp }.applyInvoicePayment(event: InvoicePaidEventData, txHash: string)insrc/services/invoice.services.ts, alongside the invoice model's other mutations — responsible for:InvoicebyinvoiceId(log and skip if not found — don't throw; ordering gaps between DB and chain are possible and not an error condition)MerchantbymerchantIdamountPaid = invoice.amountPaid + event.amountand the resulting status:PAIDifamountPaid >= invoice.amount, elsePARTIALLY_PAIDprisma.$transaction: updating theInvoice(status,payer,amountPaid,datePaid) and creating aTransactionrow (transactionType: INVOICE_PAYMENT,refId: invoiceId,amount,token,merchantId,date)src/indexer/handlers/invoicePaid.ts— thin glue only: receives the already-decoded event from the poller and callsapplyInvoicePayment. No Prisma calls in this file directly.registerEventHandler(<confirmed topic string>, handleInvoicePaid)at whatever bootstrap point Issue Core Soroban Indexer Infrastructure #24's registry expects (e.g. imported byrun.tsbeforestartPolling()is called).IndexerEventdedupe table, before dispatch ever reaches this handler — do not add a second replay guard here (e.g. noTransaction.eventId-style uniqueness needed).POST /pay/:slug/confirmfor the wallet-payment path: stop treating its input as authoritative for invoice status — either remove the route, or downgrade it to a non-authoritative log-only call that no longer mutatesInvoice.status/amountPaid. Do not remove it before this handler is proven working; there would be no confirmation path left in the interim.Acceptance Criteria
applyInvoicePayment(invoice.services.ts);handlers/invoicePaid.tscontains no direct Prisma callspay_invoiceon testnet results in the matchingInvoice.status/amountPaid/datePaidbeing updated and aTransactionrow created — with no client-submitted confirmation involvedinvoiceIdnot found in the DB → logged and skipped, not thrownamountless than the remaining balance) results instatus = PARTIALLY_PAIDstatus = PAIDanddatePaidsetIndexerEventpipeline's responsibilityPOST /pay/:slug/confirmis deprecated/de-authoritative for wallet payments once this handler is verified working — only after, not before