60, CURLOPT_FAILONERROR => TRUE, CURLOPT_FOLLOWLOCATION => FALSE, CURLOPT_HEADER => FALSE, CURLOPT_HTTPPROXYTUNNEL => FALSE, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYHOST => FALSE, CURLOPT_SSL_VERIFYPEER => FALSE, CURLOPT_VERBOSE => FALSE, ); /** * User return from Paypal after payment */ public function after(Cart $co) { SystemMessage::factory() ->title(_('Payment Processing')) ->type('info') ->body(_('Thank you for your payment with paypal. It will be processed and applied to your cart items automatically in due course.')); HTTP::redirect(URL::link('user','welcome')); } /** * User cancelled from Paypal and returned */ public function cancel(Cart $co) { SystemMessage::add(array( 'title'=>_('Payment Cancelled'), 'type'=>'info', 'body'=>sprintf('Payment with Paypal was cancelled at your request.'), )); HTTP::redirect('cart'); } /** * Paypal payment notification and verification */ public function notify(Model_Checkout_Notify $cno) { $debug_mode = Kohana::$config->load('debug')->checkout_notify; // If testing if (! $cno->status OR $cno->processed OR ($debug_mode AND Request::$client_ip == $this->ipn_test)) return _('Thank you'); $co = Cart::instance(isset($cno->data['custom']) ? $cno->data['custom'] : ''); if (! $co->contents()) return _('Thank you!'); if (! $debug_mode) { $request = Request::factory(sprintf('https://%s/cgi-bin/webscr',isset($cno->data['test_ipn']) ? $this->url_test : $this->url_prod)) ->method('POST'); $request->client()->options(Arr::merge($this->curlopts,array( CURLOPT_POSTFIELDS => Arr::merge(array('cmd'=>'_notify-validate'),$cno->data), ))); $response = $request->execute(); } switch ($debug_mode ? 'VERIFIED' : $response->body()) { case 'VERIFIED': // Verify that the IPN is for us. // @todo This should be in the DB. if ($cno->data['business'] == ($this->test_mode ? $this->email_test : $this->email_prod)) { switch ($cno->data['payment_status']) { case 'Completed': // Our cart items total. $total = $co->total(); $po = ORM::factory('Payment'); // Does the payment cover the cart total? if ($this->co->fee_passon AND $cno->data['mc_gross'] === (string)($total+$this->co->fee($total))) { // Store the amounts in an array, so we can pro-rata the fee to each item. $amts = array(); // Add the fee to each item (pro-rated) for ($c=1;$c<=$cno->data['num_cart_items'];$c++) { // The payment fee - there should only be 1 of these, and it should be the last item. // We assume fees are added to $po->items() which are invoices. if (preg_match('/^0:/',$cno->data['item_number'.$c])) { $i = $j = 0; foreach ($po->items() as $pio) { $io = ORM::factory('Invoice',$pio->invoice_id); // @todo Need to do tax. $iio = $io->add_item(); $iio->quantity = 1; $iio->module_id = $cno->mid()->id; $iio->item_type = 125; // Payment Fee $iio->price_base = (++$j==count($amts)) ? $cno->data['mc_gross_'.$c]-$i : Currency::round($pio->alloc_amt/array_sum($amts)*$cno->data['mc_gross_'.$c]); $iio->date_start = $iio->date_stop = time(); // @todo Validate Save $io->save(); $pio->alloc_amt = ($pio->alloc_amt+$iio->price_base > $pio->invoice->total()) ? $pio->alloc_amt : $pio->alloc_amt+$iio->price_base; $i += $iio->price_base; } } elseif (is_numeric($cno->data['item_number'.$c])) { array_push($amts,$cno->data['mc_gross_'.$c]); $cio = ORM::factory('cart',$cno->data['item_number'.$c]); if ($cio->loaded()) switch ($cio->motype()) { case 'invoice': // Validate we are all the same account // @todo Need to handle if the cart has more than 1 account. if (! $po->account_id AND $cio->mo()->account_id) { $po->account_id = $cio->mo()->account_id; } elseif ($po->account_id != $cio->mo()->account_id) { throw new Kohana_Exception('Unable to handle payments for multiple accounts'); } $pio = $po->payment_item; $pio->alloc_amt = $cno->data['mc_gross_'.$c]; $pio->invoice_id = $cio->module_item; $po->add_item($pio); break; default: throw new Kohana_Exception('Dont know how to handle :item',array(':item',$cio->motype())); } // Dont know how to handle this item. } else { // @todo } } // @todo Validate Save $po->account_id = $cio->mo()->account_id; $po->fees_amt = $cno->data['mc_fee']; $po->total_amt = $cno->data['mc_gross']; $po->date_payment = strtotime($cno->data['payment_date']); $po->checkout_id = $this->co->id; $po->notes = $cno->data['txn_id']; if (! $po->save()) $cno->result = array('msg'=>'Failed to save PO?','po'=>$po); // Clear the cart if (! $debug_mode) $co->delete(); } elseif (! $this->co->fee_passon AND $cno->data['mc_gross']-$cno->data['mc_fee'] == $total) { // Ignore the fee } else { $cno->result = array( 'msg'=>'IPN doesnt match cart total', 't'=>$total, 'tt'=>(string)($total+$this->co->fee($total)), 'g'=>$cno->data['mc_gross'], 'fpo'=>$this->co->fee_passon, 't1'=>($cno->data['mc_gross'] === (string)($total+$this->co->fee($total))), ); // If there is more than 1 item in the cart, we'll leave it to an admin to process. if ($cno->data['num_cart_items'] == 1) { } else { // @todo - add the payment, with no payment items } } break; case 'Refunded': default: throw new Kohana_Exception('Unable to handle payments of type :type',array(':type'=>$cno->data['payment_status'])); } } else { $cno->status = FALSE; } break; case 'INVALID': default: $cno->status = FALSE; } if (! $debug_mode) $cno->processed = TRUE; $cno->save(); return _('Processed, thank you!'); } } ?>