Перейти к содержимому

API

На странице

Создание

Создание платёжного инвойса для приёма криптовалютных платежей. Два потока: прямой крипто (токен указан) и фиатный (клиент выбирает токен на странице оплаты).

POST/v1/invoices

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 символов). Хранится в инвойсе и возвращается при чтении и в списках — по нему вы группируете инвойсы в своём учёте. Никаких ограничений 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, VES
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.

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 true, когда инвойс достиг терминального состояния
is_test boolean true для инвойсов в тестовом режиме
payment_url string URL оплаты: ссылка на бота Paymos у проекта «Telegram-бот», платёжная страница у любого другого
order object Данные заказа со стороны мерчанта
order.external_id string ID заказа мерчанта
order.client_id string? ID клиента мерчанта
order.amount string Запрошенная сумма. У фиатного инвойса знаков после точки столько, сколько ISO 4217 отводит этой валюте: "100.00" для USD, "23" для JPY, "1.500" для KWD — таблица в разделе Точность фиатной суммы. У токена такого стандарта нет, поэтому крипто-инвойс отдаёт сумму без нулей в конце: "10.00" USDT вернётся как "10". Нули только дописывают, не округляют: сумму, которая не укладывается в минорную единицу, отклоняют ещё при создании. В списке поле amount работает по тому же правилу
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)
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 каждой ошибки ведёт сразу на нужную строку.