|
|
@@ -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;
|
|
|
+ }
|
|
|
+}
|