Ijapy V2 服务商支付,分账,对账,退款

由于公司不能申请下来服务商支付通业务权限,最后找到一个商城服务商解决方案

商城下单只能下单一个商户的商品,因为商户的商品下单,需要合单支付,业务没有开通,所以唯一的弊端就是一次只能买一个店铺的商品,跨店买不支持

开发工具 idea  maven  jdk1.8
 

1.引入pom
 

<dependency>
			<groupId>com.github.javen205</groupId>
			<artifactId>IJPay-All</artifactId>
			<version>2.7.4</version>
</dependency>

<dependency>
				<groupId>cn.hutool</groupId>
				<artifactId>hutool-all</artifactId>
				<version>4.6.2</version>
</dependency>

2.微信配置文件  wxpay.properties
 

#小程序appid
wxpay.appId=xxxx
#小程序密钥
wxpay.appSecret=xxxx
#服务商商户号
wxpay.mchId=xxxx
#服务商商户密钥 也就是APIV2密钥
wxpay.partnerKey=xxxx
#apiv3密钥 v3需要
wxpay.apiV3=xxxx
#服务商商户证书证书序列号 v3 需要
wxpay.serialNo=xxxx
#证书路径,在微信商户后台下载  v2 需要
wxpay.certPath=c:/apiclient_cert.p12
#域名地址 线上得改
wxpay.domain=https://xxx.natapp4.cc/
#文件存房目录
wxpay.localdomain=D:/minio/data/
#账单存放目录
wxpay.localPath=D:/minio/data/filepath/temp/

3.创建微信参数实体

@Component
@PropertySource("classpath:/wxpay.properties")
@ConfigurationProperties(prefix = "wxpay")
@Data  //lombok
public class WxPayBean {
    private String appId;
    private String appSecret;
    private String mchId;
    private String partnerKey;
    private String certPath;
    private String domain;
    private String localdomain;
    private String serialNo;
    private String localPath;
    private String apiV3;
}

4 ijapy 支付


    @PostMapping("/pay.do")
    public Result<?> saveOneOrder(HttpServletRequest request){
        String ip = IpKit.getRealIp(request);
        if (StrUtil.isBlank(ip)) {
            ip = "127.0.0.1";
        }

        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(wxPayBean.getAppId())
                .mch_id(wxPayBean.getMchId())
                .sub_mch_id(shop.getSubMchid())
                .nonce_str(WxPayKit.generateStr())
                .body("商品支付")
                .attach("小程序商品支付")
                .out_trade_no(order.getOrderNo())
                .total_fee(multiplys.intValue()+"")
                .spbill_create_ip(ip)
                .notify_url(wxPayBean.getDomain().concat("yw/api/order/payNotifyOne"))
                .trade_type(TradeType.JSAPI.getTradeType())
                .openid(user.getOpenid())
                .profit_sharing("Y")//是否分账标志
                .build()
                .createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
        String xmlResult = WxPayApi.pushOrder(false, params);
        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
        String returnCode = result.get("return_code");
        String returnMsg = result.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode)) {
            return Result.error(returnMsg);
        }
        String resultCode = result.get("result_code");
        if (!WxPayKit.codeIsOk(resultCode)) {
            return Result.error(returnMsg);
        }
        // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回
        String prepayId = result.get("prepay_id");
        Map<String, String> packageParams = WxPayKit.miniAppPrepayIdCreateSign(wxPayBean.getAppId(), prepayId,
                wxPayBean.getPartnerKey(), SignType.HMACSHA256);
        String jsonStr = JSON.toJSONString(packageParams);
        return Result.OK(packageParams);
    }

