P PSP 商户文档
PSP Gateway API 版本 2026-07-01 商户服务端接入

PSP 商户 API 对接文档

本文档按 Apifox 常见接口文档方式组织:先说明公共规则,再逐个接口给出接口地址、请求参数、请求示例、返回响应和字段说明。商户按顺序完成即可自主接入。

完整字段、枚举和 schema 可在 API Reference 查看;本文档保留最常用、最容易接错的字段说明和可复制示例。

接入流程

  1. 完成接入凭证配置:在 Merchant Portal 的「接入凭证」中创建 Gateway Secret,生成或粘贴商户 Ed25519 公钥。
  2. 保存一次性密钥:创建成功后立即保存 API ID、API Key 和商户私钥;API Key 和私钥不能从 PSP 恢复。
  3. 配置 Webhook:在「Webhook」页面按商户账户配置 HTTPS 回调地址,并复制该账户的 PSP 验签公钥。
  4. 实现公共请求头:请求带上 X-API-ID、X-API-Key、X-Timestamp 和签名;写请求再带幂等键。
  5. 实现签名与幂等:写请求必须带 Idempotency-Key,并把幂等键放入签名字符串。
  6. 优先联调余额与 PIX 代收:先验证认证链路,再联调异步 Webhook。
  7. 接入代付和其他支付方式:代收、代付最终状态以查询接口或 Webhook 为准。

公共信息

环境变量

变量示例值说明
GATEWAY_BASE_URLhttps://gateway.example.comPSP Gateway API 地址。
API_KEYlive_xxx / test_xxx商户后台创建,服务端保存。
MERCHANT_ACCOUNT_CODEBR001商户账户编码,每次调用 Gateway 必传。
ACCOUNT_ID550e8400-e29b-41d4-a716-446655440000查询余额、配置账户相关功能时使用。
WEBHOOK_KEY_IDprod-main-2026-06Webhook 账户卡片显示的 PSP 签名 Key ID,用于识别当前 Webhook 公钥。
PRIVATE_KEY_PEM-----BEGIN OPENSSH PRIVATE KEY-----...商户 Ed25519 私钥,只保存在商户服务端。
PUBLIC_KEY_OPENSSHssh-ed25519 AAAA...上传到商户后台的 Ed25519 公钥。
WEBHOOK_PUBLIC_KEY_BASE64base64_ed25519_public_keyPSP Webhook 公钥,用于商户验签。

金额规则

字段类型说明
amountinteger金额使用最小货币单位。示例:BRL 100.00 传 10000。
currencystring币种大写三位 ISO 代码,例如 BRL、MXN、INR。
merchant_order_idstring商户订单号,建议全局唯一,便于对账和幂等排查。
metadataobject商户自定义信息,PSP 原样保存和返回。

后台配置

以下步骤对应 Merchant Portal 当前页面和弹框。文档中的“商户请求签名公钥”和“PSP Webhook 验签公钥”是两把不同的公钥,请分别按对应章节配置。

Merchant Portal 接入凭证页面和创建 Gateway Secret 弹框截图
图 1:进入「接入凭证」后点击「创建 Gateway Secret」,在弹框中选择账户、用途和环境,并填写 Ed25519 公钥。
Merchant Portal Webhook 页面和账户配置弹框截图
图 2:进入「Webhook」按账户配置 HTTPS 回调地址;保存后从账户卡片复制或下载 PSP 验签公钥。

1. 创建 Gateway Secret(接入凭证)

