На странице
Создание
Создание платёжного инвойса для приёма криптовалютных платежей. Два потока: прямой крипто (токен указан) и фиатный (клиент выбирает токен на странице оплаты).
API-ключ: Payment
Тело запроса
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
project_id |
string | Да | Идентификатор проекта с префиксом (prj_...). API-ключ привязан к мерчанту; укажите, какой проект получит инвойс |
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 символов). Привязывает инвойс к конкретному клиенту, чтобы работал лимит на число одновременно открытых инвойсов на одного клиента |
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 только после того, как клиент подтвердит оплату на странице Paymos. В прямом крипто-потоке выбирать нечего: страница оплаты подтверждает заранее заданный токен и сеть и назначает адрес одним шагом. В фиатном потоке в этот же момент фиксируется курс фиат → крипто; на инвойсе он доступен в поле 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.
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 | true, когда инвойс достиг терминального состояния |
is_test |
boolean | true для тестовых счетов |
payment_url |
string | URL платёжной страницы |
order |
object | Данные заказа со стороны мерчанта |
order.external_id |
string | ID заказа мерчанта |
order.client_id |
string? | ID клиента мерчанта |
order.amount |
string | Запрошенная сумма |
order.currency |
string | Фиатная валюта или символ криптоактива, указанный мерчантом |
order.network |
string? | Сеть, запрошенная для крипто-инвойса с фиксированной сетью |
payment |
object? | Детали платежа после выбора токена и сети |
created_at |
integer | Unix timestamp, секунды |
updated_at |
integer | Unix timestamp, секунды |
expires_at |
integer | Unix timestamp, секунды. Задаётся при создании, поэтому есть у любого инвойса в любом статусе |
completed_at |
integer? | Время завершения |
Статусы инвойса
У каждого инвойса есть поле status. Одно и то же значение приходит в ответе API, в потоке SSE на странице оплаты и в вебхуке — источник один. Таблица ниже — справочник по статусам: когда статус наступает, терминальный он или нет и при каком условии возникает. Схему переходов и оба потока создания смотрите в разделе Платёжный процесс.
Терминальный статус — конечный: инвойс на нём закрывается и больше не меняется. Терминальных пять — paid, paid_over, underpaid, expired, cancelled.
| Статус | Терминальный | Описание |
|---|---|---|
awaiting_client |
Нет | Стартовый статус любого инвойса. Клиент ещё не выбрал токен/сеть, поэтому адрес для оплаты не назначен. Отменить инвойс можно только в этом статусе |
awaiting_payment |
Нет | Адрес назначен, ожидает входящий перевод |
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 возвращает существующий инвойс — дубля не будет. HTTP-статус: 200 OK для идемпотентного совпадения, 201 Created для нового инвойса.
Это страховка от ненадёжной сети, отказа воркеров и повторных запросов. Один и тот же id — на одно бизнес-событие; новый id — на новое событие.
Используйте UUID v4 или свой ID заказа — что угодно стабильное и уникальное для каждого бизнес-события. Если случайно переиспользовать ID, вернётся тот же инвойс, что был создан под этим ID в первый раз (то самое идемпотентное совпадение 200 OK, описанное выше), а не новый.
Ошибки
См. Коды ошибок — полный каталог. URI в поле type каждой ошибки ведёт сразу на нужную строку.