Q:

PayPal IPN, lots of sign ups, one payment, all in the wrong order

I've recently implemented a PayPal IPN into CodeIgniter2, using the PayPal Lib.   I'm using the system for subscriptions. 
I have a table in my database that records all IPN requests in the database.   
For some reason, after every sign up the IPN requests aren't coming through properly.   I tend to get one subscr_payment along with several subscr_signups, all with the same subscr_id.   It's causing untolds amount of hassle within the system, for obvious reasons.   What adds to this, is the fact that the IPN requests don't come in the correct order, sometimes I get the subscr_payment before the subscr_signup - making it impossible to track as there's no subscr_id from the sign up to link it to a user. 
I've had a Google and can't find much on this, I seem to be a little bit of an anomaly.   I'm wondering if it's something to do with the PayPal Lib I'm using, but I don't really want to have to do it outside of CodeIgniter, as I am doing a lot of processing.   Below is the full IPN script. 

class Paypal extends CI_Controller {
    function _construct()
        {
        parent::_construct();
        $this->load->library('paypal_lib');
    }
function ipn()
{

    $this->output->enable_profiler(TRUE);

    $this->load->model('payments_model');
    $this->load->model('paypal_model');
    $this->load->model('users_model');

    ob_start();

    if ($this->paypal_lib->validate_ipn()) 
    {

            $paypal_id = $this->paypal_model->add_paypal_ipn($this->paypal_lib->ipn_data);
            // Split the 'custom' field up, containing ID of temp user, ID of package and coupon
            $custom = explode(';', $this->paypal_lib->ipn_data['custom']);

            ###
            # subscription sign up
            ###
            if($this->paypal_lib->ipn_data['txn_type'] == 'subscr_signup') {
                // Activate user/move from temp > live
                $this->users_model->move_temp($custom[0], $this->paypal_lib->ipn_data['subscr_id']);
            } # end subscr_signup

            ###
            # subscription payment
            ###
            if($this->paypal_lib->ipn_data['txn_type'] == 'subscr_payment') {
                // Grab the coupon info, if we have one
                $discount = 1;
                if(!empty($custom[2])){
                    $this->load->model('coupons_model');
                    $couponinfo = $this->coupons_model->get_coupon($custom[2]);
                    $discount = $couponinfo->discount;
                }                    
                // Grab the package info
                $package = $this->packages_model->get_package($custom[1]);
                $price = $package->monthly * $discount; // Calculate discount, 0.8 = 20% off

                // Does the price calculated match the gross price?   If not something fishy is going on, block it
                if($price ! = $this->paypal_lib->ipn_data['mc_gross']){
                    mail(CONTACT_EMAIL, SITE_NAME.'  failed payment attempt, possible hack', 'Price paid doesnt match price computed... paid: '.$this->paypal_lib->ipn_data['mc_gross'].' -  price worked out: '.$price. "\n\n".print_r($this->paypal_lib->ipn_data, true));
                    exit;
                }

                // Grab the user's details based on the subscr_id
                $user = $this->users_model->get_user_by_subscr_id($this->paypal_lib->ipn_data['subscr_id']);

                // Add payment to the payments table
                $data = array(
                    'user_id' => $user->user_id,
                    'subscr_id' => $user->subscr_id,
                    'txn_id' => $this->paypal_lib->ipn_data['txn_id'],
                    'amount' => $this->paypal_lib->ipn_data['mc_gross'],
                    'package_id' => $custom[1],
                    'coupon' => (empty($custom[2]) ? '' :  $custom[2])
                );
                $this->payments_model->add_payment($data);

                // Set (forced) user as active, and update their current active package
                $data1 = array(
                    'package_id' => $custom[1],
                    'active' => 1
                );
                $this->users_model->update_user($data1, $user->user_id);
            } # end subscr_payment

            ###
            # subscription failed/cancelled
            ###
            if($this->paypal_lib->ipn_data['txn_type'] == 'subscr_cancel' || $this->paypal_lib->ipn_data['txn_type'] == 'subscr_failed') {
                // Grab user
                $user = $this->users_model->get_user_by_subscr_id($this->paypal_lib->ipn_data['subscr_id']);

                // Make user inactive
                $data = array('active' => 0);
                $this->users_model->update_user($data, $user->user_id);
            } # end subscr_cancel|subscr_failed

            ###
            # subscription modified/payment changed
            ###
            if($this->paypal_lib->ipn_data['txn_type'] == 'subscr_modify') {
                // Grab the coupon info, if we have one
                $discount = 1;
                if(!empty($custom[2])){
                    $this->load->model('coupons_model');
                    $couponinfo = $this->coupons_model->get_coupon($custom[2]);
                    $discount = $couponinfo->discount;
                }                    
                // Grab the package info
                $package = $this->packages_model->get_package($custom[1]);
                $price = $package->monthly * $discount; // Calculate discount, 0.8 = 20% off

                // Does the price calculated match the gross price?   If not something fishy is going on, block it
                if($price ! = $this->paypal_lib->ipn_data['mc_gross']){
                    mail(CONTACT_EMAIL, SITE_NAME.'  failed payment attempt, possible hack', 'Price paid doesnt match price computed... paid: '.$this->paypal_lib->ipn_data['mc_gross'].' -  price worked out: '.$price. "\n\n".print_r($this->paypal_lib->ipn_data, true));
                    exit;
                }

                // Grab the user's details based on the subscr_id
                $user = $this->users_model->get_user_by_subscr_id($this->paypal_lib->ipn_data['subscr_id']);

                // Add payment to the payments table
                $data = array(
                    'user_id' => $user->user_id,
                    'subscr_id' => $user->subscr_id,
                    'txn_id' => $this->paypal_lib->ipn_data['txn_id'],
                    'amount' => $this->paypal_lib->ipn_data['mc_gross'],
                    'package_id' => $custom[1],
                    'coupon' => (empty($custom[2]) ? '' :  $custom[2])
                );
                $this->payments_model->add_payment($data);

                // Set (forced) user as active, and update their current active package
                $data1 = array(
                    'package_id' => $custom[1],
                    'active' => 1
                );
                $this->users_model->update_user($data1, $user->user_id);
            } # end subscr_modify

    }
}

Below is an example of the calls made to my IPN for each transaction (CSV). 
paypal_id,txn_id,subscr_id,txn_type,created
1,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:19:43
2,9XM95194MM564230E,I-FMUK0B5KJWKA,subscr_payment,2011-02-03 16:19:45
3,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:19:57
4,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:20:19
6,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:21:03
7,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:22:25
8,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:25:08
10,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:30:33
12,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 16:41:16
14,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 17:02:42
16,NULL,I-FMUK0B5KJWKA,subscr_signup,2011-02-03 17:45:26

A:

Consider this - PayPal is insert profanity.  Now revisit the problem. 
The chances are this isn't your fault, or CodeIgniter's or the Library's.  PayPal is very bad at giving data in a uniform and timely manner, it is also slow and doesn't link data together very well. 
My advice to you is save everything into an IPN table whenever a callback is made, even email yourself when ever an IPN call is made.  Then work to try and figure out what PayPal is actually sending you, what you want and throw out the rest. 
I think an IPN call is made even if the transaction has nothing to do with your web site.  So if your Grandma sends you your Christmas money via PayPal it'll appear on the IPN callback. 
Hope that helps a bit.