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(
@@ -2,9 +2,9 @@
/**
* @brief Invoice Item Class for Charges
* @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
+131 -24
View File
@@ -2,9 +2,9 @@
/**
* @brief Invoice Abstract Item Class
* @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
@@ -109,6 +109,11 @@ abstract class _Item
*/
public $payTo;
/**
* @brief Application
*/
public static $application;
/**
* @brief Commission percentage
*/
@@ -124,6 +129,11 @@ abstract class _Item
*/
public $extra;
/**
* @brief Group With Parent
*/
public $groupWithParent = FALSE;
/**
* Constructor
*
@@ -134,7 +144,7 @@ abstract class _Item
public function __construct( $name, \IPS\nexus\Money $price )
{
$this->name = $name;
$this->price = $price->round();
$this->price = $price;
}
/**
@@ -156,27 +166,40 @@ abstract class _Item
*/
public function linePrice()
{
return new \IPS\nexus\Money( $this->price->amount * $this->quantity, $this->price->currency );
return new \IPS\nexus\Money( $this->price->amount->multiply( new \IPS\Math\Number("{$this->quantity}") ), $this->price->currency );
}
/**
* Get tax rate
*
* @return float
* @param \IPS\GeoLocation|NULL $location Location to use for tax rate or NULL to use specified billing address
* @return \IPS\Math\Number
*/
public function taxRate()
public function taxRate( \IPS\GeoLocation $location = NULL )
{
return ( $this->tax ? $this->tax->rate( $this->billaddress ) : 0 );
return new \IPS\Math\Number( $this->tax ? $this->tax->rate( $location ?: $this->billaddress ) : '0' );
}
/**
* Get item price with tax
*
* @param \IPS\GeoLocation|NULL $location Location to use for tax rate or NULL to use specified billing address
* @return \IPS\nexus\Money
*/
public function grossPrice( \IPS\GeoLocation $location = NULL )
{
return new \IPS\nexus\Money( $this->price->amount->add( $this->price->amount->multiply( $this->taxRate( $location ) ) ), $this->price->currency );
}
/**
* Get line price with tax
*
* @param \IPS\GeoLocation|NULL $location Location to use for tax rate or NULL to use specified billing address
* @return \IPS\nexus\Money
*/
public function grossLinePrice()
public function grossLinePrice( \IPS\GeoLocation $location = NULL )
{
return new \IPS\nexus\Money( $this->linePrice()->amount + ( $this->linePrice()->amount * $this->taxRate() ), $this->price->currency );
return new \IPS\nexus\Money( $this->linePrice()->amount->add( $this->linePrice()->amount->multiply( $this->taxRate( $location ) ) ), $this->price->currency );
}
/**
@@ -192,20 +215,21 @@ abstract class _Item
{
$linePrice = $this->linePrice();
$currency = $linePrice->currency;
$commission = $this->price->amount->percentage( $this->commission );
$lineComission = $commission->multiply( new \IPS\Math\Number("{$this->quantity}") );
$commission = ( new \IPS\nexus\Money( ( $this->price->amount / 100 ) * $this->commission, $currency ) );
$return['site_commission_unit'] = $commission->round();
$return['site_commission_line'] = new \IPS\nexus\Money( $return['site_commission_unit']->amount * $this->quantity, $currency );
$return['site_commission_unit'] = new \IPS\nexus\Money( $commission, $currency );
$return['site_commission_line'] = new \IPS\nexus\Money( $lineComission, $currency );
$return['recipient_unit'] = new \IPS\nexus\Money( $this->price->amount - $return['site_commission_unit']->amount, $currency );
$return['recipient_line'] = new \IPS\nexus\Money( $linePrice->amount - $return['site_commission_line']->amount, $currency );
$return['recipient_unit'] = new \IPS\nexus\Money( $this->price->amount->subtract( $return['site_commission_unit']->amount ), $currency );
$return['recipient_line'] = new \IPS\nexus\Money( $linePrice->amount->subtract( $return['site_commission_line']->amount ), $currency );
$return['site_total'] = new \IPS\nexus\Money( $return['site_commission_line']->amount + ( $this->fee ? $this->fee->amount : 0 ), $currency );
$return['recipient_final'] = new \IPS\nexus\Money( $linePrice->amount - $return['site_total']->amount, $currency );
if ( $return['recipient_final']->amount < 0 )
{
$return['recipient_final']->amount = 0;
}
$fee = $this->fee ? $this->fee->amount : new \IPS\Math\Number('0');
$siteTotal = $return['site_commission_line']->amount->add( $fee );
$recipientTotal = $linePrice->amount->subtract( $siteTotal );
$return['site_total'] = new \IPS\nexus\Money( $siteTotal, $currency );
$return['recipient_final'] = new \IPS\nexus\Money( $recipientTotal->isGreaterThanZero() ? $recipientTotal : 0, $currency );
}
else
{
@@ -228,10 +252,8 @@ abstract class _Item
throw new \BadMethodCallException;
}
$linePrice = $this->linePrice();
$amountForSite = ( $linePrice->amount / 100 ) * $this->commission;
$amountForRecipient = ( new \IPS\nexus\Money( $linePrice->amount - $amountForSite, $linePrice->currency ) );
return $amountForRecipient->round();
$recipientAmount = $this->recipientAmounts();
return $recipientAmount['recipient_final'];
}
/**
@@ -289,6 +311,32 @@ abstract class _Item
}
/**
* Check for member
* If a user initially checks out as a guest and then logs in during checkout, this method
* is ran to check the items they are purchasing can be bought.
* Is expected to throw a DomainException with an error message to display to the user if not valid
*
* @param \IPS\Member $member The new member
* @return void
* @throws \DomainException
*/
public function memberCanPurchase( \IPS\Member $member )
{
}
/**
* Requires Billing Address
*
* @return bool
* @throws \DomainException
*/
public function requiresBillingAddress()
{
return in_array( 'other', explode( ',', \IPS\Settings::i()->nexus_require_billing ) );
}
/**
* Client Area URL
*
@@ -308,4 +356,63 @@ abstract class _Item
{
return NULL;
}
/**
* Get output for API
*
* @return array
* @apiresponse string name Item name
* @apiresponse string itemApp Key for application. For example, 'nexus' for products and renewals; 'downloads' for Downloads files
* @apiresponse string itemType Key for item type. For example, 'package' for products; 'file' for Downloads files.
* @apiresponse int itemId The ID for the item. For example, the product ID or the file ID.
* @apiresponse string itemUrl Any relevant URL (for example, for Downloads files, this will be the URL to view the file)
* @apiresponse string itemImage If the item has a relevant image (for exmaple, product image, Downloads file screenshot), the URL to it
* @apiresponse int quantity The quantity being purchased
* @apiresponse \IPS\nexus\Money itemPrice Item price, before tax
* @apiresponse \IPS\nexus\Money linePrice Line price, before tax
* @apiresponse \IPS\nexus\Tax taxClassId If the item should be taxed, the Tax Class that applies
* @apiresponse bool physical If the item is physical
* @apiresponse float weight If the item is physical, the weight in kilograms
* @apiresponse float length If the item is physical, the length in metres
* @apiresponse float width If the item is physical, the width in metres
* @apiresponse float height If the item is physical, the height in metres
* @apiresponse \IPS\nexus\Purchase\RenewalTerm renewalTerm If the item renews, the renewal term
* @apiresponse datetime expireDate If the item has been set to expire at a certain date but not automatically renew, the dare it will expire
* @apiresponse object details The values for any custom package fields
* @apiresponse \IPS\nexus\Purchase parentPurchase If when the item has been purchased it will be a child of an existing purchase, the parent purchase
* @apiresponse int parentItem If when the item has been purchased it will be a child of another item on the same invoice, the ID number of the item that will be the parent
* @apiresponse bool groupWithParent If when the item has been purchased it will have its renewals grouped with its parent
* @apiresponse \IPS\nexus\Customer payTo If the payment for this item goes to another user (for example for Downloads files), the user who will receive the payment
* @apiresonse float commission If the payment for this item goes to another user (for example for Downloads files), the percentage of the price that will be retained by the site (in addition to fee)
* @apiresonse \IPS\nexus\Money fee If the payment for this item goes to another user (for example for Downloads files), the fee that will be deducted by the site (in addition to commission)
*/
public function apiOutput()
{
return array(
'name' => $this->name,
'itemApp' => $this->appKey,
'itemType' => $this->typeKey,
'itemId' => $this->id,
'itemUrl' => $this->url() ? ( (string) $this->url() ) : null,
'itemImage' => $this->image() ? ( (string) $this->image()->url ) : null,
'quantity' => $this->quantity,
'itemPrice' => $this->price->apiOutput(),
'linePrice' => $this->linePrice()->apiOutput(),
'taxClass' => $this->tax ? $this->tax->apiOutput() : null,
'physical' => $this->physical,
'weight' => isset( $this->weight ) ? $this->weight->kilograms : null,
'length' => isset( $this->length ) ? $this->length->metres : null,
'width' => isset( $this->width ) ? $this->width->metres : null,
'height' => isset( $this->height ) ? $this->height->metres : null,
'renewalTerm' => isset( $this->renewalTerm ) ? $this->renewalTerm->apiOutput() : null,
'expireDate' => isset( $this->expireDate ) ? $this->expireDate->rfc3339() : null,
'details' => $this->details,
'parentPurchase' => ( isset( $this->parent ) and $this->parent instanceof \IPS\nexus\Purchase ) ? $this->parent->apiOutput() : null,
'parentItem' => ( isset( $this->parent ) and is_int( $this->parent ) ) ? $this->parent : null,
'groupWithParent' => isset( $this->groupWithParent ) ? $this->groupWithParent : false,
'payTo' => $this->payTo ? $this->payTo->apiOutput() : null,
'commission' => $this->commission,
'fee' => $this->fee ? $this->fee->apiOutput() : null,
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Invoice Item Class for Purchases
* @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
@@ -179,16 +179,20 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
{
$form->add( new \IPS\Helpers\Form\Text( 'ps_name', $purchase->_name, TRUE, array( 'maxLength' => 128 ) ) );
if ( !$purchase->grouped_renewals )
if ( !$purchase->grouped_renewals and ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled ) )
{
$form->add( new \IPS\Helpers\Form\Date( 'ps_expire', $purchase->expire ?: 0, FALSE, array( 'unlimited' => 0, 'unlimitedLang' => 'does_not_expire', 'disabled' => !$purchase->canChangeExpireDate() ) ) );
}
$form->add( new \IPS\nexus\Form\RenewalTerm( 'ps_renewals', $renewals, FALSE, array( 'lockTerm' => !$purchase->canChangeExpireDate() ) ) );
if ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled )
{
$form->add( new \IPS\nexus\Form\RenewalTerm( 'ps_renewals', $renewals, FALSE, array( 'lockTerm' => !$purchase->canChangeExpireDate() ) ) );
$form->add( new \IPS\Helpers\Form\Number( 'ps_grace_period', $purchase->grace_period / 86400, FALSE, array( 'max' => \IPS\Settings::i()->cm_invoice_expireafter ?: NULL ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('days') ) );
}
if ( !$purchase->grouped_renewals )
{
$form->add( new \IPS\Helpers\Form\Node( 'ps_parent', $purchase->parent(), FALSE, array( 'class' => 'IPS\nexus\Purchase', 'forceOwner' => $purchase->member, 'zeroVal' => 'no_parent' ) ) );
$form->add( new \IPS\Helpers\Form\Node( 'ps_parent', $purchase->parent(), FALSE, array( 'class' => 'IPS\nexus\Purchase', 'forceOwner' => $purchase->member, 'zeroVal' => 'no_parent', 'disabledIds' => array( $purchase->id ) ) ) );
}
}
@@ -205,14 +209,21 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
if ( $purchase->grouped_renewals )
{
$purchase->ungroupFromParent();
$purchase->renewals = $values['ps_renewals'];
if ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled )
{
$purchase->renewals = $values['ps_renewals'];
}
$purchase->save();
$purchase->groupWithParent();
}
else
{
$purchase->expire = ( $values['ps_expire'] ?: NULL );
$purchase->renewals = $values['ps_renewals'];
if ( !$purchase->billing_agreement or $purchase->billing_agreement->canceled )
{
$purchase->expire = ( $values['ps_expire'] ?: NULL );
$purchase->renewals = $values['ps_renewals'];
$purchase->grace_period = $values['ps_grace_period'] ? ( $values['ps_grace_period'] * 86400 ) : 0;
}
$purchase->parent = $values['ps_parent'] ?: NULL;
$purchase->save();
}
@@ -263,6 +274,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
return TRUE;
}
/**
* Get ACP Support View HTML
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return string
*/
public static function acpSupportView( \IPS\nexus\Purchase $purchase )
{
return '';
}
/**
* Support Severity
*
@@ -274,6 +296,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
return NULL;
}
/**
* Get renewal payment methods IDs
*
* @param \IPS\nexus\Purchase $purchase The purchase
* @return array|NULL
*/
public static function renewalPaymentMethodIds( \IPS\nexus\Purchase $purchase )
{
return NULL;
}
/**
* On Purchase Generated
*
@@ -331,6 +364,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
}
/**
* Warning to display to admin when cancelling a purchase
*
* @param \IPS\nexus\Invoice $invoice The invoice
* @return string
*/
public static function onCancelWarning( \IPS\nexus\Purchase $purchase )
{
return NULL;
}
/**
* On Purchase Deleted
*
@@ -365,6 +409,17 @@ abstract class _Purchase extends \IPS\nexus\Invoice\Item
}
/**
* Requires Billing Address
*
* @return bool
* @throws \DomainException
*/
public function requiresBillingAddress()
{
return in_array( 'product', explode( ',', \IPS\Settings::i()->nexus_require_billing ) );
}
/**
* Show Purchase Record?
*
@@ -2,9 +2,9 @@
/**
* @brief Invoice Item Class for Renewals
* @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 01 Apr 2014
* @version SVN_VERSION_NUMBER
@@ -71,6 +71,11 @@ class _Renewal extends \IPS\nexus\Invoice\Item
$obj->commission = $purchase->commission;
}
if ( $renewalPaymentMethodIds = $purchase->renewalPaymentMethodIds() )
{
$obj->paymentMethodIds = $renewalPaymentMethodIds;
}
return $obj;
}
@@ -98,11 +103,22 @@ class _Renewal extends \IPS\nexus\Invoice\Item
$purchase->expire = $expire;
$purchase->invoice_pending = NULL;
$billingAgreement = NULL;
foreach ( $invoice->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;
}
if ( $billingAgreement )
{
$purchase->billing_agreement = $billingAgreement;
}
$purchase->save();
$purchase->onRenew( $this->quantity );
$purchase->member->log( 'purchase', array( 'type' => 'renew', 'id' => $purchase->id, 'name' => $purchase->name, 'invoice_id' => $invoice->id, 'invoice_title' => $invoice->title ) );
}
}
}
/**
@@ -115,7 +131,14 @@ class _Renewal extends \IPS\nexus\Invoice\Item
{
$return = parent::onUnpaidDescription( $invoice );
$purchase = \IPS\nexus\Purchase::load( $this->id );
try
{
$purchase = \IPS\nexus\Purchase::load( $this->id );
}
catch ( \OutOfRangeException $e )
{
return $return;
}
if ( $purchase->renewals and $interval = $purchase->renewals->interval and !$purchase->cancelled and $expire = $purchase->expire )
{
@@ -139,7 +162,14 @@ class _Renewal extends \IPS\nexus\Invoice\Item
*/
public function onUnpaid( \IPS\nexus\Invoice $invoice, $status )
{
$purchase = \IPS\nexus\Purchase::load( $this->id );
try
{
$purchase = \IPS\nexus\Purchase::load( $this->id );
}
catch ( \OutOfRangeException $e )
{
return;
}
if ( $purchase->renewals and $interval = $purchase->renewals->interval and !$purchase->cancelled and $expire = $purchase->expire )
{
@@ -184,6 +214,30 @@ class _Renewal extends \IPS\nexus\Invoice\Item
*/
public function acpUrl()
{
return \IPS\nexus\Purchase::load( $this->id )->acpUrl();
try
{
return \IPS\nexus\Purchase::load( $this->id )->acpUrl();
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
/**
* Image
*
* @return |IPS\File|NULL
*/
public function image()
{
try
{
return \IPS\nexus\Purchase::load( $this->id )->image();
}
catch ( \OutOfRangeException $e )
{
return NULL;
}
}
}
@@ -0,0 +1,66 @@
<?php
/**
* @brief Tax Item
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Community Suite
* @subpackage Nexus
* @since 9 Dec 2015
* @version SVN_VERSION_NUMBER
*/
namespace IPS\nexus\Invoice\Item;
/* 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;
}
/**
* Tax Item
*/
class _TaxItem
{
/**
* @brief Class ID
*/
protected $class;
/**
* @brief Data
*/
protected $data;
/**
* Constructor
*
* @param int $class Class ID
* @param array $data Data
* @return void
*/
public function __construct( $class, $data )
{
$this->class = $class;
$this->data = $data;
}
/**
* Get output for API
*
* @return array
* @apiresponse \IPS\nexus\Tax class The tax class
* @apiresponse float rate The rate (for example 0.2 means 20%)
* @apiresponse \IPS\nexus\Money amount Amount to pay
*/
public function apiOutput()
{
return array(
'class' => \IPS\nexus\Tax::load( $this->class )->apiOutput(),
'rate' => $this->data['rate'],
'amount' => $this->data['amount']
);
}
}
@@ -2,9 +2,9 @@
/**
* @brief Invoice Items Iterator
* @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 24 Mar 2014
* @version SVN_VERSION_NUMBER
@@ -72,18 +72,68 @@ class _ItemsIterator extends \ArrayIterator
{
$class = 'IPS\nexus\extensions\nexus\Item\MiscellaneousCharge';
}
$obj = new $class( $data['itemName'], new \IPS\nexus\Money( $data['cost'], $this->currency ) );
$obj = new $class( $data['itemName'], new \IPS\nexus\Money( number_format( $data['cost'], \IPS\nexus\Money::numberOfDecimalsForCurrency( $this->currency ), '.', '' ), $this->currency ) );
}
/* Basic information */
$obj->quantity = $data['quantity'];
$obj->id = $data['itemID'];
$obj->appKey = $data['app'];
$obj->typeKey = $data['type'];
/* Details */
$details = array();
if ( isset( $data['cfields'] ) and $data['cfields'] )
{
$obj->details = $data['cfields'];
foreach( $data['cfields'] AS $key => $value )
{
try
{
$field = \IPS\nexus\Package\CustomField::load( $key );
if ( $field->invoice )
{
switch( $field->type )
{
case 'UserPass':
$value = json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE );
$display = array();
if ( $value['un'] )
{
$display[] = $value['un'];
}
if ( $value['pw'] )
{
$display[] = $value['pw'];
}
$details[ $key ] = implode( ' &sdot; ', $display );
break;
case 'Ftp':
$value = json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE );
$details[ $key ] = (string) \IPS\Http\Url::createFromComponents(
$value['server'],
( isset( $value['protocol'] ) ) ? "{$value['protocol']}://" : 'ftp://',
( isset( $value['path'] ) ) ? $value['path'] : NULL,
NULL,
( isset( $value['port'] ) ) ? $value['port'] : NULL,
( isset( $value['un'] ) ) ? $value['un'] : NULL,
( isset( $value['pw'] ) ) ? $value['pw'] : NULL
);
break;
default:
$details[ $key ] = $value;
break;
}
}
}
catch( \OutOfRangeException $e ) {}
}
}
$obj->details = $details;
/* Tax */
if ( isset( $data['tax'] ) AND $data['tax'] )