Safira Paydocs
集成指南

PIX Cash-In(收款)

概述

PIX Cash-In 端点允许您生成动态 PIX 收款码以接收付款。每笔收款会生成一个唯一的二维码和一个 PIX 代码(复制粘贴),您的客户可以使用它们完成付款。

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

功能特性

  • 动态二维码生成
  • EMV 格式 PIX 代码(复制粘贴)
  • 可配置的到期时间(5 分钟至 7 天)
  • 通过 externalId 唯一标识
  • 可自定义的附加信息
  • 自动验证 CPF/CNPJ
  • 支付分账:在多个收款方之间自动分配

端点

POST /api/pix/cash-in

生成新的 PIX 收款。

必需请求头

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

请求体

{
  "transaction": {
    "value": 150.00,
    "description": "Payment for order #12345",
    "expirationTime": 86400,
    "externalId": "ORDER-12345-20240119",
    "generateQrCode": true
  },
  "payer": {
    "fullName": "Carlos Oliveira",
    "document": "12345678901"
  },
  "additionalInfo": {
    "orderId": "12345",
    "storeName": "Tech Solutions",
    "productCategory": "Electronics"
  },
  "splits": [
    {
      "pixKey": "supplier@email.com",
      "pixKeyType": "EMAIL",
      "name": "Supplier Ltd",
      "document": "12345678000199",
      "type": "PERCENTAGE",
      "value": 10,
      "immediate": false
    }
  ]
}

请求

curl -X POST https://api.safirapay.com/api/pix/cash-in \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "transaction": {
      "value": 150.00,
      "description": "Payment for order #12345",
      "expirationTime": 86400,
      "externalId": "ORDER-12345-20240119",
      "generateQrCode": true
    },
    "payer": {
      "fullName": "Carlos Oliveira",
      "document": "12345678901"
    },
    "additionalInfo": {
      "orderId": "12345"
    },
    "splits": [
      {
        "pixKey": "supplier@email.com",
        "pixKeyType": "EMAIL",
        "name": "Supplier Ltd",
        "document": "12345678000199",
        "type": "PERCENTAGE",
        "value": 70,
        "immediate": false
      }
    ]
  }'

splits 字段为可选。有关类型、金额格式和频率的完整详情,请参阅支付分账指南

响应 (201 Created)

{
  "transactionId": "7845",
  "correlationId": "550e8400-e29b-41d4-a716-446655440000",
  "externalId": "ORDER-12345-20240119",
  "status": "PENDING",
  "pixCode": "00020126580014br.gov.bcb.pix0136550e8400-e29b-41d4-a716-4466554400005204000053039865802BR5916Tech Solutions Ltda6009SAO PAULO62070503***63041D3D",
  "generateTime": "2024-01-19T14:30:00.000Z",
  "expirationDate": "2024-01-20T14:30:00.000Z",
  "qrCodeImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51..."
}

qrCodeImage 字段仅在请求中发送 generateQrCode: true 时返回。值为 Data URL 格式的 Base64 编码 PNG 图片。

请求参数

Transaction 对象

transaction.valuenumberobrigatorio

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

最小值: 0.01

示例: 150.00

transaction.descriptionstringobrigatorio

交易描述,将显示在付款方的账单中。

最大长度: 140 个字符

示例: "Payment for order #12345"

transaction.expirationTimenumber

到期时间,单位为秒。

最小值: 300(5 分钟)

最大值: 604800(7 天)

默认值: 86400(24 小时)

transaction.externalIdstringobrigatorio

交易的唯一外部标识符。用于与您的系统关联。

最大长度: 255 个字符

建议: 使用包含日期/时间的格式以确保唯一性

示例: "ORDER-12345-20240119-143000"

transaction.generateQrCodeboolean

是否生成 Base64 格式的二维码。

默认值: false

建议: 使用 true 以向用户展示二维码

Payer 对象

payer.fullNamestringobrigatorio

付款方全名。

示例: "Carlos Oliveira"

payer.documentstringobrigatorio

付款方的 CPF 或 CNPJ(仅数字)。

CPF: 11 位数字

CNPJ: 14 位数字

示例: "12345678901""12345678000199"

Additional Info 对象

additionalInfoobject

键值对格式的附加信息(string:string)。

最大数量: 10 个键

示例:

{
  "orderId": "12345",
  "customerId": "67890",
  "storeName": "Tech Solutions"
}

Splits 数组(可选)

splitsarray

收款方列表,用于自动分配收到的付款。当 PIX-IN 确认后,金额将被分配并通过 PIX 自动发送给每位收款方。

最大数量: 每笔交易 10 个收款方

splits[].pixKeystringobrigatorio

分账收款方的 PIX 密钥。

最大长度: 255 个字符

