Invision Power Services, Inc. * @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc. * @license http://www.invisionpower.com/legal/standards/ * @package IPS Social Suite * @subpackage Nexus * @since 10 Feb 2014 * @version SVN_VERSION_NUMBER */ namespace IPS\nexus; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Invoice Model */ class _Invoice extends \IPS\Patterns\ActiveRecord { const STATUS_PAID = 'paid'; const STATUS_PENDING = 'pend'; const STATUS_EXPIRED = 'expd'; const STATUS_CANCELED = 'canc'; /** * @brief Multiton Store */ protected static $multitons; /** * @brief Database Table */ public static $databaseTable = 'nexus_invoices'; /** * @brief Database Prefix */ public static $databasePrefix = 'i_'; /** * Load and check permissions * * @return \IPS\Content\Item * @throws \OutOfRangeException */ public static function loadAndCheckPerms( $id ) { $obj = static::load( $id ); if ( !$obj->canView() ) { throw new \OutOfRangeException; } return $obj; } /** * Get statuses * * @return array */ public static function statuses() { $options = array(); $reflection = new \ReflectionClass( get_called_class() ); foreach ( $reflection->getConstants() as $k => $v ) { if ( mb_substr( $k, 0, 7 ) === 'STATUS_' ) { $options[ $v ] = "istatus_{$v}"; } } return $options; } /** * Get invoices table * * @param array $where Where clause * @param string $ref Referer * @return \IPS\Helpers\Table\Db */ public static function table( $where = array(), \IPS\Http\Url $url, $ref = 't' ) { /* Create the table */ $table = new \IPS\Helpers\Table\Db( 'nexus_invoices', $url, $where ); $table->sortBy = $table->sortBy ?: 'i_date'; /* Format Columns */ $table->include = array( 'i_status', 'i_id', 'i_title', 'i_member', 'i_total', 'i_date' ); $table->parsers = array( 'i_status' => function( $val ) { return \IPS\Theme::i()->getTemplate('invoices')->status( $val ); }, 'i_member' => function ( $val, $row ) { if ( $val ) { return \IPS\Theme::i()->getTemplate('global')->userLink( \IPS\Member::load( $val ) ); } elseif ( $row['i_guest_data'] ) { $data = json_decode( $row['i_guest_data'], TRUE ); return \IPS\nexus\Customer::constructFromData( $data['member'] )->cm_name; } else { return \IPS\Theme::i()->getTemplate('global')->userLink( \IPS\Member::load( 0 ) ); } }, 'i_total' => function( $val, $row ) { return (string) new \IPS\nexus\Money( $val, $row['i_currency'] ); }, 'i_date' => function( $val ) { return \IPS\DateTime::ts( $val ); } ); /* Buttons */ $table->rowButtons = function( $row ) use( $ref ) { return array_merge( array( 'view' => array( 'icon' => 'search', 'title' => 'invoice_view', 'link' => \IPS\Http\Url::internal( "app=nexus&module=payments&controller=invoices&do=view&id={$row['i_id']}" ) ), ), \IPS\nexus\Invoice::constructFromData( $row )->buttons( $ref ) ); }; return $table; } /** * @brief Being generated by system? */ public $system = FALSE; /** * Set Default Values * * @return void */ public function setDefaultValues() { $this->status = static::STATUS_PENDING; $this->items = json_encode( array() ); $this->date = new \IPS\DateTime; $this->renewal_ids = array(); $this->guest_data = array(); $this->_data['total'] = 0; $this->_data['member'] = 0; $this->status_extra = array(); } /** * Member can view? * * @param \IPS\Member|NULL $member The member to check for, or NULL for currently logged in member * @return bool */ public function canView( \IPS\Member $member = NULL ) { $member = $member ?: \IPS\Member::loggedIn(); return $this->member->member_id === $member->member_id or array_key_exists( $member->member_id, iterator_to_array( $this->member->alternativeContacts( array( 'billing=1' ) ) ) ); } /** * Set title * * @param string * @return void * @throws \LengthException */ public function set_title( $title ) { if ( mb_strlen( $title ) > 128 ) { throw new \LengthException; } $this->_data['title'] = $title; } /** * @brief Member */ protected $_member; /** * Get member * * @return \IPS\Member */ public function get_member() { if ( $this->_member === NULL ) { try { $this->_member = \IPS\nexus\Customer::load( $this->_data['member'] ); } catch ( \OutOfRangeException $e ) { $this->_member = new \IPS\nexus\Customer; if ( $this->_data['member'] ) { $this->_member->_data['cm_first_name'] = \IPS\Member::loggedIn()->language()->addToStack('deleted_member'); } elseif ( $this->guest_data ) { foreach ( $this->guest_data['member'] as $k => $v ) { $this->_member->_data[ $k ] = $v; $this->_member->changed[ $k ] = $v; } } } } return $this->_member; } /** * Set member * * @param \IPS\Member * @return void */ public function set_member( \IPS\Member $member ) { $this->_data['member'] = (int) $member->member_id; $this->_member = NULL; if ( !$this->currency and $member instanceof \IPS\nexus\Customer ) { $this->currency = $member->defaultCurrency(); } } /** * @brief Items iterator */ protected $_itemsIterator; /** * Get items * * @return array */ public function get_items() { if ( $this->_itemsIterator === NULL ) { /* Decode */ $itemsJson = json_decode( $this->_data['items'], TRUE ) ?: array(); /* Check we actually have a valid array with items (very old invoices from IP.Subscriptions may not have anything) and just create a dummy if so */ $itemsJson = array_filter( $itemsJson, function( $val ) { return is_array( $val ) and isset( $val['act'] ); } ); if ( !$itemsJson and $this->total->amount ) { $itemsJson = array( array( 'act' => 'charge', 'app' => 'nexus', 'type' => 'charge', 'cost' => $this->total->amount, 'tax' => NULL, 'quantity' => 1, 'itemName' => $this->title, 'itemID' => NULL, 'physical' => FALSE, 'methods' => '*', 'cfields' => array(), 'extra' => NULL, ) ); } /* Return it */ $this->_itemsIterator = new \IPS\nexus\Invoice\ItemsIterator( $itemsJson ); $this->_itemsIterator->currency = $this->currency; } return $this->_itemsIterator; } /** * Get total * * @return \IPS\nexus\Money */ public function get_total() { return new \IPS\nexus\Money( $this->_data['total'], $this->currency ); } /** * Set total * * @param \IPS\nexus\Money $total The total * @return void */ public function set_total( \IPS\nexus\Money $total ) { $this->_data['total'] = $total->amount; } /** * Get date * * @return \IPS\DateTime */ public function get_date() { return \IPS\DateTime::ts( $this->_data['date'] ); } /** * Set date * * @param \IPS\DateTime $date The invoice date * @return void */ public function set_date( \IPS\DateTime $date ) { $this->_data['date'] = $date->getTimestamp(); } /** * Get paid date date * * @return \IPS\DateTime|NULL */ public function get_paid() { return $this->_data['paid'] ? \IPS\DateTime::ts( $this->_data['paid'] ) : NULL; } /** * Set paid date * * @param \IPS\DateTime $date The invoice date * @return void */ public function set_paid( \IPS\DateTime $date ) { $this->_data['paid'] = $date->getTimestamp(); } /** * Get return URI * * @return \IPS\Http\Url|NULL */ public function get_return_uri() { return $this->_data['return_uri'] ? \IPS\Http\Url::internal( $this->_data['return_uri'] ) : NULL; } /** * Get status information * * @return mixed */ public function get_status_extra() { return json_decode( $this->_data['status_extra'], TRUE ); } /** * Set status information * * @param mixed $extra The data * @return void */ public function set_status_extra( $extra ) { $this->_data['status_extra'] = json_encode( $extra ); } /** * Get renewal IDs * * @return array */ public function get_renewal_ids() { return explode( ',', $this->_data['renewal_ids'] ); } /** * Set renewal IDs * * @param array $ids The IDs * @return void */ public function set_renewal_ids( $ids ) { array_unique( $ids ); $ids = array_filter( $ids ); $this->_data['renewal_ids'] = implode( ',', $ids ); } /** * Get shipping address * * @return \IPS\GeoLocation|NULL */ public function get_shipaddress() { if ( !$this->hasPhysicalItems() ) { return NULL; } if ( isset( $this->_data['shipaddress'] ) and $this->_data['shipaddress'] ) { return \IPS\GeoLocation::buildFromJson( $this->_data['shipaddress'] ); } else { try { return \IPS\GeoLocation::buildFromJson( \IPS\Db::i()->select( 'address', 'nexus_customer_addresses', array( 'member=? AND primary_shipping=1', $this->member->member_id ) )->first() ); } catch ( \UnderflowException $e ) { return NULL; } } } /** * Set shipping address * * @param \IPS\GeoLocation $shippingAddress The shipping address * @return void */ public function set_shipaddress( \IPS\GeoLocation $shippingAddress ) { $this->_data['shipaddress'] = json_encode( $shippingAddress ); } /** * Get billing address * * @return \IPS\GeoLocation|NULL */ public function get_billaddress() { if ( isset( $this->_data['billaddress'] ) and $this->_data['billaddress'] ) { return \IPS\GeoLocation::buildFromJson( $this->_data['billaddress'] ); } else { try { return \IPS\GeoLocation::buildFromJson( \IPS\Db::i()->select( 'address', 'nexus_customer_addresses', array( 'member=? AND primary_billing=1', $this->member->member_id ) )->first() ); } catch ( \UnderflowException $e ) { return NULL; } } } /** * Set billing address * * @param \IPS\GeoLocation $billingAddress The billing address * @return void */ public function set_billaddress( \IPS\GeoLocation $billingAddress ) { $this->_data['billaddress'] = json_encode( $billingAddress ); $this->_data['billcountry'] = $billingAddress->country; } /** * Get guest data * * @return mixed */ public function get_guest_data() { return $this->_data['guest_data'] ? json_decode( $this->_data['guest_data'], TRUE ) : NULL; } /** * Set guest data * * @param mixed $data The data * @return void */ public function set_guest_data( $data ) { $this->_data['guest_data'] = $data ? json_encode( $data ) : NULL; } /** * Save Changed Columns * * @return void */ public function save() { if ( !$this->title ) { $titles = array(); foreach ( $this->items as $item ) { $titles[] = ( $item->name . ( $item->quantity > 1 ? " x{$item->quantity}" : '' ) ); } $thisTitle = implode( ', ', $titles ); if ( mb_strlen( $thisTitle ) > 128 ) { $thisTitle = mb_substr( $thisTitle, 0, 125 ) . '...'; } $this->title = $thisTitle; } if ( $this->_new ) { parent::save(); $this->member->log( 'invoice', array( 'id' => $this->id, 'title' => $this->title, 'status' => $this->status, 'system' => $this->system ), $this->system ? FALSE : NULL ); } else { parent::save(); } } /** * Delete * * @return void */ public function delete() { parent::delete(); foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW ) ) as $transaction ) { if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PENDING ) { $transaction->delete(); } else { try { $transaction->method->void( $transaction ); } catch ( \Exception $e ){} $extra = $transaction->extra; $extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'note' => 'invoiceDeleted' ); $transaction->extra = $extra; $transaction->status = \IPS\nexus\Transaction::STATUS_REFUSED; $transaction->save(); } } } /* !Items */ /** * Add item * * @param \IPS\nexus\Invoice\Item $item The item * @return int */ public function addItem( \IPS\nexus\Invoice\Item $item ) { /* Set basic data */ $data = array( 'act' => $item::$act, 'app' => isset( $item::$application ) ? $item::$application : $item->application, 'type' => isset( $item::$type ) ? $item::$type : $item->type, 'cost' => $item->price->amount, 'tax' => $item->tax ? $item->tax->id : NULL, 'quantity' => $item->quantity, 'itemName' => $item->name, 'itemID' => $item->id, 'physical' => FALSE, 'methods' => $item->paymentMethodIds ?: '*', 'cfields' => $item->details, 'extra' => $item->extra, ); /* Parent? */ if ( isset( $item->parent ) and $item->parent !== NULL ) { if ( is_int( $item->parent ) ) { $data['assoc'] = $item->parent; $data['assocBought'] = FALSE; } else { $data['assoc'] = $item->parent->id; $data['assocBought'] = TRUE; } $data['groupParent'] = $item->groupWithParent; } /* Renewal term? */ if ( isset( $item->renewalTerm ) ) { $renewTermData = $item->renewalTerm->getTerm(); $data['renew_term'] = $renewTermData['term']; $data['renew_units'] = $renewTermData['unit']; $data['renew_cost'] = $item->renewalTerm->cost->amount; if ( $item->renewalTerm->gracePeriod ) { $data['grace_period'] = \IPS\DateTime::create()->add( $item->renewalTerm->gracePeriod )->getTimestamp() - time(); } } /* Expire date? */ if ( isset( $item->expireDate ) ) { $data['expires'] = $item->expireDate->getTimestamp(); } /* Physical? */ if ( $item->physical ) { $data['physical'] = TRUE; $data['shipping'] = $item->shippingMethodIds ?: '*'; $data['weight'] = $item->weight->kilograms; $data['length'] = $item->length->metres; $data['width'] = $item->width->metres; $data['height'] = $item->height->metres; } /* Pay to someone else? */ if ( $item->payTo ) { $data['payTo'] = $item->payTo->member_id; if ( $item->commission ) { $data['commission'] = $item->commission; } if ( $item->fee ) { $data['fee'] = $item->fee->amount; } } /* Add it */ $items = json_decode( $this->_data['items'], TRUE ); $items[] = $data; /* Get the ID */ $ids = array_keys( $items ); $id = array_pop( $ids ); /* Save it */ $this->items = json_encode( $items ); $this->_itemsIterator = NULL; /* Is it a renewal */ if ( $item instanceof \IPS\nexus\Invoice\Item\Renewal ) { $renewalIds = $this->renewal_ids; $renewalIds[] = $item->id; $this->renewal_ids = $renewalIds; } /* Increase the total */ $this->recalculateTotal(); /* Return the ID */ return $id; } /** * Change item * * @param int $index The index * @param array $data Data to set * @return void */ public function changeItem( $index, $data ) { $items = json_decode( $this->_data['items'], TRUE ); foreach ( $data as $k => $v ) { $items[ $index ][ $k ] = $v; } $this->items = json_encode( $items ); $this->_itemsIterator = NULL; } /** * Remove item * * @param int $index The index * @return void */ public function removeItem( $index ) { $items = json_decode( $this->_data['items'], TRUE ); unset( $items[ $index ] ); $this->items = json_encode( $items ); $this->_itemsIterator = NULL; $this->recalculateTotal(); } /** * Recalculate the total amount * * @return void */ public function recalculateTotal() { $summary = $this->summary(); $this->total = $summary['total']; } /** * Get summary for invoice tables and so on * * @return array */ public function summary() { $return = array(); $return['items'] = array(); $shippingItems = array(); $subtotal = 0; $taxableAmounts = array(); $taxClasses = \IPS\nexus\Tax::roots(); /* Work out the line totals */ foreach ( $this->items as $k => $item ) { if ( $item instanceof \IPS\nexus\extensions\nexus\Item\ShippingCharge ) { $shippingItems[ $k ] = $item; } else { $subtotal += $item->linePrice()->amount; if ( isset( $item->tax ) ) { if ( isset( $taxableAmounts[ $item->tax->id ] ) ) { $taxableAmounts[ $item->tax->id ] += $item->linePrice()->amount; } else { $taxableAmounts[ $item->tax->id ] = $item->linePrice()->amount; } } $return['items'][ $k ] = $item; } } /* Subtotal (subtotal is caulculated BEFORE shipping is added) */ $return['subtotal'] = new \IPS\nexus\Money( $subtotal, $this->currency ); /* Add shipping */ $return['shipping'] = array(); $shipping = 0; foreach ( $shippingItems as $k => $item ) { $linePrice = $item->linePrice()->amount; $shipping += $linePrice; if ( isset( $item->tax ) ) { if ( isset( $taxableAmounts[ $item->tax->id ] ) ) { $taxableAmounts[ $item->tax->id ] += $linePrice; } else { $taxableAmounts[ $item->tax->id ] = $linePrice; } } $return['shipping'][ $k ] = $item; } $return['shippingTotal'] = new \IPS\nexus\Money( $shipping, $this->currency ); /* Now work out the tax */ $return['tax'] = array(); $tax = 0; foreach ( $taxableAmounts as $taxId => $amount ) { $rate = $taxClasses[ $taxId ]->rate( $this->billaddress ); $amount = $amount * $rate; $amount = new \IPS\nexus\Money( $amount, $this->currency ); $amount = $amount->round(); // Tax value may be something like $1.005, but needs to be a valid currency amount. Always rounds up. $return['tax'][ $taxId ] = array( 'name' => $taxClasses[ $taxId ]->_title, 'rate' => $rate, 'amount' => $amount ); $tax += $amount->amount; } $return['taxTotal'] = new \IPS\nexus\Money( $tax, $this->currency ); /* Add it all together */ $return['total'] = new \IPS\nexus\Money( $subtotal + $shipping + $tax, $this->currency ); $return['total'] = $return['total']->round(); // Total value must also be a valid currency amount return $return; } /* !Information */ /** * Does the invoice contain physical items that need shipping? * * @return bool */ public function hasPhysicalItems() { foreach ( $this->items as $item ) { if ( $item->physical ) { return TRUE; } } return FALSE; } /** * Requires login? * * @return bool */ public function requiresLogin() { foreach ( $this->items as $item ) { if ( $item::$requiresAccount ) { return TRUE; } } return FALSE; } /** * Amounts that go to a member * * @return array */ public function payToRecipients() { $recipients = array(); foreach ( $this->items as $item ) { if ( $item->payTo ) { if ( !isset( $recipients[ $item->payTo->member_id ] ) ) { $recipients[ $item->payTo->member_id ] = 0; } $itemRecipientAmounts = $item->recipientAmounts(); $recipients[ $item->payTo->member_id ] += $itemRecipientAmounts['recipient_final']->amount; } } return $recipients; } /** * Get the commission information * * @param float $amountReceived The amount the invoice is for, minus amounts being given to other recipients * @return array( 'rule' => \IPS\nexus\CommissionRule, 'referrer' => \IPS\nexus\Customer, 'amount' => \IPS\nexus\Money ) */ public function commission( $amountReceived ) { if ( !isset( $this->status_extra['commissionrule'] ) ) { try { /* Get the referrer */ $referrer = \IPS\Member::load( \IPS\Db::i()->select( 'referred_by', 'nexus_referrals', array( 'member_id=?', $this->member->member_id ) )->first() ); if ( !$referrer->member_id or $referrer->inGroup( explode( ',', \IPS\Settings::i()->nexus_no_commission ) ) ) { throw new \UnderflowException; } /* Get pertinent values */ $byNumber = \IPS\Db::i()->select( 'COUNT( i_id )', 'nexus_invoices', array( "i_member=? AND i_status=?", $referrer->member_id, static::STATUS_PAID ) )->first(); $byAmounts = iterator_to_array( \IPS\Db::i()->select( 'i_currency, SUM( i_total ) as value', 'nexus_invoices', array( "i_member=? AND i_status=?", $referrer->member_id, static::STATUS_PAID ), NULL, NULL, 'i_currency' )->setKeyField( 'i_currency' )->setValueField( 'value' ) ); $forNumber = \IPS\Db::i()->select( 'COUNT( i_id )', 'nexus_invoices', array( "i_member=? AND i_status=?", $this->member->member_id, static::STATUS_PAID ) )->first(); $forAmounts = iterator_to_array( \IPS\Db::i()->select( 'i_currency, SUM( i_total ) as value', 'nexus_invoices', array( "i_member=? AND i_status=?", $this->member->member_id, static::STATUS_PAID ), NULL, NULL, 'i_currency' )->setKeyField( 'i_currency' )->setValueField( 'value' ) ); /* Loop the rules to find out the best one */ $maxAmount = 0; $actingRule = NULL; foreach ( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_referral_rules', array( array( "rrule_by_group='*' OR " . \IPS\Db::i()->findInSet( 'rrule_by_group', $referrer->groups ) ), array( "rrule_for_group='*' OR " . \IPS\Db::i()->findInSet( 'rrule_for_group', $this->member->groups ) ) ) ), 'IPS\nexus\CommissionRule' ) as $commissionRule ) { /* Before we go nuts, don't bother if it's not going to give us more commission */ if ( round( ( ( $amountReceived / 100 ) * $commissionRule->commission ), 2 ) <= $maxAmount ) { continue; } if ( $commissionRule->commission_limit ) { $commisionLimit = json_decode( $commissionRule->commission_limit, TRUE ); if ( isset( $commisionLimit[ $this->currency ] ) and $commisionLimit[ $this->currency ]['amount'] <= $maxAmount ) { continue; } } /* Is the invoice amount right? */ if ( $commissionRule->purchase_amount_op ) { $amount = json_decode( $commissionRule->purchase_amount_unit, TRUE ); if ( !$this->_checkCondition( $this->total->amount, $commissionRule->purchase_amount_op, $amount[ $this->currency ] ) ) { continue; } } /* Does the referring user have the right amount of purchases? */ if ( $commissionRule->by_purchases_op ) { $val = $commissionRule->by_purchases_unit; if ( $commissionRule->by_purchases_type == 'v' ) { $val = json_decode( $val, TRUE ); $val = $val[ $this->currency ]; } if ( !$this->_checkCondition( ( $commissionRule->by_purchases_type == 'n' ? $byNumber : $byAmounts[ $this->currency ] ), $commissionRule->by_purchases_op, $val ) ) { continue; } } /* Does the purchasing user have the right amount of purchases? */ if ( $commissionRule->for_purchases_op ) { $val = $commissionRule->for_purchases_unit; if ( $commissionRule->for_purchases_type == 'v' ) { $val = json_decode( $val, TRUE ); $val = $val[ $this->currency ]; } if ( !$this->_checkCondition( ( $commissionRule->for_purchases_type == 'n' ? $forNumber : $forAmounts[ $this->currency ] ), $commissionRule->for_purchases_op, $val ) ) { continue; } } /* Work out what packages apply */ $amountToApplyCommissionOn = $amountReceived; if ( $commissionRule->purchase_packages ) { if ( $commissionRule->purchase_package_limit ) { $amountToApplyCommissionOn = 0; } $itemsToCount = array(); foreach ( $this->items as $k => $item ) { /* What's the package ID? */ if ( $item instanceof \IPS\nexus\extensions\nexus\Item\Package ) { $packageId = $item->id; } elseif ( $commissionRule->purchase_renewal and $item instanceof \IPS\nexus\Invoice\Item\Renewal and $item->application == 'nexus' and $item->type == 'package' ) { try { $packageId = \IPS\nexus\Purchase::load( $item->id )->item_id; } catch ( \OutOfRangeException $e ) { } } /* Is it in our list? */ if ( !$packageId or !in_array( $packageId, explode( ',', $commissionRule->purchase_packages ) ) ) { continue; } /* Add it to the list */ if ( $packageId ) { $itemsToCount[ $k ] = $packageId; } if ( $commissionRule->purchase_package_limit ) { if ( isset( $amountReceivedForItem[ $k ] ) ) { $amountToApplyCommissionOn += $amountReceivedForItem[ $k ]; } else { $amountToApplyCommissionOn += ( $item->price->amount * $item->quantity ); } } } /* If we need ALL of the packages, do we have them all? */ if ( $commissionRule->purchase_any ) { if ( empty( $itemsToCount ) ) { continue; } } else { foreach ( explode( ',', $commissionRule->purchase_packages ) as $pid ) { if ( !in_array( $pid, $itemsToCount ) ) { continue 2; } } } } /* If we're still here, we're applying commission */ $amountToGive = round( ( ( $amountToApplyCommissionOn / 100 ) * $commissionRule->commission ), 2 ); if ( $commissionRule->commission_limit and $commissionRule->commission_limit != '*' ) { $limit = json_decode( $commissionRule->commission_limit, TRUE ); if ( isset( $limit[ $this->currency ] ) and $amountToGive > $limit[ $this->currency ]['amount'] ) { $amountToGive = $limit[ $this->currency ]['amount']; } } /* Is this more than what we have? */ if ( ( $amountToGive > $maxAmount ) or ( $amountToGive and !$maxAmount ) ) { $maxAmount = $amountToGive; $actingRule = $commissionRule; } } /* Return */ $extra = $this->status_extra; $extra['commissionrule'] = $actingRule ? $actingRule->id : 0; $extra['commissionref'] = $referrer->member_id; $extra['commissionamount'] = $maxAmount; $this->status_extra = $extra; } catch ( \UnderflowException $e ) { $extra = $this->status_extra; $extra['commissionrule'] = 0; $extra['commissionref'] = 0; $extra['commissionamount'] = 0; $this->status_extra = $extra; } $this->save(); } return array( 'rule' => $this->status_extra['commissionrule'] ? \IPS\nexus\CommissionRule::load( $this->status_extra['commissionrule'] ) : NULL, 'referrer' => $this->status_extra['commissionref'] ? \IPS\nexus\Customer::load( $this->status_extra['commissionref'] ) : NULL, 'amount' => $this->status_extra['commissionamount'] ? new \IPS\nexus\Money( $this->status_extra['commissionamount'], $this->currency ) : NULL ); } /** * Check condition * * @param float $a First parameter * @param string $operator Operator (g = greater than, e = equal to, l = less than) * @param float $b Second parameter * @return bool */ protected function _checkCondition( $a, $operator, $b ) { $a = floatval( $a ); $b = floatval( $b ); switch ( $operator ) { case 'g': return $a > $b; case 'e': return $a == $b; case 'l': return $a < $b; } return FALSE; } /** * Get amount left to pay * * @return \IPS\nexus\Money */ public function amountToPay() { $amountToPay = $this->total; foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ) ) as $transaction ) { if ( $transaction->currency === $this->currency ) { $amountToPay->amount -= ( $transaction->amount->amount - ( $transaction->status === \IPS\nexus\Transaction::STATUS_PART_REFUNDED ? $transaction->partial_refund->amount : 0 ) ); } } return $amountToPay->round(); } /** * ACP Buttons * * @param string $ref Referer * @return array */ public function buttons( $ref='v' ) { $url = $this->acpUrl()->setQueryString( 'r', $ref ); $return = array(); if ( $this->status === \IPS\nexus\Invoice::STATUS_PENDING ) { if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) ) { $return['paid'] = array( 'title' => 'invoice_mark_paid', 'icon' => 'check', 'link' => $url->setQueryString( 'do', 'paid' ), 'data' => count( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW ) ) ) ? array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('invoice_mark_paid') ) : array( 'confirm' => '' ) ); } if ( count( \IPS\nexus\Gateway::manualChargeGateways() ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'chargetocard' ) ) { $return['card'] = array( 'title' => 'invoice_charge_to_card', 'icon' => 'credit-card', 'link' => $url->setQueryString( array( 'do' => 'card', '_new' => 1 ) ), ); } if ( isset( $this->member->cm_credits[ $this->currency ] ) and $this->member->cm_credits[ $this->currency ]->amount and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) ) { $return['credit'] = array( 'title' => 'invoice_charge_to_credit', 'icon' => 'money', 'link' => $url->setQueryString( 'do', 'credit' ), 'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('invoice_charge_to_credit') ) ); } } if ( $this->status !== \IPS\nexus\Invoice::STATUS_PAID and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_resend' ) ) { $return['resend'] = array( 'title' => 'invoice_reissue', 'icon' => 'refresh', 'link' => $url->setQueryString( 'do', 'resend' ), 'data' => array( 'confirm' => '', 'confirmSubMessage' => \IPS\Member::loggedIn()->language()->addToStack('invoice_reissue_confirm'), 'confirmType' => 'verify', 'confirmIcon' => 'question', 'confirmButtons' => json_encode( array( 'yes' => \IPS\Member::loggedIn()->language()->addToStack('invoice_reissue_yes'), 'no' => \IPS\Member::loggedIn()->language()->addToStack('invoice_reissue_no'), 'cancel' => \IPS\Member::loggedIn()->language()->addToStack('cancel'), ) ) ) ); } $return['print'] = array( 'title' => 'print', 'icon' => 'print', 'link' => $url->setQueryString( 'do', 'printout' ), ); if ( $this->status !== \IPS\nexus\Invoice::STATUS_CANCELED and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) ) { $return['unpaid'] = array( 'title' => $this->status === \IPS\nexus\Invoice::STATUS_PAID ? 'invoice_mark_unpaid' : 'cancel', 'icon' => 'times', 'link' => $url->setQueryString( 'do', 'unpaid' ), 'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('cancel') ) ); } if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_delete' ) ) { $return['delete'] = array( 'title' => 'delete', 'icon' => 'times-circle', 'link' => $url->setQueryString( 'do', 'delete' ), 'data' => array( 'confirm' => '', 'confirmSubMessage' => \IPS\Member::loggedIn()->language()->addToStack('invoice_delete_warning') ) ); } return $return; } /** * Can payment be split? Returns the minnimum split amount * * @return FALSE|float */ public function canSplitPayment() { if ( \IPS\Settings::i()->nexus_split_payments == -1 ) { return FALSE; } if ( \IPS\Settings::i()->nexus_split_payments ) { $decoded = json_decode( \IPS\Settings::i()->nexus_split_payments, TRUE ); if ( isset( $decoded[ $this->currency ] ) ) { if ( $decoded[ $this->currency ]['amount'] <= $this->amountToPay()->amount ) { return $decoded[ $this->currency ]['amount']; } } return FALSE; } else { return 0.01; } } /* !Related Resources */ /** * Get transactions * * @param array|null $statuses Statuses to get, or NULL for all * @return \IPS\Patterns\ActiveRecordIteator */ public function transactions( $statuses=NULL ) { $where = array(); $where[] = array( 't_invoice=?', $this->id ); if ( $statuses ) { $where[] = \IPS\Db::i()->in( 't_status', $statuses ); } return new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_transactions', $where ), 'IPS\nexus\Transaction' ); } /** * Get shipments * * @param array|null $statuses Statuses to get, or NULL for all * @return \IPS\Patterns\ActiveRecordIteator */ public function shipments( $statuses=NULL ) { $where = array(); $where[] = array( 'o_invoice=?', $this->id ); if ( $statuses ) { $where[] = \IPS\Db::i()->in( 'o_status', $statuses ); } return new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_ship_orders', $where ), 'IPS\nexus\Shipping\Order' ); } /** * Get purchases created by this invoice * * @return \IPS\Patterns\ActiveRecordIteator */ public function purchasesCreated() { return new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_purchases', array( 'ps_original_invoice=? AND ps_show=1', $this->id ) ), 'IPS\nexus\Purchase' ); } /* !Actions */ /** * Mark as paid * * @param \IPS\Member|NULL $member The member changing the status. Only set if the status is being manually changed by an admin. * @return void * @note It is the responsibility of the code calling this to log the action on the customer's account *before* calling this method */ public function markPaid( \IPS\Member $member = NULL ) { if ( $this->status !== static::STATUS_PAID ) { /* Create the member account if this was a guest */ if ( !$this->member->member_id and $this->guest_data ) { $profileFields = $this->guest_data['profileFields']; $memberToSave = new \IPS\nexus\Customer; foreach ( $this->guest_data['member'] as $k => $v ) { $memberToSave->_data[ $k ] = $v; $memberToSave->changed[ $k ] = $v; } $memberToSave->save(); $this->member = $memberToSave; $this->guest_data = NULL; $this->save(); /* If we've entered an address during checkout, save it */ if ( $this->billaddress !== NULL ) { $billing = new \IPS\nexus\Customer\Address; $billing->member = $this->member; $billing->address = $this->billaddress; $billing->primary_billing = 1; $billing->save(); } if ( $this->shipaddress !== NULL ) { $shipping = new \IPS\nexus\Customer\Address; $shipping->member = $this->member; $shipping->address = $this->shipaddress; $shipping->primary_shipping = 1; $shipping->save(); } $profileFields['member_id'] = $memberToSave->member_id; \IPS\Db::i()->replace( 'core_pfields_content', $profileFields ); /* Notify the incoming mail address */ if( \IPS\Settings::i()->new_reg_notify ) { \IPS\Email::buildFromTemplate( 'core', 'registration_notify', array( $memberToSave, $profileFields ) )->send( \IPS\Settings::i()->email_in ); } /* Update associated transactions */ \IPS\Db::i()->update( 'nexus_transactions', array( 't_member' => $this->member->member_id ), array( 't_invoice=? AND t_member=0', $this->id ) ); /* Did we use any coupons? */ foreach ( $this->items as $item ) { if ( $item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount ) { $coupon = \IPS\nexus\Coupon::load( $item->id ); $uses = $coupon->used_by ? json_decode( $coupon->used_by, TRUE ) : array(); if ( isset( $uses[ $this->member->email ] ) ) { $uses[ $this->member->member_id ] = $uses[ $this->member->email ]; unset( $uses[ $this->member->email ] ); } else { $uses[ $this->member->member_id ] = 1; } $coupon->used_by = json_encode( $uses ); $coupon->save(); } } } /* Init */ $extra = $this->status_extra; $shipOrders = array(); /* Loop items */ $purchases = array(); $setParentIds = array(); $groupParentIds = array(); $amountReceived = $this->total->amount; foreach ( $this->items as $k => $item ) { /* onPaid */ $item->onPaid( $this ); /* Create a purchase record */ $purchase = NULL; if( $item instanceof \IPS\nexus\Invoice\Item\Purchase ) { $showPurchaseRecord = $item->showPurchaseRecord(); for ( $i = 0; $i < $item->quantity; $i++ ) { /* Create a purchase record */ $purchase = new \IPS\nexus\Purchase; $purchase->member = $this->member; $purchase->name = $item->name; if ( $item->renewalTerm ) { $purchase->renewals = $item->renewalTerm; $purchase->expire = \IPS\DateTime::create()->add( $item->renewalTerm->interval ); if ( $item->renewalTerm->gracePeriod ) { $purchase->grace_period = \IPS\DateTime::create()->add( $item->renewalTerm->gracePeriod )->getTimestamp() - time(); } } if ( $item->expireDate ) { $purchase->expire = $item->expireDate; } $purchase->app = $item::$application; $purchase->type = $item::$type; $purchase->item_id = intval( $item->id ); $purchase->custom_fields = $item->details; $purchase->original_invoice = $this; if ( $item->payTo ) { $purchase->pay_to = $item->payTo; $purchase->commission = $item->commission; } if ( $item->parent !== NULL ) { if ( $item->parent instanceof \IPS\nexus\Purchase ) { $purchase->parent = $item->parent; } else { $setParentIds[ $k ] = $item->parent; if ( $item->groupWithParent ) { $groupParentIds[ $k ] = TRUE; } } } $purchase->extra = $item->extra; $purchase->show = (int) $showPurchaseRecord; $purchase->save(); if( $purchase->parent and $item->groupWithParent ) { $purchase->groupWithParent(); } /* Make a note of the ID */ if ( !isset( $purchases[ $k ] ) ) { $purchases[ $k ] = array(); } $purchases[ $k ][] = $purchase; /* Log */ if ( $showPurchaseRecord ) { $this->member->log( 'purchase', array( 'type' => 'new', 'id' => $purchase->id, 'name' => $purchase->name, 'invoice_id' => $this->id, 'invoice_title' => $this->title ), FALSE ); } } } /* Does it need shipping? */ if ( $item->physical ) { if ( !isset( $shipOrders[ $item->chosenShippingMethodId ] ) ) { $shipOrders[ $item->chosenShippingMethodId ] = array(); } $shipOrders[ $item->chosenShippingMethodId ][ $k ] = $item; } /* Is the money going to another member? */ if ( $item->payTo and !$item->payTo->inGroup( explode( ',', \IPS\Settings::i()->nexus_no_commission ) ) ) { $recipientAmounts = $item->recipientAmounts(); $credits = $item->payTo->cm_credits; $credits[ $this->currency ]->amount += $recipientAmounts['recipient_final']->amount; $item->payTo->cm_credits = $credits; $item->payTo->save(); $amountReceived -= $recipientAmounts['recipient_final']->amount; if ( !isset( $extra['commission'][ $item->payTo->member_id ] ) ) { $extra['commission'][ $item->payTo->member_id ] = array( 'amount' => 0 ); } $extra['commission'][ $item->payTo->member_id ]['amount'] += $recipientAmounts['recipient_final']->amount; $item->payTo->log( 'comission', array( 'type' => 'purchase', 'currency' => $this->currency, 'amount' => $recipientAmounts['recipient_final']->amount, 'new_amount' => $credits[ $this->currency ]->amount, 'member' => $this->member->member_id, 'id' => $purchase ? $purchase->id : 0, 'name' => $item->name ), FALSE ); \IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, $item, $recipientAmounts['recipient_final'] ) ) ->send( $item->payTo, array_map( function( $contact ) { return $contact->alt_id->email; }, iterator_to_array( $item->payTo->alternativeContacts( array( 'billing=1' ) ) ) ), ( ( in_array( 'commission_earned', explode( ',', \IPS\Settings::i()->nexus_notify_copy_types ) ) AND \IPS\Settings::i()->nexus_notify_copy_email ) ? explode( ',', \IPS\Settings::i()->nexus_notify_copy_email ) : array() ) ); } } /* Referral Commission */ if ( \IPS\Settings::i()->cm_ref_on and $commission = $this->commission( $amountReceived ) and $commission['amount'] ) { $credits = $commission['referrer']->cm_credits; $credits[ $this->currency ]->amount += $commission['amount']->amount; $commission['referrer']->cm_credits = $credits; $commission['referrer']->save(); if ( !isset( $extra['commission'][ $commission['referrer']->member_id ] ) ) { $extra['commission'][ $commission['referrer']->member_id ] = array( 'amount' => 0 ); } $extra['commission'][ $commission['referrer']->member_id ]['amount'] += $commission['amount']->amount; $commission['referrer']->log( 'comission', array( 'type' => 'invoice', 'currency' => $this->currency, 'amount' => $commission['amount']->amount, 'new_amount' => $credits[ $this->currency ]->amount, 'member' => $this->member->member_id, 'invoice_id' => $this->id, 'invoice_title' => $this->title ), FALSE ); $record = \IPS\Db::i()->select( '*', 'nexus_referrals', array( 'member_id=? AND referred_by=?', $this->member->member_id, $commission['referrer']->member_id ) )->first(); $amounts = $record['amount'] ? json_decode( $record['amount'], TRUE ) : array(); if ( !isset( $amounts[ $this->currency ] ) ) { $amounts[ $this->currency ] = 0; } $amounts[ $this->currency ] += $commission['amount']->amount; \IPS\Db::i()->update( 'nexus_referrals', array( 'amount' => json_encode( $amounts ) ), array( 'member_id=? AND referred_by=?', $this->member->member_id, $commission['referrer']->member_id ) ); \IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, NULL, $commission['amount'] ) ) ->send( $commission['referrer'], array_map( function( $contact ) { return $contact->alt_id->email; }, iterator_to_array( $commission['referrer']->alternativeContacts( array( 'billing=1' ) ) ) ), ( ( in_array( 'commission_earned', explode( ',', \IPS\Settings::i()->nexus_notify_copy_types ) ) AND \IPS\Settings::i()->nexus_notify_copy_email ) ? explode( ',', \IPS\Settings::i()->nexus_notify_copy_email ) : array() ) ); } /* Set parent IDs */ foreach ( $setParentIds as $k => $v ) { foreach ( $purchases[ $k ] as $k2 => $_purchase ) { $_purchase->parent = isset( $purchases[ $v ][ $k2 ] ) ? $purchases[ $v ][ $k2 ] : $purchases[ $v ][0]; $_purchase->save(); if( isset( $groupParentIds[ $k ] ) ) { $_purchase->groupWithParent(); } } } /* Run callbacks */ foreach ( $purchases as $_purchases ) { foreach ( $_purchases as $purchase ) { $purchase->onPurchaseGenerated( $this ); } } /* Create shipping orders */ foreach ( $shipOrders as $method => $items ) { $shipOrder = new \IPS\nexus\Shipping\Order; $shipOrder->invoice = $this; $shipOrder->data = array( 'cm_first_name' => $this->member->cm_first_name, 'cm_last_name' => $this->member->cm_last_name, 'address' => $this->shipaddress, 'cm_phone' => $this->member->cm_phone ); if ( $method ) { try { $shipOrder->method = \IPS\nexus\Shipping\FlatRate::load( $method ); } catch ( \OutOfRangeException $e ) { $shipOrder->api_service = $method; } } $shipOrder->items = $items; $shipOrder->save(); $this->member->log( 'shipping', array( 'type' => 'new', 'id' => $shipOrder->id, 'invoice_id' => $this->id, 'invoice_title' => $this->title ), FALSE ); } /* Refuse any pending transactions */ foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW ) ) as $transaction ) { if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PENDING ) { $transaction->delete(); } else { try { $transaction->method->void( $transaction ); } catch ( \Exception $e ){} $extra = $transaction->extra; $extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'note' => 'invoicePaid' ); $transaction->extra = $extra; $transaction->status = \IPS\nexus\Transaction::STATUS_REFUSED; $transaction->save(); } } /* Update invoice */ $this->status = static::STATUS_PAID; $this->paid = new \IPS\DateTime; /* Log */ if ( $member ) { $extra['type'] = 'manual'; $extra['setTo'] = static::STATUS_PAID; $extra['setByID'] = $member->member_id; $extra['date'] = time(); } /* Save */ $this->status_extra = $extra; $this->save(); } } /** * Mark as unpaid * * @param string|NULL $status The status to set to * @param \IPS\Member|NULL $member The member changing the status. Only set if the status is being manually changed by an admin. * @return void */ public function markUnpaid( $status, \IPS\Member $member = NULL ) { /* Do stuff */ if ( $this->status === static::STATUS_PAID ) { /* Loop items */ foreach ( $this->items as $k => $item ) { $item->onUnpaid( $this, $this->status ); /* Is the money going to another member? */ if ( $item->payTo and !$item->payTo->inGroup( explode( ',', \IPS\Settings::i()->nexus_no_commission ) ) ) { $recipientAmounts = $item->recipientAmounts(); $credits = $item->payTo->cm_credits; $credits[ $this->currency ]->amount -= $recipientAmounts['recipient_final']->amount; $item->payTo->cm_credits = $credits; $item->payTo->save(); $item->payTo->log( 'comission', array( 'type' => 'invoice_refund', 'currency' => $this->currency, 'amount' => $recipientAmounts['recipient_final']->amount, 'new_amount' => $credits[ $this->currency ]->amount, 'member' => $this->member->member_id, 'invoice_id' => $this->id, 'invoice_title' => $this->title ), FALSE ); \IPS\Email::buildFromTemplate( 'nexus', 'commissionRevoked', array( $this, $item, $recipientAmounts['recipient_final'] ) ) ->send( $item->payTo, array_map( function( $contact ) { return $contact->alt_id->email; }, iterator_to_array( $item->payTo->alternativeContacts( array( 'billing=1' ) ) ) ), ( ( in_array( 'commission_earned', explode( ',', \IPS\Settings::i()->nexus_notify_copy_types ) ) AND \IPS\Settings::i()->nexus_notify_copy_email ) ? explode( ',', \IPS\Settings::i()->nexus_notify_copy_email ) : array() ) ); } } /* Delete purchases */ foreach ( $this->purchasesCreated() as $purchase ) { $purchase->delete(); } /* Delete shipping orders */ foreach ( $this->shipments() as $shipment ) { $shipment->delete(); } } else { foreach ( $this->items as $k => $item ) { $item->onInvoiceCancel( $this ); } } /* Set the status */ $this->status = $status; /* Log */ if ( $member ) { $extra = $this->status_extra; $extra['type'] = 'manual'; $extra['setTo'] = $status; $extra['setByID'] = $member->member_id; $extra['date'] = time(); $this->status_extra = $extra; } /* Save */ $this->save(); } /** * What will happen if we mark this unpaid? * * @return array */ public function unpaidConsequences() { $return = array(); $purchases = $this->purchasesCreated(); if ( count( $purchases ) ) { $return['purchases'] = array(); foreach ( $purchases as $purchase ) { $return['purchases'][] = \IPS\Theme::i()->getTemplate('purchases')->link( $purchase ); } } $shipments = $this->shipments(); if ( count( $shipments ) ) { $return['shipments'] = array(); foreach ( $shipments as $shipment ) { if ( $shipment->status === \IPS\nexus\Shipping\Order::STATUS_SHIPPED ) { $return['shipments'][] = \IPS\Theme::i()->getTemplate('shiporders')->link( $shipment ); } else { $return['shipments'][] = array( 'message' => \IPS\Theme::i()->getTemplate('shiporders')->link( $shipment ), 'warning' => \IPS\Member::loggedIn()->language()->addToStack('invoice_unpaid_shipments_shipped'), ); } } } foreach ( $this->items as $item ) { if ( $item->payTo ) { $amount = $item->amountForRecipient(); $message = \IPS\Member::loggedIn()->language()->addToStack('account_credit_remove', FALSE, array( 'sprintf' => array( $amount, $item->payTo->cm_name ) ) ); $credits = $item->payTo->cm_credits; if ( ( $credits[ $amount->currency ]->amount - $amount->amount ) < 0 ) { $return[] = array( 'message' => $message, 'warning' => \IPS\Member::loggedIn()->language()->addToStack('account_credit_remove_neg') ); } else { $return[] = $message; } } if( count( $item->onUnpaidDescription( $this ) ) ) { foreach( $item->onUnpaidDescription( $this ) as $desc ) { $return[] = $desc; } } } return $return; } /** * Send the notification to the member that this invoice has been generated * * @return void */ public function sendNotification() { \IPS\Email::buildFromTemplate( 'nexus', 'newInvoice', array( $this, $this->summary() ) ) ->send( $this->member, array_map( function( $contact ) { return $contact->alt_id->email; }, iterator_to_array( $this->member->alternativeContacts( array( 'billing=1' ) ) ) ), ( ( in_array( 'new_invoice', explode( ',', \IPS\Settings::i()->nexus_notify_copy_types ) ) AND \IPS\Settings::i()->nexus_notify_copy_email ) ? explode( ',', \IPS\Settings::i()->nexus_notify_copy_email ) : array() ) ); } /* !URLS */ /** * Get checkout URL * * @return void */ public function checkoutUrl() { if ( !$this->id ) { $this->save(); } return \IPS\Http\Url::internal( 'app=nexus&module=checkout&controller=checkout&id=' . $this->id, 'front', 'nexus_checkout', array(), \IPS\Settings::i()->nexus_https ); } /** * @brief Cached URL */ protected $_url = NULL; /** * Get URL * * @return \IPS\Http\Url */ public function url() { if( $this->_url === NULL ) { $this->_url = \IPS\Http\Url::internal( "app=nexus&module=clients&controller=invoices&do=view&id={$this->id}", 'front', 'clientsinvoice' ); } return $this->_url; } /** * ACP "View Invoice" URL * * @return \IPS\Http\Url */ public function acpUrl() { return \IPS\Http\Url::internal( "app=nexus&module=payments&controller=invoices&do=view&id={$this->id}", 'admin' ); } }