准备工作
1、微信公众平台接入微信支付
商户号mchid、appid
开发 ---> 开发管理 ---> 开发设置
appSecret 管理员可设置、查看
证书相关参数
点击查看跳转到微信支付(管理员账号登录)
申请API证书和AIPv3证书
API证书申请成功后可以下载一个压缩包,压缩包内容
把私钥放在服务器上,记录地址privateKeyPath
serialNo:api证书序列号,从api证书点击查看进去可以看到序列号
apiV3Key:AIPv3证书私钥,申请AIPv3证书时设置的32位私钥
2、相关代码
引入依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
微信支付工具类
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sdecloud.common.utils.StringUtils;
import com.sdecloud.modules.collectdata.util.HttpClientUtil;
import com.sdecloud.modules.hswjordermanage.util.dto.WxPayParam;
import com.sdecloud.modules.hswjordermanage.util.dto.WxRefundParam;
import com.sdecloud.modules.hswjordermanage.util.enums.PayConfig;
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.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.exception.ParseException;
import com.wechat.pay.contrib.apache.httpclient.exception.ValidationException;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;
import java.util.Objects;
/**
* 微信支付工具类
*
* @author jhx
*/
@Component
public class WxPayUtils {
/**
* 支付相关参数
*/
private PayConfig config;
/**
* 证书管理器
*/
private CertificatesManager certificatesManager;
private CloseableHttpClient httpClient;
private static WxPayUtils wxPayUtils;
private static final Logger log = LoggerFactory.getLogger(WxPayUtils.class);
/**
* 项目地址
*/
private final String realPath = System.getProperty("user.dir") + File.separator;
/**
* 微信app支付相关接口地址
*/
private final String WX_TRADE_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/";
private final String WX_QUERY_TRADE_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/";
private final String WX_REFUND_URL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
private final String WX_QUERY_REFUND_URL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/";
private final String WX_QUERY_OPENID_URL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
/**
* 微信支付相关配置
*/
private final String wxAppid = "wxc*************fb";
private final String wxAppSecret = "f615ee4*************627dc4a30e31";
private final String wxMchid = "16******64";
private final String wxPrivateKeyPath = "wxCrt/wxPrivateKey.pem";
private final String wxSerialNo = "7C9CD9294F*************1F0F252775BEA5008";
private final String wxApiV3Key = "3538ab7354*************7b1f480c2";
@PostConstruct
public void init() {
wxPayUtils = this;
setVerifier();
wxPayUtils.certificatesManager = this.certificatesManager;
wxPayUtils.httpClient = this.httpClient;
}
public void setVerifier() {
// 获取证书管理器实例
this.certificatesManager = CertificatesManager.getInstance();
try {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(realPath + wxPrivateKeyPath));
// 向证书管理器增加需要自动更新平台证书的商户信息
this.certificatesManager.putMerchant(wxMchid, new WechatPay2Credentials(wxMchid, new PrivateKeySigner(wxSerialNo, merchantPrivateKey)), wxApiV3Key.getBytes(StandardCharsets.UTF_8));
Verifier verifier = wxPayUtils.certificatesManager.getVerifier(wxMchid);
wxPayUtils.httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(wxMchid, wxSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
} catch (IOException e) {
e.printStackTrace();
log.error("加载秘钥文件失败");
} catch (GeneralSecurityException e) {
e.printStackTrace();
log.error("获取平台证书失败");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取预支付id
* @param param {@link WxPayParam}
* @return JSONObject
*/
public JSONObject prePay(WxPayParam param) {
// 请求URL
HttpPost httpPost = new HttpPost(WX_TRADE_URL + param.getPayPath());
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", wxMchid)
.put("appid", wxAppid)
.put("description", "商品售卖")
.put("notify_url", param.getNotifyUrl())
.put("out_trade_no", param.getOutTradeNo());
rootNode.putObject("amount").put("total", param.getTotalFee());
rootNode.putObject("payer").put("openid", param.getOpenid());
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = wxPayUtils.httpClient.execute(httpPost);
log.info("微信发起支付返回结果:" + response);
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject result = JSONObject.parseObject(bodyAsString);
// 返回给前端的参数
JSONObject jsonObject = new JSONObject();
long timeStamp = System.currentTimeMillis() / 1000;
String uuid = IdUtil.simpleUUID();
String paySign = getPaySign(timeStamp, uuid, result.getString("prepay_id"));
jsonObject.put("prepayid", result.getString("prepay_id"));
jsonObject.put("timestamp", timeStamp);
jsonObject.put("nonceStr", uuid);
jsonObject.put("paySign", paySign);
jsonObject.put("appid", wxAppid);
jsonObject.put("partnerid", wxMchid);
jsonObject.put("package", "Sign=WXPay");
jsonObject.put("outTradeNo", param.getOutTradeNo());
return jsonObject;
} catch (IOException e) {
log.error("微信发起支付失败,报错信息:" + e.getMessage());
e.printStackTrace();
}
return null;
}
/**
* 查询订单是否支付成功,用于订单超时检测,返回SUCCESS则支付成功
*
* @param outTradeNo 订单号
* @return 返回交易状态 SUCCESS:支付成功 * REFUND:转入退款 * NOTPAY:未支付 * CLOSED:已关闭 * REVOKED:已撤销(仅付款码支付会返回) * USERPAYING:用户支付中(仅付款码支付会返回) * PAYERROR:支付失败(仅付款码支付会返回)
*/
public String queryTrade(String outTradeNo) {
try {
URIBuilder uriBuilder = new URIBuilder(WX_QUERY_TRADE_URL + outTradeNo + "?mchid=" + wxMchid);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayUtils.httpClient.execute(httpGet);
log.info("微信查询支付状态返回结果:" + response);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
return EntityUtils.toString(response.getEntity());
} else {
return "";
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 退款
*/
public JSONObject dpRefund(WxRefundParam param) {
// 请求URL
HttpPost httpPost = new HttpPost(WX_REFUND_URL);
JSONObject paramJson = new JSONObject();
paramJson.put("transaction_id", param.getTradeNo());
paramJson.put("out_refund_no", param.getOutRefundNo());
paramJson.put("reason", param.getReason());
JSONObject refundAmount = new JSONObject();
refundAmount.put("refund", param.getRefundFee());
refundAmount.put("total", param.getTotalFee());
refundAmount.put("currency", "CNY");
paramJson.put("amount", refundAmount);
StringEntity entity = new StringEntity(paramJson.toString(), StandardCharsets.UTF_8);
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
try {
// 完成签名并执行请求
CloseableHttpResponse response = wxPayUtils.httpClient.execute(httpPost);
log.info("微信发起退款返回结果:" + response);
int statusCode = response.getStatusLine().getStatusCode();
// 处理成功
if (statusCode == 200) {
return JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
}
} catch (Exception e) {
log.error("微信发起退款失败,报错信息:" + e.getMessage());
e.printStackTrace();
}
return null;
}
/**
* 微信查询退款结果
* @param outRefundNo 商户退款单号
* @return String
*/
public String queryRefund(String outRefundNo) {
try {
URIBuilder uriBuilder = new URIBuilder(WX_QUERY_REFUND_URL + outRefundNo);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayUtils.httpClient.execute(httpGet);
log.info("微信查询退款状态返回结果:" + response);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
return EntityUtils.toString(response.getEntity());
} else {
return "";
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 支付回调
*
* @param request request
* @return JSONObject
*/
public JSONObject callBack(HttpServletRequest request) {
JSONObject resultJson = new JSONObject();
// 从请求头获取验签字段
JSONObject requestJson = new JSONObject();
requestJson.put("nonce", request.getHeader("Wechatpay-Nonce"));
requestJson.put("signature", request.getHeader("Wechatpay-Signature"));
requestJson.put("timestamp", request.getHeader("Wechatpay-Timestamp"));
requestJson.put("serial", request.getHeader("Wechatpay-Serial"));
try {
ServletInputStream inputStream = request.getInputStream();
StringBuilder sb = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
// 读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
sb.append(s);
}
requestJson.put("body", sb.toString());
Notification notification = this.notificationHandler(requestJson);
if (Objects.isNull(notification)) {
resultJson.put("code", "FAIL");
resultJson.put("message", "解析请求体失败");
return resultJson;
}
String data = notification.getDecryptData();
log.info("微信回调返回结果:" + data);
resultJson.put("code", "SUCCESS");
resultJson.put("message", "成功");
resultJson.put("data", data);
} catch (IOException e) {
resultJson.put("code", "FAIL");
resultJson.put("message", e.getMessage());
e.printStackTrace();
}
return resultJson;
}
public Notification notificationHandler(JSONObject jsonObject) {
try {
Verifier verifier = wxPayUtils.certificatesManager.getVerifier(wxMchid);
// 构建request,传入必要参数
NotificationRequest request = new NotificationRequest.Builder()
.withSerialNumber(jsonObject.getString("serial"))
.withNonce(jsonObject.getString("nonce"))
.withTimestamp(jsonObject.getString("timestamp"))
.withSignature(jsonObject.getString("signature"))
.withBody(jsonObject.getString("body"))
.build();
NotificationHandler handler = new NotificationHandler(verifier, wxApiV3Key.getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
return handler.parse(request);
} catch (NotFoundException | ValidationException | ParseException e) {
e.printStackTrace();
log.error("解析请求体失败,报错信息:" + e.getMessage());
}
return null;
}
public String getOpenid(String code) {
String url = String.format(WX_QUERY_OPENID_URL, wxAppid, wxAppSecret, code);
String response = HttpClientUtil.doGet(url);
JSONObject resultJson = JSON.parseObject(response);
return resultJson.getString("openid");
}
public String getPaySign(long timestamp, String randString, String prepayid) {
String data = wxAppid + "\n" +
timestamp + "\n" +
randString + "\n" +
"prepay_id=" + prepayid + "\n";
String signature = null;
try {
signature = sign(data.getBytes(StandardCharsets.UTF_8));
} catch (SignatureException e) {
e.printStackTrace();
}
return signature;
}
public String sign(byte[] message) throws SignatureException {
Signature sign = null;
try {
sign = Signature.getInstance("SHA256withRSA");
PrivateKey privateKey = PemUtil
.loadPrivateKey(new FileInputStream(realPath + wxPrivateKeyPath));
sign.initSign(privateKey);
sign.update(message);
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IOException e) {
e.printStackTrace();
}
return Base64.getEncoder().encodeToString(sign.sign());
}
}
sevice调用
/**
* 微信预支付
* @param orderId 订单id
* @param payPath 支付方式,app,jsapi(小程序支付)
* @return JSONObject
*/
@Override
public R<JSONObject> wxPrePay(String orderId, String payPath) {
// 校验是否有不可支付订单
String errorMsg = this.checkPay(orderId);
if (StringUtils.isNotBlank(errorMsg)) {
return R.failed(errorMsg);
}
// 计算订单总金额
double totalFee = baseMapper.getTotalFee(orderId);
// todo 添加支付记录
// todo 支付记录id
String outTradeNo = "";
// expire 超时时间,自定义
LocalDateTime expireTime = LocalDateTime.now().plusMinutes(expire);
WxPayParam payParam = new WxPayParam();
payParam.setOutTradeNo(outTradeNo);
payParam.setTimeExpire(expireTime);
// backUrl为服务域名,https
payParam.setNotifyUrl(backUrl + "/orderInfo/wxCallBack");
// 微信支付金额单位为分
payParam.setTotalFee((int) (totalFee * 100));
payParam.setPayPath(payPath);
// 获取当前登录人,自定义
SysUser sysUser = Objects.requireNonNull(SecurityUtils.getLoginUser()).getUser();
// 获取openid:进入小程序调用wx.login()方法获取code,code传到后端使用支付工具类中的getOpenid()方法获取到openid,存到用户信息表中
payParam.setOpenid(sysUser.getWxOpenId());
log.info("微信发起支付参数:" + payParam);
JSONObject jsonObject = wxPayUtils.prePay(payParam);
if (Objects.isNull(jsonObject)) {
return R.failed("拉起支付失败");
}
return R.ok(jsonObject);
}
/**
* 微信支付回调
*/
@Override
public JSONObject wxCallBack() {
HttpServletRequest request = ServletUtils.getRequest();
JSONObject result = wxPayUtils.callBack(request);
if ("SUCCESS".equals(result.getString("code"))) {
JSONObject dataJson = JSON.parseObject(result.getString("data"));
result.remove("data");
String appId = dataJson.getString("appid");
// 商户id
String mchId = dataJson.getString("mchid");
// 商户支付流水号
String outTradeNo = dataJson.getString("out_trade_no");
// 微信支付流水号
String tradeNo = dataJson.getString("transaction_id");
// 交易方式
String tradeType = dataJson.getString("trade_type");
// 交易状态
String tradeState = dataJson.getString("trade_state");
// 交易状态描述
String tradeStateDesc = dataJson.getString("trade_state_desc");
// 商户添加的附加属性
String attach = dataJson.getString("attach");
// 交易成功时间
String successTime = dataJson.getString("success_time");
JSONObject payer = dataJson.getJSONObject("payer");
// 根据交易状态获取系统订单状态(自定义方法)
String payStatus = this.getWxTradeStatus(tradeState);
// 支付记录
OrderPayRecord record = orderPayRecordService.getById(outTradeNo);
// 如果微信支付流水号存在则表示已经接收到回调,不需要再次处理回调信息
if (StringUtils.isBlank(record.getTradeNo()) || record.getTradeStatus().equals(tradeState)) {
// 更新支付记录
record.setTradeStatus(tradeState);
record.setPayTime(successTime);
record.setTradeNo(tradeNo);
record.setBuyerLogonId(payer.getString("openid"));
orderPayRecordService.updateById(record);
String[] orderIdList = StringUtils.split(record.getOrderId(), ",");
// 修改订单支付状态
baseMapper.update(null, new UpdateWrapper<OrderInfo>()
.set("pay_status", payStatus)
.set("trade_no", tradeNo)
.set("out_trade_no", outTradeNo)
.eq("id", record.getOrderId()));
// todo 其他处理订单相关逻辑
}
}
result.remove("data");
return result;
}
/**
* 主动发起支付状态查询
* @param outTradeNo 交易id
* @return 订单状态
*/
@Override
public R<String> queryPayStatus(String outTradeNo) {
OrderPayRecord record = orderPayRecordService.getById(outTradeNo);
String tradeStatus = "";
String payStatus = "";
String tradeNo = "";
switch (record.getPayType()) {
case "1":
// todo 支付宝处理逻辑
break;
case "2":
String response = wxPayUtils.queryTrade(outTradeNo);
if (StringUtils.isBlank(response)) {
return R.failed("查询失败");
}
JSONObject responseJson = JSON.parseObject(response);
tradeStatus = responseJson.getString("trade_state");
payStatus = this.getWxTradeStatus(tradeStatus);
tradeNo = responseJson.getString("transaction_id");
break;
default:
break;
}
// 支付记录的支付宝/微信交易流水号为空表示未收到回调
if (StringUtils.isBlank(record.getTradeNo()) || !record.getTradeStatus().equals(tradeStatus)) {
// 修改订单支付状态
baseMapper.update(null, new UpdateWrapper<OrderInfo>()
.set("pay_status", payStatus)
.set("trade_no", tradeNo)
.eq("id", record.getOrderId()));
// 修改支付记录
record.setTradeStatus(tradeStatus);
record.setTradeNo(tradeNo);
orderPayRecordService.updateById(record);
// todo 其他订单相关处理逻辑
}
return R.ok(payStatus);
}
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 微信支付参数
* @author jhx
*/
@Data
public class WxPayParam implements Serializable {
@ApiModelProperty("微信openid")
private String openid;
@ApiModelProperty("订单号")
private String outTradeNo;
@ApiModelProperty("支付金额,单位分")
private Integer totalFee;
@ApiModelProperty("微信服务器调用支付结果通知路径")
private String notifyUrl;
@ApiModelProperty("交易结束时间")
private LocalDateTime timeExpire;
@ApiModelProperty("支付方式,app,jsapi(小程序支付)")
private String payPath;
}
注:原代码中带有多租户逻辑,已删除;拉起支付、查询支付状态、回调部分已调通,退款及查询待更新