Feb 21

Zend Framework and Cybersource API

Zend Framework and Cybersource API. Oddly enough, you don’t need the Zend_Soap_Client class for this. We are better off just using the SoapClient class provided by PHP. We can then authorize and capture funds, create subscriptions, and access reporting features using the Cybersource API as described in their documentation.

Here is the code:

<?php
define('CYBERSOURCE_MERCHANT_ID', 'YourMerchantID');
define('CYBERSOURCE_TRANSACTION_KEY', 'YourTransactionKey');
 
// The test URL is https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.26.wsdl
// The live URL is https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.26.wsdl
 
class Cybersource_Client extends SoapClient
{
 
	/**
	 * We need to override this method in order to set up the SOAP header 
	 * with our Cybersource Merchant ID and Transaction Key.
	 */
	public function __doRequest($request, $location, $action, $version, $one_way = null) {
		$settings = Zre_Config::getSettingsCached();
 
		$merchant_id = (string) CYBERSOURCE_MERCHANT_ID;
		$transaction_key = (string) CYBERSOURCE_TRANSACTION_KEY;
 
		$soapHeader = "<SOAP-ENV:Header xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " . 
				"xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">" . 
				"<wsse:Security SOAP-ENV:mustUnderstand=\"1\"><wsse:UsernameToken><wsse:Username>$merchant_id</wsse:Username>" . 
				"<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">$transaction_key</wsse:Password>" . 
				"</wsse:UsernameToken></wsse:Security></SOAP-ENV:Header>";
 
		$requestDOM = new DOMDocument('1.0');
		$requestDOM->loadXML($request);
 
		$soapHeaderDOM = new DOMDocument('1.0');
		$soapHeaderDOM->loadXML($soapHeader);
 
		$node = $requestDOM->importNode($soapHeaderDOM->firstChild, true);
		$requestDOM->firstChild->insertBefore($node, $requestDOM->firstChild->firstChild);
 
		$request = $requestDOM->saveXML();
 
		return parent::__doRequest($request, $location, $action, $version);
	}
}
Sep 09

A jQuery Google Maps Plug-in example

jQuery Google Maps plug-in example. This code demonstrates how to achieve integrating a Google Map with custom Markers using jQuery. The two snippets here target jQuery 1.4.x and 1.5.x or higher respectively, but the html snippet further down the page can include either version of this plugin.

NOTE: You must include the jQuery UI library in order to use the newer version of this example plugin, as it relies on the jQuery.widget() call to construct itself.

jQuery 1.4.1 compatible:

/**
 * A generic jQuery Google (r) map plug-in. Requires inclusion of the following javascript file before being included:  http://maps.google.com/maps/api/js?sensor=false
 * http://www.alexventure.com
 * 
 * Copyright (c) Alexventure.com
 * Dual licensed under the MIT and GPL licenses. (Re)Use as you wish.
 * http://docs.jquery.com/License
 * 
 * Date: 2011-09-07 23:32:00 -0700 (Wed, 07 Sep 2011)
 * Revision: 0001
 */
/**
 * The 'points' variable contains an indexed array of objects containing marker data:
 * 
 * [
 *	{
 *		text: '<span>Boudin Bakery at Embarcadero</span><br /><p>Go upstairs for the Boudin Bakery restaurant, and order a clam chowder!</p>',
 *		lat: '37.795203',
 *		lng: '-122.395849'
 *	},
 *	{
 *		text: '<span>Gott's Roadside</span><br />Delicious burgers and more!</p>',
 *		lat: '37.77493',
 *		lng: '-122.419416'
 *	},
 *	...
 * ]
 */
