Skip to content

feat: Invoice Model & Core CRUD API (/invoices)#17

Merged
codebestia merged 1 commit into
ShadeProtocol:mainfrom
josephchimebuka:feat/invoice-crud
Jun 24, 2026
Merged

feat: Invoice Model & Core CRUD API (/invoices)#17
codebestia merged 1 commit into
ShadeProtocol:mainfrom
josephchimebuka:feat/invoice-crud

Conversation

@josephchimebuka

@josephchimebuka josephchimebuka commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the invoice model expansion and merchant-facing CRUD described in #12: create, list, get, and void — all protected and scoped to the authenticated merchant.

Note

Rebased onto the latest main (now includes the merged merchant-auth work from #16 and the auth-verify endpoint from #15). No conflicts; the diff is limited to invoice files.

Endpoints

All routes live under /api/v1/invoices and require a valid merchant session (Authorization: Bearer <token>).

Method Path Description
POST /invoices Create an invoice
GET /invoices List invoices (filtered, paginated)
GET /invoices/:id Get one invoice by UUID
PATCH /invoices/:id/void Void a PENDING invoice

Create

Accepts description, amount, token, and optional payerEmail, expiresAt, isDraft. A unique, url-safe paymentSlug (base64url) is generated server-side. Status is DRAFT when isDraft: true, otherwise PENDING.

List

Query filters: status, token, startDate, endDate. Pagination: limit (default 20, clamped to max 100) and offset. Returns { data, pagination: { limit, offset, total } }, scoped to the authenticated merchant and ordered newest-first.

Changes

  • Schema (prisma/schema.prisma): adds DRAFT to the Status enum and expands Invoice with paymentSlug (@unique), description, payerEmail, expiresAt, a status default of PENDING, makes invoiceId/ref optional, and indexes merchantId / (merchantId, status).
  • Services (src/services/invoice.services.ts): createInvoice, listInvoices, getInvoice, voidInvoice. Ownership is enforced via merchantId on every query. sanitizeInvoice serializes the BigInt amount to a string (JSON-safe). Slug creation retries on the rare unique-constraint collision.
  • Validation (src/utils/invoice.validation.ts): positive-integer amount parsing, non-empty token, optional email/date checks, and list-query parsing with limit clamping.
  • Slug (src/utils/slug.ts): base64url payment slug generator (~96 bits of entropy).
  • Controllers / routes (src/controllers/invoice.controllers.ts, src/routes/invoice.routes.ts): wired under /invoices behind authenticateMerchant and mounted in src/routes/index.ts.

Important

@prisma/client is imported type-only throughout the invoice modules (enum/status handled via local string constants, Prisma error detection via duck-typing on code === 'P2002'). This keeps the test runtime free of the generated client — consistent with the project's fully-mocked Jest setup, where CI does not run prisma generate.

Acceptance criteria

  • POST /invoices returns 201 with the created invoice including paymentSlug
  • paymentSlug is unique and url-safe
  • isDraft: true creates an invoice with status = DRAFT
  • GET /invoices returns a paginated list scoped to the authenticated merchant
  • Status filter works correctly
  • GET /invoices/:id returns 404 when the invoice doesn't exist or belongs to another merchant
  • PATCH /invoices/:id/void on a non-PENDING invoice → 400
  • Amount must be positive; token must be a non-empty string

Test plan

  • npm test — full suite green (8 suites, including the merchant/auth suites already on main and the new invoice suites). New tests: tests/unit/invoice.services.test.ts and tests/integration/invoice.routes.test.ts cover each acceptance criterion (auth, create PENDING/DRAFT, validation, scoped list + filter + limit clamping, 404 ownership, void rules).
  • Verified the invoice suites pass even without a generated Prisma client, reproducing the CI environment.
  • prettier --check "src/**" — passes.

Notes / follow-ups

  • The Invoice schema changed, so a migration must be generated and applied (npm run prisma:migrate) in environments with a database. No migration files are committed because the repo has no migrations baseline yet.
  • Payment/settlement flow (moving PENDINGPAID, setting invoiceId/datePaid) is out of scope here and left for a follow-up.

Closes #12

Summary by CodeRabbit

  • New Features

    • Added invoice management support, including creating, listing, viewing, and voiding invoices.
    • Introduced draft invoice status and improved invoice URL generation.
    • Added filtering and pagination for invoice lists.
  • Bug Fixes

    • Improved validation for invoice amounts, tokens, emails, dates, and pagination values.
    • Prevented voiding invoices unless they are in a pending state.
    • Added clearer handling for unauthorized and missing-invoice cases.

@codebestia

Copy link
Copy Markdown
Contributor

Hello @josephchimebuka
Please resolve the conflicts and fix the CI.
Thank you

@josephchimebuka

Copy link
Copy Markdown
Contributor Author

Alright I am on it and I am fixing CI now

Expands the Invoice model and adds the merchant-facing CRUD endpoints
described in ShadeProtocol#12. All routes are protected and scoped to the
authenticated merchant.

- POST /invoices: create an invoice with a unique, url-safe paymentSlug;
  status DRAFT when isDraft is true, otherwise PENDING
- GET /invoices: paginated list (limit default 20, max 100) with status,
  token, startDate and endDate filters, scoped to the merchant
- GET /invoices/:id: fetch a single invoice, 404 when missing or owned
  by another merchant
- PATCH /invoices/:id/void: cancel a PENDING invoice, 400 otherwise
- Validate positive integer amount and non-empty token
- Expand Invoice schema (paymentSlug, description, payerEmail, expiresAt,
  status default, DRAFT enum value) and serialize BigInt amount as string
- Add unit and integration tests covering all acceptance criteria

Stacked on top of ShadeProtocol#16 (merchant auth middleware).

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8230567b-7b79-40dd-96b4-2cd1f378f1e8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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 1a0d6f7 into ShadeProtocol:main Jun 24, 2026
2 of 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.

Invoice Model & Core CRUD API

2 participants