Helius
2024-01-23 cf59c548d06bcb8dd824a97403658ca90164abfd
Merge branch 'blnka' into blnka-vip
22 files added
14 files modified
2374 ■■■■■ changed files
pom.xml 10 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/common/utils/AppContants.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java 43 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java 44 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java 36 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java 65 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java 4 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/entity/MallOrderItem.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java 4 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java 31 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java 160 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/vo/AdminInvoiceDetailVo.java 47 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java 26 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java 22 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java 615 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/util/JCEUtil.java 70 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/util/RequestBodyUtils.java 76 ●●●●● patch | view | raw | blame | history
src/main/resources/mapper/modules/MallInvoiceMapper.xml 67 ●●●●● patch | view | raw | blame | history
src/main/resources/mapper/modules/MallOrderInfoMapper.xml 8 ●●●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/order/invoiceDetail.html 248 ●●●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/order/invoiceList.html 233 ●●●●● patch | view | raw | blame | history
src/main/resources/wxP12/apiclient_key.pem 28 ●●●●● patch | view | raw | blame | history
src/test/java/cc/mrbird/febs/ProfitTest.java 276 ●●●● patch | view | raw | blame | history
pom.xml
@@ -30,6 +30,11 @@
    <dependencies>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.7</version>
        </dependency>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.12</version>
        </dependency>
@@ -357,6 +362,11 @@
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>3.8</version>
        </dependency>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.7</version>
        </dependency>
    </dependencies>
    <build>
src/main/java/cc/mrbird/febs/common/utils/AppContants.java
@@ -23,6 +23,8 @@
    public static final String PC_LOGIN_PREFIX = "pc_";
    public static final String FP_TOKEN_HEADER_TYPE = "WECHATPAY2-SHA256-RSA2048 ";
    /**
     * token头部
     */
@@ -72,5 +74,6 @@
    public static final String AGENT_LEVEL_REQUIRE = "AGENT_LEVEL_REQUIRE";
    public static final String SIGN_MD5 = "MD5";
    public static final String WX_CARD_NUM = "221D49AEC4EA538A63941D1936709C8559EB05C5";//证书编号
}
src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java
@@ -22,6 +22,7 @@
import cc.mrbird.febs.mall.service.IAdminMallGoodsService;
import cc.mrbird.febs.mall.service.IAdminMallOrderService;
import cc.mrbird.febs.mall.service.IApiMallTeamLeaderService;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.mall.vo.AdminAddAddressTreeVo;
import cc.mrbird.febs.mall.vo.AdminMallOrderRefundAddressVo;
import cc.mrbird.febs.pay.model.OrderStateDto;
@@ -661,4 +662,46 @@
        return new FebsResponse().success().data(data);
    }
    private final MallInvoiceService mallInvoiceService;
    /**
     * 发票列表
     * @param mallInvoiceDto
     * @param request
     * @return
     */
    @GetMapping("invoiceList")
    public FebsResponse invoiceList(AdminMallInvoiceDto mallInvoiceDto, QueryRequest request) {
        Map<String, Object> data = getDataTable(mallInvoiceService.getInvoiceList(mallInvoiceDto, request));
        return new FebsResponse().success().data(data);
    }
    /**
     * 发票列表-订单子表
     */
    @GetMapping(value = "/invoiceItemList")
    public FebsResponse invoiceItemList() {
        return new FebsResponse().success().data(mallInvoiceService.invoiceItemList());
    }
    /**
     * 发票列表-上传发票
     */
    @PostMapping("addInvoiceUrl")
    @ControllerEndpoint(operation = " 发票列表-上传发票", exceptionMessage = "操作失败")
    public FebsResponse addInvoiceUrl(@Valid MallInvoice mallInvoice) {
        return mallInvoiceService.addInvoiceUrl(mallInvoice);
    }
    /**
     * 发票列表-更新抬头
     * @param id
     * @return
     */
    @GetMapping("updateInvoiceDetail/{id}")
    @ControllerEndpoint(operation = "发票列表-更新抬头", exceptionMessage = "操作失败")
    public FebsResponse updateInvoiceDetail(@NotNull(message = "{required}") @PathVariable Long id) {
        return mallInvoiceService.updateInvoiceDetail(id);
    }
}
src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java
@@ -3,6 +3,7 @@
import cc.mrbird.febs.common.annotation.ControllerEndpoint;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.enumerates.DataDictionaryEnum;
import cc.mrbird.febs.common.utils.AppContants;
import cc.mrbird.febs.mall.dto.*;
import cc.mrbird.febs.mall.entity.DataDictionaryCustom;
import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper;
@@ -29,6 +30,8 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -136,39 +139,34 @@
                DataDictionaryEnum.FP_CALLBACK_URL.getCode(),
                faPiaoDto.getCallbackUrl()
        );
        KeyPair privateKey = wxFaPiaoService.getPrivateKey();
        HeaderDto headerDto = new HeaderDto();
        headerDto.setCallback_url(faPiaoDto.getCallbackUrl());
        headerDto.setCallback_url("https://api.blnka.cn/api/xcxPay/fapiaoCallBack");
        headerDto.setShow_fapiao_cell(false);
        String parseObj = JSONUtil.parseObj(headerDto).toString();
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/merchant/development-config";
        String postStr = wxFaPiaoService.createAuthorization(
                "POST",
                canonicalUrl,
                parseObj,
                privateKey
        );
        // 创建httppost
        String postStr = null;
        try {
            HttpClient httpClient = new HttpClient();
            PostMethod post = new PostMethod(baseUrl+canonicalUrl);
            post.setRequestHeader("Accept", "application/json");
            post.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36");
            post.setRequestHeader("Content-Type", "application/json");
            post.setRequestHeader("Connection", "keep-alive");
            post.setRequestHeader("Authorization",  "WECHATPAY2-SHA256-RSA2048 "+postStr);
            RequestEntity entity = new StringRequestEntity(parseObj, "text/html", "utf-8");
            post.setRequestEntity(entity);
            httpClient.executeMethod(post);
            String responseBodyAsString = post.getResponseBodyAsString();
            cn.hutool.json.JSONObject maps = JSONUtil.parseObj(responseBodyAsString);
            System.out.println(maps);
            PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3();
            postStr = wxFaPiaoService.createAuthorization(
                    "PATCH",
                    baseUrl+canonicalUrl,
                    parseObj,
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new FebsResponse().success().message("操作成功");
        String token = AppContants.FP_TOKEN_HEADER_TYPE+postStr;
        System.out.println("WECHATPAY2-SHA256-RSA2048 "+postStr);
        String s = wxFaPiaoService.sendPatch(baseUrl + canonicalUrl, parseObj, token);
        log.info("配置开发选项:"+s);
        return new FebsResponse().success().message(s);
    }
    @PostMapping(value = "/agentDetail")
src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java
New file
@@ -0,0 +1,33 @@
package cc.mrbird.febs.mall.controller;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.mall.vo.ApiMallInvoiceVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/api/invoice")
@Api(value = "ApiMallInvoiceController", tags = "发票管理")
public class ApiMallInvoiceController {
    private final MallInvoiceService mallInvoiceService;
    @ApiOperation(value = "获取发票列表", notes = "获取发票列表")
    @ApiResponses({
            @ApiResponse(code = 200, message = "success", response = ApiMallInvoiceVo.class)
    })
    @PostMapping(value = "/getInvoices")
    public FebsResponse getInvoices(@RequestBody ApiMallInvoiceDto apiMallInvoiceDto) {
        return mallInvoiceService.getInvoices(apiMallInvoiceDto);
    }
}
src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java
@@ -8,8 +8,10 @@
import cc.mrbird.febs.mall.entity.MallExpressInfo;
import cc.mrbird.febs.mall.entity.MallOrderRefund;
import cc.mrbird.febs.mall.mapper.MallExpressInfoMapper;
import cc.mrbird.febs.mall.mapper.MallInvoiceMapper;
import cc.mrbird.febs.mall.mapper.MallOrderRefundMapper;
import cc.mrbird.febs.mall.service.IAdminMallOrderService;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.mall.vo.*;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -29,6 +31,7 @@
public class ViewMallOrderController extends BaseController {
    private final IAdminMallOrderService adminMallOrderService;
    private final MallInvoiceMapper mallInvoiceMapper;
    private final MallOrderRefundMapper mallOrderRefundMapper;
@@ -255,4 +258,37 @@
        return FebsUtil.view("modules/order/goodsStatistics");
    }
    /**
     * 发票列表
     * @return
     */
    @GetMapping("invoiceList")
    @RequiresPermissions("invoiceList:view")
    public String invoiceList() {
        return FebsUtil.view("modules/order/invoiceList");
    }
    /**
     * 发票列表-详情
     * @param id
     * @param model
     * @return
     */
    @GetMapping("invoiceDetail/{id}")
    @RequiresPermissions("invoiceDetail:view")
    public String invoiceDetail(@PathVariable long id, Model model) {
        String existToken = redisUtils.getString("ADMIN_INVOICE_ID");
        if (StrUtil.isNotBlank(existToken)) {
            Object o = redisUtils.get(existToken);
            if (ObjectUtil.isNotEmpty(o)) {
                redisUtils.del(existToken);
            }
        }
        redisUtils.set("ADMIN_INVOICE_ID", id, -1);
        AdminInvoiceDetailVo data = mallInvoiceMapper.getAdminInvoiceDetailVoById(id);
        model.addAttribute("invoiceDetail", data);
        return FebsUtil.view("modules/order/invoiceDetail");
    }
}
src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java
New file
@@ -0,0 +1,16 @@
package cc.mrbird.febs.mall.dto;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "AdminMallInvoiceDto", description = "参数接收类")
public class AdminMallInvoiceDto {
    private Integer state;//开票状态:0:未开票 1:开票中 2:已开票
    private String orderNo;
    private String payOrderNo;
}
src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java
New file
@@ -0,0 +1,23 @@
package cc.mrbird.febs.mall.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "ApiMallInvoiceDto", description = "参数接收类")
public class ApiMallInvoiceDto {
    @ApiModelProperty(value = "一页数量", example = "10")
    private Integer pageSize;
    @ApiModelProperty(value = "第几页", example = "1")
    private Integer pageNum;
    @ApiModelProperty(value = "开票状态:0:未开票 1:开票中 2:已开票")
    private Integer state;
    @ApiModelProperty(hidden = true)
    private Long memberId;
}
src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java
New file
@@ -0,0 +1,65 @@
package cc.mrbird.febs.mall.entity;
import cc.mrbird.febs.common.entity.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("mall_invoice")
public class MallInvoice extends BaseEntity {
    private Long memberId;
    /**
     * fapiao_apply_id
     * 发票申请单号,唯一标识一次开票行为。微信支付场景下,为微信支付订单号;非微信支付场景下,为调用【获取抬头填写链接】接口时指定的发票申请单号
     * 对应订单信息的pay_order_no支付订单号
     */
    private String fapiaoApplyId;
    /**
     *【购买方类型】 购买方类型
     * 可选取值:
     * INDIVIDUAL: 个人
     * ORGANIZATION: 单位
     */
    private String type;
    /**
     *【名称】 购买方名称
     */
    private String name;
    /**
     *【纳税人识别号】 购买方纳税人识别号,购买方类型为ORGANIZATION时必须存在
     */
    private String taxpayerId;
    /**
     *【地址】 购买方地址
     */
    private String address;
    /**
     *【电话】 购买方电话
     */
    private String telephone;
    /**
     *【开户银行】 购买方开户银行
     */
    private String bankName;
    /**
     *【银行账号】 购买方银行账号
     */
    private String bankAccount;
    /**
     * 匹配的订单ID
     */
    private Long orderId;
    private String orderNo;//订单编号
    private Integer status;//订单状态
    private BigDecimal amount;//订单金额
    private String goodsImg;//商品图片
    private Integer state;//开票状态:0:未开票 1:开票中 2:已开票
    private String invoiceUrl;//发票的URL路径
}
src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java
@@ -132,6 +132,10 @@
     * 是否送货上门  1:是 2:否
     */
    private Integer isHome;
    /**
     * 是否开过发票 0:未开 1:已开
     */
    private Integer isInvoice;
    @TableField(exist = false)
    private String orderIds;
