Paypal Payments Component Using cURL

by parris
Updated: July 5th, 2009: Added Express Checkout and modified both the component and the file under vendors. Also you will need to change the line that calls the paypal component for the direct payment controller. ----------------------------------------------- Looking for a lightweight, easy to use, Paypal credit card processing script? You have found it! This is for Cake 1.2. All you need is cURL and a Paypal API account! I will be updating it with more features when I get a chance. On my to do list is refund by transaction Id, and mass refund by an array of transaction ids.
Pre-requisites:
-cURL
-Paypal Account with API keys and such

vendors/paypal/Paypal.php

<?php
/***********************************************************
This File Sets Up Calls to Paypal by arranging url information.
***********************************************************/
class Paypal{
    
    function 
__construct(){
        
    }
    
    function 
DoDirectPayment($paymentInfo=array()){
        
/**
         * Get required parameters from the web form for the request
         */
        
$paymentType =urlencode('Sale');
        
$firstName =urlencode($paymentInfo['Member']['first_name']);
        
$lastName =urlencode($paymentInfo['Member']['last_name']);
        
$creditCardType =urlencode($paymentInfo['CreditCard']['credit_type']);
        
$creditCardNumber urlencode($paymentInfo['CreditCard']['card_number']);
        
$expDateMonth =urlencode($paymentInfo['CreditCard']['expiration_month']);
        
$padDateMonth str_pad($expDateMonth2'0'STR_PAD_LEFT);
        
$expDateYear =urlencode($paymentInfo['CreditCard']['expiration_year']);
        
$cvv2Number urlencode($paymentInfo['CreditCard']['cv_code']);
        
$address1 urlencode($paymentInfo['Member']['billing_address']);
        
$address2 urlencode($paymentInfo['Member']['billing_address2']);
        
$country urlencode($paymentInfo['Member']['billing_country']);
        
$city urlencode($paymentInfo['Member']['billing_city']);
        
$state =urlencode($paymentInfo['Member']['billing_state']);
        
$zip urlencode($paymentInfo['Member']['billing_zip']);
        
        
$amount urlencode($paymentInfo['Order']['theTotal']);
        
$currencyCode="USD";
        
$paymentType=urlencode('Sale');
        
        
$ip=$_SERVER['REMOTE_ADDR'];
        
        
/* Construct the request string that will be sent to PayPal.
           The variable $nvpstr contains all the variables and is a
           name value pair string with & as a delimiter */
        
$nvpstr="&PAYMENTACTION=Sale&IPADDRESS=$ip&AMT=$amount&CREDITCARDTYPE=$creditCardType&ACCT=$creditCardNumber&EXPDATE=".$padDateMonth.$expDateYear."&CVV2=$cvv2Number&FIRSTNAME=$firstName&LASTNAME=$lastName&STREET=$address1&STREET2=$address2&CITYNAME=$city&STATEORPROVINCE=$state".
        
"&POSTALCODE=$zip&COUNTRY=$country&CURRENCYCODE=$currencyCode";
        
        
/* Make the API call to PayPal, using API signature.
           The API response is stored in an associative array called $resArray */
        
$resArray=$this->hash_call("doDirectPayment",$nvpstr);
        
        
/* Display the API response back to the browser.
           If the response from PayPal was a success, display the response parameters'
           If the response was an error, display the errors received using APIError.php.
           */
        
        
return $resArray;
        
//Contains 'TRANSACTIONID,AMT,AVSCODE,CVV2MATCH, Or Error Codes'
    
}

    function 
SetExpressCheckout($paymentInfo=array()){
        
$amount urlencode($paymentInfo['Order']['theTotal']);
        
$paymentType=urlencode('Sale');
        
$currencyCode=urlencode('USD');
        
        
$returnURL =urlencode($paymentInfo['Order']['returnUrl']);
        
$cancelURL =urlencode($paymentInfo['Order']['cancelUrl']);

        
$nvpstr='&AMT='.$amount.'&PAYMENTACTION='.$paymentType.'&CURRENCYCODE='.$currencyCode.'&RETURNURL='.$returnURL.'&CANCELURL='.$cancelURL;
        
$resArray=$this->hash_call("SetExpressCheckout",$nvpstr);
        return 
$resArray;
    }
    
    function 
GetExpressCheckoutDetails($token){
        
$nvpstr='&TOKEN='.$token;
        
$resArray=$this->hash_call("GetExpressCheckoutDetails",$nvpstr);
        return 
$resArray;
    }
    
    function 
DoExpressCheckoutPayment($paymentInfo=array()){
        
$paymentType='Sale';
        
$currencyCode='USD';
        
$serverName $_SERVER['SERVER_NAME'];
        
$nvpstr='&TOKEN='.urlencode($paymentInfo['TOKEN']).'&PAYERID='.urlencode($paymentInfo['PAYERID']).'&PAYMENTACTION='.urlencode($paymentType).'&AMT='.urlencode($paymentInfo['ORDERTOTAL']).'&CURRENCYCODE='.urlencode($currencyCode).'&IPADDRESS='.urlencode($serverName); 
        
$resArray=$this->hash_call("DoExpressCheckoutPayment",$nvpstr);
        return 
$resArray;
    }
    
    function 
APIError($errorNo,$errorMsg,$resArray){
        
$resArray['Error']['Number']=$errorNo;
        
$resArray['Error']['Number']=$errorMsg;
        return 
$resArray;
    }
    
