How to Accept Card Payments and Make Payouts (H2H)
You need to accept payments from bank cards (Payin) and make payouts to cards (Payout), where card data is transmitted through your Gateway. You are implementing an H2H flow (Host-to-Host) — direct exchange of card data between your microservice and provider, without user involvement.
1. Implement Payin Processing (POST /pay)
Section titled “1. Implement Payin Processing (POST /pay)”Step 1. Extract Payer’s Card Data
Section titled “Step 1. Extract Payer’s Card Data”Incoming request contains card data in params:
{ "params": { "pan": "4111111111111111", "cvv": "123", "expires": "12/25", "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.
Step 2. Convert Amount
Section titled “Step 2. Convert Amount”Parameter gateway_amount is always passed in kopecks (10000 = 100.00 USD).
Step 3. Send Request to Provider
Section titled “Step 3. Send Request to Provider”Use settings.api_key for authentication. Formulate payload according to your provider’s specification.
Working with settings parameters is described in Settings and Statuses.
Step 4. Handle Three Possible Response Scenarios
Section titled “Step 4. Handle Three Possible Response Scenarios”| Scenario | Response Status | Action |
|---|---|---|
| Payment successful | "approved" |
Return result: true, status: "approved" |
| 3DS required | "pending" |
Return redirect_request with ACS server URL |
| Payment declined | "declined" |
Return result: true, status: "declined" + details with reason |
Example response with 3DS:
{ "result": true, "status": "pending", "gateway_token": "txn_12345", "redirect_request": { "url": "https://acs-bank.com/challenge", "type": "post", "params": { "creq": "eyJhbGciOiJIUzI1NiIs..." } }, "logs": [...]}More details about
redirect_requestformat — in Basic Contracts (section 3).
Step 5. Return logs
Section titled “Step 5. Return logs”Record each HTTP request to provider in logs array:
{ "request": { "url": "...", "body": "..." }, "status": 200, "response": "{...}", "kind": "pay", "duration": 0.35}Field kind accepts values: "pay", "payout", "status", "callback".
2. Implement Payout Processing (POST /payout)
Section titled “2. Implement Payout Processing (POST /payout)”Step 1. Extract Recipient’s Data
Section titled “Step 1. Extract Recipient’s Data”Unlike Payin, here params contains the recipient’s card (PAN only) or bank_account:
{ "params": { "card": { "pan": "4111111111111111" }, "customer": { "email": "receiver@example.com" } }, "payment": { "token": "payout_tok_xxxx", "gateway_amount": 5000, "gateway_currency": "EUR" }}Step 2. Send Payout to Provider
Section titled “Step 2. Send Payout to Provider”Convert gateway_amount from kopecks and send request according to provider’s specification.
Step 3. Return Result
Section titled “Step 3. Return Result”If payout is instant:
{ "result": true, "status": "approved", "gateway_token": "payout_12345", "logs": [...]}If payout requires confirmation — return status: "pending" and wait for callback from provider.
More details about Payout request format — in Status and Payout.
3. Implement Status Check (POST /status)
Section titled “3. Implement Status Check (POST /status)”For asynchronous payments (3DS, pending), implement an endpoint that queries the provider for current transaction status and returns it in normalized form:
{ "result": true, "status": "approved", "amount": 10000, "currency": "USD", "logs": [...]}Detailed specification — in Status and Payout.
4. Implement Callback Handling (POST /callback)
Section titled “4. Implement Callback Handling (POST /callback)”When provider sends a webhook:
- Validate provider’s signature (mechanism depends on provider — JWT, RSA, HMAC).
- Normalize status (
approved/declined). - Send result to Business, signing with JWT key
settings.sign_key.
Full callback and JWT instruction — in Callbacks & JWT. Error handling is described in Errors and Timeouts.
Node.js Implementation Example
Section titled “Node.js Implementation Example”Payment (H2H Payin)
Section titled “Payment (H2H Payin)”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, card: { number: body.params.pan, cvv: body.params.cvv, expiry: body.params.expires, }, customer: body.params.customer, reference: body.payment.token, };
const start = Date.now(); const providerRes = await fetch("https://api.provider.com/v1/charge", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify(providerPayload), }); const providerData = await providerRes.json(); const duration = (Date.now() - start) / 1000;
let response = { result: true, gateway_token: providerData.id, amount: body.payment.gateway_amount, currency: body.payment.gateway_currency, logs: [ { request: { body: providerPayload }, status: providerRes.status, response: providerData, kind: "pay", duration, }, ], };
if (providerData.status === "success") { response.status = "approved"; response.details = "Payment successful"; } else if (providerData.status === "3ds_required") { response.status = "pending"; response.redirect_request = { url: providerData.acs_url, type: "post", params: { creq: providerData.creq }, }; } else { response.status = "declined"; response.details = providerData.message || "Payment declined"; }
res.status(200).json(response);});Payout (H2H Payout)
Section titled “Payout (H2H Payout)”app.post("/payout", 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, card: body.params.card?.pan, bank_account: body.params.bank_account, customer: body.params.customer, 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.payment.gateway_amount, currency: body.payment.gateway_currency, logs: [ { request: { body: providerPayload }, status: providerRes.status, response: data, kind: "payout", duration: 0.5, }, ], });});