Paypal integration with QCodo

This article details a basic Paypal IPN processor implemented with QCodo. Details include the database schema, a simple form to direct the user to paypal for payment completion, and the paypal IPN processor which will store the transaction details in the previously defined database. The code for this example can be downloaded here.

The database schema

The MySQL script to create the database can be found here.

There are several “type” tables which are used to define the various bits of information for which there is a fixed set of possibile values. These tables are all defined similar to the txn_type table below. This was done to take advantage of the type table code generation in QCodo.

CREATE TABLE txn_type (
id int(20) UNSIGNED NOT NULL auto_increment PRIMARY KEY,
name varchar(20) NOT NULL UNIQUE
) TYPE=InnoDB;
INSERT INTO txn_type (`name`) VALUES ("cart"), ("express_checkout"), ("send_money");
INSERT INTO txn_type (`name`) VALUES ("virtual_terminal"), ("web_accept");

Since there are several possibilities for the data that Paypal send via the ipn function, the data is broken up into several tables. An individual transaction will consist of records in some of these tables, but not necessarily all of them.

The primary table is the transaction table. There will be a row in this table for every transaction processed. The transaction table is created like this:

CREATE TABLE transaction (
id int(20) UNSIGNED NOT NULL auto_increment PRIMARY KEY,
buyer_id int(20) UNSIGNED NOT NULL,
seller_id int(20) UNSIGNED NOT NULL,
authorization_id int(20) UNSIGNED default NULL,
txn_id varchar(19) NOT NULL,
txn_type int(20) UNSIGNED NOT NULL,
num_cart_items int(12) UNSIGNED default NULL,
parent_txn_id varchar(19) default NULL,
payment_date varchar(28) default NULL,
payment_status int(20) UNSIGNED NULL,
payment_type int(20) UNSIGNED NULL,
pending_reason int(20) UNSIGNED NULL,
reason_code int(20) UNSIGNED NULL,
remaining_settle float NULL,
shipping float NULL,
transaction_entity int(20) UNSIGNED NULL,
exchange_rate float NULL,
mc_currency int(20) UNSIGNED NOT NULL,
mc_fee float NULL,
mc_gross float NOT NULL default 0,
mc_handling float default NULL,
mc_shipping float default NULL,
settle_amount float default NULL,
settle_currency int(20) UNSIGNED default NULL,
invoice varchar(127) default NULL,
custom varchar(255) default NULL,
memo varchar(255) default NULL,
option_name_1 varchar(64) default NULL,
option_name_2 varchar(64) default NULL,
option_selection_1 varchar(200) default NULL,
option_selection_2 varchar(200) default NULL,
tax float default NULL,
INDEX (buyer_id),
INDEX (txn_id),
INDEX (txn_type),
INDEX (authorization_id),
INDEX (payment_status),
INDEX (payment_type),
INDEX (pending_reason),
INDEX (reason_code),
INDEX (transaction_entity),
INDEX (mc_currency)
) TYPE=InnoDB;
ALTER TABLE transaction ADD FOREIGN KEY (buyer_id) REFERENCES buyer (id);
ALTER TABLE transaction ADD FOREIGN KEY (seller_id) REFERENCES seller (id);
ALTER TABLE transaction ADD FOREIGN KEY (authorization_id) REFERENCES authorization (id);
ALTER TABLE transaction ADD FOREIGN KEY (txn_type) REFERENCES txn_type (id);
ALTER TABLE transaction ADD FOREIGN KEY (payment_status) REFERENCES payment_status_type (id);
ALTER TABLE transaction ADD FOREIGN KEY (payment_type) REFERENCES payment_type (id);
ALTER TABLE transaction ADD FOREIGN KEY (pending_reason) REFERENCES pending_reason_type (id);
ALTER TABLE transaction ADD FOREIGN KEY (reason_code) REFERENCES reason_code_type (id);
ALTER TABLE transaction ADD FOREIGN KEY (mc_currency) REFERENCES currency_type (id);
ALTER TABLE transaction ADD FOREIGN KEY (transaction_entity) REFERENCES transaction_entity_type (id);

