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 * @author xiaochen * @date 2022-03-22 12:47 */ public Map 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 verifyNotify(HttpServletRequest request, TypeReference 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 * @author xiaochen */ @Override public Map 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 refund_out_trade_no * @return */ @Override public Map queryRefund(String refund_out_trade_no){ String url = String.format("/v3/refund/domestic/refunds/%s", refund_out_trade_no); return queryRefund(this.httpClient, this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), url); } /** * 关闭订单 * @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); } }