(function(jQuery) {
	var widget = null;
 
	var methods = {
		"_create": function (settings) {
			if (settings) {
				jQuery.extend(options, settings);
			}
			widget.html('<div class="ui-gmap-container"></div>');
 
			methods._initGmap();
 
			// Load the map data and render.
			methods.load();
 
		},
		"_initGmap": function () {
			var elem = widget;
 
			// Set up the map options.
			var gmapOptions = {
				center: new google.maps.LatLng(options.center_lat, options.center_lng),
				size: new google.maps.Size(options.map_width, options.map_height),
				zoom: options.map_zoom,
				mapTypeId: google.maps.MapTypeId.HYBRID,
				mapTypeControl: true,
				mapTypeControlOptions: {
					style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
					mapTypeIds: [
						google.maps.MapTypeId.ROADMAP,
						google.maps.MapTypeId.SATELLITE,
						google.maps.MapTypeId.HYBRID
					]
				}
			};
			// Create the map object with the raw DOM element and our options.
			var map = new google.maps.Map( document.getElementById( elem.attr('id') ), gmapOptions);
 
			options.gmap = map;
		},
		"load": function () {
			var map = options.gmap;
			var points = options.points;
			var bounds = new google.maps.LatLngBounds();
			var infoWin = null;
			var gmap_min_markers = options.gmap_min_markers;
 
			// Set up the markers.
			if (points != null && points.length > 0)
			{
				// Cycle through each point, and create a marker on the map.
				for (i = 0; i < points.length; i++){
					var point = points[i];
					var gmapPoint = new google.maps.LatLng(point.lat, point.lng);
					var marker = new google.maps.Marker({
						position: gmapPoint,
						map: map
					});
 
					// Set up the marker event handler.
					(function (i, map, marker, point) {
 
						google.maps.event.addListener(marker, 'click', function () {
							if (!infoWin) {
								infoWin = new google.maps.InfoWindow();
							}
							// Show balloon when clicked.
							var content = '<div id="info">' + point.text + '</div>';
 
							infoWin.setContent( content );
							infoWin.open(map, marker);
						});
					})(i, map, marker, point);
 
					// ...We're going to track these in order to adjust the map...
					bounds.extend(gmapPoint);
				};
 
				// Have the map adjust itself.
				if (points.length < gmap_min_markers) {
					if (points.length <= 0) {
						map.setZoom(1);
						map.setCenter(map.center);
					} else {
						map.setZoom(11);
						map.setCenter(map.center);
					}
				} else {
					map.fitBounds(bounds);
				}
			}
		}
	};
 
	var options = {
		"points": null,
		"center_lat": "37.795203",
		"center_lng": "-122.395849",
		"map_width": 500,
		"map_height": 400,
		"map_zoom": 4,
		"gmap": null,
		"gmap_min_markers": 2
	};
 
	jQuery.fn.gmap = function (settings) {
		widget = jQuery(this);
 
		if (methods[settings]) {
			return methods[ settings ].apply(this, Array.prototype.slice.call(arguments, 1));
		} else if ( typeof settings == 'object' || !settings) {
			return methods._create.apply(this, arguments);
		} else {
			jQuery.error('Method' + settings + ' does not exist in jQuery.gmap');
		}
	}
})(jQuery);

jQuery 1.5.x or Higher

/**
 * A generic jQuery Google (r) map plug-in. Requires https://maps.google.com/maps/api/js?sensor=false
 * http://www.alexventure.com
 * 
 * Copyright (c) Alexventure.com
 * Dual licensed under the MIT and GPL licenses. (Re)Use as you wish.
 * http://docs.jquery.com/License
 * 
 * Date: 2011-09-07 23:32:00 -0700 (Wed, 07 Sep 2011)
 * Revision: 0001
 */
