文章目录
前言
最近项目中需要对接支付模块,需要考虑到微信支付与支付宝支付的实现。
由于微信支付的还在审核中,先预研demo做一个支付宝支付码获取的实现。
官方文档以及说明
本次开发环境中编写demo实现,采用的是支付宝的沙箱
方式,后续上线会重新申请正式的appid等信息。
支付宝开发平台链接:https://open.alipay.com/
1、申请沙箱
进入支付宝的开发平台,用支付宝登录后,在控制台面板中找到沙箱
,并创建第一个沙箱。
https://open.alipay.com/develop/manage
2、进入沙箱获取对应的关键信息
对于像授权回调地址
、应用网关地址
这些暂时不设置。
3、拿到系统生成的公钥和密钥
这里会有应用公钥
、支付宝公钥
和应用私钥
。但这里通过个人实践,只需要支付宝公钥
和应用私钥
。
应用公钥
在某些特定的场景中并不会生效还会出现各种奇怪的报错。
注意事项
沙箱
并不是所有的操作都能够在其中进行开发操作,详情参考官方文档说明。
https://opendocs.alipay.com/common/097jyi?pathHash=9fcbe0d0&highlight_field=%E6%B2%99%E7%AE%B1%E7%8E%AF%E5%A2%83
创建springboot项目
1、引入依赖
选用的是springboot 2.x
的版本,并且引用的依赖在官方文档中有版本说明。
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.34.47.ALL</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.0.0</version>
</dependency>
参考官方文档依赖版本规定:
https://opendocs.alipay.com/common/02n6z6?pathHash=f5e2a056
其中,针对通用版
的版本要求,官方文档中有做说明:
https://opendocs.alipay.com/common/02kkv2?pathHash=358ff034
2、配置连接参数
在src/main/resources
中新建一个application.yml
文件,如果是nacos项目,可以在对应的配置中心中新增配置项。
# 支付宝支付
alipay:
# 应用ID,标识你的应用
appId:
# 应用私钥,用于签名请求
appPrivateKey:
# 应用公钥,用于验证支付宝的响应
#alipayPublicKey: 沙箱页面中的应用公钥
# 支付宝公钥(推荐使用这个)
alipayPublicKey: 支付宝公钥
# 支付通知的回调地址,支付宝会在支付完成后通知这个地址
notifyUrl: 支付成功后的回调地址,必须要求公网能够访问
# 页面跳转地址,支付完成后跳转到此URL
returnUrl: 场景一中登录成功后的重定向页面
# 签名类型,通常为RSA2
signType: RSA2
# 字符编码,通常为utf-8
charset: utf-8
# 支付宝网关地址,用于发送请求
gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
3、创建配置类,用于接收这些参数
package cn.alipay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 支付宝 支付参数配置类
*/
@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayProperties {
/**
* APPID
*/
private String appId;
/**
* 应用私钥,就是工具生成的应用私钥
*/
public String appPrivateKey;
/**
* 支付宝公钥,对应APPID下的支付宝公钥
*/
public String alipayPublicKey;
/**
* 支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息
*/
public String notifyUrl;
/**
* 同步通知,支付成功,一般跳转到成功页
*/
public String returnUrl;
/**
* 签名方式
*/
private String signType;
/**
* 字符编码格式
*/
private String charset;
/**
* 支付宝网关;https://openapi-sandbox.dl.alipaydev.com/gateway.do
*/
public String gatewayUrl;
/**
* 订单超时时间
*/
private String timeout = "5m";
}
新建配置类,获取对应的配置参数,并构建连接对象
package cn.alipay.config;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradePayModel;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.domain.BusinessParams;
import com.alipay.api.domain.ExtendParams;
import com.alipay.api.domain.GoodsDetail;
import com.alipay.api.domain.SettleDetailInfo;
import com.alipay.api.domain.SettleInfo;
import com.alipay.api.internal.util.WebUtils;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradePayRequest;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradePayResponse;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* 支付宝支付 模板
*/
@Slf4j
@Data
@Component
public class AliPayTemplate {
@Autowired
private AlipayProperties alipayProperties;
private AlipayClient alipayClient;
@PostConstruct
public void init() {
log.info("APPID:" + alipayProperties.getAppId());
log.info("应用私钥:" + alipayProperties.getAppPrivateKey());
log.info("支付宝公钥:" + alipayProperties.getAlipayPublicKey());
log.info("支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息:" + alipayProperties.getNotifyUrl());
log.info("同步通知,支付成功,一般跳转到成功页:" + alipayProperties.getReturnUrl());
log.info("签名方式:" + alipayProperties.getSignType());
log.info("字符编码格式:" + alipayProperties.getCharset());
log.info("订单超时时间:" + alipayProperties.getTimeout());
log.info("支付宝网关:" + alipayProperties.getGatewayUrl());
// 1. 根据支付宝的配置生成一个支付客户端
alipayClient = new DefaultAlipayClient(
alipayProperties.getGatewayUrl(),
alipayProperties.getAppId(),
alipayProperties.getAppPrivateKey(),
"json",
alipayProperties.getCharset(),
alipayProperties.getAlipayPublicKey(),
alipayProperties.getSignType()
);
}
}
4、中间类的定义(订单类)
创建一个订单类,主要用于一些基本信息的传参处理。
package cn.alipay.config;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 订单表
*/
@Data
public class Order implements Serializable {
/**
* 订单Id
*/
private Long id;
/**
* 用户Id
*/
private Long userId;
/**
* 接口Id
*/
private Long interfaceInfoId;
/**
* 支付金额
*/
private Double money;
/**
* 支付方式
*/
private String paymentMethod;
/**
* 0 - 未支付 1 - 已支付
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否删除
*/
private Integer isDelete;
private static final long serialVersionUID = 1L;
}
5、编写测试接口
场景一、pc端请求后端后,生成支付宝的特制页面(AlipayTradePagePayRequest)
在支付宝的PC端案例文档
中,有一个注意事项。
https://opendocs.alipay.com/open/00dn7o?pathHash=c1e36251
像这种传递订单信息至后台,后台调用支付宝的对应接口后,返回前端页面样式或者地址,前端再进行渲染实现。可以参照以下的方式。
定义接口
package cn.alipay.controller;
import cn.alipay.config.AliPayTemplate;
import cn.alipay.config.Order;
import com.alipay.api.AlipayApiException;
import com.alipay.easysdk.factory.Factory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* 阿里支付宝支付与回调接口
*/
@Slf4j
@RestController
@RequestMapping("/alipay")
public class AliPayController {
@Autowired
private AliPayTemplate aliPayTemplate;
/**
* 获取支付宝
* post 请求 生成 frame 页面
* get 请求生成 页面url
* @param id
* @return
* @throws AlipayApiException
*/
@GetMapping(value = "/pay", produces = MimeTypeUtils.TEXT_HTML_VALUE)
@ResponseBody
public String pay(@RequestParam long id) throws AlipayApiException {
// 创建订单对象并设置属性
Order order = createOrder(id);
// 调用支付宝支付模板进行支付
return aliPayTemplate.pay(order);
}
/**
* 伪造订单数据
* @param id
* @return
*/
private Order createOrder(long id) {
Order order = new Order();
order.setId(id);
order.setUserId(1111111L);
order.setInterfaceInfoId(294389472934L);
order.setMoney(1000.0);
order.setPaymentMethod("支付宝");
return order;
}
}
定义请求逻辑代码
package cn.alipay.config;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradePayModel;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.domain.BusinessParams;
import com.alipay.api.domain.ExtendParams;
import com.alipay.api.domain.GoodsDetail;
import com.alipay.api.domain.SettleDetailInfo;
import com.alipay.api.domain.SettleInfo;
import com.alipay.api.internal.util.WebUtils;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradePayRequest;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradePayResponse;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* 支付宝支付 模板
*/
@Slf4j
@Data
@Component
public class AliPayTemplate {
@Autowired
private AlipayProperties alipayProperties;
private AlipayClient alipayClient;
@PostConstruct
public void init() {
log.info("APPID:" + alipayProperties.getAppId());
log.info("应用私钥:" + alipayProperties.getAppPrivateKey());
log.info("支付宝公钥:" + alipayProperties.getAlipayPublicKey());
log.info("支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息:" + alipayProperties.getNotifyUrl());
log.info("同步通知,支付成功,一般跳转到成功页:" + alipayProperties.getReturnUrl());
log.info("签名方式:" + alipayProperties.getSignType());
log.info("字符编码格式:" + alipayProperties.getCharset());
log.info("订单超时时间:" + alipayProperties.getTimeout());
log.info("支付宝网关:" + alipayProperties.getGatewayUrl());
// 1. 根据支付宝的配置生成一个支付客户端
alipayClient = new DefaultAlipayClient(
alipayProperties.getGatewayUrl(),
alipayProperties.getAppId(),
alipayProperties.getAppPrivateKey(),
"json",
alipayProperties.getCharset(),
alipayProperties.getAlipayPublicKey(),
alipayProperties.getSignType()
);
}
/**
* 调用支付接口
* @param order
* @return
* @throws AlipayApiException
*/
public String pay(Order order) throws AlipayApiException {
//2、创建一个支付请求,并设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(alipayProperties.getReturnUrl());
alipayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
Long id = order.getId();
Long interfaceInfoId = order.getInterfaceInfoId();
Double money = order.getMoney();
String paymentMethod = order.getPaymentMethod();
// 设置业务内容,包含必要的支付参数
AlipayTradePagePayModel pagePayModel = new AlipayTradePagePayModel();
pagePayModel.setOutTradeNo(String.valueOf(id));
pagePayModel.setTotalAmount(String.valueOf(money));
pagePayModel.setSubject(String.valueOf(interfaceInfoId));
pagePayModel.setBody(paymentMethod);
pagePayModel.setTimeoutExpress(alipayProperties.getTimeout());
// 电脑支付场景下仅支持FAST_INSTANT_TRADE_PAY
pagePayModel.setProductCode("FAST_INSTANT_TRADE_PAY");
alipayRequest.setBizModel(pagePayModel);
// post 请求返回 frame 前端样式
AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest, "POST");
// get 请求返回一个页面地址url
AlipayTradePagePayResponse response2 = alipayClient.pageExecute(alipayRequest, "GET");
String result = response.getBody();
//返回支付宝响应的结果
return result;
}
}
启动服务,请求后的效果如下:
localhost:8080/alipay/pay?id=123456
post 请求生成的样式如下所示:
<form name="punchout_form" method="post" action="会有对应的信息">
<input type="hidden" name="biz_content" value="{"body":"支付宝","out_trade_no":"125477","product_code":"FAST_INSTANT_TRADE_PAY","subject":"294389472934","timeout_express":"5m","total_amount":"1000.0"}">
<input type="submit" value="立即支付" style="display:none" >
</form>
<script>document.forms[0].submit();</script>
如果是GET
请求,则只会是上面action
的地址。
场景二、前端请求后端,只返回对应的二维码地址(AlipayTradePrecreateRequest)
参照官方文档中的案例实现
https://opendocs.alipay.com/open/8ad49e4a_alipay.trade.precreate?scene=2ae8516856f24a5592194d237f3f235d&pathHash=d18bff53
编写对应的接口和实现类
@GetMapping(value = "/pay3")
@ResponseBody
public String pay3(@RequestParam long id) throws AlipayApiException {
// 创建订单对象并设置属性
Order order = createOrder(id);
// 调用支付宝支付模板进行支付
return aliPayTemplate.pay3(order);
}
package cn.alipay.config;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradePayModel;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.domain.BusinessParams;
import com.alipay.api.domain.ExtendParams;
import com.alipay.api.domain.GoodsDetail;
import com.alipay.api.domain.SettleDetailInfo;
import com.alipay.api.domain.SettleInfo;
import com.alipay.api.internal.util.WebUtils;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradePayRequest;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradePayResponse;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* 支付宝支付 模板
*/
@Slf4j
@Data
@Component
public class AliPayTemplate {
@Autowired
private AlipayProperties alipayProperties;
private AlipayClient alipayClient;
@PostConstruct
public void init() {
log.info("APPID:" + alipayProperties.getAppId());
log.info("应用私钥:" + alipayProperties.getAppPrivateKey());
log.info("支付宝公钥:" + alipayProperties.getAlipayPublicKey());
log.info("支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息:" + alipayProperties.getNotifyUrl());
log.info("同步通知,支付成功,一般跳转到成功页:" + alipayProperties.getReturnUrl());
log.info("签名方式:" + alipayProperties.getSignType());
log.info("字符编码格式:" + alipayProperties.getCharset());
log.info("订单超时时间:" + alipayProperties.getTimeout());
log.info("支付宝网关:" + alipayProperties.getGatewayUrl());
// 1. 根据支付宝的配置生成一个支付客户端
alipayClient = new DefaultAlipayClient(
alipayProperties.getGatewayUrl(),
alipayProperties.getAppId(),
alipayProperties.getAppPrivateKey(),
"json",
alipayProperties.getCharset(),
alipayProperties.getAlipayPublicKey(),
alipayProperties.getSignType()
);
}
public String pay3(Order order) throws AlipayApiException {
AlipayTradePrecreateRequest alipayRequest = new AlipayTradePrecreateRequest();
// 强制关闭 ssl 证书校验
WebUtils.setNeedCheckServerTrusted(false);
alipayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
JSONObject jsonObject = new JSONObject();
jsonObject.put("out_trade_no",order.getId());// 商户订单号
jsonObject.put("total_amount",order.getMoney());// 商品价格
jsonObject.put("subject","这只是个商品标题");// 商品标题
jsonObject.put("store_id","香蕉集团");//组织或公司名
// jsonObject.put("timeout_express","5m");// 订单有效时间
alipayRequest.setBizContent(jsonObject.toString());
AlipayTradePrecreateResponse response = alipayClient.execute(alipayRequest);
System.out.println("创建订单结果:"+response.getBody());
System.out.println("订单编号是"+response.getOutTradeNo());
String qrCode = response.getQrCode();
// AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
return qrCode;
}
}
请求后会得到一个支付的地址,使用ZXing
框架,将其转换为二维码。
场景三、沙箱APP扫码支付后回调处理
在每次请求AliPay
的接口时,都会传递一个回调接口
参数。
alipayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
这个参数主要是用于告知AliPay
在支付操作中,各项信息的回调信息,是个异步的操作。
针对回调,判断是否是支付成功,可以参照如下逻辑:
/**
* 支付宝 支付 异步回调接口 必须是 Post
* @param request
* @return
* @throws Exception
*/
@PostMapping("/notify")
public String payNotify(HttpServletRequest request) throws Exception {
// 检查交易状态是否为成功
if (!"TRADE_SUCCESS".equalsIgnoreCase(request.getParameter("trade_status"))) {
// 如果状态不是成功,则返回失败
return "failure";
}
log.info("=========支付宝异步回调========");
// .... 此处省略部分业务逻辑
aliPayServide.payNotify(request);
return "success";
}
业务层中的代码片段如下:
public void payNotify(HttpServletRequest request) throws AlipayApiException {
// 创建一个存储请求参数的Map
Map<String, String> params = getRequestParams(request);
String tradeNo = params.get("out_trade_no");
// 验证支付宝返回的签名
if (!verifySignature(params)) {
log.info("----验签失败----");
return;
}
// 记录交易详情
logTransactionDetails(params);
// 其他业务逻辑
}
/**
* 记录交易详情的方法
* @param params
*/
private void logTransactionDetails(Map<String, String> params) {
log.info("交易名称: " + params.get("subject"));
log.info("交易状态: " + params.get("trade_status"));
log.info("支付宝交易凭证号: " + params.get("trade_no"));
log.info("商户订单号: " + params.get("out_trade_no"));
log.info("交易金额: " + params.get("total_amount"));
log.info("买家在支付宝唯一id: " + params.get("buyer_id"));
log.info("买家付款时间: " + params.get("gmt_payment"));
log.info("买家付款金额: " + params.get("buyer_pay_amount"));
// 记录日志
ChatAliPayDetailLog chatAliPayDetailLog = ChatAliPayDetailLog.builder()
.id(IdUtil.getSnowflakeNextIdStr())
.subject(params.get("subject"))
.tradeStatus(params.get("trade_status"))
.tradeNo(params.get("trade_no"))
.outTradeNo(params.get("out_trade_no"))
.totalAmount(new BigDecimal(params.get("total_amount")))
.buyerId(params.get("buyer_id"))
.gmtPayment(DateUtil.parseDate(params.get("gmt_payment")))
.buyerPayAmount(new BigDecimal(params.get("buyer_pay_amount")))
.createdTime(new Date())
.build();
chatAliPayDetailLogMapper.insert(chatAliPayDetailLog);
}
/**
* 验证签名
* @param params
* @return
*/
private boolean verifySignature(Map<String, String> params) throws AlipayApiException {
String sign = params.get("sign");
String content = AlipaySignature.getSignCheckContentV1(params);
return AlipaySignature.rsa256CheckContent(content, sign, alipayProperties.getAlipayPublicKey(), "UTF-8");
}
/**
* 提取请求参数的方法
* @param request
* @return
*/
private Map<String, String> getRequestParams(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
params.put(name, request.getParameter(name));
}
return params;
}
但回调处理是一个异步
,如果前端界面中需要实时获取支付成功状态,可以使用轮训
的方式,定时去查询支付结果。
场景四、定时查询支付结果
在官方文档中,其中提供有一个alipay.trade.query(统一收单交易查询)
,专门提供查询操作。
https://opendocs.alipay.com/open/217dd591_alipay.trade.query?scene=common&pathHash=62ae74aa
代码片段如下所示:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(chatUserPayOrder.getOrderNo());
request.setBizModel(model);
try {
AlipayTradeQueryResponse response = alipayClient.execute(request);
if ("TRADE_SUCCESS".equalsIgnoreCase(response.getTradeStatus())) {
// 成功支付 则进行后续处理
insertLog(response);
// 根据 response.getOutTradeNo() 修改对应的支付信息
updatePayOrderStatus(response);
}
} catch (AlipayApiException e) {
throw new RuntimeException(e);
}
也可以考虑定时任务xxl-job
定时去获取所有未收到支付状态的数据,请求获取最终结果。或者超过限定时间表示失效等逻辑。