示例: "supplier@email.com"

splits[].pixKeyTypestringobrigatorio

收款方的 PIX 密钥类型。

可选值: EMAILPHONECPFCNPJRANDOM

示例: "EMAIL"

splits[].namestringobrigatorio

收款方全名。

最大长度: 255 个字符

示例: "Supplier Ltd"

splits[].documentstringobrigatorio

收款方的 CPF 或 CNPJ(仅数字)。

CPF: 11 位数字 | CNPJ: 14 位数字

示例: "12345678000199"

splits[].typestringobrigatorio

分账类型:固定金额或百分比。

可选值:

  • FIXED -- 固定金额(BRL)(例如:10.00 = R$ 10.00)
  • PERCENTAGE -- 直接百分比(例如:10 = 10%)

示例: "PERCENTAGE"

splits[].valuenumberobrigatorio

根据类型设置的分账金额:

  • FIXED: BRL 金额,最多 2 位小数。10.50 = R$ 10.50
  • PERCENTAGE: 直接百分比。10 = 10%,25.5 = 25.5%

最小值: 0.01

示例: 10(PERCENTAGE 类型表示 10%)或 10.00(FIXED 类型表示 R$ 10.00)

splits[].immediateboolean

如果为 true,分账将在 PIX-IN 确认后立即执行,忽略账户配置的频率。

默认值: false

分账总和(固定金额 + 总额百分比)加上手续费不能超过交易金额。如果超过,API 将返回 400 错误。

响应结构

transactionIdstringsempre presente

Avista 生成的内部交易 ID。

示例: "7845"

correlationIdstringsempre presente

用于跟踪和关联交易的 UUID。

示例: "550e8400-e29b-41d4-a716-446655440000"

externalIdstringsempre presente

请求中提供的外部 ID(与输入值相同)。

示例: "ORDER-12345-20240119"

statusstringsempre presente

当前交易状态。

可选值:

  • PENDING:等待付款
  • CONFIRMED:付款已确认
  • ERROR:处理错误

示例: "PENDING"

pixCodestringsempre presente

EMV 格式的 PIX 代码(复制粘贴)。

示例: "00020126580014br.gov.bcb.pix..."

generateTimestringsempre presente

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

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

expirationDatestringsempre presente

收款码到期日期和时间(ISO 8601 UTC)。

示例: "2024-01-20T14:30:00.000Z"

qrCodeImagestring

Data URL 格式的 Base64 二维码。仅在请求中设置 generateQrCode: true 时返回。

格式: data:image/png;base64,{base64_encoded_image}

示例: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51..."

用途: 可直接在 HTML <img> 标签中显示,或解码后保存为文件。

实现示例

Node.js / TypeScript

import axios from 'axios';

interface CashInRequest {
  transaction: {
    value: number;
    description: string;
    expirationTime?: number;
    externalId: string;
    generateQrCode?: boolean;
  };
  payer: {
    fullName: string;
    document: string;
  };
  additionalInfo?: Record<string, string>;
}

interface CashInResponse {
  transactionId: string;
  correlationId: string;
  externalId: string;
  status: 'PENDING' | 'CONFIRMED' | 'ERROR';
  pixCode: string;
  generateTime: string;
  expirationDate: string;
  qrCodeImage?: string; // Present only when generateQrCode: true
}

async function createPixCharge(
  token: string,
  orderId: string,
  amount: number,
  customerName: string,
  customerDocument: string
): Promise<CashInResponse> {
  const payload: CashInRequest = {
    transaction: {
      value: amount,
      description: `Payment for order ${orderId}`,
      expirationTime: 3600, // 1 hour
      externalId: `ORDER-${orderId}-${Date.now()}`,
      generateQrCode: true
    },
    payer: {
      fullName: customerName,
      document: customerDocument
    },
    additionalInfo: {
      orderId: orderId,
      timestamp: new Date().toISOString()
    }
  };

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

    console.log('PIX charge generated successfully!');
    console.log(`Transaction ID: ${response.data.transactionId}`);
    console.log(`PIX Code: ${response.data.pixCode}`);
    console.log(`Expires at: ${new Date(response.data.expirationDate).toLocaleString('pt-BR')}`);
    if (response.data.qrCodeImage) {
      console.log('QR Code Image available for display');
    }

    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.error('Error generating charge:', error.response?.data);
      throw new Error(error.response?.data?.message || 'Error generating PIX charge');
    }
    throw error;
  }
}

// Usage
const token = 'your_token_here';
createPixCharge(token, '12345', 150.00, 'Carlos Oliveira', '12345678901');

Python

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

