package com.ruoyi.payment.wx.utils;
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.ruoyi.payment.wx.model.WeixinProperties;
|
import com.ruoyi.payment.wx.model.WxCloseOrderModel;
|
import com.ruoyi.payment.wx.model.WxPaymentInfoModel;
|
import com.ruoyi.payment.api.model.WxPaymentRefundModel;
|
import com.ruoyi.payment.wx.resp.NotifyV3PayDecodeRespBody;
|
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
|
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
|
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
|
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
|
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
|
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
|
import lombok.Getter;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
import javax.servlet.http.HttpServletRequest;
|
import java.nio.charset.StandardCharsets;
|
import java.security.PrivateKey;
|
import java.util.Map;
|
|
/**
|
* @author xiaochen
|
* @ClassName WxWifiV3Pay
|
* @Description
|
* @date 2021-11-13 21:10
|
*/
|
@Slf4j
|
public class WxV3Pay extends WxAbstractPay {
|
@Getter
|
private WeixinProperties config;
|
@Getter
|
private Verifier verifier;
|
private WechatPayHttpClientBuilder builder;
|
@Getter
|
private CloseableHttpClient httpClient;
|
private PrivateKeySigner privateKeySigner;
|
private WechatPay2Validator validator;
|
@Getter
|
private PrivateKey privateKey;
|
|
/**
|
* 初始化
|
*
|
* @param config
|
*/
|
public WxV3Pay(WeixinProperties config) {
|
// 检查v3支付配置信息
|
if (WxUtils.getLogger().isDebugEnabled()) {
|
WxUtils.debug("开始检查v3支付配置信息....");
|
}
|
try {
|
this.config = config;
|
checkWxConfig();
|
this.privateKey = PemUtil.loadPrivateKey(config.getV3().getPrivateKeyStream());
|
this.privateKeySigner = new PrivateKeySigner(config.getV3().getMchSerialNo(), privateKey);
|
// 获取证书管理器实例
|
CertificatesManager certificatesManager = CertificatesManager.getInstance();
|
// 向证书管理器增加需要自动更新平台证书的商户信息
|
certificatesManager.putMerchant(config.getMchId(), new WechatPay2Credentials(config.getMchId(),
|
this.privateKeySigner), config.getV3().getApiKey().getBytes(StandardCharsets.UTF_8));
|
// 从证书管理器中获取verifier
|
this.verifier = certificatesManager.getVerifier(config.getMchId());
|
this.validator = new WechatPay2Validator(verifier);
|
this.builder = WechatPayHttpClientBuilder.create()
|
.withMerchant(config.getMchId(), config.getV3().getMchSerialNo(), this.privateKey)
|
.withValidator(this.validator);
|
this.httpClient = this.builder.build();
|
} catch (Exception e) {
|
// 打印异常信息
|
e.printStackTrace();
|
WxUtils.warn("检查v3支付配置信息出现错误,直连商户商户号:{},{}", config.getMchId(), e.getMessage());
|
return;
|
}
|
if (WxUtils.getLogger().isDebugEnabled()) {
|
WxUtils.debug("检查v3支付配置信息完成,未出现异常....");
|
}
|
}
|
|
/**
|
* 检查支付配置信息
|
*
|
* @throws Exception
|
*/
|
private void checkWxConfig() throws Exception {
|
if (this.config == null) {
|
throw new Exception("config is null");
|
}
|
if (config.getMchId() == null || config.getMchId().trim().length() == 0) {
|
throw new Exception("MchID in config is empty");
|
}
|
if (config.getV3().getMchSerialNo() == null) {
|
throw new Exception("mchSerialNo in config is empty");
|
}
|
if (config.getV3().getPrivateKeyStream() == null) {
|
throw new Exception("cert stream in config is empty");
|
}
|
if (this.config.getHttpConnectTimeoutMs() < 10) {
|
throw new Exception("http connect timeout is too small");
|
}
|
if (this.config.getHttpReadTimeoutMs() < 10) {
|
throw new Exception("http read timeout is too small");
|
}
|
}
|
|
/**
|
* jsApi下单
|
*
|
* @param tradeNo 订单号
|
* @param amount 金额 分
|
* @param openid openid
|
* @param description 订单描述
|
* @return java.util.Map<java.lang.String, java.lang.Object>
|
* @author xiaochen
|
* @date 2022-03-22 12:47
|
*/
|
public Map<String, Object> jsApi(String tradeNo, Integer amount, String openid, String notify_url, String description) {
|
WxPaymentInfoModel requestBody = WxPaymentInfoModel.builder()
|
.mchid(this.config.getMchId())
|
.appid(this.config.getAppId())
|
.description(description)
|
.out_trade_no(tradeNo)
|
.notify_url(notify_url)
|
// .attach("")
|
.amount(WxPaymentInfoModel.Amount.builder().total(amount).build())
|
.payer(WxPaymentInfoModel.Payer.builder().openid(openid).build())
|
// 分不分账
|
//.settle_info(WxPaymentInfoModel.SettleInfo.builder().profit_sharing(true).build())
|
.build();
|
// 封装基础数据
|
String reqBody = buildBaseParam(requestBody
|
, this.config.getV3().getNotifyPayUrl());
|
//请求URL
|
HttpEntityEnclosingRequestBase httpPost = requestPost(
|
"/v3/pay/transactions/jsapi"
|
, this.config.getHttpReadTimeoutMs()
|
, this.config.getHttpConnectTimeoutMs()
|
, reqBody);
|
String repBody = result(httpClient, httpPost);
|
ObjectMapper om = new ObjectMapper();
|
try {
|
JsonNode rootNode = om.readTree(repBody);
|
String prepayId = rootNode.path("prepay_id").asText();
|
return wxTuneUp(this.privateKeySigner, requestBody.getAppid(), prepayId);
|
} catch (JsonProcessingException e) {
|
throw new RuntimeException("获取支付数据错误!");
|
}
|
}
|
|
|
/**
|
* 接收回调
|
*
|
* @param request
|
* @return
|
* @throws Exception
|
*/
|
@Override
|
public <T> T verifyNotify(HttpServletRequest request, TypeReference<T> valueTypeRef) throws Exception {
|
return verifyNotify(request, this.verifier, this.config.getV3().getApiKey(), valueTypeRef);
|
}
|
|
/**
|
* 订单查询
|
*
|
* @param out_trade_no
|
* @return com.abl.biz.center.payment.wx.v3.NotifyV3PayDecodeRespBody
|
* @author xiaochen
|
* @date 2021-12-20 16:47
|
*/
|
@Override
|
public NotifyV3PayDecodeRespBody query(String out_trade_no) {
|
String url =
|
String.format("/v3/pay/transactions/out-trade-no/%s", out_trade_no) + String.format("?mchid=%s", this.getConfig().getMchId());
|
return query(this.httpClient, this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), url);
|
}
|
|
/**
|
* 退款
|
*
|
* @param refundModel
|
* @return java.util.Map<java.lang.String, java.lang.Object>
|
* @author xiaochen
|
*/
|
@Override
|
public Map<String, Object> refund(WxPaymentRefundModel refundModel) {
|
refundModel.setNotify_url(this.config.getV3().getNotifyRefundUrl() + refundModel.getNotify_url());
|
return refund(this.httpClient, "/v3/refund/domestic/refunds", this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), refundModel);
|
}
|
|
|
/**
|
* 关闭订单
|
* @param out_trade_no
|
* @return
|
*/
|
@Override
|
public String close(String out_trade_no) {
|
String uri = String.format("/v3/pay/transactions/out-trade-no/%s/close", out_trade_no);
|
WxCloseOrderModel wxCloseOrderModel = new WxCloseOrderModel();
|
wxCloseOrderModel.setMchid(this.config.getMchId());
|
return close(this.httpClient, uri, this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), wxCloseOrderModel);
|
}
|
}
|