jQuery.widget('ui.gmap', {
	"options": {
		"points": null,
		"center_lat": "37.795203",
		"center_lng": "-122.395849",
		"map_width": 500,
		"map_height": 400,
		"map_zoom": 4,
		"gmap": null,
		"gmap_min_markers": 2
	},
	"_create": function () {
			var widget = this;
			var element = jQuery(widget.element);
 
			element.html('<div class="ui-gmap-container"></div>');
 
			this._initGmap();
 
			// Load the map data and render.
			this.load();
 
		},
		"_initGmap": function () {
			var elem = jQuery(this.element);
			var options = this.options;
 
			// Set up the map options.
			var gmapOptions = {
				center: new google.maps.LatLng(options.center_lat, options.center_lng),
				size: new google.maps.Size(options.map_width, options.map_height),
				zoom: options.map_zoom,
				mapTypeId: google.maps.MapTypeId.HYBRID,
				mapTypeControl: true,
				mapTypeControlOptions: {
					style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
					mapTypeIds: [
						google.maps.MapTypeId.ROADMAP,
						google.maps.MapTypeId.SATELLITE,
						google.maps.MapTypeId.HYBRID
					]
				}
			};
			// Create the map object with the raw DOM element and our options.
			var map = new google.maps.Map( document.getElementById( elem.attr('id') ), gmapOptions);
 
			this.options.gmap = map;
		},
		"load": function () {
			var options = this.options;
			var map = options.gmap;
			var points = options.points;
			var bounds = new google.maps.LatLngBounds();
			var infoWin = null;
			var gmap_min_markers = options.gmap_min_markers;
 
			// Set up the markers.
			if (points != null && points.length > 0)
			{
				// Cycle through each point, and create a marker on the map.
				for (i = 0; i < points.length; i++){
					var point = points[i];
					var gmapPoint = new google.maps.LatLng(point.lat, point.lng);
					var marker = new google.maps.Marker({
						position: gmapPoint,
						map: map
					});
 
					// Set up the marker event handler.
					(function (i, map, marker, point) {
 
						google.maps.event.addListener(marker, 'click', function () {
							if (!infoWin) {
								infoWin = new google.maps.InfoWindow();
							}
							// Show balloon when clicked.
							var content = '<div id="info">' + point.text + '</div>';
 
							infoWin.setContent( content );
							infoWin.open(map, marker);
						});
					})(i, map, marker, point);
 
					// ...We're going to track these in order to adjust the map...
					bounds.extend(gmapPoint);
				};
 
				// Have the map adjust itself.
				if (points.length < gmap_min_markers) {
					if (points.length <= 0) {
						map.setZoom(1);
						map.setCenter(map.center);
					} else {
						map.setZoom(11);
						map.setCenter(map.center);
					}
				} else {
					map.fitBounds(bounds);
				}
			}
		}
});

And finally, here is the sample HTML. Just drop the plugin and the HTML file in the same folder, and open the HTML file in your favorite browser:

<html>
	<head>
		<title>A jQuery Google Maps plug-in example.</title>
		<!-- Google Maps API (javascript) -->
		<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
 
		<!-- jQuery 1.6.3 -->
		<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
		<!-- jQuery UI 1.8.16 -->
		<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
 
		<script type="text/javascript" src="jquery.gmap.js"></script>
		<script type="text/javascript">
 
			jQuery(document).ready(function ($) {
				$('#the_map_element').gmap({
					"map_width": 500,
					"map_height": 500,
					"points": [
						{
					 		text: "<span>Boudin Bakery at Embarcadero</span><br /><p>Go upstairs for the Boudin Bakery restaurant, and order a clam chowder!</p>",
					 		lat: '37.795203',
					 		lng: '-122.395849'
					 	},
						{
					 		text: "<span>Gott's Roadside</span><br />Delicious burgers and more!</p>",
					 		lat: '37.77493',
					 		lng: '-122.419416'
					 	}
					]
				});
			});
 
		</script>
	</head>
	<body>
		<div id="the_map_element" style="width: 500px; height: 500px;">
		</div>
	</body>
</html>

Here is the sample code as a TAR.GZ archive for your convenience: jQuery_GoogleMaps_Example.tar.gz

Jun 21

Avoid Samsung LCD TV units like the plague

So, my 52″ LCD Samsung HD TV learned a new trick a few days ago. Instead of powering on right away, it stays off, makes a series of clicks, and eventually turns on after a few minutes.

Upon searching a la Google, I’m conviced I found the culprit:

http://forums.cnet.com/7723-13973_102-349863.html

I called Samsung’s support line (1-800-SAMSUNG) and gave them the model and serial number (Model LNT5265FX/XAA). The lady that attended my call was very nice… but alas, that’s all the good that came from this conversation.

As it turns out, EVEN THOUGH THIS IS CLEARLY A MANUFACTURING DEFECT, the representative informed me that Samsung only offered to fix this known defect for a short amount of time. My TV was no longer covered.

After I heard this, I replied by saying “Thank you for your help. I will no longer recommend nor purchase Samsung products. Have a nice day.”

Needless to say, it’s something I can fix with my soldering iron and a quick trip to Radio Shack… but the kicker was that Samsung wouldn’t fix something they sold me as defective for $3,000 dollars just three years ago.

Way to treat your consumers, Samsung.

Apr 02

Zend Framework and PayPal API – Part 2 of 2

Zend Framework and PayPal API. Zend_Http_Client can be extended to call the PayPal NVP doExpressCheckout API call.

If you haven’t yet, feel free to read Part 1 – (Zend Framework and the PayPal NVP DoDirectPayment API call) …That’s where the Paypal_Client class is started.

