[WR-19269] First draft of moving payment stuff into the Wayroo.Payments with an … - #25
Merged
Merged
Conversation
…event populating Luci.Orders.
JoshB-KC
requested review from
AndyDavisRS,
DustinK-retailsuccess,
cmchenry72,
hitaishiRS and
steven-palomino
August 30, 2026 20:48
DustinK-retailsuccess
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves the ProPay account reads into Wayroo.Payments behind a provider-neutral contract, and lands the
producer half of WR-19269 (D5) —
StoreProviderConfigChangedon the interprocess bus, whichLuci.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.GetPropayAccountBalanceAsyncin Luci.Orders:GET /api/payments/v1.0/tenants/{tenantId}/stores/{storeId}/account/balancePOST .../account/refreshProvider-neutral:
MoneyAmountin major units, a six-valuePaymentAccountStatus, andCanProcessPayments/CanReceivePayoutsrather than ProPay's vocabulary. Callers never name theprovider — 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 theGateways/{Provider}/implementations, so a worker or the recorder lambda can reuse them rather thanre-deriving the choice. Adding Adyen is a new folder plus one registration line — no controller
changes.
WR-19269 producer —
StoreProviderConfigChanged(payments.StoreProviderConfigChanged.v1)published by the ConfigurationRecorder when a store's routing actually changes. Routing lives on a
per-store
#routingitem carryingAcquiringProviderId,MigrationStateand a server-incrementedConfigurationVersion, which travels as the envelope'sSequenceso consumers can discard staleupdates. 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
UpdateItem, neverPutItem. The credential recorder and the account refreshshare 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
#routingsort key.intcents. Every amount crossing the gateway is divided by 100.There's a test on it; a missed conversion is a silent hundredfold error.
PropayAccountStatusMapcollapses ProPay's 24 statuses onto the six neutral ones. That mapping isbusiness judgement, not translation — it wants a product sign-off. Unknown values fail closed to
Suspended.MigrationStatesconstant sets (event package + models) exist soWayroo.Payments.Modelsstays free of package references.
MigrationStateParityTestsfails the build if they drift, becausedrift would mean persisting one spelling and publishing another.
PropayApiBaseUrisOptionsare environment-wide defaults fromcontainer 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-roledynamodb:UpdateItemon{env}-PaymentConfiguration(new — the writers no longer usePutItem)ssm:GetParametersByPathon/luci/{env}/vendors/propay/*, pluskms:Decrypton the key thoseSecureString parameters use (new — the API fails at startup without this, the source is required)
dynamodb:GetItem/Query,cloudwatch:PutMetricData,xray:PutTraceSegments/PutTelemetryRecordsRecorder lambda role —
{env}/WorkerRoledynamodb:UpdateItemon the table (new — previously documented asPutItem)events:PutEventsscoped to the{env}-wayroo-eventsbus ARN — tracked as DEV-3868, along withthat 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:
ProviderAccountDetails/AccountStatuswithout losingProviderConfigurationavailableBalance/pendingBalanceagainst Orders'GET /tenants/{t}/stores/{s}/propay/account-balancefor the same store. They must match to thepenny; this is what catches a cents/dollars regression
blocked on the
events:PutEventsgrant above3. Sign-off
PropayAccountStatusMap— product to confirm the ProPay→neutral groupingsNot in this PR
StorePaymentProviders, EventBridge→SQS→Lambda consumer(dedupe on
EventId, native envelope parsing — notEventQueueDeserializer), Redis-cachedIStoreProviderResolverfailing static to ProPay, backfill job. Separate branch; the ticket sequencesthe publisher first. Blocks WR-19918 (D12).
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.
GetPropayAccountBalanceAsync. WatchLuci.Integrations.Api/Services/PaymentManager.cs:115, which casts the status unconditionally — theneutral
Statusis non-null wheneveraccountExists, which this preserves.ActiveProvider/routing, and the backfill driver itself. Notenothing rate-limits ProPay today, so a driver needs its own concurrency cap.
ConfigurationRecorder.Lambdastill calls Luci.Orders (PropayStoreOwnerResolver) to map an accountto 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
PutEventsstub).dotnet restore --locked-mode— the CI gate —is clean, and
cdk synthproduces 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.