Browse Source

微信电子发票

skyline 2 years ago
parent
commit
599662723e

+ 9 - 3
admin/src/main/java/com/kym/admin/controller/FinanceController.java

@@ -39,10 +39,16 @@ public class FinanceController {
     }
 
     @SysLog("开具发票")
-    @GetMapping("/handleInvoice")
-    R<?> handleInvoice() {
-        return R.success(wxPayService.baseInformation());
+    @GetMapping("/handleInvoice/{invoiceId}")
+    R<?> handleInvoice(@PathVariable("invoiceId") String invoiceId) {
+        wxPayService.fapiaoApplication(invoiceId);
+        return R.success();
     }
 
+    @SysLog("下载发票")
+    @GetMapping("/downloadInvoice/{invoiceId}")
+    R<?> downloadInvoice(@PathVariable("invoiceId") String invoiceId) {
+        return R.success(wxPayService.downloadInvoice(invoiceId));
+    }
 
 }

+ 4 - 0
entity/pom.xml

@@ -37,6 +37,10 @@
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-annotations</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.alibaba.fastjson2</groupId>
             <artifactId>fastjson2</artifactId>

+ 7 - 21
entity/src/main/java/com/kym/entity/miniapp/Invoice.java

@@ -2,10 +2,11 @@ package com.kym.entity.miniapp;
 
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
 import com.kym.entity.BaseEntity;
+import com.kym.entity.typehandle.InvoiceOrderDetailListTypeHandle;
 import com.kym.entity.wechat.InvoiceOrderDetail;
 import lombok.Getter;
+import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.experimental.Accessors;
 
@@ -19,9 +20,10 @@ import java.util.List;
  * @author skyline
  * @since 2023-09-15
  */
-@Getter
 @Setter
