PHP payment system design and typical case sharing _php example

Source: Internet
Author: User
Tags php payment system
Due to the company's business needs, spend two weeks to achieve a small payment system, perfectly formed, a variety of necessary modules such as account lock, transactional assurance, water reconciliation, etc. are fully realized, the whole development process has accumulated a lot of experience, plus on-line search, most of them are some research papers, The actual use of value is not very small, so this time deliberately to come out and share with you.
The system can be used as a small payment system or as a payment flow system for third-party applications when accessing an open platform.
The original demand is more responsible, I simplified a little:

For each application, external needs to provide access to balance, payment equipment, recharge and other interfaces
There are procedures in the background, a monthly number for liquidation
Account can be frozen
The flow of each operation needs to be recorded, and the daily flow is reconciled with the initiator.

For the above requirements, we set up the following database:

CREATE TABLE ' app_margin '. ' Tb_status ' (' AppID ' int () UNSIGNED not NULL, ' freeze ' int (TEN) NOT null DEFAULT 0, ' Create_ti   Me ' datetime NOT NULL, ' Change_time ' datetime is NOT NULL, PRIMARY KEY (' AppID ')) Engine=innodb DEFAULT Charset=utf8; CREATE TABLE ' app_margin '. ' Tb_account_earn ' (' AppID ' int (ten) UNSIGNED not null, ' create_time ' datetime NOT NULL, ' balance ' bigint ' is not null, ' Change_time ' datetime is NOT NULL, ' seqid ' int (ten) 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 (ten) not NULL, ' Amt ' bigint () ' N ' ULL, ' bill_info ' text, ' Bill_user ' char (+), ' bill_time ' datetime NOT NULL, ' Bill_type ' int (ten) not NULL, ' Bill_channel ' int ' is not null, ' Bill_ret ' int (ten) is not null, ' AppID ' int (ten) UNSIGNED not null, ' Old_balance ' bigint (a) not NULL, ' Price_info ' text, ' Src_ip ' char ($), 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 (+) ' NOT null, ' price ' int (ten) ' NOT NULL, ' info ' text is not NULL, Primar   Y KEY (' name ')) Engine=innodb DEFAULT Charset=utf8; CREATE TABLE ' app_margin '. ' Tb_applock ' (' AppID ' int () UNSIGNED not null, ' Lock_mode ' int (ten) NOT null DEFAULT 0, ' Chang   E_time ' datetime not NULL, PRIMARY KEY (' AppID ')) Engine=innodb DEFAULT Charset=utf8; INSERT ' App_margin '. ' Tb_assign ' (' id ', ' assign_time ') VALUES (100000000,now ());

Detailed explanations are as follows:
The status table for the Tb_status application. is responsible for the account is frozen, the type of account (the real demand is the application may have two kinds of accounts, here is simple so not listed)
AppID App ID
Freeze is frozen
Create_time creation Time
Change_time Last Modified Time
Account balance list for Tb_account_earn applications
AppID App ID
Balance balance (in points, do not use fractional storage, because the decimal itself is not accurate; PHP must be in the 64-bit machine to support bigint)
Create_time creation Time
Change_time Last Modified Time
Seqid Operation Serial Number (anti-concurrency, each update will be + 1)
Tb_assign the table where the flow ID is assigned, Tb_bill bill_id must be assigned with Tb_assign
ID self-Increment ID
Create_time creation Time
Tb_bill water table. Responsible for recording each operation of the water, where the bill_id is not the primary key, because the same bill_id may have to pay and rollback two streams
ID self-increment serial number
bill_id Serial Number
The amount of AMT operations (this is to distinguish between positive and negative, mainly for select all when you can directly calculate the amount of time changes)
Details of the bill_info operation, such as 3 units of webserver,2 DB
Bill_user Operating User
Bill_time Water Time
Bill_type flow type, whether to add money or reduce money
Bill_channel sources of water, such as recharge, payment, rollback, settlement or other
Bill_ret The return code of the water, including the unhandled, successful, and failed, the logic here will be explained later
AppID App ID
Old_balance account balance before the operation occurs
Price_info Record the unit price of the item to be paid when the recording operation occurs
SRC_IP Client IP
Tb_price Unit Price list, recording the unit price of the machine
Name Machine uniquely identifies
Price
Info description
Tb_applock locks the table, which is designed to avoid concurrent writes to an application, and the specific code is shown later
AppID App ID
Lock_mode locked state. 0 is locked, 1 is locked
Change_time Last Modified Time
OK, when the library table is designed, let's take a look at some of the most typical operations.

I. Payment operations
I'm only listing the way I'm doing now, probably not the best, but it's the most economical and satisfying.
First say the caller here, the logic is as follows:

Then the corresponding payment system internal logic is as follows (only the payment operation, the rollback logic is similar, the flow check is to check whether the corresponding payment flow exists):

The usual error return code may be sufficient as follows:

$g _site_error = Array (-1 = ' Server Busy ',-2 = ' database read error ',-3 = ' Database write error ',   0 = ' success ',   1 = ' no data ', 2 = > ' No rights ', 3 = ' Insufficient balance ', 4 ' account is frozen ', 5 = ' account is locked ', 6 = ' parameter error ', ';

For errors greater than 0 is a logical error, performing a payment operation, the caller does not have to log the flow. Because there is no change in the account.
Errors that are less than 0 are internal to the system, because data changes are not known, so both the caller and the payment system record the flow.
For returns equal to 0, the representation succeeds, and both sides are sure to record the flow of water.
In the payment system, the reason is to write the water first, then the account Update method is also a cause, simply to avoid the loss of water.
Finally summed up, this first deduction of money, re-delivery, the problem of the way back to roll is a mode; there is a pre-buckle, after delivery, no problem is called to pay confirmation to deduct, out of the problem on the call to pay back to cancel, if the pre-deduction for a long time without any confirmation, then the amount will automatically rollback.

Two. Implementation of account lockout
This uses the locking mechanism of the database, the specific logic is not said, the code is as follows:

Class AppLock {function __construct ($appid) {$this->m_appid = $appid;//Initialize Data $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 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 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 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 ()) $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;} Retrieve Data $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 ( )) 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 ();   }//whether to acquire the lock public $m _bgot = false; Public $m _appid; }

In order to prevent the deadlock problem, the logic to get the lock added a time-out judgment, you see the code should be able to understand

Three. Reconciliation logic
If according to the above system to design, then the reconciliation, as long as the two sides of the success (ie, bill_ret=0) of the water can, if fully consistent then the account should be no problem, if not consistent, it is necessary to check the problem.
About guaranteed account correctness here, too, some colleagues told me that before the company did, is to take as long as there is any write operation, first fetch all the water records in the water table, the value of the Amt is summed up, see whether the results and the balance of the same. If not the same should be a problem.
Select SUM (AMT) from Tb_bill where appid=1;
So that's why I'm in the water table, the Amt field is the reason to differentiate between positive and negative.

The above is the whole content of this article, I hope that everyone's learning has helped, more relevant content please pay attention to topic.alibabacloud.com (www.php.cn)!

  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.