AntonLV hace 6 años
padre
commit
e9df14369a

+ 40 - 11
inc/classes/BxDolPayments.php

@@ -70,25 +70,54 @@ class BxDolPayments extends BxDolFactory implements iBxDolSingleton
         return BxDolService::call($this->_sActive, 'is_accepting_payments', $aSrvParams);
     }
 
-	public function isPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    /**
+     * @deprecated since version 11.0.0
+     * 
+     * @see BxDolPayments::isProviderOptions
+     */
+    public function isPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
     {
-    	if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'is_payment_provider'))
-    		return false;
+    	return $this->isProviderOptions($iVendorId, $sVendorProvider);
+    }
 
-    	$aSrvParams = array($iVendorId, $sVendorProvider, $sPaymentType);
-        return BxDolService::call($this->_sActive, 'is_payment_provider', $aSrvParams);
+    /**
+     * @deprecated since version 11.0.0
+     * 
+     * @see BxDolPayments::getProviderOptions
+     */
+    public function getPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+    	return $this->getProviderOptions($iVendorId, $sVendorProvider, $sPaymentType);
     }
 
-	public function getPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    public function isProviderOptions($iVendorId, $sVendorProvider, $sPaymentType = '')
     {
-    	if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'get_payment_provider'))
-    		return false;
+        if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'is_provider_options'))
+            return false;
+
+        $aSrvParams = array($iVendorId, $sVendorProvider, $sPaymentType);
+        return BxDolService::call($this->_sActive, 'is_provider_options', $aSrvParams);
+    }
+
+    public function getProvider($sProvider, $mixedVendorId = BX_PAYMENT_EMPTY_ID)
+    {
+        if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'get_provider'))
+            return false;
+
+        $aSrvParams = array($sProvider, $mixedVendorId);
+        return BxDolService::call($this->_sActive, 'get_provider', $aSrvParams);
+    }
+
+    public function getProviderOptions($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+        if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'get_provider_options'))
+            return false;
 
-    	$aSrvParams = array($iVendorId, $sVendorProvider, $sPaymentType);
-        return BxDolService::call($this->_sActive, 'get_payment_provider', $aSrvParams);
+        $aSrvParams = array($iVendorId, $sVendorProvider, $sPaymentType);
+        return BxDolService::call($this->_sActive, 'get_provider_options', $aSrvParams);
     }
 
