最在开发一款APP,需要实现支付宝支付,记录一下实现过程
流程整体交互图如下所示
一、引入pom依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.0.3</version>
</dependency>
二、初始化请求对象
@SneakyThrows(value = AlipayApiException.class)
public AlipayClient initAlipay() {
//构造client
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
//设置网关地址
certAlipayRequest.setServerUrl(AliPayUtil.SERVER_URL);
//设置应用Id
certAlipayRequest.setAppId(AliPayUtil.APP_ID);
//设置应用私钥
certAlipayRequest.setPrivateKey(AliPayUtil.PRIVATE_KEY);
//设置请求格式,固定值json
certAlipayRequest.setFormat(AliPayUtil.FORMAT);
//设置字符集
certAlipayRequest.setCharset(AliPayUtil.CHARSET);
//设置签名类型
certAlipayRequest.setSignType(AliPayUtil.SIGN_TYPE);
//设置应用公钥证书路径
certAlipayRequest.setCertPath(AliPayUtil.CERT_PATH);
//设置支付宝公钥证书路径
certAlipayRequest.setAlipayPublicCertPath(AliPayUtil.ALIPAY_PUBLIC_CER_PATH);
//设置支付宝根证书路径
certAlipayRequest.setRootCertPath(AliPayUtil.ROOT_CER_PATH);
//构造client
return new DefaultAlipayClient(certAlipayRequest);
}
三、生成支付宝订单
注意,这里返回orderStr需要返回前端、用于唤起支付宝APP
@SneakyThrows(value = AlipayApiException.class)
public String alipayRecharge(AliPayModel aliPayModel) {
//初始化配置信息
AlipayClient alipayClient = initAlipay();
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
String orderNumber = aliPayModel.getOrderNumber();
model.setBody(aliPayModel.getBody());
model.setSubject(aliPayModel.getSubject());
model.setOutTradeNo(orderNumber);
model.setTimeoutExpress(AliPayUtil.TIME_OUT_EXPRESS);
model.setTotalAmount(aliPayModel.getTotalAmount());
request.setBizModel(model);
//异步通知地址
request.setNotifyUrl(AliPayUtil.NOTIFY_URL);
try {
//业务处理
//dosomething
//这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
log.error("接口调用错误:{}",response.getBody());
throw new AlipayApiException("接口调用错误");
}
AliPayUtil.NOT_CLOSE_ORDER.put(orderNumber,aliPayModel);
return response.getBody(); //就是orderString 可以直接给客户端请求,无需再做处理。
}catch(AlipayApiException e) {
log.error("生成支付订单错误:",e);
return null;
}
}
三、回调方法
用户在支付宝支付成功之后会回调此方法,接口在步骤二设置。
public String aliPayNotify(HttpServletRequest request) {
// 将异步通知中收到的待验证所有参数都存放到map中
Map<String, String> params = AliPayUtil.convertRequestParamsToMapWith(request);
String paramsJson = JSON.toJSONString(params);
log.info("支付宝回调,{}", paramsJson);
try {
// 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCertCheckV1(params, AliPayUtil.ALIPAY_PUBLIC_CER_PATH,
AliPayUtil.CHARSET, AliPayUtil.SIGN_TYPE);
if (signVerified) {
// 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,校验失败返回failure
AliPayUtil.check(params);
String tradeStatus = params.get("trade_status");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
//通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功
if(tradeStatus.equals(AliPayUtil.TRADE_SUCCESS) || tradeStatus.equals(AliPayUtil.TRADE_FINISHED)){
//业务逻辑处理
//do something
}
return "success";
} else {
log.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
return "failure";
}
} catch (AlipayApiException e) {
log.error("支付宝回调签名认证失败,paramsJson:{},errorMsg:{}", paramsJson, e.getMessage());
return "failure";
}
}
注意:此处需返回给支付宝状态 success 或 failure
四、查询订单
@SneakyThrows(value = AlipayApiException.class)
public String getAlipayOutTradeNo(Long outTradeNo, Long tradeNo) {
//初始化支付宝配置
AlipayClient alipayClient = initAlipay();
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo.toString());
if(tradeNo != null){
model.setTradeNo(tradeNo.toString());
}
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.certificateExecute(request);
return response.getBody();
}
注意:查询订单时可用商品订单号(自己生成的订单号)或支付订单号(支付宝返回的订单号)进行查询,两个都有的情况下优先使用支付宝订单号~
五、退款
@SneakyThrows(value = AlipayApiException.class)
public Boolean refund(String outTradeNo,String refundAmount,String refundReason) {
AlipayClient alipayClient = initAlipay();
AlipayTradeRefundRequest alipayTradeCloseRequest =new AlipayTradeRefundRequest();
//请求参数集合对象,除了公共参数之外,所有参数都可通过此对象传递
AlipayTradeRefundModel alipayTradeRefundModel =new AlipayTradeRefundModel();
//退款的订单号,传入生成支付订单时的订单号即可
alipayTradeRefundModel.setOutTradeNo(outTradeNo);
//退款金额
alipayTradeRefundModel.setRefundAmount(refundAmount);
//退款的原因
alipayTradeRefundModel.setRefundReason(refundReason);
alipayTradeCloseRequest.setBizModel(alipayTradeRefundModel);
//退款的执行流程与支付不太一样,支付时成功之后,需要通知回调接口,而退款则不需要,只需判断响应 参数 refundResponse.getFundChange().equals("Y") 判断是否发生了资金变化, equals("Y")表示资金发生了变化,退款成功
AlipayTradeRefundResponse response = alipayClient.execute(alipayTradeCloseRequest);
return response.isSuccess() && response.getFundChange().equals("Y");
}
注意:退款需要传三个参数,商品订单号(自己生成的订单号)、退款原因和退款金额
六、使用到的工具类(仅供参考)
工具类包含支付宝初始化参数、订单号生成方法、解析支付宝消息方法、订单金额验证方法和签名验证方法
@Slf4j
public class AliPayUtil {
public static Map<String, AliPayModel> NOT_CLOSE_ORDER = new HashMap<>();
public static String TIME_OUT_EXPRESS = "30m";
public static String APP_ID = "";
public static String SERVER_URL = "";
public static String PRIVATE_KEY = "";
public static String NOTIFY_URL = "";
public static String CERT_PATH = "";
public static String ALIPAY_PUBLIC_CER_PATH = "";
public static String ROOT_CER_PATH = "";
public static String CHARSET = "utf-8";
public static String FORMAT = "json";
public static String SIGN_TYPE = "RSA2";
public static String TRADE_SUCCESS = "TRADE_SUCCESS";
public static String TRADE_FINISHED = "TRADE_FINISHED";
private static final Random random = new Random();
public static String generateOrderNumber(int userId, int productId) {
// 获取当前时间戳
long timestamp = System.currentTimeMillis();
// 生成6位随机数
int randomNum = random.nextInt(900000) + 100000;
// 组装订单号
return String.format("%d%d%d%d", timestamp, randomNum, userId, productId);
}
/**
* request 转 map
*
* @param request
* @return
*/
public static Map<String, String> convertRequestParamsToMapWith(HttpServletRequest request) {
Map<String, String> params = new HashMap<String, String>();
Map requestParams = request.getParameterMap();
for (Iterator 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;
}
/**
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
* 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
*
* @param params 支付宝回调参数
*/
@SneakyThrows(value = AlipayApiException.class)
public static void check(Map<String, String> params) {
//订单号
String outTradeNo = params.get("out_trade_no");
//商户订单实际金额
String totalAmount = params.get("total_amount");
//APPId
String appId = params.get("app_id");
//验证订单号
AliPayModel aliPayModel = NOT_CLOSE_ORDER.get(outTradeNo);
if (aliPayModel == null) {
throw new AlipayApiException("out_trade_no错误");
}
//验证订单实际金额
if (!totalAmount.equals(aliPayModel.getTotalAmount())) {
throw new AlipayApiException("支付金额不一致");
}
//验证app_id是否为商户本身
if (!appId.equals(AliPayUtil.APP_ID)) {
throw new AlipayApiException("app_id不一致");
}
}
/**
* 客户端同步验签
*
* @param request
* @return true:验证成功,false:验证失败
* @throws AlipayApiException
*/
public Boolean getVerifySignResult(HttpServletRequest request) throws AlipayApiException {
Map<String, String> params = AliPayUtil.convertRequestParamsToMapWith(request);
String paramsJson = JSON.toJSONString(params);
log.info("支付宝回调,{}", paramsJson);
return AlipaySignature.rsaCertCheckV1(params, AliPayUtil.ALIPAY_PUBLIC_CER_PATH,
AliPayUtil.CHARSET, AliPayUtil.SIGN_TYPE);
}
大功告成,文章中若有不对请指出,望见谅~