Design and implementation of a typical PHP payment system

Source: Internet
Author: User
Tags array count datetime insert php payment system query return rollback

With the company's business needs, it took two weeks to implement a small payment system, though small spite, all kinds of necessary modules such as account lock, transactional assurance, running water reconciliation are fully realized, the whole development process has a lot of experience accumulation, plus online search for a while, most of them are research papers, The actual use of the value is not very small, so this is deliberately taken 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 said:

    1. For each application, external need to provide access to balance, payment equipment, recharge and other interfaces
    2. The backstage has the procedure, the monthly number carries on the liquidation
    3. Accounts can be frozen
    4. Need to record every operation of the water, the daily water must be reconciled with the initiator

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

					1
2
3
4
5
6 7 8 9 (16) He ' is in the
same
A
43-A-I-M By the----
-
65 of the same
					CREATE TABLE ' app_margin '. ' Tb_status ' (' AppID ' int () UNSIGNED not NULL, ' freeze ' int (a) NOT null DEFAULT 0, ' create_time ' datetime NOT NULL, ' change_time ' datetime NOT NULL,   PRIMARY KEY (' AppID ') engine=in
Nodb DEFAULT Charset=utf8;    CREATE TABLE ' app_margin '. ' Tb_account_earn ' (' AppID ' int () UNSIGNED not NULL, ' create_time ' datetime not NULL, ' balance ' bigint not null, ' change_time ' datetime NOT NULL, ' seqid ' int (a) NOT null DEFAULT 500000
  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, ' AM T ' bigint not NULL, ' Bill_info ' text,   ' Bill_user ' char (128), ' bill_time ' datetime NOT NULL, ' Bi Ll_type ' int ' not NULL, ' Bill_channel ' int (a) not null, ' Bill_ret ' int (a) not null,   ' AppID ' int (10 UNSIGNED not NULL, ' old_balance ' BigiNT not NULL, ' price_info ' text,   ' src_ip ' char (128),   PRIMARY key (' id '), UNIQUE key ' Uniq
Ue_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 (a) not null, ' info ' text
Not NULL,   PRIMARY KEY (' name ')) Engine=innodb DEFAULT Charset=utf8;   CREATE TABLE ' app_margin '. ' Tb_applock ' (' AppID ' int () UNSIGNED not null, ' Lock_mode ' int (a) NOT null DE
FAULT 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 ());

Detailed explanations are as follows:

  • Tb_status the status table applied. Responsible for the account is frozen, what type of account (the real demand is that the application may have two types of accounts, here for the simple so not listed)
    • AppID Application ID
    • Whether the freeze is frozen
    • Create_time creation Time
    • Change_time Last Modification time
  • Account balance sheet for Tb_account_earn application
    • AppID Application ID
    • Balance balance (unit is divided, do not use decimal storage, because the decimal itself is not accurate; In addition, PHP to 64-bit machine to support bigint)
    • Create_time creation Time
    • Change_time Last Modification time
    • Seqid Operation serial Number (anti concurrency, every update will be + 1)
  • Tb_assign allocation of Water ID table, Tb_bill bill_id must be tb_assign allocated
    • ID self-Increasing ID
    • Create_time creation Time
  • Tb_bill water table. Responsible for recording each operation of the running water, where the bill_id is not a primary key, because the same bill_id may have to pay and roll back two of streams
    • ID self-increasing serial number
    • bill_id Serial Number
    • The amount of AMT operation (this is to distinguish between positive and negative, mainly for select all when you can directly calculate the amount of time changes)
    • Details of bill_info operations, such as 3 sets of webserver,2 db
    • Bill_user Operation user
    • Bill_time Running time
    • Bill_type flow type, to distinguish whether to add money or reduce money
    • Bill_channel flow sources, such as recharge, pay, rollback, settlement or other
    • Bill_ret The return code of the stream, including unhandled, successful, and failed, the logic here will be explained later
    • AppID Application ID
    • Account balance prior to old_balance operation
    • Price_info Record the unit price of the item being paid when the record operation occurs
    • SRC_IP Client IP
  • Tb_price Unit Price list, record the unit price of the machine
    • Name Machine Unique identification
    • Price prices
    • Info description
  • Tb_applock locks the table, which is designed to avoid concurrent write operations on an application, and the specific code is shown later
    • AppID Application ID
    • Lock_mode lock status. 0 is locked, 1 is locked
    • Change_time Last Modification time

OK, after the library table is designed, let's take a look at some of the most typical operations.

I. Payment operations

I've only listed the way I'm currently implementing it, probably not the best, but it should be the most economical and satisfying demand.

First the caller here, the logic is as follows:

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

The common error-return code may be sufficient:

					1
2
3
4
5
6 7 8 9 (14)
					$g _site_error = Array (
    -1 => ' server busy ',
    -2 => ' database read error ',
    -3 => ' database write error ',
    0 => ' success ',
    1 => ' no data ',
    2 => ' no Permissions ',
    3 => ' balances not Enough ',
    4 => ' account Frozen ',
    5 => ' account Locked ',
    6 => ' parameter error ',
);
    1. For errors greater than 0, it is a logical error to perform a payment operation, and the caller does not have to record the running water. Because the account has not changed anything.
    2. An error less than 0 is an internal system error because the caller and the payment system have to log running water because they do not know if data changes have occurred.
    3. For the return equal to 0, the representative succeeds, both sides also must record the water.

And in the payment system, the reason to use the first write water, and then update the account of the way is also a reason, simply to avoid the loss of water.

Finally summed up, the first deduction of money, then shipped, the problem and then roll back the way is a model; there is a first button, after delivery, no problem is called payment confirmation to deduct, out of the question on the call to pay rollback to cancel, if the withholding after a long time does not make any confirmation, then the amount will be automatically rolled back.

Two. Account Lockout implementation

This uses the lock mechanism of the database, the concrete logic does not say, the code is as follows:

					1
2
3
4
5
6
7 8 9
30 (a) (a) This is the same as
the
57 in all of the same. The same is the same as
the
The Yi-An-yi-An-yi-an-
ba
84 The same as the same as
the
102 (
108)
109
112
113
114
116
117
118 119 120
121
122
123
124-126
127
128 129 130 131
132
134
135
136
137
138 139 140 142
143
144
145
146
147 148 149-151
152
153
154
156
157
158 159 160 161 162
163
164
					Class Applock {function __construct ($appid) {$this->m_appid = $appid;
    Initialization 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 <= 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;  //re-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 ()) <= 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 ();   $d
        B->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,ne W_Lock_mode: $new _lock_mode ");
        return false;
    return $db->affected_rows ();
 //Whether the lock public $m _bgot = False was acquired;
  Public $m _appid; }

In order to prevent the deadlock problem, the logic of acquiring the lock has added the judgment of timeout time, we can see the code should be able to understand

Three. Reconciliation logic

If you follow the above system to design, then the account, as long as the two sides of the success (that is, bill_ret=0) of the water can, if the full agreement then the account should be no problem, if not consistent, it will be to check the problem.

On the guarantee of the correctness of the account here, there are colleagues to say to me, before the company to do, is to take as long as there is any write operation before, all the water table to take all the records, the Amt of the value of the sum up to see whether the results are the same as the balance. If it's not the same, it should be a problem.

					1
					Select SUM (AMT) from Tb_bill where appid=1;

So that's why I'm in the flow table, the Amt field is to distinguish between positive and negative reasons.

OK, the whole article written very long, I hope to adhere to read the students have helped.



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.