    function 
hash_call($methodName,$nvpStr)
    {
        require_once 
'constants.php';
        
        
$API_UserName=API_USERNAME;
        
$API_Password=API_PASSWORD;
        
$API_Signature=API_SIGNATURE;
        
$API_Endpoint =API_ENDPOINT;
        
$version=VERSION;
        
        
//setting the curl parameters.
        
$ch curl_init();
        
curl_setopt($chCURLOPT_URL,$API_Endpoint);
        
curl_setopt($chCURLOPT_VERBOSE1);
    
        
//turning off the server and peer verification(TrustManager Concept).
        
curl_setopt($chCURLOPT_SSL_VERIFYPEERFALSE);
        
curl_setopt($chCURLOPT_SSL_VERIFYHOSTFALSE);
    
        
curl_setopt($chCURLOPT_RETURNTRANSFER,1);
        
curl_setopt($chCURLOPT_POST1);
        
//if USE_PROXY constant set to TRUE in Constants.php, then only proxy will be enabled.
        //Set proxy name to PROXY_HOST and port number to PROXY_PORT in constants.php 
        
        
if(USE_PROXY)
            
curl_setopt ($chCURLOPT_PROXYPROXY_HOST.":".PROXY_PORT); 
    
        
//NVPRequest for submitting to server
        
$nvpreq="METHOD=".urlencode($methodName)."&VERSION=".urlencode($version)."&PWD=".urlencode($API_Password)."&USER=".urlencode($API_UserName)."&SIGNATURE=".urlencode($API_Signature).$nvpStr;
    
        
//setting the nvpreq as POST FIELD to curl
        
curl_setopt($ch,CURLOPT_POSTFIELDS,$nvpreq);
    
        
//getting response from server
        
$response curl_exec($ch);
    
        
//convrting NVPResponse to an Associative Array
        
$nvpResArray=$this->deformatNVP($response);
        
$nvpReqArray=$this->deformatNVP($nvpreq);
    
        if (
curl_errno($ch))
            
$nvpResArray $this->APIError(curl_errno($ch),curl_error($ch),$nvpResArray);
        else 
            
curl_close($ch);
    
        return 
$nvpResArray;
    }
    
    
/** This function will take NVPString and convert it to an Associative Array and it will decode the response.
      * It is usefull to search for a particular key and displaying arrays.
      * @nvpstr is NVPString.
      * @nvpArray is Associative Array.
      */
    
    
function deformatNVP($nvpstr)
    {
    
        
$intial=0;
         
$nvpArray = array();
    
    
        while(
strlen($nvpstr)){
            
//postion of Key
            
$keyposstrpos($nvpstr,'=');
            
//position of value
            
$valuepos strpos($nvpstr,'&') ? strpos($nvpstr,'&'): strlen($nvpstr);
    
            
/*getting the Key and Value values and storing in a Associative Array*/
            
$keyval=substr($nvpstr,$intial,$keypos);
            
$valval=substr($nvpstr,$keypos+1,$valuepos-$keypos-1);
            
//decoding the respose
            
$nvpArray[urldecode($keyval)] =urldecode$valval);
            
$nvpstr=substr($nvpstr,$valuepos+1,strlen($nvpstr));
         }
        return 
$nvpArray;
    }
}
?>

vendors/paypal/constants.php

<?php
/****************************************************
constants.php

This is the configuration file for the samples.This file
defines the parameters needed to make an API call.
****************************************************/

/**
# API user: The user that is identified as making the call. you can
# also use your own API username that you created on PayPal’s sandbox
# or the PayPal live site
*/

define('API_USERNAME''YOUR USERNAME HERE');

/**
# API_password: The password associated with the API user
# If you are using your own API username, enter the API password that
# was generated by PayPal below
# IMPORTANT - HAVING YOUR API PASSWORD INCLUDED IN THE MANNER IS NOT
# SECURE, AND ITS ONLY BEING SHOWN THIS WAY FOR TESTING PURPOSES
*/

define('API_PASSWORD''YOU PASS HERE');

/**
# API_Signature:The Signature associated with the API user. which is generated by paypal.
*/

define('API_SIGNATURE''YOU SIG HERE');

/**
# Endpoint: this is the server URL which you have to connect for submitting your API request.
*/

define('API_ENDPOINT''https://api-3t.paypal.com/nvp');
/**
USE_PROXY: Set this variable to TRUE to route all the API requests through proxy.
like define('USE_PROXY',TRUE);
*/
define('USE_PROXY',FALSE);
/**
PROXY_HOST: Set the host name or the IP address of proxy server.
PROXY_PORT: Set proxy port.

PROXY_HOST and PROXY_PORT will be read only if USE_PROXY is set to TRUE
*/
define('PROXY_HOST''127.0.0.1');
define('PROXY_PORT''808');

/* Define the PayPal URL. This is the URL that the buyer is
   first sent to to authorize payment with their paypal account
   change the URL depending if you are testing on the sandbox
   or going to the live PayPal site
   For the sandbox, the URL is
   https://www.sandbox.paypal.com/webscr&cmd=_express-checkout&token=
   For the live site, the URL is
   https://www.paypal.com/webscr&cmd=_express-checkout&token=
   */
define('PAYPAL_URL''https://www.paypal.com/webscr&cmd=_express-checkout&token=');

/**
# Version: this is the API version in the request.
# It is a mandatory parameter for each API request.
# The only supported value at this time is 2.3
*/

define('VERSION''3.0');

?>

components/paypal.php

Component Class:

<?php 
<?php 
/**
 * Paypal Direct Payment API Component class file.
 */
App::import('Vendor','paypal' ,array('file'=>'paypal/Paypal.php'));
class 
PaypalComponent extends Object{
    
    function 
processPayment($paymentInfo,$function){
        
$paypal = new Paypal();
        if (
$function=="DoDirectPayment")
            return 
$paypal->DoDirectPayment($paymentInfo);
        elseif (
$function=="SetExpressCheckout")
            return 
$paypal->SetExpressCheckout($paymentInfo);
        elseif (
$function=="GetExpressCheckoutDetails")
            return 
$paypal->GetExpressCheckoutDetails($paymentInfo);
        elseif (
$function=="DoExpressCheckoutPayment")
            return 
$paypal->DoExpressCheckoutPayment($paymentInfo);
        else
            return 
"Function Does Not Exist!";
    }
}
?>
?>

