Backend:
- Surface a default retail price per code on the public partner-store
payload (config qr.default_retail_price_per_code_centavos, env
QR_DEFAULT_RETAIL_PRICE_PER_CODE_CENTAVOS, default ₱10 = 1000c).
Per-store overrides can come later — the LGU sets one official price
for now.
- Resident-facing flow uses the existing ManualPaymentDriver: resident
POSTs /me/payments/code-purchase, payment is created in pending,
admin marks it paid via /admin/payments/{uuid}/mark-paid, fulfillment
runs StoreOperations::sellToHousehold which activates the codes +
fires the CodesPurchased notification.
Frontend (customer-web/):
- /stores — list nearby partner stores (1/2/5/10/25 km radius pills),
shows in-stock pill + price + distance, links into purchase page
only when has_stock.
- /stores/[uuid] — store detail with stat row (price / stock /
distance) and PurchaseForm: quantity presets (5/10/20/50) + custom
input clamped to inventory, live total computation, blue "cash
payment" callout explaining the flow.
- /payments/[uuid] — three states:
- Pending: ticket-style card with reference + amount + 4-step
instructions; auto-polls every 8 s until paid.
- Paid: success card with checkmark + paid_at timestamp + link
to /codes.
- Failed/refunded: red card with retry CTA back to /stores.
- Resident nav adds "Buy codes" link.
- API client: PartnerStore now carries retail_price_per_code_centavos;
Payment + PaymentStatus types added; payments.show() returns the
typed status union.
Build: 21 routes, all type-check + lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- GET /partner-stores/nearby — public, residents browse active stores
ranked by distance via ST_Distance_Sphere; PublicPartnerStoreResource
excludes commission rate / owner / permit (only what a buyer needs).
- GET /partner-stores/{uuid} — public details, 404 if not active.
- GET /me/collections — paginated resident QR scan history with
optional from/to date filters.
- GET /me/upcoming-pickups — finds scheduled/in-progress trips whose
route includes the resident's assigned drop-off point. Returns
household_assigned: false when no household yet.
- GET /me/notifications — paginated database notifications inbox
with unread_only filter + unread_count in meta.
- POST /me/notifications/{id}/read, POST .../mark-all-read,
GET .../unread-count.
- config/cors.php — allow CUSTOMER_APP_URL and any EXTRA_CORS_ORIGINS
to call the API with credentials. Same-origin admin web is
unaffected.
202 feature tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>