Cards & 3-D Secure
One method id — card — accepts every major card scheme, credit and debit, across your presentment currencies. This page covers the two 3-D Secure paths (frictionless and challenge), the AVS/CVV results on the risk object, and how network tokens keep stored cards current.
One method, every scheme
You pass "payment_method": "card" and Finscale routes the transaction across every card-capable provider in your set — you never integrate per scheme or per acquirer. The scheme comes back on the payment, in payment_method_details.card:
| Field | Type | Meaning |
|---|---|---|
brand | string | Card scheme, e.g. visa, mastercard. |
last4 | string | Last four digits of the card number. |
exp_month / exp_year | integer | Expiry (1–12 / four-digit year). |
funding | enum | credit, debit, prepaid, or unknown. |
country | string | Two-letter issuing country. |
Two things are card-only in the API. Cards are the sole method that supports capture_method: "manual" — authorize now, capture within 7 days, raise the hold with incremental authorization — covered in Authorization flows. And every card-family payment is risk-screened before routing, so the risk object is always populated — see the risk engine.
3-D Secure
3-D Secure (3DS) is the card networks' authentication layer: before authorization, the issuer verifies that the person paying controls the card. Finscale runs 3DS automatically whenever the issuer or local regulation requires it — strong customer authentication in the EEA and UK, for instance — or when authenticating measurably improves approval odds. The issuer then picks one of two paths:
- Frictionless. The issuer authenticates silently from device and transaction data. No customer step: the payment moves through
processingtosucceededin the same call chain,next_actionstaysnull, and you never seerequires_action. Most 3DS authentications finish this way. - Challenge. The issuer wants proof. The API returns
status: "requires_action"withnext_action.type: "redirect"— send the customer tonext_action.url. Finscale hosts the challenge round-trip (one-time code, banking-app approval) and returns the customer to yourreturn_url. Treat the webhook, not the redirect, as the outcome.
fraudulent disputes you have to defend — see Disputes.
Creating a card payment
Same call as every other method. Always pass return_url on card payments — you can't know in advance whether the issuer will demand a challenge:
curl https://api.finscale.dev/v1/payments \
-H "Authorization: Bearer sk_test_51FinscaleDemo…" \
-H "Idempotency-Key: idem_ord_9f21_0716" \
-H "Content-Type: application/json" \
-d '{
"amount": 4900,
"currency": "EUR",
"payment_method": "card",
"reference": "ord_9f21_0716",
"customer": { "email": "anna@example.com" },
"return_url": "https://shop.example.com/checkout/return"
}'
If the issuer wants a challenge, the response parks the payment in requires_action:
{
"id": "pay_8Q2mX4nT1cVb",
"object": "payment",
"amount": 4900,
"currency": "EUR",
"status": "requires_action",
"payment_method": "card",
"reference": "ord_9f21_0716",
"provider": "prov_eu_acq_01",
"next_action": {
"type": "redirect",
"url": "https://pay.finscale.dev/r/8Q2mX4nT"
},
"created_at": "2026-07-16T09:24:31Z",
"livemode": false
}
After the customer completes the challenge, the payment authorizes and (with the default capture_method: "automatic") captures. Fetch it — or wait for payment.succeeded — and the instrument snapshot and risk result are filled in:
{
"id": "pay_8Q2mX4nT1cVb",
"object": "payment",
"amount": 4900,
"currency": "EUR",
"status": "succeeded",
"payment_method": "card",
"payment_method_details": {
"card": {
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2028,
"funding": "credit",
"country": "NL"
}
},
"risk": {
"score": 17,
"decision": "approved",
"checks": { "avs_result": "pass", "cvv_result": "pass" }
},
"provider": "prov_eu_acq_01",
"next_action": null,
"created_at": "2026-07-16T09:24:31Z",
"livemode": false
}
AVS & CVV results
During authorization the issuer verifies the billing address (AVS) and the card verification code (CVV). Both results land on risk.checks and feed the payment's risk score:
| Check | pass | fail | unavailable |
|---|---|---|---|
avs_result | Billing address matched the issuer's record. | Address did not match — raises the risk score; rarely a hard decline on its own. | The issuer or method doesn't support AVS (common outside the US and UK). |
cvv_result | Code matched. | Code did not match — issuers usually decline these (incorrect_cvc). | Code not checked — e.g. a network-token or wallet charge that carries a cryptogram instead. |
A fail on either check never silently downgrades a payment: either the issuer declines (you get a card error) or the payment proceeds and the result simply raises risk.score, which can tip the decision to review or blocked.
Network tokens & vaulting
Card numbers never touch your servers. Hosted checkout — or Finscale-hosted fields in an API integration — collects the PAN inside Finscale's environment, built to PCI DSS Level 1 standards, and hands you a token. Saving a card produces a reusable pm_… instrument you charge with payment_method_id; see the overview for the save-and-charge pattern and Security & PCI for what this does to your compliance scope.
For vaulted cards, Finscale provisions a network token: a scheme-issued token bound to the card and to your merchant, used in place of the PAN on every subsequent charge. You don't opt in per payment — it happens automatically where the scheme supports it. What it buys you:
- Cards stay current. When the plastic is reissued, expires, or is replaced after loss, the scheme updates the token. Subscriptions keep charging without asking the customer to re-enter anything.
- Higher approval rates. Issuers treat network-token traffic as lower risk — token charges carry a per-transaction cryptogram.
- Less credential exposure. Providers see the token, never the PAN.
payment_method_details.cardstill reports the realbrandandlast4, so receipts and support flows don't change.
Refunds & chargebacks
Refunds are one call — POST /v1/refunds with the payment id, full or partial — and Finscale routes the reversal through whichever provider processed the original. The refund is pending until the scheme processes it; the customer's statement typically updates within a few business days (issuer timing, not yours). Details in Refunds.
Chargebacks are the card-specific dispute rail: the cardholder disputes the charge with their bank and the money is pulled back pending an outcome. Each one surfaces as a Dispute object (dp_…) with a normalized reason — fraudulent, product_not_received, duplicate, credit_not_processed, general — and a dispute.created webhook. Respond with evidence or accept before evidence_due_by; miss the deadline and the dispute is lost. The full state machine, evidence API, and escalation stages are in Disputes.
Settlement timing
Card money moves in two beats. Authorization is instant — the issuer places a hold at approval. Clearing starts at capture (immediately with capture_method: "automatic"; whenever you call capture with manual) and takes the scheme a day or two. Cleared funds then join your unified payout on the standard T+2 schedule, with interchange and scheme fees netted at settlement — the concepts, and the reports that itemize them, are in Settlement & payouts.
Error scenarios
| Scenario | What the API does |
|---|---|
| Issuer declines | 402 with type: "card_error" and a normalized code — card_declined, insufficient_funds, expired_card, incorrect_cvc, … The payment is failed with the same code in failure_code. Final declines never trigger failover — the issuer's no is a no. |
| Issuer demands authentication mid-flow | Finscale re-runs the payment through 3-D Secure automatically. If authentication can't be completed, the payment fails with authentication_required. |
| Challenge failed | The customer failed or refused the challenge: payment.failed fires with failure_code: "authentication_required". |
| Challenge abandoned | The payment sits in requires_action until the session expires, then fails. Cancel it earlier with POST /v1/payments/{id}/cancel if the customer restarts checkout. |
| Blocked by risk | No provider is attempted; the payment is failed with failure_code: "payment_blocked" — see Risk engine. |
| Provider timeout / outage | Not your problem: automatic failover retries the next provider inside the same API call — see Smart routing. |
Test cards
In test mode (sk_test_… keys), specific card numbers and amounts script every path above:
| Input | Result |
|---|---|
4242 4242 4242 4242 | Succeeds, frictionless. |
4000 0000 0000 3220 | Returns requires_action with a simulated 3-D Secure challenge page — approve or fail it there. |
4000 0000 0000 0002 | Declined: card_declined. |
4000 0000 0000 9995 | Declined: insufficient_funds. |
"amount": 4999 | First provider fails retriably → demonstrates failover. |
"amount": 4991 | Risk holds the payment for review. |
"amount": 4992 | Risk blocks the payment (payment_blocked). |
The full matrix, including webhook rehearsal, is in Testing.
Related
- Payment methods overview — every family, payload shapes, the 43-method catalog
- Authorization flows — manual capture, incremental authorization, void
- Disputes — the chargeback state machine and evidence API
- Risk engine — scores, decisions, and velocity rules
- Security & PCI — tokenization and your SAQ scope
- Method catalog on finscale.dev — regions and currencies at a glance