# BaaS
# Payment Routing
To connect to the service and get started, please contact Support (opens new window) or your supervising manager (opens new window).
# Sub-merchant Registration
A sub-merchant can be a legal entity or a sole proprietor.
To get started, a sub-merchant must independently create a Mandarin Personal Account through the standard registration (opens new window) process, completing steps from (1) submitting an application to (4) completing the application.
In addition to the main Personal Account registration process, bulk Personal Account creation is available through the registry. To use this scenario, please contact your supervising manager.
Once your Personal Account is approved, you can proceed to linking the created Sub-Merchant Personal Account to your Marketplace account.
# Creating a Sub-Merchant Account
To create a Sub-Merchant account, use the token, which is available in the Routing section of the Project Settings in the Sub-Merchant Personal Account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| accountType | string | Yes | Account type. For a legal entity, it is set to business. |
| token | string | Yes | Legal entity account token. |
The synchronous response contains the account id.
Request
POST https://secure.mandarinpay.com/api/v1/accounts/business
{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudF9pZCI6MiwiaXNzIjoiMzAg0LzQsNGPIDIwMjIg0LMuIn0.unGctyIBkp4EHXw-7bFqWbbkmfhs2yCJ-jAKJGqRP_1"
}
Response if account is successfully created (200 OK)
{
"id": "01dab7d8-be9b-4f87-ba91-801731787725",
"type": "Business"
}
Response if the request is not created (400 Bad request)
{
"error": "Invalid request"
}
# Payment Acceptance (Routing)
Standard API requests for payment acceptance are used, as described in the documentation. Single-stage payment (api_payments.md#single-stage-payment), recurring payment (auto-debit) (api_payments.md#recurring-payment-auto-debit), and two-stage payment (api_payments.md#two-stage-payment) are possible (in this case, authorization remains standard, and payment distribution occurs upon completion of settlements).
In this case, the routing.destination object containing the payment distribution scheme is added to the pay request. To accept payments, the sum of the amount values from the routing object must equal the top-level amount!
IMPORTANT! When using two-step payment, the routing object is added to the second request with "action": "pay"
The payer can enter card details on the payment page or the 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 | Recipient account ID. |
| routing.destination. amount.value | string | Yes | The amount transferred to the recipient's account. For accepting payments, the platform fee will be deducted from this amount. Separator is a period. |
| routing.destination.amount.currency | string | Yes | The currency of the amount transferred to the recipient's account. Currently, it is always RUB. |
| routing.destination.platformFeeAmount.value | string | Yes | The platform fee, deducted from the amount transferred to the recipient's account. Separator is a period. |
| routing.destination.platformFeeAmount.currency | string | Yes | The currency of the platform fee. Currently, it is always RUB. |
| routing.destination.description | string | No | Description |
In the example below, 5,000 rubles are debited from the payer's card and will be credited to the following recipients:
- 3,950 rubles to account 16f90c5e-6bc3-11eb-9439-0242ac130002, which belongs to a legal entity.
- 50 rubles to the platform account.
- 950 rubles to account 8ba85f01-8cc8-4161-b45d-ce6442e678ae, which belongs to another legal entity.
- another 50 rubles to the platform account. Total: 100 rubles to the platform account.
Request one-step payment
POST https://api.mandarin.io/secure/transactions
{
"payment": {
"action": "pay",
"orderId": "your_unique_order_id",
"amount": {
"value": "5000.00",
"currency": "RUB"
},
"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 matches the standard response for creating transactions.
Response if the transaction is successfully created (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Response if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
# Tokenization (Routing)
Standard tokenization requests are used for tokenization. The card token can then be used to create recurring charges, payments with a saved card in interactive mode, or payments with a saved card without entering the CVV code and without completing the checkout process. 3d-secure](api_payments.md#payment-by-saved-card-without-entering-cvv-cvc-code-and-without-passing-3d-secure)
# Payment Cancellation (Routing)
Standard API requests for refunds on previously completed payments are used, in accordance with the documentation.
To cancel a successful card debit transaction ("action": "pay"), use "action": "reversal" and the id of the previously completed transaction as target.transaction.
In this case, the routing.source object is added to the pay request, containing the refund distribution scheme between Sub-merchant accounts. To accept payments: the sum of the amount values from the routing object must equal the top-level amount!
Cancellation is possible for the entire The transaction amount, as well as a partial cancellation (partial cancellation). An unlimited number of partial cancellations of a single payment transaction is allowed within the payment amount. 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 | The payer's account identifier. | |
| routing.source.amount.value | string | Yes | The amount transferred to the payer's account. Separator is a period. | |
| routing.source.amount.currency | string | Yes | The currency of the amount debited from the account Payer's Fee. Currently always RUB. | |
| routing.source.platformFeeAmount. value | string | Yes | Used when refunding the retained fee; can be 0. Separator: period. | |
| routing.source.platformFeeAmount. currency | string | Yes | Platform fee currency. Currently always RUB. | |
| routing.source.description | string | No | Description. |
The synchronous response and asynchronous callback-notification may contain a wider range of parameters than in the example.
Request
POST https://secure.mandarinpay.com/api/transactions
{
"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://..."
}
}
Reply if successful Transaction creation (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725"
}
Response if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
# Funds Transfer (Routing)
Funds are transferred to Sub-merchants automatically the next business day after the payment date.
# Working with Virtual Accounts
# Getting Started
Before starting the integration, please contact support to obtain the following information:
client_idclient_secret- access to the Sandbox environment
- (optional) webhook URL for receiving wallet status notifications
This information is used to obtain the **access_token` and make API requests.
# 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.
# Request Authentication
OAuth2 Bearer Tokens are used to access the API.
# Token Request
Production Entry Point:
POST https://accounts.mandarinbank.com/oauth/token/
Includes mandatory form-data parameters that are passed in the request body (the application.client_id and application.client_secret parameters are constant across the application and are stored on the client side).
| Parameter | Description | Example |
|---|---|---|
| grant_type | Authorization grant (always equal to client_credentials). | client_credentials |
| client_id | Client ID (equal to the application.client_id value provided by Mandarin). | VvPtlhcyldKtkuoUWY42 pErdrj4er2AwFoBWrn8n |
| client_secret | The client secret password (equal to the application.client_secret value provided by Mandarin). | uQfOIMsltZYL8x3XMqUGP5 iFM59PyFnKlN0UmD3Ihre2 Ry3AGazUAv5jPdUI4dBJqV 0Of6b9GFvWvzGahYnq2aVV xkxn9n4qWF57FP0C01Kp6l EtajhfYv3UZ2f4pAZ7 |
| scope | The requested scopes (access areas): e.g., transactions.read - "Read transactions". Separator - space. You can request any list of scopes, and the response will return the list of requested scopes that can be provided for this application. | payment-tokens:tokens.write payment-tokens:tokens.read payment-tokens:otp.write |
Example request:
curl --location 'https://accounts.mandarinbank.com/oauth/token/' \
--form 'grant_type=client_credentials' \
--form 'client_id=YOUR_CLIENT_ID' \
--form 'client_secret=YOUR_CLIENT_SECRET' \
--form 'scope=REQUESTED_SCOPES'
The scope parameter must specify the permissions for the API methods being called.
Example:
payment-tokens:tokens.write payment-tokens:tokens.read payment-tokens:otp.write
Permissions are passed separated by spaces, without additional characters.
The received token must be passed in the header of all API requests:
Authorization: Bearer {access_token}
# Creating a virtual account (ESP)
# Entry points
Sandbox:
POST https://sandbox-payment-tokens.mandarin.io/api/v1/tokens/generate
Production:
POST https://payment-tokens.mandarin.io/api/v1/tokens/generate
Required scope:
payment-tokens:tokens.write
Request
curl --location 'https://sandbox-payment-tokens.mandarin.io/api/v1/tokens/generate' \
--header 'Mid: 123' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {access_token}' \
--data-raw '{
"first_name": "Nikolai",
"last_name": "Nikolaev",
"middle_name": "Nikolaevich",
"birth_date": "1993-12-22",
"citizenship": "RU",
"registration_address": "Moscow, Petrovka st., 2, apt. 1",
"living_address": "Moscow, Petrovka st., 2, apt. 1",
"document": {
"document_type": "Passport",
"serial": "1233",
"number": "123458",
"issue_date": "2002-04-24T15:23:14.969Z",
"birth_place": "Moscow",
"issuer": "Fili Davydkovo District Department of Internal Affairs",
"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 create request is successful (200 OK)
{
"id": "f289271a-914d-4987-88c4-d7cc66482c75",
"status": "Processing",
"created_at": "2026-03-16T10:00:36.0018804Z",
"processed_at": null,
"finished_at": null
}
The id field is used as the payment_token_id in subsequent requests.
# Send to OTP
Sends an SMS confirmation code.
Entry point:
POST /payment-tokens/api/v1/otp/{payment_token_id}/send
Required scope:
payment-tokens:otp.write
Response if the SMS code was successfully sent (200 OK)
{
"to": "79017636353",
"ttl": 1773659797,
"status": "pending",
"retries": 10
}
# OTP verification
Confirms the SMS code.
Entry point:
POST /payment-tokens/api/v1/otp/{payment_token_id}/verify
Required scope:
payment-tokens:otp.write
Request:
{
"code": "0153"
}
Response if the code is successfully verified (200 OK)
{
"verified": true,
"retries": 9
}
# Checking the ESP status
Request:
GET /payment-tokens/api/v1/tokens/{payment_token_id}/status
Required scope:
payment-tokens:tokens.read
Response if the creation request is successfully sent (200 OK)
{
"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"
}
# Webhooks
You can set up a webhook to receive notifications about your wallet status.
The webhook is sent using the POST method.
Payload:
{
"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"
}
# ESP Statuses
| Status | Description |
|---|---|
| Processing | Wallet is being created |
| Success | Wallet is activated |
| Failed | Creation error |
| Blocked | Wallet is blocked |
| Suspended | Wallet suspended |
# Data for test requests
# Successful wallet creation
first_name: Successful
last_name: Success
middle_name: Successful
# Wallet creation error
first_name: Unsuccessful
last_name: Unsuccessful
middle_name: Unsuccessful
# Integration process
- Get access token
- Create ESP (
generate) - Send SMS code (
send) - User enters code
- Confirm code (
verify) - Get ESP status (
status) or via webhook
# Balance Receive ESP
Request
curl --location 'https://payment-tokens.mandarin.io/api/v1/tokens/{{payment_token_id}}/wallet/balance' \
--header 'Authorization: Bearer Token' \
--header 'Mid: 3019'
Response if balance is successfully received (200 OK)
{
"wallet_id": "fsdfrg3amt",
"balance": 1500075,
"currency": "RUB"
}
# Payment Transactions
All payment transactions are processed through a single endpoint.
POST https://secure.mandarinpay.com/api/transactions
The operation is asynchronous. The final status is transmitted in a callback notification.
# Internal Operations
Depositing funds to an ESP (business → wallet)
Request
curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Mid: 777' \
--header 'Authorization: Bearer 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"
}
Response if the transaction is not created (400 Bad Request)
{
"error": "Invalid request"
}
Withdrawing funds from the ESP to a business account (wallet → business)
Request
curl --location 'https://secure.mandarinpay.com/api/transactions'\
--header 'Content-Type: application/json' \
--header 'Mid: 777' \
--header 'Authorization: Bearer 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"
}
Response if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
# External transactions (withdrawals from ESP)
To a card by card number (wallet → knownCardNumber)
Request
curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer 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 if the transaction was successfully created (200 OK)
{
"id": "e7a2f1b409d83c6a25e0b8d14c9f3a72",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=7d14c9a3-0b8e-42f6-95c1-e3a6f0d8b247",
"jsOperationId": "k5h8293xq4m7p1tg6r0n8s2v3"
}
Response if the transaction was not created (400 Bad request)
{
"error": "Invalid request"
}
To a bound card (wallet → bound card)
Request
curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer 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 if the transaction was successfully created (200 OK)
{
"id": "a7f4c2098e3b15d60f8a2e1b7c390d4f",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=1d8f3b9a-6e05-4c12-9a7d-0c4a8b5f3e12",
"jsOperationId": "j384m76t9k5f1d2w0n8q7x3r5"
}
Response if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
On SBP (1-step)
curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "1756292895",
"price": "100"
},
"customerInfo": {
"phone": "+7926087626",
"email": "test@example.ru",
"firstName": "Ivan",
"middleName": "Valerievich",
"lastName": "Ivanov"
},
"source": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"sbp": {
"bankId": "100000000008",
"bankBic": "044525593"
}
}
}'
Response if the transaction is successfully created (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Response if the transaction is not created (400 Bad Request)
{
"error": "Invalid request"
}
On the FPS with confirmation (2-step)
Creating a payout
curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "1756291493",
"price": "100"
},
"customerInfo": {
"phone": "+7926087626",
"email": "test@example.ru",
"firstName": "Ivan",
"middleName": "Valerievich",
"lastName": "Ivanov"
},
"source": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"sbp": {
"bankId": "100000000008",
"bankBic": "044525593"
}
}
}'
Response if the transaction was successfully created (200 OK)
{
"id": "e8f2a1b90c7d34f562a8e0b9d1c7f3a5",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=9d4c7a1f-3b28-4e65-8f09-2a5b6c0d7e31",
"jsOperationId": "z5q8n2r7k1m4f9t0p3w6b8x2v"
}
Response if the transaction was not created (400 Bad request)
{
"error": "Invalid request"
}
Receiving Full Name
POST https://secure.mandarinpay.com/api/sbp/{id}/status
Payment Confirmation
POST https://secure.mandarinpay.com/api/sbp/{id}/confirm
Payment Rejection
POST https://secure.mandarinpay.com/api/sbp/{id}/reject
# Transit Transactions
Payout to Card with Transit via ESP
Request
curl --location 'https://secure.mandarinpay.com/api/transactions'\
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer 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 if the transaction was successfully created (200 OK)
{
"id": "8c3f9a2d1e7b0456f0c8d2e5a1b7f3d9",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Response if the transaction was not created (400 Bad request)
{
"error": "Invalid request"
}
# Payment Transactions
Topping up an ESP with a linked card
Request
curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer 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 if the transaction is successfully created (200 OK)
{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}
Response if the transaction is not created (400 Bad request)
{
"error": "Invalid request"
}
Important notes for merchants
All scenarios are implemented by a sequence of calls to existing API methods. There are no separate endpoints for "payout," "acceptance," "refinancing," or "funds distribution." Scenarios are generated by platform-side logic. The same API paths are used for sandbox and production. The differences lie in the service domains and the merchant_id and secret values. All operations are asynchronous and require callback notifications.