You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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"]
310
317
Rev["POST /products/{id}/reviews"]
311
318
AI["POST /assistant/chat (when AI on)"]
312
319
end
@@ -497,7 +504,7 @@ the variant's `price_cents` when set, otherwise the product's.
497
504
| GET |`/api/orders`| user | List user orders (paginated) |
498
505
| GET |`/api/orders/{id}`| user | Order detail with items (each item carries `variant_id`) |
499
506
| 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|
501
508
| 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 |
502
509
| PATCH |`/api/orders/{id}/tracking`| admin | Set tracking number (emits an `OrderShipped` event on the in-process bus) |
503
510
| GET |`/api/orders/tracking/{number}`| - | Public order tracking by number |
@@ -558,6 +565,10 @@ source.
558
565
`weight_grams` and `subtotal_cents` are optional. Malformed CEP → `400`; a well-formed CEP
559
566
outside every rule → `422`.
560
567
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
+
561
572
**Response:**
562
573
```json
563
574
{
@@ -590,20 +601,62 @@ state machine, so the frontend renders whatever the PSP needs without knowing wh
590
601
port and reuse `redirect`/`show_iframe` - only the adapter is new, not the frontend contract.
591
602
Settlement is confirmed asynchronously by the payment webhook, never by this response.
592
603
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
+
593
613
### Webhooks
594
614
595
615
| Method | Endpoint | Auth | Description |
596
616
|---|---|---|---|
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|
598
618
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.
600
620
601
621
### Search
602
622
603
623
| Method | Endpoint | Auth | Description |
604
624
|---|---|---|---|
605
625
| GET |`/api/search`| - | Faceted product search - `tsvector` full-text + category/price facets with counts, sort & pagination |
606
626
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
+
607
660
### AI Assistant (optional)
608
661
609
662
Shopping & support assistant powered by Claude, **off by default**. The route exists **only**
0 commit comments