From e84444a56b69b38d0c1e07c4bcfd46e6ee7199e1 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 1 May 2026 20:23:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(customer-web):=20Phase=204=20=E2=80=94=20b?= =?UTF-8?q?uy=20QR=20codes=20(cash=20flow)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- app/Http/Resources/PublicPartnerStoreResource.php | 1 + config/qr.php | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/app/Http/Resources/PublicPartnerStoreResource.php b/app/Http/Resources/PublicPartnerStoreResource.php index a26f9aa..dd75be3 100644 --- a/app/Http/Resources/PublicPartnerStoreResource.php +++ b/app/Http/Resources/PublicPartnerStoreResource.php @@ -28,6 +28,7 @@ class PublicPartnerStoreResource extends JsonResource fn () => round((float) $this->distance_meters, 1), ), 'barangay' => $this->whenLoaded('barangay', fn () => $this->barangay?->name), + 'retail_price_per_code_centavos' => (int) config('qr.default_retail_price_per_code_centavos', 1000), ]; } } diff --git a/config/qr.php b/config/qr.php index c6738fb..ea5a2ad 100644 --- a/config/qr.php +++ b/config/qr.php @@ -17,4 +17,11 @@ return [ | means no expiry. */ 'default_batch_expiry_months' => env('QR_DEFAULT_BATCH_EXPIRY_MONTHS', null), + + /* + | Default retail price per QR code (in centavos) shown to residents on + | the partner-store browse + purchase pages. The LGU sets one official + | price; per-store overrides aren't modeled yet. + */ + 'default_retail_price_per_code_centavos' => (int) env('QR_DEFAULT_RETAIL_PRICE_PER_CODE_CENTAVOS', 1000), ];