Fire Bankingdocs
集成指南

PIX Refund-In(退款)

概述

PIX Refund-In 端点允许您退还(退款)通过 Cash-In 生成的收款码收到的 PIX 付款。退款可以是全额部分退款,必须在收款后 89 天内申请。

此端点需要有效的 Bearer token。详情请参阅认证文档

功能特性

  • 全额或部分退款
  • 同一交易支持多次部分退款
  • 89 天退款窗口期
  • 即时处理
  • 按退款原因追踪

何时使用退款

全额退款

将收到的全部金额退还给原始付款方。

使用场景:

  • 完全取消订单
  • 商品未发货
  • 重复付款
  • 收款金额错误

部分退款

仅退还收到金额的一部分。

使用场景:

  • 退回特定商品
  • 产品/服务问题补偿
  • 金额调整
  • 追溯折扣

端点

POST /api/pix/refund-in/{id}

请求退还已收到的付款。

必需请求头

Authorization: Bearer {token}
Content-Type: application/json

路径参数

idstringobrigatorio

要退款的原始交易(Cash-In)的 ID。

示例: "7845"

请求体

{
  "refundValue": 75.00,
  "reason": "Cliente solicitou devolução de 1 item do pedido"
}

请求

curl -X POST https://api.public.firebanking.com.br/api/pix/refund-in/7845 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "refundValue": 75.00,
    "reason": "Cliente solicitou devolução de 1 item do pedido"
  }'

响应 (201 Created)

{
  "transactionId": "7846",
  "externalId": "D123456789",
  "status": "PENDING",
  "refundValue": 75.00,
  "providerTransactionId": "7ef4fc3f-a187-495e-857c-e84d70612761",
  "generateTime": "2024-01-19T16:30:00.000Z"
}

请求参数

refundValuenumberobrigatorio

退款金额,单位为巴西雷亚尔 (BRL)。最多 2 位小数。

验证规则:

  • 必须大于等于 0.01
  • 不能超过可退款金额
  • 所有退款总额不能超过原始金额

示例: 75.00

reasonstring

退款原因(可选,但建议填写)。

最大长度: 255 个字符

示例: "Cliente solicitou devolução de 1 item do pedido"

建议: 为审计目的始终提供清晰的原因

externalIdstring

退款标识的外部 ID(可选)。

在 BACEN API 中,对应 URL 参数中的 'id'。

示例: "D123456789"

响应结构

transactionIdstringsempre presente

新生成的退款交易 ID。

示例: "7846"

注意: 这是与原始交易不同的 ID

externalIdstringsempre presente

退款交易的外部 ID。

示例: "D123456789"

statusstringsempre presente

当前退款交易状态。

可选值:

  • PENDING:退款处理中
  • CONFIRMED:退款已确认并完成
  • ERROR:处理错误

示例: "PENDING"

refundValuenumbersempre presente

退款金额(BRL)。

示例: 75.00

providerTransactionIdstringsempre presente

提供商的交易 ID(用于与 webhooks 关联)。

示例: "7ef4fc3f-a187-495e-857c-e84d70612761"

generateTimestringsempre presente

退款生成日期和时间(ISO 8601 UTC)。

示例: "2024-01-19T16:30:00.000Z"

实现示例

Node.js / TypeScript

import axios from 'axios';

interface RefundRequest {
  refundValue: number;
  reason?: string;
  externalId?: string;
}

interface RefundResponse {
  transactionId: string;
  externalId: string;
  status: 'PENDING' | 'CONFIRMED' | 'ERROR';
  refundValue: number;
  providerTransactionId: string;
  generateTime: string;
}