src/main/java/cc/mrbird/febs/mall/entity/MallOrderItem.java
@@ -52,4 +52,7 @@
    @TableField(exist = false)
    private long[] orderIdsStr;
    @TableField(exist = false)
    private String unit;
}
src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java
New file
@@ -0,0 +1,26 @@
package cc.mrbird.febs.mall.mapper;
import cc.mrbird.febs.mall.dto.AdminMallInvoiceDto;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.entity.MallInvoice;
import cc.mrbird.febs.mall.entity.MallOrderItem;
import cc.mrbird.febs.mall.vo.AdminInvoiceDetailVo;
import cc.mrbird.febs.mall.vo.ApiMallInvoiceVo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface MallInvoiceMapper extends BaseMapper<MallInvoice> {
    IPage<ApiMallInvoiceVo> selectApiMallInvoiceVoInPage(IPage<ApiMallInvoiceVo> page, @Param("record")ApiMallInvoiceDto apiMallInvoiceDto);
    List<MallInvoice> selectByFapiaoApplyIdAndMemberId(@Param("fapiaoApplyId")String fapiaoApplyId, @Param("memberId")Long memberId);
    IPage<MallInvoice> getInvoiceListInPage(Page<MallInvoice> page, @Param("record")AdminMallInvoiceDto mallInvoiceDto);
    AdminInvoiceDetailVo getAdminInvoiceDetailVoById(@Param("id")long id);
    List<MallOrderItem> getInvoiceItemList(@Param("record")MallInvoice mallInvoice);
}
src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java
@@ -47,6 +47,8 @@
    List<MallOrderInfo> selectOrderInfoByStatus(@Param("status") Integer status);
    List<MallOrderInfo> selectOrderInfoByStatusAndIsInvoice(@Param("status") Integer status,@Param("isInvoice") Integer isInvoice);
    List<MallOrderInfo> selectOrderInfoUpTime(@Param("status") Integer status);
    Integer selectCntDirectOrTeam(@Param("type") Integer type, @Param("inviteId") String inviteId);
@@ -83,4 +85,6 @@
    IPage<AdminGoodsStatisticsVo> getGoodsStatisticsInPage(Page<AdminGoodsStatisticsVo> page, @Param("record") MallOrderItem mallOrderItem);
    BigDecimal selectAmountOrTeamAmount(@Param("inviteId") String inviteId, @Param("type") Integer type);
    MallOrderInfo selectBypayOrderNo(@Param("payOrderNo")String payOrderNo);
}
src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java
@@ -6,6 +6,7 @@
import cc.mrbird.febs.common.utils.SpringContextHolder;
import cc.mrbird.febs.mall.entity.MallMemberCoupon;
import cc.mrbird.febs.mall.mapper.MallMemberCouponMapper;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.pay.util.WechatConfigure;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
@@ -37,6 +38,8 @@
    private RedisUtils redisUtils;
    @Autowired
    private MallMemberCouponMapper mallMemberCouponMapper;
    @Autowired
    private MallInvoiceService mallInvoiceService;
    @Resource
    RestTemplate restTemplate;
//    @Autowired
@@ -87,4 +90,14 @@
    }
    /**
     * 更新发票记录表数据
     * 定时,每天凌晨一点
     * 已完成的订单,更新到发票记录中
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void mallInvoiceJob() {
        mallInvoiceService.mallInvoiceJob();
    }
}
src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java
New file
@@ -0,0 +1,31 @@
package cc.mrbird.febs.mall.service;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.entity.QueryRequest;
import cc.mrbird.febs.mall.dto.AdminMallInvoiceDto;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.entity.MallInvoice;
import cc.mrbird.febs.mall.entity.MallOrderItem;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
public interface MallInvoiceService {
    FebsResponse getInvoices(ApiMallInvoiceDto apiMallInvoiceDto);
     /**
     * 更新发票记录表数据
     * 定时,每天凌晨一点
     * 已完成的订单,更新到发票记录中
     */
    void mallInvoiceJob();
    IPage<MallInvoice> getInvoiceList(AdminMallInvoiceDto mallInvoiceDto, QueryRequest request);
    List<MallOrderItem> invoiceItemList();
    FebsResponse addInvoiceUrl(MallInvoice mallInvoice);
    FebsResponse updateInvoiceDetail(Long id);
}
src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java
New file
@@ -0,0 +1,160 @@
package cc.mrbird.febs.mall.service.impl;
import cc.mrbird.febs.common.entity.FebsResponse;
import cc.mrbird.febs.common.entity.QueryRequest;
import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
import cc.mrbird.febs.common.utils.LoginUserUtil;
import cc.mrbird.febs.common.utils.RedisUtils;
import cc.mrbird.febs.mall.dto.AdminMallInvoiceDto;
import cc.mrbird.febs.mall.dto.ApiMallInvoiceDto;
import cc.mrbird.febs.mall.entity.MallInvoice;
import cc.mrbird.febs.mall.entity.MallMember;
import cc.mrbird.febs.mall.entity.MallOrderInfo;
import cc.mrbird.febs.mall.entity.MallOrderItem;
import cc.mrbird.febs.mall.mapper.MallInvoiceMapper;
import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
import cc.mrbird.febs.mall.mapper.MallOrderItemMapper;
import cc.mrbird.febs.mall.service.MallInvoiceService;
import cc.mrbird.febs.mall.vo.AdminMallOrderInfoVo;
import cc.mrbird.febs.mall.vo.ApiMallInvoiceVo;
import cc.mrbird.febs.pay.service.WxFaPiaoService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class MallInvoiceServiceImpl extends ServiceImpl<MallInvoiceMapper, MallInvoice> implements MallInvoiceService {
    private final MallOrderInfoMapper mallOrderInfoMapper;
    private final MallOrderItemMapper mallOrderItemMapper;
    private final RedisUtils redisUtils;
    private final WxFaPiaoService wxFaPiaoService;
    @Override
    public FebsResponse getInvoices(ApiMallInvoiceDto apiMallInvoiceDto) {
        MallMember member = LoginUserUtil.getLoginUser();
        apiMallInvoiceDto.setMemberId(member.getId());
        IPage<ApiMallInvoiceVo> page = new Page<>(apiMallInvoiceDto.getPageNum(), apiMallInvoiceDto.getPageSize());
        IPage<ApiMallInvoiceVo> apiMallInvoiceVoIPage = this.baseMapper.selectApiMallInvoiceVoInPage(page, apiMallInvoiceDto);
        return new FebsResponse().success().data(apiMallInvoiceVoIPage);
    }
    @Override
    public void mallInvoiceJob() {
        /**
         * 获取当前已经是已完成且未开过发票的订单
         */
        List<MallOrderInfo> mallOrderInfos = mallOrderInfoMapper.selectOrderInfoByStatusAndIsInvoice(OrderStatusEnum.FINISH.getValue(), 0);
        if(CollUtil.isEmpty(mallOrderInfos)){
            return;
        }
        for(MallOrderInfo mallOrderInfo : mallOrderInfos){
            /**
             * 生成开票记录
             */
            Long memberId = mallOrderInfo.getMemberId();
            /**
             * fapiao_apply_id
             * 发票申请单号,唯一标识一次开票行为。微信支付场景下,为微信支付订单号;非微信支付场景下,为调用【获取抬头填写链接】接口时指定的发票申请单号
             * 对应订单信息的pay_order_no支付订单号
             */
            String payOrderNo = mallOrderInfo.getPayOrderNo();
            List<MallInvoice> mallInvoices = this.baseMapper.selectByFapiaoApplyIdAndMemberId(payOrderNo, memberId);
            if(CollUtil.isNotEmpty(mallInvoices)){
                MallInvoice mallInvoice = mallInvoices.get(0);
                mallInvoice.setStatus(mallOrderInfo.getStatus());
                this.baseMapper.updateById(mallInvoice);
            }else{
                MallInvoice mallInvoice = new MallInvoice();
                mallInvoice.setMemberId(memberId);
                mallInvoice.setFapiaoApplyId(payOrderNo);
                mallInvoice.setOrderNo(mallOrderInfo.getOrderNo());
                mallInvoice.setOrderId(mallOrderInfo.getId());
                mallInvoice.setStatus(mallOrderInfo.getStatus());
                mallInvoice.setAmount(mallOrderInfo.getAmount());
                List<MallOrderItem> mallOrderItemList = mallOrderItemMapper.selectListByOrderId(mallOrderInfo.getId());
                mallInvoice.setGoodsImg(mallOrderItemList.get(0).getSkuImage());
                mallInvoice.setState(0);
                this.baseMapper.insert(mallInvoice);
            }
            /**
             * 更新订单为已开票
             */
            mallOrderInfo.setIsInvoice(1);
            mallOrderInfoMapper.updateById(mallOrderInfo);
        }
    }
    @Override
    public IPage<MallInvoice> getInvoiceList(AdminMallInvoiceDto mallInvoiceDto, QueryRequest request) {
        Page<MallInvoice> page = new Page<>(request.getPageNum(), request.getPageSize());
        return  this.baseMapper.getInvoiceListInPage(page, mallInvoiceDto);
    }
    @Override
    public List<MallOrderItem> invoiceItemList() {
        Long existToken = Long.parseLong(redisUtils.getString("ADMIN_INVOICE_ID"));
        MallInvoice mallInvoice = this.baseMapper.selectById(existToken);
        if(ObjectUtil.isEmpty(mallInvoice)){
            return null;
        }
        return this.baseMapper.getInvoiceItemList(mallInvoice);
    }
    @Override
    @Transactional
    public FebsResponse addInvoiceUrl(MallInvoice mallInvoice) {
        Long id = mallInvoice.getId();
        MallInvoice mallInvoiceOld = this.baseMapper.selectById(id);
        if(ObjectUtil.isEmpty(mallInvoiceOld)){
            return new FebsResponse().fail().message("开票信息异常。");
        }
        Integer status = mallInvoiceOld.getStatus();
        if(OrderStatusEnum.FINISH.getValue() != status){
            return new FebsResponse().fail().message("订单还未完成,请勿开发票。");
        }
        mallInvoiceOld.setInvoiceUrl(mallInvoice.getInvoiceUrl());
        mallInvoiceOld.setState(2);
        this.baseMapper.updateById(mallInvoiceOld);
        return new FebsResponse().success();
    }
    @Override
    @Transactional
    public FebsResponse updateInvoiceDetail(Long id) {
        MallInvoice mallInvoice = this.baseMapper.selectById(id);
        if(ObjectUtil.isEmpty(mallInvoice)){
            return new FebsResponse().fail().message("开票信息异常。");
        }
        String fapiaoApplyId = mallInvoice.getFapiaoApplyId();
        try {
            String userInvoiceInfo = wxFaPiaoService.getUserInvoiceInfo(fapiaoApplyId);
            JSONObject userInvoiceInfoJson = JSONUtil.parseObj(userInvoiceInfo);
            mallInvoice.setType(userInvoiceInfoJson.getStr("type"));
            mallInvoice.setName(userInvoiceInfoJson.getStr("name"));
            mallInvoice.setTaxpayerId(userInvoiceInfoJson.getStr("taxpayer_id"));
            mallInvoice.setAddress(userInvoiceInfoJson.getStr("address"));
            mallInvoice.setTelephone(userInvoiceInfoJson.getStr("telephone"));
            mallInvoice.setBankName(userInvoiceInfoJson.getStr("bank_name"));
            mallInvoice.setBankAccount(userInvoiceInfoJson.getStr("bank_account"));
            this.baseMapper.updateById(mallInvoice);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new FebsResponse().success();
    }
}
src/main/java/cc/mrbird/febs/mall/vo/AdminInvoiceDetailVo.java
New file
@@ -0,0 +1,47 @@
package cc.mrbird.febs.mall.vo;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class AdminInvoiceDetailVo {
    private Long id;
    /**
     *【购买方类型】 购买方类型
     * 可选取值:
     * INDIVIDUAL: 个人
     * ORGANIZATION: 单位
     */
    private String type;
    /**
     *【名称】 购买方名称
     */
    private String name;
    /**
     *【纳税人识别号】 购买方纳税人识别号,购买方类型为ORGANIZATION时必须存在
     */
    private String taxpayerId;
    /**
     *【地址】 购买方地址
     */
    private String address;
    /**
     *【电话】 购买方电话
     */
    private String telephone;
    /**
     *【开户银行】 购买方开户银行
     */
    private String bankName;
    /**
     *【银行账号】 购买方银行账号
     */
    private String bankAccount;
    private String orderNo;//订单编号
    private BigDecimal amount;//订单金额
    private String invoiceUrl;//订单编号
}
src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java
New file
@@ -0,0 +1,30 @@
package cc.mrbird.febs.mall.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@ApiModel(value = "ApiMallInvoiceVo", description = "发票")
public class ApiMallInvoiceVo {
    @ApiModelProperty(value = "ID")
    private Long id;
    @ApiModelProperty(value = "订单编号")
    private String orderNo;
    @ApiModelProperty(value = "订单金额")
    private BigDecimal amount;
    @ApiModelProperty(value = "商品图片")
    private String goodsImg;
    @ApiModelProperty(value = "开票状态:0:未开票 1:开票中 2:已开票")
    private Integer state;
    @ApiModelProperty(value = "发票的URL路径")
    private String invoiceUrl;
}
src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java
@@ -13,6 +13,7 @@
import cc.mrbird.febs.mall.service.IMallMoneyFlowService;
import cc.mrbird.febs.pay.model.NotifyData;
import cc.mrbird.febs.pay.service.IXcxPayService;
import cc.mrbird.febs.pay.service.WxFaPiaoService;
import cc.mrbird.febs.pay.util.Signature;
import cc.mrbird.febs.pay.util.Util;
import cc.mrbird.febs.pay.util.WechatConfigure;
@@ -21,19 +22,27 @@
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -68,6 +77,9 @@
    @Autowired
    private IXcxPayService iXcxPayService;
    @Autowired
    private WxFaPiaoService wxFaPiaoService;
    private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class);
    /**
@@ -238,17 +250,16 @@
//    }
    /**
     * 微信电子发票回调接口
     * POST方式回调
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @RequestMapping(value = "/fapiaoCallBack")
    public Map<Object, Object> fapiaoCallBack(HttpServletResponse response, HttpServletRequest request) throws IOException {
        log.info("微信电子发票回调接口....");
        Map<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("code","SUCCESS");
        objectObjectHashMap.put("message","");
        return objectObjectHashMap;
    public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) {
        return wxFaPiaoService.fapiaoCallBack(request,requestBody);
    }
    /**
     * 微信支付回调接口
     */
