|  | @@ -0,0 +1,568 @@
 | 
	
		
			
				|  |  | +package com.yami.shop.wx.service.impl;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import com.alibaba.fastjson.JSON;
 | 
	
		
			
				|  |  | +import com.alibaba.fastjson2.JSONObject;
 | 
	
		
			
				|  |  | +import com.alibaba.fastjson2.TypeReference;
 | 
	
		
			
				|  |  | +import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
 | 
	
		
			
				|  |  | +import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
 | 
	
		
			
				|  |  | +import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
 | 
	
		
			
				|  |  | +import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
 | 
	
		
			
				|  |  | +import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
 | 
	
		
			
				|  |  | +import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
 | 
	
		
			
				|  |  | +import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
 | 
	
		
			
				|  |  | +import com.yami.shop.wx.config.CombinePayUrlEnum;
 | 
	
		
			
				|  |  | +import com.yami.shop.wx.config.WxConstants;
 | 
	
		
			
				|  |  | +import com.yami.shop.wx.po.JsapiPo;
 | 
	
		
			
				|  |  | +import com.yami.shop.wx.service.WxProviderService;
 | 
	
		
			
				|  |  | +import com.yami.shop.wx.utils.HttpUtils;
 | 
	
		
			
				|  |  | +import com.yami.shop.wx.utils.OrderUtils;
 | 
	
		
			
				|  |  | +import com.yami.shop.wx.utils.WechatPayValidator;
 | 
	
		
			
				|  |  | +import lombok.SneakyThrows;
 | 
	
		
			
				|  |  | +import lombok.extern.slf4j.Slf4j;
 | 
	
		
			
				|  |  | +import org.apache.http.HttpEntity;
 | 
	
		
			
				|  |  | +import org.apache.http.HttpStatus;
 | 
	
		
			
				|  |  | +import org.apache.http.client.methods.CloseableHttpResponse;
 | 
	
		
			
				|  |  | +import org.apache.http.client.methods.HttpGet;
 | 
	
		
			
				|  |  | +import org.apache.http.client.methods.HttpPost;
 | 
	
		
			
				|  |  | +import org.apache.http.entity.StringEntity;
 | 
	
		
			
				|  |  | +import org.apache.http.impl.client.CloseableHttpClient;
 | 
	
		
			
				|  |  | +import org.apache.http.util.EntityUtils;
 | 
	
		
			
				|  |  | +import org.springframework.stereotype.Service;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import javax.servlet.http.HttpServletRequest;
 | 
	
		
			
				|  |  | +import javax.servlet.http.HttpServletResponse;
 | 
	
		
			
				|  |  | +import java.io.BufferedWriter;
 | 
	
		
			
				|  |  | +import java.io.FileWriter;
 | 
	
		
			
				|  |  | +import java.io.IOException;
 | 
	
		
			
				|  |  | +import java.io.InputStream;
 | 
	
		
			
				|  |  | +import java.nio.charset.StandardCharsets;
 | 
	
		
			
				|  |  | +import java.security.PrivateKey;
 | 
	
		
			
				|  |  | +import java.security.Signature;
 | 
	
		
			
				|  |  | +import java.util.Base64;
 | 
	
		
			
				|  |  | +import java.util.HashMap;
 | 
	
		
			
				|  |  | +import java.util.List;
 | 
	
		
			
				|  |  | +import java.util.Map;
 | 
	
		
			
				|  |  | +import java.util.concurrent.locks.ReentrantLock;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Slf4j
 | 
	
		
			
				|  |  | +@Service
 | 
	
		
			
				|  |  | +public class WxProviderServiceImpl implements WxProviderService {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private final ReentrantLock lock = new ReentrantLock();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public synchronized Map<String, Object> subJsapi(JsapiPo po) {
 | 
	
		
			
				|  |  | +        System.out.println("微信支付传入参数===========" + po);
 | 
	
		
			
				|  |  | +        String type = "sub_jsapi";
 | 
	
		
			
				|  |  | +        Map<String, Object> params = new HashMap<>(8);
 | 
	
		
			
				|  |  | +        params.put("sp_appid", WxConstants.SP_APP_ID);
 | 
	
		
			
				|  |  | +        params.put("sp_mchid", WxConstants.SP_MCH_ID);
 | 
	
		
			
				|  |  | +        params.put("sub_appid", WxConstants.SUB_APP_ID);
 | 
	
		
			
				|  |  | +        params.put("sub_mchid", WxConstants.SUB_MCH_ID);
 | 
	
		
			
				|  |  | +        Map<String, Object> payerMap = new HashMap<>(4);
 | 
	
		
			
				|  |  | +        payerMap.put("sub_openid", po.getOpenId());
 | 
	
		
			
				|  |  | +        params.put("payer", payerMap);
 | 
	
		
			
				|  |  | +        params.put("description", po.getDescription());
 | 
	
		
			
				|  |  | +        params.put("out_trade_no", po.getOutTradeNo());
 | 
	
		
			
				|  |  | +        params.put("notify_url", WxConstants.NOTIFY_URL);
 | 
	
		
			
				|  |  | +        JSONObject attachJson = po.getAttachJson();
 | 
	
		
			
				|  |  | +        if (attachJson != null) {
 | 
	
		
			
				|  |  | +            params.put("attach", JSONObject.toJSONString(attachJson));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        Map<String, Object> amountMap = new HashMap<>(4);
 | 
	
		
			
				|  |  | +        amountMap.put("total", po.getTotal());
 | 
	
		
			
				|  |  | +        amountMap.put("currency", "CNY");
 | 
	
		
			
				|  |  | +        params.put("amount", amountMap);
 | 
	
		
			
				|  |  | +        Map<String, Object> sceneInfoMap = new HashMap<>(4);
 | 
	
		
			
				|  |  | +        sceneInfoMap.put("payer_client_ip", "127.0.0.1");
 | 
	
		
			
				|  |  | +        sceneInfoMap.put("device_id", "127.0.0.1");
 | 
	
		
			
				|  |  | +        params.put("scene_info", sceneInfoMap);
 | 
	
		
			
				|  |  | +        String paramsStr = JSON.toJSONString(params);
 | 
	
		
			
				|  |  | +        log.info("请求参数 ===> {}" + paramsStr);
 | 
	
		
			
				|  |  | +        String[] split = type.split("_");
 | 
	
		
			
				|  |  | +        String newType = split[split.length - 1];
 | 
	
		
			
				|  |  | +        String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType));
 | 
	
		
			
				|  |  | +        log.info("请求地址 ===> {}" + url);
 | 
	
		
			
				|  |  | +        String resStr = wechatHttpPost(url, paramsStr);
 | 
	
		
			
				|  |  | +        Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        Map<String, Object> signMap = paySignMsgApplet(resMap);
 | 
	
		
			
				|  |  | +        resMap.put("type", type);
 | 
	
		
			
				|  |  | +        resMap.put("signMap", signMap);
 | 
	
		
			
				|  |  | +        return resMap;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 构建签名
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param map 参数
 | 
	
		
			
				|  |  | +     * @return map
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private Map<String, Object> paySignMsgApplet(Map<String, Object> map) {
 | 
	
		
			
				|  |  | +        long timeMillis = System.currentTimeMillis();
 | 
	
		
			
				|  |  | +        String timeStamp = timeMillis / 1000 + "";
 | 
	
		
			
				|  |  | +        String nonceStr = String.valueOf(timeMillis);
 | 
	
		
			
				|  |  | +        String prepayId = map.get("prepay_id").toString();
 | 
	
		
			
				|  |  | +        String packageStr = "prepay_id=" + prepayId;
 | 
	
		
			
				|  |  | +        Map<String, Object> resMap = new HashMap<>();
 | 
	
		
			
				|  |  | +        resMap.put("nonceStr", nonceStr);
 | 
	
		
			
				|  |  | +        resMap.put("timeStamp", timeStamp);
 | 
	
		
			
				|  |  | +        resMap.put("appId", WxConstants.SP_APP_ID);
 | 
	
		
			
				|  |  | +        resMap.put("package", packageStr);
 | 
	
		
			
				|  |  | +        resMap.put("paySign", getWxPayResultMap(prepayId, timeStamp, nonceStr));
 | 
	
		
			
				|  |  | +        resMap.put("signType", "RSA");
 | 
	
		
			
				|  |  | +        return resMap;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @SneakyThrows
 | 
	
		
			
				|  |  | +    public String getWxPayResultMap(String prepayId, String timestamp, String nonceStr) {
 | 
	
		
			
				|  |  | +        Signature sign = Signature.getInstance("SHA256withRSA");
 | 
	
		
			
				|  |  | +        PrivateKey privateKey = getPrivateKey(WxConstants.KEY_PEM_PATH);
 | 
	
		
			
				|  |  | +        sign.initSign(privateKey);
 | 
	
		
			
				|  |  | +        String sb = WxConstants.SUB_APP_ID + "\n" +
 | 
	
		
			
				|  |  | +                timestamp + "\n" +
 | 
	
		
			
				|  |  | +                nonceStr + "\n" +
 | 
	
		
			
				|  |  | +                "prepay_id=" + prepayId + "\n";
 | 
	
		
			
				|  |  | +        sign.update(sb.getBytes(StandardCharsets.UTF_8));
 | 
	
		
			
				|  |  | +        return Base64.getEncoder().encodeToString(sign.sign());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 关闭(取消)订单
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param orderNo orderNo
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public void closeOrder(String orderNo) {
 | 
	
		
			
				|  |  | +        // TODO 用于在客户下单后,不进行支付,取消订单的场景
 | 
	
		
			
				|  |  | +        log.info("根据订单号取消订单,订单号: {}", orderNo);
 | 
	
		
			
				|  |  | +        String url = String.format(CombinePayUrlEnum.CLOSE_ORDER_BY_NO.getType(), orderNo);
 | 
	
		
			
				|  |  | +        url = WxConstants.BASE_URL.concat(url);
 | 
	
		
			
				|  |  | +        Map<String, String> params = new HashMap<>(2);
 | 
	
		
			
				|  |  | +        params.put("sp_mchid", WxConstants.SP_MCH_ID);
 | 
	
		
			
				|  |  | +        params.put("sub_mchid", WxConstants.SUB_MCH_ID);
 | 
	
		
			
				|  |  | +        String paramsStr = JSON.toJSONString(params);
 | 
	
		
			
				|  |  | +        log.info("请求参数 ===> {}" + paramsStr);
 | 
	
		
			
				|  |  | +        String res = wechatHttpPost(url, paramsStr);
 | 
	
		
			
				|  |  | +        log.info(res);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 申请退款
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param orderNo orderNo
 | 
	
		
			
				|  |  | +     * @return orderNo
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public String refundOrder(String orderNo) {
 | 
	
		
			
				|  |  | +        Integer total = 0;
 | 
	
		
			
				|  |  | +        log.info("根据订单号申请退款,订单号: {}", orderNo);
 | 
	
		
			
				|  |  | +        String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.DOMESTIC_REFUNDS.getType());
 | 
	
		
			
				|  |  | +        Map<String, Object> params = new HashMap<>(2);
 | 
	
		
			
				|  |  | +        params.put("out_trade_no", orderNo);
 | 
	
		
			
				|  |  | +        params.put("sub_mchid", WxConstants.SUB_MCH_ID);
 | 
	
		
			
				|  |  | +        String outRefundNo = OrderUtils.getOrderNo("TK");
 | 
	
		
			
				|  |  | +        log.info("退款申请号:{}", outRefundNo);
 | 
	
		
			
				|  |  | +        params.put("out_refund_no", outRefundNo + "");
 | 
	
		
			
				|  |  | +        params.put("reason", "申请退款");
 | 
	
		
			
				|  |  | +        params.put("notify_url", WxConstants.REFUND_NOTIFY_URL);
 | 
	
		
			
				|  |  | +        Map<String, Object> amountMap = new HashMap<>();
 | 
	
		
			
				|  |  | +        //退款金额,单位:分
 | 
	
		
			
				|  |  | +        amountMap.put("refund", total);
 | 
	
		
			
				|  |  | +        //原订单金额,单位:分
 | 
	
		
			
				|  |  | +        amountMap.put("total", total);
 | 
	
		
			
				|  |  | +        amountMap.put("currency", "CNY");
 | 
	
		
			
				|  |  | +        params.put("amount", amountMap);
 | 
	
		
			
				|  |  | +        String paramsStr = JSON.toJSONString(params);
 | 
	
		
			
				|  |  | +        log.info("请求参数 ===> {}" + paramsStr);
 | 
	
		
			
				|  |  | +        String res;
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            res = wechatHttpPost(url, paramsStr);
 | 
	
		
			
				|  |  | +        } catch (Exception e) {
 | 
	
		
			
				|  |  | +            throw new RuntimeException(e.getMessage());
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        log.info("退款结果:{}", res);
 | 
	
		
			
				|  |  | +        return res;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 查询单笔退款信息
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param refundNo refundNo
 | 
	
		
			
				|  |  | +     * @return return
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public Map<String, Object> queryRefundOrder(String refundNo) {
 | 
	
		
			
				|  |  | +        log.info("根据订单号查询退款订单,订单号: {}", refundNo);
 | 
	
		
			
				|  |  | +        String url = WxConstants.BASE_URL
 | 
	
		
			
				|  |  | +                .concat(CombinePayUrlEnum.DOMESTIC_REFUNDS_QUERY.getType().concat(refundNo))
 | 
	
		
			
				|  |  | +                .concat("?sub_mchid=").concat(WxConstants.SUB_MCH_ID);
 | 
	
		
			
				|  |  | +        String res = wechatHttpGet(url);
 | 
	
		
			
				|  |  | +        log.info("查询退款订单结果:{}", res);
 | 
	
		
			
				|  |  | +        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        String successTime = resMap.get("success_time").toString();
 | 
	
		
			
				|  |  | +        String refundId = resMap.get("refund_id").toString();
 | 
	
		
			
				|  |  | +        /*
 | 
	
		
			
				|  |  | +          款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
 | 
	
		
			
				|  |  | +          枚举值:
 | 
	
		
			
				|  |  | +          SUCCESS:退款成功
 | 
	
		
			
				|  |  | +          CLOSED:退款关闭
 | 
	
		
			
				|  |  | +          PROCESSING:退款处理中
 | 
	
		
			
				|  |  | +          ABNORMAL:退款异常
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        String status = resMap.get("status").toString();
 | 
	
		
			
				|  |  | +        /*
 | 
	
		
			
				|  |  | +          枚举值:
 | 
	
		
			
				|  |  | +          ORIGINAL:原路退款
 | 
	
		
			
				|  |  | +          BALANCE:退回到余额
 | 
	
		
			
				|  |  | +          OTHER_BALANCE:原账户异常退到其他余额账户
 | 
	
		
			
				|  |  | +          OTHER_BANKCARD:原银行卡异常退到其他银行卡
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        String channel = resMap.get("channel").toString();
 | 
	
		
			
				|  |  | +        String userReceivedAccount = resMap.get("user_received_account").toString();
 | 
	
		
			
				|  |  | +        log.info("successTime:" + successTime);
 | 
	
		
			
				|  |  | +        log.info("channel:" + channel);
 | 
	
		
			
				|  |  | +        log.info("refundId:" + refundId);
 | 
	
		
			
				|  |  | +        log.info("status:" + status);
 | 
	
		
			
				|  |  | +        log.info("userReceivedAccount:" + userReceivedAccount);
 | 
	
		
			
				|  |  | +        // TODO 在查询单笔退款信息时,可以再去查询一次订单的状态,保证该订单已经退款完毕了
 | 
	
		
			
				|  |  | +        return resMap;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 申请交易账单
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请 ,如果传入日期未为当天则会出错
 | 
	
		
			
				|  |  | +     * @param billType 分为:ALL、SUCCESS、REFUND
 | 
	
		
			
				|  |  | +     *                 ALL:返回当日所有订单信息(不含充值退款订单)
 | 
	
		
			
				|  |  | +     *                 SUCCESS:返回当日成功支付的订单(不含充值退款订单)
 | 
	
		
			
				|  |  | +     *                 REFUND:返回当日退款订单(不含充值退款订单)
 | 
	
		
			
				|  |  | +     * @return 结果
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public String tradeBill(String billDate, String billType) {
 | 
	
		
			
				|  |  | +        log.info("申请交易账单,billDate:{},billType:{}", billDate, billType);
 | 
	
		
			
				|  |  | +        String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.TRADE_BILLS.getType())
 | 
	
		
			
				|  |  | +                .concat("?bill_date=").concat(billDate).concat("&bill_type=").concat(billType);
 | 
	
		
			
				|  |  | +        // 填则默认返回服务商下的交易或退款数据,下载某个子商户下的交易或退款数据,则该字段必填
 | 
	
		
			
				|  |  | +        url = url.concat("&sub_mchid=").concat(WxConstants.SUB_MCH_ID);
 | 
	
		
			
				|  |  | +        String res = wechatHttpGet(url);
 | 
	
		
			
				|  |  | +        log.info("查询退款订单结果:{}", res);
 | 
	
		
			
				|  |  | +        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        return resMap.get("download_url").toString();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * @param billDate    格式yyyy-MM-dd 仅支持三个月内的账单下载申请,如果传入日期未为当天则会出错
 | 
	
		
			
				|  |  | +     * @param accountType 分为:BASIC、OPERATION、FEES
 | 
	
		
			
				|  |  | +     *                    BASIC:基本账户
 | 
	
		
			
				|  |  | +     *                    OPERATION:运营账户
 | 
	
		
			
				|  |  | +     *                    FEES:手续费账户
 | 
	
		
			
				|  |  | +     * @return 结果
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public String fundFlowBill(String billDate, String accountType) {
 | 
	
		
			
				|  |  | +        log.info("申请交易账单,billDate:{},accountType:{}", billDate, accountType);
 | 
	
		
			
				|  |  | +        String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.FUND_FLOW_BILLS.getType())
 | 
	
		
			
				|  |  | +                .concat("?bill_date=").concat(billDate).concat("&account_type=").concat(accountType);
 | 
	
		
			
				|  |  | +        String res = wechatHttpGet(url);
 | 
	
		
			
				|  |  | +        log.info("查询退款订单结果:{}", res);
 | 
	
		
			
				|  |  | +        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        return resMap.get("download_url").toString();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * @param billDate    格式yyyy-MM-dd 仅支持三个月内的账单下载申请,如果传入日期未为当天则会出错
 | 
	
		
			
				|  |  | +     * @param accountType 分为:BASIC、OPERATION、FEES
 | 
	
		
			
				|  |  | +     *                    BASIC:基本账户
 | 
	
		
			
				|  |  | +     *                    OPERATION:运营账户
 | 
	
		
			
				|  |  | +     *                    FEES:手续费账户
 | 
	
		
			
				|  |  | +     * @return 结果
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public String subMerchantFundFlowBill(String billDate, String accountType) {
 | 
	
		
			
				|  |  | +        log.info("申请单个子商户资金账单,billDate:{},accountType:{}", billDate, accountType);
 | 
	
		
			
				|  |  | +        String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.FUND_FLOW_BILLS.getType())
 | 
	
		
			
				|  |  | +                .concat("?bill_date=").concat(billDate).concat("&account_type=").concat(accountType)
 | 
	
		
			
				|  |  | +                .concat("&sub_mchid=").concat(billDate).concat("&algorithm=").concat("AEAD_AES_256_GCM");
 | 
	
		
			
				|  |  | +        String res = wechatHttpGet(url);
 | 
	
		
			
				|  |  | +        log.info("查询退款订单结果:{}", res);
 | 
	
		
			
				|  |  | +        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        String downloadBillCount = resMap.get("download_bill_count").toString();
 | 
	
		
			
				|  |  | +        String downloadBillList = resMap.get("download_bill_list").toString();
 | 
	
		
			
				|  |  | +        List<Map<String, Object>> billListMap = JSONObject.parseObject(downloadBillList, new TypeReference<List<Map<String, Object>>>() {
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        String downloadUrl = billListMap.get(0).get("download_url").toString();
 | 
	
		
			
				|  |  | +        log.info("downloadBillCount=" + downloadBillCount);
 | 
	
		
			
				|  |  | +        log.info("downloadUrl=" + downloadUrl);
 | 
	
		
			
				|  |  | +        return downloadUrl;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 下载账单
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param downloadUrl downloadUrl
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public void downloadBill(String downloadUrl) {
 | 
	
		
			
				|  |  | +        log.info("下载账单,下载地址:{}", downloadUrl);
 | 
	
		
			
				|  |  | +        HttpGet httpGet = new HttpGet(downloadUrl);
 | 
	
		
			
				|  |  | +        httpGet.addHeader("Accept", "application/json");
 | 
	
		
			
				|  |  | +        CloseableHttpResponse response = null;
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            response = noSignHttpClient().execute(httpGet);
 | 
	
		
			
				|  |  | +            String body = EntityUtils.toString(response.getEntity());
 | 
	
		
			
				|  |  | +            int statusCode = response.getStatusLine().getStatusCode();
 | 
	
		
			
				|  |  | +            if (statusCode == 200 || statusCode == 204) {
 | 
	
		
			
				|  |  | +                log.info("下载账单,返回结果 = " + body);
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                throw new RuntimeException("下载账单异常, 响应码 = " + statusCode + ", 下载账单返回结果 = " + body);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // TODO 将body内容转为excel存入本地或者输出到浏览器,演示存入本地
 | 
	
		
			
				|  |  | +            writeStringToFile(body);
 | 
	
		
			
				|  |  | +        } catch (Exception e) {
 | 
	
		
			
				|  |  | +            throw new RuntimeException(e.getMessage());
 | 
	
		
			
				|  |  | +        } finally {
 | 
	
		
			
				|  |  | +            if (response != null) {
 | 
	
		
			
				|  |  | +                try {
 | 
	
		
			
				|  |  | +                    response.close();
 | 
	
		
			
				|  |  | +                } catch (IOException e) {
 | 
	
		
			
				|  |  | +                    e.printStackTrace();
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 文件写入
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param body body
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private void writeStringToFile(String body) {
 | 
	
		
			
				|  |  | +        FileWriter fw = null;
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            String filePath = "C:\\Users\\lhz12\\Desktop\\wxPay.txt";
 | 
	
		
			
				|  |  | +            fw = new FileWriter(filePath, true);
 | 
	
		
			
				|  |  | +            BufferedWriter bw = new BufferedWriter(fw);
 | 
	
		
			
				|  |  | +            bw.write(body);
 | 
	
		
			
				|  |  | +        } catch (Exception e) {
 | 
	
		
			
				|  |  | +            e.printStackTrace();
 | 
	
		
			
				|  |  | +        } finally {
 | 
	
		
			
				|  |  | +            try {
 | 
	
		
			
				|  |  | +                if (fw != null) {
 | 
	
		
			
				|  |  | +                    fw.close();
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            } catch (IOException e) {
 | 
	
		
			
				|  |  | +                e.printStackTrace();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 获取商户的私钥文件
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param keyPemPath keyPemPath
 | 
	
		
			
				|  |  | +     * @return return
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public PrivateKey getPrivateKey(String keyPemPath) {
 | 
	
		
			
				|  |  | +        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
 | 
	
		
			
				|  |  | +        if (inputStream == null) {
 | 
	
		
			
				|  |  | +            throw new RuntimeException("私钥文件不存在");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return PemUtil.loadPrivateKey(inputStream);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 获取证书管理器实例
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @return return
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    @SneakyThrows
 | 
	
		
			
				|  |  | +    public Verifier getVerifier() {
 | 
	
		
			
				|  |  | +        log.info("获取证书管理器实例");
 | 
	
		
			
				|  |  | +        //获取商户私钥
 | 
	
		
			
				|  |  | +        PrivateKey privateKey = getPrivateKey(WxConstants.KEY_PEM_PATH);
 | 
	
		
			
				|  |  | +        //私钥签名对象
 | 
	
		
			
				|  |  | +        PrivateKeySigner privateKeySigner = new PrivateKeySigner(WxConstants.SERIAL_NO, privateKey);
 | 
	
		
			
				|  |  | +        //身份认证对象
 | 
	
		
			
				|  |  | +        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(WxConstants.SP_MCH_ID, privateKeySigner);
 | 
	
		
			
				|  |  | +        // 使用定时更新的签名验证器,不需要传入证书
 | 
	
		
			
				|  |  | +        CertificatesManager certificatesManager = CertificatesManager.getInstance();
 | 
	
		
			
				|  |  | +        certificatesManager.putMerchant(WxConstants.SP_MCH_ID,
 | 
	
		
			
				|  |  | +                wechatPay2Credentials, WxConstants.API_V3_KEY.getBytes(StandardCharsets.UTF_8));
 | 
	
		
			
				|  |  | +        return certificatesManager.getVerifier(WxConstants.SP_MCH_ID);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 获取支付http请求对象
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @return return
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public CloseableHttpClient httpClient() {
 | 
	
		
			
				|  |  | +        String keyPemPath = WxConstants.KEY_PEM_PATH;
 | 
	
		
			
				|  |  | +        PrivateKey privateKey = getPrivateKey(keyPemPath);
 | 
	
		
			
				|  |  | +        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
 | 
	
		
			
				|  |  | +                .withMerchant(WxConstants.SP_MCH_ID, WxConstants.SERIAL_NO, privateKey)
 | 
	
		
			
				|  |  | +                .withValidator(new WechatPay2Validator(getVerifier()));
 | 
	
		
			
				|  |  | +        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
 | 
	
		
			
				|  |  | +        return builder.build();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public CloseableHttpClient noSignHttpClient() {
 | 
	
		
			
				|  |  | +        //获取商户私钥
 | 
	
		
			
				|  |  | +        PrivateKey privateKey = getPrivateKey(WxConstants.KEY_PEM_PATH);
 | 
	
		
			
				|  |  | +        //用于构造HttpClient
 | 
	
		
			
				|  |  | +        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
 | 
	
		
			
				|  |  | +                //设置商户信息
 | 
	
		
			
				|  |  | +                .withMerchant(WxConstants.SP_MCH_ID, WxConstants.SERIAL_NO, privateKey)
 | 
	
		
			
				|  |  | +                //无需进行签名验证、通过withValidator((response) -> true)实现
 | 
	
		
			
				|  |  | +                .withValidator((response) -> true);
 | 
	
		
			
				|  |  | +        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
 | 
	
		
			
				|  |  | +        return builder.build();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 发送get请求
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param url 请求地址
 | 
	
		
			
				|  |  | +     * @return 结果
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public String wechatHttpGet(String url) {
 | 
	
		
			
				|  |  | +        CloseableHttpClient wxPayClient = httpClient();
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            HttpGet httpGet = new HttpGet(url);
 | 
	
		
			
				|  |  | +            httpGet.setHeader("Accept", "application/json");
 | 
	
		
			
				|  |  | +            CloseableHttpResponse response = wxPayClient.execute(httpGet);
 | 
	
		
			
				|  |  | +            return getResponseBody(response);
 | 
	
		
			
				|  |  | +        } catch (Exception e) {
 | 
	
		
			
				|  |  | +            throw new RuntimeException(e.getMessage());
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 发送post请求
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param url       请求地址
 | 
	
		
			
				|  |  | +     * @param paramsStr 参数
 | 
	
		
			
				|  |  | +     * @return 结果
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public String wechatHttpPost(String url, String paramsStr) {
 | 
	
		
			
				|  |  | +        CloseableHttpClient wxPayClient = httpClient();
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            HttpPost httpPost = new HttpPost(url);
 | 
	
		
			
				|  |  | +            StringEntity entity = new StringEntity(paramsStr, "utf-8");
 | 
	
		
			
				|  |  | +            entity.setContentType("application/json");
 | 
	
		
			
				|  |  | +            httpPost.setEntity(entity);
 | 
	
		
			
				|  |  | +            httpPost.setHeader("Accept", "application/json");
 | 
	
		
			
				|  |  | +            CloseableHttpResponse response = wxPayClient.execute(httpPost);
 | 
	
		
			
				|  |  | +            return getResponseBody(response);
 | 
	
		
			
				|  |  | +        } catch (Exception e) {
 | 
	
		
			
				|  |  | +            throw new RuntimeException(e.getMessage());
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 读取响应的请求体
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param response 响应
 | 
	
		
			
				|  |  | +     * @return 响应
 | 
	
		
			
				|  |  | +     * @throws IOException IOException
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private String getResponseBody(CloseableHttpResponse response) throws IOException {
 | 
	
		
			
				|  |  | +        HttpEntity entity = response.getEntity();
 | 
	
		
			
				|  |  | +        String body = entity == null ? "" : EntityUtils.toString(entity);
 | 
	
		
			
				|  |  | +        int statusCode = response.getStatusLine().getStatusCode();
 | 
	
		
			
				|  |  | +        if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
 | 
	
		
			
				|  |  | +            log.info("成功, 返回结果 = " + body);
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
 | 
	
		
			
				|  |  | +            log.error(msg);
 | 
	
		
			
				|  |  | +            throw new RuntimeException(msg);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return body;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public JSONObject jsapiNotify(HttpServletRequest request, HttpServletResponse response) {
 | 
	
		
			
				|  |  | +        JSONObject bodyJson = getNotifyBodyJson(request);
 | 
	
		
			
				|  |  | +        if (bodyJson == null) {
 | 
	
		
			
				|  |  | +            return falseMsg(response);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        if (lock.tryLock()) {
 | 
	
		
			
				|  |  | +            try {
 | 
	
		
			
				|  |  | +                // 解密resource中的通知数据
 | 
	
		
			
				|  |  | +                String resource = bodyJson.getString("resource");
 | 
	
		
			
				|  |  | +                JSONObject resourceJson = WechatPayValidator.decryptFromResource(resource, WxConstants.API_V3_KEY, 2);
 | 
	
		
			
				|  |  | +                log.info("  =================== 服务商小程序支付回调解密resource中的通知数据 ===================\n" + resourceJson);
 | 
	
		
			
				|  |  | +                Integer trans = statusTrans(resourceJson.getString("trade_state"));
 | 
	
		
			
				|  |  | +                JSONObject attach = resourceJson.getJSONObject("attach");
 | 
	
		
			
				|  |  | +                if (trans == 1 && attach != null) {
 | 
	
		
			
				|  |  | +                    JSONObject successJson = trueMsg(response);
 | 
	
		
			
				|  |  | +                    successJson.put("attach", attach);
 | 
	
		
			
				|  |  | +                    return successJson;
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    System.err.println("支付失败...");
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            } finally {
 | 
	
		
			
				|  |  | +                lock.unlock();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return trueMsg(response);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private JSONObject getNotifyBodyJson(HttpServletRequest request) {
 | 
	
		
			
				|  |  | +        String body = HttpUtils.readData(request);
 | 
	
		
			
				|  |  | +        log.info("===========微信回调参数===========\n" + body);
 | 
	
		
			
				|  |  | +        log.info("微信回调参数:{}", body);
 | 
	
		
			
				|  |  | +        JSONObject jsonObject = JSONObject.parseObject(body);
 | 
	
		
			
				|  |  | +        WechatPayValidator wechatPayValidator
 | 
	
		
			
				|  |  | +                = new WechatPayValidator(getVerifier(), jsonObject.getString("id"), body);
 | 
	
		
			
				|  |  | +        if (!wechatPayValidator.validate(request)) {
 | 
	
		
			
				|  |  | +            log.error("通知验签失败");
 | 
	
		
			
				|  |  | +            return null;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        log.info("通知验签成功");
 | 
	
		
			
				|  |  | +        return jsonObject;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private JSONObject falseMsg(HttpServletResponse response) {
 | 
	
		
			
				|  |  | +        JSONObject resMap = new JSONObject();
 | 
	
		
			
				|  |  | +        response.setStatus(500);
 | 
	
		
			
				|  |  | +        resMap.put("code", "ERROR");
 | 
	
		
			
				|  |  | +        resMap.put("message", "通知验签失败");
 | 
	
		
			
				|  |  | +        return resMap;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 支付状态( 1-支付成功 )
 | 
	
		
			
				|  |  | +     *
 | 
	
		
			
				|  |  | +     * @param tradeState 微信返回支付状态码
 | 
	
		
			
				|  |  | +     * @return 状态
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private Integer statusTrans(String tradeState) {
 | 
	
		
			
				|  |  | +        int payStatus;
 | 
	
		
			
				|  |  | +        if ("SUCCESS".equals(tradeState)) {
 | 
	
		
			
				|  |  | +            payStatus = 1;
 | 
	
		
			
				|  |  | +        } else if ("NOTPAY".equals(tradeState)) {
 | 
	
		
			
				|  |  | +            payStatus = 0;
 | 
	
		
			
				|  |  | +        } else if ("REVOKED".equals(tradeState)) {
 | 
	
		
			
				|  |  | +            payStatus = 4;
 | 
	
		
			
				|  |  | +        } else if ("CLOSED".equals(tradeState)) {
 | 
	
		
			
				|  |  | +            payStatus = 6;
 | 
	
		
			
				|  |  | +        } else if ("PAYERROR".equals(tradeState)) {
 | 
	
		
			
				|  |  | +            payStatus = 5;
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            payStatus = 8;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return payStatus;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private JSONObject trueMsg(HttpServletResponse response) {
 | 
	
		
			
				|  |  | +        JSONObject resMap = new JSONObject();
 | 
	
		
			
				|  |  | +        //成功应答
 | 
	
		
			
				|  |  | +        response.setStatus(200);
 | 
	
		
			
				|  |  | +        resMap.put("code", "SUCCESS");
 | 
	
		
			
				|  |  | +        resMap.put("message", "成功");
 | 
	
		
			
				|  |  | +        return resMap;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 |