async function refundPixPayment(
  token: string,
  originalTransactionId: string,
  refundAmount: number,
  reason?: string
): Promise<RefundResponse> {
  const payload: RefundRequest = {
    refundValue: refundAmount,
    reason: reason || 'Estorno solicitado pelo cliente'
  };

  try {
    const response = await axios.post<RefundResponse>(
      `https://api.public.firebanking.com.br/api/pix/refund-in/${originalTransactionId}`,
      payload,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('PIX refund initiated successfully!');
    console.log(`Refund Transaction ID: ${response.data.transactionId}`);
    console.log(`Original External ID: ${response.data.externalId}`);
    console.log(`Refund Amount: R$ ${response.data.refundValue.toFixed(2)}`);
    console.log(`Status: ${response.data.status}`);

    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      const errorData = error.response?.data;
      console.error('Error processing refund:', errorData);

      // Handle specific errors
      if (error.response?.status === 400) {
        if (errorData?.message?.includes('prazo excedido')) {
          throw new Error('The 89-day refund window has expired');
        }
        if (errorData?.message?.includes('valor inválido')) {
          throw new Error('Refund amount exceeds the available amount for refund');
        }
      }

      if (error.response?.status === 404) {
        throw new Error('Original transaction not found');
      }

      throw new Error(errorData?.message || 'Error processing refund');
    }
    throw error;
  }
}

// Usage - Full Refund
async function fullRefund(token: string, transactionId: string, originalValue: number) {
  return await refundPixPayment(
    token,
    transactionId,
    originalValue,
    'Cancelamento total do pedido'
  );
}

// Usage - Partial Refund
async function partialRefund(token: string, transactionId: string, itemValue: number) {
  return await refundPixPayment(
    token,
    transactionId,
    itemValue,
    'Devolução de 1 item do pedido'
  );
}

// Practical example
const token = 'your_token_here';
const transactionId = '7845';

// Refund R$ 75.00 from a R$ 150.00 transaction
refundPixPayment(token, transactionId, 75.00, 'Cliente solicitou devolução parcial');

Python

import requests
from datetime import datetime
from typing import Dict, Optional

def refund_pix_payment(
    token: str,
    original_transaction_id: str,
    refund_amount: float,
    reason: Optional[str] = None
) -> Dict:
    """
    Refund a received PIX payment

    Args:
        token: Valid Bearer token
        original_transaction_id: Original transaction ID (Cash-In)
        refund_amount: Amount to be refunded
        reason: Refund reason (optional)

    Returns:
        Created refund data
    """
    url = f'https://api.public.firebanking.com.br/api/pix/refund-in/{original_transaction_id}'

    payload = {
        'refundValue': round(refund_amount, 2),
        'reason': reason or 'Estorno solicitado pelo cliente'
    }

    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'application/json'
    }

    try:
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()

        data = response.json()

        print('PIX refund initiated successfully!')
        print(f"Refund Transaction ID: {data['transactionId']}")
        print(f"Original External ID: {data['externalId']}")
        print(f"Refund Amount: R$ {data['refundValue']:.2f}")
        print(f"Status: {data['status']}")

        return data

    except requests.exceptions.HTTPError as e:
        error_data = e.response.json() if e.response else {}
        error_message = error_data.get('message', str(e))

        # Handle specific errors
        if e.response.status_code == 400:
            if 'prazo excedido' in error_message:
                raise Exception('The 89-day refund window has expired')
            if 'valor inválido' in error_message:
                raise Exception('Refund amount exceeds the available amount for refund')
            raise Exception(f'Invalid data: {error_message}')

        if e.response.status_code == 404:
            raise Exception('Original transaction not found')

        raise Exception(f'Error processing refund: {error_message}')

# Usage
token = 'your_token_here'
transaction_id = '7845'

# Partial refund
refund = refund_pix_payment(
    token=token,
    original_transaction_id=transaction_id,
    refund_amount=75.00,
    reason='Cliente solicitou devolução de 1 item do pedido'
)

# Full refund
def full_refund(token: str, transaction_id: str, original_value: float):
    """Performs a full refund"""
    return refund_pix_payment(
        token=token,
        original_transaction_id=transaction_id,
        refund_amount=original_value,
        reason='Cancelamento total do pedido'
    )

响应状态码

状态码描述含义
201退款已创建PIX 退款发起成功
400金额无效退款金额超过可退款金额
400窗口期已过89 天退款窗口期已过期
401令牌无效未提供令牌、令牌已过期或无效
404交易未找到原始交易未找到

请参阅 API 参考 获取响应字段的完整详情。

最佳实践

重要说明

退款一旦发起即无法撤销。请在处理前确认金额正确。

  • 最长窗口期: 收款后 89 天
  • 最小金额: R$ 0.01
  • 多次退款: 允许,只要总额不超过原始金额

后续步骤

本页目录