5 支付回调

    @AutoLog(value = "直接支付回调")
    @PostMapping(value = "/payNotifyOne")
    public String payNotifyOne(HttpServletRequest request) {
        String xmlMsg = HttpKit.readData(request);
        System.out.println("支付通知");
        System.out.println(xmlMsg);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
        String returnCode = params.get("return_code");
        // 微信支付订单号
        String transaction_id = params.get("transaction_id");
        // 商户订单号
        String out_trade_no = params.get("out_trade_no");
        // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, out_trade_no)
        );
        if (null==order) {
            System.err.println("订单不存在:" + out_trade_no);
            return "failure";
        }
        String orderStatus = order.getOrderStatus();
        if ("2".equals(orderStatus)) {
            //发送通知等
            Map<String, String> xml = new HashMap<>(16);
            xml.put("return_code", "SUCCESS");
            xml.put("return_msg", "OK");
            return WxPayKit.toXml(xml);
        }
        // 注意此处签名方式需与统一下单的签名类型一致
        if (WxPayKit.verifyNotify(params, wxPayBean.getPartnerKey(), SignType.HMACSHA256)) {
            if (WxPayKit.codeIsOk(returnCode)) {
                // 更新订单信息
                order.setOrderStatus("2");
                order.setPayTime(new Date());
                order.setPayNo(transaction_id);
                orderService.afterPay(order);
                // 发送通知等
                Map<String, String> xml = new HashMap<String, String>(2);
                xml.put("return_code", "SUCCESS");
                xml.put("return_msg", "OK");
                return WxPayKit.toXml(xml);
            }else{
               // orderService.savePayFailTradeLog(order);
            }
        }
        return null;
    }

6.退款

    public void noSendAllRefund(String orderId) {
        String s = WxPayKit.generateStr();
        Order order = orderMapper.selectById(orderId);
        order.setRefundNo(s);
        order.setRefundFlag("1");
        orderMapper.updateById(order);
        SxzBreakfastShop shop = shopMapper.selectById(order.getShopId());
        BigDecimal totalPrice = order.getAmount().multiply(new BigDecimal(100));
        String refundStr="";
        try {
            Map<String, String> params = RefundModel.builder()
                    .appid(wxPayBean.getAppId())
                    .mch_id(wxPayBean.getMchId())
                    .sub_mch_id(shop.getSubMchid())
                    .nonce_str(WxPayKit.generateStr())
                    .transaction_id(order.getPayNo())
                    //.out_trade_no(outTradeNo)
                    .out_refund_no(s)
                    .total_fee(totalPrice.intValue()+"")
                    .refund_fee(totalPrice.intValue()+"")
                    .notify_url(wxPayBean.getDomain().concat("yw/api/order/refundNotifyAll"))
                    .build()
                    .createSign(wxPayBean.getPartnerKey(), SignType.MD5);
             refundStr = WxPayApi.orderRefund(false, params, wxPayBean.getCertPath(), wxPayBean.getMchId());
            System.out.println("退款同步信息"+refundStr);
           //orderService.saveFlowLog(order,"2","");
        } catch (Exception e) {
           orderService.saveRefundFailTradeLog(order);
           throw new MyException(refundStr);
        }
    }

退款通知


    @PostMapping(value = "/refundNotifyAll")
    public String refundNotifyAll(HttpServletRequest request) {
        String xmlMsg = HttpKit.readData(request);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
        String returnCode = params.get("return_code");
        String out_refund_no = params.get("out_refund_no");
        Order one = orderService.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getRefundNo, out_refund_no)
        );
        if (null==one) {
            System.err.println("订单不存在:" + out_refund_no);
            return "failure";
        }
        String refundFlag = one.getRefundFlag();
        if ("2".equals(refundFlag)) {
            //发送通知等
            Map<String, String> xml = new HashMap<>(16);
            xml.put("return_code", "SUCCESS");
            xml.put("return_msg", "OK");
            return WxPayKit.toXml(xml);
        }
        // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
        if (WxPayKit.codeIsOk(returnCode)) {
            String reqInfo = params.get("req_info");
            String decryptData = WxPayKit.decryptData(reqInfo, getWxPayApiConfig().getPartnerKey());
            orderDetailService.noSendRefundCode(one.getId());
            // 发送通知等
            Map<String, String> xml = new HashMap<String, String>(2);
            xml.put("return_code", "SUCCESS");
            xml.put("return_msg", "OK");
            return WxPayKit.toXml(xml);
        }else{
         //orderService.saveRefundFailTradeLog(one);
         throw new MyException(xmlMsg);
        }
    }

7.添加分账方

