Flow: testing 3-D Secure
In production the 3-D Secure challenge is enabled per integration — by acquirer, integration type and fraud rules. You cannot assume it will be there, nor that it will not: the integration has to work the same in both modes. The sandbox lets you check that — 3-D Secure is switched by the project setting card_3ds_enabled (v2.65.0).
Sandbox
Where it is switched. On the project card in the sandbox Admin UI, on by default. Without Admin UI access, ask the sandbox administrators to flip the setting for your project. The setting is per project: the protocol has no request field that would drop the challenge for a single payment.
What the setting changes, and what it does not
| Flow | 3-D Secure on | 3-D Secure off |
|---|---|---|
| Card payment via the payment form | The URL from the response points at the card-details form, followed by the confirmation page | Same URL and same form, the confirmation page is skipped: the payment is settled right after the details are submitted |
| Charge under a card contract (custom form, XML only) | The response carries an AuthorizationInfo block linking to the 3-D Secure confirmation page | The payment is settled synchronously inside CreateInvoice: no AuthorizationInfo block, AuthorizationType is SKIP, and the invoice statuses already carry the outcome |
| SBP and SberPay | No challenge by protocol design | Same — the setting does not affect them |
Reserved card endings 0001–0005 | Work | Work the same: they emulate an acquirer decline and have nothing to do with 3-D Secure |
How the flows differ in the request: the payment form is <UndefinedBankCardCustomer> without a CustomerContractInfo block, "type": "undefined_bank_card" in JSON; a charge under a contract is the same CreateInvoice with CustomerContractInfo/MerchantContractID.
The response payload changes in exactly one flow — charge by contract. For a payment through the form the difference is visible only to the payer (one screen fewer), while the partner gets the same response and the same payment notification. A check like “an AuthorizationUrl arrived, so the challenge is on” is wrong: for the payment form the URL always arrives.
Case 1 — payment form: the response is identical in both modes
- Create a card invoice with no contract block — as in the card flow.
- Follow
AuthorizationUrl(XML) orauthorization.(JSON) — the card-details form opens; test cardurl 4111 1111 1111 1111. - Setting on: after the details are submitted the confirmation page opens, and it accepts any 4 digits. Setting off: the final screen comes straight away, there is no confirmation page.
- Compare the
CreateInvoiceresponse and the payment notification across both runs — they match.
An automated test that counts redirects on the payment page or waits for the confirmation screen will break when the setting is flipped. Anchor on the callback and on the status from GetInvoice, not on the number of screens.
Case 2 — charge by contract: the outcome arrives in the response
The contract is bound up front — step 1 of the card binding flow. What follows is a regular CreateInvoice with <CustomerContractInfo>, and this is where the response depends on the setting.
Sandbox
<Response>
<CreateInvoiceResponse>
<BankCardCustomer>
<MerchantCustomerId>client_id</MerchantCustomerId>
</BankCardCustomer>
<InvoiceInfo>
<CustomerPrice amount="20.00" currency="RUB" includingVAT="true" />
<InvoiceStatus>WaitPaymentConfirmation</InvoiceStatus>
<PaymentStatus>NOT_PAID</PaymentStatus>
<AuthorizationType>AUTH_PAGE</AuthorizationType>
<IsAuthorized>false</IsAuthorized>
<InvoiceID>3c8d95c6-0006-0062-6727-4ea756dba332</InvoiceID>
<MerchantOrderID>order_126</MerchantOrderID>
</InvoiceInfo>
<BillingInfo>
<BillingChannel>BankRtp</BillingChannel>
</BillingInfo>
<CustomerContractInfo>
<ContractID>1c6495bb-0002-005f-f54e-d286731d55b9</ContractID>
<AuthorizationType>SKIP</AuthorizationType>
<ExpirationTimeUtc>27.04.2028 21:00:06</ExpirationTimeUtc>
<MerchantContractID>1234567890</MerchantContractID>
</CustomerContractInfo>
<AuthorizationInfo>
<AuthorizationUrl>https://pay-sandbox.digitalspb.com/checkout/a038a5ce-c580-4ff1-b480-e364b21a7474/pin?last4=4444</AuthorizationUrl>
</AuthorizationInfo>
</CreateInvoiceResponse>
<ResultCode>0</ResultCode>
<ResultDescription>OK</ResultDescription>
</Response><Response>
<CreateInvoiceResponse>
<BankCardCustomer>
<MerchantCustomerId>client_id</MerchantCustomerId>
</BankCardCustomer>
<InvoiceInfo>
<CustomerPrice amount="20.00" currency="RUB" includingVAT="true" />
<InvoiceStatus>Processed</InvoiceStatus>
<PaymentStatus>PAID</PaymentStatus>
<AuthorizationType>SKIP</AuthorizationType>
<IsAuthorized>true</IsAuthorized>
<InvoiceID>4d9e95c6-0006-0062-6727-4ea756dba333</InvoiceID>
<MerchantOrderID>order_127</MerchantOrderID>
</InvoiceInfo>
<BillingInfo>
<BillingChannel>BankRtp</BillingChannel>
</BillingInfo>
<CustomerContractInfo>
<ContractID>1c6495bb-0002-005f-f54e-d286731d55b9</ContractID>
<AuthorizationType>SKIP</AuthorizationType>
<ExpirationTimeUtc>27.04.2028 21:00:06</ExpirationTimeUtc>
<MerchantContractID>1234567890</MerchantContractID>
</CustomerContractInfo>
</CreateInvoiceResponse>
<ResultCode>0</ResultCode>
<ResultDescription>OK</ResultDescription>
</Response>What the code parsing this response has to handle:
- do not treat
AuthorizationInfoas mandatory — with the challenge off the block is absent, and indexing into it will throw; - take the outcome from
InvoiceStatus/PaymentStatus, not from the fact of a redirect: with the challenge off the invoice already arrives asProcessed/PAID; - do not confuse the two
AuthorizationTypefields: the one inCustomerContractInfoisSKIPin both modes (that one is about the contract — a card contract has no PIN confirmation), while the one inInvoiceInfoisAUTH_PAGEwith the challenge andSKIPwithout it.
The payment notification is sent as usual in both modes — that is what to rely on as the completion signal.
Case 3 — an acquirer decline with the challenge off
Reserved card endings work independently of 3-D Secure. Bind a contract with a card ending in 0001–0005 (see Bank card errors) and charge it with the setting off: the decline arrives in the same CreateInvoice response — InvoiceStatus Cancelled, PaymentStatus NOT_PAID. With the challenge on the same decline is only visible after the confirmation page has been passed. That is the main benefit of the no-3-D-Secure mode for tests: the negative path is checked with a single request, no browser involved.
Case 4 — SBP is not affected
SBP has no challenge by protocol design, so the SBP flow runs identically in both modes. If an SBP payment behaved differently after the setting was flipped, 3-D Secure is not the cause.
Checklist
- Case 1 run in both modes, response and callback matched.
- Case 2 run in both modes, the parser survived a response with no
AuthorizationInfo. - The outcome is taken from the statuses and the callback, not from the presence of
AuthorizationUrl. - Case 3 run: the decline by a reserved ending is visible in both modes.
- Before going live you know whether the challenge is on for your acquirer — but the code handles both.