FloPay
Guides

3D Secure

How FloPay handles 3D Secure authentication for card payments.

3D Secure (3DS)

3D Secure is a security protocol that adds an extra authentication step for card payments. FloPay handles 3DS automatically in both CheckoutForm and SplitCardForm.

Automatic Handling

In self-contained mode (no onTokenizedBody), 3DS is fully automatic:

  1. After processing a payment, the billing API may return type: '3ds_required' with a threeDSecureToken
  2. The form calls confirmCardPayment with the token, which triggers the 3DS modal
  3. The user completes authentication in the modal
  4. The form retries processPayment with the confirmed payment intent
  5. This loop repeats until the payment succeeds or fails

No code is needed on your part -- just provide onComplete and onError:

<CheckoutForm
  sessionId="sess_abc123"
  billingApiUrl="https://billing.example.com"
  email="user@example.com"
  onComplete={(result) => {
    // Payment succeeded, including any 3DS challenges
  }}
  onError={(error) => {
    // Payment failed (possibly after 3DS rejection)
  }}
/>

Delegated Mode (Manual 3DS)

When using onTokenizedBody, you receive the tokenized card data and handle backend submission yourself. You must check for 3DS responses and use the form ref to trigger authentication.

Backend Response Format

Your backend should return one of:

// Success
{ type: 'success', paymentIntentId: 'pi_...' }
 
// 3DS required
{ type: '3ds_required', threeDSecureToken: 'pi_..._secret_...' }

The threeDSecureToken is the Stripe PaymentIntent client secret that requires further action.

Handling 3DS in Delegated Mode

import { useRef } from 'react';
import { CheckoutForm, type CheckoutFormRef } from '@flopay/react';
// Also works with SplitCardForm and SplitCardFormRef
 
function DelegatedCheckout() {
  const formRef = useRef<CheckoutFormRef>(null);
 
  const handleTokenized = async (tokenizedBody: TokenizedBody) => {
    const res = await fetch('/api/payment', {
      method: 'POST',
      body: JSON.stringify(tokenizedBody),
    });
    const data = await res.json();
 
    if (data.type === '3ds_required') {
      // This opens the 3DS modal and waits for user authentication
      await formRef.current?.handleNextAction(data.threeDSecureToken);
 
      // After 3DS, retry your backend call
      const retryRes = await fetch('/api/payment/confirm', {
        method: 'POST',
        body: JSON.stringify({
          paymentIntentId: data.paymentIntentId,
        }),
      });
      // handle retry response...
    }
  };
 
  return (
    <CheckoutForm
      ref={formRef}
      sessionId="sess_abc123"
      billingApiUrl="https://billing.example.com"
      onTokenizedBody={handleTokenized}
    />
  );
}

The handleNextAction method calls Stripe's confirmCardPayment under the hood, which displays the 3DS iframe to the user. Make sure the form is still mounted when you call it.

When Does 3DS Trigger?

3DS is triggered by the card issuer, not by FloPay. Common triggers include:

  • Cards enrolled in 3DS programs (most European cards under SCA/PSD2)
  • High-value transactions
  • Transactions flagged as potentially fraudulent by the issuer
  • First-time use of a card on a new merchant

On this page