# At most one payment processor

- **Pattern:** `ab-000232` (`ai-slop-code-drift.convention-drift.dual-payment-processor`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000232
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000232)

## Why it matters

Two active payment processors means two webhook handlers, two subscription state machines, two refund flows, and two customer records — and they will diverge. Stripe webhooks fire for events that PayPal never sees; subscription cancellations processed in one system leave active billing in the other. ISO 25010 functional-suitability is directly violated when billing logic is split: a customer who cancels via the Stripe portal remains billed through the PayPal path. In a regulated context, split billing records also complicate revenue recognition and refund auditing. This drift pattern almost always originates from AI sessions adding a "quick PayPal option" without reviewing the existing Stripe integration.

## Severity rationale

Medium because split payment processors fragment billing state and refund logic, but the failure mode is financial inconsistency rather than immediate security compromise.

## Remediation

Pick the processor that handles the majority of your transaction volume as primary. Wrap it behind a unified billing interface so future processor changes don't require touching application logic.

```ts
// src/lib/billing.ts — single processor entry point
import Stripe from 'stripe'

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
})

// All subscription, refund, and webhook logic goes through here
export async function cancelSubscription(subscriptionId: string) {
  return stripe.subscriptions.cancel(subscriptionId)
}
```

If regional requirements genuinely force two processors, gate the second behind a `RegionBillingAdapter` interface and ensure webhook handlers share subscription state in a single `subscriptions` table.

## Detection

- **ID:** `dual-payment-processor`
- **Severity:** `medium`
- **What to look for:** Apply the three-condition rule against this exact payment processor allowlist: `stripe`, `@stripe/stripe-js`, `@stripe/react-stripe-js`, `braintree`, `braintree-web`, `paypal-rest-sdk`, `@paypal/checkout-server-sdk`, `@paypal/react-paypal-js`, `square`, `@square/web-sdk`, `lemonsqueezy`, `@lemonsqueezy/lemonsqueezy.js`, `paddle-sdk`, `@paddle/paddle-node-sdk`. Treat the Stripe family (`stripe` + `@stripe/stripe-js` + `@stripe/react-stripe-js`) as a single processor. Same for PayPal, Braintree, etc. Count all processor families that meet the three conditions (at least 3 importing files each).
- **Pass criteria:** 0 or 1 payment processors from the allowlist actively used. Report even on pass: "Canonical payment processor: [name] ([N] importing files)."
- **Fail criteria:** 2 or more payment processor families actively used — billing logic split across systems, refund flows fragmented.
- **Skip (N/A) when:** 0 payment processors from the allowlist appear in `RUNTIME_DEPS` (project does not handle payments).
- **Cross-reference:** For deeper payment security analysis, the Payment Security audit (`ecommerce-payment-security`) covers PCI compliance and webhook verification.
- **Detail on fail:** `"2 active payment processors: 'stripe' (10 files) AND '@paypal/checkout-server-sdk' (6 files). Two processors fragments billing — refunds, subscriptions, and webhooks must be handled twice."`
- **Remediation:** Two payment processors creates double the webhook logic, double the subscription management, and split customer records. Pick one as primary:

  ```ts
  // Good: single src/lib/billing.ts wraps your chosen processor
  // src/lib/billing.ts
  import Stripe from 'stripe'
  export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
  ```

  If you genuinely need two for regional reasons, isolate each behind a unified `Billing` interface so the rest of your code is processor-agnostic.

## External references

- iso-25010 functional-suitability

Taxons: dependency-coherence, data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000232
