npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@waffo/waffo-node

v2.0.2

Published

Official Node.js SDK for the Waffo payment platform. Supports order management, refunds, and secure RSA-signed API communication.

Downloads

92

Readme

Waffo PSP Node.js SDK

Waffo PSP(支付服务提供商)收单服务的官方 Node.js SDK。本 SDK 提供基于 RSA 签名的安全 API 通信,以及完整的收单操作类型定义支持。

语言: English | 中文 | 日本語

快速开始

import {
  Waffo,
  Environment,
  CurrencyCode,
  ProductName,
} from '@waffo/waffo-node';

// 1. 初始化 SDK
const waffo = new Waffo({
  apiKey: 'your-api-key',
  privateKey: 'your-base64-encoded-private-key',
  environment: Environment.SANDBOX,
});

// 2. 创建订单
const result = await waffo.order.create({
  paymentRequestId: 'REQ_001',
  merchantOrderId: 'ORDER_001',
  orderCurrency: CurrencyCode.IDR,
  orderAmount: '100000',
  orderDescription: '商品购买',
  notifyUrl: 'https://merchant.com/notify',
  merchantInfo: { merchantId: 'your-merchant-id' },
  userInfo: {
    userId: 'user_001',
    userEmail: '[email protected]',
  },
  paymentInfo: {
    productName: ProductName.ONE_TIME_PAYMENT,
    payMethodType: 'EWALLET',
    payMethodName: 'DANA',
  },
});

// 3. 处理响应
if (result.success) {
  console.log('订单创建成功:', result.data);
  // 引导用户跳转到支付页面
  if (result.data?.orderAction) {
    const action = JSON.parse(result.data.orderAction);
    window.location.href = action.webUrl;
  }
}

提示:需要生成新的 RSA 密钥对?使用 Waffo.generateKeyPair() 来创建:

const keyPair = Waffo.generateKeyPair();
console.log(keyPair.privateKey); // 妥善保管,用于 SDK 初始化
console.log(keyPair.publicKey);  // 提供给 Waffo

特性

  • RSA-2048 请求签名与响应验证
  • 完整的 TypeScript 类型定义支持
  • 零生产依赖(仅使用 Node.js 内置 crypto 模块)
  • 支持沙箱和生产环境
  • 双模块格式支持(ESM/CommonJS)
  • 订单管理(创建、查询、取消、退款、捕获)
  • 订阅管理(创建、查询、取消、获取管理链接)
  • 退款状态查询
  • 商户配置查询(交易限额、每日限额)
  • 支付方式配置查询(可用性、维护时段)
  • Webhook 处理器,支持自动签名验证和事件路由
  • Webhook 签名验证工具
  • 直接 HTTP 客户端访问,支持自定义 API 请求
  • 请求时间戳参数自动默认值

时间戳参数自动默认值

所有时间戳参数(orderRequestedAtrequestedAtcaptureRequestedAt)均为可选参数,如不提供将自动使用当前时间(new Date().toISOString()):

// 时间戳自动设置为当前时间
await waffo.order.create({
  paymentRequestId: 'REQ_001',
  merchantOrderId: 'ORDER_001',
  // ... 其他必填字段
  // orderRequestedAt 自动设置
});

// 或显式提供自定义时间戳
await waffo.order.create({
  paymentRequestId: 'REQ_001',
  merchantOrderId: 'ORDER_001',
  orderRequestedAt: '2025-01-01T00:00:00.000Z', // 自定义时间戳
  // ... 其他必填字段
});

此特性适用于:

  • CreateOrderParams.orderRequestedAt
  • CancelOrderParams.orderRequestedAt
  • RefundOrderParams.requestedAt
  • CaptureOrderParams.captureRequestedAt
  • CreateSubscriptionParams.requestedAt
  • CancelSubscriptionParams.requestedAt

安装

npm install @waffo/waffo-node

使用指南

初始化 SDK

import { Waffo, Environment } from '@waffo/waffo-node';

const waffo = new Waffo({
  apiKey: 'your-api-key',
  privateKey: 'your-base64-encoded-private-key',
  environment: Environment.SANDBOX, // 或 Environment.PRODUCTION
});

生成 RSA 密钥对

import { Waffo } from '@waffo/waffo-node';

const keyPair = Waffo.generateKeyPair();
console.log(keyPair.privateKey); // Base64 编码的 PKCS8 私钥
console.log(keyPair.publicKey);  // Base64 编码的 X509 公钥

创建订单

