All files / src index.ts

84.62% Statements 55/65
65% Branches 13/20
100% Functions 10/10
84.21% Lines 48/57
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 1391x 1x 1x 1x 1x   1x       1x                 1x         1x     1x 1x 1x 1x 1x               1x 1x 1x 1x                 1x 1x     1x 1x                   1x 1x 1x 1x 10x   1x 1x 1x               1x 1x 1x 1x           1x   1x   1x   1x   1x         1x 1x       1x 1x         1x                     1x       1x         1x   1x  
import * as js2xml from 'js2xmlparser';
import * as xml2json from 'xml2json';
import * as md5 from 'md5';
import * as request from 'request';
import * as url from 'url';
 
import { readomString } from './util';
 
import { WechatPayOptions, WechatSign, WechatOpenidRes, PaymentArgs } from './interface';
 
class WechatPay {
  // api hostname
  baseUrl: string;
  appid: string;
  secret: string;
  // tslint:disable-next-line:variable-name
  mch_id: string | number;
  // 商户密钥
  key: string;
  baseApiObj = {
    protocol: 'https',
    hostname: 'api.weixin.qq.com',
  };
  constructor(options: WechatPayOptions) {
    Iif (!options) {
      throw new Error('options is required');
    }
    this.baseUrl = options.baseUrl || 'https://api.weixin.qq.com';
    this.appid = options.appid;
    this.secret = options.secret;
    this.mch_id = options.mch_id;
    this.key = options.key;
  }
 
  /**
   * 获取用户openid
   * @param code url上的code
   * @param callback 回调
   */
  getUserOpenId(code: string, callback?: (result: WechatOpenidRes) => void): Promise<WechatOpenidRes> {
    const { appid, secret } = this;
    return new Promise((resolve, reject) => {
      const openidUrl = url.format(Object.assign({}, {
        pathname: '/sns/jscode2session',
        query: {
          appid,
          secret,
          js_code: code,
          grant_type: 'authorization_code',
        },
      }, this.baseApiObj));
      request.get(openidUrl, { json: true }, (err, res, data) => {
        Iif (err || res.statusCode !== 200) {
          throw new Error('get openid failed');
        } else {
          resolve(data as WechatOpenidRes);
          Eif (callback) callback(data);
        }
      });
    });
  }
 
  /**
   * generator sign
   * @param obj
   */
  private _generatorSign(obj: any) {
    let sortArr = Object.keys(obj).sort();
    let sortStr = '';
    sortArr = sortArr.map((key) => {
      return `${key}=${obj[key]}`;
    });
    sortArr.push(`key=${this.key}`);
    sortStr = sortArr.join('&');
    return md5(sortStr).toUpperCase();
  }
 
  /**
   * 发起微信支付
   * @param options 发起支付的参数
   * @param callback 回调函数
   */
  payment(options: PaymentArgs, callback?: (result: any) => void) {
    Iif (!options) throw new Error('payment method need args');
    return new Promise((resolve, reject) => {
      const basicReq = {
        appid: this.appid,
        mch_id: this.mch_id,
        nonce_str: readomString(32),
        trade_type: 'JSAPI',
      };
      const customerReq = Object.assign({}, basicReq, options);
 
      const sign = this._generatorSign(customerReq);
 
      customerReq.sign = sign;
 
      const modal2xml = js2xml.parse('xml', customerReq);
 
      request({
        url: url.format(Object.assign({}, this.baseApiObj, { hostname: 'api.mch.weixin.qq.com', pathname: '/pay/unifiedorder' })),
        method: 'POST',
        body: modal2xml,
      }, (err, res, data) => {
        let originalData: any = { xml: {} }, wechatPayData: any = {};
        Iif (err || res.statusCode !== 200) {
          reject(new Error('connect failed'));
          return;
        }
        try {
          originalData = JSON.parse(xml2json.toJson(data));
        } catch (error) {
          reject(new Error('xml parse failed'));
          return;
        }
        Iif (originalData.xml.return_code === 'SUCCESS') {
          const prepay_id = originalData.xml.prepay_id;
          wechatPayData = {
            appId: this.appid,
            timeStamp: new Date().getTime(),
            nonceStr: readomString(32),
            package: `prepay_id=${ prepay_id }`,
            signType: 'MD5',
          };
          wechatPayData.paySign = this._generatorSign(wechatPayData);
        }
        resolve({
          original_data: originalData.xml,
          wechatpay_data: wechatPayData,
        });
        Eif (callback) callback({ original_data: originalData, wechatpay_data: wechatPayData });
      });
    });
  }
 
}
 
export = WechatPay;