解决第三方支付平台提现原子性问题
AI 概述
在业务逻辑中,经常会碰到提现需求。提现的实现一般分为两个步骤:
扣除余额;
调用第三方支付接口进行提现(比如微信支付:企业付款到零钱)。
假设我们这样写(伪代码):
<?php
DB::beginTransaction();
try {
$member = Member::find($id);
$member->money -= $withdrawMoney;
$member-...

在业务逻辑中,经常会碰到提现需求。提现的实现一般分为两个步骤:
- 扣除余额;
- 调用第三方支付接口进行提现(比如微信支付:企业付款到零钱)。
假设我们这样写(伪代码):
<?php
DB::beginTransaction();
try {
$member = Member::find($id);
$member->money -= $withdrawMoney;
$member->save();
$wechat->payment->pay($openid, $withdrawMoney);
DB::commit();
} catch (\Exception $e) {
DB::rollback();
Log::error($e->getMessage());
}
这样写会有什么问题?当执行commit的时候,由于网络原因,数据库突然连不上了或者数据库挂了,怎么办?会导致什么后果?
会导致钱付出去了,但是余额没扣。
还有同学可能会有其它疑问,如果数据库操作成功,接口调用因为网络原因失败了,会怎么样?就我们上面这段代码而言,如果接口调用失败,并且调用失败后会抛出异常的话,那就会被
catch到,try里面甚至都走不到commit,直接rollback了。所以接口调用失败时,不会有原子性问题。
怎么样保证提现操作是原子性的,要么扣余额和调接口同时成功,要么同时失败?
把调微信接口的操作,转化成一个异步任务。
<?php
class WechatWithdrawJob
{
public static function withdraw($openid, $money)
{
$result = $wechat->payment->pay($openid, $money);
if ($result->return_msg == 'SUCCESS') {
return true;
}
return false;
}
}
<?php
DB::beginTransaction();
try {
$member = Member::find($id);
$member->money -= $withdrawMoney;
$member->save();
$task = new Task;
$task->callback = json_encode([WechatWithdrawJob::class, 'withdraw']);
$task->params = json_encode([$openid, $withdrawMoney]);
$task->add_time = time();
$task->save();
DB::commit();
} catch (\Exception $e) {
DB::rollback();
Log::error($e->getMessage());
}
起一个异步任务消费进程,不停地轮询消费。
<?php
$tasks = Task::whereIsNull('finish_time')->where('retries', '<', self::MAX_RETRIES)->get();
foreach ($tasks as $task) {
if ($task->retries == self::MAX_RETRIES - 1) {
//notify administrator
}
$callback = json_decode($task->callback, true);
$params = json_decode($task->params, true);
if ($result = call_user_func_array($callback, $params)) {
$task->finish_time = time();
} else {
$task->retries += 1;
}
$task->save();
}
这样就把分布式的事务转化成了本地事务,保证了提现的原子性。
除了提现,这种编程方法还可以用在所有需要调用外部接口的业务上,保证业务的原子性。
基于这种方法,还有一些优化的思路。
- 如果异步任务比较多,有些任务可能会比较耗时,有必要多起几个消费者进程,每个进程负责不同类型的异步任务。
- 也可以参照 GO 的
GPM模型,给异步任务加个type,不同进程消费不同的type,未完成的同一个type的任务量设置一个上限,比如说 100。达到上限后,再入库的异步任务就把type设置成global,type为global时,任意一个消费者进程都可以消费。消费type为global的任务时,记得加个分布式锁,避免并发问题。
以上就是关于第三方支付平台提现原子性问题解决方案,希望对大家有帮助,感谢阅读。
以上关于解决第三方支付平台提现原子性问题的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 解决第三方支付平台提现原子性问题
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » 解决第三方支付平台提现原子性问题
微信
支付宝