Cart work for payments and Paypal work to test
This commit is contained in:
parent
133ae4d5c6
commit
69645c4eea
@ -2,7 +2,7 @@
|
||||
RewriteEngine On
|
||||
|
||||
# Installation directory
|
||||
RewriteBase /osb/
|
||||
RewriteBase /
|
||||
|
||||
# Protect hidden files from being viewed
|
||||
<Files .*>
|
||||
|
@ -90,7 +90,7 @@ if (isset($_SERVER['KOHANA_ENV']))
|
||||
* - boolean expose set the X-Powered-By header FALSE
|
||||
*/
|
||||
Kohana::init(array(
|
||||
'base_url' => '/osb/',
|
||||
'base_url' => '/',
|
||||
'caching' => TRUE,
|
||||
'index_file' => '',
|
||||
));
|
||||
|
@ -254,21 +254,13 @@ class Auth_OSB extends Auth_ORM {
|
||||
$this->complete_login($user);
|
||||
|
||||
// Do we need to update databases with our new sesion ID
|
||||
// @todo figure out where this is best to go
|
||||
$session_change_trigger = array('Cart'=>'session_id');
|
||||
|
||||
if (count($session_change_trigger) AND (session_id() != $oldsess)) {
|
||||
foreach ($session_change_trigger as $t => $c) {
|
||||
if (Config::moduleexist($c)) {
|
||||
$orm = ORM::factory($t)
|
||||
->where($c,'=',$oldsess);
|
||||
|
||||
foreach ($orm->find_all() as $o)
|
||||
$sct = Kohana::$config->load('config')->session_change_trigger;
|
||||
if (session_id() != $oldsess AND count($sct))
|
||||
foreach ($sct as $t => $c)
|
||||
if (Config::moduleexist($t))
|
||||
foreach (ORM::factory(ucwords($t))->where($c,'=',$oldsess)->find_all() as $o)
|
||||
$o->set('session_id',session_id())
|
||||
->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class overrides Kohana's Cache
|
||||
*
|
||||
* @package OSB/Modifications
|
||||
* @category Classes
|
||||
* @category Helpers
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Deon George
|
||||
* @license http://dev.leenooks.net/license.html
|
||||
*/
|
||||
abstract class Cache extends Kohana_Cache {
|
||||
public static $default = 'apc';
|
||||
}
|
||||
?>
|
@ -124,7 +124,7 @@ class Config extends Kohana_Config {
|
||||
}
|
||||
|
||||
public static function moduleexist($module) {
|
||||
return array_key_exists($module,static::modules()) ? TRUE : FALSE;
|
||||
return array_key_exists(strtolower($module),static::modules()) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
public static function copywrite() {
|
||||
|
@ -12,6 +12,11 @@
|
||||
*/
|
||||
|
||||
class Controller_Login extends lnApp_Controller_Login {
|
||||
/**
|
||||
* Enable site registration
|
||||
*
|
||||
* @todo Needs to be written
|
||||
*/
|
||||
public function action_register() {
|
||||
// If user already signed-in
|
||||
if (Auth::instance()->logged_in()!= 0) {
|
||||
@ -19,53 +24,7 @@ class Controller_Login extends lnApp_Controller_Login {
|
||||
HTTP::redirect('welcome/index');
|
||||
}
|
||||
|
||||
// Instantiate a new user
|
||||
$account = ORM::factory('Account');
|
||||
|
||||
// If there is a post and $_POST is not empty
|
||||
if ($_POST) {
|
||||
// Check Auth
|
||||
$status = $account->values($_POST)->check();
|
||||
|
||||
if (! $status) {
|
||||
foreach ($account->validation()->errors('form/register') as $f => $r) {
|
||||
// $r[0] has our reason for validation failure
|
||||
switch ($r[0]) {
|
||||
// Generic validation reason
|
||||
default:
|
||||
SystemMessage::add(array(
|
||||
'title'=>_('Validation failed'),
|
||||
'type'=>'error',
|
||||
'body'=>sprintf(_('The defaults on your submission were not valid for field %s (%s).'),$f,$r)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ido = ORM::factory('Module')
|
||||
->where('name','=','account')
|
||||
->find();
|
||||
|
||||
$account->id = $ido->record_id->next_id($ido->id);
|
||||
// Save the user details
|
||||
if ($account->save()) {}
|
||||
|
||||
}
|
||||
|
||||
SystemMessage::add(array(
|
||||
'title'=>_('Already have an account?'),
|
||||
'type'=>'info',
|
||||
'body'=>_('If you already have an account, please login..')
|
||||
));
|
||||
|
||||
Block::add(array(
|
||||
'title'=>_('Register'),
|
||||
'body'=>View::factory('register')
|
||||
->set('account',$account)
|
||||
->set('errors',$account->validation()->errors('form/register')),
|
||||
));
|
||||
|
||||
$this->template->left = HTML::anchor('login','Login').'...';
|
||||
HTTP::redirect('login');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,17 +39,11 @@ class Controller_TemplateDefault extends lnApp_Controller_TemplateDefault {
|
||||
}
|
||||
|
||||
protected function _right() {
|
||||
if ($this->template->right)
|
||||
return $this->template->right;
|
||||
else
|
||||
return $this->_cart();
|
||||
return ($this->template->right) ? $this->template->right : $this->_cart();
|
||||
}
|
||||
|
||||
private function _cart() {
|
||||
if (! Config::moduleexist('cart') OR ! class_exists('cart') OR ! Cart::instance()->contents()->reset(FALSE)->count_all())
|
||||
return '';
|
||||
|
||||
return Cart::instance()->cart_block();
|
||||
return (! Config::moduleexist('cart') OR ! class_exists('Cart') OR ! count(Cart::instance()->contents()) OR strtolower(Request::current()->controller()) == 'cart') ? '' : Cart::instance()->cart_block();
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -11,21 +11,16 @@
|
||||
* @license http://dev.leenooks.net/license.html
|
||||
*/
|
||||
class HTTP_Exception_404 extends Kohana_HTTP_Exception_404 {
|
||||
public function __construct($message = NULL, array $variables = NULL, Exception $previous = NULL)
|
||||
{
|
||||
set_exception_handler(array(get_class($this),'handler'));
|
||||
parent::__construct($message, $variables, $previous);
|
||||
}
|
||||
public function get_response() {
|
||||
$response = Response::factory();
|
||||
$response->status($this->_code);
|
||||
|
||||
public static function handler(Exception $e)
|
||||
{
|
||||
SystemMessage::add(array(
|
||||
'title'=>_('Page not found'),
|
||||
'type'=>'error',
|
||||
'body'=>sprintf(_('The page [%s] you requested was not found?'),Request::detect_uri()),
|
||||
));
|
||||
$view = View::factory('errors/404');
|
||||
$view->message = $this->getMessage();
|
||||
|
||||
HTTP::redirect('welcome');
|
||||
$response->body($view->render());
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -46,5 +46,12 @@ abstract class Kohana extends Kohana_Core {
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out our Class Name as per Kohana's standards
|
||||
*/
|
||||
public static function classname($name) {
|
||||
return str_replace(' ','_',ucwords(strtolower(str_replace('_',' ',$name))));
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -36,6 +36,15 @@ class Model_Module extends ORM_OSB {
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Return an instance of this Module's Model
|
||||
*
|
||||
* @param $id PK of Model
|
||||
*/
|
||||
public function instance($id=NULL) {
|
||||
return ORM::factory(ucwords($this->name),$id);
|
||||
}
|
||||
|
||||
public function list_external() {
|
||||
return $this->_where_active()->where('external','=',TRUE)->find_all();
|
||||
}
|
||||
|
@ -37,77 +37,8 @@ abstract class ORM_OSB extends ORM {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will enhance the [Validate::filter], since it always passes
|
||||
* the value as the first argument and sometimes functions need that to not
|
||||
* be the first argument.
|
||||
*
|
||||
* Currently this implements:
|
||||
* [date()][date-ref]
|
||||
*
|
||||
* [date-ref]: http://www.php.net/date
|
||||
*
|
||||
* This function will throw an exception if called without a function
|
||||
* defined.
|
||||
*
|
||||
* @param mixed $val Value to be processed
|
||||
* @param string $func Name of function to call
|
||||
* @param string $arg Other arguments for the function
|
||||
* @todo This has probably changed in KH 3.1
|
||||
*/
|
||||
final public static function _filters($val,$func,$arg) {
|
||||
switch ($func) {
|
||||
case 'date':
|
||||
return date($arg,$val);
|
||||
default:
|
||||
throw new Exception(sprintf(_('Unknown function: %s (%s,%s)'),$func,$arg,$val));
|
||||
}
|
||||
}
|
||||
|
||||
final public static function form($table,$blank=FALSE) {
|
||||
return ORM::factory($table)->formselect($blank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Next record id
|
||||
*
|
||||
* @param array Validate object
|
||||
* @param string Primary Key
|
||||
*/
|
||||
public static function get_next_id($model,$field) {
|
||||
if (! is_null($model->$field))
|
||||
return TRUE;
|
||||
|
||||
$model->_changed[$field] = $field;
|
||||
|
||||
$ido = ORM::factory('Module')
|
||||
->where('name','=',$model->_table_name)
|
||||
->find();
|
||||
|
||||
if (! $ido->loaded())
|
||||
throw new Kohana_Exception('Problem getting record_id for :table',array(':table'=>$model->_table_name));
|
||||
|
||||
$model->$field = $ido->record_id->next_id($ido->id);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the site ID attribute for each row update
|
||||
*/
|
||||
public static function set_site_id($model,$field) {
|
||||
if (! is_null($model->$field))
|
||||
return TRUE;
|
||||
|
||||
$model->_changed[$field] = $field;
|
||||
$model->$field = Config::siteid();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function __get($column) {
|
||||
if (array_key_exists($column,$this->_table_columns)) {
|
||||
|
||||
// If the column is a blob, we'll decode it automatically
|
||||
if (
|
||||
$this->_table_columns[$column]['data_type'] == 'blob'
|
||||
@ -155,7 +86,94 @@ abstract class ORM_OSB extends ORM {
|
||||
return parent::__get($column);
|
||||
}
|
||||
|
||||
public function formselect($blank) {
|
||||
/**
|
||||
* This function will enhance the [Validate::filter], since it always passes
|
||||
* the value as the first argument and sometimes functions need that to not
|
||||
* be the first argument.
|
||||
*
|
||||
* Currently this implements:
|
||||
* [date()][date-ref]
|
||||
*
|
||||
* [date-ref]: http://www.php.net/date
|
||||
*
|
||||
* This function will throw an exception if called without a function
|
||||
* defined.
|
||||
*
|
||||
* @param mixed $val Value to be processed
|
||||
* @param string $func Name of function to call
|
||||
* @param string $arg Other arguments for the function
|
||||
* @todo This has probably changed in KH 3.1
|
||||
*/
|
||||
final public static function x_filters($val,$func,$arg) {
|
||||
switch ($func) {
|
||||
case 'date':
|
||||
return date($arg,$val);
|
||||
default:
|
||||
throw new Exception(sprintf(_('Unknown function: %s (%s,%s)'),$func,$arg,$val));
|
||||
}
|
||||
}
|
||||
|
||||
final public static function xform($table,$blank=FALSE) {
|
||||
return ORM::factory($table)->formselect($blank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and Store DB BLOB data.
|
||||
*/
|
||||
private function blob($data,$set=FALSE) {
|
||||
try {
|
||||
return $set ? gzcompress(serialize($data)) : unserialize(gzuncompress($data));
|
||||
|
||||
// Maybe the data isnt compressed?
|
||||
} catch (Exception $e) {
|
||||
return $set ? serialize($data) : unserialize($data);
|
||||
}
|
||||
}
|
||||
|
||||
public function config($key) {
|
||||
$mc = Config::instance()->so->module_config($this->_object_name);
|
||||
|
||||
return empty($mc[$key]) ? '' : $mc[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Next record id
|
||||
*
|
||||
* @param array Validate object
|
||||
* @param string Primary Key
|
||||
*/
|
||||
final public static function get_next_id($model,$field) {
|
||||
if (! is_null($model->$field))
|
||||
return TRUE;
|
||||
|
||||
$model->_changed[$field] = $field;
|
||||
|
||||
$ido = ORM::factory('Module')
|
||||
->where('name','=',$model->_table_name)
|
||||
->find();
|
||||
|
||||
if (! $ido->loaded())
|
||||
throw new Kohana_Exception('Problem getting record_id for :table',array(':table'=>$model->_table_name));
|
||||
|
||||
$model->$field = $ido->record_id->next_id($ido->id);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the site ID attribute for each row update
|
||||
*/
|
||||
final public static function set_site_id($model,$field) {
|
||||
if (! is_null($model->$field))
|
||||
return TRUE;
|
||||
|
||||
$model->_changed[$field] = $field;
|
||||
$model->$field = Config::siteid();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function xformselect($blank) {
|
||||
$result = array();
|
||||
|
||||
if ($blank)
|
||||
@ -174,6 +192,10 @@ abstract class ORM_OSB extends ORM {
|
||||
return array_key_exists($key,$this->$column) ? $this->{$column}[$key] : NULL;
|
||||
}
|
||||
|
||||
final public function mid() {
|
||||
return ORM::factory('Module',array('name'=>$this->_table_name));
|
||||
}
|
||||
|
||||
public function save(Validation $validation = NULL) {
|
||||
// Find any fields that have changed, and process them.
|
||||
if ($this->_changed)
|
||||
@ -194,19 +216,6 @@ abstract class ORM_OSB extends ORM {
|
||||
return parent::save($validation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and Store DB BLOB data.
|
||||
*/
|
||||
private function blob($data,$set=FALSE) {
|
||||
return $set ? gzcompress(serialize($data)) : unserialize(gzuncompress($data));
|
||||
}
|
||||
|
||||
public function config($key) {
|
||||
$mc = Config::instance()->so->module_config($this->_object_name);
|
||||
|
||||
return empty($mc[$key]) ? '' : $mc[$key];
|
||||
}
|
||||
|
||||
public function list_active() {
|
||||
return $this->_where_active()->find_all();
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
class Cache extends Kohana_Cache {
|
||||
public static $default = 'apc';
|
||||
}
|
||||
?>
|
@ -19,7 +19,7 @@ return array(
|
||||
|
||||
'file' => array(
|
||||
'driver' => 'file',
|
||||
'cache_dir' => Kohana::$cache_dir ? Kohana::$cache_dir : '/dev/shm/lnapp',
|
||||
'cache_dir' => Kohana::$cache_dir ? Kohana::$cache_dir : APPPATH.'cache/',
|
||||
'default_expire' => 3600,
|
||||
'ignore_on_delete' => array(
|
||||
'.gitignore',
|
||||
|
@ -16,7 +16,7 @@ return array(
|
||||
'cache_type' => 'file',
|
||||
'email_from' => array('noreply@graytech.net.au'=>'Graytech Hosting'),
|
||||
'email_admin_only'=> array(
|
||||
'adsl_traffic_notice'=>array('deon@c5t61p.leenooks.vpn'=>'Deon George'),
|
||||
// 'adsl_traffic_notice'=>array('deon@leenooks.vpn'=>'Deon George'),
|
||||
),
|
||||
'method_directory'=> array( // Out method paths for the different functions
|
||||
'admin',
|
||||
@ -26,6 +26,9 @@ return array(
|
||||
'user',
|
||||
),
|
||||
'method_security' => TRUE, // Enables Method Security. Setting to false means any method can be run without authentication
|
||||
'session_change_trigger'=>array( // Updates to tables to make when our session ID is changed
|
||||
'Cart'=>'session_id',
|
||||
),
|
||||
'site' => array(
|
||||
'172.31.9.4'=>1,
|
||||
'www.graytech.net.au'=>1,
|
||||
|
@ -15,6 +15,7 @@ return array
|
||||
(
|
||||
'ajax'=>FALSE, // AJAX actions can only be run by ajax calls if set to FALSE
|
||||
'etag'=>FALSE, // Force generating ETAGS
|
||||
'checkout_notify'=>FALSE, // Test mode to test a particular checkout_notify item
|
||||
'invoice'=>0, // Number of invoices to generate in a pass
|
||||
'show_inactive'=>FALSE, // Show Inactive Items
|
||||
'task_sim'=>FALSE, // Simulate running tasks
|
||||
|
1
application/views/errors/404.php
Normal file
1
application/views/errors/404.php
Normal file
@ -0,0 +1 @@
|
||||
<?php echo $message; ?>
|
@ -11,8 +11,14 @@
|
||||
* @license http://dev.leenooks.net/license.html
|
||||
*/
|
||||
class Cart {
|
||||
public static function instance() {
|
||||
return new Cart;
|
||||
private $id = NULL;
|
||||
|
||||
public function __construct($id=NULL) {
|
||||
$this->id = is_null($id) ? Session::instance()->id() : $id;
|
||||
}
|
||||
|
||||
public static function instance($id=NULL) {
|
||||
return new Cart($id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -20,7 +26,34 @@ class Cart {
|
||||
*/
|
||||
public function contents() {
|
||||
return ORM::factory('Cart')
|
||||
->where('session_id','=',Session::instance()->id());
|
||||
->where('session_id','=',$this->id)
|
||||
->find_all();
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
foreach (ORM::factory('Cart')->where('session_id','=',$this->id)->find_all() as $co)
|
||||
$co->delete();
|
||||
}
|
||||
|
||||
public function get($mid,$item) {
|
||||
return ORM::factory('Cart')
|
||||
->where('session_id','=',$this->id)
|
||||
->and_where('module_id','=',$mid)
|
||||
->and_where('module_item','=',$item)
|
||||
->find_all();
|
||||
}
|
||||
|
||||
public function id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function total($format=FALSE) {
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->contents() as $cio)
|
||||
$total += $cio->item()->t;
|
||||
|
||||
return $format ? Currency::display($total) : $total;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -29,8 +62,10 @@ class Cart {
|
||||
* @param bool $detail List a detailed cart or a summary cart
|
||||
*/
|
||||
public function cart_block() {
|
||||
// @todo To implement.
|
||||
return '';
|
||||
// If the cart is empty, we'll return here.
|
||||
if (! $this->contents()->count_all())
|
||||
if (! count($this->contents()))
|
||||
return 'The cart is empty.';
|
||||
|
||||
Style::add(array(
|
||||
@ -39,7 +74,7 @@ class Cart {
|
||||
));
|
||||
|
||||
$output = '<table class="cart_blocklist" border="0">';
|
||||
foreach ($this->contents()->find_all() as $item) {
|
||||
foreach ($this->contents() as $item) {
|
||||
$ppa = $item->product->get_price_array();
|
||||
$pdata = Period::details($item->recurr_schedule,$item->product->price_recurr_weekday,time(),TRUE);
|
||||
|
||||
@ -58,48 +93,5 @@ class Cart {
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to see if the cart has some trial options
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function has_trial() {
|
||||
foreach ($this->contents()->find_all() as $item)
|
||||
if ($item->product->is_trial())
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function subtotal() {
|
||||
$total = 0;
|
||||
|
||||
foreach ($this->contents()->find_all() as $item) {
|
||||
$ppa = $item->product->get_price_array();
|
||||
$period = Period::details($item->recurr_schedule,$item->product->price_recurr_weekday,time(),TRUE);
|
||||
|
||||
$total += $item->quantity*$ppa[$item->recurr_schedule]['price_base']*$period['prorata'];
|
||||
$total += $item->quantity*$ppa[$item->recurr_schedule]['price_setup'];
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Tax for the cart items
|
||||
*
|
||||
* @return unknown_type
|
||||
* @uses Tax
|
||||
*/
|
||||
public function tax() {
|
||||
// @todo Tax zone should come from somewhere else
|
||||
return Tax::detail(61,NULL,$this->subtotal());
|
||||
}
|
||||
|
||||
public function total() {
|
||||
// @todo Tax zone should come from somewhere else
|
||||
return $this->subtotal()+Tax::total(61,NULL,$this->subtotal());
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
37
modules/cart/classes/Cart/Item.php
Normal file
37
modules/cart/classes/Cart/Item.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class provides a Cart Item
|
||||
*
|
||||
* @package OSB
|
||||
* @subpackage Cart
|
||||
* @category Helpers
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Deon George
|
||||
* @license http://dev.leenooks.net/license.html
|
||||
*/
|
||||
class Cart_Item {
|
||||
// Quantity
|
||||
private $q = 0;
|
||||
// Item
|
||||
private $i = 0;
|
||||
// Total
|
||||
private $t = 0;
|
||||
|
||||
public function __construct($q,$i,$t) {
|
||||
$this->q = $q;
|
||||
$this->i = $i;
|
||||
$this->t = $t;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
switch($key) {
|
||||
case 'i':
|
||||
case 'q': return $this->{$key};
|
||||
case 't': return Currency::display($this->{$key});
|
||||
|
||||
default: throw new Kohana_Exception('Unknown Key :key',array(':key',$key));
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
19
modules/cart/classes/Cartable.php
Normal file
19
modules/cart/classes/Cartable.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This interface ensures that all cart processing objects have the right methods
|
||||
*
|
||||
* @package OSB
|
||||
* @subpackage Cart
|
||||
* @category Helpers
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Deon George
|
||||
* @license http://dev.leenooks.net/license.html
|
||||
*/
|
||||
interface Cartable {
|
||||
// Render a cart line item.
|
||||
public function cart_item();
|
||||
// Return if this invoice is already in the cart
|
||||
public function cart_exists();
|
||||
}
|
||||
?>
|
@ -12,71 +12,56 @@
|
||||
*/
|
||||
class Controller_Cart extends Controller_TemplateDefault {
|
||||
/**
|
||||
* Default action when called
|
||||
* List the cart contents
|
||||
*/
|
||||
public function action_index() {
|
||||
return $this->action_list();
|
||||
}
|
||||
|
||||
/**
|
||||
* List items in the cart
|
||||
*/
|
||||
public function action_list() {
|
||||
// @todo - this should be a global config item
|
||||
$mediapath = Route::get('default/media');
|
||||
$output = '';
|
||||
$co = Cart::instance();
|
||||
|
||||
// If the cart is empty, we'll return here.
|
||||
if (! Cart::instance()->contents()->count_all())
|
||||
if (! count($co->contents()))
|
||||
Block::add(array(
|
||||
'title'=>_('Empty Cart'),
|
||||
'body'=>_('The cart is empty')
|
||||
));
|
||||
|
||||
else {
|
||||
Style::add(array(
|
||||
'type'=>'file',
|
||||
'data'=>'css/cart_contents.css',
|
||||
Block::add(array(
|
||||
'title'=>_('Cart Items'),
|
||||
'body'=>Table::display(
|
||||
$co->contents(),
|
||||
NULL,
|
||||
array(
|
||||
'item()->q'=>array('label'=>'Quantity'),
|
||||
'item()->i'=>array('label'=>'Item'),
|
||||
'item()->t'=>array('label'=>'Total','class'=>'right'),
|
||||
),
|
||||
array(
|
||||
'type'=>'list',
|
||||
)
|
||||
),
|
||||
));
|
||||
|
||||
$output = Form::open('checkout/noready');
|
||||
foreach (Cart::instance()->contents()->find_all() as $item) {
|
||||
$ppa = $item->product->get_price_array();
|
||||
$pdata = Period::details($item->recurr_schedule,$item->product->price_recurr_weekday,time(),TRUE);
|
||||
$checkout = ORM::factory('Checkout')->where_active()->find_all()->as_array();
|
||||
|
||||
$price_box = View::factory('cart/list_pricebox')
|
||||
->set('price_recurring',Currency::display($item->quantity*$ppa[$item->recurr_schedule]['price_base']))
|
||||
->set('price_firstinvoice',Currency::display($item->quantity*$ppa[$item->recurr_schedule]['price_base']*$pdata['prorata']))
|
||||
->set('price_setup',Currency::display($item->quantity*$ppa[$item->recurr_schedule]['price_setup']))
|
||||
->set('item',$item)
|
||||
->set('mediapath',$mediapath);
|
||||
foreach ($co->contents() as $cio)
|
||||
$checkout = array_intersect($checkout,$cio->checkout()->as_array());
|
||||
|
||||
$output .= View::factory('cart/list_item')
|
||||
->set('price_box',$price_box)
|
||||
->set('service_start',$pdata['date'])
|
||||
->set('service_end',$pdata['end'])
|
||||
->set('price_recurring',Currency::display($item->quantity*$ppa[$item->recurr_schedule]['price_base']))
|
||||
->set('item',$item)
|
||||
->set('mediapath',$mediapath);
|
||||
$payopt = array();
|
||||
foreach ($checkout as $cko)
|
||||
$payopt[$cko->id] = $cko->name;
|
||||
|
||||
// If we are a plugin product, we might need more information
|
||||
// @todo If an admin, show a system message if cart_info doesnt exist.
|
||||
if ($item->product->prod_plugin AND method_exists($item->product->prod_plugin_file,'product_cart') AND Kohana::find_file('views',sprintf('%s/cart_info',strtolower($item->product->prod_plugin_file)))) {
|
||||
$output .= View::factory(sprintf('%s/cart_info',strtolower($item->product->prod_plugin_file)));
|
||||
|
||||
// @todo JS validation will need to verify data before submission
|
||||
}
|
||||
}
|
||||
$output .= '<div>'.Form::submit('submit',_('Checkout')).'</div>';
|
||||
$output .= _('Total amount due for payment').' '.$co->total(TRUE);
|
||||
$output .= Form::open('checkout/before');
|
||||
$output .= Form::select('checkout_id',$payopt);
|
||||
$output .= Form::submit('submit',_('Checkout'));
|
||||
$output .= Form::close();
|
||||
|
||||
Block::add(array(
|
||||
'title'=>_('Your Items'),
|
||||
'title'=>_('Payment'),
|
||||
'body'=>$output,
|
||||
));
|
||||
}
|
||||
|
||||
// Suppress our right hand tab
|
||||
$this->template->right = ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,13 +72,10 @@ class Controller_Cart extends Controller_TemplateDefault {
|
||||
|
||||
$cart->session_id = Session::instance()->id();
|
||||
|
||||
if (Auth::instance()->logged_in())
|
||||
$cart->account_id = Auth::instance()->get_user()->id;
|
||||
|
||||
if ($cart->values($_POST)->check())
|
||||
if ($cart->values(Request::current()->post())->check())
|
||||
$cart->save();
|
||||
else
|
||||
echo Kohana::debug($cart->validate()->errors());
|
||||
throw new Kohana_Exception('Unable to add to cart');
|
||||
|
||||
if ($cart->saved())
|
||||
HTTP::redirect('cart/index');
|
||||
@ -102,10 +84,8 @@ class Controller_Cart extends Controller_TemplateDefault {
|
||||
}
|
||||
|
||||
public function action_empty() {
|
||||
$cart = ORM::factory('Cart')
|
||||
->where('session_id','=',session_id());
|
||||
|
||||
$cart->delete_all();
|
||||
foreach (ORM::factory('Cart')->where('session_id','=',Session::instance()->id())->find_all() as $co)
|
||||
$co->delete();
|
||||
|
||||
$this->template->content = _('Cart Emptied');
|
||||
}
|
||||
|
@ -18,6 +18,10 @@ class Model_Cart extends ORM_OSB {
|
||||
// Cart doesnt use the update column
|
||||
protected $_updated_column = FALSE;
|
||||
|
||||
protected $_serialize_column = array(
|
||||
'module_data',
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters used to format the display of values into friendlier values
|
||||
*/
|
||||
@ -26,5 +30,47 @@ class Model_Cart extends ORM_OSB {
|
||||
array('StaticList_RecurSchedule::display',array(':value')),
|
||||
),
|
||||
);
|
||||
|
||||
private $mo;
|
||||
|
||||
public function __construct($id = NULL) {
|
||||
// Load our Model
|
||||
parent::__construct($id);
|
||||
|
||||
// Autoload our Sub Items
|
||||
if ($this->loaded())
|
||||
$this->_load_sub_items();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function _load_sub_items() {
|
||||
$this->mo = ORM::factory('Module',$this->module_id)->instance($this->module_item);
|
||||
|
||||
if (! $this->mo->loaded())
|
||||
throw new Kohana_Exception('Item :item not loaded?',array(':item'=>$this->module_item));
|
||||
}
|
||||
|
||||
public function checkout() {
|
||||
if (! method_exists($this->mo,'checkout'))
|
||||
throw new Kohana_Exception('Module :module doesnt implement checkout?',array(':module'=>get_class($this->mo)));
|
||||
|
||||
return $this->mo->checkout();
|
||||
}
|
||||
|
||||
public function item() {
|
||||
if (! method_exists($this->mo,'cart_item'))
|
||||
throw new Kohana_Exception('Module :module doesnt implement cart_item?',array(':module'=>get_class($this->mo)));
|
||||
|
||||
return $this->mo->cart_item();
|
||||
}
|
||||
|
||||
public function mo() {
|
||||
return $this->mo;
|
||||
}
|
||||
|
||||
public function motype() {
|
||||
return strtolower(preg_replace('/^Model_/','',get_class($this->mo)));
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -1,32 +0,0 @@
|
||||
/** Cart Block Contents Style Sheet **/
|
||||
|
||||
table.cart_blocklist {
|
||||
/* margin-left: auto; */
|
||||
/* margin-right: auto; */
|
||||
width: 100%;
|
||||
background-color: #F9F9FA;
|
||||
border: 0px solid #AAAACC;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
table.cart_blocklist tr td.sku {
|
||||
color: #000000;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
table.cart_blocklist tr td.price {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table.cart_blocklist tr td.schedule {
|
||||
font-size: 60%;
|
||||
}
|
||||
|
||||
table.cart_blocklist tr.submit td {
|
||||
text-align: center;
|
||||
}
|
||||
table.cart_blocklist tr.submit td button {
|
||||
font-size: 60%;
|
||||
font-weight: bold;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/** Cart Contents Style Sheet **/
|
||||
|
||||
table.cart_contents {
|
||||
/* margin-left: auto; */
|
||||
/* margin-right: auto; */
|
||||
width: 100%;
|
||||
background-color: #F9F9FA;
|
||||
border: 0px solid #AAAACC;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
table.cart_contents tr td.title {
|
||||
color: #000000;
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
table.cart_contents tr td.title a {
|
||||
text-decoration: none;
|
||||
color: #0000AA;
|
||||
}
|
||||
|
||||
table.cart_contents tr td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.cart_contents tr td.icon {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
table.cart_contents tr td.price_box {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
table.cart_contents tr td.price_box table.cart_detail_pricebox {
|
||||
width: 100%;
|
||||
background-color: #FAFAFB;
|
||||
}
|
||||
|
||||
table.cart_contents tr td.price_box table.cart_detail_pricebox td.head {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.cart_contents tr td.price_box table.cart_detail_pricebox td.value {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/** Checkout Cart Style Sheet **/
|
||||
|
||||
table.checkout_cartlist {
|
||||
/* margin-left: auto; */
|
||||
/* margin-right: auto; */
|
||||
width: 100%;
|
||||
background-color: #F9F9FA;
|
||||
border: 0px solid #AAAACC;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
table.checkout_cartlist tr td.title {
|
||||
color: #000000;
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
table.checkout_cartlist tr td.title a {
|
||||
text-decoration: none;
|
||||
color: #0000AA;
|
||||
}
|
||||
|
||||
table.checkout_cartlist tr td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.checkout_cartlist tr td.icon {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
table.checkout_cartlist tr td.value {
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
text-align: right;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<tr>
|
||||
<td class="sku"><?php echo $item->product->sku; ?></td>
|
||||
<td class="schedule"><?php echo $item->display('recurr_schedule');?></td>
|
||||
<td class="price"><?php echo Currency::display($price_firstinvoice+$price_setup); ?></td>
|
||||
</tr>
|
@ -1,11 +0,0 @@
|
||||
<!-- @todo Translation required -->
|
||||
<tr>
|
||||
<td colspan="3" class="title"><b><?php echo HTML::anchor(sprintf('product/view/%s',$item->product->id),$item->product->product_translate->find()->name); ?></b></td>
|
||||
<td class="value"><?php echo Currency::display($price_firstinvoice+$price_setup); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>Current Service Period:</td>
|
||||
<td><b><?php printf('%s -> %s',$service_start,$service_end);?></b></td>
|
||||
<td> </td>
|
||||
</tr>
|
@ -1,25 +0,0 @@
|
||||
<table>
|
||||
<!-- @todo This rounding should be a global configuration item -->
|
||||
<tr>
|
||||
<td><?php echo Country::icon($country); ?></td>
|
||||
<td>Cart Sub-Total:</td>
|
||||
<td><?php echo Currency::display($cart->subtotal()); ?></td>
|
||||
</tr>
|
||||
<?php if ($cart->tax()) { ?>
|
||||
<?php foreach ($cart->tax() as $tax) { ?>
|
||||
<!-- @todo This rounding should be a global configuration item -->
|
||||
<!-- @todo Tax details should come from central configuration -->
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>Tax (<?php echo $tax['description']; ?>):</td>
|
||||
<td><?php echo Currency::display($tax['amount']); ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<!-- @todo This rounding should be a global configuration item -->
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>Cart Total:</td>
|
||||
<td><?php echo Currency::display($cart->total()); ?></td>
|
||||
</tr>
|
||||
</table>
|
@ -1,17 +0,0 @@
|
||||
<!-- @todo Translation required -->
|
||||
<table class="cart_contents" border="0">
|
||||
<tr>
|
||||
<td colspan="3" class="title"><b><?php echo HTML::anchor(sprintf('product/view/%s',$item->product->id),$item->product->product_translate->find()->name); ?></b></td>
|
||||
<td class="icon"><?php echo HTML::image($mediapath->uri(array('file'=>'img/edit-delete.png')),array('alt'=>_('Remove'))); ?></td>
|
||||
<td rowspan="4" class="price_box"><?php echo $price_box; ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td><td>Pricing Structure:</td><td colspan="2"><b><?php echo $item->product->display('price_type'); ?></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td><td>Invoice Frequency:</td><td colspan="2"><b><?php echo $item->display('recurr_schedule'); ?> (<?php echo $price_recurring; ?>)</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td><td>Current Service Period:</td><td><b><?php printf('%s -> %s',$service_start,$service_end); ?></b></td>
|
||||
</tr>
|
||||
</table>
|
@ -1,27 +0,0 @@
|
||||
<!-- @todo translation needed -->
|
||||
<table class="cart_detail_pricebox" border="0">
|
||||
<tr>
|
||||
<td class="head">Re-Occuring Price</td>
|
||||
<td class="value"><?php echo $price_recurring; ?></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"> </td></tr>
|
||||
<tr>
|
||||
<td class="head">First Invoice</td>
|
||||
<td class="value"><?php echo $price_firstinvoice; ?></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<?php if ($price_setup) { ?>
|
||||
<tr>
|
||||
<td class="head">Setup</td>
|
||||
<td class="value"><?php echo $price_setup; ?></td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<td class="head">Quantity</td><!-- // @todo Quantity cannot be changed -->
|
||||
<td class="value"><?php echo Form::input('quantity',$item->quantity,array('size'=>2,'disabled'=>'disabled')); ?></td>
|
||||
<td class="icon"><?php echo HTML::image($mediapath->uri(array('file'=>'img/accessories-calculator-small.png')),array('alt'=>_('Re-Calc'))); ?></td>
|
||||
</tr>
|
||||
</table>
|
35
modules/checkout/classes/Checkout/Plugin.php
Normal file
35
modules/checkout/classes/Checkout/Plugin.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class provides CHECKOUT Plugin Support
|
||||
*
|
||||
* @package OSB
|
||||
* @subpackage Checkout Plugins
|
||||
* @category Plugins
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Deon George
|
||||
* @license http://dev.leenooks.net/license.html
|
||||
* @todo Does this need to be Serializable?
|
||||
*/
|
||||
abstract class Checkout_Plugin implements Serializable {
|
||||
protected $co; // Our Checkout Object
|
||||
protected $_object;
|
||||
|
||||
// Our required abstract classes
|
||||
public function serialize() {
|
||||
return (string)$this->_object;
|
||||
}
|
||||
public function unserialize($s) {
|
||||
$this->_object = XML::factory(NULL,NULL,$s);
|
||||
}
|
||||
|
||||
// Required abstract classes
|
||||
// Present pre-plugin processing information
|
||||
abstract public function before(Cart $co);
|
||||
abstract public function notify(Model_Checkout_Notify $cno);
|
||||
|
||||
public function __construct(Model_Checkout $co) {
|
||||
$this->co = $co;
|
||||
}
|
||||
}
|
||||
?>
|
206
modules/checkout/classes/Checkout/Plugin/Paypal.php
Normal file
206
modules/checkout/classes/Checkout/Plugin/Paypal.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class provides PAYPAL support
|
||||
*
|
||||
* @package OSB
|
||||
* @subpackage Plugins/Paypal
|
||||
* @category Plugins
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Deon George
|
||||
* @license http://dev.leenooks.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 $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::add(array(
|
||||
'title'=>_('Payment Processing'),
|
||||
'type'=>'info',
|
||||
'body'=>sprintf('Thank you for your payment with paypal. It will be processed and applied to your cart items automatically in due course.'),
|
||||
));
|
||||
|
||||
HTTP::redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',$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'] == 'deon_1260578114_biz@graytech.net.au') {
|
||||
|
||||
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'] == $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');
|
||||
|
||||
}
|
||||
|
||||
$po->add_item($cio->module_item)->alloc_amt = $cno->data['mc_gross_'.$c];
|
||||
|
||||
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_plugin_id = $this->co->id;
|
||||
$po->notes = $cno->data['txn_id'];
|
||||
$po->save();
|
||||
|
||||
// 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 {
|
||||
echo Debug::vars('IPN doesnt match cart 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) {
|
||||
echo Debug::vars('Apply to cart item');
|
||||
} else {
|
||||
// @todo - add the payment, with no payment items
|
||||
echo Debug::vars('Leave for admin');
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$cno->processed = TRUE;
|
||||
if (! $debug_mode)
|
||||
$cno->save();
|
||||
|
||||
return _('Processed, thank you!');
|
||||
}
|
||||
}
|
||||
?>
|
56
modules/checkout/classes/Checkout/Plugin/Paypal/Cart.php
Normal file
56
modules/checkout/classes/Checkout/Plugin/Paypal/Cart.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class provides PAYPAL CART support
|
||||
*
|
||||
* @package OSB
|
||||
* @subpackage Plugins/Paypal
|
||||
* @category Plugins
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Deon George
|
||||
* @license http://dev.leenooks.net/license.html
|
||||
*/
|
||||
class Checkout_Plugin_Paypal_Cart extends Checkout_Plugin_Paypal {
|
||||
private $test_mode = TRUE;
|
||||
|
||||
/**
|
||||
* Set payment via Paypal
|
||||
*/
|
||||
public function before(Cart $co) {
|
||||
$output = '';
|
||||
|
||||
$output .= View::factory('checkout/plugin/paypal/before')
|
||||
->set('checkout',$this->co)
|
||||
->set('cart',$co);
|
||||
|
||||
$output .= Form::open(sprintf('https://%s/cgi-bin/webscr',$this->test_mode ? $this->url_test : $this->url_prod),array('method'=>'POST'));
|
||||
$output .= Form::hidden('cmd','_cart');
|
||||
$output .= Form::hidden('business',$this->test_mode ? 'deon_1260578114_biz@graytech.net.au' : 'deon@graytech.net.au');
|
||||
$output .= Form::hidden('bn','Graytech_BuyNow_WPS_AU');
|
||||
$output .= Form::hidden('cancel_return',URL::site('checkout/cancel/'.$this->co->id,TRUE));
|
||||
$output .= Form::hidden('custom',$co->id());
|
||||
// @todo This should be dynamic
|
||||
$output .= Form::hidden('currency_code','AUD');
|
||||
$output .= Form::hidden('notify_url',URL::site('checkout/notify/'.$this->co->id,TRUE));
|
||||
$output .= Form::hidden('return',URL::site('checkout/after/'.$this->co->id,TRUE));
|
||||
$output .= Form::hidden('upload','1');
|
||||
|
||||
$c = 1;
|
||||
foreach ($co->contents() as $cio) {
|
||||
$output .= Form::hidden('item_number_'.$c,$cio->id);
|
||||
$output .= Form::hidden('item_name_'.$c,$cio->item()->i);
|
||||
$output .= Form::hidden('amount_'.$c,$cio->item()->t);
|
||||
$c++;
|
||||
}
|
||||
|
||||
$output .= Form::hidden('item_number_'.$c,'0:PAYFEE');
|
||||
$output .= Form::hidden('item_name_'.$c,'Paypal Fee');
|
||||
$output .= Form::hidden('amount_'.$c,$this->co->fee($co->total()));
|
||||
|
||||
$output .= Form::submit('submit','Pay Now');
|
||||
$output .= Form::close();
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
?>
|
@ -11,137 +11,85 @@
|
||||
* @license http://dev.osbill.net/license.html
|
||||
*/
|
||||
class Controller_Checkout extends Controller_TemplateDefault {
|
||||
protected $auth_required = TRUE;
|
||||
protected $noauth_redirect = 'login/register';
|
||||
protected $auth_required = FALSE;
|
||||
protected $secure_actions = array(
|
||||
'before'=>TRUE,
|
||||
'after'=>TRUE,
|
||||
'cancel'=>TRUE,
|
||||
);
|
||||
|
||||
/**
|
||||
* This is the main call to export, providing a list of items to export and
|
||||
* setting up the page to call the export plugin when submitted.
|
||||
*/
|
||||
public function action_index() {
|
||||
if ($_POST)
|
||||
return $this->checkout();
|
||||
|
||||
// @todo - this should be a global config item
|
||||
$mediapath = Route::get('default/media');
|
||||
|
||||
// @todo Items in the cart dont have account_id if they were put in the cart when the user was not logged in
|
||||
|
||||
// If the cart is empty, we'll return here.
|
||||
if (! Cart::instance()->contents()->count_all())
|
||||
Block::add(array(
|
||||
'title'=>_('Empty Cart'),
|
||||
'body'=>_('The cart is empty')
|
||||
));
|
||||
|
||||
else {
|
||||
Style::add(array(
|
||||
'type'=>'file',
|
||||
'data'=>'css/checkout_cartlist.css',
|
||||
));
|
||||
|
||||
// Show a list of items in the cart
|
||||
$output = '<table class="checkout_cartlist" border="0">';
|
||||
foreach (Cart::instance()->contents()->find_all() as $item) {
|
||||
$ppa = $item->product->get_price_array();
|
||||
$pdata = Period::details($item->recurr_schedule,$item->product->price_recurr_weekday,time(),TRUE);
|
||||
|
||||
$output .= View::factory('cart/checkout_list')
|
||||
->set('price_firstinvoice',$item->quantity*$ppa[$item->recurr_schedule]['price_base']*$pdata['prorata'])
|
||||
->set('price_setup',$item->quantity*$ppa[$item->recurr_schedule]['price_setup'])
|
||||
->set('service_start',$pdata['date'])
|
||||
->set('service_end',$pdata['end'])
|
||||
->set('price_recurring',$item->quantity*$ppa[$item->recurr_schedule]['price_base'])
|
||||
->set('item',$item)
|
||||
->set('mediapath',$mediapath);
|
||||
}
|
||||
$output .= '</table>';
|
||||
|
||||
Block::add(array(
|
||||
'title'=>_('Your Items'),
|
||||
'body'=>$output,
|
||||
));
|
||||
|
||||
$po = ORM::factory('Checkout')
|
||||
->payment_options_cart();
|
||||
|
||||
// @todo Country value should come from somewhere?
|
||||
Block::add(array(
|
||||
'title'=>_('Order Total'),
|
||||
'body'=>View::factory('cart/checkout_total')
|
||||
->set('cart',Cart::instance())
|
||||
->set('country',61),
|
||||
));
|
||||
|
||||
$output = Form::open();
|
||||
$output .= '<table class="payment_options_box" border="0">';
|
||||
|
||||
foreach ($po as $payment) {
|
||||
$output .= View::factory('checkout/payment_option')
|
||||
->set('payment',$payment);
|
||||
HTTP::redirect('cart');
|
||||
}
|
||||
|
||||
// @todo Add Javascript to stop submission if something not selected
|
||||
$output .= '<tr><td> </td></tr>';
|
||||
$output .= '<tr>';
|
||||
$output .= sprintf('<td>%s</td>',Form::submit('submit',_('Submit Order')));
|
||||
$output .= '</tr>';
|
||||
public function action_before() {
|
||||
// If we are not here by a POST operation, we'll redirect to the cart.
|
||||
if (! $cid=Request::current()->post('checkout_id'))
|
||||
HTTP::redirect('cart');
|
||||
|
||||
$output .= '</table>';
|
||||
$output .= Form::close();
|
||||
$co = ORM::factory('Checkout',$cid);
|
||||
|
||||
Block::add(array(
|
||||
'title'=>_('Available Payment Methods'),
|
||||
'body'=>$output,
|
||||
'title'=>'Checkout',
|
||||
'body'=>$co->plugin()->before(Cart::instance()),
|
||||
));
|
||||
}
|
||||
|
||||
// Suppress our right hand tab
|
||||
$this->template->right = ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process checkout
|
||||
*/
|
||||
private function checkout() {
|
||||
$invoice = ORM::factory('Invoice');
|
||||
public function action_after() {
|
||||
$co = ORM::factory('Checkout',$this->request->param('id'));
|
||||
|
||||
// Add our individual items to the invoice
|
||||
foreach (Cart::instance()->contents()->find_all() as $item) {
|
||||
$invoice_item = $invoice->add_item();
|
||||
if (! $co->loaded())
|
||||
HTTP::redirect('/');
|
||||
|
||||
$invoice_item->product_id = $item->product_id;
|
||||
$invoice_item->product_attr = $item->product_attr;
|
||||
$invoice_item->product_attr_cart = $item->product_attr;
|
||||
$invoice_item->quantity = $item->quantity;
|
||||
$invoice_item->recurring_schedule = $item->recurr_schedule;
|
||||
|
||||
$ppa = $item->product->get_price_array();
|
||||
$period = Period::details($item->recurr_schedule,$item->product->price_recurr_weekday,time(),TRUE);
|
||||
// @todo rounding should be a global config
|
||||
$invoice_item->price_base = round($item->quantity*$ppa[$item->recurr_schedule]['price_base']*$period['prorata'],2);
|
||||
$invoice_item->price_setup = round($item->quantity*$ppa[$item->recurr_schedule]['price_setup'],2);
|
||||
return method_exists($co->plugin(),'after') ? $co->plugin()->after(Cart::instance()) : HTTP::redirect('/');
|
||||
}
|
||||
|
||||
$invoice->account_id = Auth::instance()->get_user()->id;
|
||||
$invoice->type = 2; // INVOICED VIA CHECKOUT
|
||||
$invoice->status = 1; // INVOICE IS NOT CANCELLED
|
||||
$invoice->due_date = time(); // DATE INVOICE MUST BE PAID
|
||||
$invoice->billed_currency_id = 6; // @todo This should come from the site config or the currency selected
|
||||
/*
|
||||
$invoice->process_status = NULL; // TO BE PROCESSED
|
||||
$invoice->billing_status = NULL; // UNPAID
|
||||
$invoice->refund_status = NULL; // NOT REFUNDED
|
||||
$invoice->print_status = NULL; // NOT YET PRINTED
|
||||
$invoice->discount_amt = NULL; // @todo CALCULATE DISCOUNTS
|
||||
$invoice->checkout_plugin_id = NULL; // @todo Update the selected checkout plugin
|
||||
$invoice->checkout_plugin_data = NULL; // @todo Data required for the checkout plugin
|
||||
*/
|
||||
public function action_cancel() {
|
||||
$co = ORM::factory('Checkout',$this->request->param('id'));
|
||||
|
||||
if ($invoice->check())
|
||||
$invoice->save();
|
||||
else
|
||||
throw new Kohana_Exception('Problem saving invoice - Failed check()');
|
||||
if (! $co->loaded())
|
||||
HTTP::redirect('cart');
|
||||
|
||||
return method_exists($co->plugin(),'cancel') ? $co->plugin()->cancel(Cart::instance()) : HTTP::redirect('cart');
|
||||
}
|
||||
|
||||
public function action_notify() {
|
||||
$test_id = FALSE;
|
||||
$co = ORM::factory('Checkout',$this->request->param('id'));
|
||||
|
||||
if ((! $co->loaded() OR ! Request::current()->post()) AND ! $test_id=Kohana::$config->load('debug')->checkout_notify)
|
||||
throw HTTP_Exception::factory(404,'Payment not found!');
|
||||
|
||||
$this->auto_render = FALSE;
|
||||
|
||||
$cno = ORM::factory('Checkout_Notify');
|
||||
|
||||
if (! $test_id) {
|
||||
$cno->checkout_id = $co->id;
|
||||
$cno->status = 1;
|
||||
$cno->data = Request::current()->post();
|
||||
$cno->save();
|
||||
} else {
|
||||
$cno->where('id','=',$test_id)->find();
|
||||
}
|
||||
|
||||
if (! $cno->loaded())
|
||||
throw HTTP_Exception::factory(500,'Unable to save!');
|
||||
|
||||
// Process our Notify
|
||||
try {
|
||||
$this->response->body($cno->process());
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->response->body('Received, thank you!');
|
||||
}
|
||||
|
||||
$this->response->headers('Content-Type','text/plain');
|
||||
$this->response->headers('Content-Length',(string)$this->response->content_length());
|
||||
$this->response->headers('Last-Modified',time());
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -11,58 +11,38 @@
|
||||
* @license http://dev.osbill.net/license.html
|
||||
*/
|
||||
class Model_Checkout extends ORM_OSB {
|
||||
protected $_has_many = array(
|
||||
'account'=>array('through'=>'account_billing','foreign_key'=>'checkout_plugin_id'),
|
||||
'payment'=>array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Give a cart, this will present the available checkout options
|
||||
*
|
||||
* Trial Products are NEW products
|
||||
* Cart items are NEW products
|
||||
* Invoice items are RE-OCCURING items (ie: carts are not re-occuring)
|
||||
* Calcuale the fee for this checkout method
|
||||
*
|
||||
* @param $amt The amount the fee will be based on
|
||||
*/
|
||||
public function payment_options_cart() {
|
||||
$cart = Cart::instance();
|
||||
public function fee($amt) {
|
||||
if (! $this->fee_passon)
|
||||
return 0;
|
||||
|
||||
$available_payments = array();
|
||||
$net = $amt;
|
||||
|
||||
if ($cart->has_trial())
|
||||
$this->and_where('allow_trial','=',TRUE);
|
||||
if (! is_null($this->fee_fixed))
|
||||
$net += $this->fee_fixed;
|
||||
|
||||
$this->and_where('allow_new','=',TRUE);
|
||||
if (! is_null($this->fee_variable))
|
||||
$net /= (1-$this->fee_variable);
|
||||
|
||||
foreach ($this->list_active() as $item) {
|
||||
// Check that the cart total meets the minimum requirement
|
||||
if ($item->total_minimum AND $cart->total() < $item->total_minimum)
|
||||
continue;
|
||||
|
||||
// Check the cart total meets the maximum requirement
|
||||
if (($item->total_maximum AND $cart->total() > $item->total_maximum) OR ($item->total_maximum == '0' AND $cart->total()))
|
||||
continue;
|
||||
|
||||
// Check that the payment option is available to this client based on groups
|
||||
// @todo Enable this test
|
||||
|
||||
// Check that the payment option is not prohibited by an SKU item
|
||||
// @todo Enable this test
|
||||
|
||||
// Check if this payment method is a default payment method
|
||||
// @todo Enable this test
|
||||
// By Amount
|
||||
// By Currency
|
||||
// By Group
|
||||
// By Country
|
||||
|
||||
// This payment option is valid
|
||||
array_push($available_payments,$item);
|
||||
// Sort the checkout options
|
||||
// @todo Is this required?
|
||||
return Currency::round($net-$amt);
|
||||
}
|
||||
|
||||
return $available_payments;
|
||||
/**
|
||||
* Return the object of the checkout plugin
|
||||
*/
|
||||
public function plugin($type='') {
|
||||
$c = Kohana::classname('Checkout_Plugin_'.$this->plugin);
|
||||
|
||||
if (! $this->plugin OR ! class_exists($c))
|
||||
return NULL;
|
||||
|
||||
$o = new $c($this);
|
||||
|
||||
return $type ? $o->$type : $o;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
23
modules/checkout/classes/Model/Checkout/Notify.php
Normal file
23
modules/checkout/classes/Model/Checkout/Notify.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class provides checkout capabilities.
|
||||
*
|
||||
* @package OSB
|
||||
* @subpackage Checkout
|
||||
* @category Models
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Open Source Billing
|
||||
* @license http://dev.osbill.net/license.html
|
||||
*/
|
||||
class Model_Checkout_Notify extends ORM_OSB {
|
||||
// Relationships
|
||||
protected $_has_one = array(
|
||||
'checkout'=>array('far_key'=>'checkout_id','foreign_key'=>'id'),
|
||||
);
|
||||
|
||||
public function process() {
|
||||
return $this->checkout->plugin()->notify($this);
|
||||
}
|
||||
}
|
||||
?>
|
32
modules/checkout/views/checkout/plugin/paypal/before.php
Normal file
32
modules/checkout/views/checkout/plugin/paypal/before.php
Normal file
@ -0,0 +1,32 @@
|
||||
<p>Paypal will be used to pay for the following items:</p>
|
||||
</br>
|
||||
<?php
|
||||
echo Table::display(
|
||||
$cart->contents(),
|
||||
NULL,
|
||||
array(
|
||||
'item()->q'=>array('label'=>'Quantity'),
|
||||
'item()->i'=>array('label'=>'Item'),
|
||||
'item()->t'=>array('label'=>'Total','class'=>'right'),
|
||||
),
|
||||
array(
|
||||
'type'=>'list',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</br>
|
||||
<p>Please Note: Paypal charges a fee to receive payments, and that fee will be added to your payment.</p>
|
||||
<table class="list-box-left">
|
||||
<tr class="list-data">
|
||||
<th>Cart Total</th>
|
||||
<td class="right"><?php echo $t=$cart->total(TRUE); ?></td>
|
||||
</tr>
|
||||
<tr class="list-data">
|
||||
<th>Payment Fee</th>
|
||||
<td class="right"><?php echo Currency::display($f=$checkout->fee($t)); ?></td>
|
||||
</tr>
|
||||
<tr class="list-data">
|
||||
<th>Total</th>
|
||||
<td class="right"><?php echo Currency::display($t+$f); ?></td>
|
||||
</tr>
|
||||
</table>
|
@ -61,6 +61,12 @@ class Controller_User_Invoice extends Controller_TemplateDefault_User {
|
||||
->set('mediapath',Route::get('default/media'))
|
||||
->set('io',$io);
|
||||
|
||||
if ($io->due() AND ! $io->cart_exists()) {
|
||||
$output .= View::factory($this->viewpath().'/pay')
|
||||
->set('mid',$io->mid())
|
||||
->set('o',$io);
|
||||
}
|
||||
|
||||
if (! $io->status) {
|
||||
// Add a gribber popup
|
||||
// @todo Make a gribber popup a class on its own.
|
||||
|
@ -10,7 +10,7 @@
|
||||
* @copyright (c) 2010 Open Source Billing
|
||||
* @license http://dev.osbill.net/license.html
|
||||
*/
|
||||
class Model_Invoice extends ORM_OSB {
|
||||
class Model_Invoice extends ORM_OSB implements Cartable {
|
||||
protected $_belongs_to = array(
|
||||
'account'=>array()
|
||||
);
|
||||
@ -47,74 +47,68 @@ class Model_Invoice extends ORM_OSB {
|
||||
),
|
||||
);
|
||||
|
||||
/** INTERFACE REQUIREMENTS **/
|
||||
public function cart_item() {
|
||||
return new Cart_Item(1,sprintf('Invoice: %s',$this->refnum()),$this->due());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if this invoice is already in the cart
|
||||
*/
|
||||
public function cart_exists() {
|
||||
return count(Cart::instance()->get($this->mid(),$this->id));
|
||||
}
|
||||
|
||||
// Items belonging to an invoice
|
||||
private $invoice_items = array();
|
||||
private $subitems_load = FALSE;
|
||||
|
||||
public function __construct($id = NULL) {
|
||||
// Load our model.
|
||||
// Load our Model
|
||||
parent::__construct($id);
|
||||
|
||||
return $this->load_sub_items();
|
||||
}
|
||||
|
||||
private function load_sub_items() {
|
||||
// Load our sub items
|
||||
if (! $this->subitems_load AND $this->loaded()) {
|
||||
$this->invoice_items = $this->invoice_item->find_all()->as_array();
|
||||
$this->subitems_load = TRUE;
|
||||
}
|
||||
// Autoload our Sub Items
|
||||
if ($this->loaded())
|
||||
$this->_load_sub_items();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of invoice items for this payment.
|
||||
* Load our invoice items
|
||||
* We need these so that we can calculate totals, etc
|
||||
*/
|
||||
public function items() {
|
||||
$this->load_sub_items();
|
||||
|
||||
return $this->invoice_items;
|
||||
private function _load_sub_items() {
|
||||
// Load our sub items
|
||||
$this->invoice_items = $this->invoice_item->find_all()->as_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Invoice Number
|
||||
* Add an item to an invoice
|
||||
*/
|
||||
public function id() {
|
||||
return sprintf('%06s',$this->id);
|
||||
public function add_item() {
|
||||
if ($this->loaded() and ! $this->invoice_items)
|
||||
throw new Kohana_Exception('Need to load invoice_items?');
|
||||
|
||||
$c = count($this->invoice_items);
|
||||
|
||||
$this->invoice_items[$c] = ORM::factory('Invoice_Item');
|
||||
|
||||
return $this->invoice_items[$c];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Invoice Reference Number
|
||||
* Return a list of valid checkout options for this invoice
|
||||
*/
|
||||
public function refnum() {
|
||||
return sprintf('%s-%06s',$this->account->accnum(),$this->id);
|
||||
}
|
||||
public function checkout() {
|
||||
$due = $this->due();
|
||||
|
||||
/**
|
||||
* Display the amount due
|
||||
*/
|
||||
public function due($format=FALSE) {
|
||||
// If the invoice is active calculate the due amount
|
||||
$result = $this->status ? $this->total()-$this->payments_total() : 0;
|
||||
|
||||
// @todo This should not be required.
|
||||
if ((Currency::round($result) == .01) or Currency::round($result) == .02)
|
||||
$result = 0;
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the subtotal of all items
|
||||
*/
|
||||
public function subtotal($format=FALSE) {
|
||||
$result = 0;
|
||||
|
||||
foreach ($this->items() as $ito)
|
||||
$result += $ito->subtotal();
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
return ORM::factory('Checkout')
|
||||
->where_active()
|
||||
->where('amount_min','<=',$due)
|
||||
->where_open()
|
||||
->and_where('amount_max','>=',$due)
|
||||
->or_where('amount_max','is',null)
|
||||
->where_close()->find_all();
|
||||
}
|
||||
|
||||
public function credits() {
|
||||
@ -139,41 +133,32 @@ class Model_Invoice extends ORM_OSB {
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
}
|
||||
|
||||
public function tax($format=FALSE) {
|
||||
$result = 0;
|
||||
/**
|
||||
* Display the amount due
|
||||
*/
|
||||
public function due($format=FALSE) {
|
||||
// If the invoice is active calculate the due amount
|
||||
$result = $this->status ? $this->total()-$this->payments_total() : 0;
|
||||
|
||||
foreach ($this->items() as $ito)
|
||||
$result += $ito->tax();
|
||||
// @todo This should not be required.
|
||||
if ((Currency::round($result) == .01) or Currency::round($result) == .02)
|
||||
$result = 0;
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total of all items
|
||||
* Display the Invoice Number
|
||||
*/
|
||||
public function total($format=FALSE) {
|
||||
$result = 0;
|
||||
|
||||
foreach ($this->items() as $ito)
|
||||
$result += $ito->total();
|
||||
|
||||
// Reduce by any credits
|
||||
$result -= $this->credit_amt;
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
public function id() {
|
||||
return sprintf('%06s',$this->id);
|
||||
}
|
||||
|
||||
public function payments() {
|
||||
return $this->payment_item->find_all();
|
||||
}
|
||||
|
||||
public function payments_total($format=FALSE) {
|
||||
$result = 0;
|
||||
|
||||
foreach ($this->payments() as $po)
|
||||
$result += $po->alloc_amt;
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
/**
|
||||
* Return a list of invoice items for this payment.
|
||||
*/
|
||||
public function items() {
|
||||
return $this->invoice_items;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -345,79 +330,28 @@ class Model_Invoice extends ORM_OSB {
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of taxes used on this invoice
|
||||
* @todo Move some of this to invoice_item_tax.
|
||||
*/
|
||||
public function tax_summary() {
|
||||
$summary = array();
|
||||
|
||||
foreach ($this->items() as $ito) {
|
||||
foreach ($ito->invoice_item_tax->find_all() as $item_tax) {
|
||||
if (! isset($summary[$item_tax->tax_id]))
|
||||
$summary[$item_tax->tax_id] = $item_tax->amount;
|
||||
else
|
||||
$summary[$item_tax->tax_id] += $item_tax->amount;
|
||||
}
|
||||
}
|
||||
|
||||
// @todo This should be removed eventually
|
||||
if (! $summary)
|
||||
$summary[1] = $this->tax();
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to an invoice
|
||||
*/
|
||||
public function add_item() {
|
||||
if ($this->loaded() and ! $this->invoice_items)
|
||||
throw new Kohana_Exception('Need to load invoice_items?');
|
||||
|
||||
$c = count($this->invoice_items);
|
||||
|
||||
$this->invoice_items[$c] = ORM::factory('Invoice_Item');
|
||||
|
||||
return $this->invoice_items[$c];
|
||||
}
|
||||
|
||||
public function min_due($date) {
|
||||
return strtotime(date('Y-M-d',($date < time()) ? time()+ORM::factory('Invoice')->config('DUE_DAYS_MIN')*86400 : $date));
|
||||
}
|
||||
|
||||
public function save(Validation $validation = NULL) {
|
||||
// Our items will be clobbered once we save the object, so we need to save it here.
|
||||
$items = $this->items();
|
||||
|
||||
// Save the invoice
|
||||
parent::save($validation);
|
||||
|
||||
// Need to save the associated items and their taxes
|
||||
if ($this->saved()) {
|
||||
foreach ($items as $iio) {
|
||||
$iio->invoice_id = $this->id;
|
||||
|
||||
if (! $iio->check()) {
|
||||
// @todo Mark invoice as cancelled and write a memo, then...
|
||||
throw new Kohana_Exception('Problem saving invoice_item for invoice :invoice - Failed check()',array(':invoice'=>$invoice->id));
|
||||
/**
|
||||
* Display the Invoice Reference Number
|
||||
*/
|
||||
public function refnum() {
|
||||
return sprintf('%s-%06s',$this->account->accnum(),$this->id);
|
||||
}
|
||||
|
||||
$iio->save();
|
||||
|
||||
if (! $iio->saved()) {
|
||||
// @todo Mark invoice as cancelled and write a memo, then...
|
||||
throw new Kohana_Exception('Problem saving invoice_item for invoice :invoice - Failed save()',array(':invoice'=>$invoice->id));
|
||||
public function payments() {
|
||||
return $this->payment_item->find_all();
|
||||
}
|
||||
|
||||
// @todo Need to save discount information
|
||||
}
|
||||
public function payments_total($format=FALSE) {
|
||||
$result = 0;
|
||||
|
||||
foreach ($this->payments() as $po)
|
||||
$result += $po->alloc_amt;
|
||||
|
||||
} else
|
||||
throw new Kohana_Exception('Couldnt save invoice for some reason?');
|
||||
|
||||
return TRUE;
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -441,6 +375,44 @@ class Model_Invoice extends ORM_OSB {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function save(Validation $validation = NULL) {
|
||||
// Our items will be clobbered once we save the object, so we need to save it here.
|
||||
$items = $this->items();
|
||||
|
||||
// Save the invoice
|
||||
if ($this->changed())
|
||||
parent::save($validation);
|
||||
|
||||
// Need to save the associated items and their taxes
|
||||
if ($this->loaded()) {
|
||||
foreach ($items as $iio) {
|
||||
$iio->invoice_id = $this->id;
|
||||
|
||||
if (! $iio->changed())
|
||||
continue;
|
||||
|
||||
if (! $iio->check()) {
|
||||
// @todo Mark invoice as cancelled and write a memo, then...
|
||||
throw new Kohana_Exception('Problem saving invoice_item for invoice :invoice - Failed check()',array(':invoice'=>$this->id));
|
||||
}
|
||||
|
||||
$iio->save();
|
||||
|
||||
if (! $iio->saved()) {
|
||||
// @todo Mark invoice as cancelled and write a memo, then...
|
||||
throw new Kohana_Exception('Problem saving invoice_item for invoice :invoice - Failed save()',array(':invoice'=>$this->id));
|
||||
}
|
||||
|
||||
// @todo Need to save discount information
|
||||
}
|
||||
|
||||
|
||||
} else
|
||||
throw new Kohana_Exception('Couldnt save invoice for some reason?');
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
public function set_remind($key,$value,$add=FALSE) {
|
||||
if (! $this->loaded())
|
||||
throw new Kohana_Exception('Cant call :method when a record not loaded.',array(':method',__METHOD__));
|
||||
@ -484,6 +456,69 @@ class Model_Invoice extends ORM_OSB {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the subtotal of all items
|
||||
*/
|
||||
public function subtotal($format=FALSE) {
|
||||
$result = 0;
|
||||
|
||||
foreach ($this->items() as $ito)
|
||||
$result += $ito->subtotal();
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
}
|
||||
|
||||
public function tax($format=FALSE) {
|
||||
$result = 0;
|
||||
|
||||
foreach ($this->items() as $ito)
|
||||
$result += $ito->tax();
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of taxes used on this invoice
|
||||
* @todo Move some of this to invoice_item_tax.
|
||||
*/
|
||||
public function tax_summary() {
|
||||
$summary = array();
|
||||
|
||||
foreach ($this->items() as $ito) {
|
||||
foreach ($ito->invoice_item_tax->find_all() as $item_tax) {
|
||||
if (! isset($summary[$item_tax->tax_id]))
|
||||
$summary[$item_tax->tax_id] = $item_tax->amount;
|
||||
else
|
||||
$summary[$item_tax->tax_id] += $item_tax->amount;
|
||||
}
|
||||
}
|
||||
|
||||
// @todo This should be removed eventually
|
||||
if (! $summary)
|
||||
$summary[1] = $this->tax();
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total of all items
|
||||
*/
|
||||
public function total($format=FALSE) {
|
||||
$result = 0;
|
||||
|
||||
// @todo - This should be required, but during checkout payment processing $pio->invoice->total() showed no invoice items?
|
||||
if ($this->loaded() AND ! count($this->items()))
|
||||
$this->_load_sub_items();
|
||||
|
||||
foreach ($this->items() as $ito)
|
||||
$result += $ito->total();
|
||||
|
||||
// Reduce by any credits
|
||||
$result -= $this->credit_amt;
|
||||
|
||||
return $format ? Currency::display($result) : Currency::round($result);
|
||||
}
|
||||
|
||||
/** LIST FUNCTIONS **/
|
||||
|
||||
/**
|
||||
|
@ -142,6 +142,8 @@ class Model_Invoice_Item extends ORM_OSB {
|
||||
|
||||
case 6: return _('Service Excess Fee');
|
||||
|
||||
case 125: return _('Payment Fee');
|
||||
|
||||
case 126: return _('Rounding');
|
||||
|
||||
case 127: return _('Late Payment Fee');
|
||||
@ -164,6 +166,9 @@ class Model_Invoice_Item extends ORM_OSB {
|
||||
}
|
||||
|
||||
public function save(Validation $validation = NULL) {
|
||||
if (! $this->changed())
|
||||
return;
|
||||
|
||||
// Save the invoice item
|
||||
parent::save($validation);
|
||||
|
||||
|
7
modules/invoice/views/invoice/user/view/pay.php
Normal file
7
modules/invoice/views/invoice/user/view/pay.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
echo Form::open('cart/add');
|
||||
echo Form::hidden('module_id',$mid);
|
||||
echo Form::hidden('module_item',$o->id);
|
||||
?>
|
||||
Add to cart for payment: <?php echo StaticList_YesNo::form('cart_add',true); ?>
|
||||
<?php echo Form::submit('submit','Add to Cart'); echo Form::close('cart/add'); ?>
|
@ -156,7 +156,8 @@ class Model_Payment extends ORM_OSB {
|
||||
// Our items will be clobbered once we save the object, so we need to save it here.
|
||||
$items = $this->items();
|
||||
|
||||
$this->source_id = Auth::instance()->get_user()->id;
|
||||
// @todo This should not be mandatory - or there should be a source for non-users (automatic postings)
|
||||
$this->source_id = Auth::instance()->get_user() ? Auth::instance()->get_user()->id : 1;
|
||||
$this->ip = Request::$client_ip;
|
||||
|
||||
// Make sure we dont over allocate
|
||||
|
@ -11,6 +11,13 @@
|
||||
* @license http://dev.osbill.net/license.html
|
||||
*/
|
||||
class Model_Payment_Item extends ORM_OSB {
|
||||
protected $_belongs_to = array('payment'=>array(),'invoice'=>array());
|
||||
// Relationships
|
||||
protected $_has_one = array(
|
||||
'invoice'=>array('far_key'=>'invoice_id','foreign_key'=>'id'),
|
||||
);
|
||||
|
||||
protected $_belongs_to = array(
|
||||
'payment'=>array(),
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
Reference in New Issue
Block a user