def create_pix_charge(
    token: str,
    order_id: str,
    amount: float,
    customer_name: str,
    customer_document: str,
    expiration_hours: int = 1,
    additional_info: Optional[Dict[str, str]] = None
) -> Dict:
    """
    Generate a PIX charge

    Args:
        token: Valid Bearer token
        order_id: Order ID
        amount: Value in BRL
        customer_name: Customer name
        customer_document: CPF or CNPJ (numbers only)
        expiration_hours: Hours until expiration (default: 1)
        additional_info: Additional information

    Returns:
        Generated charge data
    """
    url = 'https://api.safirapay.com/api/pix/cash-in'

    payload = {
        'transaction': {
            'value': round(amount, 2),
            'description': f'Payment for order {order_id}',
            'expirationTime': expiration_hours * 3600,
            'externalId': f'ORDER-{order_id}-{int(datetime.now().timestamp())}',
            'generateQrCode': True
        },
        'payer': {
            'fullName': customer_name,
            'document': customer_document
        },
        'additionalInfo': additional_info or {}
    }

    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 charge generated successfully!')
        print(f"Transaction ID: {data['transactionId']}")
        print(f"PIX Code: {data['pixCode']}")
        print(f"Status: {data['status']}")

        expiration = datetime.fromisoformat(data['expirationDate'].replace('Z', '+00:00'))
        print(f"Expires at: {expiration.strftime('%d/%m/%Y %H:%M:%S')}")

        if 'qrCodeImage' in data:
            print('QR Code Image available for display')

        return data

    except requests.exceptions.RequestException as e:
        print(f'Error generating charge: {e}')
        if hasattr(e.response, 'json'):
            print(f'Details: {e.response.json()}')
        raise

# Usage
token = 'your_token_here'
charge = create_pix_charge(
    token=token,
    order_id='12345',
    amount=150.00,
    customer_name='Carlos Oliveira',
    customer_document='12345678901',
    expiration_hours=24,
    additional_info={
        'storeName': 'Tech Solutions',
        'productCategory': 'Electronics'
    }
)

PHP

<?php

function createPixCharge(
    string $token,
    string $orderId,
    float $amount,
    string $customerName,
    string $customerDocument,
    int $expirationHours = 1
): array {
    $url = 'https://api.safirapay.com/api/pix/cash-in';

    $payload = [
        'transaction' => [
            'value' => round($amount, 2),
            'description' => "Payment for order $orderId",
            'expirationTime' => $expirationHours * 3600,
            'externalId' => "ORDER-$orderId-" . time(),
            'generateQrCode' => true
        ],
        'payer' => [
            'fullName' => $customerName,
            'document' => $customerDocument
        ],
        'additionalInfo' => [
            'orderId' => $orderId
        ]
    ];

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json'
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 201) {
        throw new Exception("Error generating charge: HTTP $httpCode - $response");
    }

    $data = json_decode($response, true);

    echo "PIX charge generated successfully!" . PHP_EOL;
    echo "Transaction ID: {$data['transactionId']}" . PHP_EOL;
    echo "PIX Code: {$data['pixCode']}" . PHP_EOL;
    echo "Status: {$data['status']}" . PHP_EOL;
    if (isset($data['qrCodeImage'])) {
        echo "QR Code Image available for display" . PHP_EOL;
    }

    return $data;
}

// Usage
$token = 'your_token_here';
$charge = createPixCharge(
    $token,
    '12345',
    150.00,
    'Carlos Oliveira',
    '12345678901',
    24
);

使用场景

1. 电子商务 - PIX 结账

// E-commerce checkout integration
class PixCheckout {
  constructor(token) {
    this.token = token;
  }

  async generatePayment(order) {
    const charge = await createPixCharge(
      this.token,
      order.id,
      order.total,
      order.customer.name,
      order.customer.document
    );

    // Display QR Code on the page (using API image or generating locally)
    this.displayQrCode(charge);

    // Start polling to check payment
    this.startPaymentPolling(charge.transactionId);

    return charge;
  }

  displayQrCode(charge) {
    const qrCanvas = document.getElementById('qr-canvas');
    const qrImage = document.getElementById('qr-image');

    // Use Base64 image from API (preferred - avoids client-side processing)
    if (charge.qrCodeImage) {
      qrImage.src = charge.qrCodeImage;
      qrImage.style.display = 'block';
      qrCanvas.style.display = 'none';
    } else {
      // Fallback: generate QR Code locally using a library (e.g.: qrcode.js)
      QRCode.toCanvas(qrCanvas, charge.pixCode, {
        width: 300,
        margin: 2
      });
      qrCanvas.style.display = 'block';
      qrImage.style.display = 'none';
    }

    // Also show the PIX Copy and Paste code
    document.getElementById('pix-code').textContent = charge.pixCode;
  }

