Ir al contenido

API

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).

POST/v1/invoices

Clave de API: Payment

Cuerpo de la petición

Parámetro Tipo Obligatorio Descripción
project_id string 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 Importe a pagar como cadena decimal (por ejemplo, "50.00")
currency string 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 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 directoamount es el importe del token, en una cadena fija Nada: token y red están fijados
token (USDT, USDC, …) Flujo criptoamount es el importe del token, la cadena queda abierta La red en la que pagar
fiat (USD, EUR, …) Flujo fiatamount 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
underpaid 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 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 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.