|
@@ -7,12 +7,9 @@ import com.kym.entity.wechat.InvoiceOrderDetail;
|
|
|
import com.kym.huapiaoer.model.response.InvoiceInfo;
|
|
import com.kym.huapiaoer.model.response.InvoiceInfo;
|
|
|
import com.wechat.pay.java.core.http.*;
|
|
import com.wechat.pay.java.core.http.*;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import okhttp3.OkHttpClient;
|
|
|
|
|
-import okhttp3.Request;
|
|
|
|
|
|
|
+import okhttp3.*;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
-import java.nio.file.Files;
|
|
|
|
|
-import java.nio.file.Path;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.ArrayList;
|
|
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 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 INSERT_CARDS = "/v3/new-tax-control-fapiao/fapiao-applications/%s/insert-cards";
|
|
|
private static final String MCH_SERIAL = "6A45EEB068369430B2FFD45EA29F641A8E18165F";
|
|
private static final String MCH_SERIAL = "6A45EEB068369430B2FFD45EA29F641A8E18165F";
|
|
|
- private static final String APP_ID = "wx93b0ef1be901bd19";
|
|
|
|
|
|
|
|
|
|
private static final HttpHeaders JSON_HEADERS;
|
|
private static final HttpHeaders JSON_HEADERS;
|
|
|
|
|
|
|
@@ -42,10 +38,9 @@ public class WechatPayFapiaoService {
|
|
|
JSON_HEADERS = new HttpHeaders();
|
|
JSON_HEADERS = new HttpHeaders();
|
|
|
JSON_HEADERS.addHeader("Accept", "application/json");
|
|
JSON_HEADERS.addHeader("Accept", "application/json");
|
|
|
JSON_HEADERS.addHeader("Content-Type", "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)
|
|
.connectTimeout(15, TimeUnit.SECONDS)
|
|
|
.readTimeout(30, TimeUnit.SECONDS)
|
|
.readTimeout(30, TimeUnit.SECONDS)
|
|
|
.build();
|
|
.build();
|
|
@@ -62,7 +57,6 @@ public class WechatPayFapiaoService {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Path tempFile = null;
|
|
|
|
|
try {
|
|
try {
|
|
|
// Step 1: 下载航信 PDF
|
|
// Step 1: 下载航信 PDF
|
|
|
byte[] pdfBytes = downloadPdf(invoiceInfo.getInvoiceurl());
|
|
byte[] pdfBytes = downloadPdf(invoiceInfo.getInvoiceurl());
|
|
@@ -71,29 +65,25 @@ public class WechatPayFapiaoService {
|
|
|
return false;
|
|
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(
|
|
String meta = JSON.toJSONString(Map.of(
|
|
|
"file_type", "FAPIAO_PDF",
|
|
"file_type", "FAPIAO_PDF",
|
|
|
"fapiao_apply_id", invoice.getApplyId()
|
|
"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());
|
|
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()) {
|
|
if (mediaId == null || mediaId.isBlank()) {
|
|
|
log.warn("未获取到 fapiao_media_id, applyId:{}", invoice.getApplyId());
|
|
log.warn("未获取到 fapiao_media_id, applyId:{}", invoice.getApplyId());
|
|
|
return false;
|
|
return false;
|
|
@@ -109,27 +99,23 @@ public class WechatPayFapiaoService {
|
|
|
new JsonRequestBody.Builder().body(JSON.toJSONString(cardReq)).build(),
|
|
new JsonRequestBody.Builder().body(JSON.toJSONString(cardReq)).build(),
|
|
|
JSONObject.class);
|
|
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;
|
|
return true;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("微信卡包插入异常, applyId:{}, msg:{}", invoice.getApplyId(), e.getMessage());
|
|
log.error("微信卡包插入异常, applyId:{}, msg:{}", invoice.getApplyId(), e.getMessage());
|
|
|
return false;
|
|
return false;
|
|
|
- } finally {
|
|
|
|
|
- if (tempFile != null) {
|
|
|
|
|
- try { Files.deleteIfExists(tempFile); } catch (Exception ignored) {}
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private byte[] downloadPdf(String url) {
|
|
private byte[] downloadPdf(String url) {
|
|
|
try {
|
|
try {
|
|
|
var req = new Request.Builder().url(url).get().build();
|
|
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) {
|
|
} catch (Exception e) {
|
|
|
log.error("下载 PDF 异常, url:{}", url, e);
|
|
log.error("下载 PDF 异常, url:{}", url, e);
|
|
@@ -137,6 +123,43 @@ public class WechatPayFapiaoService {
|
|
|
return null;
|
|
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) {
|
|
private HashMap<String, Object> buildInsertCardsRequest(Invoice invoice, InvoiceInfo info, String mediaId) {
|
|
|
var orderDetails = invoice.getOrderDetails();
|
|
var orderDetails = invoice.getOrderDetails();
|
|
|
int elecMoney = orderDetails.stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
|
|
int elecMoney = orderDetails.stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
|
|
@@ -150,20 +173,18 @@ public class WechatPayFapiaoService {
|
|
|
|
|
|
|
|
if (elecMoney > 0) {
|
|
if (elecMoney > 0) {
|
|
|
int tax = elecMoney - (int) Math.round(elecMoney / 1.13);
|
|
int tax = elecMoney - (int) Math.round(elecMoney / 1.13);
|
|
|
- int amt = elecMoney - tax;
|
|
|
|
|
- items.add(buildItem("充电电费", amt, tax, elecMoney, "1100101020200000000", 1300));
|
|
|
|
|
totalAmount += elecMoney;
|
|
totalAmount += elecMoney;
|
|
|
totalTax += tax;
|
|
totalTax += tax;
|
|
|
|
|
+ items.add(buildItem("充电电费", elecMoney, "1100101020200000000", 1300));
|
|
|
}
|
|
}
|
|
|
if (netServiceMoney > 0) {
|
|
if (netServiceMoney > 0) {
|
|
|
int tax = netServiceMoney - (int) Math.round(netServiceMoney / 1.13);
|
|
int tax = netServiceMoney - (int) Math.round(netServiceMoney / 1.13);
|
|
|
- int amt = netServiceMoney - tax;
|
|
|
|
|
- items.add(buildItem("充电服务费", amt, tax, netServiceMoney, "1100101020200000000", 1300));
|
|
|
|
|
totalAmount += netServiceMoney;
|
|
totalAmount += netServiceMoney;
|
|
|
totalTax += tax;
|
|
totalTax += tax;
|
|
|
|
|
+ items.add(buildItem("充电服务费", netServiceMoney, "1100101020200000000", 1300));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 票面时间转 RFC 3339
|
|
|
|
|
|
|
+ // 票面时间 → RFC 3339
|
|
|
String ticketDate = info.getTicketDate();
|
|
String ticketDate = info.getTicketDate();
|
|
|
if (ticketDate != null && ticketDate.length() == 16) {
|
|
if (ticketDate != null && ticketDate.length() == 16) {
|
|
|
ticketDate += ":00";
|
|
ticketDate += ":00";
|
|
@@ -173,22 +194,12 @@ public class WechatPayFapiaoService {
|
|
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00"))
|
|
.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"));
|
|
: LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00"));
|
|
|
|
|
|
|
|
- // 销售方信息
|
|
|
|
|
var sellerInfo = new HashMap<String, Object>();
|
|
var sellerInfo = new HashMap<String, Object>();
|
|
|
sellerInfo.put("name", "深圳市快与慢科技有限公司");
|
|
sellerInfo.put("name", "深圳市快与慢科技有限公司");
|
|
|
sellerInfo.put("taxpayer_id", "91440300MA5HJNDG14");
|
|
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>();
|
|
var extraInfo = new HashMap<String, Object>();
|
|
|
- extraInfo.put("payee", "");
|
|
|
|
|
- extraInfo.put("reviewer", "");
|
|
|
|
|
- extraInfo.put("drawer", "");
|
|
|
|
|
|
|
|
|
|
- // 卡券信息
|
|
|
|
|
var cardInfo = new HashMap<String, Object>();
|
|
var cardInfo = new HashMap<String, Object>();
|
|
|
cardInfo.put("fapiao_media_id", mediaId);
|
|
cardInfo.put("fapiao_media_id", mediaId);
|
|
|
cardInfo.put("fapiao_number", info.getInvoicenumber());
|
|
cardInfo.put("fapiao_number", info.getInvoicenumber());
|
|
@@ -204,7 +215,6 @@ public class WechatPayFapiaoService {
|
|
|
cardInfo.put("remark", invoice.getRemark());
|
|
cardInfo.put("remark", invoice.getRemark());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 购买方
|
|
|
|
|
var buyerInfo = new HashMap<String, Object>();
|
|
var buyerInfo = new HashMap<String, Object>();
|
|
|
buyerInfo.put("type", invoice.getInvoiceType());
|
|
buyerInfo.put("type", invoice.getInvoiceType());
|
|
|
buyerInfo.put("name", invoice.getInvoiceTitle());
|
|
buyerInfo.put("name", invoice.getInvoiceTitle());
|
|
@@ -220,8 +230,7 @@ public class WechatPayFapiaoService {
|
|
|
return req;
|
|
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>();
|
|
var item = new HashMap<String, Object>();
|
|
|
item.put("tax_code", taxCode);
|
|
item.put("tax_code", taxCode);
|
|
|
item.put("goods_name", name);
|
|
item.put("goods_name", name);
|