关于 flutterwave
flutterwave 是面向开发者的 AI Agent 技能,用于集成 Flutterwave API。它通过 TypeScript 客户端支持 v3 和 v4,并提供支付、转账、验证等操作示例与指南。
主要功能
- 01支持 Flutterwave API v3 和 v4
- 02具备 v4 令牌缓存的 TypeScript 客户端
- 03支付与转账操作示例
- 04Webhook 验证指南
- 05沙盒测试数据
适用场景
- 01创建托管结账支付
- 02履约前验证交易
- 03发起转账并查询状态
- 04通过密钥哈希校验处理 Webhook
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ devmubarak1/ flutterwave --skill flutterwave安装后在 Claude Code 中输入/flutterwave确认已加载。
详细介绍
flutterwave
用于集成 Flutterwave API 的完整、可用于生产环境的 AI Agent 技能。原生支持 v3(生产稳定版) 和 v4(下一代公开 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(例如https://developer.flutterwave.com/docs/getting-started.md),即可获取不含 HTML 冗余内容的整洁 Markdown 文档。
架构与版本对比
| 特性 | v3(生产稳定版) | v4(下一代公开 Beta 版) |
|---|---|---|
| 状态 | 已在全球部署,经过实战检验 | 公开 Beta 版(高性能、微服务架构) |
| 基础 URL | https://api.flutterwave.com/v3 | 沙盒:https://developersandbox-api.flutterwave.com正式环境: https://f4bexperience.flutterwave.com/ |
| 身份验证 | 静态 Secret Key Bearer(FLW_SECRET_KEY) | OAuth 2.0 OpenID Connect 客户端凭据(client_id + client_secret) |
| 令牌端点 | N/A | POST https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token |
| 追踪标头 | 可选 | X-Trace-Id(12–255 个字符的 UUID,必需) |
| 幂等性 | 负载中的自定义 tx_ref | X-Idempotency-Key 标头 + 负载中的 reference |
| 主要入账 | POST /payments 或 POST /charges | 使用 customer_id 和 payment_method_id 执行 POST /charges |
| 主要出账 | 使用账户详情执行 POST /transfers | 使用 payment_instruction 和 recipient_id 执行 POST /transfers |
通用客户端实现(TypeScript)
不依赖外部 CLI。支持完整的双版本执行、自动缓存 v4 OAuth 2.0 令牌,以及请求追踪。
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;
}
}
常见操作与版本矩阵
使用此技能的任何开发者或 AI Agent 都可以在 v3 或 v4 中执行任意操作:
| 操作 | 方法 | v3 路由 | v4 Beta 路由 | 关键参数/描述 |
|---|---|---|---|---|
| 标准托管结账 | POST | /payments | /charges | tx_ref、amount、currency、redirect_url、customer: { email, name }。返回结账 URL。 |
| 验证交易(按 ID) | GET | /transactions/{id}/verify | /charges/{id} | 确认最终付款状态、结算金额和卡令牌。履行订单前必须验证。 |
| 验证交易(按引用) | GET | /transactions/verify_by_reference?tx_ref=... | /charges?reference=... | 使用商户自定义的订单/交易引用查询付款。 |
| 列出交易 | GET | /transactions | /charges | 按 from、to、status、currency 和分页参数 page 筛选。 |
| 直接银行卡扣款 | POST | /charges?type=card | /charges | v3 使用 3DES 加密载荷;v4 使用带有 authorization 的 payment_method_id。 |
| 验证扣款(OTP) | POST | /validate-charge | /charges/{id}/validate | 提交客户 OTP 验证码(flw_ref、otp)。 |
| 银行转账收款 | POST | /charges?type=bank_transfer | /virtual-accounts | 生成动态专属尼日利亚 NIP 银行账户,并设有过期计时器。 |
| 移动支付(M-Pesa) | POST | /charges?type=mpesa | /charges | 在肯尼亚客户的移动设备上触发即时 STK 推送提示(KES)。 |
| 移动支付(加纳) | POST | /charges?type=mobile_money_ghana | /charges | 从 MTN、Telecel(Vodafone 代金券)或 AirtelTigo 钱包扣款(GHS)。 |
| USSD 付款 | POST | /charges?type=ussd | /charges | 返回银行专属的交互式拨号代码(例如 GTBank 的 *737*...#),用于线下付款。 |
| 令牌化扣款 | POST | /tokenized-charges | /charges(recurring: true) | 使用已存储的 card.token 重新扣款,实现无需输入卡信息的一键结账和订阅。 |
| 发起转账(付款) | POST | /transfers | /transfers | 将资金支付到银行账户或移动钱包。v4 使用 payment_instruction + action。 |
| 获取转账状态 | GET | /transfers/{id} | /transfers/{id} | 查看付款状态(SUCCESSFUL、FAILED、PENDING)。 |
| 重试转账 | POST | /transfers/{id}/retries | /transfers/{id}/retry | 重新提交失败的转账以供处理。 |
| 列出收款人 | GET | /beneficiaries | /transfer-recipients | 列出已保存的银行账户和移动钱包付款联系人。 |
| 创建收款人 | POST | /beneficiaries | /transfer-recipients | 保存收款人(account_number、account_bank、beneficiary_name、currency)。 |
| 创建虚拟账户 | POST | /virtual-account-numbers | /virtual-accounts | 开通永久或动态虚拟银行账户,供客户直接为钱包充值。 |
| 批量创建虚拟账户 | POST | /bulk-virtual-account-numbers | /virtual-accounts | 为金融科技平台批量创建专属虚拟账户。 |
| 创建子账户(分账) | POST | /subaccounts | 分账配置 | 配置用于市场分账的供应商子账户(split_type: "percentage" | "flat")。 |
| 创建付款计划 | POST | /payment-plans | 订阅计划 | 设置自动订阅扣款周期(daily、weekly、monthly、yearly)。 |
| 创建虚拟卡 | POST | /virtual-cards | 发卡 | 发行数字美元(USD)或本地货币借记卡,并设置账单地址。 |
| 查询银行账户 | POST | /accounts/resolve | /accounts/lookup | 验证尼日利亚银行账户号码,并查询官方登记的账户持有人姓名。 |
| 验证 BVN | POST | /bvn/match | KYC 验证 | 确认银行验证号码与客户的法定身份相符,以满足合规要求。 |
| 发起退款 | POST | /transactions/{id}/refund | /refunds | 将全额或部分款项直接退回客户的付款来源账户。 |
| 管理拒付 | GET / PUT | /chargebacks | /chargebacks | 查询未结争议申诉,并在截止日期前提交辩护证据。 |
| 列出结算记录 | GET | /settlements | /settlement | 对账银行存款、交易总额和处理商扣除的费用。 |
| 支付账单和购买话费 | POST | /bills | 公用事业账单 | 购买话费、移动数据套餐、电力代币,并续订有线电视订阅。 |
操作代码示例
1. 创建标准结账支付链接(v3)
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: "[email protected]",
name: "Amina Bello",
phonenumber: "+2348012345678",
},
customizations: {
title: "Store Checkout",
description: "Payment for order #1082",
},
}),
});
console.log("Customer Payment URL:", session.data.link);
2. 强制进行服务器端交易验证(v3 和 v4)
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. 发起即时银行转账 / 付款(v3 和 v4)
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 接收与安全标准
Flutterwave 使用在 Flutterwave Dashboard(Settings > Webhooks)中配置的密钥哈希,并通过 verif-hash HTTP 标头验证 Webhook:
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 });
}
测试向量与沙盒测试
| 支付渠道 | 测试输入 | 预期行为 |
|---|---|---|
| 3DS Mastercard | 卡号:5531881111111111,CVV:123,有效期:09/32 | 需要 OTP -> 输入 12345 -> 成功 |
| Verve 卡(PIN) | 卡号:5060666666666666,CVV:111,有效期:05/29 | 需要 PIN 1234,然后输入 OTP 12345 -> 成功 |
| 模拟拒绝 | 卡号:5531882222222222,CVV:123,有效期:09/32 | 直接拒绝(Insufficient funds) |
| 肯尼亚 M-Pesa | 电话:2540700000000 | 在沙盒中自动模拟并批准 STK 推送 |
| 银行信息解析 | 账号:0690000031,银行:044(Access Bank) | 解析出姓名:FLW TEST ACCOUNT |
面向开发者和 AI Agent 的最佳实践
- 版本隔离: 不要混用 v3 请求字段(例如直接嵌入的
customer.email)和 v4 结构(payment_instruction、customer_id)。 - 切勿信任重定向 URL: 不要仅因客户访问了
redirect_url?status=successful就履行订单。务必执行服务器端验证。 - 使用确定性引用: 为每笔付款和付款操作提供唯一的
tx_ref或reference,以避免冲突并确保幂等性。 - 凭证安全: 将
FLW_SECRET_KEY、FLW_V4_CLIENT_ID、FLW_V4_CLIENT_SECRET和FLW_WEBHOOK_HASH存储在安全的环境变量中。切勿将凭证提交到 git。
评论
支持 Markdown