由于项目需要微信与支付宝支付,发现更新了v3版本,使用方式与之前不一样了。查看微信官方文档发现文档的说明并不是很详细,而网上的资料大部分都是要内置okhttp的,官方文档也是推荐,也许本人有强迫症吧,不想在加依赖,于是查看sdk源码,发现提供了一个AppServiceExtension
这个类,可以更加方便的使用。看完记得点个小星星谢谢大家。
下面代码可以直接复制粘贴使用,记得修改配置
首先引入sdk
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.39.86.ALL</version>
</dependency>
微信支付
新建 WxAppPay.java
,内容如下
package com.zyq.pay.wx.app;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
import com.wechat.pay.java.core.http.HostName;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.app.AppService;
import com.wechat.pay.java.service.payments.app.AppServiceExtension;
import com.wechat.pay.java.service.payments.app.model.Amount;
import com.wechat.pay.java.service.payments.app.model.PrepayRequest;
import com.wechat.pay.java.service.payments.app.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.app.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.app.model.SceneInfo;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.zyq.enuns.ApiResponseCode;
import com.zyq.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
/** 微信app支付
* author xiaochi
* date 2024/6/4 13:59
*/
@Slf4j
public class WxAppPay {
/** 单例
* 初始化支付配置(注意:如果还有其他微信支付方式,记得抽离去继承,配置对象必须是单例)
* @return
*/
private static Config config = null;
public Config getConfig(WxAppPayRequest request){
if (config == null){
config = new RSAAutoCertificateConfig.Builder()
.merchantId(request.getMchId())
.privateKeyFromPath(request.getPrivateKeyPath())
.merchantSerialNumber(request.getMchSerialNo())
.apiV3Key(request.getApiV3Key())
.build();
}
return config;
}
public WxAppPayResponse pay(WxAppPayRequest request) {
WxAppPayResponse payResponse = new WxAppPayResponse();
try {
log.info("微信APP支付参数:订单[{}],body:{}",request.getOutTradeNo(),JSONUtil.toJsonStr(request));
Config config = getConfig(request);
PrepayRequest prepayRequest = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(request.getTotalFee());
prepayRequest.setAmount(amount);
prepayRequest.setAppid(request.getAppid());
prepayRequest.setMchid(request.getMchId());
prepayRequest.setDescription(request.getBody());
prepayRequest.setNotifyUrl(request.getNotifyUrl());
prepayRequest.setOutTradeNo(request.getOutTradeNo());
SceneInfo sceneInfo = new SceneInfo();
sceneInfo.setPayerClientIp(request.getIp());
prepayRequest.setSceneInfo(sceneInfo);
AppServiceExtension.Builder appService = new AppServiceExtension.Builder();
appService.config(config);
appService.httpClient(new DefaultHttpClientBuilder().config(config).build());
appService.hostName(HostName.API);
PrepayWithRequestPaymentResponse response = appService.build().prepayWithRequestPayment(prepayRequest);
payResponse.setMsg("微信APP预订单成功:订单["+request.getOutTradeNo()+"]");
payResponse.setBody(JSONUtil.toJsonStr(response));
payResponse.setAppid(response.getAppid());
payResponse.setPrepayId(response.getPrepayId());
payResponse.setPartnerId(response.getPartnerId());
payResponse.setPackageVal(response.getPackageVal());
payResponse.setSign(response.getSign());
payResponse.setTimestamp(response.getTimestamp());
payResponse.setNonceStr(response.getNonceStr());
payResponse.setSuccess(Boolean.TRUE);
}catch (HttpException he){
payResponse.setMsg("微信APP支付统一下单失败:订单["+request.getOutTradeNo()+"]");
payResponse.setBody(he.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]微信APP支付统一下单失败:{}",request.getOutTradeNo(),he);
throw new BusinessException(ApiResponseCode.WX_PAY_PLACE_ORDER_FAIL);
}catch (ValidationException ve){
payResponse.setMsg("验证微信APP支付返回签名失败:订单["+request.getOutTradeNo()+"]");
payResponse.setBody(ve.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]验证微信APP支付返回签名失败:{}",request.getOutTradeNo(),ve);
throw new BusinessException(ApiResponseCode.WX_PAY_SIGN_VERIFY_FAIL);
}catch (ServiceException se){
payResponse.setMsg("微信APP支付统一下单成功,服务返回异常:订单["+request.getOutTradeNo()+"]");
payResponse.setBody(se.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]微信APP支付统一下单成功,服务返回异常:{}",request.getOutTradeNo(),se);
throw new BusinessException(ApiResponseCode.ERROR.getCode(),se.getErrorMessage());
}catch (MalformedMessageException me){
payResponse.setMsg("微信APP支付统一下单成功,content-type不为application/json解析返回体失败:订单["+request.getOutTradeNo()+"]");
payResponse.setBody(me.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]微信APP支付统一下单成功,content-type不为application/json解析返回体失败:{}",request.getOutTradeNo(),me);
throw new BusinessException(ApiResponseCode.WX_PAY_PLACE_ORDER_SERVICE_RESPONSE_TYPE_ERROR);
}
return payResponse;
}
public WxAppPayResponse notice(WxAppPayRequest.Notice notice) {
WxAppPayResponse wxAppPayResponse = new WxAppPayResponse();
WxAppPayResponse.Notice noticeResponse = new WxAppPayResponse.Notice();
// 验签
try {
String noticeParams = JSONUtil.toJsonStr(notice);
wxAppPayResponse.setBody(noticeParams);
log.info("微信支付APP回调参数Notice:{}",noticeParams);
// 构建request,传入必要参数
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(notice.getMchSerialNo())
.nonce(notice.getNonce())
.timestamp(notice.getTimestamp())
.signature(notice.getSignature())
.body(notice.getBody())
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser((NotificationConfig) config);
// 以支付通知回调为例,验签、解密并转换成 Transaction
Transaction result = parser.parse(requestParam, Transaction.class);
log.info("微信支付APP回调参数解析:{}",JSONUtil.toJsonStr(result));
if (result != null && Objects.equals(result.getTradeState(), Transaction.TradeStateEnum.SUCCESS)){
wxAppPayResponse.setSuccess(Boolean.TRUE);
noticeResponse.setAppid(result.getAppid());
noticeResponse.setTransactionId(result.getTransactionId());
noticeResponse.setOutTradeNo(result.getOutTradeNo());
noticeResponse.setSuccessTime(result.getSuccessTime());
noticeResponse.setPayerTotal(result.getAmount().getPayerTotal());
wxAppPayResponse.setNotice(noticeResponse);
return wxAppPayResponse;
}
} catch (MalformedMessageException me) {
wxAppPayResponse.setSuccess(Boolean.FALSE);
log.info("微信支付APP回调通知参数不正确、解析通知数据失败,{}",me);
} catch (ValidationException ve) {
// 签名验证失败,返回 401 UNAUTHORIZED 状态码
wxAppPayResponse.setSuccess(Boolean.FALSE);
log.info("微信支付APP回调签名验证失败,{}", ve);
} catch (Exception e) {
wxAppPayResponse.setSuccess(Boolean.FALSE);
log.info("微信支付APP回调异常:{}",e);
}
wxAppPayResponse.setSuccess(Boolean.FALSE);
wxAppPayResponse.setNotice(noticeResponse);
return wxAppPayResponse;
}
public WxAppPayResponse query(WxAppPayRequest request, WxAppPayRequest.Query query) {
Config config = getConfig(request);
WxAppPayResponse payResponse = new WxAppPayResponse();
AppServiceExtension.Builder appService = new AppServiceExtension.Builder();
appService.config(config);
appService.httpClient(new DefaultHttpClientBuilder().config(config).build());
appService.hostName(HostName.API);
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
//QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
queryRequest.setMchid(query.getMchId());
queryRequest.setOutTradeNo(query.getOutTradeNo());
try {
/*SUCCESS--支付成功
REFUND--转入退款
NOTPAY--未支付
CLOSED--已关闭
REVOKED--已撤销(刷卡支付)
USERPAYING--用户支付中
PAYERROR--支付失败(其他原因,如银行返回失败)
ACCEPT--已接收,等待扣款*/
Transaction result = appService.build().queryOrderByOutTradeNo(queryRequest);
payResponse.setMsg("微信支付查单:订单["+query.getOutTradeNo()+"]");
payResponse.setBody(JSONUtil.toJsonStr(result));
String outTradeNo = "out_trade_no-"+query.getOutTradeNo()+",";
/*if (!Objects.equals(result.get("return_code"),"SUCCESS")){
throw new BusinessException(ApiResponseCode.ERROR.getCode(),outTradeNo+result.get("return_code")+result.get("return_msg"));
}
if (!Objects.equals(result.get("result_code"),"SUCCESS")){
throw new BusinessException(ApiResponseCode.ERROR.getCode(),outTradeNo+result.get("err_code")+result.get("err_code_des"));
}*/
if (result != null){
if (Objects.equals(result.getTradeState(), Transaction.TradeStateEnum.SUCCESS)){
payResponse.setTradeState("SUCCESS");
payResponse.setSuccess(Boolean.TRUE);
log.info("微信支付查单成功:订单[{}],报文:{}",result.getOutTradeNo(),payResponse.getBody());
}else {
throw new BusinessException(ApiResponseCode.ERROR.getCode(),outTradeNo+result.getTradeState().toString());
}
}
} catch (ServiceException se) {
// API返回失败, 例如ORDER_NOT_EXISTS
//System.out.printf("code=[%s], message=[%s]\n", se.getErrorCode(), se.getErrorMessage());
//System.out.printf("reponse body=[%s]\n", se.getResponseBody());
payResponse.setMsg("微信支付查单,服务返回异常:订单["+query.getOutTradeNo()+"]");
payResponse.setBody(se.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]微信支付查单,服务返回异常:{}",query.getOutTradeNo(),se);
throw new BusinessException(ApiResponseCode.WX_PAY_PLACE_ORDER_SERVICE_ERROR);
}catch (HttpException he){
payResponse.setMsg("微微信支付查单请求失败:订单["+query.getOutTradeNo()+"]");
payResponse.setBody(he.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]微信支付查单请求失败:{}",query.getOutTradeNo(),he);
throw new BusinessException(ApiResponseCode.WX_PAY_PLACE_ORDER_FAIL);
}catch (ValidationException ve){
payResponse.setMsg("微信支付查单,验证签名失败:订单["+query.getOutTradeNo()+"]");
payResponse.setBody(ve.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]验证微信支付返回签名失败:{}",query.getOutTradeNo(),ve);
throw new BusinessException(ApiResponseCode.WX_PAY_SIGN_VERIFY_FAIL);
}catch (MalformedMessageException me){
payResponse.setMsg("微信支付查单请求成功,content-type不为application/json解析返回体失败:订单["+query.getOutTradeNo()+"]");
payResponse.setBody(me.getMessage());
payResponse.setSuccess(Boolean.FALSE);
log.info("订单[{}]微信支付查单请求成功,content-type不为application/json解析返回体失败:{}",query.getOutTradeNo(),me);
throw new BusinessException(ApiResponseCode.WX_PAY_PLACE_ORDER_SERVICE_RESPONSE_TYPE_ERROR);
}
return payResponse;
}
}
接着继续新建一个类 WxAppPayRequest.java
去继承 WxAppPay.java
,内容如下:
package com.zyq.pay.wx.app;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
/** 微信app支付
* author xiaochi
* date 2024/6/4 14:04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WxAppPayRequest extends WxAppPay {
private String appid;
private String mchId;// 商户id
private String privateKeyPath;// 商户API私钥路径(就是证书apiclient_key.pem路径)
private String mchSerialNo;// 商户证书序列号
private String apiV3Key;// 商户APIV3密钥
private String notifyUrl;// 回调地址
private String body;// 描述
private String outTradeNo;// 订单号
private Integer totalFee;// 金额(分)
private String ip = "xxxx.xx.xx.xx";// ip地址,默认正式环境
public WxAppPayResponse pay() {
return super.pay(this);
}
public WxAppPayResponse query(Query query) {
this.setMchId(query.getMchId());
this.setMchSerialNo(query.getMchSerialNo());
this.setPrivateKeyPath(query.getPrivateKeyPath());
this.setApiV3Key(query.getApiV3Key());
return super.query(this,query);
}
public WxAppPayResponse notice(Notice notice) {
return super.notice(notice);
}
/**
* 微信支付回调通知
*/
@Data
public static class Notice{
private String mchSerialNo;// 商户证书序列号
private String nonce;
private String signature;
private String timestamp;
private String body;
/**
* 获取微信回调最原始的原始报文(否则验签会失败)
* @param request
* @return
* @throws Exception
*/
public static String getBody(HttpServletRequest request) throws Exception {
BufferedReader br = request.getReader();
String str = null;
StringBuilder sb = new StringBuilder();
while ((str = br.readLine())!=null) {
sb.append(str);
}
return sb.toString();
}
}
/**
* 微信查单
*/
@Data
public static class Query{
private String mchId;// 商户id
private String privateKeyPath;// 商户API私钥路径
private String mchSerialNo;// 商户证书序列号
private String apiV3Key;// 商户APIV3密钥
private String transactionId;// 微信流水号
private String outTradeNo;// 订单号
}
}
接下来就是创建访问的接口了,进行测试了 PayController.java
,内容如下:
package com.zyq.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zyq.annotation.AccessLimit;
import com.zyq.annotation.ApiAuthorize;
import com.zyq.config.AliPayConfig;
import com.zyq.config.WxConfig;
import com.zyq.constant.Constant;
import com.zyq.entity.MemoryRecordOrder;
import com.zyq.enuns.ApiResponseCode;
import com.zyq.enuns.PayStateEnum;
import com.zyq.enuns.PayWayEnum;
import com.zyq.pay.ali.AliAppPayRequest;
import com.zyq.pay.ali.AliAppPayResponse;
import com.zyq.pay.wx.app.WxAppPayRequest;
import com.zyq.pay.wx.app.WxAppPayResponse;
import com.zyq.service.IAccountDistributionTreeLevelService;
import com.zyq.service.ICodeActiveService;
import com.zyq.service.IKickbackRecordService;
import com.zyq.service.IMemoryRecordOrderNoticeService;
import com.zyq.service.IMemoryRecordOrderService;
import com.zyq.service.IMemoryRecordService;
import com.zyq.util.AsyncUtil;
import com.zyq.util.LockUtil;
import com.zyq.util.NumberUtil;
import com.zyq.util.OrderUtil;
import com.zyq.util.R;
import com.zyq.vo.req.PayReqVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
/** 支付
* author xiaochi
* date 2024/6/4 11:09
*/
@RestController
@RequestMapping(Constant.Api.v1+"/pay")
public class PayController extends BaseController {
@Autowired
public IMemoryRecordOrderService memoryRecordOrderService;
@Autowired
public IMemoryRecordService memoryRecordService;
@Autowired
public IAccountDistributionTreeLevelService accountDistributionTreeLevelService;
@Autowired
private WxConfig wxConfig;
@Autowired
private AliPayConfig aliPayConfig;
@Autowired
private IKickbackRecordService kickbackRecordService;
@Autowired
private ICodeActiveService codeActiveService;
@Autowired
private IMemoryRecordOrderNoticeService memoryRecordOrderNoticeService;
// --------------------------------------- 微信支付 -------------------------------------------
/** 微信支付
* @param reqVo 订单vo
*/
@ApiAuthorize
@AccessLimit(count = Constant.AccessLimit.count,timeOut = Constant.AccessLimit.timeOut)
@PostMapping("/wx/app")
public R<WxAppPayResponse> wxPay(@RequestBody @Valid PayReqVo reqVo, HttpServletRequest request){
// 验证邀请码
if (StrUtil.isNotBlank(reqVo.getCode()) && !codeActiveService.checkCodeActive(reqVo.getCode())){
return R.error(ApiResponseCode.CODE_NOT_ACTIVE);
}
MemoryRecordOrder memoryRecordOrder = memoryRecordOrderService.getById(reqVo.getId());
if (ObjectUtils.isEmpty(memoryRecordOrder)){
return R.error(ApiResponseCode.PARAMS_ERROR);
}
if (Objects.equals(memoryRecordOrder.getState(),PayStateEnum.PAY.getCode())){
return R.error(ApiResponseCode.ORDER_ALREADY_PAY);
}
memoryRecordOrder.setCode(reqVo.getCode());
memoryRecordOrder.setPayWay(reqVo.getPayWay());
if (!memoryRecordOrderService.updateById(memoryRecordOrder)){
return R.error();
}
// 发起预支付成功,拼接返回参数
WxAppPayRequest wxAppPayRequest = new WxAppPayRequest();
wxAppPayRequest.setBody("描述信息");
wxAppPayRequest.setTotalFee(memoryRecordOrder.getTotalPrice().intValue());
wxAppPayRequest.setOutTradeNo(memoryRecordOrder.getOutTradeNo());
wxAppPayRequest.setAppid(wxConfig.getAppid());
wxAppPayRequest.setMchId(wxConfig.getMchId());
wxAppPayRequest.setPrivateKeyPath(wxConfig.getPrivateKeyPath());
wxAppPayRequest.setMchSerialNo(wxConfig.getMchSerialNo());
wxAppPayRequest.setApiV3Key(wxConfig.getKey());
wxAppPayRequest.setNotifyUrl(wxConfig.getPayCallbackUrl());
return R.ok(wxAppPayRequest.pay());
}
/**
* 微信支付回调
* @param request
* @return
*/
@PostMapping("/wx/app/callback")
public Map<String,String> wxCallback(HttpServletRequest request){
Map<String,String> resp = new HashMap<>();
try {
WxAppPayRequest.Notice notice = new WxAppPayRequest.Notice();
notice.setMchSerialNo(request.getHeader("Wechatpay-Serial"));
notice.setNonce(request.getHeader("Wechatpay-Nonce"));
notice.setTimestamp(request.getHeader("Wechatpay-Timestamp"));
notice.setSignature(request.getHeader("Wechatpay-Signature"));
notice.setBody(WxAppPayRequest.Notice.getBody(request));
WxAppPayRequest wxAppPayRequest = new WxAppPayRequest();
WxAppPayResponse wxAppPayResponse = wxAppPayRequest.notice(notice);
WxAppPayResponse.Notice noticeResponse = wxAppPayResponse.getNotice();
AsyncUtil.run(() -> {
boolean rs = memoryRecordOrderNoticeService.saveNotifyBody(noticeResponse.getOutTradeNo(), noticeResponse.getTransactionId(), wxAppPayResponse.getBody());
logger.info("订单[{}]交易号[{}]微信APP支付通知报文入库[{}]",noticeResponse.getOutTradeNo(),noticeResponse.getTransactionId(),rs?"成功":"失败");
});
if (wxAppPayResponse.isSuccess()){
LockUtil.run(() -> {
// 业务逻辑
MemoryRecordOrder memoryRecordOrder = memoryRecordOrderService.findByOutTradeNo(noticeResponse.getOutTradeNo());
Map<String,String> param = new HashMap<>();
param.put("out_trade_no",noticeResponse.getOutTradeNo());
param.put("total_fee",String.valueOf(noticeResponse.getPayerTotal()));
param.put("appid",noticeResponse.getAppid());
param.put("pay_time",noticeResponse.getSuccessTime());
param.put("notify_time", DateUtil.now());
if (this.check(memoryRecordOrder,param,PayWayEnum.WX)){
// 更新业务
this.updatePayStateByOrderNo(noticeResponse.getOutTradeNo(),param, PayWayEnum.WX);
//分销与佣金计算入库
this.handleSave(memoryRecordOrder);
}
});
resp.put("code","SUCCESS");
resp.put("message","成功");
return resp;
}
resp.put("code","FAIL");
resp.put("message","失败");
return resp;
}catch (Exception e){
logger.info("微信APP支付回调异常:{}",e);
}
resp.put("code","FAIL");
resp.put("message","失败");
return resp;
}
// --------------------------------------- 支付宝支付 -------------------------------------------
/** 支付宝支付
*/
@ApiAuthorize
@AccessLimit(count = Constant.AccessLimit.count,timeOut = Constant.AccessLimit.timeOut)
@PostMapping("/ali")
public R<AliAppPayResponse> aliPay(@RequestBody @Valid PayReqVo reqVo){
// 验证邀请码
if (StrUtil.isNotBlank(reqVo.getCode()) && !codeActiveService.checkCodeActive(reqVo.getCode())){
return R.error(ApiResponseCode.CODE_NOT_ACTIVE);
}
MemoryRecordOrder memoryRecordOrder = memoryRecordOrderService.getById(reqVo.getId());
if (ObjectUtils.isEmpty(memoryRecordOrder)){
return R.error(ApiResponseCode.PARAMS_ERROR);
}
if (Objects.equals(memoryRecordOrder.getState(),PayStateEnum.PAY.getCode())){
return R.error(ApiResponseCode.ORDER_ALREADY_PAY);
}
memoryRecordOrder.setCode(reqVo.getCode());
memoryRecordOrder.setPayWay(reqVo.getPayWay());
if (!memoryRecordOrderService.updateById(memoryRecordOrder)){
return R.error();
}
AliAppPayRequest aliPayAppRequest = new AliAppPayRequest();
aliPayAppRequest.setNotifyUrl(aliPayConfig.getPayCallbackUrl());
aliPayAppRequest.setAppid(aliPayConfig.getAppid());
aliPayAppRequest.setPrivateKey(aliPayConfig.getPrivateKey());
aliPayAppRequest.setPublicKey(aliPayConfig.getPublicKey());
aliPayAppRequest.setOutTradeNo(memoryRecordOrder.getOutTradeNo());
aliPayAppRequest.setTotalAmount(OrderUtil.coverRmb(memoryRecordOrder.getTotalPrice()).toString());
aliPayAppRequest.setSubject("描述信息");
return R.ok(aliPayAppRequest.pay());
}
/**
* 支付宝支付回调
* @param request
* @return
*/
@PostMapping("/ali/callback")
public String aliCallback(HttpServletRequest request){
try {
Map<String, String> params = this.toMap(request);
if (params.isEmpty()){
return "failure";
}
// 保存通知报文
AsyncUtil.run(() -> {
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
boolean rs = memoryRecordOrderNoticeService.saveNotifyBody(outTradeNo, tradeNo, JSONUtil.toJsonStr(params));
logger.info("订单[{}]交易号[{}]支付宝APP支付通知报文入库[{}]",outTradeNo,tradeNo,rs?"成功":"失败");
});
AliAppPayRequest aliPayAppRequest = new AliAppPayRequest();
AliAppPayRequest.Notice notice = new AliAppPayRequest.Notice();
notice.setPublicKey(aliPayConfig.getPublicKey());
notice.setOutTradeNo(params.get("out_trade_no"));
notice.setTransactionId(params.get("trade_no"));
notice.setBody(params);
String result = aliPayAppRequest.notice(notice,(param) -> {
//String notifyTime = param.get("notify_time");// 通知时间
String outTradeNo = param.get("out_trade_no");
LockUtil.run(() -> {
try {
MemoryRecordOrder memoryRecordOrder = memoryRecordOrderService.findByOutTradeNo(outTradeNo);
if (this.check(memoryRecordOrder,param,PayWayEnum.ALI)){
// 1.更新业务
this.updatePayStateByOrderNo(outTradeNo,params, PayWayEnum.ALI);
// 2.分销与佣金计算入库
this.handleSave(memoryRecordOrder);
}
}catch (Exception e){
logger.info("支付宝支付回调:订单[{}]业务执行异常:{}",outTradeNo,e);
}
});
},(param) -> {
logger.info("订单[{}]支付回调失败:{}",param.get("out_trade_no"),JSONUtil.toJsonStr(param));
});
logger.info("支付宝APP回调结果:{}",result);
return result;
}catch (Exception e){
logger.info("支付宝APP支付回调异常:{}",e);
return "failure";
}
}
/**
* 微信/支付宝 支付查询
* @param orderId
* @return
*/
@PostMapping("/query/{orderId}")
public R<AliAppPayResponse> query(@PathVariable String orderId){
MemoryRecordOrder memoryRecordOrder = memoryRecordOrderService.getById(orderId);
if (ObjectUtils.isEmpty(memoryRecordOrder)){
return R.error(ApiResponseCode.ORDER_NOT_EXISTS);
}
//1微信2支付宝
if (Objects.equals(PayWayEnum.WX.getCode(),memoryRecordOrder.getPayWay())){
WxAppPayRequest wxAppPayRequest = new WxAppPayRequest();
// 微信查单
WxAppPayRequest.Query wxQuery = new WxAppPayRequest.Query();
wxQuery.setMchId(wxConfig.getMchId());
wxQuery.setMchSerialNo(wxConfig.getMchSerialNo());
wxQuery.setPrivateKeyPath(wxConfig.getPrivateKeyPath());
wxQuery.setApiV3Key(wxConfig.getKey());
wxQuery.setTransactionId(memoryRecordOrder.getTransactionId());
wxQuery.setOutTradeNo(memoryRecordOrder.getOutTradeNo());
if (wxAppPayRequest.query(wxQuery).isSuccess()){
return R.ok();
}
return R.error();
}
AliAppPayRequest aliPayAppRequest = new AliAppPayRequest();
AliAppPayRequest.Query aliQuery = new AliAppPayRequest.Query();
aliQuery.setAppid(aliPayConfig.getAppid());
aliQuery.setPrivateKey(aliPayConfig.getPrivateKey());
aliQuery.setOutTradeNo(memoryRecordOrder.getOutTradeNo());
aliQuery.setTransactionId(memoryRecordOrder.getTransactionId());
aliQuery.setGateway(aliPayConfig.getGateway());
if (aliPayAppRequest.query(aliQuery).isSuccess()){
return R.ok();
}
return R.error();
}
// ----------------------------------- 业务处理----------------------------
/**
* 分销与佣金入库
* @param memoryRecordOrder
*/
private void handleSave(MemoryRecordOrder memoryRecordOrder){
if (StrUtil.isNotBlank(memoryRecordOrder.getCode())){
// 订单有邀请码,才添加分销记录与计算佣金
if (accountDistributionTreeLevelService.createTreeByCode(memoryRecordOrder.getAccountId(), memoryRecordOrder.getCode())){
logger.info("订单[{}],分销保存成功",memoryRecordOrder.getOutTradeNo());
// 佣金
if (kickbackRecordService.calcKickback(memoryRecordOrder)){
logger.info("订单[{}],accountId[{}],分佣保存成功",memoryRecordOrder.getOutTradeNo(),memoryRecordOrder.getAccountId());
}else {
logger.info("订单[{}],accountId[{}],分佣保存失败",memoryRecordOrder.getOutTradeNo(),memoryRecordOrder.getAccountId());
}
}else {
logger.info("订单[{}],分销保存失败",memoryRecordOrder.getOutTradeNo());
}
}
}
/**
* 根据订单号更新支付状态(同步锁)
* @param outTradeNo 订单号
* @param params 回调参数
* @param payWay 支付方式
* @return
*/
private void updatePayStateByOrderNo(String outTradeNo,Map<String,String> params,PayWayEnum payWay){
QueryWrapper<MemoryRecordOrder> query = Wrappers.query();
query.lambda().eq(MemoryRecordOrder::getOutTradeNo,outTradeNo)
.eq(MemoryRecordOrder::getState,PayStateEnum.UNPAY.getCode());
query.last("LIMIT 1");
MemoryRecordOrder memoryRecordOrder = memoryRecordOrderService.getOne(query);
if (ObjectUtils.isEmpty(memoryRecordOrder)){
return;
}
memoryRecordOrder.setState(PayStateEnum.PAY.getCode());
memoryRecordOrder.setPayWay(payWay.getCode());
if (Objects.equals(payWay,PayWayEnum.WX)){//微信
memoryRecordOrder.setTransactionId(params.get("transaction_id"));
memoryRecordOrder.setPayTime(DateUtil.parse(params.get("pay_time")));//交易时间
memoryRecordOrder.setNotifyTime(DateUtil.parse(params.get("notify_time")));
memoryRecordOrder.setPayPrice(Long.valueOf(params.get("total_fee")));
}
if (Objects.equals(payWay,PayWayEnum.ALI)){// 支付宝
memoryRecordOrder.setTransactionId(params.get("trade_no"));
memoryRecordOrder.setPayTime(DateUtil.parse(params.get("gmt_payment")));//交易时间
memoryRecordOrder.setNotifyTime(DateUtil.parse(params.get("notify_time")));
memoryRecordOrder.setPayPrice(OrderUtil.rmbToCover(NumberUtil.toBigDecimal(params.get("total_amount"))));
}
String res = "失败";
if (memoryRecordOrderService.updateById(memoryRecordOrder)){
res = "成功";
}
logger.info("订单状态更新[{}],[{}]-{}",memoryRecordOrder.getOutTradeNo(),PayWayEnum.getValueByKey(payWay.getCode()),res);
}
/**
* 验证回调参数
* @return
*/
private boolean check(MemoryRecordOrder memoryRecordOrder,Map<String, String> params,PayWayEnum payWayEnum){
String outTradeNo = params.get("out_trade_no");
//MemoryRecordOrder memoryRecordOrder = memoryRecordOrderService.findByOutTradeNo(outTradeNo);
if (ObjectUtils.isEmpty(memoryRecordOrder)){
logger.info("订单[{}]不存在",outTradeNo);
return false;
}
if (Objects.equals(memoryRecordOrder.getState(),PayStateEnum.PAY.getCode())){
logger.info("订单[{}]已支付",outTradeNo);
return false;
}
if (Objects.equals(PayWayEnum.WX,payWayEnum)){
// 金额验证
if (!Objects.equals(String.valueOf(memoryRecordOrder.getTotalPrice()),params.get("total_fee"))){
logger.info("订单[{}]金额[{}-{}]不一致",outTradeNo,memoryRecordOrder.getTotalPrice(),params.get("total_fee"));
return false;
}
// 验证app_id是否为该商户本身
if (!Objects.equals(wxConfig.getAppid(),params.get("appid"))){
logger.info("订单[{}]微信支付回调appid[{}-{}]不一致",outTradeNo,aliPayConfig.getAppid(),params.get("appid"));
return false;
}
return true;
}
if (Objects.equals(PayWayEnum.ALI,payWayEnum)){
// 金额验证
if (!Objects.equals(OrderUtil.coverRmb(memoryRecordOrder.getTotalPrice()).toString(),params.get("total_amount"))){
logger.info("订单[{}]金额[{}-{}]不一致",outTradeNo,memoryRecordOrder.getTotalPrice(),params.get("total_amount"));
return false;
}
// 验证app_id是否为该商户本身
if (!Objects.equals(aliPayConfig.getAppid(),params.get("app_id"))){
logger.info("订单[{}]支付宝支付回调appid[{}-{}]不一致",outTradeNo,aliPayConfig.getAppid(),params.get("app_id"));
return false;
}
return true;
}
return false;
}
/**
* 解析支付宝支付回调参数
* @param request
* @return
*/
public Map<String, String> toMap(HttpServletRequest request) {
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用。
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
return params;
}
}
访问接口已经可以进行支付了。
用到的类 WxAppPayResponse.java
,内容如下:
package com.zyq.pay.wx.app;
import lombok.Data;
/**
* author xiaochi
* date 2024/6/4 13:50
*/
@Data
public class WxAppPayResponse {
private String appid;
private String partnerId;
private String prepayId;
private String packageVal;
private String nonceStr;
private String timestamp;
private String sign;
private String tradeState;// 用于查询订单状态
//---------返回提示-------------
private String msg;// 提示信息
private String body;//主体
private boolean success = false;//是否成功
private Notice notice;// 支付回调
/**
* 通知返回参数
*/
@Data
public static class Notice{
private String appid;//订单号
private String transactionId;//订单号
private String outTradeNo;//订单号
private String successTime;// 支付完成时间
private Integer payerTotal;// 支付金额
}
}
支付宝支付
新建AliAppPay.java
package com.zyq.pay.ali;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.zyq.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** 支付宝支付
* author xiaochi
* date 2024/6/5 10:29
*/
@Slf4j
public class AliAppPay {
private static AlipayClient alipayClient;
/**
* 发起支付
* @param request
* @return
*/
public AliAppPayResponse pay(AliAppPayRequest request) {
AlipayClient alipayClient = getAlipayClient(request);
// 构造请求参数以调用接口
AlipayTradeAppPayRequest appPayRequest = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
// 设置订单绝对超时时间
//model.setTimeExpire("2016-12-31 10:05:00");
model.setOutTradeNo(request.getOutTradeNo());
model.setTotalAmount(request.getTotalAmount());// 单位元
model.setSubject(request.getSubject());
appPayRequest.setBizModel(model);
appPayRequest.setNotifyUrl(request.getNotifyUrl());
AliAppPayResponse payResponse = new AliAppPayResponse();
AlipayTradeAppPayResponse response;
try {
log.info("支付宝APP支付参数:订单[{}],body:{}",request.getOutTradeNo(),JSONUtil.toJsonStr(request));
response = alipayClient.sdkExecute(appPayRequest);
log.info("支付宝支付报文:订单[{}],报文:{}",request.getOutTradeNo(),response.getBody());
payResponse.setBody(response.getBody());
if (response.isSuccess()) {
payResponse.setSuccess(Boolean.TRUE);
payResponse.setMsg("支付宝APP支付调用成功:订单["+request.getOutTradeNo()+"]");
}else {
payResponse.setSuccess(Boolean.FALSE);
payResponse.setMsg("支付宝APP支付调用失败:订单["+request.getOutTradeNo()+"]");
}
//System.out.println("调用失败");
// sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
// String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
// System.out.println(diagnosisUrl);
} catch (AlipayApiException e) {
payResponse.setSuccess(Boolean.FALSE);
log.info("支付宝APP支付失败:订单[{}],{}",request.getOutTradeNo(),e);
throw new BusinessException(Integer.parseInt(e.getErrCode()),"订单[" + request.getOutTradeNo() + "]," +e.getMessage());
}
return payResponse;
}
/**
* 验证签名
* @param request
* @param body
* @return
*/
public boolean verifySign(AliAppPayRequest request, Map<String, String> body) {
// 调用SDK验证签名
boolean signVerified = false;
try {
log.info("订单[{}]流水号[{}]支付宝验签body参数:{}",request.getOutTradeNo(),request.getTransactionId(), JSONUtil.toJsonStr(body));
signVerified = AlipaySignature.rsaCheckV1(body, request.getPublicKey(), request.getCharset(), request.getSignType());
} catch (AlipayApiException e) {
log.info("订单[{}]流水号[{}]支付宝验签失败:{}",request.getOutTradeNo(),request.getTransactionId(),e);
}
log.info("订单[{}]流水号[{}]支付宝验签signVerified={}",request.getOutTradeNo(),request.getTransactionId(), signVerified);
return signVerified;
}
/**
* 查询支付
* @param request
* @return
*/
public AliAppPayResponse query(AliAppPayRequest request) {
AlipayClient alipayClient = getAlipayClient(request);
// 构造请求参数以调用接口
AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
// 设置订单支付时传入的商户订单号
model.setOutTradeNo(request.getOutTradeNo());
// 设置查询选项
List<String> queryOptions = new ArrayList<String>();
queryOptions.add("trade_settle_info");
model.setQueryOptions(queryOptions);
// 设置支付宝交易号
model.setTradeNo(request.getTransactionId());
queryRequest.setBizModel(model);
AliAppPayResponse payResponse = new AliAppPayResponse();
AlipayTradeQueryResponse response;
try {
response = alipayClient.execute(queryRequest);
log.info("支付宝查询报文:订单[{}],流水号[{}],{}",request.getOutTradeNo(),request.getTransactionId(),response.getBody());
payResponse.setBody(response.getBody());
// TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款)
if (response.isSuccess() && (Objects.equals(response.getTradeStatus(),"TRADE_SUCCESS")||Objects.equals(response.getTradeStatus(),"TRADE_FINISHED"))) {
payResponse.setSuccess(Boolean.TRUE);
payResponse.setMsg("支付宝查询成功:订单["+request.getOutTradeNo()+"],流水号["+request.getTransactionId()+"]");
}else {
payResponse.setSuccess(Boolean.FALSE);
payResponse.setMsg("支付宝查询失败:订单["+request.getOutTradeNo()+"],流水号["+request.getTransactionId()+"]");
}
} catch (AlipayApiException e) {
log.info("支付宝查询失败:订单号[{}],流水号[{}],{}",request.getOutTradeNo(),request.getTransactionId(),e);
throw new BusinessException(Integer.parseInt(e.getErrCode()),"订单[" + request.getOutTradeNo() + "]," +e.getMessage());
}
return payResponse;
}
/**
* 退款
* @param request
* @return
*/
public AliAppPayResponse tradeRefund(AliAppPayRequest request){
AliAppPayResponse payResponse = new AliAppPayResponse();
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo(request.getOutTradeNo());
model.setRefundAmount(request.getTotalAmount());
model.setRefundReason("正常退款");
model.setOutRequestNo(request.getOutRequestNo());
refundRequest.setBizModel(model);
AlipayTradeRefundResponse response;
try {
response = getAlipayClient(request).execute(refundRequest);
log.info("支付宝支付报文:订单[{}],报文:{}",request.getOutTradeNo(),response.getBody());
} catch (AlipayApiException e) {
payResponse.setSuccess(Boolean.FALSE);
payResponse.setBody(e.getErrMsg());
payResponse.setMsg("支付宝退款失败:订单["+request.getOutTradeNo()+"],"+e.getErrCode());
log.info("支付宝退款失败:订单[{}],{}", request.getOutTradeNo(),e.getErrMsg());
throw new BusinessException(Integer.parseInt(e.getErrCode()),"out_trade_no-"+request.getOutTradeNo()+","+ e.getErrMsg());
}
if ("10000".equals(response.getCode())) {
payResponse.setBody(response.getBody());
payResponse.setMsg("支付宝退款调用成功:订单["+request.getOutTradeNo()+"]");
payResponse.setSuccess(Boolean.TRUE);
log.info("支付宝退款调用成功:订单[{}],报文:{}" ,request.getOutTradeNo(),response.getBody());
} else {
payResponse.setBody(response.getBody());
payResponse.setMsg("支付宝退款调用失败:订单["+request.getOutTradeNo()+"]");
payResponse.setSuccess(Boolean.FALSE);
log.error("支付宝退款调用失败:订单[{}],报文:{}",request.getOutTradeNo(),response.getBody());
}
return payResponse;
}
/**
* 获取支付客户端
* @param request
* @return
*/
protected AlipayClient getAlipayClient(AliAppPayRequest request){
/*if (alipayClient != null){
return alipayClient;
}*/
AlipayConfig alipayConfig = new AlipayConfig();
alipayConfig.setServerUrl(request.getGateway());
alipayConfig.setAppId(request.getAppid());
alipayConfig.setPrivateKey(request.getPrivateKey());
alipayConfig.setAlipayPublicKey(request.getPublicKey());
alipayConfig.setFormat(request.getFormat());
alipayConfig.setCharset(StandardCharsets.UTF_8.toString());
alipayConfig.setSignType(request.getSignType());
// 初始化SDK
// AlipayClient alipayClient = null;
try {
alipayClient = new DefaultAlipayClient(alipayConfig);
} catch (AlipayApiException e) {
log.info("支付宝支付初始化失败:{}",e);
}
return alipayClient;
}
}
接着新建AliAppPayRequest.java
继承 AliAppPay.java
,内容如下:
package com.zyq.pay.ali;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.Consumer;
/** 支付宝app支付
* author xiaochi
* date 2024/6/4 14:04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class AliAppPayRequest extends AliAppPay {
private String gateway = "https://openapi.alipay.com/gateway.do";// 网关地址
private String appid;// 应用id
private String privateKey;// 私钥
private String publicKey;// 公钥
private String notifyUrl;// 回调地址
private String outTradeNo;// 订单号
private String totalAmount;// 金额(元)
private String subject;// 订单标题
private String format = "JSON";// 仅支持JSON
private String signType = "RSA2";// 签名类型
private String charset = StandardCharsets.UTF_8.toString();// 请求使用的编码格式
// ------------------------------------ 查询参数 ---------------------------
private String transactionId;// 支付宝流水号
// ------------------------------------ 退款参数 ---------------------------
private String outRequestNo;//退款单号
public AliAppPayResponse pay() {
return super.pay(this);
}
public String notice(Notice notice,Consumer<Map<String, String>> paySuccessback, Consumer<Map<String, String>> payFailback) {
this.setPublicKey(notice.getPublicKey());
this.setOutTradeNo(notice.getOutTradeNo());
this.setTransactionId(notice.getTransactionId());
if (super.verifySign(this,notice.getBody()) && (notice.getBody().get("app_id").equals(notice.getAppid()))){
String tradeStatus = notice.getBody().get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)){
if (paySuccessback != null){
paySuccessback.accept(notice.getBody());
}
}else {
if (payFailback != null){
payFailback.accept(notice.getBody());
}
}
return "success";
}else {
return "failure";
}
}
public AliAppPayResponse query(Query query) {
this.setTransactionId(query.getTransactionId());
this.setOutTradeNo(query.getOutTradeNo());
this.setAppid(query.getAppid());
this.setPrivateKey(query.getPrivateKey());
this.setGateway(query.getGateway());
return super.query(this);
}
/**
* 支付宝支付通知
*/
@Data
public static class Notice{
private String appid;// appid
private String publicKey;// 公钥
private String outTradeNo;// 订单号
private String transactionId;// 支付宝流水号
private Map<String, String> body;// 回调报文
}
/**
* 支付宝查单
*/
@Data
public static class Query{
private String appid;// appid
private String privateKey;// 私钥
private String outTradeNo;// 订单号
private String transactionId;// 支付宝流水号
private String gateway = "https://openapi.alipay.com/gateway.do";// 网关地址
}
}
接下来继续访问上边的测试接口PayController.java
,已经可以进行支付了,用到的类AliAppPayResponse.java
package com.zyq.pay.ali;
import lombok.Data;
/** 支付宝app支付vo
* author xiaochi
* date 2024/6/4 13:50
*/
@Data
public class AliAppPayResponse {
private String msg;// 提示信息
private String body;//主体
private boolean success = false;//是否成功
}
到此完成。