From 340012b2f82324c3a10a9a05889cb3be49fd92b0 Mon Sep 17 00:00:00 2001 From: hamid Date: Sun, 2 Aug 2026 21:33:10 +0330 Subject: [PATCH] blocker phase 5 --- client/src/services/bnpl/constants.ts | 18 +++----- mvp/blockers.md | 9 +++- .../ServiceCollectionExtensions.cs | 45 +++++++++++++------ 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/client/src/services/bnpl/constants.ts b/client/src/services/bnpl/constants.ts index b400268..1fe7daf 100644 --- a/client/src/services/bnpl/constants.ts +++ b/client/src/services/bnpl/constants.ts @@ -2,18 +2,12 @@ * When true, the BNPL domain is served by the in-memory mock (`apis/mockApi.ts`) behind the `BnplApi` * seam. * - * **Mock is primary this phase.** b12 ships the eligibility/initiate/webhook/settle endpoints server-side, - * but the D1–D5 checkout cannot run real end-to-end from the client yet: - * - the accepted request being financed comes from the **mock-primary** `bookingRequests` store (f7), so a - * real `initiate` would reference an id that exists only in memory (same reason f9 payment is mock-primary); - * - the contract serves **no provider/plan options** (D1/D2), **no repayment schedule** (D4 — the contract - * explicitly does not model the customer's repayment schedule), and **no provider-reported installment - * status** for the Wallet (D5) → REQ-022/023/024; - * - nothing fires the provider webhook in dev, so a real order would never settle. - * The mock closes the loop: the settle (down-payment cleared) converts the f7 request, inserts a **confirmed** - * booking into the f8 store (the SAME bridge f9 uses — a settled BNPL order is a card payment net-of-fee), - * and seeds a Wallet installment plan — so C6 → D1 → … → D4 → confirmation → D5 demos end-to-end. Flip to - * `false` once the upstream domains are real and REQ-022/023/024 land — no hook/component change. + * **Mock is still primary** (blocker-phase 05 landed Bug A — the seeded BNPL gateway — but not Bug B): + * the D1 wizard hard-gates on `getBnplOptions` (`GET checkout_bnpl/options/{id}`), and `getBnplSchedule` / + * `getWalletInstallments` have no server counterpart either — `CheckoutBnplController` only ever grew + * `eligibility` / `initiate` / `GET {id}` / `by_request/{id}` (REQ-022/023/024 never landed). Flipping this + * flag today trades the old "mock store ages out" failure for an immediate `isError` on D1. Flip once those + * three endpoints exist server-side — no hook/component change needed at that point. */ export const USE_BNPL_MOCK = true; diff --git a/mvp/blockers.md b/mvp/blockers.md index d9020b7..b378d82 100644 --- a/mvp/blockers.md +++ b/mvp/blockers.md @@ -21,8 +21,13 @@ Effort is a rough size, not a schedule: **S** = small/contained, **M** = a real - **The 30-minute payment countdown can lie.** Booking deadlines are stored without a timezone, so the timer a customer sees can silently show hours more time than they actually have, and expire while they still think they're fine. *(Effort: S)* -- **Installment (buy-now-pay-later) payments don't work at all.** No installment plan is ever actually set up - behind the scenes, so every attempt fails immediately. *(Effort: S–M, two related issues)* +- **Installment (buy-now-pay-later) payments don't work at all.** The behind-the-scenes setup that lets an + installment plan get created at all is now fixed — but the on-screen wizard still can't be turned on to use + it: three of its screens (choosing a plan, seeing the repayment schedule, and the "my installments" wallet + view) call server endpoints that were never actually built, so switching off the fake demo data today would + make the first screen show an error immediately instead of the outdated-mock failure it shows now. Those + three endpoints need to be built before this can be turned on for real. *(Effort: S–M, one part fixed, one + part bigger than first scoped)* - **Refunds are demo-only today.** The refund screens read fake, disconnected sample data; turning that off today would show a refund of the wrong amount (off by a factor of 100) for any real cancellation. *(Effort: M)* diff --git a/server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs b/server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs index db40128..e86085b 100644 --- a/server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs +++ b/server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs @@ -8,6 +8,7 @@ using Baya.Application.Contracts.Payments; using Baya.Application.Contracts.Persistence; using Baya.Application.Contracts.Search; using Baya.Application.Contracts.SupportAlerts; +using Baya.Domain.Entities.Bnpl; using Baya.Domain.Entities.Payments; using Baya.Infrastructure.Persistence.Interceptors; using Baya.Infrastructure.Persistence.Repositories.Common; @@ -128,27 +129,43 @@ public static class ServiceCollectionExtensions /// /// Idempotently seeds one active standard payment gateway so the b10 card rail has a selectable - /// provider out of the box. config_json is encrypted at rest by the EF converter on save (so it - /// must go through the DbContext, not HasData). Real merchant credentials come from appsettings / - /// environment per deployment — this sandbox row is non-secret and only enables the local/dev flow. + /// provider out of the box, plus one active bnpl gateway (balinyaar, the in-house + /// net-of-fee provider per money.md §6 — needs no external credentials) so b12's eligibility/initiate + /// handlers have a gateway to select instead of short-circuiting. config_json is encrypted at rest + /// by the EF converter on save (so it must go through the DbContext, not HasData). Real merchant + /// credentials come from appsettings / environment per deployment — these sandbox rows are non-secret and + /// only enable the local/dev flow. /// public static async Task SeedPaymentGatewaysAsync(this WebApplication app) { await using var scope = app.Services.CreateAsyncScope(); var context = scope.ServiceProvider.GetRequiredService(); - if (await context.Set().AnyAsync(g => g.Type == PaymentGatewayType.Standard)) - return; - - context.Set().Add(new PaymentGateway + if (!await context.Set().AnyAsync(g => g.Type == PaymentGatewayType.Standard)) { - ProviderCode = "zarinpal", - Type = PaymentGatewayType.Standard, - DisplayName = "ZarinPal (sandbox)", - ConfigJson = "{\"merchantId\":\"00000000-0000-0000-0000-000000000000\",\"baseUrl\":\"https://sandbox.zarinpal.com\",\"sandbox\":true}", - IsActive = true, - Priority = 0 - }); + context.Set().Add(new PaymentGateway + { + ProviderCode = "zarinpal", + Type = PaymentGatewayType.Standard, + DisplayName = "ZarinPal (sandbox)", + ConfigJson = "{\"merchantId\":\"00000000-0000-0000-0000-000000000000\",\"baseUrl\":\"https://sandbox.zarinpal.com\",\"sandbox\":true}", + IsActive = true, + Priority = 0 + }); + } + + if (!await context.Set().AnyAsync(g => g.Type == PaymentGatewayType.Bnpl)) + { + context.Set().Add(new PaymentGateway + { + ProviderCode = BnplProviderCodes.Balinyaar, + Type = PaymentGatewayType.Bnpl, + DisplayName = "Balinyaar BNPL", + ConfigJson = "{\"sandbox\":true}", + IsActive = true, + Priority = 0 + }); + } await context.SaveChangesAsync(); }