Merchant Portal Gateway Secret 调用凭证创建弹框截图
图 3:Gateway Secret 创建弹框。按账户选择密钥用途,粘贴或生成 Ed25519 公钥,再填写说明、IP 白名单和权限范围。
  1. 登录 Merchant Portal,进入「接入凭证」。在「Gateway Secret 调用凭证」区域点击「创建 Gateway Secret」。
  2. 在弹框中选择要调用的「商户账户」、密钥用途(「代收」或「转账」)和环境(staging 或 production)。每个账户、用途和环境应使用对应的凭证。
  3. 在「Ed25519 公钥」字段中粘贴已有公钥,或点击「在浏览器中生成密钥对」由浏览器本地生成。生成私钥不会上传到 PSP。
  4. 按需填写「描述」「Gateway IP 白名单」、权限范围和过期时间。IP 白名单留空表示拒绝所有外部 Gateway 请求。
  5. 提交后保存弹框中的 API ID、API Key、Ed25519 Public Key 和 SHA-256 Fingerprint。API ID、API Key、公钥和权限创建后不能原地修改。
  6. 立即点击「下载私钥(仅此一次)」和「下载公钥」。API Key 和私钥只在创建流程中显示或下载一次;私钥只存放在商户服务端密钥系统。
PSP 不会接收、保存或恢复 Gateway 私钥。轮换凭证时,请创建新的 Gateway Secret 和密钥对,先完成服务端切换,再撤销旧凭证。

2. 配置账户级 Webhook

  1. 进入 Merchant Portal 的「Webhook」页面,使用账户筛选找到目标商户账户。Webhook 配置按账户分别生效。
  2. 在账户卡片中点击配置,填写完整的 HTTPS 回调 URL、需要接收的 event type、允许域名、自定义 headers JSON 和描述,然后启用配置并保存。
  3. 保存后,在同一账户卡片中点击「复制验签公钥」或「下载当前公钥」,将公钥配置到商户 Webhook 接收服务。
  4. 收到通知时,使用该账户的 PSP Webhook 公钥验证 X-Timestamp + "." + raw_body 的 Ed25519 签名。不要使用接入凭证页面上传的商户请求签名公钥。

认证方式

商户 Gateway 使用 Account Secret 认证:X-API-ID 定位账户凭证,X-API-Key 校验密钥,X-Timestamp 防止过期请求,X-Signature 使用 Ed25519 验证请求。Invoice、Transfer、Query Gateway 使用对应类型的 Account Secret。

公共 Header 参数

参数名类型必填说明
X-API-IDstring是Account Secret 的 API ID。
X-API-Keystring是API Key,Gateway 实际认证来源。
X-Timestampinteger是Unix 秒级时间戳,默认允许 60 秒时间差。
X-Signaturestring是Ed25519 签名结果,标准 Base64 或 Raw Base64 编码。
Idempotency-Keystring写请求必填幂等键。兼容头 X-Idempotency-Key 也可使用。
Content-TypestringPOST 必填固定为 application/json。

密钥生成与格式

商户需要自己生成一对 Ed25519 密钥。私钥只保存在商户服务端,用来签名 Gateway 请求;公钥上传到商户后台,PSP 用它来验证请求签名。

推荐直接在「创建 Gateway Secret」弹框中点击「在浏览器中生成密钥对」。密钥在浏览器本地生成,PSP 只接收公钥。也可以在商户自己的服务器或本地安全环境生成后,把公钥粘贴到弹框。

密钥参数

选项推荐值说明
密钥类型ssh-ed25519Gateway 请求签名固定使用 Ed25519。
密钥格式OPENSSH 或 32-byte Base64后台支持一行 ssh-ed25519 OpenSSH 公钥,也支持 32 字节公钥的 Base64 表示。
密钥用途请求签名密钥这不是 Webhook 验签公钥。Webhook 公钥由 PSP 提供给商户。
保存位置商户服务端密钥系统不要放在前端代码、App 包、Git 仓库或可公开访问的配置文件里。

需要上传的公钥格式

商户后台上传公钥时,推荐使用 OpenSSH 公钥格式,整段复制为一行:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExamplePublicKeyBase64 merchant-prod-2026-07

商户服务端保存的私钥格式

商户服务端保存私钥时,推荐保存完整 OpenSSH 私钥 PEM 文本,包括首尾行:

-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----

本地命令生成方式(可选)

如果商户技术团队可以使用命令行,更推荐本地生成:

ssh-keygen -t ed25519 -f ./psp_gateway_ed25519 -C "merchant-prod-2026-07"
文件用途处理方式
psp_gateway_ed25519私钥只保存到商户服务端密钥系统,用于请求签名。
psp_gateway_ed25519.pub公钥复制文件内容,上传到商户后台。

两类公钥不要混淆

类型谁生成私钥由谁保存公钥由谁使用后台位置与用途
商户请求签名公钥商户商户PSP「接入凭证」创建 Gateway Secret 时上传;PSP 用它验证商户 API 请求。
PSP Webhook 验签公钥PSPPSP商户「Webhook」账户卡片中复制或下载;商户用它验证 PSP Webhook。

请求签名

签名算法

算法Ed25519
编码X-Signature 使用标准 Base64 或 Raw Base64 编码。
签名内容签名以下 9 行规范化内容,使用商户 Ed25519 私钥签名。
验签公钥商户在后台上传的 Ed25519 公钥。

读请求签名字符串

读请求也必须签名。签名字符串固定为:

<HTTP_METHOD>
<API_ID>
<MERCHANT_ACCOUNT_ID>
<ESCAPED_PATH>
<CANONICAL_QUERY>
<X-Timestamp>
<IDEMPOTENCY_KEY_OR_EMPTY>
<SHA256_HEX_OF_RAW_BODY>

写请求签名字符串

完整签名串还需以 v1 作为第一行,方法使用大写;query 参数按键和值排序后 URL 编码。无 body 时使用空 body 的 SHA-256。

签名示例

v1
api_test_001
550e8400-e29b-41d4-a716-446655440000
POST
/api/v1/payments/intents

1781440000
ORD-10001
<sha256_hex_of_raw_body>
ESCAPED_PATH 只包含请求路径,CANONICAL_QUERY 是排序并编码后的 query。body 先按实际发送的 raw bytes 计算 SHA-256,再放入签名串。

签名示例代码

Node.js

const crypto = require("crypto");

function signRequest({ method, path, canonicalQuery = "", apiId, merchantAccountId, body = "", idempotencyKey = "", privateKeyPem }) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const bodyHash = crypto.createHash("sha256").update(body, "utf8").digest("hex");
  const payload = ["v1", apiId, merchantAccountId, method.toUpperCase(), path, canonicalQuery, timestamp, idempotencyKey, bodyHash].join("\n");
  const signature = crypto.sign(null, Buffer.from(payload), privateKeyPem).toString("base64");

  return { timestamp, signature, payload };
}

module.exports = { signRequest };

Java

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.security.MessageDigest;
import java.util.HexFormat;

public class PspSigner {
  static PrivateKey loadPrivateKey(String privateKeyPem) throws Exception {
    String pem = privateKeyPem
      .replace("-----BEGIN PRIVATE KEY-----", "")
      .replace("-----END PRIVATE KEY-----", "")
      .replaceAll("\\s", "");
    byte[] keyBytes = Base64.getDecoder().decode(pem);
    return KeyFactory.getInstance("Ed25519").generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
  }

  static String sign(String apiId, String merchantAccountId, String method, String path, String canonicalQuery, long timestamp, String idempotencyKey, String body, PrivateKey key) throws Exception {
    String bodyHash = HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(body.getBytes(StandardCharsets.UTF_8)));
    String payload = "v1\n" + apiId + "\n" + merchantAccountId + "\n" + method.toUpperCase() + "\n" + path + "\n" + canonicalQuery + "\n" + timestamp + "\n" + idempotencyKey + "\n" + bodyHash;

    Signature signer = Signature.getInstance("Ed25519");
    signer.initSign(key);
    signer.update(payload.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(signer.sign());
  }
}

PHP

<?php
function sign_psp_request($apiId, $merchantAccountId, $method, $path, $canonicalQuery, $timestamp, $idempotencyKey, $body, $privateKeyBase64) {
    $privateKey = base64_decode($privateKeyBase64);
    $bodyHash = hash('sha256', $body);
    $payload = "v1\n" . $apiId . "\n" . $merchantAccountId . "\n" . strtoupper($method) . "\n" . $path . "\n" . $canonicalQuery . "\n" . $timestamp . "\n" . $idempotencyKey . "\n" . $bodyHash;

    $signature = sodium_crypto_sign_detached($payload, $privateKey);
    return base64_encode($signature);
}
?>

