Ir al contenido
Abrir .md
En esta página

Crear

Reembolsa el total o el pago en exceso de una factura pagada de vuelta a la primera dirección remitente confirmada.

POST/v1/refunds

Clave de API: Payment

Crea un reembolso en cadena para una factura en estado paid o paid_over dentro de los 14 días posteriores a su último depósito confirmado. El servidor calcula automáticamente tanto la dirección de destino como el importe: los fondos vuelven a la dirección remitente del primer depósito confirmado (first_sender) y del saldo del comercio se descuenta amount + fee.

Cuerpo de la petición

Parámetro Tipo Obligatorio Descripción
invoice_id string Sí Identificador de factura con prefijo (inv_...) perteneciente a un proyecto dentro del alcance de la clave
mode string Sí "full" (devuelve el saldo reembolsable restante de la factura) u "overpayment" (devuelve únicamente el excedente sobre el importe solicitado)
external_order_id string Sí Tu referencia de idempotencia (máximo 200 caracteres), única por comercio y entorno

Ejemplo de petición

{
  "invoice_id": "inv_7K2M9P4Q8R1X5Z3A0bC2dE",
  "mode": "full",
  "external_order_id": "refund-1042-full"
}

Ejemplos de código

Cada pestaña utiliza un SDK oficial con una clave Payment (pk_). El SDK gestiona la firma, la serialización, los reintentos seguros y los errores tipados.

API_KEY_ID="pk_live_xxxxxxxxxxxx"
API_SECRET="sk_live_xxxxxxxxxxxx"
BODY='{"invoice_id":"inv_7K2M9P4Q8R1X5Z3A0bC2dE","mode":"full","external_order_id":"refund-1042-full"}'

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/refunds '' "$BODY_HASH" \
  | openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)

curl -sS https://api.paymos.io/v1/refunds \
  -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 refund = await paymos.refunds.create({
  invoiceId: 'inv_7K2M9P4Q8R1X5Z3A0bC2dE',
  mode: 'full',
  externalOrderId: externalOrderId('refund'),
});

console.log(refund.withdrawalId, refund.status);
import os

from paymos import Paymos

paymos = Paymos(
    api_key=os.environ["PAYMOS_API_KEY"],
    api_secret=os.environ["PAYMOS_API_SECRET"],
)
refund = paymos.refunds.create(
    invoice_id="inv_7K2M9P4Q8R1X5Z3A0bC2dE",
    mode="full",
    external_order_id="refund-1042-full",
)

print(refund["withdrawal_id"], refund["status"])
<?php
use Paymos\Client;
use Paymos\ClientConfig;
use Paymos\IdempotencyKey;

$paymos = new Client(new ClientConfig(
    getenv('PAYMOS_API_KEY'),
    getenv('PAYMOS_API_SECRET')
));
$refund = $paymos->refunds()->create(array(
    'invoice_id' => 'inv_7K2M9P4Q8R1X5Z3A0bC2dE',
    'mode' => 'full',
    'external_order_id' => IdempotencyKey::externalOrderId('refund'),
));

