blocker phase 5

This commit is contained in:
hamid
2026-08-02 21:33:10 +03:30
parent 08949a24de
commit 340012b2f8
3 changed files with 44 additions and 28 deletions
+6 -12
View File
@@ -2,18 +2,12 @@
* When true, the BNPL domain is served by the in-memory mock (`apis/mockApi.ts`) behind the `BnplApi` * When true, the BNPL domain is served by the in-memory mock (`apis/mockApi.ts`) behind the `BnplApi`
* seam. * seam.
* *
* **Mock is primary this phase.** b12 ships the eligibility/initiate/webhook/settle endpoints server-side, * **Mock is still primary** (blocker-phase 05 landed Bug A — the seeded BNPL gateway — but not Bug B):
* but the D1D5 checkout cannot run real end-to-end from the client yet: * the D1 wizard hard-gates on `getBnplOptions` (`GET checkout_bnpl/options/{id}`), and `getBnplSchedule` /
* - the accepted request being financed comes from the **mock-primary** `bookingRequests` store (f7), so a * `getWalletInstallments` have no server counterpart either — `CheckoutBnplController` only ever grew
* real `initiate` would reference an id that exists only in memory (same reason f9 payment is mock-primary); * `eligibility` / `initiate` / `GET {id}` / `by_request/{id}` (REQ-022/023/024 never landed). Flipping this
* - the contract serves **no provider/plan options** (D1/D2), **no repayment schedule** (D4 — the contract * flag today trades the old "mock store ages out" failure for an immediate `isError` on D1. Flip once those
* explicitly does not model the customer's repayment schedule), and **no provider-reported installment * three endpoints exist server-side — no hook/component change needed at that point.
* 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.
*/ */
export const USE_BNPL_MOCK = true; export const USE_BNPL_MOCK = true;
+7 -2
View File
@@ -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 - **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 a customer sees can silently show hours more time than they actually have, and expire while they still
think they're fine. *(Effort: S)* 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 - **Installment (buy-now-pay-later) payments don't work at all.** The behind-the-scenes setup that lets an
behind the scenes, so every attempt fails immediately. *(Effort: SM, two related issues)* 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: SM, 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 - **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. today would show a refund of the wrong amount (off by a factor of 100) for any real cancellation.
*(Effort: M)* *(Effort: M)*
@@ -8,6 +8,7 @@ using Baya.Application.Contracts.Payments;
using Baya.Application.Contracts.Persistence; using Baya.Application.Contracts.Persistence;
using Baya.Application.Contracts.Search; using Baya.Application.Contracts.Search;
using Baya.Application.Contracts.SupportAlerts; using Baya.Application.Contracts.SupportAlerts;
using Baya.Domain.Entities.Bnpl;
using Baya.Domain.Entities.Payments; using Baya.Domain.Entities.Payments;
using Baya.Infrastructure.Persistence.Interceptors; using Baya.Infrastructure.Persistence.Interceptors;
using Baya.Infrastructure.Persistence.Repositories.Common; using Baya.Infrastructure.Persistence.Repositories.Common;
@@ -128,27 +129,43 @@ public static class ServiceCollectionExtensions
/// <summary> /// <summary>
/// Idempotently seeds one active <c>standard</c> payment gateway so the b10 card rail has a selectable /// Idempotently seeds one active <c>standard</c> payment gateway so the b10 card rail has a selectable
/// provider out of the box. <c>config_json</c> is encrypted at rest by the EF converter on save (so it /// provider out of the box, plus one active <c>bnpl</c> gateway (<c>balinyaar</c>, the in-house
/// must go through the DbContext, not <c>HasData</c>). Real merchant credentials come from appsettings / /// net-of-fee provider per money.md §6 — needs no external credentials) so b12's eligibility/initiate
/// environment per deployment — this sandbox row is non-secret and only enables the local/dev flow. /// handlers have a gateway to select instead of short-circuiting. <c>config_json</c> is encrypted at rest
/// by the EF converter on save (so it must go through the DbContext, not <c>HasData</c>). Real merchant
/// credentials come from appsettings / environment per deployment — these sandbox rows are non-secret and
/// only enable the local/dev flow.
/// </summary> /// </summary>
public static async Task SeedPaymentGatewaysAsync(this WebApplication app) public static async Task SeedPaymentGatewaysAsync(this WebApplication app)
{ {
await using var scope = app.Services.CreateAsyncScope(); await using var scope = app.Services.CreateAsyncScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
if (await context.Set<PaymentGateway>().AnyAsync(g => g.Type == PaymentGatewayType.Standard)) if (!await context.Set<PaymentGateway>().AnyAsync(g => g.Type == PaymentGatewayType.Standard))
return;
context.Set<PaymentGateway>().Add(new PaymentGateway
{ {
ProviderCode = "zarinpal", context.Set<PaymentGateway>().Add(new PaymentGateway
Type = PaymentGatewayType.Standard, {
DisplayName = "ZarinPal (sandbox)", ProviderCode = "zarinpal",
ConfigJson = "{\"merchantId\":\"00000000-0000-0000-0000-000000000000\",\"baseUrl\":\"https://sandbox.zarinpal.com\",\"sandbox\":true}", Type = PaymentGatewayType.Standard,
IsActive = true, DisplayName = "ZarinPal (sandbox)",
Priority = 0 ConfigJson = "{\"merchantId\":\"00000000-0000-0000-0000-000000000000\",\"baseUrl\":\"https://sandbox.zarinpal.com\",\"sandbox\":true}",
}); IsActive = true,
Priority = 0
});
}
if (!await context.Set<PaymentGateway>().AnyAsync(g => g.Type == PaymentGatewayType.Bnpl))
{
context.Set<PaymentGateway>().Add(new PaymentGateway
{
ProviderCode = BnplProviderCodes.Balinyaar,
Type = PaymentGatewayType.Bnpl,
DisplayName = "Balinyaar BNPL",
ConfigJson = "{\"sandbox\":true}",
IsActive = true,
Priority = 0
});
}
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }