Skip to content

ENG-56: implement PaymentMethod interface, ManualPaymentMethod, MockPADMethod, and registry#119

Merged
Connorbelez merged 2 commits intomainfrom
Connorbelez/eng56-payment-methods
Mar 18, 2026
Merged

ENG-56: implement PaymentMethod interface, ManualPaymentMethod, MockPADMethod, and registry#119
Connorbelez merged 2 commits intomainfrom
Connorbelez/eng56-payment-methods

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Mar 18, 2026

Add pluggable payment rail abstractions using the Strategy pattern with
dependency-injected scheduling for testability in Convex's stateless V8 isolates.

  • PaymentMethod interface with typed InitiateParams/Result, Confirm/Cancel/StatusResult
  • ManualPaymentMethod: immediate confirmation, no external API
  • MockPADMethod: simulated PAD with configurable delay/failure and DI scheduler
  • Two-tier registry: simple getPaymentMethod() + full DI createPaymentMethodRegistry()
  • 19 tests covering all implementations, config overrides, and ConvexError on unknown method

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Added a payment method system supporting multiple payment types with a registry for resolution.
    • Introduced manual payment method for immediate payment confirmations.
    • Introduced mock Pre-Authorized Debit payment method with configurable delay and failure rate settings.
  • Tests

    • Added comprehensive test suite covering payment method implementations, registry behavior, and error handling.

…ADMethod, and registry

Add pluggable payment rail abstractions using the Strategy pattern with
dependency-injected scheduling for testability in Convex's stateless V8 isolates.

- PaymentMethod interface with typed InitiateParams/Result, Confirm/Cancel/StatusResult
- ManualPaymentMethod: immediate confirmation, no external API
- MockPADMethod: simulated PAD with configurable delay/failure and DI scheduler
- Two-tier registry: simple getPaymentMethod() + full DI createPaymentMethodRegistry()
- 19 tests covering all implementations, config overrides, and ConvexError on unknown method

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear bot commented Mar 18, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Introduces a payment method subsystem with a PaymentMethod interface, two implementations (ManualPaymentMethod for immediate settlement and MockPADMethod for simulated delayed settlement), a registry for method resolution, and comprehensive test coverage. Includes configuration via dependency injection and error handling for invalid inputs.

Changes

Cohort / File(s) Summary
Core Interface & Implementations
convex/payments/methods/interface.ts, convex/payments/methods/manual.ts, convex/payments/methods/mockPAD.ts, convex/payments/methods/registry.ts
Defines PaymentMethod interface with four async methods (initiate, confirm, cancel, getStatus) and typed parameter/result objects. Implements ManualPaymentMethod for cash/cheque with immediate confirmation. Implements MockPADMethod with configurable delay and failure simulation, supporting dependency injection via ScheduleSettlementFn. Registry provides both simple lookup via getPaymentMethod() and full DI via createPaymentMethodRegistry(), with error handling for unknown methods.
Test Suite
convex/payments/__tests__/methods.test.ts
Comprehensive test coverage for ManualPaymentMethod, MockPADMethod, and registry behavior. Tests include provider reference generation, status transitions, configuration validation (failureRate and delayMs bounds), scheduler interaction verification, and error handling for invalid configs via ConvexError.
Specification & Planning
specs/ENG-56/chunks/chunk-01-interface-and-implementations/context.md, specs/ENG-56/chunks/chunk-01-interface-and-implementations/tasks.md, specs/ENG-56/chunks/chunk-02-tests/context.md, specs/ENG-56/chunks/chunk-02-tests/tasks.md, specs/ENG-56/chunks/manifest.md, specs/ENG-56/tasks.md
Documentation and planning artifacts outlining two-chunk implementation strategy. Chunk 1 covers interface and implementations; Chunk 2 defines test structure and quality gate procedures. Includes acceptance criteria, test conventions, and task checklists.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Registry
    participant PaymentMethod
    participant Scheduler
    participant Database

    Client->>Registry: getPaymentMethod("mock_pad")<br/>with scheduleSettlement
    Registry->>Registry: buildMethod("mock_pad")
    Registry-->>Client: MockPADMethod instance

    Client->>PaymentMethod: initiate(params)
    PaymentMethod->>PaymentMethod: generate providerRef<br/>calculate shouldFail
    PaymentMethod->>Scheduler: scheduleSettlement(delayMs, {providerRef, shouldFail, planEntryId})
    Scheduler-->>PaymentMethod: scheduled
    PaymentMethod-->>Client: {providerRef, status: "pending"}

    Client->>PaymentMethod: getStatus(providerRef)
    PaymentMethod->>Database: lookup pending payment
    Database-->>PaymentMethod: payment record
    PaymentMethod-->>Client: {status: "pending", providerData}

    Scheduler->>Database: trigger settlement based on shouldFail
    Database-->>Scheduler: settlement complete or failed

    Client->>PaymentMethod: confirm(providerRef)
    PaymentMethod-->>Client: {settledAt, providerRef}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The review involves multiple cohorts of changes: new interface definitions with clear contracts, two payment method implementations with configuration validation and DI patterns, a registry with error handling, and a detailed test suite covering nominal paths and edge cases. The logic density is moderate (validation, config merging, error handling), and the changes span multiple related files requiring consistent understanding of the pattern. Configuration validation and DI injection add some complexity but follow standard patterns.

