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.
Medium because split payment processors fragment billing state and refund logic, but the failure mode is financial inconsistency rather than immediate security compromise.
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.
// 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.
ID: ai-slop-code-drift.convention-drift.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:
// 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.