Skip to content

Commit 773863e

Browse files
docs(readme): document tracking, exchanges, newsletter, manual PIX and free shipping
Update the endpoint map, feature box, component tree and API reference: public order tracking, exchange requests, newsletter, the manual vs propay payment providers (with MANUAL_PIX_KEY), the free-shipping threshold and the empty-list fix.
1 parent b48b14b commit 773863e

1 file changed

Lines changed: 62 additions & 8 deletions

File tree

readme.md

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,16 @@
5757
│ Orders (ACID) - reservation + charge + events in one tx │
5858
│ Idempotency Keys - dedupe order create, never double-reserve │
5959
│ Refunds - full/partial reversal + opt-in restock │
60-
│ PIX Payment - ProPay/OpenPix, provider-neutral FlowStatus
60+
│ PIX Payment - manual (dev) or ProPay (prod), FlowStatus port
6161
│ Coupons / Discounts- percent or fixed, VoucherHandler port │
6262
│ Product Reviews - ratings 1-5 + aggregate, moderation │
6363
│ Optimistic Locking - product version guard, 409 on stale write │
6464
│ Rate Limiting - per-IP token bucket on auth + checkout │
6565
│ Media / Storage - URL or presigned S3/R2/MinIO upload │
6666
│ Shipping Calc - table rates by BR region (pluggable port) │
6767
│ Order Tracking - public by number + admin tracking update │
68+
│ Exchanges - user-scoped return/exchange requests │
69+
│ Newsletter - email subscribe, idempotent │
6870
│ AI Assistant (opt) - read-only JWT-scoped tools, off by default │
6971
│ Soft Delete - products + orders, hidden from public reads │
7072
│ CPF Validation - stored on profile, required at checkout │
@@ -215,11 +217,14 @@ internal/
215217
├── coupons/ # coupon codes (percent/fixed) behind the VoucherHandler port
216218
├── sourcing/ # multi-warehouse: sources + variant_stock per location + Allocator (single-source default)
217219
├── categories/ users/ addresses/ # addresses: independent default billing ≠ shipping
220+
├── exchanges/ # customer exchange/return requests - user-scoped, reason category + preferred variant
221+
├── newsletter/ # email subscriptions - idempotent (ON CONFLICT DO NOTHING)
218222
219223
│ # - Ports & provider adapters (fork swaps by config) -
220224
├── payment/ # PaymentProvider port + Registry + FlowStatus/Action state machine
221-
│ └- propay/ # ProPay/OpenPix adapter (JWT service token + HMAC webhook)
222-
├── shipping/ # ShippingProvider port + TableProvider (Brazil regions)
225+
│ ├- propay/ # ProPay/OpenPix adapter (JWT service token + HMAC webhook)
226+
│ └- manual/ # dev/E2E PIX stand-in - in-memory charges, signature-less webhook, no external creds
227+
├── shipping/ # ShippingProvider port + TableProvider (Brazil regions) + free-shipping threshold
223228
├── storage/ # object-storage port (S3/R2/MinIO) - presigned upload, gated
224229
├── promotions/ # VoucherHandler port + AppliedDiscount value object
225230
├── search/ # Filter port + facets (tsvector + GROUP BY counts)
@@ -299,6 +304,7 @@ graph LR
299304
Cat["GET /categories\nGET /categories/{id}"]
300305
Ship["POST /shipping/calculate\nGET /shipping/cep/{cep}"]
301306
Track["GET /orders/tracking/{number}\nGET /search"]
307+
News["POST /newsletter"]
302308
WH["POST /webhooks/payment"]
303309
end
304310
@@ -307,6 +313,7 @@ graph LR
307313
Addr["GET|POST|PUT|DELETE /users/{id}/addresses\nPATCH …/default[/billing|/shipping]"]
308314
Cart["GET /cart\nPOST /cart/items\nPUT|DELETE /cart/items/{variantId}\nPOST /cart/coupon · DELETE /cart/coupon/{code}\nDELETE /cart"]
309315
Orders["POST /orders (Idempotency-Key)\nGET /orders · GET /orders/{id}\nPATCH /orders/{id}/cancel\nPOST /orders/{id}/pay\n(rate-limited 30/min)"]
316+
Exch["POST /exchanges\nGET /exchanges"]
310317
Rev["POST /products/{id}/reviews"]
311318
AI["POST /assistant/chat (when AI on)"]
312319
end
@@ -497,7 +504,7 @@ the variant's `price_cents` when set, otherwise the product's.
497504
| GET | `/api/orders` | user | List user orders (paginated) |
498505
| GET | `/api/orders/{id}` | user | Order detail with items (each item carries `variant_id`) |
499506
| PATCH | `/api/orders/{id}/cancel` | user | Cancel order - releases the reservation for not-yet-paid orders |
500-
| POST | `/api/orders/{id}/pay` | user | Initiate payment (PIX via ProPay/OpenPix) |
507+
| POST | `/api/orders/{id}/pay` | user | Initiate payment via the configured provider (PIX). `manual` for dev/E2E, `propay` (ProPay/OpenPix) in production - see §Payment |
501508
| POST | `/api/orders/{id}/refund` | admin | Refund (full or partial) - reverses the charge via the PSP and **opt-in per-item restock**; `501` if the provider can't refund |
502509
| PATCH | `/api/orders/{id}/tracking` | admin | Set tracking number (emits an `OrderShipped` event on the in-process bus) |
503510
| GET | `/api/orders/tracking/{number}` | - | Public order tracking by number |
@@ -558,6 +565,10 @@ source.
558565
`weight_grams` and `subtotal_cents` are optional. Malformed CEP → `400`; a well-formed CEP
559566
outside every rule → `422`.
560567

