Sfoglia il codice sorgente

fix: 文件上传改用 OkHttp3 构建 multipart + SDK credential 签名

- SDK OkHttpClientAdapter 没有 upload 方法
- 用 SDK credential.getAuthorization() 生成签名头
- OkHttp3 MultipartBody 构建 multipart 请求
- 移除 tempFile 相关代码

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 3 giorni fa
parent
commit
0feea60306

+ 59 - 50
service/src/main/java/com/kym/service/wechat/impl/WechatPayFapiaoService.java

@@ -7,12 +7,9 @@ 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 okhttp3.OkHttpClient;
-import okhttp3.Request;
+import okhttp3.*;
 import org.springframework.stereotype.Service;
 
-import java.nio.file.Files;
-import java.nio.file.Path;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
@@ -34,7 +31,6 @@ public class WechatPayFapiaoService {
     private static final String UPLOAD_FILE = "/v3/new-tax-control-fapiao/fapiao-applications/%s/fapiao-files";
     private static final String INSERT_CARDS = "/v3/new-tax-control-fapiao/fapiao-applications/%s/insert-cards";
     private static final String MCH_SERIAL = "6A45EEB068369430B2FFD45EA29F641A8E18165F";
-    private static final String APP_ID = "wx93b0ef1be901bd19";
 
     private static final HttpHeaders JSON_HEADERS;
 
@@ -42,10 +38,9 @@ public class WechatPayFapiaoService {
         JSON_HEADERS = new HttpHeaders();
         JSON_HEADERS.addHeader("Accept", "application/json");
         JSON_HEADERS.addHeader("Content-Type", "application/json");
-        JSON_HEADERS.addHeader("Wechatpay-Serial", MCH_SERIAL);
     }
 
-    private final OkHttpClient downloadClient = new OkHttpClient.Builder()
+    private final OkHttpClient okHttpClient = new OkHttpClient.Builder()
             .connectTimeout(15, TimeUnit.SECONDS)
             .readTimeout(30, TimeUnit.SECONDS)
             .build();
@@ -62,7 +57,6 @@ public class WechatPayFapiaoService {
             return false;
         }
 
-        Path tempFile = null;
         try {
             // Step 1: 下载航信 PDF
             byte[] pdfBytes = downloadPdf(invoiceInfo.getInvoiceurl());
@@ -71,29 +65,25 @@ public class WechatPayFapiaoService {
                 return false;
             }
 
-            // Step 2: 上传 PDF 到微信支付,获取 fapiao_media_id
-            tempFile = Files.createTempFile("fapiao_", ".pdf");
-            Files.write(tempFile, pdfBytes);
-
+            // Step 2: 上传 PDF 到微信支付
             String meta = JSON.toJSONString(Map.of(
                     "file_type", "FAPIAO_PDF",
                     "fapiao_apply_id", invoice.getApplyId()
             ));
 
-            HttpHeaders uploadHeaders = new HttpHeaders();
-            uploadHeaders.addHeader("Accept", "application/json");
-            uploadHeaders.addHeader("Wechatpay-Serial", MCH_SERIAL);
-
             String uploadUrl = API_BASE + String.format(UPLOAD_FILE, invoice.getApplyId());
-            log.info("上传发票文件, applyId:{}, url:{}", invoice.getApplyId(), uploadUrl);
+            log.info("上传发票文件, applyId:{}, meta:{}", invoice.getApplyId(), meta);
 
-            HttpResponse<JSONObject> uploadResp = WxPayServiceImpl.wxHttpClient.upload(
-                    uploadHeaders, uploadUrl, meta, tempFile.toString(), JSONObject.class);
+            String uploadRespBody = uploadFile(uploadUrl, meta, pdfBytes);
+            if (uploadRespBody == null) {
+                log.warn("上传发票文件失败, applyId:{}", invoice.getApplyId());
+                return false;
+            }
 
-            JSONObject uploadBody = uploadResp.getServiceResponse();
-            log.info("上传发票文件响应, applyId:{}, resp:{}", invoice.getApplyId(), JSON.toJSONString(uploadBody));
+            JSONObject uploadJson = JSONObject.parseObject(uploadRespBody);
+            String mediaId = uploadJson.getString("fapiao_media_id");
+            log.info("上传发票文件成功, applyId:{}, mediaId:{}", invoice.getApplyId(), mediaId);
 
-            String mediaId = uploadBody.getString("fapiao_media_id");
             if (mediaId == null || mediaId.isBlank()) {
                 log.warn("未获取到 fapiao_media_id, applyId:{}", invoice.getApplyId());
                 return false;
@@ -109,27 +99,23 @@ public class WechatPayFapiaoService {
                     new JsonRequestBody.Builder().body(JSON.toJSONString(cardReq)).build(),
                     JSONObject.class);
 
-            log.info("插入发票卡包响应, applyId:{}, httpCode:{}, resp:{}",
-                    invoice.getApplyId(), cardResp.getHttpStatusCode(),
-                    JSON.toJSONString(cardResp.getServiceResponse()));
+            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;
-        } finally {
-            if (tempFile != null) {
-                try { Files.deleteIfExists(tempFile); } catch (Exception ignored) {}
-            }
         }
     }
 
     private byte[] downloadPdf(String url) {
         try {
             var req = new Request.Builder().url(url).get().build();
-            var resp = downloadClient.newCall(req).execute();
-            if (resp.isSuccessful() && resp.body() != null) {
-                return resp.body().bytes();
+            try (var resp = okHttpClient.newCall(req).execute()) {
+                if (resp.isSuccessful() && resp.body() != null) {
+                    return resp.body().bytes();
+                }
             }
         } catch (Exception e) {
             log.error("下载 PDF 异常, url:{}", url, e);
@@ -137,6 +123,43 @@ public class WechatPayFapiaoService {
         return null;
     }
 
+    /**
+     * 上传文件到微信支付,使用 SDK credential 签名 + OkHttp3 发送 multipart。
+     */
+    private String uploadFile(String url, String meta, byte[] pdfBytes) throws Exception {
+        // 构建 multipart body
+        String boundary = "WxPayUpload" + System.currentTimeMillis();
+        okhttp3.RequestBody multipartBody = new MultipartBody.Builder(boundary)
+                .setType(MultipartBody.FORM)
+                .addFormDataPart("meta", null,
+                        okhttp3.RequestBody.create(meta, MediaType.parse("application/json")))
+                .addFormDataPart("file", "invoice.pdf",
+                        okhttp3.RequestBody.create(pdfBytes, MediaType.parse("application/pdf")))
+                .build();
+
+        // 用 SDK 生成签名 — 签名的 body 是 meta 字符串
+        String auth = WxPayServiceImpl.config.createCredential()
+                .getAuthorization(url, "POST", meta);
+
+        // 发送请求
+        okhttp3.Request request = new Request.Builder()
+                .url(url)
+                .header("Authorization", auth)
+                .header("Accept", "application/json")
+                .header("Wechatpay-Serial", MCH_SERIAL)
+                .post(multipartBody)
+                .build();
+
+        try (okhttp3.Response response = okHttpClient.newCall(request).execute()) {
+            if (response.isSuccessful() && response.body() != null) {
+                return response.body().string();
+            }
+            String errBody = response.body() != null ? response.body().string() : "";
+            log.error("上传文件 HTTP {}, body:{}", response.code(), errBody);
+            return null;
+        }
+    }
+
     private HashMap<String, Object> buildInsertCardsRequest(Invoice invoice, InvoiceInfo info, String mediaId) {
         var orderDetails = invoice.getOrderDetails();
         int elecMoney = orderDetails.stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
@@ -150,20 +173,18 @@ public class WechatPayFapiaoService {
 
         if (elecMoney > 0) {
             int tax = elecMoney - (int) Math.round(elecMoney / 1.13);
-            int amt = elecMoney - tax;
-            items.add(buildItem("充电电费", amt, tax, elecMoney, "1100101020200000000", 1300));
             totalAmount += elecMoney;
             totalTax += tax;
+            items.add(buildItem("充电电费", elecMoney, "1100101020200000000", 1300));
         }
         if (netServiceMoney > 0) {
             int tax = netServiceMoney - (int) Math.round(netServiceMoney / 1.13);
-            int amt = netServiceMoney - tax;
-            items.add(buildItem("充电服务费", amt, tax, netServiceMoney, "1100101020200000000", 1300));
             totalAmount += netServiceMoney;
             totalTax += tax;
+            items.add(buildItem("充电服务费", netServiceMoney, "1100101020200000000", 1300));
         }
 
-        // 票面时间 RFC 3339
+        // 票面时间 RFC 3339
         String ticketDate = info.getTicketDate();
         if (ticketDate != null && ticketDate.length() == 16) {
             ticketDate += ":00";
@@ -173,22 +194,12 @@ public class WechatPayFapiaoService {
                         .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00"))
                 : LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00"));
 
-        // 销售方信息
         var sellerInfo = new HashMap<String, Object>();
         sellerInfo.put("name", "深圳市快与慢科技有限公司");
         sellerInfo.put("taxpayer_id", "91440300MA5HJNDG14");
-        sellerInfo.put("address", "");
-        sellerInfo.put("telephone", "");
-        sellerInfo.put("bank_name", "");
-        sellerInfo.put("bank_account", "");
 
-        // 附加信息
         var extraInfo = new HashMap<String, Object>();
-        extraInfo.put("payee", "");
-        extraInfo.put("reviewer", "");
-        extraInfo.put("drawer", "");
 
-        // 卡券信息
         var cardInfo = new HashMap<String, Object>();
         cardInfo.put("fapiao_media_id", mediaId);
         cardInfo.put("fapiao_number", info.getInvoicenumber());
@@ -204,7 +215,6 @@ public class WechatPayFapiaoService {
             cardInfo.put("remark", invoice.getRemark());
         }
 
-        // 购买方
         var buyerInfo = new HashMap<String, Object>();
         buyerInfo.put("type", invoice.getInvoiceType());
         buyerInfo.put("name", invoice.getInvoiceTitle());
@@ -220,8 +230,7 @@ public class WechatPayFapiaoService {
         return req;
     }
 
-    private HashMap<String, Object> buildItem(String name, int amount, int taxAmount, int totalAmount,
-                                               String taxCode, int taxRate) {
+    private HashMap<String, Object> buildItem(String name, int totalAmount, String taxCode, int taxRate) {
         var item = new HashMap<String, Object>();
         item.put("tax_code", taxCode);
         item.put("goods_name", name);