public void profitSharingAddReceiver(String subMchId) {
        try {
            ReceiverModel receiver = ReceiverModel.builder()
                    .type("MERCHANT_ID")//商户号
                    .account(wxPayBean.getMchId())//分帐方商户号
                    .relation_type("SERVICE_PROVIDER")//服务商
                    .build();
            Map<String, String> params = ProfitSharingModel.builder()
                    .mch_id(wxPayBean.getMchId())
                    .appid(wxPayBean.getAppId())
                    .sub_mch_id(subMchId)
                    .nonce_str(WxPayKit.generateStr())
                    .receiver(JSON.toJSONString(receiver))
                    .build()
                    .createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
            String result = WxPayApi.profitSharingAddReceiver(params);
            Map<String, String> stringStringMap = WxPayKit.xmlToMap(result);
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

8 请求分账  分两种 请求单次分账 请求多次分账
  区别: 请求单次分账后,不可再分账,会把剩余的待分账的钱直接解冻给商家
              请求多次分账,可以多次分账,最大分账金额不可超过最大分账比例,分账完成后,要请求解冻给分账方接口,剩余的分账金额才会解冻给商家
 

请求单次分账 分账结果需要调用分账查询接口

1.请求单次分账

    public synchronized void profitSharing(Order order) {
        //BigDecimal amount = order.getAmount();
        SxzBreakfastShop shop = shopMapper.selectById(order.getShopId());
        //BigDecimal wxRate = shop.getWxRate();//得除以100
        //BigDecimal fyRate = shop.getFyRate();//得除以100
        //BigDecimal wxMoney = amount.multiply(wxRate);
        //BigDecimal fenzhangMoney = amount.multiply(new BigDecimal(1).subtract(wxRate));
        //BigDecimal fymoney = fenzhangMoney.multiply(fyRate);
        BigDecimal multiply = order.getCommission().multiply(new BigDecimal(100));
        String s = WxPayKit.generateStr();//分账单号
        //查询商户信息
        List<ReceiverModel> list = new ArrayList<>();
        list.add(ReceiverModel.builder()
                .type("MERCHANT_ID")
                .account(wxPayBean.getMchId())
                .amount(multiply.intValue())
                .description(shop.getName()+" 分账")
                .build());
        Map<String, String> params = ProfitSharingModel.builder()
                .mch_id(wxPayBean.getMchId())
                .sub_mch_id(shop.getSubMchid())
                .appid(wxPayBean.getAppId())
                .nonce_str(WxPayKit.generateStr())
                .transaction_id(order.getPayNo())
                .out_order_no(s)
                .receivers(JSON.toJSONString(list))
                .build()
                .createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
        String result = WxPayApi.multiProfitSharing(params, wxPayBean.getCertPath(), wxPayBean.getMchId());
        System.out.println(result);
        Map<String, String> map = WxPayKit.xmlToMap(result);
        if("SUCCESS".equals(map.get("return_code"))&&"SUCCESS".equals(map.get("result_code"))){
            String order_id = map.get("order_id");//微信分账单号
            String out_order_no = map.get("out_order_no");//商户分账单号
            String transaction_id = map.get("transaction_id");//微信订单号
            String status = map.get("status");//分账单状态
            order.setFyNo(s);
            //order.setCommission(multiply);
            //order.setWxCommission(wxMoney);
            if("PROCESSING".equals(status)){
                order.setFyFlag("2");
                this.baseMapper.updateById(order);
            }else if("FINISHED".equals(status)){
                order.setFyFlag("3");
                order.setFzTime(new Date());
                this.baseMapper.updateById(order);
                saveFzToShopLog(order);//解冻
                saveFzLog(order);//分账
            }
        }else{
            saveFzFailLog(order);
            throw new MyException(result);
        }
    }

2.查询分账结果 最好是写个定时器去查询

 

public synchronized void profitSharingQuery(Order order) {
        SxzBreakfastShop shop = shopMapper.selectById(order.getShopId());
        Map<String, String> params = ProfitSharingModel.builder()
                .mch_id(wxPayBean.getMchId())
                .sub_mch_id(shop.getSubMchid())
                .appid(wxPayBean.getAppId())
                .nonce_str(WxPayKit.generateStr())
                .transaction_id(order.getPayNo())
                .out_order_no(order.getFyNo())
                .build()
                .createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
        String result = WxPayApi.profitSharingQuery(params);
        System.out.println(result);
        Map<String, String> map = WxPayKit.xmlToMap(result);
        if("SUCCESS".equals(map.get("return_code"))&&"SUCCESS".equals(map.get("result_code"))){
            String status = map.get("status");//分账单状态
            if("FINISHED".equals(status)){
                if(!"3".equals(order.getFyFlag())){
                    order.setFyFlag("3");
                    order.setFzTime(new Date());
                    this.baseMapper.updateById(order);
                    saveFzToShopLog(order);//解冻
                    saveFzLog(order);//分账
                }
            }
        }else{
           throw new MyException(result);
        }
    }

多次分账就不写了,和单次分账基本一样,组装参数即可

9 对账

1.对账分账账单 写个定时器 每天十点以后查询前一天的分账账单
 

public void dowloadFzBill(String date)  {
        FzBillDataResp zdUrlFz = getZdUrlFz(date);
       //保存交易分账数据
        List<FzBillLineResp> fzBillLineRespList = zdUrlFz.getFzBillLineRespList();
        FzBillTotalResp fzBillTotalResp = zdUrlFz.getFzBillTotalResp();

}

public static FzBillDataResp getZdUrlFz(String date) {
        //分账账单
        URIBuilder  uriBuilder =  new URIBuilder("https://api.mch.weixin.qq.com/v3/profitsharing/bills?bill_date="+date);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = getHttpClient().execute(httpGet);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println(bodyAsString);
        JSONObject jsonObject1 = JSONObject.parseObject(bodyAsString);
        //{"code":"NO_STATEMENT_EXIST","message":"账单文件不存在"}
        String code = jsonObject1.getString("code");
        if(StringUtils.isNotBlank(code)){
            throw new MyException(code+"__"+jsonObject1.getString("message"));
           // insertLog(req, e, "1");
        }
        String download_url = jsonObject1.getString("download_url");
        FzBillDataResp zdUrlDowloadFz = getZdUrlDowloadFz(download_url);
        return zdUrlDowloadFz;
    }

 public static FzBillDataResp getZdUrlDowloadFz(String dowloadUrl) {
        HttpRequest get = HttpUtil.createGet(dowloadUrl);
        HttpUrl parse = HttpUrl.parse(dowloadUrl);
        get.header("Authorization", "WECHATPAY2-SHA256-RSA2048"+" "+SignUtils.getToken("GET",parse,""));
        HttpResponse execute = get.execute();
        InputStream inputStream = execute.bodyStream();
       // File file = FileUtil.writeFromStream(inputStream, FileUtil.newFile("D:/zdBill"+DateUtils.getDate("yyyy-MM-dd")+System.currentTimeMillis()+".xlsx"));
        //List<String> list = FileUtil.readLines(file, "utf-8");
        FzBillDataResp fzBillDataResp = parseBodyFz(execute.body());
        return fzBillDataResp;
    }

private static FzBillDataResp parseBodyFz(String body) {
        FzBillDataResp tradeBillDataResp = null;
        List<FzBillLineResp> tradeBillLineRespList = new ArrayList<>();
        try {
            body = body.replace("`", "");
            String[] billDataStrArray = body.split("\r\n");

            if (billDataStrArray.length > 0) {
                /* 解析行数据 */
                for (int i = 1; i < billDataStrArray.length - 2; i++) {
                    // 填充空格,防止数组越界
                    String billData = billDataStrArray[i] + " ";
                    String[] data = billData.split(",");
                    FzBillLineResp tradeBillLineResp = FzBillLineResp.class.getDeclaredConstructor().newInstance();
                    for (Field field : FzBillLineResp.class.getDeclaredFields()) {
                        SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
                        if (splitIndex != null) {
                            int index = splitIndex.value();
                            setFieldValue(tradeBillLineResp, field, data[index]);
                        }
                    }
                    tradeBillLineRespList.add(tradeBillLineResp);
                }

                /* 解析汇总数据*/
                // 填充空格,防止数组越界
                String totalData = billDataStrArray[billDataStrArray.length - 1] + " ";
                String[] totalDataArray = totalData.split(",");
                FzBillTotalResp tradeBillTotalResp = FzBillTotalResp.class.getDeclaredConstructor().newInstance();
                for (Field field : FzBillTotalResp.class.getDeclaredFields()) {
                    SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
                    if (splitIndex != null) {
                        int index = splitIndex.value();
                        setFieldValue(tradeBillTotalResp, field, totalDataArray[index]);
                    }
                }

                tradeBillDataResp = FzBillDataResp.builder()
                        .fzBillTotalResp(tradeBillTotalResp)
                        .fzBillLineRespList(tradeBillLineRespList)
                        .build();
            }
            return tradeBillDataResp;
        } catch (Exception e) {
            throw new RuntimeException("解析交易账单文件响应数据异常:" + e);
        }
    }

/**
     * 使用反射设置对象的属性值。
     *
     * @param obj   要设置属性值的对象
     * @param field 属性
     * @param value 属性值
     * @throws IllegalAccessException 如果没有足够的权限访问属性
     */
    public static void setFieldValue(Object obj, Field field, String value)
            throws IllegalAccessException, IntrospectionException, InvocationTargetException, ParseException {

        Class<?> aClass = obj.getClass();
        PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), aClass);
        Method method = propertyDescriptor.getWriteMethod();
        String type = field.getGenericType()
                .toString();
        if (!org.springframework.util.StringUtils.hasText(value)) {
            return;
        }
        value = value.trim();
        switch (type) {
            case "class java.util.Date":
                method.invoke(obj, DateUtils.parseDate(value,"yyyy-MM-dd HH:mm:ss"));
                break;
            case "class java.lang.String":
                method.invoke(obj, value);
                break;
            case "class java.lang.Integer":
                method.invoke(obj, Double.valueOf(value)
                        .intValue());
                break;
            case "class java.lang.Long":
                method.invoke(obj, Double.valueOf(value)
                        .longValue());
                break;
            case "class java.lang.Double":
                method.invoke(obj, Double.valueOf(value));
                break;
            case "class java.lang.Float":
                method.invoke(obj, Double.valueOf(value)
                        .floatValue());
                break;
            case "class java.lang.Character":
                method.invoke(obj, value.toCharArray()[0]);
                break;
            case "class java.math.BigDecimal":
                method.invoke(obj, new BigDecimal(value).setScale(4, RoundingMode.HALF_UP));
                break;
            default:
                method.invoke(obj, (Object) null);
                break;
        }
    }

