|
|
@@ -2,8 +2,11 @@ import {Context} from "koa";
|
|
|
import * as crypto from 'crypto';
|
|
|
import {ChannelHandler, LoginResult, PaymentResult,} from "../interfaces/ChannelHandler";
|
|
|
import {ChannelConfig} from "../../config/channelConfig";
|
|
|
+import {ChannelConfigManager} from "../../utils/ChannelConfigManager";
|
|
|
import {formatDate, getServerList} from "../../utils/common";
|
|
|
import {Account} from "../../config/thirdParams";
|
|
|
+import {PaymentHelper} from "../../utils/PaymentHelper";
|
|
|
+
|
|
|
import Msg from "../../utils/msg";
|
|
|
import axios from "axios";
|
|
|
|
|
|
@@ -121,7 +124,7 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
private async handleIOSLogin(data: any, config: ChannelConfig): Promise<LoginResult> {
|
|
|
try {
|
|
|
const { token } = data;
|
|
|
- const gameid = config.paymentConfig.gameId;
|
|
|
+ const gameid = '1550';
|
|
|
// 验证必要参数
|
|
|
if (!token) {
|
|
|
logger.error("iOS登录鉴权失败 - 缺少token参数");
|
|
|
@@ -132,7 +135,7 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- logger.info("iOS登录鉴权请求参数:", { token });
|
|
|
+ logger.info("iOS登录鉴权请求参数:", { token,gameid });
|
|
|
|
|
|
// 调用第三方鉴权API
|
|
|
const apiUrl = `https://api.11h5.com/login?cmd=checkUserToken&userToken=${encodeURIComponent(token)}&gameid=${gameid}`;
|
|
|
@@ -207,18 +210,64 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 小程序支付回调处理
|
|
|
+ * 统一支付回调处理
|
|
|
* @param ctx Koa上下文
|
|
|
* @param config 渠道配置
|
|
|
*/
|
|
|
async handlePayment(ctx: Context, config: ChannelConfig): Promise<PaymentResult> {
|
|
|
try {
|
|
|
- // 获取请求参数
|
|
|
+ // 获取请求参数 - 支持GET和POST请求
|
|
|
const params = ctx.request.query as any;
|
|
|
- logger.info("小程序支付回调参数:", {url: ctx.href, params: params});
|
|
|
|
|
|
+ const platform = params.platform || 'miniapp'; // 平台参数:ios、Android、miniapp、h5,默认为miniapp
|
|
|
+
|
|
|
+ logger.info("统一支付回调请求", { platform, url: ctx.href, params: params });
|
|
|
+
|
|
|
+ // 根据平台选择不同的支付处理方式和配置
|
|
|
+ if (platform === 'ios') {
|
|
|
+ // iOS使用渠道12的配置
|
|
|
+ const iosConfig = ChannelConfigManager.getConfig(12);
|
|
|
+ if (!iosConfig) {
|
|
|
+ logger.error("未找到iOS渠道配置");
|
|
|
+ return {
|
|
|
+ code: -1,
|
|
|
+ msg: "iOS渠道配置不存在",
|
|
|
+ data: null
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return await this.handleIOSPayment(params, iosConfig);
|
|
|
+ } else {
|
|
|
+ // 默认使用小程序支付方式和渠道11的配置
|
|
|
+ const miniappConfig = ChannelConfigManager.getConfig(11);
|
|
|
+ if (!miniappConfig) {
|
|
|
+ logger.error("未找到小程序渠道配置");
|
|
|
+ return {
|
|
|
+ code: -1,
|
|
|
+ msg: "小程序渠道配置不存在",
|
|
|
+ data: null
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return await this.handleMiniappPayment(params, miniappConfig);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('统一支付处理错误:', error);
|
|
|
+ return {
|
|
|
+ code: -1,
|
|
|
+ msg: error.message || "支付处理失败",
|
|
|
+ data: null
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * iOS支付回调处理
|
|
|
+ * @param params 请求参数
|
|
|
+ * @param config 渠道配置
|
|
|
+ */
|
|
|
+ private async handleIOSPayment(params: any, config: ChannelConfig): Promise<PaymentResult> {
|
|
|
+ try {
|
|
|
// 验证必要参数
|
|
|
- const requiredParams = ['uid', 'rmb', 'reqid', 'trans_id', 'product_id', 'notify_id', 'sign'];
|
|
|
+ const requiredParams = ['openid', 'rmb', 'reqid', 'trans_id', 'product_id', 'notify_id', 'sign','txid'];
|
|
|
for (const param of requiredParams) {
|
|
|
if (!params[param]) {
|
|
|
logger.error(`缺少必要参数: ${param}`);
|
|
|
@@ -230,16 +279,12 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 验证签名
|
|
|
- const parameters = Object.keys(params)
|
|
|
- .filter(key => key !== 'sign')
|
|
|
- .map(key => `${key}=${params[key]}`);
|
|
|
-
|
|
|
- const expectedSignature = this.generateSignature(parameters, config.paymentConfig.signKey!);
|
|
|
- if (params.sign !== expectedSignature) {
|
|
|
- logger.error("小程序支付签名验证失败", {
|
|
|
+ // 验证签名(使用iOS的签名算法)
|
|
|
+ const isValidSignature = this.verifyPaymentSignature(params, config);
|
|
|
+ if (!isValidSignature) {
|
|
|
+ logger.error("iOS支付签名验证失败", {
|
|
|
received: params.sign,
|
|
|
- expected: expectedSignature
|
|
|
+ params: params
|
|
|
});
|
|
|
return {
|
|
|
code: -1,
|
|
|
@@ -248,42 +293,88 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // 获取订单信息
|
|
|
- const orderId = params.reqid;
|
|
|
- const orderInfo = (await Order.getOrder(orderId))[0];
|
|
|
-
|
|
|
- if (!orderInfo) {
|
|
|
- logger.error(`订单${orderId}不存在`);
|
|
|
+ // 订单校验
|
|
|
+ const orderCheckResult = await this.verifyOrderInternal(params.notify_id, config.paymentConfig.gameId, config);
|
|
|
+ if (!orderCheckResult.success) {
|
|
|
+ logger.error(`订单${params.reqid}校验失败: ${orderCheckResult.message}`);
|
|
|
return {
|
|
|
code: -1,
|
|
|
- msg: "订单不存在",
|
|
|
+ msg: `订单校验失败: ${orderCheckResult.message}`,
|
|
|
data: null
|
|
|
};
|
|
|
}
|
|
|
-
|
|
|
- if (orderInfo.status == 2) {
|
|
|
- logger.info(`订单${orderId}已经重复发货`);
|
|
|
+ const orderId = params.txid;//订单号
|
|
|
+ const out_trade_no = params.trans_id;//平台订单号
|
|
|
+ const validation = await PaymentHelper.validateOrder(orderId);
|
|
|
+ if (!validation.valid) {
|
|
|
return {
|
|
|
- code: 0,
|
|
|
- msg: "订单已发货",
|
|
|
- data: null
|
|
|
+ code: validation.message?.includes("重复发货") ? 1 : 0,
|
|
|
+ msg: validation.message || "订单验证失败"
|
|
|
};
|
|
|
}
|
|
|
+ const orderInfo = validation.orderInfo;
|
|
|
+
|
|
|
+ logger.info("IOS订单校验成功,开始处理支付发货逻辑");
|
|
|
+ const result = await PaymentHelper.deliverOrder(
|
|
|
+ orderInfo,
|
|
|
+ "127.0.0.1", // 使用默认IP地址,因为iOS支付没有客户端IP
|
|
|
+ validation.url,
|
|
|
+ out_trade_no
|
|
|
+ );
|
|
|
+ // 处理支付成功逻辑
|
|
|
+ logger.info(`iOS支付成功 - 用户: ${params.openid}, 金额: ${params.rmb}, 订单: ${out_trade_no}, 发货结果:`, result);
|
|
|
+ return result;
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ logger.error("iOS支付处理出错:", error);
|
|
|
+ return {
|
|
|
+ code: -1,
|
|
|
+ msg: "iOS支付处理失败",
|
|
|
+ data: null
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 小程序支付回调处理
|
|
|
+ * @param params 请求参数
|
|
|
+ * @param config 渠道配置
|
|
|
+ */
|
|
|
+ private async handleMiniappPayment(params: any, config: ChannelConfig): Promise<PaymentResult> {
|
|
|
+ try {
|
|
|
+
|
|
|
+ // 验证必要参数
|
|
|
+ const requiredParams = ['uid', 'rmb', 'reqid', 'trans_id', 'product_id', 'notify_id', 'sign'];
|
|
|
+ for (const param of requiredParams) {
|
|
|
+ if (!params[param]) {
|
|
|
+ logger.error(`缺少必要参数: ${param}`);
|
|
|
+ return {
|
|
|
+ code: -1,
|
|
|
+ msg: `缺少必要参数: ${param}`,
|
|
|
+ data: null
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 验证金额
|
|
|
- const amount = parseFloat(params.rmb);
|
|
|
- if (orderInfo.amount != amount) {
|
|
|
- logger.error("订单金额不一致", {
|
|
|
- "orderInfo.amount": orderInfo.amount,
|
|
|
- "params.amount": amount,
|
|
|
+ // 验证签名 - 排除platform和channel_id参数
|
|
|
+ const parameters = Object.keys(params)
|
|
|
+ .filter(key => key !== 'sign')
|
|
|
+ .map(key => `${key}=${params[key]}`);
|
|
|
+
|
|
|
+ const expectedSignature = this.generateSignature(parameters, config.paymentConfig.signKey!);
|
|
|
+ if (params.sign !== expectedSignature) {
|
|
|
+ logger.error("小程序支付签名验证失败", {
|
|
|
+ received: params.sign,
|
|
|
+ expected: expectedSignature
|
|
|
});
|
|
|
return {
|
|
|
code: -1,
|
|
|
- msg: "订单金额不一致",
|
|
|
+ msg: "签名验证失败",
|
|
|
data: null
|
|
|
};
|
|
|
}
|
|
|
-
|
|
|
+ const orderId = params.txid;//订单号
|
|
|
+ const out_trade_no = params.trans_id;//平台订单号
|
|
|
// 订单校验 - 验证支付是否成功
|
|
|
const orderCheckResult = await this.wxOrderCheck(params.notify_id, config);
|
|
|
if (!orderCheckResult.success) {
|
|
|
@@ -295,42 +386,25 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // 获取服务器URL
|
|
|
- const url = await getServerList(orderInfo.server_id, orderInfo.channel_id);
|
|
|
- if (!url) {
|
|
|
- logger.error(`区服id错误: serverId ${orderInfo.server_id}`);
|
|
|
+ const validation = await PaymentHelper.validateOrder(orderId);
|
|
|
+ if (!validation.valid) {
|
|
|
return {
|
|
|
- code: -1,
|
|
|
- msg: "区服id错误",
|
|
|
- data: null
|
|
|
+ code: validation.message?.includes("重复发货") ? 1 : 0,
|
|
|
+ msg: validation.message || "订单验证失败"
|
|
|
};
|
|
|
}
|
|
|
-
|
|
|
+ const orderInfo = validation.orderInfo;
|
|
|
// 发货处理
|
|
|
logger.info(`小程序支付订单${orderId}通知游戏发货开始`);
|
|
|
- const result = await this.deliverOrder(
|
|
|
+ const result = await PaymentHelper.deliverOrder(
|
|
|
orderInfo,
|
|
|
- ctx.request.ip,
|
|
|
- url,
|
|
|
- params.trans_id
|
|
|
+ "127.0.0.1", // 使用默认IP地址,因为小程序支付没有客户端IP
|
|
|
+ validation.url,
|
|
|
+ out_trade_no
|
|
|
);
|
|
|
-
|
|
|
- if ((result as any).code === 1) {
|
|
|
- logger.info(`小程序支付订单${orderId}发货成功`);
|
|
|
- return {
|
|
|
- code: 0,
|
|
|
- msg: "发货成功",
|
|
|
- data: null
|
|
|
- };
|
|
|
- } else {
|
|
|
- logger.error(`小程序支付订单${orderId}发货失败: ${(result as any).msg}`);
|
|
|
- return {
|
|
|
- code: -1,
|
|
|
- msg: `发货失败: ${(result as any).msg}`,
|
|
|
- data: null
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
+ // 处理支付成功逻辑
|
|
|
+ logger.info(`小程序支付成功 - 用户: ${params.uid}, 金额: ${params.rmb}, 订单: ${out_trade_no}, 发货结果:`, result);
|
|
|
+ return result;
|
|
|
} catch (error) {
|
|
|
logger.error("小程序支付处理出错:", error);
|
|
|
return {
|
|
|
@@ -846,16 +920,38 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
* @returns 签名结果
|
|
|
*/
|
|
|
private generateSignature(parameters: string[], privateKey: string): string {
|
|
|
- // 将参数数组转换为对象
|
|
|
- const params: any = {};
|
|
|
- parameters.forEach(param => {
|
|
|
+ // 按照签名算法3处理
|
|
|
+ // 1. 过滤掉空值参数
|
|
|
+ const filteredParams = parameters.filter(param => {
|
|
|
const [key, value] = param.split('=');
|
|
|
- if (key && value) {
|
|
|
- params[key] = value;
|
|
|
- }
|
|
|
+ return key && value && value.trim() !== '';
|
|
|
});
|
|
|
|
|
|
- return this.generateCommonSignature(params, privateKey, ['sign'], true);
|
|
|
+ // 2. 按字母顺序排序
|
|
|
+ filteredParams.sort();
|
|
|
+
|
|
|
+ // 3. 对参数值进行URL编码,然后用&连接
|
|
|
+ const encodedParams = filteredParams.map(param => {
|
|
|
+ const [key, value] = param.split('=');
|
|
|
+ return `${key}=${encodeURIComponent(value)}`;
|
|
|
+ });
|
|
|
+ const queryString = encodedParams.join('&');
|
|
|
+
|
|
|
+ // 4. 添加私钥
|
|
|
+ const stringToSign = `${queryString}&key=${privateKey}`;
|
|
|
+
|
|
|
+ // 5. MD5哈希并转大写
|
|
|
+ const signature = CryptoJS.MD5(stringToSign).toString().toUpperCase();
|
|
|
+
|
|
|
+ logger.info("签名算法3生成详情:", {
|
|
|
+ filteredParams,
|
|
|
+ encodedParams,
|
|
|
+ queryString,
|
|
|
+ stringToSign,
|
|
|
+ signature
|
|
|
+ });
|
|
|
+
|
|
|
+ return signature;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -894,4 +990,135 @@ export class MiniappChannelHandler implements ChannelHandler {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证iOS支付签名(签名算法3)
|
|
|
+ * @param params 请求参数
|
|
|
+ * @param config 渠道配置
|
|
|
+ * @returns 验证结果
|
|
|
+ */
|
|
|
+ private verifyPaymentSignature(params: any, config: ChannelConfig): boolean {
|
|
|
+ try {
|
|
|
+ // 获取签名密钥
|
|
|
+ const signKey = config.paymentConfig.signKey;
|
|
|
+ if (!signKey) {
|
|
|
+ logger.error("iOS支付签名密钥未配置");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 过滤掉sign参数,按参数名排序
|
|
|
+ const sortedParams = Object.keys(params)
|
|
|
+ .filter(key => key !== 'sign')
|
|
|
+ .sort()
|
|
|
+ .map(key => `${key}=${params[key]}`);
|
|
|
+
|
|
|
+ // 构建签名字符串
|
|
|
+ const signString = sortedParams.join('&') + `&key=${signKey}`;
|
|
|
+
|
|
|
+ // 计算MD5签名
|
|
|
+ const expectedSignature = CryptoJS.MD5(signString).toString().toUpperCase();
|
|
|
+
|
|
|
+ logger.info("iOS支付签名验证", {
|
|
|
+ signString,
|
|
|
+ receivedSignature: params.sign,
|
|
|
+ expectedSignature,
|
|
|
+ isValid: params.sign === expectedSignature
|
|
|
+ });
|
|
|
+
|
|
|
+ return params.sign === expectedSignature;
|
|
|
+ } catch (error) {
|
|
|
+ logger.error("iOS支付签名验证出错:", error);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内部订单校验方法
|
|
|
+ * @param notifyId 平台通知ID
|
|
|
+ * @param gameId 游戏ID
|
|
|
+ * @param config 渠道配置
|
|
|
+ * @returns 校验结果
|
|
|
+ */
|
|
|
+ private async verifyOrderInternal(notifyId: string, gameId: string, config: ChannelConfig): Promise<{ success: boolean; message: string }> {
|
|
|
+ try {
|
|
|
+ // 构建验证参数
|
|
|
+ const parameters = [
|
|
|
+ `gameid=${gameId}`,
|
|
|
+ `notify_id=${notifyId}`
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 生成签名
|
|
|
+ const signature = this.generatePaymentSignature(parameters, config.paymentConfig.signKey!);
|
|
|
+ const apiUrl = `https://login.11h5.com/pay/paygate/verify.php?${parameters.join('&')}&sign=${signature}`;
|
|
|
+
|
|
|
+ logger.info("iOS订单校验请求", { apiUrl });
|
|
|
+
|
|
|
+ // 调用第三方API验证订单
|
|
|
+ const response = await axios.get(apiUrl, {
|
|
|
+ timeout: 10000,
|
|
|
+ headers: {
|
|
|
+ 'User-Agent': 'Mozilla/5.0 (compatible; GameServer/1.0)'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ logger.info("iOS订单校验响应", {
|
|
|
+ status: response.status,
|
|
|
+ data: response.data
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const result = response.data;
|
|
|
+ // 处理纯文本"SUCCESS"响应
|
|
|
+ if (result === "SUCCESS") {
|
|
|
+ return { success: true, message: "订单校验成功" };
|
|
|
+ }
|
|
|
+ // 处理JSON格式响应
|
|
|
+ else if (result && typeof result === 'object' && result.code === 0) {
|
|
|
+ return { success: true, message: "订单校验成功" };
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ message: result?.msg || "订单校验失败"
|
|
|
+ };
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ message: `HTTP错误: ${response.status}`
|
|
|
+ };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ logger.error("iOS订单校验出错:", error);
|
|
|
+ return {
|
|
|
+ success: false,
|
|
|
+ message: error.message || "订单校验异常"
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成iOS支付签名(签名算法3)
|
|
|
+ * @param parameters 参数数组
|
|
|
+ * @param signKey 签名密钥
|
|
|
+ * @returns 签名字符串
|
|
|
+ */
|
|
|
+ private generatePaymentSignature(parameters: string[], signKey: string): string {
|
|
|
+ try {
|
|
|
+ // 构建签名字符串
|
|
|
+ const signString = parameters.join('&') + `&key=${signKey}`;
|
|
|
+
|
|
|
+ // 计算MD5签名并转为大写
|
|
|
+ const signature = CryptoJS.MD5(signString).toString().toUpperCase();
|
|
|
+
|
|
|
+ logger.info("生成iOS支付签名", {
|
|
|
+ signString,
|
|
|
+ signature
|
|
|
+ });
|
|
|
+
|
|
|
+ return signature;
|
|
|
+ } catch (error) {
|
|
|
+ logger.error("生成iOS支付签名出错:", error);
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|