跳到正文

API

本页内容

创建

创建账单以接收加密货币付款。两种流程:直接加密货币(代币和网络固定)与法币(客户在托管收银台选择代币)。

POST/v1/invoices

API 密钥: Payment

请求体

参数 类型 必填 说明
project_id string 带前缀的项目标识(prj_...)。API key 按商户授权,需指定账单归属的项目
amount string 支付金额,十进制字符串(如 "50.00"
currency string 法币代码(USDEUR 等)或加密资产符号。完整列表见 支持的币种
network string 条件必填 区块链网络代码——TRC20ERC20BEP20POLYGONARBITRUMOPTIMISMBASETONAVAXSOLNEARSUIPLASMA(见 支持的币种)。需要把加密货币账单锁定到指定网络时必填
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 位小数 JPYKRWVNDCLP
2 位小数 USDEURGBPCHFCADAUDNZDPLNCZKDKKSEKNOKHUFRUBUAHGELCNYHKDINRIDRMYRPHPTHBPKRSGDBRLMXNARSTRYAEDILSNGNZARBDTBMDLKRMMKSARTWDVEF
3 位小数 BHDKWD

示例:23.34USD 有效;23.345USD 会被拒绝;23JPY 有效;23.01JPY 会被拒绝。

流程判定

流程由你提供的字段决定。currency 决定金额按代币还是按法币计价;network 决定链是预先固定还是由客户选择。

currency network 流程 客户还需选择什么
代币(USDTUSDC……) 某个网络(TRC20ERC20……) 直接加密货币——amount 是代币数量,链固定 无需选择——代币和网络都已锁定
代币(USDTUSDC……) 加密货币流程——amount 是代币数量,链开放 支付所用的网络
法币(USDEUR……) 法币流程——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)状态是最终的:账单停在那里,不再变动。五个终态——paidpaid_overunderpaidexpiredcancelled

状态 终态 适用场景
awaiting_client 每个账单的初始状态。尚未选定代币和网络,因此还没有充值地址。只允许在此状态取消
awaiting_payment 代币、网络和充值地址已锁定。Paymos 正在监听该地址的入账转账
confirming 转账已上链,正在累积其金额档位所需的确认数
underpaid_waiting 已清算金额不足应付金额,账单保持打开等待补足。仅当 allow_multiple_paymentstrue 时才会进入此状态
underpaid 账单以不足额关闭——要么过期时低于应付金额,要么 allow_multiple_paymentsfalse 时单笔付款不足
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 会深链到对应的行。