//实体
@Data
@Builder
public class FzBillDataResp {
    /**
     * 账单数据(分条)
     */
    private List<FzBillLineResp> fzBillLineRespList;

    /**
     * 分账账单汇总数据
     */
    private FzBillTotalResp fzBillTotalResp;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FzBillLineResp {

    /**
     * 分账时间
     */
    @SplitIndex(0)
    private Date tradeDate;

    /**
     * 分账发起方
     */
    @SplitIndex(1)
    private String serviceMchId;

    /**
     * 分账方
     */
    @SplitIndex(2)
    private String specMchId;

    /**
     * 微信订单号
     */
    @SplitIndex(3)
    private String wechatOrderNo;

    /**
     * 微信分账/回退单号
     */
    @SplitIndex(4)
    private String wechatFzNo;


    /**
     * 分账明细单号
     */
    @SplitIndex(5)
    private String wechatFzDetailNo;


    /**
     * 商户分账/回退单号
     */
    @SplitIndex(6)
    private String wechatShopFzNo;

    /**
     * 订单金额
     */
    @SplitIndex(7)
    private BigDecimal orderMoney;


    /**
     * 分账接收方
     */
    @SplitIndex(8)
    private String acceptMchId;

    /**
     * 分账金额
     */
    @SplitIndex(9)
    private BigDecimal fzMoney;

    /**
     * 业务类型
     */
    @SplitIndex(10)
    private String type;

    /**
     * 处理状态
     */
    @SplitIndex(11)
    private String status;

    /**
     * 分账描述
     */
    @SplitIndex(12)
    private String fzRemark;

    /**
     * 备注
     */
    @SplitIndex(13)
    private String remark;

}


@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FzBillTotalResp {

    /**
     * 总条数
     */
    @SplitIndex(0)
    private Integer totalOrderCount;

    /**
     * 分账成功出资金
     */
    @SplitIndex(1)
    private BigDecimal totalNeedPayOrderAmount;

    /**
     * 分账失败已转回分账方
     */
    @SplitIndex(2)
    private BigDecimal totalFundAmount;

    /**
     * 解冻资金给分账方
     */
    @SplitIndex(3)
    private BigDecimal totalTopUpFundAmount;

    /**
     * 分账回退资金
     */
    @SplitIndex(4)
    private BigDecimal totalRequestFundAmount;

}

//注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SplitIndex {