const result = await waffo.order.create({
  paymentRequestId: 'REQ_001',
  merchantOrderId: 'ORDER_001',
  orderCurrency: CurrencyCode.IDR,
  orderAmount: '100000',
  orderDescription: '商品购买',
  notifyUrl: 'https://merchant.com/notify',
  merchantInfo: {
    merchantId: 'your-merchant-id',
  },
  userInfo: {
    userId: 'user_001',
    userEmail: '[email protected]',
    userPhone: '+62-81234567890',
    userTerminal: UserTerminalType.WEB,
  },
  paymentInfo: {
    productName: ProductName.ONE_TIME_PAYMENT,
    payMethodType: 'EWALLET',
    payMethodName: 'DANA',
  },
});

if (result.success) {
  console.log('订单创建成功:', result.data);
} else {
  console.error('错误:', result.error);
}

查询订单状态

const result = await waffo.order.inquiry({
  acquiringOrderId: 'A202512230000001',
  // 或使用 paymentRequestId: 'REQ_001'
});

if (result.success) {
  console.log('订单状态:', result.data?.orderStatus);
}

取消订单

const result = await waffo.order.cancel({
  acquiringOrderId: 'A202512230000001',
  merchantId: 'your-merchant-id',
  // orderRequestedAt 为可选参数,默认为当前时间
});

订单退款

const result = await waffo.order.refund({
  refundRequestId: 'REFUND_001',
  acquiringOrderId: 'A202512230000001',
  merchantId: 'your-merchant-id',
  refundAmount: '50000',
  refundReason: '用户申请退款',
  refundNotifyUrl: 'https://merchant.com/refund-notify',
  // requestedAt 为可选参数,默认为当前时间
});

查询退款状态

const result = await waffo.refund.inquiry({
  refundRequestId: 'REFUND_001',
  // 或使用 acquiringRefundOrderId: 'R202512230000001'
});

if (result.success) {
  console.log('退款状态:', result.data?.refundStatus);
}

捕获预授权支付

const result = await waffo.order.capture({
  acquiringOrderId: 'A202512230000001',
  merchantId: 'your-merchant-id',
  captureAmount: '100000',
  // captureRequestedAt 为可选参数,默认为当前时间
});

创建订阅

const result = await waffo.subscription.create({
  subscriptionRequest: 'SUB_REQ_001',
  merchantSubscriptionId: 'MERCHANT_SUB_001',
  currency: CurrencyCode.PHP,
  amount: '100',
  productInfo: {
    periodType: PeriodType.MONTHLY,
    periodInterval: '1',
    numberOfPeriod: '12',
    description: '月度订阅',
  },
  paymentInfo: {
    productName: ProductName.SUBSCRIPTION,
    payMethodType: 'EWALLET',
    payMethodName: 'GCASH',
  },
  merchantInfo: { merchantId: 'your-merchant-id' },
  userInfo: {
    userId: 'user_001',
    userEmail: '[email protected]',
  },
  goodsInfo: {
    goodsId: 'GOODS_001',
    goodsName: '高级会员',
  },
  notifyUrl: 'https://merchant.com/subscription/notify',
  // requestedAt 为可选参数,默认为当前时间
});

if (result.success) {
  console.log('订阅创建成功:', result.data);
  // 引导用户完成订阅签约
  if (result.data?.subscriptionAction?.webUrl) {
    window.location.href = result.data.subscriptionAction.webUrl;
  }
}

查询订阅状态

const result = await waffo.subscription.inquiry({
  merchantId: 'your-merchant-id',
  subscriptionId: 'SUB_202512230000001',
  paymentDetails: '1', // 包含支付历史
});

if (result.success) {
  console.log('订阅状态:', result.data?.subscriptionStatus);
}

取消订阅

const result = await waffo.subscription.cancel({
  merchantId: 'your-merchant-id',
  subscriptionId: 'SUB_202512230000001',
  // requestedAt 为可选参数,默认为当前时间
});

获取订阅管理链接

const result = await waffo.subscription.manage({
  subscriptionId: 'SUB_202512230000001',
  // 或使用 subscriptionRequest: 'SUB_REQ_001'
});

if (result.success) {
  console.log('管理链接:', result.data?.managementUrl);
  console.log('过期时间:', result.data?.expiresAt);
  // 引导用户管理其订阅
  window.location.href = result.data?.managementUrl;
}

查询商户配置

const result = await waffo.merchantConfig.inquiry({
  merchantId: 'your-merchant-id',
});

if (result.success) {
  console.log('每日限额:', result.data?.totalDailyLimit);
  console.log('剩余每日限额:', result.data?.remainingDailyLimit);
  console.log('单笔交易限额:', result.data?.transactionLimit);
}

