Selaa lähdekoodia

增加通道额外参数和额外参数的校验

test 7 kuukautta sitten
vanhempi
sitoutus
321ccbfb9e

+ 10 - 7
src/comm/utils.ts

@@ -6,6 +6,7 @@ import * as moment from 'moment';
 import * as NodeRSA from 'node-rsa';
 import * as crypto from 'crypto'
 import * as  speakeasy from 'speakeasy';
+import { BinaryToTextEncoding } from 'crypto';
 
 /**
  * 帮助类
@@ -215,24 +216,26 @@ export class Utils {
     return buffer.toString('utf8');
   }
 
-  signByRsa2(data, privateKey) {
-    const key = new NodeRSA(privateKey);
-    key.setOptions({ signingScheme: 'pkcs1-sha256' });
-    return key.sign(data, 'base64', 'utf8');
+  signByMD5WithRSA(data, privateKey) {
+    const hmac = crypto.createSign('md5WithRSAEncryption');
+    // 更新hash对象与传入的数据
+    hmac.update(data);
+    // 生成哈希值
+    return hmac.sign(privateKey, 'base64');
   }
 
-  verifyByRsa2(data, sign, publicKey) {
+  verifyByMD5WithRSA(data, sign, publicKey) {
     const key = new NodeRSA(publicKey);
     key.setOptions({ signingScheme: 'pkcs1-sha256' });
     return key.verify(data, sign);
   }
 
-  signBySha256(data, secretKey) {
+  signBySha256(data, secretKey, format: BinaryToTextEncoding = 'hex') {
     const hmac = crypto.createHmac('sha256', secretKey);
     // 更新hash对象与传入的数据
     hmac.update(data);
     // 生成哈希值
-    return hmac.digest('hex');
+    return hmac.digest(format);
   }
 
   signByHmacSha1(data, secretKey) {

+ 394 - 0
src/modules/dj/service/channels/ach.ts

@@ -0,0 +1,394 @@
+import { Inject, Logger, Provide } from '@midwayjs/decorator';
+import { BaseService } from '@cool-midway/core';
+import * as _ from 'lodash';
+import * as moment from 'moment';
+import { Utils } from '../../../../comm/utils';
+import { HttpService } from '@midwayjs/axios';
+import { ILogger } from '@midwayjs/logger';
+
+const HOST = "https://payment.alchemytech.cc";
+const MCH_ID = 'AC202299319';
+const KEY = 'r1t8hnmj86i1zt7ywbin';
+const PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIiPOgAEa8cMN08+sGKsu9lcm61aZmIXh/Yqfc4S2P234uN8MjAhzINVmYDDXSc0oSQkmqwOoUaf3uRDUUTkAVYzNcYg/IPuStsgEtbinsZSFJ5/+zhW82qd/AOsMxgcN3Jm5sNZa87Yu+E+tEUOXMRmxu4PTBA2lyBjN3D8sGsDAgMBAAECgYBEDXg+dPWO4rAXfiqhaepNJmDwUUoPXSGk08UQ8oSPX9miOwy5vsYvtvNB31nnRUt+ev1XfAzz6IzHnSJ9XdCp/Xv+BjGmD5JK3Li0wcpBuVCcd4GbwMto1+TTw3AXa9QXAnwWTUwHWPBCQAhhPUGxp5BdJ6ut2qX9h3GLqcZISQJBAMAW5M3w8YCzhRBbAJkSIg8fhQA+A9mdRw6gS0rMZiKHEudh7mvoFXWEQsy04OSrKvfQ1HBR+zkuG1+mcoTDTtkCQQC1/pnyKxumd9d/aF/5RJAiwSHinN2O16cROXYGoAndWVQJp7rvtu2AdTFC3S2eL3pQEN/g9ljGNorfKfzw/dc7AkAeGfhVBXLy9i7d90TKt/q6X/gZp5421dyywA/Mcud2dbBSrhgMtNvYBJfrOFUdwG0FVKZVy6MQvNbxUEYV4/1RAkEAjizuGw+uNCgfQ7tIbrUvXNdAH4aoVzUFoSEgoSBZVIC6dCiCk0520Am9iBy9zHwOs4nbPCV3SLHHX3uvzJyhjQJAUurBjyMEzomk8yjkcqIu4LCHKWEjsInJPeWTJ/uNEXwEqaS6wXpcJdSNWehFFr1kgSxsldod2r43XuvwiyC8Dw==\n" + "-----END RSA PRIVATE KEY-----"
+const PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUgjXpKpG6+o/uoJqRdpVaQae5IKgtXtEqoZXNQnAFk+wk/toyDxPmQNn5+Uj2pdwFBOzKTuvfKDVZHnLTgRz6mHfKrNboBwqHCj4WCzDj02P30QArr2KbuY+kfRdD8ckuFUlmMDXCFcqa+XZaCiZAcepDQ/RH7l6mL7A4/FdgMwIDAQAB';
+const PAY_URL = '/nh-gateway/card/payment';
+const QUERY_URL = '/nh-gateway/card/query'
+// const BALANCE_URL = '/api/v3/bra/query/balance'
+// const BANK_URL = '/api/v3/bra/query/bank';
+// const WITHDRAW_URL = '/api/v3/bra/createTransferOrder';
+// const QUERY_WITHDRAW_URL = '/api/v3/bra/query/transferOrder'
+
+const NOTIFY_HOST = `http://157.175.73.225/api/admin/dj/open/ach/notifyOrder`;
+// const WITHDRAW_NOTIFY_HOST = `http://157.175.73.225/api/admin/dj/open/ach/notifyWithdraw`;
+
+const PAY_TYPE = {
+  "INR": 'INDIA_PAY',
+  "BRL": "BRAZIL_PAY",
+  "MXN": "MEXICO_PAY",
+  "JPY": "JAPAN_PAY",
+  "VND": "VIETNAM_PAY",
+  "PHP": "PHILIPPINES_PAY",
+  "IDR": "INDONESIA_PAY"
+}
+
+const PAY_MODEL = {
+  "BANK": 'BankTransfer',
+  "PIX": "Pix",
+  "UPI": "UPI",
+  "MOMO": "MOMO_PAY",
+  "COINS": "Coins",
+  "BPI": "BPI",
+  "PAYMAYA": "Paymaya",
+  "QRIS": "QRIS",
+  "VA": "DIRECT"
+
+}
+@Provide()
+export class AchService extends BaseService {
+  @Inject()
+  utils: Utils;
+  @Inject()
+  httpService: HttpService;
+  @Logger()
+  logger: ILogger;
+
+  validOrderParam(payload) {
+    switch (payload.payType) {
+      case "PIX": {
+        if (_.isEmpty(payload.idendifyType)) {
+          throw new Error('身份类型【idendifyType】不能为空');
+        }
+        if (_.isEmpty(payload.idendifyNumber)) {
+          throw new Error('用户的真实CPF号码【idendifyNumber】不能为空');
+        }
+        if (_.isEmpty(payload.firstName)) {
+          throw new Error('【firstName】不能为空');
+        }
+        if (_.isEmpty(payload.lastName)) {
+          throw new Error('【lastName】不能为空');
+        }
+        break;
+      }
+      case "UPI": {
+        if (_.isEmpty(payload.phone)) {
+          throw new Error('用户手机号【phone】不能为空');
+        }
+        if (_.isEmpty(payload.email)) {
+          throw new Error('用户邮箱【email】不能为空');
+        }
+        break;
+      }
+      case "QRIS": {
+        if (_.isEmpty(payload.firstName)) {
+          throw new Error('【firstName】不能为空');
+        }
+        if (_.isEmpty(payload.lastName)) {
+          throw new Error('【lastName】不能为空');
+        }
+        break;
+      }
+    }
+  }
+
+  async order(payload, extra) {
+    const param: any = {
+      merchantNo: MCH_ID,
+      timeStamp: +moment(),
+      merchantOrderNo: payload.orderNo,
+      payType: PAY_TYPE[payload.currency],
+      payModel: PAY_MODEL[payload.payType],
+      orderAmount: (+payload.amount * 100).toFixed(0),
+      productDetail: "good",
+      callbackUrl: NOTIFY_HOST,
+    };
+    if (extra.firstName) {
+      param.firstName = extra.firstName;
+    }
+    if (extra.lastName) {
+      param.lastName = extra.lastName;
+    }
+    if (extra.phone) {
+      param.phone = extra.phone;
+    }
+    if (extra.email) {
+      param.email = extra.email;
+    }
+    if (extra.payBankCode && payload.currency === 'VND' && payload.payType === 'BANK') {
+      param.payBankCode = extra.payBankCode;
+    }
+    const signStr = this.utils.signSort(param);
+    param.sign = this.utils.signByMD5WithRSA(signStr, PRIVATE_KEY);
+    if (extra.idendifyType) {
+      param.idendifyType = extra.idendifyType;
+    }
+    if (extra.idendifyNumber) {
+      param.idendifyNumber = extra.idendifyNumber;
+    }
+    if (extra.userIp) {
+      param.userIp = extra.userIp;
+    }
+    param.language = "en";
+    param.noticeUrl = payload.returnUrl;
+    const res = await this.httpService.postForm(HOST + PAY_URL, param);
+    this.logger.info('下单接口返回', param, JSON.stringify(res.data));
+    const { meta = {}, data = {} } = res.data;
+    const { success, code, message } = meta
+    if (success && code === '0000') {
+      return {
+        payUrl: data.webUrl,
+        traceNo: data?.orderNo
+      };
+    } else {
+      throw new Error(message);
+    }
+  }
+
+  async query(payload) {
+    const param: any = {
+      merchantNo: 'AC202299319',
+      timeStamp: 1735185420342,
+      merchantOrderNo: 'DS202412251203055901',
+    };
+    const signStr = this.utils.signSort(param);
+    console.log(param, signStr)
+    param.sign = this.utils.signByMD5WithRSA(signStr, PRIVATE_KEY);
+    const res = await this.httpService.postForm(HOST + QUERY_URL, param);
+    this.logger.info('查询接口返回', param, JSON.stringify(res.data));
+    const { meta = {}, data = {} } = res.data;
+    const { success, code, message } = meta
+    if (success && code === '0000') {
+      if (data.orderStatus === 'SUCCESS') {
+        return {
+          status: 1,
+          traceNo: data.orderNo,
+          date: new Date()
+        };
+      } else {
+        return {
+          status: 0
+        };
+      }
+    } else {
+      throw new Error(message);
+    }
+  }
+
+  // async handleOrderNotify(payload, headers) {
+  //   const sign = headers['sign'];
+  //   const timestamp = headers['timestamp'];
+  //   const nonce = headers['nonce'];
+  //   const signStr = this.utils.signSort({
+  //     timestamp,
+  //     nonce,
+  //     access_key: ACCESS_KEY,
+  //     ...payload
+  //   });
+  //   const validSign = this.utils.signByHmacSha1(signStr, SECRET_KEY);
+  //   if (sign !== validSign) {
+  //     throw new Error('sign error');
+  //   }
+  //   if (+payload.orderStatusCode !== 2) {
+  //     throw new Error('order no success');
+  //   }
+  //   return {
+  //     date: new Date(),
+  //     orderNo: payload.externalOrderId,
+  //     traceNo: payload.orderId,
+  //     status: 1,
+  //   };
+  // }
+
+  // async queryBalance() {
+  //   const param = {};
+  //   const timestamp = +moment();
+  //   const nonce = uuidv4();
+  //   const signStr = this.utils.signSort({
+  //     timestamp,
+  //     nonce,
+  //     access_key: ACCESS_KEY,
+  //     ...param
+  //   });
+  //   const sign = this.utils.signByHmacSha1(signStr, SECRET_KEY);
+  //   const res = await this.httpService.get(HOST + BALANCE_URL, {
+  //     headers: {
+  //       'access_key': ACCESS_KEY,
+  //       'timestamp': timestamp,
+  //       'nonce': nonce,
+  //       'sign': sign
+  //     }
+  //   });
+  //   this.logger.info('查询余额接口返回', param, JSON.stringify(res.data));
+  //   const { success, msg, data } = res.data;
+  //   if (success && data[0]) {
+  //     return {
+  //       balance: data[0].accountBalance,
+  //       freeze: data[0].accountFreezeAmount
+  //     }
+  //   } else {
+  //     throw new Error(msg);
+  //   }
+  // }
+
+  // async queryBanks(payload) {
+  //   const param = {
+  //     currencyType: 'BRL',
+  //   };
+  //   const timestamp = +moment();
+  //   const nonce = uuidv4();
+  //   const signStr = this.utils.signSort({
+  //     timestamp,
+  //     nonce,
+  //     access_key: ACCESS_KEY,
+  //     ...param
+  //   });
+  //   const sign = this.utils.signByHmacSha1(signStr, SECRET_KEY);
+  //   const res = await this.httpService.post(HOST + BANK_URL, param, {
+  //     headers: {
+  //       'access_key': ACCESS_KEY,
+  //       'timestamp': timestamp,
+  //       'nonce': nonce,
+  //       'sign': sign
+  //     }
+  //   });
+  //   this.logger.info('查询银行接口返回', param, JSON.stringify(res.data));
+  //   const { success, msg, data } = res.data;
+  //   if (success) {
+  //     return data.map(item => {
+  //       return {
+  //         bankName: item.bankNames,
+  //         bankCode: ''
+  //       }
+  //     })
+  //   } else {
+  //     throw new Error(msg);
+  //   }
+  // }
+
+  // async withdraw(payload) {
+  //   const param = {
+  //     currencyAmount: (+payload.amount).toFixed(2),
+  //     channelType: CHANNEL_TYPE,
+  //     externalOrderId: payload.orderNo,
+  //     personIdType: payload.personIdType,
+  //     personId: payload.personId,
+  //     personName: payload.personName,
+  //     accountType: payload.bankCode,
+  //     accountId: payload.accountNo,
+  //     notifyUrl: WITHDRAW_NOTIFY_HOST
+  //   };
+  //   const timestamp = +moment();
+  //   const nonce = uuidv4();
+  //   const signStr = this.utils.signSort({
+  //     timestamp,
+  //     nonce,
+  //     access_key: ACCESS_KEY,
+  //     ...param
+  //   });
+  //   const sign = this.utils.signByHmacSha1(signStr, SECRET_KEY);
+  //   const res = await this.httpService.post(HOST + WITHDRAW_URL, param, {
+  //     headers: {
+  //       'access_key': ACCESS_KEY,
+  //       'timestamp': timestamp,
+  //       'nonce': nonce,
+  //       'sign': sign
+  //     }
+  //   });
+  //   this.logger.info('代付接口返回', param, JSON.stringify(res.data));
+  //   const { success, msg, desc, data } = res.data;
+  //   if (success && data.orderId) {
+  //     return {
+  //       status: 2,
+  //       traceNo: data.orderId
+  //     };
+  //   } else {
+  //     return {
+  //       status: 3,
+  //       message: desc || msg
+  //     }
+  //   }
+  // }
+
+  // async handleWithdrawNotify(payload, headers) {
+  //   const sign = headers['sign'];
+  //   const timestamp = headers['timestamp'];
+  //   const nonce = headers['nonce'];
+  //   const signStr = this.utils.signSort({
+  //     timestamp,
+  //     nonce,
+  //     access_key: ACCESS_KEY,
+  //     ...payload
+  //   });
+  //   const validSign = this.utils.signByHmacSha1(signStr, SECRET_KEY);
+  //   if (sign !== validSign) {
+  //     throw new Error('sign error');
+  //   }
+  //   if (+payload.orderStatusCode !== 8 && +payload.orderStatusCode !== 4 && +payload.orderStatusCode !== 16) {
+  //     throw new Error('order no result');
+  //   }
+  //   let status = 2;
+  //   if (+payload.orderStatusCode === 8) {
+  //     status = 8;
+  //   } else if (+payload.orderStatusCode === 4 || +payload.orderStatusCode === 16) {
+  //     status = 3;
+  //   }
+  //   return {
+  //     date: new Date(),
+  //     status: status,
+  //     message: payload.errorMsg,
+  //     orderNo: payload.externalOrderId
+  //   };
+  // }
+
+  // async queryWithdraw(payload) {
+  //   const param = {
+  //     externalOrderId: payload.orderNo
+  //   };
+  //   const timestamp = +moment();
+  //   const nonce = uuidv4();
+  //   const signStr = this.utils.signSort({
+  //     timestamp,
+  //     nonce,
+  //     access_key: ACCESS_KEY,
+  //     ...param
+  //   });
+  //   const sign = this.utils.signByHmacSha1(signStr, SECRET_KEY);
+  //   const res = await this.httpService.post(HOST + QUERY_WITHDRAW_URL, param, {
+  //     headers: {
+  //       'access_key': ACCESS_KEY,
+  //       'timestamp': timestamp,
+  //       'nonce': nonce,
+  //       'sign': sign
+  //     }
+  //   });
+  //   this.logger.info('查询代付接口返回', JSON.stringify(res.data));
+  //   const { success, msg, data = [] } = res.data;
+  //   const orderData = data.find(item => item.externalOrderId === payload.orderNo)
+  //   if (success && orderData) {
+  //     if (+orderData.orderStatus === 4 || +orderData.orderStatus === 16) {
+  //       return {
+  //         date: new Date(),
+  //         status: 3,
+  //         message: orderData.errorMsg,
+  //         orderNo: orderData.externalOrderId
+  //       };
+  //     } else if (+orderData.orderStatus === 8) {
+  //       return {
+  //         date: new Date(),
+  //         status: 8,
+  //         message: orderData.errorMsg,
+  //         orderNo: orderData.externalOrderId
+  //       };
+  //     }
+  //     return {
+  //       status: 1,
+  //       traceNo: orderData.orderId,
+  //       date: new Date()
+  //     };
+  //   } else {
+  //     return {
+  //       status: 3,
+  //       message: '渠道订单不存在'
+  //     };
+  //   }
+  // }
+}

+ 13 - 2
src/modules/dj/service/channels/dispatch.ts

@@ -5,6 +5,7 @@ import { ChannelService } from '../channel';
 import { HambitBraService } from './hambitBra';
 import { HambitInrService } from './hambitInr';
 import { WithdrawChannelService } from '../withdrawChannel';
+import { AchService } from './ach';
 
 @Provide()
 export class DispatchService extends BaseService {
@@ -23,6 +24,9 @@ export class DispatchService extends BaseService {
   @Inject()
   hambitInrService: HambitInrService;
 
+  @Inject()
+  achService: AchService;
+
   async refund(refund) {
     try {
       const channel = await this.channelService.queryByCode(refund.code);
@@ -47,9 +51,16 @@ export class DispatchService extends BaseService {
     }
   }
 
-  async order(order, channel) {
+  validOrderParam(channel, extra) {
+    const validParam = this[channel.service]?.validOrderParam;
+    if (validParam) {
+      validParam(extra);
+    }
+  }
+
+  async order(order, channel, extra) {
     try {
-      return await this[channel.service].order(order);
+      return await this[channel.service].order(order, extra);
     } catch (e) {
       throw new Error('请求通道接口失败,失败原因:' + e.message);
     }

+ 1 - 1
src/modules/dj/service/channels/hambitBra.ts

@@ -31,7 +31,7 @@ export class HambitBraService extends BaseService {
   @Logger()
   logger: ILogger;
 
-  async order(payload) {
+  async order(payload, extra) {
     const param = {
       "amount": payload.amount.toFixed(2),
       "channelType": CHANNEL_TYPE,

+ 1 - 1
src/modules/dj/service/channels/hambitInr.ts

@@ -31,7 +31,7 @@ export class HambitInrService extends BaseService {
   @Logger()
   logger: ILogger;
 
-  async order(payload) {
+  async order(payload, extra) {
     const param = {
       "amount": payload.amount.toFixed(2),
       "channelType": CHANNEL_TYPE,

+ 1 - 1
src/modules/dj/service/channels/sunpay.ts

@@ -161,7 +161,7 @@ export class SunPayService extends BaseService {
     }
   }
 
-  async order(payload) {
+  async order(payload, extra) {
     const data = await this.kycService.getLevel(payload.orderNo);
     if(data && data.payUrl) {
       return {

+ 3 - 1
src/modules/dj/service/pay.ts

@@ -87,6 +87,7 @@ export class PayService extends BaseService {
       const merchant = await this.validMerchant(payload.mchId);
       const rate = await this.validRate(payload.mchId, payload.payType, payload.currency);
       const channel = await this.validPayWay(payload.mchId, payload.payType, payload.amount, payload.currency);
+      this.dispatchService.validOrderParam(channel, payload);
       await this.validSign(payload, merchant);
       await this.validOutOrderNo(payload);
       let channelCharge = (+payload.amount * +channel.rate) / 100 + +channel.basicFee;
@@ -120,7 +121,8 @@ export class PayService extends BaseService {
       await this.orderService.create(order);
       const { isDone = true, payUrl, traceNo } = await this.dispatchService.order(
         order,
-        channel
+        channel,
+        payload
       );
       if (isDone) {
         order.payUrl = payUrl;