    /**
     * 填充下标
     */
    int value();
}





1.下载交易账单 写个定时器 每天十点以后查询前一天的分账账单

public void dowloadTradeBill(String date)  {
        TradeBillDataResp tradeBillDataResp = getZdUrl(date);
        //保存交易天数据
        List<TradeBillLineResp> tradeBillLineRespList = 
        tradeBillDataResp.getTradeBillLineRespList();
        TradeBillTotalResp tradeBillTotalResp = tradeBillDataResp.getTradeBillTotalResp();

    }

public static TradeBillDataResp getZdUrl(String date) throws MyException,URISyntaxException, GeneralSecurityException, IOException, NotFoundException, HttpCodeException {
        //交易账单
        URIBuilder   uriBuilder =   new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date="+date+"&bill_type=ALL");
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = getHttpClient().execute(httpGet);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        JSONObject jsonObject1 = JSONObject.parseObject(bodyAsString);
        String code = jsonObject1.getString("code");
        if(StringUtils.isNotBlank(code)){
            throw new MyException(code+"__"+jsonObject1.getString("message"));
        }
        String download_url = jsonObject1.getString("download_url");
        TradeBillDataResp zdUrlDowload = getZdUrlDowload(download_url);
        return zdUrlDowload;
    }

/**
     * 交易账单
     * @param dowloadUrl
     * @return
     * @throws Exception
     */
    public static TradeBillDataResp getZdUrlDowload(String dowloadUrl) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        HttpRequest get = HttpUtil.createGet(dowloadUrl);
        HttpUrl parse = HttpUrl.parse(dowloadUrl);
        get.header("Authorization", "WECHATPAY2-SHA256-RSA2048"+" "+SignUtils.getToken("GET",parse,""));
        HttpResponse execute = get.execute();
        InputStream inputStream = execute.bodyStream();
        File file = FileUtil.writeFromStream(inputStream, FileUtil.newFile("D:/tradeBill"+DateUtils.getDate("yyyy-MM-dd")+System.currentTimeMillis()+".xlsx"));
        //List<String> list = FileUtil.readLines(file, "utf-8");
        TradeBillDataResp tradeBillDataResp = parseBody(execute.body());
        return tradeBillDataResp;
    }

    private static TradeBillDataResp parseBody(String body) {
        TradeBillDataResp tradeBillDataResp = null;
        List<TradeBillLineResp> tradeBillLineRespList = new ArrayList<>();
        try {
            body = body.replace("`", "");
            String[] billDataStrArray = body.split("\r\n");

            if (billDataStrArray.length > 0) {
                /* 解析行数据 */
                for (int i = 1; i < billDataStrArray.length - 2; i++) {
                    // 填充空格,防止数组越界
                    String billData = billDataStrArray[i] + " ";
                    String[] data = billData.split(",");
                    TradeBillLineResp tradeBillLineResp = TradeBillLineResp.class.getDeclaredConstructor().newInstance();
                    for (Field field : TradeBillLineResp.class.getDeclaredFields()) {
                        SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
                        if (splitIndex != null) {
                            int index = splitIndex.value();
                            setFieldValue(tradeBillLineResp, field, data[index]);
                        }
                    }
                    tradeBillLineRespList.add(tradeBillLineResp);
                }
                /* 解析汇总数据*/
                // 填充空格,防止数组越界
                String totalData = billDataStrArray[billDataStrArray.length - 1] + " ";
                String[] totalDataArray = totalData.split(",");
                TradeBillTotalResp tradeBillTotalResp = TradeBillTotalResp.class.getDeclaredConstructor().newInstance();
                for (Field field : TradeBillTotalResp.class.getDeclaredFields()) {
                    SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
                    if (splitIndex != null) {
                        int index = splitIndex.value();
                        setFieldValue(tradeBillTotalResp, field, totalDataArray[index]);
                    }
                }

                tradeBillDataResp = TradeBillDataResp.builder()
                        .tradeBillTotalResp(tradeBillTotalResp)
                        .tradeBillLineRespList(tradeBillLineRespList)
                        .build();
            }
            return tradeBillDataResp;
        } catch (Exception e) {
            throw new RuntimeException("解析交易账单文件响应数据异常:" + e);
        }
    }

