springboot项目对接支付宝支付
支付宝对接文档
一、准备工作
1. 首先要到 蚂蚁金服开发者中心 https://openhome.alipay.com/platform/home.htm 注册商家账户,并认证。
2.下载java版的sdk和demo sdk&demo https://docs.open.alipay.com/270/106291/ 下载地址
3.将sdk加入到项目中,在项目根路径下新建libs文件夹,将jar包复制进去,我使用的是maven,用maven打包并上传到公司的jar包管理。
4.利用RSARSA签名验签工具生成公钥、私钥并保存。生成公钥放到如图应用公钥的位置 https://docs.open.alipay.com/291/105971/ 。详细操作按照官网教程操作,很简单的,这里就不上图片了。
二、开发接口
1、因为开发环境是使用沙箱环境,上线后会使用真实环境,所以支付宝的一些参数我们放到配置文件里 pay-dev.properties appid和支付宝公钥上面图片中有,直接在网页上复制就好。
2、支付宝配置可以选择配置类也可以选择配置参数
#支付宝配置 #支付同步返回地址 ali_return_url = #支付异步通知地址 ali_notify_url = #产品码 product_no = FAST_INSTANT_TRADE_PAY #超时时间 time_express = 15m #支付网关 url = https://openapi.alipaydev.com/gateway.do #商户号 appid = 2016091500519530 #私钥 private_key = #公钥 ali_public_key = #加密方式 sign_type = RSA2
3、开始编写写接口
这里支付宝要用的一些参数,我是通过@Value自动注入进来的,官方给的demo是,定义个AlipayConfig类,然后全部定义成静态变量,根据个人喜好问题选择,官方的demo中有,可以直接复制,然后修改为你自己的参数即可
注意:并非配置文件中写全部参数,具体参数设置可以参开官网参数列表
数据库操作:根据自己的业务需求,我这里是支付提交创建现金表,每次交互要记录支付过程产生的日志信息,我这里还需要创建交易日志表。
Param:
public class AlipayParam implements Param { @ApiModelProperty(value = "金额", required = true) @NotNull(message = "金额不能为空}") private String amount; @ApiModelProperty(value = "订单名称", required = true) @NotNull(message = "订单名称 不能为空}") private String orderName; @ApiModelProperty(value = "订单号", required = true) @NotNull(message = "订单号 不能为空}") private String orderId; } public class AlipayOrderParam implements Param { private String out_trade_no;//商户订单号 private String product_code;//销售产品码 private String total_amount;//总金额 private String subject;//订单标题 private String timeout_express;//该笔订单允许的最晚付款时间,逾期将关闭交易 private String passback_params;//公共校验参数 }
Controller:
public class AlipayController { @Resource private AlipayService alipayService; //1.申请付款 @ApiOperation("申请付款") @RequestMapping(value = "/order", method = RequestMethod.POST) public String alipay(@Valid AlipayParam alipayParam, BindingResult result) { return alipayService.alipay(alipayParam); } //2.a1lipay支持同步返回地址 @ApiOperation("同步") @RequestMapping(value = "/getReturnUrlInfo",method = RequestMethod.GET) public String alipayReturnUrlInfo(HttpServletRequest request) { return alipayService.synchronous(request); } //3.alipay异步通知调用地址 @ApiOperation("异步通知") @RequestMapping(value = "/getNotifyUrlInfo",method = RequestMethod.POST) public void alipayNotifyUrlInfo(HttpServletRequest request,HttpServletResponse response){ alipayService.notify(request,response); } }
Service:
/** * 付款 * @param alipayParam 付款参数 * @return 付款返回值 */ String alipay(AlipayParam alipayParam); /** * 付款同步返回地址 * @param request * @return */ String synchronous(HttpServletRequest request); /** * 付款异步通知调用地址 * @param request 新增参数 * @return 新增返回值 */ void notify(HttpServletRequest request,HttpServletResponse response);
ServiceImpl:
public class AlipayServiceImpl implements AlipayService { @Value("${ali_return_url}") private String ali_return_url; @Value("${ali_notify_url}") private String ali_notify_url; @Value("${product_no}") private String product_no; @Value("${time_express}") private String time_express; @Value("${url}") private String url; @Value("${appid}") private String appid; @Value("${private_key}") private String private_key; @Value("${ali_public_key}") private String ali_public_key; @Value("${sign_type}") private String sign_type; @Resource private CashLogMapper cashLogMapper; @Resource private CashMapper cashMapper; @Resource private OrderMapper orderMapper; public static final String TRADE_SUCCESS = "TRADE_SUCCESS"; //支付成功标识 public static final String TRADE_CLOSED = "TRADE_CLOSED";//交易关闭 @Override public String alipay(AlipayParam param) { // 从 session 中获取用户 UserData userData = UserContext.getUserData(); if (userData == null || userData.getUserId() == null) { return WebUtils.buildPage("登录失效, 请重新登录"); } if (!BusinessTypeConstant.USER_CATEGORY_SELLER.equals(userData.getUserCategory())) { log.info("卖家用户才可以进行支付"); return WebUtils.buildPage("卖家用户才可以进行支付"); } // 订单已支付 Cash cash = cashMapper.getCashByOrderId(param.getOrderId()); if (cash != null && "3".equals(cash.getStatus())) { log.info("订单已支付"); return WebUtils.buildPage("订单已支付"); } String merchantOrderNo; Boolean isRetry = false; if (cash != null && new Date(System.currentTimeMillis() - 7800000).before(cash.getCreateTime())) { merchantOrderNo = cash.getMerchantOrderNo(); isRetry = true; } else { merchantOrderNo = param.getOrderId() + new SimpleDateFormat("HHmmss").format(new Date()); } String urlEncodeOrderNum = ""; try { urlEncodeOrderNum = URLEncoder.encode(merchantOrderNo, "UTF-8");//公告参数订单号 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //向支付宝发送支付请求 AlipayClient alipayClient = new DefaultAlipayClient(url, appid , private_key, "json", "UTF-8" , ali_public_key, sign_type); //创建Alipay支付请求对象 AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setReturnUrl(ali_return_url); //同步通知url request.setNotifyUrl(ali_notify_url);//异步通知url AlipayOrderParam alipayOrderParam = new AlipayOrderParam(); alipayOrderParam.setOut_trade_no(merchantOrderNo);//唯一标识 alipayOrderParam.setProduct_code(product_no); alipayOrderParam.setSubject(param.getOrderName()); alipayOrderParam.setTotal_amount(param.getAmount()); alipayOrderParam.setTimeout_express(time_express); alipayOrderParam.setPassback_params(urlEncodeOrderNum); request.setBizContent(JSON.toJSONString(alipayOrderParam));//设置参数 String webForm = "";//输出页面的表单 try { webForm = alipayClient.pageExecute(request).getBody(); //调用SDK生成表单 } catch (Exception e) { log.info("支付请求发送失败"); return WebUtils.buildPage("支付请求发送失败,请联系我们客服协助处理"); } // 记录下发起付款 if (isRetry) { cashLogMapper.add(merchantOrderNo, "RETRY", JSON.toJSONString(alipayOrderParam), new Date()); } else { cashLogMapper.add(merchantOrderNo, "ADD", JSON.toJSONString(alipayOrderParam), new Date()); } // 创建一条支付状态记录 cashMapper.add(param.getOrderId(), merchantOrderNo, new Date()); return webForm; } @Override public String synchronous(HttpServletRequest request) { Map<String, String> parameters = new HashMap<>(); Map<String, String[]> requestParams = request.getParameterMap(); log.info("支付宝同步参数", requestParams); for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { String key = entry.getKey(); String[] values = entry.getValue(); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } parameters.put(key, valueStr); } //记录日志 String merchantOrderNo = request.getParameter("out_trade_no");//商户订单号 cashLogMapper.add(request.getParameter("out_trade_no"), "SYNCHRONOUS", JSON.toJSONString(parameters), new Date()); return "<html>\n" + "<head>\n" + "<script type=\"text/javascript\"> function load() { window.close(); } </script>\n" + "</head>\n" + "<body onload=\"" + "load()\"> </body>\n" + "</html>"; } @Override public void notify(HttpServletRequest request, HttpServletResponse response) { //接收参数进行校验 Map<String, String> parameters = new HashMap<>(); Map<String, String[]> requestParams = request.getParameterMap(); for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { String key = entry.getKey(); String[] values = entry.getValue(); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } parameters.put(key, valueStr); } log.info("parameters is [parameters={}]", parameters); String appId = request.getParameter("app_id");//appid String merchantOrderNo = request.getParameter("out_trade_no");//商户订单号 String orderId = cashMapper.getCashByMerchantOrderNo(merchantOrderNo).getOrderId(); if (orderId == null) { log.error("orderId is null"); ReturnData.fail(ReturnCode.SERVER_EXCEPTION); } log.info("orderId: {}", orderId); String payState = request.getParameter("trade_status");//交易状态 String encodeOrderNum = null; cashLogMapper.add(request.getParameter("out_trade_no"), "NOTIFY", JSON.toJSONString(parameters), new Date()); try { encodeOrderNum = URLDecoder.decode(request.getParameter("passback_params"), "UTF-8"); log.info("encodeOrderNum is [encodeOrderNum={}]", encodeOrderNum); boolean signVerified; signVerified = AlipaySignature.rsaCheckV1(parameters, ali_public_key, "UTF-8", sign_type);//验证签名 log.info("signVerified is [signVerified={}]", signVerified); if (signVerified) { //通过验证 log.info("payState: {}", payState); if (payState.equals(TRADE_SUCCESS)) { //判断订单号与插入的订单号是否一样 if (merchantOrderNo.equals(encodeOrderNum) == false || appid.equals(appId) == false) { log.info("vali failure"); cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } cashMapper.update(merchantOrderNo, 3); orderMapper.afterPay(orderId ); response.getOutputStream().print("success"); return; } else if (payState.equals(TRADE_CLOSED)) { //交易关闭 cashMapper.update(merchantOrderNo, 7); } else { cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } } else { //签名校验失败更状态 cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } log.info("encodeOrderNum is [encodeOrderNum={}]", encodeOrderNum); cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } catch (AlipayApiException e) { log.error(e.getErrMsg()); throw new RuntimeException("调用支付宝接口发生异常"); } catch (UnsupportedEncodingException e) { log.error(e.getMessage()); throw new RuntimeException("URLDecoderf发生异常"); } catch (IOException e) { log.error(e.getMessage()); throw new RuntimeException("IO发生异常"); } } }
总结:与支付宝对接,本地生成订单进行提交到支付宝同步处理完支付,支付宝会通过异步来获取用户给支付宝反馈的信息会最终确定是否支付成功,在开发过程中注意支付确认(异步)的逻辑处理即可,具体返回参数可以参开
4、进行沙箱环境的测试
访问接口地址 项目地址/alipay/pay 我的是 127.0.0.1/alipay/pay/order
如果后台没有报错的话,他会自动重定向到,支付宝的付款页面,如下图所示。这时候我们下载安装沙箱版的app,然后使用官方提供的账户扫描然后直接付款,付款成功后会回调后面那两个接口,在通知的那个接口里处理你的业务逻辑。查看沙箱app的登录帐户名和密码.
总结:沙箱环境每个人都可以开通支付账户并可以查看相应的买家和商家的账号,商家用户需要公司走相关流程大致一周多的时间。