echo $refund['withdrawal_id'] . ' ' . $refund['status'];
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)
	}
	refund, err := client.Refunds.Create(context.Background(), paymos.CreateRefundParams{
		InvoiceID:       "inv_7K2M9P4Q8R1X5Z3A0bC2dE",
		Mode:            paymos.RefundModeFull,
		ExternalOrderID: "refund-1042-full",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(refund.WithdrawalID, refund.Status)
}
using Paymos;

using var paymos = new PaymosClient(
    Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
    Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);

var refund = await paymos.Refunds.CreateAsync(new CreateRefundRequest(
    InvoiceId: "inv_7K2M9P4Q8R1X5Z3A0bC2dE",
    Mode: RefundMode.Full,
    ExternalOrderId: "refund-1042-full"));

Console.WriteLine($"{refund.WithdrawalId} {refund.Status}");
import io.paymos.CreateRefundRequest;
import io.paymos.PaymosClient;
import io.paymos.Refund;
import io.paymos.RefundMode;

PaymosClient paymos = new PaymosClient(
    System.getenv("PAYMOS_API_KEY"),
    System.getenv("PAYMOS_API_SECRET"));

Refund refund = paymos.refunds.create(new CreateRefundRequest(
    "inv_7K2M9P4Q8R1X5Z3A0bC2dE",
    RefundMode.FULL,
    "refund-1042-full"));

System.out.println(refund.withdrawalId() + " " + refund.status());
require 'paymos'

paymos = Paymos::Client.new(
  api_key: ENV.fetch('PAYMOS_API_KEY'),
  api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
refund = paymos.refunds.create(
  invoice_id: 'inv_7K2M9P4Q8R1X5Z3A0bC2dE',
  mode: 'full',
  external_order_id: 'refund-1042-full'
)

puts "#{refund.withdrawal_id} #{refund.status}"
use paymos::{CreateRefundRequest, PaymosClient, RefundMode};

let paymos = PaymosClient::new(
    std::env::var("PAYMOS_API_KEY")?,
    std::env::var("PAYMOS_API_SECRET")?,
)?;
let refund = paymos
    .refunds()
    .create(&CreateRefundRequest {
        invoice_id: "inv_7K2M9P4Q8R1X5Z3A0bC2dE".to_owned(),
        mode: RefundMode::Full,
        external_order_id: "refund-1042-full".to_owned(),
    })
    .await?;

println!("{} {}", refund.withdrawal_id, refund.status);

Respuesta (201 Created / 200 si hay coincidencia idempotente)

{
  "withdrawal_id": "wdr_6Qm2fK8xR4vN7cT1bY9dL5",
  "invoice_id": "inv_7K2M9P4Q8R1X5Z3A0bC2dE",
  "mode": "full",
  "external_order_id": "refund-1042-full",
  "status": "created",
  "is_final": false,
  "is_test": false,
  "amount": "100.00",
  "fee": "1.00",
  "currency": "USDT",
  "network": "TRC20",
  "destination_address": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
  "first_sender": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
  "created_at": 1767225600,
  "type": "refund",
  "project_id": "prj_3F9xL2mN8pQ4rT6vW1yZ5b"
}

Campos de la respuesta

Campo Tipo Descripción
withdrawal_id string Identificador de retiro con prefijo (wdr_...) que representa este reembolso
invoice_id string Identificador de factura con prefijo (inv_...) que se reembolsa
mode string Modo de reembolso: "full" u "overpayment"
external_order_id string Tu referencia de idempotencia enviada en la petición de creación
status string Estado actual (created, signed, pending_review, cancelling, completed, failed o cancelled)
is_final boolean true cuando el reembolso alcanza un estado terminal (completed, failed, cancelled)
is_test boolean true para reembolsos en Sandbox
amount string Importe exacto del token devuelto al pagador
fee string? Comisión fija de red por token cobrada al saldo del comercio además de amount. Se omite cuando es cero
currency string Símbolo del criptoactivo (coincide con la moneda de pago de la factura)
network string Código de la red blockchain (coincide con la red de pago de la factura)
destination_address string Dirección de destino del envío (siempre igual a first_sender)
first_sender string Dirección remitente canónica del primer depósito confirmado de la factura
tx_hash string? Hash de la transacción en cadena una vez que status es completed
explorer_url string? Enlace al explorador de bloques para tx_hash una vez que status es completed
created_at integer Marca de tiempo Unix en segundos
completed_at integer? Marca de tiempo Unix en segundos cuando se completó el reembolso
failed_at integer? Marca de tiempo Unix en segundos cuando falló el reembolso
cancelled_at integer? Marca de tiempo Unix en segundos cuando se canceló el reembolso
type string Siempre "refund"
project_id string Identificador con prefijo del proyecto (prj_...) propietario de la factura

Idempotencia

Envía siempre un external_order_id determinista en cada intento de reembolso. Reintentar POST /v1/refunds con el mismo trío (invoice_id, mode, external_order_id) devuelve el reembolso existente con 200 OK en lugar de crear un segundo envío. Reutilizar un external_order_id con otro invoice_id o mode se rechaza con 409 idempotency_conflict.

Solo puede haber un reembolso activo en curso por factura a la vez. Puedes emitir primero un reembolso "overpayment" y, una vez que alcance completed, emitir un reembolso "full" por el importe principal restante.

Errores

Código HTTP Cuándo ocurre
payment_key_required 403 La petición se firmó con una clave Payout (rk_) en lugar de una clave Payment (pk_)
refunds_not_enabled 503 Los reembolsos de facturas están deshabilitados en este entorno
invoice_not_found 404 invoice_id no existe o está fuera del alcance de proyectos de la clave de API
refund_not_eligible 409 La factura no está en estado paid o paid_over, o sus depósitos no son aptos
refund_no_overpayment 409 mode es "overpayment" y la factura no tiene saldo de sobrepago reembolsable
refund_sender_unknown 409 La dirección remitente del primer depósito confirmado no está disponible
refund_window_expired 409 Han transcurrido más de 14 días desde el último depósito confirmado de la factura
refund_network_unsupported 409 No se admiten envíos salientes en la red de pago de la factura
refund_in_flight 409 Ya hay otro reembolso para esta factura en un estado no terminal
invoice_already_refunded 409 La factura ya ha sido reembolsada en su totalidad
refund_source_inconsistent 409 El historial de depósitos o reembolsos de la factura no superó la verificación de consistencia
insufficient_balance 409 El saldo del comercio no alcanza para cubrir amount + fee

Consulta Códigos de error para ver el catálogo completo.