引言
由于在毕业设计中有缴费功能,所以想到集成支付宝的沙箱支付,但以前没有涉及过相关功能。
经过百度查阅相关资料,整理相关集成资料如下
流程概述
本流程为集成支付宝沙箱电脑网站支付流程
具体流程如下:
- 商家系统调用 alipay.trade.page.pay(统一收单下单并支付页面接口)向支付宝发起支付请求,支付宝对商家请求参数进行校验,而后重新定向至用户登录页面。
- 用户确认支付后,支付宝通过 get 请求 returnUrl(商户入参传入),返回同步返回参数。
- 交易成功后,支付宝通过 post 请求 notifyUrl(商户入参传入),返回异步通知参数。
- 若由于网络等原因,导致商家系统没有收到异步通知,商家可自行调用 alipay.trade.query(统一收单线下交易查询接口)查询交易以及支付信息(商家也可以直接调用该查询接口,不需要依赖异步通知)。
注意:
- 由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。
- 商家系统接收到异步通知以后,必须通过验签(验证通知中的 sign 参数)来确保支付通知是由支付宝发送的。详细验签规则可查看 异步通知验签。
- 接收到异步通知并验签通过后,请务必核对通知中的 app_id、out_trade_no、total_amount 等参数值是否与请求中的一致,并根据 trade_status 进行后续业务处理。
- 在支付宝端,partnerId 与 out_trade_no 唯一对应一笔单据,商家端保证不同次支付 out_trade_no 不可重复;若重复,支付宝会关联到原单据,基本信息一致的情况下会以原单据为准进行支付。
前期准备
支付宝开发平台中心
支付宝开放中心:https://openhome.alipay.com/
前期准备就是在支付宝开放中心创建沙箱应用
-
访问支付宝开放中心,登录账号后点击控制台
-
进入控制台后,将页面往下滑,会看见
沙箱
-
点进沙箱后就是如下界面,可以看到appid
-
然后我们需要设置接口加签方式,这里建议选择
自定义密钥
中的公钥模式
点击设置并查看时,在界面中会出现相关文档链接
-
设置应用网关地址
用于接收支付宝沙箱异步通知消息(例如 From蚂蚁消息等),需要传入http(s)公网可访问的网页地址。选填,若不设置,则无法接收相应的异步通知消息。
如果你要接收用户支付完成后的信息,那么这个url是必须要设置的
**注意:**设置的url地址一定要是外网可以访问的,如果是在本机进行测试,那么本机相关端口需要进行
内网穿透
处理,否则支付宝无法访问该url就比如我本地的8888端口经过
内网穿透
处理后的异步通知url就如下所示: -
设置授权回调地址
外部通过访问该网关来将请求分发给对应的沙箱内部业务系统
授权回调地址是用于第三方登录的,如果你不需要使用到支付宝用户登录功能,则可以不用设置
-
再往下划可以看见开发相关产品的文档
-
然后左侧点击沙箱账号,可以看见支付宝沙箱提供的商家账号和卖家账号
-
然后我们在沙箱工具中去下载
支付宝客户端沙箱版
(仅安卓)然后在支付宝沙箱版中去登录买家账号,到时候测试网站支付即可
内网穿透
内网穿透我使用的是
NATAPP
,主要功能就是让本地端口通过一个代理url可以让外网进行访问**在这里的作用就是:**使用内网穿透工具,代理本机后端端口,然后支付宝的异步通知才能发送到对应接口上,否则支付宝访问不到我们本地的接口
如下是我电脑的8888端口进行内网穿透,那么我使用http://4get9t.natappfree.cc
来替代这个端口的访问即可
代码示例
本代码示例为电脑网站支付示例
有疑问的位置可以查阅官方文档:https://opendocs.alipay.com/open/repo-0038oa
集成支付
-
引入依赖
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.34.0.ALL</version> </dependency>
-
编写application.yml
- gatewayUrl:支付宝网关,沙箱环境网关与生产环境网关不一致,下面的示例是沙箱环境的
- appId:在支付宝开放平台创建应用后,每个应用都有自己的appid
- appPrivateKey:应用私钥,这个需要填自己的
- format:报文格式,这个不用改
- charset:字符串编码格式,这个不用改
- signType:签名方式,这个一般不用改
- alipayPublicKey:支付宝公钥,这个需要填自己的
- returnUrl:支付完成后同步跳转的路径,这个需要填自己的,我填的是我前端页面路径
- notifyUrl:异步通知url,用来处理支付完成后业务,注意,这个路径在本地测试的话需要进行
内网穿透
,否则支付宝无法访问该路径
alipay: # 支付宝网关 gatewayUrl: https://openapi.alipaydev.com/gateway.do # appid appId: # 应用私钥 appPrivateKey: # 报文格式 format: JSON # 字符串编码格式 charset: utf-8 # 签名方式 signType: RSA2 # 支付宝公钥 alipayPublicKey: # 支付完成后同步跳转的路径(一般写支付完成后想要跳转的路径) returnUrl: http://localhost:9999/#/personal/illegalInfo # 异步通知url notifyUrl: http://4get9t.natappfree.cc/aliPay/notify
-
在config包下新建AliPayProperties
package com.lzj.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * <p> * * </p> * * @author:雷子杰 * @date:2023/2/15 */ @Data @Component @ConfigurationProperties(prefix = "alipay") public class AliPayProperties { /** * 支付宝网关 */ private String gatewayUrl; /** * 支付宝应用的appid */ private String appId; /** * 应用私钥 */ private String appPrivateKey; /** * 报文格式 */ private String format; /** * 字符串编码格式 */ private String charset; /** * 签名方式 */ private String signType; /** * 支付宝公钥 */ private String alipayPublicKey; /** * 支付完成后同步跳转的路径(一般写支付完成后想要跳转的路径) */ private String returnUrl; /** * 异步通知url */ private String notifyUrl; }
-
在entity包下新建AlipayOrder
package com.lzj.entity; import io.swagger.annotations.ApiModel; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * <p> * * </p> * * @author:雷子杰 * @date:2023/2/15 */ @Data @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) @ApiModel("支付订单类") public class AlipayOrder { /** * 用户订单号,必填 */ private String out_trade_no; /** * 订单名称,必填 */ private String subject; /** * 订单总金额,必填 */ private String total_amount; /** * 销售产品码,必填,注:目前电脑支付场景下仅支持FAST_INSTANT_TRADE_PAY,所以可以写死 */ private final String product_code = "FAST_INSTANT_TRADE_PAY"; }
-
在service包下新建一个AliPayService
package com.lzj.service; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayResponse; import com.alipay.api.response.AlipayTradePagePayResponse; import com.lzj.entity.AlipayOrder; import javax.servlet.http.HttpServletRequest; /** * <p> * * </p> * * @author:雷子杰 * @date:2023/2/15 */ public interface AliPayService { AlipayResponse pay(AlipayOrder order) throws AlipayApiException; /** * 异步通知签名验证 * @param request * @return */ boolean notifySignVerified(HttpServletRequest request); }
-
新建AliPayServiceImpl
package com.lzj.service.impl; import com.alibaba.fastjson.JSON; import com.alipay.api.*; import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.AlipayOpenPublicTemplateMessageIndustryModifyRequest; import com.alipay.api.request.AlipayTradePagePayRequest; import com.alipay.api.response.AlipayOpenPublicTemplateMessageIndustryModifyResponse; import com.alipay.api.response.AlipayTradePagePayResponse; import com.lzj.config.AliPayProperties; import com.lzj.entity.AlipayOrder; import com.lzj.service.AliPayService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * <p> * * </p> * * @author:雷子杰 * @date:2023/2/15 */ @Service("aliPayService") @Transactional @Slf4j public class AliPayServiceImpl implements AliPayService { @Autowired private AliPayProperties aliPayProperties; @Override public AlipayResponse pay(AlipayOrder order) throws AlipayApiException { //初始化AlipayConfig AlipayConfig alipayConfig = new AlipayConfig(); alipayConfig.setServerUrl(aliPayProperties.getGatewayUrl()); alipayConfig.setAppId(aliPayProperties.getAppId()); alipayConfig.setSignType(aliPayProperties.getSignType()); alipayConfig.setAlipayPublicKey(aliPayProperties.getAlipayPublicKey()); alipayConfig.setPrivateKey(aliPayProperties.getAppPrivateKey()); alipayConfig.setFormat(aliPayProperties.getFormat()); alipayConfig.setCharset(aliPayProperties.getCharset()); //实例化客户端 AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig); //设置请求参数 //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称 AlipayTradePagePayRequest request = new AlipayTradePagePayRequest (); //设置同步返回url request.setReturnUrl(aliPayProperties.getReturnUrl()); //设置异步通知url request.setNotifyUrl(aliPayProperties.getNotifyUrl()); log.debug("JSON.toJSONString(order):" + JSON.toJSONString(order)); //转换为json字符串后传入 request.setBizContent(JSON.toJSONString(order)); AlipayTradePagePayResponse response = alipayClient.pageExecute(request); //调用成功,则处理业务逻辑 return response; } @Override public boolean notifySignVerified(HttpServletRequest request) { log.debug("进入了异步通知"); 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] + ","; } params.put(name, valueStr); } //调用SDK验证签名 System.out.println(params.toString()); System.out.println(aliPayProperties.toString()); boolean signVerified = false; try { signVerified = AlipaySignature.rsaCheckV1(params, aliPayProperties.getAlipayPublicKey(), aliPayProperties.getCharset(), aliPayProperties.getSignType()); } catch (AlipayApiException e) { e.printStackTrace(); } System.out.println("SDK验证签名结果1:" + signVerified); return signVerified; } }
-
新建AliPayController
/notify
接口是用来接收支付宝支付成功后返回的异步通知/** * <p> * * </p> * * @author:雷子杰 * @date:2023/2/16 */ @RestController @RequestMapping("aliPay") @Slf4j @Api(tags = "支付宝支付模块") public class AliPayController { @Autowired private AliPayService aliPayService; @Autowired private OrderService orderService; @Autowired private IllegalInfoService illegalInfoService; @Autowired private RedisClient redisClient; @GetMapping("/return") public JsonResult<Void> getReturnMsg() { return JsonResult.ok(); } @PostMapping("/notify") public JsonResult<Void> getNotifyMsg(HttpServletRequest request) throws UnsupportedEncodingException { boolean signVerified = aliPayService.notifySignVerified(request); if (signVerified) { // 商户订单号 String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8"); // 支付宝交易号 String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8"); // 交易状态 String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8"); if (trade_status.equals("TRADE_FINISHED")) { // 判断该笔订单是否在商户网站中已经做过处理 // 如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 // 如果有做过处理,不执行商户的业务程序 log.debug("成功:TRADE_FINISHED"); // 注意: // 退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知 return JsonResult.ok(); } else if (trade_status.equals("TRADE_SUCCESS")) { // 判断该笔订单是否在商户网站中已经做过处理 // 如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 // 如果有做过处理,不执行商户的业务程序 log.debug("成功:TRADE_SUCCESS"); // 注意: // 付款完成后,支付宝系统发送该交易状态通知 return JsonResult.ok(); } else { //验证失败 // 调试用,写文本函数记录程序运行情况是否正常 return JsonResult.fail(StatusCodeEnum.ALI_PAY_ERROR.getCode(), StatusCodeEnum.ALI_PAY_ERROR.getDesc()); } } return JsonResult.ok(); } }
业务调用
-
前端页面请求相关接口
-
接口中调用AliPayService的
pay()
方法返回alipayResponse,根据alipayResponse的isSuccess()
方法来判断是否请求成功,然后来处理业务逻辑,然后返回alipayResponse的getBody()
方法示例:
-
接口返回alipayResponse的
getBody()
方法返回的参数(是一个表单字符串) -
前端页面接收到返回的字符串后对其进行操作,新开窗口跳转
**注意:**新开窗口跳转后原页面会出现空白是正常情况
payIllegalInfoMoney(row){ payIllegalInfoMoney({ illegalInfoId: row.illegalInfoId, illegalActCode: row.illegalActCode, amount: row.amount, userId: this.$store.getters.userInfo.userId }).then(res =>{ //下面的操作不可删减,删减了后可能因为缓存导致第二次无法跳转窗口的问题 const divForm = document.getElementsByTagName("div"); if (divForm.length) { document.body.removeChild(divForm[0]); } const div = document.createElement("div"); div.innerHTML = res.data; // data就是接口返回的form 表单字符串 document.body.appendChild(div); document.forms[0].setAttribute("target", "_blank"); // 新开窗口跳转 document.forms[0].submit(); }) }
-
跳转后的界面如下
**注意:**周日中午12点-周一中午12点支付宝沙箱版可能进行维护,这个时候访问的话页面可能会报502异常,如果出现这个问题了我们等第二天再进行测试就可以了
-
然后使用沙箱版支付宝扫码或者直接网站登录付款就可以了
-
扫码支付完成后,如果你在后端写明了
returnUrl
的,那么该页面会跳转至该链接,同时扫码支付完成后支付宝会向你设置的异步通知url发送相关请求 -
在接收支付宝异步通知的接口中,我们可以进行支付完成后的一个业务处理,示例如下
总结
只是做一个简单的电脑网站支付宝沙箱支付并不困难,基本上就是调一调api就可以实现。
希望以后在工作中能够遇到相关知识让我在实践中学习