568+
**Free shipping.** When `subtotal_cents >= 19900` (R$ 199,00) the quote returns
569+
`cost_cents: 0` with `method: "frete-gratis"`, so the threshold advertised on the storefront
570+
is enforced by the domain, not just claimed in the UI.
571+
561572
**Response:**
562573
```json
563574
{
@@ -590,20 +601,62 @@ state machine, so the frontend renders whatever the PSP needs without knowing wh
590601
port and reuse `redirect`/`show_iframe` - only the adapter is new, not the frontend contract.
591602
Settlement is confirmed asynchronously by the payment webhook, never by this response.
592603

604+
**Providers.** The active provider is selected by `PAYMENT_PROVIDER`:
605+
606+
| Value | Adapter | Use |
607+
|---|---|---|
608+
| `manual` | `internal/payment/manual` | Dev/E2E stand-in. Generates a valid copy-and-paste PIX BR Code from `MANUAL_PIX_KEY` and holds charges in memory. Its webhook is signature-less: `POST /api/webhooks/payment` with `{"reference_id":"<order-id>"}` marks the order paid, so the whole checkout runs end-to-end with no external credentials. |
609+
| `propay` | `internal/payment/propay` | Production. Talks to ProPay (Ruby BFF over OpenPix/Efi), which needs real PSP credentials. HMAC-signed webhooks (`X-Propay-Signature`). |
610+
611+
Both implement the same `payment.Provider` port, so switching is a config change, not a code change. The `manual` provider is intended for local development and automated tests only; never run it in production.
612+
593613
### Webhooks
594614

595615
| Method | Endpoint | Auth | Description |
596616
|---|---|---|---|
597-
| POST | `/api/webhooks/payment` | HMAC (`X-Propay-Signature`) | Payment confirmation from the PSP (ProPay/OpenPix) |
617+
| POST | `/api/webhooks/payment` | provider-verified | Payment confirmation. Signature scheme depends on the active provider: HMAC (`X-Propay-Signature`) for `propay`; signature-less `{"reference_id":"<order-id>"}` for the dev `manual` provider |
598618

599-
The webhook performs an atomic `UPDATE orders SET payment_status='paid', status='processing' WHERE id=$1 AND payment_status='pending_payment'` and Claims stock - idempotent by design.
619+
The webhook performs an atomic `UPDATE orders SET payment_status='paid', status='processing' WHERE id=$1 AND payment_status='pending_payment'` and Claims stock - idempotent by design. The active provider verifies the payload (`VerifyWebhook`) and resolves it to an order via `ReferenceID` before the update runs.
600620

601621
### Search
602622

603623
| Method | Endpoint | Auth | Description |
604624
|---|---|---|---|
605625
| GET | `/api/search` | - | Faceted product search - `tsvector` full-text + category/price facets with counts, sort & pagination |
606626

627+
### Exchanges
628+
629+
Customer-initiated exchange/return requests against a delivered order. Requests are scoped to
630+
the authenticated user, so a user only ever sees and creates their own.
631+
632+
| Method | Endpoint | Auth | Description |
633+
|---|---|---|---|
634+
| POST | `/api/exchanges` | user | Open an exchange request against one of the user's orders |
635+
| GET | `/api/exchanges` | user | List the user's exchange requests |
636+
637+
**Request** (`POST /api/exchanges`):
638+
```json
639+
{
640+
"order_id": "<uuid>",
641+
"product_id": "<uuid>",
642+
"reason_category": "size",
643+
"description": "Ficou apertada, queria um numero acima",
644+
"preferred_size": "G",
645+
"preferred_color": "Preto"
646+
}
647+
```
648+
`product_id`, `preferred_size` and `preferred_color` are optional. `reason_category` must be one
649+
of `defect`, `size`, `color`, `different`, `damaged`, `other` (mirrors the DB CHECK); anything
650+
else → `400`. New requests are created with status `pending`.
651+
652+
### Newsletter
653+
654+
| Method | Endpoint | Auth | Description |
655+
|---|---|---|---|
656+
| POST | `/api/newsletter` | - | Subscribe an email to the newsletter - body `{email}`; idempotent (re-subscribing is a no-op) |
657+
658+
Invalid email → `400`. On success returns `201` `{"status":"ok"}`.
659+
607660
### AI Assistant (optional)
608661

609662
Shopping & support assistant powered by Claude, **off by default**. The route exists **only**
@@ -644,8 +697,9 @@ PORT=4444
644697
ALLOWED_ORIGINS=https://yourstore.com,http://localhost:8880
645698
LOG_LEVEL=info # debug | info | warn | error
646699

647-
# - Payment - PIX (ProPay) --------------------------
648-
PAYMENT_PROVIDER=propay # selects the provider registered in the payment registry
700+
# - Payment - PIX --------------------------
701+
PAYMENT_PROVIDER=propay # `manual` (dev/E2E) or `propay` (production); selects the active provider
702+
MANUAL_PIX_KEY= # PIX key the `manual` dev provider embeds in the generated BR Code
649703
PROPAY_URL=https://propay.internal # when empty, the propay provider is not registered
650704
GO_TO_PROPAY_SECRET=secret-for-jwt-go-to-propay
651705
PROPAY_TO_GO_SECRET=secret-for-hmac-propay-to-go

0 commit comments

Comments
 (0)