Преглед изворни кода

微信电子发票部分代码

skyline пре 2 година
родитељ
комит
804ff4d14b
20 измењених фајлова са 253 додато и 46 уклоњено
  1. 1 0
      admin/src/main/java/com/kym/admin/controller/FinanceController.java
  2. 1 0
      admin/src/main/resources/application-dev.yml
  3. 1 0
      common/src/main/java/com/kym/common/config/WxFapiaoConfig.java
  4. 2 1
      entity/src/main/java/com/kym/entity/miniapp/ChargeOrder.java
  5. 5 0
      entity/src/main/java/com/kym/entity/miniapp/Invoice.java
  6. 33 0
      entity/src/main/java/com/kym/entity/wechat/TitleUrl.java
  7. 34 0
      entity/src/main/java/com/kym/entity/wechat/TitleWriteNotification.java
  8. 3 1
      mapper/src/main/resources/mappers/miniapp/InvoiceMapper.xml
  9. 11 15
      miniapp/src/main/java/com/kym/miniapp/controller/ChargerController.java
  10. 15 3
      miniapp/src/main/java/com/kym/miniapp/controller/InvoiceController.java
  11. 6 0
      package-lock.json
  12. 1 1
      service/src/main/java/com/kym/service/admin/impl/StationServiceImpl.java
  13. 1 1
      service/src/main/java/com/kym/service/miniapp/ChargeService.java
  14. 1 1
      service/src/main/java/com/kym/service/miniapp/DelayService.java
  15. 3 1
      service/src/main/java/com/kym/service/miniapp/InvoiceService.java
  16. 6 3
      service/src/main/java/com/kym/service/miniapp/impl/ChargeServiceImpl.java
  17. 3 3
      service/src/main/java/com/kym/service/miniapp/impl/DelayServiceImpl.java
  18. 33 14
      service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java
  19. 10 0
      service/src/main/java/com/kym/service/wechat/WxPayService.java
  20. 83 2
      service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

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

@@ -43,6 +43,7 @@ public class FinanceController {
         return R.success();
     }
 
