以PC端支付为例简述支付宝支付的案例

1、申请支付宝商户

首先要申请好商户,目前不对个人用户申请,只针对企业、个体户,需要上传营业执照完成验证。开通电脑网站支付后,可在支付宝开放平台新建应用并配置好商户公钥、签名算法类型(推荐使用RSA2)。这一步完成后就有APPID、商户私钥、支付宝公钥,基本上可以按照官方的操作指引开通。

2、验证商户是否正常

可以直接参考官方提供的例子验证一下https://opendocs.alipay.com/open/028r8t?scene=22

 AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", "app_id", "商户私钥", "json", "UTF-8", "支付宝公钥", "RSA2");
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", String.valueOf(System.currentTimeMillis()));
bizContent.put("total_amount", 0.01);
bizContent.put("subject", "测试商品");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
//bizContent.put("time_expire", "2022-08-01 22:00:00");
request.setBizContent(bizContent.toString());
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
    System.out.println("调用成功");
    System.out.println(response.getBody());
} else {
    System.out.println("调用失败");
}

注意:其中的time_expire字段,可以用来解决在订单即将超时(如20分钟)的临界点支付的问题,比如在时间19分50秒拉起了支付,订单在整20分钟时因超时取消了,紧接着用户又才支付成功了,导致订单异常,因此可以在拉起支付的时候将time_expire时间设置在20分钟取消的时间点之前,通过交易的时效来解决此问题。

在调用接口成功后,返回的response.getBody()为一段自动提交的form表单代码,可将此表单代码内容保存到文件pay.html中(注意保存的文件编码须为UTF-8),再用浏览器打开即可跳转到支付宝支付页面,如下

<form name="punchout_form" method="post" action="https://openapi.alipay.com/gateway.do?charset=UTF-8&method=alipay.trade.page.pay&sign=atSjYwukhX1kvDdQdr6mW68H8dfJ9EnJKSJd11h%2FUqmEp5o6DcgBZY166IP%2FpjNwFIlFhYxgR3klNO9Qm9vNKvrgzQrq5K324LsSEeFad9qzP1%2BTTDZ2foWRS1D%2Fgz6Zwfxgc9ks9udIOn0W2swxt2iuzA1xjrwqD57HzuoQbZPsU7sQq7aIkMydfWfXVSZxBVhTxvil8kGpIYvjxKRF1qgUDqWSAPU5dvLzzoS2e%2F5LmaeNhrytyujKCRaz0Ia5Iz4%2BpmkyCxIgiXHkvhVKeQurKhgYg912aq9L6KQ1xHFQKvsZPLqsMugyY%2ByogLxA3LTfjDTCRHWEaSFdVgRPWQ%3D%3D&version=1.0&app_id=2021003129610223&sign_type=RSA2&timestamp=2022-08-10+19%3A02%3A03&alipay_sdk=alipay-sdk-java-dynamicVersionNo&format=json">
<input type="hidden" name="biz_content" value="{&quot;out_trade_no&quot;:&quot;1660129323876&quot;,&quot;total_amount&quot;:0.01,&quot;subject&quot;:&quot;测试商品&quot;,&quot;product_code&quot;:&quot;FAST_INSTANT_TRADE_PAY&quot;}">
<input type="submit" value="立即支付" style="display:none" >
</form>
<script>document.forms[0].submit();</script>

针对此javademo,也可以简单使用spring-boot演示一下,将响应的表单输出完成,同样需要注意编码问题,如下

引入的maven依赖

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.31.84.ALL</version>
</dependency>

演示代码

@GetMapping("/pay")
public void pay(HttpServletResponse httpServletResponse) throws Exception {
    ...省略
    AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
    if (response.isSuccess()) {
        System.out.println("调用成功");
        httpServletResponse.setContentType("text/html;charset=" + CHARSET_UTF8);
        PrintWriter out = httpServletResponse.getWriter();
        out.write(response.getBody());
        out.flush();
        out.close();
    } else {
        System.out.println("调用失败");
    }

}

可以访问演示地址:https://www.mixfate.com/pay/alipay 或通过 curl https://www.mixfate.com/pay/alipay

如能正常打开页面扫码支付即表示商户可正常使用了。

简单看一下pageExecute方法,如下

