Direct Card API and 3D Secure
Advanced. Only for merchants that are PCI DSS compliant and receive raw card data on their own servers. Raw card access must also be enabled on your Dominipay account (otherwise you get
RAW_CARD_NOT_ENABLED). If this does not describe you, use the hosted checkout.
The direct card API is server to server, but 3D Secure still needs the cardholder browser. A backend only integration cannot complete cards that require authentication (common for EU and UK cards, and for countries where 3D Secure is forced).
Flow
- Your server calls
POST /wallet/card/chargewith the card. - If the response is
status: ok, the payment is approved. - If the response is
status: requires_action, sendclient_secretandpublishable_keyto the cardholder browser and runstripe.handleNextAction({ clientSecret }). - The cardholder authenticates with their bank.
- Your server calls
POST /wallet/card/confirmwithpayment_intent_id. - You receive the
paidcallback and the USD balance is credited.
1. Charge
POST /wallet/card/charge
Headers:
Content-Type: application/json
Idempotency-Key: order-1042-attempt-1
Body:
{
"token": "YOUR_TOKEN",
"secret": "YOUR_SECRET",
"amount": 100.00,
"card": {
"number": "4242424242424242",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
},
"email": "[email protected]",
"name": "John Doe",
"postback": "https://example.com/callbacks/card",
"return_url": "https://example.com/orders/1042/3ds-return"
}| Field | Required | Description |
|---|---|---|
amount | Yes | USD. Business minimum 5.00. |
card.number | Yes | 12 to 19 digits, no spaces. Must pass the Luhn check. |
card.exp_month | Yes | 1 to 12. |
card.exp_year | Yes | Four digits, 2024 to 2099. |
card.cvc | Yes | 3 or 4 digits, as a string. |
email, name | No | Cardholder details. Recommended. |
postback | No | Callback URL. Must be a valid URL. Without it you get no callbacks. |
return_url | No | Used when 3D Secure needs a full page redirect. A platform default is used when omitted. |
radar_session | No | Stripe Radar session id collected with Stripe.js. Improves fraud scoring. |
idempotency_key | No | Same as the Idempotency-Key header. |
Send an Idempotency-Key per logical payment. A retry with the same key returns the same charge instead of charging twice. Without a key, every request is a new charge.
Never log, store or echo card data. Responses never include it. All responses carry Cache-Control: no-store.
Approved
{
"status": "ok",
"idTransaction": "3f2b8c1e-7a4d-4e3b-9c2a-1d5e6f7a8b9c",
"payment_intent_id": "pi_3TestExample0000000001",
"stripe_status": "succeeded",
"requires_action": false
}The USD balance is credited asynchronously and a paid callback follows.
3D Secure required
{
"status": "requires_action",
"idTransaction": "3f2b8c1e-7a4d-4e3b-9c2a-1d5e6f7a8b9c",
"payment_intent_id": "pi_3TestExample0000000001",
"client_secret": "pi_3TestExample0000000001_secret_TestExample",
"next_action": { "type": "use_stripe_sdk" },
"publishable_key": "pk_test_YOUR_PUBLISHABLE_KEY",
"message": "Card requires 3DS authentication. Run stripe.handleNextAction(client_secret) on the customer browser, then POST /api/wallet/card/confirm with payment_intent_id."
}Processing
status: processing means the result is not known yet. Wait for the callback or check the status endpoint.
Declined (HTTP 422)
{
"status": "error",
"message": "Your card has insufficient funds.",
"error_code": "insufficient_funds",
"decline_code": "insufficient_funds",
"idTransaction": "3f2b8c1e-7a4d-4e3b-9c2a-1d5e6f7a8b9c"
}Each direct API attempt is final. To retry, call /wallet/card/charge again with a new idempotency key. Do not retry hard declines such as stolen_card, lost_card or fraudulent.
2. Run 3D Secure in the browser
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe(publishableKey); // publishable_key from the charge response
const { error, paymentIntent } = await stripe.handleNextAction({ clientSecret });
// Whatever the outcome, tell your server to call /wallet/card/confirm.
</script>3. Confirm
POST /wallet/card/confirm
{
"token": "YOUR_TOKEN",
"secret": "YOUR_SECRET",
"payment_intent_id": "pi_3TestExample0000000001"
}Responses:
| Result | Body |
|---|---|
| Approved (200) | {"status": "ok", "idTransaction": "...", "payment_intent_id": "...", "stripe_status": "succeeded"} |
| Another challenge (200, rare) | status: requires_action with a new client_secret and next_action. Repeat step 2. |
| Processing (200) | status: processing. |
| Failed (422) | status: error with error_code, decline_code, stripe_status (requires_payment_method or canceled) and message. |
| Not yours (404) | error_code: NOT_FOUND. |
| Unexpected (422) | error_code: CONFIRM_FAILED. |
Calling confirm for a payment that already succeeded returns status: ok again, so it is safe to repeat.
If you never call confirm after 3D Secure, the payment stays in requires_action and is not approved.
Callbacks
Direct API charges use the same callbacks as the hosted checkout (paid, refunded, partially_refunded, disputes). A failure that you already received synchronously (422 from /charge or /confirm) does not produce a callback. A failure that the processor reports before you call /confirm (for example a failed 3D Secure) produces a cancelled callback with failure_code. Treat the synchronous response as the source of truth and the callback as a backup. See Webhooks.
Errors specific to this API
| HTTP | error_code | Meaning |
|---|---|---|
| 422 | INVALID_CARD_NUMBER | Fails the Luhn check. Nothing was sent to the processor. |
| 422 | cvc_check_failed | The issuer reported a wrong CVC. The attempt is recorded as failed. |
| 422 | RAW_CARD_NOT_ENABLED | Raw card access is not enabled for the account. |
| 422 | PM_CREATE_FAILED, PI_CREATE_FAILED | Unexpected processor error. Check the status before retrying. |
| 422 | issuer or processor code | Declined. See Error codes. |
| 429 | RATE_LIMITED | Platform wide limit reached. Back off. |
| 429 | CARD_TESTING_BLOCKED, CARD_VELOCITY_BLOCKED | This card is temporarily blocked after repeated failures or approvals. |
| 503 | STRIPE_TEMPORARILY_UNAVAILABLE | Processor unreachable. No charge was created. Safe to retry with the same idempotency key. |
Validation errors on this API return only the names of the invalid fields, never their values:
{
"status": "error",
"message": "Validation failed",
"errors": ["card.number", "card.cvc"]
}Updated about 2 hours ago
