官方 SDK
Liqfy 为 Node.js、Python、PHP 和 .NET 发布官方客户端。它们封装的是
API 参考 中描述的同一套 /v1 REST API —— 凡是能用 curl 完成的操作,
不用 SDK 也同样能完成。SDK 额外提供的,是那些容易在细节上出错的部分:
幂等性、重试策略以及 webhook 签名校验。
四个 SDK 共用同一个版本号:0.8.0。
| 语言 | 包 | 安装 | 运行时 |
|---|---|---|---|
| Node.js / TypeScript | @liqfy/node | npm i @liqfy/node | Node 18+ |
| Python | liqfy | pip install liqfy | Python 3.8+ |
| PHP | liqfy/sdk-php | composer require liqfy/sdk-php | PHP 8.1+ |
| .NET / C# | Liqfy | dotnet add package Liqfy | netstandard2.0, net8.0 |
#每个 SDK 的保证
这四个客户端在行为上完全一致,而不仅仅是相似。尤其是 webhook 签名校验,在 CI 中针对同一组共享测试向量运行 —— 只要 Node 接受某个签名,Python、PHP 和 .NET 也会逐字节地接受同一个签名。
#资源族
每个客户端都暴露相同的六个资源族 —— 路由和传输格式均与 API 参考 一致:
| 资源族 | Node | Python | PHP | .NET |
|---|---|---|---|---|
收款(/v1/charges) | client.charges | client.charges | $client->charges | client.Charges |
支付操作(/v1/payments) | client.payments | client.payments | $client->payments | client.Payments |
付款(/v1/withdrawals) | client.payouts | client.payouts | $client->payouts | client.Payouts |
余额(/v1/wallets) | client.balance | client.balance | $client->balance | client.Balance |
Webhooks(/v1/webhooks) | client.webhooks(含 client.webhooks.endpoints) | client.webhooks(含 client.webhooks.endpoints) | $client->webhooks(含 $client->webhooks->endpoints) | client.Webhooks(含 client.Webhooks.Endpoints) |
| MED 案例 | client.medCases | client.med_cases | $client->medCases | client.MedCases |
方法名遵循各语言的惯例(Node 中为 client.payments.create(input, { idempotencyKey }),Python 中为 client.payments.create(input, idempotency_key),PHP 中为 $client->payments->create($input, $idempotencyKey),.NET 中为 client.Payments.CreateAsync(input, idempotencyKey))。payments.refund 被有意省略:对于所有使用 API 密钥的请求,API 都会返回 403 —— error.code 为 "forbidden",且消息以 REFUND_ADMIN_ONLY 标记开头(这是消息前缀,而非错误码),因为退款只能通过管理后台发起。
#身份验证
Authorization: Bearer lq_live_…SDK 发送规范的 Authorization: Bearer 请求头;出于兼容性考虑,API 仍接受旧版的
apikey: 请求头。环境(测试或生产)由密钥本身决定。你无需配置环境,
也永远不会传入或返回任何账户或商户标识符。
#资金类写操作必须提供幂等键
创建收款或付款需要提供幂等键。缺少幂等键时,SDK 会直接拒绝调用, 而不是发出请求后听天由命。
请根据你自己的订单(pedido-1234)生成该键,切勿使用随机值。随机值
会让整个机制失效:如果你的请求已经到达但响应丢失,
用新键重试会产生第二笔收款;而用同一个键重试则会返回原始结果。
#重试策略
| 情形 | 是否重试? |
|---|---|
GET / HEAD 遇到 5xx 或网络错误 | 是 |
POST 带有幂等键 | 是 —— 每次尝试使用同一个键 |
POST 未带幂等键 | 绝不 |
任何 4xx | 绝不 —— 请求本身有误,重试无法解决 |
退避采用带完全抖动(full jitter)的指数退避,上限为 8 秒。
关键在于第三行。网络故障并不能告诉你服务器是否已处理该请求 —— 只能说明没有收到响应。在这种不确定的情况下重试不带幂等键的 资金类写操作,正是客户被重复扣款的原因。
#快速入门
#Node.js
import { LiqfyClient } from '@liqfy/node';
const liqfy = new LiqfyClient({ apiKey: process.env.LIQFY_API_KEY });
const charge = await liqfy.charges.create(
{
amount: 15000, // R$ 150.00 in centavos — always an integer
currency: 'BRL',
payment_method: 'pix',
customer: { name: 'Maria Silva', document: '12345678901' },
},
{ idempotencyKey: `pedido-${orderId}` },
);
charge.pix.br_code; // Pix Copia e Cola#Python
import os
from liqfy import LiqfyClient
liqfy = LiqfyClient(api_key=os.environ["LIQFY_API_KEY"])
cobranca = liqfy.charges.create(
{
"amount": 15000,
"currency": "BRL",
"payment_method": "pix",
"customer": {"name": "Maria Silva", "document": "12345678901"},
},
idempotency_key=f"pedido-{pedido_id}",
)
cobranca["pix"]["br_code"]Python SDK 没有任何运行时依赖 —— 仅使用标准库。支付 SDK 运行在你的进程中;它引入的每一个传递依赖包,都会成为你从我们这里继承的 供应链攻击面。
#PHP
<?php
require __DIR__ . '/vendor/autoload.php';
use Liqfy\LiqfyClient;
$cliente = new LiqfyClient(['apiKey' => getenv('LIQFY_API_KEY')]);
$cobranca = $cliente->charges->create([
'amount' => 15000,
'currency' => 'BRL',
'payment_method' => 'pix',
'customer' => ['name' => 'Maria Silva', 'document' => '12345678901'],
], 'pedido-' . $pedidoId);
$cobranca['pix']['br_code'];#C#
using Liqfy;
// Register as a SINGLETON — it is thread-safe and reuses its HttpClient.
// One client per request exhausts TCP ports, the classic .NET trap.
var liqfy = new LiqfyClient(Environment.GetEnvironmentVariable("LIQFY_API_KEY")!);
var cobranca = await liqfy.Charges.CreateAsync(new
{
amount = 15000,
currency = "BRL",
payment_method = "pix",
customer = new { name = "Maria Silva", document = "12345678901" },
}, idempotencyKey: $"pedido-{orderId}");
var brCode = cobranca!.RootElement.GetProperty("pix").GetProperty("br_code").GetString();#校验 webhook
校验不需要 API 密钥 —— 单独实例化 webhook 辅助工具即可。
// Node — Express with a raw body parser
app.post('/webhooks/liqfy', express.raw({ type: 'application/json' }), (req, res) => {
if (!liqfy.webhooks.verify(req.body, req.headers['x-liqfy-signature'], secret)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString('utf8'));
res.status(200).end(); // any 2xx confirms delivery
});# Python — Flask
if not webhooks.verify(request.get_data(), request.headers.get("X-Liqfy-Signature"), secret):
return "", 401// C# — minimal API
if (!webhooks.Verify(corpo, req.Headers["X-Liqfy-Signature"], secret))
return Results.Unauthorized();请传入原始字节。 校验前不要对请求体进行反序列化再重新序列化: 空白字符或键顺序的任何差异都会改变 HMAC,导致签名校验失败。 这是所有语言中最常见的 webhook 集成错误。
默认情况下,超过 5 分钟的签名会被拒绝,从而防止被截获的 POST 请求被无限重放。签名格式和重试计划请参阅 Webhooks。
#错误
每个 SDK 都会抛出带有 status、code、type 和 request_id 的类型化错误。
| 失败类别 | Node | Python | PHP | C# |
|---|---|---|---|---|
| 401 / 403 | LiqfyAuthError | LiqfyAuthError | LiqfyAuthError | LiqfyAuthException |
| 400 / 422 | LiqfyValidationError | LiqfyValidationError | LiqfyValidationError | LiqfyValidationException |
| 409 | LiqfyConflictError | LiqfyConflictError | LiqfyConflictError | LiqfyConflictException |
| 完全没有响应 | LiqfyNetworkError | LiqfyNetworkError | LiqfyNetworkError | LiqfyNetworkException |
请根据 code 进行分支判断,切勿依据消息内容 —— 消息是写给人看的,可能随时变更且不另行通知。code 属于契约的一部分;完整目录请参阅 错误。唯一有文档说明的消息标记是 REFUND_ADMIN_ONLY(退款仅限管理后台操作,并返回普通的 forbidden);请将其作为前缀匹配,而不是作为错误码。
在全部四个 SDK 中,网络错误都被有意设计为不是 API 错误的子类。 当没有收到响应时,你无法知道服务器是否已处理该请求。 在同一个分支中同时捕获两者,恰恰会掩盖决定重试是否安全的那个关键区别。
联系支持团队时请提供 request_id —— 它可以帮助我们在日志中精确定位该请求。