SCA and 3D Secure

SCA is why your European checkout drops customers

Strong Customer Authentication made two-factor the default on European card payments, and legacy 3D Secure abandoned 15% of checkouts doing it. EMV 3DS challenges 10-20% instead, if you send enough context for the issuer to judge. Six exemptions skip the challenge entirely, and the issuer can refuse every one of them. This is the 3D Secure material from chapter 17, the exemption engine from chapter 33, and the Stripe handling from chapter 9.

This is engineering guidance, not legal advice. Whether SCA applies depends on where your acquirer and your customer's issuer sit, and your acquirer is the one to confirm which exemptions you can request.

What is 3D Secure? (And Why Europeans Made It Mandatory)

3D Secure (3DS) authentication is the default expectation under EU Strong Customer Authentication (SCA) rules in PSD2 (with documented exemptions for low-value sub-€30 transactions, MITs, TRA, trusted beneficiaries, and recurring fixed amounts). 3DS also gives you liability shift: when authentication succeeds, responsibility for fraud chargebacks moves from you to the card issuer. Without 3DS, you carry 100% of fraud losses, which average 0.5-2% of transaction volume. EMV 3DS (3DS 2.x) cuts customer friction with risk-based authentication: only 10-20% of transactions get a customer challenge when you send rich context data (purchase history, device fingerprinting, shipping details). That's the fraud protection without the 15% checkout abandonment of legacy 3DS 1.0.

3D Secure is the credit card industry's version of two-factor authentication. When someone tries to buy something, their bank might pop up and say "prove you actually own this card" via SMS, app notification, or biometric check.

Europeans made it mandatory under Strong Customer Authentication, and honestly, it's not terrible. Yes, it adds friction. But it also shifts fraud liability from you to the bank. That's a good trade.

EMV 3DS (3DS 2.x): The Version That Doesn't Totally Suck

The original 3D Secure (1.0) was a UX nightmare - popup windows, confusing redirects, and a 15% abandonment rate. EMV 3DS (also called 3DS 2.x, with versions 2.2 and 2.3 currently active in the market) is much smarter. It uses risk-based authentication to decide which transactions actually need extra verification, so most customers never see a challenge - this is the frictionless flow. Only higher-risk transactions get pushed into the challenge flow.

Send rich context data (customer history, device info, shipping details) and issuers can make better risk decisions, which means fewer challenges for your legitimate customers.

SCA exemptions worth knowing under PSD2:

  • Low-value transactions - under €30 (with cumulative caps per card).
  • Merchant-initiated transactions (MITs) - subscriptions and stored-credential charges with prior consent.
  • Transaction Risk Analysis (TRA) - acquirers below specified fraud thresholds can request exemption for lower-risk transactions, up to value-based limits.
  • Trusted beneficiaries - the customer has added you to their bank's whitelist.
  • Recurring transactions of fixed amount - same merchant, same amount, after an initial authenticated transaction.
  • Corporate cards processed via secure B2B protocols.

The exemption is requested by the acquirer but granted (or refused) by the issuer, so don't assume an exemption flag guarantees no challenge.

def create_3ds_payment(amount, card_details, customer_info):
    """
    Create payment with 3D Secure authentication.

    3DS 2.0 is much smarter than the old version - it uses machine learning
    to decide which transactions actually need extra authentication. Many
    transactions go through without any customer friction.

    The key is providing rich context data so the bank can make an
    intelligent risk assessment.
    """
    payment_intent = {
        'amount': amount,
        'currency': 'EUR',  # SCA applies under PSD2; sub-€30 transactions may qualify for the low-value exemption
        'payment_method_data': {
            'type': 'card',
            'card': card_details
        },

        # This metadata is crucial for 3DS 2.0 risk assessment
        # The more context you provide, the less likely customers
        # will be challenged for authentication
        'metadata': {
            'customer_email': customer_info['email'],           # Helps identify returning customers
            'customer_phone': customer_info['phone'],           # Additional identity verification
            'shipping_address': customer_info['address'],       # Flags if different from billing
            'billing_address': customer_info['billing_address'], # Primary verification point
            'customer_age_days': customer_info.get('account_age_days'),  # Established customers = lower risk
            'previous_purchases': customer_info.get('purchase_count', 0), # Purchase history helps
            'delivery_timeframe': 'same_day',                   # Delivery speed affects risk score
            'purchase_category': 'digital_goods'                # Product type influences risk
        },

        # Request 3DS explicitly for EU compliance
        'payment_method_options':
        }
    }

    return payment_processor.create_payment(payment_intent)

def handle_3ds_authentication(payment_intent):
    """
    Handle the 3D Secure authentication flow.

    This is where customers might get redirected to their bank's website
    to prove they own the card. The flow varies by bank - some use SMS,
    others use apps, some use biometrics.

    Your job is to handle the redirect gracefully and process the result.
    """
    if payment_intent.status == 'requires_action':
        # Customer needs to complete 3DS authentication
        # This happens when the bank decides extra verification is needed

        # Get the authentication URL from the payment processor
        auth_url = payment_intent.next_action.redirect_to_url.url
        return_url = payment_intent.next_action.redirect_to_url.return_url

        return
    elif payment_intent.status == 'succeeded':
        # Payment went through without challenge, or customer successfully authenticated
        # You now have liability shift protection from the card network
        return
    elif payment_intent.status == 'requires_payment_method':
        # 3DS authentication failed - customer couldn't prove card ownership
        # This is different from a regular decline - the card might be valid
        # but the customer failed authentication
        return
    else:
        # Other failure reasons - expired card, insufficient funds, etc.
        return

The exemptions as code, with the TRA fraud-rate thresholds that decide eligibility. From chapter 33.

SCA Exemption Engine

