Skip to content

feat(agents): Phase 6 — Payment agent, MockPaymentProvider, and transaction API - #6

Merged
NavneetHegde merged 2 commits into
mainfrom
feat/phase-6-payment-agent
Mar 9, 2026
Merged

NavneetHegde merged 2 commits into
mainfrom
feat/phase-6-payment-agent

Conversation

@NavneetHegde

Copy link
Copy Markdown
Owner

What changed

  • New IPaymentProvider interface + PaymentResult record in Domain
  • MockPaymentProvider (Infrastructure) — simulates payments with 90% success rate, 1-2s delay, idempotency cache, and randomised failure reasons
  • PaymentProcessor — extracted business logic class (watch lookup, status transitions, provider call, transaction persistence, event publishing)
  • PaymentWorker — thin Service Bus adapter listening on approval-decided, filtering non-approved decisions and delegating to PaymentProcessor
  • GET /api/transactions?userId= and GET /api/transactions/{id}?userId= endpoints to expose the payment ledger
  • appsettings.json for the Payment agent with logging + success rate config
  • New test project AgentPayWatch.Agents.Payment.Tests (23 tests)
  • 13 new TransactionEndpointsTests in the existing Api.Tests project

Why

Phase 6 completes the full end-to-end agent workflow:
Active → Matched → AwaitingApproval → Approved → Purchasing → Completed

On payment failure the watch returns to Active so the ProductWatch agent automatically picks it up again on the next scan cycle. PaymentProcessor was extracted from PaymentWorker (mirroring the MatchingService pattern from Phase 4) specifically to make the business logic unit-testable without requiring a live Service Bus.

How to test

Automated (82 tests, all passing):

dotnet test tests/AgentPayWatch.Api.Tests
dotnet test tests/AgentPayWatch.Agents.Payment.Tests

End-to-end via curl:

  1. dotnet run --project appHost/apphost.cs
  2. Create a watch: POST /api/watches
  3. Wait ~15s for ProductWatch agent to find a match
  4. Poll GET /api/watches?userId=demo-user until status = AwaitingApproval
  5. Get approval token: GET /api/matches/{watchId}
  6. Approve: POST /api/a2p/callback with { "token": "...", "decision": "BUY" }
  7. Watch transitions: Approved → Purchasing → Completed within ~3s
  8. Verify: GET /api/transactions?userId=demo-user shows a Succeeded transaction
  9. For failure path: ~10% of payments fail and the watch returns to Active

Screenshots

No UI changes in this phase (Phase 7 covers Blazor UI).

🤖 Generated with Claude Code

NavneetHegde and others added 2 commits March 9, 2026 01:18
Complete the end-to-end agent workflow by adding the final agent that
executes payments on approved matches.

Key design decisions:
- Extract PaymentProcessor from PaymentWorker so business logic can be
  unit-tested without Service Bus infrastructure (mirrors MatchingService
  pattern from Phase 4)
- IPaymentProvider in Domain keeps the interface dependency-free; the
  MockPaymentProvider in Infrastructure simulates 90% success rate with
  a 1-2s delay and idempotency via an in-memory cache
- PaymentWorker is now a thin Service Bus adapter: deserialise, filter
  non-approved decisions, delegate to PaymentProcessor
- On success: watch → Purchasing → Completed, PaymentCompleted published
- On failure: watch → Purchasing → Active (re-queued for scanning),
  PaymentFailed published
- GET /api/transactions and GET /api/transactions/{id} endpoints expose
  the transaction ledger with userId partition-key scoping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nEndpoints

82 tests total (23 new in Payment.Tests, 13 new in Api.Tests).

PaymentProcessorTests (16): covers all branches of the processor —
watch/match not found, successful payment (transaction fields, status
transitions via history, PaymentCompleted event), failed payment
(failed transaction, watch returns to Active, PaymentFailed event),
idempotency key format, payment token fallback to "tok_demo".

MockPaymentProviderTests (7): result shape for 0%/100% rates,
failure reason drawn from known set, idempotency (same key → same
cached result), valid result shape when using default config.

TransactionEndpointsTests (13): GET list returns 200/empty/sorted
desc/all fields/400 on missing userId; GET by id returns 200/404/400
on missing userId/correct failed-status shape.

ApiTestFactory extended with IPaymentTransactionRepository NSubstitute
mock so TransactionEndpoints tests can control repo responses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NavneetHegde NavneetHegde added the enhancement New feature or request label Mar 9, 2026

@NavneetHegde NavneetHegde left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase 6 Code Review — Payment Agent & Transaction API

Commits

SHA Message
`2b030b1` feat(agents): implement Phase 6 — Payment agent and transaction API
`4ff7f85` test(agents): add unit tests for Phase 6 Payment agent and TransactionEndpoints

Files Changed (17 files, +1440 / -2)

