Skip to content

[WR-19269] First draft of moving payment stuff into the Wayroo.Payments with an … - #25

Merged
JoshB-KC merged 3 commits into
mainfrom
feature/WR-19269_Update_Routing
Sep 1, 2026
Merged

JoshB-KC merged 3 commits into
mainfrom
feature/WR-19269_Update_Routing

Conversation

@JoshB-KC

@JoshB-KC JoshB-KC commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Moves the ProPay account reads into Wayroo.Payments behind a provider-neutral contract, and lands the
producer half of WR-19269 (D5)StoreProviderConfigChanged on the interprocess bus, which
Luci.Orders will build its provider-routing read model from.

Important

Not ready to merge. Needs local validation and two Ops changes — see
Before this merges. The Ops grants have to land first, or the deploy fails.

What's in it

New API surface — replaces StorePropayController.GetPropayAccountBalanceAsync in Luci.Orders:

GET /api/payments/v1.0/tenants/{tenantId}/stores/{storeId}/account/balance live balance + current standing
POST .../account/refresh re-reads account info from the provider and records it — the per-store unit of work a backfill drives

Provider-neutral: MoneyAmount in major units, a six-value PaymentAccountStatus, and
CanProcessPayments / CanReceivePayouts rather than ProPay's vocabulary. Callers never name the
provider
— the service resolves it, so a store moving between providers changes nothing at the call
site. ?providerId= survives as a support override.

Wayroo.Payments.BusinessLogic — the tier the other Wayroo micros already have and this one didn't.
Owns provider selection (IPaymentAccountManager, IPaymentGatewayRegistry) and the
Gateways/{Provider}/ implementations, so a worker or the recorder lambda can reuse them rather than
re-deriving the choice. Adding Adyen is a new folder plus one registration line — no controller
changes
.

WR-19269 producerStoreProviderConfigChanged (payments.StoreProviderConfigChanged.v1)
published by the ConfigurationRecorder when a store's routing actually changes. Routing lives on a
per-store #routing item carrying AcquiringProviderId, MigrationState and a server-incremented
ConfigurationVersion, which travels as the envelope's Sequence so consumers can discard stale
updates. Seeded at ProPay on first sight of a store, so the deploy is inert — every store resolves
exactly as it does today, matching "absent row = ProPay" on the Orders side.

No dependency on Luci.Orders. The tier reaches its payment providers, its own table and the event
buses — nothing else. A store whose account reference this service has never been told is reported as
having no account; a backfill supplies it on the refresh call.

Things worth a reviewer's attention

  • Every writer uses UpdateItem, never PutItem. The credential recorder and the account refresh
    share a record and each names only the attributes it owns; a whole-item put by either would silently
    erase the other's. Held in place by integration tests against DynamoDB Local. Routing is a third item
    in the same partition — anything listing a store's providers must skip the #routing sort key.
  • ProPay reports balances in int cents. Every amount crossing the gateway is divided by 100.
    There's a test on it; a missed conversion is a silent hundredfold error.
  • PropayAccountStatusMap collapses ProPay's 24 statuses onto the six neutral ones. That mapping is
    business judgement, not translation — it wants a product sign-off. Unknown values fail closed to
    Suspended.
  • Two MigrationStates constant sets (event package + models) exist so Wayroo.Payments.Models
    stays free of package references. MigrationStateParityTests fails the build if they drift, because
    drift would mean persisting one spelling and publishing another.
  • ProPay config is two layers: PropayApiBaseUrisOptions are environment-wide defaults from
    container env vars (new pipeline variables → CFN parameters); Parameter Store holds the per-tenant
    credentials, and a tenant's optional base-URL override there wins for that tenant alone.

Before this merges

1. Ops — IAM (blocking; both roles are imported Mutable = false, so CDK cannot grant these)

API task role — {env}-payments-service-role

  • dynamodb:UpdateItem on {env}-PaymentConfiguration (new — the writers no longer use PutItem)
  • ssm:GetParametersByPath on /luci/{env}/vendors/propay/*, plus kms:Decrypt on the key those
    SecureString parameters use (new — the API fails at startup without this, the source is required)
  • Existing and still needed: dynamodb:GetItem/Query, cloudwatch:PutMetricData,
    xray:PutTraceSegments/PutTelemetryRecords

Recorder lambda role — {env}/WorkerRole

  • dynamodb:UpdateItem on the table (new — previously documented as PutItem)
  • events:PutEvents scoped to the {env}-wayroo-events bus ARN — tracked as DEV-3868, along with
    that bus's archive and event-bus logging. Now load-bearing: without it every routing announcement
    fails and the message dead-letters.

2. Local validation still outstanding

Verified so far: the API boots, routes resolve, provider selection works, tenant credentials load from
Parameter Store under a developer's SSO identity, and a ProPay XML call reaches the provider and comes
back (checked with a deliberately invalid account number, so nothing was written).

Not yet done:

  • A successful refresh for a real dev store/tenant, and confirm the DynamoDB item gains
    ProviderAccountDetails / AccountStatus without losing ProviderConfiguration
  • Balance cross-check — compare availableBalance/pendingBalance against Orders'
    GET /tenants/{t}/stores/{s}/propay/account-balance for the same store. They must match to the
    penny; this is what catches a cents/dollars regression
  • The publish path against real EventBridge — currently only proven against a local stub, and
    blocked on the events:PutEvents grant above

3. Sign-off

  • PropayAccountStatusMap — product to confirm the ProPay→neutral groupings

Not in this PR

  • The Luci.Orders half of WR-19269StorePaymentProviders, EventBridge→SQS→Lambda consumer
    (dedupe on EventId, native envelope parsing — not EventQueueDeserializer), Redis-cached
    IStoreProviderResolver failing static to ProPay, backfill job. Separate branch; the ticket sequences
    the publisher first. Blocks WR-19918 (D12).
    • Open question for that work: whether its backfill seeds from Orders' own data, or expects Payments
      to emit an event per existing store. This producer only fires on an inbound credential webhook, so
      it cannot seed a read model on its own.
  • Migrating the six existing callers off GetPropayAccountBalanceAsync. Watch
    Luci.Integrations.Api/Services/PaymentManager.cs:115, which casts the status unconditionally — the
    neutral Status is non-null whenever accountExists, which this preserves.
  • The cutover tool that writes ActiveProvider/routing, and the backfill driver itself. Note
    nothing rate-limits ProPay today, so a driver needs its own concurrency cap.
  • ConfigurationRecorder.Lambda still calls Luci.Orders (PropayStoreOwnerResolver) to map an account
    to a store — pre-existing, breaks the no-sibling-service rule, wants its own ticket.

Tests

All eight test projects pass, including two Docker-backed integration suites (DynamoDB Local, plus
WireMock for the Orders stub and a PutEvents stub). dotnet restore --locked-mode — the CI gate —
is clean, and cdk synth produces the expected task definition and lambda environment.

Worth knowing which gaps the new tests close: the merge semantics between the three writers, the
cents→dollars conversion, provider resolution in every branch, the config-sync contract's wire shape,
and ProPay credential binding from the flattened Parameter Store shape — the last of which was a
live defect that no mocked test could have caught.

@JoshB-KC JoshB-KC changed the title First draft of moving payment stuff into the Wayroo.Payments with an … [WR-19269] First draft of moving payment stuff into the Wayroo.Payments with an … Aug 30, 2026
@JoshB-KC
JoshB-KC merged commit 0b29b39 into main Sep 1, 2026
8 checks passed
@JoshB-KC
JoshB-KC deleted the feature/WR-19269_Update_Routing branch September 1, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants