Skip to content

feat : Indexer Handler InvoicePaidEvent - #38

Merged
codebestia merged 3 commits into
ShadeProtocol:mainfrom
ryzen-xp:feat/Indexer-Handler-InvoicePaidEvent
Jul 29, 2026
Merged

feat : Indexer Handler InvoicePaidEvent#38
codebestia merged 3 commits into
ShadeProtocol:mainfrom
ryzen-xp:feat/Indexer-Handler-InvoicePaidEvent

Conversation

@ryzen-xp

@ryzen-xp ryzen-xp commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Soroban InvoicePaid event handling to synchronize on-chain invoice payments into backend state.

  • Registers the InvoicePaid handler when the indexer starts.
  • Normalizes the contract’s decoded snake_case event payload into InvoicePaidEventData.
  • Adds applyInvoicePayment to update invoices and create INVOICE_PAYMENT transactions atomically.
  • Supports partial payments (PARTIALLY_PAID) and completed payments (PAID with datePaid).
  • Logs and safely skips events whose invoice or merchant is absent from the database.
  • Keeps POST /pay/:slug/confirm backward-compatible but explicitly non-authoritative; invoice state is now driven by on-chain events.
  • Documents that deposit-account detection remains out of scope.

Validation

  • npm test -- --runInBand — 32 suites, 251 tests passed
  • npx tsc --noEmit
  • npm run format:check
  • npm run lint:check — no errors; existing warnings remain
Screenshot From 2026-07-29 08-17-56

Notes

InvoicePaid is registered from the contract event definition and normalized using its actual payload fields (invoice_id, merchant_id, merchant_amount, etc.). Live testnet E2E verification still requires a configured STELLAR_CONTRACT_ID and matching database records.

Summary by CodeRabbit

  • New Features

    • Added automatic indexing and processing of confirmed on-chain invoice payment events.
    • Invoices now update payment progress (partial vs completed), payer details, totals, and associated payment transaction history.
    • Added validation and normalization for invoice payment event payloads.
  • Bug Fixes

    • Prevented mismatched or missing invoice/merchant records from being updated; logs warnings and safely skips.
    • Updated the payment confirmation response message to maintain backward compatibility.
  • Tests

    • Added unit tests for event decoding/dispatching and invoice payment application scenarios.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 540f71cc-a30a-4eb4-8ada-7821c2b62ab4

📥 Commits

Reviewing files that changed from the base of the PR and between 2842dd4 and 726586d.

📒 Files selected for processing (2)
  • src/services/invoice.services.ts
  • tests/unit/invoice.services.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/invoice.services.test.ts
  • src/services/invoice.services.ts

📝 Walkthrough

Walkthrough

The indexer now registers and processes InvoicePaid events, normalizes decoded contract data, and applies payment state and transaction records through the invoice service. The confirmation endpoint identifies on-chain events as authoritative.

Changes

Invoice payment synchronization