Possibly related issues

  • ENG-56: This PR directly implements the payment method interface, manual and mock PAD implementations, registry pattern, and test structure outlined in this epic specification.

Poem

🐰 Hops with glee through payment streams,
Manual paths and deferred dreams!
Registry guides the flow with care,
Mock PAD scheduling in the air! ✨💳

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: implementing the PaymentMethod interface along with two concrete implementations (ManualPaymentMethod, MockPADMethod) and a registry to manage them. It is concise, directly related to the changeset, and provides sufficient context for understanding the primary contribution.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Connorbelez/eng56-payment-methods
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @Connorbelez, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Copy link
Copy Markdown
Owner Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@Connorbelez Connorbelez marked this pull request as ready for review March 18, 2026 06:29
Copilot AI review requested due to automatic review settings March 18, 2026 06:29
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Connorbelez has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a pluggable payment-rail abstraction under convex/payments/ using a PaymentMethod strategy interface, with concrete manual and mock_pad implementations and a small registry API (plus a Vitest suite) to support downstream payment scheduling work (ENG-63).

Changes:

  • Added PaymentMethod interface with typed params/results.
  • Implemented ManualPaymentMethod (immediate confirmation) and MockPADMethod (pending + injected scheduler).
  • Added a two-tier method registry (getPaymentMethod + DI-friendly createPaymentMethodRegistry) and a dedicated test suite.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
specs/ENG-56/tasks.md Work plan/checklist for ENG-56.
specs/ENG-56/chunks/manifest.md Chunk manifest for implementation/testing phases.
specs/ENG-56/chunks/chunk-01-interface-and-implementations/tasks.md Chunk 1 task breakdown.
specs/ENG-56/chunks/chunk-01-interface-and-implementations/context.md Chunk 1 design/acceptance context.
specs/ENG-56/chunks/chunk-02-tests/tasks.md Chunk 2 task breakdown.
specs/ENG-56/chunks/chunk-02-tests/context.md Chunk 2 testing/quality gate context.
convex/payments/methods/interface.ts Defines PaymentMethod and typed params/results.
convex/payments/methods/manual.ts Manual rail implementation (immediate confirmation).
convex/payments/methods/mockPAD.ts Mock PAD rail implementation with DI scheduler and config.
convex/payments/methods/registry.ts Two-tier lookup + DI registry factory; unknown method errors.
convex/payments/tests/methods.test.ts Vitest coverage for both methods + registry behaviors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread convex/payments/methods/mockPAD.ts
Comment thread convex/payments/methods/mockPAD.ts
Comment thread convex/payments/methods/manual.ts Outdated
Comment thread convex/payments/methods/mockPAD.ts
Comment thread specs/ENG-56/chunks/manifest.md
Comment thread convex/payments/__tests__/methods.test.ts
…niqueness, test coverage