@@ -295,6 +306,8 @@
                        resXml = WechatConfigure.RESULT_XML_SUCCESS;
                        // 支付费用
                        Double total_fee = Double.parseDouble(data.getTotal_fee());
                        //微信支付订单号
                        String transaction_id = data.getTransaction_id();
                        // 商户订单号
                        String payNum = data.getOut_trade_no();
@@ -310,6 +323,7 @@
                            order.setPayResult("1");
                            order.setPayTime(new Date());
                            order.setDeliveryState(OrderDeliveryStateEnum.DELIVERY_WAIT.getValue());
                            order.setPayOrderNo(transaction_id);
                            mallOrderInfoMapper.updateById(order);
                            agentProducer.sendOrderCoupon(order.getId());
src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java
New file
@@ -0,0 +1,21 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
@Data
public class FPCardTemplateCustomCellDto {
    /**
     * 【cell位文字】 展示在卡券详情页自定义cell位上的文字
     */
    private String words;
    /**
     * 【cell位描述】 展示在卡券详情页自定义cell位右侧的描述
     */
    private String description;
    private String jump_url;
    private String miniprogram_user_name;
    private String miniprogram_path;
}
src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java
New file
@@ -0,0 +1,17 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
@Data
public class FPCardTemplateDto {
    /**
     * 【插卡公众号AppID】 插卡公众号AppID。
     * 若是服务商模式,则可以是服务商申请的AppId,也可以是子商户申请的AppId;
     * 若是直连模式,则是直连商户申请的AppId
     */
    private String card_appid;
    /**
     * 【卡券模板信息】 卡券模板信息
     */
    private FPCardTemplateInformationDto card_template_information;
}
src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java
New file
@@ -0,0 +1,22 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
/**
 * 【卡券模板信息】 卡券模板信息
 */
