This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
khosb/modules/checkout/classes/Checkout/Plugin/Paypal.php
2018-06-13 21:40:08 +10:00

222 lines
7.0 KiB
PHP

<?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';
protected $email_prod = 'deon@graytech.net.au'; // @todo This should be in the DB
protected $email_test = ''; // @todo This should be in the DB
protected $test_mode = FALSE;
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) {
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->active 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->subitems() as $pio) {
$io = ORM::factory('Invoice',$pio->invoice_id);
$iio = ORM::factory('Invoice_Item');
$iio->quantity = 1;
$iio->module_id = $cno->mid()->id;
$iio->module_ref = $cno->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();
$io->subitem_add($iio,$io->account->country,TRUE);
// @todo Validate Save
$io->save();
$pio->alloc_amt = ($pio->alloc_amt+$iio->total() > $pio->invoice->total()) ? $pio->alloc_amt : $pio->alloc_amt+$iio->total();
$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->active = FALSE;
}
break;
case 'INVALID':
default:
$cno->active = FALSE;
}
if (! $debug_mode)
$cno->processed = TRUE;
$cno->save();
return _('Processed, thank you!');
}
}
?>