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