feat/commerce-operations #4

Merged
mihir merged 5 commits from feat/commerce-operations into main 2026-09-11 17:01:26 +05:30
9 changed files with 122 additions and 11 deletions
Showing only changes of commit 2ee2e7dd1f - Show all commits

View File

@ -31,3 +31,4 @@ Production must use a managed secret store, TLS termination, a restricted databa
Identity: see [API contract](docs/identity-api.md) and [setup/release guide](docs/identity-operations.md).
Phase 1C adds catalog, private addresses and inventory. See [commerce API](docs/commerce-api.md), [errors](docs/error-contract.md), [migrations](docs/migrations.md), [security preparation](docs/vapt-readiness.md) and [verification](docs/verification.md).
Phase 1D adds versioned carts, coupons, atomic checkout and private order snapshots. See [checkout API and pricing boundary](docs/checkout-api.md). Payment remains disabled until tax, shipping and payment rules are finalized.
Phase 1E provides a [provider-independent blueprint and test matrix](docs/phase1e-blueprint.md), configurable pricing snapshots and an [operational outbox/API](docs/operations-api.md). No real gateway or message delivery is enabled.

View File

@ -28,9 +28,9 @@ Orders snapshot SKU, product/variant names, unit prices, quantities, line totals
## Pricing and payment boundary
Orders start as PENDING_PAYMENT but expose pricingStatus UNFINALIZED, paymentAvailable false, and null taxTotal, shippingTotal and payableTotal. Merchandise total is subtotal minus discount; it is not a final amount to charge. Null charges must never be rendered as free shipping or zero tax.
Orders start as PENDING_PAYMENT. Without a matching active pricing policy, pricingStatus is UNFINALIZED and taxTotal, shippingTotal and payableTotal are null. Phase 1E adds optional finalized pricing snapshots; see [pricing configuration](operations-api.md). paymentAvailable remains false in all cases. Merchandise total is subtotal minus discount; it is not a final amount to charge. Null charges must never be rendered as free shipping or zero tax.
Tax and shipping rules have not been supplied. This phase makes no assumption about tax treatment or delivery charges and does not create payments. Before enabling payment, finalize and snapshot those rules through a new migration and implement the verified payment lifecycle in Phase 1E. A fully discounted order still requires that workflow.
Tax and shipping rules have not been supplied. This phase makes no assumption about tax treatment or delivery charges and does not create payments. Configure approved pricing policy versions before using finalized totals. Real payment integration is deferred at the user request; follow the Phase 1E blueprint before enabling it. A fully discounted order still requires that workflow.
## Coupon rules

View File

@ -8,3 +8,4 @@ Login failures deliberately share a public message to prevent enumeration; inter
Responses include X-Request-Id and Cache-Control: no-store. Throttled responses include Retry-After seconds. Client request IDs are not trusted. Configure restricted log access, retention and alerting at deployment.
Checkout codes are defined in src/common/errors/checkout-errors.ts. Coupon eligibility failures share a safe public response with separate internal diagnostic reasons.
Phase 1E codes live in src/common/errors/operations-errors.ts. Delivery logs use fixed DELIVERY_FAILED and DELIVERY_LEASE_LOST events with event IDs, never raw adapter errors.

View File

@ -13,3 +13,4 @@ Production uses `pnpm db:deploy`, then `pnpm db:status`. Never use db push in pr
Phase 1C appends four migrations after the original three: catalog/addresses, inventory, commerce integrity and inventory actor scope. SQL maintains additional integrity constraints and the append-only ledger trigger.
Phase 1D appends checkout tables, reservation ownership, immutable snapshot guards and deferred order/line reconciliation. Earlier migrations are unchanged.
Phase 1E adds pricing policies, immutable final-price snapshots and commerce events with leased delivery state in two further timestamped migrations. All eleven preceding migrations remain unchanged.

39
docs/operations-api.md Normal file
View File

