Payment Orchestrator
Marketplace
3D Secure Payment

Split 3D Secure Payment

POST /payments/marketplace/3d is the split 2D payment with a 3DS challenge in the middle. The 3DS orchestration — redirect, bank challenge, signed callback, expiry sweeping — is exactly the core flow; this page covers only what split adds. For the full 3DS mechanics (redirect handling, callback semantics, status transitions) read Card — 3D Secure.

⚠️

All amounts are minor units (kuruş): "amount": 25000 = ₺250.00. Seller legs use minor-unit strings ("amountMinor": "15000"); the legs must add up to exactly amount.

How it works

Create — eligibility & split resolved up front

You call POST /payments/marketplace/3d with sellers[]. Seller eligibility is checked and the split computed at create time — before the buyer is ever redirected.

Buyer completes the challenge

The response carries a redirect (AWAITING_3D). You send the buyer to the returned URL; the bank challenge and the signed merchant callback work exactly as in the core 3DS flow.

Charge applies the split

On a successful challenge the charge executes and the pre-computed seller legs — commission, hold, net — are written to the transaction record.

Request

The body is the split 2D body plus your callbackUrl. Step-Up auth and Idempotency-Key apply as on every money-movement endpoint.

curl -X POST https://api-gateway.lunixi.com/api/v1/payments/marketplace/3d \
  -H "Authorization: Bearer <access-token>" \
  -H "X-Key-Id: <key-id>" \
  -H "X-Date: <iso-timestamp>" \
  -H "X-Nonce: <nonce>" \
  -H "X-Signature: <ed25519-signature>" \
  -H "Idempotency-Key: 0b7c9d4e-2f61-4c3a-9e5b-8a1d2c3e4f50" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25000,
    "currency": "TRY",
    "orderId": "ORD-2026-1043",
    "callbackUrl": "https://platform.example.com/payments/return",
    "card": {
      "cardNumber": "5400000000000005",
      "expireMonth": "12",
      "expireYear": "27",
      "cvcNumber": "123"
    },
    "sellers": [
      { "subDealerId": "d_8f2a...", "amountMinor": "15000" },
      { "subDealerId": "d_c41b...", "amountMinor": "10000" }
    ],
    "multiSellerPolicy": "ALL_OR_NOTHING"
  }'

Response

{
  "success": true,
  "inProgress": true,
  "intentId": "pay_01HV3K9X7QWE...",
  "status": "AWAITING_3D",
  "isRedirect": true,
  "redirectUrl": "/api/v1/payments/threed/pay_01HV3K9X7QWE..."
}

Send the buyer to redirectUrl (a Lunixi-hosted, single-use redirect — the provider's page is never exposed directly). From here the flow is byte-for-byte the core 3DS flow: bank challenge → ALL /payments/threed/callback/{token} → informational form POST to your callbackUrl (with the _signature + _timestamp platform integrity fields — do not trust it for fulfilment), plus the result webhook as the authoritative source.

What split changes in the 3DS flow

  • Eligibility is enforced at create, not at capture. An ineligible seller under ALL_OR_NOTHING produces the 422 immediately — the buyer never reaches the bank page for a payment that could not settle. See the reason codes.
  • ALL_OR_NOTHING means all legs or no charge. There is no state where the challenge succeeds but only part of the split applies: either the payment was admitted with all legs eligible, or it was rejected up front. With PARTIAL_ALLOWED, rejected legs were already marked at create time and only eligible legs settle.
  • Failure and abandonment behave like core. A failed challenge fails the payment (no leg settles); abandoned sessions are swept to EXPIRED automatically. There is nothing marketplace-specific to clean up.

Errors

StatusMeaningWhat to do
422Seller(s) ineligible — same shape and reason codes as split 2DFix the seller's state or drop the leg
400Validation failedFix the request body
401 / 403Step-Up auth failedRe-sign — see Authentication

A challenge-level failure (buyer fails 3DS at the bank) is not an HTTP error on this call — it arrives on your callbackUrl and as a payment.failed webhook, exactly like core 3DS.

Webhooks

Same as any 3DS payment: payment.authorized / payment.captured on success, payment.failed on a failed challenge, payment.expired for abandoned sessions — the full payment.* family.

Next steps