个人使用的是IJPay的开源支付工具类https://javen205.gitee.io/ijpay/,不用也行,支付宝的sdk本身也很方便了。
1.在配置文件中定义好各种常量配置,如appid/证书路径等
# 支付宝小程序支付
alipay.miniapp.appId=
alipay.miniapp.privateKey=
alipay.miniapp.aesEncryptKey=
alipay.miniapp.aliPayCertPath=/alipayCertPublicKey_RSA2.crt
alipay.miniapp.appCertPath=/appCertPublicKey.crt
alipay.miniapp.rootCertPath=/alipayRootCert.crt
alipay.miniapp.onlineSignUpNotifyUrl=https://xx.com.cn/alipayNotify/doProcess?invokeMethod=xxProcessor
# 支付宝APP支付 (一个账户下的不同应用支付宝证书和根证书都相同)
alipay.app.appId=
alipay.app.appCertPath=/appCertPublicKey.crt
2.APP支付
1.新建配置实体类,用于接收配置文件的各种配置
import com.alipay.api.AlipayApiException;
import com.ijpay.alipay.AliPayApiConfig;
import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
/**
* 支付宝APP支付配置
*
* @author yz
* @className AliPayAppConfig
* @date 2020/7/29 13:53
*/
@Component
@Data
@Validated
public class AliPayAppConfig {
private final static Logger LOGGER = LoggerFactory.getLogger(AliPayAppConfig.class);
/**
* 支付宝支付 APP appId
*/
@NotEmpty
@Value("${alipay.app.appId}")
private String appId;
/**
* 支付宝支付 APP app证书
*/
@NotEmpty
@Value("${alipay.app.appCertPath}")
private String appAppCertPath;
/**
* 支付宝支付 支付宝证书
*/
@NotEmpty
@Value("${alipay.miniapp.aliPayCertPath}")
private String miniAliPayCertPath;
/**
* 支付宝支付 根证书
*/
@NotEmpty
@Value("${alipay.miniapp.rootCertPath}")
private String miniRootCertPath;
/**
* 支付宝支付 小程序私钥
*/
@NotEmpty
@Value("${alipay.miniapp.privateKey}")
private String miniPrivateKey;
/**
* 支付宝支付 线上报名回调URL
*/
@NotEmpty
@Value("${alipay.miniapp.onlineSignUpNotifyUrl}")
private String onlineSignUpNotifyUrl;
/**
* 支付宝APP支付配置
*
* @return {@link AliPayApiConfig}
* @author yz
* @date 2020/7/30 15:28
*/
public AliPayApiConfig getConfig() throws AlipayApiException {
return AliPayApiConfig.builder()
.setAppId(this.appId)
.setAliPayCertPath(this.miniAliPayCertPath)
.setAliPayRootCertPath(this.miniRootCertPath)
.setAppCertPath(this.appAppCertPath)
.setCharset("UTF-8")
.setPrivateKey(this.miniPrivateKey)
.setServiceUrl("https://openapi.alipay.com/gateway.do")
.setSignType("RSA2")
.buildByCert();
}
}
2.APP预支付订单下单接口
import cn.hutool.core.map.MapUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeCreateModel;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* @author yz
* @className AliPayAppPayStrategy
* @description 支付宝APP支付策略
* @date 2020/7/29 10:49
*/
@Component("AliPayAppPayStrategy")
public class AliPayAppPayStrategy<T> implements IPayChannelStrategy<T> {
private final static Logger LOGGER = LoggerFactory.getLogger(AliPayApiConfig.class);
/**
* @description 创建预交易支付单方法
* @author yz
* @date 2020/7/29 11:22
*/
@Override
public Map<String, Object> createTrade(AllPayConfigObj allPayConfigObj, CommonPayRequest<T> payReq) throws AlipayApiException {
if (allPayConfigObj == null || allPayConfigObj.getAliPayApiConfigApp() == null) {
throw new BusiException(AppErrCode.INVALID_PARAMETER.getCode(), "支付宝APP支付出错![Invalid pay config]");
}
AliPayApiConfigKit.setThreadLocalAliPayApiConfig(allPayConfigObj.getAliPayApiConfigApp());
//发起下单请求
AlipayTradeAppPayModel alipayTradeAppPayModel = generateAlipayAppTradeCreateModel(payReq);
AlipayTradeAppPayResponse appPayResponse = AliPayApi.appPayToResponse(alipayTradeAppPayModel,
allPayConfigObj.getAlipayNotifyUrl());
//如果请求失败
if (!appPayResponse.isSuccess()) {
throw new BusiException(AppErrCode.SYSTEM_ERROR.getCode(), "支付宝APP支付出错!" +
appPayResponse.getSubCode() + appPayResponse.getSubMsg());
}
return MapUtil.builder("orderStr", (Object) appPayResponse.getBody()).build();
}
/**
* @param payReq {@link CommonPayRequest}
* @return {@link AlipayTradeCreateModel}
* @description 构造支付宝交易创建单Dto
* @author yz
* @date 2020/7/30 17:57
*/
public AlipayTradeAppPayModel generateAlipayAppTradeCreateModel(CommonPayRequest<T> payReq) {
AlipayTradeAppPayModel alipayTradeAppPayModel = new AlipayTradeAppPayModel();
alipayTradeAppPayModel.setSubject(payReq.getOrderName());
alipayTradeAppPayModel.setTotalAmount(String.valueOf(payReq.getOrderAmount()));
alipayTradeAppPayModel.setOutTradeNo(payReq.getOrderId());
//销售产品码,商家和支付宝签约的产品码,APP支付功能中该值固定为下面这个
alipayTradeAppPayModel.setProductCode("QUICK_MSECURITY_PAY");
return alipayTradeAppPayModel;
}
}
3.小程序支付
1.新建配置实体类,用于接收配置文件的各种配置
/**
* 支付宝小程序支付配置
*
* @author yz
* @className AliPayMiniAppConfig
* @date 2020/7/29 13:53
*/
@Component
@Data
@Validated
public class AliPayMiniAppConfig {
private final static Logger LOGGER = LoggerFactory.getLogger(AliPayMiniAppConfig.class);
/**
* 支付宝支付 小程序 appId
*/
@NotEmpty
@Value("${alipay.miniapp.appId}")
private String appId;
/**
* 支付宝支付 小程序 app证书
*/
@NotEmpty
@Value("${alipay.miniapp.appCertPath}")
private String miniAppCertPath;
/**
* 支付宝支付 支付宝证书
*/
@NotEmpty
@Value("${alipay.miniapp.aliPayCertPath}")
private String miniAliPayCertPath;
/**
* 支付宝支付 根证书
*/
@NotEmpty
@Value("${alipay.miniapp.rootCertPath}")
private String miniRootCertPath;
/**
* 支付宝支付 小程序私钥
*/
@NotEmpty
@Value("${alipay.miniapp.privateKey}")
private String miniPrivateKey;
/**
* 支付宝支付 报名回调URL
*/
@NotEmpty
@Value("${alipay.miniapp.onlineSignUpNotifyUrl}")
private String onlineSignUpNotifyUrl;
/**
* 支付宝小程序支付配置
*
* @return {@link AliPayApiConfig}
* @author yz
* @date 2020/7/30 15:28
*/
public AliPayApiConfig getConfig() throws AlipayApiException {
return AliPayApiConfig.builder()
.setAppId(this.appId)
.setAliPayCertPath(this.miniAliPayCertPath)
.setAliPayRootCertPath(this.miniRootCertPath)
.setAppCertPath(this.miniAppCertPath)
.setCharset("UTF-8")
.setPrivateKey(this.miniPrivateKey)
.setServiceUrl("https://openapi.alipay.com/gateway.do")
.setSignType("RSA2")
.buildByCert();
}
}
2.小程序预支付订单下单接口
import cn.hutool.core.map.MapUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeCreateModel;
import com.alipay.api.response.AlipayTradeCreateResponse;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* @author yz
* @className AliPayMiniPayStrategy
* @description 支付宝小程序支付策略
* @date 2020/7/29 10:49
*/
@Component("AliPayMiniPayStrategy")
public class AliPayMiniPayStrategy<T> implements IPayChannelStrategy<T> {
private final static Logger LOGGER = LoggerFactory.getLogger(AliPayApiConfig.class);
@Autowired
private PayFeeDao payFeeDao;
/**
* @description 创建预交易支付单方法
* @author yz
* @date 2020/7/29 11:22
*/
@Override
public Map<String, Object> createTrade(AllPayConfigObj allPayConfigObj, CommonPayRequest<T> payReq) throws AlipayApiException {
if (allPayConfigObj == null || allPayConfigObj.getAliPayApiConfigMini() == null) {
throw new BusiException(AppErrCode.INVALID_PARAMETER.getCode(), "支付宝小程序支付出错![Invalid pay config]");
}
AliPayApiConfigKit.setThreadLocalAliPayApiConfig(allPayConfigObj.getAliPayApiConfigMini());
//发起下单请求
AlipayTradeCreateModel alipayTradeCreateModel = generateAlipayTradeCreateModel(payReq);
AlipayTradeCreateResponse alipayTradeCreateResponse = AliPayApi.tradeCreateToResponse(alipayTradeCreateModel,
allPayConfigObj.getAlipayNotifyUrl());
//如果请求失败
if (!alipayTradeCreateResponse.isSuccess()) {
throw new BusiException(AppErrCode.SYSTEM_ERROR.getCode(), "支付宝小程序支付出错!" + alipayTradeCreateResponse.getSubCode() + alipayTradeCreateResponse.getSubMsg());
}
// 录入支付预支付订单记录
PayWxOrderRecDto dto = generatePrepayOrderRecDto(payReq, alipayTradeCreateResponse);
if (payFeeDao.insertPayWxOrderRec(dto) != 1) {
throw new BusiException(AppErrCode.DATABASE_INSERT_ERROR.getCode(), "录入支付宝支付预支付订单记录失败");
}
return MapUtil.builder("tradeNo", (Object) alipayTradeCreateResponse.getTradeNo()).build();
}
/**
* @param payReq {@link CommonPayRequest}
* @return {@link com.alipay.api.domain.AlipayTradeCreateModel}
* @description 构造支付宝统一下单交易创建单Dto
* @author yz
* @date 2020/7/30 17:57
*/
public AlipayTradeCreateModel generateAlipayTradeCreateModel(CommonPayRequest<T> payReq) {
AlipayTradeCreateModel alipayTradeCreateModel = new AlipayTradeCreateModel();
alipayTradeCreateModel.setSubject(payReq.getOrderName());
alipayTradeCreateModel.setBuyerLogonId(payReq.getBuyerLogonId());
alipayTradeCreateModel.setBuyerId(payReq.getBuyerId());
alipayTradeCreateModel.setTotalAmount(String.valueOf(payReq.getOrderAmount()));
alipayTradeCreateModel.setOutTradeNo(payReq.getOrderId());
return alipayTradeCreateModel;
}
}
回调处理(APP/小程序共用)
在上面指定回调URL及在自定义参数中指定处理方法,这里使用反射来调用处理方法
import cn.hutool.core.map.MapUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.ijpay.alipay.AliPayApiConfig;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* 支付宝异步通知处理服务
*
* @author yz
* @className AlipayNotifyProcessSvc
* @date 2020/8/2 21:38
*/
@Controller
@RequestMapping("/alipayNotify")
public class AlipayNotifyProcessSvc {
private final static Logger LOGGER = LoggerFactory.getLogger(AlipayNotifyProcessSvc.class);
/**
* 成功返回
*/
private final static String RTN_SUCCESS = "success";
/**
* 失败返回
*/
private final static String RTN_ERROR = "error";
@Autowired
private AliPayMiniAppConfig aliPayMiniAppConfig;
@Autowired
private OrderBusiDao orderBusiDao;
@Autowired
private SignUpSvc signUpSvc;
@Autowired
private PayBusiDao payBusiDao;
/**
* 支付宝支付费用统一回调处理接口,后续只需在该服务中定义与invokeMethod值同名的方法即可
*
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletResponse}
* @author yz
* @date 2020/8/3 11:22
*/
@RequestMapping("/doProcess")
@ResponseBody
public String doProcess(HttpServletRequest request, HttpServletResponse response) {
//1.获取支付宝POST过来反馈信息
Map<String, String> params = getNotifyParaMap(request);
LOGGER.info("接收到支付宝异步通知:{}", params);
//调用方法名
String invokeMethodName = params.get("invokeMethod");
AliPayApiConfig config;
try {
config = aliPayMiniAppConfig.getConfig();
} catch (AlipayApiException e) {
LOGGER.error("获取支付宝支付配置出错!", e);
return RTN_ERROR;
}
// 2.验证合法性
if (!verifyLegitimacy(config, params)) {
return RTN_ERROR;
}
//3.校验通过,进行订单处理
PayWxNotifyRecDto payWxNotifyRecDto = PayWxNotifyRecDto.generatePayWxNotifyRecDto(params);
// 插入支付支付通知记录
if (payBusiDao.insertPayWxNotifyRec(payWxNotifyRecDto) != 1) {
LOGGER.error("插入pay_wx_notify_rec支付通知记录失败,请检查!{}", payWxNotifyRecDto);
}
//4.根据notify_url中传入的invokeMethod来调用对应的同名方法
if (StringUtils.isBlank(invokeMethodName)) {
LOGGER.error("支付宝回调url中未配置invokeMethod,请检查!");
return RTN_ERROR;
}
try {
//获取同名且参数为Map的方法
Method method = this.getClass().getMethod(invokeMethodName, Map.class);
// 4.返回执行成功的标识与支付宝服务器通信,否则支付宝服务器会多次再POST数据
return ((boolean) method.invoke(this, params)) ? RTN_SUCCESS : RTN_ERROR;
} catch (NoSuchMethodException e) {
LOGGER.error("支付宝回调处理找不到对应方法,请检查!{}-{}", this.getClass().getSimpleName(), invokeMethodName);
} catch (IllegalAccessException e) {
LOGGER.error("支付宝回调处理调用方法失败,请检查!{}-{}", this.getClass().getSimpleName(), invokeMethodName);
} catch (InvocationTargetException e) {
LOGGER.error("支付宝异步通知处理出错!", e);
}
return RTN_ERROR;
}
/**
* 报名回调处理方法(需在notify_url中指定 invokeMethod = onlineSignUpProcessor)
*
* @param params 支付宝通知参数
* @author yz
* @date 2020/8/7 14:40
*/
public boolean onlineSignUpProcessor(Map<String, String> params) throws Exception {
// 处理业务逻辑
//.....
return true;
}
/**
* *
* 校验合法性
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 3、验证app_id是否为该商户本身。
* 上述有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS,支付宝才会认定为买家付款成功。
*
* @param config 支付宝支付配置
* @param params 异步通知携带参数
* @return {@link boolean}
* @author yz
* @date 2020/8/2 22:14
*/
private boolean verifyLegitimacy(AliPayApiConfig config, Map<String, String> params) {
String orderId = params.get("out_trade_no");
String appId = params.get("app_id");
//订单金额
String totalAmount = params.get("total_amount");
if (StringUtils.isEmpty(orderId)) {
LOGGER.error("未能获取到回调订单ID");
return false;
}
//1.验签。
try {
//注意:验签前需把notify_url中的自定义参数去除
params.remove("invokeMethod");
boolean flag = AlipaySignature.rsaCertCheckV1(params, config.getAliPayCertPath(), config.getCharset(), "RSA2");
if (!flag) {
LOGGER.error("验签失败!orderId={}", orderId);
return false;
}
} catch (AlipayApiException e) {
LOGGER.error("验签失败!orderId={}", orderId, e);
return false;
}
//2.判断APPID是否正确
// if (!config.getAppId().equals(appId)) {
// LOGGER.error("appId错误,appId={}", appId);
// return false;
// }
//3.判断该订单是否已经完成(是否是重复回调)
//判断交易状态是不是TRADE_SUCCESS
if (!"TRADE_SUCCESS".equals(params.get("trade_status"))) {
return false;
}
Map<String, Object> orderMap = MapUtil.builder("orderId", (Object) orderId).build();
// 获取订单数据
OrderDataEntity orderDataDto = orderBusiDao.queryOrderData(orderMap);
if (null != orderDataDto && OrderDataDto.OrderStatus.PAID.getCode().equals(orderDataDto.getStatus())) {
// 已完成 此处应为重复回调所致
return false;
}
// if (!(order.getInformationFee().compareTo(new BigDecimal(totalAmount)) == 0)) {
// LOGGER.error("订单金额错误");
// return false;
// }
return true;
}
/**
* 获取传入的通知参数并转为Map
*
* @param request HttpServletRequest
* @return {@link Map<String,String>}
* @author yz
* @date 2020/8/2 22:01
*/
private Map<String, String> getNotifyParaMap(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Object o : requestParams.keySet()) {
String name = (String) o;
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;
}
}