# 支付宝支付

支付宝开放平台官网地址 (opens new window)

支付宝能力中心 (opens new window)

电脑网站支付能力 (opens new window)

我们使用的就是 电脑网站支付能力,它的 快速接入文档 (opens new window),时序图如下

image-20210217184059543

可以看得见,和微信支付是类似的:

  1. 在支付宝发起支付请求:预下单
  2. 确认支付
  3. 异步通知支付支付状态

# 前端代码分析

// 支付宝支付直接跳转
window.location.href = "alipay.html?orderId=" + orderId + "&amount="+this.totalAmount;
window.open("alipayTempTransit.html?orderId=" + orderId);
1
2
3

可以看到,先改变当前浏览器页面到 alipay.html 页面,然后再打开一个窗口 alipayTempTransit.html 页面。

这里支付,还是基于前面用户在购物车结算订单,也就是在我们系统创建完订单之后,发起的操作。

  • alipay.html:是一个中转页,等待支付成功,在这个页面会去轮询后端请求订单的支付状态

    image-20210217185142241

  • alipayTempTransit.html 是一个真正发起支付的表单页面

      // 获得支付宝构建的支付提交form
      getAliPayForm (orderId) {
        var userInfo = this.userInfo
        // 往支付中心发起请求
        var paymentServerUrl = app.paymentServerUrl
        axios.defaults.withCredentials = true
        axios.post(
          paymentServerUrl + '/payment/goAlipay?merchantUserId=' + userInfo.id + '&merchantOrderId=' + orderId,
          {},
          {
            headers: {
              'imoocUserId': '123',
              'password': '123'
            }
          })
          .then(res => {
            if (res.data.status == 200) {
              // 返回了一个 form 表单
              var alipayForm = res.data.data
              console.log(alipayForm)
              // this.alipayForm = alipayForm;

              document.write(alipayForm)
            } else {
              alert(res.data.msg)
            }
          })
      },
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

# 支付中心支付宝预下单代码

/**
	 *
	 * @Description: 前往支付宝进行支付
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@RequestMapping(value="/goAlipay")
	public IMOOCJSONResult goAlipay(String merchantOrderId, String merchantUserId) throws Exception{

		// 查询订单详情
		Orders waitPayOrder = paymentOrderService.queryOrderByStatus(merchantUserId, merchantOrderId, PaymentStatus.WAIT_PAY.type);

		//获得初始化的AlipayClient
		AlipayClient alipayClient = new DefaultAlipayClient(aliPayResource.getGatewayUrl(),
															aliPayResource.getAppId(),
															aliPayResource.getMerchantPrivateKey(),
															"json",
															aliPayResource.getCharset(),
															aliPayResource.getAlipayPublicKey(),
															aliPayResource.getSignType());

		//设置请求参数
		AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
		alipayRequest.setReturnUrl(aliPayResource.getReturnUrl());
		alipayRequest.setNotifyUrl(aliPayResource.getNotifyUrl());

		// 商户订单号, 商户网站订单系统中唯一订单号, 必填
		String out_trade_no = merchantOrderId;
		// 付款金额, 必填 单位元
		String total_amount = CurrencyUtils.getFen2YuanWithPoint(waitPayOrder.getAmount());
//    	String total_amount = "0.01";	// 测试用 1分钱
		// 订单名称, 必填
		String subject = "天天吃货-付款用户[" + merchantUserId + "]";
		// 商品描述, 可空, 目前先用订单名称
		String body = subject;

		// 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。
		String timeout_express = "1d";

		alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
				+ "\"total_amount\":\""+ total_amount +"\","
				+ "\"subject\":\""+ subject +"\","
				+ "\"body\":\""+ body +"\","
				+ "\"timeout_express\":\""+ timeout_express +"\","
				+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

		//若想给BizContent增加其他可选请求参数, 以增加自定义超时时间参数timeout_express来举例说明
		//alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
		//		+ "\"total_amount\":\""+ total_amount +"\","
		//		+ "\"subject\":\""+ subject +"\","
		//		+ "\"body\":\""+ body +"\","
		//		+ "\"timeout_express\":\"10m\","
		//		+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
		//请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节

		//请求
		String alipayForm = "";
		try {
			alipayForm = alipayClient.pageExecute(alipayRequest).getBody();
		} catch (AlipayApiException e) {
			e.printStackTrace();
		}

		log.info("支付宝支付 - 前往支付页面, alipayForm: \n{}", alipayForm);

		return IMOOCJSONResult.ok(alipayForm);
	}
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

支付中心,接受到前端发起的支付请求,然后使用 alipay 的 sdk 发起预下单请求后,得到一个 html 代码。

再由前端打开的窗口渲染这段 HTML 代码,完成后面的支付。

返回的 html 代码如下所示

image-20210217185923630

上面的表单被加载之后,直接就提交了一个 post 表单,页面就被重定向到如下页面了

image-20210217190043043

这个时候就可以使用支付宝支付功能了

# 支付中心-异步通知

	/**
	 * @Description: 支付成功后的支付宝异步通知
	 */
	@RequestMapping(value="/alipay")
	public String alipay(HttpServletRequest request, HttpServletResponse response) throws Exception {

		log.info("支付成功后的支付宝异步通知");

		//获取支付宝POST过来反馈信息
		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] + ",";
			}
			//乱码解决,这段代码在出现乱码时使用
//			valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
			params.put(name, valueStr);
		}

		boolean signVerified = AlipaySignature.rsaCheckV1(params,
														aliPayResource.getAlipayPublicKey(),
														aliPayResource.getCharset(),
														aliPayResource.getSignType()); //调用SDK验证签名

		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");
			// 付款金额
			String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");

			if (trade_status.equals("TRADE_SUCCESS")){
				String merchantReturnUrl = paymentOrderService.updateOrderPaid(out_trade_no, CurrencyUtils.getYuan2Fen(total_amount));
        // 在这里会往我们的后台发送回调通知
				notifyFoodieShop(out_trade_no, merchantReturnUrl);
			}

			log.info("************* 支付成功(支付宝异步通知) - 时间: {} *************", DateUtil.getCurrentDateString(DateUtil.DATETIME_PATTERN));
			log.info("* 订单号: {}", out_trade_no);
			log.info("* 支付宝交易号: {}", trade_no);
			log.info("* 实付金额: {}", total_amount);
			log.info("* 交易状态: {}", trade_status);
			log.info("*****************************************************************************");

			return "success";
		}else {
			//验证失败
			log.info("验签失败, 时间: {}", DateUtil.getCurrentDateString(DateUtil.DATETIME_PATTERN));
			return "fail";
		}
	}
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

# 后端代码-异步通知

由于这里使用了支付中心,简化了我们的代码,我们的异步通知使用了与 wx 一样的通知代码。