# 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_id
  • client_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 --request POST \
  --url 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 --request POST \
  --url 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)

{
  "status": "pending",
  "retries": 10,
  "channel": "sms",
  "channel_status": "queued"
}

channel_status values:

Value Description
queued Message queued for delivery
delivered Message successfully delivered
unknown Delivery status unknown or delivery not confirmed

Note:

  • the channel_status field is informational only;
  • delivery status does not affect OTP success.

# 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,
  "channel": "sms",
  "channel_status": "delivered"
}

# Get OTP status

This method returns:

  • current OTP status;
  • remaining retry count;
  • SMS delivery status.

Entry point:

POST /payment-tokens/api/v1/otp/{payment_token_id}/status

Required scope:

payment-tokens:otp.write

Example request:

curl --request GET \
  --url https://payment-tokens.mandarin.io/api/v1/otp/{payment_token_id}/status \
--header 'Authorization: Bearer <token>'

OTP pending confirmation (200 OK):

{
  "status": "pending",
  "retries": 9,
  "channel_status": "queued"
}

OTP successfully confirmed (200 OK):

{
  "status": "correct",
  "retries": 9,
  "channel_status": "delivered"
}

status values:

Value Description
pending Pending
correct Successfully sent

channel_status values:

Value Description
queued Awaiting confirmation
delivered Successfully confirmed
unknown Status unknown or message not confirmed

# 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

  1. Get access token
  2. Create ESP (generate)
  3. Send SMS code (send)
  4. User enters code
  5. Confirm code (verify)
  6. Get ESP status (status) or via webhook

# Balance Receive ESP

Request

curl --request GET \
  --url 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 --request POST \
  --url 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 --request POST \
  --url 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 --request POST \
  --url 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 --request POST \
  --url 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 --request POST \
  --url 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 --request POST \
  --url 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 --request POST \
  --url 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 --request POST \
  --url 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"
}

# Topping up ESP from card and routing to sub-merchants (card2wallet2account)

This transit operation combines topping up an ESP from a card and distributing funds to sub-merchant accounts in a single request.

The amount payment.price is debited from the payer's card. The specified portion is distributed to sub-merchant accounts via routing.destination. The operation goes through the wallet (ESP) whose identifier is specified in destination.paymentToken.

The sum of amount.value in routing.destination may be less than the transaction amount (payment.price). The undistributed remainder stays on the wallet.

The sum of all routing.destination.amount.value must not exceed payment.price.

The operation is asynchronous. The final status is delivered in a callback notification.

Prerequisites

Before calling the method:

  1. Enable BaaS / payment routing and the card2wallet2account scenario via Support (opens new window) or your account manager.
  2. Create and activate a wallet (ESP) — see Creating a virtual account (ESP). The ESP must be in Success status — see ESP Statuses.
  3. Register sub-merchants and obtain their accountId — see Sub-merchant Registration and Creating a Sub-Merchant Account.

accountId in routing.destination is the account identifier from the POST /api/v1/accounts/business response, not the merchantId from the personal account.

# Authentication

Requests are authenticated via OAuth 2.0 (Bearer). See Authentication for token generation.

Required scopes:

secure:transactions.write payment-tokens:tokens.read

Pass the token in headers:

Authorization: Bearer {{access_token}}
Mid: {{mid}}
Content-Type: application/json

# Endpoint

POST https://secure.mandarinpay.com/api/transactions

# Request parameters

Parameter Type Required Description
payment object Yes Payment parameters.
payment.action string Yes Action. For this operation: pay.
payment.orderId string Yes Unique order identifier on the merchant side.
payment.price string Yes Amount debited from the card. Decimal separator is a dot.
payment.orderActualTill string No Order validity period. Format: 2026-02-20 12:34:56+00:00.
customerInfo object Yes Payer details.
customerInfo.email string Yes Payer email.
customerInfo.phone string Yes Payer phone.
destination object Yes Transit credit destination.
destination.paymentToken string Yes Wallet (ESP) identifier for topping up.
routing object Yes Routing parameters. See Payment Acceptance (Routing).
routing.destination array Yes Array of recipients (sub-merchants).
routing.destination[].accountId string Yes Sub-merchant account identifier.
routing.destination[].amount.value string Yes Amount transferred to the recipient account. Decimal separator is a dot.
routing.destination[].amount.currency string No Currency. Currently always RUB.
routing.destination[].platformFeeAmount.value string No Platform fee from the recipient amount. Decimal separator is a dot.
routing.destination[].platformFeeAmount.currency string No Fee currency. Currently always RUB.
routing.destination[].description string No Description.
urls object No Callback and return URLs.
urls.callback string No URL for asynchronous callback notification.
urls.return string No URL to redirect the user after payment.
customValues array No Custom parameters for display.
metadata object No Metadata for callback.

Example: 100 ₽ is debited from the card: 20 ₽ to sub-merchant 1, 30 ₽ to sub-merchant 2. The remaining 50 ₽ stays on 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": "sub_merchant_id_1",
                "amount": {
                    "value": "20.00",
                    "currency": "RUB"
                },
                "description": "test"
            },
            {
                "accountId": "sub_merchant_id_2",
                "amount": {
                    "value": "30.00",
                    "currency": "RUB"
                },
                "description": "test"
            }
        ]
    },
    "urls": {
        "callback": "https://example.com/callback",
        "return": "https://example.com/return"
    }
}'

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 was not created (400 Bad request)

{
    "error": "Invalid request"
}

What happens after the request

  1. The API returns userWebLink — a link to the payment page or embedded payment form.
  2. The payer enters card details.
  3. The final status arrives asynchronously at urls.callback.
  4. Transfer to sub-merchants' bank accounts occurs on the next business day — see Funds Transfer (Routing).
  5. The wallet balance can be checked via Balance Receive ESP.

Important notes

  • For card2wallet2account, partial routing is allowed: the sum in routing.destination may be less than payment.price.
  • If the routing sum exceeds the transaction amount, the request is rejected.
  • There is no separate endpoint for this operation — the scenario is formed by combining destination, routing, and the standard transaction creation request.
  • Do not consider the payment successful based on the synchronous 200 OK response — 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 "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.