From cf59c548d06bcb8dd824a97403658ca90164abfd Mon Sep 17 00:00:00 2001 From: Helius <wangdoubleone@gmail.com> Date: Tue, 23 Jan 2024 10:06:08 +0800 Subject: [PATCH] Merge branch 'blnka' into blnka-vip --- src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java | 36 src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java | 31 src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java | 22 src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java | 11 src/test/java/cc/mrbird/febs/ProfitTest.java | 276 +++++- src/main/java/cc/mrbird/febs/pay/util/RequestBodyUtils.java | 76 + src/main/java/cc/mrbird/febs/pay/util/JCEUtil.java | 70 + src/main/resources/templates/febs/views/modules/order/invoiceList.html | 233 +++++ pom.xml | 10 src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java | 21 src/main/java/cc/mrbird/febs/mall/entity/MallOrderItem.java | 3 src/main/java/cc/mrbird/febs/mall/vo/AdminInvoiceDetailVo.java | 47 + src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java | 44 src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java | 26 src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java | 17 src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java | 20 src/main/resources/templates/febs/views/modules/order/invoiceDetail.html | 248 ++++++ src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java | 43 + src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java | 65 + src/main/resources/mapper/modules/MallInvoiceMapper.xml | 67 + src/main/resources/wxP12/apiclient_key.pem | 28 src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java | 16 src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java | 4 src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java | 10 src/main/resources/mapper/modules/MallOrderInfoMapper.xml | 8 src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java | 26 src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java | 13 src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java | 30 src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java | 13 src/main/java/cc/mrbird/febs/common/utils/AppContants.java | 3 src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java | 23 src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java | 160 ++++ src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java | 4 src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java | 33 src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java | 615 +++++++++++++- src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java | 22 36 files changed, 2,230 insertions(+), 144 deletions(-) diff --git a/pom.xml b/pom.xml index 3f7a5b4..af6a5ee 100644 --- a/pom.xml +++ b/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> diff --git a/src/main/java/cc/mrbird/febs/common/utils/AppContants.java b/src/main/java/cc/mrbird/febs/common/utils/AppContants.java index 43d6356..a212e5c 100644 --- a/src/main/java/cc/mrbird/febs/common/utils/AppContants.java +++ b/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";//证书编号 } diff --git a/src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java b/src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java index 4265f7a..cff652b 100644 --- a/src/main/java/cc/mrbird/febs/mall/controller/AdminMallOrderController.java +++ b/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); + } + } diff --git a/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java b/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java index bef7b04..3f9d310 100644 --- a/src/main/java/cc/mrbird/febs/mall/controller/AdminSystemController.java +++ b/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") diff --git a/src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java b/src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java new file mode 100644 index 0000000..5c92b77 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/controller/ApiMallInvoiceController.java @@ -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); + } + +} diff --git a/src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java b/src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java index 678afeb..a91c4db 100644 --- a/src/main/java/cc/mrbird/febs/mall/controller/ViewMallOrderController.java +++ b/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"); + } + } diff --git a/src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java b/src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java new file mode 100644 index 0000000..9c04ce0 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/dto/AdminMallInvoiceDto.java @@ -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; + +} diff --git a/src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java b/src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java new file mode 100644 index 0000000..85c38f5 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/dto/ApiMallInvoiceDto.java @@ -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; + +} diff --git a/src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java b/src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java new file mode 100644 index 0000000..d13954c --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/entity/MallInvoice.java @@ -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路径 +} diff --git a/src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java b/src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java index cef66a2..56224de 100644 --- a/src/main/java/cc/mrbird/febs/mall/entity/MallOrderInfo.java +++ b/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; diff --git a/src/main/java/cc/mrbird/febs/mall/entity/MallOrderItem.java b/src/main/java/cc/mrbird/febs/mall/entity/MallOrderItem.java index d2a07b9..3bdbce5 100644 --- a/src/main/java/cc/mrbird/febs/mall/entity/MallOrderItem.java +++ b/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; } diff --git a/src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java b/src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java new file mode 100644 index 0000000..74c7d8a --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/mapper/MallInvoiceMapper.java @@ -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); +} diff --git a/src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java b/src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java index 30edcae..841527e 100644 --- a/src/main/java/cc/mrbird/febs/mall/mapper/MallOrderInfoMapper.java +++ b/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); } diff --git a/src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java b/src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java index 2c84ea3..fc4cb16 100644 --- a/src/main/java/cc/mrbird/febs/mall/quartz/WxxcxJob.java +++ b/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(); + } + } diff --git a/src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java b/src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java new file mode 100644 index 0000000..4be679a --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/service/MallInvoiceService.java @@ -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); +} diff --git a/src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java b/src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java new file mode 100644 index 0000000..1739659 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/service/impl/MallInvoiceServiceImpl.java @@ -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(); + } +} diff --git a/src/main/java/cc/mrbird/febs/mall/vo/AdminInvoiceDetailVo.java b/src/main/java/cc/mrbird/febs/mall/vo/AdminInvoiceDetailVo.java new file mode 100644 index 0000000..043767f --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/vo/AdminInvoiceDetailVo.java @@ -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;//订单编号 + +} diff --git a/src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java b/src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java new file mode 100644 index 0000000..86d138e --- /dev/null +++ b/src/main/java/cc/mrbird/febs/mall/vo/ApiMallInvoiceVo.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java b/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java index 6830a9f..0d95241 100644 --- a/src/main/java/cc/mrbird/febs/pay/controller/XcxPayController.java +++ b/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()); diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java new file mode 100644 index 0000000..ece6db2 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateCustomCellDto.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java new file mode 100644 index 0000000..525fc96 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateDto.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java new file mode 100644 index 0000000..56f2851 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCardTemplateInformationDto.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java b/src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java new file mode 100644 index 0000000..2490f68 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCertificateVo.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java b/src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java new file mode 100644 index 0000000..07d8e95 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPCertificates.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java b/src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java new file mode 100644 index 0000000..fe4005a --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPEncryptCertificate.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java b/src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java new file mode 100644 index 0000000..48889ec --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/model/FPUserTitleDto.java @@ -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; +} diff --git a/src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java b/src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java index 960e8e2..7b73f49 100644 --- a/src/main/java/cc/mrbird/febs/pay/service/WxFaPiaoService.java +++ b/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; } diff --git a/src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java b/src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java index a35aa8d..935ab4a 100644 --- a/src/main/java/cc/mrbird/febs/pay/service/impl/WxFaPiaoServiceImpl.java +++ b/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); +// +// } } } diff --git a/src/main/java/cc/mrbird/febs/pay/util/JCEUtil.java b/src/main/java/cc/mrbird/febs/pay/util/JCEUtil.java new file mode 100644 index 0000000..1bb2695 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/util/JCEUtil.java @@ -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); + } +} diff --git a/src/main/java/cc/mrbird/febs/pay/util/RequestBodyUtils.java b/src/main/java/cc/mrbird/febs/pay/util/RequestBodyUtils.java new file mode 100644 index 0000000..eff106d --- /dev/null +++ b/src/main/java/cc/mrbird/febs/pay/util/RequestBodyUtils.java @@ -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); + } + +} diff --git a/src/main/resources/mapper/modules/MallInvoiceMapper.xml b/src/main/resources/mapper/modules/MallInvoiceMapper.xml new file mode 100644 index 0000000..e717aa3 --- /dev/null +++ b/src/main/resources/mapper/modules/MallInvoiceMapper.xml @@ -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> \ No newline at end of file diff --git a/src/main/resources/mapper/modules/MallOrderInfoMapper.xml b/src/main/resources/mapper/modules/MallOrderInfoMapper.xml index d082bf8..e1f180e 100644 --- a/src/main/resources/mapper/modules/MallOrderInfoMapper.xml +++ b/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> \ No newline at end of file diff --git a/src/main/resources/templates/febs/views/modules/order/invoiceDetail.html b/src/main/resources/templates/febs/views/modules/order/invoiceDetail.html new file mode 100644 index 0000000..215ac03 --- /dev/null +++ b/src/main/resources/templates/febs/views/modules/order/invoiceDetail.html @@ -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> \ No newline at end of file diff --git a/src/main/resources/templates/febs/views/modules/order/invoiceList.html b/src/main/resources/templates/febs/views/modules/order/invoiceList.html new file mode 100644 index 0000000..5a19dee --- /dev/null +++ b/src/main/resources/templates/febs/views/modules/order/invoiceList.html @@ -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"></i> + </div> + <div class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain table-action" id="reset"> + <i class="layui-icon"></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"></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> \ No newline at end of file diff --git a/src/main/resources/wxP12/apiclient_key.pem b/src/main/resources/wxP12/apiclient_key.pem new file mode 100644 index 0000000..a754832 --- /dev/null +++ b/src/main/resources/wxP12/apiclient_key.pem @@ -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----- diff --git a/src/test/java/cc/mrbird/febs/ProfitTest.java b/src/test/java/cc/mrbird/febs/ProfitTest.java index 9ee9405..8a8d677 100644 --- a/src/test/java/cc/mrbird/febs/ProfitTest.java +++ b/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); } -- Gitblit v1.9.1