|
|
@@ -0,0 +1,161 @@
|
|
|
+import {Context} from "koa";
|
|
|
+import axios from "axios";
|
|
|
+import {parseString} from "xml2js";
|
|
|
+
|
|
|
+import {ChannelHandler, LoginResult, PaymentResult} from "../interfaces/ChannelHandler";
|
|
|
+import {ChannelConfig} from "../../config/channelConfig";
|
|
|
+import {SignatureVerifier} from "../../utils/SignatureVerifier";
|
|
|
+import QuickAsy from "../../utils/quickAsy";
|
|
|
+import {PaymentHelper} from "../../utils/PaymentHelper";
|
|
|
+
|
|
|
+const logger = require("../../utils/log");
|
|
|
+
|
|
|
+/**
|
|
|
+ * Hupu(QuickSDK)渠道处理器
|
|
|
+ * 负责登录验证与支付回调处理
|
|
|
+ */
|
|
|
+export class HupuChannelHandler implements ChannelHandler {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * QuickSDK 登录验证
|
|
|
+ * @param ctx Koa上下文
|
|
|
+ * @param config 渠道配置
|
|
|
+ */
|
|
|
+ async handleLogin(ctx: Context, config: ChannelConfig): Promise<LoginResult> {
|
|
|
+ const data = ctx.request.body as any;
|
|
|
+ const {token, uid, channel_code, product_code} = data || {};
|
|
|
+ const finalProductCode = product_code || config.loginConfig?.productCode;
|
|
|
+
|
|
|
+ if (!token || !uid) {
|
|
|
+ logger.warn("Hupu登录验证失败: 缺少必要参数", {token, uid});
|
|
|
+ return {code: 0, msg: "缺少必要参数 token 或 uid"};
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!finalProductCode) {
|
|
|
+ logger.error("Hupu登录验证失败: 未配置productCode");
|
|
|
+ return {code: 0, msg: "服务器未配置productCode"};
|
|
|
+ }
|
|
|
+
|
|
|
+ const requestUrl = "http://checkuser.quickapi.net/v2/checkUserInfo";
|
|
|
+ const params: Record<string, string> = {
|
|
|
+ token,
|
|
|
+ uid,
|
|
|
+ product_code: finalProductCode
|
|
|
+ };
|
|
|
+ if (channel_code) {
|
|
|
+ params.channel_code = channel_code;
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info("Hupu登录验证请求", {url: requestUrl, params});
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.get(requestUrl, {params, timeout: 8000});
|
|
|
+ logger.info("Hupu登录验证响应", {data: response.data});
|
|
|
+
|
|
|
+ if (response.data === "1") {
|
|
|
+ return {code: 1, msg: "success"};
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.warn("Hupu登录验证失败: 接口返回非1", {data: response.data});
|
|
|
+ return {code: 0, msg: "登录验证失败"};
|
|
|
+ } catch (error) {
|
|
|
+ logger.error("Hupu登录验证异常", error);
|
|
|
+ return {code: 0, msg: "登录验证异常"};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * QuickSDK 支付回调
|
|
|
+ * @param ctx Koa上下文
|
|
|
+ * @param config 渠道配置
|
|
|
+ */
|
|
|
+ async handlePayment(ctx: Context, config: ChannelConfig): Promise<PaymentResult> {
|
|
|
+ const data = ctx.request.body as any;
|
|
|
+ logger.info("Hupu支付回调参数", {url: ctx.href, params: data});
|
|
|
+
|
|
|
+ const {nt_data, sign, md5Sign} = data || {};
|
|
|
+ if (!nt_data || !sign || !md5Sign) {
|
|
|
+ logger.warn("Hupu支付回调失败: 缺少必要参数", {nt_data, sign, md5Sign});
|
|
|
+ return {code: 0, msg: "缺少必要参数"};
|
|
|
+ }
|
|
|
+
|
|
|
+ const md5Key = config.paymentConfig?.signKey;
|
|
|
+ const callbackKey = config.paymentConfig?.callbackKey;
|
|
|
+
|
|
|
+ if (!md5Key || !callbackKey) {
|
|
|
+ logger.error("Hupu支付回调失败: 未配置QuickSDK密钥");
|
|
|
+ return {code: 0, msg: "服务器未配置渠道密钥"};
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!SignatureVerifier.verifyQuickSign(data, md5Key)) {
|
|
|
+ logger.warn("Hupu支付回调失败: 签名验证失败");
|
|
|
+ return {code: 0, msg: "签名验证失败"};
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const xmlData = QuickAsy.decode(nt_data, callbackKey);
|
|
|
+ const parsed = await this.parseQuickXml(xmlData);
|
|
|
+
|
|
|
+ const message = parsed.quicksdk_message.message[0];
|
|
|
+ const orderId = message.game_order?.[0];
|
|
|
+ const outTradeNo = message.order_no?.[0];
|
|
|
+ const status = message.status?.[0];
|
|
|
+ const amountStr = message.amount?.[0];
|
|
|
+
|
|
|
+ if (!orderId || !outTradeNo || typeof status === "undefined" || !amountStr) {
|
|
|
+ logger.error("Hupu支付回调失败: XML缺少必要字段", {message});
|
|
|
+ return {code: 0, msg: "回调数据不完整"};
|
|
|
+ }
|
|
|
+
|
|
|
+ if (status !== "0") {
|
|
|
+ logger.warn("Hupu支付状态非成功", {status});
|
|
|
+ return {code: 0, msg: "支付状态失败"};
|
|
|
+ }
|
|
|
+
|
|
|
+ const amount = parseFloat(amountStr);
|
|
|
+
|
|
|
+ const validation = await PaymentHelper.validateOrder(orderId);
|
|
|
+ if (!validation.valid) {
|
|
|
+ return {
|
|
|
+ code: validation.message?.includes("重复发货") ? 1 : 0,
|
|
|
+ msg: validation.message || "订单验证失败"
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ const orderInfo = validation.orderInfo;
|
|
|
+ if (Number(orderInfo.amount) !== amount) {
|
|
|
+ logger.warn("Hupu支付金额不匹配", {
|
|
|
+ orderId,
|
|
|
+ requestAmount: amount,
|
|
|
+ orderAmount: orderInfo.amount
|
|
|
+ });
|
|
|
+ return {code: 0, msg: "订单金额不一致"};
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info(`Hupu支付订单${orderId}开始发货`);
|
|
|
+ const result = await PaymentHelper.deliverOrder(
|
|
|
+ orderInfo,
|
|
|
+ ctx.request.ip,
|
|
|
+ validation.url,
|
|
|
+ outTradeNo
|
|
|
+ );
|
|
|
+ logger.info(`Hupu支付订单${orderId}发货完成`, {result});
|
|
|
+ return result;
|
|
|
+ } catch (error) {
|
|
|
+ logger.error("Hupu支付回调解析异常", error);
|
|
|
+ return {code: 0, msg: "回调解析异常"};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private parseQuickXml(xml: string): Promise<any> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ parseString(xml, (err, result) => {
|
|
|
+ if (err) {
|
|
|
+ return reject(err);
|
|
|
+ }
|
|
|
+ resolve(result);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|