前言
通过前面三篇文章,我们已经把个人网站接入支付宝支付时需要做的准备工作全部准备就绪了,如果还不清楚,可以先行了解一下:
接下来就该正式写代码实现了,但考虑再三之后,我还是决定不过多介绍开发细节,因为这涉及到 沙箱环境 和 SDK
的使用,感觉解释起来会比较墨迹,而且想来有开发需求的人看了上面的文章之后,再自行参考官方API开发文档,思路可能会更清晰,毕竟官方文档支持在线调试,效果也更直观。
看过我往期文章的应该知道,我的网站都是基于 PHP
的 Typecho 二次开发的,因此,我这里就以 PHP
为例,简单梳理一下 电脑网站支付 的接入要点吧。
1. 开发准备
这里所说的准备主要指的运行环境、AppId/密钥
和 SDK
,虽然都很简单,但还是有一些需要说明一下的。
1.1 运行环境
在运行环境方面,由于对接支付宝时需要用到 OpenSSL
签名,因此,在我们的运行环境中需要安装 OpenSSL
,并启用 PHP
的 OpenSSL
扩展。无论是 Windows 还是 Linux 的安装方法,网上相关的文章都很多,这里就不介绍了。但如果你采用的也是和我一样基于 Docker
的部署方式,那么你可以在你的 Dockerfile
中加入如下脚本,然后重新启动容器就OK了。
RUN apt-get update && apt-get install -y libssl-dev && \
docker-php-ext-install openssl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
1.2 AppId/密钥
这里的 密钥 指的就是 应用私钥 和 支付宝公钥,不清楚的可以看一下 为个人网站接入支付功能 - 支付宝准备篇 这篇文章,里面有生成和获取的方法,而 AppId 则可以在创建应用后,从下图所示的位置获取直接获取。
有了这三个值就可以调试接口了,当然,开发测试的时候应该从沙箱环境获取测试专用的这三个值。
1.3 SDK
关于 SDK
,支付宝一共提供了两个版本:Alipay SDK(通用版) 和 Alipay Easy SDK。其中,Alipay SDK(通用版) 又分为 V2
和 V3
两个小版本。对我们而言,它们三个并没有什么区别,毕竟 99%
的功能,我们都用不上。我最开始准备用 Alipay Easy SDK,毕竟是简易版,文件数量少一点,后来我发现文档不是很丰富,需要自己摸索用法,后来又研究了一下Alipay SDK V3
版本,发现存在同样的问题。
最后,我没有直接使用任何一个 SDK
,而是从 V2
的源码中复制出了必要的 8
个文件,毕竟完整的包有一万多个文件,这对于有代码洁癖的人来说,看着实在是很不舒服。
当然,拷贝过来并不能直接使用,需要删掉 AopClient.php
中的 exec
方法,因为它依赖了其它文件,但这个方法我们用不上。另外还需要修复如下BUG:
这些BUG可能只会在高版本的 PHP
中才会出现,我的环境就报错了。但我想无论这里是否报错,也无论是什么开发语言,可选参数必须位于所有必选参数之后,应该是一个常识性的问题,这代码应该是新手写的,所以这里最简单的解法就是把 = 0
去掉。
最后,选择 V2
的原因也很简单,就是支付宝的在线集成工具用的就是 V2
。
上图中调试时所需的参数都是来自于 手动输入 或者 沙箱环境(AppId、应用私钥、支付宝公钥),然后动态生成 SDK
的调用代码。线上测试没问题后,复制代码,稍微改改就可以直接使用了,非常方便直观。也就是说,如果后续支付宝这里的示例代码改成了 V3
,我们就也可以用 V3
了,所见即所得,也能免去不少麻烦。
到此,开发之前的所有准备工作就全部完成了。
2. 支付流程
详细的接入指南可以参考官方的开发文档,文档中有完整且详细的说明,但我们的支付需求非常简单,我们大部分情况下,只需要基于扫码支付,实现如会员、付费阅读以及虚拟产品销售(如插件、主题、软件激活码等)等功能,因此,我们的支付流程如下图所示:
乍一看有点复杂,其实简单来说就是三个步骤:
- 客户端下单,支付宝返回支付二维码;
- 客户端在展示二维码的同时,开启定时器,定时 查询支付状态,当查询到状态为
已支付
时,关闭定时器,并重新加载相关资源。当用户取消支付(关闭二维码页面)时,也应该停止定时器; - 客户支付成功后,支付宝会通过
notifyUrl
向商家服务器推送消息,商家端据此更新订单状态。
每个步骤都对应一个与支付宝交互的接口,也就是说,接下来我们完成这三个接口的对接就可以了。至于其它如关闭订单、退款之类的接口,我们用不上,就不考虑了,因为通常虚拟商品也是不支持退款的,你有见过充值了会员又退款的吗?
3. 开发要点
我是基于 Typecho 插件的形式开发的,但考虑到每个人的支付场景、交互界面,支付后要实现的业务都可能完全不同,很难通用,所以就没有开源,而是想着写篇文章把这个实现过程说清楚,但事与愿违,我发现一两句话根本解释不清楚。
纠结了很久之后,我最终决定,后续结合我的博客主题(WaterDrop),开发一个定向的赞赏插件,然后开源,这样只要能看懂 PHP
代码的人,就都可以参考我的实现,定制自己的 Typecho 插件了,这样也能省去大部分时间,毕竟 90%
以上的代码还是可以通用的。所以,这里我就只简单地介绍一下支付宝对接过程中的一些开发要点,便于大家理解代码。
3.1 客户端下单
下单的核心代码如下:
$alipayClient = $this->createAopClient();
$request = new AlipayTradePagePayRequest();
$bizParams = [
'out_trade_no' => $trade_no,
'total_amount' => $amount,
'subject' => _t('捐赠') . '【' . $this->options->title . '】',
"product_code" => "FAST_INSTANT_TRADE_PAY",
"qr_pay_mode" => "4",
"qrcode_width" => "200L"
];
$request->setBizContent(json_encode($bizParams, JSON_UNESCAPED_UNICODE));
$request->setNotifyUrl(Common::url('/alipay_notify', $this->options->siteUrl));
$result = $alipayClient->pageExecute($request, "POST");
通过前面的流程图,我们知道客户下单时调用 $alipayClient->pageExecute($request, "POST")
,服务端并没有与支付宝服务器产生交互,仅仅只是生成了一段如下形式的表单代码返回给客户端:
<form name="punchout_form" method="post" action="https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=UTF8&method=alipay.trade.page.pay&sign=QvsIsoL19c3cKu6k4taYLzem2RNk3mqj0%2BniBsaRhjUBMFtH24SYepHP4Os7HwRiyRhOzYv4UA9s4tJDQR4tTWBocS5NpnAJRPqO5%2B0uf7ySb%2BUs0aMjV95QffH1EadZTrbGqm%2BGb%2BOSlhPBlbkn58VRcZBCe7mFXPImL5UKWb%2BYgwa1zYxG00iusT%2B3CjAs1cSjUCg%2FIoy6CCaKqzuyJSq2AvzRhTFNAjuI7o7AkbxNxe0YZgLS%2FOfyxLqFDzkjgpbW7xgHp7pUaBZZFJKZSqgb7UEl1SiyvKxT69dZKqAdqV9OViiTX05PXr8y%2F83L6PXxR260AhfthWBSPUsLvw%3D%3D&app_id=9021000142682499&sign_type=RSA2×tamp=2025-01-08+21%3A01%3A40&alipay_sdk=alipay-sdk-java-4.35.171.ALL&format=json"> <input type="hidden" name="biz_content" value="{"out_trade_no":"20150320010101001","total_amount":"88.88","subject":"Iphone6 16G","product_code":"FAST_INSTANT_TRADE_PAY","qr_pay_mode":"4","qrcode_width":"200"}"> <input type="submit" value="立即支付" style="display:none" > </form>
<script>document.forms[0].submit();</script>
原则上,服务端应该将上述代码包装成一个完整的页面返回给客户端,然后客户端会自动将这个表单提交给支付宝,并在支付宝页面显示二维码,完成支付操作,但这显然不满足我们的需求。为了实现在我们自己的网站上显示二维码,就需要做两件事情,第一个就是上述代码中的 qr_pay_mode
要设置为 4
,并指定 qrcode_width
,即二维码的尺寸;第二个是客户端需要用 iframe
来显示上述表单,代码大致形式如下:
<div class="qrcode"></div>
<script>
function renderQRCode(paytype, qrcode) {
const qrEl = payModalEl.querySelector(".modal-body .qrcode");
let newEl = null;
if (paytype === 'alipay') {
const iframe = document.createElement('iframe');
iframe.width = '200';
iframe.height = '200';
iframe.frameBorder = '0';
iframe.scrolling = 'no';
iframe.srcdoc = qrcode;
qrEl.innerHTML = iframe.outerHTML;
} else if (paytype === 'wxpay') {
// ...
}
}
</script>
客户端只需要在调用下单接口成功后,调用一下 renderQRCode
方法就可以了,其中,iframe
的尺寸要与二维码的尺寸一致,并且 iframe
必须设置为无边框、无滚动条才行。
3.2 查询支付状态
查询支付状态的代码比较简单,核心代码如下:
$alipayClient = $this->createAopClient();
$request = new AlipayTradeQueryRequest();
$bizParams = [
'out_trade_no' => $trade_no
];
$request->setBizContent(json_encode($bizParams, JSON_UNESCAPED_UNICODE));
$responseResult = $alipayClient->execute($request);
$responseApiName = str_replace(".", "_", $request->getApiMethodName()) . "_response";
$response = $responseResult->$responseApiName;
if (!empty($response->code) && $response->code == 10000 && $response->trade_status == 'TRADE_SUCCESS') {
$this->paySuccess($trade_no, $trade->uid, $trade->amount);
$this->response->throwJson([
'code' => 1,
'message' => $succssMessage
]);
} else {
$this->response->throwJson([
'code' => -1,
'message' => _t('等待支付')
]);
}
需要注意的是,该接口需要前端定时查询,并控制查询的时机与频率,即打开二维码时开始查询,关闭后停止查询,并且查询到已支付的状态后就应该停止查询。
另外,服务端在调用 $alipayClient->execute($request)
远程查询支付状态之前,应该先查本地数据库,只有在本地数据库显示 未支付
时才需要查询远程,并且远程返回支付成功的状态后,也应该更新本地数据库的订单支付状态。
3.3 通知回调
通知回调接口是我们自己开发,提供给支付宝调用的,在下单的时候通过$request->setNotifyUrl(Common::url('/alipay_notify', $this->options->siteUrl))
设置,为个人网站接入支付功能 - 支付宝准备篇 一文中的 应用网关 配置的也是这个回调地址,这里为了让回调地址是 https://域名/alipay_notify
的形式,需要通过自定义路由的方式来实现,代码如下:
Helper::addRoute('alipay_notify', '/alipay_notify', Action::class, 'alipayNotify');
处理通知消息的具体实现就在 Action
类的 alipayNotify
方法中,当然,该方法也可以放在其它地方,具体实现就不介绍了,该方法的主要工作就是修改支付完成后本地数据库的订单状态,但需要注意的是,该方法一定要做参数签名验证,确保调用方是支付宝,并且该方法中也不适合做耗时的操作,如同步发邮件等。
结语
好了,这篇文章就写到这里,只是简单地介绍了一下对接支付宝支付功能的一些思路和开发要点,让不了解的同学心里有个底,虽然略微有些繁琐,但每个步骤其实还是很简单的。
正如前文提到的,等我后续把微信支付的也开发完成之后,我会把这个插件完善一下并开源出来。有了前面这些文章的铺垫,我想插件使用起来也会容易理解很多。
评论4
老孙
写个插件我想用😁
老朱
微信支付有点坑,审核一次300块,审的也比支付宝严,还不包过,只能后面再说了,可能还是得搞服务商模式🤣
老孙
我看tepay 这个插件微信似乎就是用的第三方
老朱
是的,用第三方的可能是最优解了,官方的太麻烦了,而且成本比第三方还高