PHP Share 26: Payment system Design

Source: Internet
Author: User
Tags php payment system

Design and implementation of a typical PHP payment system

Reference: blog.sina.com.cn/s/blog_81f6205801017ec8.html

Payment development:

Http://www.cnblogs.com/txw1958/p/wxpayv3-bill.html

Drew 2 weeks to write, small perfectly formed, a variety of necessary modules such as account lock, transactional assurance, water reconciliation, etc. are all complete implementation, the whole development process has a lot of experience, plus in the online search, most of them are some research papers, the actual use of small value, So this time to take it out and share it 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:

    1. For each application, external needs to provide access to balance, payment equipment, recharge and other interfaces
    2. There are procedures in the background, a monthly number for liquidation
    3. Account can be frozen
    4. 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 (ten) UNSIGNED not NULL,
' Freeze ' int (ten) is 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 (ten) UNSIGNED not NULL,
' Create_time ' datetime not NULL,
' Balance ' bigint () not NULL,
' Change_time ' datetime not NULL,
' Seqid ' int (ten) is not NULL DEFAULT 500000000,

PRIMARY KEY (' AppID ')
) Engine=innodb DEFAULT Charset=utf8;

CREATE TABLE ' app_margin '. ' Tb_bill ' (
' ID ' intauto_increment not NULL,
' bill_id ' int (ten) is not NULL,
' Amt ' bigint () not NULL,
' Bill_info ' text,

' Bill_user ' char (128),
' Bill_time ' datetime not NULL,
' Bill_type ' int (ten) is not NULL,
' Bill_channel ' int (ten) is not NULL,
' Bill_ret ' int (ten) is not NULL,

' AppID ' int (ten) UNSIGNED not NULL,
' Old_balance ' bigint () 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 ' intauto_increment not NULL,
' Assign_time ' datetime not NULL,

PRIMARY KEY (' id ')
) Engine=innodb DEFAULT Charset=utf8;

CREATE TABLE ' app_margin '. ' Tb_price ' (
' Name ' char (+) is not NULL,
' Price ' int (ten) is not NULL,
' Info ' Textnot NULL,

PRIMARY KEY (' name ')
) Engine=innodb DEFAULT Charset=utf8;

CREATE TABLE ' app_margin '. ' Tb_applock ' (
' AppID ' int (ten) UNSIGNED not NULL,
' Lock_mode ' int (ten) is 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 ());

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 permissions ',
3=> ' Insufficient balance ',
4=> ' account is frozen ',
5=> ' account is locked ',
6=> ' parameter error ',
);
    1. 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.
    2. 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.
    3. 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:

Classapplock
{
Function__construct ($appid)
{
$this->m_appid = $appid;
Initializing data
$this->get ();
}

Function__destruct ()
{
$this->free ();
}


Publicfunction 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;
}

Publicfunction 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;
}

Functionrepairdata ()
{
$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;
}

Privatefunction 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;
}

Get Data back
$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;
}

Privatefunction 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 applockerror,appid: $appid, Old_lock_mode: $old _lock_mode,new_lock_mode: $new _lock_mode");
return false;
}
return $db->affected_rows ();
}

Whether the lock was acquired
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.

                                   1
                                   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.

OK, the whole article is written very long, I hope to adhere to the students have been reading help.

PHP Share 26: Payment system Design

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.