Răsfoiți Sursa

发票相关代码

skyline 2 ani în urmă
părinte
comite
04648fcac6

+ 5 - 0
admin/src/main/java/com/kym/admin/controller/FinanceController.java

@@ -38,5 +38,10 @@ public class FinanceController {
         return R.success();
     }
 
+    @GetMapping("handleInvoice")
+    R<?> handleInvoice(){
+        return R.success(wxPayService.baseInformation());
+    }
+
 
 }

+ 22 - 0
common/src/main/java/com/kym/common/config/WxFapiaoConfig.java

@@ -0,0 +1,22 @@
+package com.kym.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author skyline
+ * @description 微信发票
+ * @date 2023-07-22 23:09
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "wechat.fapiao")
+public class WxFapiaoConfig {
+
+    private String baseInformation;
+    private String taxCodes;
+    private String fapiaoApplications;
+    private String notifyUrl;
+
+}

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

@@ -1,10 +1,10 @@
 package com.kym.entity.miniapp;
 
-import com.alibaba.fastjson2.JSONArray;
 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.wechat.InvoiceOrderDetail;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.experimental.Accessors;
@@ -33,7 +33,7 @@ public class Invoice extends BaseEntity {
      * 发票关联订单详情
      */
     @TableField(typeHandler = JacksonTypeHandler.class)
-    private List orderDetails;
+    private List<InvoiceOrderDetail> orderDetails;
 
     /**
      * 累积充电量(度)
@@ -68,7 +68,7 @@ public class Invoice extends BaseEntity {
     /**
      * 发票类型:INDIVIDUAL-个人 ORGANIZATION-企业
      */
-    private int invoiceType;
+    private String invoiceType;
 
     /**
      * 发票抬头

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

@@ -39,7 +39,7 @@ public class FaPiao {
     /**
      * 购买方信息,即发票抬头。若商户使用微信官方抬头,可从【获取用户填写的抬头】接口获取用户填写的抬头;也可自行收集发票抬头
      */
-    private List<BuyerInformation> buyer_information;
+    private BuyerInformation buyer_information;
 
     /**
      * 需要开具的发票信息。注意:同一个开票申请单最多申请5张发票
@@ -52,7 +52,7 @@ public class FaPiao {
      */
     @Data
     @Accessors(chain = true)
-    public class BuyerInformation{
+    public static class BuyerInformation{
         /**
          * INDIVIDUAL: 个人
          * ORGANIZATION: 单位
@@ -105,7 +105,7 @@ public class FaPiao {
 
     @Data
     @Accessors(chain = true)
-    public class FaPiaoInfomation{
+    public static class FaPiaoInfomation{
 
         /**
          * 商户发票单号,唯一标识一张要开具的发票。只能是字母、数字、中划线-、下划线_、竖线|、星号*
@@ -127,7 +127,7 @@ public class FaPiao {
 
         @Data
         @Accessors(chain = true)
-        public class IssueItem {
+        public static class IssueItem {
 
             /**
              * 【税局侧规定的货物或应税劳务、服务税收分类编码】
@@ -142,7 +142,7 @@ public class FaPiao {
              * 数量,展示在发票中间的数量列,单位为10^-8^,100000000表示数量为1。
              * 若是折扣行或者没有数量概念,则默认为100000000
              */
-            private String quantity;
+            private int quantity = 100000000;
 
             /**
              * 单行金额和税费的和,折扣行的金额为负数,非折扣行的金额为正数,单位:分

+ 19 - 0
entity/src/main/java/com/kym/entity/wechat/InvoiceOrderDetail.java

@@ -0,0 +1,19 @@
+package com.kym.entity.wechat;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author skyline
+ * @description 用于发票的订单详情
+ * @date 2023-09-23 21:27
+ */
+@Data
+@Accessors(chain = true)
+public class InvoiceOrderDetail {
+    private String startChargeSeq;
+    private double totalPower;
+    private int totalMoney;
+    private int elecMoney;
+    private int serviceMoney;
+}

+ 1 - 1
entity/src/main/java/com/kym/entity/wechat/TaxCodes.java

@@ -55,7 +55,7 @@ public class TaxCodes {
         /**
          * 税局侧规定的货物或应税劳务、服务税收分类编码
          */
-        private int tax_code;
+        private String tax_code;
 
         /**
          * 税率,单位为万分之一,如1300代表13%

+ 1 - 3
service/src/main/java/com/kym/service/admin/impl/StationServiceImpl.java

@@ -63,9 +63,7 @@ public class StationServiceImpl extends ServiceImpl<StationMapper, Station> impl
         var enStations = JSONObject.parseObject(AESUtil.decrypt(response.getData()));
         var stationList = enStations.getJSONArray("StationInfos").toJavaList(StationVo.class);
         stationList.forEach(station -> station.getEquipmentInfos().forEach(enEquipmentInfo -> enEquipmentInfo.setShortId(kymCache.getShortId(enEquipmentInfo.getEquipmentId()))));
-        stationList.forEach(vo -> {
-            vo.setEquipmentInfos(vo.getEquipmentInfos().stream().sorted(Comparator.comparing(EnEquipmentInfo::getShortId)).toList());
-        });
+        stationList.forEach(vo -> vo.setEquipmentInfos(vo.getEquipmentInfos().stream().sorted(Comparator.comparing(EnEquipmentInfo::getShortId)).toList()));
         return stationList;
 
     }

+ 7 - 13
service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

@@ -1,22 +1,17 @@
 package com.kym.service.miniapp.impl;
 
 import cn.dev33.satoken.stp.StpUtil;
-import com.alibaba.fastjson2.JSONArray;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.kym.entity.miniapp.ChargeOrder;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.miniapp.params.ApplyInvoiceParams;
+import com.kym.entity.wechat.InvoiceOrderDetail;
 import com.kym.mapper.miniapp.InvoiceMapper;
 import com.kym.service.miniapp.ChargeOrderService;
 import com.kym.service.miniapp.InvoiceService;
 import org.springframework.stereotype.Service;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
 /**
  * <p>
  * 发票记录表 服务实现类
@@ -41,17 +36,16 @@ public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> impl
         // TODO: 2023-09-21 校验订单是未开票且为该用户的订单
         var userId = StpUtil.getLoginIdAsLong();
         var orders = chargeOrderService.getChargeOrdersBySeqs(params.getStartChargeSeqs());
-        var orderDetails = orders.stream().collect(Collectors.toMap(ChargeOrder::getStartChargeSeq, chargeOrder -> Map.of(
-                "totalPower", chargeOrder.getTotalPower(),
-                "totalMoney", chargeOrder.getTotalMoney(),
-                "elecMoney", chargeOrder.getElecMoney(),
-                "serviceMoney", chargeOrder.getServiceMoney()
-        )));
+
+        var orderDetails = orders.stream().map(item -> new InvoiceOrderDetail()
+                .setStartChargeSeq(item.getStartChargeSeq())
+                .setTotalPower(item.getTotalPower()).setTotalMoney(item.getTotalMoney())
+                .setElecMoney(item.getElecMoney()).setServiceMoney(item.getServiceMoney())).toList();
 
         // 组装invoice
         var invoice = new Invoice()
                 .setUserId(userId)
-                .setOrderDetails(List.of(orderDetails)) // 订单信息
+                .setOrderDetails(orderDetails) // 订单信息
                 .setInvoiceAmount(orders.stream().mapToInt(ChargeOrder::getTotalMoney).sum())   // 总金额
                 .setElecMoney(orders.stream().mapToInt(ChargeOrder::getElecMoney).sum())    // 电费
                 .setElecMoney(orders.stream().mapToInt(ChargeOrder::getServiceMoney).sum())    // 服务费

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

@@ -1,6 +1,7 @@
 package com.kym.service.wechat;
 
 import com.alibaba.fastjson2.JSONObject;
+import com.kym.entity.wechat.InvoiceBaseInfo;
 import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
 import com.wechat.pay.java.service.refund.model.Refund;
 import jakarta.servlet.http.HttpServletRequest;
@@ -26,4 +27,6 @@ public interface WxPayService {
     PrepayWithRequestPaymentResponse wxPay(JSONObject rechargeAmount);
 
     void wxNotify(HttpServletRequest request) throws IOException;
+
+    InvoiceBaseInfo baseInformation();
 }

+ 80 - 45
service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -8,6 +8,7 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.dynamic.datasource.annotation.DS;
+import com.kym.common.config.WxFapiaoConfig;
 import com.kym.common.config.WxPayConfig;
 import com.kym.common.constant.ResponseEnum;
 import com.kym.common.exception.BusinessException;
@@ -15,19 +16,18 @@ import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.HttpUtil;
 import com.kym.common.utils.LambadaTools;
 import com.kym.common.utils.OrderUtils;
+import com.kym.entity.miniapp.*;
 import com.kym.entity.miniapp.Account;
-import com.kym.entity.miniapp.PayLog;
-import com.kym.entity.miniapp.RefundLog;
-import com.kym.entity.miniapp.WalletDetail;
-import com.kym.entity.wechat.FaPiao;
-import com.kym.entity.wechat.InvoiceBaseInfo;
-import com.kym.entity.wechat.InvoiceNotification;
-import com.kym.entity.wechat.TaxCodes;
+import com.kym.entity.wechat.*;
 import com.kym.service.miniapp.*;
 import com.kym.service.wechat.WxPayService;
 import com.wechat.pay.java.core.Config;
 import com.wechat.pay.java.core.RSAAutoCertificateConfig;
 import com.wechat.pay.java.core.exception.ValidationException;
+import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
+import com.wechat.pay.java.core.http.HttpHeaders;
+import com.wechat.pay.java.core.http.JsonRequestBody;
+import com.wechat.pay.java.core.http.okhttp.OkHttpClientAdapter;
 import com.wechat.pay.java.core.notification.NotificationConfig;
 import com.wechat.pay.java.core.notification.NotificationParser;
 import com.wechat.pay.java.core.notification.RequestParam;
@@ -41,7 +41,6 @@ import com.wechat.pay.java.service.refund.model.*;
 import jakarta.annotation.PostConstruct;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.SneakyThrows;
-import okhttp3.Headers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.io.ClassPathResource;
@@ -55,7 +54,10 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
 
 
 /**
@@ -76,6 +78,8 @@ public class WxPayServiceImpl implements WxPayService {
 
     private final WxPayConfig conf;
 
+    private final WxFapiaoConfig fapiaoConfig;
+
     private final WalletDetailService walletDetailService;
 
     private final PayLogService payLogService;
@@ -86,14 +90,23 @@ public class WxPayServiceImpl implements WxPayService {
 
     private final RefundLogService refundLogService;
 
+    private final InvoiceService invoiceService;
+
+    /**
+     * 微信支付专用,支持自动签名验签解密等
+     */
+    private OkHttpClientAdapter wxHttpClient;
+
 
-    public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService, RefundLogService refundLogService) {
+    public WxPayServiceImpl(WxPayConfig conf, WxFapiaoConfig fapiaoConfig, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService, RefundLogService refundLogService, InvoiceService invoiceService) {
         this.conf = conf;
+        this.fapiaoConfig = fapiaoConfig;
         this.walletDetailService = walletDetailService;
         this.payLogService = payLogService;
         this.accountService = accountService;
         this.chargeOrderService = chargeOrderService;
         this.refundLogService = refundLogService;
+        this.invoiceService = invoiceService;
     }
 
     /**
@@ -130,6 +143,8 @@ public class WxPayServiceImpl implements WxPayService {
                 .build();
         jsapiService = new JsapiService.Builder().config(config).build();
         refundService = new RefundService.Builder().config(config).build();
+
+        wxHttpClient = (OkHttpClientAdapter) new DefaultHttpClientBuilder().newInstance().config(config).build();
     }
 
 
@@ -518,53 +533,73 @@ public class WxPayServiceImpl implements WxPayService {
     /**
      * 获取商户开票基础信息
      */
-    private InvoiceBaseInfo baseInformation() {
-        var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/base-information";
-        Headers headers = Headers.of(
-                "Authorization", "",
-                "Accept", "application/json",
-                "Content-Type", "application/json"
-        );
-        var res = HttpUtil.post(url, headers);
-        return JSONObject.parseObject(res).toJavaObject(InvoiceBaseInfo.class);
+    @Override
+    public InvoiceBaseInfo baseInformation() {
+        var headers = new HttpHeaders();
+        headers.addHeader("Accept", "application/json");
+        var res = wxHttpClient.get(headers, fapiaoConfig.getBaseInformation(), InvoiceBaseInfo.class);
+        return res.getServiceResponse();
     }
 
 
     /**
      * 获取商户可开具的商品和服务税收分类编码对照表
      */
-    private TaxCodes.TaxCodeItem getTaxCodes() {
-        var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/tax-codes";
-        Headers headers = Headers.of(
-                "Authorization", "",
-                "Accept", "application/json"
-        );
-        var params = """
-                {
-                    "offset" : %d,
-                    "limit" :%d
-                }
-                """.stripIndent().formatted(0, 100);
-        var res = HttpUtil.post(url, params, headers);
-        return JSONObject.parseObject(res).toJavaObject(TaxCodes.TaxCodeItem.class);
+    private TaxCodes getTaxCodes() {
+        var headers = new HttpHeaders();
+        headers.addHeader("Accept", "application/json");
+        var url = fapiaoConfig.getTaxCodes().concat("?offset=%d&limit=%d").formatted(0, 5);
+        var res = wxHttpClient.get(headers, url, TaxCodes.class);
+        return res.getServiceResponse();
     }
 
 
     /**
      * 申请发票
      */
-    public void applyInvoice() {
-        var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications";
-        Headers headers = Headers.of(
-                "Authorization", "",
-                "Accept", "application/json",
-                "Content-Type", "application/json",
-                "Wechatpay-Serial", "6A45EEB068369430B2FFD45EA29F641A8E18165F"
-        );
-
-        var invoiceId = OrderUtils.getOrderNo();
-        var fapiao = new FaPiao();
-        var res = HttpUtil.post(url, JSONObject.toJSONString(fapiao), headers);
+
+    public void applyInvoice(String invoiceId) {
+        var invoice = invoiceService.lambdaQuery().eq(Invoice::getId,invoiceId).one();
+        // 电费 税务商品编码:1100101020000000000
+        var elecMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
+        var elecMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(elecMoney).setTaxCode("1100101020000000000");
+        // 服务费 税务商品编码:3040100000000000000
+        var serviceMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
+        var serviceMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(serviceMoney).setTaxCode("3040100000000000000");
+
+        var headers = new HttpHeaders();
+        headers.addHeader("Accept", "application/json");
+        headers.addHeader("Content-Type", "application/json");
+        headers.addHeader("Wechatpay-Serial", conf.getMchsn());
+
+        // 发票申请单号
+        var fapiaoApplyId = OrderUtils.getOrderNo();
+
+        // 客户开票信息
+        var buyerInformation = new FaPiao.BuyerInformation()
+                .setType(invoice.getInvoiceType())
+                .setName(invoice.getInvoiceTitle())
+                .setTaxpayer_id(invoice.getTaxId())
+                .setAddress(invoice.getAddress())
+                .setTelephone(invoice.getPhone())
+                .setBank_name(invoice.getBankName())
+                .setBank_account(invoice.getBankAccount());
+
+        // 发票信息
+        var fapiaoInformation = new FaPiao.FaPiaoInfomation();
+        fapiaoInformation.setFapiao_id(String.valueOf(invoice.getId()))
+                .setTotal_amount(invoice.getInvoiceAmount())
+                .setItems(List.of(elecMoneyItem,serviceMoneyItem));
+
+        // 请求参数
+        var fapiao = new FaPiao()
+                .setScene("WITHOUT_WECHATPAY")
+                .setFapiao_apply_id(fapiaoApplyId)
+                .setBuyer_information(buyerInformation)
+                .setFapiao_information(List.of(fapiaoInformation));
+
+        var requestBody = new JsonRequestBody.Builder().body(JSONObject.toJSONString(fapiao)).build();
+        wxHttpClient.post(headers, fapiaoConfig.getFapiaoApplications(), requestBody,null);
     }
 
     /**
@@ -580,7 +615,7 @@ public class WxPayServiceImpl implements WxPayService {
             LOGGER.info("微信开具发票结果通知回调{}:验签解密完毕,数据:\n{}", notifyRes[2], invoiceNotification);
             // TODO: 2023-09-17 业务逻辑
 
-        }  catch (ValidationException e) {
+        } catch (ValidationException e) {
             // 签名验证失败,返回 401 UNAUTHORIZED 状态码
             LOGGER.error("微信开具发票结果通知验签失败", e);
             throw new BusinessException("验签失败");