PayPal IPN, много регистраций, один платеж, все в неправильном порядке

Недавно я внедрил IPN PayPal в CodeIgniter2, используя PayPal Lib. Я использую систему для подписок.

У меня есть таблица в моей базе данных, которая записывает все запросы IPN в базе данных.

По какой-то причине после каждой регистрации запросы IPN не проходят должным образом. Я склонен получать один subscr_payment вместе с несколькими subscr_signups, все с одним и тем же subscr_id. Это вызывает неисчислимые хлопоты в системе по очевидным причинам. Что добавляет к этому, тот факт, что запросы IPN не приходят в правильном порядке, иногда я получаю subscr_payment до subscr_signup - что делает невозможным отслеживание, так как нет подписки subscr_id от регистрации, чтобы связать его с пользователем.

У меня есть Google, и я не могу найти много информации по этому поводу. Кажется, я немного аномалия. Мне интересно, связано ли это с PayPal Lib, которую я использую, но на самом деле я не хочу делать это вне CodeIgniter, так как я занимаюсь большой обработкой. Ниже приведен полный скрипт IPN.

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

    }
}

Ниже приведен пример звонков на мой IPN для каждой транзакции (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

3 ответа

Решение

Учтите это - PayPal - это ненормативная лексика. Теперь вернемся к проблеме.

Скорее всего, это не ваша вина, ни CodeIgniter, ни библиотека. PayPal очень плохо передает данные единообразно и своевременно, он также медленный и не очень хорошо связывает данные.

Мой вам совет - сохраняйте все в таблицу IPN всякий раз, когда выполняется обратный вызов, и даже отправляйте электронное письмо самому себе, когда выполняется вызов IPN. Затем постарайтесь выяснить, что PayPal на самом деле отправляет вам, что вы хотите, и отбросьте все остальное.

Я думаю, что IPN-вызов выполняется, даже если транзакция не имеет никакого отношения к вашему веб-сайту. Так что, если ваша бабушка отправит вам ваши рождественские деньги через PayPal, они появятся в обратном вызове IPN.

Надеюсь, это немного поможет.

PayPal не совсем прост в использовании, но позвольте мне поделиться 3 советами для решения проблем, с которыми вы сталкиваетесь.

1) Создайте таблицу для хранения всех ответов IPN от PayPal. Убедитесь, что у вас есть столбец с именем "raw", в котором хранится ВСЕ... do "json_encode($this->paypal_lib->ipn_data)". Это спасет вас... поскольку позже вы сможете написать скрипт для извлечения данных из необработанного столбца в его собственный столбец. Это также помогает с отладкой.

