美文网首页
系统重构--微信提现模块

系统重构--微信提现模块

作者: 天草二十六_简村人 | 来源:发表于2019-03-27 14:35 被阅读0次

一、总述

用户登录微信公众号,把自己的系统余额提现到微信账户里。微信提现接口参考:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
这里的交互实体有用户、微信、平台。第一步用户需要在微信取得授权;第二步用户登录平台;第三步平台调用微信提现接口。

二、时序图

1. 用户在微信进行登录授权

用户在微信进行登录授权.png

2.用户登录平台

用户登录平台.png

3. 平台调用微信提现接口

平台调用微信提现接口.png

三、微信提现的关键代码

1、调用微信提现接口:

/**
 * 请求报文示例:
 * <xml>
 * <mch_appid>wx91f15555be5d81f8</mch_appid>
 * <mchid>1487727532</mchid>
 * <nonce_str>ce758408f6ef98d7c7a7b786eca7b3a8</nonce_str>
 * <sign>8D21F9E7A76FBE7DBCB39AFD0552E900</sign>
 * <partner_trade_no>1511343699128386</partner_trade_no>
 * <openid>o6WWEwA9AlhoFWMxdnWkiiixBSHw</openid>
 * <check_name>NO_CHECK</check_name>
 * <amount>2000000</amount>
 * <desc>收益发放</desc>
 * <spbill_create_ip>192.168.80.103</spbill_create_ip>
 * </xml>
 * 返回报文示例:
 * <xml>
 * <return_code><![CDATA[SUCCESS]]></return_code>
 * <return_msg><![CDATA[]]></return_msg>
 * <mch_appid><![CDATA[wx91f15555be5d81f8]]></mch_appid>
 * <mchid><![CDATA[1487727532]]></mchid>
 * <nonce_str><![CDATA[ce758408f6ef98d7c7a7b786eca7b3a8]]></nonce_str>
 * <result_code><![CDATA[SUCCESS]]></result_code>
 * <partner_trade_no><![CDATA[1511343699128386]]></partner_trade_no>
 * <payment_no><![CDATA[1000018301201805027429564640]]></payment_no>
 * <payment_time><![CDATA[2018-05-02 17:50:04]]></payment_time>
 * </xml>
 * 或者
 * <xml>
 * <return_code><![CDATA[SUCCESS]]></return_code>
 * <return_msg><![CDATA[支付失败]]></return_msg>
 * <mch_appid><![CDATA[wx91f15555be5d81f8]]></mch_appid>
 * <mchid><![CDATA[1487727532]]></mchid>
 * <result_code><![CDATA[FAIL]]></result_code>
 * <err_code><![CDATA[SENDNUM_LIMIT]]></err_code>
 * <err_code_des><![CDATA[该用户今日付款次数超过限制,如有需要请登录微信支付商户平台更改API安全配置.]]></err_code_des>
 * </xml>
 */
@Service
public class PayServiceImpl implements PayService {
    @Value("${wechat.appId}")
    private String wxAppId;

    @Value("${wechat.appSecret}")
    private String wxAppSecret;

    @Value("${webchat.merchantId}")
    private String wxMerchantId;

    @Value("${webchat.merchantKey}")
    private String wxMerchantKey;

    @Autowired
    private WechatMessageLogService wechatMessageLogService;
    /**
     * 微信转账url
     */
    private static final String WECHAT_TRANSFER_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";

