Springboot 对接支付宝实现扫码支付功能
目录
- 前言
- 官方文档以及说明
- 1、申请沙箱
- 2、进入沙箱获取对应的关键信息
- 注意事项
- 创建springboot项目
- 1、引入依赖
- 2、配置连接参数
- 3、创建配置类,用于接收这些参数
- 4、中间类的定义(订单类)
- 5、编写测试接口
前言
最近项目中需要对接支付模块,需要考虑到微信支付与支付宝支付的实现。
由于微信支付的还在审核中,先预研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; /** * 支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息www.devze.com */ 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="立即支付" > </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.AlipayTradeP编程客栈agePayModel; importjs 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; impo编程rt 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. 根据支付宝的配置生成一个支付客户端 alipayClienandroidt = 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
定时去获取所有未收到支付状态的数据,请求获取最终结果。或者超过限定时间表示失效等逻辑。
到此这篇关于Springboot 对接支付宝实现扫码支付的文章就介绍到这了,更多相关Springboot 扫码支付内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论