@ -0,0 +1,39 @@
# Pricing and operations APIs
All routes begin with /api/v1 and require an active bearer session. Organization scope comes from that session.
| Route | Permission |
| ---------------------------------------- | ------------------- |
| GET/POST /admin/pricing-policies | pricing.manage |
| PATCH /admin/pricing-policies/:id/status | pricing.manage |
| GET /admin/operations/summary | operations.read |
| GET /admin/operations/events | operations.read |
| POST /admin/operations/events/:id/retry | notifications.retry |
List routes use the existing limit/offset bounds. Existing system roles gain these permissions through migration; custom roles require explicit grants.
## Pricing configuration
Create a policy with name, currency, countryCode, optional region, taxMode (INCLUSIVE or EXCLUSIVE), merchandiseTaxBps, shippingFee, shippingTaxBps and optional freeShippingMinimum. Rates use basis points (100 means 1%) and must be 010000. Money inputs are nonnegative decimal strings with two fractional digits. Free-shipping minimum may be null. Country/region values normalize to uppercase.
Every policy starts inactive. PATCH status accepts only active. Activating a version disables the previous active version for the same organization/currency/country/region. A matching region-specific rule takes precedence over the country's empty-region fallback. No policy values are seeded for Mani Candles.
Checkout selects an active matching policy and snapshots its rules and calculated totals. No match leaves pricing UNFINALIZED. Existing orders are never retroactively priced or repriced. Policy rules and order-pricing rows are immutable; changes require a new policy version.
Merchandise tax is calculated on the discounted merchandise amount. INCLUSIVE extracts tax; EXCLUSIVE adds it. ShippingFee is tax-exclusive; shippingTaxBps applies to that fee. Free-shipping eligibility uses the discounted merchandise amount before adding tax or shipping. Calculations use exact integer minor units and round half up.
Order detail exposes pricingStatus, taxTotal, shippingTotal, payableTotal and pricing breakdown. shippingTotal is the net shipping fee; taxTotal includes merchandise and shipping tax, including tax already included in merchandise prices where applicable. Clients must use payableTotal as authoritative and must not reconstruct it by blindly summing these display fields. Decimal output may omit trailing zeroes.
paymentAvailable remains false regardless of pricing configuration because no production gateway exists. Finalized pricing is not proof of payment or shipping eligibility.
## Event delivery and operations
Successful checkout and cancellation append unique immutable commerce events with IDs and order references only. Event creation shares the business transaction, so rollback creates no event. A separate mutable delivery row tracks attempts, availability and delivery state. Private addresses and credentials are not copied into events.
DeliveryStore claims one event with FOR UPDATE SKIP LOCKED and a 60-second lease. Acknowledgement must match the current, unexpired lease. Failures back off for 30, 60, 120 and 240 seconds between automatic attempts; a fifth failed attempt exhausts automatic eligibility. Authorized manual retry resets a failed or pending event, but rejects delivered events and active leases.
Delivery is at-least-once. Adapters must use the stable event ID for deduplication where supported. A crash after external delivery but before acknowledgement may otherwise cause a duplicate; the queue does not promise exactly-once delivery. Logs contain event IDs and fixed error codes, never raw adapter exceptions.
The default DeliveryPort refuses to dispatch. There is no scheduled worker or real message adapter enabled. Tests inject fake delivery behavior. When adding an adapter, implement bounded network timeouts below the lease duration, event-ID deduplication, recipient authorization and a supervised worker.
Summary returns scoped pending, expired and cancelled order counts, active orders without pricing, pending delivery counts and exhausted delivery counts. These are operational counts, not revenue reports. Event listing omits lease tokens and private message contents.

66
docs/phase1e-blueprint.md Normal file
View File

