En esta página
Crear
Crea una factura para aceptar un pago en cripto. Dos flujos: cripto directo (token y red fijados) y fiat (el cliente elige el token en la página de pago).
Clave de API: Payment
Cuerpo de la petición
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
project_id |
string | Sí | Identificador de proyecto con prefijo (prj_...). La clave de API tiene alcance de comercio; con este campo indicas qué proyecto recibe la factura |
amount |
string | Sí | Importe a pagar como cadena decimal (por ejemplo, "50.00") |
currency |
string | Sí | Código de moneda fiat (USD, EUR, etc.) o símbolo de activo cripto. La lista completa está en Monedas admitidas |
network |
string | Condicional | Código de la red blockchain: TRC20, ERC20, BEP20, POLYGON, ARBITRUM, OPTIMISM, BASE, TON, AVAX, SOL, NEAR, SUI o PLASMA (consulta Monedas admitidas). Obligatorio cuando quieras fijar una factura en cripto a una red concreta |
external_order_id |
string | Sí | Tu identificador único de pedido (máximo 128 caracteres), único por proyecto. Hace idempotente la creación de facturas: si en el proyecto ya existe una factura con este external_order_id, recibes esa misma factura con 200 OK, ni un duplicado ni un error |
client_id |
string | No | Tu identificador de cliente (máximo 200 caracteres). Se guarda en la factura y vuelve en las lecturas y en los elementos de lista, así agrupas facturas por tu propio cliente. Paymos no aplica ningún límite a partir de este campo |
allow_multiple_payments |
boolean | No | Si la factura acepta varios pagos parciales. Valor por defecto: true |
customer_fee_percent |
integer | No | Porcentaje de comisión que se traslada al cliente (0-100). Sustituye el ajuste del proyecto para esta factura |
Precisión del importe en fiat
En las facturas del flujo fiat, amount debe ajustarse a la unidad menor de currency.
| Unidades menores | Monedas |
|---|---|
| 0 decimales | JPY, KRW, VND, CLP |
| 2 decimales | 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 decimales | BHD, KWD |
Ejemplos: 23.34 es válido para USD; 23.345 se rechaza para USD; 23 es válido para JPY; 23.01 se rechaza para JPY.
Determinación del flujo
El flujo lo determinan los campos que envías. currency decide si el importe se expresa en un token o en fiat; network decide si la cadena queda fijada de antemano o la elige el cliente.
currency |
network |
Flujo | Qué le queda por elegir al cliente |
|---|---|---|---|
token (USDT, USDC, …) |
una red (TRC20, ERC20, …) |
Cripto directo — amount es el importe del token, en una cadena fija |
Nada: token y red están fijados |
token (USDT, USDC, …) |
— | Flujo cripto — amount es el importe del token, la cadena queda abierta |
La red en la que pagar |
fiat (USD, EUR, …) |
— | Flujo fiat — amount es un importe en fiat |
El token y la red |
Toda factura se crea en awaiting_client: todavía no existe dirección de pago. Es la elección del cliente en la página de pago la que asigna la dirección y mueve la factura a awaiting_payment. En el caso de cripto directo no queda nada por elegir, así que la página de pago confirma el token y la red predefinidos y asigna la dirección en un solo paso. En el flujo fiat, el tipo de cambio de fiat a cripto se fija en ese momento y se devuelve en payment.exchange_rate.
Consulta Flujo de pago → Dos formas de crear una factura para el ciclo de vida completo, los estados iniciales y la semántica de fijación del tipo de cambio.
Petición de ejemplo — flujo fiat
El importe va en fiat; el cliente elige un token en la página de pago y el tipo de cambio se fija en ese instante.
{
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"amount": "50.00",
"currency": "USD",
"external_order_id": "order-12345",
"client_id": "customer-67890"
}
Petición de ejemplo — cripto directo
Token y red quedan fijados en la creación; la dirección se asigna de inmediato.
{
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"amount": "50.00",
"currency": "USDT",
"network": "TRC20",
"external_order_id": "order-12345",
"client_id": "customer-67890"
}
Ejemplos de código
Cada pestaña usa el SDK oficial de ese lenguaje. La firma, la serialización, los reintentos seguros y los errores tipados los resuelve el 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);
Respuesta (201 Created / 200 si hay coincidencia idempotente)
{
"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
}
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
invoice_id |
string | Identificador de factura con prefijo (inv_...) |
project_id |
string | Identificador de proyecto con prefijo (prj_...) |
status |
string | Estado de la factura (snake_case; véase Estados de la factura) |
is_final |
boolean | Si la factura está en un estado final |
is_test |
boolean | true en facturas de Sandbox |
payment_url |
string | URL de pago: enlace que abre el bot de Paymos en un proyecto de Bot de Telegram; página de pago alojada en cualquier otro |
order |
object | Datos del pedido desde la perspectiva del comercio |
order.external_id |
string | Número de pedido del comercio |
order.client_id |
string? | Identificador de cliente del comercio |
order.amount |
string | Importe solicitado |
order.currency |
string | Código de moneda fiat o símbolo del activo cripto que solicitó el comercio |
order.network |
string? | Red solicitada en facturas de cripto directo |
payment |
object? | Detalles del pago; aparece una vez elegidos token y red. Contiene la dirección de depósito, el importe esperado en cripto, el tipo de cambio fijado y el avance de cada transferencia. La referencia completa de subcampos está en Obtener factura → Campos de pago |
created_at |
integer | Marca temporal Unix, en segundos |
updated_at |
integer | Marca temporal Unix, en segundos |
expires_at |
integer | Marca temporal Unix, en segundos. Se fija al crear la factura, así que está presente en toda factura y en cualquier estado |
completed_at |
integer? | Marca temporal de finalización |
Estados de la factura
Toda factura informa de un status en forma de cadena. Ese mismo valor alimenta la respuesta de la Merchant API, el flujo SSE de la página de pago y el contenido del webhook: hay una sola fuente de verdad. La tabla siguiente es la referencia por estado: cuándo aparece el valor, si es final y cuál es la condición exacta que lo produce. Para el diagrama de transiciones y las dos formas de crear una factura, consulta Flujo de pago.
Un estado final es definitivo: la factura se queda ahí y ya no vuelve a moverse. Cinco estados son finales: paid, paid_over, underpaid, expired y cancelled.
| Estado | Final | Cuándo se aplica |
|---|---|---|
awaiting_client |
No | Estado inicial de toda factura. Todavía no se ha elegido token ni red, así que no existe dirección de depósito. La cancelación solo se permite aquí |
awaiting_payment |
No | Token, red y dirección de depósito quedan fijados. Paymos vigila la dirección a la espera de una transferencia entrante |
confirming |
No | Una transferencia ha llegado a la cadena y acumula las confirmaciones que exige su tramo de importe |
underpaid_waiting |
No | Ha entrado menos del importe esperado y la factura sigue abierta esperando el resto. Solo se alcanza cuando allow_multiple_payments vale true |
paid |
Sí | El importe esperado entró íntegro (o dentro de la tolerancia de pago insuficiente del proyecto) |
paid_over |
Sí | Entró más del importe esperado; la transferencia se abona completa |
underpaid |
Sí | La factura se cerró con menos de lo debido: o venció por debajo del importe esperado, o un único pago se quedó corto con allow_multiple_payments en false |
expired |
Sí | El plazo venció sin recibir nada, o en el flujo fiat se agotó la ventana de selección de token antes de elegir uno |
cancelled |
Sí | El comercio canceló la factura mientras seguía en awaiting_client |
Idempotencia
Envía siempre external_order_id al crear una factura. Repetir la misma llamada con el mismo external_order_id devuelve la factura existente, nunca un duplicado. El estado de la respuesta es 200 OK en una coincidencia idempotente y 201 Created en una factura nueva.
Es tu red de seguridad frente a redes inestables, procesos caídos y bucles de reintento. Usa el mismo identificador para el mismo evento de negocio y uno nuevo para un evento nuevo.
Usa un UUID v4 o tu propio número de pedido: cualquier valor estable y único por evento de negocio. Si reutilizas un identificador por error, recibirás la factura que ya existía con ese identificador (la coincidencia idempotente con 200 OK descrita arriba), no una nueva.
Errores
Consulta Códigos de error para el catálogo completo. La URI de type en cada respuesta de error enlaza directamente con la fila correspondiente.