REGISTER WITH PAYPAL TUTORIAL (2/3): A Real Register with PayPal Project
Explain how PayPal works (IPN and PDT process). Chapter One Chapter Three
Chapter Two
This chapter introduces a real project: “registration with payment”, from start to end, for better explaining PayPal account setup and integration with register form and database.
Project Scenario
- First, we have a registration form.
- After completing the form correctly (all validation passed), user clicks on the ‘Register’ button.
- Then redirect to PayPal, user pay for the registration fee.
- After paid, PayPal will redirect to result page, and 10 seconds auto redirect back to our website, then PayPal PDT will process the payment record.
- But, user may close the browser, so we have to implement PayPal IPN for backup plan.
Database Structure
E-R Diagram:
- temp_register table: temporary store user account and user password, wait for payment. If paid, the tuple will be deleted, and move to users table.
- payment table: uid is a foreign key referencing to users, to connect user information and payment information.
- users table: store users information, token is verification token in confirmation email. If user verified their user account, verified will be set as 1.
Database Schema:
CREATE TABLE IF NOT EXISTS `payment` (
`payId` int(11) NOT NULL AUTO_INCREMENT,
`timestamp` bigint(20) DEFAULT NULL,
`paid` float DEFAULT NULL COMMENT 'user paid amount returned by paypal',
`bankFee` float DEFAULT NULL,
`currency` varchar(4) DEFAULT NULL,
`txnId` varchar(32) DEFAULT NULL COMMENT 'Transaction ID: specify single unique transaction from paypal. if this field is NOT NULL, means this payment has been process already. So if IPN returns to PHP, we can refuse to update our database.',
`status` varchar(16) DEFAULT NULL,
`uid` int(11) DEFAULT NULL COMMENT 'FK to users PK',
PRIMARY KEY (`payId`),
KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `temp_register` (
`tRegId` int(11) NOT NULL AUTO_INCREMENT,
`fullName` varchar(255) DEFAULT NULL,
`uAcc` varchar(255) DEFAULT NULL,
`uPwd` varchar(32) DEFAULT NULL,
PRIMARY KEY (`tRegId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='prevent unpaid user take uAcc(UNIQUE) in our users table' AUTO_INCREMENT=1 ;
CREATE TABLE IF NOT EXISTS `users` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`fullName` varchar(255) DEFAULT NULL,
`uAcc` varchar(255) NOT NULL,
`uPwd` varchar(32) NOT NULL,
`token` varchar(32) DEFAULT NULL,
`verified` tinyint(1) NOT NULL DEFAULT '0',
`priviledge` enum('delegate','admin','developer') NOT NULL DEFAULT 'delegate',
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
ALTER TABLE `payment`
ADD CONSTRAINT `payment_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `users` (`uid`);
User Interface
Project Workflow
- Fill the form, and all inputs are validated.
- Click Register button, redirect to PayPal.
- PayPal Sandbox ‘Pay Now’:
- Redirect PayPal result page.
- Wait for redirect (PDT works) show success/fail page, or close browser (IPN works).
Setup PayPal Sandbox
Before we start coding, we need to setup PayPal Sandbox account first.
Suppose you have registered a PayPal developer account, and have created one business user account and one buyer account in Sandbox.
Then select business account, and click on the ‘Enter Sandbox Test Site’ Button.
You can see the main panel pop-out page:
Then you can see all information and selling preference settings.
So let’s setup all the three options one by one in order.
1. Enable PDT and Settings
Setup your PDT handler function calling URL.
2. Enable IPN and Settings
Setup your IPN handler function calling URL.
2. Create a PayPal Button and PayPal Parameter Settings
After you saving your change, you can see the source code of your PayPal Button:
It is easy to find out that the button is actually a form, so we need to POST data using its inputs.
When we generate our ‘register’ button, the redirect URL, should contain ‘&cmd=_s-xclick’ and ‘&hosted_button_id=HA9DZBCKXKCL2’.
Now, PayPal Sandbox account has been setup. Then start to code your PDT and IPN handlers.
PDT Handler Function
Source Code:
/**
* PAYPAL: PDT HANDLER:
* ====================
* called by PayPal, send tokens back
* get payment details and payment result
* @return $ret array contains result true/false, and user account or error message
*/
private function _PDT()
{
// some indexes can not be missing:
$ruler = array(
'tx', // token from paypal
);
if(count(array_diff($ruler, array_keys($_GET))))
{
return array('result' => false, 'error' => 'Index missing ... ', 'index' => $_GET, 'missing' => array_diff($ruler, array_keys($_GET)));
}
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-synch';
// get token and prepare request url (send back to paypal)
$tx_token = $_GET['tx'];$auth_token = "_PJaHiwRfwMmWzW-9nuPuSguYxC-1d9KpxaasaNANtIvyOcmqY6jXNkRmxW";
// $auth_token = "OxDenzKmrWPyEXU0YzIg2zs-VAe7ufCADyjbfxF_RpREL4rLEslZrSa21R4";
$req .= "&tx=$tx_token&at=$auth_token";
// post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.0rn";
$header .= "Host: www.sandbox.paypal.comrn";
// $header .= "Host: www.paypal.comrn";
$header .= "Content-Type: application/x-www-form-urlencodedrn";
$header .= "Content-Length: " . strlen($req) . "rnrn";
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30); // open socket
// $fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30); // open socket
if (!$fp)
{
// HTML FAIL
return array('result' => false, 'error' => 'HTTP error ... ');
}
else
{
fputs ($fp, $header . $req);
// read the body data
$res = '';
$headerdone = false;
while (!feof($fp))
{
$line = fgets ($fp, 1024);
if (strcmp($line, "rn") == 0)
{
$headerdone = true; // read the header
}
else if ($headerdone)
{
$res .= $line; // header has been read. now read the contents
}
}
// parse the data
$lines = explode("n", $res);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0)
{
for ($i=1; $i_validatePaypal($keyarray);
}
// log for manual investigation
else if (strcmp ($lines[0], "FAIL") == 0)
{
// skipped
return array('result' => false, 'error' => 'Transaction failed ... ');
}
}
fclose ($fp);
return $ret;
}
Explanation:
PayPal call PDTHandler() function, then this handler function process _PDT(). As you see, it receives parameters provided by PayPal, from the URL ($_GET). So we POST token and tx back to PayPal, via fsock ssl. Then PayPal will return payment record, and payment result (SUCCESS/FAIL). _PDT() passes those data to _validatePaypal() which saves data into database. Then, page redirect according to the return.
IPN Handler Function
Source Code:
/**
* PAYPAL: IPN HANDLER:
* ====================
* called by PayPal, send POSTed payment data back (handshake)
* get payment result
* return: payment VERIFIED: array('result' => true/false, and other user details)
* payment INVALID: false // no further process (see handler in Register Module)
* TODO: return true if success, then send email to buyers
*/
private function _IPN()
{
// get IPN data
$postData = $_POST;// read the post from PayPal system and add 'cmd'
$req = 'cmd=' . urlencode('_notify-validate');
foreach ($postData as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// CURL: copy from paypal sample code
$url= 'https://www.sandbox.paypal.com/cgi-bin/webscr';
// $url= 'https://www.paypal.com/cgi-bin/webscr';
$curl_result=$curl_err='';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/x-www-form-urlencoded", "Content-Length: " . strlen($req)));
curl_setopt($ch, CURLOPT_HEADER , array('Host: www.sandbox.paypal.com'));
// curl_setopt($ch, CURLOPT_HEADER , array('Host: www.paypal.com'));
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$res = @curl_exec($ch);
$curl_err = curl_error($ch);
curl_close($ch);
$keyarray = $postData;
if (strcmp (substr($res, '-8'), "VERIFIED") == 0)
{
// validate paypal information
return $this->_validatePaypal($keyarray);
}
elseif (strcmp (substr($res, '-7'), "INVALID") == 0)
{
// log for manual investigation
return false;
}
// return $ret; // Do not need to return, because IPN runs in background
}
Explanation:
PayPal call IPNHandler() function, then this handler function process _ IPN (). As you see, it receives data provided by PayPal, from the request ($_POST). So we POST payment details back to PayPal, via cURL, this is the handshake process. Then PayPal send back payment result (VERIFIED/INVALID). If it’s verified, _IPN() passes those data to _validatePaypal() which saves data into database.
In the zip file, it contains templates, javascript files, css, bootstrap, jquery, debug plugin and smarty plugin, and all core php source code.