Update remote soul-content with local content
This commit is contained in:
99
lib/payment/wechat.tsx
Normal file
99
lib/payment/wechat.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user