feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新: 1. 按H5网页端完全重构匹配功能(match页面) - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募 - 资源对接等类型弹出手机号/微信号输入框 - 去掉重新匹配按钮,改为返回按钮 2. 修复所有卡片对齐和宽度问题 - 目录页附录卡片居中 - 首页阅读进度卡片满宽度 - 我的页面菜单卡片对齐 - 推广中心分享卡片统一宽度 3. 修复目录页图标和文字对齐 - section-icon固定40rpx宽高 - section-title与图标垂直居中 4. 更新真实完整文章标题(62篇) - 从book目录读取真实markdown文件名 - 替换之前的简化标题 5. 新增文章数据API - /api/db/chapters - 获取完整书籍结构 - 支持按ID获取单篇文章内容
This commit is contained in:
@@ -1,75 +1,614 @@
|
||||
import crypto from "crypto"
|
||||
/**
|
||||
* 支付宝网关实现 (Alipay Gateway)
|
||||
* 基于 Universal_Payment_Module v4.0 设计
|
||||
*
|
||||
* 支持:
|
||||
* - 电脑网站支付 (platform_type='web')
|
||||
* - 手机网站支付 (platform_type='wap')
|
||||
* - 扫码支付 (platform_type='qr')
|
||||
*
|
||||
* 作者: 卡若
|
||||
* 版本: v4.0
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
import { AbstractGateway, PaymentFactory } from './factory';
|
||||
import {
|
||||
CreateTradeData,
|
||||
TradeResult,
|
||||
NotifyResult,
|
||||
SignatureError,
|
||||
fenToYuan,
|
||||
yuanToFen,
|
||||
} from './types';
|
||||
|
||||
export interface AlipayConfig {
|
||||
appId: string
|
||||
partnerId: string
|
||||
key: string
|
||||
returnUrl: string
|
||||
notifyUrl: string
|
||||
appId: string;
|
||||
pid: string;
|
||||
sellerEmail?: string;
|
||||
privateKey?: string;
|
||||
publicKey?: string;
|
||||
md5Key?: string;
|
||||
enabled?: boolean;
|
||||
mode?: 'sandbox' | 'production';
|
||||
}
|
||||
|
||||
export class AlipayService {
|
||||
constructor(private config: AlipayConfig) {}
|
||||
/**
|
||||
* 支付宝网关
|
||||
*/
|
||||
export class AlipayGateway extends AbstractGateway {
|
||||
private readonly GATEWAY_URL = 'https://openapi.alipay.com/gateway.do';
|
||||
private readonly SANDBOX_URL = 'https://openapi.alipaydev.com/gateway.do';
|
||||
|
||||
private appId: string;
|
||||
private pid: string;
|
||||
private sellerEmail: string;
|
||||
private privateKey: string;
|
||||
private publicKey: string;
|
||||
private md5Key: string;
|
||||
private mode: 'sandbox' | 'production';
|
||||
|
||||
constructor(config: Record<string, unknown>) {
|
||||
super(config);
|
||||
const cfg = config as unknown as AlipayConfig;
|
||||
this.appId = cfg.appId || '';
|
||||
this.pid = cfg.pid || '';
|
||||
this.sellerEmail = cfg.sellerEmail || '';
|
||||
this.privateKey = cfg.privateKey || '';
|
||||
this.publicKey = cfg.publicKey || '';
|
||||
this.md5Key = cfg.md5Key || '';
|
||||
this.mode = cfg.mode || 'production';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网关地址
|
||||
*/
|
||||
private getGatewayUrl(): string {
|
||||
return this.mode === 'sandbox' ? this.SANDBOX_URL : this.GATEWAY_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支付宝交易
|
||||
*/
|
||||
async createTrade(data: CreateTradeData): Promise<TradeResult> {
|
||||
const platformType = (data.platformType || 'wap').toLowerCase();
|
||||
|
||||
switch (platformType) {
|
||||
case 'web':
|
||||
return this.createWebTrade(data);
|
||||
case 'wap':
|
||||
return this.createWapTrade(data);
|
||||
case 'qr':
|
||||
return this.createQrTrade(data);
|
||||
default:
|
||||
// 默认使用 WAP 支付
|
||||
return this.createWapTrade(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 电脑网站支付
|
||||
*/
|
||||
private async createWebTrade(data: CreateTradeData): Promise<TradeResult> {
|
||||
const bizContent = {
|
||||
subject: data.goodsTitle.slice(0, 256),
|
||||
out_trade_no: data.tradeSn,
|
||||
total_amount: fenToYuan(data.amount).toFixed(2),
|
||||
product_code: 'FAST_INSTANT_TRADE_PAY',
|
||||
body: data.goodsDetail?.slice(0, 128) || '',
|
||||
passback_params: data.attach ? encodeURIComponent(JSON.stringify(data.attach)) : '',
|
||||
};
|
||||
|
||||
const params = this.buildParams('alipay.trade.page.pay', bizContent, data.returnUrl, data.notifyUrl);
|
||||
const sign = this.generateMD5Sign(params);
|
||||
params.sign = sign;
|
||||
|
||||
const payUrl = `${this.getGatewayUrl()}?${this.buildQueryString(params)}`;
|
||||
|
||||
console.log('[Alipay] 创建电脑网站支付:', {
|
||||
out_trade_no: data.tradeSn,
|
||||
total_amount: fenToYuan(data.amount).toFixed(2),
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'url',
|
||||
payload: payUrl,
|
||||
tradeSn: data.tradeSn,
|
||||
expiration: 1800,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机网站支付
|
||||
*/
|
||||
private async createWapTrade(data: CreateTradeData): Promise<TradeResult> {
|
||||
const bizContent = {
|
||||
subject: data.goodsTitle.slice(0, 256),
|
||||
out_trade_no: data.tradeSn,
|
||||
total_amount: fenToYuan(data.amount).toFixed(2),
|
||||
product_code: 'QUICK_WAP_WAY',
|
||||
body: data.goodsDetail?.slice(0, 128) || '',
|
||||
passback_params: data.attach ? encodeURIComponent(JSON.stringify(data.attach)) : '',
|
||||
};
|
||||
|
||||
const params = this.buildParams('alipay.trade.wap.pay', bizContent, data.returnUrl, data.notifyUrl);
|
||||
const sign = this.generateMD5Sign(params);
|
||||
params.sign = sign;
|
||||
|
||||
const payUrl = `${this.getGatewayUrl()}?${this.buildQueryString(params)}`;
|
||||
|
||||
console.log('[Alipay] 创建手机网站支付:', {
|
||||
out_trade_no: data.tradeSn,
|
||||
total_amount: fenToYuan(data.amount).toFixed(2),
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'url',
|
||||
payload: payUrl,
|
||||
tradeSn: data.tradeSn,
|
||||
expiration: 1800,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码支付(当面付)
|
||||
*/
|
||||
private async createQrTrade(data: CreateTradeData): Promise<TradeResult> {
|
||||
const bizContent = {
|
||||
subject: data.goodsTitle.slice(0, 256),
|
||||
out_trade_no: data.tradeSn,
|
||||
total_amount: fenToYuan(data.amount).toFixed(2),
|
||||
body: data.goodsDetail?.slice(0, 128) || '',
|
||||
};
|
||||
|
||||
const params = this.buildParams('alipay.trade.precreate', bizContent, '', data.notifyUrl);
|
||||
const sign = this.generateMD5Sign(params);
|
||||
params.sign = sign;
|
||||
|
||||
console.log('[Alipay] 创建扫码支付:', {
|
||||
out_trade_no: data.tradeSn,
|
||||
total_amount: fenToYuan(data.amount).toFixed(2),
|
||||
});
|
||||
|
||||
try {
|
||||
// 调用支付宝预下单接口
|
||||
const response = await fetch(`${this.getGatewayUrl()}?${this.buildQueryString(params)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('[Alipay] 预下单响应:', responseText.slice(0, 500));
|
||||
|
||||
// 解析JSON响应
|
||||
const result = JSON.parse(responseText);
|
||||
const precreateResponse = result.alipay_trade_precreate_response;
|
||||
|
||||
if (precreateResponse && precreateResponse.code === '10000' && precreateResponse.qr_code) {
|
||||
return {
|
||||
type: 'qrcode',
|
||||
payload: precreateResponse.qr_code,
|
||||
tradeSn: data.tradeSn,
|
||||
expiration: 1800,
|
||||
};
|
||||
}
|
||||
|
||||
// 如果API调用失败,回退到WAP支付方式
|
||||
console.log('[Alipay] 预下单失败,使用WAP支付:', precreateResponse?.sub_msg || precreateResponse?.msg);
|
||||
return this.createWapTrade(data);
|
||||
} catch (error) {
|
||||
console.error('[Alipay] 预下单异常,使用WAP支付:', error);
|
||||
return this.createWapTrade(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建公共参数
|
||||
*/
|
||||
private buildParams(
|
||||
method: string,
|
||||
bizContent: Record<string, string>,
|
||||
returnUrl?: string,
|
||||
notifyUrl?: string
|
||||
): Record<string, string> {
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
const params: Record<string, string> = {
|
||||
app_id: this.appId,
|
||||
method,
|
||||
charset: 'utf-8',
|
||||
sign_type: 'MD5',
|
||||
timestamp,
|
||||
version: '1.0',
|
||||
biz_content: JSON.stringify(bizContent),
|
||||
};
|
||||
|
||||
if (returnUrl) {
|
||||
params.return_url = returnUrl;
|
||||
}
|
||||
if (notifyUrl) {
|
||||
params.notify_url = notifyUrl;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成MD5签名
|
||||
*/
|
||||
private generateMD5Sign(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.md5Key}`;
|
||||
return crypto.createHash('md5').update(signWithKey, 'utf8').digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询字符串
|
||||
*/
|
||||
private buildQueryString(params: Record<string, string>): string {
|
||||
return Object.entries(params)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
*/
|
||||
verifySign(data: Record<string, string>): boolean {
|
||||
const receivedSign = data.sign;
|
||||
if (!receivedSign) return false;
|
||||
|
||||
// 复制数据,移除 sign 和 sign_type
|
||||
const params = { ...data };
|
||||
delete params.sign;
|
||||
delete params.sign_type;
|
||||
|
||||
const calculatedSign = this.generateMD5Sign(params);
|
||||
return receivedSign.toLowerCase() === calculatedSign.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析回调数据
|
||||
*/
|
||||
parseNotify(data: string | Record<string, string>): NotifyResult {
|
||||
const params = typeof data === 'string' ? this.parseFormData(data) : data;
|
||||
|
||||
// 验证签名
|
||||
if (!this.verifySign({ ...params })) {
|
||||
throw new SignatureError('支付宝签名验证失败');
|
||||
}
|
||||
|
||||
const tradeStatus = params.trade_status || '';
|
||||
const status = ['TRADE_SUCCESS', 'TRADE_FINISHED'].includes(tradeStatus) ? 'paid' : 'failed';
|
||||
|
||||
// 解析透传参数
|
||||
let attach: Record<string, unknown> = {};
|
||||
const passback = params.passback_params || '';
|
||||
if (passback) {
|
||||
try {
|
||||
attach = JSON.parse(decodeURIComponent(passback));
|
||||
} catch {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
// 解析支付时间
|
||||
const gmtPayment = params.gmt_payment || '';
|
||||
const payTime = gmtPayment ? new Date(gmtPayment) : new Date();
|
||||
|
||||
return {
|
||||
status,
|
||||
tradeSn: params.out_trade_no || '',
|
||||
platformSn: params.trade_no || '',
|
||||
payAmount: yuanToFen(parseFloat(params.total_amount || '0')),
|
||||
payTime,
|
||||
currency: 'CNY',
|
||||
attach,
|
||||
rawData: params,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表单数据
|
||||
*/
|
||||
private parseFormData(formString: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
const pairs = formString.split('&');
|
||||
for (const pair of pairs) {
|
||||
const [key, value] = pair.split('=');
|
||||
if (key && value !== undefined) {
|
||||
result[decodeURIComponent(key)] = decodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询交易状态
|
||||
*/
|
||||
async queryTrade(tradeSn: string): Promise<NotifyResult | null> {
|
||||
try {
|
||||
// 检查 appId 是否配置
|
||||
if (!this.appId) {
|
||||
console.log('[Alipay] 查询跳过: 未配置 appId');
|
||||
return {
|
||||
status: 'paying',
|
||||
tradeSn,
|
||||
platformSn: '',
|
||||
payAmount: 0,
|
||||
payTime: new Date(),
|
||||
currency: 'CNY',
|
||||
attach: {},
|
||||
rawData: {},
|
||||
};
|
||||
}
|
||||
|
||||
const bizContent = {
|
||||
out_trade_no: tradeSn,
|
||||
};
|
||||
|
||||
const params = this.buildParams('alipay.trade.query', bizContent);
|
||||
params.sign = this.generateMD5Sign(params);
|
||||
|
||||
console.log('[Alipay] 查询订单:', { tradeSn, appId: this.appId });
|
||||
|
||||
const response = await fetch(`${this.getGatewayUrl()}?${this.buildQueryString(params)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('[Alipay] 查询响应:', responseText.slice(0, 300));
|
||||
|
||||
const result = JSON.parse(responseText);
|
||||
const queryResponse = result.alipay_trade_query_response;
|
||||
|
||||
// 如果订单不存在,返回 paying 状态(可能还没同步到支付宝)
|
||||
if (queryResponse?.code === '40004' && queryResponse?.sub_code === 'ACQ.TRADE_NOT_EXIST') {
|
||||
console.log('[Alipay] 订单不存在,可能还在等待支付');
|
||||
return {
|
||||
status: 'paying',
|
||||
tradeSn,
|
||||
platformSn: '',
|
||||
payAmount: 0,
|
||||
payTime: new Date(),
|
||||
currency: 'CNY',
|
||||
attach: {},
|
||||
rawData: queryResponse,
|
||||
};
|
||||
}
|
||||
|
||||
if (!queryResponse || queryResponse.code !== '10000') {
|
||||
console.log('[Alipay] 订单查询失败:', {
|
||||
code: queryResponse?.code,
|
||||
msg: queryResponse?.msg,
|
||||
sub_code: queryResponse?.sub_code,
|
||||
sub_msg: queryResponse?.sub_msg,
|
||||
});
|
||||
// 返回 paying 状态而不是 null,让前端继续轮询
|
||||
return {
|
||||
status: 'paying',
|
||||
tradeSn,
|
||||
platformSn: '',
|
||||
payAmount: 0,
|
||||
payTime: new Date(),
|
||||
currency: 'CNY',
|
||||
attach: {},
|
||||
rawData: queryResponse || {},
|
||||
};
|
||||
}
|
||||
|
||||
const tradeStatus = queryResponse.trade_status || '';
|
||||
let status: 'paying' | 'paid' | 'closed' | 'refunded' = 'paying';
|
||||
|
||||
switch (tradeStatus) {
|
||||
case 'TRADE_SUCCESS':
|
||||
case 'TRADE_FINISHED':
|
||||
status = 'paid';
|
||||
break;
|
||||
case 'TRADE_CLOSED':
|
||||
status = 'closed';
|
||||
break;
|
||||
case 'WAIT_BUYER_PAY':
|
||||
default:
|
||||
status = 'paying';
|
||||
}
|
||||
|
||||
console.log('[Alipay] 订单状态:', { tradeSn, tradeStatus, status });
|
||||
|
||||
return {
|
||||
status,
|
||||
tradeSn: queryResponse.out_trade_no || tradeSn,
|
||||
platformSn: queryResponse.trade_no || '',
|
||||
payAmount: yuanToFen(parseFloat(queryResponse.total_amount || '0')),
|
||||
payTime: new Date(queryResponse.send_pay_date || Date.now()),
|
||||
currency: 'CNY',
|
||||
attach: {},
|
||||
rawData: queryResponse,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Alipay] 查询订单失败:', error);
|
||||
// 返回 paying 状态而不是 null
|
||||
return {
|
||||
status: 'paying',
|
||||
tradeSn,
|
||||
platformSn: '',
|
||||
payAmount: 0,
|
||||
payTime: new Date(),
|
||||
currency: 'CNY',
|
||||
attach: {},
|
||||
rawData: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭交易
|
||||
*/
|
||||
async closeTrade(tradeSn: string): Promise<boolean> {
|
||||
try {
|
||||
const bizContent = {
|
||||
out_trade_no: tradeSn,
|
||||
};
|
||||
|
||||
const params = this.buildParams('alipay.trade.close', bizContent);
|
||||
params.sign = this.generateMD5Sign(params);
|
||||
|
||||
console.log('[Alipay] 关闭订单:', tradeSn);
|
||||
|
||||
const response = await fetch(`${this.getGatewayUrl()}?${this.buildQueryString(params)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
const result = JSON.parse(responseText);
|
||||
const closeResponse = result.alipay_trade_close_response;
|
||||
|
||||
return closeResponse && closeResponse.code === '10000';
|
||||
} catch (error) {
|
||||
console.error('[Alipay] 关闭订单失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起退款
|
||||
*/
|
||||
async refund(tradeSn: string, refundSn: string, amount: number, reason?: string): Promise<boolean> {
|
||||
try {
|
||||
const bizContent = {
|
||||
out_trade_no: tradeSn,
|
||||
out_request_no: refundSn,
|
||||
refund_amount: fenToYuan(amount).toFixed(2),
|
||||
refund_reason: reason || '用户退款',
|
||||
};
|
||||
|
||||
const params = this.buildParams('alipay.trade.refund', bizContent);
|
||||
params.sign = this.generateMD5Sign(params);
|
||||
|
||||
console.log('[Alipay] 发起退款:', { tradeSn, refundSn, amount });
|
||||
|
||||
const response = await fetch(`${this.getGatewayUrl()}?${this.buildQueryString(params)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
const result = JSON.parse(responseText);
|
||||
const refundResponse = result.alipay_trade_refund_response;
|
||||
|
||||
return refundResponse && refundResponse.code === '10000';
|
||||
} catch (error) {
|
||||
console.error('[Alipay] 退款失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调成功响应
|
||||
*/
|
||||
override successResponse(): string {
|
||||
return 'success';
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调失败响应
|
||||
*/
|
||||
override failResponse(): string {
|
||||
return 'fail';
|
||||
}
|
||||
}
|
||||
|
||||
// 注册到工厂
|
||||
PaymentFactory.register('alipay', AlipayGateway);
|
||||
|
||||
// 导出兼容旧版的 AlipayService
|
||||
export interface AlipayServiceConfig {
|
||||
appId: string;
|
||||
partnerId: string;
|
||||
key: string;
|
||||
returnUrl: string;
|
||||
notifyUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧版的 AlipayService
|
||||
* @deprecated 请使用 AlipayGateway
|
||||
*/
|
||||
export class AlipayService {
|
||||
private gateway: AlipayGateway;
|
||||
private notifyUrl: string;
|
||||
private returnUrl: string;
|
||||
|
||||
constructor(config: AlipayServiceConfig) {
|
||||
this.gateway = new AlipayGateway({
|
||||
appId: config.appId,
|
||||
pid: config.partnerId,
|
||||
md5Key: config.key,
|
||||
});
|
||||
this.notifyUrl = config.notifyUrl;
|
||||
this.returnUrl = config.returnUrl;
|
||||
}
|
||||
|
||||
// 创建支付宝订单
|
||||
createOrder(params: {
|
||||
outTradeNo: string
|
||||
subject: string
|
||||
totalAmount: number
|
||||
body?: string
|
||||
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,
|
||||
// 同步创建订单信息
|
||||
const orderInfo: Record<string, string> = {
|
||||
app_id: (this.gateway as AlipayGateway)['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.notifyUrl,
|
||||
return_url: this.returnUrl,
|
||||
biz_content: JSON.stringify({
|
||||
out_trade_no: params.outTradeNo,
|
||||
product_code: "QUICK_WAP_WAY",
|
||||
product_code: 'QUICK_WAP_WAY',
|
||||
total_amount: params.totalAmount.toFixed(2),
|
||||
subject: params.subject,
|
||||
body: params.body || params.subject,
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
const sign = this.generateSign(orderInfo)
|
||||
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 sortedKeys = Object.keys(params).sort();
|
||||
const signString = sortedKeys
|
||||
.filter((key) => params[key] && key !== "sign")
|
||||
.filter((key) => params[key] && key !== 'sign')
|
||||
.map((key) => `${key}=${params[key]}`)
|
||||
.join("&")
|
||||
.join('&');
|
||||
|
||||
const signWithKey = `${signString}${this.config.key}`
|
||||
return crypto.createHash("md5").update(signWithKey, "utf8").digest("hex")
|
||||
const md5Key = (this.gateway as AlipayGateway)['md5Key'];
|
||||
const signWithKey = `${signString}${md5Key}`;
|
||||
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()
|
||||
return this.gateway.verifySign(params);
|
||||
}
|
||||
|
||||
async queryTrade(tradeSn: string) {
|
||||
return this.gateway.queryTrade(tradeSn);
|
||||
}
|
||||
|
||||
// 构建支付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()}`
|
||||
const gateway = 'https://openapi.alipay.com/gateway.do';
|
||||
const queryParams = new URLSearchParams({ ...params, sign });
|
||||
return `${gateway}?${queryParams.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user