TailTemplate Build stunning websites faster with our pre-designed Tailwind CSS templates

How to implement PayPal Express Checkout for Digital Good in CakePHP - Part 2

PayPal Digital Goods for Express Checkout Provides a very easy and user friendly way for users to make payment. All transactions happen in an overlayer modal box, users will not need to leave your site during the whole process unlike other methods, where users will be redirected to PayPal official site to make payment.

In part one of this series of tutorial, we have wrote a Paypal model class, in this tutorial, we will show you how to implement the front-end (Javascript part) and utilize the Paypal class to communicate with PayPal API server. If you have not yet read part one yet, go ahead and read it first.

Preparation

  1. Make sure you have required knowledge as discussed in part one.
  2. Set up a working copy of CakePHP 1.3

Setup View

Create four view files as follow

  1. views/purchases/payment.ctp: This is a dummy payment page and it is where the PayPal overlayer modal box will show.
<?php echo $this->Form->create('Purchase',array('url'=>array('action'=>'paypal_set_ec')));?>
<?php echo $this->Form->submit('Pay now',array('id'=>'paypal-pay'));
<?php echo $this->Form->end();?>
 
<!-- PayPal payment -->
<?php echo $this->Javascript->link('https://www.paypalobjects.com/js/external/dg.js',false);?>
<script>
var dg = new PAYPAL.apps.DGFlow({
// the HTML ID of the form submit button which calls setEC
trigger: 'paypal-pay',
// the experience type: instant or mini
expType: 'instant'
});
</script>

In this page, we create a simple form with a submit button with id of "paypal-pay". Then we include digital goods Javascript from PayPal CDN. Last part, we initiate DGFlow object.

  1. views/purchases/paypal_set_ec.ctp: This is the page to make API call SetExpressCheckout.
<script language='javascript'>parent.opener.location.reload();</script>

This page is very simple. What it does is to reload the parent page of the iframe. Actually this will only happen if the API call does not go through because in the controller action function, we will redirect user to PayPal page if the API call succeeds. If it fails, we will reload the parent page with an error message.

  1. views/purchases/paypal_back.ctp: This is view page after a successful payment or user will be directed to after clicking on cancel payment button.
<?php echo $this->Javascript->link('https://www.paypalobjects.com/js/external/dg.js',false);?>
<script>
    if (window.opener){
    window.close();
    }
    else if (top.dg.isOpen() == true){
    top.dg.closeFlow();
    }
    parent.opener.location.reload();
</script>

As discussed above, this is the page when user returns from PayPal after successful payment, or when he clicks on cancel button. Logic behind this page is to close the modal box and reload the parent page of the modal box after user returns. This view page will be shared between two action functions, since they will render the same view content.

Setup Controller

We have already built our view files above. Now let us create a controller under directory app/controllers/purchases_controller.php copy content below to the file:

App::import('Core','Router');
class PurchasesController extends AppController {
    
    function payment() {
         
    }
     
    function paypal_set_ec() {
        if (!empty($this->data)) {                     
            //build nvp string
            //use your own logic to get and set each variable
            $returnURL = Router::url(array('controller'=>'purchases','action'=>'paypal_return'),true);
            $cancelURL = Router::url(array('controller'=>'purchases','action'=>'paypal_cancel'),true);
            $nvpStr=
             "RETURNURL=$returnURL&CANCELURL=$cancelURL"
            ."&PAYMENTREQUEST_0_CURRENCYCODE=SGD"
            ."&PAYMENTREQUEST_0_AMT=10.00"  
            ."&PAYMENTREQUEST_0_ITEMAMT=10.00"
            ."&AYMENTREQUEST_0_PAYMENTACTION=Sale"
            ."&L_PAYMENTREQUEST_0_ITEMCATEGORY0=Digital"  
            ."&L_PAYMENTREQUEST_0_NAME0=test"
            ."&L_PAYMENTREQUEST_0_QTY0=1"
            ."&L_PAYMENTREQUEST_0_AMT0=10.00"          
            ;           
            //do paypal setECCheckout
            App::import('Model','Paypal');
            $paypal = new Paypal();
            if($paypal->setExpressCheckout($nvpStr)) {
                $result = $paypal->getPaypalUrl($paypal->token);
            }else {
                $this->log($paypal->errors);
                $result=false;
            }
             
            if(false!==$result) {
                $this->redirect($result);
            }else {
                $this->Session->setFlash(__('Error while connecting to PayPal, Please try again', true));
            }
        }
    }
     
     /*
     * page when user clicks on Cancel on Paypal page
     */
    function paypal_cancel($id=null) {
        $this->layout = 'clean';    
        $this->render('paypal_back');   
    }
 
    /*
     *redirects buyer after the buyer approves the payment
     */
    function paypal_return($id=null) {
            $payerId    = $this->params['url']['PayerID'];
            $token      = $this->params['url']['token'];  
            //get nvp string
            //use your own logic to get and set each variable
            $nvpStr=
                 "TOKEN=$token&PAYERID=$payerId"
                ."&PAYMENTREQUEST_0_CURRENCYCODE=SGD"
                ."&PAYMENTREQUEST_0_AMT=10.00"  
                ."&PAYMENTREQUEST_0_ITEMAMT=10.00"
                ."&AYMENTREQUEST_0_PAYMENTACTION=Sale"
                ."&L_PAYMENTREQUEST_0_ITEMCATEGORY0=Digital"  
                ."&L_PAYMENTREQUEST_0_NAME0=test"
                ."&L_PAYMENTREQUEST_0_QTY0=1"
                ."&L_PAYMENTREQUEST_0_AMT0=10.00"
                ;
            //do paypal setECCheckout
            App::import('Model','Paypal');
            $paypal = new Paypal();
            if($paypal->doExpressCheckoutPayment($nvpStr)) {
                $result = true;
            }else {
                $this->log($paypal->errors);
                $result = false;
            }       
             
            if (false==$result) {
                $this->Session->setFlash(__('Error while making payment, Please try again', true),'message_fail');
            } else {
                $this->Session->setFlash(__('Thank you for purchasing our deal.', true),'message_ok');
            }          
             
            $this->render('paypal_back');   
    }
     
}

As you can see, there are two main functions in this controller, which are paypal_set_ec() and paypal_return(). Let us explain a bit more about these two functions.

  1. paypal_set_ec(): When user clicks on "Pay now" button on payment page, dg.js will trigger to call this function. And what this function does is to do an API call (setExpressCheckout) with an nvp string. If API server acknowledges the nvp string, it will return a token. Then this function will redirect to a PayPal page with that token, which will render user a PayPal official page.
  2. paypal_return(): This function is triggered when PayPal redirects user back to our site after successful payment. This function will get two pieces of important data from the URL, which are PayerID and Token. And then it calls PayPal API(doExpressCheckoutPayment) with an nvp string.

Some important notes to take:

  1. We have used hard coded values for building the nvp string. You should use your own application logic to get those values (normally get it from models), or it makes more sense to build the nvp string using model (fat model).
  2. All the variables in the nvp string are required to be filled. If you forget or supply wrong values, you may get error message as "We are unable to complete your request at this time.Please try again later. We apologize for the inconvenience."
  3. If the API call fails, you can check "tmp/logs/error.log" file to investigate.
  4. You will have to enable express checkout from PayPal.

The End

This is the end of the two parts tutorial. You can always review part one here. Hopefully this simple tutorial helped you with your development. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.