手机版

PHP支付系统设计及典型案例分享

时间:2021-09-08 来源:互联网 编辑:宝哥软件园 浏览:

由于公司业务需要,用了两周时间实现了一个小额支付系统。麻雀虽小而全,但账户锁定、交易担保、流量对账等所有必要模块。已经完全实现了。在整个开发过程中积累了很多经验。另外在网上搜了一下,大部分都是研究论文,没有什么实际的使用价值,所以这次特意拿出来和大家分享一下。该系统既可以作为小型支付系统,也可以在第三方应用访问开放平台时作为支付管道系统。原来的需求比较负责,我就简化一下说:

对于每个应用,都需要提供获取余额、支付设备、充值等外部接口。后台有程序,每月第一天结算账户可以冻结。需要记录每次作业的流水,每天的流水必须与发起方核对

根据上述要求,我们设置了以下数据库:

CREATE TABLE ` app _ margin ` . ` TB _ status `(` appid ' int(10)UNSIGNED NOT NULL,` freeze ' int(10)NOT NULL DEFAULT 0,` create _ time` datetime NOT NULL,` change _ time` datetime NOT NULL,PRIMARY KEY(` appid `))ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE ` app _ margin ` . ` TB _ account _ inch `(` appid ' int(10)UNSIGNED NOT NULL,` create _ time` datetime NOT NULL,` balance ' big int(20)NOT NULL,` change _ time` datetime NOT NULL,` seqid ' int(10)NOT NULL DEFAULT 50000000,PRIMARY KEY(` appid `)) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE ` app _ margin `.` TB _ bill `(` id ' int AUTO _ INCREMENT NOT NULL,` bill _ id ' int(10)NOT NULL,` amt` bigint(20) NOT NULL,` bill _ info` text,` bill _ user ' char(128),` bill _ time` datetime NOT NULL,` bill _ type ' int(10)NOT NULL,` bill _ channel ' int(10)NOT NULL,` appid ' int(10)UNSIGNED NOT NULL,` old _ balance` bigintCREATE TABLE ` app _ margin ` . ` TB _ assign `(` id ` int AUTO _ INCREMENT NOT NULL,` assign_time` datetime NOT NULL,PRIMARY KEY(` id `))ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE ` app _ margin ` . ` TB _ price `(` name ' char(128)NOT NULL,` price ' int(10)NOT NULL,` info` text NOT NULL,PRIMARY KEY(` name `))ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE ` app _ margin `.` TB _ applock `(` appid ` int(10)UNSIGNED NOT NULL,` lock _ mode ` int(10)NOT NULL DEFAULT 0,` change_time` datetime NOT NULL,PRIMARY KEY(` appid `))ENGINE=InnoDB DEFAULT CHARSET=utf8;INSERT ` app _ margin ` . ` TB _ assign `(` id `,` assign_time`) VALUES (100000000,now());详细说明:tb_status应用的如下状态表。负责账户是否冻结,账户类型是什么(真正的需求是应用中可能有两种账户,这里不列出,因为简单)appid Application idfreeze是否冻结create _ Time Creation Time change _ Time Last Modified Time TB _ Account _ inch Applied Account Balance Table appid Application id Balance(单位是分钟,不要用小数,因为小数不准确;另外,php只能支持big int)create _ time creation time change _ time最后修改时间seqid操作序号(反并发,每次更新都会是1)tb_assign一个流id的表,而tb_bill的bill_id必须是一个流表,且tb_assign赋值id自增idcreate_time创建时间tb_bill。负责记录每一个操作序号,其中bill_id不是主键,因为同一个bill_id可能会用bill_id序号加上的两个序号来支付和回退amt操作的金额(这是为了区分正负,主要是选择全部时直接计算某段时间内的金额变化)。比如3个webserver,2个dbbill_user操作用户bill_time flow time bill_type流类型,区分是加钱还是减钱bill_channel流源,如bill_ret流的充值、支付、回退、结算或其他返回代码,包括未处理、成功和失败。这里的逻辑后面会解释,当应用idold_balance之前的账户余额price_info记录操作发生时,记录appid goods src_ip客户端iptb_price单价表的单价,记录机器名称机器唯一标识符price info description tb_applock表的单价,这是为了避免并发写入一个应用。具体的代码将在后面显示appid应用程序idlock_mode的锁定状态。0表示锁定,1表示锁定change_time的最后修改时间。库表设计好之后,我们来看看一些典型的操作。

一、支付操作我只列出我目前已经实施的方式,可能不是最好的,但应该是最经济的,满足需求的。先说说打电话的人。逻辑如下:

那么对应支付系统的内部逻辑如下(只列出支付操作,回滚逻辑类似,流程检查是检查对应的支付流程是否存在):

常用的错误返回代码可能如下,对于:来说已经足够了

$g_site_error=array(-1='服务器忙',-2='数据库读取错误',-3='数据库写入错误',0='成功',1='无数据',2='无权限',3='余额不足',4='帐户冻结。大于0的错误都是逻辑错误,调用者在执行支付操作时不需要记录管道。因为账户没有任何变化。小于0的错误是内部系统错误,因为不知道是否发生了数据更改,所以呼叫者和支付系统都应该记录该流程。对于等于0的回报,意味着成功,双方必须记录流水。在支付系统中,使用先写流程再更新账户的方法也是有原因的。简单来说就是尽量避免流失流量。最后总结一下,这种先扣钱,再出货,出现问题再滚回来的方式是一种模式;另一种方法是先扣款再发货。如果没有问题,打电话确认付款再扣钱。如果有问题,请调用付款回滚取消。如果预扣后长时间没有确认,金额会自动回退。

第二,这里账户锁定的实现使用的是数据库的锁定机制,具体逻辑就不说了。代码如下:

class APPlock { function _ _ construct($ appid){ $ this-m _ appid=$ appid;//初始化数据$ this-get();} function _ _ destrust(){ $ this-free();} public function alloc(){ if($ this-m _ bGot==true){ return true;} $ this-修复数据();$ appid=$ this-m _ appid;$ret=$this-update($appid,APPLOCK_MODE_FREE,APPLOCK _ MODE _ ALLOC);if($ ret===false){ app _ error _ log(' applock alloc fail ');返回false } if($ ret=0){ app _ error _ log(' applock alloc fail,affected _ rows 3360 $ ret ');返回false } $ this-m _ bGot=true;返回真;}公共函数free() { if ($this-m_bGot!=true){ 0返回真的;} $ appid=$ this-m _ appid;$ret=$this-update($appid,APPLOCK_MODE_ALLOC,APPLOCK _ MODE _ FREE);if($ ret===false){ app _ error _ log(' app锁定自由失败');返回false } if($ ret=0){ app _ error _ log(' app锁定自由失败,受影响_ rows 3360 $ ret ');返回false } $ this-m _ bGot=false;返回真;}函数修复数据(){ $ DB=APP _ DB();$ appid=$ this-m _ appid;$ now=time();$ need _ time=$ now-APPLOCK _ REPAIR _ SECS;$ str _ need _ time=date(' Y-m-d h : I :s ',$ need _ time);$ db-其中(' appid ',$ appid);$ db-其中(' lock_mode ',APPLOCK _ MODE _ ALLOC);$db-where('change_time=',$ str _ need _ time);$db-set('lock_mode ',APPLOCK _ MODE _ FREE);$db-set('change_time ',' NOW()',false);$ ret=$ db-update(TB _ APPLOCK);if($ ret===false){ app _ error _ log('修复app锁定错误,app id : $ app id ');返回false}返回真}私有函数get(){ $ DB=APP _ DB();$ appid=$ this-m _ appid;$db-where('appid ',$ appid);$ query=$ db-get(TB _ APPLOCK);if($ query===false){ app _ error _ log(' AppLock get fail。appid : $ appid ');返回false } if(count($ query-result _ array())=0){ $ APPLOCK _ data=array(' appid '=$ appid,' lock_mode'=APPLOCK_MODE_FREE,$db-set('change_time ',' NOW()',false);$ret=$db-insert(TB_APPLOCK,$ APPLOCK _ data);if($ ret===false){ app _ error _ log(' app锁定插入失败: $ appid ');返回false} //重新获取数据$db-where('appid ',$ appid);$ query=$ db-get(TB _ APPLOCK);if($ query===false){ app _ error _ log(' AppLock get fail。appid : $ appid ');返回false } if(count($ query-result _ array())=0){ app _ error _ log(' AppLock不是数据,appid : $ appid ');返回false } } $ app lock _ data=$ query-row _ array();返回$ applock _ data}私有函数更新($appid,$old_lock_mode,$ new _ lock _ mode){ $ DB=APP _ DB();$ db-其中(' appid ',$ appid);$db-where('lock_mode ',$ old _ lock _ mode);$db-set('lock_mode ',$ new _ lock _ mode);$db-set('change_time ',' NOW()',false);$ ret=$ db-update(TB _ APPLOCK);if($ ret===false){ app _ error _ log(' update app lock error,appid:$appid,old _ lock _ mode : $ old _ lock _ mode,new _ lock _ mode : $ new _ lock _ mode ');返回false}返回$ db-affected _ rows();} //是否获取到了锁public $ m _ bGot=false public $ m _ appid }为了防止死锁的问题,获取锁的逻辑中加入了超时时间的判断,大家看代码应该就能看懂

三。对帐逻辑如果按照上面的系统来设计,那么对帐的时候,只要对一下两边成功(即bill_ret=0)的流水即可,如果完全一致那么账户应该是没有问题的,如果不一致,那就要去查问题了。关于保证账户正确性这里,也有同事跟我说,之前在公司做的时候,是采取只要有任何写操作之前,都先取一下流水表中所有的流水记录,将手自一体的值累加起来,看得到的结果是否和余额相同。如果不相同应该就是出问题了从tb_bill中选择总和(金额),其中appid=1;所以这也是为什么我在流水表中,amt字段是要区分正负的原因。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

版权声明:PHP支付系统设计及典型案例分享是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。