1

我最近使用PayPal Lib在 CodeIgniter2 中实现了一个 PayPal IPN 。我正在使用该系统进行订阅。

我的数据库中有一个表,记录了数据库中的所有 IPN 请求。

出于某种原因,每次注册后,IPN 请求都无法正常通过。我倾向于获得一个 subscr_payment 和几个 subscr_signups,所有这些都具有相同的 subscr_id。由于显而易见的原因,它在系统内造成了无数的麻烦。更重要的是,IPN 请求的顺序不正确,有时我在 subscr_signup 之前得到 subscr_payment - 由于注册时没有 subscr_id 将其链接到用户,因此无法跟踪。

我有一个谷歌,在这方面找不到太多,我似乎有点反常。我想知道这是否与我正在使用的 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

    }
}

以下是为每笔交易 (CSV) 对我的 IPN 进行的调用示例。

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
4

3 回答 3

4

考虑一下 - 贝宝是插入亵渎。现在重新审视这个问题。

这可能不是你的错,也不是 CodeIgniter 或图书馆的错。PayPal 非常不擅长统一及时地提供数据,它也很慢,并且不能很好地将数据链接在一起。

我对您的建议是,无论何时进行回叫,都将所有内容保存到 IPN 表中,甚至在进行 IPN 呼叫时给您自己发送电子邮件。然后努力弄清楚 PayPal 实际发送给您的是什么,您想要什么,然后扔掉其余的。

我认为即使交易与您的网站无关,也会进行 IPN 呼叫。因此,如果您的祖母通过 PayPal 将您的圣诞钱寄给您,它将出现在 IPN 回调中。

希望有点帮助。

于 2011-02-04T01:35:10.133 回答
2

paypal 不是很容易使用,但让我分享 3 个技巧来解决您面临的问题。

1) 创建一个表来存储来自 PayPal 的所有 IPN 响应。确保你有一个名为“raw”的列来存储所有内容......执行“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

但是 ^sigh^ 不要相信它会被更新。我发现他们的医生说的和他们发回给我的内容不一致。

3)好吧,我不得不承认这是贝宝做的另一件愚蠢的事情——即使他们不保证它到达你服务器的顺序,他们也不会给你一个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 订购”,我向您保证一切都会按照正确的顺序进行。

ps 请注意,我的第一个示例代码没有此列,但它应该存在。我只是复制和粘贴我的开发代码给你一个想法。

于 2012-02-18T14:02:34.300 回答
1

我所做的是忽略注册,只在实际支付交易中处理(创建新用户等)。而且我不会费心存储所有这些IPN trans。不过,让您的 IPN 脚本向您发送一封电子邮件,其中包含所有已发布字段的回声。然后你会有他们的记录。

于 2012-10-05T16:25:12.053 回答