Layer / File(s) Summary
InvoicePaid event decoding
src/indexer/types.ts, tests/unit/invoice-paid.handler.test.ts
Defines and validates normalized event payloads, including field aliases, numeric ranges, bigint values, required strings, and invalid-payload behavior.
Invoice payment projection
src/services/invoice.services.ts, tests/unit/invoice.services.test.ts
Adds partial and completed payment handling, invoice updates, transaction creation, missing-record handling, and coverage for these paths.
Handler registration and confirmation boundary
src/indexer/handlers/*, src/indexer/run.ts, src/controllers/pay.controllers.ts
Registers InvoicePaid, loads handlers at startup, dispatches decoded events to the payment service, and updates the confirmation response message.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Indexer
  participant handleInvoicePaid
  participant decodeInvoicePaidEventData
  participant applyInvoicePayment
  participant Prisma
  Indexer->>handleInvoicePaid: dispatch InvoicePaid event
  handleInvoicePaid->>decodeInvoicePaidEventData: decode payload
  decodeInvoicePaidEventData-->>handleInvoicePaid: normalized event data
  handleInvoicePaid->>applyInvoicePayment: pass event data and txHash
  applyInvoicePayment->>Prisma: update invoice and create transaction
Loading

Possibly related PRs

Suggested reviewers: codebestia

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive Most issue #30 requirements appear met, but the summaries do not confirm the required explicit note that deposit-based detection is still unimplemented. Add a clear code comment or follow-up ticket reference stating deposit-based payment detection is intentionally not implemented yet.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is specific and matches the main change: adding InvoicePaid event handling in the indexer.
Out of Scope Changes check ✅ Passed The changes stay focused on InvoicePaid event handling, service mutation, handler registration, tests, and backward-compatibility updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/unit/invoice.services.test.ts (1)

164-254: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the merchant-not-found and merchant-mismatch branches.

The new suite exercises partial payment, completed payment, and missing-invoice skip, but not the merchant-not-found or invoice.merchantId !== merchant.id skip branches in applyInvoicePayment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/invoice.services.test.ts` around lines 164 - 254, Extend the
applyInvoicePayment tests to cover both merchant skip branches: when
merchant.findUnique returns null and when the invoice merchantId differs from
the fetched merchant id. Assert each case logs the expected warning, resolves
without applying payment, and does not invoke transaction processing.
src/indexer/types.ts (1)

39-62: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add non-negativity checks for monetary bigint fields; tighten toString typing.

toBigInt/toSafeNumber enforce safe-integer bounds only for invoiceId/merchantId/timestamp; amount, fee, and merchantAmount are converted with plain toBigInt and accept negative values. Soroban amounts are typically i128 (signed), so nothing structurally prevents a negative value from reaching applyInvoicePayment, where it's added directly to invoice.amountPaid and could corrupt payment totals/status.

Separately, toString calls String(value) unconditionally — a non-string/non-null value (e.g. an object) would silently coerce to "[object Object]" instead of failing loudly, unlike the other coercion helpers.

♻️ Suggested tightening
+const toNonNegativeBigInt = (value: unknown, field: string): bigint => {
+  const parsed = toBigInt(value, field);
+  if (parsed < 0n) {
+    throw new Error(`InvoicePaid event field "${field}" cannot be negative`);
+  }
+  return parsed;
+};
+
 const toString = (value: unknown, field: string): string => {
-  if (value === null || value === undefined) {
+  if (typeof value !== 'string') {
     throw new Error(`InvoicePaid event field "${field}" is required`);
   }
-  return String(value);
+  return value;
 };

Then use toNonNegativeBigInt for amount, fee, and merchantAmount in decodeInvoicePaidEventData.

Please confirm whether the Soroban InvoicePaidEvent contract enforces non-negative amounts on-chain (e.g. via an assert!), which would make this defensive check redundant but still cheap insurance against malformed/adversarial decoded payloads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/indexer/types.ts` around lines 39 - 62, Update the monetary-field
conversion in decodeInvoicePaidEventData to use a new toNonNegativeBigInt helper
for amount, fee, and merchantAmount, rejecting values below zero before
applyInvoicePayment receives them. Tighten toString to accept only string values
(while retaining the existing required check), and throw for other types instead
of coercing them with String(value).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/invoice.services.ts`:
- Around line 190-251: Move the `amountPaid` and `status` calculation into the
`prisma.$transaction` callback in `applyInvoicePayment`, re-read the invoice
there using the transaction client, and derive both values from that fresh row
before `tx.invoice.update`. Use the transaction-scoped invoice amount and
identifier while preserving the existing payment and transaction creation
behavior.

---

Nitpick comments:
In `@src/indexer/types.ts`:
- Around line 39-62: Update the monetary-field conversion in
decodeInvoicePaidEventData to use a new toNonNegativeBigInt helper for amount,
fee, and merchantAmount, rejecting values below zero before applyInvoicePayment
receives them. Tighten toString to accept only string values (while retaining
the existing required check), and throw for other types instead of coercing them
with String(value).

In `@tests/unit/invoice.services.test.ts`:
- Around line 164-254: Extend the applyInvoicePayment tests to cover both
merchant skip branches: when merchant.findUnique returns null and when the
invoice merchantId differs from the fetched merchant id. Assert each case logs
the expected warning, resolves without applying payment, and does not invoke
transaction processing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: de4da130-5ade-4da8-beed-4e5fe7a4bada

📥 Commits

Reviewing files that changed from the base of the PR and between d7b5942 and e6c8b6a.

📒 Files selected for processing (8)
  • src/controllers/pay.controllers.ts
  • src/indexer/handlers/index.ts
  • src/indexer/handlers/invoicePaid.ts
  • src/indexer/run.ts
  • src/indexer/types.ts
  • src/services/invoice.services.ts
  • tests/unit/invoice-paid.handler.test.ts
  • tests/unit/invoice.services.test.ts

Comment thread src/services/invoice.services.ts
@codebestia

Copy link
Copy Markdown
Contributor

Hello @ryzen-xp
Please address the coderabbit reviews

@ryzen-xp

Copy link
Copy Markdown
Contributor Author

Hello @codebestia check again

@codebestia codebestia 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.

LGTM!
Nice implementation.
Thank you for your contribution.

@codebestia
codebestia merged commit f7a0e92 into ShadeProtocol:main Jul 29, 2026
3 checks 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.

Indexer Handler: InvoicePaidEvent → Invoice State Sync

2 participants