2
0
Эх сурвалжийг харах

✨ feat(mini-payment): 实现微信小程序支付模块

- 创建独立Payment实体,支持与外部订单系统通过externalOrderId关联
- 实现支付创建、状态查询和回调处理核心功能
- 集成微信支付v3 SDK,支持小程序支付流程
- 设计完整的支付状态管理(PENDING, PROCESSING, PAID等)
- 添加支付路由和中间件,支持API集成

📝 docs(mini-payment): 更新支付模块文档

- 添加Payment实体设计说明
- 记录文档版本历史,当前版本1.3
- 完善支付流程和状态说明
yourname 3 долоо хоног өмнө
parent
commit
0468def938

+ 18 - 0
docs/stories/005.004.mini-payment-package.story.md

@@ -81,6 +81,23 @@ Draft
   - 查询支付状态 [Source: mini-auth-demo/packages/server/src/modules/payment/payment.service.ts:234]
 - **支付状态**: PENDING, PROCESSING, SUCCESS, FAILED, REFUNDED, CLOSED [Source: mini-auth-demo/packages/server/src/modules/payment/payment.service.ts:3]
 
+### Payment 实体设计
+- **独立实体**: 创建独立的 Payment 实体,不依赖外部 Order 实体
+- **关联设计**: 通过 `externalOrderId` 字段与外部订单系统关联
+- **字段设计**:
+  - `id`: 支付记录ID
+  - `externalOrderId`: 外部订单ID(用于与业务系统集成)
+  - `userId`: 用户ID
+  - `totalAmount`: 支付金额(分)
+  - `description`: 支付描述
+  - `paymentStatus`: 支付状态
+  - `wechatTransactionId`: 微信支付交易ID
+  - `outTradeNo`: 商户订单号
+  - `openid`: 用户OpenID
+  - `createdAt`: 创建时间
+  - `updatedAt`: 更新时间
+- **集成方式**: 外部系统通过 `externalOrderId` 与 Payment 实体建立关联
+
 ### 集成点
 - **认证集成**: 使用现有 auth.middleware [Source: architecture/source-tree.md:126]
 - **用户集成**: 依赖 user-module 获取用户信息 [Source: architecture/source-tree.md:98]
@@ -126,6 +143,7 @@ Draft
 
 | Date | Version | Description | Author |
 |------|---------|-------------|---------|
+| 2025-11-11 | 1.3 | 添加独立Payment实体设计,支持与外部订单系统集成 | James (Developer) |
 | 2025-11-11 | 1.2 | 添加测试套件详细用法说明,参考auth-module模式 | Bob (Scrum Master) |
 | 2025-11-11 | 1.1 | 添加现有测试文件迁移任务,优化测试策略 | Bob (Scrum Master) |
 | 2025-11-11 | 1.0 | 创建小程序支付模块故事文档 | Bob (Scrum Master) |

+ 43 - 0
packages/mini-payment/package.json

@@ -0,0 +1,43 @@
+{
+  "name": "@d8d/mini-payment",
+  "version": "1.0.0",
+  "description": "微信小程序支付模块",
+  "type": "module",
+  "main": "dist/index.js",
+  "types": "dist/index.d.ts",
+  "exports": {
+    ".": {
+      "import": "./dist/index.js",
+      "types": "./dist/index.d.ts"
+    },
+    "./package.json": "./package.json"
+  },
+  "files": [
+    "dist"
+  ],
+  "scripts": {
+    "build": "tsc",
+    "dev": "tsc --watch",
+    "test": "vitest run",
+    "test:watch": "vitest",
+    "test:coverage": "vitest run --coverage",
+    "lint": "eslint src --ext .ts",
+    "typecheck": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@d8d/shared-types": "workspace:*",
+    "@d8d/shared-utils": "workspace:*",
+    "@d8d/user-module": "workspace:*",
+    "@d8d/auth-module": "workspace:*",
+    "wechatpay-node-v3": "^1.3.0"
+  },
+  "devDependencies": {
+    "@types/node": "^20.10.5",
+    "typescript": "^5.3.3",
+    "vitest": "^1.2.2",
+    "@vitest/coverage-v8": "^1.2.2"
+  },
+  "peerDependencies": {
+    "hono": "^4.8.5"
+  }
+}

+ 43 - 0
packages/mini-payment/src/entities/payment.entity.ts