@ -0,0 +1,66 @@
# Phase 1E provider-independent blueprint
The user chose a blueprint and test cases before purchasing a gateway. No payment, shipping or messaging provider is selected or registered. Fake gateway code exists only under test/helpers; its HMAC format is illustrative and must never be treated as a production provider protocol.
## Implemented now
- Inactive-by-default, immutable pricing policy versions with explicit tax and shipping settings.
- Checkout snapshots of configured final totals, with database reconciliation and preserved historical prices.
- Transactional order-created/order-cancelled events, leased delivery, bounded retries and safe diagnostics.
- Authorized operational summary, event inspection and retry APIs.
- Gateway contracts and pure decision policies for capture validation, late capture compensation, refund bounds, partial shipping and returns.
- Fake-provider contract tests and database integration tests.
There are no production payment/capture/refund/webhook/shipment/return endpoints. No real payment or notification is sent. Order status remains the Phase 1D pending/cancelled model. Payment and fulfillment decisions are tested policies, not a persisted live payment lifecycle.
## Integration boundaries
```mermaid
flowchart LR
Checkout --> PricingSnapshot
Checkout --> Order
Order --> TransactionalEvents
TransactionalEvents --> LeasedDelivery
LeasedDelivery --> DeliveryAdapter["Future notification adapter"]
GatewayAdapter["Future gateway adapter"] --> VerifiedCapture
VerifiedCapture --> CapturePolicy
CapturePolicy --> StockCommit["Future transactional stock commit"]
CapturePolicy --> ReviewRefund["Late/mismatched capture review"]
```
GatewayPort defines idempotent payment creation, raw-byte capture verification and refund submission. Money crosses the provider boundary as canonical integer minor-unit strings with currency. Never accept amounts, successful payment flags or fulfillment authorization from the frontend. Normalize a provider's verified payload into CapturedPayment, then compare it with server-stored references and totals.
Capture decisions reject reference, amount and currency mismatches. Duplicate captured payments cause no additional stock commitment. A cancelled or expired order requires review and compensation, even if the provider says payment succeeded: its stock may already have been allocated elsewhere. Do not automatically refund from this pure decision function; a persisted, idempotent refund workflow must execute that decision.
Refund eligibility subtracts completed and pending refunds from captured money. Gateway acceptance means PENDING, not refunded. Only verified terminal provider state may mark a refund completed.
## Persistence required when a gateway is selected
Add new timestamped migrations for payment attempts, authenticated event receipts, refund requests and state transitions. Enforce unique provider/event IDs and compare payload digests when a repeated event ID arrives. Store an idempotency key before network work. Do not hold database locks while calling providers.
Process verified capture under order/stock locks in one transaction: recheck expiry, references and amount, commit each reserved quantity once, update payment/order state, preserve paid coupon usage, append ledger/audit records and enqueue notifications. Persist unmatched or late captures for reconciliation and compensation. Cancellation after capture must use refund policy, not the current pending-order cancellation route.
Refund requests must lock the payment while reserving refund capacity; pending amounts count against the refundable balance. Provider calls and callbacks need durable retry/reconciliation. Never interpret a timeout as proof that the external write failed.
Shipping persistence should include shipment lines and carrier events. Check paid eligibility and remaining quantities under locks before booking partial shipments. Track provider references and deduplicate events; out-of-order tracking must not regress delivery state. Do not conflate booking, dispatch and delivery.
Returns need request lines, delivered quantities, configurable time windows, approval and receipt/QC states. Pending returns reserve eligibility. Refund and restock are separate actions: a requested/approved return does not prove that inventory was physically received or saleable.
## Acceptance test matrix
| Area | Tests now | Required provider-stage tests |
| ---------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Payment creation | Same-key replay; changed input rejected by fake adapter | Timeout reconciliation and durable attempt recovery |
| Webhooks | Fake signature, raw-byte tampering, malformed and oversized payloads | Selected provider signatures, key rotation, actual event payloads and persisted replay protection |
| Capture | References, exact amount/currency, duplicate and late/cancelled cases | Atomic payment/stock/ledger commit under real PostgreSQL races |
| Refunds | Completed + pending bounds; fake retry idempotency | Partial refunds, concurrent limits, provider failure/reconciliation |
| Shipping | Paid eligibility, partial quantity bounds, monotonic tracking | Provider booking, replay, split shipment and tracking fixtures |
| Returns | Delivered/pending/returned bounds and explicit window | Approval, receipt/QC, separate restock/refund transactions |
| Pricing | Inclusive/exclusive arithmetic, rounding, region selection, immutable snapshots | Approved tax treatment, invoices, product tax classes and shipping services |
| Notifications | Atomic enqueue, leases, backoff, retries, redacted failures | Real adapter deduplication and delivery receipts |
The current tax calculator supports one configured merchandise rate per matching geographic policy, plus a shipping rate. Test values are synthetic and are not tax advice or the store's approved rates. Mixed tax classes, invoice requirements, provider shipping quotations and real business rules must be designed before production enablement.
## Before enabling providers
Select the gateway and shipping/messaging services, implement their official adapters, run their sandbox fixtures and persist the workflows above. Keep raw webhook bytes available only at the gateway boundary and redact credentials and personal information from logs. Use environment-managed secrets, native PostgreSQL concurrency checks and an independent VAPT of the deployed service. Purchasing a gateway is not needed to run the current tests.

View File

@ -8,7 +8,7 @@ Source: Mani Candles Commerce Platform specification and project pack created in
- 1B (implemented; native CI and deployment configuration pending): authentication, sessions, recovery, users, configurable RBAC, organization access, approval status, audit events. Test denied access and cross-organization access.
- 1C (implemented; native concurrency CI pending): catalog, variants, collections, addresses, inventory ledger and reservations. Test concurrent reservation and stock reconciliation.
- 1D (core implemented; tax/shipping rules, payment enablement and native CI pending): cart, checkout, orders, coupons and pricing snapshots. Test money precision, discount eligibility, retries and transaction rollback.
- 1E: verified payments/refunds, shipping/tracking, returns, notifications and operational dashboard. Test signatures, replay, partial fulfillment and reconciliation.
- 1E (blueprint and supporting pricing/outbox APIs implemented; live providers deferred at user request): verified payments/refunds, shipping/tracking, returns, notifications and operational dashboard. Test signatures, replay, partial fulfillment and reconciliation.
## Phase 2: Internal operations