from dataclasses import dataclass
from decimal import Decimal
from enum import Enum
from typing import Optional

class SCAExemption(Enum):
    LOW_VALUE = "low_value"           # < €30
    TRUSTED_BENEFICIARY = "trusted"   # Whitelisted recipient
    RECURRING = "recurring"           # Subsequent recurring payment
    LOW_RISK = "low_risk"             # TRA score < threshold
    CORPORATE = "corporate"           # Secure corporate payment

@dataclass
class SCADecision:
    sca_required: bool
    exemption: Optional[SCAExemption] = None
    risk_score: Optional[float] = None
    reason: Optional[str] = None

class SCAExemptionEngine:
    """Determine SCA exemptions per PSD2 RTS."""

    def __init__(self, tra_model, trusted_beneficiary_store):
        self.tra_model = tra_model
        self.trusted_store = trusted_beneficiary_store

        # Fraud rate thresholds for TRA exemption
        self.tra_thresholds =
    def evaluate(
        self,
        customer_id: str,
        amount: Decimal,
        creditor_iban: str,
        is_recurring: bool = False,
        is_corporate: bool = False
    ) -> SCADecision:
        """Evaluate transaction for SCA exemption eligibility."""

        # 1. Low value exemption (< €30)
        if amount < 30:
            # Check cumulative: 5 consecutive or €100 total triggers SCA
            if not self._exceeds_low_value_limits(customer_id, amount):
                return SCADecision(
                    sca_required=False,
                    exemption=SCAExemption.LOW_VALUE,
                    reason=f"Amount €{amount} below €30 threshold"
                )

        # 2. Trusted beneficiary exemption
        if self.trusted_store.is_trusted(customer_id, creditor_iban):
            return SCADecision(
                sca_required=False,
                exemption=SCAExemption.TRUSTED_BENEFICIARY,
                reason="Creditor on trusted beneficiary list"
            )

        # 3. Recurring payment exemption (after initial SCA)
        if is_recurring and self._has_initial_sca(customer_id, creditor_iban):
            return SCADecision(
                sca_required=False,
                exemption=SCAExemption.RECURRING,
                reason="Recurring payment with prior SCA"
            )

        # 4. Corporate exemption (dedicated secure protocols)
        if is_corporate:
            return SCADecision(
                sca_required=False,
                exemption=SCAExemption.CORPORATE,
                reason="Corporate payment with secure protocol"
            )

        # 5. Transaction Risk Analysis (TRA) exemption
        risk_score = self.tra_model.score_transaction(
            customer_id=customer_id,
            amount=float(amount),
            creditor_iban=creditor_iban
        )

        tra_eligible = self._check_tra_eligibility(amount, risk_score)
        if tra_eligible:
            return SCADecision(
                sca_required=False,
                exemption=SCAExemption.LOW_RISK,
                risk_score=risk_score,
                reason=f"TRA score {risk_score:.2f} below threshold for €{amount}"
            )

        # No exemption - SCA required
        return SCADecision(
            sca_required=True,
            risk_score=risk_score,
            reason="No applicable exemption"
        )

    def _check_tra_eligibility(self, amount: Decimal, risk_score: float) -> bool:
        """Check if transaction qualifies for TRA exemption."""
        # Must have fraud rate below threshold for amount tier
        our_fraud_rate = self._get_fraud_rate()

        for threshold_amount, max_fraud_rate in self.tra_thresholds.items():
            if amount <= threshold_amount and our_fraud_rate < max_fraud_rate:
                # Also check risk score - typically need < 30 risk points
                if risk_score < 0.30:
                    return True

        return False

    def _exceeds_low_value_limits(self, customer_id: str, amount: Decimal) -> bool:
        """Check if low-value cumulative limits exceeded."""
        # Get recent low-value transactions without SCA
        recent = self._get_recent_low_value_transactions(customer_id)

        consecutive_count = len(recent)
        cumulative_amount = sum(t.amount for t in recent) + amount

        # PSD2 limits: 5 consecutive OR €100 cumulative
        return consecutive_count >= 5 or cumulative_amount >= 100

SCA exemptions aren't automatic. You have to request them, and the issuing bank can still override you and require SCA anyway. If your TRA fraud rate goes over the threshold (0.13% for transactions under €500), you lose the exemption entirely for 6+ months. Monitor your fraud rate continuously: lose TRA eligibility and every transaction needs full two-factor authentication, which drops conversion 20-30%.

What the challenge looks like on Stripe. From chapter 9.

3D Secure Handling

3D Secure adds authentication for card payments (required in Europe under PSD2). Handle the requires_action state:

if intent.status == 'requires_action':
    # Customer needs to authenticate - redirect to 3DS flow
    return
elif intent.status == 'succeeded':
    fulfill_order(intent.metadata['order_id'])

Where the rules are heading, from chapter 26.

Strong Customer Authentication (SCA) under PSD2 RTS (Regulation (EU) 2018/389) remains the baseline for EEA card-not-present, account access, and remote payment initiation, with the usual two-factor + dynamic linking requirements and exemptions (TRA, low-value, trusted beneficiaries, MIT). PSD3 and the Payment Services Regulation (PSR) were politically agreed in November 2025, finalized in April 2026, and apply 21 months after publication in the Official Journal. Expect tighter fraud liability, harmonized SCA exemptions, stronger open banking access (PSD2 successor), and explicit treatment of IBAN-name checks. Treat the timetable as moving, and design SCA/3DS flows so authentication policy is configurable.

What the book adds

What chapters 17, 33 and 26 add

  • Tokenisation, and how it shrinks PCI scope
  • KYC and AML obligations
  • Open banking payment initiation under PSD2
  • Sanctions screening across 80,000+ entities
  • The PSD3 and PSR timetable

30-day money-back · Instant PDF and EPUB

All 42 chapters