本页内容
创建
为单个付款人创建可重复使用的收款身份,以你自己的外部标识为键,每条支持的网络配一个永久地址。
API 密钥: Payment
收款通道是你为某一个付款人开出的永久收款身份。它和账单不一样:没有金额,没有过期时间,也没有终态。创建一次,把地址展示出去,之后到达的每一笔转账都成为一条充值,计入你的余额。
请求体
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
project_id |
string | 是 | 带前缀的项目标识(prj_…)。通道接受哪些代币由项目决定 |
external_id |
string | 是 | 你自己给付款人的稳定标识,最长 128 字符。通道建立后不可更改 |
{
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"external_id": "customer-42"
}
幂等性
在你签名所用 API key 的那个环境里,project_id + external_id 这一对就是幂等键。除此之外没有别的字段可发,因此重放请求不存在需要裁定的载荷冲突。
- —首次调用——
201 Created,Location响应头指向新建的通道 - —之后每次用同一对值调用——
200 OK,返回同一个通道,不会产生重复 - —同一个
external_id在沙箱和正式环境是两个互不相干的通道
发送你系统里已经在用的付款人标识,并且每次结账都调一次,不必自己缓存 pc_ 标识。重复调用既不是错误,也不会产生重复:两个状态码返回的都是通道,200 带回的是这个通道今天拥有的地址和代币。请求超时后重试拿回的是原来的通道,而不是又开一个。
代码示例
每个标签页使用该语言的官方 SDK。签名、序列化、安全重试和类型化错误都由 SDK 处理。
API_KEY_ID="pk_live_xxxxxxxxxxxx"
API_SECRET="sk_live_xxxxxxxxxxxx"
BODY='{"project_id":"prj_xxxxxxxxxxxx","external_id":"customer-42"}'
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIGNATURE=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" POST /v1/payment-channels '' "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)
curl -sS https://api.paymos.io/v1/payment-channels \
-H "Authorization: HMAC-SHA256 $API_KEY_ID:$SIGNATURE" \
-H "X-Request-Timestamp: $TS" \
-H "Content-Type: application/json" \
-d "$BODY"
import { Paymos } from '@paymos/sdk';
const paymos = new Paymos({
apiKey: process.env.PAYMOS_API_KEY,
apiSecret: process.env.PAYMOS_API_SECRET,
});
const channel = await paymos.paymentChannels.create({
projectId: 'prj_xxxxxxxxxxxx',
externalId: 'customer-42',
});
for (const rail of channel.networks) {
console.log(rail.network, rail.address ?? rail.status);
}
import os
from paymos import Paymos
paymos = Paymos(
api_key=os.environ["PAYMOS_API_KEY"],
api_secret=os.environ["PAYMOS_API_SECRET"],
)
channel = paymos.payment_channels.create(
project_id="prj_xxxxxxxxxxxx",
external_id="customer-42",
)
for rail in channel["networks"]:
print(rail["network"], rail.get("address", rail["status"]))
<?php
use Paymos\Client;
use Paymos\ClientConfig;
$paymos = new Client(new ClientConfig(
getenv('PAYMOS_API_KEY'),
getenv('PAYMOS_API_SECRET')
));
$channel = $paymos->paymentChannels()->create(array(
'project_id' => 'prj_xxxxxxxxxxxx',
'external_id' => 'customer-42',
));
foreach ($channel['networks'] as $rail) {
$address = isset($rail['address']) ? $rail['address'] : $rail['status'];
echo $rail['network'] . ' ' . $address . PHP_EOL;
}
package main
import (
"context"
"fmt"
"os"
paymos "github.com/Paymos-labs/go-sdk/v2"
)
func main() {
client, err := paymos.NewClient(os.Getenv("PAYMOS_API_KEY"), os.Getenv("PAYMOS_API_SECRET"))
if err != nil {
panic(err)
}
channel, err := client.PaymentChannels.Create(context.Background(), paymos.CreatePaymentChannelParams{
ProjectID: "prj_xxxxxxxxxxxx",
ExternalID: "customer-42",
})
if err != nil {
panic(err)
}
for _, rail := range channel.Networks {
fmt.Println(rail.Network, addressOrStatus(rail))
}
}
func addressOrStatus(rail paymos.PaymentChannelNetwork) string {
if rail.Address == nil {
return string(rail.Status)
}
return *rail.Address
}
using Paymos;
using var paymos = new PaymosClient(
Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);
var channel = await paymos.PaymentChannels.CreateAsync(new CreatePaymentChannelRequest(
ProjectId: "prj_xxxxxxxxxxxx",
ExternalId: "customer-42"));
foreach (var rail in channel.Networks)
{
Console.WriteLine($"{rail.Network} {rail.Address ?? rail.Status}");
}
import io.paymos.CreatePaymentChannelRequest;
import io.paymos.PaymentChannel;
import io.paymos.PaymentChannelNetwork;
import io.paymos.PaymosClient;
PaymosClient paymos = new PaymosClient(
System.getenv("PAYMOS_API_KEY"),
System.getenv("PAYMOS_API_SECRET"));
PaymentChannel channel = paymos.paymentChannels.create(
new CreatePaymentChannelRequest("prj_xxxxxxxxxxxx", "customer-42"));
for (PaymentChannelNetwork rail : channel.networks()) {
System.out.println(rail.network() + " "
+ (rail.address() == null ? rail.status() : rail.address()));
}
require 'paymos'
paymos = Paymos::Client.new(
api_key: ENV.fetch('PAYMOS_API_KEY'),
api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
channel = paymos.payment_channels.create(
project_id: 'prj_xxxxxxxxxxxx',
external_id: 'customer-42'
)
channel.networks.each do |rail|
puts "#{rail.network} #{rail.address || rail.status}"
end
use paymos::{CreatePaymentChannelRequest, PaymosClient};
let paymos = PaymosClient::new(
std::env::var("PAYMOS_API_KEY")?,
std::env::var("PAYMOS_API_SECRET")?,
)?;
let channel = paymos
.payment_channels()
.create(&CreatePaymentChannelRequest {
project_id: "prj_xxxxxxxxxxxx".to_owned(),
external_id: "customer-42".to_owned(),
})
.await?;
for rail in &channel.networks {
println!("{} {}", rail.network, rail.address.as_deref().unwrap_or(&rail.status));
}
响应 (201 Created,重放时 200)
{
"id": "pc_2QhKZv6mRt9WdA3nYpLbXf",
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"external_id": "customer-42",
"status": "active",
"is_accepting_payments": true,
"is_fully_provisioned": false,
"is_test": false,
"applied_fee_percent": 1.0,
"customer_fee_percent": 0,
"networks": [
{
"network": "TRC20",
"status": "active",
"address": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
"tokens": [
{ "symbol": "USDT", "minimum_deposit": "1.00" },
{ "symbol": "USDC", "minimum_deposit": "1.00" }
]
},
{
"network": "ERC20",
"status": "active",
"address": "0x9f2a6c1b4e8d7350ab11c9e5f0d2743b86ce41d9",
"tokens": [
{ "symbol": "USDT", "minimum_deposit": "12.00" },
{ "symbol": "USDC", "minimum_deposit": "12.00" }
]
},
{
"network": "SOL",
"status": "provisioning",
"tokens": [
{ "symbol": "USDC", "minimum_deposit": null }
]
}
],
"created_at": 1767225600,
"updated_at": 1767225780
}
网络与地址
通道自己没有代币列表。项目接受什么,它就接受什么——在项目上启用一个代币,该项目下每个通道都会多出这个代币;停用则处处消失。可接受的集合在 控制台 → 项目 管理。
networks 中的每一项描述一条链:
| 字段 | 说明 |
|---|---|
network |
网络代码,如 TRC20 或 ERC20 |
status |
地址就绪后为 active,在此之前为 provisioning |
address |
永久收款地址。该网络开通完成之前不出现——意思是还没有,不是不会有。一旦返回就不再变 |
tokens |
该网络当前接受什么。每一项都是对象:symbol 加 minimum_deposit,不是一个光秃秃的代币代码 |
给付款人展示什么,由两条规则决定:
- —只展示
tokens数组非空的active网络——provisioning的条目还没有地址可展示 - —API 不再返回的路线就停止展示——地址永不重新分配,但你从项目里移除的网络会从响应中消失,发到那里的钱也不再在预期之内
地址一旦返回,就永久属于这个通道。缓存它。重新读取通道拿回的是同一个值,而且该网络上的每一种代币都用这一个地址。
列表里每一种代币都带着自己的 minimum_deposit:这条路线上能够入账的最小转账额。请从你正要展示的那个响应里取值,并放在地址旁边。这个字段的完整说明(包括 null 的情况)见获取收款通道。
开通
新建的正式通道以 provisioning 开始,此时没有地址。每条网络各自独立开通,自己的地址一出现就能用——不必等最后一条链。第一个地址就绪时通道转为 active,并停在这个状态。
is_fully_provisioned 告诉你是否所有必需网络都已就绪。通道可以是 active 但尚未完全开通;其余链还在跟进时,这就是正常状态。
沙箱通道立即可用:它的地址在本地推导,不触碰任何链。
手续费
applied_fee_percent 和 customer_fee_percent 是你当前的费率。它们仅供参考,不是报价——你的定价变了,它们就变。
有约束力的数字在每一条充值上。每条充值都记录归属那一刻生效的费率,并给出自己的 gross、fee 和 net。改价永远不会改写你已经收到的款。没有费用预览接口:想看清这笔算术,就在沙箱造一条充值。
错误
| 错误码 | HTTP | 何时出现 |
|---|---|---|
payment_channels_disabled |
503 | 本环境下你的账户未开通收款通道 |
payment_key_required |
403 | 请求用 Payout(rk_)key 签名 |
project_not_found |
404 | project_id 解析不到该凭证可见的任何项目 |
payment_channel_external_id_invalid |
400 | external_id 为空、全是空白,或超过 128 字符 |
payment_channel_project_has_no_supported_tokens |
409 | 项目没有启用任何通道可收的代币 |
完整目录见错误码。
这套 API 不提供什么
没有更新、删除、重新分配、单通道回调 URL、单通道代币和费用预览接口。通道身份按设计不可变:付款人存下来的地址,绝不能变成别人的。要让一个通道退出使用,停用它。