Files
soul/lib/payment/wechat.tsx
2026-01-09 11:58:08 +08:00

100 lines
2.8 KiB
TypeScript

import crypto from "crypto"
export interface WechatPayConfig {
appId: string
appSecret: string
mchId: string
apiKey: string
notifyUrl: string
}
export class WechatPayService {
constructor(private config: WechatPayConfig) {}
// 创建微信支付订单(扫码支付)
async createOrder(params: {
outTradeNo: string
body: string
totalFee: number
spbillCreateIp: string
}) {
const orderParams = {
appid: this.config.appId,
mch_id: this.config.mchId,
nonce_str: this.generateNonceStr(),
body: params.body,
out_trade_no: params.outTradeNo,
total_fee: Math.round(params.totalFee * 100).toString(), // 转换为分
spbill_create_ip: params.spbillCreateIp,
notify_url: this.config.notifyUrl,
trade_type: "NATIVE", // 扫码支付
}
const sign = this.generateSign(orderParams)
const xmlData = this.buildXML({ ...orderParams, sign })
// In production, make actual API call to WeChat
// const response = await fetch("https://api.mch.weixin.qq.com/pay/unifiedorder", {
// method: "POST",
// body: xmlData,
// headers: { "Content-Type": "application/xml" },
// })
// Mock response for development
return {
codeUrl: `weixin://wxpay/bizpayurl?pr=${this.generateNonceStr()}`,
prepayId: `prepay_${Date.now()}`,
outTradeNo: params.outTradeNo,
}
}
// 生成随机字符串
private generateNonceStr(): string {
return crypto.randomBytes(16).toString("hex")
}
// 生成签名
generateSign(params: Record<string, string>): string {
const sortedKeys = Object.keys(params).sort()
const signString = sortedKeys
.filter((key) => params[key] && key !== "sign")
.map((key) => `${key}=${params[key]}`)
.join("&")
const signWithKey = `${signString}&key=${this.config.apiKey}`
return crypto.createHash("md5").update(signWithKey, "utf8").digest("hex").toUpperCase()
}
// 验证签名
verifySign(params: Record<string, string>): boolean {
const receivedSign = params.sign
if (!receivedSign) return false
const calculatedSign = this.generateSign(params)
return receivedSign === calculatedSign
}
// 构建XML数据
private buildXML(params: Record<string, string>): string {
const xml = ["<xml>"]
for (const [key, value] of Object.entries(params)) {
xml.push(`<${key}><![CDATA[${value}]]></${key}>`)
}
xml.push("</xml>")
return xml.join("")
}
// 解析XML数据
private async parseXML(xml: string): Promise<Record<string, string>> {
const result: Record<string, string> = {}
const regex = /<(\w+)><!\[CDATA\[(.*?)\]\]><\/\1>/g
let match
while ((match = regex.exec(xml)) !== null) {
result[match[1]] = match[2]
}
return result
}
}