wechat.js 7.6 KB
/*
 * @Author: Targaryen
 * @Date: 2017-01-03 17:42:41
 * @Last Modified by: Targaryen
 * @Last Modified time: 2017-04-20 21:01:44
 */

'use strict';
const config = global.yoho.config;
const WxPayConfig = config.WxPayConfig;
const _ = require('lodash');
const logger = global.yoho.logger;
const rp = require('request-promise');
const Promise = require('bluebird');
const co = Promise.coroutine;
const sign = require('./sign');
const md5 = require('yoho-md5');
const moment = require('moment');
const common = require('./common');


class PaySign extends global.yoho.BaseModel {
    constructor(ctx) {
        super(ctx);
    }
    getPayData({uid, orderCode, paymentCode, openId, ip}) {
        let options = {
            uid,
            order_code: orderCode,
            payment_code: paymentCode,
            open_id: openId,
            ip
        };

        return this.post({
            api: global.yoho.ServiceAPI,
            url: '/payment/weixin_data',
            data: options
        }).then(result => {
            if (result && result.code === 200) {
                return result.data || {};
            } else {
                logger.error(`[wchat pay error] orderCode: orderCode | result: ${JSON.stringify(result)}`);
                return {};
            }
        }).catch(err => {
            logger.error(`[wchat pay error] error: ${JSON.stringify(err)}`);
            return {};
        });
    }
}


/**
 * 微信支付相关工具类
 */
const tools = {

    /**
     * 拼接签名字符串
     */
    toUrlParams(urlObj) {
        let buff = '';

        _.forEach(urlObj, (value, key) => {
            if (key !== 'sign') {
                buff += key + '=' + value + '&';
            }
        });

        buff = _.trimEnd(buff, '&');

        return buff;
    },

    /**
     * 构造获取code的url连接
     */
    createOauthUrlForCode(redirectUrl) {
        let urlObj = {
            appid: WxPayConfig.appId,
            redirect_uri: redirectUrl,
            response_type: 'code',
            scope: 'snsapi_base',
            state: 'STATE#wechat_redirect',
        };

        let bizString = tools.toUrlParams(urlObj);

        return 'https://open.weixin.qq.com/connect/oauth2/authorize?' + bizString;
    },

    /**
     * 构造获取open和access_toke的url地址
     */
    createOauthUrlForOpenid(code) {
        let urlObj = {
            appid: WxPayConfig.appId,
            secret: WxPayConfig.appSecret,
            code: code,
            grant_type: 'authorization_code'
        };

        let bizString = tools.toUrlParams(urlObj);

        return 'https://api.weixin.qq.com/sns/oauth2/access_token?' + bizString;
    },

    /**
     * 通过code从工作平台获取openid机器access_token
     */
    getOpenidFromMp(code) {
        let uri = tools.createOauthUrlForOpenid(code);

        let rpOption = {
            method: 'POST',
            uri: uri,
            body: {
                some: 'payload'
            },
            json: true
        };

        return rp(rpOption).then(resultFromWx => {
            return resultFromWx && resultFromWx.openid;
        }).catch(err => {
            logger.error(err);
        });
    },

    /**
     * 微信统一下单接口
     */
    unifiedOrder(params) {
        let unifiedParams = {
            appid: WxPayConfig.appId,
            mch_id: WxPayConfig.mchId,
            notify_url: WxPayConfig.notifyUrl,
            device_info: 'WEB',
            nonce_str: common.nonceStr(),
            body: '有货订单号:' + params.orderCode,
            out_trade_no: 'YOHOBuy_' + params.orderCode,
            total_fee: Math.round(params.totalFee * 100),
            trade_type: 'JSAPI',
            time_start: moment().format('YYYYMMDDHHmmss'),
            time_expire: moment().add(2, 'hours').format('YYYYMMDDHHmmss'),
            spbill_create_ip: params.ip,
            openid: params.openId,
            sign_type: 'MD5'
        };

        let signStr = md5(sign.raw(unifiedParams) + '&key=' + WxPayConfig.key).toUpperCase();

        _.assign(unifiedParams, { sign: signStr });

        let xml = common.toXml(unifiedParams);

        let xmlParams = {
            method: 'POST',
            uri: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: xml,
            timeout: 5000
        };

        return co(function* () {
            let xmlResult = yield rp(xmlParams);
            let jsonResult = yield common.xml2Obj(xmlResult);

            return jsonResult && jsonResult.xml;
        })();
    }
};

// TODO 微信支付
const Wechat = {
    /**
     * 支付中心微信支付相关处理
     */
    getOpenid(code, originalUrl) {
        if (!code) {
            let isProduction = process.env.NODE_ENV === 'production';
            let baseUrl = (isProduction ? 'https://m.yohobuy.com' : 'http://m.yohobuy.com') + originalUrl;
            let redirectUrl = tools.createOauthUrlForCode(baseUrl);

            logger.info('payCenter: wechat pay no code');
            return Promise.resolve({ redirectUrl: redirectUrl });
        } else {
            return tools.getOpenidFromMp(code).then(openid => {
                return { openid: openid };
            });
        }
    },

    /**
     * 异步拉起微信支付相关处理
     */
    // // 原node生成预支付订单
    // pay(ctx, order, extra) {
    //     let {openId, ip, paymentCode} = extra || {};

    //     return co(function* () {
    //         let unifiedOrderResult = yield tools.unifiedOrder({
    //             orderCode: order.order_code,
    //             totalFee: parseFloat(order.payment_amount),
    //             openId: openId,
    //             ip: ip
    //         });

    //         if (unifiedOrderResult && unifiedOrderResult.appid && unifiedOrderResult.prepay_id) {
    //             let nonceStr = common.nonceStr();

    //             let resParams = {
    //                 appId: unifiedOrderResult.appid,
    //                 timeStamp: parseInt(Date.parse(new Date()) / 1000, 10) + '',
    //                 nonceStr: nonceStr,
    //                 package: 'prepay_id=' + unifiedOrderResult.prepay_id,
    //                 signType: 'MD5',
    //             };

    //             let paySign = md5(sign.raw(resParams) + '&key=' + WxPayConfig.key).toUpperCase();

    //             _.assign(resParams, { paySign: paySign });
    //             return {
    //                 code: 200,
    //                 data: {
    //                     jsApiParameters: resParams
    //                 }
    //             };
    //         } else {
    //             logger.info(`wechat unifiedorder result: ${JSON.stringify(unifiedOrderResult)}`);
    //             logger.info('payCenter: wechat pay no unifiedOrderResult');
    //             return {};
    //         }

    //     })();
    // },
    pay(ctx, order, extra) {
        let {openId, ip, paymentCode} = extra || {};

        return new PaySign(ctx).getPayData({
            uid: _.get(ctx, 'req.user.uid'),
            orderCode: order.order_code,
            paymentCode,
            openId,
            ip
        }).then(res => {
            if (res && res.package) {
                return {
                    code: 200,
                    data: {
                        jsApiParameters: Object.assign(res, {
                            appId: WxPayConfig.appId
                        })
                    }
                };
            } else {
                logger.info('payCenter: wechat pay no unifiedOrderResult');
                return {};
            }
        });
    }
};

module.exports = Wechat;