The difference between the PayPal doExpressCheckout API call and doDirectPayment API call lies in what they were meant for. As mentioned in part 1, doDirectPayment is meant for use with PayPal’s Website Payments Pro API, and hence, involves a fixed processing fee per month. The doExpressCheckout API requires us to redirect our user to PayPal’s payment gateway in order to receive a token we can use to authorize and capture funds. Since doExpressCheckout is run through PayPal’s Express Checkout API, each transaction incurs a small percentage fee that PayPal gets to keep. So in theory, doExpressCheckout can be used for sites that don’t yet have a high volume of transactions, and require less up front costs. The doDirectPayment API call is great for sites that have a large volume of transactions per month so as to justify paying a fixed fee per month, instead of a percentage of each transaction.

Extending upon the class described in Part 1, we can add the following code to our PayPal_Client class:

/**
 * NOTICE: This class begins in Part 1 of this tutorial! It is the same class, Paypal_Client, but with additional code.
 * New class variable needed by the ecSetExpressCheckout API call.
 *
 * The live URL is: https://www.paypal.com/webscr
 * The test URL is: https://www.sandbox.paypal.com/webscr
 *
 * Let's use the test URL for now
 *
 * @var string The express checkout page URL
 */
public $api_expresscheckout_uri = 'https://www.sandbox.paypal.com/webscr';

…Let’s add a few methods as well:

/**
*
* Calls the 'ECDoExpressCheckout' API call. Requires a token that can
* be obtained using the 'SetExpressCheckout' API call. The payer_id is
* obtained from the 'SetExpressCheckout' or 'GetExpressCheckoutDetails' API call.
*
* @param string $token
* @param string $payer_id
* @param float  $payment_amount
* @param string $currency_code
* @param string $payment_action Can be 'Authorization', 'Sale', or 'Order'
*
* @return Zend_Http_Response
* @throws Zend_Http_Client_Exception
*/
function ecDoExpressCheckout($token, $payer_id, $payment_amount, $currency_code, $payment_action = 'Sale') {
	$this->setParameterGet('METHOD', 'DoExpressCheckoutPayment');
	$this->setParameterGet('AMT', $payment_amount);
	$this->setParameterGet('TOKEN', $token);
	$this->setParameterGet('PAYERID', $payer_id);
	$this->setParameterGet('PAYMENTACTION', $payment_action); // Can be 'Authorization', 'Sale', or 'Order'
 
	return $this->request(Zend_Http_Client::GET);
}
 
/**
* Request an authorization token.
*
* @param float $paymentAmount
* @param string $returnURL
* @param string $cancelURL
* @param string $currencyID
* @param string $payment_action Can be 'Authorization', 'Sale', or 'Order'. Default is 'Authorization'
* @return Zend_Http_Response
*/
function ecSetExpressCheckout($paymentAmount, $returnURL, $cancelURL, $currencyID, $payment_action = 'Authorization') {
	$this->setParameterGet('METHOD', 'SetExpressCheckout');
 
	// The paypal PDF says to use this and not AMT, but in practice, 
	// that doesnt work yet as of the time of this writting...
//	    $this->setParameterGet('PAYMENTREQUEST_0_AMT', $paymentAmount);
 
	// ...So we will do this for now.
	$this->setParameterGet('AMT', $paymentAmount);
 
	$this->setParameterGet('RETURNURL', $returnURL);
	$this->setParameterGet('CANCELURL', $cancelURL);
	$this->setParameterGet('PAYMENTREQUEST_0_PAYMENTACTION', $payment_action); // Can be 'Authorization', 'Sale', or 'Order'
 
	return $this->request(Zend_Http_Client::GET);
}

Keep in mind, the response body that PayPal sends back will be a Name-Value Pair string, kind of like the stuff after the question mark in a GET request. To make our life a little easier, we can add a static helper method to our PayPal_Client class to parse this string into something usable:

/**
* Parse a Name-Value Pair response into an object.
* @param string $response
* @return object Returns an object representation of the response.
*/
public static function parse($response) {
 
	$responseArray = explode("&amp;", $response);
 
	$result = array();
 
	if (count($responseArray) > 0) {
		foreach ($responseArray as $i => $value) {
 
			$keyValuePair = explode("=", $value);
 
			if(sizeof($keyValuePair) > 1) {
				$result[$keyValuePair[0]] = urldecode($keyValuePair[1]);
			}
		}
	}
 
	if (empty($result)) {
		$result = null;
	} else {
		$result = (object) $result;
	}
 
	return $result;
}

