feat: implement public endpoints for invoice resolution and payment confirmation#23
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds public ChangesPublic Payment Endpoints
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/services/pay.services.ts (1)
14-17: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winNarrow the merchant lookup to the fields this endpoint actually needs. The public response only uses
merchant.businessName, soinclude: { merchant: true }pulls in unnecessary merchant data (address,apiKeys, etc.) on an unauthenticated path. Selecting justbusinessNamekeeps 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
📒 Files selected for processing (7)
jest.config.tsprisma.config.tsprisma/schema.prismasrc/controllers/pay.controllers.tssrc/routes/index.tssrc/routes/pay.routes.tssrc/services/pay.services.ts
|
Hello @Dannyorji |
codebestia
left a comment
There was a problem hiding this comment.
LGTM!
Thank you for your contribution.
Overview
This pull request introduces the unauthenticated, public-facing
/payendpoints 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
PaymentConfirmationmodel toprisma/schema.prismato persistently track user payment confirmations.PaymentConfirmationto theInvoicemodel via a one-to-many relationship using a composite foreign key (invoiceIdandmerchantId).src/services/pay.services.ts):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 Goneif the invoice is expired, CANCELLED, PAID, or REFUNDED).confirmPayment: Validates the state of the invoice and records a new payment confirmation with the payer's address and an optional transaction hash.src/controllers/pay.controllers.ts):resolveInvoiceControllerto handle the HTTP logic for fetching public invoice data.confirmPaymentControllerto securely validate incoming payloads and return a202 Acceptedstatus upon successful payment confirmation.src/routes/pay.routes.ts./payrouter into the main API tree withinsrc/routes/index.ts.Verification Details
/pay/:validSlugon a PENDING invoice correctly returns a 200 OK along with the safe public invoice payload (includingmerchantName)./pay/:invalidSlugproperly returns a 404 Not Found./pay/:slugon a CANCELLED, PAID, or REFUNDED invoice successfully triggers a 410 Gone./pay/:slugon an expired invoice returns a 410 Gone with{ reason: "expired" }./pay/:slug/confirmaccepts valid payloads, generates thePaymentConfirmationrecord, and returns a 202 Accepted.Closes #13
Summary by CodeRabbit
/payflow to resolve invoice details by slug and submit payment confirmations.