接口接入示例

以下接口示例都放在本文档内。创建类接口必须使用 Idempotency-Key,查询类接口不需要 body。

Account Secret 认证 写请求必须带 Idempotency-Key 签名使用 Base64 编码 签名包含 body SHA-256
POST /api/v1/payments/intents

创建收款

创建一笔代收订单。PIX 返回二维码或复制码;SPEI 和 UPI 返回对应收款指令。支付结果以查询接口或 Webhook 为准。

接口信息

请求地址{GATEWAY_BASE_URL}/api/v1/payments/intents
请求方式POST
Content-Typeapplication/json
认证需要公共 Header、签名和 Idempotency-Key。

Body 参数

参数名类型必填说明
payment_methodstring是pix、spei、upi、credit_card、apple_pay、google_pay、cash_app、boleto。
rail_variantstring是支付通道变体,由 PSP 为对应支付方式配置。
amountinteger是收款金额,最小货币单位。
currencystring是币种,例如 BRL、MXN、INR。
instructionobject是支付方式指令字段,不同方式字段不同。
payer_infoobject否付款人信息:name、tax_id、email、phone、account。
merchant_order_idstring是商户订单号,最长 100 字符。
itemsarray否订单商品明细。
return_urlstring否需要跳转的支付方式使用的返回地址。
webhook_urlstring否该订单的商户通知地址。
expires_in_minutesinteger否过期分钟数,范围 1-1440。
allow_different_amountboolean否是否允许不同金额付款。
allow_multiple_paymentsboolean否是否允许多次付款。
metadataobject否商户自定义数据。

instruction 参数示例

payment_methodinstruction 字段说明
pixpix_key、pix_key_typePIX 收款 key 和 key 类型。
speibeneficiary_clabe、beneficiary_nameSPEI 收款 CLABE 和收款方名称。
upiupi_id、upi_id_typeUPI VPA 或其他 UPI 标识。

请求示例代码

curl -X POST "$GATEWAY_BASE_URL/api/v1/payments/intents" \
  -H "X-API-Key: $API_KEY" \
  -H "X-API-ID: $API_ID" \
  -H "X-Timestamp: 1781440000" \
  -H "X-Signature: <signature_base64>" \
  -H "Idempotency-Key: ORD-10001" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "payment_method": "pix",
    "rail_variant": "pix_qr",
    "amount": 10000,
    "currency": "BRL",
    "instruction": {
      "pix_key": "pay@example.com",
      "pix_key_type": "email"
    },
    "payer_info": {
      "name": "Joao Silva",
      "tax_id": "12345678909",
      "email": "joao@example.com"
    },
    "merchant_order_id": "ORD-10001",
    "expires_in_minutes": 30,
    "metadata": {
      "order_id": "ORD-10001"
    }
  }'

返回响应

{
  "id": "019ec682-e915-742a-8810-d4a888712b1b",
  "collection_number": "PI_pix_20260614142208_d4a888712b1b",
  "payment_method": "pix",
  "amount": 10000,
  "currency": "BRL",
  "status": "pending",
  "state": "awaiting_payment",
  "merchant_order_id": "ORD-10001",
  "provider_transaction_id": "BRADESCO-IN-xxx",
  "instruction": {
    "pix_key": "pay@example.com",
    "pix_key_type": "email",
    "pix_txid": "BRADESCO-IN-xxx",
    "qr_code": "000201...",
    "qr_code_url": "https://...",
    "emv_code": "000201..."
  },
  "created_at": "2026-06-14T14:22:08Z",
  "updated_at": "2026-06-14T14:22:08Z",
  "expires_at": "2026-06-14T14:52:08Z",
  "allow_different_amount": false,
  "allow_multiple_payments": false,
  "metadata": {
    "order_id": "ORD-10001"
  }
}

