Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 47 additions & 51 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Statuses (ENUM): `pending`, `payment_uploaded`, `confirmed`, `preparing`, `ready

- GCash / bank payment APIs (no third-party money movement).
- Lalamove / Grab delivery APIs (no live tracking, no dispatch integration).
- ~~Card payment processing (UI tile only, "Coming Soon").~~ — superseded by §8b (PayMongo, opt-in, guarded).
- Card payment processing (UI tile only, "Coming Soon").
- Push notifications (stretch; not promised).
- Multi-tenant / multi-seller (single owner).

Expand All @@ -70,7 +70,9 @@ Statuses (ENUM): `pending`, `payment_uploaded`, `confirmed`, `preparing`, `ready
> Phase-1 prohibitions in §7 / AGENTS.md §5 *only* for the items below, and only
> as described. Everything else in §7 stays prohibited. The seller still books
> couriers manually and pastes the booking link — no courier dispatch API is
> introduced, so the "no Lalamove/Grab API" rule is preserved.
> introduced, so the "no Lalamove/Grab API" rule is preserved. Payment remains
> buyer receipt upload plus seller verification; no payment processor API is
> introduced.

### 8a. Shipping tier system (centerpiece)
- Seller ships from Cavite (Molino, Bacoor — see `settings.pickup_address`).
Expand All @@ -81,10 +83,8 @@ Statuses (ENUM): `pending`, `payment_uploaded`, `confirmed`, `preparing`, `ready
fee charged at the **upper end** of that courier's observed price range (to
absorb surges). No courier API is called. Research + sources live in
`docs/planning/shipping-research.md`; at least 15 destinations are seeded.
- When `GOOGLE_MAPS_API_KEY` is present (§8c), the fee is derived from road
distance via a distance-band heuristic (0–10 / 10–25 / 25–50 / 50+ km). When
the key is absent, the buyer picks a city from a dropdown and the tier table
supplies the fee directly. Both paths land in the same `shipping_fee` column.
- Buyer picks a destination city from the tier table. The tier row supplies the
fee directly and no map, geocoding, distance, or courier API is required.
- `orders.shipping_fee` (DECIMAL(10,2), default 0) + `orders.courier_link`
(VARCHAR(255), nullable) are added. `total_amount = subtotal + shipping_fee`,
computed inside the existing `DB::transaction` with `lockForUpdate`
Expand All @@ -94,57 +94,53 @@ Statuses (ENUM): `pending`, `payment_uploaded`, `confirmed`, `preparing`, `ready
`courier_note`. `courier_note` keeps its meaning (free-text rider/booking
notes the customer sees); `courier_link` holds the tracking/booking URL the
seller pastes post-payment. Splitting them keeps the link renderable as a
clickable card in the tracker and lets the confirmation email (§8d) fire on
clickable card in the tracker and lets the confirmation email (§8e) fire on
link-paste specifically, not on every note edit.
- On payment confirmation the order enters `preparing` and the success screen
shows: "We're preparing your order. Your Lalamove/Grab tracking link will
appear here shortly once the seller books the courier."
- Seller admin UI: for each `out_for_delivery` (or `confirmed`/`preparing`
delivery) order, a field to paste the courier booking/tracking link. Pasting
it surfaces the link to the customer's order tracker **and** triggers the
confirmation email (§8d).

### 8b. PayMongo (opt-in, guarded)
- **Reasoning first (weakest point):** a real PayMongo flow needs live secret
keys the project does not have; without them the card tile is
"temporarily unavailable" — the same end state as the old "Coming Soon" tile
for a live demo. So PayMongo adds architecture/defensibility value (a real
PHP→third-party JSON integration, a mocked test, a webhook signature check)
but little *demo* value over the Phase-1 tile. We implement it anyway, **fully
guarded**, because the graded primitives it surfaces (server-side JSON API
calls, signature validation, a testable singleton) are worth more than the
tile's demo state — and the task explicitly asks for it with a graceful
fallback.
- **Architecture decision:** call the PayMongo REST API via Laravel's `Http`
facade through a `PayMongoService` bound as a singleton in
`AppServiceProvider` (same test seam as Cloudinary) — **not** the PayMongo
PHP SDK. Reason: raw HTTP keeps the JSON request/response handling visible in
source (rubric V), avoids a fragile composer dependency that can't be
exercised without real keys, and mirrors how the graded PHP integration is
expected to look. The singleton is swapped with a mock in tests.
- Flow: server creates a Checkout Session, returns the redirect URL; the SPA
redirects the buyer. On return, a webhook endpoint validates the
`Paymongo-Signature` header and marks the order `payment_uploaded`/`confirmed`.
**Card data never touches the server** — PayMongo hosts the card form. If any
approach required storing card numbers it would be rejected.
- `PAYMONGO_SECRET_KEY=` / `PAYMONGO_PUBLIC_KEY=` added to `.env.example`
(placeholders). Left blank in `.env`; the service short-circuits to a
"temporarily unavailable" response and the card tile renders that state. COD /
GCash / Bank verification workflows are preserved as fallback so the rubric's
verification flow still exists.