-	public function getPayments()
+    public function getPayments()
     {
         $aPayments = array(
 			'' => _t('_Select_one')

+ 25 - 1
modules/base/payment/classes/BxBaseModPaymentModule.php

@@ -127,7 +127,7 @@ class BxBaseModPaymentModule extends BxBaseModGeneralModule
     /** 
      * @ref bx_base_payment-get_option "get_option"
      */
-	public function serviceGetOption($sOption)
+    public function serviceGetOption($sOption)
     {
     	$sMethod = 'get' . bx_gen_method_name($sOption);
     	if(method_exists($this->_oConfig, $sMethod))
@@ -136,6 +136,30 @@ class BxBaseModPaymentModule extends BxBaseModGeneralModule
     	return $this->_oDb->getParam($this->_oConfig->getPrefix('options') . $sOption);
     }
 
+    /**
+     * @page service Service Calls
+     * @section bx_base_payment Base Payment
+     * @subsection bx_base_payment-other Other
+     * @subsubsection bx_base_payment-get_provider get_provider
+     * 
+     * @code bx_srv('bx_payment', 'get_provider', [...]); @endcode
+     * 
+     * Get payment provider object by its name and vendor ID. 
+     *
+     * @param $sProvider string value with provider name. 
+     * @param $mixedVendorId mixed value with vendor ID.
+     * @return provider object, instance of BxBaseModPaymentProvider.
+     * 
+     * @see BxBaseModPaymentModule::serviceGetProvider
+     */
+    /** 
+     * @ref bx_base_payment-get_provider "get_provider"
+     */
+    public function serviceGetProvider($sProvider, $mixedVendorId = BX_PAYMENT_EMPTY_ID)
+    {
+        return $this->getObjectProvider($sProvider, $mixedVendorId);
+    }
+
     /**
      * @page service Service Calls
      * @section bx_base_payment Base Payment

+ 39 - 8
modules/boonex/payment/classes/BxPaymentCart.php

@@ -299,7 +299,10 @@ class BxPaymentCart extends BxBaseModPaymentCart
         $fItemsPrice = 0;
         $aItemsInfo = array();
         foreach($aItems as $aItem) {
-            $aItemInfo = $this->_oModule->callGetCartItem((int)$aItem['module_id'], array($aItem['item_id'], isset($aItem['custom']) ? $aItem['custom'] : array()));
+            $aCustom = isset($aItem['custom']) ? $aItem['custom'] : array();
+
+            //--- Get item main info
+            $aItemInfo = $this->_oModule->callGetCartItem((int)$aItem['module_id'], array($aItem['item_id'], $aCustom));
             if(empty($aItemInfo) || !is_array($aItemInfo)) {
                 if($bTypeSingle) {
                     $sCartItems = $this->_oModule->_oDb->getCartItems($iClientId);
@@ -313,21 +316,49 @@ class BxPaymentCart extends BxBaseModPaymentCart
             $aItemInfo['module_id'] = (int)$aItem['module_id'];
             $aItemInfo['quantity'] = (int)$aItem['item_count'];
 
-            $iItemsCount += $aItem['item_count'];
-            $fItemsPrice += $aItem['item_count'] * $this->_oModule->_oConfig->getPrice($sType, $aItemInfo);
+            //--- Get item addons' info
+            $aItemAddons = array();
+            if(!empty($aItem['item_addons'])) {
+                $aAddons = $this->_oModule->_oConfig->s2a($aItem['item_addons']);
+                foreach($aAddons as $sAddon) {
+                    if(isset($aItemAddons[$sAddon])) {
+                        $aItemAddons[$sAddon]['quantity'] += 1;
+                        continue;
+                    }
+
+                    $aAddonInfo = $this->_oModule->callGetCartItem((int)$aItem['module_id'], array($sAddon, $aCustom));
+                    if(empty($aAddonInfo) || !is_array($aAddonInfo))
+                        continue;
+
+                    $aAddonInfo['module_id'] = (int)$aItem['module_id'];
+                    $aAddonInfo['quantity'] = 1;
+
+                    $aItemAddons[$sAddon] = $aAddonInfo;
+                }
+            }
+
+            $aItemInfo['addons'] = $aItemAddons;
             $aItemsInfo[] = $aItemInfo;
+
+            //--- Update items' summary
+            $fAddonsPrice = 0;
+            foreach($aItemInfo['addons'] as $aAddonInfo)
+                $fAddonsPrice += $aAddonInfo['quantity'] * $this->_oModule->_oConfig->getPrice($sType, $aAddonInfo);
+
+            $iItemsCount += $aItemInfo['quantity'];
+            $fItemsPrice += $aItemInfo['quantity'] * ($this->_oModule->_oConfig->getPrice($sType, $aItemInfo) + $fAddonsPrice);
         }
 
         $aSeller = $this->_oModule->getVendorInfo((int)$iSellerId);
         return array(
-        	'client_id' => $iClientId,
+            'client_id' => $iClientId,
             'vendor_id' => $aSeller['id'],
             'vendor_name' => $aSeller['name'],
-        	'vendor_link' => $aSeller['link'],
+            'vendor_link' => $aSeller['link'],
             'vendor_icon' => $aSeller['icon'],
-        	'vendor_thumb' => $aSeller['thumb'],
-        	'vendor_avatar' => $aSeller['avatar'],
-        	'vendor_unit' => $aSeller['unit'],
+            'vendor_thumb' => $aSeller['thumb'],
+            'vendor_avatar' => $aSeller['avatar'],
+            'vendor_unit' => $aSeller['unit'],
             'vendor_currency_code' => $aSeller['currency_code'],
             'vendor_currency_sign' => $aSeller['currency_sign'],
             'items_count' => $iItemsCount,

+ 19 - 4
modules/boonex/payment/classes/BxPaymentConfig.php

@@ -170,6 +170,16 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
         return 0;
     }
 
+    public function a2s($a)
+    {
+        return base64_encode(serialize($a));
+    }
+
+    public function s2a($s)
+    {
+        return unserialize(base64_decode($s));
+    }
+            
     public function descriptorA2S($a) 
     {
     	return implode($this->getDivider('DIVIDER_DESCRIPTOR'), $a);
@@ -188,7 +198,7 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
      */
     public function descriptorsM2A($mixed)
     {
-        $aResult = array();
+        $aResults = array();
 
         if(is_string($mixed))
            $aItems = explode($this->getDivider('DIVIDER_DESCRIPTORS'), $mixed);
@@ -199,10 +209,15 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
 
         foreach($aItems as $sItem) {
             $aItem = $this->descriptorS2A($sItem);
-            $aResult[] = array('vendor_id' => $aItem[0], 'module_id' => $aItem[1], 'item_id' => $aItem[2], 'item_count' => $aItem[3]);
+
+            $aResult = array('vendor_id' => $aItem[0], 'module_id' => $aItem[1], 'item_id' => $aItem[2], 'item_count' => $aItem[3]);
+            if(isset($aItem[4]))
+                $aResult['item_addons'] = $aItem[4];
+
+            $aResults[] = $aResult;
         }
 
-        return $aResult;
+        return $aResults;
     }
 
     public function putCustom($mDsc, $aCustom, &$aCustoms)
@@ -241,7 +256,7 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
         return $aResult;
     }
 
-	public function http2https($s)
+    public function http2https($s)
     {
     	if(strncmp($s, 'https://', 8) === 0)
     		return $s;

+ 8 - 1
modules/boonex/payment/classes/BxPaymentDb.php

@@ -262,9 +262,16 @@ class BxPaymentDb extends BxBaseModPaymentDb
     public function insertOrderPending($iClientId, $sType, $sProvider, $aCartInfo, $aCustom = array())
     {
         $sItems = "";
-        foreach($aCartInfo['items'] as $aItem)
+        foreach($aCartInfo['items'] as $aItem) {
             $sItems .= $this->_oConfig->descriptorA2S(array($aCartInfo['vendor_id'], $aItem['module_id'], $aItem['id'], $aItem['quantity'])) . ':';
 
+            if(empty($aItem['addons']) || !is_array($aItem['addons']))
+                continue;
+
+            foreach($aItem['addons'] as $sAddon => $aAddon)
+                $sItems .= $this->_oConfig->descriptorA2S(array($aCartInfo['vendor_id'], $aAddon['module_id'], $aAddon['id'], $aAddon['quantity'])) . ':';
+        }
+
         return (int)$this->query("INSERT INTO `" . $this->_sPrefix . "transactions_pending` SET `client_id`=:client_id, `seller_id`=:seller_id, `type`=:type, `provider`=:provider, `items`=:items, `customs`=:customs, `amount`=:amount, `date`=UNIX_TIMESTAMP()", array(
             'client_id' => $iClientId,
             'seller_id' => $aCartInfo['vendor_id'],

+ 94 - 62
modules/boonex/payment/classes/BxPaymentModule.php

@@ -183,13 +183,33 @@ class BxPaymentModule extends BxBaseModPaymentModule
 		return $bResult;
     }
 
+    /** 
+     * @deprecated since version 11.0.0
+     * 
+     * @see BxPaymentModule::serviceIsProviderOptions
+     */
+    public function serviceIsPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+    	return $this->serviceIsProviderOptions($iVendorId, $sVendorProvider, $sPaymentType);
+    }
+
+    /** 
+     * @deprecated since version 11.0.0
+     * 
+     * @see BxPaymentModule::serviceGetProviderOptions
+     */
+    public function serviceGetPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    {
+    	return $this->serviceGetProviderOptions($iVendorId, $sVendorProvider, $sPaymentType);
+    }
+
     /**
      * @page service Service Calls
      * @section bx_payment Payment
      * @subsection bx_payment-purchase_processing Purchase Processing
-     * @subsubsection bx_payment-is_payment_provider is_payment_provider
+     * @subsubsection bx_payment-is_provider_options is_provider_options
      * 
-     * @code bx_srv('bx_payment', 'is_payment_provider', [...]); @endcode
+     * @code bx_srv('bx_payment', 'is_provider_options', [...]); @endcode
      * 
      * Check whether the specified vendor has configured the specified payment provider or not.
      *
@@ -198,14 +218,14 @@ class BxPaymentModule extends BxBaseModPaymentModule
      * @param $sPaymentType (optional) string value with payment type. If specified then the vendor will be checked for having the payment provider of the requested type.
      * @return boolean value determining the result of checking.
      * 
-     * @see BxPaymentModule::serviceIsPaymentProvider
+     * @see BxPaymentModule::serviceIsProviderOptions
      */
     /** 
-     * @ref bx_payment-is_payment_provider "is_payment_provider"
+     * @ref bx_payment-is_provider_options "is_provider_options"
      */
-    public function serviceIsPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    public function serviceIsProviderOptions($iVendorId, $sVendorProvider, $sPaymentType = '')
     {
-    	$aProvider = $this->serviceGetPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType);
+    	$aProvider = $this->serviceGetProviderOptions($iVendorId, $sVendorProvider, $sPaymentType);
     	return $aProvider !== false;
     }
 
@@ -213,9 +233,9 @@ class BxPaymentModule extends BxBaseModPaymentModule
      * @page service Service Calls
      * @section bx_payment Payment
      * @subsection bx_payment-purchase_processing Purchase Processing
-     * @subsubsection bx_payment-get_payment_provider get_payment_provider
+     * @subsubsection bx_payment-get_provider_options get_provider_options
      * 
-     * @code bx_srv('bx_payment', 'is_payment_provider', [...]); @endcode
+     * @code bx_srv('bx_payment', 'get_provider_options', [...]); @endcode
      * 
      * Get configuration settings for the payment provider entered by the specified vendor.
      *
@@ -224,25 +244,25 @@ class BxPaymentModule extends BxBaseModPaymentModule
      * @param $sPaymentType (optional) string value with payment type. If specified then the vendor will be checked for having the payment provider of the requested type.
      * @return an array with special format describing the payment provider or false value if something is wrong.
      * 
-     * @see BxPaymentModule::serviceGetPaymentProvider
+     * @see BxPaymentModule::serviceGetProviderOptions
      */
     /** 
-     * @ref bx_payment-get_payment_provider "get_payment_provider"
+     * @ref bx_payment-get_provider_options "get_provider_options"
      */
-    public function serviceGetPaymentProvider($iVendorId, $sVendorProvider, $sPaymentType = '')
+    public function serviceGetProviderOptions($iVendorId, $sVendorProvider, $sPaymentType = '')
     {
     	$aProviders = array();
     	switch($sPaymentType) {
-    		case BX_PAYMENT_TYPE_SINGLE:
-    			$aProviders = $this->_oDb->getVendorInfoProvidersSingle($iVendorId);
-    			break;
+            case BX_PAYMENT_TYPE_SINGLE:
+                $aProviders = $this->_oDb->getVendorInfoProvidersSingle($iVendorId);
+                break;
 
-    		case BX_PAYMENT_TYPE_RECURRING:
-    			$aProviders = $this->_oDb->getVendorInfoProvidersRecurring($iVendorId);
-    			break;
+            case BX_PAYMENT_TYPE_RECURRING:
+                $aProviders = $this->_oDb->getVendorInfoProvidersRecurring($iVendorId);
+                break;
 
-    		default:
-    			$aProviders = $this->_oDb->getVendorInfoProviders($iVendorId);
+            default:
+                $aProviders = $this->_oDb->getVendorInfoProviders($iVendorId);
     	}
 
     	return !empty($aProviders) && !empty($aProviders[$sVendorProvider]) && is_array(($aProviders[$sVendorProvider])) ? $aProviders[$sVendorProvider] : false;
@@ -406,7 +426,14 @@ class BxPaymentModule extends BxBaseModPaymentModule
 
     	$iItemCount = bx_process_input(bx_get('item_count'), BX_DATA_INT);
     	if(empty($iItemCount))
-    		$iItemCount = 1;
+            $iItemCount = 1;
+
+        $sItemAddons = array();
+        if(bx_get('item_addons') !== false) {
+            $sItemAddons = bx_process_input(bx_get('item_addons'));
+            if(is_array($sItemAddons))
+                $sItemAddons = $this->_oConfig->a2s($sItemAddons);
+        }
 
         $sRedirect = bx_process_input(bx_get('redirect'));
         $sCustom = bx_process_input(bx_get('custom'));
@@ -418,8 +445,8 @@ class BxPaymentModule extends BxBaseModPaymentModule
                 $aCustom = array();
         }
 
-        $aResult = $this->getObjectSubscriptions()->serviceSubscribe($iSellerId, $sSellerProvider, $iModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
-		echoJson($aResult);
+        $aResult = $this->getObjectSubscriptions()->serviceSubscribeWithAddons($iSellerId, $sSellerProvider, $iModuleId, $iItemId, $iItemCount, $sItemAddons, $sRedirect, $aCustom);
+        echoJson($aResult);
     }
 
     public function actionSubscriptionGetDetails($iId)
@@ -559,64 +586,69 @@ class BxPaymentModule extends BxBaseModPaymentModule
     /** 
      * @ref bx_payment-initialize_checkout "initialize_checkout"
      */
-	public function serviceInitializeCheckout($sType, $iSellerId, $sProvider, $aItems = array(), $sRedirect = '', $aCustoms = array())
-	{
-	    $bTypeSingle = $sType == BX_PAYMENT_TYPE_SINGLE;
-	    $bTypeRecurring = $sType == BX_PAYMENT_TYPE_RECURRING;
+    public function serviceInitializeCheckout($sType, $iSellerId, $sProvider, $aItems = array(), $sRedirect = '', $aCustoms = array())
+    {
+        $bTypeSingle = $sType == BX_PAYMENT_TYPE_SINGLE;
+        $bTypeRecurring = $sType == BX_PAYMENT_TYPE_RECURRING;
 
-		if(!is_array($aItems))
-			$aItems = array($aItems);
+        if(!is_array($aItems))
+            $aItems = array($aItems);
 
-		$iSellerId = (int)$iSellerId;
+        $iSellerId = (int)$iSellerId;
         if($iSellerId == BX_PAYMENT_EMPTY_ID)
             return $this->_sLangsPrefix . 'err_unknown_vendor';
 
-		$oProvider = $this->getObjectProvider($sProvider, $iSellerId);
+        $oProvider = $this->getObjectProvider($sProvider, $iSellerId);
         if($oProvider === false || !$oProvider->isActive())
-        	return $this->_sLangsPrefix . 'err_incorrect_provider';
+            return $this->_sLangsPrefix . 'err_incorrect_provider';
 
         $aInfo = $this->getObjectCart()->getInfo($sType, $this->_iUserId, $iSellerId, $aItems);
         if(empty($aInfo) || $aInfo['vendor_id'] == BX_PAYMENT_EMPTY_ID || empty($aInfo['items']))
             return $this->_sLangsPrefix . 'err_empty_order';
-            
-		/*
-		 * Process FREE (price = 0) items for LOGGED IN members
-		 * WITHOUT processing via payment provider.
-		 * Note. This section isn't used for now!
-		 */
-		$bProcessedFree = false;
-		$sKeyPriceSingle = $this->_oConfig->getKey('KEY_ARRAY_PRICE_SINGLE');
-		$sKeyPriceRecurring = $this->_oConfig->getKey('KEY_ARRAY_PRICE_RECURRING');
-		foreach($aInfo['items'] as $iIndex => $aItem) {
-		    if((int)$aInfo['client_id'] == 0)
-		        continue;
+
+        /*
+         * Process FREE (price = 0) items for LOGGED IN members
+         * WITHOUT processing via payment provider.
+         * Note. This section isn't used for now!
+         */
+        $bProcessedFree = false;
+        $sKeyPriceSingle = $this->_oConfig->getKey('KEY_ARRAY_PRICE_SINGLE');
+        $sKeyPriceRecurring = $this->_oConfig->getKey('KEY_ARRAY_PRICE_RECURRING');
+        foreach($aInfo['items'] as $iIndex => $aItem) {
+            if((int)$aInfo['client_id'] == 0)
+                continue;
 
             //--- For Single time payments.
-			if($bTypeSingle && (float)$aItem[$sKeyPriceSingle] == 0) {
-			    $aCart = $this->_oDb->getCartContent($aInfo['client_id']);
-			    $aCartCustoms = !empty($aCart['customs']) ? unserialize($aCart['customs']) : array();
+            if($bTypeSingle && (float)$aItem[$sKeyPriceSingle] == 0) {
+                $aCart = $this->_oDb->getCartContent($aInfo['client_id']);
+                $aCartCustoms = !empty($aCart['customs']) ? unserialize($aCart['customs']) : array();
 
-			    $aCartItem = array($aInfo['vendor_id'], $aItem['module_id'], $aItem['id'], $aItem['quantity']);
-			    $aCartItemCustom = $this->_oConfig->pullCustom($aCartItem, $aCartCustoms);
+                $aCartItem = array($aInfo['vendor_id'], $aItem['module_id'], $aItem['id'], $aItem['quantity']);
+                $aCartItemCustom = $this->_oConfig->pullCustom($aCartItem, $aCartCustoms);
 
-				$aItemInfo = $this->callRegisterCartItem((int)$aItem['module_id'], array($aInfo['client_id'], $aInfo['vendor_id'], $aItem['id'], $aItem['quantity'], $this->_oConfig->getLicense(), $aCartItemCustom));
-	            if(is_array($aItemInfo) && !empty($aItemInfo))
-	            	$bProcessedFree = true;
+                $aItemInfo = $this->callRegisterCartItem((int)$aItem['module_id'], array($aInfo['client_id'], $aInfo['vendor_id'], $aItem['id'], $aItem['quantity'], $this->_oConfig->getLicense(), $aCartItemCustom));
+                if(!empty($aItemInfo) && is_array($aItemInfo)) {
+                    if(!empty($aItemInfo['addons']) && is_array($aItemInfo['addons']))
+                        foreach($aItemInfo['addons'] as $sAddon => $aAddon)
+                            $this->callRegisterCartItem((int)$aAddon['module_id'], array($aInfo['client_id'], $aInfo['vendor_id'], $aAddon['id'], $aAddon['quantity'], $this->_oConfig->getLicense(), $aCartItemCustom));
 
-	            $aInfo['items_count'] -= 1;
-	            unset($aInfo['items'][$iIndex]);
+                    $bProcessedFree = true;
+                }
 
-	            $aCart['items'] = trim(preg_replace("'" . $this->_oConfig->descriptorA2S($aCartItem) . ":?'", "", $aCart['items']), ":");
-	            $this->_oDb->setCartItems($aInfo['client_id'], $aCart['items'], $aCartCustoms);
-			}
+                $aInfo['items_count'] -= 1;
+                unset($aInfo['items'][$iIndex]);
 
-			//--- For Recurring payments.
-			if($bTypeRecurring && (float)$aItem[$sKeyPriceRecurring] == 0) {
-			    //TODO: Process FREE subscription here, if such situation will be possible.
-			}
-		}
+                $aCart['items'] = trim(preg_replace("'" . $this->_oConfig->descriptorA2S($aCartItem) . ":?'", "", $aCart['items']), ":");
+                $this->_oDb->setCartItems($aInfo['client_id'], $aCart['items'], $aCartCustoms);
+            }
+
+            //--- For Recurring payments.
+            if($bTypeRecurring && (float)$aItem[$sKeyPriceRecurring] == 0) {
+                //TODO: Process FREE subscription here, if such situation will be possible.
+            }
+        }
 
-		if(empty($aInfo['items']))
+        if(empty($aInfo['items']))
             return $this->_sLangsPrefix . ($bProcessedFree ? 'msg_successfully_processed_free' : 'err_empty_order');
 
         if($bTypeSingle && (empty($aCustoms) || !is_array($aCustoms))) {

+ 195 - 112
modules/boonex/payment/classes/BxPaymentProviderChargebee.php

@@ -48,20 +48,20 @@ class BxPaymentProviderChargebee extends BxBaseModPaymentProvider implements iBx
     {
     	$aItem = array_shift($aCartInfo['items']);
     	if(empty($aItem) || !is_array($aItem))
-    		return $this->_sLangsPrefix . 'err_empty_items';
+            return $this->_sLangsPrefix . 'err_empty_items';
 
-		$aClient = $this->_oModule->getProfileInfo();
-		$aVendor = $this->_oModule->getProfileInfo($aCartInfo['vendor_id']);
+        $aClient = $this->_oModule->getProfileInfo();
+        $aVendor = $this->_oModule->getProfileInfo($aCartInfo['vendor_id']);
 
-		$oPage = $this->createHostedPage($aItem, $aClient, $aVendor, $iPendingId);
-		if($oPage === false)
-			return $this->_sLangsPrefix . 'err_cannot_perform';
+        $oPage = $this->createHostedPage($aItem, $aClient, $aVendor, $iPendingId);
+        if($oPage === false)
+            return $this->_sLangsPrefix . 'err_cannot_perform';
 
-		return array(
-			'code' => 0,
-			'eval' => $this->_oModule->_oConfig->getJsObject('cart') . '.onSubscribeSubmit(oData);',
-			'redirect' => $oPage->url
-		);
+        return array(
+            'code' => 0,
+            'eval' => $this->_oModule->_oConfig->getJsObject('cart') . '.onSubscribeSubmit(oData);',
+            'redirect' => $oPage->url
+        );
     }
 
     public function finalizeCheckout(&$aData)
@@ -116,113 +116,214 @@ class BxPaymentProviderChargebee extends BxBaseModPaymentProvider implements iBx
         return $this->deleteSubscription($sSubscriptionId);
     }
 
-	public function createHostedPage($aItem, $aClient, $aVendor = array(), $iPendingId = 0) {
-		$oPage = false;
-
-		try {
-			$aPage = array(
-                            'embed' => false, //--- Note. 'embed' should be disabled to allow payments via PayPal
-                            'subscription' => array(
-                                    'planId' => $aItem['name']
-                            ), 
-                            'customer' => array(
-                                    'email' => $aClient['email'],
-                                    'firstName' => $aClient['name']
-                            ), 
-			);
-
-			if(!empty($aVendor) && is_array($aVendor) && !empty($aVendor))
+    public function createHostedPage($aItem, $aClient, $aVendor = array(), $iPendingId = 0)
+    {
+        $oPage = false;
+
+        try {
+            $aPage = array(
+                'embed' => false, //--- Note. 'embed' should be disabled to allow payments via PayPal
+                'subscription' => array(
+                    'planId' => $aItem['name']
+                ),
+                'customer' => array(
+                    'email' => $aClient['email'],
+                    'firstName' => $aClient['name']
+                ), 
+            );
+
+            if(!empty($aItem['addons']) && is_array($aItem['addons']))
+                $aPage['addons'] = $aItem['addons'];
+
+            if(!empty($aVendor) && is_array($aVendor) && !empty($aVendor))
                 $aPage['redirectUrl'] = bx_append_url_params($this->getReturnDataUrl($aVendor['id']), array(
-					'pending_id' => $iPendingId
-				));
+                    'pending_id' => $iPendingId
+                ));
 
-			ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
-			$oResult = ChargeBee_HostedPage::checkoutNew($aPage);
-			$oPage = $oResult->hostedPage();
-		}
-		catch (Exception $oException) {
-			$iError = $oException->getCode();
-			$sError = $oException->getMessage();
+            ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
+            $oResult = ChargeBee_HostedPage::checkoutNew($aPage);
 
-			$this->log('Create Hosted Page Error: ' . $sError . '(' . $iError . ')');
+            $oPage = $oResult->hostedPage();
+        }
+        catch (Exception $oException) {
+            $iError = $oException->getCode();
+            $sError = $oException->getMessage();
 
-			return false;
-		}
+            $this->log('Create Hosted Page Error: ' . $sError . '(' . $iError . ')');
 
-		return $oPage;
-	}
+            return false;
+        }
 
-	public function retreiveHostedPage($sPageId) {
-		$oPage = null;
+        return $oPage;
+    }
 
-		try {
-			ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
-			$oResult = ChargeBee_HostedPage::retrieve($sPageId);
+    public function retreiveHostedPage($sPageId)
+    {
+        $oPage = null;
 
-			$oPage = $oResult->hostedPage();
-		}
-		catch (Exception $oException) {
-			$iError = $oException->getCode();
-			$sError = $oException->getMessage();
+        try {
+            ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
+            $oResult = ChargeBee_HostedPage::retrieve($sPageId);
 
-			$this->log('Retrieve Hosted Page Error: ' . $sError . '(' . $iError . ')');
+            $oPage = $oResult->hostedPage();
+        }
+        catch (Exception $oException) {
+            $iError = $oException->getCode();
+            $sError = $oException->getMessage();
 
-			return false;
-		}
+            $this->log('Retrieve Hosted Page Error: ' . $sError . '(' . $iError . ')');
 
-		return $oPage;
-	}
+            return false;
+        }
 
-	public function retrieveSubscription($sSubscriptionId)
-	{
-		$oSubscription = null;
+        return $oPage;
+    }
 
-		try {
-			ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
-			$oResult = ChargeBee_Subscription::retrieve($sSubscriptionId);
+    public function retrieveSubscription($sSubscriptionId)
+    {
+        $oSubscription = null;
 
-			$oSubscription = $oResult->subscription();
-			if($oSubscription->id != $sSubscriptionId)
-				return false;
-		}
-		catch (Exception $oException) {
-			$iError = $oException->getCode();
-			$sError = $oException->getMessage();
+        try {
+            ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
+            $oResult = ChargeBee_Subscription::retrieve($sSubscriptionId);
 
-			$this->log('Retrieve Subscription Error: ' . $sError . '(' . $iError . ')');
+            $oSubscription = $oResult->subscription();
+            if($oSubscription->id != $sSubscriptionId)
+                return false;
+        }
+        catch (Exception $oException) {
+            $iError = $oException->getCode();
+            $sError = $oException->getMessage();
 
-			return false;
-		}
+            $this->log('Retrieve Subscription Error: ' . $sError . '(' . $iError . ')');
 
-		return $oSubscription;
-	}
+            return false;
+        }
 
-	public function deleteSubscription($sSubscriptionId)
-	{
-            try {
-                ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
-                $oResult = ChargeBee_Subscription::cancel($sSubscriptionId);
+        return $oSubscription;
+    }
 
-                $oSubscription = $oResult->subscription();
-                if($oSubscription->status != 'cancelled')
-                    return false;
-            }
-            catch (Exception $oException) {
-                $aError = $oException->getJsonBody();
+    public function getSubscription($iPendingId, $sCustomerId, $sSubscriptionId)
+    {
+        $oSubscription = $this->retrieveSubscription($sSubscriptionId);
+        if($oSubscription === false)
+            return array();
 
-                $this->log('Delete Subscription Error: ' . $aError['error']['message']);
-                $this->log($aError);
+        $sStatus = $oSubscription->status;
+        $sStatus = isset($this->_aSbsStatuses[$sStatus]) ? $this->_aSbsStatuses[$sStatus] : BX_PAYMENT_SBS_STATUS_UNKNOWN;
+
+        return array(
+            'status' => $sStatus,
+            'created' => $oSubscription->createdAt,
+            'started' => $oSubscription->startedAt,
+            'trial_start' => $oSubscription->trialStart,
+            'trial_end' => $oSubscription->trialEnd,
+            'cperiod_start' => $oSubscription->currentTermStart,
+            'cperiod_end' => $oSubscription->currentTermEnd,
+        );
+    }
+    
+    public function deleteSubscription($sSubscriptionId)
+    {
+        try {
+            ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
+            $oResult = ChargeBee_Subscription::cancel($sSubscriptionId);
 
+            $oSubscription = $oResult->subscription();
+            if($oSubscription->status != 'cancelled')
                 return false;
-            }
+        }
+        catch (Exception $oException) {
+            $aError = $oException->getJsonBody();
 
-            bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_cancel_subscription', 0, false, array(
-                'subscription_id' => $sSubscriptionId,
-                'subscription_object' => &$oSubscription
-            ));
+            $this->log('Delete Subscription Error: ' . $aError['error']['message']);
+            $this->log($aError);
+
+            return false;
+        }
 
-            return true;
-	}
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_cancel_subscription', 0, false, array(
+            'subscription_id' => $sSubscriptionId,
+            'subscription_object' => &$oSubscription
+        ));
+
+        return true;
+    }
+
+    public function retrieveAddons($sStatus = 'active', $iLimit = 0)
+    {
+        $aAddons = array();
+
+        try {
+            $aParams = array();
+            if(!empty($sStatus))
+                $aParams['status[is]'] = $sStatus;
+            if(!empty($iLimit))
+                $aParams['limit'] = $iLimit;
+            
+            ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
+            $oResults = ChargeBee_Addon::all($aParams);
+
+            foreach($oResults as $oResult)
+                $aAddons[] = $oResult->addon();
+        }
+        catch (Exception $oException) {
+            $iError = $oException->getCode();
+            $sError = $oException->getMessage();
+
+            $this->log('Retrieve Addons Error: ' . $sError . '(' . $iError . ')');
+
+            return false;
+        }
+
+        return $aAddons;
+    }
+
+    public function getAddons($sStatus = 'active', $iLimit = 0)
+    {
+        $aAddons = $this->retrieveAddons($sStatus, $iLimit);
+        if(empty($aAddons) || !is_array($aAddons))
+            return array();
+
+        $aResult = array();
+        foreach($aAddons as $oAddon)
+            $aResult[] = $oAddon->getValues();
+
+        return $aResult;
+    }
+
+    public function retrieveAddon($sId)
+    {
+        $oAddon = null;
+
+        try {
+            ChargeBee_Environment::configure($this->_getSite(), $this->_getApiKey());
+            $oResult = ChargeBee_Addon::retrieve($sId);
+
+            $oAddon = $oResult->addon();
+            if($oAddon->id != $sId)
+                return false;
+        }
+        catch (Exception $oException) {
+            $iError = $oException->getCode();
+            $sError = $oException->getMessage();
+
+            $this->log('Retrieve Addon Error: ' . $sError . '(' . $iError . ')');
+
+            return false;
+        }
+
+        return $oAddon;
+    }
+
+    public function getAddon($sId)
+    {
+        $oAddon = $this->retrieveAddon($sId);
+        if($oAddon === false)
+            return array();
+
+        return $oAddon->getValues();
+    }
 
     public function retrieveCustomer($sCustomerId)
     {
@@ -248,25 +349,7 @@ class BxPaymentProviderChargebee extends BxBaseModPaymentProvider implements iBx
         return $oCustomer;
     }
 