返回字段说明

字段说明
collection_numberPSP 收款单号,查询详情和对账使用。
status收款主状态,例如 pending、paid、expired、cancelled、refunded。
state更细的业务状态,例如等待付款、处理中等。
instruction.qr_codePIX 二维码内容或复制码。
expires_at订单过期时间。

其他支付方式 Body 示例

{
  "payment_method": "spei",
  "amount": 100000,
  "currency": "MXN",
  "instruction": {
    "beneficiary_clabe": "012345678901234567",
    "beneficiary_name": "Merchant Mexico SA"
  },
  "merchant_order_id": "ORD-MX-10001",
  "expires_in_minutes": 1440
}
{
  "payment_method": "upi",
  "amount": 500000,
  "currency": "INR",
  "instruction": {
    "upi_id": "merchant@upi",
    "upi_id_type": "vpa"
  },
  "merchant_order_id": "ORD-IN-10001",
  "expires_in_minutes": 15
}
GET /api/v1/payments/intents

查询收款列表

按支付方式、状态、商户订单号和创建时间查询当前商户账户下的收款订单。

Query 参数

参数名类型必填说明
payment_methodstring否支付方式筛选。
statusstring否状态筛选。
merchant_order_idstring否商户订单号。
created_afterstring否创建时间起始,RFC3339 格式。
created_beforestring否创建时间截止,RFC3339 格式。
limitinteger否每页数量,默认 20,最大 100。
offsetinteger否offset 分页偏移量。
cursorstring否游标分页。传该参数时响应为 items、next_cursor、has_more。

请求示例代码

curl -G "$GATEWAY_BASE_URL/api/v1/payments/intents" \
  -H "X-API-Key: $API_KEY" \
  -H "X-API-ID: $API_ID" \
  -H "X-Timestamp: 1781440200" \
  -H "X-Signature: <signature_base64>" \
  --data-urlencode "payment_method=pix" \
  --data-urlencode "status=pending" \
  --data-urlencode "limit=20" \
  --data-urlencode "offset=0"

返回响应

{
  "items": [
    {
      "collection_number": "PI_pix_20260614142208_d4a888712b1b",
      "payment_method": "pix",
      "amount": 10000,
      "currency": "BRL",
      "status": "pending",
      "merchant_order_id": "ORD-10001",
      "created_at": "2026-06-14T14:22:08Z"
    }
  ],
  "limit": 20,
  "offset": 0,
  "total": 1
}
GET /api/v1/payments/intents/{intent_id}

查询收款详情

根据 PSP 收款单号查询单笔收款详情。路径参数 intent_id 传 collection_number。

Path 参数

参数名类型必填说明
intent_idstring是收款系统单号,即 collection_number。

请求示例代码

curl "$GATEWAY_BASE_URL/api/v1/payments/intents/PI_pix_20260614142208_d4a888712b1b" \
  -H "X-API-Key: $API_KEY" \
  -H "X-API-ID: $API_ID" \
  -H "X-Timestamp: 1781440250" \
  -H "X-Signature: <signature_base64>"
POST /api/v1/payments/transfers

创建出款

创建一笔代付订单。代付通常异步处理,商户应通过查询接口或 Webhook 获取最终结果。

接口信息

请求地址{GATEWAY_BASE_URL}/api/v1/payments/transfers
请求方式POST
Content-Typeapplication/json
认证需要公共 Header、签名和 Idempotency-Key。

Body 参数

参数名类型必填说明
payment_methodstring是pix、spei、upi、america_ach。
rail_variantstring是支付通道变体;PIX QR 出款使用 pix_qr。
webhook_urlstring否该订单的商户通知地址。
amountinteger是出款金额,最小货币单位。
currencystring是币种。
beneficiaryobject是收款人信息。至少包含 name 和 account。
instructionobject否支付方式补充指令。
merchant_order_idstring否商户订单号,最长 100 字符。
descriptionstring否订单描述,最长 500 字符。
metadataobject否商户自定义数据。