查询支付方式配置

const result = await waffo.payMethodConfig.inquiry({
  merchantId: 'your-merchant-id',
});

if (result.success) {
  result.data?.payMethodDetails.forEach(method => {
    console.log(`${method.payMethodName}: ${method.currentStatus === '1' ? '可用' : '不可用'}`);
    if (method.fixedMaintenanceRules) {
      console.log('维护时段:', method.fixedMaintenanceRules);
    }
  });
}

Webhook 处理器

SDK 提供了内置的 Webhook 处理器,可自动处理签名验证、事件路由和响应签名:

// 在 Webhook 处理器中
app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-signature'] as string;
  const body = JSON.stringify(req.body);

  const result = await waffo.webhook.handle(body, signature, {
    onPayment: async ({ notification }) => {
      console.log('支付状态:', notification.result.orderStatus);
      // 处理支付通知
    },
    onRefund: async ({ notification }) => {
      console.log('退款状态:', notification.result.refundStatus);
      // 处理退款通知
    },
    onSubscriptionStatus: async ({ notification }) => {
      console.log('订阅状态:', notification.result.subscriptionStatus);
      // 处理订阅状态变更
    },
    onSubscriptionPayment: async ({ notification }) => {
      console.log('订阅支付:', notification.result.orderStatus);
      // 处理订阅周期性支付
    },
    onError: async (error) => {
      console.error('Webhook 错误:', error.message);
    },
  });

  return res.json(result.response);
});

手动 Webhook 签名验证

如需更细粒度的控制,可以使用底层 Webhook 工具函数:

import {
  verifyWebhookSignature,
  buildSuccessResponse,
  buildFailedResponse,
  isPaymentNotification,
  isRefundNotification,
} from '@waffo/waffo-node';

// 在 Webhook 处理器中
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const body = JSON.stringify(req.body);

  // 验证签名
  const verifyResult = verifyWebhookSignature(body, signature, waffoPublicKey);

  if (!verifyResult.valid) {
    return res.json(buildFailedResponse('签名验证失败', merchantPrivateKey));
  }

  // 根据类型处理通知
  const notification = verifyResult.notification;

  if (isPaymentNotification(notification)) {
    // 处理支付通知
    console.log('支付状态:', notification.result.orderStatus);
  } else if (isRefundNotification(notification)) {
    // 处理退款通知
    console.log('退款状态:', notification.result.refundStatus);
  }

  // 返回成功响应
  return res.json(buildSuccessResponse(merchantPrivateKey));
});

直接 HTTP 客户端访问

用于 SDK 方法未覆盖的自定义 API 请求:

const response = await waffo.httpClient.post<CustomResponseType>('/custom/endpoint', {
  body: { key: 'value' }
});

if (response.success) {
  console.log(response.data);
}

配置选项

| 选项 | 类型 | 必填 | 默认值 | 描述 | |------|------|------|--------|------| | apiKey | string | 是 | - | Waffo 提供的 API 密钥 | | privateKey | string | 是 | - | Base64 编码的 PKCS8 私钥 | | waffoPublicKey | string | 否 | 内置公钥 | 用于响应验证的自定义 Waffo 公钥 | | environment | Environment | 否 | PRODUCTION | API 环境(SANDBOX 或 PRODUCTION) | | timeout | number | 否 | 30000 | 请求超时时间(毫秒) | | logger | Logger | 否 | - | 调试用的日志实例(可使用 console) |

API 响应格式

所有 API 方法返回 ApiResponse<T> 对象:

interface ApiResponse<T> {
  success: boolean;      // 请求是否成功
  statusCode: number;    // HTTP 状态码
  data?: T;              // 响应数据(成功时)
  error?: string;        // 错误信息(失败时)
}

类型定义

