Skip to content

feat: implement public endpoints for invoice resolution and payment confirmation#23

Merged
codebestia merged 2 commits into
ShadeProtocol:mainfrom
Dannyorji:feat/invoive-pay
Jun 29, 2026
Merged

feat: implement public endpoints for invoice resolution and payment confirmation#23
codebestia merged 2 commits into
ShadeProtocol:mainfrom
Dannyorji:feat/invoive-pay

Conversation

@Dannyorji

@Dannyorji Dannyorji commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Overview

This pull request introduces the unauthenticated, public-facing /pay endpoints necessary for the core payment flow. It enables clients to resolve an invoice using its unique payment slug and to confirm payments without requiring merchant authentication.

Key Features & Changes

  • Database Schema Extensions:
    • Added the PaymentConfirmation model to prisma/schema.prisma to persistently track user payment confirmations.
    • Linked PaymentConfirmation to the Invoice model via a one-to-many relationship using a composite foreign key (invoiceId and merchantId).
  • Payment Services (src/services/pay.services.ts):
    • Created resolveInvoiceBySlug: Retrieves the invoice by slug, including the merchant's business name, while ensuring sensitive data remains hidden. Enforces business rules by returning specific error states (e.g., 410 Gone if the invoice is expired, CANCELLED, PAID, or REFUNDED).
    • Created confirmPayment: Validates the state of the invoice and records a new payment confirmation with the payer's address and an optional transaction hash.
  • Payment Controllers (src/controllers/pay.controllers.ts):
    • Implemented the resolveInvoiceController to handle the HTTP logic for fetching public invoice data.
    • Implemented the confirmPaymentController to securely validate incoming payloads and return a 202 Accepted status upon successful payment confirmation.
  • Routing Setup:
    • Defined the new routes in src/routes/pay.routes.ts.
    • Mounted the /pay router into the main API tree within src/routes/index.ts.

Verification Details

  • Valid Resolve: A GET request to /pay/:validSlug on a PENDING invoice correctly returns a 200 OK along with the safe public invoice payload (including merchantName).
  • Not Found: A GET request to /pay/:invalidSlug properly returns a 404 Not Found.
  • Unavailable Invoices: A GET request to /pay/:slug on a CANCELLED, PAID, or REFUNDED invoice successfully triggers a 410 Gone.
  • Expired Invoices: A GET request to /pay/:slug on an expired invoice returns a 410 Gone with { reason: "expired" }.
  • Payment Confirmation: A POST request to /pay/:slug/confirm accepts valid payloads, generates the PaymentConfirmation record, and returns a 202 Accepted.

Closes #13

Summary by CodeRabbit

  • New Features
    • Added a new /pay flow to resolve invoice details by slug and submit payment confirmations.
    • Added support for storing payment confirmations and linking them to invoices (with idempotent handling to prevent duplicates).
  • Bug Fixes
    • Improved responses for missing or expired/no-longer-available invoices.
    • Added validation for payment confirmation inputs (e.g., payer address and optional transaction hash).
  • Chores
    • Updated configuration formatting for Prisma and the test runner.

@coderabbitai

coderabbitai Bot commented Jun 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: 20363f62-4e92-4b04-9c64-eaaf6dde0cf9

📥 Commits

Reviewing files that changed from the base of the PR and between 896a646 and 57c9673.

📒 Files selected for processing (3)
  • prisma/schema.prisma
  • src/controllers/pay.controllers.ts
  • src/services/pay.services.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/controllers/pay.controllers.ts

📝 Walkthrough

Walkthrough

Adds public GET /pay/:slug and POST /pay/:slug/confirm endpoints, new payment-confirmation persistence in Prisma, invoice resolution and confirmation service logic, controller/routing wiring, and config reformatting.

Changes

Public Payment Endpoints

Layer / File(s) Summary
Config reformatting
jest.config.ts, prisma.config.ts
Reformats the Jest and Prisma config files without changing their effective values.
PaymentConfirmation Prisma model
prisma/schema.prisma
Adds the PaymentConfirmation model and the paymentConfirmations relation on Invoice.
Pay service logic
src/services/pay.services.ts
Resolves invoices by slug with 404/410/expiry handling, then creates payment confirmations with idempotency handling inside a transaction.
Controllers and routing
src/controllers/pay.controllers.ts, src/routes/pay.routes.ts, src/routes/index.ts
Adds the public invoice/confirmation controllers, wires GET /:slug and POST /:slug/confirm, and mounts the router under /pay.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PayRouter
  participant PayController
  participant PayService
  participant Prisma

  Client->>PayRouter: GET /pay/:slug
  PayRouter->>PayController: resolveInvoiceController
  PayController->>PayService: resolveInvoiceBySlug(slug)
  PayService->>Prisma: find invoice by paymentSlug
  Prisma-->>PayService: invoice or null
  PayService-->>PayController: DTO or AppError
  PayController-->>Client: 200 / 404 / 410

  Client->>PayRouter: POST /pay/:slug/confirm
  PayRouter->>PayController: confirmPaymentController
  PayController->>PayService: confirmPayment(slug, payerAddress, txHash)
  PayService->>Prisma: transaction + upsert PaymentConfirmation
  Prisma-->>PayService: confirmation row
  PayController-->>Client: 202
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • codebestia