  startPaymentPolling(transactionId) {
    // Check status every 3 seconds
    const interval = setInterval(async () => {
      const status = await this.checkPaymentStatus(transactionId);

      if (status === 'CONFIRMED') {
        clearInterval(interval);
        this.onPaymentConfirmed();
      }
    }, 3000);

    // Stop after 10 minutes
    setTimeout(() => clearInterval(interval), 10 * 60 * 1000);
  }

  onPaymentConfirmed() {
    // Redirect to success page
    window.location.href = '/payment/success';
  }
}

2. POS(销售终端)

class PixPDV:
    """POS system with PIX charge"""

    def __init__(self, token: str):
        self.token = token

    def process_sale(self, items: list, customer: dict) -> dict:
        """Process sale and generate PIX charge"""

        # Calculate total
        total = sum(item['price'] * item['quantity'] for item in items)

        # Generate description
        description = self.generate_sale_description(items)

        # Create PIX charge (expires in 15 minutes)
        charge = create_pix_charge(
            token=self.token,
            order_id=self.generate_sale_id(),
            amount=total,
            customer_name=customer['name'],
            customer_document=customer['document'],
            expiration_hours=0.25,  # 15 minutes
            additional_info={
                'items_count': str(len(items)),
                'cashier_id': self.get_cashier_id()
            }
        )

        # Print receipt with QR Code
        self.print_receipt(charge, items, total)

        return charge

    def generate_sale_description(self, items: list) -> str:
        """Generate a summary description of the sale"""
        if len(items) == 1:
            return f"{items[0]['name']}"
        else:
            return f"{len(items)} items - {items[0]['name']} and more"

    def print_receipt(self, charge: dict, items: list, total: float):
        """Print receipt with QR Code"""
        # Implement thermal printing or generate PDF
        print("\n" + "="*50)
        print("PIX CHARGE RECEIPT")
        print("="*50)
        for item in items:
            print(f"{item['name']}: R$ {item['price']:.2f}")
        print("-"*50)
        print(f"TOTAL: R$ {total:.2f}")
        print(f"\nTransaction ID: {charge['transactionId']}")
        print(f"PIX Code:\n{charge['pixCode']}")
        print("="*50 + "\n")

3. SaaS - 订阅计费

class SubscriptionBilling {
  constructor(private token: string) {}

  async chargeMonthlySubscription(
    subscriptionId: string,
    userId: string,
    planValue: number
  ) {
    // Fetch user data
    const user = await this.getUserData(userId);

    // Generate charge with 3-day expiration
    const charge = await createPixCharge(
      this.token,
      `SUB-${subscriptionId}-${new Date().getMonth() + 1}`,
      planValue,
      user.name,
      user.document
    );

    // Send email with payment link
    await this.sendPaymentEmail(user.email, charge);

    // Schedule reminder 1 day before expiration
    await this.scheduleReminder(user, charge, 24);

    return charge;
  }

  async sendPaymentEmail(email: string, charge: CashInResponse) {
    // Implement email sending
    const paymentLink = `https://app.example.com/payment/${charge.transactionId}`;

    await sendEmail({
      to: email,
      subject: 'Invoice available - Pay with PIX',
      html: `
        <h2>Your invoice is available</h2>
        <p>Amount: R$ ${charge.value}</p>
        <p>Due date: ${new Date(charge.expirationDate).toLocaleDateString('pt-BR')}</p>
        <p><a href="${paymentLink}">Click here to pay with PIX</a></p>
      `
    });
  }
}

付款监控

要在付款确认时收到通知,您可以:

配置 webhooks 以在状态变更时接收自动通知。

// Webhook endpoint on your server
app.post('/webhooks/pix', (req, res) => {
  const { transactionId, status, externalId } = req.body;

  if (status === 'CONFIRMED') {
    // Process confirmed payment
    processPaymentConfirmation(externalId);
  }

  res.sendStatus(200);
});

定期查询交易状态。

async function monitorPayment(transactionId, maxAttempts = 200) {
  for (let i = 0; i < maxAttempts; i++) {
    const status = await checkTransactionStatus(transactionId);

    if (status === 'CONFIRMED') {
      return true;
    }

    // Wait 3 seconds before trying again
    await new Promise(resolve => setTimeout(resolve, 3000));
  }

  return false; // Timeout
}

响应状态码

状态码描述含义
201收款已创建PIX 收款码生成成功
400数据无效请检查必填字段和格式
401令牌无效未提供令牌、令牌已过期或无效

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

最佳实践

重要说明

已过期的收款码无法重新激活。如需要,请生成新的收款码。

  • 最小金额: R$ 0.01
  • 最短到期时间: 5 分钟(300 秒)
  • 最长到期时间: 7 天(604,800 秒)

后续步骤

本页目录