2) Для начала просто извлеките то, что необходимо, в столбцы таблицы ipn, чтобы вы могли легко запрашивать... вот все, что я считаю необходимым для моего варианта использования, который, вероятно, похож на ваш.

    $this->payment_model->create_ipn(array(
        'invoice' => $this->paypal_lib->ipn_data['invoice'],
        'txn_type' => $this->paypal_lib->ipn_data['txn_id'],
        'parent_txn_id' => $this->paypal_lib->ipn_data['parent_txn_id'],
        'txn_type' => $this->paypal_lib->ipn_data['txn_type'],
        'item_name' => $this->paypal_lib->ipn_data['item_name'],
        'item_number' => $this->paypal_lib->ipn_data['item_number'],
        'quantity' => $this->paypal_lib->ipn_data['quantity'],
        'exchange_rate' => $this->paypal_lib->ipn_data['exchange_rate'],
        'settle_amount' => $this->paypal_lib->ipn_data['settle_currency'],
        'settle_amount' => $this->paypal_lib->ipn_data['settle_amount'],
        'mc_currency' => $this->paypal_lib->ipn_data['mc_currency'],
        'mc_fee' => $this->paypal_lib->ipn_data['mc_fee'],
        'mc_gross' => $this->paypal_lib->ipn_data['mc_gross'],
        'payment_date' => $this->paypal_lib->ipn_data['payment_date'],
        'payment_status' => $this->paypal_lib->ipn_data['payment_status'],
        'payment_type' => $this->paypal_lib->ipn_data['payment_type'],
        'pending_reason' => $this->paypal_lib->ipn_data['pending_reason'],
        'reason_code' => $this->paypal_lib->ipn_data['reason_code'],
        'subscr_id' => $this->paypal_lib->ipn_data['subscr_id'],
        'subscr_date' => $this->paypal_lib->ipn_data['subscr_date'] ? mdate('%Y-%m-%d %H:%i:%s', strtotime($this->paypal_lib->ipn_data['subscr_date'])) : NULL,
        'subscr_effective' => $this->paypal_lib->ipn_data['subscr_effective'] ? mdate('%Y-%m-%d %H:%i:%s', strtotime($this->paypal_lib->ipn_data['subscr_effective'])) : NULL,
        'period1' => $this->paypal_lib->ipn_data['period1'],
        'period2' => $this->paypal_lib->ipn_data['period2'],
        'period3' => $this->paypal_lib->ipn_data['period3'],
        'amount1' => $this->paypal_lib->ipn_data['amount1'],
        'amount2' => $this->paypal_lib->ipn_data['amount2'],
        'amount3' => $this->paypal_lib->ipn_data['amount3'],
        'mc_amount1' => $this->paypal_lib->ipn_data['mc_amount1'],
        'mc_amount2' => $this->paypal_lib->ipn_data['mc_amount2'],
        'mc_amount3' => $this->paypal_lib->ipn_data['mc_amount3'],
        'recurring' => $this->paypal_lib->ipn_data['recurring'],
        'reattempt' => $this->paypal_lib->ipn_data['reattempt'],
        'retry_at' => $this->paypal_lib->ipn_data['retry_at'] ? mdate('%Y-%m-%d %H:%i:%s', strtotime($this->paypal_lib->ipn_data['retry_at'])) : NULL,
        'recur_times' => $this->paypal_lib->ipn_data['recur_times'],
        'payer_id' => $this->paypal_lib->ipn_data['payer_id'],
        'payer_email' => $this->paypal_lib->ipn_data['payer_email'],
        'payer_status' => $this->paypal_lib->ipn_data['payer_status'],
        'payer_business_name' => $this->paypal_lib->ipn_data['payer_business_name'],
        'ipn_track_id' => $this->paypal_lib->ipn_data['ipn_track_id'],
        'raw' => json_encode($this->paypal_lib->ipn_data_arr),
        'test_ipn' => $this->paypal_lib->ipn_data['test_ipn']
    ));

не копируйте мой код выше, так как он предназначен только для того, чтобы дать вам некоторые грубые идеи... если вы адаптируете мой код, убедитесь, что функция ipn_data похожа на это (иначе вы получите множество ошибок)

function ipn_data($key)
{
    return isset($this->fields[$key]) ? $this->fields[$key] : NULL;
}

чтобы понять все возможные вещи, которые они могут отправить по этой ссылке, это золото https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_html_IPNandPDTVariables

но ^ вздох ^ не верь, что оно будет обновлено. я обнаружил несоответствия в том, что они говорят, и что они отправили мне обратно.

3) ОК, я должен признать, это еще одна глупость, которую делает PayPal - они не дают вам дату IPN, даже если они не гарантируют порядок, в котором она поступает на ваш сервер. Для subscr_payment они дают вам payment_date... для subscr_signup они дают вам subscr_date... так что для того, чтобы получить IPN в правильном порядке, нужно иметь столбец с именем ipn_date.

'ipn_date' => isset($this->paypal_lib->ipn_data['payment_date']) ? 
    mdate('%Y-%m-%d %H:%i:%s', strtotime($this->paypal_lib->ipn_data['payment_date'])) : 
        (isset($this->paypal_lib->ipn_data['subscr_date']) ? 
            mdate('%Y-%m-%d %H:%i:%s', strtotime($this->paypal_lib->ipn_data['subscr_date'])) : NULL),

теперь все круто, вы можете "заказать по ipn_date", и я уверяю вас, все будет в правильном порядке.

Обратите внимание, что мой первый пример кода не имеет этот столбец, но он должен быть там. Я просто копирую и вставляю свой код разработки, чтобы дать вам идею.

Что я делаю, так это игнорирую регистрацию и просто обрабатываю (создаю нового пользователя и т. Д.) Фактическую транзакцию оплаты. И я бы не стал хранить все эти трансляции IPN. Пусть ваш IPN-скрипт отправит вам EMail на каждый из них с эхом всех опубликованных полей. Тогда у вас будет запись о них.

Другие вопросы по тегам