-    public function getSubscription($iPendingId, $sCustomerId, $sSubscriptionId)
-    {
-        $oSubscription = $this->retrieveSubscription($sSubscriptionId);
-        if($oSubscription === false)
-            return array();
-
-        $sStatus = $oSubscription->status;
-        $sStatus = isset($this->_aSbsStatuses[$sStatus]) ? $this->_aSbsStatuses[$sStatus] : BX_PAYMENT_SBS_STATUS_UNKNOWN;
 
-        return array(
-            'status' => $sStatus,
-            'created' => $oSubscription->createdAt,
-            'started' => $oSubscription->startedAt,
-            'trial_start' => $oSubscription->trialStart,
-            'trial_end' => $oSubscription->trialEnd,
-            'cperiod_start' => $oSubscription->currentTermStart,
-            'cperiod_end' => $oSubscription->currentTermEnd,
-        );
-    }
 
     protected function _getSite()
     {

+ 45 - 35
modules/boonex/payment/classes/BxPaymentProviderChargebeeV3.php

@@ -15,29 +15,39 @@ class BxPaymentProviderChargebeeV3 extends BxPaymentProviderChargebee
 {
     function __construct($aConfig)
     {
-    	$this->MODULE = 'bx_payment';
+        $this->MODULE = 'bx_payment';
 
         parent::__construct($aConfig);
 
         $this->_aIncludeJs = array(
-        	'https://js.chargebee.com/v2/chargebee.js',
-        	'main.js',
-        	'chargebee_v3.js'
+            'https://js.chargebee.com/v2/chargebee.js',
+            'main.js',
+            'chargebee_v3.js'
         );
 
         $this->_aIncludeCss = array(
-        	'chargebee_v3.css'
+            'chargebee_v3.css'
         );
     }
 
-    public function actionGetHostedPage($iClientId, $iVendorId, $sItemName)
+    public function actionGetHostedPage($iClientId, $iVendorId, $sItemName, $sItemAddons = '')
     {
         $this->setOptionsByVendor($iVendorId);
 
+        $aItem = array('name' => $sItemName);
+        if(!empty($sItemAddons)) {
+            $aItemAddons = $this->_oModule->_oConfig->s2a($sItemAddons);
+
+            foreach($aItemAddons as $sItemAddon)
+                $aItem['addons'][] = array(
+                    'id' => $sItemAddon
+                );
+        }
         $aClient = $this->_oModule->getProfileInfo($iClientId);
-	    $oPage = $this->createHostedPage(array('name' => $sItemName), $aClient);
-		if($oPage === false)
-		    return echoJson(array());
+
+        $oPage = $this->createHostedPage($aItem, $aClient);
+        if($oPage === false)
+            return echoJson(array());
 
         header('Content-type: text/html; charset=utf-8');
         echo $oPage->toJson();
@@ -208,42 +218,42 @@ class BxPaymentProviderChargebeeV3 extends BxPaymentProviderChargebee
 
     public function getButtonRecurring($iClientId, $iVendorId, $aParams = array())
     {
-		return $this->_getButton(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iVendorId, $aParams);
+        return $this->_getButton(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iVendorId, $aParams);
     }
 
     protected function _getButton($sType, $iClientId, $iVendorId, $aParams = array())
     {
-    	$sSite = '';
-    	bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_get_button', 0, $iClientId, array(
-			'type' => &$sType, 
-			'site' => &$sSite,
-    	    'params' => &$aParams
-		));
-
-    	return $this->_oModule->_oTemplate->parseHtmlByName('cbee_v3_button_' . $sType . '.html', array(
-    		'type' => $sType,
-    	    'link' => 'javascript:void(0)',
-    		'caption' => _t($this->_sLangsPrefix . 'cbee_txt_checkout_with_' . $sType, $this->_sCaption),
-    		'js_object' => $this->_oModule->_oConfig->getJsObject($this->_sName),
-    		'js_code' => $this->_oModule->_oTemplate->getJsCode($this->_sName, array_merge(array(
-	    		'sProvider' => $this->_sName,
-	    		'sSite' => !empty($sSite) ? $sSite : $this->_getSite(),
-    	        'iClientId' => $iClientId
-	    	), $aParams))
-    	));
+        $sSite = '';
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_get_button', 0, $iClientId, array(
+            'type' => &$sType, 
+            'site' => &$sSite,
+            'params' => &$aParams
+        ));
+
+        return $this->_oModule->_oTemplate->parseHtmlByName('cbee_v3_button_' . $sType . '.html', array(
+            'type' => $sType,
+            'link' => 'javascript:void(0)',
+            'caption' => _t($this->_sLangsPrefix . 'cbee_txt_checkout_with_' . $sType, $this->_sCaption),
+            'js_object' => $this->_oModule->_oConfig->getJsObject($this->_sName),
+            'js_code' => $this->_oModule->_oTemplate->getJsCode($this->_sName, array_merge(array(
+                'sProvider' => $this->_sName,
+                'sSite' => !empty($sSite) ? $sSite : $this->_getSite(),
+                'iClientId' => $iClientId
+            ), $aParams))
+        ));
     }
 
     public function getMenuItemsActionsRecurring($iClientId, $iVendorId, $aParams = array())
     {
-    	if(empty($aParams['id']))
-    		return array();
+        if(empty($aParams['id']))
+            return array();
 
-    	$sPrefix = 'bx-payment-strp-';
-    	$sJsObject = $this->_oModule->_oConfig->getJsObject($this->_sName);
+        $sPrefix = 'bx-payment-strp-';
+        $sJsObject = $this->_oModule->_oConfig->getJsObject($this->_sName);
 
-    	return array(
-   			array('id' => $sPrefix . 'manager', 'name' => $sPrefix . 'manager', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:return " . $sJsObject . ".manage(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_cbee_menu_item_title_manager'))
-    	);
+        return array(
+            array('id' => $sPrefix . 'manager', 'name' => $sPrefix . 'manager', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:return " . $sJsObject . ".manage(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_cbee_menu_item_title_manager'))
+        );
     }
 }
 

+ 51 - 27
modules/boonex/payment/classes/BxPaymentSubscriptions.php

@@ -193,6 +193,35 @@ class BxPaymentSubscriptions extends BxBaseModPaymentSubscriptions
      * @ref bx_payment-subscribe "subscribe"
      */
     public function serviceSubscribe($iSellerId, $sSellerProvider, $iModuleId, $iItemId, $iItemCount, $sRedirect = '', $aCustom = array())
+    {
+        return $this->serviceSubscribeWithAddons($iSellerId, $sSellerProvider, $iModuleId, $iItemId, $iItemCount, array(), $sRedirect, $aCustom);
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_payment Payment
+     * @subsection bx_payment-purchase_processing Purchase Processing
+     * @subsubsection bx_payment-subscribe_with_addons subscribe_with_addons
+     * 
+     * @code bx_srv('bx_payment', 'subscribe_with_addons', [...], 'Subscriptions'); @endcode
+     * 
+     * Initialize subscription for specified item.
+     *
+     * @param $iSellerId integer value with seller ID.
+     * @param $sSellerProvider string value with a name of payment provider to be used for processing. Empty value means that payment provider selector should be shown.
+     * @param $iModuleId integer value with module ID.
+     * @param $iItemId integer value with item ID.
+     * @param $iItemCount integer value with a number of items for purchasing. It's equal to 1 in case of subscription.
+     * @param $aItemAddons (optional) array with attached addons.
+     * @param $sRedirect (optional) string value with redirect URL, if it's needed. 
+     * @return an array with special format which describes the result of operation.
+     * 
+     * @see BxPaymentSubscriptions::serviceSubscribeWithAddons
+     */
+    /** 
+     * @ref bx_payment-subscribe_with_addons "subscribe_with_addons"
+     */
+    public function serviceSubscribeWithAddons($iSellerId, $sSellerProvider, $iModuleId, $iItemId, $iItemCount, $sItemAddons = '', $sRedirect = '', $aCustom = array())
     {
     	$CNF = &$this->_oModule->_oConfig->CNF;
 
@@ -200,56 +229,51 @@ class BxPaymentSubscriptions extends BxBaseModPaymentSubscriptions
 
     	$mixedResult = $this->_oModule->checkData($iClientId, $iSellerId, $iModuleId, $iItemId, $iItemCount);
     	if($mixedResult !== true)
-    		return $mixedResult;
+            return $mixedResult;
 
         $aSellerProviders = $this->_oModule->_oDb->getVendorInfoProvidersRecurring($iSellerId);
         if(empty($aSellerProviders))
             return array('code' => 5, 'message' => _t($CNF['T']['ERR_NOT_ACCEPT_PAYMENTS']));
 
-        $aCartItem = array($iSellerId, $iModuleId, $iItemId, $iItemCount);
-        $sCartItem = $this->_oModule->_oConfig->descriptorA2S($aCartItem);
+        $aCartItem = array($iSellerId, $iModuleId, $iItemId, $iItemCount, $sItemAddons);
 
-		if(empty($sSellerProvider)) {
-			$sId = $this->_oModule->_oConfig->getHtmlIds('cart', 'providers_select') . BX_PAYMENT_TYPE_RECURRING;
-			$sTitle = _t($CNF['T']['POPUP_PROVIDERS_SELECT']);
-			return array('popup' => array(
-				'html' => BxTemplStudioFunctions::getInstance()->popupBox($sId, $sTitle, $this->_oModule->_oTemplate->displayProvidersSelector($aCartItem, $aSellerProviders, $sRedirect, $aCustom)), 
-				'options' => array('closeOnOuterClick' => true)
-			));
-		}
+        if(empty($sSellerProvider)) {
+            $sId = $this->_oModule->_oConfig->getHtmlIds('cart', 'providers_select') . BX_PAYMENT_TYPE_RECURRING;
+            $sTitle = _t($CNF['T']['POPUP_PROVIDERS_SELECT']);
+            return array('popup' => array(
+                'html' => BxTemplStudioFunctions::getInstance()->popupBox($sId, $sTitle, $this->_oModule->_oTemplate->displayProvidersSelector($aCartItem, $aSellerProviders, $sRedirect, $aCustom)), 
+                'options' => array('closeOnOuterClick' => true)
+            ));
+        }
 
-		$aCustoms = array();
-		$this->_oModule->_oConfig->putCustom($aCartItem, $aCustom, $aCustoms);
+        $aCustoms = array();
+        $this->_oModule->_oConfig->putCustom($aCartItem, $aCustom, $aCustoms);
 
+        $sCartItem = $this->_oModule->_oConfig->descriptorA2S($aCartItem);
         $mixedResult = $this->_oModule->serviceInitializeCheckout(BX_PAYMENT_TYPE_RECURRING, $iSellerId, $aSellerProviders[$sSellerProvider]['name'], array($sCartItem), $sRedirect, $aCustoms);
         if(is_string($mixedResult))
-        	return array('code' => 6, 'message' => _t($mixedResult));
+            return array('code' => 6, 'message' => _t($mixedResult));
 
-		return $mixedResult;
+        return $mixedResult;
     }
 
     /**
      * @page service Service Calls
      * @section bx_payment Payment
      * @subsection bx_payment-purchase_processing Purchase Processing
-     * @subsubsection bx_payment-subscribe subscribe
+     * @subsubsection bx_payment-send_subscription_expiration_letters send_subscription_expiration_letters
      * 
-     * @code bx_srv('bx_payment', 'subscribe', [...], 'Subscriptions'); @endcode
+     * @code bx_srv('bx_payment', 'send_subscription_expiration_letters', [...], 'Subscriptions'); @endcode
      * 
-     * Initialize subscription for specified item.
+     * Send subscription expiration letters.
      *
-     * @param $iSellerId integer value with seller ID.
-     * @param $sSellerProvider string value with a name of payment provider to be used for processing. Empty value means that payment provider selector should be shown.
-     * @param $iModuleId integer value with module ID.
-     * @param $iItemId integer value with item ID.
-     * @param $iItemCount integer value with a number of items for purchasing. It's equal to 1 in case of subscription.
-     * @param $sRedirect (optional) string value with redirect URL, if it's needed. 
-     * @return an array with special format which describes the result of operation.
+     * @param $iPendingId integer value with pending transaction ID.
+     * @param $sOrderId string value with order ID.
      * 
-     * @see BxPaymentSubscriptions::serviceSubscribe
+     * @see BxPaymentSubscriptions::serviceSendSubscriptionExpirationLetters
      */
     /** 
-     * @ref bx_payment-subscribe "subscribe"
+     * @ref bx_payment-send_subscription_expiration_letters "send_subscription_expiration_letters"
      */
     public function serviceSendSubscriptionExpirationLetters($iPendingId, $sOrderId)
     {

+ 57 - 51
modules/boonex/payment/classes/BxPaymentTemplate.php

@@ -346,57 +346,63 @@ class BxPaymentTemplate extends BxBaseModPaymentTemplate
     	$oModule = $this->getModule();
     	$iClientId = $oModule->getProfileId();
 
-		$oCart = $oModule->getObjectCart();
-		$oSubscriptions = $oModule->getObjectSubscriptions();
-		list($iSellerId, $iModuleId, $iItemId, $iItemCount) = $aCartItem;
-
-		$aTmplVarsProviders = array();
-		foreach($aProviders as $sProvider => $aProvider) {
-		    $sButton = '';
-
-			$oProvider = $oModule->getObjectProvider($sProvider, $iSellerId);
-			if($oProvider !== false && method_exists($oProvider, 'getButtonRecurring')) {
-			    $aParams = array(
-					'sObjNameCart' => $oModule->_oConfig->getJsObject('cart'),
-					'iSellerId' => $iSellerId,
-					'iModuleId' => $iModuleId,
-					'iItemId' => $iItemId,
-					'iItemCount' => $iItemCount,
-				    'sRedirect' => $sRedirect,
-			        'sCustom' => base64_encode(serialize($aCustom))
-				);
-
-			    $aCartInfo = $oCart->getInfo(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iSellerId, $this->_oConfig->descriptorA2S($aCartItem));
-			    if(!empty($aCartInfo['items_price']) && !empty($aCartInfo['items']) && is_array($aCartInfo['items'])) {
-			        $aItem = array_shift($aCartInfo['items']);
-
-			        $aParams = array_merge($aParams, array(
-			            'iAmount' => (int)round(100 * (float)$aCartInfo['items_price']),
-			        	'sItemName' => $aItem['name'],
-			        	'sItemTitle' => $aItem['title']
-			        ));
-			    }
-
-				$sButton = $oProvider->getButtonRecurring($iClientId, $iSellerId, $aParams);
-			}
-
-			if(empty($sButton)) {
-				list($sJsCode, $sJsOnclick) = $oSubscriptions->serviceGetSubscribeJs($iSellerId, $aProvider['name'], $iModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
-
-				$sButton = $this->parsePageByName('providers_select_button.html', array(
-					'onclick' => $sJsOnclick,
-					'title' => _t('_bx_payment_txt_checkout_with', _t($aProvider['caption']))
-				));
-			}
-
-			$aTmplVarsProviders[] = array(
-				'button' => $sButton
-			);
-		}
-
-		return $this->parseHtmlByName('providers_select.html', array(
-			'bx_repeat:providers' => $aTmplVarsProviders
-		));
+        $oCart = $oModule->getObjectCart();
+        $oSubscriptions = $oModule->getObjectSubscriptions();
+
+        $sItemAddons = '';
+        if(count($aCartItem) == 5)
+            $sItemAddons = array_pop($aCartItem);
+
+        list($iSellerId, $iModuleId, $iItemId, $iItemCount) = $aCartItem;
+
+        $aTmplVarsProviders = array();
+        foreach($aProviders as $sProvider => $aProvider) {
+            $sButton = '';
+
+            $oProvider = $oModule->getObjectProvider($sProvider, $iSellerId);
+            if($oProvider !== false && method_exists($oProvider, 'getButtonRecurring')) {
+                $aParams = array(
+                    'sObjNameCart' => $oModule->_oConfig->getJsObject('cart'),
+                    'iSellerId' => $iSellerId,
+                    'iModuleId' => $iModuleId,
+                    'iItemId' => $iItemId,
+                    'iItemCount' => $iItemCount,
+                    'sItemAddons' => $sItemAddons,
+                    'sRedirect' => $sRedirect,
+                    'sCustom' => base64_encode(serialize($aCustom))
+                );
+
+                $aCartInfo = $oCart->getInfo(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iSellerId, $this->_oConfig->descriptorA2S($aCartItem));
+                if(!empty($aCartInfo['items_price']) && !empty($aCartInfo['items']) && is_array($aCartInfo['items'])) {
+                    $aItem = array_shift($aCartInfo['items']);
+
+                    $aParams = array_merge($aParams, array(
+                        'iAmount' => (int)round(100 * (float)$aCartInfo['items_price']),
+                        'sItemName' => $aItem['name'],
+                        'sItemTitle' => $aItem['title']
+                    ));
+                }
+
+                $sButton = $oProvider->getButtonRecurring($iClientId, $iSellerId, $aParams);
+            }
+
+            if(empty($sButton)) {
+                list($sJsCode, $sJsOnclick) = $oSubscriptions->serviceGetSubscribeJs($iSellerId, $aProvider['name'], $iModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
+
+                $sButton = $this->parsePageByName('providers_select_button.html', array(
+                    'onclick' => $sJsOnclick,
+                    'title' => _t('_bx_payment_txt_checkout_with', _t($aProvider['caption']))
+                ));
+            }
+
+            $aTmplVarsProviders[] = array(
+                'button' => $sButton
+            );
+        }
+
+        return $this->parseHtmlByName('providers_select.html', array(
+                'bx_repeat:providers' => $aTmplVarsProviders
+        ));
     }
 
     protected function _displayBlockHistory($sObject, $iClientId, $iSellerId = 0)

+ 24 - 19
modules/boonex/payment/js/cart.js

@@ -9,62 +9,66 @@
  */
 
 function BxPaymentCart(oOptions) {
-	this.init(oOptions);
+    this.init(oOptions);
 }
 
 BxPaymentCart.prototype = new BxPaymentMain();
 
 BxPaymentCart.prototype.init = function(oOptions) {
-	if($.isEmptyObject(oOptions))
-		return;
+    if($.isEmptyObject(oOptions))
+        return;
 
-	this._sActionsUrl = oOptions.sActionUrl;
+    this._sActionsUrl = oOptions.sActionUrl;
     this._sObjName = oOptions.sObjName == undefined ? 'oPmtCart' : oOptions.sObjName;
     this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
     this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
 };
 
 BxPaymentCart.prototype.addToCart = function(iSellerId, iModuleId, iItemId, iItemCount, iRedirect, sCustom) {
-	var $this = this;
+    var $this = this;
     var oDate = new Date();
 
     var sRedirect = '';
     if(typeof iRedirect == 'number' && parseInt(iRedirect) > 0)
-    	sRedirect = sUrlRoot + 'cart.php?seller_id=' + iSellerId;
-	else if(typeof iRedirect == 'string' && iRedirect.length > 0)
-		sRedirect = iRedirect;
+        sRedirect = sUrlRoot + 'cart.php?seller_id=' + iSellerId;
+    else if(typeof iRedirect == 'string' && iRedirect.length > 0)
+            sRedirect = iRedirect;
 
     $.post(
         this._sActionsUrl + 'add_to_cart/' + iSellerId + '/' + iModuleId + '/' + iItemId + '/' + iItemCount + '/',
         {
-        	custom: sCustom ? sCustom : '',
+            custom: sCustom ? sCustom : '',
             _t:oDate.getTime()
         },
         function(oData) {
-        	if(sRedirect)
-        		oData.redirect = sRedirect;
+            if(sRedirect)
+                oData.redirect = sRedirect;
 
-        	processJsonData(oData);
+            processJsonData(oData);
         },
         'json'
     );
 };
 
 BxPaymentCart.prototype.onCartContinue = function(oData) {
-	if (!oData || oData.link == undefined)
-		return;
+    if (!oData || oData.link == undefined)
+        return;
 
-	location.href = oData.link;
+    location.href = oData.link;
 };
 
 BxPaymentCart.prototype.onCartCheckout = function(oData) {
-	if (!oData || oData.link == undefined)
-		return;
+    if (!oData || oData.link == undefined)
+        return;
 
-	location.href = oData.link;
+    location.href = oData.link;
 };
 
 BxPaymentCart.prototype.subscribe = function(iSellerId, sSellerProvider, iModuleId, iItemId, iItemCount, sRedirect, sCustom) {
+    this.subscribeWithAddons(iSellerId, sSellerProvider, iModuleId, iItemId, iItemCount, '', sRedirect, sCustom);
+};
+
+BxPaymentCart.prototype.subscribeWithAddons = function(iSellerId, sSellerProvider, iModuleId, iItemId, iItemCount, sItemAddons, sRedirect, sCustom) {
     var $this = this;
     var oDate = new Date();
 
@@ -74,6 +78,7 @@ BxPaymentCart.prototype.subscribe = function(iSellerId, sSellerProvider, iModule
     	module_id: iModuleId,
     	item_id: iItemId,
     	item_count: 1,
+        item_addons: sItemAddons,
     	redirect: sRedirect ? sRedirect : '',
     	custom: sCustom ? sCustom : '',
     	_t: oDate.getTime()
@@ -86,7 +91,7 @@ BxPaymentCart.prototype.subscribe = function(iSellerId, sSellerProvider, iModule
         this._sActionsUrl + 'subscribe_json/',
         oParams,
         function(oData){
-        	processJsonData(oData);
+            processJsonData(oData);
         },
         'json'
     );

+ 52 - 50
modules/boonex/payment/js/chargebee_v3.js

@@ -9,14 +9,14 @@
  */
 
 function BxPaymentProviderChargebeeV3(oOptions) {
-	this.init(oOptions);
+    this.init(oOptions);
 }
 
 BxPaymentProviderChargebeeV3.prototype = new BxPaymentMain();
 
 BxPaymentProviderChargebeeV3.prototype.init = function(oOptions) {
-	this._sProvider = oOptions.sProvider;
-	this._sActionsUrl = oOptions.sActionUrl;
+    this._sProvider = oOptions.sProvider;
+    this._sActionsUrl = oOptions.sActionUrl;
     this._sObjName = oOptions.sObjName == undefined ? 'oPaymentProviderChargebeeV3' : oOptions.sObjName;
     this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
     this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
@@ -28,64 +28,66 @@ BxPaymentProviderChargebeeV3.prototype.init = function(oOptions) {
     this._iItemId  = oOptions.iItemId == undefined ? 0 : oOptions.iItemId;
     this._sItemName = oOptions.sItemName == undefined ? '' : oOptions.sItemName;
     this._iItemCount  = oOptions.iItemCount == undefined ? 0 : oOptions.iItemCount;
+    this._sItemAddons  = oOptions.sItemAddons == undefined ? '' : oOptions.sItemAddons;
     this._sRedirect  = oOptions.sRedirect == undefined ? '' : oOptions.sRedirect;
     this._sCustom  = oOptions.sCustom == undefined ? '' : oOptions.sCustom;
 
-	this._rHandler = Chargebee.init({
+    this._rHandler = Chargebee.init({
         site: oOptions.sSite
     });
-	this._rHandler = Chargebee.getInstance();
+    this._rHandler = Chargebee.getInstance();
 };
 
 BxPaymentProviderChargebeeV3.prototype.subscribe = function(oLink) {
-	var $this = this;
+    var $this = this;
     var oDate = new Date();
 
-  	oLink = jQuery(oLink);
-	if(oLink.hasClass('bx-btn-disabled'))
-		return;
-
-	oLink.addClass('bx-btn-disabled');
-
-	this._rHandler.openCheckout({
-	    hostedPage: function() {
-	    	return $.post({
-	    		url: $this._sActionsUrl + 'call/' + $this._sProvider + '/get_hosted_page/' + $this._iClientId + '/' + $this._iSellerId + '/' + $this._sItemName + '/',
-	    		dataType: 'json'
-	    	});
-	    },
-	    success: function(sHostedPageId) {
-	    	$this.loadingInPopup(oLink, true);
-
-		    var oParams = {
-		    	seller_id: $this._iSellerId,
-		    	seller_provider: $this._sProvider,
-		    	module_id: $this._iModuleId,
-		    	item_id: $this._iItemId,
-		    	item_count: $this._iItemCount,
-		    	redirect: $this._sRedirect,
-		    	custom: $this._sCustom,
-		    	page_id: sHostedPageId,
-		    	_t: oDate.getTime()
-		    };
-
-		    $.post(
-		        $this._sActionsUrl + 'subscribe_json/',
-		        oParams,
-		        function(oData){
-		        	$this.loadingInPopup(oLink, true);
-
-		        	processJsonData(oData);
-		        },
-		        'json'
-		    );
-	    },
-	    close: function() {
-	    	oLink.removeClass('bx-btn-disabled');
-	    }
-	});
+    oLink = jQuery(oLink);
+    if(oLink.hasClass('bx-btn-disabled'))
+        return;
+
+    oLink.addClass('bx-btn-disabled');
+
+    this._rHandler.openCheckout({
+        hostedPage: function() {
+            return $.post({
+                url: $this._sActionsUrl + 'call/' + $this._sProvider + '/get_hosted_page/' + $this._iClientId + '/' + $this._iSellerId + '/' + $this._sItemName + '/' + $this._sItemAddons + '/',
+                dataType: 'json'
+            });
+        },
+        success: function(sHostedPageId) {
+            $this.loadingInPopup(oLink, true);
+
+            var oParams = {
+                seller_id: $this._iSellerId,
+                seller_provider: $this._sProvider,
+                module_id: $this._iModuleId,
+                item_id: $this._iItemId,
+                item_count: $this._iItemCount,
+                item_addons: $this._sItemAddons,
+                redirect: $this._sRedirect,
+                custom: $this._sCustom,
+                page_id: sHostedPageId,
+                _t: oDate.getTime()
+            };
+
+            $.post(
+                $this._sActionsUrl + 'subscribe_json/',
+                oParams,
+                function(oData){
+                    $this.loadingInPopup(oLink, true);
+
+                    processJsonData(oData);
+                },
+                'json'
+            );
+        },
+        close: function() {
+            oLink.removeClass('bx-btn-disabled');
+        }
+    });
 
-	return false;
+    return false;
 };
 
 BxPaymentProviderChargebeeV3.prototype.manage = function(oLink, iPendingId) {