فهرست منبع

refactor: 卡包插入改用微信支付 v3 电子发票 API

- 新增 WechatPayFapiaoService,通过微信支付 v3 接口实现发票卡包插入
- 流程:创建发票申请单 → 插入卡包(SDK 自动处理认证签名)
- WxPayServiceImpl.wxHttpClient 改为 public static 供内部服务使用
- 移除旧的微信公众号卡券 API 调用

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 روز پیش
والد
کامیت
2a5a1ac3f8

+ 8 - 42
service/src/main/java/com/kym/service/miniapp/HuapiaoerInvoiceService.java

@@ -5,7 +5,6 @@ import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 import com.kym.common.config.HuapiaoerProperties;
-import com.kym.common.utils.wx.WxPbUtil;
 import com.kym.entity.admin.InvoiceDetail;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.wechat.FaPiao;
@@ -21,6 +20,7 @@ import com.kym.huapiaoer.model.request.OpenBlueInvoiceRequest;
 import com.kym.huapiaoer.model.response.InvoiceInfo;
 import com.kym.huapiaoer.model.response.OpenInvoiceResponse;
 import com.kym.service.admin.InvoiceDetailService;
+import com.kym.service.wechat.impl.WechatPayFapiaoService;
 import jakarta.annotation.PostConstruct;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -39,6 +39,7 @@ public class HuapiaoerInvoiceService {
 
     private final HuapiaoerProperties properties;
     private final InvoiceDetailService invoiceDetailService;
+    private final WechatPayFapiaoService wechatPayFapiaoService;
 
     private HuapiaoerClient client;
     private volatile String issuerId;
@@ -110,7 +111,7 @@ public class HuapiaoerInvoiceService {
     }
 
     /**
-     * 将已开具的电子发票插入用户微信卡包
+     * 将已开具的电子发票插入用户微信卡包(通过微信支付 v3 API)
      */
     public void insertToCardPackage(Invoice invoice, InvoiceInfo result) {
         if (invoice.getOpenid() == null || invoice.getOpenid().isBlank()) {
@@ -118,56 +119,21 @@ public class HuapiaoerInvoiceService {
             return;
         }
 
-        var orderDetails = invoice.getOrderDetails();
-        int elecMoney = orderDetails.stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
-        int serviceMoney = orderDetails.stream().mapToInt(InvoiceOrderDetail::getServiceMoney).sum();
-        int discount = orderDetails.stream().mapToInt(InvoiceOrderDetail::getServiceMoneyDiscount).sum();
-        int netServiceMoney = serviceMoney - discount;
-
-        var items = new ArrayList<Map<String, Object>>();
-        if (elecMoney > 0) {
-            var item = new HashMap<String, Object>();
-            item.put("name", "充电电费");
-            item.put("price", elecMoney);
-            item.put("unit", "");
-            item.put("num", 1);
-            items.add(item);
-        }
-        if (netServiceMoney > 0) {
-            var item = new HashMap<String, Object>();
-            item.put("name", "充电服务费");
-            item.put("price", netServiceMoney);
-            item.put("unit", "");
-            item.put("num", 1);
-            items.add(item);
-        }
-
-        String cardCode = WxPbUtil.insertInvoiceToCard(
-                invoice.getOpenid(),
-                invoice.getInvoiceTitle(),
-                result.getInvoicenumber(),
-                result.getInvoicecode(),
-                invoice.getInvoiceAmount(),
-                invoice.getTaxId(),
-                properties.getNsrsbh(),
-                result.getInvoiceurl(),
-                items
-        );
+        boolean success = wechatPayFapiaoService.insertToCard(invoice, result, properties.getNsrsbh());
 
-        if (cardCode != null) {
+        if (success) {
             try {
                 DynamicDataSourceContextHolder.push("db-admin");
                 var cardInfo = new FapiaoApplications.CardInfo();
-                cardInfo.setCard_appid(WxPbUtil.getAppId());
+                cardInfo.setCard_appid("wx93b0ef1be901bd19");
                 cardInfo.setCard_openid(invoice.getOpenid());
-                cardInfo.setCard_code(cardCode);
-                cardInfo.setCard_status("INSERTED");
+                cardInfo.setCard_status("INSERT_ACCEPTED");
 
                 invoiceDetailService.lambdaUpdate()
                         .set(InvoiceDetail::getCardInformation, cardInfo)
                         .eq(InvoiceDetail::getApplyId, invoice.getApplyId())
                         .update();
-                log.info("卡包插入成功, applyId:{}, cardCode:{}", invoice.getApplyId(), cardCode);
+                log.info("卡包插入请求已提交, applyId:{}", invoice.getApplyId());
             } finally {
                 DynamicDataSourceContextHolder.poll();
             }

+ 152 - 0
service/src/main/java/com/kym/service/wechat/impl/WechatPayFapiaoService.java

@@ -0,0 +1,152 @@
+package com.kym.service.wechat.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.kym.entity.miniapp.Invoice;
+import com.kym.entity.wechat.InvoiceOrderDetail;
+import com.kym.huapiaoer.model.response.InvoiceInfo;
+import com.wechat.pay.java.core.http.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * 通过微信支付 v3 电子发票 API 将航信发票插入用户微信卡包
+ */
+@Service
+@Slf4j
+public class WechatPayFapiaoService {
+
+    private static final String API_BASE = "https://api.mch.weixin.qq.com";
+    private static final String CREATE_APPLICATION = "/v3/new-tax-control-fapiao/fapiao-applications";
+    private static final String INSERT_CARD = "/v3/new-tax-control-fapiao/fapiao-applications/%s/card";
+
+    private static final HttpHeaders JSON_HEADERS;
+
+    static {
+        JSON_HEADERS = new HttpHeaders();
+        JSON_HEADERS.addHeader("Content-Type", "application/json");
+    }
+
+    /**
+     * 将已开具的航信电子发票插入用户微信卡包。
+     * 通过微信支付 v3 接口创建发票申请单后直接插卡。
+     *
+     * @return true 表示调用成功
+     */
+    public boolean insertToCard(Invoice invoice, InvoiceInfo invoiceInfo, String sellerTaxId) {
+        if (invoice.getOpenid() == null || invoice.getOpenid().isBlank()) {
+            log.info("用户无 openid,跳过卡包插入, applyId:{}", invoice.getApplyId());
+            return false;
+        }
+
+        try {
+            // Step 1: 创建发票申请单
+            var appReq = buildApplicationRequest(invoice, invoiceInfo, sellerTaxId);
+            log.info("创建微信发票申请单, applyId:{}, body:{}", invoice.getApplyId(), JSON.toJSONString(appReq));
+
+            HttpResponse<JSONObject> appResp = WxPayServiceImpl.wxHttpClient.post(
+                    JSON_HEADERS, API_BASE + CREATE_APPLICATION,
+                    new JsonRequestBody.Builder().body(JSON.toJSONString(appReq)).build(),
+                    JSONObject.class);
+
+            log.info("微信发票申请单响应, applyId:{}, resp:{}",
+                    invoice.getApplyId(), JSON.toJSONString(appResp.getServiceResponse()));
+
+            // Step 2: 插入卡包
+            var cardReq = new HashMap<String, Object>();
+            cardReq.put("card_appid", "wx93b0ef1be901bd19");
+            cardReq.put("card_openid", invoice.getOpenid());
+
+            String cardUrl = API_BASE + String.format(INSERT_CARD, invoice.getApplyId());
+            log.info("插入发票卡包, applyId:{}, body:{}", invoice.getApplyId(), JSON.toJSONString(cardReq));
+
+            HttpResponse<JSONObject> cardResp = WxPayServiceImpl.wxHttpClient.post(
+                    JSON_HEADERS, cardUrl,
+                    new JsonRequestBody.Builder().body(JSON.toJSONString(cardReq)).build(),
+                    JSONObject.class);
+
+            log.info("插入发票卡包响应, applyId:{}, resp:{}",
+                    invoice.getApplyId(), JSON.toJSONString(cardResp.getServiceResponse()));
+
+            return true;
+        } catch (Exception e) {
+            log.error("微信卡包插入异常, applyId:{}, msg:{}", invoice.getApplyId(), e.getMessage());
+            return false;
+        }
+    }
+
+    private HashMap<String, Object> buildApplicationRequest(Invoice invoice, InvoiceInfo result, String sellerTaxId) {
+        var orderDetails = invoice.getOrderDetails();
+        int elecMoney = orderDetails.stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
+        int serviceMoney = orderDetails.stream().mapToInt(InvoiceOrderDetail::getServiceMoney).sum();
+        int discount = orderDetails.stream().mapToInt(InvoiceOrderDetail::getServiceMoneyDiscount).sum();
+        int netServiceMoney = serviceMoney - discount;
+
+        var items = new ArrayList<HashMap<String, Object>>();
+        int totalAmount = 0;
+        int totalTax = 0;
+
+        if (elecMoney > 0) {
+            int amt = elecMoney;
+            int tax = amt - (int) Math.round(amt / 1.13);
+            items.add(buildItem("充电电费", amt, tax, "1100101020200000000", 1300));
+            totalAmount += amt;
+            totalTax += tax;
+        }
+        if (netServiceMoney > 0) {
+            int amt = netServiceMoney;
+            int tax = amt - (int) Math.round(amt / 1.13);
+            items.add(buildItem("充电服务费", amt, tax, "1100101020200000000", 1300));
+            totalAmount += amt;
+            totalTax += tax;
+        }
+
+        var fapiaoInfo = new HashMap<String, Object>();
+        fapiaoInfo.put("fapiao_id", invoice.getApplyId());
+        fapiaoInfo.put("total_amount", totalAmount);
+        fapiaoInfo.put("tax_amount", totalTax);
+        fapiaoInfo.put("amount", totalAmount - totalTax);
+        fapiaoInfo.put("items", items);
+        fapiaoInfo.put("invoice_type", "082");
+        if (invoice.getRemark() != null) {
+            fapiaoInfo.put("remark", invoice.getRemark());
+        }
+
+        var buyerInfo = new HashMap<String, Object>();
+        buyerInfo.put("type", invoice.getInvoiceType() != null && invoice.getInvoiceType().equals("企业")
+                ? "ENTERPRISE" : "INDIVIDUAL");
+        buyerInfo.put("name", invoice.getInvoiceTitle());
+        if (invoice.getTaxId() != null && !invoice.getTaxId().isBlank()) {
+            buyerInfo.put("taxpayer_id", invoice.getTaxId());
+        }
+
+        var req = new HashMap<String, Object>();
+        req.put("fapiao_apply_id", invoice.getApplyId());
+        req.put("scene", "WITHOUT_WECHATPAY");
+        req.put("buyer_information", buyerInfo);
+        req.put("fapiao_information", List.of(fapiaoInfo));
+
+        return req;
+    }
+
+    private HashMap<String, Object> buildItem(String name, int totalAmount, int taxAmount, String taxCode, int taxRate) {
+        int amount = totalAmount - taxAmount;
+        var item = new HashMap<String, Object>();
+        item.put("tax_code", taxCode);
+        item.put("goods_name", name);
+        item.put("specification", "");
+        item.put("unit", "");
+        item.put("quantity", 100000000);
+        item.put("unit_price", (long) totalAmount * 10000L);
+        item.put("amount", amount);
+        item.put("tax_amount", taxAmount);
+        item.put("total_amount", totalAmount);
+        item.put("tax_rate", taxRate);
+        item.put("tax_prefer_mark", "NO_FAVORABLE");
+        return item;
+    }
+}

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

@@ -81,6 +81,8 @@ public class WxPayServiceImpl implements WxPayService {
 
     public static Config config;
 
+    public static OkHttpClientAdapter wxHttpClient;
+
     private final WxPayConfig conf;
 
     private final WalletDetailService walletDetailService;
@@ -100,9 +102,6 @@ public class WxPayServiceImpl implements WxPayService {
     private final UserRechargeRightsService userRechargeRightsService;
 
 
-    private OkHttpClientAdapter wxHttpClient;
-
-
     public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService,
                             PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService,
                             RefundLogService refundLogService,