beneficiary 参数

参数名类型必填说明
namestring是收款人名称。
accountstring是收款账户。PIX 代付时该字段就是实际 PIX key。
tax_idstring否收款人税号或身份标识。
emailstring否收款人邮箱。
phonestring否收款人手机号。
bank_codestring否银行代码,SPEI 等银行转账方式常用。
branch_codestring否支行代码。
PIX 出款中 beneficiary.account 是实际 PIX key。如同时提交 instruction.pix_key,必须与 beneficiary.account 完全一致。

请求示例代码

curl -X POST "$GATEWAY_BASE_URL/api/v1/payments/transfers" \
  -H "X-API-Key: $API_KEY" \
  -H "X-API-ID: $API_ID" \
  -H "X-Timestamp: 1781440300" \
  -H "X-Signature: <signature_base64>" \
  -H "Idempotency-Key: PAYOUT-BR-20001" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "payment_method": "pix",
    "rail_variant": "pix_qr",
    "amount": 10000,
    "currency": "BRL",
    "beneficiary": {
      "name": "Maria Silva",
      "tax_id": "12345678909",
      "email": "maria@example.com",
      "account": "maria@example.com"
    },
    "instruction": {
      "pix_key": "maria@example.com",
      "pix_key_type": "email"
    },
    "merchant_order_id": "PAYOUT-BR-20001"
  }'

返回响应

{
  "id": "019ec6fb-e6a6-7cbd-bb84-36b51cdfda2d",
  "payout_number": "TI_pix_20260614163417_36b51cdfda2d",
  "payment_method": "pix",
  "amount": 10000,
  "currency": "BRL",
  "status": "pending",
  "state": "created",
  "merchant_order_id": "PAYOUT-BR-20001",
  "beneficiary": {
    "name": "Maria Silva",
    "tax_id": "12345678909",
    "email": "maria@example.com",
    "account": "maria@example.com"
  },
  "created_at": "2026-06-14T16:34:17Z",
  "updated_at": "2026-06-14T16:34:17Z"
}

其他支付方式 Body 示例

{
  "payment_method": "spei",
  "amount": 10000,
  "currency": "MXN",
  "beneficiary": {
    "name": "Carlos Garcia",
    "tax_id": "GACR850101HDFRRL09",
    "account": "012345678901234567",
    "bank_code": "012"
  },
  "instruction": {
    "concepto_pago": "Pago de comisiones",
    "numeric_reference": "1234567"
  },
  "merchant_order_id": "PAYOUT-MX-20001"
}
{
  "payment_method": "upi",
  "amount": 100000,
  "currency": "INR",
  "beneficiary": {
    "name": "Rajesh Kumar",
    "account": "rajesh@upi",
    "phone": "+919876543210"
  },
  "merchant_order_id": "PAYOUT-IN-20001"
}
GET /api/v1/payments/transfers

查询出款列表

按支付方式、状态、商户订单号和创建时间查询当前商户账户下的出款订单。

Query 参数

参数名类型必填说明
payment_methodstring否支付方式筛选。
statusstring否状态筛选:pending、processing、completed、failed、returned、cancelled。
merchant_order_idstring否商户订单号。
created_afterstring否创建时间起始,RFC3339 格式。
created_beforestring否创建时间截止,RFC3339 格式。
limitinteger否每页数量,默认 20,最大 100。
offsetinteger否offset 分页偏移量。

请求示例代码

curl -G "$GATEWAY_BASE_URL/api/v1/payments/transfers" \
  -H "X-API-Key: $API_KEY" \
  -H "X-API-ID: $API_ID" \
  -H "X-Timestamp: 1781440400" \
  -H "X-Signature: <signature_base64>" \
  --data-urlencode "payment_method=pix" \
  --data-urlencode "status=processing" \
  --data-urlencode "limit=20" \
  --data-urlencode "offset=0"

返回响应