### 8c. Maps API
- Google Maps JavaScript API + Geocoding + Distance Matrix in `apps/web`.
- Server-side: a thin `POST /api/distance` (auth) endpoint proxies a Distance
Matrix call using a server-stored `GOOGLE_MAPS_API_KEY` (never exposed to the
client). Rate-limited by auth.
- Address autocomplete (Places Autocomplete, restricted to Philippines) on the
checkout address field, **only when the key is present**.
- `GOOGLE_MAPS_API_KEY=` added to `.env.example` (placeholder). **Feature flag:**
when the key is empty, the shipping tier falls back to the manual city
dropdown (§8a tier table) so the app stays fully functional for grading.

### 8d. Transactional email
confirmation email (§8e).

### 8b. OCR-assisted receipt verification
- **Reasoning first (weakest point):** OCR can misread noisy GCash and bank
screenshots, so it must not approve or reject payments automatically. It is a
seller aid only: the buyer still uploads a receipt screenshot, the backend
stores raw extracted text when available, and the seller manually verifies the
receipt against the expected total.
- `orders.proof_ocr_text` (TEXT, nullable) stores raw OCR output for uploaded
GCash/bank receipts. No amount/reference parsing is attempted because wallet
and bank screenshot layouts vary. Three-mirror sync: `db/schema.sql`,
migration, `packages/shared/src/index.ts`, `apps/web/src/types.ts`.
Comment on lines +113 to +116
- OCR strategy is best-effort and non-blocking: first use Cloudinary OCR output
when the upload response provides it, then optionally fall back to local
Tesseract when `OCR_TECHNIQUE=tesseract` and `TESSERACT_BIN` is configured.
If neither path works, `proof_ocr_text` stays `null` and the admin UI shows
"OCR not available — verify manually."
- Admin payment verification shows the receipt screenshot, expected total, and
OCR text side-by-side. The seller still chooses **Verify** or **Reject**.

### 8c. Multiple QR/account payment methods
- The existing `payment_methods` table remains the model for multiple GCash
numbers and multiple bank accounts: one row per account/QR. This avoids a new
schema and keeps the buyer flow simple.
- Admin Settings manages all rows, active and inactive: type, label, account
name, account number, QR image URL, active toggle, and sort order. QR image
upload reuses the Cloudinary data-URL pattern under a `payment-methods`
folder.
- Checkout lists only active methods and shows the selected QR/account details
before receipt upload. Card remains disabled as "Coming Soon."

### 8d. Maps API (deprioritized, optional)
- Maps is not required for the graded flow because shipping tiers already solve
delivery fee calculation without API keys or network-dependent demos.
- If all required work is complete and time remains, a future enhancement may
add address autocomplete or distance estimates behind a feature flag. It must
not replace the tier-table fallback or introduce courier dispatch.
Comment on lines +139 to +141

### 8e. Transactional email
- Laravel mail configured via `MAIL_*` in `.env.example` (default `log` driver
for dev — writes the rendered HTML to `storage/logs/laravel.log`).
- `App\Mail\OrderConfirmationMail` Mailable, inline-CSS HTML template styled
Expand All @@ -156,7 +152,7 @@ Statuses (ENUM): `pending`, `payment_uploaded`, `confirmed`, `preparing`, `ready
- Feature test asserts the mailable renders and is sent on the
status-transition event via `Mail::fake()`.

### 8e. Post-payment success screen
### 8f. Post-payment success screen
- Replaces the post-checkout redirect with a dedicated `/orders/:id/success`
route showing the "preparing your order, delivery link will appear here
shortly" state. Same screen for same-day and pre-orders. The existing
Expand Down