以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×tamp=2022-08-10+19%3A02%3A03&alipay_sdk=alipay-sdk-java-dynamicVersionNo&format=json">
<input type="hidden" name="biz_content" value="{"out_trade_no":"1660129323876","total_amount":0.01,"subject":"测试商品","product_code":"FAST_INSTANT_TRADE_PAY"}">
<input type="submit" value="立即支付" style="display:none" >
</form>
<script>document.forms[0].submit();</script>
针对此java
的demo
,也可以简单使用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)
}
...省略
展示二维码的将iframe
的src
设置为返回自动提交的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
这只是一个简单的实现支付宝支付的案例,如增加了微信支付、云闪付等多种支付方式的时候就需要考虑好支付路由的设计,支付网关的设计的问题了,还需要考虑掉单、重复支付、多笔退款、对账等等问题。