|
@@ -8,6 +8,7 @@ import cn.hutool.core.io.IoUtil;
|
|
|
import cn.hutool.core.util.RandomUtil;
|
|
import cn.hutool.core.util.RandomUtil;
|
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
|
|
|
|
+import com.kym.common.config.WxFapiaoConfig;
|
|
|
import com.kym.common.config.WxPayConfig;
|
|
import com.kym.common.config.WxPayConfig;
|
|
|
import com.kym.common.constant.ResponseEnum;
|
|
import com.kym.common.constant.ResponseEnum;
|
|
|
import com.kym.common.exception.BusinessException;
|
|
import com.kym.common.exception.BusinessException;
|
|
@@ -15,19 +16,18 @@ import com.kym.common.utils.CommUtil;
|
|
|
import com.kym.common.utils.HttpUtil;
|
|
import com.kym.common.utils.HttpUtil;
|
|
|
import com.kym.common.utils.LambadaTools;
|
|
import com.kym.common.utils.LambadaTools;
|
|
|
import com.kym.common.utils.OrderUtils;
|
|
import com.kym.common.utils.OrderUtils;
|
|
|
|
|
+import com.kym.entity.miniapp.*;
|
|
|
import com.kym.entity.miniapp.Account;
|
|
import com.kym.entity.miniapp.Account;
|
|
|
-import com.kym.entity.miniapp.PayLog;
|
|
|
|
|
-import com.kym.entity.miniapp.RefundLog;
|
|
|
|
|
-import com.kym.entity.miniapp.WalletDetail;
|
|
|
|
|
-import com.kym.entity.wechat.FaPiao;
|
|
|
|
|
-import com.kym.entity.wechat.InvoiceBaseInfo;
|
|
|
|
|
-import com.kym.entity.wechat.InvoiceNotification;
|
|
|
|
|
-import com.kym.entity.wechat.TaxCodes;
|
|
|
|
|
|
|
+import com.kym.entity.wechat.*;
|
|
|
import com.kym.service.miniapp.*;
|
|
import com.kym.service.miniapp.*;
|
|
|
import com.kym.service.wechat.WxPayService;
|
|
import com.kym.service.wechat.WxPayService;
|
|
|
import com.wechat.pay.java.core.Config;
|
|
import com.wechat.pay.java.core.Config;
|
|
|
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
|
|
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
|
|
|
import com.wechat.pay.java.core.exception.ValidationException;
|
|
import com.wechat.pay.java.core.exception.ValidationException;
|
|
|
|
|
+import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
|
|
|
|
|
+import com.wechat.pay.java.core.http.HttpHeaders;
|
|
|
|
|
+import com.wechat.pay.java.core.http.JsonRequestBody;
|
|
|
|
|
+import com.wechat.pay.java.core.http.okhttp.OkHttpClientAdapter;
|
|
|
import com.wechat.pay.java.core.notification.NotificationConfig;
|
|
import com.wechat.pay.java.core.notification.NotificationConfig;
|
|
|
import com.wechat.pay.java.core.notification.NotificationParser;
|
|
import com.wechat.pay.java.core.notification.NotificationParser;
|
|
|
import com.wechat.pay.java.core.notification.RequestParam;
|
|
import com.wechat.pay.java.core.notification.RequestParam;
|
|
@@ -41,7 +41,6 @@ import com.wechat.pay.java.service.refund.model.*;
|
|
|
import jakarta.annotation.PostConstruct;
|
|
import jakarta.annotation.PostConstruct;
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
|
import lombok.SneakyThrows;
|
|
import lombok.SneakyThrows;
|
|
|
-import okhttp3.Headers;
|
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.core.io.ClassPathResource;
|
|
import org.springframework.core.io.ClassPathResource;
|
|
@@ -55,7 +54,10 @@ import java.io.IOException;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
|
|
+import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -76,6 +78,8 @@ public class WxPayServiceImpl implements WxPayService {
|
|
|
|
|
|
|
|
private final WxPayConfig conf;
|
|
private final WxPayConfig conf;
|
|
|
|
|
|
|
|
|
|
+ private final WxFapiaoConfig fapiaoConfig;
|
|
|
|
|
+
|
|
|
private final WalletDetailService walletDetailService;
|
|
private final WalletDetailService walletDetailService;
|
|
|
|
|
|
|
|
private final PayLogService payLogService;
|
|
private final PayLogService payLogService;
|
|
@@ -86,14 +90,23 @@ public class WxPayServiceImpl implements WxPayService {
|
|
|
|
|
|
|
|
private final RefundLogService refundLogService;
|
|
private final RefundLogService refundLogService;
|
|
|
|
|
|
|
|
|
|
+ private final InvoiceService invoiceService;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 微信支付专用,支持自动签名验签解密等
|
|
|
|
|
+ */
|
|
|
|
|
+ private OkHttpClientAdapter wxHttpClient;
|
|
|
|
|
+
|
|
|
|
|
|
|
|
- public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService, RefundLogService refundLogService) {
|
|
|
|
|
|
|
+ public WxPayServiceImpl(WxPayConfig conf, WxFapiaoConfig fapiaoConfig, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService, RefundLogService refundLogService, InvoiceService invoiceService) {
|
|
|
this.conf = conf;
|
|
this.conf = conf;
|
|
|
|
|
+ this.fapiaoConfig = fapiaoConfig;
|
|
|
this.walletDetailService = walletDetailService;
|
|
this.walletDetailService = walletDetailService;
|
|
|
this.payLogService = payLogService;
|
|
this.payLogService = payLogService;
|
|
|
this.accountService = accountService;
|
|
this.accountService = accountService;
|
|
|
this.chargeOrderService = chargeOrderService;
|
|
this.chargeOrderService = chargeOrderService;
|
|
|
this.refundLogService = refundLogService;
|
|
this.refundLogService = refundLogService;
|
|
|
|
|
+ this.invoiceService = invoiceService;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -130,6 +143,8 @@ public class WxPayServiceImpl implements WxPayService {
|
|
|
.build();
|
|
.build();
|
|
|
jsapiService = new JsapiService.Builder().config(config).build();
|
|
jsapiService = new JsapiService.Builder().config(config).build();
|
|
|
refundService = new RefundService.Builder().config(config).build();
|
|
refundService = new RefundService.Builder().config(config).build();
|
|
|
|
|
+
|
|
|
|
|
+ wxHttpClient = (OkHttpClientAdapter) new DefaultHttpClientBuilder().newInstance().config(config).build();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -518,53 +533,73 @@ public class WxPayServiceImpl implements WxPayService {
|
|
|
/**
|
|
/**
|
|
|
* 获取商户开票基础信息
|
|
* 获取商户开票基础信息
|
|
|
*/
|
|
*/
|
|
|
- private InvoiceBaseInfo baseInformation() {
|
|
|
|
|
- var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/base-information";
|
|
|
|
|
- Headers headers = Headers.of(
|
|
|
|
|
- "Authorization", "",
|
|
|
|
|
- "Accept", "application/json",
|
|
|
|
|
- "Content-Type", "application/json"
|
|
|
|
|
- );
|
|
|
|
|
- var res = HttpUtil.post(url, headers);
|
|
|
|
|
- return JSONObject.parseObject(res).toJavaObject(InvoiceBaseInfo.class);
|
|
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public InvoiceBaseInfo baseInformation() {
|
|
|
|
|
+ var headers = new HttpHeaders();
|
|
|
|
|
+ headers.addHeader("Accept", "application/json");
|
|
|
|
|
+ var res = wxHttpClient.get(headers, fapiaoConfig.getBaseInformation(), InvoiceBaseInfo.class);
|
|
|
|
|
+ return res.getServiceResponse();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 获取商户可开具的商品和服务税收分类编码对照表
|
|
* 获取商户可开具的商品和服务税收分类编码对照表
|
|
|
*/
|
|
*/
|
|
|
- private TaxCodes.TaxCodeItem getTaxCodes() {
|
|
|
|
|
- var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/merchant/tax-codes";
|
|
|
|
|
- Headers headers = Headers.of(
|
|
|
|
|
- "Authorization", "",
|
|
|
|
|
- "Accept", "application/json"
|
|
|
|
|
- );
|
|
|
|
|
- var params = """
|
|
|
|
|
- {
|
|
|
|
|
- "offset" : %d,
|
|
|
|
|
- "limit" :%d
|
|
|
|
|
- }
|
|
|
|
|
- """.stripIndent().formatted(0, 100);
|
|
|
|
|
- var res = HttpUtil.post(url, params, headers);
|
|
|
|
|
- return JSONObject.parseObject(res).toJavaObject(TaxCodes.TaxCodeItem.class);
|
|
|
|
|
|
|
+ private TaxCodes getTaxCodes() {
|
|
|
|
|
+ var headers = new HttpHeaders();
|
|
|
|
|
+ headers.addHeader("Accept", "application/json");
|
|
|
|
|
+ var url = fapiaoConfig.getTaxCodes().concat("?offset=%d&limit=%d").formatted(0, 5);
|
|
|
|
|
+ var res = wxHttpClient.get(headers, url, TaxCodes.class);
|
|
|
|
|
+ return res.getServiceResponse();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 申请发票
|
|
* 申请发票
|
|
|
*/
|
|
*/
|
|
|
- public void applyInvoice() {
|
|
|
|
|
- var url = "https://api.mch.weixin.qq.com/v3/new-tax-control-fapiao/fapiao-applications";
|
|
|
|
|
- Headers headers = Headers.of(
|
|
|
|
|
- "Authorization", "",
|
|
|
|
|
- "Accept", "application/json",
|
|
|
|
|
- "Content-Type", "application/json",
|
|
|
|
|
- "Wechatpay-Serial", "6A45EEB068369430B2FFD45EA29F641A8E18165F"
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- var invoiceId = OrderUtils.getOrderNo();
|
|
|
|
|
- var fapiao = new FaPiao();
|
|
|
|
|
- var res = HttpUtil.post(url, JSONObject.toJSONString(fapiao), headers);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ public void applyInvoice(String invoiceId) {
|
|
|
|
|
+ var invoice = invoiceService.lambdaQuery().eq(Invoice::getId,invoiceId).one();
|
|
|
|
|
+ // 电费 税务商品编码:1100101020000000000
|
|
|
|
|
+ var elecMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
|
|
|
|
|
+ var elecMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(elecMoney).setTaxCode("1100101020000000000");
|
|
|
|
|
+ // 服务费 税务商品编码:3040100000000000000
|
|
|
|
|
+ var serviceMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
|
|
|
|
|
+ var serviceMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotalAmount(serviceMoney).setTaxCode("3040100000000000000");
|
|
|
|
|
+
|
|
|
|
|
+ var headers = new HttpHeaders();
|
|
|
|
|
+ headers.addHeader("Accept", "application/json");
|
|
|
|
|
+ headers.addHeader("Content-Type", "application/json");
|
|
|
|
|
+ headers.addHeader("Wechatpay-Serial", conf.getMchsn());
|
|
|
|
|
+
|
|
|
|
|
+ // 发票申请单号
|
|
|
|
|
+ var fapiaoApplyId = OrderUtils.getOrderNo();
|
|
|
|
|
+
|
|
|
|
|
+ // 客户开票信息
|
|
|
|
|
+ var buyerInformation = new FaPiao.BuyerInformation()
|
|
|
|
|
+ .setType(invoice.getInvoiceType())
|
|
|
|
|
+ .setName(invoice.getInvoiceTitle())
|
|
|
|
|
+ .setTaxpayer_id(invoice.getTaxId())
|
|
|
|
|
+ .setAddress(invoice.getAddress())
|
|
|
|
|
+ .setTelephone(invoice.getPhone())
|
|
|
|
|
+ .setBank_name(invoice.getBankName())
|
|
|
|
|
+ .setBank_account(invoice.getBankAccount());
|
|
|
|
|
+
|
|
|
|
|
+ // 发票信息
|
|
|
|
|
+ var fapiaoInformation = new FaPiao.FaPiaoInfomation();
|
|
|
|
|
+ fapiaoInformation.setFapiao_id(String.valueOf(invoice.getId()))
|
|
|
|
|
+ .setTotal_amount(invoice.getInvoiceAmount())
|
|
|
|
|
+ .setItems(List.of(elecMoneyItem,serviceMoneyItem));
|
|
|
|
|
+
|
|
|
|
|
+ // 请求参数
|
|
|
|
|
+ var fapiao = new FaPiao()
|
|
|
|
|
+ .setScene("WITHOUT_WECHATPAY")
|
|
|
|
|
+ .setFapiao_apply_id(fapiaoApplyId)
|
|
|
|
|
+ .setBuyer_information(buyerInformation)
|
|
|
|
|
+ .setFapiao_information(List.of(fapiaoInformation));
|
|
|
|
|
+
|
|
|
|
|
+ var requestBody = new JsonRequestBody.Builder().body(JSONObject.toJSONString(fapiao)).build();
|
|
|
|
|
+ wxHttpClient.post(headers, fapiaoConfig.getFapiaoApplications(), requestBody,null);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -580,7 +615,7 @@ public class WxPayServiceImpl implements WxPayService {
|
|
|
LOGGER.info("微信开具发票结果通知回调{}:验签解密完毕,数据:\n{}", notifyRes[2], invoiceNotification);
|
|
LOGGER.info("微信开具发票结果通知回调{}:验签解密完毕,数据:\n{}", notifyRes[2], invoiceNotification);
|
|
|
// TODO: 2023-09-17 业务逻辑
|
|
// TODO: 2023-09-17 业务逻辑
|
|
|
|
|
|
|
|
- } catch (ValidationException e) {
|
|
|
|
|
|
|
+ } catch (ValidationException e) {
|
|
|
// 签名验证失败,返回 401 UNAUTHORIZED 状态码
|
|
// 签名验证失败,返回 401 UNAUTHORIZED 状态码
|
|
|
LOGGER.error("微信开具发票结果通知验签失败", e);
|
|
LOGGER.error("微信开具发票结果通知验签失败", e);
|
|
|
throw new BusinessException("验签失败");
|
|
throw new BusinessException("验签失败");
|