Pagamento do provedor de serviços Ijapy V2, compartilhamento de conta, reconciliação, reembolso

Como a empresa não pôde solicitar a permissão de serviço de pagamento do provedor de serviços, finalmente encontrou uma solução de provedor de serviços de shopping.

Você só pode encomendar produtos de um comerciante ao fazer um pedido no shopping. Isso ocorre porque o pedido de produtos de um comerciante exige pagamento combinado e o negócio não foi ativado. Portanto, a única desvantagem é que você só pode comprar produtos de uma loja de cada vez. Compras entre lojas não são suportadas.

Ideia de ferramenta de desenvolvimento maven jdk1.8
 

1. Apresente o 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. Arquivo de configuração do WeChat 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. Criar entidade de parâmetro WeChat

@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 pagamento 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 retorno de chamada de pagamento

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

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

Aviso de reembolso


    @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. Adicione uma subconta

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. Solicitação de parcelamento de contas. Existem dois tipos de solicitação: parcelamento único e solicitação de parcelamento múltiplo.
  Diferença: Após a solicitação de parcelamento único, nenhum outro parcelamento será permitido. O dinheiro restante a ser parcelado será descongelado diretamente para o lojista.
              Solicitação de parcelamento múltiplo as divisões podem ser divididas várias vezes. A divisão máxima é O valor não pode exceder a proporção máxima de divisão. Após a conclusão da divisão, você deve solicitar o descongelamento para a parte da conta dividida, e o valor restante da divisão será descongelado para o comerciante.
 

Solicitar os resultados da divisão para uma única divisão requer chamar a interface de consulta de divisão.

1. Solicite uma única divisão de contas

    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. Para consultar os resultados do razão, é melhor escrever um cronômetro para consulta.

 

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

Não vou escrever para divisões múltiplas. É basicamente o mesmo que divisão única. Basta montar os parâmetros.

9 Reconciliação

1. Escreva um cronômetro para reconciliar as contas separadas e verificar as contas separadas do dia anterior depois das dez horas todos os dias.
 

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. Baixe o extrato da transação e defina um cronômetro para verificar o extrato dividido do dia anterior após as dez horas todos os dias.

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;

}






Obtenha o link httpClient v3

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

Criar link httpClient, disponível no site oficial
 

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

}

 Resumo: Pagamento->Retorno de chamada de pagamento->Reembolso->Retorno de chamada de reembolso->Iniciar conta dividida->Adicionar conta dividida->Conta dividida->Baixar fatura de conta dividida e fatura de transação->Lógica de reconciliação. Além disso, há um comerciante especial interface de importação
. Se você tiver alguma dúvida ou erro no próximo artigo, sinta-se à vontade para me enviar uma mensagem em particular.

Acho que você gosta

Origin blog.csdn.net/weixin_41018853/article/details/131846685
Recomendado
Clasificación