sample direct payment controller function:

Controller Class:

<?php 
function processPayment(){
     
$paymentInfo = array('Member'=>
                           array(
                               
'first_name'=>'name_here',
                               
'last_name'=>'lastName_here',
                               
'billing_address'=>'address_here',
                               
'billing_address2'=>'address2_here',
                               
'billing_country'=>'country_here',
                               
'billing_city'=>'city_here',
                               
'billing_state'=>'state_here',
                               
'billing_zip'=>'zip_here'
                           
),
                          
'CreditCard'=>
                           array(
                               
'card_number'=>'number_here',
                               
'expiration_month'=>'month_here',
                               
'expiration_year'=>'year_here',
                               
'cv_code'=>'code_here'
                           
),
                          
'Order'=>
                          array(
'theTotal'=>1.00)
                    );

   
/*
    * On Success, $result contains [AMT] [CURRENCYCODE] [AVSCODE] [CVV2MATCH] 
    * [TRANSACTIONID] [TIMESTAMP] [CORRELATIONID] [ACK] [VERSION] [BUILD]
    * 
    * On Fail, $ result contains [AMT] [CURRENCYCODE] [TIMESTAMP] [CORRELATIONID] 
    * [ACK] [VERSION] [BUILD] [L_ERRORCODE0] [L_SHORTMESSAGE0] [L_LONGMESSAGE0] 
    * [L_SEVERITYCODE0] 
    * 
    * Success or Failure is best tested using [ACK].
    * ACK will either be "Success" or "Failure"
    */
 
    
$result $this->Paypal->processPayment($paymentInfo,"DoDirectPayment");
    
$ack strtoupper($result["ACK"]);
                
    if(
$ack!="SUCCESS")
        
$error $result['L_LONGMESSAGE0'];
    else{
        
/* successful do something here! */
    
}
}
?>

Express Checkout Controller Example

Controller Class:

<?php 
function _get($var) {
    return isset(
$this->params['url'][$var])? $this->params['url'][$var]: null;
}
    
function 
expressCheckout($step=1){
    
$this->Ssl->force();
    
$this->set('step',$step);
    
//first get a token
    
if ($step==1){
        
// set
        
$paymentInfo['Order']['theTotal']= .01;
        
$paymentInfo['Order']['returnUrl']= "https://fullPathHere/orders/expressCheckout/2/";
        
$paymentInfo['Order']['cancelUrl']= "https://fullPathToCancelUrl";
            
        
// call paypal
        
$result $this->Paypal->processPayment($paymentInfo,"SetExpressCheckout");
        
$ack strtoupper($result["ACK"]);
        
//Detect Errors
        
if($ack!="SUCCESS")
            
$error $result['L_LONGMESSAGE0'];
        else {
            
// send user to paypal
            
$token urldecode($result["TOKEN"]);
            
$payPalURL PAYPAL_URL.$token;
            
$this->redirect($payPalURL);
        }
    }
    
//next have the user confirm
    
elseif($step==2){
        
//we now have the payer id and token, using the token we should get the shipping address
        //of the payer. Compile all the info into the session then set for the view.
        //Add the order total also
        
$result $this->Paypal->processPayment($this->_get('token'),"GetExpressCheckoutDetails");
        
$result['PAYERID'] = $this->_get('PayerID');
        
$result['TOKEN'] = $this->_get('token');
        
$result['ORDERTOTAL'] = .01;
        
$ack strtoupper($result["ACK"]);
        
//Detect errors
        
if($ack!="SUCCESS"){
            
$error $result['L_LONGMESSAGE0'];
            
$this->set('error',$error);
        }
        else {
            
$this->set('result',$this->Session->read('result'));
            
$this->Session->write('result',$result);
            
/*
             * Result at this point contains the below fields. This will be the result passed 
             * in Step 3. I used a session, but I suppose one could just use a hidden field
             * in the view:[TOKEN] [TIMESTAMP] [CORRELATIONID] [ACK] [VERSION] [BUILD] [EMAIL] [PAYERID]
             * [PAYERSTATUS]  [FIRSTNAME][LASTNAME] [COUNTRYCODE] [SHIPTONAME] [SHIPTOSTREET]
             * [SHIPTOCITY] [SHIPTOSTATE] [SHIPTOZIP] [SHIPTOCOUNTRYCODE] [SHIPTOCOUNTRYNAME]
             * [ADDRESSSTATUS] [ORDERTOTAL]
             */
        
}
    }
    
//show the confirmation
    
elseif($step==3){
        
$result $this->Paypal->processPayment($this->Session->read('result'),"DoExpressCheckoutPayment");
    
//Detect errors
        
$ack strtoupper($result["ACK"]);
        if(
$ack!="SUCCESS"){
            
$error $result['L_LONGMESSAGE0'];
            
$this->set('error',$error);
        }
        else {
            
$this->set('result',$this->Session->read('result'));
        }
    }
}
?>

Express Checkout View: express_checkout.ctp

View Template:


<?php 
    
if (!isset($error)){
        if (
$step==2){
            echo 
$form->create('Order',array('type' => 'post''action' => 'expressCheckout/3''id' => 'OrderExpressCheckoutConfirmation')); 
            
//all shipping info contained in $result display it here and ask user to confirm.
            //echo pr($result);
            
echo $form->end('Confirm Payment'); 
        }
        if (
$step==3){
            
//show confirmation once again all information is contained in $result or $error
            
echo '<h2>Congrats</h2>';
        }
    }
    else
        echo 
$error;
?>

One thing to note about express checkout is that it takes you away from your website and goes to paypal. Therefore all your session data is lost. You can bypass this by setting your security to low in the core. You may be able to set it on the fly, but I have not tested that.

I hope that was clear enough...

The controller array section has not been checked for errors, but I am sure you get the idea!
Thanks/Enjoy :)