{
  "items": [
    {
      "payout_number": "TI_pix_20260614163417_36b51cdfda2d",
      "payment_method": "pix",
      "amount": 10000,
      "currency": "BRL",
      "status": "processing",
      "merchant_order_id": "PAYOUT-BR-20001",
      "created_at": "2026-06-14T16:34:17Z"
    }
  ],
  "limit": 20,
  "offset": 0,
  "total": 1
}
GET /api/v1/payments/transfers/{intent_id}

查询出款详情

根据 PSP 出款单号查询单笔出款详情。路径参数 intent_id 传 payout_number。

Path 参数

参数名类型必填说明
intent_idstring是出款系统单号,即 payout_number。

请求示例代码

curl "$GATEWAY_BASE_URL/api/v1/payments/transfers/TI_pix_20260614163417_36b51cdfda2d" \
  -H "X-API-Key: $API_KEY" \
  -H "X-API-ID: $API_ID" \
  -H "X-Timestamp: 1781440450" \
  -H "X-Signature: <signature_base64>"
GET /api/v1/merchant-accounts/{account_id}/balance

查询账户余额

出款前建议先查询账户余额,重点关注 available_balance。

Path 参数

参数名类型必填说明
account_idstring是商户账户 ID。

请求示例代码

curl "$GATEWAY_BASE_URL/api/v1/merchant-accounts/$ACCOUNT_ID/balance" \
  -H "X-API-Key: $API_KEY" \
  -H "X-API-ID: $API_ID" \
  -H "X-Timestamp: 1781440500" \
  -H "X-Signature: <signature_base64>"

返回响应

{
  "merchant_account_id": "550e8400-e29b-41d4-a716-446655440000",
  "ledger_account_id": "ledger_001",
  "processing_balance": 200000,
  "settlement_balance": 1800000,
  "frozen_balance": 100000,
  "available_balance": 1500000,
  "currency": "BRL",
  "as_of": "2026-06-14T16:40:00Z",
  "updated_at": "2026-06-14T16:40:00Z"
}

返回字段说明

字段说明
processing_balance处理中余额。
settlement_balance已结算余额。
frozen_balance冻结余额。
available_balance可用余额,创建出款前重点判断。
as_of余额快照时间。

Webhook 通知

