Skip to content

The NSFW Lock Stores Every Processor's Merchant Account

This ADR replaces one rule of Settle-Then-Lock NSFW Processor Assignment: that nsfw_processor_mid is written and replayed only for NMI. The rest of settle-then-lock is unchanged.

FOXY-1364 loads the high-risk SSB and Esquire MIDs onto Cybersource next to the SFW Coastal MID. All three settle as Primer processor CYBERSOURCE, so all three lock as psp=cybersource. Under the NMI-only rule a full-access user who paid on the Cybersource SSB account stored no MID, and every later payment reached the Full Access Cybersource workflow with nothing to tell it which account to use. The workflow default is Coastal for Apple Pay, so SSB customers would have renewed on the SFW MID.

The same rollout keeps existing users, meaning users locked before the deploy, on their current routing. Primer sees only metadata, so it needs a marker that separates users locked after the change from users locked before it, on EMP as well as on Cybersource.

Every NSFW lock stores the processorMerchantId of the payment that settled it, for every processor on the lane, and every NSFW payment replays it as psp_mid when the persisted lock matches the processor being requested. The SFW lane never stores or sends one: Cybersource is locked on both lanes, and a user can hold a Cybersource lock on each.

Two supporting rules:

  • The first lock tries to get a MID. Renewal create responses name the processor but carry no merchant ID. When the processor in hand has no MID, the lock hook fetches the payment first.
    • If the fetch fails, or returns no MID, the hook locks without one and logs processor lock: mid fetch failed, locking without mid with a reason (fetch_error, no_mid). A caller that holds no processor at all fetches the payment anyway, and a MID-less result logs the same line with reason=no_mid. So does a MID-less lock repaired from payment history. The line is written only when the call wrote the lock: a lost race or an unmapped processor leaves none.
    • If the fetched payment names another processor, nothing locks (reason=processor_mismatch). A skipped lock heals on the next settle; a wrong one never does.
  • Backfill stays NMI-only. Locks written before this change keep an empty MID, and the empty MID is what routes them through the existing-user branches.
  • Full Access Cybersource can branch on psp_mid for SSB and Esquire, and Full Access Traffic on the EMP MID for new EMP users. Locks without a MID keep today’s routing.
  • billing_processor_lock_mid_drift_total and the log line processor lock: settled merchant differs from locked mid report a Cybersource settle on another merchant account than the locked one. The counter also counts the expected SSB/Esquire wallet fallbacks, so it is an upper bound; triage reads settled_mid, and a Coastal value is the case to chase. NMI is excluded because both of its accounts are high-risk. The check sees customer-initiated subscription settles and the renewal path that re-reads an existing payment; a fresh renewal’s create response carries no MID, so it cannot drift-check. Re-fetching renewals for it is deferred to the Primer rollout (FOXY-1426/1427).
  • A credits read error on the MID lookup fails the checkout, top-up or vaulting request, as a failed processor lookup already does. So does a lock that names another processor than the one read a moment earlier, which an operator override or a first lock landing during vaulting can cause. Operators set or clear a user’s MID in Strip from a fixed list of processor and MID pairs in Strip’s code (live merchant accounts in production, sandbox ones elsewhere), so Strip writes no MID outside that list. A stored MID the list does not name stays until an operator picks another route. Omitting psp_mid instead would send an SSB customer to the Coastal default.
  • The Primer branches must match psp_mid exactly against the SSB, Esquire and EMP merchant IDs and send any other value to the default branch. Locks written on Coastal between this deploy and go-live store the Coastal MID, and must keep today’s routing.
  • A lock written without a MID (a failed fetch) routes as an existing user until the runbook’s per-user SQL sets it.
  • Any first lock written after the deploy whose settle carries a MID stores it and routes as new, including a lock repaired from payment history and a renewal’s first lock, even for a user who subscribed before the deploy. Both run only for users with no lock, so no existing lock changes.
  • Once customers are locked to the SSB account, reverting this change stops psp_mid and sends them to the Coastal default. It is fixed forward, not reverted.
  • Widen the backfill to Cybersource. It would stamp the existing empty-MID Coastal locks with whichever MID settles next, for example SSB through a Google Pay payment, and silently move their renewals.
  • A PostHog flag for new users. Primer routes on payment metadata, so the flag would still have to become metadata on every payment path. That adds a network dependency on money paths without replacing the stored value.