In diesem Artikel werden hauptsächlich das Design und typische Fälle des PHP-Zahlungssystems im Detail vorgestellt. Es kann als kleines Zahlungssystem oder als Zahlungsflusssystem verwendet werden, wenn Anwendungen von Drittanbietern mit der offenen Plattform verbunden sind Interessierte Freunde können sich an
Aufgrund der geschäftlichen Anforderungen des Unternehmens dauerte die Implementierung eines kleinen Zahlungssystems. Obwohl es klein ist, verfügt es über alle erforderlichen Module, wie z. B. Kontosperre und Transaktionsgarantie , Fließwasserabgleich usw. sind im gesamten Entwicklungsprozess vollständig umgesetzt. Darüber hinaus handelt es sich bei den meisten davon um Forschungsarbeiten, die wenig praktischen Wert haben Ich habe es speziell herausgenommen, um es mit Ihnen zu teilen.
Dieses System kann als kleines Zahlungssystem oder als Zahlungsflusssystem verwendet werden, wenn Anwendungen von Drittanbietern an die offene Plattform angeschlossen werden.
Die ursprüngliche Forderung ist verantwortungsvoller, ich werde sie etwas vereinfachen:
Für jede Anwendung müssen externe Anforderungen bereitgestellt werden Guthaben erhalten, Geräte bezahlen, aufladen Warten Sie auf die Schnittstelle
Es gibt ein Programm im Hintergrund und die Liquidation wird am Ersten eines jeden Monats durchgeführt
Konten können eingefroren werden
Es ist notwendig, den Ablauf jedes Vorgangs aufzuzeichnen , und der Tagesablauf muss mit dem Initiator abgestimmt werden
Angesichts der oben genannten Anforderungen haben wir folgende Datenbank eingerichtet:
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_earn` ( `appid` int(10) UNSIGNED NOT NULL, `create_time` datetime NOT NULL, `balance` bigint(20) NOT NULL, `change_time` datetime NOT NULL, `seqid` int(10) NOT NULL DEFAULT 500000000, 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, `bill_ret` int(10) NOT NULL, `appid` int(10) UNSIGNED NOT NULL, `old_balance` bigint(20) NOT NULL, `price_info` text, `src_ip` char(128), PRIMARY KEY (`id`), UNIQUE KEY `unique_bill` (`bill_id`,`bill_channel`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE 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());
Die detaillierte Erklärung lautet wie folgt:
tb_status Anwendungsstatustabelle. Verantwortlich dafür, ob das Konto eingefroren ist und um welche Art von Konto es sich handelt (die eigentliche Anforderung besteht darin, dass die Anwendung zwei Konten haben darf, daher wird sie der Einfachheit halber hier nicht aufgeführt)
Appid-Anwendungs-ID
einfrieren, ob eingefroren werden soll
create_time Erstellungszeit
change_time letzte Änderungszeit
tb_account_earn Anwendungskontostandstabelle
appid Anwendungs-ID
Saldosaldo (Einheit ist Cent, verwenden Sie keinen Dezimalspeicher, da die Dezimalzahl selbst nicht genau ist; Darüber hinaus muss sich PHP auf einem 64-Bit-Computer befinden, der Bigint unterstützen kann >tb_assign-Tabelle zum Zuweisen der Serien-ID, bill_id von tb_bill. Muss durch tb_assign zugewiesen werden.
ID-Auto-Inkrement-ID
create_time-Erstellungszeit
tb_bill-Flusstabelle. Verantwortlich für die Aufzeichnung jedes Vorgangsablaufs ist hier nicht der Primärschlüssel, da die gleiche Rechnungsnummer zwei Flüsse haben kann:
ID-Auto-Inkrement-Sequenznummer
Rechnungs-ID-Seriennummer
Amtsbetrag Vorgang (dies ist notwendig, um zwischen positiv und negativ zu unterscheiden, hauptsächlich um die Änderung des Betrags während eines bestimmten Zeitraums direkt zu berechnen, wenn alle ausgewählt werden)
bill_info detaillierte Informationen des Vorgangs, z. B. 3 Webserver, 2 Datenbanken
bill_user Betriebsbenutzer
bill_time Die laufende Zeit
bill_type Der laufende Typ, der unterscheidet, ob Geld hinzugefügt oder abgezogen werden soll
bill_channel Die fließende Wasserquelle, wie Aufladen, Zahlung, Rollback, Abrechnung oder andere
bill_ret Der Rückgabecode des fließenden Wassers, einschließlich unverarbeitet und erfolgreich, ist fehlgeschlagen. Die Logik hier wird später erläutert Artikel, der bezahlt wird, wenn der Vorgang ausgeführt wird
src_ip client ip
tb_price Stückpreistabelle, zeichnet den Stückpreis der Maschine auf
Name eindeutige Kennung der Maschine
Preis Preis
Infobeschreibung
tb_applock-Sperrtabelle, die speziell dazu dient, gleichzeitige Schreibvorgänge für eine Anwendung zu vermeiden. Der Code zeigt später den Sperrstatus
appid application id
lock_mode an. Wenn es 0 ist, ist es gesperrt, wenn es 1 ist, ist es gesperrt
change_time Letzte Änderungszeit
OK, nachdem die Bibliothekstabelle entworfen wurde, werfen wir einen Blick auf einige der typischsten Vorgänge.
1. Zahlungsvorgang
Ich liste nur auf, wie ich es derzeit umsetze. Es ist vielleicht nicht das Beste, aber es sollte das wirtschaftlichste sein und den Bedürfnissen entsprechen.
Lassen Sie uns zuerst über den Anrufer sprechen:
Dann ist die entsprechende interne Logik des Zahlungssystems wie folgt (es werden nur Zahlungsvorgänge aufgeführt, Die Rollback-Logik ist ähnlich und die Flussprüfung besteht darin, zu überprüfen, ob der entsprechende Zahlungsfluss vorhanden ist):
Häufige Fehlerrückgabecodes können wie folgt ausreichen:
$g_site_error = array( -1 => '服务器繁忙', -2 => '数据库读取错误', -3 => '数据库写入错误', 0 => '成功', 1 => '没有数据', 2 => '没有权限', 3 => '余额不足', 4 => '账户被冻结', 5 => '账户被锁定', 6 => '参数错误', );
对于大于0的错误都算是逻辑错误,执行支付操作,调用方是不用记录流水的。因为账户并没有发生任何改变。
对于小于0的错误是系统内部错误,因为不知道是否发生了数据更改,所以调用方和支付系统都要记录流水。
对于等于0的返回,代表成功,两边也肯定要记录流水。
而在支付系统内部,之所以采用先写入流水,再进行账户更新的方式也是有原因的,简单来说就是尽量避免丢失流水。
最后总结一下,这种先扣钱,再发货,出问题再回滚的方式是一种模式;还有一种是先预扣,后发货,没有出问题则调用支付确认来扣款,出了问题就调用支付回滚来取消,如果预扣之后很长时间不做任何确认,那么金额会自动回滚。
二. 账户锁定的实现
这里利用了数据库的加锁机制,具体逻辑就不说了,代码如下:
class AppLock { function __construct($appid) { $this->m_appid = $appid; //初始化数据 $this->get(); } function __destruct() { $this->free(); } public function alloc() { if ($this->m_bGot == true) { return true; } $this->repairData(); $appid = $this->m_appid; $ret = $this->update($appid,APPLOCK_MODE_FREE,APPLOCK_MODE_ALLOC); if ($ret === false) { app_error_log("applock alloc fail"); return false; } if ($ret <= 0) { app_error_log("applock alloc fail,affected_rows:$ret"); return false; } $this->m_bGot = true; return true; } public function free() { if ($this->m_bGot != true) { return true; } $appid = $this->m_appid; $ret = $this->update($appid,APPLOCK_MODE_ALLOC,APPLOCK_MODE_FREE); if ($ret === false) { app_error_log("applock free fail"); return false; } if ($ret <= 0) { app_error_log("applock free fail,affected_rows:$ret"); return false; } $this->m_bGot = false; return true; } function repairData() { $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->where("appid",$appid); $db->where("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("repair applock error,appid:$appid"); return false; } return true; } private function 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"); return 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("applock insert fail:$appid"); return false; } //重新获取数据 $db->where('appid', $appid); $query = $db->get(TB_APPLOCK); if ($query === false) { app_error_log("AppLock get fail.appid:$appid"); return false; } if (count($query->result_array()) <= 0) { app_error_log("AppLock not data,appid:$appid"); return false; } } $applock_data = $query->row_array(); return $applock_data; } private function update($appid,$old_lock_mode,$new_lock_mode) { $db = APP_DB(); $db->where('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 applock error,appid:$appid,old_lock_mode:$old_lock_mode,new_lock_mode:$new_lock_mode"); return false; } return $db->affected_rows(); } //是否获取到了锁 public $m_bGot = false; public $m_appid; }
为了防止死锁的问题,获取锁的逻辑中加入了超时时间的判断,大家看代码应该就能看懂
三. 对帐逻辑
如果按照上面的系统来设计,那么对帐的时候,只要对一下两边成功(即bill_ret=0)的流水即可,如果完全一致那么账户应该是没有问题的,如果不一致,那就要去查问题了。
关于保证账户正确性这里,也有同事跟我说,之前在公司做的时候,是采取只要有任何写操作之前,都先取一下流水表中所有的流水记录,将amt的值累加起来,看得到的结果是否和余额相同。如果不相同应该就是出问题了。
select sum(amt) from tb_bill where appid=1;
所以这也是为什么我在流水表中,amt字段是要区分正负的原因。
总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。
相关推荐:
Das obige ist der detaillierte Inhalt vonPHP-Zahlungssystemdesign und typische Fälle (empfohlen). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!