flutterwave
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseflutterwave
flutterwave
The complete, production-ready AI agent skill for integrating the Flutterwave API. Natively supports both v3 (Production Stable) and v4 (Next-Gen Public Beta) so developers and AI agents can choose and switch versions seamlessly without third-party CLI or proxy dependencies.
- Official Docs (v3): https://developer.flutterwave.com/docs
- API Reference (v3): https://developer.flutterwave.com/reference
- Official AI Index (v3): https://developer.flutterwave.com/llms.txt
- Official Docs & AI Index (v4): https://developer.flutterwave.com/v4.0.0/llms.txt
- Markdown Docs Shortcut: Append to any Flutterwave documentation URL (e.g.
.md) for clean markdown without HTML noise.https://developer.flutterwave.com/docs/getting-started.md
用于集成 Flutterwave API 的完整、生产就绪 AI Agent skill。原生支持 v3(Production Stable,生产稳定版) 和 v4(Next-Gen Public Beta,下一代公开测试版),让开发者和 AI Agent 可以无缝选择并切换版本,无需依赖第三方 CLI 或代理。
- 官方文档(v3): https://developer.flutterwave.com/docs
- API 参考(v3): https://developer.flutterwave.com/reference
- 官方 AI 索引(v3): https://developer.flutterwave.com/llms.txt
- 官方文档与 AI 索引(v4): https://developer.flutterwave.com/v4.0.0/llms.txt
- Markdown 文档快捷方式: 在任意 Flutterwave 文档 URL 后追加 (例如
.md),即可获得不含 HTML 干扰的简洁 Markdown 内容。https://developer.flutterwave.com/docs/getting-started.md
Architecture & Version Comparison
架构与版本对比
| Feature | v3 (Production Stable) | v4 (Next-Gen Public Beta) |
|---|---|---|
| Status | Globally deployed, battle-tested | Public Beta (high performance, microservices) |
| Base URL | | Sandbox: |
| Authentication | Static Secret Key Bearer ( | OAuth 2.0 OpenID Connect Client Credentials ( |
| Token Endpoint | N/A | |
| Tracing Header | Optional | |
| Idempotency | Custom | |
| Primary Inflow | | |
| Primary Outflow | | |
| 特性 | v3(Production Stable,生产稳定版) | v4(Next-Gen Public Beta,下一代公开测试版) |
|---|---|---|
| 状态 | 全球部署,经过实战验证 | 公开测试版(高性能、微服务) |
| 基础 URL | | Sandbox(沙箱): |
| 身份验证 | 静态 Secret Key Bearer( | OAuth 2.0 OpenID Connect Client Credentials( |
| Token 端点 | 不适用 | |
| 追踪请求头 | 可选 | |
| 幂等性 | 请求体中使用自定义 | |
| 主要资金流入 | | |
| 主要资金流出 | | |
Universal Client Implementation (TypeScript)
通用客户端实现(TypeScript)
Zero external CLI dependencies. Provides full dual-version execution, automatic v4 OAuth 2.0 token caching, and request tracing.
ts
import crypto from "node:crypto";
export interface FlutterwaveConfig {
secretKey?: string; // v3 secret key (e.g., FLWSECK_TEST-... or FLWSECK-...)
encryptionKey?: string; // v3 3DES encryption key for direct card charges
v4ClientId?: string; // v4 OAuth2 client_id
v4ClientSecret?: string; // v4 OAuth2 client_secret
v3BaseUrl?: string; // default: https://api.flutterwave.com/v3
v4BaseUrl?: string; // sandbox: https://developersandbox-api.flutterwave.com | live: https://f4bexperience.flutterwave.com/
v4IdpUrl?: string; // default: https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
}
export interface FlwResponse<T = unknown> {
status: "success" | "error";
message: string;
data: T;
meta?: Record<string, unknown>;
}
export class FlutterwaveApiError extends Error {
constructor(
message: string,
public readonly statusCode: number,
public readonly payload?: unknown,
) {
super(message);
this.name = "FlutterwaveApiError";
}
}
export class Flutterwave {
private readonly config: FlutterwaveConfig;
private v4TokenCache: { token: string; expiresAt: number } | null = null;
constructor(config: FlutterwaveConfig = {}) {
this.config = {
secretKey: config.secretKey ?? process.env.FLW_SECRET_KEY,
encryptionKey: config.encryptionKey ?? process.env.FLW_ENCRYPTION_KEY,
v4ClientId: config.v4ClientId ?? process.env.FLW_V4_CLIENT_ID,
v4ClientSecret: config.v4ClientSecret ?? process.env.FLW_V4_CLIENT_SECRET,
v3BaseUrl: config.v3BaseUrl ?? process.env.FLW_V3_BASE_URL ?? "https://api.flutterwave.com/v3",
v4BaseUrl: config.v4BaseUrl ?? process.env.FLW_V4_BASE_URL ?? "https://developersandbox-api.flutterwave.com",
v4IdpUrl: config.v4IdpUrl ?? "https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token",
};
}
/**
* Retrieves or refreshes OAuth 2.0 access token for v4 requests.
*/
private async getV4Token(): Promise<string> {
const now = Date.now();
if (this.v4TokenCache && this.v4TokenCache.expiresAt > now + 60_000) {
return this.v4TokenCache.token;
}
if (!this.config.v4ClientId || !this.config.v4ClientSecret) {
throw new Error("Missing v4 credentials: FLW_V4_CLIENT_ID and FLW_V4_CLIENT_SECRET are required for v4 calls.");
}
const res = await fetch(this.config.v4IdpUrl!, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: this.config.v4ClientId,
client_secret: this.config.v4ClientSecret,
}).toString(),
});
if (!res.ok) {
throw new FlutterwaveApiError(`Failed to fetch v4 OAuth token: ${res.statusText}`, res.status);
}
const data = (await res.json()) as { access_token: string; expires_in: number };
this.v4TokenCache = {
token: data.access_token,
expiresAt: now + data.expires_in * 1000,
};
return data.access_token;
}
/**
* Executes a typed API request to either v3 or v4.
*/
async request<T = unknown>(
version: "v3" | "v4",
path: string,
options: RequestInit & { idempotencyKey?: string } = {},
): Promise<FlwResponse<T>> {
const { idempotencyKey, headers: customHeaders, ...init } = options;
const headers: Record<string, string> = {
"Content-Type": "application/json",
...(customHeaders as Record<string, string>),
};
let baseUrl: string;
if (version === "v3") {
if (!this.config.secretKey) {
throw new Error("Missing FLW_SECRET_KEY for v3 request");
}
baseUrl = this.config.v3BaseUrl!;
headers["Authorization"] = `Bearer ${this.config.secretKey}`;
} else {
baseUrl = this.config.v4BaseUrl!;
const token = await this.getV4Token();
headers["Authorization"] = `Bearer ${token}`;
headers["X-Trace-Id"] = crypto.randomUUID();
if (idempotencyKey) {
headers["X-Idempotency-Key"] = idempotencyKey;
}
}
const url = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
const response = await fetch(url, { ...init, headers });
const body = (await response.json()) as FlwResponse<T>;
if (!response.ok || body.status === "error") {
throw new FlutterwaveApiError(
body.message || `Flutterwave ${version} request failed with status ${response.status}`,
response.status,
body,
);
}
return body;
}
}无外部 CLI 依赖。提供完整的双版本执行能力、自动缓存 v4 OAuth 2.0 token 和请求追踪功能。
ts
import crypto from "node:crypto";
export interface FlutterwaveConfig {
secretKey?: string; // v3 secret key (e.g., FLWSECK_TEST-... or FLWSECK-...)
encryptionKey?: string; // v3 3DES encryption key for direct card charges
v4ClientId?: string; // v4 OAuth2 client_id
v4ClientSecret?: string; // v4 OAuth2 client_secret
v3BaseUrl?: string; // default: https://api.flutterwave.com/v3
v4BaseUrl?: string; // sandbox: https://developersandbox-api.flutterwave.com | live: https://f4bexperience.flutterwave.com/
v4IdpUrl?: string; // default: https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
}
export interface FlwResponse<T = unknown> {
status: "success" | "error";
message: string;
data: T;
meta?: Record<string, unknown>;
}
export class FlutterwaveApiError extends Error {
constructor(
message: string,
public readonly statusCode: number,
public readonly payload?: unknown,
) {
super(message);
this.name = "FlutterwaveApiError";
}
}
export class Flutterwave {
private readonly config: FlutterwaveConfig;
private v4TokenCache: { token: string; expiresAt: number } | null = null;
constructor(config: FlutterwaveConfig = {}) {
this.config = {
secretKey: config.secretKey ?? process.env.FLW_SECRET_KEY,
encryptionKey: config.encryptionKey ?? process.env.FLW_ENCRYPTION_KEY,
v4ClientId: config.v4ClientId ?? process.env.FLW_V4_CLIENT_ID,
v4ClientSecret: config.v4ClientSecret ?? process.env.FLW_V4_CLIENT_SECRET,
v3BaseUrl: config.v3BaseUrl ?? process.env.FLW_V3_BASE_URL ?? "https://api.flutterwave.com/v3",
v4BaseUrl: config.v4BaseUrl ?? process.env.FLW_V4_BASE_URL ?? "https://developersandbox-api.flutterwave.com",
v4IdpUrl: config.v4IdpUrl ?? process.env.FLW_V4_IDP_URL ?? "https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token",
};
}
/**
* Retrieves or refreshes OAuth 2.0 access token for v4 requests.
*/
private async getV4Token(): Promise<string> {
const now = Date.now();
if (this.v4TokenCache && this.v4TokenCache.expiresAt > now + 60_000) {
return this.v4TokenCache.token;
}
if (!this.config.v4ClientId || !this.config.v4ClientSecret) {
throw new Error("Missing v4 credentials: FLW_V4_CLIENT_ID and FLW_V4_CLIENT_SECRET are required for v4 calls.");
}
const res = await fetch(this.config.v4IdpUrl!, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: this.config.v4ClientId,
client_secret: this.config.v4ClientSecret,
}).toString(),
});
if (!res.ok) {
throw new FlutterwaveApiError(`Failed to fetch v4 OAuth token: ${res.statusText}`, res.status);
}
const data = (await res.json()) as { access_token: string; expires_in: number };
this.v4TokenCache = {
token: data.access_token,
expiresAt: now + data.expires_in * 1000,
};
return data.access_token;
}
/**
* Executes a typed API request to either v3 or v4.
*/
async request<T = unknown>(
version: "v3" | "v4",
path: string,
options: RequestInit & { idempotencyKey?: string } = {},
): Promise<FlwResponse<T>> {
const { idempotencyKey, headers: customHeaders, ...init } = options;
const headers: Record<string, string> = {
"Content-Type": "application/json",
...(customHeaders as Record<string, string>),
};
let baseUrl: string;
if (version === "v3") {
if (!this.config.secretKey) {
throw new Error("Missing FLW_SECRET_KEY for v3 request");
}
baseUrl = this.config.v3BaseUrl!;
headers["Authorization"] = `Bearer ${this.config.secretKey}`;
} else {
baseUrl = this.config.v4BaseUrl!;
const token = await this.getV4Token();
headers["Authorization"] = `Bearer ${token}`;
headers["X-Trace-Id"] = crypto.randomUUID();
if (idempotencyKey) {
headers["X-Idempotency-Key"] = idempotencyKey;
}
}
const url = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
const response = await fetch(url, { ...init, headers });
const body = (await response.json()) as FlwResponse<T>;
if (!response.ok || body.status === "error") {
throw new FlutterwaveApiError(
body.message || `Flutterwave ${version} request failed with status ${response.status}`,
response.status,
body,
);
}
return body;
}
}Popular Actions & Version Matrix
常用操作与版本对照表
Any developer or AI agent using this skill can execute any operation in either v3 or v4:
| Action | Method | v3 Route | v4 Beta Route | Key Parameters / Description |
|---|---|---|---|---|
| Standard Hosted Checkout | | | | |
| Verify Transaction (by ID) | | | | Confirms final payment status, settled amount, and card token. Mandatory before order fulfillment. |
| Verify Transaction (by ref) | | | | Queries payment using the merchant's custom order/transaction reference. |
| List Transactions | | | | Filters by |
| Direct Card Charge | | | | v3 uses 3DES encrypted payload; v4 uses |
| Validate Charge (OTP) | | | | Submits customer OTP challenge code ( |
| Bank Transfer Collection | | | | Generates dynamic dedicated Nigerian NIP bank account with expiry timer. |
| Mobile Money (M-Pesa) | | | | Triggers instant STK push prompt on customer mobile device in Kenya ( |
| Mobile Money (Ghana) | | | | Charges MTN, Telecel (Vodafone voucher), or AirtelTigo wallets ( |
| USSD Payment | | | | Returns bank-specific interactive dial code (e.g. |
| Tokenized Charge | | | | Re-bills stored |
| Initiate Transfer (Payout) | | | | Disburses funds to bank accounts or mobile wallets. v4 uses |
| Get Transfer Status | | | | Inspects payout status ( |
| Retry Transfer | | | | Resubmits failed transfer for reprocessing. |
| List Beneficiaries | | | | Lists saved bank account and mobile wallet payout contacts. |
| Create Beneficiary | | | | Saves recipient ( |
| Create Virtual Account | | | | Provisions permanent or dynamic virtual bank accounts for direct customer wallet funding. |
| Bulk Virtual Accounts | | | | Batches creation of dedicated virtual accounts for fintech platforms. |
| Create Subaccount (Split) | | | Split configuration | Configures vendor subaccount for marketplace splits ( |
| Create Payment Plan | | | Subscription plans | Sets automated subscription billing intervals ( |
| Create Virtual Card | | | Card issuing | Issues digital dollar ( |
| Resolve Bank Account | | | | Verifies Nigerian bank account number and resolves official registered account holder name. |
| Validate BVN | | | KYC validation | Confirms Bank Verification Number matches customer legal identity for compliance. |
| Issue Refund | | | | Issues full or partial refunds directly to customer's source account. |
| Manage Chargebacks | | | | Queries open dispute claims and submits defense evidence before the due date. |
| List Settlements | | | | Reconciles bank deposits, gross transactions, and processor fee deductions. |
| Pay Bills & Airtime | | | Utility bills | Purchases airtime, mobile data bundles, power tokens, and renews cable TV subscriptions. |
使用此 skill 的开发者或 AI Agent 均可在 v3 或 v4 中执行任意操作:
| 操作 | 方法 | v3 路由 | v4 Beta 路由 | 关键参数/说明 |
|---|---|---|---|---|
| 标准托管结账 | | | | |
| 验证交易(按 ID) | | | | 确认最终支付状态、结算金额和银行卡 token。订单履约前必须执行。 |
| 验证交易(按 ref) | | | | 使用商户自定义的订单或交易 reference 查询付款。 |
| 交易列表 | | | | 支持按 |
| 银行卡直接扣款 | | | | v3 使用 3DES 加密请求体;v4 使用 |
| 验证扣款(OTP) | | | | 提交客户 OTP 挑战验证码( |
| 银行转账收款 | | | | 生成带到期计时器的尼日利亚 NIP 动态专用银行账户。 |
| 移动支付(M-Pesa) | | | | 在肯尼亚客户移动设备上触发即时 STK push 提示( |
| 移动支付(加纳) | | | | 从 MTN、Telecel(Vodafone voucher)或 AirtelTigo 钱包扣款( |
| USSD 支付 | | | | 返回银行专属的交互式拨号代码(例如 GTBank 的 |
| Token 化扣款 | | | | 使用已保存的 |
| 发起转账(付款) | | | | 向银行账户或移动钱包拨付资金。v4 使用 |
| 获取转账状态 | | | | 查看付款状态( |
| 重试转账 | | | | 重新提交失败的转账以供再次处理。 |
| 受益人列表 | | | | 列出已保存的银行账户和移动钱包付款联系人。 |
| 创建受益人 | | | | 保存收款人( |
| 创建虚拟账户 | | | | 开通永久或动态虚拟银行账户,供客户钱包直接充值。 |
| 批量虚拟账户 | | | | 为 fintech 平台批量创建专用虚拟账户。 |
| 创建子账户(分账) | | | 分账配置 | 为 marketplace 分账配置商户子账户( |
| 创建支付计划 | | | 订阅计划 | 设置自动订阅扣款周期( |
| 创建虚拟卡 | | | 发卡 | 签发数字美元( |
| 解析银行账户 | | | | 验证尼日利亚银行账号,并解析官方登记的账户持有人姓名。 |
| 验证 BVN | | | KYC 验证 | 确认 Bank Verification Number 与客户法定身份一致,以满足合规要求。 |
| 发起退款 | | | | 直接向客户原始账户发放全额或部分退款。 |
| 管理拒付 | | | | 查询未结争议,并在截止日期前提交抗辩证据。 |
| 结算记录列表 | | | | 对账银行存款、交易总额和支付处理商费用扣除。 |
| 支付账单与购买话费 | | | 公用事业账单 | 购买话费、移动流量包、用电代扣券,并续订有线电视服务。 |
Action Code Recipes
操作代码示例
1. Create a Standard Checkout Payment Link (v3)
1. 创建标准托管结账支付链接(v3)
ts
const flw = new Flutterwave();
const session = await flw.request<{ link: string }>("v3", "/payments", {
method: "POST",
body: JSON.stringify({
tx_ref: `ORD-${Date.now()}`,
amount: "7500.00",
currency: "NGN",
redirect_url: "https://yourdomain.com/checkout/callback",
customer: {
email: "buyer@example.com",
name: "Amina Bello",
phonenumber: "+2348012345678",
},
customizations: {
title: "Store Checkout",
description: "Payment for order #1082",
},
}),
});
console.log("Customer Payment URL:", session.data.link);ts
const flw = new Flutterwave();
const session = await flw.request<{ link: string }>("v3", "/payments", {
method: "POST",
body: JSON.stringify({
tx_ref: `ORD-${Date.now()}`,
amount: "7500.00",
currency: "NGN",
redirect_url: "https://yourdomain.com/checkout/callback",
customer: {
email: "buyer@example.com",
name: "Amina Bello",
phonenumber: "+2348012345678",
},
customizations: {
title: "Store Checkout",
description: "Payment for order #1082",
},
}),
});
console.log("Customer Payment URL:", session.data.link);2. Mandatory Server-Side Transaction Verification (v3 & v4)
2. 服务端交易验证(必需,v3 和 v4)
ts
const flw = new Flutterwave();
// v3 verification by Transaction ID
const v3Tx = await flw.request<{
status: string;
amount: number;
currency: string;
tx_ref: string;
card?: { token: string };
}>("v3", `/transactions/${transactionId}/verify`, { method: "GET" });
if (v3Tx.data.status === "successful" && v3Tx.data.amount >= orderAmount) {
// Safe to credit account or ship goods
const savedCardToken = v3Tx.data.card?.token;
}
// v4 Next-Gen verification by Charge ID
const v4Charge = await flw.request<{
status: string;
amount: number;
currency: string;
reference: string;
}>("v4", `/charges/${chargeId}`, { method: "GET" });ts
const flw = new Flutterwave();
// v3 verification by Transaction ID
const v3Tx = await flw.request<{
status: string;
amount: number;
currency: string;
tx_ref: string;
card?: { token: string };
}>("v3", `/transactions/${transactionId}/verify`, { method: "GET" });
if (v3Tx.data.status === "successful" && v3Tx.data.amount >= orderAmount) {
// Safe to credit account or ship goods
const savedCardToken = v3Tx.data.card?.token;
}
// v4 Next-Gen verification by Charge ID
const v4Charge = await flw.request<{
status: string;
amount: number;
currency: string;
reference: string;
}>("v4", `/charges/${chargeId}`, { method: "GET" });3. Initiate Instant Bank Transfer / Payout (v3 & v4)
3. 发起即时银行转账/付款(v3 和 v4)
ts
const flw = new Flutterwave();
// v3 Payout
const v3Payout = await flw.request("v3", "/transfers", {
method: "POST",
body: JSON.stringify({
account_bank: "058", // GTBank
account_number: "0123456789",
amount: 50000,
currency: "NGN",
reference: `PAYOUT-${Date.now()}`,
narration: "Vendor disbursement",
}),
});
// v4 Next-Gen Transfer Orchestrator
const v4Payout = await flw.request("v4", "/transfers", {
method: "POST",
idempotencyKey: `IDEMP-${Date.now()}`,
body: JSON.stringify({
action: "instant",
reference: `V4-PAYOUT-${Date.now()}`,
narration: "Vendor payment",
payment_instruction: {
amount: 50000,
currency: "NGN",
recipient_id: "rcb_B9aAgsdzzl",
},
}),
});ts
const flw = new Flutterwave();
// v3 Payout
const v3Payout = await flw.request("v3", "/transfers", {
method: "POST",
body: JSON.stringify({
account_bank: "058", // GTBank
account_number: "0123456789",
amount: 50000,
currency: "NGN",
reference: `PAYOUT-${Date.now()}`,
narration: "Vendor disbursement",
}),
});
// v4 Next-Gen Transfer Orchestrator
const v4Payout = await flw.request("v4", "/transfers", {
method: "POST",
idempotencyKey: `IDEMP-${Date.now()}`,
body: JSON.stringify({
action: "instant",
reference: `V4-PAYOUT-${Date.now()}`,
narration: "Vendor payment",
payment_instruction: {
amount: 50000,
currency: "NGN",
recipient_id: "rcb_B9aAgsdzzl",
},
}),
});Webhook Ingestion & Security Standard
Webhook 接收与安全标准
Flutterwave verifies webhooks using a secret hash configured in your Flutterwave Dashboard (Settings > Webhooks) and passed in the HTTP header:
verif-hashts
import crypto from "node:crypto";
export function handleFlutterwaveWebhook(req: Request) {
const incomingHash = req.headers.get("verif-hash");
const secretHash = process.env.FLW_WEBHOOK_HASH;
if (!incomingHash || !secretHash) {
return new Response("Unauthorized", { status: 401 });
}
// Timing-safe constant-time comparison to prevent side-channel leaks
const a = Buffer.from(incomingHash, "utf8");
const b = Buffer.from(secretHash, "utf8");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return new Response("Forbidden", { status: 403 });
}
// Respond HTTP 200 immediately to meet Flutterwave's 60-second SLA
// Process the event asynchronously in the background
return new Response(JSON.stringify({ status: "success" }), { status: 200 });
}Flutterwave 使用在 Flutterwave Dashboard(Settings > Webhooks)中配置、并通过 HTTP 请求头传入的 secret hash 验证 webhook:
verif-hashts
import crypto from "node:crypto";
export function handleFlutterwaveWebhook(req: Request) {
const incomingHash = req.headers.get("verif-hash");
const secretHash = process.env.FLW_WEBHOOK_HASH;
if (!incomingHash || !secretHash) {
return new Response("Unauthorized", { status: 401 });
}
// Timing-safe constant-time comparison to prevent side-channel leaks
const a = Buffer.from(incomingHash, "utf8");
const b = Buffer.from(secretHash, "utf8");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return new Response("Forbidden", { status: 403 });
}
// Respond HTTP 200 immediately to meet Flutterwave's 60-second SLA
// Process the event asynchronously in the background
return new Response(JSON.stringify({ status: "success" }), { status: 200 });
}Test Vectors & Sandbox Testing
测试向量与沙箱测试
| Rail | Test Input | Expected Behavior |
|---|---|---|
| 3DS Mastercard | Card: | Requires OTP -> Enter |
| Verve Card (PIN) | Card: | Requires PIN |
| Decline Simulation | Card: | Direct decline ( |
| Kenya M-Pesa | Phone: | Auto-approves simulated STK push in sandbox |
| Bank Resolution | Account: | Resolves name: |
| 支付通道 | 测试输入 | 预期行为 |
|---|---|---|
| 3DS Mastercard | 银行卡: | 需要 OTP -> 输入 |
| Verve Card(PIN) | 银行卡: | 需要 PIN |
| 拒绝支付模拟 | 银行卡: | 直接拒绝( |
| Kenya M-Pesa | 手机号: | 在沙箱中自动批准模拟的 STK push |
| 银行账户解析 | 账号: | 解析出姓名: |
Best Practices for Developers and AI Agents
开发者和 AI Agent 最佳实践
- Version Isolation: Do not mix v3 request fields (e.g. embedded directly) with v4 structures (
customer.email,payment_instruction).customer_id - Never Trust Redirect URLs: Never fulfill an order simply because a customer landed on . Always execute server-side verification.
redirect_url?status=successful - Deterministic References: Provide a unique or
tx_refon every payment and payout to avoid collision and guarantee idempotency.reference - Credential Security: Store ,
FLW_SECRET_KEY,FLW_V4_CLIENT_ID, andFLW_V4_CLIENT_SECRETin secure environment variables. Never commit credentials to git.FLW_WEBHOOK_HASH
- 版本隔离: 不要将 v3 请求字段(例如直接嵌入的 )与 v4 结构(
customer.email、payment_instruction)混用。customer_id - 永不信任重定向 URL: 不要仅因为客户跳转到了 就履约。始终执行服务端验证。
redirect_url?status=successful - 确定性引用: 为每笔支付和资金转移提供唯一的 或
tx_ref,避免冲突并确保幂等性。reference - 凭据安全: 将 、
FLW_SECRET_KEY、FLW_V4_CLIENT_ID和FLW_V4_CLIENT_SECRET存储在安全的环境变量中。切勿将凭据提交到 git。FLW_WEBHOOK_HASH