SDK 导出完整的 TypeScript 类型定义,包括:

  • Environment - SDK 环境枚举
  • CountryCode - ISO 3166-1 alpha-3 国家代码
  • CurrencyCode - ISO 4217 货币代码
  • ProductName - 支付产品类型枚举(ONE_TIME_PAYMENT、SUBSCRIPTION)
  • payMethodType - 支付方式类别(字符串: "EWALLET"、"CREDITCARD"、"BANKTRANSFER"、"ONLINE_BANKING"、"DIGITAL_BANKING"、"OTC"、"DEBITCARD")
  • payMethodName - 具体支付方式(字符串: "OVO"、"DANA"、"GOPAY"、"GCASH"、"CC_VISA"、"CC_MASTERCARD"、"VA_BCA"、"VA_BNI" 等)
  • OrderStatus - 订单状态枚举(PAY_IN_PROGRESS、AUTHORIZATION_REQUIRED、PAY_SUCCESS、ORDER_CLOSE 等)
  • RefundStatus - 退款状态枚举(REFUND_IN_PROGRESS、ORDER_PARTIALLY_REFUNDED、ORDER_FULLY_REFUNDED、ORDER_REFUND_FAILED)
  • SubscriptionStatus - 订阅状态枚举(AUTHORIZATION_REQUIRED、ACTIVE、PAUSED、MERCHANT_CANCELLED 等)
  • PeriodType - 订阅周期类型枚举(DAILY、WEEKLY、MONTHLY、YEARLY)
  • UserTerminalType - 用户终端类型枚举(WEB、APP、IN_WALLET_APP、IN_MINI_PROGRAM)
  • 所有 API 操作的请求/响应接口

开发

# 安装依赖
npm install

# 构建(同时生成 ESM 和 CJS)
npm run build

# 运行测试
npm test

# 运行测试并生成覆盖率报告
npm run test:coverage

# 监听模式运行测试
npm run test:watch

# 代码检查
npm run lint

# 代码检查并自动修复
npm run lint:fix

# 格式化代码
npm run format

# 检查代码格式
npm run format:check

代码质量

本项目使用以下工具:

  • ESLint - 代码检查,支持 TypeScript
  • Prettier - 代码格式化
  • Husky - Git 钩子
  • lint-staged - 对暂存文件运行检查

提交代码时,pre-commit 钩子会自动对暂存的 .ts 文件运行 ESLint 和 Prettier。

运行 E2E 测试

E2E 测试需要 Waffo 沙箱环境凭证。SDK 支持多个商户配置,用于不同的测试场景,并基于官方 Waffo 测试用例文档提供全面的测试覆盖。

# 复制模板文件并填写凭证
cp .env.template .env
# 编辑 .env 文件,填入你的凭证

环境变量说明:

| 变量名 | 必填 | 描述 | |--------|------|------| | WAFFO_PUBLIC_KEY | 否 | 用于签名验证的 Waffo 公钥(通用) | | ACQUIRING_MERCHANT_ID | 是* | 支付/订单测试用的商户 ID | | ACQUIRING_API_KEY | 是* | 支付/订单测试用的 API 密钥 | | ACQUIRING_MERCHANT_PRIVATE_KEY | 是* | 支付/订单测试用的私钥 | | SUBSCRIPTION_MERCHANT_ID | 是** | 订阅测试用的商户 ID | | SUBSCRIPTION_API_KEY | 是** | 订阅测试用的 API 密钥 | | SUBSCRIPTION_MERCHANT_PRIVATE_KEY | 是** | 订阅测试用的私钥 |

* 运行支付/订单 E2E 测试时必填 ** 运行订阅 E2E 测试时必填

E2E 测试覆盖范围:

| 模块 | 测试用例 | |------|----------| | 创建订单 | 支付成功/失败、渠道拒绝 (C0005)、幂等错误 (A0011)、系统错误 (C0001)、未知状态 (E0001) | | 查询订单 | 支付前/支付后查询 | | 取消订单 | 支付前取消、渠道不支持 (A0015)、已支付 (A0013) | | 退款订单 | 全额/部分退款、参数校验 (A0003)、退款规则 (A0014) | | 创建订阅 | 订阅成功/失败、下期支付模拟 | | 取消订阅 | 商户发起取消 | | Webhook 通知 | 支付、退款、订阅通知签名验证 |

沙箱环境金额触发规则:

| 金额模式 | 错误码 | 描述 | |----------|--------|------| | 9, 90, 990, 1990, 19990 | C0005 | 渠道拒绝 | | 9.1, 91, 991, 1991, 19991 | C0001 | 系统错误 | | 9.2, 92, 992, 1992, 19992 | E0001 | 未知状态 | | 9.3, 93, 993, 1993, 19993 | C0001 | 取消系统错误 | | 9.4, 94, 994, 1994, 19994 | E0001 | 取消未知状态 | | 9.5, 95, 995, 1995, 19995 | C0001 | 退款系统错误 | | 9.6, 96, 996, 1996, 199996 | E0001 | 退款未知状态 |

# 运行所有测试
npm test

构建产物

SDK 使用 tsup 构建,输出以下文件:

| 文件 | 格式 | 描述 | |------|------|------| | dist/index.js | CommonJS | 用于 require() 导入 | | dist/index.mjs | ESM | 用于 import 语句 | | dist/index.d.ts | TypeScript | 类型声明文件 |