Ijapy V2 service provider payment, account sharing, reconciliation, refund

Since the company could not apply for the service provider's payment service permission, it finally found a mall service provider solution.

You can only order products from one merchant when placing an order in the mall. This is because ordering products from a merchant requires combined payment and the business has not been activated. Therefore, the only drawback is that you can only buy products from one store at a time. Cross-store purchases are not supported.

Development tool idea maven jdk1.8
 

1.Introduce 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. WeChat configuration file 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. Create WeChat parameter entity

@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 payment


    @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 payment callback

    @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.Refund

    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);
        }
    }

Refund Notice


    @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. Add a sub-account party

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. Request for splitting accounts. There are two types of requests: single splitting and requesting multiple splits.
  Difference: After requesting single splitting, no further splitting is allowed. The remaining money to be split will be directly unfrozen to the merchant.
              Requesting multiple splits can be split multiple times. The maximum split is The amount cannot exceed the maximum split ratio. After the split is completed, you must request to be unfrozen to the split account party, and the remaining split amount will be unfrozen to the merchant.
 

Requesting the splitting results for a single split requires calling the splitting query interface.

1. Request a single split of accounts

    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. To query the ledger results, it is best to write a timer to query.

 

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);
        }
    }

I won’t write it down for multiple splits. It’s basically the same as single split. Just assemble the parameters.

9 Reconciliation

1. Write a timer to reconcile the separate accounts and check the previous day's separate accounts after ten o'clock every day.
 

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. Download the transaction statement and set a timer to check the previous day's split statement after ten o'clock every day.

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;

}






Get v3 httpClient link

Introduce 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>

Create httpClient link, available on the official website
 

@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();
    }

}

 Summary: Payment->Payment callback->Refund->Refund callback->Initiate split account->Add split account->Split account->Download split account bill and transaction bill->Reconciliation logic. In addition, there is a special merchant import interface
. If you have any questions or errors in the next article, please feel free to message me privately.

Guess you like

Origin blog.csdn.net/weixin_41018853/article/details/131846685