    /**
     * 微信转账处理
     *
     * @param uid
     * @param recordId
     * @param openId
     * @param amount
     * @return
     */
    @Override
    public WechatMessageLog handleWechatTransfer(Long uid, Long recordId, String openId, BigDecimal amount) {
        //组装退款数据
        WeixinTransferSendData transferSendData = buildWeixinTransferSendData(recordId, openId, amount);

        XmlUtil<WeixinTransferSendData> xmlUtil = new XmlUtil<>();
        String inTransferSendXml = xmlUtil.parseToXml(transferSendData, WeixinTransferSendData.class);
        GwsLogger.info("微信转账处理开始,用户ID:{},记录ID:{},请求入参是{}", uid, recordId, inTransferSendXml);
        //https转账
        WechatMessageLog wechatMessageLog = new WechatMessageLog();
        wechatMessageLog.setUid(uid);
        wechatMessageLog.setRecordId(recordId);
        wechatMessageLog.setAmount(amount);
        wechatMessageLog.setInParam(inTransferSendXml);
        wechatMessageLog.setTradeStatus(WechatWithdrawStatus.FAILURE.getCode());
        try {
            String resultXML = transfer(WECHAT_TRANSFER_URL, inTransferSendXml, wxMerchantId);
            wechatMessageLog.setOutParam(resultXML);
            GwsLogger.info("微信转账返回报文是{},用户ID:{},记录ID:{}", resultXML, uid, recordId);
            Map<String, String> resultMap = XMLUtil.doXMLParse(resultXML);
            if (CollectionUtils.isEmpty(resultMap)) {
                return wechatMessageLog;
            }

            if ("SUCCESS".equals(resultMap.get("return_code"))) {
                // 结果响应正常
                if ("SUCCESS".equals(resultMap.get("result_code"))) {
                    String outTradeNo = resultMap.get("payment_no");
                    wechatMessageLog.setOutTradeNo(outTradeNo);
                    wechatMessageLog.setTradeStatus(WechatWithdrawStatus.SUCCESS.getCode());
                    GwsLogger.info("微信转账成功, 转账金额: {}, 用户ID:{},记录ID:{}", amount, uid, recordId);
                    return wechatMessageLog;
                } else {
                    String errCode = resultMap.get("err_code");
                    String errCodeDes = resultMap.get("err_code_des");
                    wechatMessageLog.setErrorCode(errCode);
                    wechatMessageLog.setErrorMsg(errCodeDes);
                    GwsLogger.info("微信转账失败,错误码是{}, 错误信息是{}, 用户ID:{},记录ID:{}",
                            errCode, errCodeDes, uid, recordId);
                    return wechatMessageLog;
                }
            }
        } catch (Exception e) {
            GwsLogger.info("微信转账处理系统异常,用户ID:{},记录ID:{},异常明细:{}", uid, recordId, e);
            wechatMessageLog.setErrorCode("500001");
            wechatMessageLog.setErrorMsg("微信转账失败");
            return wechatMessageLog;
        } finally {
            wechatMessageLogService.saveWechatMessageLog(wechatMessageLog);
        }
        return wechatMessageLog;
    }

    private String transfer(String url, String data, String mchId) throws Exception {
        /**
         * PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        Resource resource = new ClassPathResource("apiclient_cert.p12");
        InputStream instream = resource.getInputStream();
        try {
            /**密码: MCHID*/
            keyStore.load(instream, mchId.toCharArray());
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,
                new String[]{"TLSv1"}, null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        try {
            // 设置响应头信息
            HttpPost httpost = new HttpPost(url);
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");

            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }

    /**
     * 组装退款数据
     *
     * @param recordId
     * @param openId
     * @param amount
     * @return
     */
    private WeixinTransferSendData buildWeixinTransferSendData(Long recordId, String openId, BigDecimal amount) {
        WeixinTransferSendData transferSendData = new WeixinTransferSendData();
        transferSendData.setAmount(amount.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN).intValue());
        transferSendData.setCheckName("NO_CHECK");
        transferSendData.setDesc("收益发放");
        transferSendData.setMchAppId(wxAppId);
        transferSendData.setMchid(wxMerchantId);
        transferSendData.setNonceStr(WeixinSignUtil.getNonceStr());
        transferSendData.setOpenid(openId);
        transferSendData.setPartnerTradeNo(recordId.toString());
        transferSendData.setSpbillCreateIp(GwsUtil.getLocalIP());
        // 签名
        String sign = WeixinSignUtil.createSign(transferSendData.getSortedMap(), wxMerchantKey);
        transferSendData.setSign(sign);
        return transferSendData;
    }

}

