网上虽然有不少关于如何接入微信支付的教程,但是毫不客气的说,没有可以直接拿来用的,在我看来这种东西,完全不应该让我们花时间去研究它,所以呢,我现在将它分享出来,这是基于最新的微信支付SDK
和汇付支付SDK
,封装的一个代码,拿过去直接使用完全没问题,只需要在你的application.yml
文件中配置以下属性,那么他就能正常工作了。
wechat:
appId: xxxxxx // 应用id,如小程序的appid
mchId: xxxxx // 微信支付的商户号
mchSerialNumber: xxxxx // 商户证书序列号
apiKey: xxxx // 你自己设置的api调用的key
// 下面这两个就是接入微信支付时通过微信的密钥创建工具生成的这两个文件
apiclientCertPath: /queue-genie/weixin/apiclient_cert.pem
apiclientKeyPath: /queue-genie/weixin/apiclient_key.pem
// 这两个回调地址,一个是支付成功的回调,一个是退款成功的回调,当支付、退款完后,微信会调用你设置的地址,将结果通知商户系统,你需要在这个回调中处理相关的逻辑
wxNotifyUrl: https://www.xxx.com/api/wechat/notify
wxRefundNotifyUrl: https://www.xxx.com/api/wechat/refund-notify
huifu:
productId: PAYUN // 汇付的产品id
sysId: 66666xxxxxxx // 系统ID,就是6666开头的那个
huifuId: 6666xxxxx // 汇付ID,类似微信的商户号
rsaPublicKey: xxxxx // 这俩就不用解释了,公钥和私钥,通过商户平台可以查到
rsaPrivateKey:
huifuNotifyUrl: https://www.xxx.com/api/huifu/notify
huifuRefundNotifyUrl: https://www.xxx.com/api/huifu/refund-notify
先看微信支付的代码,详细的解释都在各自方法的注释上:
在文档末尾,我给出了使用示例
这里我简单说下为什么使用JsapiServiceExtension类,是因为这个是对JsapiService做的一个扩展类,提供了如自动给请求加签这样的功能,就不需要咱们自己在请求时生成签名了。
package com.xxxxx.xxxxxx.modules.weixin.pay; // 记得复制后改成你的包路径
import com.resgoing.queuegenie.common.exception.ApiException;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByIdRequest;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import org.springframework.beans.factory.annotation.Value;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
* 微信支付服务
* Created by doudou on 2024/01/09
*/
@Slf4j
@Service
public class WechatPayServiceExtension {
private final String appId;
private final String mchId;
private final String mchSerialNumber;
private final String apiKey;
private final String wxNotifyUrl;
private final String wxRefundNotifyUrl;
private final String apiclientKeyPath;
private JsapiServiceExtension jsapiServiceExtension;
private NotificationParser notificationParser;
public WechatPayServiceExtension(
@Value("${wechat.appId}") String appId,
@Value("${wechat.mchId}") String mchId,
@Value("${wechat.mchSerialNumber}") String mchSerialNumber,
@Value("${wechat.apiKey}") String apiKey,
@Value("${wechat.wxNotifyUrl}") String wxNotifyUrl,
@Value("${wechat.wxRefundNotifyUrl}") String wxRefundNotifyUrl,
@Value("${wechat.apiclientKeyPath}") String apiclientKeyPath
) {
this.appId = appId;
this.mchId = mchId;
this.mchSerialNumber = mchSerialNumber;
this.apiKey = apiKey;
this.wxNotifyUrl = wxNotifyUrl;
this.wxRefundNotifyUrl = wxRefundNotifyUrl;
this.apiclientKeyPath = apiclientKeyPath;
this.jsapiServiceExtension = new JsapiServiceExtension.Builder()
.config(createConfig())
.build();
}
private Config createConfig() {
try {
RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(mchId)
.privateKeyFromPath(apiclientKeyPath)
.merchantSerialNumber(mchSerialNumber)
.apiV3Key(apiKey)
.build();
this.notificationParser = new NotificationParser(config);
return config;
} catch (Exception e) {
log.error("微信支付配置初始化失败", e);
throw new RuntimeException("微信支付配置初始化失败", e);
}
}
/**
* JSAPI 支付下单,并返回 JSAPI 调起支付数据。推荐使用!
*
* <p>请求成功后,该方法返回预支付交易会话标识 prepay_id 和客户端 JSAPI 调起支付所需参数。 它相比 JsApiService.prepay
* 更简单易用,因为无需开发者自行计算调起支付签名。
*
* @param request 请求参数
* @return PrepayWithRequestPaymentResponse
* @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
* @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
* @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
* @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
*/
public PrepayWithRequestPaymentResponse prepayWithRequestPayment(String openid, String orderId, BigDecimal amount, String description) {
PrepayRequest request = new PrepayRequest();
Amount payAmount = new Amount();
// 将元转换为分
payAmount.setTotal(amount.multiply(new BigDecimal("100")).intValue());
Payer payer = new Payer();
payer.setOpenid(openid);
request.setAmount(payAmount);
request.setAppid(appId);
request.setMchid(mchId);
request.setDescription(description);
request.setNotifyUrl(wxNotifyUrl);
request.setOutTradeNo(orderId);
request.setPayer(payer);
return jsapiServiceExtension.prepayWithRequestPayment(request);
}
/**
* 微信支付订单号查询订单
*
* @param wxOrderId 微信支付订单号
* @return Transaction
* @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
* @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
* @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
* @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
*/
public Transaction queryOrderByWxPayId(String wxOrderId) {
try {
QueryOrderByIdRequest request = new QueryOrderByIdRequest();
request.setMchid(mchId);
request.setTransactionId(wxOrderId);
return jsapiServiceExtension.queryOrderById(request);
} catch (Exception e) {
log.error("微信支付:::查询订单:::wxOrderId:{}:::message:{}", wxOrderId, e.getMessage());
throw new ApiException("申请退款失败,请联系平台管理员处理");
}
}
/**
* 商户订单号查询订单
*
* @param orderId 商户订单号
* @return Transaction
* @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
* @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
* @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
* @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
*/
public Transaction queryOrderByOutTradeNo(String orderId) {
try {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setMchid(mchId);
request.setOutTradeNo(orderId);
return jsapiServiceExtension.queryOrderByOutTradeNo(request);
} catch (Exception e) {
log.error("微信支付:::查询订单:::orderId:{}:::message:{}", orderId, e.getMessage());
throw new ApiException("申请退款失败,请联系平台管理员处理");
}
}
/**
* 关闭订单
*
* @param orderId 商户订单号
* @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
* @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
* @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
* @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
*/
public void closeOrder(String orderId) {
try {
CloseOrderRequest request = new CloseOrderRequest();
request.setMchid(mchId);
request.setOutTradeNo(orderId);
jsapiServiceExtension.closeOrder(request);
} catch (Exception e) {
log.error("微信支付:::关闭订单:::orderId:{}:::message:{}", orderId, e.getMessage());
throw new ApiException("申请退款失败,请联系平台管理员处理");
}
}
/**
* 解密回调数据并验签
*
* @param resource 回调数据
* @return 解密后的数据
*/
public <T> T decryptAndVerifyCallbackData(RequestParam requestParam, Class<T> tClass) {
try {
T transaction = notificationParser.parse(requestParam, tClass);
return transaction;
} catch (Exception e) {
log.error("微信支付:::解密回调数据:::message:{}", e.getMessage());
return null;
}
}
/**
* 申请退款
*
* @param orderId 原商户订单号
* @param refundId 退款单号
* @param refundAmount 退款金额(元)
* @param totalAmount 原订单金额(元)
* @param reason 退款原因
* @return RefundResponse
*/
public Refund refund(String orderId, String refundId, BigDecimal refundAmount, BigDecimal totalAmount, String reason) {
try {
RefundService service = new RefundService.Builder().config(createConfig()).build();
CreateRequest request = new CreateRequest();
AmountReq amount = new AmountReq();
// 将元转换为分
amount.setRefund(refundAmount.multiply(new BigDecimal("100")).longValue());
amount.setTotal(totalAmount.multiply(new BigDecimal("100")).longValue());
amount.setCurrency("CNY");
request.setOutTradeNo(orderId);
request.setOutRefundNo(refundId);
request.setAmount(amount);
request.setReason(reason);
request.setNotifyUrl(wxRefundNotifyUrl);
return service.create(request);
} catch (Exception e) {
log.error("微信支付:::申请退款:::orderId:{}:::message:{}", orderId, e.getMessage());
throw new ApiException("申请退款失败,请联系平台管理员处理");
}
}
/**
* 查询退款
*
* @param orderRefundId 系统内的退款单号
* @return RefundResponse
*/
public Refund queryRefund(String orderRefundId) {
try {
RefundService service = new RefundService.Builder().config(createConfig()).build();
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setOutRefundNo(orderRefundId);
return service.queryByOutRefundNo(request);
} catch (Exception e) {
log.error("微信支付:::查询退款:::refundId:{}:::message:{}", orderRefundId, e.getMessage());
throw new ApiException("申请退款失败,请联系平台管理员处理");
}
}
}
这是汇付支付的:
package com.resgoing.queuegenie.modules.huifu;
import java.math.BigDecimal;
import java.util.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.resgoing.queuegenie.common.exception.ApiException;
import com.resgoing.queuegenie.common.utils.CommonUtils;
import com.resgoing.queuegenie.common.utils.IdGenerator;
import lombok.extern.slf4j.Slf4j;
import com.huifu.bspay.sdk.opps.core.utils.DateTools;
import com.huifu.bspay.sdk.opps.core.utils.RsaUtils;
import com.huifu.bspay.sdk.opps.core.net.BasePayRequest;
import com.huifu.bspay.sdk.opps.core.BasePay;
import com.huifu.bspay.sdk.opps.core.config.MerConfig;
@Slf4j
@Service
public class HuifuV2TradePaymentJspay {
private final String miniAppId;
private final String huifuId;
private final String rsaPublicKey;
private final String huifuNotifyUrl;
private final String huifuRefundNotifyUrl;
public HuifuV2TradePaymentJspay(
@Value("${wechat.appId}") String appId,
@Value("${huifu.productId}") String productId,
@Value("${huifu.sysId}") String sysId,
@Value("${huifu.huifuId}") String huifuId,
@Value("${huifu.rsaPrivateKey}") String rsaPrivateKey,
@Value("${huifu.rsaPublicKey}") String rsaPublicKey,
@Value("${huifu.huifuNotifyUrl}") String huifuNotifyUrl,
@Value("${huifu.huifuRefundNotifyUrl}") String huifuRefundNotifyUrl
) {
this.miniAppId = appId;
this.huifuId = huifuId;
this.rsaPublicKey = rsaPublicKey;
this.huifuNotifyUrl = huifuNotifyUrl;
this.huifuRefundNotifyUrl = huifuRefundNotifyUrl;
try {
/**
* debug 模式,开启后有详细的日志
*/
BasePay.debug = false;
/**
* prodMode 模式,默认为生产模式
* MODE_PROD = "prod"; // 生产环境
* MODE_TEST = "test"; // 线上联调环境(针对商户联调测试)
*/
BasePay.prodMode = BasePay.MODE_PROD;
/**
* 设置商户信息
*/
MerConfig merConfig = new MerConfig();
merConfig.setProcutId(productId);
merConfig.setSysId(sysId);
merConfig.setRsaPrivateKey(rsaPrivateKey);
merConfig.setRsaPublicKey(rsaPublicKey);
BasePay.initWithMerConfig(merConfig);
} catch (Exception e) {
log.error("汇付支付-初始化商户配置错误: {}", e);
throw new RuntimeException("初始化商户配置失败", e);
}
}
/**
* 验签方法
*/
public boolean verifySignature(String resData, String sign) {
try {
return RsaUtils.verify(resData, rsaPublicKey, sign);
} catch (Exception e) {
log.error("汇付支付-验签失败: {}", e);
return false;
}
}
/**
* 获取订单预支付信息
*
* @param orderId 系统内部订单ID,该id用于关联支付信息与系统内订单数据
* @param amt 交易金额
* @return 用于调起支付的参数
*/
public String getOrderPrePayInfo(String orderId, BigDecimal amt, String miniOpenId) {
// 组装请求参数
Map<String, Object> paramsInfo = new HashMap<>();
// 请求日期
paramsInfo.put("req_date", DateTools.getCurrentDateYYYYMMDD());
// 请求流水号
paramsInfo.put("req_seq_id", "p_" + IdGenerator.getId());
// 商户号
paramsInfo.put("huifu_id", huifuId);
// 商品描述
paramsInfo.put("goods_desc", "排队取号费用");
// 交易类型
paramsInfo.put("trade_type", "T_MINIAPP");
// 交易金额
paramsInfo.put("trans_amt", amt);
// 我们系统内不得订单ID,因为汇付没有提供相应的字段用来携带商户系统的订单号,不得已这样做
paramsInfo.put("remark", orderId);
// 异步通知地址
paramsInfo.put("notify_url", huifuNotifyUrl);
// 微信信息
Map<String, Object> wxData = new HashMap<>();
wxData.put("sub_appid", miniAppId);
wxData.put("sub_openid", miniOpenId);
String bodyJson = CommonUtils.toJson(wxData);
paramsInfo.put("wx_data", bodyJson);
Map<String, Object> response = new HashMap<>();
try {
// 这个api路径去看汇付支付的文档即可
response = BasePayRequest.requestBasePay("/v2/trade/payment/jspay", paramsInfo, null, false);
} catch (Exception e) {
throw new ApiException(e.getMessage());
}
String transStat = (String) response.get("trans_stat");
String respDesc = (String) response.get("resp_desc");
if (transStat.equals("F")) throw new ApiException(respDesc);
String payInfo = (String) response.get("pay_info");
if (CommonUtils.isNullOrEmpty(payInfo)) throw new ApiException("预支付信息获取失败");
return payInfo;
}
/**
* 退款
* @param orderId 系统内部订单ID,该id用于关联支付信息与系统内订单数据
* @param payId 原支付信息中的req_seq_id,也就是记录到我们系统中的payId
* @param amt 退款金额
* @param payTime 原支付时间
* @return 退款提交的返回结果
*/
public Map<String, Object> refund(String orderId, String payId, BigDecimal amt, Date payDate) {
Map<String, Object> paramsInfo = new HashMap<>();
// 请求日期
paramsInfo.put("req_date", DateTools.getCurrentDateYYYYMMDD());
// 请求流水号
paramsInfo.put("req_seq_id", "r_" + IdGenerator.getId());
// 商户号
paramsInfo.put("huifu_id", huifuId);
// 申请退款金额
paramsInfo.put("ord_amt", amt);
// 原交易请求日期
paramsInfo.put("org_req_date", DateTools.formatYYYYMMDD(payDate));
// 原交易全局流水号
paramsInfo.put("org_req_seq_id", payId);
// 系统内的订单ID
paramsInfo.put("remark", orderId);
// 异步通知地址
paramsInfo.put("notify_url", huifuRefundNotifyUrl);
// 发起API调用
Map<String, Object> response = new HashMap<>();
try {
response = BasePayRequest.requestBasePay("v2/trade/payment/scanpay/refund", paramsInfo, null, false);
} catch (Exception e) {
throw new ApiException(e.getMessage());
}
String transStat = (String) response.get("trans_stat");
String respDesc = (String) response.get("resp_desc");
if (transStat.equals("F")) throw new ApiException(respDesc);
return response;
}
}
创建预支付订单、查询订单这些没什么说的,按照方法要求的参数传入就行,比较特殊的就是回调通知,下面是完整的controller方法代码:
先看看微信支付成功、退款成功的回调,两个控制器方法都是一样的,不同的就是api地址的不一样:
/**
* 微信的回调验签稍微特殊一些,
* 比如这个支付成功的回调,需要将请求头中的这些参数按顺序组装起来,当然sdk提供了相关的方法就是这个:
* com.wechat.pay.java.core.notification.RequestParam.Builder;
* 然后去对组装的这个参数验签、解密,从而拿到实际的交易数据
*/
@ApiOperation("支付成功回调")
@PostMapping("/api/wechat/notify")
public ResponseEntity<HttpStatus> payOrderCallbackForWxCtl(HttpServletRequest request, @RequestBody String body) {
String signature = request.getHeader("Wechatpay-Signature");
// 请求头Wechatpay-nonce
String nonce = request.getHeader("Wechatpay-Nonce");
// 请求头Wechatpay-Timestamp
String timestamp = request.getHeader("Wechatpay-Timestamp");
// 微信支付证书序列号
String serial = request.getHeader("Wechatpay-Serial");
// 签名方式
String signType = request.getHeader("Wechatpay-Signature-Type");
com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
.serialNumber(serial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.signType(signType)
.body(body)
.build();
boolean is = clientQueueOrderService.paySuccessCallbackForWx(requestParam);
return is ? ResponseEntity.ok().build() : ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
/**
* 对于验签就调用我们上面封装的这个方法,返回的transaction就是解密后的数据,
* 至于这里面都包含什么,可以直接点进sdk中的这个接口类看看
*/
Transaction transaction = this.wechatPayServiceExtension.decryptAndVerifyCallbackData(requestParam, Transaction.class);
// 后续就是结合你的业务逻辑进行下一步的处理了
接下来看看汇付支付的:
/**
* 汇付支付
* 支付完成后的回调通知
*/
@ApiOperation("汇付支付-支付完成后的回调通知")
@PostMapping("/api/huifu/notify")
public ResponseEntity<HttpStatus> paySuccessCallbackForHuifuCtl(HttpServletRequest request) {
String data = request.getParameter("resp_data");
String sign = request.getParameter("sign");
boolean is = clientQueueOrderService.paySuccessCallbackForHuifu(data, sign);
return is ? ResponseEntity.ok().build() : ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
/**
* 汇付支付
* 退款完成后的回调通知
*/
@ApiOperation("汇付支付-退款完成后的回调通知")
@PostMapping("/api/refund-notify")
public ResponseEntity<HttpStatus> refundSuccessCallbackForHuifuCtl(HttpServletRequest request) {
String data = request.getParameter("resp_data");
String sign = request.getParameter("sign");
boolean is = clientQueueOrderService.refundSuccessCallbackForHuifu(data, sign);
return is ? ResponseEntity.ok().build() : ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
// 控制器代码也一样,逻辑上没什么不同,至于验签也是使用我们封装的验签方法,当然这个还是sdk提供的方法
huifuV2TradePaymentJspay.verifySignature(resData, sign);
至此,微信支付如何接入就到此结束了。如果有用,请点个小攒攒吧。