Search code examples
phpcodeigniterpaypalpaypal-ipn

PayPal IPN: (PHP) Send custom field without <form>


I'm using CodeIgniter to create a page where users can register for a charity cycle my club is organising.

The registration page accepts info such as name/dob/phone number etc etc, and asks the user if they want to pay online or on the day. Clicking the submit button sends the info to the same page, runs form validation, inserts the info to the database (including timestamp) and checks if the user selected to pay online. If so, the user is redirected to the PayPal donate button URL to complete their payment.

My question is, how do I send the PayPal custom field through the URL? I have tried appending &custom=whatever to the button URL, (where 'whatever' is the new database row ID and the timestamp) but this doesn't work after completing a donation with a sandbox account.

IPN:

public function ipn()
{
    // STEP 1: Read POST data

    // reading posted data from directly from $_POST causes serialization 
    // issues with array data in POST
    // reading raw POST data from input stream instead. 
    $raw_post_data = file_get_contents('php://input');
    $raw_post_array = explode('&', $raw_post_data);
    $myPost = array();
    foreach($raw_post_array as $keyval) 
    {
        $keyval = explode ('=', $keyval);
        if(count($keyval) == 2)
        {
            $myPost[$keyval[0]] = urldecode($keyval[1]);
        }
    }

    // read the post from PayPal system and add 'cmd'
    $req = 'cmd=_notify-validate';
    if(function_exists('get_magic_quotes_gpc')) 
    {
        $get_magic_quotes_exists = true;
    } 
    foreach($myPost as $key => $value) 
    {        
        if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) 
        { 
            $value = urlencode(stripslashes($value)); 
        } 
        else 
        {
            $value = urlencode($value);
        }
        $req .= "&$key=$value";
    }


    // STEP 2: Post IPN data back to paypal to validate

    $ch = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
    curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));

    // In wamp like environments that do not come bundled with root authority certificates,
    // please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set the directory path 
    // of the certificate as shown below.
    // curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem');
    if(!($res = curl_exec($ch))) 
    {
        // error_log("Got " . curl_error($ch) . " when processing IPN data");
        curl_close($ch);
        exit;
    }
    curl_close($ch);


    // STEP 3: Inspect IPN validation result and act accordingly    

    if(strcmp($res, "VERIFIED") == 0) 
    {
        // PAYMENT VALIDATED & VERIFIED! 
        $custom = explode('_', $_POST['custom']);
        $reg_id = $custom[0];
        $date_registered = $custom[1];

        $query = $this->db->query('SELECT date_registered FROM registrations WHERE id=' . $reg_id);
        $array = $query->result_array();
        if($array['date_registered'] == $date_registered)
        {
            $data = array(
                'has_payed' => 1
            );
            $this->db->where('id', $reg_id);
            $this->db->update('registrations', $data);      
        }           
    }  
    elseif(strcmp($res, "INVALID") == 0) 
    {  
        // do whatever
    }           
}

Note that I need this to work to update the 'has_payed' field of the database, which requires the row ID to be sent when the registration page is submitted.

Registration controller:

// check payment online
if($this->input->post('pay_online') == 1) 
{
    // redirect to paypal depending on route
    switch($route)
    {
        case '80':
        case '40':
            // €20 for over 16s
            if($age >= 16)
            {
                redirect('https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=H3PFP5GGN2X5J&custom=' . urlencode($reg_id . '_' . $sql['date_registered']));
            }
            // €10 for anyone under 16
            else 
            {
                redirect('a different button URL');
            }
            break;
        case '15':
            // €15 for family
            redirect('a different button URL');
            break;
    }
}

The above code is working, as in, I'm being redirected to the button URL when I submit the form with the appropriate values (i.e. doing the 80km or 40km route, and being over 16yo). When I complete the payment at the PayPal page, the 'has_payed' field in the database is not updated as it should be (see IPN page).

Also, note that I do obviously have IPN enabled in my sandbox seller account.

I have searched for hours for a solution to this, but cannot find anything. Maybe I'm overlooking something simple! I'd appreciate any help.

Thanks!


UPDATE

Instead of redirecting the user to the PayPal button URL, I'm now redirecting them to a HTML page with the <form> code on it, and a Javascript which auto-submits the form when they load the page. This is working fine, but the database field is still not updating when the user has donated.

<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" id="paypal_form" method="post" target="_top">
    <input type="hidden" name="cmd" value="_s-xclick">
    <input type="hidden" name="hosted_button_id" value="H3PFP5GGN2X5J">
    <input type="hidden" name="notify_url" value="#IPN URL#" />
    <input type="hidden" name="custom" value="<?php echo urlencode($reg_id . '_' . $sql['date_registered']); ?>">
    <input type="image" src="https://www.sandbox.paypal.com/en_US/GB/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal – The safer, easier way to pay online.">
    <img alt="" border="0" src="https://www.sandbox.paypal.com/en_GB/i/scr/pixel.gif" width="1" height="1">
</form>

Solution

  • The problem, of course, was simple.

    CSRF protection was enabled in config.php, which caused the IPN to not work. Disabling it fixed the issue.