public <T extends AlipayResponse> T pageExecute(AlipayRequest<T> request, String httpMethod) throws AlipayApiException {
    RequestParametersHolder requestHolder = this.getRequestHolderWithSign(request, (String)null, (String)null, this.appCertSN, (String)null);
    ...省略
    } else {
        String baseUrl = this.getRequestUrl(requestHolder);
        rsp.setBody(WebUtils.buildForm(baseUrl, requestHolder.getApplicationParams()));
    }
   ...省略
}

主要是通过getRequestHolderWithSign方法完成参数的初始化以及签名的生成,再由WebUtils.buildForm生成html form表单。

3、实现一个简易的PC端支付宝支付案例

前端使用vue,后端api接口简单使用spring-boot实现返回form表单以及订单支付的结果查询。

  • 前端页面

演示有两个页面,一个支付页pay和支付成功页pay-success,支付页中使用支付宝的qr_pay_mode=4的模式返回二维码,页面中使用iframe嵌入。支付宝的官方文档4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小,参考地址https://opendocs.alipay.com/open/028r8t?ref=api

同时增加了一个定时查询订单状态的逻辑,保证用户在异步支付成功后及时获得支付结果。

关键代码如下

<div id="alipay">
  <iframe id="frame" class="pay-iframe" :src="alipay" frameborder="0"></iframe>
</div>

export default {
  name: 'Pay',
  data() {
    return {
      isShow: false,
      payInfo: {totalFee: 0.01, orderNo: new Date().getTime()},
      tradeStatus: '',
      alipay: ''
    }
  },
  methods: {
    async pay() {
      this.isShow = true
      this.alipay = process.env.VUE_APP_API_HOST + '/' + this.payInfo.orderNo
      this.timer = setInterval(async () => {
        const {tradeStatus} = await reqPay(this.payInfo.orderNo)
        console.log(tradeStatus)
        this.tradeStatus = tradeStatus
        if (tradeStatus === 'success') {
          this.$router.push('/paySuccess')
        }
      }, 2000)
    }
    ...省略

展示二维码的将iframesrc设置为返回自动提交的form表单的url

  • 后端支付接口和查询接口

后端支付接口直接返回form表单内容,支付结果查询接口则返回订单的支付状态。

@GetMapping("/pay/{orderNo}")
public void pay(@PathVariable("orderNo") String orderNo, HttpServletResponse httpServletResponse) throws Exception {
    AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", orderNo);
    bizContent.put("total_amount", 0.01);
    bizContent.put("subject", "测试服务费");
    bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
    bizContent.put("qr_pay_mode", 4);
    request.setBizContent(bizContent.toString());
    AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
    if (response.isSuccess()) {
        httpServletResponse.setContentType("text/html;charset=" + CHARSET_UTF8);
        PrintWriter out = httpServletResponse.getWriter();
        out.write(response.getBody());
        out.flush();
        out.close();
    } else {
        System.out.println("调用失败");
    }
}

@GetMapping("/pay/query/{orderNo}")
public Result query(@PathVariable("orderNo") String orderNo) throws Exception {
    AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
    JSONObject bizContent = new JSONObject();
    bizContent.put("out_trade_no", orderNo);
    request.setBizContent(bizContent.toString());
    AlipayTradeQueryResponse response = alipayClient.execute(request);
    TradeStatus status = new TradeStatus();
    if (response.isSuccess()) {
        log.info("查询结果[{}]", response.getBody());
        if ("WAIT_BUYER_PAY".equalsIgnoreCase(response.getTradeStatus())) {
            status.setTradeStatus("wait_buyer_pay");
            return Result.success(status);
        } else {
            return Result.success(status);
        }
    } else {
        if ("ACQ.TRADE_NOT_EXIST".equalsIgnoreCase(response.getSubCode())) {
            status.setTradeStatus("trade_not_exist");
        } else {
            status.setTradeStatus("wait_buyer_pay");
        }
        return Result.success(status);
    }
}

案例地址:https://www.mixfate.com/pcpay

这只是一个简单的实现支付宝支付的案例,如增加了微信支付、云闪付等多种支付方式的时候就需要考虑好支付路由的设计,支付网关的设计的问题了,还需要考虑掉单、重复支付、多笔退款、对账等等问题。


赞赏(Donation)
微信(Wechat Pay)

donation-wechatpay