跳到正文

API

本页内容

创建

为单个付款人创建可重复使用的收款身份,以你自己的外部标识为键,每条支持的网络配一个永久地址。

POST/v1/payment-channels

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 CreatedLocation 响应头指向新建的通道
  • 之后每次用同一对值调用——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 网络代码,如 TRC20ERC20
status 地址就绪后为 active,在此之前为 provisioning
address 永久收款地址。该网络开通完成之前不出现——意思是还没有,不是不会有。一旦返回就不再变
tokens 该网络当前接受什么。每一项都是对象:symbolminimum_deposit,不是一个光秃秃的代币代码

给付款人展示什么,由两条规则决定:

  • 只展示 tokens 数组非空的 active 网络——provisioning 的条目还没有地址可展示
  • API 不再返回的路线就停止展示——地址永不重新分配,但你从项目里移除的网络会从响应中消失,发到那里的钱也不再在预期之内

地址一旦返回,就永久属于这个通道。缓存它。重新读取通道拿回的是同一个值,而且该网络上的每一种代币都用这一个地址。

列表里每一种代币都带着自己的 minimum_deposit:这条路线上能够入账的最小转账额。请从你正要展示的那个响应里取值,并放在地址旁边。这个字段的完整说明(包括 null 的情况)见获取收款通道

开通

新建的正式通道以 provisioning 开始,此时没有地址。每条网络各自独立开通,自己的地址一出现就能用——不必等最后一条链。第一个地址就绪时通道转为 active,并停在这个状态。

is_fully_provisioned 告诉你是否所有必需网络都已就绪。通道可以是 active 但尚未完全开通;其余链还在跟进时,这就是正常状态。

沙箱通道立即可用:它的地址在本地推导,不触碰任何链。

手续费

applied_fee_percentcustomer_fee_percent 是你当前的费率。它们仅供参考,不是报价——你的定价变了,它们就变。

有约束力的数字在每一条充值上。每条充值都记录归属那一刻生效的费率,并给出自己的 grossfeenet。改价永远不会改写你已经收到的款。没有费用预览接口:想看清这笔算术,就在沙箱造一条充值。

错误

错误码 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、单通道代币和费用预览接口。通道身份按设计不可变:付款人存下来的地址,绝不能变成别人的。要让一个通道退出使用,停用它。