Subscription Creation & Payment Guarantees

TL;DR

Subscription creation is designed as an economic invariant: funds are never permanently deducted unless a subscription becomes active, and access is never granted without confirmed payment. This is enforced using a saga-style workflow with explicit compensation, where payments are tentatively held and later finalized or refunded based on subscription persistence. The design avoids distributed transactions while preserving economic correctness under partial failures and retries.

Subscription Invariant

A subscription must never become active without confirmed payment, and funds must never be permanently deducted without an active subscription. Under all failure and retry scenarios, the system converges to one of two valid states: active with payment finalized, or inactive with funds refunded.

Problem

Creating a subscription is an economic operation that spans multiple services. Funds must be deducted exactly once, and subscriptions must never become active without a corresponding successful payment.

This flow crosses service boundaries, database writes, and network calls, making single-database transactions or two-phase commit impractical.

Why Naïve Approaches Fail

Deducting wallet balance and creating a subscription in separate, uncoordinated steps risks permanent inconsistency under partial failure.

If payment succeeds but subscription persistence fails, funds may be lost. If subscription creation succeeds without confirmed payment, access is granted without economic backing.

Solution

Subscription creation is implemented as a saga-style workflow with explicit compensation, separating economic intent from final activation.

The payment service is treated as the authority for wallet state, while the subscription service controls activation and access.

  1. The subscription service initiates a synchronous RPC call to the payment service to request wallet deduction.
  2. The payment service creates a transaction in pending state and tentatively deducts the required amount from the subscriber’s wallet.
  3. If the deduction fails (e.g., insufficient balance), the flow aborts immediately and no subscription state is created.
  4. On successful deduction, the subscription service attempts to persist the subscription in its database.
  5. The subscription service emits a completion event containing the transaction ID and the outcome of subscription creation.
  6. The payment service finalizes the transaction:
    • Marks it successful if the subscription was created
    • Marks it failed and refunds the wallet if creation failed

This guarantees that funds are never permanently deducted without an active subscription, even in the presence of database or network failures.

Failure Model & Guarantees

The only synchronous boundary in this workflow is the initial wallet deduction RPC. All subsequent state transitions are designed to be idempotent and compensatable.

A subscription can exist only in one of two economically valid states: inactive with refunded balance or active with confirmed payment. No intermediate state grants access.

Tradeoffs Accepted

No distributed transactions: Two-phase commit was avoided in favor of explicit compensation to reduce operational complexity and coupling.

Eventual completion: Final transaction state depends on asynchronous event delivery rather than synchronous locking.

Deferred Complexity

Deferred: Client-supplied idempotency keys for subscription and payment initiation.

Subscription creation currently relies on server-side transaction tracking and compensating actions to recover from partial failures. While this guarantees eventual correctness, repeated client retries can still trigger duplicate initiation attempts under network uncertainty.

Introducing a unique request identifier from the client would allow the payment service to enforce idempotency at the boundary, ensuring that wallet deduction and transaction creation occur at most once per logical request. This has been deferred to avoid additional client-side coordination and storage complexity at the current scale.

What I’d Change at Scale

At higher financial volume or stricter audit requirements, this flow would evolve toward an append-only transaction ledger with stronger delivery guarantees and automated reconciliation tooling.