Report

More on Components

Advertising

Comments

  • yjaques posted on 01/31/11 12:53:00 PM
    Regarding the comment "One thing to note about express checkout is that it takes you away from your website and goes to paypal. Therefore all your session data is lost. You can bypass this by setting your security to low in the core. You may be able to set it on the fly, but I have not tested that." I found that this was actually handled quite competently by the other cake component "direct payment API". Unfortunately that code relies on a whole set of outdated PayPal code. So I managed to cannibalize the session handling part and add it into this code.

    Couple of things you have to do:

    Copy and paste the save and restore session functions from the other component into this component:
    ================================
    /**
    * Stores the current session to a temporary file for later retrieval.
    *
    * @return bool true if stored, false otherwise
    *
    * @access public
    * @since 1.0
    */
    function storeSession()
    {
    $sessionFileHandle = fopen(CAKE_COMPONENT_PAYPAL_SESSION_SAVE_PATH . DS . session_id() . '.ser.tmp', 'w');

    if ($sessionFileHandle !== false)
    {
    fwrite($sessionFileHandle, serialize($_SESSION));
    fclose($sessionFileHandle);

    return true;
    }

    return false;
    }

    /**
    * Restores the specified session.
    *
    * @param string Session ID
    *
    * @return bool true if able to restore, false otherwise
    *
    * @access public
    * @since 1.0
    */
    function restoreSession($session_id)
    {
    if (preg_match('/^[A-Za-z0-9]*$/', $session_id))
    {
    $sessionFile = CAKE_COMPONENT_PAYPAL_SESSION_SAVE_PATH . DS . $session_id . '.ser.tmp';

    if (@file_exists($sessionFile) && @is_file($sessionFile) && @is_readable($sessionFile) && @filesize($sessionFile) > 0)
    {
    $contents = file_get_contents($sessionFile);
    $oldSession = @unserialize($contents);

    if (is_array($oldSession) && count($oldSession) > 0)
    {
    foreach($oldSession as $id => $value)
    {
    $this->Session->write($id, $value);
    }
    }

    @unlink($sessionFile);

    return true;
    }
    }

    return false;
    }
    }
    ====================================
    Next be sure and add the constant to the session save path, and the session component at the top of the component:

    define ('CAKE_COMPONENT_PAYPAL_SESSION_SAVE_PATH', ROOT . DS . APP_DIR . DS . 'tmp' . DS . 'sessions'); // No trailing slash!

    class PaypalComponent extends Object{
    //needs this declared or has no access to recreating session
    public $components = array('Session');

    =====================================

    Now all you have to do is:

    1. add the session id on the strings that you send to Paypal:

    $paymentInfo['Order']['returnUrl']= 'https://' . $_SERVER['SERVER_NAME'] . '/yourcontroller/expressCheckout/2?csid=' . session_id();
    $paymentInfo['Order']['cancelUrl']= 'https://' . $_SERVER['SERVER_NAME'] . '/yourcontroller/charge?csid=' . session_id();

    2. Save session right before you redirect user:

    $this->Paypal->storeSession();

    3. Restore session when user returns to your site (step 2 of expressCheckout):

    if (isset($_REQUEST['csid'])) {
    // Restore session
    if (!$this->Paypal->restoreSession($_REQUEST['csid'])) {
    $this->redirect('/');
    exit;
    }
    }

    =============================
    That's it! Took me something insane like 8 hours to solve this problem trying dead end after dead end...
  • ProLoser posted on 12/15/10 08:04:17 PM
    I know this may sound spammy or stealing your gusto, but I'm trying to consolidate efforts. I've been working on a unified payments/cart solution for cakephp and finally had an opportunity to resume development.

    Please check it out here: https://github.com/ProLoser/CakePHP-Cart/

    I could use any input and support offered. The aktive branch currently handles Paypal Pro (direct payment), Paypal Express, Authorize.net, and a butt-load more I haven't even tested yet.

    I believe forking is only useful if you merge :)
  • marcozunino posted on 05/15/10 09:55:47 AM
    Thank you for sharing this very good solution. When I try to use it in my app, I have just a little problem, I wrote about it because I think it can happen also to other users: on file Paypal.php (the one in vendors/paypal dir) at line 103:


    curl_setopt($ch, CURLOPT_VERBOSE, 1); 

    I commented this line since the output caused an internal server error on my hosted web server (Register.it). Now it works very well for me. Another issue may cause some problem is due to the fact that the vendors/paypal/Paypal.php and /controllers/components/paypal.php have tha same filename, so my version of Filezilla overwrite both the files when I upload one of them.

    These are not bug, but I think they can make some loss of time to someone. Thank you Parris, nice job.
  • castlino posted on 04/20/10 08:00:32 PM
    Hi Parris

    This module works! I am definitely using it in my new project. I haven't tested it in my actual business account/API though, but it works smoothly on my paypal sandbox account. I've also tried Dan's method but somehow it was like half baked or something, my testing gets stuck in Step 2 so I would recommend the original method from Parris... Cheers mates!! =)
  • danque posted on 02/14/10 10:34:55 AM
    Hey Parris,
    Hey guys,

    thank you for first-approach-paypal-component. In order to get things a little bit more integrated in CakePHP specific conventions i did it my (different) way and would like to share it with you.

    No additional vendor-file or constant.php needed, just one single and very generic CakePHP-component with configuration coming from core.php (like it should in my opinion).

    Have fun.
    Dan


    The main component:

    Component Class:

    <?php 
    /**
     * Paypal Payments Component Using cURL
     * 
     * inspired by PayPal-Code-Samples and Parris Khachi
     * see https://www.paypal.com/IntegrationCenter/sdk/PayPal_PHP_NVP_Samples.zip
     * see http://bakery.cakephp.org/articles/view/paypal-payments-component-using-curl
     *
     * @package         default
     * @subpackage      app
     * @version         $Revision: 94 $ ($Date: 2010-02-14 15:51:06 +0100 (So, 14 Feb 2010) $)
     * @author          Created by Daniel Quappe on 11.02.2010 14:08:36. Last Editor: $Author: dan $
     * @copyright       Copyright (c) 2010 Daniel Quappe. All rights reserved.
     */

    /**
     * Paypal Payment API Component class file.
     */
    class PaypalComponent extends Object
    {

    /* Benutzte Zusatz-Komponenten */
        
    var $components = array();

    /* Klassen-Member */
        
    var $controller null;


    /**
     * Initialize component
     * 
     * @access public
     * @return array
     * @author Daniel Quappe
     */
        
    function initialize(&$controller$settings = array())
        {
            
    /* Saving the controller reference for later use (as usual, if necessary) */
            
    $this->controller =& $controller;
        }

    /**
     * SetExpressCheckout
     *
     * @param array   $nvpDataArray Daten-Array
     * @return array  Ergebnis-Array
     * @access public
     * @author Daniel Quappe
     */
        
    function SetExpressCheckout($nvpDataArray = array())
        {
            
    $nvpDataArray['PAYMENTACTION'] = 'Sale';
            
    $nvpDataArray['CURRENCYCODE']  = Configure::read('PayPal.CURRENCYCODE');

            
    /* Shop-Logo-URL (optional, wenn moeglich https-URL) */
            
    if(!is_null($hdrimg Configure::read('PayPal.HDRIMG'))) {
                
    $nvpDataArray['HDRIMG'] = $hdrimg;
            }
            
            return 
    $this->hash_call("SetExpressCheckout"$nvpDataArray);
        }

    /**
     * GetExpressCheckoutDetails
     *
     * @param string   $token Verifizierungs-TOKEN
     * @return array   Ergebnis-Array
     * @access public
     * @author Daniel Quappe
     */
        
    function GetExpressCheckoutDetails($token '')
        {
            return 
    $this->hash_call("GetExpressCheckoutDetails", array('TOKEN' => $token));
        }

    /**
     * DoExpressCheckoutPayment
     *
     * @param array   $nvpDataArray Daten-Array
     * @return array  Ergebnis-Array
     * @access public
     * @author Daniel Quappe
     */
        
    function DoExpressCheckoutPayment($nvpDataArray = array())
        {
            
    $nvpDataArray['PAYMENTACTION'] = 'Sale';
            
    $nvpDataArray['CURRENCYCODE']  = Configure::read('PayPal.CURRENCYCODE');
            
    $nvpDataArray['IPADDRESS']     = env('SERVER_NAME');

            return 
    $this->hash_call("DoExpressCheckoutPayment"$nvpDataArray);
        }
        
    /**
     * Zentrale cURL-Methode zur Kommunikation mit Paypal
     *
     * @param string  $methodName   API-Methode
     * @param array   $nvpDataArray Name-Value-Pair-Data
     * @return array  $nvpResArray  Ergebnis-Array
     * @access private
     * @author Daniel Quappe
     */
        
    function hash_call($methodName ''$nvpDataArray = array())
        {
            
    /* Init */
            
    $nvpReqArray    = array();
            
    $nvpResArray    = array();
            
    $response       null;

            
    /* PayPal-API-Credentials etc. */
            
    $nvpReqArray['METHOD']       = $methodName;
            
    $nvpReqArray['VERSION']      = Configure::read('PayPal.VERSION');
            
    $nvpReqArray['PWD']          = Configure::read('PayPal.API_PASSWORD');
            
    $nvpReqArray['USER']         = Configure::read('PayPal.API_USERNAME');
            
    $nvpReqArray['SIGNATURE']    = Configure::read('PayPal.API_SIGNATURE');

            
    // Setting the curl parameters.
            
    $ch curl_init();
            
    curl_setopt($chCURLOPT_URLConfigure::read('PayPal.API_ENDPOINT'));
            
    curl_setopt($chCURLOPT_VERBOSE1);
        
            
    // Turning off the server and peer verification (TrustManager Concept).
            
    curl_setopt($chCURLOPT_SSL_VERIFYPEERFALSE);
            
    curl_setopt($chCURLOPT_SSL_VERIFYHOSTFALSE);
        
            
    curl_setopt($chCURLOPT_RETURNTRANSFER1);
            
    curl_setopt($chCURLOPT_POST1);

            
    // If USE_PROXY constant set to TRUE in core.php, then only proxy will be enabled.
            // Set proxy name to PROXY_HOST and port number to PROXY_PORT in core.php 
            
    if(Configure::read('PayPal.USE_PROXY') === true) {
                
    curl_setopt ($chCURLOPT_PROXYConfigure::read('PayPal.PROXY_HOST').":".Configure::read('PayPal.PROXY_PORT')); 
            }

            
    /* Kombinieren der NVP-Daten mit den PayPal-Credentials 
             * (inklusive Moeglichkeit zum Ueberschreiben einzelner Default-Werte, falls notwendig) */
            
    $nvpReqArray array_merge($nvpReqArray$nvpDataArray);

            
    // Setting the nvpReqArray as POST FIELD to curl
            
    curl_setopt($chCURLOPT_POSTFIELDShttp_build_query($nvpReqArraynull'&'));
        
            
    // Getting response from server
            
    $response curl_exec($ch);
        
            
    // Converting $response to an Associative Array
            
    $nvpResArray $this->deformatNVP($response);
        
            if (
    curl_errno($ch)) {
                
    $nvpResArray $this->APIError(curl_errno($ch), curl_error($ch), $nvpResArray);
            } else {
                
    curl_close($ch);
            }
            return 
    $nvpResArray;
        }

        
    /**
         * Saves error parameters
         *
         * @param string  $errorNo  Error-Number
         * @param string  $errorMsg Error-Description
         * @param array   $resArray Data-Array
         * @return array  $resArray Extended Data-Array
         * @access public
         * @author Daniel Quappe
         */
        
    function APIError($errorNo ''$errorMsg ''$resArray = array()){
            
    $resArray['Error']['Number']  = $errorNo;
            
    $resArray['Error']['Message'] = $errorMsg;
            return 
    $resArray;
        }

        
        
    /** This function will take NVPString and convert it to an Associative Array and it will decode the response.
          * It is usefull to search for a particular key and displaying arrays.
          * @nvpstr is NVPString.
          * @nvpArray is Associative Array.
          */
        
    function deformatNVP($nvpstr '')
        {
            
    $intial=0;
            
    $nvpArray = array();
        
            while(
    strlen($nvpstr))
            {
                
    //postion of Key
                
    $keyposstrpos($nvpstr,'=');

                
    //position of value
                
    $valuepos strpos($nvpstr,'&') ? strpos($nvpstr,'&'): strlen($nvpstr);
        
                
    /*getting the Key and Value values and storing in a Associative Array*/
                
    $keyval substr($nvpstr,$intial,$keypos);
                
    $valval substr($nvpstr,$keypos+1,$valuepos-$keypos-1);

                
    //decoding the respose
                
    $nvpArray[urldecode($keyval)] = urldecode$valval);
                
    $nvpstr substr($nvpstr,$valuepos+1,strlen($nvpstr));
            }
            return 
    $nvpArray;
        }
    }

    ?>


    1. Steps: SetExpressCheckout and GetExpressCheckoutDetails

    Controller Class:

    <?php 
    var $components = array('Paypal'); 

    function 
    _get($var) {
        return isset(
    $this->params['url'][$var])? $this->params['url'][$var]: null;


    [...]

    function 
    orderreview()
    {
    if(isset(
    $data['Order']['paymenttype']) && $data['Order']['paymenttype'] == 'paypal')
    {
        
    /* Init */
        
    $paypalRESULT   = array();
        
    $paypalACK      '';

        
    /* Step 1: SetExpressCheckout */
        
    if(is_null($this->_get('token')) || is_null($this->_get('PayerID')) )
        {
            
    /* Prepare PayPal-data for usage with cURL */
            
    $paymentInfo['AMT']         = 47.11;
            
    $paymentInfo['RETURNURL']   = Router::url(array('action' => 'orderreview'), true);
            
    $paymentInfo['CANCELURL']   = Router::url(array('action' => 'choosepayment'), true);

            
    /* Be prepared for giropay and banktransfer */
            
    $paymentInfo['GIROPAYSUCCESSURL'] = Router::url(array('action' => 'paypal_payment''id' => 'cleared')), true);
            
    $paymentInfo['GIROPAYCANCELURL']  = Router::url(array('action' => 'paypal_payment''id' => 'cancel')), true);
            
    $paymentInfo['BANKTXNPENDINGURL'] = Router::url(array('action' => 'paypal_payment''id' => 'cancel')), true);

            
    /* Shippingaddress (if necessary) */
            
    $paymentInfo['SHIPTONAME']          = 'foo';
            
    $paymentInfo['SHIPTOSTREET']        = 'bar';
            
    $paymentInfo['SHIPTOCITY']          = 'some';
            
    $paymentInfo['SHIPTOZIP']           = 'where';
            
    $paymentInfo['SHIPTOCOUNTRYNAME']   = 'Germany';
            
    $paymentInfo['SHIPTOCOUNTRYCODE']   = 'DE';

            
    /* Call the Paypal-API-component */
            
    $paypalRESULT   $this->Paypal->SetExpressCheckout($paymentInfo);
            
    $paypalACK      strtoupper($paypalRESULT["ACK"]);

            
    /* Redirect user to paypal, if API-result is okay */
            
    if($paypalACK == 'SUCCESS') {
                
    $this->redirect(Configure::read('PayPal.PAYPAL_URL').
                    
    Router::querystring(array('cmd' => '_express-checkout''token' => $paypalRESULT["TOKEN"])),
                    
    '302' // see https://www.paypal.com/en_US/ebook/PP_NVPAPI_DeveloperGuide/Appx_fieldreference.html#2830886
                
    );
            } 
        }
        
    /* Step 2: GetExpressCheckoutDetails */
        
    else
        {
            
    /* Call the Paypal-API-component */
            
    $paypalRESULT $this->Paypal->GetExpressCheckoutDetails($this->_get('token'));
            
    $paypalACK strtoupper($paypalRESULT["ACK"]);

            
    /* Save PayPal-Result for further usage and prepare the orderreview-view */
            
    if($paypalACK == 'SUCCESS'){
                
    $this->Session->write('paypalGetExpressCheckoutDetails'$paypalRESULT);
                
    $this->set('paypalEmail'$paypalRESULT['EMAIL']);
                
    $this->set('paypalEditLink'Configure::read('PayPal.PAYPAL_URL').
                    
    Router::querystring(array('cmd' => '_express-checkout''token' => $paypalRESULT["TOKEN"]))
                );
            }
        }

        
    /* In case of error */
        
    if($paypalACK != 'SUCCESS')
        {
            
    /* Generate message */
            
    $this->Session->setFlash('Problems paying with PayPal:<br>'.$paypalRESULT['L_LONGMESSAGE0'].'<br>Please try a different paymenttype if applicable.');

            
    //$error = $paypalRESULT['L_LONGMESSAGE0'];
            //$this->set('paymentError', $paypalRESULT['L_LONGMESSAGE0']);

            
    $this->redirect(array('action' => 'choosepayment'));
        }
    }
    ?>


    2. Steps: GetExpressCheckoutDetails

    Controller Class:

    <?php 
    /* Paypal-Integration */
    if($this->Session->check('paypalGetExpressCheckoutDetails'))
    {
        
    /* Get persistant data and delete session-key */
        
    $tmpPaypal $this->Session->read('paypalGetExpressCheckoutDetails');
        
    $this->Session->delete('paypalGetExpressCheckoutDetails');

        
    /* Prepare DoExpressCheckout-request-data */
        
    if(is_array($tmpPaypal) && $tmpPaypal['ACK'] == 'Success')
        {
            
    $paymentInfo['TOKEN']   = $tmpPaypal['TOKEN'];
            
    $paymentInfo['PAYERID'] = $tmpPaypal['PAYERID'];
            
    $paymentInfo['AMT']     = 47.11;
            
    $paymentInfo['DESC']    = 'Ordernumber. "'.$orderno.'"';
            
    $paymentInfo['CUSTOM']  = $orderno;

            
    /* Call the Paypal-API-component */
            
    $paypalRESULT   $this->Paypal->DoExpressCheckoutPayment($paymentInfo);
            
    $paypalACK      strtoupper($paypalRESULT["ACK"]);

            
    /* Save transaction-data if api-call was successful and amt is not different */
            
    if($paypalACK == 'SUCCESS')
            {
                if(isset(
    $paypalRESULT['TRANSACTIONID']) && !empty($paypalRESULT['TRANSACTIONID'])
                   && isset(
    $paypalRESULT['AMT']) && $paymentInfo['AMT'] == $paypalRESULT['AMT'])
                {        
                    
    /* Update database */
                    
    $this->Order->save(array(
                        
    'orderno'               => $orderno,
                        
    'pp_transactionid'      => $paypalRESULT['TRANSACTIONID'],
                        
    'pp_transactiontype'    => $paypalRESULT['TRANSACTIONTYPE'],
                        
    'pp_ordertime'          => $paypalRESULT['ORDERTIME'],
                        
    'pp_amt'                => $paypalRESULT['AMT'],
                        
    'pp_paymenttype'        => $paypalRESULT['PAYMENTTYPE'],
                        
    'pp_paymentstatus'      => $paypalRESULT['PAYMENTSTATUS'],
                        
    'pp_pendingreason'      => $paypalRESULT['PENDINGREASON'],
                        
    'pp_reasoncode'         => $paypalRESULT['REASONCODE']
                    ));

                    
    /* Prepare view */
                    
    $this->set('paypalEmail'$tmpPaypal['EMAIL']);

                    if(isset(
    $paypalRESULT['REDIRECTREQUIRED'])) {
                        
    $this->set('paypalRedirectUrl'Configure::read('PayPal.PAYPAL_URL').
                            
    Router::querystring(array(
                                
    'cmd'       => '_complete-express-checkout'
                                
    'token'     => $paypalRESULT["TOKEN"],
                                
    'payerid'   => $tmpPaypal["PAYERID"]
                        )));
                    }
                }
            } 
            
    /* In case of error */
            
    else {
                
    // see flash-call and redirect from above
            
    }
        }
    }
    ?>

    My core.php-paypal-configuration

    Component Class:

    <?php 

    /**
     * PayPal-configuration
     */
    Configure::write('PayPal.VERSION''3.0');
    Configure::write('PayPal.CURRENCYCODE''EUR');
    Configure::write('PayPal.HDRIMG''https://YOURSITE/img/head_logo.jpg');
    Configure::write('PayPal.USE_PROXY'FALSE);
    Configure::write('PayPal.PROXY_HOST''127.0.0.1');
    Configure::write('PayPal.PROXY_PORT''808');

    /* Sandbox-mode (true on development-system) */
    if (preg_match("/YOURSITE.local$/i"$_SERVER['SERVER_NAME'] ) ) {
        
    Configure::write('PayPal.API_USERNAME',  'sandbox-user');
        
    Configure::write('PayPal.API_PASSWORD',  'sandbox-pwd');
        
    Configure::write('PayPal.API_SIGNATURE''sandbox-signature');
        
    Configure::write('PayPal.API_ENDPOINT',  'https://api.sandbox.paypal.com/nvp');
        
    Configure::write('PayPal.PAYPAL_URL',    'https://www.sandbox.paypal.com/cgi-bin/webscr');
    }
    /* Live-mode */
    else {
        
    Configure::write('PayPal.API_USERNAME',  'live-user');
        
    Configure::write('PayPal.API_PASSWORD',  'live-pwd');
        
    Configure::write('PayPal.API_SIGNATURE''live-signature');
        
    Configure::write('PayPal.API_ENDPOINT',  'https://api-3t.paypal.com/nvp');
        
    Configure::write('PayPal.PAYPAL_URL',    'https://www.paypal.com/cgi-bin/webscr');
    }
    ?>
  • nezencreation posted on 09/24/09 05:29:43 PM
    First, the maddening issue I ran into while using this code to use PayPalPro's CreateRecurringPaymentsProfile... I kept getting Error Code 10004 "Transaction refused because of an invalid argument. See additional error messages for details." "Invalid argument".

    After checking over everything MANY times, I changed the VERSION constant to 51.0 like so: define('VERSION', '51.0'); based upon some of PayPal's example code.

    All worked perfectly after that!

    Second, here is the method I added to the Paypal.php class for recurring payments.

    function CreateRecurringPayments($paymentInfo=array()){
            
            $firstName =urlencode($paymentInfo['Member']['first_name']);
            $lastName =urlencode($paymentInfo['Member']['last_name']);
            $email =urlencode($paymentInfo['Member']['email']);
            $creditCardType =urlencode($paymentInfo['CreditCard']['credit_type']);
            $creditCardNumber = urlencode($paymentInfo['CreditCard']['card_number']);
            $expDateMonth =urlencode($paymentInfo['CreditCard']['expiration_month']);
            $padDateMonth = str_pad($expDateMonth, 2, '0', STR_PAD_LEFT);
            $expDateYear =urlencode($paymentInfo['CreditCard']['expiration_year']);
            $cvv2Number = urlencode($paymentInfo['CreditCard']['cv_code']);
            $address1 = urlencode($paymentInfo['Member']['billing_address']);
            $address2 = urlencode($paymentInfo['Member']['billing_address2']);
            $country = urlencode($paymentInfo['Member']['billing_country']);
            $city = urlencode($paymentInfo['Member']['billing_city']);
            $state =urlencode($paymentInfo['Member']['billing_state']);
            $zip = urlencode($paymentInfo['Member']['billing_zip']);
            
            $description = urlencode('DowntownFirst $16.00');
            $billingPeriod =urlencode(BILLINGPERIOD);
            $billingFrequency =urlencode(BILLINGFREQUENCY);
            $trialBillingPeriod =urlencode(TRIALBILLINGPERIOD);
            $trialBillingFrequency =urlencode(TRIALBILLINGFREQUENCY);
            $amt = urlencode(AMT);
            $trialAmt = urlencode(TRIALAMT);
            $failedInitAmtAction = urlencode("ContinueOnFailure");
            $autoBillAmt = urlencode("AddToNextBilling");
            $profileReference = urlencode("Anonymous");
            
            $currencyCode="USD";
            
            $startDate = urlencode(date('Y-m-d',time()+3600+24*30).'T00:00:00Z');
            
            $ip=$_SERVER['REMOTE_ADDR'];
            
            /* Construct the request string that will be sent to PayPal.
               The variable $nvpstr contains all the variables and is a
               name value pair string with & as a delimiter */
            $nvpstr="&EMAIL=$email&DESC=$description&IPADDRESS=$ip&CREDITCARDTYPE=$creditCardType&ACCT=$creditCardNumber&EXPDATE=".$padDateMonth.$expDateYear;
            $nvpstr.="&CVV2=$cvv2Number&FIRSTNAME=$firstName&LASTNAME=$lastName&STREET=$address1&STREET2=$address2&CITY=$city&STATE=$state&ZIP=$zip";
            $nvpstr.="&COUNTRYCODE=$country&CURRENCYCODE=$currencyCode&PROFILESTARTDATE=$startDate&BILLINGPERIOD=$billingPeriod&BILLINGFREQUENCY=$billingFrequency&AMT=$amt";
            $nvpstr.='&AUTOBILLAMT='.$autoBillAmt.'&PROFILEREFERENCE='.$profileReference.'&FAILEDINITAMTACTION='.$failedInitAmtAction;
            $nvpstr.="&TRIALBILLINGPERIOD=$trialBillingPeriod&TRIALBILLINGFREQUENCY=$trialBillingFrequency&TRIALAMT=$trialAmt&TRIALTOTALBILLINGCYCLES=1&MAXFAILEDPAYMENTS=1";
            
            //echo $nvpstr;
            
            /* Make the API call to PayPal, using API signature.
               The API response is stored in an associative array called $resArray */
            $resArray=$this->hash_call("CreateRecurringPaymentsProfile",$nvpstr);
            
            /* Display the API response back to the browser.
               If the response from PayPal was a success, display the response parameters'
               If the response was an error, display the errors received using APIError.php.
               */
            
            return $resArray;
            //Contains 'TRANSACTIONID,AMT,AVSCODE,CVV2MATCH, Or Error Codes'
        }
    And the modifications to constants.php

    define('BILLINGPERIOD','Month');
    define('BILLINGFREQUENCY','1');
    define('AMT','16.00');
    define('TRIALBILLINGPERIOD','Month');
    define('TRIALBILLINGFREQUENCY','1');
    define('TRIALAMT','16.00');
    define('TRIALTOTALBILLINGCYCLES','1');
    define('MAXFAILEDPAYMENTS','1');

    Lastly, my thanks to Sai for the Sandbox code snippet. Very useful.
  • sai.perchard posted on 08/30/09 02:08:08 AM
    Thank you for the component, proved very useful.

    A quick note for Australian users. I do not believe we can use the direct payment functionality. From what I have read, this is only available to US and UK users.

    I also needed an easier way to switch between the Sandbox and live configurations. I have modified the constants.php file as follows. One should also note that in the original constants.php file, it isn't mentioned that 'API_ENDPOINT' must be changed in order to use the sandbox.


    <?php
    $sandbox 
    FALSE;

    if (
    $sandbox == TRUE)
    {
        
    define('API_USERNAME''sandbox_username');
        
    define('API_PASSWORD''sandbox_password');
        
    define('API_SIGNATURE''sandbox_signature');
        
    define('API_ENDPOINT''https://api.sandbox.paypal.com/nvp');
        
    define('PAYPAL_URL''https://www.sandbox.paypal.com/webscr&cmd=_express-checkout&token=');
    }
    else
    {
        
    define('API_USERNAME''live_username');
        
    define('API_PASSWORD''live_password');
        
    define('API_SIGNATURE''live_signature');
        
    define('API_ENDPOINT''https://api-3t.paypal.com/nvp');
        
    define('PAYPAL_URL''https://www.paypal.com/webscr&cmd=_express-checkout&token=');
    }

    define('USE_PROXY',FALSE);
    define('PROXY_HOST''127.0.0.1');
    define('PROXY_PORT''808');
    define('VERSION''3.0');
    ?>
  • daemonfire300 posted on 08/19/09 07:58:23 AM
    i miss a short (and clear) list within an example showing the use in a controllr..
  • philemon posted on 08/04/09 08:22:48 AM
    It's exacly what I needed for a little project.
    Thanks for your article
  • parris posted on 07/05/09 07:47:32 PM
    :)
  • parris posted on 06/27/09 10:12:51 PM
    Hi David,
    Sorry for the late response. In the next week I will publish a component for express checkout and refund transaction based on transaction id. Also maybe a mass transaction id refund feature too.
  • dknell posted on 05/17/09 10:44:05 AM
    Hi, thanks for the component! I was wondering if you are going to implement the Express Checkout functionality.
login to post a comment.