/**
     * 使用反射设置对象的属性值。
     *
     * @param obj   要设置属性值的对象
     * @param field 属性
     * @param value 属性值
     * @throws IllegalAccessException 如果没有足够的权限访问属性
     */
    public static void setFieldValue(Object obj, Field field, String value)
            throws IllegalAccessException, IntrospectionException, InvocationTargetException, ParseException {

        Class<?> aClass = obj.getClass();
        PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), aClass);
        Method method = propertyDescriptor.getWriteMethod();
        String type = field.getGenericType()
                .toString();
        if (!org.springframework.util.StringUtils.hasText(value)) {
            return;
        }
        value = value.trim();
        switch (type) {
            case "class java.util.Date":
                method.invoke(obj, DateUtils.parseDate(value,"yyyy-MM-dd HH:mm:ss"));
                break;
            case "class java.lang.String":
                method.invoke(obj, value);
                break;
            case "class java.lang.Integer":
                method.invoke(obj, Double.valueOf(value)
                        .intValue());
                break;
            case "class java.lang.Long":
                method.invoke(obj, Double.valueOf(value)
                        .longValue());
                break;
            case "class java.lang.Double":
                method.invoke(obj, Double.valueOf(value));
                break;
            case "class java.lang.Float":
                method.invoke(obj, Double.valueOf(value)
                        .floatValue());
                break;
            case "class java.lang.Character":
                method.invoke(obj, value.toCharArray()[0]);
                break;
            case "class java.math.BigDecimal":
                method.invoke(obj, new BigDecimal(value).setScale(4, RoundingMode.HALF_UP));
                break;
            default:
                method.invoke(obj, (Object) null);
                break;
        }
    }

