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

一、总述

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

二、时序图

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

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

2.用户登录平台

用户登录平台.png

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

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

三、微信提现的关键代码

1、调用微信提现接口:

/**
 * 请求报文示例:
 * 
 * wx91f15555be5d81f8
 * 1487727532
 * ce758408f6ef98d7c7a7b786eca7b3a8
 * 8D21F9E7A76FBE7DBCB39AFD0552E900
 * 1511343699128386
 * o6WWEwA9AlhoFWMxdnWkiiixBSHw
 * NO_CHECK
 * 2000000
 * 收益发放
 * 192.168.80.103
 * 
 * 返回报文示例:
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 或者
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 */
@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 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 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 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);
        }
    }

你可能感兴趣的:(系统重构--微信提现模块)