2、用户在平台发起提现申请

 /**
     * 微信转账.
     * 1.创建微信提现记录
     * 2.调用trade服务, 扣减账户余额
     * 3.开始发起微信转账
     * 4.判断转账成功还是失败.如果失败,则调用trade服务的返还用户的余额
     * 如果成功,则更新提现状态, 累计提现成功的次数.
     *
     * @param uid
     * @param amount
     * @param openId
     * @param account
     * @return
     */
    @Override
    public OperationResult<Boolean> wechatWithdraw(Long uid, BigDecimal amount, String openId, String account) {
        /**记录微信提现记录*/
        WechatWithdrawRecord withdrawRecord = createWechatWithdrawRecord(uid, WechatWithdrawStatus.WAIT_PROCESS, amount);
        Long recordId = withdrawRecord.getRecordId();
        GwsLogger.info("uid=[{}]创建微信提现记录,记录ID:{}", uid, recordId);

        /**调用trade服务, 扣减账户余额*/
        CommonResponse response = accountRemoteService.callHandleWechatAcct(uid, recordId, WechatCategory.WITHDRAW, amount);
        if (!SystemCode.SUCCESS.getCode().equals(response.getCode())) {
            GwsLogger.error("调用trade服务扣减账户余额失败,用户ID:{},记录ID:{},返回结果:{}",
                    uid, recordId, JSON.toJSONString(response));
            withdrawRecordService.updateWechatWithdrawRecords(recordId, WechatWithdrawStatus.FAILURE);
            return new OperationResult(response);
        }

        /**发起微信转账*/
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("转账处理任务, 用户ID: " + uid);
        WechatMessageLog wechatMessageLog = payService.handleWechatTransfer(uid, recordId, openId, amount);
        stopWatch.stop();
        GwsLogger.info(stopWatch.getLastTaskName() + ",耗时:" + stopWatch.getTotalTimeMillis());

        /**返回转账结果信息*/
        if (null == wechatMessageLog) {
            return new OperationResult(BizErrorCode.WITHDRAW_ERROR);
        }
        this.handleWechatMessageLog(wechatMessageLog);
        if (WechatWithdrawStatus.SUCCESS.getCode().equals(wechatMessageLog.getTradeStatus())) {
            return new OperationResult(true);
        }

        return new OperationResult(wechatMessageLog.getErrorCode(), wechatMessageLog.getErrorMsg());
    }

    @Async
    public void handleWechatMessageLog(WechatMessageLog wechatMessageLog) {
        Long uid = wechatMessageLog.getUid();
        Long recordId = wechatMessageLog.getRecordId();
        BigDecimal amount = wechatMessageLog.getAmount();

        if (WechatWithdrawStatus.FAILURE.getCode().equals(wechatMessageLog.getTradeStatus())) {
            // 转账失败. 调用trade服务的返还账户余额接口
            GwsLogger.info("微信转账失败, 开始返还账户余额接口. 用户ID:{},记录ID:{}", uid, recordId);
            CommonResponse result = accountRemoteService.callHandleWechatAcct(uid,
                    recordId, WechatCategory.WITHDRAW_BACK, amount);
            if (SystemCode.SUCCESS.getCode().equals(result.getCode())) {
                withdrawRecordService.updateWechatWithdrawRecords(recordId, WechatWithdrawStatus.FAILURE);
                GwsLogger.info("微信提现失败, 账户余额已返还至账户! 用户ID:{},记录ID:{}", uid, recordId);
            } else {
                // warn 发送邮件警告. 自动返还提现失败的金额到账户余额
                GwsLogger.error("微信提现失败, 自动返还提现失败的金额到账户余额出现错误! 用户ID:{},记录ID:{}", uid, recordId);
                emailService.sendEmail("微信提现, 自动返还提现失败的金额到账户余额出现错误",
                        "用户ID是:" + uid + ", 提现记录ID是:" + recordId);
            }
        } else if (WechatWithdrawStatus.SUCCESS.getCode().equals(wechatMessageLog.getTradeStatus())) {
            // 转账成功.
            GwsLogger.info("微信转账成功. 用户ID:{},领取金额:{},记录ID:{}", uid, amount, recordId);
            String nowYayStr = DateUtil.getFormatDate(new Date(), DateUtil.DATA_PATTON_YYYYMMDD);
            // 累计用户提现成功的次数
            redis.increment(CachePrefix.WECHAT_SUCCESS_TIMES_PREFIX, nowYayStr + "_" + uid, 1L);
            withdrawRecordService.updateWechatWithdrawRecords(recordId, WechatWithdrawStatus.SUCCESS);
        }
    }

相关文章

网友评论

      本文标题:系统重构--微信提现模块

      本文链接:https://www.haomeiwen.com/subject/jprzvqtx.html