…So, now that we have our methods in place, let’s tie it all together. We are going to need a few pages: a page that the PayPal payment page will re-direct to upon completion, and a page that the PayPal payment page will re-direct to if the user cancels payment.

Let’s begin by creating our checkout page:

<?php
/**
 * Checkout Page. This is our starting point before sending off 
 * a request for the authorization token needed to authorize and
 * capture funds for this order. 
 */
 
// Assume we are displaying US dollars.
setlocale(LC_MONETARY, 'en_US');
 
$items = array(
	1001 => array(
		'name' => 'Dog Bowl',
		'quantity' => 1,
		'cost_per_unit' => 5.00,
		'weight_per_unit' => 0.5
	),
	1013 => array(
		'name' => 'Chew Toy',
		'quantity' => 3,
		'cost_per_unit' => 0.99,
		'weight_per_unit' => 1
	),
	201 => array(
		'name' => 'Doggy Mints',
		'quantity' => 1,
		'cost_per_unit' => 4.99,
		'weight_per_unit' => 2
	)
);
 
// ...Did the user submit the form?
if (!empty($_REQUEST['submit']) {
	// Great! They've confirmed their order.
	// Let's try out our new checkout code.
 
	// First off, we need to obtain an
	// authorization token.
 
	$adapter = new PayPal_Adapter();
 
	$amount = 0.0;
 
	foreach($items as $item) {
		$amount += $item['quantity'] * $item['cost_per_unit'];
	}
 
	$returnURL = 'https://www.yoursite.com/payment-complete.php';
	$cancelURL = 'https://www.yoursite.com/payment-cancelled.php';
 
	$currency_code = 'USD'; // Assuming we're using the US Dollar.
 
	// Let's ask for a token.
	$reply = $adapter->ecSetExpressCheckout(
		$amount, 
		$returnURL, 
		$cancelURL, 
		$currency_code
	);
 
	// ...If we succeed, we must redirected to PayPal at this point.
	if ($reply->isSuccessfull()) {
		// Let's turn that message body into something we can use...
		$replyData = $adapter->parse($reply->getBody());
 
		// If we did in fact succeed, we will now have a token to use
		if ($replyData->ACK == 'SUCCESS' || $replyData->ACK == 'SUCCESSWITHWARNING') {
 
			$token = $data->TOKEN; // ...It's already URL encoded for us.
 
			// Save the amount total... We must use this when we capture the funds.
			$_SESSION['CHECKOUT_AMOUNT'] = $amount;
 
			// Redirect to the PayPal express checkout page, using the token.
			header(
				'Location: ' . 
				$adapter->api_expresscheckout_uri . 
				'?&cmd=_express-checkout&token=' . $token
			);
		}
	} else {
		// Something went wrong.
		throw new Exception('ECSetExpressCheckout: We failed to get a successfull response from PayPal.');
	}
}
?>
 
<h1>Checkout</h1>
 
Here is your order. Please confirm your order by clicking the SUBMIT button.
 
<h2>Cart Items</h2>
<!-- This form will just submit to itself, and then our token request begins. -->
<form>
	<ul>
 
	<?php foreach($items as $sku => $item): ?>
		<?php 
		// Get each item's properties, and list them one item per row.
		$name = $item['name'];	
		$cpu = $item['cost_per_unit'];
		$quantity = $item['quantity'];
 
		$total_cost = $cpu * $quantity;
 
		?>
		<li>
			SKU <?php echo $sku; ?> - <?php echo $name; ?> (<?php echo money_format('%i', $total_cost); ?>)<br />
			<span style="font-size: xx-small"><?php echo $quantity; ?> @ <?php echo money_format('%i', $cpu); ?>each</span>
		</li>
	<?php endforeach; ?>
 
	</ul>
 
	<input type="submit" name="submit" value="SUBMIT" />
</form>

Next, we will create our ‘payment complete’ page. Let’s name it ‘payment-complete.php’

<?php
/**
 * payment-complete.php
 *
 * Payment complete page. As per our own request, this page is 
 * redirected to by PayPal when a payment is authorized (still need to capture the funds though). 
 */
 
// Instantiate our payment adapter
$adapter = new PayPal_Adapter();
 
// PayPal will call this page, and send back the 'confirm_payment' variable.
$paymentConfirmed = !empty($_REQUEST['confirm_payment']);
 
// ...Are we good to go?
if ($paymentConfirmed == true) {
	// Yes. We now have a token, and the payer ID needed
	// to call the ECDoExpressCheckout API call.
 
	$token = $_REQUEST['token'];
	$payer_id = $_REQUEST['PayerID'];
	$amount = $_SESSION['CHECKOUT_AMOUNT'];
	$currency_code = 'USD'; // Assuming we are still using the US Dollar.
 
	// Capture the funds!
	$reply = $adapter->ecDoExpressCheckout($token, $payer_id, $amount, $currency_code);
 
	// Did we get a valid reply?
	if ($reply->isSuccessfull()) {
		// Yes! We would usually save our order data at this point,
		// but, we can just output to the screen for now. :-)
		// The funds may or may not have been captured.
		// Check the $replyData->ACK property to know for sure.
 
		$replyData = $adapter->parse($reply->getBody());
 
		print_r($replyData);
 
		// Clean up.
		unset($_SESSION['CHECKOUT_AMOUNT']);
	} else {
		// No. Throw an exception.
		throw new Exception('We were unable to complete the ECDoExpressCheckout API call.');
	}
} else {
	// No. Throw an exception.
	throw new Exception('It appears we did not receive a confirmed payment flag.');
}
 
?>

…And lastly, we need a ‘payment cancelled’ page to inform our users that their payment was not received. Paypal will re-direct our users to this page if a payment authorization could not be secured for what ever reason. Let’s call it ‘payment-cancelled.php’

<?php
/**
 * payment-cancelled.php
 *
 * Payment cancelled page. As per our own request, this page is 
 * redirected to by PayPal upon failed/cancelled payment. 
 */
?>
<h1>Payment Cancelled</h1>
<p>
	No charges were made.
</p>

Thank you for reading. Hopefully, this should get you up and running with the PayPal Express Checkout API and Zend Framework’s Zend_Http_Client class.

Feb 28

Zend Framework and PayPal API – Part 1 of 2

Zend Framework and Paypal. Leveraging the Zend Framework to use the PayPal NVP API is accomplished using the Zend_Http_Client class, and PayPal API credentials. The following article series shows how to complete a transaction using DoDirectPayment (Website Payments Pro), and how to DoExpressCheckout (Express Checkout) using the Zend_Http_Client class. Much of this information was compiled using the PayPal Name-Value Pair API Developer Guide, and PayPal’s examples, along with the Zend Framework documentation.

In part 1, I will be showing you how to accomplish a call to the DoDirectPayment PayPal NVP API using the Zend_Http_Client class.

The PayPal API Client

First off, let’s build a PayPal API client that will extend the Zend_Http_Client. This will help keep our code properly organized, and object-oriented.

You will need to register at https://developer.paypal.com/ for test API credentials.

class Paypal_Client extends Zend_Http_Client
{
      private $_api_version = '56.0';
      private $_api_username = 'your_user_name'; // These differ depending if you're in live mode or test mode.
      private $_api_password = 'your_password'; // I'm certain you can come up with something to take that into account. :-)
      private $_api_signature = 'your_api_signature';
 
}

Next, we will want to add methods that call ‘doExpressCheckout’ and call ‘doDirectPayment’ via PayPal’s NVP API. But before we do that, let’s make our life a little easier and ensure our HTTP client always adds required fields for us before sending any API calls. To accomplish this, we can simply override the class __construct() method.

function __construct($uri = null, $options = null)
{
      parent::__construct($uri, $options);
 
      // NOTE: Parameters must always be url encoded, as per PayPal documentation.
      $this->setParameterGet('USER', urlencode($this->_api_username));
      $this->setParameterGet('PWD', urlencode($this->_api_password);
      $this->setParameterGet('SIGNATURE', urlencode($this->_api_signature));
      $this->setParameterGet('VERSION', urlencode($this->_api_version));
}

DoDirectPayment

The DoDirectPayment method allows us to authorize and capture funds without the need of a customer signing in to paypal. The customer simply fills out our form with relevant fields, and we submit our data via the PayPal DoDirectPayment NVP API call. The upside of this method is the ease of implementation. Send the API call, save the transaction ID PayPal sends back, and continue on our merry way. The downside (or, other upside, depending on your monthly profits) is, your PayPal account must be configured with Website Payments Pro, which has a fixed monthly fee.

Here is the method that belongs within our class in order to handle the PayPal DoDirectPayment NVP API call using the inherited Zend_Http_Client functionality:

/**
 * Calls the 'DoDirectPayment' API call. Note - Keep track of the
 * transaction ID on success! You'll need it to get transaction details
 * at a later date.
 *
 * @param float $amount
 * @param string $credit_card_type
 * @param string $credit_card_number
 * @param string $expiration_month
 * @param string $expiration_year
 * @param string $cvv2
 * @param string $first_name
 * @param string $last_name
 * @param string $address1
 * @param string $address2
 * @param string $city
 * @param string $state
 * @param string $zip
 * @param string $country
 * @param string $currency_code
 * @param string $ip_address
 * @param string $payment_action Can be 'Authorization' (default) or 'Sale'
 *
 * @return Zend_Http_Response
 * @throws Zend_Http_Client_Exception
 */
function doDirectPayment(
      $amount,
      $credit_card_type,
      $credit_card_number,
      $expiration_month,
      $expiration_year,
      $cvv2,
      $first_name,
      $last_name,
      $address1,
      $address2,
      $city,
      $state,
      $zip,
      $country,
      $currency_code,
      $ip_address,
      $payment_action = 'Sale'
) {
      $this->setParameterGet('METHOD', 'DoDirectPayment');
 
      $expiration_date = str_pad($expiration_month, 2, STR_PAD_LEFT) .
            $expiration_year;
 
      $this->setParameterGet('PAYMENTACTION', urlencode($payment_action)); // Can be 'Authorization', or 'Sale'
      $this->setParameterGet('AMT', urlencode($amount));
      $this->setParameterGet('CREDITCARDTYPE', urlencode($credit_card_type));
      $this->setParameterGet('ACCT', urlencode($credit_card_number));
      $this->setParameterGet('EXPDATE', urlencode($expiration_date));
      $this->setParameterGet('CVV2', urlencode($cvv2));
      $this->setParameterGet('FIRSTNAME', urlencode($first_name));
      $this->setParameterGet('LASTNAME', urlencode($last_name));
      $this->setParameterGet('STREET', urlencode($address1));
 
      if (!empty($address2)) {
            $this->setParameterGet('STREET2', urlencode($address2));
      }
 
      $this->setParameterGet('CITY', urlencode($city));
      $this->setParameterGet('STATE', urlencode($state));
      $this->setParameterGet('ZIP', urlencode($zip));
      $this->setParameterGet('COUNTRYCODE', urlencode($country));
      $this->setParameterGet('CURRENCYCODE', urlencode($currency_code));
      $this->setParameterGet('IPADDRESS', urlencode($ip_address));
 
      return $this->request(Zend_Http_Client::GET);
 
}

Now that we have our code in place, we can try our stuff out. Lets make a PHP script to handle our form.

<?php
if (isset($_REQUEST['submit'])
{
      // Live URL: https://api-3t.paypal.com/nvp
      // Test URL: https://api-3t.sandbox.paypal.com/nvp
      $url = 'https://api-3t.paypal.com/nvp';
 
      $amount = 10.00; // Obviously, we would sum up the contents of some cart to fill in this value.
      $credit_card_type = $_REQUEST['credit_card_type'];
      $credit_card_number = $_REQUEST['credit_card_number'];
      $expiration_month = $_REQUEST['expiration_month'];
      $expiration_year = $_REQUEST['expiration_year'];
      $cvv2 = $_REQUEST['cvv2'];
 
      $first_name = $_REQUEST['first_name'];
      $last_name = $_REQUEST['last_name'];
      $address1 = $_REQUEST['address1'];
      $address2 = $_REQUEST['address2'];
      $city = $_REQUEST['city'];
      $state = $_REQUEST['state'];
      $zip = $_REQUEST['zip'];
 
      $country = 'US'; // Assuming we are only accepting transactions within the United States.
      $currency_code = 'USD'; // Assuming we are using the United States Dollar
      $ip_address = $_SERVER['REMOTE_ADDR']; // Get the IP Address, assuming we are in a LAMP environment
 
      // Create an instance of our PayPal NVP client
      $client = new PayPal_Client($url);
 
      // Send our API request!
      $result = $client->doDirectPayment(
            $amount,
            $credit_card_type,
            $credit_card_number,
            $expiration_month,
            $expiration_year,
            $cvv2,
            $first_name,
            $last_name,
            $address1,
            $address2,
            $city,
            $state,
            $zip,
            $country,
            $currency_code,
            $ip_address);
 
      // Remember to store the transaction ID! You'll need it 
      // to lookup the transaction details. For now, let's just
      // display the results.
      echo $result->getBody();
}
?>
<form>
      <ul>
            <li><label>First Name:</label><input type="" name="" value="" /></li>
            <li><label>Last Name:</label><input type="" name="" value="" /></li>
            <li><label>Address, Line 1:</label><input type="" name="" value="" /></li>
            <li><label>Address, Line 2:</label><input type="" name="" value="" /></li>
            <li><label>City:</label><input type="" name="" value="" /></li>
            <li><label>State (2-Letter):</label><input type="" name="" value="" /></li>
            <li><label>Zip:</label><input type="" name="" value="" /></li>
      </ul>
      <ul>
            <li><label>Card Type</label><input type="" name="" value="" /></li>
            <li><label></label><input type="" name="" value="" /></li>
      </ul>
</form>

…And that concludes part 1 of 2.

Stay tunned for Part 2 of this article series: Zend Framework and the PayPal API – Part 2 of 2.

NEXT: Zend Framework and the PayPal API – Part 2 of 2

NOTE: These samples assume you have configured auto-loading within your environment. Needless to say, you should fill in the API credentials with your own data before attempting to run these samples. Additionally, you could also move credential information into a different location instead of hard-coding credentials into a class. If you find something that can be improved, feel free to leave a comment. Thanks!

Jan 12

Latest updates to ZRECommerce

Plugin tags are now working. New plugins are on the way.

To display an array of products, simply add the following tag anywhere in your article:

{%plugin type=Product_Array%}

And save.

In other news, work will now commence on a WordPress plugin that will allow for use of ZRECommerce cart and checkout awesomeness within wordpress installations.

Oct 23

What is ZRECommerce about?

It’s all about collaboration and growth of something that will re-invigorate and unify the idea of a multi-purpose web commerce library. Kind of like how Eclipse is a multipurpose IDE, and the Zend Framework is an all-purpose library.

Interested in collaborating? Leave a comment!
The project is hosted on github at http://github.com/aalbino/ZRECommerce

I’ll be setting up a meetup soon on meetup.com …Stay tuned.

Oct 22

Zend Framework and Google Maps API Geocoding, Geolocalization

Zend Framework and Geocoding, Google Maps API. This post explains how to use the Zend Framework and Geocoding via the Google Maps API.

Recently, I was assigned a task that involved geolocating adresses based on their physical address. Thankfully, a bit of investigation a la google yielded plenty of examples that use RESTful queries to the Googl Maps API. It was incredibly easy to use the Zend_Http_Client class from the Zend Framework to query google for geolocations on physical address. There were a few things I kept in mind:

  • Google imposes a cap of 2500 queries per day, unless we sign up for a premier account, which gives us 10k queries per day. Signing up for a premier google maps API account gets us a SPECIAL API KEY
  • Addresses must be formatted as best as possible. For example, “123 Some St, Apt A, San Francisco, CA USA” would be an acceptable address, whereas “123 Some St #A, San Francisco, CA” would not.
  • Here is a code example using the Zend_Http_Client component of the Zend Framework, and the freely accessible Google Maps API for geocoding. Remember to read Google’s terms of use, and that non ‘premier’ API access is limited to 2500 queries per IP, per day.

    $client = new Zend_Http_Client('http://maps.googleapis.com/maps/api/geocode/json');
     
    $urlencodedAddress = urlencode('1600 Amphitheatre Parkway, Mountain View, CA');
     
    $client->setParameterGet('sensor', 'false'); // Do we have a GPS sensor? Probably not on most servers.
    $client->setParameterGet('address', $urlencodedAddress); // Should now be '1600+Amphitheatre+Parkway,+Mountain+View,+CA'
     
    $response = $client->request('GET'); // We must send our parameters in GET mode, not POST
     
    print_r($response);