@@ -0,0 +1,43 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { PaymentStatus } from './payment.types.js';
+
+@Entity('payments')
+export class PaymentEntity {
+  @PrimaryGeneratedColumn({ comment: '支付记录ID' })
+  id!: number;
+
+  @Column({ type: 'int', unsigned: true, comment: '外部订单ID(用于与业务系统集成)' })
+  externalOrderId!: number;
+
+  @Column({ type: 'int', unsigned: true, comment: '用户ID' })
+  userId!: number;
+
+  @Column({ type: 'int', unsigned: true, comment: '支付金额(分)' })
+  totalAmount!: number;
+
+  @Column({ type: 'varchar', length: 128, comment: '支付描述' })
+  description!: string;
+
+  @Column({
+    type: 'enum',
+    enum: PaymentStatus,
+    default: PaymentStatus.PENDING,
+    comment: '支付状态'
+  })
+  paymentStatus!: PaymentStatus;
+
+  @Column({ type: 'varchar', length: 64, nullable: true, comment: '微信支付交易ID' })
+  wechatTransactionId?: string;
+
+  @Column({ type: 'varchar', length: 64, nullable: true, comment: '商户订单号' })
+  outTradeNo?: string;
+
+  @Column({ type: 'varchar', length: 64, comment: '用户OpenID' })
+  openid!: string;
+
+  @CreateDateColumn({ comment: '创建时间' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ comment: '更新时间' })
+  updatedAt!: Date;
+}

+ 42 - 0
packages/mini-payment/src/entities/payment.types.ts

@@ -0,0 +1,42 @@
+export enum PaymentStatus {
+  PENDING = '待支付',
+  PROCESSING = '支付中',
+  PAID = '已支付',
+  FAILED = '支付失败',
+  REFUNDED = '已退款',
+  CLOSED = '已关闭'
+}
+
+// 微信支付回调数据结构
+export interface WechatPaymentCallbackData {
+  id: string;
+  create_time: string;
+  event_type: string;
+  resource_type: string;
+  resource: {
+    algorithm: string;
+    ciphertext: string;
+    associated_data?: string;
+    nonce: string;
+  };
+  summary: string;
+}
+
+// 微信支付回调头信息
+export interface WechatPaymentCallbackHeaders {
+  'wechatpay-timestamp': string;
+  'wechatpay-nonce': string;
+  'wechatpay-signature': string;
+  'wechatpay-serial': string;
+}
+
+// 支付创建响应
+export interface PaymentCreateResponse {
+  paymentId: string;
+  timeStamp: string;
+  nonceStr: string;
+  package: string;
+  signType: string;
+  paySign: string;
+  totalAmount: number;
+}

+ 5 - 0
packages/mini-payment/src/index.ts

@@ -0,0 +1,5 @@
+export { PaymentService } from './services/payment.service.js';
+export { PaymentRoutes } from './routes/payment.routes.js';
+export { PaymentEntity } from './entities/payment.entity.js';
+export { PaymentStatus } from './entities/payment.types.js';
+export type { PaymentCreateRequest, PaymentCreateResponse } from './schemas/payment.schema.js';

+ 35 - 0
packages/mini-payment/src/middleware/auth.middleware.ts

@@ -0,0 +1,35 @@
+import { createMiddleware } from 'hono/factory';
+
+/**
+ * 认证中间件
+ * 用于验证用户身份并获取用户信息
+ */
+export const authMiddleware = createMiddleware(async (c, next) => {
+  try {
+    // 从认证模块获取用户信息
+    const user = c.get('user');
+
+    if (!user) {
+      return c.json({
+        message: '未授权访问'
+      }, 401);
+    }
+
+    // 设置用户OpenID到上下文
+    const userOpenid = user.openid;
+    if (!userOpenid) {
+      return c.json({
+        message: '用户未绑定微信小程序'
+      }, 400);
+    }
+
+    c.set('userOpenid', userOpenid);
+
+    await next();
+  } catch (error) {
+    console.error('认证中间件错误:', error);
+    return c.json({
+      message: '认证失败'
+    }, 401);
+  }
+});

+ 1 - 0
packages/mini-payment/src/middleware/index.ts

@@ -0,0 +1 @@
+export { authMiddleware } from './auth.middleware.js';

