本文最后更新于 2023年9月17日 上午
作者:Mintimate
博客:https://www.mintimate.cn
Mintimate’s Blog,只为与你分享
前言
如何在前端Vue,后端Springboot情况下,适配支付宝沙箱呢?使用沙箱模拟支付宝支付环境,若要切换为正式的支付宝环境,所要修改的代码极少嗷。
本文参考:
准备工作
首先需要注册支付宝开发平台的账号,注册地址:https://open.alipay.com/
之后进入开发者空间:https://open.alipay.com/dev/workspace,找到沙箱功能:
这里我们可以获取:APPID
,并可以设置RSA2密钥
。
接口加签方式
就可以了,一定要先设置,英文支付宝上可能有几个小时的更新延时,在延时期间我们测试,即使密钥准确也会报错……
设置方法官方写的太详细了,可以参考:https://opendocs.alipay.com/common/02kipl
最后需要注意,公钥是要支付宝公钥:
到此,我们前期准备完成,准备了:
APPID
:应用信息的APPID。
私钥
:我们生成的RSA2密钥的私钥。
支付宝公钥
:我们提供公钥后,支付宝返还的支付宝公钥。
上述三个参数,后续都会用到。
Maven引用SDK
根据支付宝开发者文档,我们需要引用Java的SDK:
1 2 3 4 5 6
| <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.22.37.ALL</version> </dependency>
|
Springboot配置
支付宝配置
这里实现配置类的方法很多,你可以编写配置类,用IO容器注入;我这里使用的是简单的编写个constantc
常量类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class AlipayConfig { public static String app_id = "202****"; public static String merchant_private_key = "MIIE******"; public static String alipay_public_key = "MIIBIj*****";
public static String notify_url = "https://****/notifyUrl"; */ public static String return_url = "http://*****/alipay/returnUrl"; public static String sign_type = "RSA2"; public static String charset = "utf-8"; public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do"; }
|
其中:
notify_url
:用户支付成功后,发送成功的通知消息消息地址(一般返回给后端)。
return_url
:用户支付成功后,重定向的地址。
Controller层
Controller
接受前端请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
@RestController public class PayController { @Resource private PayService payService;
@RequestMapping("/alipay/pay") @ResponseBody public Result payByAlipay(@RequestBody AlipayOrderVO alipayOrderVO) { String out_trade_no = UuidUtil.getTimeBasedUuid().toString(); alipayOrderVO.setOut_trade_no(out_trade_no); String redirect=payService.saveOrderInfo(alipayOrderVO); if (redirect!=null){ return Result.ok(redirect); } else { return Result.fail("支付参数错误"); } }
@RequestMapping("/alipay/notifyUrl") public void notifyUrl(HttpServletRequest request, HttpServletResponse response) { String out_trade_no=null; double price = 0; Map<String, String[]> parameterMap = request.getParameterMap(); for (String s : parameterMap.keySet()) { String[] strings = parameterMap.get(s); for (int i = 0; i < strings.length; i++) { if (s.equals("out_trade_no")){ out_trade_no=strings[i]; } if (s.equals("total_amount")){ price= Double.parseDouble(strings[i]); } } } payService.finishPayment(out_trade_no,price); }
@RequestMapping("/alipay/returnUrl") public String returnUrl(HttpServletRequest request, HttpServletResponse response) { return "<script>" + "window.opener = null;" + "window.open(\"\", \"_self\");" + "window.close();" + "</script>"; }
}
|
其中:
Result
为我的结果分装类,如果你没自己的结果封装,可以使用String类型返回给前台
AlipayOrderVO
为前端传入的结算信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class AlipayOrderVO {
private String out_trade_no;
private String subject;
private double total_amount;
private String product_code="FAST_INSTANT_TRADE_PAY";
}
|
接下来就看业务服务层了。
Service层
Service我就不展示过多代码了,首先是上文提到的saveOrderInfo
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public String saveOrderInfo(AlipayOrderVO alipayOrderVO) { if (Objects.isNull(alipayOrderVO)){ throw new dataException("订单信息不可为空"); } redisUtil.hset(IMG2D_ALIPAY_ORDER,alipayOrderVO.getOut_trade_no(),request.getAttribute("userID")); AlipayClient alipayClient = new DefaultAlipayClient( AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type ); AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest(); alipayRequest.setReturnUrl(AlipayConfig.return_url); alipayRequest.setNotifyUrl(AlipayConfig.notify_url); try { alipayRequest.setBizContent(JSON.toJSONString(alipayOrderVO)); String result = alipayClient.pageExecute(alipayRequest, "GET").getBody(); return result; } catch (Exception e) { e.printStackTrace(); return null; } }
|
这里是根据订单信息,到支付宝处生成交易直链并访问给Controller控制器,由控制器分装成结果对象给前端Vue。
同时,订单号和用户id绑定,存入Redis缓存,等待支付宝notify
回调确认。
而Service层的这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void finishPayment(String out_trade_no, double total_amount) { String userid= String.valueOf(redisUtil.hget(IMG2D_ALIPAY_ORDER,out_trade_no)); User user=userDao.selectById(userid); if (Objects.isNull(user)){ throw new dataException("订单获取用户失败!!"); } UserBalance userBalance= userBalanceDao.selectOne(new LambdaQueryWrapper<UserBalance>() .eq(UserBalance::getUserId,userid)); ...... }
|
这样,我们的支付宝沙线业务的后端就搭建好了。在写前端前,我们使用PAW
进行测试,你也可以使用Postman等工具进行测试。
PAW接口测试
接下来我们使用PAW进行接口测试:
把回调复制到浏览器内,无痕浏览:
同时,Redis内也有订单记录:
之后,就可以使用沙箱版本支付宝付款了。需要注意,支付成功后,会发生请求到你填写的notify
内,并重定向到你填写的returnUrl
内。
Vue内配置
Vue内配置就简单了,简单地说,前端封装商品名称和商品订单金额给后端并解析后端发回的对象(也就是支付宝付款页面)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| handlerToken(newValue){ this.showChargeToken=false successTips("请在新窗口内完成支付宝的支付(*≧ω≦)") post("/img2d/alipay/pay",newValue).then(({data})=>{ if(data.flag){ window.open(data.data) } }) this.$buefy.dialog.confirm({ message: '已经完成支付?现在获取最新数据?', cancelText:'并没有', confirmText:'已支付', onConfirm: () => { this.$buefy.toast.open('正在获取最新数据') this.getLatestToken() } }) }
|
其中的newValue
对象为:
1 2 3 4
| userBalance:{ historyBalance:"--", balance:"--" },
|
前端UI代码就不方便展示了,我觉得展示这样,应该都知道怎么去适配了。
最后看看效果:
选择“钞能力”后,可以选择支付金额:
提交订单,会在新窗口内打开支付宝交易页面,页面保留一个支付完成确认:
如果用户选择已完成,前端拉取数据库/Redis内最新数据。
END
以上就是支付宝沙箱适配的全部流程了。可能会随着支付宝沙箱版本升级而有所不同,需要依照自己情况不同做出改变了。
还有就是,支付宝密钥一定要最开始就部署,因为支付宝的更新可能有网络延时。测试沙箱环境,要开启浏览器的无痕模式哦,否则浏览器可能判断沙箱网站为盗版网站。