Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+406 -115
View File
@@ -2,9 +2,9 @@
/**
* @brief Invoice Model
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @subpackage Nexus
* @since 10 Feb 2014
* @version SVN_VERSION_NUMBER
@@ -211,6 +211,12 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
try
{
$this->_member = \IPS\nexus\Customer::load( $this->_data['member'] );
/* \IPS\nexus\Customer does not necessarily throw an OutOfRangeException if passed a value of 0 (a guest instance is valid) - throw one here so we can bubble up to get the correct information for guests. */
if ( !$this->_member->member_id )
{
throw new \OutOfRangeException;
}
}
catch ( \OutOfRangeException $e )
{
@@ -274,13 +280,13 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
return is_array( $val ) and isset( $val['act'] );
} );
if ( !$itemsJson and $this->total->amount )
if ( !$itemsJson and $this->total->amount->isGreaterThanZero() )
{
$itemsJson = array( array(
'act' => 'charge',
'app' => 'nexus',
'type' => 'charge',
'cost' => $this->total->amount,
'cost' => $this->total->amountAsString(),
'tax' => NULL,
'quantity' => 1,
'itemName' => $this->title,
@@ -291,7 +297,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'extra' => NULL,
) );
}
/* Return it */
$this->_itemsIterator = new \IPS\nexus\Invoice\ItemsIterator( $itemsJson );
$this->_itemsIterator->currency = $this->currency;
@@ -470,14 +476,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
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;
}
return NULL;
}
}
@@ -514,6 +513,28 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$this->_data['guest_data'] = $data ? json_encode( $data ) : NULL;
}
/**
* Set a title automatically based on item names
*
* @return void
*/
public function setDefaultTitle()
{
$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;
}
/**
* Save Changed Columns
*
@@ -523,19 +544,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
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;
$this->setDefaultTitle();
}
if ( $this->_new )
@@ -556,9 +565,11 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*/
public function delete()
{
\IPS\File::unclaimAttachments( 'nexus_Purchases', NULL, NULL, "invoice-{$this->id}" );
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 )
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW, \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING ) ) as $transaction )
{
if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PENDING )
{
@@ -596,7 +607,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'act' => $item::$act,
'app' => isset( $item::$application ) ? $item::$application : $item->application,
'type' => isset( $item::$type ) ? $item::$type : $item->type,
'cost' => $item->price->amount,
'cost' => $item->price->amountAsString(),
'tax' => $item->tax ? $item->tax->id : NULL,
'quantity' => $item->quantity,
'itemName' => $item->name,
@@ -629,7 +640,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$renewTermData = $item->renewalTerm->getTerm();
$data['renew_term'] = $renewTermData['term'];
$data['renew_units'] = $renewTermData['unit'];
$data['renew_cost'] = $item->renewalTerm->cost->amount;
$data['renew_cost'] = $item->renewalTerm->cost->amountAsString();
if ( $item->renewalTerm->gracePeriod )
{
$data['grace_period'] = \IPS\DateTime::create()->add( $item->renewalTerm->gracePeriod )->getTimestamp() - time();
@@ -748,10 +759,61 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$return = array();
$return['items'] = array();
$shippingItems = array();
$subtotal = 0;
$subtotal = new \IPS\Math\Number('0');
$taxableAmounts = array();
$taxClasses = \IPS\nexus\Tax::roots();
/* Reduce taxable amount from coupons */
$couponTaxReductions = array();
foreach ( $this->items as $itemId => $item )
{
if ( $item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount )
{
if ( isset( $item->extra['type'] ) )
{
if ( isset( $item->extra['items'] ) )
{
$itemCount = count( $item->extra['items'] );
}
else
{
$itemCount = 0;
foreach ( clone $this->items as $k => $_item ) // We have to clone because $items is an iterator, and we're already looping them, but we need a separate loop
{
if ( $_item != $item and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\ShippingCharge ) and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount ) )
{
$itemCount++;
}
}
}
foreach ( clone $this->items as $k => $_item ) // We have to clone because $items is an iterator, and we're already looping them, but we need a separate loop
{
if ( $_item != $item and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount ) and !( $_item instanceof \IPS\nexus\extensions\nexus\Item\ShippingCharge ) and ( !isset( $item->extra['items'] ) or in_array( $k, $item->extra['items'] ) ) )
{
if ( $item->extra['type'] == 'p' )
{
$reductionAmount = $_item->linePrice()->amount->multiply( new \IPS\Math\Number("{$item->extra['value']}") )->divide( new \IPS\Math\Number('100') );
}
else
{
$reductionAmount = ( new \IPS\Math\Number( "{$item->extra['value']}" ) )->divide( new \IPS\Math\Number( "{$itemCount}" ) )->multiply( new \IPS\Math\Number( '-1' ) );
}
if ( isset( $couponTaxReductions[ $k ] ) )
{
$couponTaxReductions[ $k ] = $couponTaxReductions[ $k ]->add( $reductionAmount );
}
else
{
$couponTaxReductions[ $k ] = $reductionAmount;
}
}
}
}
}
}
/* Work out the line totals */
foreach ( $this->items as $k => $item )
{
@@ -761,16 +823,24 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
else
{
$subtotal += $item->linePrice()->amount;
$subtotal = $subtotal->add( $item->linePrice()->amount );
if ( isset( $item->tax ) )
{
$taxAmount = $item->linePrice()->amount;
if ( isset( $couponTaxReductions[ $k ] ) )
{
$taxAmount = $taxAmount->subtract( $couponTaxReductions[ $k ] );
}
if ( isset( $taxableAmounts[ $item->tax->id ] ) )
{
$taxableAmounts[ $item->tax->id ] += $item->linePrice()->amount;
$taxableAmounts[ $item->tax->id ] = $taxableAmounts[ $item->tax->id ]->add( $taxAmount );
}
else
{
$taxableAmounts[ $item->tax->id ] = $item->linePrice()->amount;
$taxableAmounts[ $item->tax->id ] = $taxAmount;
}
}
@@ -783,17 +853,17 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
/* Add shipping */
$return['shipping'] = array();
$shipping = 0;
$shipping = new \IPS\Math\Number('0');
foreach ( $shippingItems as $k => $item )
{
$linePrice = $item->linePrice()->amount;
$shipping += $linePrice;
$shipping = $shipping->add( $linePrice );
if ( isset( $item->tax ) )
{
if ( isset( $taxableAmounts[ $item->tax->id ] ) )
{
$taxableAmounts[ $item->tax->id ] += $linePrice;
$taxableAmounts[ $item->tax->id ] = $taxableAmounts[ $item->tax->id ]->add( $linePrice );
}
else
{
@@ -807,29 +877,45 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
/* Now work out the tax */
$return['tax'] = array();
$tax = 0;
$tax = new \IPS\Math\Number('0');
foreach ( $taxableAmounts as $taxId => $amount )
{
$rate = $taxClasses[ $taxId ]->rate( $this->billaddress );
$amount = $amount * $rate;
$amount = $amount->multiply( new \IPS\Math\Number( $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;
$tax = $tax->add( $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['total'] = new \IPS\nexus\Money( $subtotal->add( $shipping )->add( $tax ), $this->currency );
return $return;
}
/* !Information */
/**
* Does the invoice contain items that require a billing address?
*
* @return bool
*/
public function hasItemsRequiringBillingAddress()
{
foreach ( $this->items as $item )
{
if ( $item->requiresBillingAddress() )
{
return TRUE;
}
}
return FALSE;
}
/**
* Does the invoice contain physical items that need shipping?
*
@@ -881,23 +967,23 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
if ( !isset( $recipients[ $item->payTo->member_id ] ) )
{
$recipients[ $item->payTo->member_id ] = 0;
$recipients[ $item->payTo->member_id ] = new \IPS\Math\Number('0');
}
$itemRecipientAmounts = $item->recipientAmounts();
$recipients[ $item->payTo->member_id ] += $itemRecipientAmounts['recipient_final']->amount;
$recipients[ $item->payTo->member_id ] = $recipients[ $item->payTo->member_id ]->add( $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
* @param \IPS\Math\Number $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 )
public function commission( \IPS\Math\Number $amountReceived )
{
if ( !isset( $this->status_extra['commissionrule'] ) )
{
@@ -917,19 +1003,19 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$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;
$maxAmount = new \IPS\Math\Number('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 )
if ( $amountReceived->percentage( $commissionRule->commission )->compare( $maxAmount ) === -1 )
{
continue;
}
if ( $commissionRule->commission_limit )
{
$commisionLimit = json_decode( $commissionRule->commission_limit, TRUE );
if ( isset( $commisionLimit[ $this->currency ] ) and $commisionLimit[ $this->currency ]['amount'] <= $maxAmount )
if ( isset( $commisionLimit[ $this->currency ] ) and ( new \IPS\Math\Number( number_format( $commisionLimit[ $this->currency ]['amount'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) ) )->compare( $maxAmount ) === -1 )
{
continue;
}
@@ -984,7 +1070,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
if ( $commissionRule->purchase_package_limit )
{
$amountToApplyCommissionOn = 0;
$amountToApplyCommissionOn = new \IPS\Math\Number('0');
}
$itemsToCount = array();
@@ -995,7 +1081,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
$packageId = $item->id;
}
elseif ( $commissionRule->purchase_renewal and $item instanceof \IPS\nexus\Invoice\Item\Renewal and $item->application == 'nexus' and $item->type == 'package' )
elseif ( $commissionRule->purchase_renewal and $item instanceof \IPS\nexus\Invoice\Item\Renewal and $item::$application == 'nexus' and $item::$type == 'package' )
{
try
{
@@ -1017,14 +1103,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
if ( $commissionRule->purchase_package_limit )
{
if ( isset( $amountReceivedForItem[ $k ] ) )
{
$amountToApplyCommissionOn += $amountReceivedForItem[ $k ];
}
else
{
$amountToApplyCommissionOn += ( $item->price->amount * $item->quantity );
}
$amountToApplyCommissionOn = $amountToApplyCommissionOn->add( $item->price->amount->multiply( new \IPS\Math\Number( "{$item->quantity}" ) ) );
}
}
@@ -1049,18 +1128,18 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
/* If we're still here, we're applying commission */
$amountToGive = round( ( ( $amountToApplyCommissionOn / 100 ) * $commissionRule->commission ), 2 );
$amountToGive = $amountToApplyCommissionOn->percentage( $commissionRule->commission );
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'] )
if ( isset( $limit[ $this->currency ] ) and $amountToGive->compare( new \IPS\Math\Number( number_format( $limit[ $this->currency ]['amount'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) ) ) === 1 )
{
$amountToGive = $limit[ $this->currency ]['amount'];
$amountToGive = new \IPS\Math\Number( number_format( $limit[ $this->currency ]['amount'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) );
}
}
/* Is this more than what we have? */
if ( ( $amountToGive > $maxAmount ) or ( $amountToGive and !$maxAmount ) )
if ( ( $amountToGive->compare( $maxAmount ) === 1 ) or ( $amountToGive->isGreaterThanZero() and !$maxAmount->isGreaterThanZero() ) )
{
$maxAmount = $amountToGive;
$actingRule = $commissionRule;
@@ -1086,11 +1165,25 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$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
);
try
{
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
);
}
catch( \OutOfRangeException $e )
{
/* If either the commission rule, or the commission ref, are no longer valid, then let's try again and see if it matches up with a different one. */
$extra = $this->status_extra;
unset( $extra['commissionrule'], $extra['commissionref'], $extra['commissionamount'] );
$this->status_extra = $extra;
$this->save();
return $this->commission( $amountReceived );
}
}
/**
@@ -1103,17 +1196,44 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*/
protected function _checkCondition( $a, $operator, $b )
{
$a = floatval( $a );
$b = floatval( $b );
if ( $a instanceof \IPS\Math\Number and !( $b instanceof \IPS\Math\Number ) )
{
$b = new \IPS\Math\Number( "{$b}" );
}
if ( !( $a instanceof \IPS\Math\Number ) and $b instanceof \IPS\Math\Number )
{
$a = new \IPS\Math\Number( "{$a}" );
}
switch ( $operator )
{
case 'g':
return $a > $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 1;
}
else
{
return $a > $b;
}
case 'e':
return $a == $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 0;
}
else
{
return $a == $b;
}
case 'l':
return $a < $b;
if ( $a instanceof \IPS\Math\Number )
{
return $a->compare( $b ) === 11;
}
else
{
return $a < $b;
}
}
return FALSE;
}
@@ -1123,19 +1243,33 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*
* @return \IPS\nexus\Money
*/
public function amountToPay()
public function amountToPay( $deductHeldTransactions=FALSE )
{
$amountToPay = $this->total;
$amountToPay = $this->total;
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ) ) as $transaction )
$statuses = array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED );
if ( $deductHeldTransactions )
{
$statuses[] = \IPS\nexus\Transaction::STATUS_HELD;
$statuses[] = \IPS\nexus\Transaction::STATUS_REVIEW;
$statuses[] = \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING;
}
foreach ( $this->transactions( $statuses ) 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 ) );
$amountToSubtract = $transaction->amount->amount;
if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PART_REFUNDED )
{
$amountToSubtract = $amountToSubtract->subtract( $transaction->partial_refund->amount );
}
$amountToPay->amount = $amountToPay->amount->subtract( $amountToSubtract );
}
}
return $amountToPay->round();
return $amountToPay;
}
/**
@@ -1149,6 +1283,15 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$url = $this->acpUrl()->setQueryString( 'r', $ref );
$return = array();
if ( $this->status !== \IPS\nexus\Invoice::STATUS_PAID and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_add' ) )
{
$return['edit'] = array(
'title' => 'edit',
'icon' => 'pencil',
'link' => $url->setQueryString( array( 'do' => 'generate', '_new' => 1 ) )
);
}
if ( $this->status === \IPS\nexus\Invoice::STATUS_PENDING )
{
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
@@ -1157,7 +1300,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'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' => '' )
'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, \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING ) ) ) ? 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' ) )
@@ -1168,7 +1311,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'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' ) )
if ( isset( $this->member->cm_credits[ $this->currency ] ) and $this->member->cm_credits[ $this->currency ]->amount->isGreaterThanZero() and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
{
$return['credit'] = array(
'title' => 'invoice_charge_to_credit',
@@ -1200,6 +1343,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$return['print'] = array(
'title' => 'print',
'icon' => 'print',
'target' => '_blank',
'link' => $url->setQueryString( 'do', 'printout' ),
);
if ( $this->status !== \IPS\nexus\Invoice::STATUS_CANCELED and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'invoices_edit' ) )
@@ -1253,6 +1397,81 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
return 0.01;
}
}
/**
* Get output for API
*
* @return array
* @apiresponse int id ID number
* @apiresponse string title Title
* @apiresponse string status 'paid' = Paid; 'pend' = Pending; 'expd' = Expired; 'canc' = Cancelled
* @apiresponse datetime issueDate The date the invoice was issued
* @apiresponse datetime paidDate The date the invoice was paid
* @apiresponse \IPS\nexus\Customer customer Customer
* @apiresponse [\IPS\nexus\Invoice\Item] items Items
* @apiresponse \IPS\nexus\Money subTotal Subtotal of item costs
* @apiresponse [\IPS\nexus\Invoice\Item] shippingCharges Shipping charges
* @apiresponse \IPS\nexus\Money shippingTotal Total cost of shipping charges
* @apiresponse [\IPS\nexus\Invoice\Item\TaxItem] tax Tax charges
* @apiresponse \IPS\nexus\Money taxTotal Total cost of tax charges
* @apiresponse \IPS\nexus\Money total Total
* @apiresponse \IPS\nexus\Money outstanding The outstanding amount to be paid
* @apiresponse \IPS\GeoLocation billingAddress Billing address
* @apiresponse \IPS\GeoLocation shippingAddress Shipping Address
* @apiresponse string poNumber PO Number (set by customer)
* @apiresponse string notes Notes (set by customer)
* @apiresponse [\IPS\nexus\Transaction] transactions Transactions against this invoice
* @apiresponse [\IPS\nexus\Shipping\Order] shipments Shipments generated from this invoice (if paid)
* @apiresponse [\IPS\nexus\Purchase] purchases Purchases generated from this invoice (if paid)
* @apiresponse string viewUrl The URL to where the customer can view this invoice
* @apiresponse string checkoutUrl The URL to where the customer can pay this invoice
*/
public function apiOutput()
{
$summary = $this->summary();
$tax = array();
foreach ( $summary['tax'] as $classId => $data )
{
$tax[] = ( new \IPS\nexus\Invoice\Item\TaxItem( $classId, $data ) )->apiOutput();
}
return array(
'id' => $this->id,
'title' => $this->title,
'status' => $this->status,
'issueDate' => $this->date->rfc3339(),
'paidDate' => $this->paid ? $this->paid->rfc3339() : null,
'customer' => $this->member->apiOutput(),
'items' => array_map( function( $item ){
return $item->apiOutput();
}, $summary['items'] ),
'subTotal' => $summary['subtotal']->apiOutput(),
'shippingCharges' => array_map( function( $item ){
return $item->apiOutput();
}, $summary['shipping'] ),
'shippingTotal' => $summary['shippingTotal']->apiOutput(),
'tax' => $tax,
'taxTotal' => $summary['taxTotal']->apiOutput(),
'total' => $summary['total']->apiOutput(),
'outstanding' => $this->amountToPay()->apiOutput(),
'billingAddress' => $this->billaddress ? $this->billaddress->apiOutput() : null,
'shippingAddress' => $this->shipaddress ? $this->shipaddress->apiOutput() : null,
'poNumber' => $this->po,
'notes' => $this->notes,
'transactions' => array_map( function( $transaction ){
return $transaction->apiOutput();
}, iterator_to_array( $this->transactions() ) ),
'shipments' => array_map( function( $shipment ){
return $shipment->apiOutput();
}, iterator_to_array( $this->shipments() ) ),
'purchases' => array_map( function( $purchase ){
return $purchase->apiOutput();
}, iterator_to_array( $this->purchasesCreated() ) ),
'viewUrl' => (string) $this->url(),
'checkoutUrl' => (string) $this->checkoutUrl(),
);
}
/* !Related Resources */
@@ -1260,11 +1479,11 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
* Get transactions
*
* @param array|null $statuses Statuses to get, or NULL for all
* @param array $where Initial where clause
* @return \IPS\Patterns\ActiveRecordIteator
*/
public function transactions( $statuses=NULL )
public function transactions( $statuses=NULL, $where = array() )
{
$where = array();
$where[] = array( 't_invoice=?', $this->id );
if ( $statuses )
{
@@ -1312,25 +1531,56 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
* @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 )
if ( !$this->member->member_id and $this->guest_data and $this->requiresLogin() )
{
$profileFields = $this->guest_data['profileFields'];
$securityAnswers = isset( $this->guest_data['securityAnswers'] ) ? $this->guest_data['securityAnswers'] : array();
$memberToSave = new \IPS\nexus\Customer;
foreach ( $this->guest_data['member'] as $k => $v )
/* If the member has already been created (the user made two separate purchases with the same data), use that, otherwise create one */
try
{
$memberToSave->_data[ $k ] = $v;
$memberToSave->changed[ $k ] = $v;
$memberToSave = \IPS\nexus\Customer::load( $this->guest_data['member']['email'], 'email' );
}
catch ( \OutOfRangeException $e )
{
$memberToSave = new \IPS\nexus\Customer;
foreach ( $this->guest_data['member'] as $k => $v )
{
$memberToSave->_data[ $k ] = $v;
$memberToSave->changed[ $k ] = $v;
}
$memberToSave->save();
}
$memberToSave->save();
$this->member = $memberToSave;
$this->guest_data = NULL;
$this->save();
/* If we provided answers to security questions in check out, save those */
if ( \IPS\Settings::i()->security_questions_enabled and in_array( \IPS\Settings::i()->security_questions_prompt, array( 'register', 'optional' ) ) )
{
if ( $securityAnswers )
{
foreach ( $securityAnswers as $k => $v )
{
\IPS\Db::i()->insert( 'core_security_answers', array(
'answer_question_id' => $k,
'answer_member_id' => $this->member->member_id,
'answer_answer' => $v
) );
}
$this->member->members_bitoptions['has_security_answers'] = TRUE;
}
else
{
$this->member->members_bitoptions['security_questions_opt_out'] = TRUE;
}
$this->member->save();
}
/* If we've entered an address during checkout, save it */
if ( $this->billaddress !== NULL )
{
@@ -1353,13 +1603,18 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$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 )
/* Validation */
if ( \IPS\Settings::i()->nexus_checkreg_validate or !$this->total->amount->isGreaterThanZero() )
{
\IPS\Email::buildFromTemplate( 'core', 'registration_notify', array( $memberToSave, $profileFields ) )->send( \IPS\Settings::i()->email_in );
$this->member->postRegistration( FALSE, TRUE );
}
else if( \IPS\Settings::i()->new_reg_notify )
{
\IPS\Email::buildFromTemplate( 'core', 'registration_notify', array( $memberToSave, $profileFields ), \IPS\Email::TYPE_LIST )->send( \IPS\Settings::i()->email_in );
}
/* Update associated transactions */
/* Update associated transactions */
\IPS\Db::i()->update( 'nexus_billing_agreements', array( 'ba_member' => $this->member->member_id ), array( 'ba_id IN(?)', \IPS\Db::i()->select( 't_billing_agreement', 'nexus_transactions', array( 't_billing_agreement>0 AND t_invoice=? AND t_member=0', $this->id ) ) ) );
\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? */
@@ -1388,6 +1643,13 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$extra = $this->status_extra;
$shipOrders = array();
/* Find any billing agreements */
$billingAgreement = NULL;
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ), array( array( 't_billing_agreement IS NOT NULL' ) ) ) as $transaction )
{
$billingAgreement = $transaction->billing_agreement;
}
/* Loop items */
$purchases = array();
$setParentIds = array();
@@ -1418,6 +1680,10 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
{
$purchase->grace_period = \IPS\DateTime::create()->add( $item->renewalTerm->gracePeriod )->getTimestamp() - time();
}
if ( $billingAgreement )
{
$purchase->billing_agreement = $billingAgreement;
}
}
if ( $item->expireDate )
{
@@ -1463,6 +1729,14 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
$purchases[ $k ][] = $purchase;
/* Claim attachments */
foreach ( \IPS\Db::i()->select( '*', 'core_attachments_map', array( 'location_key=? AND id1=? AND id3=?', 'nexus_Purchases', $k, "invoice-{$this->id}" ) ) as $attachMap )
{
$attachMap['id1'] = $purchase->id;
$attachMap['id3'] = 'purchase';
\IPS\Db::i()->insert( 'core_attachments_map', $attachMap );
}
/* Log */
if ( $showPurchaseRecord )
{
@@ -1493,16 +1767,16 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$recipientAmounts = $item->recipientAmounts();
$credits = $item->payTo->cm_credits;
$credits[ $this->currency ]->amount += $recipientAmounts['recipient_final']->amount;
$credits[ $this->currency ]->amount = $credits[ $this->currency ]->amount->add( $recipientAmounts['recipient_final']->amount );
$item->payTo->cm_credits = $credits;
$item->payTo->save();
$amountReceived -= $recipientAmounts['recipient_final']->amount;
$amountReceived = $amountReceived->subtract( $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 ] = array( 'amount' => new \IPS\Math\Number('0') );
}
$extra['commission'][ $item->payTo->member_id ]['amount'] += $recipientAmounts['recipient_final']->amount;
$extra['commission'][ $item->payTo->member_id ]['amount'] = $extra['commission'][ $item->payTo->member_id ]['amount']->add( $recipientAmounts['recipient_final']->amount );
$item->payTo->log( 'comission', array(
'type' => 'purchase',
@@ -1514,7 +1788,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'name' => $item->name
), FALSE );
\IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, $item, $recipientAmounts['recipient_final'] ) )
\IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, $item, $recipientAmounts['recipient_final'] ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$item->payTo,
array_map(
@@ -1533,15 +1807,16 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
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;
$credits[ $this->currency ]->amount = $credits[ $this->currency ]->amount->add( $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 ] = array( 'amount' => new \IPS\Math\Number('0') );
}
$extra['commission'][ $commission['referrer']->member_id ]['amount'] += $commission['amount']->amount;
$extra['commission'][ $commission['referrer']->member_id ]['amount'] = $extra['commission'][ $commission['referrer']->member_id ]['amount']->add( $commission['amount']->amount );
$commission['referrer']->log( 'comission', array(
'type' => 'invoice',
@@ -1557,12 +1832,16 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$amounts = $record['amount'] ? json_decode( $record['amount'], TRUE ) : array();
if ( !isset( $amounts[ $this->currency ] ) )
{
$amounts[ $this->currency ] = 0;
$amounts[ $this->currency ] = new \IPS\Math\Number('0');
}
$amounts[ $this->currency ] += $commission['amount']->amount;
elseif ( !( $amounts[ $this->currency ] instanceof \IPS\Math\Number ) )
{
$amounts[ $this->currency ] = new \IPS\Math\Number( number_format( $amounts[ $this->currency ], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ) );
}
$amounts[ $this->currency ] = $amounts[ $this->currency ]->add( $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'] ) )
\IPS\Email::buildFromTemplate( 'nexus', 'commissionEarned', array( $this, NULL, $commission['amount'] ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$commission['referrer'],
array_map(
@@ -1576,6 +1855,18 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
);
}
/* Change commission values back to numbers */
if ( isset( $extra['commission'] ) )
{
foreach ( $extra['commission'] as $memberId => $data )
{
if ( $data['amount'] and $data['amount'] instanceof \IPS\Math\Number )
{
$extra['commission'][ $memberId ]['amount'] = (string) $data['amount'];
}
}
}
/* Set parent IDs */
foreach ( $setParentIds as $k => $v )
{
@@ -1629,7 +1920,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
/* 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 )
foreach ( $this->transactions( array( \IPS\nexus\Transaction::STATUS_PENDING, \IPS\nexus\Transaction::STATUS_WAITING, \IPS\nexus\Transaction::STATUS_HELD, \IPS\nexus\Transaction::STATUS_REVIEW, \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING ) ) as $transaction )
{
if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PENDING )
{
@@ -1643,9 +1934,9 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
}
catch ( \Exception $e ){}
$extra = $transaction->extra;
$extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'note' => 'invoicePaid' );
$transaction->extra = $extra;
$textra = $transaction->extra;
$textra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'note' => 'invoicePaid' );
$transaction->extra = $textra;
$transaction->status = \IPS\nexus\Transaction::STATUS_REFUSED;
$transaction->save();
}
@@ -1693,7 +1984,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$recipientAmounts = $item->recipientAmounts();
$credits = $item->payTo->cm_credits;
$credits[ $this->currency ]->amount -= $recipientAmounts['recipient_final']->amount;
$credits[ $this->currency ]->amount = $credits[ $this->currency ]->amount->subtract( $recipientAmounts['recipient_final']->amount );
$item->payTo->cm_credits = $credits;
$item->payTo->save();
@@ -1707,7 +1998,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
'invoice_title' => $this->title
), FALSE );
\IPS\Email::buildFromTemplate( 'nexus', 'commissionRevoked', array( $this, $item, $recipientAmounts['recipient_final'] ) )
\IPS\Email::buildFromTemplate( 'nexus', 'commissionRevoked', array( $this, $item, $recipientAmounts['recipient_final'] ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$item->payTo,
array_map(
@@ -1807,7 +2098,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
$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 )
if ( !$credits[ $amount->currency ]->amount->subtract( $amount->amount )->isPositive() )
{
$return[] = array( 'message' => $message, 'warning' => \IPS\Member::loggedIn()->language()->addToStack('account_credit_remove_neg') );
}
@@ -1836,7 +2127,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
*/
public function sendNotification()
{
\IPS\Email::buildFromTemplate( 'nexus', 'newInvoice', array( $this, $this->summary() ) )
\IPS\Email::buildFromTemplate( 'nexus', 'newInvoice', array( $this, $this->summary() ), \IPS\Email::TYPE_TRANSACTIONAL )
->send(
$this->member,
array_map(