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

Создать

Оформите полный возврат или возврат переплаты по оплаченному инвойсу обратно на адрес первого подтверждённого отправителя.

POST/v1/refunds

API-ключ: Payment

Создаёт ончейн-возврат по инвойсу в статусе paid или paid_over в течение 14 дней с момента последнего подтверждённого депозита. Адрес получателя и сумма рассчитываются сервером автоматически: средства возвращаются на адрес отправителя первого подтверждённого депозита (first_sender), а с баланса мерчанта списывается amount + fee.

Тело запроса

Параметр Тип Обязателен Описание
invoice_id string Да Идентификатор инвойса с префиксом (inv_...), принадлежащего проекту в области видимости ключа
mode string Да "full" (возвращает весь оставшийся доступный к возврату баланс инвойса) или "overpayment" (возвращает только сумму переплаты сверх запрошенной суммы инвойса)
external_order_id string Да Ваш ключ идемпотентности (до 200 символов), уникальный в рамках мерчанта и окружения

Пример запроса

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

Примеры кода

Каждая вкладка использует официальный SDK с платёжным ключом (pk_). Подпись запроса, сериализация, безопасные повторы и типизированные ошибки обрабатываются внутри SDK.

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

Ответ (201 Created / 200 при идемпотентном совпадении)

{
  "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"
}

Поля ответа

Поле Тип Описание
withdrawal_id string Идентификатор выплаты с префиксом (wdr_...), представляющий данный возврат
invoice_id string Идентификатор инвойса (inv_...), по которому оформлен возврат
mode string Режим возврата: "full" или "overpayment"
external_order_id string Ваш идентификатор операции, переданный при создании запроса
status string Текущий статус (created, signed, pending_review, cancelling, completed, failed или cancelled)
is_final boolean true, когда возврат перешёл в финальный статус (completed, failed, cancelled)
is_test boolean true для возвратов в песочнице
amount string Точная сумма токенов, возвращаемая плательщику
fee string? Фиксированная сетевая комиссия за отправку токена, списываемая с баланса мерчанта сверх amount. Опускается, если равна нулю
currency string Символ криптоактива (совпадает с валютой оплаты инвойса)
network string Код блокчейн-сети (совпадает с сетью оплаты инвойса)
destination_address string Адрес назначения выплаты (всегда совпадает с first_sender)
first_sender string Канонический адрес отправителя первого подтверждённого депозита по инвойсу
tx_hash string? Хеш транзакции в блокчейне, заполняется при переходе в статус completed
explorer_url string? Ссылка на транзакцию в блокчейн-эксплорере, заполняется при наличии tx_hash
created_at integer Unix-время создания в секундах
completed_at integer? Unix-время завершения в секундах
failed_at integer? Unix-время перехода в статус ошибки в секундах
cancelled_at integer? Unix-время отмены в секундах
type string Всегда "refund"
project_id string Идентификатор проекта (prj_...), которому принадлежит инвойс

Идемпотентность

Всегда передавайте детерминированный external_order_id для каждой попытки возврата. Повторный вызов POST /v1/refunds с тем же набором (invoice_id, mode, external_order_id) вернёт уже созданный возврат со статусом 200 OK без повторного списания средств. Повторное использование external_order_id с другим invoice_id или mode отклоняется с ошибкой 409 idempotency_conflict.

Одновременно по одному инвойсу может выполняться только один незавершённый возврат. Вы можете сначала оформить возврат переплаты ("overpayment"), а после его перехода в completed — полный возврат оставшейся суммы ("full").

Ошибки

Код HTTP Когда возникает
payment_key_required 403 Запрос подписан ключом Payout (rk_) вместо ключа Payment (pk_)
refunds_not_enabled 503 Возвраты по инвойсам отключены в данном окружении
invoice_not_found 404 invoice_id не существует или не входит в область видимости проектов API-ключа
refund_not_eligible 409 Инвойс не находится в статусе paid или paid_over, либо его депозиты не подходят для возврата
refund_no_overpayment 409 Выбран режим "overpayment", но по инвойсу нет доступной к возврату переплаты
refund_sender_unknown 409 Адрес отправителя первого подтверждённого депозита недоступен
refund_window_expired 409 С момента последнего подтверждённого депозита по инвойсу прошло более 14 дней
refund_network_unsupported 409 Выплаты в сети оплаты инвойса не поддерживаются
refund_in_flight 409 По этому инвойсу уже выполняется другой незавершённый возврат
invoice_already_refunded 409 Инвойс уже полностью возвращён
refund_source_inconsistent 409 История депозитов или возвратов по инвойсу не прошла проверку целостности
insufficient_balance 409 На балансе мерчанта недостаточно средств для покрытия amount + fee

Полный справочник приведён в разделе Коды ошибок.