Poem

🐇 A slug hops out, the invoice appears,
A payment is noted, with no extra fears.
The bunny nods once, then bounds away bright,
With /pay in the moonlight, all snug and right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: public payment endpoints for invoice resolution and confirmation.
Linked Issues check ✅ Passed The PR implements the public GET and POST payment endpoints, returns the required public invoice fields, handles 404/410 cases, and stores payment confirmations.
Out of Scope Changes check ✅ Passed The remaining config and formatting changes support the payment flow and do not introduce unrelated scope.
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

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: 3

🧹 Nitpick comments (1)
src/services/pay.services.ts (1)

14-17: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Narrow the merchant lookup to the fields this endpoint actually needs. The public response only uses merchant.businessName, so include: { merchant: true } pulls in unnecessary merchant data (email, address, apiKeys, etc.) on an unauthenticated path. Selecting just businessName keeps the query aligned with the safe-data contract.

🤖 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/services/pay.services.ts` around lines 14 - 17, The invoice lookup in
pay.services.ts is over-fetching merchant data by using the full merchant
relation when this endpoint only needs merchant.businessName. Update the
prisma.invoice.findUnique query in the pay service to narrow the merchant
selection to just businessName instead of include: { merchant: true }, keeping
the response aligned with the public safe-data contract. Use the existing
invoice lookup block to locate the change.
🤖 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 `@prisma/schema.prisma`:
- Around line 234-243: The PaymentConfirmation write path currently allows
duplicate records for the same confirmation request, so add a DB-enforced
idempotency boundary to the PaymentConfirmation model and the confirm flow in
pay.services.ts. Introduce a unique constraint or idempotency key on
PaymentConfirmation that can collapse retries for the same invoice/request, then
update the POST /pay/:slug/confirm logic to use an upsert/find-or-create pattern
or return the existing row instead of always creating a new one. Use the
PaymentConfirmation model and the service method that performs the insert as the
main touchpoints.

In `@src/controllers/pay.controllers.ts`:
- Around line 26-35: In pay.controllers.ts, the txHash validation in the request
handler is too permissive because it only checks typeof txHash when the value is
truthy, so falsy non-strings slip through. Update the validation around the
req.body destructuring in the controller to explicitly allow only
undefined/missing txHash or a string value, and reject any other provided value
such as 0, false, null, or objects with the same 400 response used for invalid
txHash input.

In `@src/services/pay.services.ts`:
- Around line 47-75: The availability guard in confirmPayment is split from the
paymentConfirmation insert, so the invoice state can change between the check
and the write. Refactor confirmPayment to perform the invoice lookup,
status/expiry validation, and prisma.paymentConfirmation.create within a single
transaction or equivalent conditional write so the same invoice state is used
for both decisions. Keep the existing invoice.status and expiresAt checks, but
ensure they atomically gate the creation of the confirmation record.

---

Nitpick comments:
In `@src/services/pay.services.ts`:
- Around line 14-17: The invoice lookup in pay.services.ts is over-fetching
merchant data by using the full merchant relation when this endpoint only needs
merchant.businessName. Update the prisma.invoice.findUnique query in the pay
service to narrow the merchant selection to just businessName instead of
include: { merchant: true }, keeping the response aligned with the public
safe-data contract. Use the existing invoice lookup block to locate the change.
🪄 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: 3c06b5ab-8bc7-4d5c-a1f5-6b7f146cebc1

📥 Commits

Reviewing files that changed from the base of the PR and between 971a528 and 896a646.

📒 Files selected for processing (7)
  • jest.config.ts
  • prisma.config.ts
  • prisma/schema.prisma
  • src/controllers/pay.controllers.ts
  • src/routes/index.ts
  • src/routes/pay.routes.ts
  • src/services/pay.services.ts

Comment thread prisma/schema.prisma
Comment thread src/controllers/pay.controllers.ts
Comment thread src/services/pay.services.ts Outdated
@codebestia

Copy link
Copy Markdown
Contributor

Hello @Dannyorji
Please implement the coderabbit reviews.

@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!
Thank you for your contribution.

@codebestia codebestia merged commit fac3b10 into ShadeProtocol:main Jun 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.

Public Payment Link Endpoint

2 participants