File Change
`Domain/Interfaces/IPaymentProvider.cs` New interface + `PaymentResult` record
`Infrastructure/Mocks/MockPaymentProvider.cs` Mock with 90% success, idempotency cache, simulated delay
`Infrastructure/DependencyInjection.cs` Registers `IPaymentProvider → MockPaymentProvider`
`Agents.Payment/PaymentProcessor.cs` Extracted business logic — watch lookup, transitions, payment call, event publishing
`Agents.Payment/PaymentWorker.cs` Thin Service Bus adapter; deserialises `ApprovalDecided`, delegates to `PaymentProcessor`
`Agents.Payment/Program.cs` Wires up infrastructure, `PaymentProcessor`, and `PaymentWorker`
`Agents.Payment/appsettings.json` Logging config + `Payment:MockSuccessRatePercent: 90`
`Api/Contracts/TransactionResponse.cs` Response DTO with `FromEntity` factory
`Api/Endpoints/TransactionEndpoints.cs` `GET /api/transactions` + `GET /api/transactions/{id}`
`Api/Program.cs` Registers `MapTransactionEndpoints()`
`AgentPayWatch.slnx` Adds `AgentPayWatch.Agents.Payment.Tests` project
`tests/AgentPayWatch.Agents.Payment.Tests/` New test project (3 files)
`tests/AgentPayWatch.Api.Tests/ApiTestFactory.cs` Adds `IPaymentTransactionRepository` NSubstitute mock
`tests/AgentPayWatch.Api.Tests/TransactionEndpointsTests.cs` 13 endpoint tests

Test Coverage

  • ✅ Unit tests passing: 82/82 (59 Api.Tests + 23 Payment.Tests)
  • ✅ New tests added: 36 net-new tests (23 Payment agent + 13 TransactionEndpoints)
  • ✅ No compiler warnings (beyond the pre-existing ASPDEPR002 on WithOpenApi)
  • ✅ No debug code, TODO comments, hardcoded secrets, or dead imports found

PaymentProcessorTests coverage:

  • Watch not found / wrong status → no-op
  • Match not found → no-op
  • Success path: transaction fields, Purchasing → Completed via status history, PaymentCompleted event on correct topic
  • Failure path: failed transaction, Purchasing → Active via status history, PaymentFailed event, failure reason propagated
  • Idempotency key format ({matchId}:{approvalId})
  • Token fallback to tok_demo when PaymentMethodToken is empty
  • Provider reference populated on success, empty on failure

MockPaymentProviderTests coverage:

  • 100%/0% success rate result shapes
  • Failure reason always drawn from the known set
  • Idempotency: same key returns the same cached result for both success and failure
  • Valid result shape when using default (unconfigured) success rate

TransactionEndpointsTests coverage:

  • GET /api/transactions: 200 with list, 200 with empty list, sorted descending by InitiatedAt, all fields mapped, 400 on missing userId
  • GET /api/transactions/{id}: 200, 404, 400 on missing userId, failed transaction status + failure reason

Potential Issues

  1. MockPaymentProvider delay in tests — The provider has a hardcoded 1–2s Task.Delay per call. The PaymentProcessorTests use FakePaymentProvider (no delay) so those run fast, but MockPaymentProviderTests with 3 iterations of the failure-reason test still take ~15s total. Acceptable for now, but worth extracting the delay into a configurable option (e.g. Payment:MockDelayMs) in a follow-up so the test suite can set it to 0.

  2. PaymentProcessor registered as Singleton, repositories as Scoped — PaymentProcessor is AddSingleton in Program.cs and injects IWatchRequestRepository etc. which are AddScoped. This is a standard DI lifetime mismatch, but it's consistent with how the other agent workers (ApprovalWorker, ProductWatchWorker) handle their dependencies. Since agents run as long-lived background services rather than per-request, this works in practice. A follow-up could make all agent-injected repos singletons.

  3. GetByStatusAsync for watch lookup in PaymentProcessor — The processor calls GetByStatusAsync(Approved) and then does a client-side FirstOrDefault(w => w.Id == watchRequestId). This works correctly but performs a cross-partition Cosmos scan. The Approval agent uses the same pattern. This is fine for the current mock/emulator phase; the phase docs call this out as acceptable until a real product source is added.

  4. No PaymentWorker message-path tests — The deserialization, null-check, and non-Approved decision filtering in PaymentWorker are not directly unit-tested (would require mocking ServiceBusClient / ServiceBusProcessorOptions, which are sealed Azure SDK types). The logic is simple and the critical business paths are covered via PaymentProcessor tests. Acceptable.


Recommendation

✅ Ready to merge

No blocking issues. The extraction of PaymentProcessor is a clean architectural decision that keeps the worker testable without Service Bus infrastructure. All tests pass, error handling is consistent with the rest of the codebase, and the implementation faithfully follows the Phase 6 spec.

Suggested follow-ups (non-blocking):

  • Make MockPaymentProvider delay configurable for faster test runs
  • Resolve the DI lifetime mismatch for agent repositories consistently across all agents

@NavneetHegde
NavneetHegde merged commit ad0cbb0 into main Mar 9, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant