CakePHP2.0: PayPal IPN-Handler

Sometimes it’s hard to implement a little snipped of code… Last time when I tried to use the IPN-Handler from PayPal. But at the end, it works pretty good. For that reason I will share my code with you.

First, set this in the Controller (in the beforeFilter):

 PHP |  copy code |? 
1
if ($this->action == 'payPalHandler') { 
2
 $this->Security->validatePost = false;
3
 $this->Security->csrfCheck = false;
4
}

If you are using the security component, you have to set these things to false, otherwise nothing will work correctly because the handler works as a form.

Then you have to build the PayPal-Handler in the Controller. Here we go:

 PHP |  copy code |? 
01
public function payPalHandler()
02
{ 
03
        // sandbox:
04
        $paypalspecs = array('url' => 'ssl://www.sandbox.paypal.com', 'primaryemail' => 'email_sandbox@example.com');
05
        // live:
06
        //$paypalspecs = array('url' => 'ssl://www.paypal.com', 'primaryemail' => 'email@example.com');
07
 
08
        // read the post from PayPal system and add 'cmd'
09
        $req = 'cmd=_notify-validate';
10
 
11
        foreach ($_POST as $key => $value) {
12
                $value = urlencode(stripslashes($value));
13
                $req .= "&$key=$value";
14
        }
15
 
16
        // post back to PayPal system to validate
17
        $header  = "POST /cgi-bin/webscr HTTP/1.0\r\n";
18
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
19
        $header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
20
 
21
        $fp = fsockopen ($paypalspecs['url'], 443, $errno, $errstr, 30);
22
 
23
        if (!$fp) 
24
        {
25
                // HTTP ERROR
26
        } 
27
        else 
28
        {
29
                fputs ($fp, $header . $req);
30
                while (!feof($fp)) 
31
                {
32
                        $res = fgets ($fp, 1024);
33
                        $fgts = $fgts.$res;
34
                        if (strcmp ($res, "VERIFIED") == 0) 
35
                        {
36
                                // check the payment_status is Completed  -> done
37
                                // check that txn_id has not been previously processed -> done, will be checked in the model
38
                                // check that receiver_email is your Primary PayPal email  -> done
39
                                // check that payment_amount/payment_currency are correct -> NOT done...
40
                                // process payment
41
 
42
                                $item_name = $_POST['item_name'];
43
                                $id = $_POST['item_number']; // -> the item_number is the id of the user in my case
44
                                $receiver_email = $_POST['receiver_email'];
45
                                $txn_id = $_POST['txn_id'];
46
                                $mc_gross = $_POST['mc_gross'];
47
                                $payment_status = $_POST['payment_status'];
48
 
49
                                if($item_name == 'credits' && $payment_status == 'Completed' && $receiver_email == $paypalspecs['primaryemail'])
50
                                {
51
                                        if(!empty($id))
52
                                        {
53
                                                $lastAmmount = $this->Credit->checkCredits($id);
54
                                                $amount = (int) $_POST['mc_gross'];
55
                                                $debugvar['response'] = $res;
56
                                                $debugvar['post'] = $_POST;
57
                                                $debugvar['fgets'] = $fgts;
58
                                                $paypalvars = print_r($debugvar, true);
59
                                                $data = array('Credit' => array(
60
                                                        'count' => $amount,
61
                                                        'action' => 'Payment via Paypal',
62
                                                        'cumulated' => $lastAmmount+$amount,
63
                                                        'user_id' => $id,
64
                                                        'txn_id' => $txn_id,
65
                                                        'mc_gross' => $mc_gross,
66
                                                        'paypal_vars_array' => $paypalvars,
67
                                                        'payment_status' => $payment_status
68
                                                        ));
69
                                                $this->Credit->save($data);
70
                                        } else {
71
                                        }
72
                                }
73
                        }
74
                        else if (strcmp ($res, "INVALID") == 0) 
75
                        {
76
                                // log for manual investigation
77
                        }
78
                }
79
                fclose ($fp);
80
        }
81
        $this->autoRender = false;
82
 
83
}

Most of the code is from the official IPN-Handler. But some things I want to explain:

Line 03 to 06: Here we go to set the credentials. You should know one important thing: If you are testing your handler via the PayPal Sandbox Instant Payment Notification (IPN) simulator, then make sure using the sandbox credentials. Otherwise the thing will never give a proper response.

Line 49: Now we have to check if the information retrieved is ok or not

Line 53: I get some other information from the model

Line 59 to 69: Here we are going to save the information

Line 81: At the end, we don’t use a view nor a layout, so we set autoRender as false.

Other comments: In this version I’m not checking the value and the currency. If you have suggestions or questions about this snippet, leave a comment!

CakePHP2.0: Different arrays in Views

Today, I’m gonna show you how you can access to different arrays (made in a model) in a view:

I the Model I have something like this:

 PHP |  copy code |? 
01
public function getAllPosts() 
02
    { 
03
        return $this->find('all', array('contain' => FALSE, 'fields' 
04
=> array('id','title','created',),)); 
05
    } 
06
 
