← automation
sherpa-api#464 Done · In review ⏱ 3h 54m
Multiple payment providers per program (per-outlet routing)
⎇ auto/api-464-multiple-payment-providers-per-p
⤷ based on #456
Portal localhost:3009Embed localhost:3109API localhost:4009
🔍 iterative-review ✓ (main-pass) 🧪 test:api failed 214 passed · 7 failed · 0 flaky · 6 skipped · report 🧑‍🔬 QA ✅ PASS

Feedback

Pull requests

loading…

QA evaluation

✅ PASS

PASS. All four acceptance sub-criteria are genuinely delivered and were verified against the running stack, not from the implementer's screenshots. The portal Payment Routing screen (selector + live most-specific-wins resolution preview) and the per-outlet -> per-provider mapping (MediaMarkt/Stripe, Elkjop/Adyen, Power/Worldpay) work in the real UI with no console/network errors. I drove three outlet-attributed sales and opened each resulting checkout in a browser against the correct provider (real Stripe Checkout for Stripe; sandbox hosted pages for Adyen/Worldpay), and the portal 'Premium by Outlet' financials now show each outlet's revenue attributed to its provider - fixing both round-1 FAIL items. Caveats worth noting for stakeholders: outlet attribution on a sale is a server-to-server field (no portal/embed screen sets it; the embed funnel has no outlet awareness), and Adyen/Worldpay are sandbox connectors pending live PSP integration. The test:api suite is red, but the 7 failures are pre-existing/environmental (bulk-creation timeout cascade + a translations seed dependency), not regressions from #464.

4/4 acceptance criteria passed
  • Portal companion: per-outlet provider selector + live resolution previewDrove the real portal at http://localhost:3009/products/254/payment-routing (auth seeded by minting a fresh HS256 token for the org-22222222 super_admin and injecting auth-storage). Screen renders heading 'Payment Routing' with an Organization-default provider <select> and a 'Per-outlet routing' table; each outlet row has a provider-override dropdown and a live 'Resolves to ...' preview. Nav shows 'Payment Routing' under the OPERATE group. Zero console errors, zero 4xx/5xx responses. eval-1-payment-routing.png.
  • One program, three outlets each mapped to a different provider (most-specific-wins)On the same screen the three outlets resolve as MediaMarkt -> Stripe, Elkjop -> Adyen, Power -> Worldpay, each labelled 'Outlet override'; the organization default resolves to Stripe ('Organization default'). Confirmed independently via GET /v1/plus/payment-provider-assignments/resolve (source=outlet for each). eval-1-payment-routing.png.
  • Three sales each open a checkout against the correct providerDrove three real sales (POST /v1/policy/premium, one per outletId) on product 254 and opened each returned checkout URL in a headless browser: MediaMarkt -> real Stripe Checkout at checkout.stripe.com (cs_test_..., $33.43, Card/Klarna/Cash App); Elkjop -> Adyen-branded hosted checkout; Power -> Worldpay-branded hosted checkout. Each PolicyOrder.externalPaymentProvider matched the outlet's provider. Caveat: sales are API-initiated because no front-end attributes a sale to an outlet (sherpa-embed has zero outlet awareness; portal is CRM); Adyen/Worldpay are sandbox hosted-checkout connectors (live PSP deferred per the disclosed scope note; prod sandbox routes are env-gated). eval-2-checkout-stripe.png, eval-3-checkout-adyen.png, eval-4-checkout-worldpay.png.
  • Financials attribute each transaction to its provider + outletPortal /financials -> 'By Outlet' tab ('Premium by Outlet') renders each outlet with a colour-coded per-provider sub-row: Elkjop -> Adyen $100.29, MediaMarkt -> Stripe $66.86, Power -> Worldpay $66.86 (plus the unattributed historical Stripe bucket). Matches the API rollup GET /v1/plus/financials/revenue/by-outlet. The round-1 gap (providers[] dropped by the portal, tab empty) is fixed. eval-5-financials-by-outlet.png.
tests_ok: true · quality: Solid. Resolution cascade (outlet override -> organization default -> product default) wired into all sale/collection sites; PaymentProviderAssignments has RLS + partial unique indexes. Sandbox Adyen/Worldpay checkout+confirm routes are correctly env-gated (SHERPA_ENV !== 'production') so the no-auth activate path cannot bypass payment in prod; checkout HTML is escaped. Honest limitation: in production Adyen/Worldpay route+attribute but cannot actually collect (sandbox routes gated off until live connectors ship) - disclosed in the scope note. test:api is red (214 pass / 7 fail / 6 skip) but I read the actual failures: 02-bulk-policy-creation = 240s timeout (shared-Postgres contention across concurrent worktrees); 04-quote-policy-flow and 05-policy-orders-and-financials = cascades from the missing bulk policies (assertions receive 0); 06-product-translations = seed dependency (0 coverages). None carry a #464 signature, #464's own payment-routing spec passes, and I exercised #464's premium/resolution/financials code paths live without error - so no NEW failures are introduced by this ticket.