Inkress ADRsarchitecture decisions
Overview / Subscriptions
ADR 0025

Subscriptions bill a vaulted card-on-file token; renewals charge it server-side

AcceptedSubscriptions

Context

A subscription charges a buyer's card again on each billing period, with no cardholder present at renewal. That requires a durable, reusable reference to the card — which must not be a stored PAN (ADR 0006) — and a way to charge it unattended. Card schemes also distinguish the customer-initiated first payment (which should carry 3-D Secure) from later merchant-initiated renewals, and expect stored-credential transactions to be flagged as such.

Decision

Bill subscriptions against a vaulted card-on-file token: an initial charge mints a reusable token (intended to be customer-present + 3-D Secure — but see Current state), and each renewal charges that token server-side, cardholder-absent, with 3-D Secure off, driven by a background schedule.

  • The reusable credential is a vaulted token, never a PAN. The initial charge tokenizes the card in the CDE and stores the PanToken sealed in the vault (ADR 0006), exposed only as an opaque card_ref; that reference is persisted encrypted on the subscription. Renewals resolve the token at charge time and never see a PAN.
  • A subscription is created from a paid order. When an order is paid and is a subscription (a subscription order kind, or any order carrying a billing_plan_id), the paid-order transition (ADR 0013) enqueues the Oban billing worker to create the subscription and record the card reference.
  • Renewals are unattended and server-driven. Renewal charges run cardholder-absent with 3-D Secure off (the initial carried the authentication), scheduled by a self-rescheduling Oban chain — each successful charge schedules the next period — with a fixed dunning policy (a few retries a day apart, then mark the subscription payment failed and notify).
  • Live model: charge the token as a plain transaction; the network-anchor model is built but dormant. The live billing model (recurring_tokenized) charges the stored token as an ordinary transaction — so renewals are operationally merchant-initiated but are not flagged as network stored-credential MITs. The flagged model (recurring_authorised: a RecurringInitial anchor on the first sale and Recurring-flagged MITs on renewals) is implemented but gated off, because the PowerTranz SPI path does not honour those flags.

Consequences

  • Positive: no PAN is stored for rebilling — the durable credential is a vaulted token (ADR 0006), and renewals need no cardholder.
  • Current state / to revisit — the 3DS initial does not actually vault on prod. The intended customer-present 3DS initial cannot mint a token today: the prod PowerTranz profile withholds the PanToken on a 3DS challenge, so the SPI+3DS path vaults zero cards and renewals would fail "card_ref required". The working vault path is a flag-gated, TEMPORARY no-3DS stopgap (FAC_ALLOW_RECURRING_NO_3DS) that runs the subscription-initial as a single-step FPI sale without 3DS to obtain the token — which forfeits the 3DS liability shift on the initial. So on the live path the initial is currently not 3DS-authenticated; restoring that depends on the gateway returning the token through the 3DS flow.
  • Neutral / to revisit — scheme flagging. Renewals are not currently marked as stored-credential MITs because the gateway's SPI path ignores the anchor/recurring flags; the flagged flow is kept behind a switch for when gateway support exists. Until then, renewals may not carry the stored-credential provenance schemes expect (inferred implication).
  • Neutral / to revisit — no safety net. The renewal driver is a self-rescheduling chain; the standalone scheduler cron that would sweep and recover a broken chain is currently commented out in the deployed config, so a chain that stops has no automatic recovery. This is a known operational gap.
  • Neutral / to revisit: renewals run with 3-D Secure off by design, so fraud protection for recurring charges rests on the initial authentication and the risk gates (ADR 0024), not per-renewal 3DS — and while the no-3DS initial stopgap (above) is in force, even the initial lacks 3DS, leaning that protection entirely on the risk gates.

Alternatives considered

  • Store the PAN (encrypted) to rebill: rejected — it keeps a reversible PAN in our datastore, the exact liability ADR 0006 removes; the vaulted provider token is the durable credential instead.
  • Require the cardholder present + 3-D Secure on every renewal: rejected — it defeats unattended recurring billing; authentication is done once at the initial and the token is reused.
  • Adopt the network stored-credential (RecurringInitial/MIT-flagged) model as the live path now: not taken — it is built but dormant because the PowerTranz SPI integration does not honour the flags, so the tokenized plain-charge model is the working default until the gateway path supports proper stored-credential flagging.
← 0024 Risk and KYC are a standalone decision service that money services enforce, with a split fail posture 0026 API authentication accepts many credential types through one resolution pipeline →