View File

@ -12,3 +12,4 @@ Live SMTP and the recovery frontend remain pending. Recovery delivery is synchro
References: [OWASP ASVS](https://owasp.org/www-project-application-security-verification-standard/) and [Logging Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html). Conformance has not been independently assessed.
Phase 1D adds version checks, scoped idempotency, immutable order snapshots, deferred total reconciliation, order-held stock, coupon quotas and resource bounds. Native checkout race tests are included but await TEST_DATABASE_URL. Tax/shipping pricing and payment enablement remain blocked on business rules and Phase 1E.
The Phase 1E gateway is a test-only fake. Signature tests against it do not establish security for a real provider. Production payment/refund/webhook and fulfillment workflows require the provider-stage controls and tests in [the blueprint](phase1e-blueprint.md). The default notification adapter refuses delivery.

View File

@ -1,14 +1,16 @@
# Verification record — Phase 1D
# Verification record — Phase 1E blueprint
- 154 passing tests across 28 suites; five native PostgreSQL concurrency tests are skipped without TEST_DATABASE_URL (one suite is entirely native).
- Coverage: 99.54% statements, 99.65% lines, 87.83% branches and 100% functions for measured application code.
- 174 passing tests across 34 suites; six native PostgreSQL concurrency tests are skipped without TEST_DATABASE_URL (two suites are entirely native).
- Coverage: 99.60% statements, 99.70% lines, 88.49% branches and 100% functions for measured application code.
- Formatting, migration checksums, Prisma validation, strict TypeScript checks and production compilation pass.
- All eleven migrations execute through Prisma migrate deploy; status is up to date and schema diff reports no drift against the disposable embedded PostgreSQL engine. The prior seven migrations remain unchanged; four timestamped migrations were appended.
- New tests cover private/versioned carts, exact discount arithmetic, coupon eligibility, immutable order snapshots, deferred subtotal reconciliation, authorization, stock allocation, idempotent checkout/cancellation and rollback after final-write failure.
- All thirteen migrations execute through Prisma migrate deploy; status is up to date and schema diff reports no drift against the disposable embedded PostgreSQL engine. The prior eleven migrations remain unchanged; two timestamped migrations were appended.
- New tests cover fake-gateway idempotency and raw-byte signature checks, payment/refund decision policies, partial shipment/return bounds, exact inclusive/exclusive pricing, immutable pricing snapshots, atomic event creation, leased delivery, retries and safe logging.
- Production dependency audit reports no known vulnerabilities. No dependency versions changed in this phase.
Native PostgreSQL concurrency and remote Gitea CI remain pending. CI provisions PostgreSQL 17. Native tests cover recovery consumption, inventory competition, checkout replay, coupon competition and checkout versus standalone inventory reservations. Embedded tests do not establish multi-connection locking behavior.
The user requested a provider-independent blueprint before selecting or purchasing a gateway. Payment/refund/shipping/return contracts and decision policies are tested, but there are no live provider endpoints or persisted capture/refund/fulfillment workflows yet. Pricing policies, pricing snapshots, the outbox and operational read/retry APIs are runnable. Gateway and notification delivery remain disabled.
Tax/shipping rules have not been supplied. Orders expose unfinalized pricing, null payable totals and disabled payments. Full checkout-to-payment acceptance remains pending those rules and Phase 1E. No production deployment, real SMTP delivery or formal VAPT was performed. See [checkout contract](checkout-api.md) and [security assessment preparation](vapt-readiness.md).
Native PostgreSQL concurrency and remote Gitea CI remain pending. The new native test covers simultaneous outbox claims; prior concurrency tests remain available. Embedded tests do not establish multi-connection locking behavior.
Git author: mihir <motiyanimihir@gmail.com>. Branch: feat/checkout-orders, based on merged Phase 1C at 72cb947.
No store tax/shipping settings were invented or seeded. All configured rates in tests are synthetic. No real payment, notification, production deployment or formal VAPT was performed. See [blueprint and acceptance matrix](phase1e-blueprint.md) and [operations API](operations-api.md).
Git author: mihir <motiyanimihir@gmail.com>. Branch: feat/commerce-operations, based on merged Phase 1D at 068b0f9.