07
public function getSomePosts() 
08
    { 
09
        return $this->find('all', array('contain' => 
10
FALSE,'conditions' => array('Post.user_id' => $authorId), 'fields'  => 
11
array('id','title','created',),)); 
12
    } 

In my Controller I have something like this: 

 PHP |  copy code |? 
1
public function index() 
2
{ 
3
    $authorId = $this->Auth->user('id'); 
4
    $allposts = $this->Post->getAuthorPosts($authorId); 
5
    $someposts = $this->Post->getAllPosts(); 
6
    $this->set(compact('allposts', 'someposts'));
7
} 

Now I can read the two arrays with $allposts and $someposts. Isn’t that easy? Hey, I haven’t tried it so far, but I think that should work.

CakePHP2.0: dynamic meta-tags in the layout

My next problem is: Where do I set the code for the meta tags in the layout? Some tags are the same on all pages, others will be different.

My first idea was to set up an array in the AppController (as default meta tags) and then to change some values in the array in certain Controllers. But furthermore, I have to set separate meta tags in the different Views/pages…

At this time, I don’t have any solution for that. But I will post my best solution here if I will find one…

Do you have any ideas? Solutions? Links for help?

CakePHP2.0: It’s hard to find pages about the right CakePHP-version

As a CakePHP2.0-Novice it’s hard to find help about CakePHP. It’s not the fact that there are no websites (there are a lot of them) , it’s the fact that the most of them (in the search results of search engines) are about CakePHP 1.2 or 1.3.

That’s the reason why my posts about CakePHP have the string “CakePHP2.0″ in the title. I hope this helps a little bit if somebody is looking for help about CakePHP 2.0. It would be great if you would name your posts and questions with the same string in the title. Your thougths about that are welcome.

CakePHP2.0: List items with conditions

Today, I found a simple solution to list items in a view and how I can set a condition (or more of them) via url. I used the pagination()-function to do that. With this solution, I don’t need special code in the Model nor in the View.

Here is my index()-function in the Controller (that’s all):

 PHP |  copy code |? 
01
public function index($oid = null) {
02
 
03
        $this->paginate = array(
04
                'limit' => 10,
05
                'order' => array(
06
                    'Track.created' => 'desc',
07
                    ),
08
                );
09
        if($oid != null) {
10
            $this->paginate = array(
11
                'conditions' => array(
12
                    'Track.offer_id LIKE' => $oid
13
                    ),
14
                );
15
        }
16
        $data = $this->paginate('Track');
17
        $this->set(compact('data'));
18
}

Now, you can call the List like www.example.com/track/index to view all datasets or www.example.com/track/index/394 to view only the datasets with offer_id = 394.

From line 2 to 7 I’m going to set the general paginate-settings. From line 8 to 14 I’m going to set a condition if the $oid is not null.

CakePHP2.0: Search form

I was trying to implement a search form in an existing Model/Controller/View-thing. I tried a lot. At the end I came up with a very skinny solution. Here it is:

Model:

 PHP |  copy code |? 
1
echo $this->Form->create('Offer', array('type' => 'post'));
2
 echo $this->Form->input('searchterm', array('type' => 'text', 'label' => __('Suchausdruck:')));
3
 echo $this->Form->end(__('suchen'));

Controller:

 PHP |  copy code |? 
01
    public function index($searchterm = null) {
02
        if($this->request->is('post')) {
03
            $searchterm = $this->data['Offer']['searchterm'];
04
            $this->paginate = array(
05
                'Offer' => array(
06
                    'conditions' => $this->Offer->searchConditions($searchterm),
07
                ),
08
            );
09
            $this->set('offers',$this->paginate());
10
        } else {
11
            $this->paginate = array(
12
                'Offer' => array(
13
                    //'conditions' => $this->Offer->searchConditions($searchterm),
14
                ),
15
            );
16
            $this->set('offers',$this->paginate());
17
        }     
18
    }

View:

 PHP |  copy code |? 
1
echo $this->Form->create('Offer', array('type' => 'post'));
2
 echo $this->Form->input('searchterm', array('type' => 'text', 'label' => __('Suchausdruck:')));
3
 echo $this->Form->end(__('suchen'));

For this easy solution, we are going to use the pagination.

CakePHP2.0: Best Practices – Fat Models and Skinny Controllers

I’m pretty new with CakePHP and I’m working hard getting better in it. I use the one and only book to learn the things. But there is an important thing I missed in the CakePHP-Book:

http://bitfluxx.com/2008/01/23/cakephp-best-practices-fat-models-and-skinny-controllers.html

Read this article and think about it. Now, I think twice which code snippet I place in the controller and which in the model. My models are getting fatter and fatter and my controllers are clean and skinny.

MySQL generating a Random-Token

Recently, I needed a random token (integer) in a db-table. I came up with this short solution:

 MySQL |  copy code |? 
1
UPDATE
2
    `offers`
3
SET
4
    `token` = SUBSTRING(RAND(), 3)

As described in the MySQL-Manual, the function RAND() is not a real random function, but it generates numbers between 0 and 1. Therefore I take the SUBSTRING(xy, 3) to cutting the two characters “0.”. As a result I get an integer which is suitable for my needs. It’s only a “quick and dirty” solution.