本页内容
创建
创建账单以接收加密货币付款。两种流程:直接加密货币(代币和网络固定)与法币(客户在托管收银台选择代币)。
API 密钥: Payment
请求体
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
project_id |
string | 是 | 带前缀的项目标识(prj_...)。API key 按商户授权,需指定账单归属的项目 |
amount |
string | 是 | 支付金额,十进制字符串(如 "50.00") |
currency |
string | 是 | 法币代码(USD、EUR 等)或加密资产符号。完整列表见 支持的币种 |
network |
string | 条件必填 | 区块链网络代码——TRC20、ERC20、BEP20、POLYGON、ARBITRUM、OPTIMISM、BASE、TON、AVAX、SOL、NEAR、SUI 或 PLASMA(见 支持的币种)。需要把加密货币账单锁定到指定网络时必填 |
external_order_id |
string | 是 | 你的唯一订单标识(最长 128 字符),同一项目内唯一。用于账单创建的幂等:如果项目中已存在该 external_order_id 的账单,直接返回已有账单和 200 OK——不产生重复,也不报错 |
client_id |
string | 否 | 你的客户标识(最长 200 字符)。保存在账单上,读取响应和列表条目中原样返回,便于你按自己的客户归集账单。Paymos 不会据此设置任何上限 |
allow_multiple_payments |
boolean | 否 | 账单是否接受多笔部分付款。默认值:true |
customer_fee_percent |
integer | 否 | 面向客户的手续费百分比(0-100)。覆盖该账单的项目级设置 |
法币金额精度
法币流程的账单,amount 必须符合 currency 的最小单位。
| 最小单位 | 币种 |
|---|---|
| 0 位小数 | JPY、KRW、VND、CLP |
| 2 位小数 | USD、EUR、GBP、CHF、CAD、AUD、NZD、PLN、CZK、DKK、SEK、NOK、HUF、RUB、UAH、GEL、CNY、HKD、INR、IDR、MYR、PHP、THB、PKR、SGD、BRL、MXN、ARS、TRY、AED、ILS、NGN、ZAR、BDT、BMD、LKR、MMK、SAR、TWD、VEF |
| 3 位小数 | BHD、KWD |
示例:23.34 对 USD 有效;23.345 对 USD 会被拒绝;23 对 JPY 有效;23.01 对 JPY 会被拒绝。
流程判定
流程由你提供的字段决定。currency 决定金额按代币还是按法币计价;network 决定链是预先固定还是由客户选择。
currency |
network |
流程 | 客户还需选择什么 |
|---|---|---|---|
代币(USDT、USDC……) |
某个网络(TRC20、ERC20……) |
直接加密货币——amount 是代币数量,链固定 |
无需选择——代币和网络都已锁定 |
代币(USDT、USDC……) |
— | 加密货币流程——amount 是代币数量,链开放 |
支付所用的网络 |
法币(USD、EUR……) |
— | 法币流程——amount 是法币金额 |
代币和网络 |
每张账单都以 awaiting_client 创建——此时还没有付款地址。客户在托管收银台上的选择才会分配地址并把账单推进到 awaiting_payment。直接加密货币场景下没有可选的东西,收银台会一步确认预设的代币和网络并分配地址。法币流程中,法币兑加密货币的汇率在那一刻锁定,并以 payment.exchange_rate 返回。
完整生命周期、初始状态和汇率锁定语义见付款流程 → 两种创建流程。
示例请求——法币流程
金额以法币计;客户在托管页面选择代币,汇率在选择时锁定。
{
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"amount": "50.00",
"currency": "USD",
"external_order_id": "order-12345",
"client_id": "customer-67890"
}
示例请求——直接加密货币
代币+网络在创建时固定;地址立即分配。
{
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"amount": "50.00",
"currency": "USDT",
"network": "TRC20",
"external_order_id": "order-12345",
"client_id": "customer-67890"
}
代码示例
每个标签页使用该语言的官方 SDK。签名、序列化、安全重试和类型化错误都由 SDK 处理。
API_KEY_ID="pk_live_xxxxxxxxxxxx"
API_SECRET="sk_live_xxxxxxxxxxxx"
BODY='{"project_id":"prj_xxxxxxxxxxxx","amount":"100.00","currency":"USD","external_order_id":"order-123","client_id":"customer-456"}'
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/invoices '' "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)
curl -sS https://api.paymos.io/v1/invoices \
-H "Authorization: HMAC-SHA256 $API_KEY_ID:$SIGNATURE" \
-H "X-Request-Timestamp: $TS" \
-H "Content-Type: application/json" \
-d "$BODY"
import { Paymos, externalOrderId } from '@paymos/sdk';
const paymos = new Paymos({
apiKey: process.env.PAYMOS_API_KEY,
apiSecret: process.env.PAYMOS_API_SECRET,
});
const invoice = await paymos.invoices.create({
projectId: 'prj_xxxxxxxxxxxx',
amount: '100.00',
currency: 'USD',
externalOrderId: externalOrderId('order'),
clientId: 'customer-456',
});
console.log(invoice.invoiceId, invoice.paymentUrl);
import os
from paymos import Paymos
paymos = Paymos(
api_key=os.environ["PAYMOS_API_KEY"],
api_secret=os.environ["PAYMOS_API_SECRET"],
)
invoice = paymos.invoices.create(
project_id="prj_xxxxxxxxxxxx",
amount="100.00",
currency="USD",
external_order_id="order-123",
client_id="customer-456",
)
print(invoice["invoice_id"], invoice["payment_url"])
<?php
use Paymos\Client;
use Paymos\ClientConfig;
use Paymos\IdempotencyKey;
$paymos = new Client(new ClientConfig(
getenv('PAYMOS_API_KEY'),
getenv('PAYMOS_API_SECRET')
));
$invoice = $paymos->invoices()->create(array(
'project_id' => 'prj_xxxxxxxxxxxx',
'amount' => '100.00',
'currency' => 'USD',
'external_order_id' => IdempotencyKey::externalOrderId('order'),
'client_id' => 'customer-456',
));
echo $invoice['invoice_id'] . ' ' . $invoice['payment_url'];
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)
}
invoice, err := client.Invoices.Create(context.Background(), paymos.CreateInvoiceParams{
ProjectID: "prj_xxxxxxxxxxxx",
Amount: "100.00",
Currency: "USD",
ExternalOrderID: "order-123",
ClientID: stringPointer("customer-456"),
})
if err != nil {
panic(err)
}
fmt.Println(invoice.InvoiceID, invoice.PaymentURL)
}
func stringPointer(value string) *string { return &value }
using Paymos;
using var paymos = new PaymosClient(
Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);
var invoice = await paymos.Invoices.CreateAsync(new CreateInvoiceRequest(
ProjectId: "prj_xxxxxxxxxxxx",
Amount: "100.00",
Currency: "USD",
ExternalOrderId: "order-123",
ClientId: "customer-456"));
Console.WriteLine($"{invoice.InvoiceId} {invoice.PaymentUrl}");
import io.paymos.CreateInvoiceRequest;
import io.paymos.Invoice;
import io.paymos.PaymosClient;
PaymosClient paymos = new PaymosClient(
System.getenv("PAYMOS_API_KEY"),
System.getenv("PAYMOS_API_SECRET"));
Invoice invoice = paymos.invoices.create(
CreateInvoiceRequest.builder()
.projectId("prj_xxxxxxxxxxxx")
.amount("100.00")
.currency("USD")
.externalOrderId("order-123")
.clientId("customer-456")
.build());
System.out.println(invoice.invoiceId() + " " + invoice.paymentUrl());
require 'paymos'
paymos = Paymos::Client.new(
api_key: ENV.fetch('PAYMOS_API_KEY'),
api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
invoice = paymos.invoices.create(
project_id: 'prj_xxxxxxxxxxxx',
amount: '100.00',
currency: 'USD',
external_order_id: 'order-123',
client_id: 'customer-456'
)
puts "#{invoice.invoice_id} #{invoice.payment_url}"
use paymos::{CreateInvoiceRequest, PaymosClient};
let paymos = PaymosClient::new(
std::env::var("PAYMOS_API_KEY")?,
std::env::var("PAYMOS_API_SECRET")?,
)?;
let invoice = paymos
.invoices()
.create(&CreateInvoiceRequest {
project_id: "prj_xxxxxxxxxxxx".to_owned(),
amount: "100.00".to_owned(),
currency: "USD".to_owned(),
external_order_id: "order-123".to_owned(),
network: None,
allow_multiple_payments: None,
customer_fee_percent: None,
client_id: Some("customer-456".to_owned()),
})
.await?;
println!("{} {}", invoice.invoice_id, invoice.payment_url);
响应(201 Created / 幂等命中时 200)
{
"invoice_id": "inv_5CcyDYmMUGtzYL10q0Iimr",
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"status": "awaiting_client",
"is_final": false,
"is_test": true,
"payment_url": "https://checkout.paymos.io/invoice/inv_5CcyDYmMUGtzYL10q0Iimr",
"order": {
"external_id": "order-12345",
"client_id": "customer-67890",
"amount": "50.00",
"currency": "USD"
},
"created_at": 1743594000,
"updated_at": 1743594000,
"expires_at": 1743597600
}
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
invoice_id |
string | 带前缀的账单标识(inv_...) |
project_id |
string | 带前缀的项目标识(prj_...) |
status |
string | 账单状态(snake_case——见 账单状态) |
is_final |
boolean | 账单是否处于终态 |
is_test |
boolean | 沙箱账单为 true |
payment_url |
string | 付款 URL:Telegram 机器人项目为打开 Paymos 机器人的链接,其他项目为托管收银台页面 |
order |
object | 面向商户的订单数据 |
order.external_id |
string | 商户订单号 |
order.client_id |
string? | 商户客户标识 |
order.amount |
string | 请求的金额 |
order.currency |
string | 商户请求的法币代码或加密资产符号 |
order.network |
string? | 直接加密货币账单请求的网络 |
payment |
object? | 付款详情——选定代币和网络后出现。包含充值地址、应付加密货币金额、锁定汇率和每笔转账的进度。完整子字段说明见 获取账单 → Payment 字段 |
created_at |
integer | Unix 时间戳,秒 |
updated_at |
integer | Unix 时间戳,秒 |
expires_at |
integer | Unix 时间戳,秒。创建时设定,因此任何状态下的账单都带此字段 |
completed_at |
integer? | 完成时间戳 |
账单状态
每个账单都带一个 status 字符串。同一个值同时驱动商户 API 响应、收银台 SSE 流和 webhook 负载——只有一个事实来源。下表是逐状态参考:该值何时出现、是否终态、背后的精确条件。状态流转图和两种创建流程见 付款流程。
终态(terminal)状态是最终的:账单停在那里,不再变动。五个终态——paid、paid_over、underpaid、expired、cancelled。
| 状态 | 终态 | 适用场景 |
|---|---|---|
awaiting_client |
否 | 每个账单的初始状态。尚未选定代币和网络,因此还没有充值地址。只允许在此状态取消 |
awaiting_payment |
否 | 代币、网络和充值地址已锁定。Paymos 正在监听该地址的入账转账 |
confirming |
否 | 转账已上链,正在累积其金额档位所需的确认数 |
underpaid_waiting |
否 | 已清算金额不足应付金额,账单保持打开等待补足。仅当 allow_multiple_payments 为 true 时才会进入此状态 |
paid |
是 | 应付金额已全额清算(或在项目的少付容忍范围内) |
paid_over |
是 | 清算金额超过应付金额;整笔转账全额入账 |
underpaid |
是 | 账单以不足额关闭——要么过期时低于应付金额,要么 allow_multiple_payments 为 false 时单笔付款不足 |
expired |
是 | 计时耗尽且未收到任何款项,或法币流程中选币窗口在选择代币前已失效 |
cancelled |
是 | 商户在账单仍处于 awaiting_client 时取消了它 |
幂等性
创建账单时始终发送 external_order_id。用同一个 external_order_id 重试同一调用会返回已存在的账单——绝不产生重复。幂等命中的响应状态是 200 OK,新建账单是 201 Created。
这是抵御网络抖动、worker 崩溃和重试循环的安全网。同一业务事件用同一个 id;新业务事件换一个新 id。
用 UUID v4 或你自己的订单 ID——任何稳定且按业务事件唯一的值。如果不小心复用了某个 ID,你会拿回该 ID 的既有账单(上面的 200 OK 幂等命中),而不是新账单。
错误
完整目录见错误码。每个错误响应中的 type URI 会深链到对应的行。