2013-10-10 02:44:53 +00:00
|
|
|
<?php defined('SYSPATH') or die('No direct access allowed.');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This class provides PAYPAL support
|
|
|
|
*
|
|
|
|
* @package Checkout
|
|
|
|
* @category Plugins
|
|
|
|
* @author Deon George
|
|
|
|
* @copyright (c) 2009-2013 Open Source Billing
|
|
|
|
* @license http://dev.osbill.net/license.html
|
|
|
|
*/
|
|
|
|
abstract class Checkout_Plugin_Paypal extends Checkout_Plugin {
|
|
|
|
protected $url_prod = 'www.paypal.com';
|
|
|
|
protected $url_test = 'www.sandbox.paypal.com';
|
|
|
|
private $ipn_test = '173.0.82.126';
|
2013-12-04 10:37:09 +00:00
|
|
|
protected $email_prod = ''; // @todo This should be in the DB
|
|
|
|
protected $email_test = ''; // @todo This should be in the DB
|
|
|
|
protected $test_mode = FALSE;
|
2013-10-10 02:44:53 +00:00
|
|
|
|
|
|
|
protected $curlopts = array(
|
|
|
|
CURLOPT_CONNECTTIMEOUT => 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) {
|
2013-12-04 10:37:09 +00:00
|
|
|
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.'));
|
2013-10-10 02:44:53 +00:00
|
|
|
|
2013-12-04 10:37:09 +00:00
|
|
|
HTTP::redirect(URL::link('user','welcome'));
|
2013-10-10 02:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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))
|
2013-12-04 10:37:09 +00:00
|
|
|
return _('Thank you');
|
2013-10-10 02:44:53 +00:00
|
|
|
|
|
|
|
$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.
|
2013-12-04 10:37:09 +00:00
|
|
|
if ($cno->data['business'] == ($this->test_mode ? $this->email_test : $this->email_prod)) {
|
2013-10-10 02:44:53 +00:00
|
|
|
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?
|
2013-12-04 10:37:09 +00:00
|
|
|
if ($this->co->fee_passon AND $cno->data['mc_gross'] === (string)($total+$this->co->fee($total))) {
|
2013-10-10 02:44:53 +00:00
|
|
|
// 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;
|
2013-12-05 05:22:23 +00:00
|
|
|
foreach ($po->subitems() as $pio) {
|
2013-10-10 02:44:53 +00:00
|
|
|
$io = ORM::factory('Invoice',$pio->invoice_id);
|
2013-12-05 05:22:23 +00:00
|
|
|
|
|
|
|
$iio = ORM::factory('Invoice_Item');
|
2013-10-10 02:44:53 +00:00
|
|
|
$iio->quantity = 1;
|
|
|
|
$iio->module_id = $cno->mid()->id;
|
2013-12-19 23:00:32 +00:00
|
|
|
$iio->module_ref = $cno->id;
|
2013-10-10 02:44:53 +00:00
|
|
|
$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();
|
|
|
|
|
2013-12-05 05:22:23 +00:00
|
|
|
$io->subitem_add($iio,$io->account->country,TRUE);
|
|
|
|
|
2013-10-10 02:44:53 +00:00
|
|
|
// @todo Validate Save
|
|
|
|
$io->save();
|
|
|
|
|
2013-12-05 05:22:23 +00:00
|
|
|
$pio->alloc_amt = ($pio->alloc_amt+$iio->total() > $pio->invoice->total()) ? $pio->alloc_amt : $pio->alloc_amt+$iio->total();
|
2013-10-10 02:44:53 +00:00
|
|
|
$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');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-12-04 10:37:09 +00:00
|
|
|
$pio = $po->payment_item;
|
|
|
|
$pio->alloc_amt = $cno->data['mc_gross_'.$c];
|
|
|
|
$pio->invoice_id = $cio->module_item;
|
|
|
|
$po->add_item($pio);
|
2013-10-10 02:44:53 +00:00
|
|
|
|
|
|
|
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']);
|
2013-06-17 12:47:44 +00:00
|
|
|
$po->checkout_id = $this->co->id;
|
2013-10-10 02:44:53 +00:00
|
|
|
$po->notes = $cno->data['txn_id'];
|
2013-12-04 10:37:09 +00:00
|
|
|
|
|
|
|
if (! $po->save())
|
|
|
|
$cno->result = array('msg'=>'Failed to save PO?','po'=>$po);
|
2013-10-10 02:44:53 +00:00
|
|
|
|
|
|
|
// 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 {
|
2013-12-04 10:37:09 +00:00
|
|
|
$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))),
|
|
|
|
);
|
|
|
|
|
2013-10-10 02:44:53 +00:00
|
|
|
// 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)
|
2013-12-04 10:37:09 +00:00
|
|
|
$cno->processed = TRUE;
|
|
|
|
|
|
|
|
$cno->save();
|
2013-10-10 02:44:53 +00:00
|
|
|
|
|
|
|
return _('Processed, thank you!');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
?>
|