Skip to content

How to Accept P2P Payments and Make Payouts

You need to accept deposits via P2P transfers (user manually transfers money using provided details) and make payouts to cards or accounts. You are implementing a P2P flow: Gateway creates an order with the provider, returns transfer details, and then tracks the receipt of funds.

Difference from H2H: Payer’s card data is not transmitted — Gateway only creates an order and returns transfer details where the user should send money. What you’ll need: Provider’s API key, specification of their endpoints (pay, status, callback), test account/card for payouts.


1. Implement P2P Deposit (Payin) — Receiving Transfer Details

Section titled “1. Implement P2P Deposit (Payin) — Receiving Transfer Details”

Incoming request does not contain card data — only user information:

{
"method_name": "pay",
"params": {
"customer": {
"email": "user@example.com",
"ip": "192.168.1.1"
}
},
"payment": {
"token": "tok_xxxx",
"gateway_amount": 10000,
"gateway_currency": "USD"
},
"settings": {
"api_key": "provider_api_token"
}
}

Full request structure is described in Basic Contracts.

Send request to provider with amount, currency, and reference (payment token). Provider will create an order and return transfer details.

Response must contain status: "pending" and requisites field:

{
"result": true,
"status": "pending",
"gateway_token": "order_12345",
"amount": 10000,
"currency": "USD",
"requisites": {
"card": "461761****3933",
"holder": "IVAN I.",
"bank_name": "BankOfBaku",
"phone": "+77001234567"
},
"logs": [...]
}

Field requisites may contain:

  • card — recipient’s card number (masked)
  • holder — recipient’s name
  • bank_name — bank name
  • phone — phone number (for phone transfers)
  • account — account number
  • link — URL for redirection (SberPay, etc.)
  • deeplink — true if details contain a redirect link
  • qr_data — QR code for transfer

After user transfers money, provider sends webhook to POST /callback or status is checked via POST /status.

Callback implementation is described in Callbacks & JWT. Status checking — in Status and Payout.


{
"params": {
"amount": 50000,
"currency": "EUR",
"customer": { "email": "receiver@example.com" },
"card": { "pan": "4111111111111111" }
},
"payment": {
"token": "payout_tok_xxxx"
},
"settings": {
"api_key": "provider_api_token"
}
}

Important: In P2P Payout, amount and currency may be in params, not in payment — check your provider’s specification.

amount is passed in kopecks. Divide by 100 before sending to provider.

{
"result": true,
"status": "approved",
"gateway_token": "payout_12345",
"amount": 50000,
"currency": "EUR",
"details": "Payout successful",
"logs": [...]
}

If payout is not instant — return status: "pending" and wait for callback.


  • Create endpoint POST /pay
  • Create order with provider
  • Get transfer details
  • Return status: "pending" + requisites + gateway_token + logs
  • Implement POST /callback or POST /status for status finalization
  • Create endpoint POST /payout
  • Extract params.card.pan or params.bank_account
  • Convert params.amount (in kopecks) to base units for provider
  • Send payout to provider
  • Return logs with request/response details
  • If payout is not instant — return status: "pending" and wait for callback

app.post("/pay", async (req, res) => {
const body = req.body;
const amount = body.payment.gateway_amount / 100;
const apiKey = body.settings.api_key;
const providerPayload = {
amount,
currency: body.payment.gateway_currency,
reference: body.payment.token,
};
const providerRes = await fetch("https://api.provider.com/v1/orders", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: JSON.stringify(providerPayload),
});
const data = await providerRes.json();
res.status(200).json({
result: true,
status: "pending",
gateway_token: data.id,
amount: body.payment.gateway_amount,
currency: body.payment.gateway_currency,
requisites: {
card: data.card_number,
holder: data.beneficiary,
bank_name: data.bank_name,
},
logs: [
{
request: { body: providerPayload },
status: providerRes.status,
response: data,
kind: "pay",
duration: 0.5,
},
],
});
});
app.post("/payout", async (req, res) => {
const body = req.body;
const amount = body.params.amount / 100;
const apiKey = body.settings.api_key;
const providerPayload = {
amount,
currency: body.params.currency,
card: body.params.card?.pan,
bank_account: body.params.bank_account,
reference: body.payment.token,
};
const providerRes = await fetch("https://api.provider.com/v1/payouts", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: JSON.stringify(providerPayload),
});
const data = await providerRes.json();
res.status(200).json({
result: true,
status: data.status === "success" ? "approved" : "pending",
gateway_token: data.id,
amount: body.params.amount,
currency: body.params.currency,
logs: [
{
request: { body: providerPayload },
status: providerRes.status,
response: data,
kind: "payout",
duration: 0.5,
},
],
});
});