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

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 символов). Привязывает инвойс к конкретному клиенту, чтобы работал лимит на число одновременно открытых инвойсов на одного клиента
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)
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 каждой ошибки ведёт сразу на нужную строку.