PSP 会将代收、代付结果推送到商户配置的 webhook_url。商户需要验证签名、按投递 ID 做幂等处理,并快速返回 HTTP 2xx。API Reference 中的 /api/v1/webhooks/* 是 PSP 接收渠道回调的内部入口,不是商户需要调用的地址。

获取 PSP Webhook 公钥

  1. 登录 Merchant Portal,进入「Webhook」,在账户筛选中找到接收通知的商户账户。
  2. 先确认该账户已保存 HTTPS endpoint URL。账户卡片会显示配置状态、event type、Key ID、签名算法和 Fingerprint。
  3. 点击「复制验签公钥」直接复制当前公钥,或点击「下载当前公钥」保存文件。PSP 只公开验签公钥,不会透露 Webhook 私钥。
  4. 在商户 Webhook 接收服务中按账户保存公钥,并使用对应账户的公钥验证通知。不同账户不要混用公钥。

Webhook 公钥轮换

  1. 在目标账户卡片点击「准备轮换」,然后点击「下载 next 公钥」。
  2. 先把 next 公钥部署到商户接收服务,使服务在过渡期同时支持当前公钥和 next 公钥。
  3. 确认服务已部署后,回到后台点击「启用 next 密钥」。确认新旧密钥切换完成后,再从服务端移除旧公钥。

Webhook Header

参数名说明
X-Webhook-Event事件类型,例如 payment.intent.completed。
X-Webhook-Event-ID事件 ID。
X-Webhook-ID本次投递 ID,建议做唯一约束。
X-Webhook-VersionWebhook 版本。
X-Timestamp通知时间戳。
X-Signature对 <X-Timestamp>.<raw_body> 签名后的标准 Base64 签名。
X-Signature-Algorithm固定为 Ed25519。

Webhook Body 示例

{
  "event": "payment.intent.completed",
  "timestamp": "2026-06-14T14:30:00Z",
  "data": {
    "collection_number": "PI_pix_20260614142208_d4a888712b1b",
    "payment_method": "pix",
    "rail_variant": "pix_qr",
    "amount": 10000,
    "paid_amount": 10000,
    "currency": "BRL",
    "merchant_order_id": "ORD-10001",
    "provider_transaction_id": "BRADESCO-IN-xxx",
    "paid_at": "2026-06-14T14:30:00Z"
  }
}

Webhook 事件

事件说明商户处理建议
payment.intent.completed代收完成标记订单已支付,并用订单号做幂等。
payment.intent.failed代收失败标记失败,可提示用户重新支付。
payment.transfer.completed代付完成标记打款成功。
payment.transfer.failed代付失败记录失败原因,按业务决定是否重试。

Node.js Webhook 验签示例

// npm install tweetnacl
const nacl = require("tweetnacl");

function verifyWebhook(publicKeyBase64, timestamp, rawBodyBuffer, signatureBase64) {
  const publicKey = Buffer.from(publicKeyBase64, "base64");
  const signature = Buffer.from(signatureBase64, "base64");
  const payload = Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), rawBodyBuffer]);
  return nacl.sign.detached.verify(payload, signature, publicKey);
}

function handleWebhook(req, res) {
  const rawBody = req.rawBody; // 必须是未解析、未格式化的原始 body
  const timestamp = req.headers["x-timestamp"];
  const signature = req.headers["x-signature"];
  const webhookId = req.headers["x-webhook-id"];

  if (!verifyWebhook(process.env.WEBHOOK_PUBLIC_KEY_BASE64, timestamp, rawBody, signature)) {
    res.statusCode = 401;
    res.end("invalid signature");
    return;
  }

  // 用 webhookId 做唯一约束,重复通知直接返回 2xx。
  res.statusCode = 204;
  res.end();
}

联调检查项

步骤验收结果
创建 sandbox/test API Key商户服务端可以读取到 API Key。
上传签名公钥请求签名校验通过。
配置 Webhook URL商户服务可收到 PSP 通知。
调用余额查询返回账户余额,认证链路可用。
创建 PIX 代收返回 collection_number 和二维码/复制码。
接收代收 Webhook验签通过,订单状态更新为已支付。
重复同一幂等键请求同 key + 同 body 不重复创建订单。
创建 PIX 代付最终查询状态和 Webhook 状态一致。

幂等重试示例

// 第一次请求
Idempotency-Key: ORD-10001
body_sha256: 6b3f...

// 安全重试:同 key + 同 body
Idempotency-Key: ORD-10001
body_sha256: 6b3f...

// 错误用法:同 key + 不同 body,会返回 422 VALIDATION_ERROR
Idempotency-Key: ORD-10001
body_sha256: 9a01...

错误码

HTTP / Code常见原因处理方式
400 INVALID_REQUEST缺少 API ID、时间戳或写请求幂等键,或请求体格式错误。检查请求体和必需 Header。
401 UNAUTHORIZEDAPI ID/API Key 无效、签名无效、时间戳超出 60 秒窗口。确认 Account Secret、签名串和服务器时间。
401 SIGNATURE_INVALID签名串、规范化 query、body SHA-256 或 Base64 编码不一致。使用实际发送的 raw body 重新计算签名。
403 FORBIDDENAPI Key scope 不足、商户或账户状态不可用、IP 未进白名单。检查权限、商户状态和出口 IP。
409 IDEMPOTENCY_CONFLICT幂等键冲突。确认重试是否使用同一个 body。
422 VALIDATION_ERROR同一个 Idempotency-Key 搭配了不同 body。重试必须保持 body 不变。
422 INSUFFICIENT_BALANCE可用余额不足。先查询余额,充值或等待入账后重试。
下一步:打开 API Reference,按接口确认完整字段、枚举、响应结构和错误模型。