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 на каждый из них с эхом всех опубликованных полей. Тогда у вас будет запись о них.