Version 4.7.0
This commit is contained in:
1 parent
517a5e1f70
commit
8ba7d43898
1933 files changed
+65613
-11332
No files matched your search
@@ -29,6 +29,12 @@ class _Package extends \IPS\Node\Model
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Caches
|
||||
* @note Defined cache keys will be cleared automatically as needed
|
||||
*/
|
||||
protected $caches = array( 'nexus_sub_count' );
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
@@ -602,15 +608,15 @@ class _Package extends \IPS\Node\Model
|
||||
{
|
||||
/* Fetch purchase */
|
||||
$purchase = NULL;
|
||||
foreach ( \IPS\nexus\extensions\nexus\Item\Subscription::getPurchases( $member, $package->id ) as $row )
|
||||
foreach ( \IPS\nexus\extensions\nexus\Item\Subscription::getPurchases( $member, $package->id, TRUE, TRUE ) as $row )
|
||||
{
|
||||
if ( $row->active and ! $row->cancelled )
|
||||
if ( !$row->cancelled OR ( $row->cancelled AND $row->can_reactivate ) )
|
||||
{
|
||||
$purchase = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( $purchase === NULL )
|
||||
{
|
||||
return NULL;
|
||||
@@ -624,14 +630,13 @@ class _Package extends \IPS\Node\Model
|
||||
{
|
||||
$currency = $purchase->member->defaultCurrency();
|
||||
}
|
||||
|
||||
|
||||
$priceOfExistingPackage = json_decode( $package->price, TRUE );
|
||||
if ( isset( $priceOfExistingPackage['cost'] ) )
|
||||
{
|
||||
$priceOfExistingPackage = $priceOfExistingPackage['cost'];
|
||||
}
|
||||
$priceOfExistingPackage = $priceOfExistingPackage[ $currency ]['amount'];
|
||||
$renewalOptionsOnOldPackage = json_decode( $package->renew_options, TRUE );
|
||||
|
||||
$priceOfThisPackage = json_decode( $this->price, TRUE );
|
||||
if ( isset( $priceOfThisPackage['cost'] ) )
|
||||
@@ -640,7 +645,6 @@ class _Package extends \IPS\Node\Model
|
||||
}
|
||||
$priceOfThisPackage = $priceOfThisPackage[ $currency ]['amount'];
|
||||
$renewalOptionsOnNewPackage = json_decode( $this->renew_options, TRUE );
|
||||
|
||||
|
||||
/* It's a non-recurring subscription */
|
||||
if ( empty( $renewalOptionsOnNewPackage ) )
|
||||
@@ -677,20 +681,37 @@ class _Package extends \IPS\Node\Model
|
||||
return new \IPS\nexus\Money( 0, $currency );
|
||||
|
||||
case 1:
|
||||
/* If the purchase is expired, charge the full amount */
|
||||
if( !$purchase->active )
|
||||
{
|
||||
$newPrice = new \IPS\nexus\Money( $priceOfThisPackage, $currency );
|
||||
|
||||
/* If the package has a free trial, charge the renewal fee instead to stop endless free trials */
|
||||
if( $newPrice->amount->isZero() )
|
||||
{
|
||||
return new \IPS\nexus\Money( $renewalOptionsOnNewPackage['cost'][ $currency ]['amount'], $currency );
|
||||
}
|
||||
|
||||
return new \IPS\nexus\Money( $priceOfThisPackage, $currency );
|
||||
}
|
||||
|
||||
return new \IPS\nexus\Money( $priceOfThisPackage - $priceOfExistingPackage, $currency );
|
||||
|
||||
case 2:
|
||||
|
||||
if ( !$purchase->renewals )
|
||||
{
|
||||
return new \IPS\nexus\Money( 0, $currency );
|
||||
}
|
||||
|
||||
if ( !$renewalOptionsOnNewPackage )
|
||||
{
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
/* What is the closest renewal option on the new package? We'll use that one */
|
||||
$renewalOptionsInDays = array();
|
||||
$term = ( new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $renewalOptionsOnNewPackage['cost'][ $currency ]['amount'], $currency ), new \DateInterval( 'P' . $renewalOptionsOnNewPackage['term'] . mb_strtoupper( $renewalOptionsOnNewPackage['unit'] ) ), $purchase->renewals->tax ) );
|
||||
$renewalOptionsInDays[ $term->days() ] = $term;
|
||||
|
||||
|
||||
$closestRenewalOption = null;
|
||||
$numberOfDaysInCurrentRenewalTerm = $purchase->renewals->days();
|
||||
foreach ( $renewalOptionsInDays as $days => $term )
|
||||
@@ -701,23 +722,64 @@ class _Package extends \IPS\Node\Model
|
||||
}
|
||||
}
|
||||
$renewalTermToUse = $renewalOptionsInDays[ $closestRenewalOption ];
|
||||
|
||||
/* What is the difference between our current renewal term and the renewal term we're moving to? */
|
||||
$diff = $purchase->renewals->diff( $renewalTermToUse );
|
||||
|
||||
/* Multiply that by how many days are left */
|
||||
$numberOfDaysInCurrentRenewalTerm = new \IPS\Math\Number( $numberOfDaysInCurrentRenewalTerm );
|
||||
$daysLeftUntilExpiry = new \IPS\Math\Number( (string) ceil( ( $purchase->expire->getTimestamp() - time() ) / 86400 ) );
|
||||
if ( $numberOfDaysInCurrentRenewalTerm->compare( $daysLeftUntilExpiry ) === 0 )
|
||||
|
||||
|
||||
/* If the purchase is not active, it is the full cost */
|
||||
if( !$purchase->active )
|
||||
{
|
||||
return $diff;
|
||||
return new \IPS\nexus\Money( $renewalTermToUse->cost->amount, $currency );
|
||||
}
|
||||
|
||||
/* Current Period Start Date */
|
||||
$startOfCurrentPeriod = $purchase->expire->sub( $purchase->renewals->interval );
|
||||
|
||||
/* DateInterval of time between subscription start and now */
|
||||
$timeSinceStartOfCurrentPeriod = $startOfCurrentPeriod->diff( \IPS\DateTime::create() );
|
||||
$numberOfDaysInCurrentRenewalTerm = new \IPS\Math\Number( (string) round( $numberOfDaysInCurrentRenewalTerm ) );
|
||||
|
||||
/* Count Days in Period */
|
||||
$daysUsedInPeriod = \IPS\DateTime::intervalToDays( $timeSinceStartOfCurrentPeriod );
|
||||
$daysRemainingInPeriod = new \IPS\Math\Number( (string) $numberOfDaysInCurrentRenewalTerm->subtract( new \IPS\Math\Number( (string) $daysUsedInPeriod ) ) );
|
||||
|
||||
/* Currency Worth of value used in current sub */
|
||||
$valueUsedOfSubscription = $purchase->renewals->cost->amount->divide( $numberOfDaysInCurrentRenewalTerm )->multiply( $daysRemainingInPeriod );
|
||||
|
||||
if ( !$daysRemainingInPeriod->isGreaterThanZero() )
|
||||
{
|
||||
return new \IPS\nexus\Money( new \IPS\Math\Number('0'), $currency );
|
||||
}
|
||||
else
|
||||
{
|
||||
return new \IPS\nexus\Money( $diff->amount->divide( $numberOfDaysInCurrentRenewalTerm )->multiply( $daysLeftUntilExpiry ), $currency );
|
||||
return new \IPS\nexus\Money( $renewalTermToUse->cost->amount->subtract( $valueUsedOfSubscription ), $currency );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cost to upgrade to this package (may return negative value for refund)
|
||||
*
|
||||
* @param \IPS\nexus\Subscription\Package $package The currently subscribed package
|
||||
* @param \IPS\Member $member The member.
|
||||
* @return \IPS\nexus\Money|NULL
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function costToUpgradeIncludingTax( \IPS\nexus\Subscription\Package $package, \IPS\Member $member )
|
||||
{
|
||||
$upgradeCost = $this->costToUpgrade( $package, $member );
|
||||
|
||||
if( $upgradeCost !== NULL AND $upgradeCost->amount->isGreaterThanZero() AND \IPS\Settings::i()->nexus_show_tax and $this->tax )
|
||||
{
|
||||
try
|
||||
{
|
||||
$taxRate = new \IPS\Math\Number( \IPS\nexus\Tax::load( $this->tax )->rate( \IPS\nexus\Customer::loggedIn()->estimatedLocation() ) );
|
||||
|
||||
$upgradeCost->amount = $upgradeCost->amount->add( $upgradeCost->amount->multiply( $taxRate ) );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
|
||||
return $upgradeCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the upgrade/downgrade invoice or refund
|
||||
@@ -787,7 +849,15 @@ class _Package extends \IPS\Node\Model
|
||||
{
|
||||
$currency = $purchase->member->defaultCurrency();
|
||||
}
|
||||
$term = new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $term['cost'][$currency]['amount'], $currency ), new \DateInterval( 'P' . $term['term'] . mb_strtoupper( $term['unit'] ) ), $newPackage->tax ? \IPS\nexus\Tax::load( $newPackage->tax ) : NULL );
|
||||
|
||||
/* Check Tax exists */
|
||||
$tax = NULL;
|
||||
try
|
||||
{
|
||||
$tax = $newPackage->tax ? \IPS\nexus\Tax::load( $newPackage->tax ) : NULL;
|
||||
}
|
||||
catch( \OutOfRangeException $e ) {}
|
||||
$term = new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $term['cost'][$currency]['amount'], $currency ), new \DateInterval( 'P' . $term['term'] . mb_strtoupper( $term['unit'] ) ), $tax );
|
||||
}
|
||||
|
||||
/* Remove usergroups */
|
||||
@@ -806,24 +876,39 @@ class _Package extends \IPS\Node\Model
|
||||
/* We have a term, but the unit is different from the existing package */
|
||||
elseif ( $purchase->expire and $term and $oldTerm and ( $rawTerm['unit'] != $oldTerm['unit'] ) )
|
||||
{
|
||||
$difference = $purchase->expire->getTimestamp() - time();
|
||||
|
||||
if ( $difference > 0 )
|
||||
/* If there is an upgrade cost, the expire date should be from when the current billing period started. */
|
||||
if( $costToUpgrade->amount->isGreaterThanZero() AND \IPS\Settings::i()->nexus_subs_upgrade > 0 )
|
||||
{
|
||||
$newExpire = \IPS\DateTime::ts( ( time() - $difference ) );
|
||||
$newExpire = $newExpire->add( $term->interval );
|
||||
|
||||
if ( $newExpire->getTimestamp() < time() )
|
||||
{
|
||||
$newExpire = \IPS\DateTime::create()->add( $term->interval );
|
||||
}
|
||||
|
||||
$purchase->expire = $newExpire;
|
||||
$startOfCurrentPeriod = $purchase->expire->sub( new \DateInterval( 'P' . $oldTerm['term'] . mb_strtoupper( $oldTerm['unit'] ) ) );
|
||||
$purchase->expire = $startOfCurrentPeriod < $purchase->start ? $startOfCurrentPeriod->add( $term->interval ) : $purchase->start->add( $term->interval );
|
||||
}
|
||||
else
|
||||
{
|
||||
$difference = $purchase->expire->getTimestamp() - time();
|
||||
|
||||
if ( $difference > 0 )
|
||||
{
|
||||
$newExpire = \IPS\DateTime::ts( ( time() - $difference ) );
|
||||
$newExpire = $newExpire->add( $term->interval );
|
||||
|
||||
if ( $newExpire->getTimestamp() < time() )
|
||||
{
|
||||
$newExpire = \IPS\DateTime::create()->add( $term->interval );
|
||||
}
|
||||
|
||||
$purchase->expire = $newExpire;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Purchase expired in the past, so it needs one renewal term adding */
|
||||
elseif( $purchase->expire->getTimestamp() < time() AND $term )
|
||||
{
|
||||
$purchase->expire = \IPS\DateTime::create()->add( $term->interval );
|
||||
}
|
||||
|
||||
/* Update Purchase */
|
||||
$purchase->name = \IPS\Member::loggedIn()->language()->get( "nexus_subs_{$newPackage->id}" );
|
||||
$purchase->cancelled = FALSE;
|
||||
$purchase->item_id = $newPackage->id;
|
||||
$purchase->renewals = $term;
|
||||
$purchase->save();
|
||||
@@ -843,7 +928,7 @@ class _Package extends \IPS\Node\Model
|
||||
/* Change the subscription itself */
|
||||
try
|
||||
{
|
||||
\IPS\nexus\Subscription::loadActiveByMember( $purchase->member )->changePackage( $newPackage, $purchase->expire );
|
||||
\IPS\nexus\Subscription::load( $purchase->id, 'sub_purchase_id' )->changePackage( $newPackage, $purchase->expire );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
@@ -896,10 +981,12 @@ class _Package extends \IPS\Node\Model
|
||||
/**
|
||||
* Adds a member to the subscription package
|
||||
*
|
||||
* @param \IPS\nexus\Customer $member The cutomer innit
|
||||
* @return \IPS\nexus\Subscription The new subscription object added
|
||||
* @param \IPS\nexus\Customer $member The customer innit
|
||||
* @param bool $generatePurchase Generate purchase when adding member (for manual subscriptions)
|
||||
* @param bool $purchaseRenews Does the purchase renew, or is it forever
|
||||
* @return \IPS\nexus\Subscription The new subscription object added
|
||||
*/
|
||||
public function addMember( $member )
|
||||
public function addMember( \IPS\nexus\Customer $member, bool $generatePurchase=FALSE, bool $purchaseRenews=FALSE ): \IPS\nexus\Subscription
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -929,6 +1016,39 @@ class _Package extends \IPS\Node\Model
|
||||
$sub->expire = $expires;
|
||||
$sub->renews = $renews;
|
||||
$sub->save();
|
||||
|
||||
/* Do we need to generate a purchase to track this sub? */
|
||||
if( $generatePurchase )
|
||||
{
|
||||
$purchase = new \IPS\nexus\Purchase;
|
||||
$purchase->member = $member;
|
||||
$purchase->name = $member->language()->get( static::$titleLangPrefix . $this->id );
|
||||
$purchase->app = 'nexus';
|
||||
$purchase->type = 'subscription';
|
||||
$purchase->item_id = $this->id;
|
||||
$purchase->tax = $this->tax;
|
||||
$purchase->show = 1;
|
||||
|
||||
if( $purchaseRenews )
|
||||
{
|
||||
if ( $renewalTerm = $this->renewalTerm() )
|
||||
{
|
||||
$purchase->renewals = $renewalTerm;
|
||||
}
|
||||
$purchase->expire = \IPS\DateTime::ts( $expires );
|
||||
}
|
||||
else
|
||||
{
|
||||
$sub->expire = 0;
|
||||
$sub->renews = 0;
|
||||
}
|
||||
|
||||
$purchase->save();
|
||||
|
||||
/* update sub with purchase id */
|
||||
$sub->purchase_id = $purchase->id;
|
||||
$sub->save();
|
||||
}
|
||||
|
||||
$this->_addUsergroups( $member );
|
||||
|
||||
@@ -956,7 +1076,7 @@ class _Package extends \IPS\Node\Model
|
||||
* @param \IPS\nexus\Customer $member The cutomer innit
|
||||
* @return void
|
||||
*/
|
||||
public function cancelMember( $member)
|
||||
public function cancelMember( $member )
|
||||
{
|
||||
/* Run before marking it inactive or it won't find the row in _removeUsergroups */
|
||||
$this->_removeUsergroups( $member );
|
||||
@@ -981,7 +1101,7 @@ class _Package extends \IPS\Node\Model
|
||||
* @param \IPS\nexus\Customer $member The cutomer innit
|
||||
* @return void
|
||||
*/
|
||||
public function removeMember( $member)
|
||||
public function removeMember( $member )
|
||||
{
|
||||
/* Run before marking it inactive or it won't find the row in _removeUsergroups */
|
||||
$this->_removeUsergroups( $member );
|
||||
@@ -1013,14 +1133,23 @@ class _Package extends \IPS\Node\Model
|
||||
/* Hang on, are we about to boot someone out the ACP? */
|
||||
if ( ! ( $member->isAdmin() and !\in_array( $this->primary_group, array_keys( \IPS\Member::administrators()['g'] ) ) ) )
|
||||
{
|
||||
/* Save the current group */
|
||||
$previousGroup = $member->member_group_id;
|
||||
/* Only do this if the target group exists */
|
||||
try
|
||||
{
|
||||
$group = \IPS\Member\Group::load( $this->primary_group);
|
||||
/* Save the current group */
|
||||
$previousGroup = $member->member_group_id;
|
||||
|
||||
/* And update to the new group */
|
||||
$member->member_group_id = $this->primary_group;
|
||||
$member->members_bitoptions['ignore_promotions'] = true;
|
||||
$member->save();
|
||||
$member->logHistory( 'core', 'group', array( 'type' => 'primary', 'by' => 'subscription', 'action' => 'add', 'id' => $this->id, 'old' => $previousGroup, 'new' => $member->member_group_id ) );
|
||||
/* And update to the new group */
|
||||
$member->member_group_id = $this->primary_group;
|
||||
$member->members_bitoptions['ignore_promotions'] = true;
|
||||
$member->save();
|
||||
$member->logHistory( 'core', 'group', array( 'type' => 'primary', 'by' => 'subscription', 'action' => 'add', 'id' => $this->id, 'old' => $previousGroup, 'new' => $member->member_group_id ) );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,7 +1164,16 @@ class _Package extends \IPS\Node\Model
|
||||
{
|
||||
if ( !\in_array( $gid, $newSecondary ) )
|
||||
{
|
||||
$newSecondary[] = $gid;
|
||||
/* Only do this if the target group exists */
|
||||
try
|
||||
{
|
||||
$group = \IPS\Member\Group::load( $gid );
|
||||
$newSecondary[] = $gid;
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user