mcpField
F
flutterwave

flutterwave

github.com/devmubarak1开发3150 次安装2 周前更新
代码仓库

关于 flutterwave

flutterwave 是面向开发者的 AI Agent 技能,用于集成 Flutterwave API。它通过 TypeScript 客户端支持 v3 和 v4,并提供支付、转账、验证等操作示例与指南。

主要功能

  1. 01支持 Flutterwave API v3 和 v4
  2. 02具备 v4 令牌缓存的 TypeScript 客户端
  3. 03支付与转账操作示例
  4. 04Webhook 验证指南
  5. 05沙盒测试数据

适用场景

  1. 01创建托管结账支付
  2. 02履约前验证交易
  3. 03发起转账并查询状态
  4. 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(生产稳定版)v4(下一代公开 Beta 版)
状态已在全球部署,经过实战检验公开 Beta 版(高性能、微服务架构)
基础 URLhttps://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/APOST https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token
追踪标头可选X-Trace-Id(12–255 个字符的 UUID,必需)
幂等性负载中的自定义 tx_refX-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/chargestx_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/chargesv3 使用 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验证尼日利亚银行账户号码,并查询官方登记的账户持有人姓名。
验证 BVNPOST/bvn/matchKYC 验证确认银行验证号码与客户的法定身份相符,以满足合规要求。
发起退款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 的最佳实践

  1. 版本隔离: 不要混用 v3 请求字段(例如直接嵌入的 customer.email)和 v4 结构(payment_instruction、customer_id)。
  2. 切勿信任重定向 URL: 不要仅因客户访问了 redirect_url?status=successful 就履行订单。务必执行服务器端验证。
  3. 使用确定性引用: 为每笔付款和付款操作提供唯一的 tx_ref 或 reference,以避免冲突并确保幂等性。
  4. 凭证安全: 将 FLW_SECRET_KEY、FLW_V4_CLIENT_ID、FLW_V4_CLIENT_SECRET 和 FLW_WEBHOOK_HASH 存储在安全的环境变量中。切勿将凭证提交到 git。

评论

支持 Markdown