+    @SysLog("开具发票")
     @GetMapping("handleInvoice")
     R<?> handleInvoice() {
         return R.success(wxPayService.baseInformation());

+ 1 - 0
admin/src/main/resources/application-dev.yml

@@ -44,6 +44,7 @@ wechat:
     devConfig: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/development-config
     fapiaoFiles: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/%s/fapiao-files
     queryFapiao: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications/%s
+    titleUrl: https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/user-title/title-url
 
 spring:
   datasource:

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

@@ -21,5 +21,6 @@ public class WxFapiaoConfig {
     private String devConfig;
     private String fapiaoFiles;
     private String queryFapiao;
+    private String titleUrl;
 
 }

+ 2 - 1
entity/src/main/java/com/kym/entity/miniapp/ChargeOrder.java

@@ -40,7 +40,8 @@ public class ChargeOrder extends BaseEntity implements Serializable {
 
 
     public static int INVOICE_STATUS_未开票 = 0;
-    public static int INVOICE_STATUS_已开票 = 1;
+    public static int INVOICE_STATUS_开票中 = 1;
+    public static int INVOICE_STATUS_已开票 = 2;
 
     private Long userId;
 

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

@@ -40,6 +40,11 @@ public class Invoice extends BaseEntity {
 
     private Long userId;
 
+    /**
+     * 发票抬头填写人的openid
+     */
+    private String openid;
+
     /**
      * 发票关联订单详情
      */

+ 33 - 0
entity/src/main/java/com/kym/entity/wechat/TitleUrl.java

@@ -0,0 +1,33 @@
+package com.kym.entity.wechat;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * @author skyline
+ * @description 发票抬头填写链接
+ * @date 2023-10-12 20:19
+ */
+@Data
+@Accessors(chain = true)
+public class TitleUrl {
+    /**
+     * 小程序appid
+     */
+    @SerializedName("miniprogram_appid")
+    private String miniprogramAppid;
+
+    /**
+     * 小程序appid
+     */
+    @SerializedName("miniprogram_path")
+    private String miniprogramPath;
+
+    /**
+     * 抬头填写小程序的用户名,即小程序的原始id,当开票来源为WEB时存在
+     */
+    @SerializedName("miniprogram_user_name")
+    private String miniprogramUsername;
+
+}

+ 34 - 0
entity/src/main/java/com/kym/entity/wechat/TitleWriteNotification.java

@@ -0,0 +1,34 @@
+package com.kym.entity.wechat;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author skyline
+ * @description 发票抬头填写完成回调通知
+ * @date 2023-09-17 11:30
+ */
+@Data
+public class TitleWriteNotification {
+
+    /**
+     * 微信支付分配的商户号
+     */
+    @SerializedName("mchid")
+    private String mchid;
+
+    /**
+     * 开票时指定的发票申请单号
+     */
+    @SerializedName("fapiao_apply_id")
+    private String fapiaoApplyId;
+
+    /**
+     * 用户完成发票抬头填写的时间
+     */
+    @SerializedName("apply_time")
+    private LocalDateTime applyTime;
+
+}

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

@@ -7,12 +7,14 @@
         <result column="id" property="id" />
         <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="total_power" property="totalPower" />
         <result column="total_money" property="totalMoney" />
         <result column="elec_money" property="elecMoney" />
         <result column="service_money" property="serviceMoney" />
         <result column="email" property="email" />
+        <result column="phone" property="phone" />
         <result column="invoice_type" property="invoiceType" />
         <result column="invoice_title" property="invoiceTitle" />
         <result column="tax_id" property="taxId" />
@@ -30,7 +32,7 @@
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id,apply_id,user_id, order_details, total_power, total_money, elec_money, service_money, email, invoice_type, invoice_title, tax_id, address, bank_name, bank_account, invoice_amount, tax_info, biller, status,remark,create_time,update_time
+        id,apply_id,user_id, openid,order_details, total_power, total_money, elec_money, service_money, email,phone, invoice_type, invoice_title, tax_id, address, bank_name, bank_account, invoice_amount, tax_info, biller, status,remark,create_time,update_time
     </sql>
 
 </mapper>

+ 11 - 15
miniapp/src/main/java/com/kym/miniapp/controller/ChargerController.java

@@ -57,7 +57,7 @@ public class ChargerController {
      */
     @SneakyThrows
     @GetMapping("/listStation")
-    R listChargeStation(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
+    R<?> listChargeStation(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
         // 请求en+接口
         List<StationVo> response = stationService.queryStationInfo(pageNum, pageSize);
         return R.success(response);
@@ -70,7 +70,7 @@ public class ChargerController {
      * @return
      */
     @GetMapping("/stationStatus")
-    R stationStatus(@RequestParam("stationIds") String ids) {
+    R<?> stationStatus(@RequestParam("stationIds") String ids) {
         // 请求en+接口
         var response = stationService.stationStatus(ids.split(","));
         return R.success(response);
@@ -83,9 +83,9 @@ public class ChargerController {
 
     @ApiLog("启动充电")
     @GetMapping({"/startCharge/{connectorId}", "/startCharge/{connectorId}"})
-    R startCharge(@PathVariable("connectorId") String connectorId,
-                  @RequestParam(value = "isBooking", defaultValue = "false") Boolean isBooking,
-                  @RequestParam(value = "startTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime) {
+    R<?> startCharge(@PathVariable("connectorId") String connectorId,
+                     @RequestParam(value = "isBooking", defaultValue = "false") Boolean isBooking,
+                     @RequestParam(value = "startTime", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime) {
         var userId = StpUtil.getLoginIdAsLong();
         return R.success(chargeService.queryStartCharge(userId, connectorId, isBooking, startTime));
     }
@@ -93,7 +93,8 @@ public class ChargerController {
     @ApiLog("修改预约充电时间")
     @GetMapping("/modifyBookingTime/{startChargeSeq}")
     R<?> modifyBookingTime(@PathVariable("startChargeSeq") String startChargeSeq, @RequestParam LocalDateTime startTime) {
-        return R.success(chargeService.modifyBookingTime(startChargeSeq, startTime));
+        chargeService.modifyBookingTime(startChargeSeq, startTime);
+        return R.success();
     }
 
     @ApiLog("预约改立即充电")
@@ -104,7 +105,7 @@ public class ChargerController {
 
     @ApiLog("停止充电")
     @GetMapping("/stopCharge/{connectorId}")
-    R stopCharge(@PathVariable("connectorId") String connectorId) {
+    R<?> stopCharge(@PathVariable("connectorId") String connectorId) {
         chargeService.queryStopCharge(connectorId);
         return R.success();
     }
@@ -126,7 +127,7 @@ public class ChargerController {
      * @return
      */
     @GetMapping("/businessPolicy/{connectorId}")
-    R businessPolicy(@PathVariable("connectorId") String connectorId) {
+    R<?> businessPolicy(@PathVariable("connectorId") String connectorId) {
         return R.success(chargeService.queryEquipBusinessPolicy(connectorId));
     }
 
@@ -137,7 +138,7 @@ public class ChargerController {
      * @return
      */
     @GetMapping("/orderDetail/{startChargeSeq}")
-    public R orderDetail(@PathVariable("startChargeSeq") String startChargeSeq) {
+    R<?> orderDetail(@PathVariable("startChargeSeq") String startChargeSeq) {
         return R.success(chargeOrderService.orderDetailForApp(startChargeSeq));
     }
 
@@ -158,7 +159,6 @@ public class ChargerController {
         return new EnResponse(enNotifyService.handleNotificationStationStatus(json));
     }
 
-
     /**
      * EN+推送启动充电结果
      *
@@ -171,7 +171,6 @@ public class ChargerController {
         return new EnResponse(enNotifyService.handleNotificationStartChargeResult(json));
     }
 
-
     /**
      * EN+推送充电状态
      *
@@ -183,9 +182,6 @@ public class ChargerController {
         return new EnResponse(enNotifyService.handleNotificationEquipChargeStatus(json));
     }
 
-    // TODO: 2023-08-21 推送启动充电结果
-
-
     /**
      * EN+推送停止充电结果
      *
@@ -219,7 +215,7 @@ public class ChargerController {
      * @return
      */
     @GetMapping("/pullEnStations")
-    R pullEnStationInfos() {
+    R<?> pullEnStationInfos() {
         stationService.pullEnStationInfos();
         return R.success();
     }

+ 15 - 3
miniapp/src/main/java/com/kym/miniapp/controller/InvoiceController.java

@@ -3,6 +3,7 @@ package com.kym.miniapp.controller;
 import com.kym.common.R;
 import com.kym.entity.miniapp.params.ApplyInvoiceParams;
 import com.kym.service.miniapp.InvoiceService;
+import jakarta.servlet.http.HttpServletRequest;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -26,16 +27,27 @@ public class InvoiceController {
         this.invoiceService = invoiceService;
     }
 
+
+    /**
+     * 用户发票抬头填写完成通知
+     *
+     * @param request
+     * @return
+     */
+    @PostMapping("titleWriteNotice")
+    R<?> titleWriteNotice(HttpServletRequest request) {
+        return R.success();
+    }
+
     /**
      * 申请发票
      *
      * @param params
-     * @return
+     * @return 用户抬头填写URL
      */
     @PostMapping("/applyInvoice")
     R<?> applyInvoice(@RequestBody ApplyInvoiceParams params) {
-        invoiceService.applyInvoice(params);
-        return R.success();
+        return R.success(invoiceService.applyInvoice(params));
     }
 
 }

+ 6 - 0
package-lock.json

@@ -0,0 +1,6 @@
+{
+  "name": "charge-java",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {}
+}

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

@@ -116,7 +116,7 @@ public class StationServiceImpl extends ServiceImpl<StationMapper, Station> impl
         var stationList = new ArrayList<Station>();
         var equipmentList = new ArrayList<EquipmentInfo>();
         stationVoList.forEach(vo -> {
-            if (vo.getEquipmentInfos().size() > 0) {
+            if (!vo.getEquipmentInfos().isEmpty()) {
                 vo.getEquipmentInfos().forEach(item -> {
                     var equipment = new EquipmentInfo()
                             .setStationId(vo.getStationId())

+ 1 - 1
service/src/main/java/com/kym/service/miniapp/ChargeService.java

@@ -27,5 +27,5 @@ public interface ChargeService {
 
     Map<String, String> immediatelyCharge(String connectorId);
 
-    Object modifyBookingTime(String startChargeSeq, LocalDateTime startTime);
+    void modifyBookingTime(String startChargeSeq, LocalDateTime startTime);
 }

+ 1 - 1
service/src/main/java/com/kym/service/miniapp/DelayService.java

@@ -29,6 +29,6 @@ public interface DelayService<T> {
      *
      * @param data
      */
-    void removeToOrderDelayQueue(T data);
+    boolean removeToOrderDelayQueue(T data);
 
 }

+ 3 - 1
service/src/main/java/com/kym/service/miniapp/InvoiceService.java

@@ -3,6 +3,7 @@ package com.kym.service.miniapp;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.miniapp.params.ApplyInvoiceParams;
+import com.kym.entity.wechat.TitleUrl;
 
 /**
  * <p>
@@ -14,5 +15,6 @@ import com.kym.entity.miniapp.params.ApplyInvoiceParams;
  */
 public interface InvoiceService extends IService<Invoice> {
 
-    void applyInvoice(ApplyInvoiceParams params);
+    TitleUrl applyInvoice(ApplyInvoiceParams params);
+
 }

+ 6 - 3
service/src/main/java/com/kym/service/miniapp/impl/ChargeServiceImpl.java

@@ -90,11 +90,13 @@ public class ChargeServiceImpl implements ChargeService {
      * @return
      */
     @Override
-    public Object modifyBookingTime(String startChargeSeq, LocalDateTime startTime) {
+    public void modifyBookingTime(String startChargeSeq, LocalDateTime startTime) {
         // 预约充电队列更新
         var chargeOrder = chargeOrderService.getChargingOrderByStartChargeSeq(startChargeSeq);
-        delayService.removeToOrderDelayQueue(chargeOrder);
-        return null;
+        boolean success = delayService.removeToOrderDelayQueue(chargeOrder);
+        if (!success) {
+            throw new BusinessException("修改预约时间失败");
+        }
     }
 
 
@@ -110,6 +112,7 @@ public class ChargeServiceImpl implements ChargeService {
     @DSTransactional(rollbackFor = Exception.class)
     public Map<String, String> queryStartCharge(Long userId, String connectorId, Boolean isBooking, LocalDateTime startTime) {
         if (isBooking) {
+            // TODO: 2023-10-11 预约时间不能超过未来24H
             // 预约充电通过connectorId查询预约中的订单
             var bookingOrder = chargeOrderService.lambdaQuery()
                     .eq(ChargeOrder::getConnectorId, connectorId).eq(ChargeOrder::getChargeStatus, ChargeOrder.CHARGE_STATUS_预约中).one();

+ 3 - 3
service/src/main/java/com/kym/service/miniapp/impl/DelayServiceImpl.java

@@ -99,10 +99,10 @@ public class DelayServiceImpl implements DelayService<ChargeOrder> {
     }
 
     @Override
-    public void removeToOrderDelayQueue(ChargeOrder chargeOrder) {
+    public boolean removeToOrderDelayQueue(ChargeOrder chargeOrder) {
         if (chargeOrder == null) {
-            return;
+            return false;
         }
-        DELAY_QUEUE.removeIf(queue -> queue.data.getStartChargeSeq().equals(chargeOrder.getStartChargeSeq()));
+        return DELAY_QUEUE.removeIf(queue -> queue.data.getStartChargeSeq().equals(chargeOrder.getStartChargeSeq()));
     }
 }

+ 33 - 14
service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

@@ -3,13 +3,18 @@ package com.kym.service.miniapp.impl;
 import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.kym.common.exception.BusinessException;
+import com.kym.common.utils.OrderUtils;
 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.entity.wechat.TitleUrl;
 import com.kym.mapper.miniapp.InvoiceMapper;
 import com.kym.service.miniapp.ChargeOrderService;
 import com.kym.service.miniapp.InvoiceService;
+import com.kym.service.wechat.WxPayService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 /**
@@ -21,22 +26,34 @@ import org.springframework.stereotype.Service;
  * @since 2023-09-15
  */
 @Service
+@Slf4j
 @DS("db-miniapp")
 public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> implements InvoiceService {
 
     private final ChargeOrderService chargeOrderService;
+    private final WxPayService wxPayService;
 
-    public InvoiceServiceImpl(ChargeOrderService chargeOrderService) {
+    public InvoiceServiceImpl(ChargeOrderService chargeOrderService, WxPayService wxPayService) {
         this.chargeOrderService = chargeOrderService;
+        this.wxPayService = wxPayService;
     }
 
+
     @Override
-    public void applyInvoice(ApplyInvoiceParams params) {
+    public TitleUrl applyInvoice(ApplyInvoiceParams params) {
+        // TODO: 2023-10-12 抬头信息去微信接口获取
+
         // orderDetails 获取申请开票的订单
-        // TODO: 2023-09-21 校验订单是未开票且为该用户的订单
         var userId = StpUtil.getLoginIdAsLong();
         var orders = chargeOrderService.getChargeOrdersBySeqs(params.getStartChargeSeqs());
 
+        // 校验订单是未开票
+        var invoiced = orders.stream().filter(o -> o.getInvoiceStatus().equals(ChargeOrder.INVOICE_STATUS_开票中) || o.getInvoiceStatus().equals(ChargeOrder.INVOICE_STATUS_已开票)).toList();
+        if (!invoiced.isEmpty()) {
+            log.error("存在已开票的订单:{}", invoiced.stream().map(ChargeOrder::getStartChargeSeq).toArray());
+            throw new BusinessException("存在已开票的订单");
+        }
+
         var orderDetails = orders.stream().map(item -> new InvoiceOrderDetail()
                 .setStartChargeSeq(item.getStartChargeSeq())
                 .setTotalPower(item.getTotalPower()).setTotalMoney(item.getTotalMoney())
@@ -45,23 +62,25 @@ public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> impl
         // 组装invoice
         var invoice = new Invoice()
                 .setUserId(userId)
+                .setOpenid(StpUtil.getSession().getString("openid"))
+                .setApplyId(OrderUtils.getOrderNo())
                 .setOrderDetails(orderDetails) // 订单信息
                 .setInvoiceAmount(orders.stream().mapToInt(ChargeOrder::getTotalMoney).sum())   // 总金额
                 .setElecMoney(orders.stream().mapToInt(ChargeOrder::getElecMoney).sum())    // 电费
                 .setElecMoney(orders.stream().mapToInt(ChargeOrder::getServiceMoney).sum())    // 服务费
-                .setTotalPower(orders.stream().mapToDouble(ChargeOrder::getTotalPower).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());    // 邮箱
+                .setTotalPower(orders.stream().mapToDouble(ChargeOrder::getTotalPower).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);
 
     }
 
-
 }

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

@@ -1,7 +1,9 @@
 package com.kym.service.wechat;
 
 import com.alibaba.fastjson2.JSONObject;
+import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.wechat.InvoiceBaseInfo;
+import com.kym.entity.wechat.TitleUrl;
 import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
 import com.wechat.pay.java.service.refund.model.Refund;
 import jakarta.servlet.http.HttpServletRequest;
@@ -28,9 +30,17 @@ public interface WxPayService {
 
     void wxNotify(HttpServletRequest request) throws IOException;
 
+
+    //================================================================发票=====================================================================
+    // TODO: 2023-10-12 补充数据
+    TitleUrl titleUrl(Invoice invoice);
+
+    void titleWriteNotice(HttpServletRequest request);
+
     InvoiceBaseInfo baseInformation();
 
     void applyInvoice(String invoiceId);
 
     void wxInvoiceNotify(HttpServletRequest request);
+
 }

+ 83 - 2
service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.URLUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.kym.common.config.WxFapiaoConfig;
@@ -54,6 +55,7 @@ import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 
@@ -526,6 +528,85 @@ public class WxPayServiceImpl implements WxPayService {
         }
     }
 
+    //================================================================发票=====================================================================
+
+    /**
+     * 获取用户抬头信息填写URL
+     *
+     * @param invoice
+     * @return
+     */
+    @Override
+    public TitleUrl titleUrl(Invoice invoice) {
+        var headers = new HttpHeaders();
+        var params = Map.of(
+                "fapiao_apply_id", invoice.getApplyId(),
+                "appid", conf.getAppid(),
+                "openid", invoice.getOpenid(),
+                "total_amount", invoice.getTotalMoney(),
+                "source", "MINIPROGRAM"
+        );
+        headers.addHeader("Accept", "application/json");
+        var res = wxHttpClient.get(headers, fapiaoConfig.getTitleUrl().concat(URLUtil.buildQuery(params, StandardCharsets.UTF_8)), TitleUrl.class);
+        return res.getServiceResponse();
+    }
+
+
+    /**
+     * 处理发票抬头填写完成通知
+     *
+     * @param request
+     */
+    @Override
+    public void titleWriteNotice(HttpServletRequest request) {
+        var notifyRes = handleWxNotify(request);
+        try {
+            // TODO: 2023-10-12 步骤 用户选择开票的订单,确定(数据库插入invoice数据)进入抬头填写页面,输入抬头确定开票,收到抬头填写完成通知后
+            //  再获取用户抬头更新invoice抬头数据。后台审核人员进行开票操作
+            TitleWriteNotification titleWriteNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], TitleWriteNotification.class);
+            // 查询用户填写的抬头信息
+            var applyId = titleWriteNotification.getFapiaoApplyId();
+            var buyerInformation = userTitle(applyId);
+            // 更新invoice表数据
+            invoiceService.lambdaUpdate()
+                    .eq(Invoice::getApplyId, applyId)
+                    .set(Invoice::getInvoiceType, buyerInformation.getType())
+                    .set(Invoice::getInvoiceTitle, buyerInformation.getName())
+                    .set(Invoice::getTaxId, buyerInformation.getTaxpayer_id())
+                    .set(Invoice::getAddress, buyerInformation.getAddress())
+                    .set(Invoice::getBankName, buyerInformation.getBank_name())
+                    .set(Invoice::getBankAccount, buyerInformation.getBank_account())
+                    .set(Invoice::getPhone, buyerInformation.getPhone())
+                    .set(Invoice::getEmail, buyerInformation.getEmail())
+                    .update();
+            // 运营后台财务操作开具发票
+        } catch (ValidationException e) {
+            // 签名验证失败,返回 401 UNAUTHORIZED 状态码
+            LOGGER.error("微信处理发票抬头填写完成通知验签失败", e);
+            throw new BusinessException("验签失败");
+        }
+    }
+
+    /**
+     * 获取用户填写的抬头信息
+     *
+     * @param applyId
+     * @return
+     */
+    public FaPiao.BuyerInformation userTitle(String applyId) {
+        var headers = new HttpHeaders();
+        headers.addHeader("Accept", "application/json");
+        var params = """
+                {
+                  "fapiao_apply_id" : %s,
+                  "scene" : %s
+                }
+                """.stripIndent().formatted(applyId, "WITHOUT_WECHATPAY"); // 非微信支付场景
+        var requestBody = new JsonRequestBody.Builder().body(params).build();
+        var res = wxHttpClient.patch(headers, fapiaoConfig.getDevConfig(), requestBody, FaPiao.BuyerInformation.class);
+        LOGGER.info("用户发票申请applyId:{},抬头信息:{}", applyId, res);
+        return res.getServiceResponse();
+    }
 
     /**
      * 获取商户开票基础信息
@@ -614,10 +695,9 @@ public class WxPayServiceImpl implements WxPayService {
             InvoiceNotification invoiceNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], InvoiceNotification.class);
             LOGGER.info("微信开具发票结果通知回调{}:验签解密完毕,数据:\n{}", notifyRes[2], invoiceNotification);
             // 业务逻辑
-            if (invoiceNotification.getFapiaoInformation().get(0).getFapiaoStatus().equals(InvoiceNotification.FapiaoStatus.ISSUED)) {
+            if (invoiceNotification.getFapiaoInformation().get(0).getFapiaoStatus().equals(InvoiceNotification.FapiaoStatus.ISSUED.name())) {
                 // 更新发票状态
                 invoiceService.lambdaUpdate().set(Invoice::getStatus, Invoice.STATUS_已开票).eq(Invoice::getApplyId, invoiceNotification.getFapiaoApplyId()).update();
-
             } else {
                 LOGGER.error("微信开具发票失败:{}", invoiceNotification);
             }
@@ -628,6 +708,7 @@ public class WxPayServiceImpl implements WxPayService {
         }
     }
 
+
     /**
      * 微信电子发票配置开发选项,例如回调地址、全部账单展示开发票入口开关
      */