Skip to content

feat(gateway,core): recurring/subscription payment flows - #48

Merged
aguilar1x merged 12 commits into
PACTO-LAT:mainfrom
gabvega08:feat/recurring-subscriptions
Jul 19, 2026
Merged

feat(gateway,core): recurring/subscription payment flows#48
aguilar1x merged 12 commits into
PACTO-LAT:mainfrom
gabvega08:feat/recurring-subscriptions

Conversation

@gabvega08

Copy link
Copy Markdown
Contributor

Summary

Adds a recurring/subscription payment primitive to Connect, so merchants with subscription business models can charge on a schedule through Pacto. Closes #41.

A subscription is a DB-backed plan (amount, interval, asset, FX pair) tied to a merchant + payer. A polling scheduler creates a new escrow on each billing cycle, reusing the quote engine (#18) for FX pricing at charge time, and the subscription lifecycle is surfaced through the existing signed-webhook pipeline. Test mode only for now (live returns 501 not_implemented, consistent with escrows).

What's included

  • Data modelSubscription + SubscriptionCharge Prisma models, enums, and a migration.
  • Charge cycle (subscriptions/charge.ts) — re-prices via createQuote(), creates an escrow via the test-mode simulator, records the charge, emits events, with bounded retry (SUBSCRIPTION_MAX_ATTEMPTS, default 3) → past_due + stop.
  • Scheduler (subscriptions/runner.ts) — polling runner mirroring the webhook delivery runner; wired into index.ts.
  • HTTP routes /v1/subscriptions — create / retrieve / list / cancel (session-scoped auth like escrows; idempotency on create).
  • Webhook eventssubscription.created, subscription.charged, subscription.failed, subscription.canceled through the existing dispatchEvent() pipeline.
  • Test controls/v1/test/subscriptions/:id/advance (deterministic synchronous charge) and /fail-next (simulate insufficient funds).
  • SDKsubscriptions resource (create/retrieve/list/cancel) + test-mode helpers in @pacto-connect/core.
  • Docs — subscription events + env vars documented in WEBHOOKS.md.

Acceptance criteria

  • Auto-escrow each cycle without manual intervention — the runner charges due subscriptions on its poll interval (accelerated in test mode via TESTMODE_SUB_INTERVAL_MS); advance provides a deterministic path for tests.
  • Failed charge emits subscription.failed and does not retry indefinitely — bounded retry with backoff, then past_due + a single subscription.failed; the runner never re-selects a non-active subscription.
  • Cancel stops future charges and emits subscription.canceled — cancel is terminal and the event is emitted exactly once, only on a real active → canceled transition.

Testing

  • Gateway: 149 tests pass. Core: 67 tests pass.
  • Monorepo type-check clean (8/8 workspaces); biome check clean.
  • Charge/runner/route/domain suites cover each acceptance criterion, session-scoping, live-mode 501, idempotency, and the bounded-retry cap.

Notes / scope

  • Test-mode only; live on-chain settlement is out of scope.
  • Known test-mode limitation: on gateway restart the in-memory simulator escrows are lost; the DB-backed subscription survives and keeps charging.

@aguilar1x
aguilar1x merged commit aafa36e into PACTO-LAT:main Jul 19, 2026
2 checks passed
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.

feat(gateway,core): recurring/subscription payment flows

2 participants