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("&", $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.
I’m certain you could, but I don’t cover that here. This tutorial isn’t meant to show how to make an HTML form to submit data. I’ve seen it done, but I don’t recommend it because then you would be exposing your e-mail, or API Key, or some other information you may not actually want floating around the internet.
Hi. I was wondering if it’s posible to use only the email of the Store using this class, just like a regular POST to paypal.
Thanks, and good luck.
please study object oriented development, flat php is like tape.
3 suggestions:
unit test, DIC, OO
Thank you, Karl!
// 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);
This is because the new one is from version 63.0 and you set 56.0 in your version field!!!!!!!!!
snip[PAYMENTREQUEST_n_AMT
AMT (deprecated)
The total cost of the transaction to the buyer. If shipping cost (not applicable to digital goods) and tax charges are known, include them in this value. If not, this value should be the current sub-total of the order. If the transaction includes one or more one-time purchases, this field must be equal to the sum of the purchases. Set this field to 0 if the transaction does not include a one-time purchase such as when you set up a billing agreement for a recurring payment that is not immediately charged. When the field is set to 0, purchase-specific fields are ignored. You can specify up to 10 payments, where n is a digit between 0 and 9, inclusive; except for digital goods, which supports single payments only. For digital goods, the following must be true:
total cost > 0
total cost <= total cost passed in the call to SetExpressCheckout
Character length and limitations: Value is a positive number which cannot exceed $10,000 USD in any currency. It includes no currency symbol. It must have 2 decimal places, the decimal separator must be a period (.), and the optional thousands separator must be a comma (,).
AMT is deprecated since version 63.0. Use PAYMENTREQUEST_0_AMT instead.]
Great tutorial Alex! Very helpful, especially for those who don’t want to use curl!
PHP has a cool function called parse_str() This is a NVP string decoder and can output it into an array.
public static function parse($response) {
$result = array();
parse_str($response,$result);
if (empty($result)) {
return null;
}
return (object) $result;
}
This achieves the same functionality and also supports “?” being used as the NVP pair as per the HTTP spec.
As parse_str() is a native function it should also be faster then implementing one yourself!
Alex, your implementation of parse_str is miles better than the one paypal used in their sample code for NVP API!!!!!!!
Please see http://php.net/manual/en/language.oop5.php for a good intro to php classes.
The Paypal_Adapter class begins here – http://www.alexventure.com/2011/02/28/zend-framework-and-the-paypal-api-part-1-of-2/
hi
thanks a lot for the tutorial. I’m not asking for sample code downloads, but, being the noob I am, who has never written a php class before (let alone for ZendF), I’m wondering Paypal_Adapter is.
thanks
If enough folks buy me a beer or coffee (donate), or request for sample code downloads (unruly angry mob), I’ll consider it.
Can you kindly post a downloadable file containing all the code.
thanks