Files
Arbitrate-Backend/pay/src/main/java/com/ruoyi/wx/WxPayElegentTrade.java
T
2023-09-09 16:50:34 +08:00

460 lines
19 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.ruoyi.wx;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.ruoyi.annotation.TradePlatform;
import com.ruoyi.config.CallbackConfig;
import com.ruoyi.constant.PayConstant;
import com.ruoyi.constant.Platform;
import com.ruoyi.core.ElegentTrade;
import com.ruoyi.dto.*;
import com.ruoyi.exceptions.TradeException;
import com.ruoyi.util.FileUtil;
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.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.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
@TradePlatform(Platform.WX)
public class WxPayElegentTrade implements ElegentTrade {
@Autowired
private WxpayConfig wxpayConfig;
@Autowired
private CallbackConfig callbackConfig;
/**
* 创建微信支付订单方法
* 这里参考官网代码:
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml Native下单
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml JSAPI 下单
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml H5下单
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml APP下单
* @param payRequest 支付请求
* @return 支付响应
* @throws IOException
*/
@Override
public PayResponse requestPay(PayRequest payRequest, String tradeType)throws TradeException {
PayResponse payResponse = new PayResponse(); //返回结果
try {
// 请求body参数构建
Map<String, Object> params = new HashMap<String, Object>() {
{
put("mchid", wxpayConfig.getMchId());
put("appid", wxpayConfig.getAppId());
put("notify_url", callbackConfig.getDomain()+ PayConstant.CALLBACK_PATH + PayConstant.NOTIFY +"/"+ Platform.WX );
put("out_trade_no", payRequest.getOrderSn());
put("amount", new HashMap<String, Object>() {
{
put("total", payRequest.getTotalFee());//金额,单位:分
put("currency", "CNY");//人民币
}
});
put("description", payRequest.getBody());
}
};
if("h5".equals(tradeType)){ //h5
params.put("scene_info", new HashMap<String, Object>() {
{
put("payer_client_ip", "127.0.0.1");
put("h5_info", new HashMap<String, Object>() {
{
put("type", "Wap");
}
});
}
});
}
if ("jsapi".equals(tradeType)) { //如果是小程序支付
params.put("payer", new HashMap<String, Object>() {
{
put("openid", payRequest.getOpenid());
}
});
}
String url = WxpayConstant.createOrder + tradeType; //创建订单
log.info("elegent-pay 请求参数{}",params);
Map<String, String> map = postApiTemplate(url, params);
if("SUCCESS".equals( map.get("code") )){
payResponse.setOrder_sn(payRequest.getOrderSn());
payResponse.setSuccess(true);
payResponse.setCode_url(map.get("code_url"));
payResponse.setMessage(map.get("message"));
payResponse.setPrepay_id(map.get("prepay_id"));
payResponse.setH5_url( map.get("h5_url") );
payResponse.setExpand(map); //全部数据
if("jsapi".equals(tradeType)){//如果是小程序,需要封装到Expand
Map<String, String> data=new HashMap<>();
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = IdUtil.simpleUUID();
String packages = "prepay_id=" + payResponse.getPrepay_id();
String privateKey = FileUtil.readToStr("wxpay_private.key");
String paySign = this.createPaySign(wxpayConfig.getAppId(), timeStamp, nonceStr, packages, privateKey);
data.put("appId", wxpayConfig.getAppId());// appid
data.put("timeStamp", timeStamp);// 时间戳
data.put("nonceStr", nonceStr);// 随机字符串
data.put("package","prepay_id="+payResponse.getPrepay_id());
data.put("signType", "RSA");// 签名类型,默认为RSA,仅支持RSA
data.put("paySign", paySign);// 签名
data.put("orderNo",payRequest.getOrderSn());
payResponse.setJsapiData(data);
}
log.info("createOrder: {}", payResponse);
}else{
payResponse.setSuccess(false);
payResponse.setMessage( map.get("message") );
}
return payResponse;
}catch (Exception e){
e.printStackTrace();
payResponse.setSuccess(false);
}
return payResponse;
}
/**
* 关闭订单
* 参考官网代码
* https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_2.shtml 3.2.5. 【服务端】关闭订单
* @return
*/
@Override
public Boolean closePay(String orderSn) throws TradeException {
// 请求body参数构建
Map<String, Object> params = new HashMap<String, Object>();
params.put("mchid", wxpayConfig.getMchId());
String url = WxpayConstant.closeOrder.replaceAll("\\{out_trade_no\\}",orderSn);
Map map = postApiTemplate(url, params);
if("SUCCESS".equals( map.get("code"))){
return true;
}else{
return false;
}
}
/**
* 退款
* 参考官网代码 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_9.shtml
* @param refundRequest
* @return
*/
@Override
public Boolean refund(RefundRequest refundRequest) {
// 请求body参数构建
// 请求body参数构建
Map<String, Object> params = new HashMap<String, Object>();
params.put("notify_url", callbackConfig.getDomain()+ PayConstant.CALLBACK_PATH +PayConstant.REFUND_NOTIFY +"/"+ Platform.WX ); //回调地址
params.put("out_trade_no", refundRequest.getOrderSn());//订单编号
//out_refund_no
params.put("out_refund_no", refundRequest.getRequestNo());//退款申请单编号 (多次退款需要不一样才行)
params.put("amount", new HashMap<String, Object>() {
{
put("refund",refundRequest.getRefundAmount());//退款金额
put("total", refundRequest.getTotalFee());//原金额,单位:分
put("currency", "CNY");//人民币
}
});
String url = WxpayConstant.refund; //创建订单
Map map = postApiTemplate(url, params);
if("SUCCESS".equals( map.get("code"))){
return true;
}else{
return false;
}
}
/**
* 手动查询订单
* 参考官网代码: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_6_2.shtml 3.2.4. 【服务端】查询订单
* @param orderSn
* @return
*/
@Override
public QueryResponse queryTradingOrderNo(String orderSn) throws TradeException {
// 请求body参数构建
Map<String, String> params = new HashMap<String, String>();
params.put("mchid", wxpayConfig.getMchId());
String url = WxpayConstant.queryOrderNo+orderSn;;
try {
Map map = getApiTemplate(url, params);
QueryResponse queryResponse=new QueryResponse();
queryResponse.setOrder_sn((String)map.get("out_trade_no") ); //订单号
queryResponse.setTransaction_id((String)map.get("transaction_id") ); //交易单类型
queryResponse.setTrade_state( (String) map.get("trade_state") );//交易状态
Map amount = (Map)map.get("amount");
if(amount!=null){
queryResponse.setTotal( (Integer) amount.get("total") ); //总金额
}
Map payer= (Map)map.get("payer");
if(payer!=null){
queryResponse.setOpenid( (String)payer.get("openid") );
}
queryResponse.setExpand(map);//全部数据
return queryResponse;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 查询退款订单
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_10.shtml
* @param out_refund_no
* @return
* @throws TradeException
*/
@Override
public QueryRefundResponse queryRefundTrading(String out_refund_no) throws TradeException {
// 请求body参数构建
Map<String, String> params = new HashMap<String, String>();
String url = WxpayConstant.queryRufundOrderNo + out_refund_no;
try {
Map map = getApiTemplate(url, params);
QueryRefundResponse queryRefundResponse=new QueryRefundResponse();
queryRefundResponse.setOrder_sn( (String) map.get("out_trade_no") );
queryRefundResponse.setTransaction_id( (String) map.get("transaction_id") );
Map amount = (Map)map.get("amount");
queryRefundResponse.setTotal( (Integer) amount.get("total") ); //总金额
queryRefundResponse.setRefund((Integer) amount.get("payer_refund") ); //退款金额
queryRefundResponse.setRefund_id((String) map.get("refund_id") ); //退款单号
queryRefundResponse.setOut_refund_no( (String) map.get("out_refund_no") );//退款订单号
queryRefundResponse.setChannel( (String) map.get("channel") ); //通道
queryRefundResponse.setUser_received_account( (String) map.get("user_received_account") ); //账号
queryRefundResponse.setStatus( (String) map.get("status") ); //状态
queryRefundResponse.setSuccess_time( (String) map.get("success_time") );
queryRefundResponse.setExpand(map);
return queryRefundResponse;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String getOpenid(String code) {
String getOpenIdUrl = "https://api.weixin.qq.com/sns/jscode2session?" +
"appid="+wxpayConfig.getAppId()
+"&secret="+wxpayConfig.getAppSecret()
+"&js_code="+code+"&grant_type=authorization_code";
RestTemplate restTemplate = new RestTemplate();
String respResult = restTemplate.getForObject(getOpenIdUrl,String.class);
log.info("获取openid的url:{},respResult:{}",getOpenIdUrl,respResult);
if( respResult==null || "".equals(respResult) ) return "";
try{
Map<String,String> map = JSON.parseObject(respResult, Map.class);
String errorCode = map.get("errcode") ;
if(errorCode!=null && !"".equals(errorCode)){
int errorCodeInt = Integer.valueOf(errorCode).intValue();
log.info("获取openid的errorCode,{}",errorCodeInt);
if(errorCodeInt != 0) return "";
}
return map.get("openid");
}catch (Exception ex){
ex.printStackTrace();
return "";
}
}
private Map getApiTemplate(String url, Map<String, String> params) throws URISyntaxException, IOException {
URIBuilder uriBuilder = new URIBuilder(url);
//添加参数
for (String key : params.keySet()) {
uriBuilder.addParameter(key, params.get(key));
}
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpClient httpClient = getWxHttpClient();
CloseableHttpResponse response = httpClient.execute(httpGet);
return responseTemplate(response);
}
private Map responseTemplate(CloseableHttpResponse response) {
Map result = null;
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
// log.info("success,return body = " + EntityUtils.toString(response.getEntity()));
result = JSON.parseObject(EntityUtils.toString(response.getEntity()), Map.class);
result.put("code", "SUCCESS");
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("success");
result = new HashMap<String, Object>() {
{
put("code", "SUCCESS");
}
};
} else {
String returnBody = EntityUtils.toString(response.getEntity());
log.error("failed,resp code = " + statusCode + ",return body = " + returnBody);
//throw new TradeException("创建本地支付订单失败!"+payDTO.getOrderSn());
Map<String, String> map = JSON.parseObject(returnBody, Map.class);
result = new HashMap<String, Object>() {
{
put("code", "FAIL");
put("message", map.get("message"));
}
};
}
} catch (Exception e) {
result = new HashMap<String, Object>() {
{
put("code", "FAIL");
put("message", e.getMessage());
}
};
} finally {
closeConnect(response);
return result;
}
}
private Map postApiTemplate(String url, Map<String, Object> params) {
try {
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(JSON.toJSONString(params));
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpClient httpClient = getWxHttpClient();
CloseableHttpResponse response = httpClient.execute(httpPost);
return responseTemplate( response );
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获取微信HTTP连接
*
* @return
*/
private CloseableHttpClient getWxHttpClient() {
try {
//这里对秘钥进行加密使用的完全是官网上的代码
//TODO 参考官网代码 https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_6_2.shtml
// 加载商户私钥(privateKey:私钥字符串)
String key = FileUtil.readToStr("wxpay_private.key");
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(key.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
//PrivateKey merchantPrivateKey = keyManager.getPrivateKey("wxpay_private.key");//读取私钥
PrivateKeySigner privateKeySigner = new PrivateKeySigner(wxpayConfig.getMchSerialNo(), merchantPrivateKey);
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(
wxpayConfig.getMchId(), privateKeySigner);
// 向证书管理器增加需要自动更新平台证书的商户信息
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(wxpayConfig.getMchId(), wechatPay2Credentials, wxpayConfig.getApiV3Key().getBytes("utf-8"));
// 初始化httpClient
return WechatPayHttpClientBuilder.create()
.withMerchant(wxpayConfig.getMchId(), wxpayConfig.getMchSerialNo(), merchantPrivateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(wxpayConfig.getMchId()))).build();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("微信支付--初始化,校验系统参数失败");
}
}
/**
* 关闭资源
*/
private void closeConnect(CloseableHttpResponse response) {
try {
response.close();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("资源回收出错");
}
}
/**
* 创建支付签名
* @param appid
* @param timeStamp
* @param nonceStr
* @param packages
* @param privateKey
* @return
* @throws Exception
*/
private String createPaySign(String appid, String timeStamp, String nonceStr, String packages,String privateKey) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
// 加载商户私钥
PrivateKey key = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes(CharsetUtil.CHARSET_UTF_8)));
sign.initSign(key);
String message = StrUtil.format("{}\n{}\n{}\n{}\n",
appid,
timeStamp,
nonceStr,
packages);
sign.update(message.getBytes());
return Base64.getEncoder().encodeToString(sign.sign());
}
}