@Data
public class FPCardTemplateInformationDto {
    /**
     * 【收款方名称】 收款方名称,显示在电子发票卡券信息中,若不传则默认取商户名称
     */
    private String payee_name;
    /**
     * 【卡券logo地址】 卡券logo地址
     */
    private String logo_url;
    /**
     * 【卡券自定义cell位配置】
     */
    private FPCardTemplateCustomCellDto custom_cell;
}
src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java
New file
@@ -0,0 +1,10 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
import java.util.List;
@Data
public class FPCertificateVo {
    private List<FPCertificates> data;
}
src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java
New file
@@ -0,0 +1,13 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
import java.util.List;
@Data
public class FPCertificates {
    private String serial_no;
    private String effective_time;
    private String expire_time;
    private FPEncryptCertificate encrypt_certificate;
}
src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java
New file
@@ -0,0 +1,11 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
@Data
public class FPEncryptCertificate {
    private String algorithm;
    private String nonce;
    private String associated_data;
    private String ciphertext;
}
src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java
New file
@@ -0,0 +1,20 @@
package cc.mrbird.febs.pay.model;
import lombok.Data;
@Data
public class FPUserTitleDto {
    /**
     * 【发票申请单号】 发票申请单号,唯一标识一次开票行为。
     * 当开票场景为WITHOUT_WECHATPAY时,为调用【获取抬头填写链接】接口时指定的发票申请单号;
     * 当开票场景为WITH_WECHATPAY时,为与本次开票关联的微信支付订单号,且必须是属于相应商户的订单
     */
    private String fapiao_apply_id;
    /**
     * 【开票场景】 开票场景
     * 可选取值:
     * WITH_WECHATPAY: 微信支付场景
     * WITHOUT_WECHATPAY: 非微信支付场景
     */
    private String scene;
}
src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java
@@ -1,11 +1,27 @@
package cc.mrbird.febs.pay.service;
import java.security.KeyPair;
import org.springframework.web.bind.annotation.RequestBody;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.Map;
public interface WxFaPiaoService {
    String createAuthorization(String method, String canonicalUrl, String body, KeyPair keyPair);//生成Token
    String createAuthorization(String method, String canonicalUrl, String body, PrivateKey keyPair) throws UnsupportedEncodingException, NoSuchAlgorithmException;//生成Token
    KeyPair getPrivateKey();//通过证书获取私钥公钥
    PrivateKey getPrivateKeyV3() throws IOException;//获取私钥
    String sendPatch(String url, String params, String token);
    String sendPost(String url, String params, String token);
    String sendGet(String url, Map<String, Object> params, String token);
    Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody);
    String getUserInvoiceInfo(String fapiaoApplyId) throws IOException;
}
src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java
@@ -1,108 +1,597 @@
package cc.mrbird.febs.pay.service.impl;
import cc.mrbird.febs.common.properties.XcxProperties;
import cc.mrbird.febs.common.utils.AppContants;
import cc.mrbird.febs.common.utils.SpringContextHolder;
import cc.mrbird.febs.mall.entity.MallInvoice;
import cc.mrbird.febs.mall.entity.MallOrderInfo;
import cc.mrbird.febs.mall.entity.MallOrderItem;
import cc.mrbird.febs.mall.mapper.MallInvoiceMapper;
import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
import cc.mrbird.febs.mall.mapper.MallOrderItemMapper;
import cc.mrbird.febs.pay.model.FPCertificateVo;
import cc.mrbird.febs.pay.model.FPCertificates;
import cc.mrbird.febs.pay.model.FPEncryptCertificate;
import cc.mrbird.febs.pay.service.WxFaPiaoService;
import cc.mrbird.febs.pay.util.JCEUtil;
import cc.mrbird.febs.pay.util.RandomStringGenerator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.RequestBody;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
@Service
@RequiredArgsConstructor
public class WxFaPiaoServiceImpl implements WxFaPiaoService {
    private final MallOrderInfoMapper mallOrderInfoMapper;
    private final MallOrderItemMapper mallOrderItemMapper;
    private final MallInvoiceMapper mallInvoiceMapper;
    private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class);
    @Override
    public String createAuthorization(String method, String canonicalUrl, String body, KeyPair keyPair) {
    public String createAuthorization(String method, String canonicalUrl, String body, PrivateKey keyPair) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        String nonceStr = RandomStringGenerator.getRandomStringByLength(32);//随机字符串
        long timestamp = System.currentTimeMillis() / 1000;//时间戳
        String signature = sign(method, canonicalUrl, timestamp, nonceStr, body, keyPair);//签名加密
        HttpUrl httpurl = HttpUrl.parse(canonicalUrl);
        String message = buildMessage(method, httpurl, timestamp, nonceStr, body);
        log.info("签名串:\n"+message);
        log.info("签名串长度:\n"+getWordCount(message));
        String signature = sign2(message.getBytes("utf-8"), keyPair);
        log.info("签名串sign:\n"+signature);
        log.info("签名串长度sign:\n"+getWordCount(signature));
//        String yourCertificateSerialNo = "221D49AEC4EA538A63941D1936709C8559EB05C5";
        return "mchid=\"" + xcxProperties.getWecharpayMchid() + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + "50F37206347BCC9E6AC9860DAACE52AC035F7C24" + "\","//证书序列号
                + "serial_no=\"" + AppContants.WX_CARD_NUM + "\","
                + "signature=\"" + signature + "\"";
    }
    @Override
    public KeyPair getPrivateKey() {
        return createPKCS12("Tenpay Certificate", "1658958205");
    }
    /**
     * V3  SHA256withRSA 签名.
     *
     * @param method       请求方法  GET  POST PUT DELETE 等
     * @param canonicalUrl 例如  https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
     * @param timestamp    当前时间戳   因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
     * @param nonceStr     随机字符串  要和TOKEN中的保持一致
     * @param body         请求体 GET 为 "" POST 为JSON
     * @param keyPair      商户API 证书解析的密钥对  实际使用的是其中的私钥
     * @return the string
     */
    @SneakyThrows
    public String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair)  {
        String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
                .collect(Collectors.joining("\n", "", "\n"));
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(keyPair.getPrivate());
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }
    public   int  getWordCount(String s)
    {
        int  length  =   0 ;
        for ( int  i  =   0 ; i  <  s.length(); i ++ )
        {
            int  ascii  =  Character.codePointAt(s, i);
            if (ascii  >=   0   &&  ascii  <= 255 )
                length ++ ;
            else
                length  +=   2 ;
    /**
     * 获取公私钥.通过证书
     */
    private KeyStore store;
    private final Object lock = new Object();
    public KeyPair createPKCS12(String keyAlias, String keyPass) {
        ClassPathResource resource = new ClassPathResource(xcxProperties.getCertLocalPath());
//        File file = new File("src/main/resources/wxP12/apiclient_cert.p12");
        char[] pem = keyPass.toCharArray();
        try {
            synchronized (lock) {
                if (store == null) {
                    synchronized (lock) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(resource.getInputStream(), pem);
//                        store.load(new FileInputStream(file), pem);
                    }
                }
            }
            X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
            certificate.checkValidity();
            // 证书的序列号 也有用 50F37206347BCC9E6AC9860DAACE52AC035F7C24
            String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
            // 证书的 公钥
            PublicKey publicKey = certificate.getPublicKey();
            // 证书的私钥
            PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);
            return new KeyPair(publicKey, storeKey);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot load keys from store: " , e);
        }
        return  length;
    }
    public static void main(String[] args) {
    public String sign2(byte[] message,PrivateKey keyPair) throws NoSuchAlgorithmException {
        Signature sign = Signature.getInstance("SHA256withRSA");
        String s = null;
        try {
            System.out.println(new ClassPathResource("wxP12/apiclient_cert.p12").getFile().exists());
        } catch (IOException e) {
            sign.initSign(keyPair);
            sign.update(message);
            s = Base64.getEncoder().encodeToString(sign.sign());
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        }
        return s;
    }
    public String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }
    @Override
    public PrivateKey getPrivateKeyV3() throws IOException {
        InputStream inputStream = new ClassPathResource("wxP12/apiclient_key.pem")
                .getInputStream();
        String content = new BufferedReader(new InputStreamReader(inputStream))
                .lines().collect(Collectors.joining(System.lineSeparator()));
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }
    @Override
    public String sendPatch(String url, String params, String token) {
        String result = "";
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPatch httpPatch = new HttpPatch(url);
        CloseableHttpResponse response = null;
        httpPatch.setHeader("Content-type", "application/json");
        httpPatch.setHeader("Charset", "utf-8");
        httpPatch.setHeader("Accept", "application/json");
        httpPatch.setHeader("Accept-Charset", "utf-8");
        httpPatch.setHeader("Authorization", token);
        try {
            StringEntity data = new StringEntity(params, "utf-8");
            httpPatch.setEntity(data);
            response = httpClient.execute(httpPatch);
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity);
        } catch (Exception e) {
            result = "{\"status\":\"1\",\"error\":\"" + e.getMessage() + "\"}";
        }finally {
            try {
                httpClient.close();
                if (response != null) {
                    response.close();
                }
            } catch (IOException var22) {
                var22.printStackTrace();
            }
        }
        return result;
    }
    @Override
    public String sendPost(String url, String params, String token) {
        String result = "";
        int err = 0;
        while (true) {
            CloseableHttpClient client = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(url);
            CloseableHttpResponse response = null;
            try {
                httpPost.addHeader("Content-type", "application/json");
                httpPost.addHeader("Charset", "utf-8");
                httpPost.addHeader("Accept", "application/json");
                httpPost.addHeader("Accept-Charset", "utf-8");
                httpPost.addHeader("Authorization", token);
                StringEntity data = new StringEntity(params, "utf-8");
                httpPost.setEntity(data);
                response = client.execute(httpPost);
                HttpEntity resEntity = response.getEntity();
                result = EntityUtils.toString(resEntity);
                return result;
            } catch (IOException e) {
                result = "{\"status\":\"1\",\"errors\":\"" + e.getMessage() + "\"}";
                if (err++ > 2) {
                    break;
                }
                try {
                    Thread.sleep((err + 2) * 1000);
                } catch (InterruptedException e1) {
                    result = "{\"status\":\"1\",\"errors\":\"" + e1.getMessage() + "\"}";
                }
            }finally {
                try {
                    client.close();
                    if (response != null) {
                        response.close();
                    }
                } catch (IOException var22) {
                    var22.printStackTrace();
                }
            }
        }
        return result;
    }
    @Override
    public String sendGet(String url, Map<String, Object> params, String token) {
        String result = "";
        int err = 0;
        while (true) {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = null;
            try {
                List<BasicNameValuePair> parameters = new ArrayList<>();
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
                }
                StringBuilder dataparm = new StringBuilder();
                params.forEach((k, v) -> dataparm.append("&" + k + "=" + v));
                String urlparm = "";
                String string = dataparm.toString();
                if (!"".equals(string)) {
                    urlparm = dataparm.toString().substring(1);
                }
                HttpGet httpget = new HttpGet(url + "?" + urlparm);
                httpget.addHeader("Accept", "application/json");
                httpget.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36");
                httpget.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
                httpget.addHeader("Authorization", token);
                response = httpClient.execute(httpget, HttpClientContext.create());
                HttpEntity resEntity = response.getEntity();
                result = EntityUtils.toString(resEntity);
                return result;
            } catch (IOException e) {
                result = "{\"status\":\"1\",\"errors\":\"" + e.getMessage() + "\"}";
                if (err++ > 2) {
                    break;
                }
                try {
                    Thread.sleep((err + 2) * 1000);
                } catch (InterruptedException e1) {
                    result = "{\"status\":\"1\",\"errors\":\"" + e1.getMessage() + "\"}";
                }
            }finally {
                try {
                    httpClient.close();
                    if (response != null) {
                        response.close();
                    }
                } catch (IOException var22) {
                    var22.printStackTrace();
                }
            }
        }
        return result;
    }
    @Override
    public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody){
        Map<String,Object> map = new HashMap<>();
        String signature = request.getHeader("Wechatpay-Signature");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        //平台证书序列号不是API证书序列号
        String serial = request.getHeader("Wechatpay-Serial");
        String body = com.alibaba.fastjson.JSONObject.toJSONString(requestBody);
        log.info("头信息---签名:" + signature);
        log.info("头信息---时间戳:" + timestamp);
        log.info("头信息---随机字符:" + nonce);
        log.info("头信息---平台证书序列号:" + serial);
        log.info("获取到的body信息:" + body);
        //应对签名探测流量
        if(signature.contains("WECHATPAY/SIGNTEST")){
            map.put("code",500);
            map.put("message", "失败");
            return map;
        }
        //验签
        boolean signCheck = false;
        try {
            signCheck = signCheck(timestamp, nonce, requestBody, signature);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        log.info("验签结果:" + signCheck);
        if (signCheck) {
            try {
                //解析请求体
                JSONObject jsonObject = JSONUtil.parseObj(body);
                log.info("微信电子发票回调接口....解析请求体:"+jsonObject);
                String id = jsonObject.getStr("id");//可能是支付业务的回调数据
                String create_time = jsonObject.getStr("create_time");//可能是支付业务的回调数据
                String resource_type = jsonObject.getStr("resource_type");//可能是支付业务的回调数据
                String event_type = jsonObject.getStr("event_type");//可能是支付业务的回调数据
                String resource = jsonObject.getStr("resource");//可能是支付业务的回调数据
                log.info("微信电子发票回调接口....id:"+ id);
                log.info("微信电子发票回调接口....create_time:"+ create_time);
                log.info("微信电子发票回调接口....resource_type:"+ resource_type);
                log.info("微信电子发票回调接口....event_type:"+ event_type);
                log.info("微信电子发票回调接口....resource:"+ resource);
                if ("FAPIAO.USER_APPLIED".equals(event_type)//用户发票抬头填写完成类型:FAPIAO.USER_APPLIED
                        && "encrypt-resource".equals(resource_type)) {//通知的资源数据类型,确认成功通知为encryptresource。
                    JCEUtil.removeCryptographyRestrictions();
                    //解密
                    AesUtil aesUtil = new AesUtil(xcxProperties.getWecharpaySecretV3().getBytes("utf-8"));
                    JSONObject jsonObjectResource = JSONUtil.parseObj(resource);
                    String original_type = jsonObjectResource.getStr("original_type");//可能是支付业务的回调数据
                    String algorithm = jsonObjectResource.getStr("algorithm");//可能是支付业务的回调数据
                    String ciphertext = jsonObjectResource.getStr("ciphertext");//可能是支付业务的回调数据
                    String associated_data = jsonObjectResource.getStr("associated_data");//可能是支付业务的回调数据
                    String nonceStr = jsonObjectResource.getStr("nonce");//可能是支付业务的回调数据
                    String decryptToString = aesUtil.decryptToString(
                            associated_data.getBytes("utf-8"),
                            nonceStr.getBytes("utf-8"),
                            ciphertext);
                    log.info("微信电子发票回调接口....resource解密:"+decryptToString);
                    JSONObject parseObj = JSONUtil.parseObj(decryptToString);
                    log.info("微信电子发票回调接口....resource解密-JSONObject:"+parseObj);
                    String fapiao_apply_id = parseObj.getStr("fapiao_apply_id");
                    log.info("微信电子发票回调接口....resource解密-fapiao_apply_id:"+fapiao_apply_id);
                    MallOrderInfo mallOrderInfo = mallOrderInfoMapper.selectBypayOrderNo(fapiao_apply_id);
                    if(ObjectUtil.isEmpty(mallOrderInfo)){
                        map.put("code",404);
                        map.put("message", "订单不存在");
                        return map;
                    }
                    if(ObjectUtil.isNotEmpty(mallOrderInfo)){
                        //更新订单状态
//                        mallOrderInfo.setIsInvoice(1);
//                        mallOrderInfoMapper.updateById(mallOrderInfo);
                        //获取用户的抬头信息
                        String userInvoiceInfo = this.getUserInvoiceInfo(fapiao_apply_id);
                        JSONObject userInvoiceInfoJson = JSONUtil.parseObj(userInvoiceInfo);
                        //查看当前订单是否已经开具发票
                        Long memberId = mallOrderInfo.getMemberId();
                        String payOrderNo = mallOrderInfo.getPayOrderNo();
                        List<MallInvoice> mallInvoices = mallInvoiceMapper.selectByFapiaoApplyIdAndMemberId(fapiao_apply_id, mallOrderInfo.getMemberId());
                        if(CollUtil.isEmpty(mallInvoices)){
                            MallInvoice mallInvoice = new MallInvoice();
                            mallInvoice.setMemberId(memberId);
                            mallInvoice.setFapiaoApplyId(payOrderNo);
                            mallInvoice.setOrderNo(mallOrderInfo.getOrderNo());
                            mallInvoice.setOrderId(mallOrderInfo.getId());
                            mallInvoice.setStatus(mallOrderInfo.getStatus());
                            mallInvoice.setAmount(mallOrderInfo.getAmount());
                            List<MallOrderItem> mallOrderItemList = mallOrderItemMapper.selectListByOrderId(mallOrderInfo.getId());
                            mallInvoice.setGoodsImg(mallOrderItemList.get(0).getSkuImage());
                            mallInvoice.setState(1);
                            mallInvoice.setType(userInvoiceInfoJson.getStr("type"));
                            mallInvoice.setName(userInvoiceInfoJson.getStr("name"));
                            mallInvoice.setTaxpayerId(userInvoiceInfoJson.getStr("taxpayer_id"));
                            mallInvoice.setAddress(userInvoiceInfoJson.getStr("address"));
                            mallInvoice.setTelephone(userInvoiceInfoJson.getStr("telephone"));
                            mallInvoice.setBankName(userInvoiceInfoJson.getStr("bank_name"));
                            mallInvoice.setBankAccount(userInvoiceInfoJson.getStr("bank_account"));
                            mallInvoiceMapper.insert(mallInvoice);
                        }else{
                            MallInvoice mallInvoice = mallInvoices.get(0);
                            if(2 != mallInvoice.getState()){
                                mallInvoice.setState(1);
                                mallInvoice.setType(userInvoiceInfoJson.getStr("type"));
                                mallInvoice.setName(userInvoiceInfoJson.getStr("name"));
                                mallInvoice.setTaxpayerId(userInvoiceInfoJson.getStr("taxpayer_id"));
                                mallInvoice.setAddress(userInvoiceInfoJson.getStr("address"));
                                mallInvoice.setTelephone(userInvoiceInfoJson.getStr("telephone"));
                                mallInvoice.setBankName(userInvoiceInfoJson.getStr("bank_name"));
                                mallInvoice.setBankAccount(userInvoiceInfoJson.getStr("bank_account"));
                                mallInvoiceMapper.updateById(mallInvoice);
                            }
                        }
                        //省略查询订单
                        //此处处理业务
                        map.put("code","SUCCESS");
                        map.put("message","成功");
                        //消息推送成功
                        return map;
                    }
                }
                map.put("code",404);
                map.put("message", "订单不存在");
                return map;
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        map.put("code",500);
        map.put("message", "失败");
        return map;
    }
    /**
     * 获取用户的抬头信息
     */
    @Override
    public String getUserInvoiceInfo(String fapiaoApplyId) throws IOException {
        //初始化请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("scene","WITH_WECHATPAY");
//        params.put("fapiao_apply_id","4200002070202401175245187105");
        params.put("fapiao_apply_id",fapiaoApplyId);
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/user-title";
        String urlparm = baseUrl+canonicalUrl;//有参数的get请求,加密是需要把参数加载进去URL
        List<BasicNameValuePair> parameters = new ArrayList<>();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
        }
        StringBuilder dataparm = new StringBuilder();
        params.forEach((k, v) -> dataparm.append("&" + k + "=" + v));
        String string = dataparm.toString();
        if (!"".equals(string)) {
            urlparm = baseUrl+canonicalUrl + "?" + dataparm.substring(1);
        }
        PrivateKey privateKey = this.getPrivateKeyV3();
        String postStr = null;
        try {
            postStr = this.createAuthorization(
                    "GET",
                    urlparm,
                    "",
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return this.sendGet(baseUrl+canonicalUrl, params, "WECHATPAY2-SHA256-RSA2048 "+postStr);
    }
    /**
     * 验证签名
     *
     * @param timestamp   微信平台传入的时间戳
     * @param nonce       微信平台传入的随机字符串
     * @param requestBody 微信平台传入的消息体
     * @param signature   微信平台传入的签名
     * @return
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws IOException
     * @throws InvalidKeyException
     */
    public boolean signCheck(String timestamp, String nonce, Map<String, Object> requestBody, String signature) throws
            NoSuchAlgorithmException,
            SignatureException,
            IOException,
            InvalidKeyException,
            ParseException {
        //构造验签名串
        String signatureStr = timestamp + "\n" + nonce + "\n" + com.alibaba.fastjson.JSONObject.toJSONString(requestBody) + "\n";
        // 加载SHA256withRSA签名器
        Signature signer = Signature.getInstance("SHA256withRSA");
        // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法)
        signer.initVerify(getCertificates());
        // 把我们构造的验签名串更新到签名器中
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证
        boolean result = signer.verify(Base64Utils.decodeFromString(signature));
        return result;
    }
    /**
     * 获取平台证书
     */
    public X509Certificate getCertificates() throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException, ParseException {
        JCEUtil.removeCryptographyRestrictions();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        PrivateKey privateKey = this.getPrivateKeyV3();
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/certificates";
        String postStr = null;
        try {
            postStr = this.createAuthorization(
                    "GET",
                    baseUrl+canonicalUrl,
                    "",
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        //完成签名并执行请求
        X509Certificate x509Certificate = null;
        try {
            String responseBodyAsString = this.sendGet(baseUrl + canonicalUrl, new HashMap<>(), "WECHATPAY2-SHA256-RSA2048 "+postStr);
            FPCertificateVo certificateVo = com.alibaba.fastjson.JSONObject.parseObject(responseBodyAsString, FPCertificateVo.class);
            for (FPCertificates certificates : certificateVo.getData()) {
                if (format.parse(certificates.getEffective_time()).before(new Date())
                        && format.parse(certificates.getExpire_time()).after(new Date())) {
                    FPEncryptCertificate encrypt_certificate = certificates.getEncrypt_certificate();
                    //解密
                    AesUtil aesUtil = new AesUtil(xcxProperties.getWecharpaySecretV3().getBytes("utf-8"));
                    String pulicKey = aesUtil.decryptToString(
                            encrypt_certificate.getAssociated_data().getBytes("utf-8"),
                            encrypt_certificate.getNonce().getBytes("utf-8"),
                            encrypt_certificate.getCiphertext());
                    //获取平台证书
                    final CertificateFactory cf = CertificateFactory.getInstance("X509");
                    ByteArrayInputStream inputStream = new ByteArrayInputStream(pulicKey.getBytes(StandardCharsets.UTF_8));
                    x509Certificate = (X509Certificate) cf.generateCertificate(inputStream);
                }
            }
            return x509Certificate;
        } catch (GeneralSecurityException | ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException {
        String signature = "WECHATPAY/SIGNTEST/9WwfXW/noMdzDPcOrFD51Bf5YIThXLTtLCl5hoGlSfIBdY2UpE+5eHLs0XyF7y2cQc4OlzTzDDxSMTp/p/8ZenE2hMBaQjxVrdbTtrOLA1h13/WjCNaTr1URTqQ5+IF5bLCxAW8BmZ0jEqpmI/HYR3wdK8/7W91c1zfKODYgkvJzxFzd8OWi2GtELE5tLamkOLyb0GEcsGww2DUOypO5HjNyITgsy9R00w3OH92UInCD8Z6c5BAsQNySqFK8N52y38AlIWTtKROHmJtu+kHbt+nMOU0kbB5bZcPAptsnVYpp9KMmjMrmyjiKtHZb2TGIWiN/L+bgceyN3g==";
        if(signature.contains("WECHATPAY/SIGNTEST")){
            System.out.println(1);
        }
//        JCEUtil.removeCryptographyRestrictions();
//        String body = "{\"id\":\"5fb7f522-466f-5c99-a325-0eb4001f1ec3\",\"create_time\":\"2024-01-17T15:38:33+08:00\",\"resource_type\":\"encrypt-resource\",\"event_type\":\"FAPIAO.USER_APPLIED\",\"summary\":\"用户已申请开票\",\"resource\":{\"original_type\":\"fapiao\",\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"bBOs5WTUV1AhwiYSIUugaw9z4EHXxC28ZVsGHOppCNP4qpxYgnR+W+l983UyETmD/Wgu8ukJhe/xYkSsF947pgDP1ElTXC8MJA3lCc8NlsXQ1csPfWTS6A0XrxBoNMgwGEsYFo/7tPW/zDrEpzRZL/GgEwEtK6X1Y4Ci9IofzeI=\",\"associated_data\":\"fapiao\",\"nonce\":\"7a5dzuYqStef\"}}";
//        JSONObject jsonObject = JSONUtil.parseObj(body);
//        log.info("微信电子发票回调接口....解析请求体:"+jsonObject);
//
//        String id = jsonObject.getStr("id");//可能是支付业务的回调数据
//        String create_time = jsonObject.getStr("create_time");//可能是支付业务的回调数据
//        String resource_type = jsonObject.getStr("resource_type");//可能是支付业务的回调数据
//        String event_type = jsonObject.getStr("event_type");//可能是支付业务的回调数据
//        String resource = jsonObject.getStr("resource");//可能是支付业务的回调数据
//        log.info("微信电子发票回调接口....id:"+ id);
//        log.info("微信电子发票回调接口....create_time:"+ create_time);
//        log.info("微信电子发票回调接口....resource_type:"+ resource_type);
//        log.info("微信电子发票回调接口....event_type:"+ event_type);
//        log.info("微信电子发票回调接口....resource:"+ resource);
//
//        if ("FAPIAO.USER_APPLIED".equals(event_type)//用户发票抬头填写完成类型:FAPIAO.USER_APPLIED
//                && !"encryptresource".equals(resource_type)) {//通知的资源数据类型,确认成功通知为encryptresource。
//            //解密
//            AesUtil aesUtil = new AesUtil("daL341aN5orDt13puXadsAf2rpuX12v3".getBytes("utf-8"));
//            JSONObject jsonObjectResource = JSONUtil.parseObj(resource);
////            String original_type = jsonObjectResource.getStr("original_type");//可能是支付业务的回调数据
////            String algorithm = jsonObjectResource.getStr("algorithm");//可能是支付业务的回调数据
//            String ciphertext = jsonObjectResource.getStr("ciphertext");//可能是支付业务的回调数据
//            String associated_data = jsonObjectResource.getStr("associated_data");//可能是支付业务的回调数据
//            String nonce = jsonObjectResource.getStr("nonce");//可能是支付业务的回调数据
//            String decryptToString = aesUtil.decryptToString(
//                    associated_data.getBytes("utf-8"),
//                    nonce.getBytes("utf-8"),
//                    ciphertext);
//            log.info("微信电子发票回调接口....resource解密:"+decryptToString);
//
//            JSONObject parseObj = JSONUtil.parseObj(decryptToString);
//            log.info("微信电子发票回调接口....resource解密-JSONObject:"+parseObj);
//            String fapiao_apply_id = parseObj.getStr("fapiao_apply_id");
//            log.info("微信电子发票回调接口....resource解密-fapiao_apply_id:"+fapiao_apply_id);
//
//        }
    }
}
src/main/java/cc/mrbird/febs/pay/util/JCEUtil.java
New file
@@ -0,0 +1,70 @@
package cc.mrbird.febs.pay.util;
import javax.crypto.Cipher;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Map;
/**
 * 解决因jdk版本问题不支持aes256加密问题(Illegal key size or default parameters)
 */
public class JCEUtil {
    public static void removeCryptographyRestrictions() {
        if (!isRestrictedCryptography()) {
            System.out.println("Cryptography restrictions removal not needed");
            return;
        }
        try {
            /*
             * Do the following, but with reflection to bypass access checks:
             *
             * JceSecurity.isRestricted = false;
             * JceSecurity.defaultPolicy.perms.clear();
             * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
             */
            final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
            final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
            final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");
            final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
            isRestrictedField.setAccessible(true);
            //去除isRestricted的final限制
            final Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
            isRestrictedField.set(null, false);
            final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
            defaultPolicyField.setAccessible(true);
            final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);
            final Field perms = cryptoPermissions.getDeclaredField("perms");
            perms.setAccessible(true);
            ((Map<?, ?>) perms.get(defaultPolicy)).clear();
            final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
            instance.setAccessible(true);
            defaultPolicy.add((Permission) instance.get(null));
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
    public static boolean isRestrictedCryptography() {
        // This matches Oracle Java 7 and 8, but not Java 9 or OpenJDK.
        final String name = System.getProperty("java.runtime.name");
        final String ver = System.getProperty("java.version");
        return name != null && name.equals("Java(TM) SE Runtime Environment")
                && ver != null && (ver.startsWith("1.7") || ver.startsWith("1.8"));
    }
    public static void main(String[] args) throws NoSuchAlgorithmException {
//        removeCryptographyRestrictions();
        long length = Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding");
        System.out.println(length);
    }
}
src/main/java/cc/mrbird/febs/pay/util/RequestBodyUtils.java
New file
@@ -0,0 +1,76 @@
package cc.mrbird.febs.pay.util;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
public class RequestBodyUtils {
    private static final int BUFFER_SIZE = 1024 * 8;
    /**
     * read string.
     *
     * @param reader Reader instance.
     * @return String.
     * @throws IOException
     */
    public static String read(Reader reader) throws IOException {
        try (StringWriter writer = new StringWriter()) {
            write(reader, writer);
            return writer.getBuffer().toString();
        }
    }
    /**
     * write.
     *
     * @param reader Reader.
     * @param writer Writer.
     * @return count.
     * @throws IOException
     */
    public static long write(Reader reader, Writer writer) throws IOException {
        return write(reader, writer, BUFFER_SIZE);
    }
    /**
     * write.
     *
     * @param reader     Reader.
     * @param writer     Writer.
     * @param bufferSize buffer size.
     * @return count.
     * @throws IOException
     */
    public static long write(Reader reader, Writer writer, int bufferSize) throws IOException {
        int read;
        long total = 0;
        char[] buf = new char[bufferSize];
        while ((read = reader.read(buf)) != -1) {
            writer.write(buf, 0, read);
            total += read;
        }
        return total;
    }
    /**
     * 获取requestBody
     */
    public static String getRequestBody() throws IOException {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
        assert servletRequestAttributes != null;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
        //读取输入流的内容转换为String类型IOUtils必须引入org.apache.dubbo.common.utils.IOUtils;包
        return RequestBodyUtils.read(reader);
    }
}
src/main/resources/mapper/modules/MallInvoiceMapper.xml
New file
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cc.mrbird.febs.mall.mapper.MallInvoiceMapper">
    <select id="selectApiMallInvoiceVoInPage" resultType="cc.mrbird.febs.mall.vo.ApiMallInvoiceVo">
        select
            a.id,
            a.order_no,
            a.amount,
            a.goods_img,
            a.state,
            a.invoice_url
        from mall_invoice a
        <where>
            <if test="record.memberId != null">
                and a.member_id=#{record.memberId}
            </if>
            <if test="record.state != null">
                and a.state=#{record.state}
            </if>
        </where>
        order by a.created_time desc
    </select>
    <select id="selectByFapiaoApplyIdAndMemberId" resultType="cc.mrbird.febs.mall.entity.MallInvoice">
        select
            a.*
        from mall_invoice a
        where fapiao_apply_id = #{fapiaoApplyId}
            and member_id = #{memberId}
    </select>
    <select id="getInvoiceListInPage" resultType="cc.mrbird.febs.mall.entity.MallInvoice">
        select
        a.*
        from mall_invoice a
        <where>
            <if test="record.state != null">
                and a.state = #{record.state}
            </if>
            <if test="record.orderNo != null and record.orderNo != ''">
                and a.order_no = #{record.orderNo}
            </if>
            <if test="record.payOrderNo != null and record.payOrderNo != ''">
                and a.fapiao_apply_id = #{record.payOrderNo}
            </if>
        </where>
        order by a.created_time desc
    </select>
    <select id="getAdminInvoiceDetailVoById" resultType="cc.mrbird.febs.mall.vo.AdminInvoiceDetailVo">
        select
            a.*
        from mall_invoice a
        where id = #{id}
    </select>
    <select id="getInvoiceItemList" resultType="cc.mrbird.febs.mall.entity.MallOrderItem">
        select
        a.*,
               b.unit
        from mall_order_item a
        left join mall_goods b on a.goods_id = b.id
        where order_id = #{record.orderId}
    </select>
</mapper>
src/main/resources/mapper/modules/MallOrderInfoMapper.xml
@@ -233,6 +233,10 @@
        select * from mall_order_info where status=#{status}
    </select>
    <select id="selectOrderInfoByStatusAndIsInvoice" resultType="cc.mrbird.febs.mall.entity.MallOrderInfo">
        select * from mall_order_info where status = #{status} and is_invoice = #{isInvoice}
    </select>
    <select id="selectOrderInfoUpTime" resultType="cc.mrbird.febs.mall.entity.MallOrderInfo">
        select * from mall_order_info where status=#{status} and now() > date_add(order_time,interval 1440 minute)
    </select>
@@ -458,4 +462,8 @@
            </if>
        </where>
    </select>
    <select id="selectBypayOrderNo" resultType="cc.mrbird.febs.mall.entity.MallOrderInfo">
        select * from mall_order_info where pay_order_no = #{payOrderNo}
    </select>
</mapper>
src/main/resources/templates/febs/views/modules/order/invoiceDetail.html
New file
@@ -0,0 +1,248 @@
<style>
    #invoice-info {
        padding: 20px 25px 25px 0;
    }
    #invoice-info .layui-treeSelect .ztree li a, .ztree li span {
        margin: 0 0 2px 3px !important;
    }
    #invoice-info #data-permission-tree-block {
        border: 1px solid #eee;
        border-radius: 2px;
        padding: 3px 0;
    }
    #invoice-info .layui-treeSelect .ztree li span.button.switch {
        top: 1px;
        left: 3px;
    }
</style>
<div class="layui-fluid" id="invoice-info">
    <blockquote class="layui-elem-quote blue-border">抬头信息</blockquote>
    <form class="layui-form" action="" lay-filter="invoice-info-form">
        <div class="layui-card">
            <div class="layui-form-item febs-hide">
                <label class="layui-form-label febs-form-item-require">id:</label>
                <div class="layui-input-block">
                    <input type="text" name="id">
                </div>
            </div>
            <div class="layui-row layui-col-space10 layui-form-item">
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">开票类型:</label>
                    <div class="layui-input-block">
                        <input type="radio" name="type" value="INDIVIDUAL" title="个人" checked="" readonly>
                        <input type="radio" name="type" value="ORGANIZATION" title="单位" readonly>
                    </div>
                </div>
            </div>
            <div class="layui-row layui-col-space10 layui-form-item">
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">名称:</label>
                    <div class="layui-input-block">
                        <input type="text" name="name" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">识别号:</label>
                    <div class="layui-input-block">
                        <input type="text" name="taxpayerId" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
            </div>
            <div class="layui-row layui-col-space10 layui-form-item">
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">地址:</label>
                    <div class="layui-input-block">
                        <input type="text" name="address" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">电话:</label>
                    <div class="layui-input-block">
                        <input type="text" name="telephone" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
            </div>
            <div class="layui-row layui-col-space10 layui-form-item">
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">开户银行:</label>
                    <div class="layui-input-block">
                        <input type="text" name="bankName" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">银行账号:</label>
                    <div class="layui-input-block">
                        <input type="text" name="bankAccount" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
            </div>
            <div class="layui-row layui-col-space10 layui-form-item">
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">订单编号:</label>
                    <div class="layui-input-block">
                        <input type="text" name="orderNo" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
                <div class="layui-col-lg6">
                    <label class="layui-form-label febs-form-item-require">订单金额:</label>
                    <div class="layui-input-block">
                        <input type="text" name="amount" autocomplete="off" class="layui-input" readonly>
                    </div>
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label febs-form-item-require">发票:</label>
                <div class="layui-input-block">
                    <div class="layui-upload">
                        <button type="button" class="layui-btn layui-btn-normal layui-btn" id="test2">上传发票</button>
                        <blockquote class="layui-elem-quote layui-quote-nm" style="margin-top: 10px;">
                            <div class="layui-upload-list" id="demo2"></div>
                        </blockquote>
                        <div class="layui-word-aux">双击图片删除</div>
                    </div>
                </div>
            </div>
            <div class="layui-form-item febs-hide">
                <label class="layui-form-label">发票链接:</label>
                <div class="layui-input-block">
                    <input type="text" id="invoiceUrl" lay-verify="required" name="invoiceUrl" autocomplete="off" class="layui-input" readonly>
                </div>
            </div>
            <div class="layui-row layui-col-space10 layui-form-item" style="text-align: center;">
                    <button  class="layui-btn layui-btn-normal" lay-submit=""
                            lay-filter="invoice-detail-form-submit" id="submit">保存</button>
            </div>
        </div>
    </form>
</div>
    <blockquote class="layui-elem-quote blue-border">订单详情</blockquote>
    <div class="layui-card-body febs-table-full">
        <table id="invoiceOrderItemTable" lay-filter="invoiceOrderItemTable"></table>
    </div>
<script data-th-inline="javascript">
    layui.use(['upload','febs', 'form', 'formSelects', 'validate', 'treeSelect', 'table','eleTree'], function () {
        var $ = layui.$,
            febs = layui.febs,
            layer = layui.layer,
            table = layui.table,
            formSelects = layui.formSelects,
            treeSelect = layui.treeSelect,
            form = layui.form,
            eleTree = layui.eleTree,
            invoiceDetail = [[${invoiceDetail}]],
            $view = $('#invoice-info'),
            validate = layui.validate,
            upload = layui.upload,
            _deptTree;
        form.render();
        table.render({
            elem: '#invoiceOrderItemTable'
            ,cols: [[ //表头
                {field: 'skuName', title: '货物、应税劳务、服务名称', minWidth: 300,align:'left'},
                {field: 'skuName', title: '规格型号', minWidth: 150,align:'left'},
                {field: 'unit', title: '单位', minWidth: 100,align:'left'},
                {field: 'cnt', title: '数量', minWidth: 100,align:'left'},
                {field: 'price', title: '单价', minWidth: 100,align:'left'},
                {field: 'amount', title: '金额', minWidth: 150,align:'left'}
            ]]
            ,data: []
        });
        dicDataReq("invoiceOrderItemTable");
        function dicDataReq(type) {
            $.get(ctx + 'admin/order/invoiceItemList/', function (r) {
                if (r.code === 200) {
                    var data = r.data;
                    table.reload('invoiceOrderItemTable', {
                        data : data
                    });
                }
            });
        }
        // 表格初始化
        initInvoiceDetailValue();
        function initInvoiceDetailValue() {
            if(invoiceDetail.invoiceUrl != null || invoiceDetail.invoiceUrl != ''){
                $('#demo2').append('<img src="'+ invoiceDetail.invoiceUrl +'" class="layui-upload-img" style="width: 100px">')
                $("#invoiceUrl").val(invoiceDetail.invoiceUrl);
            }
            form.val("invoice-info-form", {
                "id": invoiceDetail.id,
                "type": invoiceDetail.type,
                "name": invoiceDetail.name,
                "taxpayerId": invoiceDetail.taxpayerId,
                "address": invoiceDetail.address,
                "telephone": invoiceDetail.telephone,
                "bankName": invoiceDetail.bankName,
                "bankAccount": invoiceDetail.bankAccount,
                "orderNo": invoiceDetail.orderNo,
                "invoiceUrl": invoiceDetail.invoiceUrl,
                "amount": invoiceDetail.amount
            });
        }
        //图片上传
        upload.render({
            elem: '#test2'
            ,url: ctx + 'admin/goods/uploadFileBase64' //改成您自己的上传接口
            // ,multiple: true
            ,accept: 'file'
            ,before: function(obj){
                //预读本地文件示例,不支持ie8
                obj.preview(function(index, file, result){
                    if ($("#invoiceUrl").val()) {
                        $('#demo2').html('<img src="'+ result +'" alt="'+ file.name +'" class="layui-upload-img single-image" style="width: 130px">')
                    } else {
                        $('#demo2').append('<img src="'+ result +'" alt="'+ file.name +'" class="layui-upload-img single-image" style="width: 130px">')
                    }
                });
            }
            ,done: function(res){
                var item = this.item;
                //如果上传失败
                if(res.code !== 0){
                    return layer.msg('上传失败');
                }
                $("#invoiceUrl").val(res.data.src);
                layer.msg('上传完毕', {icon: 1});
                imgUnBind(".single-image");
                imgSingleBind();
            }
            ,error: function(err){
                return layer.msg('上传失败');
            }
        });
        function imgUnBind(className) {
            $(className).each(function() {
                $(this).unbind('dblclick');
            })
        }
        function imgSingleBind() {
            $(".single-image").each(function(index, element) {
                $(this).on("dblclick", function() {
                    var imgThumb = $(".single-image")[index];
                    $(imgThumb).remove();
                    $("#invoiceUrl").val("");
                    imgUnBind(".single-image");
                    imgSingleBind();
                });
            })
        }
        form.on('submit(invoice-detail-form-submit)', function (data) {
            febs.post(ctx + 'admin/order/addInvoiceUrl', data.field, function () {
                layer.closeAll();
                febs.alert.success('操作成功');
                $('#febs-invoice').find('#query').click();
            });
            return false;
        });
    });
</script>
src/main/resources/templates/febs/views/modules/order/invoiceList.html
New file
@@ -0,0 +1,233 @@
<div class="layui-fluid layui-anim febs-anim" id="febs-invoice" lay-title="发票管理">
    <div class="layui-row febs-container">
        <div class="layui-col-md12">
            <div class="layui-card">
                <div class="layui-card-body febs-table-full">
                    <form class="layui-form layui-table-form" lay-filter="user-table-form">
                        <div class="layui-form-item">
                            <div class="layui-col-md10">
                                <div class="layui-inline">
                                    <label class="layui-form-label">编号:</label>
                                    <div class="layui-input-inline">
                                        <input type="text" placeholder="发票申请单号/微信支付订单号" name="payOrderNo" autocomplete="off" class="layui-input">
                                    </div>
                                </div>
                                <div class="layui-inline">
                                    <label class="layui-form-label">订单编号:</label>
                                    <div class="layui-input-inline">
                                        <input type="text" placeholder="订单编号" name="orderNo" autocomplete="off" class="layui-input">
                                    </div>
                                </div>
                                <div class="layui-inline">
                                    <label class="layui-form-label layui-form-label-sm">状态:</label>
                                    <div class="layui-input-inline">
                                        <select name="state">
                                            <option value="">请选择</option>
                                            <option value="0">待申请</option>
                                            <option value="1">待开票</option>
                                            <option value="2">已开票</option>
                                        </select>
                                    </div>
                                </div>
                            </div>
                            <div class="layui-col-md2 layui-col-sm12 layui-col-xs12 table-action-area">
                                <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-blue-plain table-action" id="query">
                                    <i class="layui-icon">&#xe848;</i>
                                </div>
                                <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain table-action" id="reset">
                                    <i class="layui-icon">&#xe79b;</i>
                                </div>
                            </div>
                            <div class="layui-col-md10">
                                <label class="layui-form-label layui-form-label-sm">注意:</label>
                                <div class="layui-inline">
                                    <div class="layui-form-mid layui-word-aux">
                                        <span style="color:red;">订单状态【已完成】,才能开具发票。</span>
                                    </div>
                                    <div class="layui-form-mid layui-word-aux">
                                        <span style="color:red;">开具发票之前,请先点击【更新抬头】按钮,确保用户提供了抬头信息。</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                    <table lay-filter="invoiceTable" lay-data="{id: 'invoiceTable'}"></table>
                    <style type="text/css">
                        .layui-table cell{
                            text-align:center;
                            height: auto;
                            white-space: nowrap; /*文本不会换行,在同一行显示*/
                            overflow: hidden; /*超出隐藏*/
                            text-overflow: ellipsis; /*省略号显示*/
                        }
                        .layui-table img{
                            max-width:100px
                        }
                        ::-webkit-scrollbar {
                            height: 20px !important;
                            background-color: #f4f4f4;
                        }
                    </style>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- 表格操作栏 start -->
<script type="text/html" id="user-option">
    <span shiro:lacksPermission="user:view,user:update,user:delete">
        <span class="layui-badge-dot febs-bg-orange"></span> 无权限
    </span>
    <a lay-event="edit" shiro:hasPermission="user:update"><i
            class="layui-icon febs-edit-area febs-blue">&#xe7a5;</i></a>
</script>
<!-- 表格操作栏 end -->
<script data-th-inline="none" type="text/javascript">
    // 引入组件并初始化
    layui.use([ 'jquery', 'form', 'table', 'febs', 'formSelects', 'upload','laydate'], function () {
        var $ = layui.jquery,
            febs = layui.febs,
            form = layui.form,
            table = layui.table,
            upload = layui.upload,
            $view = $('#febs-invoice'),
            $query = $view.find('#query'),
            $reset = $view.find('#reset'),
            $searchForm = $view.find('form'),
            $add = $view.find('#add'),
            sortObject = {field: 'orderTime', type: 'desc'},
            formSelects = layui.formSelects,
            laydate = layui.laydate,
            tableIns;
        let currPageOrder = 1;//首先默认值为1,防止出错
        //获取当前页
        currPageOrder = $view.find(".layui-laypage-em").next().html();
        form.render();
        formSelects.render();
        // 表格初始化
        initInvoiceTable();
        // 初始化表格操作栏各个按钮功能
        table.on('tool(invoiceTable)', function (obj) {
            var data = obj.data,
                layEvent = obj.event;
            if (layEvent === 'seeInvoiceDetail') {
                febs.modal.open('开票信息', 'modules/order/invoiceDetail/'+ data.id, {
                    area:['100%','100%'],
                });
            }
            if (layEvent === 'updateInvoiceDetail') {
                febs.modal.confirm('更新', '是否更新用户的抬头信息?', function () {
                    updateInvoiceDetail(data.id);
                });
            }
        });
        function updateInvoiceDetail(id) {
            febs.get(ctx + 'admin/order/updateInvoiceDetail/' + id, null, function () {
                febs.alert.success('操作成功');
                $query.click();
            });
        }
        // 查询按钮
        $query.on('click', function () {
            var params = $.extend(getQueryParams(), {field: sortObject.field, order: sortObject.type});
            tableIns.reload({where: params, page: {curr: currPageOrder}});
        });
        // 刷新按钮
        $reset.on('click', function () {
            $searchForm[0].reset();
            sortObject.type = 'null';
            tableIns.reload({where: getQueryParams(), page: {curr: currPageOrder}, initSort: sortObject});
        });
        function initInvoiceTable() {
            tableIns = febs.table.init({
                elem: $view.find('table'),
                id: 'invoiceTable',
                url: ctx + 'admin/order/invoiceList',
                defaultToolbar: [],
                //系统自带打印导出
                totalRow : true,
                cols: [[
                        {field: 'orderNo', title: '订单编号', minWidth: 100,align:'left' ,totalRowText:"合计"},
                        {field: 'fapiaoApplyId', title: '发票申请单号', minWidth: 150,align:'left'},
                        {field: 'type', title: '购买方类型',
                            templet: function (d) {
                                if (d.type === 'INDIVIDUAL') {
                                    return '<span style="color:red;">个人</span>'
                                } else if (d.type === 'ORGANIZATION') {
                                    return '<span style="color:green;">单位</span>'
                                }else{
                                    return ''
                                }
                            }, minWidth: 80,align:'center'},
                        {field: 'name', title: '名称', minWidth: 120,align:'left'},
                        {field: 'taxpayerId', title: '纳税人识别号', minWidth: 100,align:'left'},
                        {field: 'address', title: '地址', minWidth: 200,align:'left'},
                        {field: 'telephone', title: '电话', minWidth: 100,align:'left'},
                        {field: 'bankName', title: '开户银行', minWidth: 100,align:'left', totalRow:true},
                        {field: 'bankAccount', title: '银行账号', minWidth: 100,align:'left', totalRow:true},
                        {field: 'state', title: '状态',
                            templet: function (d) {
                                if (d.state === 0) {
                                    return '<span style="color:red;">待申请</span>'
                                } else if (d.state === 1) {
                                    return '<span style="color:green;">待开票</span>'
                                }else if (d.state === 2) {
                                    return '<span style="color:green;">已开票</span>'
                                }else{
                                    return ''
                                }
                            }, minWidth: 100,align:'center'},
                    {field: 'status', title: '订单状态',
                        templet: function (d) {
                            if (d.status === 1) {
                                return '<span style="color:red;">待支付</span>'
                            } else if (d.status === 2) {
                                return '<span style="color:green;">待发货</span>'
                            }else if (d.status === 3) {
                                return '<span style="color:green;">待收货</span>'
                            }else if (d.status === 4) {
                                return '<span style="color:green;">已完成</span>'
                            }else if (d.status === 5) {
                                return '<span style="color:green;">退款中</span>'
                            }else if (d.status === 6) {
                                return '<span style="color:green;">已退款</span>'
                            }else if (d.status === 7) {
                                return '<span style="color:green;">已取消</span>'
                            }else{
                                return ''
                            }
                        }, minWidth: 100,align:'center'},
                        {title: '操作',
                            templet: function (d) {
                                if(d.status === 4){
                                    return '<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="seeInvoiceDetail" shiro:hasPermission="user:update">开票信息</button>' +
                                        '<button class="layui-btn layui-btn-normal layui-btn-xs" lay-event="updateInvoiceDetail" shiro:hasPermission="user:update">更新抬头</button>'
                                }else{
                                    return ''
                                }
                            },minWidth: 300,align:'center', fixed:'right'}
                ]]
            });
        }
        // 获取查询参数
        function getQueryParams() {
            return {
                orderNo: $searchForm.find('input[name="orderNo"]').val().trim(),
                payOrderNo: $searchForm.find('input[name="payOrderNo"]').val().trim(),
                state: $searchForm.find("select[name='state']").val(),
            };
        }
    })
</script>
src/main/resources/wxP12/apiclient_key.pem
New file
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDfoX/j+3GsXcRf
vVGaTBtewpzyKhkJPXK/QElmEAaGFyjX3PkevbXm5HiL1HXtZnewaLnLOrBiiPyP
PRuuRIyx5Yx7OK2MWGtzj5pByYnvAqWJD048kYdW2sMuF17ARlj+DzRxNNmySCZF
EFI0QKJTz4CQTcYHPOJQXPR5kCzWXgueIWowdUQCRWQZM3xB8l0nhMA3dqMQjZXK
cYbISrNfU5X0aynHhhwozk1onQG19a1o2mF+FCIpqViis6iJ0rVi9NRax+9mvA1x
CAyqAmw2XrYd+uNbAHpxMLDr8A4Nu+e9U9I7Ns3HPuPkGUITTIdShfQWIdKYy+Ye
tx5oTtnjAgMBAAECggEAD9feOqWeuQpvliTQejD4I2+ANPvciDYNQhVo3c022Rsc
7qYeFBp2n6QCEk38CNSgmCPjPc2rn197HR7ELkV2/kd3+2bZjL7a5fX/hLeliQRT
+/DUpx/cSb/34fUP5mlVdrJXEcofviy0YWI1oLQPjAaAYjjQVcTvd6gOPeqD8q/X
Fa3glF/bpm7xNJay7FSaSDywtvaDnhJmTo1norJNV0Sg7Gz5x0eKGa+kW/rm1z8Z
kumhEdaK+yl4mZ47hz/R6xbUyh7BV3xLekC49nAduGgCfU8TQm1Gq8eQdxjmtJeJ
WM7zFy2/1/2ZRMfJ9zdh4B/jiosmikpyxc5tCQIvaQKBgQDwXJascXQ7bLa6Pzmq
Ddby3v+xTJBOsB/NbI275kHEMCNc3ExAhnbwrPcrWyRsWR7mKPClTiK2drFfbo7o
vqEi+pC5vuShxv55riK7ehIcIttG9H0iKFMapJKYsF199JpGoYKyQ8pPtokdd3eH
xYzc65S0/bW9gQBgixs0/DG9dQKBgQDuLj8GLpOkDcckuNgMl31cAv5qvUn8ICNv
/DQPsCO6jWs1c8N7ru3mxVoUkKCDKMsLSak535Mzskg154M+236y8YesgOVfeZse
ZmueUJNJ0STFKGmZMjTlHe5qWZoShknwC6U91/MUfiHUMAiERPZ3bYRCb1WxN0da
/zmcl8UW9wKBgQDYlbW3oWvo+CcXYE1nrJzZsJOagbEvFokxo/V7MRpl3DKhRGj5
Y7DdYh4+1RvW/d9X6eAeNHAXFpDxuz/O/adZS4sJtLd0B0na/0yQvoh/DfGk2D3B
f7CSu2TVbWZxTu2NY4/PbkJo3cRj5viwQRSTJTyUcmFHZ2ydkftsHSv82QKBgQCZ
uD0yxzLAaagCWk4N6mOc083Ro/MxBqj4aIRNL7gPeaAcsmcS1zZxfyOBLEp7/AL3
eQX/9PyzG7ghGhcJpW3jHaEbw2DGOtzTRPTmC0jUFmgt2sQ0fEPJL4UlHEkk9YiY
pVtzS4F102YP5SRKAZw0PfljSym6I1wpLZnIJ+eqxwKBgQDEtVO2Nja7AVl0n99Q
DXW/mrtRZ6ToROXmzcJMiMEN24zstitaB+s01QAasz1VwdFZkZLBHFBzekBSkCJa
xiwZOQXJhzMSL9MYGLTXo/Jhqlsb03923zO45ZFJKnlY7T0W33G0oEfCyAVsXEMM
nemv1UBmk/fW7KrS2Eg46wanwg==
-----END PRIVATE KEY-----
src/test/java/cc/mrbird/febs/ProfitTest.java
@@ -4,6 +4,7 @@
import cc.mrbird.febs.common.enumerates.FlowTypeEnum;
import cc.mrbird.febs.common.enumerates.MoneyFlowTypeEnum;
import cc.mrbird.febs.common.exception.FebsException;
import cc.mrbird.febs.common.utils.AppContants;
import cc.mrbird.febs.common.utils.HttpCurlUtil;
import cc.mrbird.febs.common.utils.MallUtils;
import cc.mrbird.febs.common.utils.RedisUtils;
@@ -11,19 +12,26 @@
import cc.mrbird.febs.mall.entity.*;
import cc.mrbird.febs.mall.mapper.*;
import cc.mrbird.febs.mall.service.*;
import cc.mrbird.febs.mall.vo.MallGoodsListVo;
import cc.mrbird.febs.mall.vo.MallMemberCouponVo;
import cc.mrbird.febs.pay.model.BrandWCPayRequestData;
import cc.mrbird.febs.pay.model.HeaderDto;
import cc.mrbird.febs.pay.model.*;
import cc.mrbird.febs.pay.service.IXcxPayService;
import cc.mrbird.febs.pay.service.WxFaPiaoService;
import cc.mrbird.febs.pay.util.JCEUtil;
import cc.mrbird.febs.pay.util.WechatConfigure;
import cc.mrbird.febs.rabbit.consumer.AgentConsumer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import okhttp3.HttpUrl;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.httpclient.HttpClient;
@@ -35,15 +43,19 @@
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Base64Utils;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@@ -51,7 +63,12 @@
import java.math.BigDecimal;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
@@ -105,77 +122,238 @@
    private WxFaPiaoService wxFaPiaoService;
    @Autowired
    ResourceLoader resourceLoader;
    @Test
    public void rankProfit() throws IOException {
    public void redisTest() throws IOException {//配置开发选项
        String obj = redisUtils.getString("mall_goods_json");//获取JSONARRAY字符串对象
        System.out.println(obj);
        JSONArray jsonArray = JSONUtil.parseArray(obj);//转换成JSONARRAY对象
        List<MallGoodsListVo> mallGoodsListVos = JSONUtil.toList(jsonArray, MallGoodsListVo.class);//转换成对象集合
        System.out.println(mallGoodsListVos.size());
    }
    @Test
    public void rankProfit() throws IOException {//配置开发选项
//        System.out.println(new ClassPathResource("wxP12/apiclient_cert.p12").getFile().exists());
//        System.out.println(new File("src/main/resources/wxP12/apiclient_cert.p12").exists());
//
//        InputStream inputStream = new FileInputStream(file);
//        System.out.println(resourceLoader.getResource("classpath:/wxP12/apiclient_cert.p12").exists());
        KeyPair privateKey = wxFaPiaoService.getPrivateKey();
        PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3();
        HeaderDto headerDto = new HeaderDto();
        headerDto.setCallback_url("https://api.blnka.cn/api/xcxPay/fapiaoCallBack");
        headerDto.setShow_fapiao_cell(true);
        headerDto.setShow_fapiao_cell(false);
        String parseObj = JSONUtil.parseObj(headerDto).toString();
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/merchant/development-config";
        String postStr = wxFaPiaoService.createAuthorization(
                "POST",
                canonicalUrl,
                parseObj,
                privateKey
        );
        // 创建httppost
        String postStr = null;
        try {
            HttpClient httpClient = new HttpClient();
            PostMethod post = new PostMethod("https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/development-config");
            post.setRequestHeader("Accept", "application/json");
            post.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36");
            post.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
            post.setRequestHeader("Connection", "keep-alive");
            post.setRequestHeader("Authorization",  "WECHATPAY2-SHA256-RSA2048 "+postStr);
            RequestEntity entity = new StringRequestEntity(parseObj, "text/html", "utf-8");
            post.setRequestEntity(entity);
            httpClient.executeMethod(post);
            String responseBodyAsString = post.getResponseBodyAsString();
            cn.hutool.json.JSONObject maps = JSONUtil.parseObj(responseBodyAsString);
            System.out.println(maps);
        } catch (IOException e) {
            postStr = wxFaPiaoService.createAuthorization(
                    "PATCH",
                    baseUrl+canonicalUrl,
                    parseObj,
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        String token = AppContants.FP_TOKEN_HEADER_TYPE+postStr;
        System.out.println("WECHATPAY2-SHA256-RSA2048 "+postStr);
        String s = wxFaPiaoService.sendPatch(baseUrl + canonicalUrl, parseObj, token);
        System.out.println(s);
    }
    @Test
    public void rankProfit03() throws IOException {//创建电子发票卡券模板
        FPCardTemplateDto fpCardTemplateDto = new FPCardTemplateDto();
        fpCardTemplateDto.setCard_appid("wxad2fdb2fcad10fb2");
        FPCardTemplateInformationDto fpCardTemplateInformationDto = new FPCardTemplateInformationDto();
        fpCardTemplateInformationDto.setLogo_url("http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFNjakmxibMLGWpXrEXB33367o7zHN0CwngnQY7zb7g/0");
        fpCardTemplateInformationDto.setPayee_name("发票测试");
        FPCardTemplateCustomCellDto fpCardTemplateCustomCellDto = new FPCardTemplateCustomCellDto();
        fpCardTemplateCustomCellDto.setWords("电子发票");
        fpCardTemplateCustomCellDto.setDescription("查看发票");
        fpCardTemplateCustomCellDto.setJump_url("http://www.qq.com");
        fpCardTemplateCustomCellDto.setMiniprogram_path("gh_86a091e50ad4@app");
        fpCardTemplateCustomCellDto.setMiniprogram_user_name("pages/xxxPage");
        fpCardTemplateInformationDto.setCustom_cell(fpCardTemplateCustomCellDto);
        fpCardTemplateDto.setCard_template_information(fpCardTemplateInformationDto);
        String parseObj = JSONUtil.parseObj(fpCardTemplateDto).toString();
        System.out.println(parseObj);
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/card-template";
        PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3();
        String postStr = null;
        try {
            postStr = wxFaPiaoService.createAuthorization(
                    "POST",
                    baseUrl+canonicalUrl,
                    parseObj,
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        String token = AppContants.FP_TOKEN_HEADER_TYPE+postStr;
        System.out.println("WECHATPAY2-SHA256-RSA2048 "+postStr);
        String s = wxFaPiaoService.sendPost(baseUrl + canonicalUrl, parseObj, "WECHATPAY2-SHA256-RSA2048 "+postStr);
        System.out.println(s);
    }
    @Test
    public void rankProfit04() throws IOException {//获取用户填写的抬头
        //初始化请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("scene","WITH_WECHATPAY");
        params.put("fapiao_apply_id","4200002059202401191880748568");
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/user-title";
        String urlparm = baseUrl+canonicalUrl;//有参数的get请求,加密是需要把参数加载进去URL
        List<BasicNameValuePair> parameters = new ArrayList<>();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
        }
        StringBuilder dataparm = new StringBuilder();
        params.forEach((k, v) -> dataparm.append("&" + k + "=" + v));
        String string = dataparm.toString();
        if (!"".equals(string)) {
            urlparm = baseUrl+canonicalUrl + "?" + dataparm.substring(1);
        }
        PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3();
        String postStr = null;
        try {
            postStr = wxFaPiaoService.createAuthorization(
                    "GET",
                    urlparm,
                    "",
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        String s = wxFaPiaoService.sendGet(baseUrl+canonicalUrl, params, "WECHATPAY2-SHA256-RSA2048 "+postStr);
        System.out.println(s);
    }
    @Test
    public void rankProfit2() throws IOException {//查询配置开发选项
        PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3();
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/merchant/development-config";
        String postStr = null;
        try {
            postStr = wxFaPiaoService.createAuthorization(
                    "GET",
                    baseUrl+canonicalUrl,
                    "",
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        System.out.println("WECHATPAY2-SHA256-RSA2048"+postStr);
        String s = wxFaPiaoService.sendGet(baseUrl + canonicalUrl, new HashMap<>(), "WECHATPAY2-SHA256-RSA2048 "+postStr);
        System.out.println(s);
//        try {
//            HttpClient httpClient = new HttpClient();
//            GetMethod method = new GetMethod(baseUrl+canonicalUrl);
//            method.setRequestHeader("Accept", "application/json");
//            method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36");
//            method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
//            method.setRequestHeader("Connection", "keep-alive");
//            method.setRequestHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 "+postStr);
//            httpClient.executeMethod(method);
//            System.out.println(method);
//            String responseBodyAsString = method.getResponseBodyAsString();
//            cn.hutool.json.JSONObject maps = JSONUtil.parseObj(responseBodyAsString);
//            System.out.println(maps);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
    }
    @Test
    public void rankProfit2() throws IOException {
        KeyPair privateKey = wxFaPiaoService.getPrivateKey();
    public void rankProfit2_01() throws IOException, ParseException, GeneralSecurityException {//获取平台证书
        JCEUtil.removeCryptographyRestrictions();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        PrivateKey privateKey = wxFaPiaoService.getPrivateKeyV3();
        String baseUrl = "https://api.mch.weixin.qq.com";
        String canonicalUrl = "/v3/new-tax-control-fapiao/merchant/development-config";
        String postStr = wxFaPiaoService.createAuthorization(
                "GET",
                canonicalUrl,
                "",
                privateKey
        );
        String canonicalUrl = "/v3/certificates";
        String postStr = null;
        try {
            HttpClient httpClient = new HttpClient();
            GetMethod method = new GetMethod(baseUrl+canonicalUrl);
            method.setRequestHeader("Accept", "application/json");
            method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36");
            method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            method.setRequestHeader("Connection", "keep-alive");
            method.setRequestHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 "+postStr);
            httpClient.executeMethod(method);
            System.out.println(method);
            String responseBodyAsString = method.getResponseBodyAsString();
            postStr = wxFaPiaoService.createAuthorization(
                    "GET",
                    baseUrl+canonicalUrl,
                    "",
                    privateKey
            );
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        String responseBodyAsString = wxFaPiaoService.sendGet(baseUrl + canonicalUrl, new HashMap<>(), "WECHATPAY2-SHA256-RSA2048 "+postStr);
        //完成签名并执行请求
        X509Certificate x509Certificate = null;
        try {
            cn.hutool.json.JSONObject maps = JSONUtil.parseObj(responseBodyAsString);
            System.out.println(maps);
            FPCertificateVo certificateVo = com.alibaba.fastjson.JSONObject.parseObject(responseBodyAsString, FPCertificateVo.class);
            for (FPCertificates certificates : certificateVo.getData()) {
                if (format.parse(certificates.getEffective_time()).before(new Date())
                        && format.parse(certificates.getExpire_time()).after(new Date())) {
                    FPEncryptCertificate encrypt_certificate = certificates.getEncrypt_certificate();
                    //解密
                    AesUtil aesUtil = new AesUtil("daL341aN5orDt13puXadsAf2rpuX12v3".getBytes("utf-8"));
                    String pulicKey = aesUtil.decryptToString(
                            encrypt_certificate.getAssociated_data().getBytes("utf-8"),
                            encrypt_certificate.getNonce().getBytes("utf-8"),
                            encrypt_certificate.getCiphertext());
                    //获取平台证书
                    final CertificateFactory cf = CertificateFactory.getInstance("X509");
                    ByteArrayInputStream inputStream = new ByteArrayInputStream(pulicKey.getBytes(StandardCharsets.UTF_8));
                    x509Certificate = (X509Certificate) cf.generateCertificate(inputStream);
                    System.out.println(x509Certificate);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        String timestamp = "1705548628";
        String nonce = "sJBCGwYIM8WFJCpTE3gDGifC6VOyrPFY";
        String body = "{\"id\":\"f23942b4-b128-56ef-9172-2da8204b5159\",\"create_time\":\"2024-01-18T11:30:28+08:00\",\"resource_type\":\"encrypt-resource\",\"event_type\":\"FAPIAO.USER_APPLIED\",\"summary\":\"用户已申请开票\",\"resource\":{\"original_type\":\"fapiao\",\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"X7XjgHrepsnVpFqNgjl9EfRbJIdMUf15izFrtnvEf7So3SWlg6CFNofcQAJE+iL1XfkFk7DNNW0rILNbY1cGWGlAlKbbPhv5SFEakZVe9GZsSTd4EC5zTk9D6g+FTS6EQNxCkj4ut2WyYaSSqJjHgYaqdKzLNPSTGRVQnynafFE=\",\"associated_data\":\"fapiao\",\"nonce\":\"X7daAHdejNsm\"}}";
        Map<String, Object> requestBody = JSONUtil.parseObj(body);
        String signature = "WECHATPAY/SIGNTEST/9WwfXW/noMdzDPcOrFD51Bf5YIThXLTtLCl5hoGlSfIBdY2UpE+5eHLs0XyF7y2cQc4OlzTzDDxSMTp/p/8ZenE2hMBaQjxVrdbTtrOLA1h13/WjCNaTr1URTqQ5+IF5bLCxAW8BmZ0jEqpmI/HYR3wdK8/7W91c1zfKODYgkvJzxFzd8OWi2GtELE5tLamkOLyb0GEcsGww2DUOypO5HjNyITgsy9R00w3OH92UInCD8Z6c5BAsQNySqFK8N52y38AlIWTtKROHmJtu+kHbt+nMOU0kbB5bZcPAptsnVYpp9KMmjMrmyjiKtHZb2TGIWiN/L+bgceyN3g==";
        //构造验签名串
        String signatureStr = timestamp + "\n" + nonce + "\n" + com.alibaba.fastjson.JSONObject.toJSONString(requestBody) + "\n";
        System.out.println(signature.length());
        System.out.println(signature);
        System.out.println(signatureStr.length());
        System.out.println(signatureStr);
        // 加载SHA256withRSA签名器
        Signature signer = Signature.getInstance("SHA256withRSA");
        // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法)
        signer.initVerify(x509Certificate);
        // 把我们构造的验签名串更新到签名器中
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证
        boolean result = signer.verify(Base64Utils.decodeFromString(signature));
        System.out.println(result);
    }