题目里说的个人收款指的不是普通的扫个码,而是说那种可以支持回调的,例如网上商城支付之后,商城可以知道支付状态并且自动修改订单的状态为“已支付”。这种支付的形式,无论是微信、支付宝还是银联,目前都是不对个人开放的,必须有企业资质才能申请。但是对于很多开发者而言,有时候就是一个小小的验证性应用,想要拥有支付功能,而自己又没有企业资质,自然没法申请到微信支付宝这种接口,甚至连第三方的聚合支付(Ping++)也是无法申请的。本文就介绍一种利用个人支付宝(微信也是可以的)自己实现支付功能的思路,成本是一部旧的安卓手机,其他的都是完全免费的,配合支付宝的收款码(提现免费),可以做到零费率。
一、基本思路
这个方案的基本思路是非常简单的,跟之前大家常用的用爬虫爬取网页账单数据类似,但是这里我们用的是手机App。相对来说,截取手机App的推送消息更为简单,不需要应为微信支付宝的各种反爬措施;但是缺点是能够获取到的信息较少,没有诸如流水号、付款人之类的信息,只有一个金额。
所以,我们的思路就是:
- 创建一个订单,将二维码(定额或者非定额都可以)展示给用户
- 用户支付后,商家手机App上收到支付宝的付款推送
- 安卓App截取支付宝的付款推送,然后将付款信息发送给服务器
- 服务器根据付款金额,确定到底是哪一笔订单,然后将该订单标记为“已付款”,然后根据需要进行回调通知之类的操作。
二、关键问题及其解决方案
这个方案里的关键问题有以下几个:
1.支付宝App的通知截取
这个问题其实网上已经有很多的解决方案了,其利用的是Android中的NotificationListenerService
这个类,通过注册这个Listener,可以在推送通知弹出来的时候,获取到其发送的App、标题、内容等信息。我们最关心的就是App和推送内容。
判断发送App的包为支付宝的包,然后再从推送的内容中获取到具体的内容,即可得到付款金额。
示例代码如下:
public class AlipayNotificationListenerService extends NotificationListenerService {
public AlipayNotificationListenerService() {
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
// 这里可以拿到包名,可以按照需要判断。
String packageName = sbn.getPackageName();
Notification notification = sbn.getNotification();
if (notification == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.e("SevenNLS","in 1");
Bundle extras = notification.extras;
if (extras != null) {
// 这里是具体的title和content,可以从中提取金额
String title = extras.getString(Notification.EXTRA_TITLE, "");
String content = extras.getString(Notification.EXTRA_TEXT, "");
Log.d("Zachary", "title:" + title + " content:" + content);
}
}
}
@Override
public void onListenerConnected()
{
Log.e("Zachary","connected");
}
}
当然,为了让这个App能够顺利进行,还要给它获取通知的权限,保证它不被清理等等,需要做一些相应的保护措施。
2.订单的确定
刚才我们说过,服务器收到App发来的收款信息之后,还需要找到对应的订单。这一步是相对比较难的一步,因为我们知道相同金额的订单可能有很多,到底哪一个才是刚刚支付的订单呢?
这里,我们可以再详细思考一下,其实这个订单不仅仅是由这个金额确定的,而是一个多元组共同确定的。最简单的一种实现方式就是 (订单金额-支付状态)。通过这个二元组可以确定一个订单。其含义是,如果这个订单已经支付过了,那么我在查找订单的时候,就可以不用理会它了,我只需要查找(指定金额-未支付)的订单就可以了。
这样可以基本解决这个问题。但是,我们考虑到除了正常支付外,还有可能会有另外一些情况。比如用户创建了订单之后,突然不想支付了,没有进行接下来的操作。或者说,有人恶意在网站上创建了大量的订单并且不支付。 这样的后果是,这些订单的状态永远都是未支付
,当你想要继续创建订单的时候,就会受到限制,不能创建跟这些订单相同金额的订单,否则你的系统将无法分辨到底是哪一笔订单被支付了。
为了应对这种情况,我们想到其实很多的支付都是有时间限制的,也就是说,订单是有有效期的,一旦过了有效期,订单就不能被支付了。所以我们也可以给订单加一个有效时间的限制,比如5分钟,一旦五分钟内没有被支付,就认为这个订单已经失效了。这时,订单的确定方式就变成了一个三元组(订单金额-支付状态-是否过期)。查找的时候,只需要查找(指定金额-未支付-未过期)的订单就可以了。也就是说,任意一个订单,最多只会占用这个金额5分钟,一旦超过五分钟,不管支付与否,你都可以继续创建相同金额的订单了。
但是这样我们还是觉得不满意,特别是对于某些支付金额相对单一的情况,可能每次都需要创建相同金额的订单,这样的话,再最坏情况下我们只能每隔五分钟处理一个订单,这个效率可以说是非常低效了。
在这里,我们提出了一种trade-off的解决方法。一般的正常支付是不会使用这种方式的,也难以接受,但是对于我们来说,为了避免企业资质的认证和手续费,在一定程度上是可以接受的。
这种方式就是,当目前系统中已经有了某一金额的订单的时候,如果我们要继续创建相同金额的订单,那么我们就在指定金额上进行上下浮动,比如下浮一分钱,这样金额就可以和之前的订单区分开来,避免出现不能同时支付的情况。这样,虽然我们在高并发情况下可能会有一定的损失(同时支付的人越多,差距越大),但是满足了我们的高并发要求。
友情提示:如果金额发生浮动,可以告诉用户这是随机立减,一定程度上可以避免定价和实际支付金额的差距带来的问题。(这种情况下就只能下浮,不能上浮,不然就变成随机立加了)。
三、总结
总体上来说,我认为这种方案对于普通的个人用户来说,是一种可以接受的方案。其优缺点总结如下:
优点:
- 不需要企业资质
- 没有手续费
- 不对支付宝进行任何操作,没有被支付宝进行风控的风险
缺点:
- 需要有一部手机一直运行,且要求网络条件良好,否则会丢失支付数据(可以有人工解决方案)
- 高并发时,订单金额会产生浮动
- 如果金额浮动策略不合理,并且被人探索出规律,可能造成财产损失!!(例如短时间内创建大量订单,这样订单价格会不断下降,需要针对这种情况做出防范)
参考:
PaysApi: www.paysapi.com