+ 15 - 0
packages/mini-payment/src/routes/payment.routes.ts

@@ -0,0 +1,15 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import createPaymentRoute from './payment/create.js';
+import paymentCallbackRoute from './payment/callback.js';
+import paymentStatusRoute from './payment/status.js';
+
+// 支付模块主路由
+export const PaymentRoutes = new OpenAPIHono()
+  .route('/payment', createPaymentRoute)
+  .route('/payment/callback', paymentCallbackRoute)
+  .route('/payment/status', paymentStatusRoute);
+
+// 导出路由配置,用于集成到主应用
+export const paymentRoutesExport = {
+  '/api/v1': PaymentRoutes
+};

+ 67 - 0
packages/mini-payment/src/routes/payment/callback.ts

@@ -0,0 +1,67 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@d8d/shared-utils';
+import { PaymentService } from '../../services/payment.service.js';
+
+// 支付回调路由定义
+const paymentCallbackRoute = createRoute({
+  method: 'post',
+  path: '/callback',
+  request: {
+    body: {
+      content: {
+        'text/plain': { schema: z.string() }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '回调处理成功',
+      content: { 'text/plain': { schema: z.string() } }
+    },
+    400: {
+      description: '回调数据错误',
+      content: { 'application/json': { schema: z.object({ message: z.string() }) } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'text/plain': { schema: z.string() } }
+    }
+  }
+});
+
+const app = new OpenAPIHono()
+  .openapi(paymentCallbackRoute, async (c) => {
+    try {
+      // 获取原始请求体(用于签名验证)
+      const rawBody = await c.req.text();
+
+      console.log('原始请求体', rawBody)
+
+      // 解析回调数据
+      const callbackData = JSON.parse(rawBody);
+
+      // 获取微信支付回调头信息
+      const headers = {
+        'wechatpay-timestamp': c.req.header('wechatpay-timestamp') || '',
+        'wechatpay-nonce': c.req.header('wechatpay-nonce') || '',
+        'wechatpay-signature': c.req.header('wechatpay-signature') || '',
+        'wechatpay-serial': c.req.header('wechatpay-serial') || ''
+      };
+
+      // 创建支付服务实例
+      const paymentService = new PaymentService(AppDataSource);
+
+      // 处理支付回调
+      await paymentService.handlePaymentCallback(callbackData, headers, rawBody);
+
+      // 返回成功响应给微信支付
+      return c.text('SUCCESS', 200);
+    } catch (error) {
+      console.error('支付回调处理失败:', error);
+      // 返回失败响应给微信支付
+      return c.text('FAIL', 500);
+    }
+  });
+
+export default app;

+ 72 - 0
packages/mini-payment/src/routes/payment/create.ts

@@ -0,0 +1,72 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@d8d/shared-utils';
+import { PaymentService } from '../../services/payment.service.js';
+import { PaymentCreateRequestSchema, PaymentCreateResponseSchema } from '../../schemas/payment.schema.js';
+
+// 支付创建路由定义
+const createPaymentRoute = createRoute({
+  method: 'post',
+  path: '/',
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: PaymentCreateRequestSchema }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '支付创建成功',
+      content: { 'application/json': { schema: PaymentCreateResponseSchema } }
+    },
+    400: {
+      description: '参数错误',
+      content: { 'application/json': { schema: z.object({ message: z.string() }) } }
+    },
+    401: {
+      description: '未授权',
+      content: { 'application/json': { schema: z.object({ message: z.string() }) } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: z.object({ message: z.string() }) } }
+    }
+  }
+});
+
+const app = new OpenAPIHono()
+  .openapi(createPaymentRoute, async (c) => {
+    try {
+      const paymentData = c.req.valid('json');
+      const user = c.get('user');
+
+      // 检查用户是否有openid(小程序用户必需)
+      if (!user?.openid) {
+        return c.json({
+          message: '用户未绑定微信小程序,无法进行支付'
+        }, 400);
+      }
+
+      // 创建支付服务实例
+      const paymentService = new PaymentService(AppDataSource);
+
+      // 创建支付订单,从认证用户中获取openid
+      const paymentResult = await paymentService.createPayment(
+        paymentData.orderId,
+        user.id,
+        paymentData.totalAmount,
+        paymentData.description,
+        user.openid
+      );
+
+      return c.json(paymentResult, 200);
+    } catch (error) {
+      console.error('支付创建失败:', error);
+      return c.json({
+        message: error instanceof Error ? error.message : '支付创建失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 51 - 0
packages/mini-payment/src/routes/payment/status.ts

@@ -0,0 +1,51 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@d8d/shared-utils';
+import { PaymentService } from '../../services/payment.service.js';
+
+// 支付状态查询路由定义
+const paymentStatusRoute = createRoute({
+  method: 'get',
+  path: '/',
+  request: {
+    query: z.object({
+      orderId: z.string().transform(val => parseInt(val)).pipe(z.number().int().positive())
+    })
+  },
+  responses: {
+    200: {
+      description: '支付状态查询成功',
+      content: { 'application/json': { schema: z.object({ paymentStatus: z.string() }) } }
+    },
+    400: {
+      description: '参数错误',
+      content: { 'application/json': { schema: z.object({ message: z.string() }) } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: z.object({ message: z.string() }) } }
+    }
+  }
+});
+
+const app = new OpenAPIHono()
+  .openapi(paymentStatusRoute, async (c) => {
+    try {
+      const { orderId } = c.req.valid('query');
+
+      // 创建支付服务实例
+      const paymentService = new PaymentService(AppDataSource);
+      const paymentStatus = await paymentService.getPaymentStatus(orderId);
+
+      return c.json({
+        paymentStatus
+      }, 200);
+    } catch (error) {
+      console.error('支付状态查询失败:', error);
+      return c.json({
+        message: error instanceof Error ? error.message : '支付状态查询失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 47 - 0
packages/mini-payment/src/schemas/payment.schema.ts

@@ -0,0 +1,47 @@
+import { z } from 'zod';
+
+// 支付创建请求Schema
+export const PaymentCreateRequestSchema = z.object({
+  orderId: z.number().int().positive(),
+  totalAmount: z.number().int().positive(),
+  description: z.string().min(1).max(128)
+});
+
+export type PaymentCreateRequest = z.infer<typeof PaymentCreateRequestSchema>;
+
+// 支付创建响应Schema
+export const PaymentCreateResponseSchema = z.object({
+  paymentId: z.string(),
+  timeStamp: z.string(),
+  nonceStr: z.string(),
+  package: z.string(),
+  signType: z.string(),
+  paySign: z.string(),
+  totalAmount: z.number()
+});
+
+export type PaymentCreateResponse = z.infer<typeof PaymentCreateResponseSchema>;
+
+// 支付状态查询响应Schema
+export const PaymentStatusResponseSchema = z.object({
+  paymentStatus: z.enum(['待支付', '支付中', '已支付', '支付失败', '已退款', '已关闭'])
+});
+
+export type PaymentStatusResponse = z.infer<typeof PaymentStatusResponseSchema>;
+
+// 支付回调请求Schema
+export const PaymentCallbackRequestSchema = z.object({
+  id: z.string(),
+  create_time: z.string(),
+  event_type: z.string(),
+  resource_type: z.string(),
+  resource: z.object({
+    algorithm: z.string(),
+    ciphertext: z.string(),
+    associated_data: z.string().optional(),
+    nonce: z.string()
+  }),
+  summary: z.string()
+});
+
+export type PaymentCallbackRequest = z.infer<typeof PaymentCallbackRequestSchema>;

+ 262 - 0
packages/mini-payment/src/services/payment.service.ts

@@ -0,0 +1,262 @@
+import { DataSource } from 'typeorm';
+import WxPay from 'wechatpay-node-v3';
+import { Buffer } from 'buffer';
+import { PaymentEntity } from '../entities/payment.entity.js';
+import { PaymentStatus } from '../entities/payment.types.js';
+import { PaymentCreateResponse } from '../entities/payment.types.js';
+
+/**
+ * 微信支付服务
+ * 使用微信支付v3 SDK,支持小程序支付
+ */
+export class PaymentService {
+  private readonly wxPay: WxPay;
+  private readonly merchantId: string;
+  private readonly appId: string;
+  private readonly v3Key: string;
+  private readonly notifyUrl: string;
+
+  constructor(
+    private readonly dataSource: DataSource
+  ) {
+    // 从环境变量获取支付配置
+    this.merchantId = process.env.WECHAT_MERCHANT_ID || '';
+    this.appId = process.env.WX_MINI_APP_ID || '';
+    this.v3Key = process.env.WECHAT_V3_KEY || '';
+    this.notifyUrl = process.env.WECHAT_PAY_NOTIFY_URL || '';
+    const certSerialNo = process.env.WECHAT_MERCHANT_CERT_SERIAL_NO || '';
+
+    if (!this.merchantId || !this.appId || !this.v3Key || !certSerialNo) {
+      throw new Error('微信支付配置不完整,请检查环境变量');
+    }
+
+    // 处理证书字符串,将 \n 转换为实际换行符
+    const publicKey = (process.env.WECHAT_PUBLIC_KEY || '').replace(/\\n/g, '\n');
+    const privateKey = (process.env.WECHAT_PRIVATE_KEY || '').replace(/\\n/g, '\n');
+
+    // 初始化微信支付SDK
+    this.wxPay = new WxPay({
+      appid: this.appId,
+      mchid: this.merchantId,
+      publicKey: Buffer.from(publicKey),
+      privateKey: Buffer.from(privateKey),
+      key: this.v3Key,
+      serial_no: certSerialNo
+    });
+  }
+
+  /**
+   * 创建微信支付订单
+   * @param externalOrderId 外部订单ID
+   * @param userId 用户ID
+   * @param totalAmount 支付金额(分)
+   * @param description 支付描述
+   * @param openid 用户OpenID
+   */
+  async createPayment(
+    externalOrderId: number,
+    userId: number,
+    totalAmount: number,
+    description: string,
+    openid: string
+  ): Promise<PaymentCreateResponse> {
+    // 检查是否已存在相同外部订单ID的支付记录
+    const paymentRepository = this.dataSource.getRepository(PaymentEntity);
+    const existingPayment = await paymentRepository.findOne({
+      where: { externalOrderId }
+    });
+
+    if (existingPayment) {
+      if (existingPayment.paymentStatus !== PaymentStatus.PENDING) {
+        throw new Error('该订单已存在支付记录且状态不正确');
+      }
+      // 如果存在待支付的记录,可以更新或重新创建,这里选择重新创建
+      await paymentRepository.remove(existingPayment);
+    }
+
+    if (!openid) {
+      throw new Error('用户OpenID不能为空');
+    }
+
+    try {
+      // 创建商户订单号
+      const outTradeNo = `PAYMENT_${externalOrderId}_${Date.now()}`;
+
+      // 使用微信支付SDK创建JSAPI支付
+      const result = await this.wxPay.transactions_jsapi({
+        appid: this.appId,
+        mchid: this.merchantId,
+        description,
+        out_trade_no: outTradeNo,
+        notify_url: this.notifyUrl,
+        amount: {
+          total: totalAmount,
+        },
+        payer: {
+          openid
+        }
+      });
+
+      console.debug('微信支付SDK返回结果:', result);
+
+      // 从 package 字段中提取 prepay_id
+      const prepayId = result.package ? result.package.replace('prepay_id=', '') : undefined;
+
+      // 创建支付记录
+      const payment = new PaymentEntity();
+      payment.externalOrderId = externalOrderId;
+      payment.userId = userId;
+      payment.totalAmount = totalAmount;
+      payment.description = description;
+      payment.paymentStatus = PaymentStatus.PROCESSING;
+      payment.outTradeNo = outTradeNo;
+      payment.openid = openid;
+
+      await paymentRepository.save(payment);
+
+      // 直接返回微信支付SDK生成的参数
+      return {
+        paymentId: prepayId,
+        timeStamp: result.timeStamp,
+        nonceStr: result.nonceStr,
+        package: result.package,
+        signType: result.signType,
+        paySign: result.paySign,
+        totalAmount: totalAmount // 添加金额字段用于前端验证
+      };
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : '未知错误';
+      throw new Error(`微信支付创建失败: ${errorMessage}`);
+    }
+  }
+
+  /**
+   * 处理支付回调
+   */
+  async handlePaymentCallback(
+    callbackData: any,
+    headers: any,
+    rawBody: string // 添加原始请求体参数
+  ): Promise<void> {
+    console.debug('收到支付回调请求:', {
+      headers,
+      callbackData,
+      rawBody
+    });
+
+    // 验证回调签名
+    const isValid = await this.wxPay.verifySign({
+      timestamp: headers['wechatpay-timestamp'],
+      nonce: headers['wechatpay-nonce'],
+      body: rawBody, // 优先使用原始请求体
+      serial: headers['wechatpay-serial'],
+      signature: headers['wechatpay-signature']
+    });
+
+    console.debug('回调签名验证结果:', isValid);
+
+    if (!isValid) {
+      throw new Error('回调签名验证失败');
+    }
+
+    // 解密回调数据
+    const decryptedData = this.wxPay.decipher_gcm(
+      callbackData.resource.ciphertext,
+      callbackData.resource.associated_data || '',
+      callbackData.resource.nonce
+    );
+
+    console.log('解密回调数据', decryptedData)
+    console.log('解密回调数据类型:', typeof decryptedData)
+
+    // 处理解密后的数据,可能是字符串或对象
+    let parsedData;
+    if (typeof decryptedData === 'string') {
+      parsedData = JSON.parse(decryptedData);
+    } else {
+      parsedData = decryptedData;
+    }
+
+    const paymentRepository = this.dataSource.getRepository(PaymentEntity);
+    const outTradeNo = parsedData.out_trade_no;
+    const payment = await paymentRepository.findOne({
+      where: { outTradeNo }
+    });
+
+    if (!payment) {
+      throw new Error('支付记录不存在');
+    }
+
+    // 根据回调结果更新支付状态
+    if (parsedData.trade_state === 'SUCCESS') {
+      payment.paymentStatus = PaymentStatus.PAID;
+      payment.wechatTransactionId = parsedData.transaction_id;
+    } else if (parsedData.trade_state === 'FAIL') {
+      payment.paymentStatus = PaymentStatus.FAILED;
+    } else if (parsedData.trade_state === 'REFUND') {
+      payment.paymentStatus = PaymentStatus.REFUNDED;
+    }
+
+    await paymentRepository.save(payment);
+  }
+
+  /**
+   * 查询支付状态
+   */
+  async getPaymentStatus(externalOrderId: number): Promise<PaymentStatus> {
+    const paymentRepository = this.dataSource.getRepository(PaymentEntity);
+    const payment = await paymentRepository.findOne({
+      where: { externalOrderId }
+    });
+
+    if (!payment) {
+      throw new Error('支付记录不存在');
+    }
+
+    return payment.paymentStatus;
+  }
+
+  /**
+   * 生成随机字符串
+   */
+  private generateNonceStr(length: number = 32): string {
+    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+    let result = '';
+    for (let i = 0; i < length; i++) {
+      result += chars.charAt(Math.floor(Math.random() * chars.length));
+    }
+    return result;
+  }
+
+  /**
+   * 生成回调签名(用于测试)
+   */
+  generateCallbackSignature(
+    timestamp: string,
+    nonce: string,
+    callbackData: any
+  ): string {
+    return this.wxPay.getSignature(
+      'POST',
+      nonce,
+      timestamp,
+      '/v3/pay/transactions/jsapi',
+      callbackData
+    );
+  }
+
+  /**
+   * 获取微信支付平台证书(用于测试)
+   */
+  async getPlatformCertificates(): Promise<any> {
+    try {
+      console.debug('开始获取微信支付平台证书...');
+      const certificates = await this.wxPay.get_certificates(this.v3Key);
+      console.debug('获取平台证书成功:', certificates);
+      return certificates;
+    } catch (error) {
+      console.debug('获取平台证书失败:', error);
+      throw error;
+    }
+  }
+}

+ 21 - 0
packages/mini-payment/tsconfig.json

@@ -0,0 +1,21 @@
+{
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "./dist",
+    "rootDir": ".",
+    "declaration": true,
+    "declarationMap": true,
+    "sourceMap": true,
+    "strict": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true
+  },
+  "include": [
+    "src/**/*",
+    "tests"
+  ],
+  "exclude": [
+    "dist",
+    "node_modules"
+  ]
+}

+ 19 - 0
packages/mini-payment/vitest.config.ts

@@ -0,0 +1,19 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+  test: {
+    globals: true,
+    environment: 'node',
+    include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+    coverage: {
+      provider: 'v8',
+      reporter: ['text', 'json', 'html'],
+      exclude: [
+        'tests/**',
+        '**/*.d.ts',
+        '**/*.config.*',
+        '**/dist/**'
+      ]
+    }
+  }
+});