Version 4.3.6

This commit is contained in:
Neo committed 2025-12-19 05:49:12 -08:00
1 parent fe1acf984a
commit 1a0c7fd3c2
609 files changed
+14707 -5726

No files matched your search

@@ -447,4 +447,28 @@ class _Coupon extends \IPS\Node\Model
$this->used_by = array();
$this->save();
}
/**
* Search
*
* @param string $column Column to search
* @param string $query Search query
* @param string|null $order Column to order by
* @param mixed $where Where clause
* @return array
*/
public static function search( $column, $query, $order, $where=array() )
{
if( $column === '_title' )
{
$column = 'c_code';
}
if( $order === '_title' )
{
$order = 'c_code';
}
return parent::search( $column, $query, $order, $where );
}
}
+1 -1
View File
@@ -873,7 +873,7 @@ class _Rule extends \IPS\Node\Model
$amounts = json_decode( $this->customer_spend_unit, TRUE );
$spent = array();
foreach ( \IPS\Db::i()->select( 't_currency, ( SUM(t_amount)-SUM(t_partial_refund) ) AS amount', 'nexus_transactions', array( 't_member=? AND ( t_status=? OR t_status=? )', $transaction->member->member_id, \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ), NULL, NULL, 't_currency' ) as $amount )
foreach ( \IPS\Db::i()->select( 't_currency, ( SUM(t_amount)-SUM(t_partial_refund) ) AS amount', 'nexus_transactions', array( 't_member=? AND ( t_status=? OR t_status=? ) AND t_method>0', $transaction->member->member_id, \IPS\nexus\Transaction::STATUS_PAID, \IPS\nexus\Transaction::STATUS_PART_REFUNDED ), NULL, NULL, 't_currency' ) as $amount )
{
$spent[ $amount['t_currency'] ] = $amount['amount'];
}
@@ -90,7 +90,7 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
* @return \IPS\nexus\Transaction
* @throws \IPS\nexus\Gateway\PayPal\Exception
* @throws \OutOfRangeException
* @throws \IPS\Http\Exception
* @throws \IPS\Http\Url\Exception
*/
public function latestUnclaimedTransaction()
{
@@ -121,7 +121,7 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
}
}
}
catch ( \IPS\Http\Exception $e )
catch ( \IPS\Http\Url\Exception $e )
{
throw $e;
}
@@ -44,7 +44,8 @@ class _PayPal extends \IPS\nexus\Gateway
/* Card payments can only be certain currency amounts and require name and billing address */
if ( $settings['type'] === 'card' )
{
if ( !in_array( $amount->currency, array( 'USD', 'GBP', 'CAD', 'EUR', 'JPY' ) ) or !$customer->cm_first_name or !$customer->cm_last_name or !$billingAddress )
/* Supported Currencies via https://developer.paypal.com/docs/integration/direct/rest/currency-codes/ */
if ( !\in_array( $amount->currency, array( 'AUD', 'USD', 'CZK', 'DKK', 'HKD', 'ILS', 'CAD', 'EUR', 'MXN', 'NZD', 'NOK', 'PHP', 'PLN', 'JPY', 'GBP', 'RUB', 'SGD', 'SEK', 'CHF', 'THB' ) ) or !$customer->cm_first_name or !$customer->cm_last_name or !$billingAddress )
{
return FALSE;
}
@@ -465,6 +465,10 @@ class _Stripe extends \IPS\nexus\Gateway
{
return FALSE;
}
if ( $billingAddress and !in_array( $billingAddress->country, array( 'AT', 'AU', 'BE', 'BR', 'CA', 'CH', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'HK', 'IE', 'IN', 'IT', 'JP', 'LT', 'LU', 'LV', 'MX', 'NL', 'NZ', 'NO', 'PH', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'US' ) ) )
{
return FALSE;
}
}
/* Still here? Do normal checks */
@@ -659,7 +663,7 @@ class _Stripe extends \IPS\nexus\Gateway
$maxMind->setTransactionType('creditcard');
}
}
$transaction->save();
/* Build data */
$data = array(
'amount' => static::_amountAsCents( $transaction->amount ),
@@ -669,7 +673,7 @@ class _Stripe extends \IPS\nexus\Gateway
'metadata' => array(
"Transaction ID" => $transaction->id,
"Invoice ID" => $transaction->invoice->id,
"Customer ID" => $transaction->member->id,
"Customer ID" => $transaction->member->member_id,
"Customer Email" => $transaction->member->email,
),
);
@@ -728,6 +732,8 @@ class _Stripe extends \IPS\nexus\Gateway
{
$transaction->save();
/* Create the 3DSecure source */
$returnUrl = \IPS\Settings::i()->base_url . 'applications/nexus/interface/gateways/stripe-redirector.php?nexusTransactionId=' . $transaction->id;
$response = $this->api( "sources", array(
'type' => 'three_d_secure',
'amount' => static::_amountAsCents( $transaction->amount ),
@@ -736,14 +742,30 @@ class _Stripe extends \IPS\nexus\Gateway
'card' => $card->token
),
'redirect' => array(
'return_url' => \IPS\Settings::i()->base_url . 'applications/nexus/interface/gateways/stripe-redirector.php?nexusTransactionId=' . $transaction->id
'return_url' => $returnUrl
),
) );
/* If it's ready for them, send the customer over to the URL */
if ( $response['status'] == 'pending' )
{
\IPS\Output::i()->redirect( $response['redirect']['url'] );
}
/* It it's already chargeable (from Stripe: "This can happen if the customer's card has not yet
been enrolled in 3D Secure") then we don't need to redirect them, but the webhooks will handle
it from here, as if the customer had been redirected and authorized. Therefore, we can redirect
the customer to the return URL where they would have been redirected had they actually authorized */
elseif ( $response['status'] == 'chargeable' )
{
\IPS\Output::i()->redirect( $returnUrl );
}
// If it failed (the only other possible value for $response['status']) Stripe advises "you can either
// continue with a regular card payment, interrupt the payment flow, or attempt to create a 3D Secure
// source later". We'll opt for continuing with a regular card payment, which means there's no else
// clause here, we're just letting the code fall through below as if 3DSecure had been detected as not
// being required.
}
/* Are we saving it? */
@@ -853,7 +875,20 @@ class _Stripe extends \IPS\nexus\Gateway
return;
}
$this->api( "charges/{$transaction->gw_id}/capture" );
try
{
$this->api( "charges/{$transaction->gw_id}/capture" );
}
catch( \IPS\nexus\Gateway\Stripe\Exception $e )
{
/* If we have already captured/refunded the charge we don't need to let an exception bubble up */
if( $e->details['code'] == 'charge_already_captured' or $e->details['code'] == 'charge_already_refunded' )
{
return;
}
throw $e;
}
}
/**
@@ -929,6 +964,10 @@ class _Stripe extends \IPS\nexus\Gateway
return \IPS\Theme::i()->getTemplate( 'transactions', 'nexus', 'admin' )->stripeData( $response, 'error' );
}
}
elseif ( $response['source']['object'] === 'card' and isset( $response['card'] ) ) // For cards stored in older versions
{
$response['source']['card'] = $response['card'];
}
return \IPS\Theme::i()->getTemplate( 'transactions', 'nexus', 'admin' )->stripeData( $response );
}
@@ -1270,6 +1270,7 @@ class _Invoice extends \IPS\Patterns\ActiveRecord
if ( $transaction->status === \IPS\nexus\Transaction::STATUS_PART_REFUNDED )
{
$amountToSubtract = $amountToSubtract->subtract( $transaction->partial_refund->amount );
$amountToSubtract = $amountToSubtract->subtract( $transaction->credit->amount );
}
$amountToPay->amount = $amountToPay->amount->subtract( $amountToSubtract );
+16 -17
View File
@@ -343,23 +343,22 @@ class _Ad extends \IPS\nexus\Package
$field = new \IPS\Helpers\Form\Url( 'advertisement_url' . $k, NULL, TRUE );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('advertisement_url');
$form->add( $field );
$maxDims = TRUE;
if ( $this->max_height or $this->max_width )
{
$maxDims = array();
if ( $this->max_width )
{
$maxDims['maxWidth'] = $this->max_width;
}
if ( $this->max_height )
{
$maxDims['maxHeight'] = $this->max_height;
}
}
$field = new \IPS\Helpers\Form\Upload( 'advertisement_image' . $k, NULL, TRUE, array( 'storageExtension' => 'nexus_Ads', 'image' => $maxDims ) );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('advertisement_image');
$field = new \IPS\Helpers\Form\Upload( 'ad_image' . $k, NULL, TRUE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ) );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image');
$form->add( $field );
$field = new \IPS\Helpers\Form\YesNo( 'ad_image_more' . $k, FALSE, FALSE, array( 'togglesOn' => array( 'ad_image_small' . $k, 'ad_image_medium' . $k ) ) );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image_more');
$form->add( $field );
$field = new \IPS\Helpers\Form\Upload( 'ad_image_small' . $k, NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_small' . $k );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image_small');
$form->add( $field );
$field = new \IPS\Helpers\Form\Upload( 'ad_image_medium' . $k, NULL, FALSE, array( 'image' => TRUE, 'storageExtension' => 'nexus_Ads' ), NULL, NULL, NULL, 'ad_image_medium' . $k );
$field->label = \IPS\Member::loggedIn()->language()->addToStack('ad_image_medium');
$form->add( $field );
}
/**
@@ -373,7 +372,7 @@ class _Ad extends \IPS\nexus\Package
*/
public function acpAddToInvoice( \IPS\nexus\extensions\nexus\Item\Package $item, array $values, $k, \IPS\nexus\Invoice $invoice )
{
$item->extra['image'] = (string) $values[ 'advertisement_image' . $k ];
$item->extra['image'] = json_encode( array( 'large' => (string) $values[ 'ad_image' . $k ], 'medium' => (string) $values[ 'ad_image_medium' . $k ], 'small' => (string) $values[ 'ad_image_small' . $k ] ) );
$item->extra['link'] = (string) $values[ 'advertisement_url' . $k ];
}
@@ -106,6 +106,23 @@ class _CustomField extends \IPS\CustomField
*/
public static $editorOptions = array( 'app' => 'nexus', 'key' => 'Purchases' );
/**
* @brief [CustomField] Additional Field Toggles
*/
public static $additionalFieldToggles = array(
'Address' => array( 'cf_sticky' ),
'Checkbox' => array( 'cf_sticky' ),
'CheckboxSet' => array( 'cf_sticky' ),
'Date' => array( 'cf_sticky' ),
'Email' => array( 'cf_sticky' ),
'Number' => array( 'cf_sticky' ),
'Password' => array( 'cf_sticky' ),
'Radio' => array( 'cf_sticky' ),
'Text' => array( 'cf_sticky' ),
'Url' => array( 'cf_sticky' ),
'YesNo' => array( 'cf_sticky' ),
);
/**
* [Node] Add/Edit Form
*
@@ -129,7 +146,7 @@ class _CustomField extends \IPS\CustomField
{
return !( $node instanceof \IPS\nexus\Package\Group );
} ) ), 'pf_desc' );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_sticky', $this->sticky, FALSE ), 'pf_type' );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_sticky', $this->sticky, FALSE, array(), NULL, NULL, NULL, 'cf_sticky' ), 'pf_type' );
$form->add( new \IPS\Helpers\Form\YesNo( 'cf_allow_attachments', $this->id ? $this->allow_attachments : 1, FALSE, array( ), NULL, NULL, NULL, 'pf_allow_attachments' ) );
$form->addHeader( 'display_settings' );
+147 -94
View File
@@ -699,7 +699,7 @@ class _Package extends \IPS\Node\Model
{
if ( $v and isset( $customFields[ $k ] ) and $customFields[ $k ]->sticky )
{
$stickyFields[] = $v;
$stickyFields[] = strip_tags( \IPS\nexus\Package\CustomField::load( $k )->displayValue( $v, TRUE ) ); // We need to call displayValue so things like dates and select boxes show the right value, but we *also* need to call strip_tags as Url fields will return full links and we need text only. Note that fields which would show something other than a simple text output are prevented from being used in this context
}
}
}
@@ -1327,90 +1327,100 @@ class _Package extends \IPS\Node\Model
unset( $values['p_stock_price_type'] );
unset( $values['custom_fields'] );
}
/* Discounts */
if( isset( $values['p_usergroup_discounts'] ) OR isset( $values['p_loyalty_discounts'] ) OR isset( $values['p_bulk_discounts'] ) )
/* Reset the discounts ? */
if ( isset( $values['p_set_discounts'] ) AND !$values['p_set_discounts'] )
{
$discounts = array();
}
if( isset( $values['p_usergroup_discounts'] ) )
{
foreach ( $values['p_usergroup_discounts'] as $k => $data )
{
if ( isset( $data['group'] ) and $data['group'] and isset( $data['price'] ) )
{
foreach( $data['price'] as $price )
{
if ( !$price )
{
continue 2;
}
}
$discounts['usergroup'][] = array(
'group' => $data['group'],
'price' => $data['price'],
'secondary' => (int) isset( $data['secondary'] ),
);
}
}
unset( $values['p_usergroup_discounts'] );
}
if( isset( $values['p_loyalty_discounts'] ) )
{
foreach ( $values['p_loyalty_discounts'] as $k => $data )
{
if ( $data['owns'] )
{
foreach( $data['price'] as $price )
{
if ( !$price )
{
continue 2;
}
}
$packageId = $data['package'] ? mb_substr( $data['package'], 0, mb_strpos( $data['package'], '.' ) ) : 0;
$discounts['loyalty'][] = array(
'owns' => $data['owns'],
'package' => ( $data['this'] or ( $this->id and $this->id == $packageId ) ) ? '0' : $packageId,
'price' => $data['price'],
'active' => (int) isset( $data['active'] ),
);
}
}
unset( $values['p_loyalty_discounts'] );
}
if( isset( $values['p_bulk_discounts'] ) )
{
foreach ( $values['p_bulk_discounts'] as $k => $data )
{
if ( $data['buying'] )
{
foreach( $data['price'] as $price )
{
if ( !$price )
{
continue 2;
}
}
$packageId = $data['package'] ? mb_substr( $data['package'], 0, mb_strpos( $data['package'], '.' ) ) : 0;
$discounts['bulk'][] = array(
'buying' => $data['buying'],
'package' => ( $data['this'] or ( $this->id and $this->id == $packageId ) ) ? '0' : $packageId,
'price' => $data['price'],
);
}
}
unset( $values['p_bulk_discounts'] );
$values['discounts'] = NULL;
}
else
{
/* Discounts */
if (isset($values['p_usergroup_discounts']) OR isset($values['p_loyalty_discounts']) OR isset($values['p_bulk_discounts']))
{
$discounts = array();
}
if (isset($values['p_usergroup_discounts']))
{
foreach ($values['p_usergroup_discounts'] as $k => $data)
{
if (isset($data['group']) and $data['group'] and isset($data['price']))
{
foreach ($data['price'] as $price)
{
if (!$price)
{
continue 2;
}
}
$discounts['usergroup'][] = array(
'group' => $data['group'],
'price' => $data['price'],
'secondary' => (int)isset($data['secondary']),
);
}
}
unset($values['p_usergroup_discounts']);
}
if (isset($values['p_loyalty_discounts']))
{
foreach ($values['p_loyalty_discounts'] as $k => $data)
{
if ($data['owns'])
{
foreach ($data['price'] as $price)
{
if (!$price)
{
continue 2;
}
}
$packageId = $data['package'] ? mb_substr($data['package'], 0, mb_strpos($data['package'], '.')) : 0;
$discounts['loyalty'][] = array(
'owns' => $data['owns'],
'package' => ($data['this'] or ($this->id and $this->id == $packageId)) ? '0' : $packageId,
'price' => $data['price'],
'active' => (int)isset($data['active']),
);
}
}
unset($values['p_loyalty_discounts']);
}
if (isset($values['p_bulk_discounts']))
{
foreach ($values['p_bulk_discounts'] as $k => $data)
{
if ($data['buying'])
{
foreach ($data['price'] as $price)
{
if (!$price)
{
continue 2;
}
}
$packageId = $data['package'] ? mb_substr($data['package'], 0, mb_strpos($data['package'], '.')) : 0;
$discounts['bulk'][] = array(
'buying' => $data['buying'],
'package' => ($data['this'] or ($this->id and $this->id == $packageId)) ? '0' : $packageId,
'price' => $data['price'],
);
}
}
unset($values['p_bulk_discounts']);
}
}
if( isset( $discounts ) )
{
$values['discounts'] = json_encode( $discounts );
@@ -1821,6 +1831,18 @@ class _Package extends \IPS\Node\Model
{
$optionValues = $this->optionValues( $details );
$return = array();
/* If we do not have a currency in our session data, set it now so that if the language is changed, it is not confused about
what currency the store should be in */
if ( ( isset( $_SESSION['currency'] ) and in_array( $_SESSION['currency'], \IPS\nexus\Money::currencies() ) ) )
{
$memberCurrency = $_SESSION['currency'];
}
else
{
$memberCurrency = \IPS\nexus\Customer::loggedIn()->defaultCurrency();
$_SESSION['currency'] = $memberCurrency;
}
/* Work out how we're splitting these */
$items = array();
@@ -1828,8 +1850,11 @@ class _Package extends \IPS\Node\Model
$inThisBatch = 0;
for ( $i=0; $i<$quantity;$i++ )
{
$price = $this->price( NULL, TRUE, TRUE, TRUE, $i );
$memberCurrency = $price->currency;
$price = $this->price( NULL, TRUE, TRUE, TRUE, $i );
if ( $price->currency !== $memberCurrency )
{
throw new \RuntimeException('CURRENCY_MISMATCH');
}
$price = $price->amount;
if ( $previousPrice !== NULL and $previousPrice != $price )
@@ -1886,7 +1911,7 @@ class _Package extends \IPS\Node\Model
$item->parent = $parent;
}
/* Do any package-sepcific modifications */
/* Do any package-specific modifications */
$extraItems = ( $values === NULL ) ? array() : $this->addToCart( $item, $values, $memberCurrency );
/* Hang on, is that the same as anything else in the cart? */
@@ -1914,7 +1939,7 @@ class _Package extends \IPS\Node\Model
}
/* Associate extras */
if ( count( $extraItems ) )
if ( is_array( $extraItems ) and count( $extraItems ) )
{
foreach ( $extraItems as $item )
{
@@ -1983,7 +2008,7 @@ class _Package extends \IPS\Node\Model
foreach ( $customFields as $field )
{
if( array_key_exists( $field->type, \IPS\nexus\Package\CustomField::$additionalFieldTypes ) )
if( array_key_exists( $field->type, \IPS\nexus\Package\CustomField::$additionalFieldTypes ) and !class_exists( 'IPS\Helpers\Form\\' . $field->type ) )
{
$class = 'IPS\nexus\Form\\' . $field->type;
}
@@ -2113,7 +2138,18 @@ class _Package extends \IPS\Node\Model
{
\IPS\Session::i()->csrfCheck();
$term = array_pop( $renewalOptions );
$purchase->renewals = new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $term['cost'][$currency]['amount'], $currency ), new \DateInterval( 'P' . $term['term'] . mb_strtoupper( $term['unit'] ) ) );
$tax = NULL;
if ( $this->_data['tax'] )
{
try
{
$tax = \IPS\nexus\Tax::load( $purchase->tax );
}
catch ( \Exception $e ) { }
}
$purchase->renewals = new \IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $term['cost'][$currency]['amount'], $currency ), new \DateInterval( 'P' . $term['term'] . mb_strtoupper( $term['unit'] ) ), $tax );
$purchase->cancelled = FALSE;
$purchase->save();
@@ -2611,7 +2647,6 @@ class _Package extends \IPS\Node\Model
return array();
}
/* Cannot upgrade/downgrade without permission unless $overrideRestrictions is set */
if ( ( !$this->allow_upgrading AND !$this->allow_downgrading ) AND !$overrideRestrictions )
{
@@ -2624,8 +2659,7 @@ class _Package extends \IPS\Node\Model
return array();
}
/* Loop through our siblings */
$return = array();
/* Work out how much this package costs, including considering if the renewal term adds to the base price */
try
{
$currency = $purchase->original_invoice->currency;
@@ -2633,9 +2667,24 @@ class _Package extends \IPS\Node\Model
catch ( \Exception $e )
{
$currency = $purchase->member->defaultCurrency();
}
}
$prices = json_decode( $this->base_price, TRUE );
$thisPrice = $prices[ $currency ]['amount'];
if ( $this->renew_options )
{
$renewalOptions = json_decode( $this->renew_options, TRUE );
if ( !empty( $renewalOptions ) )
{
$option = array_shift( $renewalOptions );
if ( $option['add'] )
{
$thisPrice += ( $option['cost'][ $currency ]['amount'] );
}
}
}
/* Loop through our siblings */
$return = array();
foreach ( $this->parent()->children() as $package )
{
if ( $package->id === $this->id )
@@ -2679,6 +2728,7 @@ class _Package extends \IPS\Node\Model
}
}
/* Return */
return $return;
}
@@ -3247,7 +3297,7 @@ class _Package extends \IPS\Node\Model
/* Have we made other purchases that have changed their primary group? */
try
{
$next = \IPS\Db::i()->select( array( 'ps_id', 'ps_name', 'p_primary_group' ), 'nexus_purchases', array( 'ps_member=? AND ps_app=? AND ps_type=? AND ps_active=1 AND p_primary_group<>0 AND ps_id<>?', $purchase->member->member_id, 'nexus', 'package', $purchase->id ) )
$next = \IPS\Db::i()->select( array( 'ps_id', 'ps_name', 'p_primary_group' ), 'nexus_purchases', array( "ps_member=? AND ps_app=? AND ps_type IN('package','subscription') AND ps_active=1 AND p_primary_group<>0 AND ps_id<>?", $purchase->member->member_id, 'nexus', $purchase->id ) )
->join( 'nexus_packages', 'p_id=ps_item_id' )
->first();
@@ -3299,7 +3349,7 @@ class _Package extends \IPS\Node\Model
/* Have we made other purchases that have added secondary groups? */
$secondaryGroupsAwardedByOtherPurchases = array();
foreach ( \IPS\Db::i()->select( 'p_secondary_group', 'nexus_purchases', array( 'ps_member=? AND ps_app=? AND ps_type=? AND ps_active=1 AND p_secondary_group IS NOT NULL AND p_secondary_group<>? AND ps_id<>?', $purchase->member->member_id, 'nexus', 'package', '', $purchase->id ) )->join( 'nexus_packages', 'p_id=ps_item_id' ) as $secondaryGroups )
foreach ( \IPS\Db::i()->select( 'p_secondary_group', 'nexus_purchases', array( "ps_member=? AND ps_app=? AND ps_type IN('package','subscription') AND ps_active=1 AND p_secondary_group IS NOT NULL AND p_secondary_group<>? AND ps_id<>?", $purchase->member->member_id, 'nexus', '', $purchase->id ) )->join( 'nexus_packages', 'p_id=ps_item_id' ) as $secondaryGroups )
{
$secondaryGroupsAwardedByOtherPurchases = array_merge( $secondaryGroupsAwardedByOtherPurchases, array_filter( explode( ',', $secondaryGroups ) ) );
}
@@ -3338,9 +3388,12 @@ class _Package extends \IPS\Node\Model
});
/* Save */
$purchase->member->mgroup_others = implode( ',', $membersSecondaryGroups );
$purchase->member->save();
$purchase->member->logHistory( 'core', 'group', array( 'type' => 'secondary', 'by' => 'purchase', 'action' => 'remove', 'id' => $purchase->id, 'name' => $purchase->name, 'old' => $currentSecondaryGroups, 'new' => $membersSecondaryGroups ) );
if( $currentSecondaryGroups != $membersSecondaryGroups )
{
$purchase->member->mgroup_others = implode( ',', $membersSecondaryGroups );
$purchase->member->save();
$purchase->member->logHistory( 'core', 'group', array( 'type' => 'secondary', 'by' => 'purchase', 'action' => 'remove', 'id' => $purchase->id, 'name' => $purchase->name, 'old' => $currentSecondaryGroups, 'new' => $membersSecondaryGroups ) );
}
}
}
}
@@ -31,7 +31,7 @@ class _Purchase extends \IPS\Node\Model
* @param string $ref Referer
* @param \IPS\nexus\Purchase|NULL $root Root (NULL for all root purchases)
* @param bool $includeRoot Show the root?
* @return \IPS\Helpers\Tree
* @return \IPS\Helpers\Tree\Tree
*/
public static function tree( \IPS\Http\Url $url, array $where, $ref = 'c', \IPS\nexus\Purchase $root = NULL, $includeRoot = TRUE )
{
@@ -112,13 +112,17 @@ class _Package extends \IPS\Node\Model
parent::__clone();
/* Copy across images */
try
{
$file = \IPS\File::get( 'nexus_Products', $old->image );
$this->image = (string) \IPS\File::create( 'nexus_Products', $file->originalFilename, $file->contents() );
$this->save();
}
catch( \Exception $e ) {}
if ( $old->image )
{
try
{
$file = \IPS\File::get( 'nexus_Products', $old->image );
$this->image = (string) \IPS\File::create( 'nexus_Products', $file->originalFilename, $file->contents() );
$this->save();
}
catch( \Exception $e ) {}
}
}
/**
@@ -421,9 +425,31 @@ class _Package extends \IPS\Node\Model
{
if ( $price = $this->price() )
{
/* Include tax? */
if ( \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() ) );
$price->amount = $price->amount->add( $price->amount->multiply( $taxRate ) );
}
catch ( \OutOfRangeException $e ) { }
}
try
{
$renewalTerm = $this->renewalTerm( $price->currency );
/* Include tax? */
if ( \IPS\Settings::i()->nexus_show_tax and $this->tax and isset( $taxRate ) and $renewalTerm )
{
try
{
$renewalTerm->cost->amount = $renewalTerm->cost->amount->add( $renewalTerm->cost->amount->multiply( $taxRate ) );
}
catch ( \OutOfRangeException $e ) { }
}
if ( $renewalTerm and $renewalTerm->cost->amount == $price->amount )
{
@@ -582,14 +608,14 @@ class _Package extends \IPS\Node\Model
if ( $newPackage->gateways and $newPackage->gateways != '*' )
{
$item->paymentMethodIds = explode( ',', $newPackage->methods );
$item->paymentMethodIds = explode( ',', $newPackage->gateways );
}
$invoice = new \IPS\nexus\Invoice;
$invoice->member = $purchase->member;
$invoice->currency = $costToUpgrade->currency;
$invoice->addItem( $item );
$invoice->return_uri = "app=nexus&module=subscriptons&controller=subscriptons";
$invoice->return_uri = "app=nexus&module=subscriptions&controller=subscriptions";
$invoice->renewal_ids = array( $purchase->id );
$invoice->save();
return $invoice;
@@ -781,7 +807,7 @@ class _Package extends \IPS\Node\Model
}
/* Secondary Groups */
$secondary = array_filter( explode( ',', $this->secondary_group ), create_function( '$v', 'return (bool) $v;' ) );
$secondary = array_filter( explode( ',', $this->secondary_group ), function( $v ){ return (bool) $v; } );
$current_secondary = $member->mgroup_others ? explode( ',', $member->mgroup_others ) : array();
$newSecondary = $current_secondary;
@@ -949,11 +975,12 @@ class _Package extends \IPS\Node\Model
}
else if ( $secondaryGroupsAwardedByThisPurchase )
{
$currentSecondaryGroups = $membersSecondaryGroups;
foreach( $membersSecondaryGroups as $group )
{
if ( in_array( $group, $secondaryGroupsAwardedByThisPurchase ) )
{
unset( $membersSecondaryGroups[ array_search( $groupId, $membersSecondaryGroups ) ] );
unset( $membersSecondaryGroups[ array_search( $group, $membersSecondaryGroups ) ] );
}
}
@@ -46,6 +46,21 @@ class _Subscription extends \IPS\Patterns\ActiveRecord
{
if ( $this->expire )
{
if ( $this->purchase_id )
{
/* This has been invoiced, so there is a purchase row - fetch the expiration from that */
try
{
$purchase = \IPS\nexus\Purchase::load( $this->purchase_id );
if ( $purchase->expire )
{
return $purchase->expire;
}
}
catch( \OutOfRangeException $e ) { }
}
return \IPS\DateTime::ts( $this->expire );
}
@@ -141,9 +156,9 @@ class _Subscription extends \IPS\Patterns\ActiveRecord
*
* @param \IPS\Member $member Take a guess
* @param \IPS\nexus\Subscription\Package $package I mean it's really writing itself
* @param boolean $active Only get active packages
* @param boolean $activeOnly Only get active packages
* @return \IPS\nexus\Subscription
* @throws OutOfRangeException
* @throws \OutOfRangeException
*/
public static function loadByMemberAndPackage( \IPS\Member $member, \IPS\nexus\Subscription\Package $package, $activeOnly = TRUE )
{
@@ -85,7 +85,12 @@ class _Member
* @return array
*/
public function meta()
{
{
if( !\IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'customer', 'customers_view_statistics' ) )
{
return array( $this->customer->email );
}
return array(
$this->customer->email,
\IPS\Member::loggedIn()->language()->addToStack( 'transaction_customer_since', FALSE, array( 'sprintf' => array( $this->customer->joined->localeDate() ) ) ),
@@ -403,6 +403,27 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$this->_data['partial_refund'] = (string) $amount->amount;
}
/**
* Get credit amount
*
* @return \IPS\nexus\Money
*/
public function get_credit()
{
return new \IPS\nexus\Money( $this->_data['credit'], $this->_data['currency'] );
}
/**
* Set credit amount
*
* @param \IPS\nexus\Money $amount The total
* @return void
*/
public function set_credit( \IPS\nexus\Money $amount )
{
$this->_data['credit'] = (string) $amount->amount;
}
/**
* Get date transaction must be captured by (is set after authorisation. once captured, should be NULL)
*
@@ -648,6 +669,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
$extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'by' => \IPS\Member::loggedIn()->member_id );
$this->extra = $extra;
$this->status = \IPS\nexus\Transaction::STATUS_REFUSED;
$this->auth = NULL;
$this->save();
/* Log it */
@@ -672,6 +694,8 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
*/
public function refund( $refundMethod='gateway', $amount=NULL, $reason=NULL )
{
$extra = $this->extra;
/* What's the amount? */
if ( $amount )
{
@@ -684,42 +708,18 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
{
$amount = NULL;
}
/* Refund */
$refundReference = NULL;
/* Actual Refund */
if ( $refundMethod === 'gateway' and method_exists( $this->method, 'refund' ) )
{
/* Refund with gateway */
$refundReference = $this->method->refund( $this, $amount, $reason );
}
elseif ( $refundMethod === 'credit' )
{
$credits = $this->member->cm_credits;
$credits[ $this->amount->currency ]->amount = $credits[ $this->amount->currency ]->amount->add( $amount ?: $this->amount->amount );
$this->member->cm_credits = $credits;
$this->member->save();
}
/* Update transaction */
$extra = $this->extra;
if ( $refundMethod === 'none' )
{
$this->status = static::STATUS_REFUSED;
$extra['history'][] = array( 's' => static::STATUS_REFUSED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time() );
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_REFUSED,
'id' => $this->id
) );
}
}
else
{
/* Update transaction and log */
if ( $amount === NULL )
{
$this->status = static::STATUS_REFUNDED;
$extra['history'][] = array( 's' => static::STATUS_REFUNDED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time(), 'to' => $refundMethod, 'ref' => $refundReference );
if ( $this->member )
@@ -733,19 +733,18 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
}
}
else
{
{
$this->partial_refund = new \IPS\nexus\Money( $this->partial_refund->amount->add( $amount ), $this->currency );
$extra['history'][] = array( 's' => static::STATUS_PART_REFUNDED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time(), 'to' => $refundMethod, 'amount' => $amount, 'ref' => $refundReference );
if ( $amount >= $this->amount->amount )
{
$this->status = static::STATUS_REFUNDED;
$extra['history'][] = array( 's' => static::STATUS_REFUNDED );
}
else
{
$this->status = static::STATUS_PART_REFUNDED;
}
$extra['history'][] = array( 's' => $this->status, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time(), 'to' => $refundMethod, 'amount' => $amount, 'ref' => $refundReference );
if ( $this->member )
{
@@ -759,8 +758,89 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
) );
}
}
}
/* Credit */
elseif ( $refundMethod === 'credit' )
{
$amount = $amount ?: $this->amount->amount->subtract( $this->credit->amount );
/* Add the credit */
$credits = $this->member->cm_credits;
$credits[ $this->amount->currency ]->amount = $credits[ $this->amount->currency ]->amount->add( $amount );
$this->member->cm_credits = $credits;
$this->member->save();
/* Update transaction */
$this->status = static::STATUS_PART_REFUNDED;
$this->credit = new \IPS\nexus\Money( $this->credit->amount->add( $amount ), $this->currency );
/* Log */
$extra['history'][] = array( 's' => static::STATUS_PART_REFUNDED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time(), 'to' => $refundMethod, 'amount' => $amount, 'ref' => NULL );
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_PART_REFUNDED,
'id' => $this->id,
'refund' => $refundMethod,
'amount' => $amount,
'currency' => $this->currency
) );
}
}
/* Mark refused, but don't actually do anything */
elseif ( $refundMethod === 'none' )
{
/* Update transaction */
$this->status = static::STATUS_REFUSED;
$extra['history'][] = array( 's' => static::STATUS_REFUSED, 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time() );
/* Log */
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'status',
'status' => static::STATUS_REFUSED,
'id' => $this->id
) );
}
}
/* Save */
$this->extra = $extra;
$this->auth = NULL;
$this->save();
}
/**
* Reverse previously given credit (will log, but does not change status - status must be set separately)
*
* @return void
* @throws \Exception
*/
public function reverseCredit()
{
$credits = $this->member->cm_credits;
$credits[ $this->amount->currency ]->amount = $credits[ $this->amount->currency ]->amount->subtract( $this->credit->amount );
$this->member->cm_credits = $credits;
$this->member->save();
$extra = $this->extra;
$extra['history'][] = array( 's' => 'undo_credit', 'by' => \IPS\Member::loggedIn()->member_id, 'on' => time(), 'amount' => $this->credit->amount );
if ( $this->member )
{
$this->member->log( 'transaction', array(
'type' => 'undo_credit',
'id' => $this->id,
'amount' => $this->credit->amount,
'currency' => $this->currency
) );
}
$this->extra = $extra;
$this->credit = new \IPS\nexus\Money( 0, $this->currency );
$this->save();
}
@@ -911,13 +991,13 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
elseif ( in_array( $this->status, array( static::STATUS_PAID, static::STATUS_HELD, static::STATUS_REVIEW, static::STATUS_PART_REFUNDED ) ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'transactions_refund' ) )
{
$return['refund'] = array(
'title' => 'transaction_refund',
'title' => 'transaction_refund_credit',
'icon' => 'reply',
'link' => $url->setQueryString( array( 'do' => 'refund' ) ),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'transaction_refund_title', FALSE, array( 'sprintf' => array( $this->amount ) ) ) )
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack( 'transaction_refund_credit_title', FALSE, array( 'sprintf' => array( $this->amount ) ) ) )
);
}
/* Delete button */
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'nexus', 'payments', 'transactions_delete' ) )
{
@@ -971,6 +1051,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
* @apiresponse int invoiceId Invoice ID Number
* @apiresponse \IPS\nexus\Money amount Amount
* @apiresponse \IPS\nexus\Money refundAmount If partially refunded, the amount that has been refunded
* @apiresponse \IPS\nexus\Money credit If credited, the amount that has been credited
* @apiresponse \IPS\nexus\Gateway gateway The gateway
* @clientapiresponse string gatewayId Any ID number provided by the gateway to identify the transaction on their end
* @apiresponse datetime date Date
@@ -984,6 +1065,7 @@ class _Transaction extends \IPS\Patterns\ActiveRecord
'invoiceId' => $this->invoice->id,
'amount' => $this->amount->apiOutput( $authorizedMember ),
'refundAmount' => $this->partial_refund ? $this->partial_refund->apiOutput( $authorizedMember ) : null,
'creditAmount' => $this->credit ? $this->credit->apiOutput( $authorizedMember ) : null,
'gateway' => $this->method ? $this->method->apiOutput( $authorizedMember ) : null,
'gatewayId' => $this->gw_id,
'date' => $this->date->rfc3339(),