In addition to a row in the transaction table, each transaction will also have a row in the seller table and a row in the buyer table. As expected, these table simply store seller and buyer specific data for each transaction.

A sample form

Below is a QPanel subclass which simply contains the “Buy” button

class PaypalTestPanel extends QPanel {
protected $btnBuy;

public function __construct($objParentObject, $strControlId = null) {
try {
parent::__construct($objParentObject, $strControlId);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}

$this->AutoRenderChildren = true;

$this->btnBuy = new QImageButton($this);
$this->btnBuy->ImageUrl = “http://images.paypal.com/images/x-click-but3.gif”;
$this->btnBuy->AddAction(new QClickEvent(), new QServerControlAction($this, ‘btnBuy_Click’));
}

public function PreRender() {
}

public function btnBuy_Click($strFormId, $strControlId, $strParameter){
$_SESSION[’item_name’] = “widgets from assortedbits”;
$_SESSION[’item_number’] = “wid01″;
$_SESSION[’quantity’] = “2″;
$_SESSION[’amount’] = “3.75″;
QApplication::Redirect(__VIRTUAL_DIRECTORY__ . __SUBDIRECTORY__ . ‘/paypal/paypalsubmit.php’);
}
}
?>

The __construct method creates a QImageButton which pulls an image directly from the Paypal website. This is the Paypal recommended method for getting the button image. It allows your button image to stay up to date is Paypal changes the standard buttons.

The interesting part of this code is the btnBuy_Click method. Paypal transactions are initiated by POSTing a form containing all of the information to the paypal website. QCodo has no facility for POSTing to an external website. All POSTs go back to the form from processing and possible redirection, etc. Redirection is a GET not a POST, so that won’t work as a means to POST the data to Paypal. Searching the QCodo forums, I found a simple solution which I adapted to my needs here.

As you can see in the btnBuy_Click method, we store all of the pertinent information in the $_SESSION and redirect to a form called paypalsubmit.php. paypalsubmit.php simply pulls the data out of the $_SESSION and creates a form which it immediately POSTs to Paypal. The entire process is transparent to the user. One additional piece of information is added to the POST. The form also specifies “notify_url” which is the URL to our ipn processor function. Paypal will POST all of the transaction details to this URL upon completion of the transaction.

The IPN Processor

The IPN processor (paypalipn.php) is a very simple function which simply logs the data that Paypal POSTed and instantiates an instance of IPNProcessor to actually parse the data and store it in the database. This is a very straightforward process. IPNProcessor assumes that all possible data is present and tries to store it all. Any pieces that are not there will simply not get created in the corresponding table.

An important design note is the fact that the IPNProcessor has only one task. It simply stores all of the data in the appropriate tables. It makes no assumptions about what should happen as a result of a particular transaction, it simply verifies and stores the data.

Data class modifications

In order to make the sample code work after code generation, you will have make a small addition to each of the Type classes. In order to convert the various constants that Paypal returns into the appropriate Type index, IPNProcessor uses the GetKey method of the Type class in question. Below is the GetKey method that was added to AddressStatusType. Each Type class has a similar method added.

public static function GetKey($statusStr) {
$key = array_search($statusStr, AddressStatusType::$NameArray);
return $key;
}

Where to go from here

The sample code only handles a small portion of the possible transaction types that Paypal can return. There is much code to write in order to handle the remaining transaction types.

This code could easily be integrated into a QCodo ecommerce project as the Paypal handler. In order to do so, a means of reconciling the data received with what was expected would need to be implemented.

Reporting capabilities could be added to help in tracking status and types of Paypal transaction processed.

This example is only the beginning of a full implementation, but hopefully it gives you a starting point for your own implementation.

Other sources of information

The Paypal Developer Network
paypaldev.org

Leave a Reply