- Add runtime validation in MockPADMethod constructor for failureRate [0,1] and delayMs >= 0
- Replace Date.now() with crypto.randomUUID() in providerRef generation for both methods
- Add getStatus() test for MockPADMethod and constructor validation tests
- Use afterEach(vi.restoreAllMocks) instead of manual cleanup in test blocks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
convex/payments/methods/registry.ts (1)

67-73: Consider failing fast for mock_pad in the simple registry tier.

Returning a scheduler-dependent method from getPaymentMethod() defers failure to initiate(). Throwing immediately for "mock_pad" in this path would make misuse more obvious.

♻️ Proposed fail-fast guard
 export function getPaymentMethod(method: string): PaymentMethod {
+	if (method === "mock_pad") {
+		throw new ConvexError(
+			'Payment method "mock_pad" requires scheduler injection. ' +
+				"Use createPaymentMethodRegistry()."
+		);
+	}
 	return buildMethod(method, noopScheduler);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/payments/methods/registry.ts` around lines 67 - 73, getPaymentMethod
currently always returns buildMethod(method, noopScheduler), which defers
scheduler-dependent errors until initiate(); add a fail-fast guard inside
getPaymentMethod to check if method === "mock_pad" and throw a clear Error
immediately instead of returning a noop-scheduled method. Locate
getPaymentMethod and modify it to throw for "mock_pad" (mentioning the
problematic method name) while keeping other paths calling buildMethod(method,
noopScheduler); reference buildMethod, noopScheduler and initiate in the error
message to guide callers.
convex/payments/methods/interface.ts (1)

16-20: Use Convex Id<"..."> types for entity references in InitiateParams.

borrowerId and mortgageId are currently modeled as plain string, which loses table-level type safety at call sites. These should be typed as Id<"borrowers"> and Id<"mortgages"> respectively to match the schema and align with how references are typed elsewhere in the codebase.

For planEntryId, the corresponding table is not yet defined in the schema; type it as Id<"..."> once the exact table name is confirmed.

♻️ Proposed type-safety refactor
+import type { Id } from "../../_generated/dataModel";
+
 export interface InitiateParams {
 	/** Amount in cents */
 	amount: number;
-	borrowerId: string;
+	borrowerId: Id<"borrowers">;
 	metadata?: Record<string, unknown>;
 	method: string;
-	mortgageId: string;
-	planEntryId: string;
+	mortgageId: Id<"mortgages">;
+	planEntryId: string; // Replace with Id<"..."> once exact table name is confirmed.
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/payments/methods/interface.ts` around lines 16 - 20, Update the
InitiateParams interface to use Convex's Id generic types for entity references:
change borrowerId from string to Id<"borrowers"> and mortgageId from string to
Id<"mortgages"> (and set planEntryId to Id<"..."> once the exact table name is
known); import the Id type from Convex where this interface is declared so
type-checking is consistent with other references in the codebase (look for the
InitiateParams interface in this file to apply the changes).
convex/payments/__tests__/methods.test.ts (1)

25-31: Use method-specific fixtures to avoid semantic mismatch in MockPAD tests.

sampleParams is fixed to method: "manual" but is also used in MockPADMethod tests. This can hide regressions if method-specific validation is added later. Consider splitting fixtures (manualParams and mockPadParams) and use "mock_pad" in the MockPAD suite.

♻️ Suggested fixture split
-const sampleParams: InitiateParams = {
+const manualParams: InitiateParams = {
 	amount: 100_000, // $1000 in cents
 	mortgageId: "mortgage_123",
 	borrowerId: "borrower_456",
 	planEntryId: "entry_789",
 	method: "manual",
 };
+
+const mockPadParams: InitiateParams = {
+	...manualParams,
+	method: "mock_pad",
+};

Then use manualParams in ManualPaymentMethod tests and mockPadParams in MockPADMethod tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/payments/__tests__/methods.test.ts` around lines 25 - 31, The current
shared fixture sampleParams (type InitiateParams) uses method: "manual" but is
also used by MockPADMethod tests; split into two explicit fixtures (e.g.,
manualParams and mockPadParams) where manualParams keeps method: "manual" and
mockPadParams sets method: "mock_pad" (and otherwise mirrors required fields
like amount, mortgageId, borrowerId, planEntryId); update tests referencing
sampleParams to use manualParams in ManualPaymentMethod suites and mockPadParams
in MockPADMethod suites to avoid semantic mismatches and catch method-specific
validation regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@specs/ENG-56/chunks/chunk-01-interface-and-implementations/context.md`:
- Line 127: The spec text wrongly states that the `convex/payments/` directory
does not exist yet; update or remove that outdated sentence referencing
`convex/payments/` in the Markdown so the document matches the implementation
introduced in this PR, ensuring the spec no longer claims the directory is
missing.

In `@specs/ENG-56/chunks/chunk-02-tests/context.md`:
- Line 76: The documented error string for getPaymentMethod is out of sync with
the runtime: the registry currently throws ConvexError("Unknown payment method:
\"unknown\"") (with quotes). Update the spec line in context.md so the example
matches the implementation (e.g., `getPaymentMethod("unknown")` throws
`ConvexError("Unknown payment method: \"unknown\"")`), or alternatively change
the registry's thrown message in the getPaymentMethod/registry code to remove
the quotes—pick one consistent approach and make sure the example and the thrown
message match exactly.

In `@specs/ENG-56/chunks/manifest.md`:
- Around line 5-6: The manifest rows for chunk-01-interface-and-implementations
and chunk-02-tests are still marked "pending" but the corresponding tasks are
completed; update the manifest entries for
chunk-01-interface-and-implementations and chunk-02-tests to the correct
completed status to match the tasks document and ensure the manifest and tasks
are consistent (also scan for any other ENG-56 chunk rows and align their status
if needed).

---

Nitpick comments:
In `@convex/payments/__tests__/methods.test.ts`:
- Around line 25-31: The current shared fixture sampleParams (type
InitiateParams) uses method: "manual" but is also used by MockPADMethod tests;
split into two explicit fixtures (e.g., manualParams and mockPadParams) where
manualParams keeps method: "manual" and mockPadParams sets method: "mock_pad"
(and otherwise mirrors required fields like amount, mortgageId, borrowerId,
planEntryId); update tests referencing sampleParams to use manualParams in
ManualPaymentMethod suites and mockPadParams in MockPADMethod suites to avoid
semantic mismatches and catch method-specific validation regressions.

In `@convex/payments/methods/interface.ts`:
- Around line 16-20: Update the InitiateParams interface to use Convex's Id
generic types for entity references: change borrowerId from string to
Id<"borrowers"> and mortgageId from string to Id<"mortgages"> (and set
planEntryId to Id<"..."> once the exact table name is known); import the Id type
from Convex where this interface is declared so type-checking is consistent with
other references in the codebase (look for the InitiateParams interface in this
file to apply the changes).

In `@convex/payments/methods/registry.ts`:
- Around line 67-73: getPaymentMethod currently always returns
buildMethod(method, noopScheduler), which defers scheduler-dependent errors
until initiate(); add a fail-fast guard inside getPaymentMethod to check if
method === "mock_pad" and throw a clear Error immediately instead of returning a
noop-scheduled method. Locate getPaymentMethod and modify it to throw for
"mock_pad" (mentioning the problematic method name) while keeping other paths
calling buildMethod(method, noopScheduler); reference buildMethod, noopScheduler
and initiate in the error message to guide callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f844e87e-da93-4dd6-a423-20cfbb11804a

📥 Commits

Reviewing files that changed from the base of the PR and between 707bc9c and fb6add8.

📒 Files selected for processing (11)
  • convex/payments/__tests__/methods.test.ts
  • convex/payments/methods/interface.ts
  • convex/payments/methods/manual.ts
  • convex/payments/methods/mockPAD.ts
  • convex/payments/methods/registry.ts
  • specs/ENG-56/chunks/chunk-01-interface-and-implementations/context.md
  • specs/ENG-56/chunks/chunk-01-interface-and-implementations/tasks.md
  • specs/ENG-56/chunks/chunk-02-tests/context.md
  • specs/ENG-56/chunks/chunk-02-tests/tasks.md
  • specs/ENG-56/chunks/manifest.md
  • specs/ENG-56/tasks.md

- No `any` types — project enforces this
- File naming: camelCase for files (e.g., `mockPAD.ts`, not `mock-pad.ts`)
- Directory structure: `convex/payments/methods/` (new directory, needs creating)
- The `convex/payments/` directory does NOT exist yet — must create it
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update stale implementation-state statement.

Line 127 says convex/payments/ does not exist yet, but this PR already introduces that directory and files. Please update/remove this line to avoid spec drift.

Based on learnings: Do not flag Markdown linting/formatting issues in this repository; only raise findings when .md content drifts from implemented architecture.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/ENG-56/chunks/chunk-01-interface-and-implementations/context.md` at
line 127, The spec text wrongly states that the `convex/payments/` directory
does not exist yet; update or remove that outdated sentence referencing
`convex/payments/` in the Markdown so the document matches the implementation
introduced in this PR, ensuring the spec no longer claims the directory is
missing.

### Registry
- `getPaymentMethod("manual")` returns ManualPaymentMethod instance
- `getPaymentMethod("mock_pad")` returns MockPADMethod instance
- `getPaymentMethod("unknown")` throws `ConvexError("Unknown payment method: unknown")`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Documented unknown-method error string does not match implementation.

This line documents Unknown payment method: unknown, but the registry currently throws with quotes around the method value (Unknown payment method: "unknown"). Please align doc and runtime string to prevent expectation drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/ENG-56/chunks/chunk-02-tests/context.md` at line 76, The documented
error string for getPaymentMethod is out of sync with the runtime: the registry
currently throws ConvexError("Unknown payment method: \"unknown\"") (with
quotes). Update the spec line in context.md so the example matches the
implementation (e.g., `getPaymentMethod("unknown")` throws `ConvexError("Unknown
payment method: \"unknown\"")`), or alternatively change the registry's thrown
message in the getPaymentMethod/registry code to remove the quotes—pick one
consistent approach and make sure the example and the thrown message match
exactly.

Comment on lines +5 to +6
| chunk-01-interface-and-implementations | T-001 through T-004 | pending |
| chunk-02-tests | T-005 through T-006 | pending |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Manifest status is stale relative to this PR’s completed state.

Both chunks are marked pending, but specs/ENG-56/tasks.md marks all ENG-56 items complete. This creates tracking drift across planning docs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/ENG-56/chunks/manifest.md` around lines 5 - 6, The manifest rows for
chunk-01-interface-and-implementations and chunk-02-tests are still marked
"pending" but the corresponding tasks are completed; update the manifest entries
for chunk-01-interface-and-implementations and chunk-02-tests to the correct
completed status to match the tasks document and ensure the manifest and tasks
are consistent (also scan for any other ENG-56 chunk rows and align their status
if needed).

@Connorbelez Connorbelez merged commit b46be69 into main Mar 18, 2026
1 of 3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Mar 27, 2026
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