* @copyright 2009 Deon George * @link http://osb.leenooks.net * * @link http://www.agileco.com/ * @copyright 2004-2008 Agileco, LLC. * @license http://www.agileco.com/agilebill/license1-4.txt * @author Tony Landis * @package AgileBill * @subpackage Modules:Invoice */ /** * The main AgileBill Invoice Class * * @package AgileBill * @subpackage Modules:Invoice */ class invoice extends OSB_module { # Array holding all our print information public $print = array(); # Enable summary invoice view that rolls multiple instances of the same sku w/identical base&setup price & attributes into one line item private $summarizeInvoice = true; # Holds sku's of line items to exclude from invoice summarys (pdf view only) # @todo not implemented private $summarizeInvoiceExclude = array(); /** Invoice type, 0=new, 1=recurr */ var $type=0; # Invoice Creation Timestamp private $date_orig; # Last modification Timestamp private $date_last; # Invoice Id # @todo change to invoice_id? public $record_id; /** Invoice Item Id */ private $item_id=0; # Parent Invoice Id for recurring public $parent_id; # Account Id for invoice public $account_id; # Affiliate Id of Invoice public $affiliate_id; # Account Billing Id public $account_billing_id; # Campaign Id public $campaign_id; # Reseller Id public $reseller_id; # Billed Currency Id public $billed_currency_id; # Actual Billed Currency selected by Client public $actual_billed_currency_id; # Checkout Plugin Id public $checkout_plugin_id; # array of checkout plugin data returned by checkout plugin public $checkout_plugin_data; /** Current Notice Count */ var $notice_count=0; /** Last Notice Timestamp */ var $notice_date; # Due Date Timestamp private $due_date; /** Net Term Id */ var $net_term_id=false; /** Net Term Last Notice/Late fee Timestamp */ var $net_term_date_last; /** Net Term Interval Count */ var $net_term_intervals=0; # Process Status private $process_status = 0; # Billing Status private $billing_status = 0; /** Suspend Billing Status */ var $suspend_billing=0; # Printed Invoice Status private $print_status = 0; /** Refunded Invoice Status */ var $refund_status=0; /** Calcuate Taxes */ var $tax=true; /** Calculate Discounts */ var $discount=true; # Total Invoice Amount public $total_amt = 0; # Total Amount Billed private $billed_amt = 0; /** Actual total amount billed (converted to local) */ var $actual_billed_amt=0; # Total Tax Amount public $tax_amt = 0; # Total Discount Amount public $discount_amt = 0; /** Recurring Amount */ public $recur_amt = 0; /** Recurring array for Later Processing */ var $recur_arr; # IP Address of User private $ip = USER_IP; /** array of the Invoice items */ var $invoice_item; /** array of the discounts for the Invoice items */ var $item_discount; /** array of the taxes for the Invoice Items */ var $item_tax; /** Tracking to determine payment options */ var $any_new=false; var $any_trial=false; var $any_recurring=false; /** Invoice Config Global Options */ var $invoice_delivery=1; var $invoice_format=0; var $notice_max=MAX_BILLING_NOTICE; var $grace_period=GRACE_PERIOD; /** * Determine the number of days in advance an invoice should be generated. * Invoices are generated when the greater of: * + system default (setup:max_inv_gen_period), (to be deprecated) * + system default (setup_invoice:invoice_advance_gen), */ public function invoice_days() { $db = &DB(); # Get the max invoice days from the setup_invoice table $days = 0; # First from the setup table. $setup = $db->Execute(sqlSelect($db,'setup','max_inv_gen_period','')); if (isset($setup->fields['max_inv_gen_period'])) $days = $setup->fields['max_inv_gen_period']; # Then from the setup_invoice table. $setup = $db->Execute(sqlSelect($db,'setup_invoice','invoice_advance_gen,advance_notice','')); if (isset($setup->fields['invoice_advance_gen']) && $setup->fields['invoice_advance_gen'] > $days) $days = $setup->fields['invoice_advance_gen']; if (isset($setup->fields['advance_notice']) && $setup->fields['advance_notice'] > $days) $days = $setup->fields['advance_notice']; return $days; } /** * Return the SQL that determines which invoices need to be generated * * Invoices are generated when the greater of: * + system default (setup:max_inv_gen_period), (to be deprecated) * + system default (setup_invoice:invoice_advance_gen), * + the account setting (account:invoice_advance_gen) * + the net_terms setting (net_term:invoice_advance_gen) (linked from the account:net_term_id)) * * This function can be called to display the SQL that is need to generate upcoming invoices. * * @param string The SQL to use in the SELECT portion of the query, to return the fields * @param int Days in advance of the normal invoice generation date to obtain * @param int|array Limit the query to just a(n) (set of) Account ID(s) * @param string The SQL to use in the ORDER BY portion of the query. */ public function sql_invoice_soon($fields=null,$adddays=0,$account=null,$orderby=null) { global $C_list; $db = &DB(); # Get the max invoice days from the system configuration tables. $days = $this->invoice_days(); # Pre-notification date for service $max_date = date('Y-m-d',time()+(($adddays+$days)*86400)); if ($account) { if (is_array($account)) $account_where = sprintf('AND account.id IN (%s)',implode(',',$account)); else $account_where = sprintf('AND account.id=%s',$account); } else $account_where = ''; if (is_null($fields)) $fields = "DISTINCT service.id AS sid,account.id AS aid,invoice.id AS iid,FROM_UNIXTIME(service.date_next_invoice,'%Y-%m-%d') AS invoice_date,service.sku AS sku,service.price AS price,account.first_name AS first_name,account.last_name AS last_name,account.currency_id AS billed_currency_id"; if (is_null($orderby)) $orderby = 'ORDER BY aid,invoice_date,sid'; $p=AGILE_DB_PREFIX; $s=DEFAULT_SITE; // @todo NET_TERM is not tested. if ($C_list->is_installed('net_term')) { $net_term = "LEFT JOIN {$p}net_term AS net_term ON (account.net_term_id=net_term.id AND net_term.site_id={$s})"; $net_term_where = "OR ((net_term.invoice_advance_gen!='' OR net_term.invoice_advance_gen IS NOT NULL) AND service.date_next_invoice<=((86400*(net_term.invoice_advance_gen+{$adddays}+{$days}))+(UNIX_TIMESTAMP(CURDATE()))))"; } else { $net_term = ''; $net_term_where = ''; } $sql = " SELECT {$fields} FROM {$p}service AS service JOIN {$p}account AS account ON (service.account_id=account.id AND account.site_id={$s}) LEFT JOIN {$p}invoice AS invoice ON (service.invoice_id=invoice.id AND invoice.site_id={$s}) {$net_term} WHERE service.site_id={$s} AND service.active=1 AND price > 0 AND (service.suspend_billing IS NULL OR service.suspend_billing=0) AND (service.date_next_invoice>0 AND service.date_next_invoice IS NOT NULL) AND ( ((account.invoice_advance_gen!='' OR account.invoice_advance_gen IS NOT NULL) AND service.date_next_invoice<=((86400*(account.invoice_advance_gen+{$adddays}+{$days}))+(UNIX_TIMESTAMP(CURDATE())))) {$net_term_where} OR service.date_next_invoice<=UNIX_TIMESTAMP('{$max_date}') ) {$account_where} {$orderby}"; return $sql; } /** * List all invoices that will be generated soon */ public function invoicesoon($VAR) { global $smarty,$C_list; $db = &DB(); # Then from the setup_invoice table. $setup = $db->Execute(sqlSelect($db,'setup_invoice','advance_notice','')); # Run the database query $result = $db->Execute($this->sql_invoice_soon(null,$setup->fields['advance_notice'])); $invoice = array(); $i = 0; $amount = 0; while (! $result->EOF) { $result->fields['_C'] = ++$i%2 ? 'row1' : 'row2'; array_push($invoice,$result->fields); $amount += $result->fields['price']; $result->MoveNext(); } # Error reporting if ($result === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); return false; } $results = $result->RecordCount(); # Create the definition for fast-forwarding to a single record: if ($results == 1 && ! isset($this->fast_forward)) $smarty->assign('record_id',$record_id); # Create the search record: if($results > 0) { # create the search record include_once(PATH_CORE.'search.inc.php'); $search = new CORE_search; $arr['module'] = $this->module; $arr['sql'] = $this->sql_invoice_soon(); # $arr['limit'] = $limit; # $arr['order_by']= $order_by; $arr['results'] = $results; $search->add($arr); # define the search id and other parameters for Smarty $smarty->assign('search_id', $search->id); # page: $smarty->assign('page', '1'); # limit: # $smarty->assign('limit', $limit); # order_by: # $smarty->assign('order_by', $order_by); } # define the result count $smarty->assign('results',$results); $smarty->assign('invoice',$invoice); $smarty->assign('total_amount',$C_list->format_currency($amount,DEFAULT_CURRENCY)); } public function task_overdue() { $db = &DB(); $rs = $db->Execute($q=sqlSelect($db,array('invoice','account'),'A.id,A.account_id,SUM(A.total_amt-A.billed_amt) as total,B.first_name,B.last_name,A.due_date',sprintf('A.status=1 AND A.total_amt-A.billed_amt>0 AND A.account_id=B.id AND A.due_date<%s',time()),false,false,false,'account_id,id')); if ($rs && $rs->RecordCount()) { $body = ''; $account_total = 0; $account_id = ''; $i = 0; $count = 0; $grand_total = 0; while (! $rs->EOF) { if ($account_id != $rs->fields['account_id'] && $i) { $body .= sprintf("Total: %3.2f\n\n",$account_total); $account_total = 0; $i = 0; } if (! $i) $body .= sprintf("%s %s (%s)\n",$rs->fields['last_name'],$rs->fields['first_name'],$rs->fields['account_id']); $body .= sprintf(" Invoice: %s, Due Date: %s, Amount: %3.2f\n",$rs->fields['id'],date('Y-m-d',$rs->fields['due_date']),$rs->fields['total']); $account_total += $rs->fields['total']; $grand_total += $rs->fields['total']; $account_id = $rs->fields['account_id']; $count++; $i++; $rs->MoveNext(); } if ($account_total) $body .= sprintf("Total: %3.2f\n",$account_total); $body .= "\n"; if ($count || $ground_total) $body .= sprintf("%3.2f outstanding in %s invoices\n",$grand_total,$count); # Send the e-mail.... require_once(PATH_INCLUDES.'phpmailer/class.phpmailer.php'); $mail = new PHPMailer(); $mail->AddAddress('deon@leenooks.vpn','Deon George'); $mail->From = SITE_EMAIL; $mail->FromName = SITE_NAME; $mail->Subject = 'Overdue invoices'; $mail->Body = $body; $mail->Send(); } } /** * Get a list of unpaid invoices for an account */ public function unpaid($account_id) { $db = &DB(); $invoices = array(); $rs = $db->Execute($q=sqlSelect($db,'invoice','id,SUM(total_amt-billed_amt) as total',array('account_id'=>$account_id,'billing_status'=>0,'refund_status'=>0),false,false,'','id')); if ($rs && $rs->RecordCount()) { while (! $rs->EOF) { $invoices[$rs->fields['id']] = $rs->fields['total']; $rs->MoveNext(); } } return $invoices; } /** * Get the account level invoice options * * @param int $id Account Id */ private function setupAccount() { if (! $this->account_id) return; $db = &DB(); $acctrs = $db->Execute(sqlSelect($db,'account','invoice_grace,invoice_advance_gen',sprintf('id=%s',$this->account_id))); if ($acctrs && $acctrs->RecordCount()) { $this->advance_gen = $acctrs->fields['invoice_advance_gen']; if ($this->grace_period == GRACE_PERIOD && ! empty($acctrs->fields['invoice_grace'])) $this->grace_period = $acctrs->fields['invoice_grace']; } } /** * Return the invoice ID */ public function getInvoiceID() { return sprintf('%02s-%06s-%06s',DEFAULT_SITE,$this->print['account']['id'],$this->getInvoiceNum()); } public function getInvoiceNum() { return $this->print['invoice']['id']; } public function getPreviousBalance() { static $bal = 0; if ($bal) return $bal; foreach ($this->print['previousinvoices'] as $item) $bal += $item['total_amt']-$item['billed_amt']; return $bal; } /** * Initialize new invoice creation * * @param bool $type 0=new 1=recur */ public function initNew($type=0) { $this->type = $type; $this->date_orig = time(); $this->date_last = time(); global $C_list; $this->net_term = $C_list->is_installed('net_term'); } /** HERE **/ /** * Commit the current invoice/items/discounts/taxes * * @param object $taxObj Object for Tax Calculation * @param object $discountObj Object for Discount Calculation * @param bool $email Send customer/admin e-mails */ function commitNew(&$taxObj, &$discountObj, $email=true) { # init DB transaction $db = &DB(); $db->BeginTrans(); # Get invoice id if (empty($this->record_id)) $this->record_id = sqlGenID($db,'invoice'); # Serialized records: if (is_array($this->checkout_plugin_data)) $this->checkout_plugin_data=serialize($this->checkout_plugin_data); if (is_array($this->recur_arr)) $this->recur_arr=serialize($this->recur_arr); # Dates & defaults if (empty($this->due_date)) $this->due_date = time(); if (empty($this->date_orig)) $this->date_orig = time(); if (empty($this->date_last)) $this->date_last = time(); if (empty($this->notice_next_date)) $this->notice_next_date = $this->due_date+86400; # net terms if ($this->net_term && ! $this->billing_status && $this->total_amt>0) { include_once(PATH_MODULES.'net_term/net_term.inc.php'); $net = new net_term; $this->net_term_id = $net->termsAllowed($this->account_id, $this->checkout_plugin_id); if (empty($this->net_term_date_last)) $this->net_term_date_last = time(); } # Insert invoice $fields=array( 'date_orig'=>$this->date_orig, 'date_last'=>$this->date_last, 'parent_id'=>$this->parent_id, 'type'=>$this->type, 'process_status'=>$this->process_status, 'billing_status'=>$this->billing_status, 'suspend_billing'=>$this->suspend_billing, 'refund_status'=>$this->refund_status, 'print_status'=>$this->print_status, 'account_id'=>$this->account_id, 'account_billing_id'=>$this->account_billing_id, 'affiliate_id'=>$this->affiliate_id, 'campaign_id'=>$this->campaign_id, 'reseller_id'=>$this->reseller_id, 'checkout_plugin_id'=>$this->checkout_plugin_id, 'checkout_plugin_data'=>$this->checkout_plugin_data, 'tax_amt'=>$this->tax_amt, 'discount_amt'=>$this->discount_amt, 'total_amt'=>$this->total_amt, 'billed_amt'=>$this->billed_amt, 'recur_amt'=>$this->recur_amt, 'recur_arr'=>$this->recur_arr, 'actual_billed_amt'=>$this->actual_billed_amt, 'billed_currency_id'=>$this->billed_currency_id, 'actual_billed_currency_id'=>$this->actual_billed_currency_id, 'notice_count'=>$this->notice_count, 'notice_max'=>$this->notice_max, 'notice_next_date'=>$this->notice_next_date, 'due_date'=>$this->due_date, 'grace_period'=>$this->grace_period, 'net_term_id'=>$this->net_term_id, 'net_term_date_last'=>$this->net_term_date_last, 'net_term_intervals'=>$this->net_term_intervals, 'ip'=>$this->ip ); $db->Execute(sqlInsert($db,'invoice',$fields,$this->record_id)); # Loop through invoice items if (is_array($this->invoice_item)) { foreach ($this->invoice_item as $id=>$fields) { # Get an invoice_item id $invoice_item_id = sqlGenID($db,'invoice_item'); # Domain sku's if ($fields['item_type'] == 2) { $fields['sku'] = sprintf('DOMAIN-%s',strtoupper($fields['domain_type'])); $fields['price_type'] = '0'; } # Build e-mail item details if ($email) { $email_instructions = ''; if ($fields['item_type']<2 && !empty($fields['product_id'])) { // product, get email instructions and translated name $translate_prod = $db->Execute( sqlSelect($db,'product_translate','email_template,name',array('product_id'=>$fields['product_id'],'language_id'=>SESS_LANGUAGE))); if ($translate_prod && $translate_prod->RecordCount()) { $instructions=$translate_prod->fields['email_template']; $name=$translate_prod->fields['name']; } else { $name = $fields['sku']; } } elseif ($fields['item_type'] == 2) { $name=strtoupper($fields['domain_name'].".".$fields['domain_tld']); } else { if (! empty($fields['product_name'])) $name = $fields['product_name']; else $name = $fields['sku']; } // add to e-mail array $email_arr[] = array('Qty' => '('.$fields["quantity"].')', 'Item' => 'SKU '.$fields["sku"], 'Price' => number_format($fields["total_amt"],2), 'Name' => $name, 'Instructions' => $instructions); } // insert the invoice item_id $fields['invoice_id']=$this->record_id; $fields['date_orig']=time(); $attr = serialize($fields['product_attr']); $fields['product_attr']=$fields['product_attr_cart']; $fields['product_attr_cart']=$attr; $db->Execute(sqlInsert($db,'invoice_item',$fields,$invoice_item_id)); # Load tax object for tax calculation include_once(PATH_MODULES.'tax/tax.inc.php'); $taxObj = new tax; # Load discount object for discount calculation include_once(PATH_MODULES.'discount/discount.inc.php'); $discountObj = new discount; $discountObj->available_discounts($this->account_id); // insert taxes if ($this->tax && $this->tax_amt > 0 && ! empty($this->tax_arr[$id])) $taxObj->invoice_item($this->record_id,$invoice_item_id,$this->account_id,$this->tax_arr[$id]); // insert discounts if($this->discount && $this->discount_amt>0 && !empty($this->discount_arr[$id])) $discountObj->invoice_item($this->record_id, $invoice_item_id, $this->account_id, $this->discount_arr[$id]); } } // complete DB transaction $db->CompleteTrans(); // complete building e-mail notices and send if ($email) { include_once(PATH_MODULES.'email_template/email_template.inc.php'); // Create the products order list for the e-mail: $e_itm_usr = ''; $e_itm_adm = ''; if(is_array($email_arr)) { foreach($email_arr as $i=>$em) { $e_itm_usr .= $em['Qty'].' '.$em['Item'].' ('.$em['Name'].') '.$em['Price']; $e_itm_adm .= $em['Qty'].' '.$em['Item'].' ('.$em['Name'].') '.$em['Price']."\r\n"; if(!empty($email_arr[$i]['Instructions'])) $e_itm_usr .= "\r\n * " . $email_item_arr[$i]['Instructions']; $e_itm_usr .= "\r\n"; } $e_arr_user = array('%products%' => $e_itm_usr); $e_arr_adm = array('%products%' => $e_itm_adm); } // e-mail invoice creation confirmation $mail = new email_template; $mail->send('invoice_confirm_user', $this->account_id, $this->record_id, $this->checkout_plugin_id, $e_arr_user); $email = new email_template; $email->send('admin->invoice_confirm_admin', $this->account_id, $this->record_id, $this->checkout_plugin_id, $e_arr_adm); } // net terms? if($this->net_term_id) { $this->approveInvoice(array('id'=>$this->record_id), $this); return $this->record_id; } // Determine the approval status by checkout plugin type & settings: if($email && $this->billing_status == 0 && $this->billed_amt > 0) { global $C_list; if($this->checkout_type == 'redirect') { // User e-mail alert of due invoice $email = new email_template; $email->send('invoice_due_user', $this->account_id, $this->record_id, $user_currency, $C_list->date($this->due_date)); } elseif ($this->checkout_type == 'other') { // Admin e-mail alert of manual payment processing $email = new email_template; $email->send('admin->invoice_due_admin', $this->account_id, $this->record_id, $admin_currency, $C_list->date($this->due_date)); } } elseif($this->billed_amt>0) { if($email) { // User alert of payment processed $email = new email_template; $email->send('invoice_paid_user', $this->account_id, $this->record_id, $this->billed_currency_id, ''); // Admin alert of payment processed $email = new email_template; $email->send('admin->invoice_paid_admin', $this->account_id, $this->record_id, $this->billed_currency_id, ''); } $this->autoApproveInvoice($this->record_id); } elseif($this->billed_amt == 0 && $this->billing_status == 1) { $this->autoApproveInvoice($this->record_id); } // return invoice id return $this->record_id; } /** * Add an invoice item * * @param int $id Reference ID for use in Cart or false * @param object $cartrs RS from cart query */ public function addItem($id,$cartrs) { # Setup Variables $item_type = $cartrs->fields['cart_type']; $product_id = $cartrs->fields['product_id']; $recurring_schedule = $cartrs->fields['recurr_schedule']; $domain_tld = $cartrs->fields['domain_tld']; $domain_term = $cartrs->fields['domain_term']; $domain_type = $cartrs->fields['domain_type']; # Define correct qty if ($cartrs->fields['quantity']<0) $quantity = 1; else $quantity = $cartrs->fields['quantity']; if (! empty($cartrs->fields['product_attr']) && ! is_array($cartrs->fields['product_attr'])) $product_attr = unserialize($result->fields['product_attr']); else $product_attr = array(); # Special processing for ADHOC items if ($item_type == 3) { $taxable = $cartrs->fields['cart_type']; $product_name = $cartrs->fields['ad_hoc_name']; $sku = $cartrs->fields['ad_hoc_sku']; $price_base = $cartrs->fields['ad_hoc_amount']; $price_setup = $cartrs->fields['ad_hoc_setup']; $discount_manual = $cartrs->fields['ad_hoc_discount']; } else { $taxable = false; $product_name = ''; $sku = ''; $price_base = false; $price_setup = false; $discount_manual = false; } $tax_amt = 0; $total_amt = 0; $discount_amt = 0; $item = null; # Load tax object for tax calculation include_once(PATH_MODULES.'tax/tax.inc.php'); $taxObj = new tax; # Load discount object for discount calculation include_once(PATH_MODULES.'discount/discount.inc.php'); $discountObj = new discount; $discountObj->available_discounts($this->account_id); # Determine the reference id for this item if ($id>0) $this->item_id=$id; else $this->item_id++; if ($product_id) { include_once(PATH_MODULES.'product/product.inc.php'); $item_object = new product; $item = $item_object->getID($product_id); $this->product[$this->item_id] = $item; } switch ($item_type) { # Get the tld details case '2': include_once(PATH_MODULES.'host_tld/host_tld.inc.php'); $item_object = new host_tld; $item = $item_object->getName($domain_tld); # Get the TLD pricing if ($price_base===false && $price_setup===false && $domain_tld && $domain_term && $domain_type) { $tldprice = $item_object->price_tld_arr($domain_tld,$domain_type,false,false,false,$this->account_id); if ($domain_type == 'park') { $price_base = $tldprice; } else { $price_base = $tldprice[$domain_term]; $this->tld_arr[$this->item_id] = $tldprice; } } $price_recur = $price_base; break; default: # Get the product pricing $price_type = 0; if ($price_base===false && $price_setup===false && $this->product[$this->item_id]) { $price_type = $this->product[$this->item_id]['price_type']; $sku = $this->product[$this->item_id]['sku']; # Get pricing for this product: $prod_price = $item_object->price_prod($this->product[$this->item_id],$recurring_schedule,$this->account_id); $price_base = $prod_price['base']; $price_setup = $prod_price['setup']; $prod_price = $item_object->price_prod($this->product[$this->item_id],$recurring_schedule,$this->account_id,false); $price_recur = $prod_price['base']; # Calculate any product attributes fees $attr_price = $item_object->price_attr($this->product[$this->item_id],$product_attr,$recurring_schedule,$this->account_id); $price_base += $attr_price['base']; $price_setup += $attr_price['setup']; # @todo this cant be the right value for recurring addons? $price_recur += $attr_price['base']; # Determine price type for checkout switch ($price_type) { case 0: $this->any_new = true; break; case 1: $this->any_recurring = true; break; case 2: $this->any_trial = true; break; } } else { $this->any_new = true; $price_recur = $price_base; } break; } if ($item && isset($item['taxable'])) $taxable = $item['taxable']; # Set total amount for this line item before attributes, taxes, or discounts $price_base *= $quantity; $price_setup *= $quantity; $total_amt = ($price_setup+$price_base); //echo '
';print_r(array('q'=>$quantity,'pb'=>$price_base,'pt'=>$price_type,'pr'=>$price_recur,'id'=>$id,'iid'=>$this->item_id,'p'=>$product->fields));

		# Format product attributes for storage
		$product_attr_cart = false;
		if (($item_type==0 || $item_type>2) && is_array($product_attr))
			$product_attr_cart = $this->get_product_attr_cart($product_attr);

		# Recurring taxes and arrays
		if (($price_recur || $price_base) && $price_type==1) {
			$this->recur_amt += $price_recur;

			# Determine taxes for the recurring amount
			if ($this->tax && $taxable && $price_recur>0 && $this->account_id) {
				$recur_tax_arr = $taxObj->calculate($price_recur,$this->country_id,$this->state);
				if (is_array($recur_tax_arr))
					foreach ($recur_tax_arr as $tx)
						$this->recur_amt += $tx['rate'];
			}

			# Get the recurring arrays for price and invoice
			if ($item_object) {
				$this->price_arr[$this->item_id] = $item_object->price_recurr_arr($this->product[$this->item_id],$this->account_id);
				$this->recur_arr[] = array(
					'price' 		 => $price_base*$quantity,
					'recurr_schedule'=> $recurring_schedule,
					'recurr_type' 	 => $this->product[$this->item_id]['price_recurr_type'],
					'recurr_weekday' => $this->product[$this->item_id]['price_recurr_weekday'],
					'recurr_week' 	 => $this->product[$this->item_id]['price_recurr_week']
				);
			}
		}

		# Calculate any ad-hoc line item level (admin) discounts
		if ($this->discount && $discount_manual>0) {
			$total_amt -= $discount_manual;
			$discount_amt += $discount_manual;
			$this->discount_amt += $discount_amt;
			$discountObj->add_manual_discount($discount_manual,'MISC',$this->item_id);
		}

		# Account level discounts
		if ($this->discount && $this->account_id) {
			# Calculate any database level discounts for this item (both account specific and session specific)
			$discount_amt = $discountObj->calc_all_discounts(0,$this->item_id,$product_id,$total_amt,$this->account_id,$this->total_amt+$total_amt);
			$total_amt -= $discount_amt;
			$this->discount_amt += $discount_amt;
		}

		# Add to total discount array
		if (is_array($discountObj->discount_arr)) {
			$this->discount_arr[$this->item_id] = $discountObj->discount_arr;
		}

		# Increment invoice total amount
		$this->total_amt += $total_amt;

		// calculate any taxes for current item
		if ($this->tax && $taxable && $total_amt>0 && $this->account_id) {
			$tax_arr = $taxObj->calculate($total_amt, $this->country_id, $this->state);
			if(is_array($tax_arr)) {
				foreach($tax_arr as $tx) $tax_amt += $tx['rate'];
				$this->item_tax[$this->item_id] = $tax_arr;
				$this->tax_arr[$this->item_id] = $tax_arr;
			}
			$this->tax_amt += $tax_amt;
			$this->total_amt += $tax_amt;
		}

		// store the fields to an array
		$this->invoice_item[$this->item_id] = array(
			'item_type'=>$item_type,
			'price_type'=>$price_type,
			'taxable'=>$taxable,
			'service_id'=>$cartrs->fields['service_id'],
			'parent_id'=>$cartrs->fields['parent_id'],
			'product_id'=>$product_id,
			'product_attr'=>$product_attr,
			'product_attr_cart'=>$product_attr_cart,
			'product_name'=>$product_name,
			'sku'=>$sku,
			'quantity'=>$quantity,
			'price_base'=>$price_base,
			'price_setup'=>$price_setup,
			'recurring_schedule'=>$recurring_schedule,
			'date_start'=>'',
			'date_stop'=>'',
			'domain_name'=>$cartrs->fields['domain_name'],
			'domain_tld'=>$domain_tld,
			'domain_term'=>$domain_term,
			'domain_type'=>$domain_type,
			'total_amt'=>$total_amt,
			'tax_amt'=>$tax_amt,
			'discount_amt'=>$discount_amt
		);
	}

	/**
	 * Group all taxes to lump sums
	 */
	public function group_taxes() {
		if(is_array($this->tax_arr)) {
			foreach($this->tax_arr as $taxarr) foreach($taxarr as $taxes) $arr[$taxes["name"]]+=$taxes["rate"];
			if(is_array($arr)) {
				foreach($arr as $a=>$b) $ret[] = array('name'=>$a, 'rate'=>$b);
				return $ret;
			}
		}
	}

	/**
	 * Group all discounts to lump sums
	 */
	public function group_discounts() {
		if(is_array($this->discount_arr)) {
			foreach($this->discount_arr as $discarr) foreach($discarr as $discounts) $arr[$discounts["discount"]]+=$discounts["amount"];
			if(is_array($arr)) {
				foreach($arr as $a=>$b) $ret[] = array('name'=>$a, 'total'=>$b);
				return $ret;
			}
		}
	}

	/**
	 * Build a formatted product attribute list
	 *
	 * @param array $attributes
	 * @return string Formatted product attribute list
	 */
	public function get_product_attr_cart($attributes) {
		# Set the attribute array:
		if(!empty($attributes) && is_array($attributes)) {
			$db=&DB();
			$product_attr = false;
			foreach($attributes as $id=>$value) {
				if (!empty($value)) {
					if(is_numeric($id)) {
						$attr = $db->Execute(sqlSelect($db,"product_attr","name","id=$id"));
						if ($attr && $attr->RecordCount()) $product_attr .= "{$attr->fields['name']}==".ereg_replace("\r\n", "
", $value)."\r\n"; } else { $product_attr .= "{$id}=={$value}\r\n"; } } } } return $product_attr; } /** * Custom Tracking * @todo: should this be here, or under the affilate module? what does it do? */ public function custom_tracking($VAR) { # Get the invoice id if (SESS_LOGGED == false) return false; # Check if we are in the iframe if (empty($VAR['_escape']) || empty($VAR['confirm'])) { printf('', md5(microtime())); return; } # Get the un-tracked invoice details $db = &DB(); $result = $db->Execute(sqlSelect($db,'invoice','*',sprintf('(custom_affiliate_status IS NULL OR custom_affiliate_status=0) AND billing_status=1 AND account_id=%s',SESS_ACCOUNT))); if ($result === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,sprintf('%s (%s)',$db->ErrorMsg(),$sql)); return false; } if ($result->RecordCount() == 0) return false; # Get the totals $invoice = ''; $total_amount = false; while (! $result->EOF) { if (! empty($invoice)) $invoice .= '-'; $invoice .= $result->fields['id']; $amt = $result->fields['total_amt']; $total_amount += $amt; $result->MoveNext(); } # Echo the custom tracking code to the screen: if (! is_file(PATH_FILES.'tracking.txt')) return false; $tracking = file_get_contents(PATH_FILES.'tracking.txt'); $tracking = str_replace('%%amount%%',$total_amount,$tracking); $tracking = str_replace('%%invoice%%',$invoice,$tracking); $tracking = str_replace('%%affiliate%%',SESS_AFFILIATE,$tracking); $tracking = str_replace('%%campaign%%',SESS_CAMPAIGN,$tracking); $tracking = str_replace('%%account%%',SESS_ACCOUNT,$tracking); echo $tracking; # Update the record so it is not tracked again $rs = $db->Execute(sqlUpdate($db,'invoice',array('custom_affiliate_status'=>1),sprintf('account_id=%s AND billing_status=1',SESS_ACCOUNT))); if ($rs === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,sprintf('%s (%s)',$db->ErrorMsg(),$sql)); } return true; } /** Performance: (for the admin dashboard) */ public function performance($VAR) { global $smarty, $C_list, $C_translate; # Get the period type, default to month if (empty($VAR['period'])) $p = 'm'; else $p = $VAR['period']; # Determine the correct period language: if($p=='' or $p == 'm') { $pTrans = $C_translate->translate('thismonth','invoice','') . ' '. $C_translate->translate('vs','invoice','') . ' ' . $C_translate->translate('lastmonth','invoice',''); $pFore = $C_translate->translate('thismonth','invoice',''); } elseif ($p == 'w') { $pTrans = $C_translate->translate('thisweek','invoice','') . ' '. $C_translate->translate('vs','invoice','') . ' ' . $C_translate->translate('lastweek','invoice',''); $pFore = $C_translate->translate('thisweek','invoice',''); } elseif ($p == 'y') { $pTrans = $C_translate->translate('thisyear','invoice','') . ' '. $C_translate->translate('vs','invoice','') . ' ' . $C_translate->translate('lastyear','invoice',''); $pFore = $C_translate->translate('thisyear','invoice',''); } $smarty->assign('period_compare', $pTrans); $smarty->assign('period_forcast', $pFore); # Get the period start & end switch ($p) { case 'w': $dow = date('w'); $this_start = mktime(0,0,0,date('m'), date('d')-$dow, date('y')); $this_end = mktime(23,59,59,date('m'), date('d'), date('y')); $last_start = mktime(0,0,0,date('m'), date('d', $this_start)-7, date('y')); $last_end = $this_start-1; break; case 'm': $this_start = mktime(0,0,0,date('m'), 1, date('y')); $this_end = mktime(23,59,59,date('m'), date('d'), date('y')); $last_start = mktime(0,0,0, date('m', $this_start)-1, 1, date('y')); $last_end = $this_start-1; break; case 'y': $this_start = mktime(0,0,0,1,1, date('y', time())); $this_end = mktime(23,59,59, date('m'), date('d'), date('y')); $last_start = mktime(0,0,0,1,1, date('y', $this_start)-1); $last_end = $this_start-1; break; } ############################## # Get sales for this period ############################## $db = &DB(); $this_amt = 0; $sql = 'SELECT total_amt FROM ' . AGILE_DB_PREFIX . 'invoice WHERE date_orig >= ' . $db->qstr($this_start) . ' AND date_orig <= ' . $db->qstr($this_end) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); while(!$rs->EOF) { $this_amt += $rs->fields['total_amt']; $rs->MoveNext(); } $smarty->assign('sales_current', $this_amt); ############################### # Get sales for last period ############################### $last_amt = 0; $sql = 'SELECT total_amt FROM ' . AGILE_DB_PREFIX . 'invoice WHERE date_orig >= ' . $db->qstr($last_start) . ' AND date_orig <= ' . $db->qstr($last_end) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); while(!$rs->EOF) { $last_amt += $rs->fields['total_amt']; $rs->MoveNext(); } $smarty->assign('sales_previous', $last_amt); ############################### # Get sales change percentage ############################### if($last_amt > 0) $sales_change = $this_amt/$last_amt *100 -100; else $sales_change = 0; if($sales_change == 0) $sales_change = ''; elseif($sales_change < 0) $sales_change = '' . number_format($sales_change, 1). '%'; else $sales_change = '+'.number_format($sales_change, 1). '%'; $smarty->assign('sales_change', $sales_change); ################################# # Get forcast for current period ################################# switch ($p) { case 'w': $dow = date('w')+1; $forcast_daily = $this_amt/$dow ; $forcast_l_daily = $last_amt / 7; @$forcast_change = $forcast_daily / $forcast_1_daily *100 -100; $forcast_current = $forcast_daily * 7; break; case 'm': $forcast_daily = $this_amt / date('d'); $forcast_1_daily = $last_amt / date('t', mktime(0,0,0,date('m')-1, 1, date('y'))); @$forcast_change = $forcast_daily / $forcast_1_daily *100 -100; $forcast_current = $forcast_daily * date('t'); break; case 'y': $forcast_daily = $this_amt / date('z'); $forcast_1_daily = $last_amt / 356; @$forcast_change = $forcast_daily / $forcast_1_daily *100 -100; $forcast_current = $forcast_daily * 365; break; } $smarty->assign('forcast_current', $forcast_current); ############################### # Get forcast change percentage ############################### if($last_amt > 0) @$forcast_change = $forcast_daily/$forcast_1_daily *100; else $forcast_change = 0; if($forcast_change == 0) $forcast_change = '-'; elseif($forcast_change < 0) $forcast_change = '' . number_format($forcast_change, 1). '%'; else $forcast_change = '+'.number_format($forcast_change, 1). '%'; $smarty->assign('forcast_change', $forcast_change); #################################################### # Get Quota for Today to meet Forcasted sales: #################################################### $smarty->assign('quota_current', $forcast_daily); ############################## # Get AR credits for this period ############################## $this_billed_amt = 0; $sql = 'SELECT billed_amt FROM ' . AGILE_DB_PREFIX . 'invoice WHERE date_orig >= ' . $db->qstr($this_start) . ' AND date_orig <= ' . $db->qstr($this_end) . ' AND billed_amt > ' . $db->qstr(0) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); while(!$rs->EOF) { $this_billed_amt += $rs->fields['billed_amt']; $rs->MoveNext(); } $smarty->assign('ar_credits_current', $this_billed_amt); ############################### # Get AR credits for last period ############################### $last_billed_amt = 0; $sql = 'SELECT billed_amt FROM ' . AGILE_DB_PREFIX . 'invoice WHERE date_orig >= ' . $db->qstr($last_start) . ' AND date_orig <= ' . $db->qstr($last_end) . ' AND billed_amt > ' . $db->qstr(0) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); while(!$rs->EOF) { $last_billed_amt += $rs->fields['billed_amt']; $rs->MoveNext(); } $smarty->assign('ar_credits_previous', $last_billed_amt); ############################### # Get AR Credits change percentage ############################### if($last_billed_amt > 0) $ar_change = $this_billed_amt/$last_billed_amt *100 -100; else $ar_change = 0; if($ar_change == 0) $ar_change = '-'; elseif($ar_change < 0) $ar_change = '' . number_format($ar_change, 1). '%'; else $ar_change = '+'.number_format($ar_change, 1). '%'; $smarty->assign('ar_credit_change', $ar_change); ########################################## # Get AR Balance ########################################## $this_ar_balance = $this_billed_amt - $this_amt; $last_ar_balance = $last_billed_amt - $last_amt; $smarty->assign('ar_balance_current', $this_ar_balance); $smarty->assign('ar_balance_last', $last_ar_balance); ######################################### # Get Users (current) ######################################### $sql = 'SELECT id FROM ' . AGILE_DB_PREFIX . 'account WHERE date_orig >= ' . $db->qstr($this_start) . ' AND date_orig <= ' . $db->qstr($this_end) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); $users_current = $rs->RecordCount(); $smarty->assign('users_current', $users_current); ######################################### # Get Users (previous) ######################################### $sql = 'SELECT id FROM ' . AGILE_DB_PREFIX . 'account WHERE date_orig >= ' . $db->qstr($last_start) . ' AND date_orig <= ' . $db->qstr($last_end) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); $users_previous = $rs->RecordCount(); $smarty->assign('users_previous', $users_previous); ############################### # Get users change percentage ############################### if($users_previous > 0) @$users_change = $users_current/$users_current *100 -100; else $users_change = 0; if($users_change == 0) $users_change = '-'; elseif($users_change < 0) $users_change = '' . number_format($users_change, 1). '%'; else $users_change = '+'.number_format($users_change, 1). '%'; $smarty->assign('users_change', $users_change); # Get Affiliate stats if($C_list->is_installed('affiliate')) { $smarty->assign('show_affiliates', true); ########################################### # Get affiliate sales for this period ########################################### $this_amt = 0; $sql = 'SELECT total_amt FROM ' . AGILE_DB_PREFIX . 'invoice WHERE date_orig >= ' . $db->qstr($this_start) . ' AND date_orig <= ' . $db->qstr($this_end) . ' AND affiliate_id != ' . $db->qstr(0) . ' AND affiliate_id != ' . $db->qstr('') . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); while(!$rs->EOF) { $this_amt += $rs->fields['total_amt']; $rs->MoveNext(); } $smarty->assign('affiliate_sales_current', $this_amt); ########################################## # Get affiliate sales for last period ########################################## $last_amt = 0; $sql = 'SELECT total_amt FROM ' . AGILE_DB_PREFIX . 'invoice WHERE date_orig >= ' . $db->qstr($last_start) . ' AND date_orig <= ' . $db->qstr($last_end) . ' AND affiliate_id != ' . $db->qstr(0) . ' AND affiliate_id != ' . $db->qstr('') . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); while(!$rs->EOF) { $last_amt += $rs->fields['total_amt']; $rs->MoveNext(); } $smarty->assign('affiliate_sales_previous', $last_amt); ########################################### # Get affiliate sales change percentage ########################################### if($last_amt > 0) $sales_change = $this_amt/$last_amt *100 -100; else $sales_change = 0; if($sales_change == 0) $sales_change = '-'; elseif($sales_change < 0) $sales_change = '' . number_format($sales_change, 1). '%'; else $sales_change = '+'.number_format($sales_change, 1). '%'; $smarty->assign('affiliate_sales_change', $sales_change); } /** Get VoIP Performance Data */ if( $C_list->is_installed('voip') ) { # Avg. Call Duration for this period $rs = $db->Execute(sqlSelect($db, "voip_cdr", "avg(ceiling(billsec/60))", "disposition='ANSWERED' AND date_orig >= $this_start AND date_orig <= $this_end")); if(empty($rs->fields[0])) $acd=0; else $acd = $rs->fields[0]; $smarty->assign('acd',$acd); # Avg. Call Duration for last period $rs = $db->Execute(sqlSelect($db, "voip_cdr", "avg(ceiling(billsec/60))", "disposition='ANSWERED' AND date_orig >= $last_start AND date_orig <= $last_end")); if(empty($rs->fields[0])) $acd_last=0; else $acd_last = $rs->fields[0]; $smarty->assign('acd_last',$acd_last); # Get Avg. Call Duration change Percentage if($acd > 0) $acd_change = $acd/$acd_last*100-100; else $acd_change = 0; if($acd_change == 0) $acd_change = '-'; elseif ($acd_change < 0) $acd_change = '' . number_format($acd_change, 1). '%'; else $acd_change = '+'.number_format($acd_change, 1). '%'; $smarty->assign('acd_change', $acd_change); # Avg. Successful Rate for this period $rs = $db->Execute(sqlSelect($db, "voip_cdr", "count(*)", "disposition='ANSWERED' AND date_orig >= $this_start AND date_orig <= $this_end")); $rs1 = $db->Execute(sqlSelect($db, "voip_cdr", "count(*)", "date_orig >= $this_start AND date_orig <= $this_end")); if ($rs->fields[0]) $asr = number_format(($rs->fields[0] / $rs1->fields[0]) * 100,3)." %"; else $asr = "-"; $smarty->assign('asr', $asr); # Number of CDRs for this period $cdrs = $rs1->fields[0]; $smarty->assign('cdrs', number_format($cdrs,0)); # Avg. Successful Rate for last period $rs = $db->Execute(sqlSelect($db, "voip_cdr", "count(*)", "disposition='ANSWERED' AND date_orig >= $last_start AND date_orig <= $last_end")); $rs1 = $db->Execute(sqlSelect($db, "voip_cdr", "count(*)", "date_orig >= $last_start AND date_orig <= $last_end")); if ($rs->fields[0]) $asr_last = number_format(($rs->fields[0] / $rs1->fields[0]) * 100,3)." %"; else $asr_last = "-"; $smarty->assign('asr_last', $asr_last); # Number of CDRS for last period $cdrs_last = $rs1->fields[0]; $smarty->assign('cdrs_last', number_format($cdrs_last,0)); # Get Avg. Successful Rate change Percentage if($asr > 0) $asr_change = $asr/$asr_last*100-100; else $asr_change = 0; if($asr_change == 0) $asr_change = '-'; elseif ($asr_change < 0) $asr_change = '' . number_format($asr_change, 1). '%'; else $asr_change = '+'.number_format($asr_change, 1). '%'; $smarty->assign('asr_change', $asr_change); # Get Number of CDRs change Percentage if($cdrs > 0) $cdrs_change = $cdrs/$cdrs_last*100-100; else $cdrs_change = 0; if($cdrs_change == 0) $cdrs_change = '-'; elseif ($cdrs_change < 0) $cdrs_change = '' . number_format($cdrs_change, 1). '%'; else $cdrs_change = '+'.number_format($cdrs_change, 1). '%'; $smarty->assign('cdrs_change', $cdrs_change); } # Generate the Calendar Overview include_once(PATH_MODULES.'core/calendar.inc.php'); $calendar = new calendar; $start = $calendar->start; $end = $calendar->end; global $C_list; $C_list->currency(DEFAULT_CURRENCY); $currency_symbol=$C_list->format_currency[DEFAULT_CURRENCY]['symbol']; # Get the paid/due invoice statistics $rs = $db->Execute($sql=sqlSelect($db,"invoice","date_orig,total_amt,billing_status,refund_status,billed_amt,suspend_billing","date_orig >= $start and date_orig <= $end")); if($rs && $rs->RecordCount()) { while(!$rs->EOF) { $day = date("j", $rs->fields['date_orig']); if($rs->fields['billed_amt'] > 0 && ($rs->fields['billing_status'] == 1 || $rs->fields['refund_status'] != 1)) { $paid[$day] += $rs->fields['billed_amt']; } if ($rs->fields['billing_status'] != 1 && $rs->fields['refund_status'] != 1) { $amt = $rs->fields['total_amt'] - $rs->fields['billed_amt']; $due[$day] += $amt; } $rs->MoveNext(); } if(is_array($paid)) foreach($paid as $day=>$item) $calendar->add("Paid - {$currency_symbol}". number_format($item,2), $day, 'green', 'green'); if(is_array($due)) foreach($due as $day=>$item) $calendar->add("Due - {$currency_symbol}". number_format($item,2), $day,'red','red'); } # Get the upcoming due services $rs = $db->Execute($sql=sqlSelect($db,"service","date_next_invoice,price","price > 0 AND date_next_invoice >= $start AND date_next_invoice <= $end AND suspend_billing <> 1")); if($rs && $rs->RecordCount()) { while(!$rs->EOF) { $day = date("j", $rs->fields['date_next_invoice']); $sdue[$day] += $rs->fields['price']; $rs->MoveNext(); } foreach($sdue as $day=>$item) { $calendar->add("Recurring - {$currency_symbol}". number_format($item,2), $day, 'grey', 'grey'); } } $calout= $calendar->generate(); $smarty->assign("calendar", $calout); return; } /** AUTO APPROVE INVOICE */ function autoApproveInvoice($invoice_id) { $do = false; # Get the invoice details: $db = &DB(); $q = "SELECT * FROM ".AGILE_DB_PREFIX."invoice WHERE id = ".$db->qstr($invoice_id)." AND site_id = ".$db->qstr(DEFAULT_SITE); $invoice = $db->Execute($q); if ($invoice === false) { global $C_debug; $C_debug->error(__FILE__,'autoApproveInvoice', $db->ErrorMsg()); return false; } # Get the checkout details: $q = "SELECT * FROM ".AGILE_DB_PREFIX."checkout WHERE id = ".$db->qstr($invoice->fields['checkout_plugin_id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $checkout = $db->Execute($q); if ($checkout === false) { global $C_debug; $C_debug->error(__FILE__,'autoApproveInvoice', $db->ErrorMsg()); return false; } # Get the account details: $q = "SELECT * FROM ".AGILE_DB_PREFIX."account WHERE id = ".$db->qstr($invoice->fields['account_id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $account = $db->Execute($q); if ($account === false) { global $C_debug; $C_debug->error(__FILE__,'autoApproveInvoice', $db->ErrorMsg()); return false; } # is this a recurring invoices, and is manual approvale req? if ($invoice->fields['type'] == 1 && $checkout->fields['manual_approval_recur'] != 1) { $do = true; } # Manual approval required for all? if($invoice->fields['type'] != 1 && $checkout->fields['manual_approval_all'] != 1) { $do = true; } if(!$do) { # manual approval req. for invoice amount? if(!empty($checkout->fields['manual_approval_amount']) && $do == true) if($checkout->fields['manual_approval_amount'] <= $invoice->fields['total_amt']) $do = false; # manual approval req. for user's country? if(!empty($checkout->fields['manual_approval_country']) && $do == true) { $arr = unserialize($checkout->fields['manual_approval_country']); for($i=0; $ifields['country_id'] == $arr[$i]) $do = false; } } # manual approval req. for user's currency? if(!empty($checkout->fields['manual_approval_currency']) && $do == true) { $arr = unserialize($checkout->fields['manual_approval_currency']); for($i=0; $ifields['actual_billed_currency_id'] == $arr[$i]) $do = false; } } # manual approval req. for user's group(s)? if(!empty($checkout->fields['manual_approval_group']) && $do == true) { # Get the group details: $q = "SELECT group_id FROM ".AGILE_DB_PREFIX."account_group WHERE account_id = ".$db->qstr($invoice->fields['account_id'])." AND active = ".$db->qstr('1')." AND site_id = ".$db->qstr(DEFAULT_SITE); $groups = $db->Execute($q); if ($groups === false) { global $C_debug; $C_debug->error(__FILE__,'autoApproveInvoice', $db->ErrorMsg()); } $arr = unserialize($checkout->fields['manual_approval_group']); while(!$groups->EOF) { for($i=0; $ifields["group_id"]; if($idx == $arr[$i]) $do = false; } $groups->MoveNext(); } } } if ($do) { # Approve the invoice $arr['id'] = $invoice_id; $this->approveInvoice($arr, $this); } else { # Admin manual approval notice include_once(PATH_MODULES.'email_template/email_template.inc.php'); $mail = new email_template; $mail->send('invoice_manual_auth_admin', $invoice->fields['account_id'], $invoice->fields['id'], $invoice->fields['checkout_plugin_id'], ''); } } /* APPROVE INVOICE */ function approveInvoice($VAR) { # Get the invoice details: $db = &DB(); $q = "SELECT * FROM ".AGILE_DB_PREFIX."invoice WHERE id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $invoice = $db->Execute($q); if ($invoice === false) { global $C_debug; $C_debug->error(__FILE__,'approveInvoice', $db->ErrorMsg()); return false; } # Validate invoice exists & needs approval: if($invoice->fields['id'] != $VAR['id'] || $invoice->fields['process_status'] == '1') return false; # Update the invoice approval status: $q = "UPDATE ".AGILE_DB_PREFIX."invoice SET date_last = ".$db->qstr(time()).", process_status = ".$db->qstr('1')." WHERE id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $result = $db->Execute($q); if ($result === false) { global $C_debug; $C_debug->error(__FILE__,'approveInvoice', $db->ErrorMsg()); return false; } # Send approval notice to user: include_once(PATH_MODULES.'email_template/email_template.inc.php'); $mail = new email_template; $mail->send('invoice_approved_user', $invoice->fields['account_id'], $VAR['id'], '', ''); # Include the service class include_once(PATH_MODULES.'service/service.inc.php'); $srvc = new service; # Determine if services have already been created for this invoice: if($invoice->fields['type'] != 1) { $q = "SELECT id FROM ".AGILE_DB_PREFIX."service WHERE invoice_id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $service = $db->Execute($q); if ($service === false) { global $C_debug; $C_debug->error(__FILE__,'approveInvoice', $db->ErrorMsg()); return false; } if ($service->RecordCount() > 0) { # Update services to approved status: while(!$service->EOF) { $srvc->approveService($service->fields['id']); $service->MoveNext(); } return true; } # Get the parent items in this invoice : $q = "SELECT * FROM ".AGILE_DB_PREFIX."invoice_item WHERE (parent_id = 0 OR parent_id IS NULL OR parent_id = '') AND invoice_id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $ii = $db->Execute($q); if ($ii === false) { global $C_debug; $C_debug->error(__FILE__,'approveInvoice', $db->ErrorMsg()); return false; } while(!$ii->EOF) { if(empty($ii->fields['service_id'])) { # Add the service $srvc->invoiceItemToService($ii->fields['id'], $invoice); # Check for any children items in this invoice: $q = "SELECT * FROM ".AGILE_DB_PREFIX."invoice_item WHERE parent_id = ".$db->qstr($ii->fields['id'])." AND invoice_id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $iii = $db->Execute($q); if ($iii === false) { global $C_debug; $C_debug->error(__FILE__,'approveInvoice', $db->ErrorMsg()); return false; } while(!$iii->EOF) { # Add the service $srvc->invoiceItemToService($ii->fields['id'], $invoice); $iii->MoveNext(); } } else { $srvc = new service; if($ii->fields['item_type'] == 2 && $ii->fields['domain_type'] == 'renew') { # this is a domain renewal $srvc->renewDomain($ii, $invoice->fields['account_billing_id']); } else { # this is an upgrade for an existing service $srvc->modifyService($ii, $invoice->fields['account_billing_id']); } } $ii->MoveNext(); } } elseif ($invoice->fields['type'] == 1) { # recurring invoice, just update assoc services # Loop through invoice items & approve assoc services $q = "SELECT service_id FROM ".AGILE_DB_PREFIX."invoice_item WHERE invoice_id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $service = $db->Execute($q); if ($service === false) { global $C_debug; $C_debug->error(__FILE__,'voidInvoice', $db->ErrorMsg()); return false; } if ($service->RecordCount() > 0) { # Include the service class include_once(PATH_MODULES.'service/service.inc.php'); $srvc = new service; # Update services to inactive status: while(!$service->EOF) { $srvc->approveService($service->fields['service_id']); $service->MoveNext(); } } } # get account id if(defined("SESS_ACCOUNT")) $account_id = SESS_ACCOUNT; else $account_id = 0; # if approved, create a memo $id = $db->GenID(AGILE_DB_PREFIX . 'invoice_memo_id'); $q = "INSERT INTO ".AGILE_DB_PREFIX."invoice_memo SET id = ".$db->qstr($id).", site_id = ".$db->qstr(DEFAULT_SITE).", date_orig = ".$db->qstr(time()).", invoice_id = ".$db->qstr($VAR['id']).", account_id = ".$db->qstr($account_id).", type = ".$db->qstr('approval').", memo = ".$db->qstr('NA'); $memo = $db->Execute($q); if ($memo === false) { global $C_debug; $C_debug->error(__FILE__,'approveInvoice', $db->ErrorMsg()); return false; } return true; } /** VOID INVOICE */ function voidInvoice($VAR) { # Update the invoice approval status: $db = &DB(); $q = "UPDATE ".AGILE_DB_PREFIX."invoice SET date_last = ".$db->qstr(time()).", process_status = ".$db->qstr('0')." WHERE id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $update = $db->Execute($q); if ($update === false) { global $C_debug; $C_debug->error(__FILE__,'voidInvoice', $db->ErrorMsg()); return false; } # Determine if services have already been created for this invoice and deactivate: $q = "SELECT id FROM ".AGILE_DB_PREFIX."service WHERE invoice_id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $service = $db->Execute($q); if ($service === false) { global $C_debug; $C_debug->error(__FILE__,'voidInvoice', $db->ErrorMsg()); return false; } if ($service->RecordCount() > 0) { # Include the service class include_once(PATH_MODULES.'service/service.inc.php'); $srvc = new service; # Update services to inactive status: while(!$service->EOF) { $srvc->voidService($service->fields['id']); $service->MoveNext(); } } # Loop through invoice items & delete assoc services $q = "SELECT service_id FROM ".AGILE_DB_PREFIX."invoice_item WHERE invoice_id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $service = $db->Execute($q); if ($service === false) { global $C_debug; $C_debug->error(__FILE__,'voidInvoice', $db->ErrorMsg()); return false; } if ($service->RecordCount() > 0) { # Include the service class include_once(PATH_MODULES.'service/service.inc.php'); $srvc = new service; # Update services to inactive status: while(!$service->EOF) { $srvc->voidService($service->fields['service_id']); $service->MoveNext(); } } # if voided, create a memo $id = $db->GenID(AGILE_DB_PREFIX . 'invoice_memo_id'); $q = "INSERT INTO ".AGILE_DB_PREFIX."invoice_memo SET id = ".$db->qstr($id).", site_id = ".$db->qstr(DEFAULT_SITE).", date_orig = ".$db->qstr(time()).", invoice_id = ".$db->qstr($VAR['id']).", account_id = ".$db->qstr(SESS_ACCOUNT).", type = ".$db->qstr('void').", memo = ".$db->qstr('NA'); $insert = $db->Execute($q); if ($insert === false) { global $C_debug; $C_debug->error(__FILE__,'voidInvoice', $db->ErrorMsg()); return false; } return true; } /** RECONCILE INVOICE */ function reconcile($VAR) { global $C_translate, $C_debug; # validate amt if($VAR['amount'] <= 0) { $C_debug->alert("Payment amount to low!"); return false; } # get the invoice details $db = &DB(); $sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'invoice WHERE id = ' . $db->qstr($VAR['id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); if ($rs === false) { global $C_debug; $C_debug->error(__FILE__,'reconcileInvoice', $db->ErrorMsg()); return false; } if(@$rs->RecordCount() == 0) return false; $amt = $VAR['amount']; $total_amt = $rs->fields['total_amt']; $billed_amt = $rs->fields['billed_amt']; $billed_currency_id = $rs->fields['billed_currency_id']; $actual_billed_amt = $rs->fields['actual_billed_amt']; $actual_billed_currency_id = $rs->fields['actual_billed_currency_id']; $due = $total_amt - $billed_amt; $overpaid = false; if($amt > $due) { $billed = 1; $update = $total_amt; $overpaid = $amt - $due; $C_translate->value['invoice']['amt'] = number_format($overpaid, 2); $alert = $C_translate->translate('rec_over','invoice',''); } elseif ($amt == $due) { $billed = 1; $update = $total_amt; } else { $billed = 0; $update = $amt + $billed_amt; } # Update the invoice record $sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET billed_amt = ' . $db->qstr($update) . ', billing_status = ' . $db->qstr($billed) . ' WHERE id = ' . $db->qstr($VAR['id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $db->Execute($sql); # Create a memo $id = $db->GenID(AGILE_DB_PREFIX . 'invoice_memo_id'); $q = "INSERT INTO ".AGILE_DB_PREFIX."invoice_memo SET id = ".$db->qstr($id).", site_id = ".$db->qstr(DEFAULT_SITE).", date_orig = ".$db->qstr(time()).", invoice_id = ".$db->qstr($VAR['id']).", account_id = ".$db->qstr(SESS_ACCOUNT).", type = ".$db->qstr('reconcile').", memo = ".$db->qstr('+ '.number_format($VAR['amount'],2) . " \r\n" . @$VAR['memo']); $db->Execute($q); # Reciept printing include_once PATH_MODULES.'invoice/receipt_print.php'; $receipt = new receipt_print; $receipt->add($rs, number_format($VAR['amount'],2), number_format($update,2)); # Auto update if billed complete if($billed) { $this->autoApproveInvoice($VAR['id']); # Get the default currency ISO $q = "SELECT ".AGILE_DB_PREFIX."invoice_memo SET id = ".$db->qstr($id).", site_id = ".$db->qstr(DEFAULT_SITE).", date_orig = ".$db->qstr(time()).", invoice_id = ".$db->qstr($VAR['id']).", account_id = ".$db->qstr(SESS_ACCOUNT).", type = ".$db->qstr('reconcile').", memo = ".$db->qstr('+ '.number_format($VAR['amount'],2) . " \r\n" . @$VAR['memo']); $currency = $db->Execute($q); # User invoice creation confirmation include_once(PATH_MODULES.'email_template/email_template.inc.php'); $email = new email_template; $email->send('invoice_paid_user', $rs->fields['account_id'], $VAR['id'], $rs->fields['billed_currency_id'], ''); # Admin alert of payment processed $email = new email_template; $email->send('admin->invoice_paid_admin', $rs->fields['account_id'], $VAR['id'], $rs->fields['billed_currency_id'], ''); } # Redirect if(!empty($VAR['redirect'])) { echo ' '; exit; } $msg = $C_translate->translate('ref_comp','invoice',''); $C_debug->alert($msg); return; } /** REFUND INVOICE */ function refund($VAR) { global $C_translate, $C_debug; # validate amt if($VAR['amount'] <= 0) { $C_debug->alert("Refund amount to low!"); return false; } # get the invoice details $db = &DB(); $sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'invoice WHERE id = ' . $db->qstr($VAR['id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $rs = $db->Execute($sql); if(@$rs->RecordCount() == 0) return false; $amt = $VAR['amount']; $total_amt = $rs->fields['total_amt']; $billed_amt = $rs->fields['billed_amt']; $billed_currency_id = $rs->fields['billed_currency_id']; $actual_billed_amt = $rs->fields['actual_billed_amt']; $actual_billed_currency_id = $rs->fields['actual_billed_currency_id']; $update = $billed_amt - $amt; if($update>0) $billing_status=1; else $billing_status=0; # Update the invoice record echo $sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET billed_amt = '.$db->qstr($update).', billing_status = '.$billing_status.', suspend_billing = 1, refund_status = 1 WHERE id = ' . $db->qstr($VAR['id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $update2 = $db->Execute($sql); if ($update2 === false) { global $C_debug; $C_debug->error(__FILE__,'refundInvoice', $db->ErrorMsg()); return false; } # Create a memo $id = $db->GenID(AGILE_DB_PREFIX . 'invoice_memo_id'); $q = "INSERT INTO ".AGILE_DB_PREFIX."invoice_memo SET id = ".$db->qstr($id).", site_id = ".$db->qstr(DEFAULT_SITE).", date_orig = ".$db->qstr(time()).", invoice_id = ".$db->qstr($VAR['id']).", account_id = ".$db->qstr(SESS_ACCOUNT).", type = ".$db->qstr('refund').", memo = ".$db->qstr('- '.number_format($VAR['amount'],2) . " \r\n" . @$VAR['memo']); $insert = $db->Execute($q); if ($insert === false) { global $C_debug; $C_debug->error(__FILE__,'refundInvoice', $db->ErrorMsg()); return false; } # Void: $this->voidInvoice($VAR, $this); # Call into the checkout plugin and attempt realtime refund $billing = $db->Execute($sql=sqlSelect($db, array('account_billing','checkout'), 'A.*,B.checkout_plugin', "A.id = ::{$rs->fields['account_billing_id']}:: AND A.checkout_plugin_id=B.id")); if($billing && $billing->RecordCount() && !empty($billing->fields['checkout_plugin'])) { $plugin_file = PATH_PLUGINS.'checkout/'. $billing->fields['checkout_plugin'] .'.php'; if(is_file($plugin_file)) { include_once ($plugin_file); eval('$PLG = new plg_chout_' . $billing->fields['checkout_plugin'] . '("'.$billing->fields['checkout_plugin_id'].'");'); if(is_callable(array($PLG,"refund"))) $PLG->refund($rs->fields, $billing->fields, $amt); } } # Redirect if(!empty($VAR['redirect'])) { echo ' '; return; } $msg = $C_translate->translate('ref_comp','invoice',''); $C_debug->alert($msg); return; } # Get translated/hardcoded line item description for PDF invoice # @uses CORE_list; private function getLineItemDesc($sku,$id,$domain=false,$item_name) { if (! empty($item_name)) return $item_name; global $C_translate; if (! empty($sku) && in_array($sku,array('DOMAIN-PARK','DOMAIN-TRANSFER','DOMAIN-REGISTER','DOMAIN-RENEW'))) { switch ($sku) { case 'DOMAIN-REGISTER': $name = $C_translate->translate('register','cart',''); break; case 'DOMAIN-TRANSFER': $name = $C_translate->translate('transfer','cart',''); break; case 'DOMAIN-PARK': $name = $C_translate->translate('park','cart',''); break; case 'DOMAIN-RENEW': $name = $C_translate->translate('renew','cart',''); break; } if ($domain) return sprinf("%s\r\n ( %s )",$domain,$name); else return $name; } else { include_once(PATH_CORE.'list.inc.php'); $C_list = new CORE_list; if (empty($this->product_desc[$id])) { $desc = $C_list->translate('product_translate','name','product_id',$id,'translate_product'); $this->product_desc[$id] = $desc['name']; } if (! empty($this->product_desc[$id])) return $this->product_desc[$id]; else return $sku ? $sku : 'Other Item'; } } // @todo: To be depreciated function delivery_task() { return $this->task_mail_invoices(); } /** * Task based function to e-mail or store printable PDF of all unprinted invoices * @todo This seems to be hard limited to do 100 invoices in a run - why? (make it configurable, or remove the limit?) */ public function task_mail_invoices() { # Get all unprinted invoices $db = &DB(); $rs = $db->SelectLimit(sqlSelect($db,array('invoice','account'), 'A.id,B.email,B.first_name,B.last_name,B.invoice_delivery,B.invoice_show_itemized', '(A.billing_status=0 OR A.billing_status IS NULL) AND (A.print_status=0 OR A.print_status=NULL) and A.account_id=B.id and (B.invoice_delivery IS NOT NULL AND B.invoice_delivery>0)'),100); if ($rs && $rs->RecordCount()) { # Send the e-mail.... require_once(PATH_INCLUDES.'phpmailer/class.phpmailer.php'); $mail = new PHPMailer(); $mail->From = SITE_EMAIL; $mail->FromName = SITE_NAME; /* $mail->SMTPAuth = true; $mail->Host = "smtp.domain.com"; $mail->Username = "user"; $mail->Password = "pass"; $mail->Mailer = "smtp"; $mail->Debug = true; */ while (! $rs->EOF) { if (! $pdf = $this->initInvoicePDF(array('id'=>$rs->fields['id']))) continue; $this->pdfInvoiceSummary($rs->fields['id'],$pdf); switch ($rs->fields['invoice_delivery']) { # Email Invoice case 1: $file = $pdf->Output(null,'S'); $mail->AddAddress($rs->fields['email'], sprintf('%s %s',$rs->fields['first_name'],$rs->fields['last_name'])); $mail->Subject = sprintf('%s %s: %s',SITE_NAME,_('Invoice'),$rs->fields['id']); $mail->Body = sprintf("Please find the printable version of invoice number %s attached.\r\n\r\nThank you,\r\n%s",$rs->fields['id'],SITE_NAME); $mail->AddStringAttachment($file,sprintf('%s.pdf',$this->getInvoiceID()),'base64','application/pdf'); if ($mail->Send()) $db->Execute(sqlUpdate($db,'invoice',array('print_status'=>1),array('id'=>$rs->fields['id']))); else printf('Unable to email invoice # %s to %s
',$rs->fields['id'],$rs->fields['email']); $mail->ClearAddresses(); $mail->ClearAttachments(); break; # Print Invoice case 2: $file = tempnam(PATH_FILES,sprintf('pdf_inv_%s.pdf',$this->getInvoiceID())); $pdf->Output($file,'F'); if (copy($file,sprintf('%sinvoice_%s.pdf',AGILE_PDF_INVOICE_PATH,$this->getInvoiceID()))) $db->Execute(sqlUpdate($db,'invoice',array('print_status'=>1),array('id'=>$rs->fields['id']))); unlink($file); break; default: printf('Unknown invoice_delivery: %s for %s
',$rs->fields['invoice_delivery'],$rs->fields['id']); } $rs->MoveNext(); } } } /** * Initialise an invoice * * This function is responsible for getting all the information required to render an invoice. */ private function initInvoicePrint($VAR) { # Check invoice if (! isset($VAR['id'])) { echo 'No Invoice Specified.'; return false; } # Check admin authentication: global $C_auth; $db = &DB(); if ($C_auth->auth_method_by_name('invoice','view') == false) { # Validate on account level $rs = $db->Execute(sqlSelect($db,'invoice','account_id',array('id'=>$VAR['id']))); # @todo redirect to login page if not logged if ($rs->fields['account_id'] != SESS_ACCOUNT) return false; } $invoice = array(); #@todo this should be in setup_invoice $invoice['site']['TAXID'] = SITE_TAXID; $invoice['site']['NAME'] = SITE_NAME; $invoice['site']['ADDRESS'] = SITE_ADDRESS; $invoice['site']['CITY'] = SITE_CITY; $invoice['site']['STATE'] = SITE_STATE; $invoice['site']['ZIP'] = SITE_ZIP; $invoice['site']['FAX'] = SITE_FAX; $invoice['site']['PHONE'] = SITE_PHONE; $invoice['site']['EMAIL'] = SITE_EMAIL; $invoice['site']['URL'] = URL; # Invoice Configuration $rs = $db->Execute(sqlSelect($db,'setup_invoice','*','')); $invoice['invcfg'] = $rs->fields; # Invoice details $rs = $db->Execute($a=sqlSelect($db,array('invoice','currency'),'A.*,B.symbol',sprintf('A.id=%s AND B.id=A.billed_currency_id',$VAR['id']))); $invoice['invoice'] = $rs->fields; # Account detail: $rs = $db->Execute($q = sqlSelect($db,'account','*',array('id'=>$invoice['invoice']['account_id']))); $invoice['account'] = $rs->fields; # Invoice Item details $invoice['invoiceitems'] = array(); $rs = $db->Execute($b=sqlSelect($db,'invoice_item','*',array('invoice_id'=>$VAR['id']))); while (! $rs->EOF) { array_push($invoice['invoiceitems'],$rs->fields); $rs->moveNext(); } # Get previous invoices $invoice['previousinvoices'] = array(); $rs = $db->Execute($q=sqlSelect($db,'invoice','*',sprintf('account_id=%s AND (billed_amt < total_amt OR billed_amt IS NULL) AND date_orig < %s', $invoice['invoice']['account_id'],$invoice['invoice']['date_orig']))); while (! $rs->EOF) { array_push($invoice['previousinvoices'],$rs->fields); $rs->moveNext(); } # If we get here, all is OK. return $invoice; } /** * Initialise a PDF invoice * * This function is resonsible for setting up a PDF invoice */ private function initInvoicePDF($VAR) { # Get our invoice details if (! $this->print = $this->initInvoicePrint($VAR)) return false; #@todo since the view template dynamic finds available plugins, this should also find the plugin (incase the prefix/dir is moved). require_once(sprintf('%sinvoice/PDF/pdf_invoice_%s.inc.php',PATH_MODULES,$this->print['invcfg']['invoice_pdf_plugin'])); $pdf = new pdf_invoice_overview($this); #@todo This should be deprecated $null = false; $pdf->load_setup($null); if ($pdf->getTemplate()) { $pagecount = $pdf->setSourceFile($pdf->getTemplate()); $tplidx = $pdf->ImportPage(1); } $pdf->addPage(); # If we are using FPDI if (isset($tplidx)) $pdf->useTemplate($tplidx); # If we get here, all is OK. return $pdf; } /** * Display a PDF invoice in the browser for download. */ public function pdf($VAR) { if (! $pdf = $this->initInvoicePDF($VAR)) return false; $this->pdfInvoiceSummary($VAR['id'],$pdf); $pdf->Output(sprintf('%s.pdf',$this->getInvoiceID()),'I'); } /** Export multiple invoices */ function pdfExport(&$rs) { $db =& DB(); $invcfg = $db->Execute(sqlSelect($db,"setup_invoice","*","")); ob_start(); $pdf = new pdf_invoice_overview(); $pdf->companyName = SITE_NAME; $pdf->companyAddress = SITE_ADDRESS; $pdf->companyCity = SITE_CITY; $pdf->companyState = SITE_STATE; $pdf->companyZip = SITE_ZIP; $pdf->load_setup($invcfg); if ($pdf->getTemplate()) $pagecount = $pdf->setSourceFile($pdf->getTemplate()); $tplidx = $pdf->ImportPage(1); while(!$rs->EOF) { $pdf->addPage(); $pdf->useTemplate($tplidx); $this->pdfInvoiceSummary($rs->fields['id'], $pdf); $rs->MoveNext(); unset($pdf->itemsSummary); } $pdf->Output(); ob_end_flush(); } /** * Render an invoice with the summary page * @todo Draw discounts * @todo Draw tax details */ private function pdfInvoiceSummary($id,$pdf) { # Invoice details: $db = &DB(); # Draw Invoice Basics $pdf->drawCompanyLogo(); $pdf->drawCompanyAddress($this); $pdf->drawInvoiceHeader($this); $pdf->drawNews($this->print['invcfg']['news']); $pdf->drawRemittenceStub($this); $pdf->drawPaymentMethods($this); if ($this->print['invoice']['billing_status'] !=1 && $this->print['invoice']['suspend_billing'] != 1 && $this->print['invoice']['due_date'] <= time()) $pdf->drawInvoiceDueNotice(); elseif($this->print['invoice']['billing_status'] == 1) $pdf->drawInvoicePaidNotice(); if ($this->getPreviousBalance()) $pdf->drawSummaryInvoicesDue($this->print['previousinvoices']); $pdf->drawSummaryLineItems($this); unset($pdf->itemsSummary); # BEGIN loop for enumerating information in multiple ways on the invoice $iteration = 0; while ($pdf->drawLineItems_pre($iteration)) { # Get the line items: $items = $db->Execute(sqlSelect($db,'invoice_item','*',array('invoice_id'=>$this->getInvoiceNum()))); if ($items && $items->RecordCount()) { while (! $items->EOF) { # Get the date range if set if (! empty($items->fields['date_start']) && ! empty($items->fields['date_stop'])) { global $C_translate; $C_translate->value('invoice','start',date(UNIX_DATE_FORMAT,$items->fields['date_start'])); $C_translate->value('invoice','stop',date(UNIX_DATE_FORMAT,$items->fields['date_stop'])); } $cost = $items->fields['price_base']; $total = $cost * $items->fields['quantity']; $desc = $this->getLineItemDesc($items->fields['sku'],$items->fields['product_id'],strtolower($items->fields['domain_name'].'.'.$items->fields['domain_tld']),$items->fields['product_name']); $line = array( 'name' => $desc, 'domain' => $items->fields['domain_name'], 'amount' => $cost, 'sku'=>$items->fields['sku'], 'qty'=>$items->fields['quantity'], 'cost'=>$cost, 'attr'=>$items->fields['product_attr'], 'price_type'=>$items->fields['price_type'], 'price_base'=>$items->fields['price_base'], 'item_type'=>$items->fields['item_type'], 'daterange'=>sprintf('%s - %s',date(UNIX_DATE_FORMAT,$items->fields['date_start']),date(UNIX_DATE_FORMAT,$items->fields['date_stop'])), 'total_amt'=>$items->fields['total_amt'] ); $pdf->drawLineItems($db,$line,$this->getInvoiceNum()); if ($items->fields['price_setup']) { $desc .= ' Set-up Charge'; $total = $items->fields['price_setup']; $line = array('name'=>$desc,'amount'=>$total,'qty'=>'1','sku'=>$items->fields['sku'],'cost'=>$total,'price_base'=>$total,'price_type'=>999); $pdf->drawLineItems($db,$line,$this->getInvoiceNum()); } $items->MoveNext(); } } if ($this->print['invoice']['discount_amt']) { $desc = 'Discount'; $total = -($this->print['invoice']['discount_amt']); $line = array('name'=>$desc,'amount'=>$total,'qty'=>'1','cost'=>$total,'price_base'=>$total,'price_type'=>999); $pdf->drawLineItems($db,$line,$this->getInvoiceNum()); } if ($this->print['invoice']['tax_amt']) { $trs = $db->Execute(sqlSelect($db,array('invoice_item_tax','tax'),'A.amount,B.description',sprintf('A.tax_id=B.id AND A.invoice_id=%s',$this->getInvoiceNum()))); if ($trs && $trs->RecordCount()) { $taxes = array(); while (! $trs->EOF) { if (! isset($taxes[$trs->fields['description']])) $taxes[$trs->fields['description']] = $trs->fields['amount']; else $taxes[$trs->fields['description']] += $trs->fields['amount']; $trs->MoveNext(); } foreach ($taxes as $txds=>$txamt) { $line = array('name'=>$txds,'amount'=>$txamt,'total_amt'=>$txamt,'price_type'=>999); $pdf->drawLineItems($db,$line,$this->getInvoiceNum()); } } } # Increment the iteration ++$iteration; } # Custom functions: $pdf->drawCustom(); unset($db); } /** RESEND DUE NOTICE */ function resend($VAR) { global $C_debug; # User invoice creation confirmation include_once(PATH_MODULES.'email_template/email_template.inc.php'); $mail = new email_template; $mail->send('invoice_resend', $VAR['account_id'], $VAR['id'], '', ''); # Alert $C_debug->alert('Sent payment due notice to user'); # Update invoice $db = &DB(); $q = "SELECT notice_count FROM ".AGILE_DB_PREFIX."invoice WHERE id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $rs = $db->Execute($q); $count = $rs->fields['notice_count'] + 1; $q = "UPDATE ".AGILE_DB_PREFIX."invoice SET notice_count = ".$db->qstr($count)." WHERE id = ".$db->qstr($VAR['id'])." AND site_id = ".$db->qstr(DEFAULT_SITE); $rs = $db->Execute($q); } /** * Generate all invoices for recurring services/charges/domains */ public function generate() { # Check if charge module installed global $C_list; $isChargeInstalled = $C_list->is_installed('charge'); // get services to be billed grouped by account and date if (! defined('MAX_INV_GEN_PERIOD') || MAX_INV_GEN_PERIOD <= 0) $max_date = time()+86400; else $max_date = time()+(MAX_INV_GEN_PERIOD*86400); #$p=AGILE_DB_PREFIX; $s=DEFAULT_SITE; /* $sql = "SELECT DISTINCT service.id as serviceId, account.id as accountId, invoice.id as invoiceId, from_unixtime(service.date_next_invoice,'%Y-%m-%d') as dayGroup FROM {$p}service as service JOIN {$p}account as account ON ( service.account_id=account.id and account.site_id={$s} ) LEFT JOIN {$p}invoice as invoice ON ( service.invoice_id=invoice.id and invoice.site_id={$s} ) WHERE service.site_id={$s} AND service.active = 1 AND ( service.suspend_billing IS NULL OR service.suspend_billing = 0 ) AND ( service.date_next_invoice > 0 AND service.date_next_invoice IS NOT NULL ) AND (( (account.invoice_advance_gen!='' OR account.invoice_advance_gen is not null) AND service.date_next_invoice <= (UNIX_TIMESTAMP(CURDATE())+(account.invoice_advance_gen*86400)) ) OR ( (account.invoice_advance_gen='' OR account.invoice_advance_gen is null) AND service.date_next_invoice <= {$max_date} )) ORDER BY accountId, dayGroup, serviceId"; */ $db=&DB(); $rs = $db->Execute($sql = $this->sql_invoice_soon()); if (! $rs) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); } elseif($rs->RecordCount()) { $ids = ''; $account = ''; $date = ''; $invoice = ''; while (! $rs->EOF) { if ($ids && (($rs->fields['aid'] != $account) || ($rs->fields['invoice_date'] != $date))) { $this->generateInvoices($ids,$account,$invoice,$isChargeInstalled); $ids = ''; } # Set the current account and date $account = $rs->fields['aid']; $invoice = $rs->fields['iid']; $date = $rs->fields['invoice_date']; # Add to id list if ($ids) $ids .= ','.$rs->fields['sid']; else $ids = $rs->fields['sid']; $rs->MoveNext(); } if ($ids) $this->generateInvoices($ids,$account,$invoice,$isChargeInstalled); } // Generate invoices for any domains expiring in X days. if ($C_list->is_installed('host_tld')) $this->generateDomains(); return true; } public function generateinvoice_account($VAR) { # Check if charge module installed global $C_list; $charge_installed = $C_list->is_installed('charge'); $db = &DB(); $rs = $db->Execute($q = $this->sql_invoice_soon(null,0,$VAR['account_id'])); if (! $rs) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); return; } # Set the invoice and date $invoice = $rs->fields['iid']; $date = $rs->fields['invoice_date']; $ids = ''; while (! $rs->EOF) { if ($ids && ($rs->fields['invoice_date'] != $date)) { $this->generateInvoices($ids,$VAR['account_id'],$invoice,$charge_installed); $ids = ''; } # Add to id list if ($ids) $ids .= ','; $ids .= $rs->fields['sid']; $rs->MoveNext(); } if ($ids) $this->generateInvoices($ids,$VAR['account_id'],$invoice,$charge_installed); if (isset($VAR['_page_next'])) define('REDIRECT_PAGE','?_page='.$VAR['_page_next']); } /** * Generate an Invoice */ private function generateInvoices($ids,$account_id,$invoice_id,$charge_installed=false) { if (empty($ids)) return false; # Load required elements include_once(PATH_MODULES.'service/service.inc.php'); $serviceObj = new service; include_once(PATH_MODULES.'discount/discount.inc.php'); $discountObj = new discount; include_once(PATH_MODULES.'tax/tax.inc.php'); $taxObj = new tax; # Start a transaction $db = &DB(); if (AGILE_DB_TYPE == 'mysqlt') { $db->StartTrans(); if (! $db->hasTransactions) { global $C_debug; $msg = "Transactions not supported in 'mysql' driver. Use 'mysqlt' or 'mysqli' driver"; $C_debug->alert($msg); $C_debug->error(__FILE__,__METHOD__,$msg); return false; } } # Generate an invoice id $invoice = sqlGenID($db,'invoice'); # Check for any discounts for the parent invoice or account_id # applied at checkout and should continue to be applied if recurring type discount $discountObj->available_discounts($account_id,1,$invoice_id); # Beginning totals $sub_total = 0; $total = 0; $taxable_amount = 0; $tax_amt = 0; $discount_amt = 0; # Get the full account and service and invoice details $p=AGILE_DB_PREFIX; $s=DEFAULT_SITE; $sql = "SELECT DISTINCT service.id, service.parent_id, service.invoice_id, service.invoice_item_id, service.account_id, service.account_billing_id, service.product_id, service.sku, service.active, service.bind, service.type, service.price, service.price_type, service.taxable, service.date_last_invoice, service.date_next_invoice, service.recur_type, service.recur_schedule, service.recur_weekday, service.recur_week, service.domain_name, service.domain_tld, service.domain_type, service.domain_term, service.prod_attr, service.prod_attr_cart, account.currency_id, account.first_name, account.last_name, account.country_id, account.state, account.invoice_grace, account.invoice_advance_gen, account.affiliate_id as account_affiliate_id, invoice.affiliate_id, invoice.campaign_id, invoice.reseller_id, invoice.checkout_plugin_id, invoice.checkout_plugin_data, invoice.billed_currency_id, invoice.actual_billed_currency_id FROM {$p}service as service JOIN {$p}account as account ON (service.account_id=account.id AND account.site_id={$s}) LEFT JOIN {$p}invoice as invoice ON (service.invoice_id=invoice.id AND invoice.site_id={$s}) WHERE service.id in ({$ids})"; $service = $db->Execute($sql); if ($service === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); $db->FailTrans(); return false; } if ($service && $service->RecordCount()) { while (! $service->EOF) { if (empty($service->fields['billed_currency_id'])) $service->fields['billed_currency_id'] = DEFAULT_CURRENCY; if (empty($service->fields['actual_billed_currency_id'])) $service->fields['actual_billed_currency_id'] = $service->fields['billed_currency_id']; $this->account_id = $service->fields['account_id']; $this->parent_id = $service->fields['invoice_id']; $this->account_billing_id = $service->fields['account_billing_id']; if (! empty($service->fields['account_affiliate_id'])) $this->affiliate_id = $service->fields['account_affiliate_id']; else $this->affiliate_id = $service->fields['affiliate_id']; $this->campaign_id = $service->fields['campaign_id']; $this->reseller_id = $service->fields['reseller_id']; $this->checkout_plugin_id = $service->fields['checkout_plugin_id']; $this->checkout_plugin_data = $service->fields['checkout_plugin_data']; $this->billed_currency_id = $service->fields['billed_currency_id']; $this->actual_billed_currency_id = $service->fields['actual_billed_currency_id']; $this->invoice_grace = $service->fields['invoice_grace']; $item_tax_amt = 0; $item_total_amt = 0; $item_discount_amt = 0; # Gen item_id $item = sqlGenID($db,'invoice_item'); # Calculate any recurring discounts for this item $item_total_amt = $service->fields['price']; $item_discount_amt = $discountObj->calc_all_discounts(1,$item,$service->fields['product_id'],$service->fields['price'],$service->fields['account_id'],$sub_total+$service->fields['price']); $item_total_amt -= $item_discount_amt; $sub_total += $item_total_amt; $discount_amt += $item_discount_amt; # Calculate any taxes for this item if ($service->fields['taxable'] == 1) { $item_tax_amt = 0; $item_tax_arr = $taxObj->calculate($item_total_amt,$service->fields['country_id'],$service->fields['state']); if (is_array($item_tax_arr)) foreach($item_tax_arr as $tx) $item_tax_amt += $tx['rate']; $tax_amt += $item_tax_amt; } # Calculate next invoice date $next_invoice = $serviceObj->calcNextInvoiceDate($service->fields['date_next_invoice'],$service->fields['recur_schedule'],$service->fields['recur_type'],$service->fields['recur_weekday']); $due_date = $service->fields['date_next_invoice']; $recur_schedule = 0; if (! empty($service->fields['recur_schedule'])) $recur_schedule = $service->fields['recur_schedule']; # Create the invoice item $itemrs = $db->Execute(sqlInsert($db,'invoice_item',array( 'date_orig'=>time(), 'invoice_id'=>$invoice, 'account_id'=>$service->fields['account_id'], 'service_id'=>$service->fields['id'], 'product_id'=>$service->fields['product_id'], 'product_attr'=>$service->fields['prod_attr'], 'product_attr_cart'=>$service->fields['prod_attr_cart'], 'sku'=>$service->fields['sku'], 'quantity'=>1, 'item_type'=>0, 'price_type'=>$service->fields['price_type'], 'price_base'=>$service->fields['price'], 'price_setup'=>0, 'recurring_schedule'=>$recur_schedule, 'date_start'=>$service->fields['date_next_invoice'], 'date_stop'=>$next_invoice, 'domain_name'=>$service->fields['domain_name'], 'domain_tld'=>$service->fields['domain_tld'], 'domain_type'=>$service->fields['domain_type'], 'tax_amt'=>$tax_amt, 'total_amt'=>$item_total_amt, 'discount_amt'=>$item_discount_amt ),$item)); if ($itemrs === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); $db->FailTrans(); return false; } # Insert tax records $taxObj->invoice_item($invoice,$item,$service->fields['account_id'],@$item_tax_arr); # Insert discount records $discountObj->invoice_item($invoice,$item,$service->fields['account_id'],@$discountObj->discount_arr); # Update the last & next invoice date for this service $srvsrs = $db->Execute(sqlUpdate($db,'service',array('date_last_invoice'=>$service->fields['date_next_invoice'],'date_next_invoice'=>$next_invoice),array('id'=>$service->fields['id']))); if ($srvsrs === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); $db->FailTrans(); return false; } # Get any charges for this service and create them as invoice items if ($charge_installed) { $sql = "SELECT * FROM ".AGILE_DB_PREFIX."charge WHERE (status=0 or status is null) and site_id=".DEFAULT_SITE." AND service_id = ".$service->fields['id']." AND date_orig < ". $service->fields['date_next_invoice']; $charge = $db->Execute($sql); if($charge && $charge->RecordCount()) { while(!$charge->EOF) { $item_tax_amt=0; $item_total_amt=0; $item_discount_amt=0; // Calculate any recurring discounts for this charge item $item_total_amt = ($charge->fields['quantity']*$charge->fields['amount']); $item_discount_amt = $discountObj->calc_all_discounts(1, $item, $charge->fields['product_id'], $item_total_amt, $service->fields['account_id'], $sub_total+$item_total_amt); $item_total_amt -= $item_discount_amt; $sub_total += $item_total_amt; $discount_amt += $item_discount_amt; // calculate any taxes for this item if($charge->fields['taxable'] == 1) { $item_tax_amt=0; $item_tax_arr = $taxObj->calculate($chargeamt, $service->fields['country_id'], $service->fields['state']); if(is_array($item_tax_arr)) foreach($item_tax_arr as $tx) $item_tax_amt += $tx['rate']; $tax_amt += $item_tax_amt; } // create the invoice item $charge_item_id = sqlGenID($db, 'invoice_item'); $sql = "INSERT INTO {$p}invoice_item SET id = $charge_item_id, site_id = $s, charge_id = {$charge->fields['id']}, date_orig = ".time().", invoice_id = $invoice, account_id = ".$this->account_id.", service_id = ".$db->qstr($service->fields['id']).", product_id = ".$db->qstr($charge->fields['product_id']).", product_attr= ".$db->qstr($charge->fields['attributes']).", sku = ".$db->qstr($service->fields['sku']).", price_base = ".$db->qstr($charge->fields['amount']).", quantity = ".$charge->fields['quantity'].", item_type = 5, price_type = 0, price_setup = 0, tax_amt = $item_tax_amt, total_amt = $item_total_amt, discount_amt= $item_discount_amt"; $itemrs=$db->Execute($sql); if($itemrs === false) {global $C_debug; $C_debug->error('invoice.inc.php','generateInvoices()4', $sql . " \r\n\r\n " . @$db->ErrorMsg()); $db->FailTrans(); return false; } # Insert tax records $taxObj->invoice_item($invoice, $charge_item_id, $charge->fields['account_id'], @$item_tax_arr); # Insert discount records $discountObj->invoice_item($invoice, $charge_item_id, $charge->fields['account_id'], @$discountObj->discount_arr); # update charge status $chargers=$db->Execute("UPDATE ".AGILE_DB_PREFIX."charge set status=1 WHERE id={$charge->fields['id']} AND site_id=".DEFAULT_SITE); if($chargers === false) {global $C_debug; $C_debug->error('invoice.inc.php','generateInvoices()2', $sql . " \r\n\r\n " . @$db->ErrorMsg()); $db->FailTrans(); return false; } $charge->MoveNext(); } } } $service->MoveNext(); } # Add any taxes $total = $sub_total+$tax_amt; # Get invoice grace period from global/account if (! empty($this->invoice_grace)) $grace_period = $this->invoice_grace; else $grace_period = GRACE_PERIOD; $invoicers = $db->Execute($a=sqlInsert($db,'invoice',array( 'date_orig'=>time(), 'date_last'=>time(), 'notice_next_date'=>time(), 'type'=>1, 'process_status'=>0, 'billing_status'=>0, 'suspend_billing'=>0, 'print_status'=>0, 'refund_status'=>0, 'billed_amt'=>0, 'actual_billed_amt'=>0, 'notice_count'=>0, 'parent_id'=>$this->parent_id, 'account_id'=>$this->account_id, 'account_billing_id'=>$this->account_billing_id, 'affiliate_id'=>$this->affiliate_id, 'campaign_id'=>$this->campaign_id, 'reseller_id'=>$this->reseller_id, 'checkout_plugin_id'=>$this->checkout_plugin_id, 'checkout_plugin_data'=>$this->checkout_plugin_data, 'actual_billed_currency_id'=>$this->actual_billed_currency_id, 'billed_currency_id'=>$this->billed_currency_id, 'notice_max'=>MAX_BILLING_NOTICE, 'grace_period'=>$grace_period, 'tax_amt'=>$tax_amt, 'discount_amt'=>$discount_amt, 'total_amt'=>$total, 'due_date'=>$due_date ),$invoice)); if ($invoicers === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); $db->FailTrans(); return false; } } if (AGILE_DB_TYPE == 'mysqlt') $db->CompleteTrans(); } /** Invoice expiring domains */ function generateDomains() { $db = &DB(); define('DEFAULT_DOMAIN_INVOICE', 30); //how far out to generate expiring domain invoices $expire = time() + (DEFAULT_DOMAIN_INVOICE*86400); ### Get domains expiring soon: $rs = $db->Execute( sqlSelect( $db, 'service', '*', " active=1 AND domain_date_expire <= $expire AND type = 'domain' AND queue = 'none' AND ( domain_type = 'register' OR domain_type = 'transfer' OR domain_type = 'renew' ) AND ( suspend_billing = 0 OR suspend_billing IS NULL) " ) ); if($rs && $rs->RecordCount() > 0 ) { while(!$rs->EOF) { # Check that this domain has not already been invoiced $invoiced = $db->Execute(sqlSelect ($db, array('invoice_item','invoice'), array('A.*','B.*'), " A.invoice_id = B.id AND A.service_id = {$rs->fields['id']} AND A.sku = 'DOMAIN-RENEW' AND domain_type = 'renew' AND date_start = {$rs->fields['date_last_invoice']} AND date_stop = {$rs->fields['domain_date_expire']}" ) ); if($invoiced && $invoiced->RecordCount() == 0) { # Not previously invoiced, generate now! $this->generatedomaininvoice( $rs->fields, $this ); } $rs->MoveNext(); } } } /** Invoice expiring domains p2 */ function generatedomaininvoice($VAR) { include_once(PATH_MODULES . 'tax/tax.inc.php'); $taxObj = new tax; $db = &DB(); if( is_array( $VAR ) ) { $expire = time(); $rs = $db->Execute(sqlSelect($db, 'service', '*', " id = ::{$VAR['id']}:: AND active=1 AND type = 'domain' AND queue = 'none' AND (domain_type = 'register' OR domain_type = 'transfer' OR domain_type = 'renew') AND (suspend_billing = 0 OR suspend_billing IS NULL) ")); $service = $rs->fields; } else { $service = $VAR; } if(empty($service['id'])) { global $C_debug; $C_debug->alert("Unable to generate domain renweal invoice due to domain status."); return false; } # Get the parent invoice details: if(!empty($service['invoice_id'])) { $rs = $db->Execute(sqlSelect($db, 'invoice', '*', " id = {$service['invoice_id']} ", "")); $invoice = $rs->fields; } else { $invoice = false; } # Get the account details: $rs = $db->Execute(sqlSelect($db, 'account', '*', " id = {$service['account_id']} ", "")); $account = $rs->fields; # Get the account price include_once(PATH_MODULES.'host_tld/host_tld.inc.php'); $tldObj=new host_tld; $tld_arr = $tldObj->price_tld_arr($service['domain_tld'], 'renew', false, false, false, $service['account_id']); foreach($tld_arr as $term => $price) break; # Calculate taxes: $rs = $db->Execute($sql=sqlSelect($db,"host_tld","taxable","name = ::{$service['domain_tld']}::")); if( $service['taxable'] || @$rs->fields['taxable'] ) { $tax_arr = $taxObj->calculate($price, $account["country_id"], $account["state"]); } else { $tax_arr = false; } $total = $price; $tax_amt = 0; if(is_array($tax_arr)) { foreach($tax_arr as $tx) { $tax_amt += $tx['rate']; } $total += $tax_amt; } # calculate the dates $expire = $service['domain_date_expire'] + ($term*86400); $due_date = $service['domain_date_expire'] - (86400*3); # Create the invoice $id = sqlGenID($db, "invoice"); $insert = $db->Execute($sql = sqlInsert($db, "invoice", array( 'date_orig' => time(), 'date_last' => time(), 'type' => 2, 'process_status' => 0, 'billing_status' => 0, 'suspend_billing' => 0, 'print_status' => 0, 'parent_id' => $service['invoice_id'], 'account_id' => $service['account_id'], 'account_billing_id'=> $service['account_billing_id'], 'affiliate_id' => @$invoice['affiliate_id'], 'campaign_id' => @$invoice['campaign_id'], 'reseller_id' => @$invoice['reseller_id'], 'checkout_plugin_id'=> @$invoice['checkout_plugin_id'], 'tax_amt' => $tax_amt, 'discount_arr' => serialize(@$discount_arr), 'discount_amt' => @$discount_amt, 'total_amt' => $total, 'billed_amt' => 0, 'billed_currency_id'=> DEFAULT_CURRENCY, 'actual_billed_amt' => 0, 'actual_billed_currency_id' => @$invoice['actual_billed_currency_id'], 'notice_count' => 0, 'notice_next_date' => time(), 'notice_max' => MAX_BILLING_NOTICE, 'grace_period' => 0, 'due_date' => $due_date ), $id)) ; # create the invoice item: if($insert) { $db->Execute ($idx = sqlInsert($db, "invoice_item", array( 'date_orig' => time(), 'invoice_id' => $id, 'account_id' => $service['account_id'], 'service_id' => $service['id'], 'sku' => 'DOMAIN-RENEW', 'quantity' => 1, 'item_type' => 2, 'price_type' => 0, 'price_base' => $price, 'price_setup' => 0, 'domain_type' => 'renew', 'date_start' => $service['domain_date_expire'], 'date_stop' => $expire, 'domain_name' => $service['domain_name'], 'domain_tld' => $service['domain_tld'], 'domain_term' => $term, 'tax_amt' => $tax_amt, 'total_amt' => $price ))); # Insert tax records $taxObj->invoice_item($id, $idx, $service['account_id'], @$item_tax_arr); # Update the service record $fields=array('active' => 0); $db->Execute(sqlUpdate($db,"service",$fields,"id = {$service['id']}")); global $C_debug; $C_debug->alert("Generated domain renewal invoice for {$service['domain_name']}.{$service['domain_tld']}"); return $id; } } /** Run AutoBilling and Due Notices */ function autobill($VAR) { global $VAR, $C_debug, $C_list; # User invoice creation confirmation include_once(PATH_MODULES.'email_template/email_template.inc.php'); $mail = new email_template; # get all due invoices $db = &DB(); #$db->debug = true; if(empty($VAR['invoice_id'])) { $this->bill_one = false; $sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'invoice WHERE notice_next_date <= ' . $db->qstr( time() ) . ' AND ( billing_status = 0 OR billing_status IS NULL ) AND ( suspend_billing = 0 OR suspend_billing IS NULL ) AND site_id = ' . $db->qstr(DEFAULT_SITE); $invoice = $db->Execute($sql); if($invoice->RecordCount() == 0) { $C_debug->alert('No Invoices to Autobill'); return false; } } else { # get the specified invoice: $this->bill_one = true; $sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'invoice WHERE ( billing_status = 0 OR billing_status IS NULL ) AND id = ' . $db->qstr($VAR['invoice_id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $invoice = $db->Execute($sql); } # Check for results if($invoice->RecordCount() == 0) { $C_debug->alert('Invoice could not be billed!'); return false; } # Loop through results while(!$invoice->EOF) { $db->StartTrans(); $due = true; # get currency code $cyid = $invoice->fields['actual_billed_currency_id']; $billed_currency_id = $invoice->fields['billed_currency_id']; if(empty($this->currency_iso[$cyid])) { $q = "SELECT three_digit,convert_array FROM ". AGILE_DB_PREFIX ."currency WHERE id = ". $db->qstr($cyid)." AND site_id = ". $db->qstr(DEFAULT_SITE); $currb = $db->Execute($q); $this->format_currency[$cyid] = array('convert' => unserialize($currb->fields["convert_array"]), 'iso' => $currb->fields["three_digit"]); } # get the currency codes (default) if(empty($this->format_currency[$billed_currency_id])) { # Get the billed currency id currency info: $q = "SELECT three_digit,convert_array FROM ".AGILE_DB_PREFIX."currency WHERE id = ".$db->qstr($billed_currency_id)." AND site_id = ".$db->qstr(DEFAULT_SITE); $currb = $db->Execute($q); $this->format_currency[$billed_currency_id] = array('convert' => unserialize($currb->fields["convert_array"]), 'iso' => $currb->fields["three_digit"]); } # attempt to autobill? if(!empty($invoice->fields['account_billing_id'])) { # get checkout plugin details: $billing =& $db->Execute($sql=sqlSelect($db, array('account_billing','checkout'), 'A.*,B.checkout_plugin', "A.id = ::{$invoice->fields['account_billing_id']}:: AND A.checkout_plugin_id=B.id")); if($billing && $billing->RecordCount() == 1 && !empty($billing->fields['checkout_plugin'])) { $plugin_file = PATH_PLUGINS.'checkout/'. $billing->fields['checkout_plugin'] .'.php'; if(!is_file($plugin_file)) { $err = $plugin_file .' missing when autobilling invoice id ' . $invoice->fields['id']; $C_debug->error(__FILE__,__METHOD__,$err); } else { include_once ($plugin_file); eval('$PLG = new plg_chout_' . $billing->fields['checkout_plugin'] . '("'.$billing->fields['checkout_plugin_id'].'");'); } } else { $err = 'account_billing.id '.$invoice->fields['account_billing_id'].' empty or not associated with a checkout plugin when autobilling invoice id ' . $invoice->fields['id']; $C_debug->error(__FILE__,__METHOD__,$err); } } # get the actual billed amount $amount = $invoice->fields['total_amt'] - $invoice->fields['billed_amt']; $billed_amt = $invoice->fields['total_amt']; $actual_billed_amt = $invoice->fields['total_amt']; if($amount <= 0) $due = false; if(!empty($PLG) && is_object($PLG) && $PLG->type == 'gateway' && $amount > 0) { # attempt autobilling if account billing exists and gateway plugin if($invoice->fields['account_billing_id'] > 0) { /* get the account details */ $account = $db->Execute(sqlSelect($db,"account","id,email","id=::{$invoice->fields['account_id']}")); /* Convert the invoice amount to the actual billed currency amount */ if($cyid != $invoice->fields['billed_currency_id']) { $conversion = $this->format_currency[$billed_currency_id]["convert"][$cyid]["rate"]; $amount *= $conversion; $actual_billed_amt = $invoice->fields['actual_billed_amt'] + $amount; } /* load the billing details from the database */ $PLG->setBillingFromDBObj($billing, true); /* attempt to auto-bill */ if(!$checkout_plugin_data = $PLG->bill_checkout( number_format($amount,2), $invoice->fields['id'], $this->format_currency[$cyid]['iso'], $account->fields, 0,0) ) { $due = true; $email = new email_template; $email->send('invoice_decline_user', $invoice->fields['account_id'], $invoice->fields['id'],$C_list->format_currency($invoice->fields['total_amt'],$cyid), $C_list->date($invoice->fields['due_date'])); $email = new email_template; $email->send('admin->invoice_decline_admin', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],''), $C_list->date($invoice->fields['due_date'])); } else { $due = false; } } } # send proper alert & manage services if ($due) { # determine if overdue $due = $invoice->fields['due_date']; $grace = $invoice->fields['grace_period']; if(time() < $due+(86400*$grace)) { if($invoice->fields['notice_count'] <= 0) { # send out first alert - new invoice created! $email = new email_template; $email->send('invoice_recur_user', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],$cyid), $C_list->date($invoice->fields['due_date'])); $email = new email_template; $email->send('admin->invoice_recur_admin', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],''), $C_list->date($invoice->fields['due_date'])); } else { # send out payment due notice if(empty($PLG) || $PLG->type == 'gateway') { $email = new email_template; $email->send('invoice_due_user', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$cyid]["iso"], $C_list->date($invoice->fields['due_date'])); $email = new email_template; $email->send('admin->invoice_due_admin', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$billed_currency_id]["iso"], $C_list->date($invoice->fields['due_date'])); } elseif($PLG->type == 'redirect') { $email = new email_template; $email->send('invoice_due_user', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$cyid]["iso"], $C_list->date($invoice->fields['due_date'])); } elseif ($PLG->type == 'other') { $email = new email_template; $email->send('admin->invoice_due_admin', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$billed_currency_id]["iso"], $C_list->date($invoice->fields['due_date'])); } } # increment notice counter $sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET notice_count = ' . $db->qstr($invoice->fields['notice_count']+1) . ', notice_next_date = ' . $db->qstr(time()+86400*3) . ' WHERE id = ' . $db->qstr($invoice->fields['id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $db->Execute($sql); } else { # send service cancelation notice $email = new email_template; $email->send('service_suspend_user', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],$cyid), $C_list->date($invoice->fields['due_date'])); $email = new email_template; $email->send('admin->service_suspend_admin', $invoice->fields['account_id'], $invoice->fields['id'], $C_list->format_currency($invoice->fields['total_amt'],''), $C_list->date($invoice->fields['due_date'])); # overdue - cancel services $vara['id'] = $invoice->fields['id']; $this->voidInvoice($vara, $this); # suspend billing activity $sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET notice_count = ' . $db->qstr($invoice->fields['notice_count']+1) . ', suspend_billing = ' . $db->qstr('1') . ' WHERE id = ' . $db->qstr($invoice->fields['id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $db->Execute($sql); } } else { # update billing stauts $sql = 'UPDATE ' . AGILE_DB_PREFIX . 'invoice SET notice_count = ' . $db->qstr($invoice->fields['notice_count']+1) . ', billing_status = ' . $db->qstr('1') . ', billed_amt = ' . $db->qstr($billed_amt) . ', actual_billed_amt = ' . $db->qstr($actual_billed_amt) . ' WHERE id = ' . $db->qstr($invoice->fields['id']) . ' AND site_id = ' . $db->qstr(DEFAULT_SITE); $db->Execute($sql); # update invoice via autoapproveInvoice $this->autoApproveInvoice($invoice->fields['id']); # User alert of payment processed $email = new email_template; $email->send('invoice_paid_user', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$cyid]['iso'], ''); # Admin alert of payment processed $email = new email_template; $email->send('admin->invoice_paid_admin', $invoice->fields['account_id'], $invoice->fields['id'], $this->format_currency[$billed_currency_id]['iso'], ''); } $invoice->MoveNext(); unset($PLG); /* finish transaction */ $db->CompleteTrans(); } } /** * Find out if a user has unpaid invoices */ public function has_unpaid($VAR) { global $smarty, $C_list; if (! SESS_LOGGED) return false; $total = 0; foreach ($this->unpaid(SESS_ACCOUNT) as $amount) $total += $amount; if ($total) $smarty->assign('has_unpaid',$C_list->format_currency_num($total,SESS_CURRENCY)); } /** * Get the totals for multiple invoices or for a group of invoices stored in temp * * The retrieved invoice numbers are stored in $this->invoice; * * @param string If invoice is 'MULTI-*', get unpaid invoices from the temporary_data table, otherwise * get the unpaid invoices listed, or all invoices if blank. */ private function multiple_invoice_total($invoice,$account_id=SESS_ACCOUNT) { $this->invoice = array(); $db = &DB(); if (! preg_match('/^MULTI-/',$invoice)) { $id_list=''; if ($invoice = preg_replace('/,$/','',$invoice)) $id_list = sprintf('id in (%s) AND',$invoice); # Get invoice totals $total = 0; $rs = $db->Execute(sqlSelect($db,'invoice','id,total_amt,billed_amt',sprintf('%s account_id=%s AND billing_status=0 AND refund_status=0',$id_list,SESS_ACCOUNT))); if ($rs && $rs->RecordCount()) { while (! $rs->EOF) { $this->invoice[$rs->fields['id']] = $rs->fields['total_amt']-$rs->fields['billed_amt']; $total += $this->invoice[$rs->fields['id']]; $rs->MoveNext(); } return $total; } } else { # Get total from temp data $rs = $db->Execute(sqlSelect($db,'temporary_data','data,field1',array('field2'=>$invoice))); if ($rs && $rs->RecordCount() && $rs->fields['field1'] > 0) { $this->invoice = unserialize($rs->fields['data']); return $rs->fields['field1']; } } return false; } /** * Preview checkout of multiple invoices */ public function tpl_checkout_multiple_preview($VAR) { global $smarty,$C_list; if (! SESS_LOGGED) return false; # If the ID is blank, this will get all unpaid invoices. if (! isset($VAR['id'])) $VAR['id'] = ''; $total = $this->multiple_invoice_total($VAR['id'],SESS_ACCOUNT); $db = &DB(); if ($total > 0 && count($this->invoice) > 1) { # Get country id for checkout options $account = $db->Execute(sqlSelect($db,'account','country_id',array('id'=>SESS_ACCOUNT))); # Get payment options include_once(PATH_MODULES.'checkout/checkout.inc.php'); $checkout = new checkout; $checkoutoptions = $checkout->get_checkout_options(SESS_ACCOUNT,$total,false,$account->fields['country_id'],true); # Get a temporary id (48 hours) $id = sqlGenID($db,'temporary_data'); $invoice['id'] = sprintf('MULTI-%s',$id); $invoice['total'] = $total; $fields = array('date_orig'=>time(),'date_expire'=>time()+86400*3,'field2'=>$invoice['id'],'field1'=>$total,'data'=>serialize($this->invoice)); $rs = $db->Execute($q=sqlInsert($db,'temporary_data',$fields,$id)); $smarty->assign('record',$invoice); $smarty->assign('total',$C_list->format_currency_num($total,SESS_CURRENCY)); $smarty->assign('checkoutoptions',$checkoutoptions); } elseif (count($this->invoice) == 1) { printf("",key($this->invoice)); } else { echo _('No due invoices selected for payment.'); } } /** * Make a payment now */ public function checkoutnow($VAR) { global $C_translate,$smarty,$C_list,$VAR; # Validate user logged in: if (SESS_LOGGED != '1') { echo ''; return false; } # If the ID is blank, this will get all unpaid invoices. if (! isset($VAR['invoice_id'])) return false; # Some defaults $recur_amt = 0; $db = &DB(); if(preg_match('/^MULTI-/',@$VAR['invoice_id'])) { # Get multi-invoice details $total = $this->multiple_invoice_total($VAR['invoice_id'],SESS_ACCOUNT); if (! $total) return false; $recur_arr = false; $account_id = SESS_ACCOUNT; $this->invoice_id = $VAR['invoice_id']; $CURRENCY = DEFAULT_CURRENCY; $multi = true; } else { # Validate the invoice selected, & get the totals: $result = $db->Execute($q=sqlSelect($db,'invoice','*',array('id'=>$VAR['invoice_id']))); if (! $result || $result->RecordCount() == 0) return false; # Determine the price & currency if ($result->fields['billed_currency_id'] != $result->fields['actual_billed_currency_id']) { global $C_list; $CURRENCY = $result->fields['actual_billed_currency_id']; if($result->fields['billed_amt'] <= 0) $total = $C_list->format_currency_decimal($result->fields['total_amt'],$CURRENCY); else $total = $C_list->format_currency_decimal($result->fields['total_amt'],$CURRENCY)-$result->fields['actual_billed_amt']; } else { $CURRENCY = $result->fields['billed_currency_id']; $total = $result->fields['total_amt']-$result->fields['billed_amt']; } if ($result->fields['recur_amt'] > 0) $recur_amt = $C_list->format_currency_decimal($result->fields['recur_amt'],$CURRENCY); @$recur_arr = unserialize($result->fields['recur_arr']); $account_id = $result->fields['account_id']; $this->invoice_id = $result->fields['id']; $this->invoice[$result->fields['id']] = $total; $multi = false; } $amount = round($total, 2); # Get the account details: $sql = 'SELECT * FROM ' . AGILE_DB_PREFIX . 'account WHERE site_id = ' . $db->qstr(DEFAULT_SITE) . ' AND id = ' . $db->qstr($account_id); $account = $db->Execute($sql); if (!$account || !$account->RecordCount()) return false; # Validate checkout option selected is allowed for purchase: $q = "SELECT * FROM ".AGILE_DB_PREFIX."checkout WHERE site_id = ".$db->qstr(DEFAULT_SITE)." AND id = ".$db->qstr(@$VAR['option'])." AND active = 1 AND "; if($recur_amt>0 && @$billed_amt == 0) $q .= "allow_recurring = 1 "; else $q .= "allow_new = 1 "; $chopt = $db->Execute($q); if (!$chopt || !$chopt->RecordCount()) return false; if($chopt && $chopt->RecordCount()) { $show = true; if ( @$chopt->fields["total_maximum"] != "" && $total > $chopt->fields["total_maximum"] ) $show = false; if ( @$chopt->fields["total_miniumum"] != "" && $total < $chopt->fields["total_miniumum"] ) $show = false; } if(!$show) { echo ' '; return false; } # Load the checkout plugin: $plugin_file = PATH_PLUGINS . 'checkout/'. $chopt->fields["checkout_plugin"] . '.php'; include_once ( $plugin_file ); eval ( '$PLG = new plg_chout_' . $chopt->fields["checkout_plugin"] . '("'.@$VAR["option"].'",$multi);'); if(!empty($VAR['account_billing_id']) && @$VAR['new_card']==2) { /* validate credit card on file details */ $account_billing_id=$VAR['account_billing_id']; if(!$PLG->setBillingFromDB($account_id, $account_billing_id, $VAR['option'])) { global $C_debug; $C_debug->alert("Sorry, we cannot use that billing record for this purchase."); return false; } } else { /* use passed in vars */ $PLG->setBillingFromParams($VAR); } # Set Invoice Vars: $this->total_amt = $amount; $this->currency_iso = $C_list->currency_iso($CURRENCY); $this->currency_iso_admin = $C_list->currency_iso($CURRENCY); $this->account_id = $account_id; $this->actual_billed_currency_id = $CURRENCY; $this->billed_currency_id = $CURRENCY; $this->checkout_plugin_id = @$VAR["option"]; # Run the plugin bill_checkout() method: $this->checkout_plugin_data = $PLG->bill_checkout($amount, $this->invoice_id, $this->currency_iso, $account->fields, $recur_amt, $recur_arr,$this->invoice); # redirect if(!empty($this->checkout_plugin_data['redirect'])) echo $this->checkout_plugin_data['redirect']; # determine results if( $this->checkout_plugin_data === false ) { if(!empty($PLG->redirect)) echo $PLG->redirect; return false; } elseif ($PLG->type == "gateway" && empty($PLG->redirect)) { if(empty($this->admin_checkout)) { $VAR['_page'] = "invoice:thankyou"; } else { $VAR['_page'] = "invoice:view"; } } elseif ($PLG->type == "redirect") { echo "
Please wait while we redirect you to the secure payment site.... {$PLG->redirect}
"; } # Call the Plugin method for storing the checkout data, if new data entered: $this->account_billing_id = $PLG->store_billing($VAR, $PLG); # Load the email template module include_once(PATH_MODULES.'email_template/email_template.inc.php'); $mail = new email_template; # Update billing details for this invoice, if realtime billing succeeded: if($PLG->type == 'gateway' || $amount == 0) { $q = "UPDATE ".AGILE_DB_PREFIX."invoice SET account_billing_id = " .$db->qstr($this->account_billing_id). ", billing_status = " .$db->qstr(1). ", billed_amt = " .$db->qstr($total). ", actual_billed_amt = " .$db->qstr($amount). ", date_last = " .$db->qstr(time()). ", checkout_plugin_id = " .$db->qstr($this->checkout_plugin_id) .", checkout_plugin_data = " .$db->qstr(serialize($this->checkout_plugin_data)). " WHERE site_id = ".$db->qstr(DEFAULT_SITE)." AND id = ".$db->qstr($this->invoice_id); $rst = $db->Execute($q); if ($rst === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); return false; } // loop through each invoice paid foreach($this->invoice as $this->invoice_id) { # Send billed e-mail notice to user $email = new email_template; $email->send('invoice_paid_user', $this->account_id, $this->invoice_id, $this->currency_iso, ''); # Admin alert of payment processed $email = new email_template; $email->send('admin->invoice_paid_admin', $this->account_id, $this->invoice_id, $this->currency_iso_admin, ''); # Submit the invoice for approval $arr['id'] = $this->invoice_id; $this->approveInvoice($arr, $this); } } else { # Just update the last_date and plugin data $q = "UPDATE ".AGILE_DB_PREFIX."invoice SET account_billing_id = " .$db->qstr($this->account_billing_id). ", date_last = " .$db->qstr(time()). ", checkout_plugin_id = " .$db->qstr($this->checkout_plugin_id) .", checkout_plugin_data = " .$db->qstr(serialize($this->checkout_plugin_data)). " WHERE site_id = ".$db->qstr(DEFAULT_SITE)." AND id = ".$db->qstr($this->invoice_id); $rst = $db->Execute($q); if ($rst === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); return false; } # Admin e-mail alert of manual payment processing if ( $PLG->name == 'MANUAL' ) { $date_due = $C_list->date(time()); foreach($this->invoice as $this->invoice_id) { $email = new email_template; $email->send('admin->invoice_due_admin', $this->account_id, $this->invoice_id, '', $date_due); } global $C_debug; $C_debug->alert($C_translate->translate('manual_alert','checkout')); } } } /** * Create modified array for invoice summarization * * This function will summarise the invoice items based on the same * SKU, BASE_PRICE, SETUP_PRICE & PRODUCT_ATTR */ public function summarizeLineItems($smart_items) { //$ignore['SKU']=true; $sum = array(); if (is_array($smart_items)) { foreach ($smart_items as $it) { # Unique line item if (! isset($sum[$it['sku']])) { if (! isset($ignore[$it['sku']])) $sum[$it['sku']] = array($it); $sum[$it['sku']][0]['summaryname'] = $this->getLineItemDesc($it['sku'],$it['product_id'],false,$it['sku'] ? false : $it['product_name']); } else { # Is unique price/attributes? $unique = true; foreach ($sum[$it['sku']] as $sid => $flds) { if ($flds['price_base'] == $it['price_base'] && $flds['price_setup'] == $it['price_setup'] && $flds['product_attr'] == $it['product_attr']) { $sum[$it['sku']][$sid]['quantity'] += 1; $unique = false; break; } } # Unique line item if ($unique) { $a = count($sum[$it['sku']]); array_push($sum[$it['sku']],$it); $sum[$it['sku']][$a]['summaryname'] = $this->getLineItemDesc($it['sku'],$it['product_id'],false,$it['sku'] ? false : $it['product_name']); } } } } if (count($sum)) { $smart_items = array(); foreach ($sum as $sku => $item) foreach ($item as $sitem) array_push($smart_items,$sitem); return $smart_items; } } /** * View an Invoice * Shown both in the admin pages and after checkout * * @uses net_term */ public function view($VAR) { global $C_translate,$C_list; $db = &DB(); if ($smart = parent::view($VAR)) { # Get the product checkout plugin name if (! empty($smart['checkout_plugin_id'])) { $cplg = $db->Execute(sqlSelect($db,'checkout','name',sprintf('id=%s',$smart['checkout_plugin_id']))); if ($cplg && $cplg->RecordCount()) $smart['checkout_plugin'] = $cplg->fields['name']; } if ($smart['total_amt'] == 0) $smart['balance'] = 0; else $smart['balance'] = $smart['total_amt']-$smart['billed_amt']; # Get the tax details if (! empty($smart['tax_amt'])) { $trs = $db->Execute(sqlSelect($db,array('invoice_item_tax','tax'),'A.amount,B.description',sprintf('A.tax_id=B.id AND A.invoice_id=%s',$smart['id']))); if ($trs && $trs->RecordCount()) { $taxes = array(); while (!$trs->EOF) { @$taxes[$trs->fields['description']] += $trs->fields['amount']; $trs->MoveNext(); } $smart['tax_arr'] = array(); foreach ($taxes as $txds => $txamt) array_push($smart['tax_arr'],array('description'=>$txds,'amount'=>$txamt)); } } # Get the discount details if (! empty($smart['discount_amt'])) { $drs = $db->Execute(sqlSelect($db,'invoice_item_discount','amount,discount',sprintf('invoice_id=%s',$smart['id']))); if ($drs && $drs->RecordCount()) { $discounts = array(); while(!$drs->EOF) { @$discounts[$drs->fields['discount']] += $drs->fields["amount"]; $drs->MoveNext(); } $dhtml = ''; foreach ($discounts as $dsds=>$dsamt) $dhtml .= sprintf('%s -
',$dsds,$dsds,number_format($dsamt,2)); $smart['discount_popup'] = $dhtml; $dhtml = ''; foreach ($discounts as $dsds=>$dsamt) $dhtml .= sprintf('%s - %s
',$dsds,number_format($dsamt,2)); $smart['discount_popup_user'] = $dhtml; } } # Get the checkout plugin details if (! empty($smart['checkout_plugin_data'])) { $plugin_data = unserialize($smart['checkout_plugin_data']); if (is_array($plugin_data)) $smart['checkout_plugin_data'] = $plugin_data; else $smart['checkout_plugin_data'] = array(0=>$smart['checkout_plugin_data']); } # Get the line items $q = sqlSelect($db,'invoice_item','*',sprintf('invoice_id=%s',$smart['id'])); if ($C_list->is_installed('voip')) $q .= ' AND item_type!=5'; $items = $db->Execute($q); if ($items === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); return false; } # Get the term dates include_once(PATH_MODULES.'net_term/net_term.inc.php'); $net_term = new net_term; $smart['termdates'] = $net_term->getTermDates($smart['net_term_id'],$smart['date_orig'],$smart['due_date']); $ii =0; while (! $items->EOF) { $smart_items[$ii] = $items->fields; # Get the product attribs if (! empty($items->fields['product_attr'])) { @$attrib = explode("\r\n",$items->fields['product_attr']); $js = ''; for ($attr_i=0; $attr_i%s : %s
',$attributei[0],$attributei[1]); } $smart_items[$ii]['attribute_popup'] = $js; } # Get the date range if set if (! empty($items->fields['date_start']) && ! empty($items->fields['date_stop'])) { $C_translate->value('invoice','start',date(UNIX_DATE_FORMAT,$items->fields['date_start'])); $C_translate->value('invoice','stop',date(UNIX_DATE_FORMAT,$items->fields['date_stop'])); $smart_items[$ii]['range'] = $C_translate->translate('recur_date_range','invoice',''); } # Set charge type for payment option list $any_new = true; if ($items->fields['price_type']=='1' && ! empty($smart['recurr_arr']) && is_array(unserialize($smart['recurr_arr']))) $any_recurring = true; $items->MoveNext(); $ii++; } # Create a summary (for duplicate skus w/identical price,and attributes, roll into a single value if ($this->summarizeInvoice) $smart_items = $this->summarizeLineItems($smart_items); # Get the checkout (payment) options if ($VAR['_page'] != 'invoice:view') { # Get the converted amount due: if ($smart['billed_currency_id'] != $smart['actual_billed_currency_id']) { global $C_list; $CURRENCY = $smart['actual_billed_currency_id']; if ($smart['billed_amt'] <= 0) $total = $C_list->format_currency_decimal($smart['total_amt'],$CURRENCY); else $total = $C_list->format_currency_decimal($smart['total_amt'],$CURRENCY)-$smart['actual_billed_amt']; } else { $CURRENCY = $smart['billed_currency_id']; $total = $smart['total_amt']-$smart['billed_amt']; } $q = sqlSelect($db,'checkout','*','active=1'); if ($any_trial) $q .= sprintf(' AND allow_trial = %s',$db->qstr('1')); if ($any_recurring) $q .= sprintf(' AND allow_recurring = %s',$db->qstr('1')); if ($any_new) $q .= sprintf(' AND allow_new = %s',$db->qstr('1')); $chopt = $db->Execute($q); if ($chopt === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); return false; } if ($chopt != false && $chopt->RecordCount() > 0) { while (! $chopt->EOF) { $show = true; # Check that the cart total is not to high: if ($chopt->fields['total_maximum'] != '' && $smart['total_amt'] >= $chopt->fields['total_maximum']) $show = false; # Check that the cart total is not to low: if ($chopt->fields['total_miniumum'] != '' && $smart['total_amt'] <= $chopt->fields['total_miniumum']) $show = false; # Check that the group requirement is met: if ($show && ! empty($chopt->fields['required_groups'])) { global $C_auth; $arr = unserialize($chopt->fields['required_groups']); if (count($arr) > 0 && ! empty($arr[0])) $show = false; for ($i=0; $iauth_group_by_id($arr)) { $show = true; $i = count($arr); } } } # Check that the customer is not ordering a blocked SKU if ($show && ! empty($chopt->fields['excluded_products'])) { $arr = unserialize($chopt->fields['excluded_products']); if (count($arr) > 0) { for ($i=0; $ifields['default_when_amount'])) { $arr = unserialize($chopt->fields['default_when_amount']); for ($idx=0; $idx= $arr[$idx]) $list_ord--; $idx = count($arr); } } # By Currency if (! empty($chopt->fields['default_when_currency'])) { $arr = unserialize($chopt->fields['default_when_currency']); for ($idx=0; $idxfields['default_when_group'])) { $arr = unserialize($chopt->fields['default_when_group']); global $C_auth; for ($idx=0; $idxauth_group_by_id($arr[$idx])) $list_ord--; $idx = count($arr); } } # By Country if (! empty($chopt->fields['default_when_country'])) { $arr = unserialize($chopt->fields['default_when_country']); for ($idx=0; $idxfields['country_id'] == $arr[$idx]) $list_ord--; $idx = count($arr); } } # Add to the array $checkout_optionsx[] = array('sort'=>$list_ord,'fields'=>$chopt->fields); } $chopt->MoveNext(); } # Sort the checkout_options array by the [fields] element if (count($checkout_optionsx)>0) { foreach ($checkout_optionsx as $key => $row) $sort[$key] = $row['sort']; array_multisort($sort,SORT_ASC,$checkout_optionsx); } } } # No results if (count($smart) == 0) { global $C_debug; $C_debug->error(__FILE__, __METHOD__,'The selected record does not exist any longer, or your account is not authorized to view it'); return; } # Define the DB vars as a Smarty accessible block global $smarty; # Define the results $smarty->assign('cart',$smart_items); $smarty->assign('record',$smart); $smarty->assign('checkoutoptions',$checkout_optionsx); } } /** * Delete an invoice */ public function delete($VAR) { $db = &DB(); # Get the array if (isset($VAR['delete_id'])) $ids = explode(',',preg_replace('/,$/','',$VAR['delete_id'])); elseif (isset($VAR['id'])) $ids = explode(',',preg_replace('/,$/','',$VAR['id'])); # Load the service module include_once(PATH_MODULES.'service/service.inc.php'); $service = new service; # Loop: for ($i=0; $iExecute(sqlSelect($db,'service',array('invoice_id'=>$ids[$i]))); if ($rs === false) { global $C_debug; $C_debug->error(__FILE__,'delete', $db->ErrorMsg()); return false; } if ($rs->RecordCount() > 0) { while (! $rs->EOF) { $service->delete($rs->fields['id'],$service); $rs->MoveNext(); } } # Delete the service record $this->associated_DELETE = array(); array_push($this->associated_DELETE,array('table'=>'invoice_commission','field'=>'invoice_id')); array_push($this->associated_DELETE,array('table'=>'invoice_item','field'=>'invoice_id')); array_push($this->associated_DELETE,array('table'=>'invoice_memo','field'=>'invoice_id')); // array_push($this->associated_DELETE,array('table'=>'service','field'=>'invoice_id')); array_push($this->associated_DELETE,array('table'=>'invoice_item_tax','field'=>'invoice_id')); array_push($this->associated_DELETE,array('table'=>'invoice_item_discount','field'=>'invoice_id')); $result = parent::delete($VAR); } } /** * User view an invoice */ public function user_view($VAR) { global $C_auth; if (! SESS_LOGGED) return false; # Verify the account_id for this order is the SESS_ACCOUNT if ($C_auth->auth_method_by_name('invoice','view') == false) { $id = preg_replace('/,$/','',$VAR['id']); $db = &DB(); $rs = $db->Execute($q=sqlSelect($db,'invoice','account_id',array('id'=>$id))); if ($rs === false) { global $C_debug; $C_debug->error(__FILE__,__METHOD__,$db->ErrorMsg()); return false; } if ($rs->fields['account_id'] != SESS_ACCOUNT) return false; } $this->view($VAR,$this); } } ?>