# BaaS
# Payment routing
# Authentication
Requests to the routing API are authenticated by a header x-auth. Formation of the value is in the Request Authentication.
To connect the service and get started, you need to contact Support Service (opens new window) or your supervising manager (opens new window).
# Sub-merchant registration
A sub-merchant can be a legal entity or an individual entrepreneur.
To get started, the Sub-merchant must independently create a Mandarin Personal Account through the standard registration (opens new window) procedure, going through the steps from (1) submitting an application to (4) completing the processing of the questionnaire.
In addition to the main process of registering a Personal Account, mass establishment of Personal Accounts through the registry is available. To use this script, please contact your supervising manager.
After approval of your Personal Account, you can proceed to the procedure of linking the created Sub-merchant Personal Account to your Marketplace account
# Creating a sub-merchant account
To create a Sub-merchant account, use token, which is available in the Sub-merchant’s Personal Account in the Routing section of the Project Settings.
| Parameter | Type | Required | Description |
|---|---|---|---|
| accountType | string | Yes | Account type for a legal entity takes on the value business. |
| token | string | Yes | Legal entity account token. |
The synchronous response contains id(account ID).
Request
curl --request POST \
--url https://secure.mandarinpay.com/api/v1/accounts/business \
--header 'Content-Type: application/json' \
--header 'x-auth: {{x_auth}}' \
--data-raw '{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudF9pZCI6MiwiaXNzIjoiMzAg0LzQsNGPIDIwMjIg0LMuIn0.unGctyIBkp4EHXw-7bFqWbbkmfhs2yCJ-jAKJGqRP_1"
}'
Response if of successful account creation (200 OK)
{
"id": "01dab7d8-be9b-4f87-ba91-801731787725",
"type": "Business"
}
Response if the request has not been created (400 Bad request)
{
"error": "Invalid request"
}
# Accepting payments (Routing)
Standard API requests are used to accept payments, in accordance with documentation. Possible single-stage payment, recurrent payment (auto-debit) and two-stage payment (in this case, authorization remains standard, and distribution of payments occurs upon completion of settlements).
In this case, the pay object is added to the requestrouting.destination, containing the payment distribution scheme. To accept payments: sum of values amountfrom object routing``must be equal to the top-levelamount`!
IMPORTANT! When using two-stage payment, the object routing``added to the second request with"action": "pay"`
The payer can enter card details on payment page or embedded Mandarin Custom Pay payment form.
| Parameter | Type | Required | Description |
|---|---|---|---|
| routing | Yes | An object containing routing parameters. | |
| routing.destination | string | Yes | An array containing payment recipients. |
| routing.destination. accountId | string | Yes | The recipient's account ID. |
| routing.destination. amount.value | string | Yes | The amount transferred to the recipient's account. To accept payments: the platform commission will be deducted from this amount. The separator is a dot. |
| routing.destination. amount.currency | string | Yes | Currency of the amount transferred to the recipient's account. Now alwaysRUB. |
| routing.destination. platformFeeAmount. value | string | Yes | Platform commission, based on the amount transferred to the recipient’s account. The separator is a dot. |
| routing.destination. platformFeeAmount. currency | string | Yes | Platform commission currency. Now alwaysRUB. |
| routing.destination. description | string | No | Description. |
In the example below, 5,000 rubles are debited from the payer’s card, which will be credited to the following recipients:
- 3950 rubles per account
16f90c5e-6bc3-11eb-9439-0242ac130002owned by a legal entity. - 50 rubles per platform account.
- 950 rubles per account
8ba85f01-8cc8-4161-b45d-ce6442e678aewhich belongs to another legal entity. - another 50 rubles per platform account. Total 100 rubles per platform account.
Request one-step payment
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'x-auth: {{x_auth}}' \
--data-raw '{
"payment": {
"action": "pay",
"orderId": "your_unique_order_id",
"price": "5000.00"
},
"orderActualTill": "2020-02-20 12:34:56+00:00"
},
"routing": {
"destination": [{
"accountId": "16f90c5e-6bc3-11eb-9439-0242ac130002",
"amount": {
"value": "4000.00",
"currency": "RUB"
},
"platformFeeAmount": {
"value": "50.00",
"currency": "RUB"
},
"description": ""
},
{
"accountId": "8ba85f01-8cc8-4161-b45d-ce6442e678ae",
"amount": {
"value": "1000.00",
"currency": "RUB"
},
"platformFeeAmount": {
"value": "50.00",
"currency": "RUB"
},
"description": ""
}
]
},
"customerInfo": {
"email": "user@example.com",
"phone": "+79001234567"
}
}'
The response to the request corresponds to the standard response for creating transactions.
Response in case of successful transaction creation (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
# Tokenization (Routing)
For tokenization, standard tokenization requests are used. Subsequently, the card token can be used to create recurrent debits, payments with a saved card in interactive mode or payments with a saved card without entering a cvv code and without passing 3d-secure
# Payment cancellation (Routing)
Standard API requests are used to refund a previously completed payment, in accordance with documentation.
To cancel a successful transaction to debit funds from your card ("action": "pay") use"action": "reversal"Andidpreviously completed transaction astarget.transaction
In this case, the pay object is added to the requestrouting.source, containing a scheme for distributing returns between Sub-merchant accounts. To accept payments: sum of values amountfrom object routing``must be equal to the top-levelprice`!
Cancellation is possible both for the entire transaction amount and for part of the amount (partial cancellation). An unlimited number of partial cancellations of one payment transaction is allowed within the payment amount. The active participation of the payer is not required.
| Parameter | Required | Parameter | Required | |
|---|---|---|---|---|
| routing | Yes | An object containing routing parameters. | ||
| routing.source | string | Yes | An array containing payers. | |
| routing.source.accountId | string | Yes | Payer account ID. | |
| routing.source.amount.value | string | Yes | The amount transferred to the payer's account. The separator is a dot. | |
| routing.source.amount.currency | string | Yes | Currency of the amount debited from the payer's account. Now alwaysRUB. | |
| routing.source.platformFeeAmount. value | string | Yes | Used when returning the withheld commission, can be equal to 0. The separator is a dot. | |
| routing.source.platformFeeAmount. currency | string | Yes | Platform commission currency. Now alwaysRUB. | |
| routing.source.description | string | No | Description. |
Synchronous response and asynchronous callback-notification can contain a wider set of parameters compared to the example.
Request
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'x-auth: {{x_auth}}' \
--data-raw '{
"payment": {
"action": "reversal",
"orderId": "your_unique_order_id",
"price": "5000.00"
},
"target": {
"transaction": "43913ddc000c4d3990fddbd3980c1725"
},
"customValues": [
{"name": "first parameter to save and show", "value": "p1"},
{"name": "second parameter to save and show", "value": "p2"}
],
"metadata": {
"first_parameter_to_callback_and_not_to_show": "p1",
"second_parameter_to_callback_and_not_to_show": "p2"
},
"routing": {
"source": [{
"accountId": "16f90c5e-6bc3-11eb-9439-0242ac130002",
"amount": {
"value": "4000.00",
"currency": "RUB"
},
"platformFeeAmount": {
"value": "50.00",
"currency": "RUB"
},
"description": ""
},
{
"accountId": "8ba85f01-8cc8-4161-b45d-ce6442e678ae",
"amount": {
"value": "1000.00",
"currency": "RUB"
},
"platformFeeAmount": {
"value": "50.00",
"currency": "RUB"
},
"description": ""
}
]
},
"urls": {
"callback": "http://...",
"return": "http://..."
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
# Transfer of funds (Routing)
Funds are transferred to Sub-merchants automatically on the next business day after the payment date.
# Working with virtual accounts
TESTING
Before integration, obtain test credentials and scripts in the BaaS section: OAuth application, Sandbox, test names for creating ESP.
# Environments
Two environments are available:
| Environment | Base URL |
|---|---|
| Sandbox | https://sandbox-payment-tokens.mandarin.io |
| Production | https://payment-tokens.mandarin.io |
Sandbox is used for integration testing.
# Integration process
- Get
access_token2. Create ESP (generate) - Send SMS code (
send) - User enters code
- Confirm the code (
verify) - Get ESP status (
status) or via webhook
# Authentication
Requests to the virtual account API are authenticated using the OAuth 2.0 (Bearer) protocol. Token generation is in the Request Authentication.
The examples below use the template--header 'Authorization: Bearer '.
Request for a token
curl --request POST \
--url https://accounts.mandarin.io/oauth/token/ \
--form 'grant_type=client_credentials' \
--form 'client_id={{client_id}}' \
--form 'client_secret={{client_secret}}' \
--form 'scope=payment-tokens:tokens.write payment-tokens:tokens.read payment-tokens:otp.write'
In the parameterscopepass rights for called API methods, separated by a space. Example:payment-tokens:tokens.write payment-tokens:tokens.read payment-tokens:otp.write.
# Creating a virtual account (ESP)
# Entry points
Scope required:payment-tokens:tokens.write
Sandbox:POST https://sandbox-payment-tokens.mandarin.io/api/v1/tokens/generateProduction:POST https://payment-tokens.mandarin.io/api/v1/tokens/generate
Request
curl --request POST \
--url https://payment-tokens.mandarin.io/api/v1/tokens/generate \
--header 'Mid: {{mid}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"first_name": "Николай",
"last_name": "Николаев",
"middle_name": "Николаевич",
"birth_date": "1993-12-22",
"citizenship": "RU",
"registration_address": "г. Москва, ул. Петровка, д.2, кв.1",
"living_address": "г. Москва, ул. Петровка, д.2, кв.1",
"document": {
"document_type": "Passport",
"serial": "1233",
"number": "123458",
"issue_date": "2002-04-24T15:23:14.969Z",
"birth_place": "г. Москва",
"issuer": "ОВД района Фили Давыдково",
"issue_code": "404-004",
"expiration_date": null
},
"inn": "777777777777",
"snils": "140-120-150 35",
"phones": [
{
"phone": "79017636353",
"type": "Personal"
}
],
"email": "ivanoff@mail.ru",
"fax": "string",
"phone_check": true,
"terms_agreement": true,
"is_public_official_person": false,
"presence_of_beneficiary": false,
"beneficiary_information": false,
"exist_fatf_government_bills": false,
"affiliation_with_foreign_taxpayers": false
}'
Response if the creation request was successfully sent (200 OK)
{
"id": "f289271a-914d-4987-88c4-d7cc66482c75",
"status": "Processing",
"created_at": "2026-03-16T10:00:36.0018804Z",
"processed_at": null,
"finished_at": null
}
Fieldidused aspayment_token_idin subsequent requests.
# Sending to OTP
Sends an SMS confirmation code.
Info
- standard code is valid for 2 minutes;
- if the code is entered incorrectly, then minus the attempt and you need to request a new code;
- only 10 attempts to enter the code.
POST https://payment-tokens.mandarin.io/api/v1/otp/{payment_token_id}/send
Request
curl --request POST \
--url https://payment-tokens.mandarin.io/api/v1/otp/{payment_token_id}/send \
--header 'Authorization: Bearer {{access_token}}'
Scope required:payment-tokens:otp.write
Response in case of successful sending of SMS code (200 OK)```json
{
"status": "pending",
"retries": 10,
"channel": "sms",
"channel_status": "queued"
}
**Description`channel_status`:**
| Meaning | Description |
|----------|----------|
| queued | Message queued for sending |
| delivered | Message delivered successfully |
| unknown | Delivery status unknown or delivery not confirmed |
> **Note:**
> - field`channel_status`is of an informational nature;
> - delivery status does not affect the success of OTP.
---
### OTP verification
Confirms SMS code.
`POST https://payment-tokens.mandarin.io/api/v1/otp/{payment_token_id}/verify`
**Request**
```bash
curl --request POST \
--url https://payment-tokens.mandarin.io/api/v1/otp/{payment_token_id}/verify \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"code": "0153"
}'
Scope required:payment-tokens:otp.write
Response in case of successful code confirmation (200 OK)```json
{
"verified": true,
"retries": 9,
"channel": "sms",
"channel_status": "delivered"
}
---
### Obtaining OTP status
`GET https://payment-tokens.mandarin.io/api/v1/otp/{payment_token_id}/status`
The method allows you to get:
- current OTP status;
- number of remaining attempts;
- SMS delivery status.
**Request**
```bash
curl --request GET \
--url https://payment-tokens.mandarin.io/api/v1/otp/{payment_token_id}/status \
--header 'Authorization: Bearer {{access_token}}'
Scope required:payment-tokens:otp.write
OTP awaiting confirmation (200 OK):```json
{
"status": "pending",
"retries": 9,
"channel_status": "queued"
}
**OTP successfully confirmed (`200 OK`):**```json
{
"status": "correct",
"retries": 9,
"channel_status": "delivered"
}
Description of valuesstatus:
| Meaning | Description |
|---|---|
| pending | Pending status |
| correct | Successful Confirmation Status |
Description of valueschannel_status:
| Meaning | Description |
|---|---|
| queued | Pending confirmation |
| delivered | Successfully confirmed |
| unknown | Status unknown or message not confirmed |
# Checking the ESP status
GET https://payment-tokens.mandarin.io/api/v1/tokens/{payment_token_id}/status
Request
curl --request GET \
--url https://payment-tokens.mandarin.io/api/v1/tokens/{payment_token_id}/status \
--header 'Authorization: Bearer {{access_token}}'
Scope required:payment-tokens:tokens.read
Response if the creation request was successfully sent (200 OK)```json
{
"id": "06dca2f1-4e1c-44e7-8848-3e9e3adba875",
"status": "Success",
"created_at": "2025-11-21T10:33:27.021823Z",
"processed_at": "2025-11-21T10:33:32.431478Z",
"finished_at": "2025-11-21T10:33:33.022295Z"
}
**Description of values`status`:**
| Status | Description |
|--------|-----------|
| Processing | wallet is created |
| Success | wallet activated |
| Failed | creation error |
| Blocked | wallet blocked |
| Suspended | wallet suspended |
| Deleted | wallet deleted (final status, recovery impossible) |
---
### Webhooks
To receive notifications about the status of the wallet, you can set up a webhook.
Webhook is sent using the POST method.
**Payload:**```json
{
"event_id": "17c5fdaf-3570-4d77-8966-6ae4505c2454",
"payment_token_id": "98a4a683-305e-4664-8d96-5f51d9e668f6",
"status": "Success",
"created_at": "2026-03-04T14:44:57.079171Z"
}
# Removing a blocked ESP
The method deletes an electronic wallet (ESP) in the statusBlockedand transfers it to final statusDeleted. Deletion is irreversible: ESP cannot be restored.
It is used when the ESP is blocked due to unsuccessful identification (SMEV) and a new wallet with corrected client data needs to be issued. Blocked ESP cannot be used in payments and disbursements; Unlocking via API is not supported.
DELETE https://payment-tokens.mandarin.io/api/v1/tokens/{payment_token_id}/wallet
The request body is not transmitted.payment_token_id— payment token Mandarin identifier (GUID).
Required scope:payment-tokens:tokens.write
Request
curl --request DELETE \
--url https://payment-tokens.mandarin.io/api/v1/tokens/{{payment_token_id}}/wallet \
--header 'Authorization: Bearer {{access_token}}' \
--header 'Mid: {{mid}}'
Response in case of successful deletion (200 OK)
{
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"status": "Deleted",
"created_at": "2026-07-24T12:00:00Z",
"processed_at": "2026-07-24T12:01:00Z",
"finished_at": "2026-07-24T15:00:00Z"
}
| Field | Type | Description |
|---|---|---|
| id | string | Payment token ID |
| status | string | Final status. If successful -Deleted |
| created_at | string | Token creation date (ISO 8601) |
| processed_at | string | Date of last processing (ISO 8601) |
| finished_at | string | End of life date (ISO 8601) |
WARNING
The method is executed only for ESP in the statusBlocked. For any other status, the API returns409 Conflict. The token must belong to the MID from the header Mid.
Response in case of error
| HTTP code | Description |
|---|---|
| 401 Unauthorized | Invalid or expired access token |
| 403 Forbidden | No scopepayment-tokens:tokens.writeor the token does not belong to MID |
| 404 Not Found | payment_token_idnot found |
| 409 Conflict | ESP status is notBlocked |
| 502 / 503 | Temporary provider error; ESP status in Mandarin does not change |
Upon successful deletion, if a webhook is configured for the project, you will receive a notification with the statusDeleted- see Webhooks.
# Reissue of ESP
- Check status:
GET /api/v1/tokens/{payment_token_id}/status(scopepayment-tokens:tokens.read). It makes sense to delete and re-release when blocked due to SMEV (SMEV_PASSPORT_INVALID,SMEV_INN_INVALID,SMEV_SNILS_INVALID). AtSANCTIONS_LIST_MATCHorBLOCKED_UNKNOWNContact Customer Service (opens new window). - Call delete - see request above. The operation is irreversible, old
payment_token_idgoes intoDeletedand is no longer used in operations. - Create a new ESP with corrected data: Creating a virtual account (ESP) → OTP (
send/verify) → waitSuccessin Checking ESP status. Newpayment_token_iduse in payments and disbursements as usual.
If the new ESP is blocked again, the client’s data most likely still does not pass the SMEV check. Check your passport details; If you are blocked again, please contact support.
# Obtaining ESP balance
GET https://payment-tokens.mandarin.io/api/v1/tokens/{payment_token_id}/wallet/balance
Request
curl --request GET \
--url https://payment-tokens.mandarin.io/api/v1/tokens/{{payment_token_id}}/wallet/balance \
--header 'Authorization: Bearer {{access_token}}' \
--header 'Mid: {{mid}}'
Response in case of successful receipt of balance (200 OK)
{
"wallet_id": "fsdfrg3amt",
"balance": 1500075,
"currency": "RUB"
}
# Payment transactions
All payment transactions are performed through a single endpoint:POST https://secure.mandarinpay.com/api/transactions.
The operation is asynchronous. The final status is transmitted in the callback notification.
# Internal Operations
Depositing funds to ESP (business → wallet)
Request
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Mid: {{mid}}' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "your_unique_order_id",
"price": "10.00",
"orderActualTill": "2026-02-20 12:34:56+00:00"
},
"target": {
"paymentToken": "payment_token_id"
},
"customerInfo": {
"email": "user@example.com",
"phone": "+79001234567"
},
"customValues": [
{ "name": "first parameter to save and show", "value": "p1" },
{ "name": "second parameter to save and show", "value": "p2" }
],
"metadata": {
"first_parameter_to_callback_and_not_to_show": "p1",
"second_parameter_to_callback_and_not_to_show": "p2"
},
"urls": {
"callback": "http://...",
"return": "http://..."
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
Debiting funds from ESP to business account (wallet → business)
Request
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Mid: {{mid}}' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "pay",
"orderId": "your_unique_orde1f313fr_id",
"price": "10.00",
"orderActualTill": "2026-02-20 12:34:56+00:00"
},
"target": {
"paymentToken": "payment_token_id"
},
"customerInfo": {
"email": "user@example.com",
"phone": "+79001234567"
},
"customValues": [
{ "name": "first parameter to save and show", "value": "p1" },
{ "name": "second parameter to save and show", "value": "p2" }
],
"metadata": {
"first_parameter_to_callback_and_not_to_show": "p1",
"second_parameter_to_callback_and_not_to_show": "p2"
},
"urls": {
"callback": "http://...",
"return": "http://..."
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "9b3c8a1f7e52d4096b0c2f8d3a5e17b4",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=f2a85b1c-3e9d-4871-a0b4-6c83d9f1e502",
"jsOperationId": "p3j8m267t1n9f4k5d0q8b3v6"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
```---
#### External operations (withdrawal of funds from ESP)
**To card by card number (wallet → knownCardNumber)**
**Request**
```bash
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "1756292752",
"price": "100"
},
"customerInfo": {
"email": "noreply@example.ru",
"phone": "+72244861047"
},
"source": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"knownCardNumber": "2202202244861047"
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "e7a2f1b409d83c6a25e0b8d14c9f3a72",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=7d14c9a3-0b8e-42f6-95c1-e3a6f0d8b247",
"jsOperationId": "k5h8293xq4m7p1tg6r0n8s2v3"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
To a bound card (wallet → bound card)
Request
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "1756292807",
"price": "100"
},
"customerInfo": {
"email": "noreply@example.ru",
"phone": "+72244861047"
},
"source": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"card": "63eed817-c56e-4aac-ad38-15113ed13722"
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "a7f4c2098e3b15d60f8a2e1b7c390d4f",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=1d8f3b9a-6e05-4c12-9a7d-0c4a8b5f3e12",
"jsOperationId": "j384m76t9k5f1d2w0n8q7x3r5"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
On SBP (1-step)
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "1756292895",
"price": "100"
},
"customerInfo": {
"phone": "+7926087626",
"email": "test@example.ru",
"firstName": "Иван",
"middleName": "Валерьевич",
"lastName": "Иванов"
},
"source": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"sbp": {
"bankId": "100000000008",
"bankBic": "044525593"
}
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
On SBP with confirmation (2-step)
Creating a payout
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "1756291493",
"price": "100"
},
"customerInfo": {
"phone": "+7926087626",
"email": "test@example.ru",
"firstName": "Иван",
"middleName": "Валерьевич",
"lastName": "Иванов"
},
"source": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"sbp": {
"bankId": "100000000008",
"bankBic": "044525593"
}
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "e8f2a1b90c7d34f562a8e0b9d1c7f3a5",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=9d4c7a1f-3b28-4e65-8f09-2a5b6c0d7e31",
"jsOperationId": "z5q8n2r7k1m4f9t0p3w6b8x2v"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
Receiving full name
curl --request POST \
--url https://secure.mandarinpay.com/api/sbp/{id}/status \
--header 'Authorization: Bearer {{access_token}}'
Payment confirmation
curl --request POST \
--url https://secure.mandarinpay.com/api/sbp/{id}/confirm \
--header 'Authorization: Bearer {{access_token}}'
Payment Rejected
curl --request POST \
--url https://secure.mandarinpay.com/api/sbp/{id}/reject \
--header 'Authorization: Bearer {{access_token}}'
```---
#### Transit operations
**Payment to card with transit through ESP**
**Request**
```bash
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "1756291886",
"price": "100"
},
"customerInfo": {
"email": "noreply@example.ru",
"phone": "+72244861047"
},
"transit": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"target": {
"card": "63eed817-c56e-4aac-ad38-15113ed13722"
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "8c3f9a2d1e7b0456f0c8d2e5a1b7f3d9",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
```---
#### Payment transactions
**Replenishment of ESP from a linked card**
**Request**
```bash
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "pay",
"orderId": "1756293713",
"price": "1"
},
"customerInfo": {
"email": "noreply@example.ru",
"phone": "+72244861047"
},
"source": {
"card": "63eed817-c56e-4aac-ad38-15113ed13722"
},
"destination": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13721"
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
# Replenishment of ESP from a card and routing for sub-merchants (card2wallet2account)
The transit operation combines replenishment of ESP from a card and distribution of funds to sub-merchant accounts in one request.
The amount is debited from the payer's cardpayment.price. The specified part of the funds is distributed to the accounts of sub-merchants throughrouting.destination. The operation takes place through a wallet (ESP), the identifier of which is indicated indestination.paymentToken.
Sum of valuesamount.valueVrouting.destinationmay be less transaction amount (payment.price). The undistributed balance remains on the wallet.
Sum of allrouting.destination.amount.valuemust not exceedpayment.price.
The operation is asynchronous. The final status is transmitted in the callback notification.
Prerequisites
Before calling the method:
- Connect BaaS / routing and the card2wallet2account script via Help Desk (opens new window) or Supervising Manager (opens new window).
- Create and activate a wallet (ESP) - see Creating a virtual account (ESP). ESP must be in status
Success— see Checking ESP status. - Create sub-merchants and get them
accountId— see [Sub-merchant registration](#sub-merchant registration) and [Creating a sub-merchant account](#sub-merchant account creation).
accountIdVrouting.destination— account ID from the responsePOST /api/v1/accounts/business, notmerchantIdfrom your Personal Account.
# Authentication
Requests are authenticated using the OAuth 2.0 protocol (Bearer). Token generation is in the Request Authentication.
Required scopes:
secure:transactions.write payment-tokens:tokens.read
The received token is transmitted in the headers:
Authorization: Bearer {{access_token}}
Mid: {{mid}}
Content-Type: application/json
# Entry point
POST https://secure.mandarinpay.com/api/transactions
# Request parameters
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
| payment | object | Да | Параметры платежа. |
| payment.action | string | Да | Действие. Для данной операции: pay. |
| payment.orderId | string | Да | Уникальный идентификатор заказа на стороне мерчанта. |
| payment.price | string | Да | Сумма списания с карты. Разделитель — точка. |
| payment.orderActualTill | string | Нет | Срок актуальности заказа. Формат: 2026-02-20 12:34:56+00:00. |
| customerInfo | object | Да | Данные плательщика. |
| customerInfo.email | string | Да | Email плательщика. |
| customerInfo.phone | string | Да | Телефон плательщика. |
| destination | object | Да | Назначение транзитного зачисления. |
| destination.paymentToken | string | Да | Идентификатор кошелька (ЭСП) для пополнения. |
| routing | object | Да | Объект, содержащий параметры роутинга. См. Прием платежей (Роутинг). |
| routing.destination | array | Да | Массив получателей (саб-мерчантов). |
| routing.destination[].accountId | string | Да | Идентификатор аккаунта саб-мерчанта. |
| routing.destination[].amount.value | string | Да | Сумма, перечисляемая на аккаунт получателя. Разделитель — точка. |
| routing.destination[].amount.currency | string | Нет | Валюта. Сейчас всегда RUB. |
| routing.destination[].platformFeeAmount.value | string | Нет | Комиссия платформы с суммы получателя. Разделитель — точка. |
| routing.destination[].platformFeeAmount.currency | string | Нет | Валюта комиссии. Сейчас всегда RUB. |
| routing.destination[].description | string | Нет | Описание. |
| urls | object | Нет | URL для callback и return. |
| urls.callback | string | Нет | URL для асинхронного callback-уведомления. |
| urls.return | string | Нет | URL для возврата пользователя после оплаты. |
| customValues | array | Нет | Пользовательские параметры для отображения. |
| metadata | object | Нет | Метаданные для callback. |
Example: 100 ₽ is debited from the card: 20 ₽ - to sub-merchant 1, 30 ₽ - to sub-merchant 2. The balance 50 ₽ remains in the wallet.
Request
curl --request POST \
--url https://secure.mandarinpay.com/api/transactions \
--header 'Mid: {{mid}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{access_token}}' \
--data-raw '{
"payment": {
"action": "pay",
"orderId": "test6",
"price": "100.00"
},
"customerInfo": {
"email": "noreply@test.ru",
"phone": "+79017636353"
},
"destination": {
"paymentToken": "payment_token_id"
},
"routing": {
"destination": [
{
"accountId": "id_сабмерчанта_1",
"amount": {
"value": "20.00",
"currency": "RUB"
},
"description": "test"
},
{
"accountId": "id_сабмерчанта_2",
"amount": {
"value": "30.00",
"currency": "RUB"
},
"description": "test"
}
]
},
"urls": {
"callback": "https://example.com/callback",
"return": "https://example.com/return"
}
}'
Response in case of successful transaction creation (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Answer if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
What happens after the request
- API returns
userWebLink— link to payment page or embedded payment form. - The payer enters card details.
- The final status comes asynchronously to
urls.callback. - Transfer of funds to sub-merchants to the current account - on the next business day, see Transfer of funds (Routing).
- The balance on your wallet can be checked through Receiving ESP balance.
Testing and diagnostics
Important notes
- For the card2wallet2account operation partial routing is allowed: amount in
routing.destinationmaybe lesspayment.price. - If the routing amount exceeds the transaction amount, the request is rejected.
- There is no separate endpoint for the operation - the scenario is formed by a combination
destination,routingand a standard request to create a transaction. - Do not consider the payment successful based on a synchronous response
200 OK— wait for the callback.
Important notes for merchants
All scenarios are implemented by a sequence of calls to existing API methods. There are no separate endpoints for “payment”, “acceptance”, “refinancing” or “distribution of funds”. Scenarios are generated by logic on the platform side. Sandbox and production use the same API paths. The difference lies in the service domains and merchant_id and secret values. All operations are asynchronous and require processing callback notifications.
← Payments Mandarin.ID →