import { RedisClientType } from '@redis/client'; import Crypto, { CipherKey } from 'crypto'; // eslint-disable-next-line @typescript-eslint/no-var-requires const XML2JS = require(`xml2js`); // eslint-disable-next-line @typescript-eslint/no-var-requires // const random = require('string-random') const randomStr = (length?:number,nonum?:boolean)=>{ let arr = '1234567890abcdefghijklmnopqrstuvwxyz'; if(nonum) arr = 'abcdefghijklmnopqrstuvwxyz'; let str = ''; length = length?length:3; for(let i = 0;i{ const certificate = await this._redisClient.get(`WECHAT_MCH_${this._mchId}_PLATFORM_CERT`); if(certificate) return resolve(certificate); const result = await this._getNewPlatformCertificates(); const cert = result.cert; if(cert){ await this._redisClient.set(`WECHAT_MCH_${this._mchId}_PLATFORM_CERT`,cert.certificate); await this._redisClient.expire(`WECHAT_MCH_${this._mchId}_PLATFORM_CERT`,7200); resolve(cert.certificate); }else{ resolve(null); } }); } async _getNewPlatformCertificates(){ const signedObj = this.makeSignRequestData(this._wechatMchApiURL.getPlatformCertificates,'GET',''); const getPlatformCertsResult = await this._requestMchV3Api(this._wechatMchApiURL.getPlatformCertificates,'GET',signedObj.timestamp,signedObj.nonce_str,'',signedObj.signature) as AnyKeyString; if(getPlatformCertsResult.statusCode==200){ const cert = getPlatformCertsResult.data.data[0]; const certContent = this._decryptByV3Key(cert.encrypt_certificate.ciphertext,cert.encrypt_certificate.nonce,cert.encrypt_certificate.associated_data); const certSerial = cert.serial_no; return({cert:{serial:certSerial,certificate:certContent}}); }else{ LOGGER.error('Get new platform certificates failed'); LOGGER.error(getPlatformCertsResult.toString()); return({cert:null}); } } async orderTrade_JSAPI(openid:string,trade_no:string,amount:number,desc:string,expire_unix_timestamp:number,notify_url:string){ const postData = { appid:this._appId, mchid:this._mchId, description:desc, out_trade_no:trade_no, time_expire:Moment.unix(expire_unix_timestamp).format(), notify_url:notify_url, amount:{ total:amount, currency:'CNY' }, payer:{ openid:openid } }; const originBody = JSON.stringify(postData); const signedObj = this.makeSignRequestData(this._wechatMchApiURL.orderTrade_JsAPI,'POST',postData); const result = await this._requestMchV3Api(this._wechatMchApiURL.orderTrade_JsAPI,'POST',signedObj.timestamp,signedObj.nonce_str,originBody,signedObj.signature) as AnyKeyString; if(result.statusCode==200){ return result.data.prepay_id; }else{ LOGGER.error('Wechat Mch orderTrade error:'); LOGGER.error(JSON.stringify(result)); return null; } } businessPay_To_Openid(orderid:string,openid:string,amount:number,desc:string){ return new Promise(resolve=>{ const postDataArr:any[] = []; const postData:AnyKeyString = { mch_appid: this._appId, mchid: this._mchId, nonce_str: randomStr(32).toUpperCase(), partner_trade_no: orderid, openid, check_name: 'NO_CHECK', amount, desc } Object.keys(postData).forEach(key=>{ postDataArr.push(`${key}=${postData[key]}`); }); postDataArr.sort(); let postStr = postDataArr.join('&'); postStr += `&key=${this._privateAPIKey}`; const md5 = Crypto.createHash('md5') postData.sign = md5.update(postStr).digest('hex').toUpperCase(); let postXml = ''; Object.keys(postData).forEach(key=>{ postXml += `<${key}>${postData[key]}`; }); postXml += '' REQUEST({url:this._wechatMchApiURL.businessPay_To_Openid,method:'POST',body:postXml,encoding:'utf-8',cert:this._privateCertPem,key:this._privateKeyPem},(err:any,response:any,body:any)=>{ const x2jparser = XML2JS.parseString; x2jparser(body, function (err:any, result:any) { if(result.xml && result.xml.result_code[0]!='SUCCESS'){ return resolve({err:result.xml.err_code[0],data:result.xml.err_code_des[0]}) } resolve({err:null,data:'SUCCESS'}); }); }) }); } makeSignPaymentData(prepayId:string){ const randStr = randomStr(32).toUpperCase(),timeStamp = Moment().unix(),packageStr = `prepay_id=${prepayId}`; let preStr = '' preStr += `${this._appId}\n`; preStr += `${timeStamp}\n`; preStr += `${randStr}\n`; preStr += `${packageStr}\n`; // console.log(preStr) const signedStr = this._signWithRSASHA256(preStr); // console.log(signedStr) return {timeStamp:`${timeStamp}`,nonceStr:randStr,paySign:signedStr,package:packageStr,signType:'RSA'}; } makeSignRequestData(requestUrl:string,method:string,content:AnyKeyString|string){ const randStr = randomStr(32).toUpperCase(),timestamp = Moment().unix(); const url = new URL(requestUrl); const postData = (typeof content === 'object')?JSON.stringify(content):content; let preStr = '' preStr += `${method.toUpperCase()}\n`; preStr += `${url.pathname}${url.search}\n`; preStr += `${timestamp}\n`; preStr += `${randStr}\n`; preStr += `${postData}\n`; // console.log(preStr) const signedStr = this._signWithRSASHA256(preStr); // console.log(signedStr) return {origin:preStr,timestamp,nonce_str:randStr,signature:signedStr}; } callbackDecrypt(ciphertext:string,nonce:string,associatedData:string){ const result = this._decryptByV3Key(ciphertext,nonce,associatedData); return result; } _signWithRSASHA256(data:string){ const signObj = Crypto.createSign('RSA-SHA256') signObj.update(data); const signedStr = signObj.sign(this._privateKeyPem,'base64'); return signedStr; } _requestMchV3Api(url:string,method:string,timestamp:number,nonce_str:string,originBody:string|AnyKeyString,signature:string){ return new Promise((resolve,reject)=>{ const Authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${this._mchId}",nonce_str="${nonce_str}",signature="${signature}",timestamp="${timestamp}",serial_no="${this._privateKeySerial}"`; REQUEST({url:url,method,encoding:'utf-8',jsonReviver: true,body:originBody,headers: { 'Content-Type' : 'application/json', 'Accept' : 'application/json', 'User-Agent' : 'WECHAT_MCH_SDK/v1', 'Authorization' : Authorization, }},(err:any,response:any,body:any)=>{ // console.log(response.headers,body) if(typeof body ==='string'){ try{ body = JSON.parse(body); }catch(e){} } resolve({statusCode:response.statusCode,data:body}); }); }); } _decryptByV3Key(ciphertext:string,nonce:string,associatedData:string){ const ciphertextBuffer = Buffer.from(ciphertext, 'base64') const authTag = ciphertextBuffer.slice(ciphertextBuffer.length - 16) const data = ciphertextBuffer.slice(0, ciphertextBuffer.length - 16) const decipherIv = Crypto.createDecipheriv('aes-256-gcm', this._privateAPIV3Key, nonce) decipherIv.setAuthTag(Buffer.from(authTag)) decipherIv.setAAD(Buffer.from(associatedData)) let decryptStr = decipherIv.update(data, undefined, 'utf8') decipherIv.final(); if(typeof decryptStr == 'string'){ try{ decryptStr = JSON.parse(decryptStr); }catch(e){} } return decryptStr; } async _decryptByPlatfromKey(ciphertext:string,nonce:string,associatedData:string){ const platformKey = await this.getPlatformCertificates() as CipherKey; const ciphertextBuffer = Buffer.from(ciphertext, 'base64'); const authTag = ciphertextBuffer.slice(ciphertextBuffer.length - 16) const data = ciphertextBuffer.slice(0, ciphertextBuffer.length - 16) const decipherIv = Crypto.createDecipheriv('aes-256-gcm', platformKey, nonce) decipherIv.setAuthTag(Buffer.from(authTag)) decipherIv.setAAD(Buffer.from(associatedData)) const decryptStr = decipherIv.update(data, undefined, 'utf8') decipherIv.final(); return decryptStr; } } module.exports = WechatMchSDK;