⚡  3DS frictionless

3D Secure authentication that completes silently — no challenge popup. The auth happens in a hidden iframe, takes ~3-6 seconds, and you go straight to the success screen.

What you're testing. That 3DS data (cavv / eci / xid) is forwarded to NMI, that the auth flow doesn't visibly interrupt the customer, and that payment.succeeded still fires correctly.

What to do

  1. Click Run scenario.
  2. On the hosted page, enter 4000 0000 0000 2701, any future expiry, any CVV.
  3. Click Pay. The button might look stuck for a few seconds — that's the silent 3DS auth running in the background. You should NOT see an OTP popup.
  4. You'll land on the success screen as if 3DS wasn't there at all.
  5. payment.succeeded webhook arrives — check the envelope on the Event log for the auth fields.

The code (same as Standard)

No client-side change needed — the merchant's nmi3dsEnabled flag toggles 3DS on the hosted page. From the API side, the request body is identical.

// Node.js
const r = await fetch('https://app.sknpay.com/api/v1/payments', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.SKNPAY_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'order-' + Date.now(),
  },
  body: JSON.stringify({
    amount: 1500,                 // minor units — 15.00 USD
    currency: 'USD',
    description: '3DS frictionless test',
    customer_email: 'test@example.com',
    success_url: 'https://yoursite.com/thanks',
    cancel_url:  'https://yoursite.com/cart',
  }),
})
const payment = await r.json()
res.redirect(303, payment.url)        // → customer pays on hosted page
<?php
$ch = curl_init('https://app.sknpay.com/api/v1/payments');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST           => true,
  CURLOPT_HTTPHEADER     => [
    'Authorization: Bearer ' . getenv('SKNPAY_API_KEY'),
    'Content-Type: application/json',
    'Idempotency-Key: order-' . time(),
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'amount'         => 1500,
    'currency'       => 'USD',
    'description'    => '3DS frictionless test',
    'customer_email' => 'test@example.com',
    'success_url'    => 'https://yoursite.com/thanks',
    'cancel_url'     => 'https://yoursite.com/cart',
  ]),
]);
$payment = json_decode(curl_exec($ch), true);
header('Location: ' . $payment['url'], true, 303);
# Python (Flask + requests)
r = requests.post(
    'https://app.sknpay.com/api/v1/payments',
    headers={
        'Authorization':   f'Bearer {os.environ["SKNPAY_API_KEY"]}',
        'Content-Type':    'application/json',
        'Idempotency-Key': f'order-{int(time.time())}',
    },
    json={
        'amount':         1500,
        'currency':       'USD',
        'description':    '3DS frictionless test',
        'customer_email': 'test@example.com',
        'success_url':    'https://yoursite.com/thanks',
        'cancel_url':     'https://yoursite.com/cart',
    },
)
return redirect(r.json()['url'], code=303)
curl -X POST https://app.sknpay.com/api/v1/payments \
  -H "Authorization: Bearer $SKNPAY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-$(date +%s)" \
  -d '{
    "amount": 1500,
    "currency": "USD",
    "description": "3DS frictionless test",
    "customer_email": "test@example.com",
    "success_url": "https://yoursite.com/thanks",
    "cancel_url":  "https://yoursite.com/cart"
  }'