ZeroOneChannelHandler.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import {Context} from "koa";
  2. import axios from "axios";
  3. import {parseString} from "xml2js";
  4. import {ChannelHandler, LoginResult, PaymentResult} from "../interfaces/ChannelHandler";
  5. import {ChannelConfig} from "../../config/channelConfig";
  6. import {SignatureVerifier} from "../../utils/SignatureVerifier";
  7. import QuickAsy from "../../utils/quickAsy";
  8. import {PaymentHelper} from "../../utils/PaymentHelper";
  9. const logger = require("../../utils/log");
  10. /**
  11. * 0.1渠道(QuickSDK)渠道处理器
  12. * 负责登录验证与支付回调处理
  13. */
  14. export class ZeroOneChannelHandler implements ChannelHandler {
  15. /**
  16. * QuickSDK 登录验证
  17. * @param ctx Koa上下文
  18. * @param config 渠道配置
  19. */
  20. async handleLogin(ctx: Context, config: ChannelConfig): Promise<LoginResult> {
  21. const data = ctx.request.body as any;
  22. const {token, uid, channel_code, product_code} = data || {};
  23. const finalProductCode = product_code || config.loginConfig?.productCode;
  24. const tag = `[渠道${config.channelId}]`;
  25. if (!token || !uid) {
  26. logger.warn(`${tag}登录验证失败: 缺少必要参数`, {token, uid});
  27. return {code: 0, msg: "缺少必要参数 token 或 uid"};
  28. }
  29. if (!finalProductCode) {
  30. logger.error(`${tag}登录验证失败: 未配置productCode`);
  31. return {code: 0, msg: "服务器未配置productCode"};
  32. }
  33. const requestUrl = "http://checkuser.quickapi.net/v2/checkUserInfo";
  34. const params: Record<string, string> = {
  35. token,
  36. uid,
  37. product_code: finalProductCode
  38. };
  39. if (channel_code) {
  40. params.channel_code = channel_code;
  41. }
  42. logger.info(`${tag}登录验证请求`, {url: requestUrl, params});
  43. try {
  44. const response = await axios.get(requestUrl, {params, timeout: 8000});
  45. logger.info(`${tag}登录验证响应`, {data: response.data});
  46. if (response.data == "1") {
  47. return {code: 1, msg: "success"};
  48. }
  49. logger.warn(`${tag}登录验证失败: 接口返回非1`, {data: response.data});
  50. return {code: 0, msg: "登录验证失败"};
  51. } catch (error) {
  52. logger.error(`${tag}登录验证异常`, error);
  53. return {code: 0, msg: "登录验证异常"};
  54. }
  55. }
  56. /**
  57. * QuickSDK 支付回调
  58. * @param ctx Koa上下文
  59. * @param config 渠道配置
  60. */
  61. async handlePayment(ctx: Context, config: ChannelConfig): Promise<PaymentResult> {
  62. const data = ctx.request.body as any;
  63. const tag = `[渠道${config.channelId}]`;
  64. logger.info(`${tag}支付回调参数`, {url: ctx.href, params: data});
  65. const {nt_data, sign, md5Sign} = data || {};
  66. if (!nt_data || !sign || !md5Sign) {
  67. logger.warn(`${tag}支付回调失败: 缺少必要参数`, {nt_data, sign, md5Sign});
  68. return {code: 0, msg: "缺少必要参数"};
  69. }
  70. const md5Key = config.paymentConfig?.signKey;
  71. const callbackKey = config.paymentConfig?.callbackKey;
  72. if (!md5Key || !callbackKey) {
  73. logger.error(`${tag}支付回调失败: 未配置QuickSDK密钥`);
  74. return {code: 0, msg: "服务器未配置渠道密钥"};
  75. }
  76. if (!SignatureVerifier.verifyQuickSign(data, md5Key)) {
  77. logger.warn(`${tag}支付回调失败: 签名验证失败`);
  78. return {code: 0, msg: "签名验证失败"};
  79. }
  80. try {
  81. const xmlData = QuickAsy.decode(nt_data, callbackKey);
  82. const parsed = await this.parseQuickXml(xmlData);
  83. const message = parsed.quicksdk_message.message[0];
  84. const orderId = message.game_order?.[0];
  85. const outTradeNo = message.order_no?.[0];
  86. const status = message.status?.[0];
  87. const amountStr = message.amount?.[0];
  88. if (!orderId || !outTradeNo || typeof status === "undefined" || !amountStr) {
  89. logger.error(`${tag}支付回调失败: XML缺少必要字段`, {message});
  90. return {code: 0, msg: "回调数据不完整"};
  91. }
  92. if (status !== "0") {
  93. logger.warn(`${tag}支付状态非成功`, {status});
  94. return {code: 0, msg: "支付状态失败"};
  95. }
  96. const amount = parseFloat(amountStr);
  97. const validation = await PaymentHelper.validateOrder(orderId);
  98. if (!validation.valid) {
  99. return {
  100. code: validation.message?.includes("重复发货") ? 1 : 0,
  101. msg: validation.message || "订单验证失败"
  102. };
  103. }
  104. const orderInfo = validation.orderInfo;
  105. if (Number(orderInfo.amount) !== amount) {
  106. logger.warn(`${tag}支付金额不匹配`, {
  107. orderId,
  108. requestAmount: amount,
  109. orderAmount: orderInfo.amount
  110. });
  111. return {code: 0, msg: "订单金额不一致"};
  112. }
  113. logger.info(`${tag}支付订单${orderId}开始发货`);
  114. const result = await PaymentHelper.deliverOrder(
  115. orderInfo,
  116. ctx.request.ip,
  117. validation.url,
  118. outTradeNo
  119. );
  120. logger.info(`${tag}支付订单${orderId}发货完成`, {result});
  121. return result;
  122. } catch (error) {
  123. logger.error(`${tag}支付回调解析异常`, error);
  124. return {code: 0, msg: "回调解析异常"};
  125. }
  126. }
  127. private parseQuickXml(xml: string): Promise<any> {
  128. return new Promise((resolve, reject) => {
  129. parseString(xml, (err, result) => {
  130. if (err) {
  131. return reject(err);
  132. }
  133. resolve(result);
  134. });
  135. });
  136. }
  137. }