//实体
@Data
@Builder
public class TradeBillDataResp {

    /**
     * 账单数据(分条)
     */
    private List<TradeBillLineResp> tradeBillLineRespList;

    /**
     * 账单汇总数据
     */
    private TradeBillTotalResp tradeBillTotalResp;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TradeBillLineResp {

    /**
     * 交易时间
     */
    @SplitIndex(0)
    private Date tradeDate;

    /**
     * 公众账号ID
     */
    @SplitIndex(1)
    private String appId;

    /**
     * 商户号
     */
    @SplitIndex(2)
    private String mchId;

    /**
     * 特约商户号
     */
    @SplitIndex(3)
    private String specMchId;

    /**
     * 设备号
     */
    @SplitIndex(4)
    private String deviceId;

    /**
     * 微信订单号
     */
    @SplitIndex(5)
    private String wechatOrderNo;

    /**
     * 商户订单号
     */
    @SplitIndex(6)
    private String mchOrderNo;

    /**
     * 用户标识
     */
    @SplitIndex(7)
    private String userSign;

    /**
     * 交易类型
     */
    @SplitIndex(8)
    private String tradeType;

    /**
     * 交易状态
     */
    @SplitIndex(9)
    private String tradeStatus;

    /**
     * 付款银行
     */
    @SplitIndex(10)
    private String payBank;

    /**
     * 货币种类
     */
    @SplitIndex(11)
    private String payCurrency;

    /**
     * 应结订单金额
     */
    @SplitIndex(12)
    private BigDecimal needPayAmount;

    /**
     * 代金券金额
     */
    @SplitIndex(13)
    private BigDecimal voucherAmount;

    /**
     * 微信退款单号
     */
    @SplitIndex(14)
    private String refundWechatNo;

    /**
     * 商户退款单号
     */
    @SplitIndex(15)
    private String refundMchNo;

    /**
     * 退款金额
     */
    @SplitIndex(16)
    private BigDecimal refundAmount;

    /**
     * 充值券退款金额
     */
    @SplitIndex(17)
    private BigDecimal topUpRefundAmount;

    /**
     * 退款类型
     */
    @SplitIndex(18)
    private String refundType;

    /**
     * 退款状态
     */
    @SplitIndex(19)
    private String refundStatus;

    /**
     * 商品名称
     */
    @SplitIndex(20)
    private String productName;

    /**
     * 商户数据包
     */
    @SplitIndex(21)
    private String mchData;

    /**
     * 手续费
     */
    @SplitIndex(22)
    private BigDecimal handlingFee;

    /**
     * 费率
     */
    @SplitIndex(23)
    private String feeRate;

    /**
     * 订单金额
     */
    @SplitIndex(24)
    private BigDecimal orderAmount;

    /**
     * 申请退款金额
     */
    @SplitIndex(25)
    private BigDecimal requestRefundAmount;

    /**
     * 费率备注
     */
    @SplitIndex(26)
    private String feeRateRemark;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TradeBillTotalResp {

    /**
     * 总交易单数
     */
    @SplitIndex(0)
    private Integer totalOrderCount;

    /**
     * 应结订单总金额
     */
    @SplitIndex(1)
    private BigDecimal totalNeedPayOrderAmount;

    /**
     * 退款总金额
     */
    @SplitIndex(2)
    private BigDecimal totalFundAmount;

    /**
     * 充值券退款总金额
     */
    @SplitIndex(3)
    private BigDecimal totalTopUpFundAmount;

    /**
     * 手续费总金额
     */
    @SplitIndex(4)
    private BigDecimal totalHandlingFee;

    /**
     * 订单总金额
     */
    @SplitIndex(5)
    private BigDecimal totalOrderAmount;

    /**
     * 申请退款总金额
     */
    @SplitIndex(6)
    private BigDecimal totalRequestFundAmount;

}






获取 v3 httpClient 链接

引入pom

<dependency>
			<groupId>com.github.wechatpay-apiv3</groupId>
			<artifactId>wechatpay-java</artifactId>
</dependency>
<dependency>
			<groupId>com.github.wechatpay-apiv3</groupId>
			<artifactId>wechatpay-apache-httpclient</artifactId>
			<version>0.4.5</version>
</dependency>

创建httpClient链接,官网上有
 

@Slf4j
public class WechatPay {
    private static final String appId = "xxxxxx";  //  服务商商户的 APPID 小程序等appid
    private static final String merchantId = "xxxxx";//  服务商商户号
    private static final String merchantSerialNumber = "xxxxx";//商户API证书的证书序列号
    private static final String apiV3Key = "xxxxx";//apiv3密钥
    /**
     * 商户API私钥,如何加载商户API私钥请看 常见问题。 @link: https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient#%E5%A6%82%E4%BD%95%E5%8A%A0%E8%BD%BD%E5%95%86%E6%88%B7%E7%A7%81%E9%92%A5
     */
    private static PrivateKey merchantPrivateKey = null;
    static {
        try {
            merchantPrivateKey = PemUtil.loadPrivateKey(
                    new ClassPathResource("apiclient_key.pem").getInputStream());
            String format = merchantPrivateKey.getFormat();
            System.out.println(format);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 微信支付平台证书列表  证书第一次需要手动下载 可以运行下main方法
     */
    private static List<X509Certificate> wechatPayCertificates = new ArrayList<>();
    static {
        try {
            wechatPayCertificates.add(PemUtil.loadCertificate(new ClassPathResource("cert.pem").getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 public static void main(String[] args) throws Exception {
            getptcert();
    }

/**
     * 获取平台证书  只要首次执行
     * @throws URISyntaxException
     * @throws IOException
     */
    public static void getptcert() throws URISyntaxException, IOException, NotFoundException, GeneralSecurityException, HttpCodeException {
        URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/certificates");
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = getHttpClient().execute(httpGet);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println(bodyAsString);
        JSONObject resourceJsons = JSONObject.parseObject(bodyAsString);
        JSONArray jsonArray = JSONArray.parseArray(resourceJsons.getString("data"));
        JSONObject resourceJson2 = jsonArray.getJSONObject(0);
        JSONObject resourceJson = resourceJson2.getJSONObject("encrypt_certificate");
        String associated_data = resourceJson.getString("associated_data");
        String nonce = resourceJson.getString("nonce");
        String ciphertext = resourceJson.getString("ciphertext");
        //解密,如果这里报错,就一定是APIv3密钥错误
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes());
        String resourceData = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
        System.out.println("解密后=" + resourceData);
//解密后的字符串替换掉  cert.pem里的内容即可  cert.pem 是自己创建的文件

    }
/**
     * 获取微信HttpClient
     * @return
     */
    public static CloseableHttpClient getHttpClient() throws HttpCodeException, GeneralSecurityException, IOException, NotFoundException {
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
                new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
        // ... 若有多个商户号,可继续调用putMerchant添加商户信息
        // 从证书管理器中获取verifier
        Verifier verifier = certificatesManager.getVerifier(merchantId);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .withWechatPay(wechatPayCertificates);
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        //WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
           //     .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
           //     .withWechatPay(wechatPayCertificates);
        return builder.build();
    }

}

 总结:支付->支付回调->退款->退款回调->发起分账->添加分账方->分账->下载分账账单和交易账单->对账逻辑
另外还有特约商户进件接口,下一篇介绍  有什么疑问和错误欢迎私信我

猜你喜欢

转载自blog.csdn.net/weixin_41018853/article/details/131846685
v2