Update remote soul-content with local content
This commit is contained in:
75
lib/payment/alipay.ts
Normal file
75
lib/payment/alipay.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import crypto from "crypto"
|
||||
|
||||
export interface AlipayConfig {
|
||||
appId: string
|
||||
partnerId: string
|
||||
key: string
|
||||
returnUrl: string
|
||||
notifyUrl: string
|
||||
}
|
||||
|
||||
export class AlipayService {
|
||||
constructor(private config: AlipayConfig) {}
|
||||
|
||||
// 创建支付宝订单
|
||||
createOrder(params: {
|
||||
outTradeNo: string
|
||||
subject: string
|
||||
totalAmount: number
|
||||
body?: string
|
||||
}) {
|
||||
const orderInfo = {
|
||||
app_id: this.config.appId,
|
||||
method: "alipay.trade.wap.pay",
|
||||
format: "JSON",
|
||||
charset: "utf-8",
|
||||
sign_type: "MD5",
|
||||
timestamp: new Date().toISOString().slice(0, 19).replace("T", " "),
|
||||
version: "1.0",
|
||||
notify_url: this.config.notifyUrl,
|
||||
return_url: this.config.returnUrl,
|
||||
biz_content: JSON.stringify({
|
||||
out_trade_no: params.outTradeNo,
|
||||
product_code: "QUICK_WAP_WAY",
|
||||
total_amount: params.totalAmount.toFixed(2),
|
||||
subject: params.subject,
|
||||
body: params.body || params.subject,
|
||||
}),
|
||||
}
|
||||
|
||||
const sign = this.generateSign(orderInfo)
|
||||
return {
|
||||
...orderInfo,
|
||||
sign,
|
||||
paymentUrl: this.buildPaymentUrl(orderInfo, sign),
|
||||
}
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
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}${this.config.key}`
|
||||
return crypto.createHash("md5").update(signWithKey, "utf8").digest("hex")
|
||||
}
|
||||
|
||||
// 验证回调签名
|
||||
verifySign(params: Record<string, string>): boolean {
|
||||
const receivedSign = params.sign
|
||||
if (!receivedSign) return false
|
||||
|
||||
const calculatedSign = this.generateSign(params)
|
||||
return receivedSign.toLowerCase() === calculatedSign.toLowerCase()
|
||||
}
|
||||
|
||||
// 构建支付URL
|
||||
private buildPaymentUrl(params: Record<string, string>, sign: string): string {
|
||||
const gateway = "https://openapi.alipay.com/gateway.do"
|
||||
const queryParams = new URLSearchParams({ ...params, sign })
|
||||
return `${gateway}?${queryParams.toString()}`
|
||||
}
|
||||
}
|
||||
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