Перейти к содержимому
API
Открыть .md
На странице

Создание

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

POST/v1/invoices

API-ключ: Payment

Тело запроса

Параметр Тип Обязателен Описание
project_id string Да Идентификатор проекта с префиксом (prj_...). API-ключ привязан к мерчанту; укажите, какой проект получит инвойс
amount string Да Сумма платежа строкой с десятичной точкой (например, "50.00")
currency string Да Код фиатной валюты (USD, EUR и т.д.) или символ криптоактива. Полный список — Поддерживаемые валюты. У криптовалюты на вкладке «Валюты» проекта должна остаться включённой хотя бы одна сеть; если выключены все, запрос вернёт 400 currency_not_enabled_for_project
network string Условно Код сети блокчейна — TRC20, ERC20, BEP20, POLYGON, ARBITRUM, OPTIMISM, BASE, TON, AVAX, SOL, NEAR, SUI или PLASMA (см. Поддерживаемые валюты). Нужна, если вы хотите зафиксировать крипто-инвойс на конкретной сети. Сеть, выключенную в проекте, API отклоняет с 400 field_invalid_enum по полю network
external_order_id string Да Ваш уникальный идентификатор заказа (макс. 128 символов), уникальный в проекте — один на тестовый и рабочий режимы. Делает создание инвойса идемпотентным: на повтор того же запроса придёт уже созданный инвойс со статусом 200 OK, а не дубль. Тот же id с другими amount, currency, network или из другого режима вернёт 409 (invoice_idempotency_conflict). Не может начинаться с wgt_ — этот префикс занят инвойсами Low-Code SDK
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 для нового инвойса.

Повтор должен быть тем же запросом. Если amount, currency или network отличаются от инвойса, уже созданного под этим id, или запрос подписан ключом другого режима, API ответит 409 с кодом invoice_idempotency_conflict и ничего не создаст. Id уникален в проекте сразу для тестового и рабочего режимов: реальный заказ не может взять id, который уже был в тестах.

Это страховка от ненадёжной сети, сбоя фоновых процессов и повторных запросов. Один и тот же id — на одно бизнес-событие; новый id — на новое событие.

Используйте UUID v4 или свой ID заказа — что угодно стабильное и уникальное для каждого бизнес-события. Случайный повтор ID второго инвойса не создаст: на тот же запрос вернётся уже созданный (200 OK), на отличающийся — 409. ID с префиксом wgt_ зарезервированы за инвойсами Low-Code SDK и отклоняются с field_invalid_format.

Ошибки

См. Коды ошибок — полный каталог. URI в поле type каждой ошибки ведёт сразу на нужную строку.