-@TableName("t_invoice")
+@Getter
+@NoArgsConstructor
+@TableName(value = "t_invoice", autoResultMap = true)
 @Accessors(chain = true)
 public class Invoice extends BaseEntity {
     public static final String TYPE_个人 = "INDIVIDUAL";
@@ -48,96 +50,80 @@ public class Invoice extends BaseEntity {
     /**
      * 发票关联订单详情
      */
-    @TableField(typeHandler = JacksonTypeHandler.class)
+    @TableField(typeHandler = InvoiceOrderDetailListTypeHandle.class)
     private List<InvoiceOrderDetail> orderDetails;
 
     /**
      * 累积充电量(度)
      */
     private Double totalPower;
-
     /**
      * 累积总金额(分)
      */
     private Integer totalMoney;
-
     /**
      * 累积电费(分)
      */
     private Integer elecMoney;
-
     /**
      * 累积服务费(分)
      */
     private Integer serviceMoney;
-
     /**
      * 服务费优惠金额(分)
      */
     private Integer serviceMoneyDiscount;
-
     /**
      * 接收发票邮箱
      */
     private String email;
-
     /**
      * 电话
      */
     private String phone;
-
     /**
      * 发票类型:INDIVIDUAL-个人 ORGANIZATION-企业
      */
     private String invoiceType;
-
     /**
      * 发票抬头
      */
     private String invoiceTitle;
-
     /**
      * 公司税号
      */
     private String taxId;
-
     /**
      * 公司地址
      */
     private String address;
-
     /**
      * 开户银行
      */
     private String bankName;
-
     /**
      * 银行账户
      */
     private String bankAccount;
-
     /**
      * 发票金额(单位:分)
      */
     private Integer invoiceAmount;
-
     /**
      * 税额详情信息
      */
     private String taxInfo;
-
     /**
      * 开票人
      */
     private String biller;
-
     /**
      * 发票状态
      */
     private Integer status;
-
     /**
      * 备注
      */
     private String remark;
+
 }

+ 30 - 0
entity/src/main/java/com/kym/entity/typehandle/InvoiceOrderDetailListTypeHandle.java

@@ -0,0 +1,30 @@
+package com.kym.entity.typehandle;
+
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.kym.entity.wechat.InvoiceOrderDetail;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author skyline
+ * @description 发票中订单详情序列化处理
+ * https://github.com/baomidou/mybatis-plus-samples/blob/master/mybatis-plus-sample-typehandler/src/main/java/com/baomidou/mybatisplus/samples/typehandler/WalletListTypeHandler.java
+ * @date 2023-10-31 16:34
+ */
+public class InvoiceOrderDetailListTypeHandle extends JacksonTypeHandler {
+    public InvoiceOrderDetailListTypeHandle(Class<?> type) {
+        super(type);
+    }
+
+    @Override
+    protected Object parse(String json) {
+        try {
+            return getObjectMapper().readValue(json, new TypeReference<List<InvoiceOrderDetail>>() {
+            });
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 2 - 2
entity/src/main/java/com/kym/entity/wechat/FaPiao.java

@@ -136,7 +136,7 @@ public class FaPiao {
              * 若使用在电子发票商户平台配置的商品类型,需要从接口【获取商户可开具的商品和服务税收分类编码对照表】获得商户已配置的编码;
              * 若该行为折扣行,必须与被折扣行的编码相同。
              */
-            private String taxCode;
+            private String tax_code;
 
             /**
              * 数量,展示在发票中间的数量列,单位为10^-8^,100000000表示数量为1。
@@ -147,7 +147,7 @@ public class FaPiao {
             /**
              * 单行金额和税费的和,折扣行的金额为负数,非折扣行的金额为正数,单位:分
              */
-            private int totalAmount;
+            private int total_amount;
 
             /**
              * 指定该发票行是否折扣行,折扣行必须是被折扣行的下一行

+ 12 - 5
entity/src/main/java/com/kym/entity/wechat/FapiaoApplications.java

@@ -1,5 +1,7 @@
 package com.kym.entity.wechat;
 
+import lombok.Data;
+
 import java.util.List;
 
 /**
@@ -7,11 +9,13 @@ import java.util.List;
  * @description 查询电子发票
  * @date 2023-10-07 16:02
  */
+@Data
 public class FapiaoApplications {
     private int total_count;
     private List<FapiaoEntity> fapiao_information;
 
-    class FapiaoEntity {
+    @Data
+    public class FapiaoEntity {
         private String fapiao_id;
         /**
          * ISSUE_ACCEPTED: 开票申请已受理
@@ -70,7 +74,8 @@ public class FapiaoApplications {
         private String remark;
     }
 
-    class FapiaoInfo {
+    @Data
+    public class FapiaoInfo {
         /**
          * 发票代码
          */
@@ -94,7 +99,8 @@ public class FapiaoApplications {
         private String fapiao_time;
     }
 
-    class CardInfo {
+    @Data
+    public class CardInfo {
         private String card_appid;
         private String card_openid;
         private String card_id;
@@ -108,7 +114,8 @@ public class FapiaoApplications {
         private String card_status;
     }
 
-    class FapiaoItem {
+    @Data
+    public class FapiaoItem {
         /**
          * 税局侧规定的货物或应税劳务、服务税收分类编码
          */
@@ -153,7 +160,7 @@ public class FapiaoApplications {
          * NORMAL_ZERO_RATED: 普通零税率
          * EXPORT_ZERO_RATED: 出口零税率
          */
-        private int tax_prefer_mark;
+        private String tax_prefer_mark;
         /**
          * 指定该发票行是否折扣行,折扣行一定是被折扣行的下一行
          */

+ 1 - 1
mapper/src/main/resources/mappers/miniapp/InvoiceMapper.xml

@@ -8,7 +8,7 @@
         <result column="applyId" property="applyId" />
         <result column="user_id" property="userId" />
         <result column="openid" property="openid" />
-        <result column="order_details" property="orderDetails" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
+        <result column="order_details" property="orderDetails" typeHandler="com.kym.entity.typehandle.InvoiceOrderDetailListTypeHandle" />
         <result column="total_power" property="totalPower" />
         <result column="total_money" property="totalMoney" />
         <result column="elec_money" property="elecMoney" />

+ 11 - 10
miniapp/src/main/java/com/kym/miniapp/controller/InvoiceController.java

@@ -29,16 +29,6 @@ public class InvoiceController {
         this.wxPayService = wxPayService;
     }
 
-    @GetMapping("test1")
-    R<?> test1(HttpServletRequest request) {
-        return R.success(wxPayService.devConfig());
-    }
-
-    @GetMapping("test2")
-    R<?> test2(HttpServletRequest request) {
-        return R.success(wxPayService.getDevConfig());
-    }
-
     /**
      * 用户发票抬头填写完成通知
      *
@@ -86,4 +76,15 @@ public class InvoiceController {
         return R.success();
     }
 
+    /**
+     * 下载发票
+     *
+     * @param invoiceId
+     * @return
+     */
+    @GetMapping("/downloadInvoice/{invoiceId}")
+    R<?> downloadInvoice(@PathVariable("invoiceId") String invoiceId) {
+        return R.success(wxPayService.downloadInvoice(invoiceId));
+    }
+
 }

+ 3 - 0
miniapp/src/main/resources/application.yml

@@ -3,6 +3,9 @@ spring:
     active: dev
   application:
     name: miniapp
+  jackson:
+    serialization:
+      fail-on-empty-beans: false
 
 mybatis-plus:
   mapper-locations: classpath:mappers/**/*.xml

+ 0 - 9
service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

@@ -91,15 +91,6 @@ public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> impl
                 .setTotalPower(orders.stream().mapToDouble(ChargeOrder::getTotalPower).sum())   // 总电量
                 .setServiceMoneyDiscount(orders.stream().mapToInt(ChargeOrder::getServiceMoneyDiscount).sum()); //总优惠服务费
 
-//                .setInvoiceType(String.valueOf(params.getInvoiceType()))    // 类型
-//                .setInvoiceTitle(params.getInvoiceTitle())  //抬头
-//                .setTaxId(params.getTaxId())    // 税号
-//                .setAddress(params.getAddress())    // 公司地址
-//                .setBankName(params.getBankName())  // 开户银行
-//                .setBankAccount(params.getBankAccount())  // 银行账户
-//                .setPhone(params.getPhone())    // 公司电话
-//                .setRemark(params.getRemark())  // 备注
-//                .setEmail(params.getEmail());    // 邮箱
         baseMapper.insert(invoice);
         return wxPayService.titleUrl(invoice);
 

+ 9 - 3
service/src/main/java/com/kym/service/wechat/WxPayService.java

@@ -2,16 +2,16 @@ package com.kym.service.wechat;
 
 import com.alibaba.fastjson2.JSONObject;
 import com.kym.entity.miniapp.Invoice;
-import com.kym.entity.wechat.FaPiao;
-import com.kym.entity.wechat.InvoiceBaseInfo;
-import com.kym.entity.wechat.TitleUrl;
+import com.kym.entity.wechat.*;
 import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
 import com.wechat.pay.java.service.refund.model.Refund;
 import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.SneakyThrows;
 import org.springframework.http.ResponseEntity;
 
 import java.io.IOException;
+import java.util.Map;
 
 /**
  * @author skyline
@@ -53,4 +53,10 @@ public interface WxPayService {
     void wxInvoiceNotify(Object[] notifyRes);
 
     void invoiceNotify(HttpServletRequest request);
+
+    Map<String, String> downloadInvoice(String invoiceId);
+
+    FapiaoDownload getFapiaoDownloadInfo(String applyId);
+
+    FapiaoApplications queryFapiao(String applyId);
 }

+ 48 - 18
service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -103,13 +103,17 @@ public class WxPayServiceImpl implements WxPayService {
 
     private final ActivityService activityService;
 
+
     /**
      * 微信支付专用,支持自动签名验签解密等
      */
     private OkHttpClientAdapter wxHttpClient;
 
 
-    public WxPayServiceImpl(WxPayConfig conf, WxFapiaoConfig fapiaoConfig, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService, RefundLogService refundLogService, InvoiceService invoiceService, InvoiceTitleService invoiceTitleService, EnPlusService enPlusService, ActivityService activityService) {
+    public WxPayServiceImpl(WxPayConfig conf, WxFapiaoConfig fapiaoConfig, WalletDetailService walletDetailService,
+                            PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService,
+                            RefundLogService refundLogService, InvoiceService invoiceService, InvoiceTitleService invoiceTitleService,
+                            EnPlusService enPlusService, ActivityService activityService) {
         this.conf = conf;
         this.fapiaoConfig = fapiaoConfig;
         this.walletDetailService = walletDetailService;
@@ -753,20 +757,34 @@ public class WxPayServiceImpl implements WxPayService {
      * 开具电子发票
      */
     @Override
+    @DS("db-miniapp")
     public void fapiaoApplication(String invoiceId) {
         var invoice = invoiceService.lambdaQuery().eq(Invoice::getId, invoiceId).one();
 
         // 订单金额+ ,服务费优惠金额- 服务费优惠金额单独一个item,金额为负数
-
+        var itemList = new ArrayList<FaPiao.FaPiaoInfomation.IssueItem>();
         // 电费 税务商品编码:1100101020000000000
         var elecMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
-        var elecMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(elecMoney).setTaxCode("1100101020000000000");
+        // 必须为正数
+        if (elecMoney > 0) {
+            var elecMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotal_amount(elecMoney).setTax_code("1100101020000000000");
+            itemList.add(elecMoneyItem);
+        }
         // 服务费 税务商品编码:3040100000000000000
-        var serviceMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
-        var serviceMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(serviceMoney).setTaxCode("3040100000000000000");
+        var serviceMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getServiceMoney).sum();
+        // 必须为正数
+        if (serviceMoney > 0) {
+            var serviceMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotal_amount(serviceMoney).setTax_code("3040100000000000000");
+            itemList.add(serviceMoneyItem);
+        }
+
         // 服务费优惠 税务商品编码:3040100000000000000
         var serviceMoneyDiscount = -invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getServiceMoneyDiscount).sum();
-        var serviceMoneyDiscountItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(serviceMoneyDiscount).setTaxCode("3040100000000000000");
+        // 必须为正数
+        if (serviceMoneyDiscount > 0) {
+            var serviceMoneyDiscountItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotal_amount(serviceMoneyDiscount).setTax_code("3040100000000000000");
+            itemList.add(serviceMoneyDiscountItem);
+        }
 
         var headers = new HttpHeaders();
         headers.addHeader("Accept", "application/json");
@@ -788,7 +806,7 @@ public class WxPayServiceImpl implements WxPayService {
         // fapiao_id为invoice.id
         fapiaoInformation.setFapiao_id(String.valueOf(invoice.getId()))
                 .setTotal_amount(invoice.getInvoiceAmount())
-                .setItems(List.of(elecMoneyItem, serviceMoneyItem, serviceMoneyDiscountItem));
+                .setItems(itemList);
 
         // 请求参数
         var fapiao = new FaPiao()
@@ -803,7 +821,7 @@ public class WxPayServiceImpl implements WxPayService {
 
     /**
      * 微信开票结果通知
-     * event_tyoe为FAPIAO.ISSUED
+     * event_type为FAPIAO.ISSUED
      *
      * @param notifyRes
      * @return
@@ -844,21 +862,31 @@ public class WxPayServiceImpl implements WxPayService {
                 // 发票开具结果
             case "FAPIAO.ISSUED":
                 wxInvoiceNotify(notifyRes);
-            default:
-                throw new IllegalStateException("Unexpected value: " + notification.getEventType());
         }
     }
 
 
-    // TODO: 2023-10-07 下载发票
-    void downloadInvoice(String invoiceId) {
+    /**
+     * 下载发票,下载链接30s有效
+     *
+     * @param invoiceId
+     * @return
+     */
+    @Override
+    public Map<String, String> downloadInvoice(String invoiceId) {
         var invoice = invoiceService.getById(invoiceId);
-        // TODO: 2023-10-07 查询发票下载信息,下载链接30s有效
         var download = getFapiaoDownloadInfo(invoice.getApplyId());
         var downloadInfo = download.getFapiao_download_info_list().get(0);
-        var params = "?token=%s&mchid=%s&openid=%s&invoice_code=%s&invoice_no=%s&fapiao_id=%s";
-        var inputStream = wxHttpClient.download(downloadInfo.getDownload_url().concat(params.formatted("", conf.getMchid(), "", "", "", invoice.getId())));
-
+        // 查询电子发票
+        var fapiaoApplications = queryFapiao(invoice.getApplyId());
+
+        var params = "&mchid=%s&openid=%s&invoice_code=%s&invoice_no=%s&fapiao_id=%s";
+        // openid 通过查询电子发票接口获取的card_openid
+        var fapiao = fapiaoApplications.getFapiao_information().get(0);
+        var downloadUrl = downloadInfo.getDownload_url().concat(params.formatted(conf.getMchid(),
+                fapiao.getCard_information().getCard_openid(), fapiao.getBlue_fapiao().getFapiao_code(),
+                fapiao.getBlue_fapiao().getFapiao_number(), invoice.getId()));
+        return Map.of("downloadUrl", downloadUrl);
     }
 
     /**
@@ -867,7 +895,8 @@ public class WxPayServiceImpl implements WxPayService {
      * @param applyId
      * @return
      */
-    private FapiaoDownload getFapiaoDownloadInfo(String applyId) {
+    @Override
+    public FapiaoDownload getFapiaoDownloadInfo(String applyId) {
         var downloadUrl = fapiaoConfig.getFapiaoFiles().formatted(applyId);
         var headers = new HttpHeaders();
         headers.addHeader("Accept", "application/json");
@@ -880,7 +909,8 @@ public class WxPayServiceImpl implements WxPayService {
      *
      * @param applyId
      */
-    private FapiaoApplications queryFapiao(String applyId) {
+    @Override
+    public FapiaoApplications queryFapiao(String applyId) {
         var headers = new HttpHeaders();
         headers.addHeader("Accept", "application/json");
         headers.addHeader("Content-Type", "application/json");