Просмотр исходного кода

Ticket #2992 - Time Traker and the other Single Seller related changes. Refactoring for Subscriptions.

AntonLV 5 лет назад
Родитель
Сommit
9c6cfe2b23
23 измененных файлов с 1174 добавлено и 727 удалено
  1. 45 0
      modules/base/payment/classes/BxBaseModPaymentGridOrders.php
  2. 10 0
      modules/base/payment/classes/BxBaseModPaymentGridProviders.php
  3. 24 9
      modules/base/payment/classes/BxBaseModPaymentModule.php
  4. 13 18
      modules/base/payment/classes/BxBaseModPaymentProvider.php
  5. 29 8
      modules/base/payment/classes/BxBaseModPaymentSubscriptions.php
  6. 6 2
      modules/boonex/market/classes/BxMarketTemplate.php
  7. 16 0
      modules/boonex/payment/classes/BxPaymentConfig.php
  8. 37 9
      modules/boonex/payment/classes/BxPaymentDb.php
  9. 49 19
      modules/boonex/payment/classes/BxPaymentGridProcessed.php
  10. 16 4
      modules/boonex/payment/classes/BxPaymentGridSbsAdministration.php
  11. 104 66
      modules/boonex/payment/classes/BxPaymentModule.php
  12. 1 3
      modules/boonex/payment/classes/BxPaymentProviderAppleInApp.php
  13. 2 4
      modules/boonex/payment/classes/BxPaymentProviderChargebee.php
  14. 1 1
      modules/boonex/payment/classes/BxPaymentProviderRecurly.php
  15. 465 466
      modules/boonex/payment/classes/BxPaymentProviderStripe.php
  16. 176 13
      modules/boonex/payment/classes/BxPaymentSubscriptions.php
  17. 16 3
      modules/boonex/payment/install/langs/en.xml
  18. 4 0
      modules/boonex/payment/install/sql/disable.sql
  19. 5 0
      modules/boonex/payment/install/sql/enable.sql
  20. 58 40
      modules/boonex/payment/install/sql/install.sql
  21. 6 0
      modules/boonex/payment/js/orders.js
  22. 2 0
      modules/boonex/payment/template/css/orders.css
  23. 89 62
      modules/boonex/russian/data/langs/bx_payment/ru.xml

+ 45 - 0
modules/base/payment/classes/BxBaseModPaymentGridOrders.php

@@ -16,6 +16,9 @@ class BxBaseModPaymentGridOrders extends BxTemplGrid
 
     protected $_sOrdersType;
 
+    protected $_bSingleSeller;
+    protected $_iSingleSeller;
+
     protected $_sJsObject;
     protected $_sLangsPrefix;
     protected $_sCurrencySign;
@@ -26,6 +29,12 @@ class BxBaseModPaymentGridOrders extends BxTemplGrid
 
         $this->_oModule = BxDolModule::getInstance($this->MODULE);
 
+        $this->_bSingleSeller = $this->_oModule->_oConfig->isSingleSeller();
+
+        $this->_iSingleSeller = 0;
+        if($this->_bSingleSeller)
+            $this->_iSingleSeller = $this->_oModule->_oConfig->getSiteAdmin();
+
         $this->_sLangsPrefix = $this->_oModule->_oConfig->getPrefix('langs');
         $this->_sCurrencySign = $this->_oModule->_oConfig->getDefaultCurrencySign();
 
@@ -83,6 +92,17 @@ class BxBaseModPaymentGridOrders extends BxTemplGrid
         echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aAffected) : array('msg' => _t($this->_sLangsPrefix . 'err_cannot_perform')));
     }
 
+    protected function _getCellHeaderAuthorId ($sKey, $aField)
+    {
+        if(!$this->_bSingleSeller)
+            return '';
+
+        if($this->_aQueryAppend['seller_id'] != $this->_iSingleSeller)
+            $aField['title'] = _t('_bx_payment_grid_column_title_ods_seller_id');
+
+        return parent::_getCellHeaderDefault($sKey, $aField);
+    }
+
     protected function _getCellClientId($mixedValue, $sKey, $aField, $aRow)
     {
         return parent::_getCellDefault($this->_oModule->_oTemplate->displayProfileLink($mixedValue), $sKey, $aField, $aRow);
@@ -92,6 +112,21 @@ class BxBaseModPaymentGridOrders extends BxTemplGrid
     {
         return parent::_getCellDefault($this->_oModule->_oTemplate->displayProfileLink($mixedValue), $sKey, $aField, $aRow);
     }
+    
+    protected function _getCellAuthorId($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(!$this->_bSingleSeller)
+            return '';
+
+        if($this->_aQueryAppend['seller_id'] != $this->_iSingleSeller)
+            $mixedValue = $aRow['seller_id'];
+        
+        $sResult = '';
+        if(!empty($mixedValue) && is_numeric($mixedValue))
+            $sResult = $this->_oModule->_oTemplate->displayProfileLink($mixedValue);
+
+        return parent::_getCellDefault($sResult, $sKey, $aField, $aRow);
+    }
 
     protected function _getCellItem($mixedValue, $sKey, $aField, $aRow)
     {
@@ -117,10 +152,20 @@ class BxBaseModPaymentGridOrders extends BxTemplGrid
     }
 
     protected function _getCellDate($mixedValue, $sKey, $aField, $aRow)
+    {
+        return $this->_getCellDefaultDateTime($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellDefaultDate($mixedValue, $sKey, $aField, $aRow)
     {
         return parent::_getCellDefault(bx_time_js($mixedValue, BX_FORMAT_DATE, true), $sKey, $aField, $aRow);
     }
 
+    protected function _getCellDefaultDateTime($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(bx_time_js($mixedValue, BX_FORMAT_DATE_TIME, true), $sKey, $aField, $aRow);
+    }
+
     protected function _getFilterSelectAll($sName, $aParams = array())
     {
         $sJsObject = !empty($aParams['js_object']) ? $aParams['js_object'] : $this->_sJsObject;

+ 10 - 0
modules/base/payment/classes/BxBaseModPaymentGridProviders.php

@@ -36,6 +36,16 @@ class BxBaseModPaymentGridProviders extends BxTemplGrid
         return parent::_getCellDefault($this->_getCellValue($mixedValue), $sKey, $aField, $aRow);
     }
 
+    protected function _getCellSingleSeller($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_getCellValue($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellTimeTracker($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_getCellValue($mixedValue), $sKey, $aField, $aRow);
+    }
+
     protected function _getCellValue($mixedValue)
     {
         return _t((int)$mixedValue != 0 ? '_Yes' : '_No');

+ 24 - 9
modules/base/payment/classes/BxBaseModPaymentModule.php

@@ -50,15 +50,15 @@ class BxBaseModPaymentModule extends BxBaseModGeneralModule
     	$aModules = $sModule == 'all' ? $this->_oDb->getModulesBy(array('type' => 'modules'), false) : array($this->_oDb->getModuleByName($sModule, false));
 
         foreach($aModules as $aModule) {
-        	if(empty($aModule) || empty($aModule['name']))
-        		continue;
+            if(empty($aModule) || empty($aModule['name']))
+                continue;
 
-        	$mixedData = $this->callGetPaymentData($aModule['name']);
-        	if($mixedData === false)
-        		continue;
+            $mixedData = $this->callGetPaymentData($aModule['name']);
+            if($mixedData === false)
+                continue;
 
-			$sMethodName = $bInstall ? 'insertModule' : 'deleteModule';
-			$this->_oDb->$sMethodName($mixedData);
+            $sMethodName = $bInstall ? 'insertModule' : 'deleteModule';
+            $this->_oDb->$sMethodName($mixedData);
         }
     }
 
@@ -320,18 +320,33 @@ class BxBaseModPaymentModule extends BxBaseModGeneralModule
 
     public function callUnregisterCartItem($mixedModule, $aParams)
     {
-		return BxDolService::call($mixedModule, 'unregister_cart_item', $aParams);
+        return BxDolService::call($mixedModule, 'unregister_cart_item', $aParams);
     }
 
     public function callUnregisterSubscriptionItem($mixedModule, $aParams)
     {
-		return BxDolService::call($mixedModule, 'unregister_subscription_item', $aParams);
+        return BxDolService::call($mixedModule, 'unregister_subscription_item', $aParams);
     }
 
     public function callCancelSubscriptionItem($mixedModule, $aParams)
     {
     	return BxDolService::call($mixedModule, 'cancel_subscription_item', $aParams);
     }
+
+    public function log($mixedContents, $sSection = '', $sTitle = '')
+    {
+        if(is_array($mixedContents))
+            $mixedContents = var_export($mixedContents, true);	
+        else if(is_object($mixedContents))
+            $mixedContents = json_encode($mixedContents);
+
+        if(empty($sSection))
+            $sSection = "Core";
+
+        $sTitle .= "\n";
+
+        bx_log('sys_payments', '[' . $sSection . '] ' . $sTitle . $mixedContents);
+    }
 }
 
 /** @} */

+ 13 - 18
modules/base/payment/classes/BxBaseModPaymentProvider.php

@@ -173,24 +173,19 @@ class BxBaseModPaymentProvider extends BxDol
     }
 
     /**
-	 *
-	 * Writes $contents to a log file specified in the bp_options file or, if missing,
-	 * defaults to a standard filename of 'bplog.txt'.
-	 *
-	 * @param mixed $contents
-	 * @return
-	 * @throws Exception $e 
-	 *
-	 */
-	protected function log($sContents)
-	{
-		if (is_array($sContents))
-			$sContents = var_export($sContents, true);	
-		else if (is_object($sContents))
-			$sContents = json_encode($sContents);
-		
-		bx_log('sys_payments', '[' . $this->_sName . '] ' . $sContents);
-	}
+     *
+     * Writes $contents to a log file specified in the bp_options file or, if missing,
+     * defaults to a standard filename of 'bplog.txt'.
+     *
+     * @param mixed $contents
+     * @return
+     * @throws Exception $e 
+     *
+     */
+    protected function log($mixedContents)
+    {
+        $this->_oModule->log($mixedContents, $this->_sName);
+    }
 }
 
 /** @} */

+ 29 - 8
modules/base/payment/classes/BxBaseModPaymentSubscriptions.php

@@ -11,14 +11,23 @@
 
 class BxBaseModPaymentSubscriptions extends BxDol
 {
-	protected $MODULE;
-	protected $_oModule;
+    protected $MODULE;
+    protected $_oModule;
 
-	function __construct()
+    protected $_bSingleSeller;
+    protected $_iSingleSeller;
+
+    function __construct()
     {
         parent::__construct();
 
         $this->_oModule = BxDolModule::getInstance($this->MODULE);
+
+        $this->_bSingleSeller = $this->_oModule->_oConfig->isSingleSeller();
+
+        $this->_iSingleSeller = 0;
+        if($this->_bSingleSeller)
+            $this->_iSingleSeller = $this->_oModule->_oConfig->getSiteAdmin();
     }
 
     /**
@@ -39,13 +48,13 @@ class BxBaseModPaymentSubscriptions extends BxDol
     /** 
      * @ref bx_base_payment-get_subscriptions_url "get_subscriptions_url"
      */
-	public function serviceGetSubscriptionsUrl($iVendor = 0)
+    public function serviceGetSubscriptionsUrl($iVendor = 0)
     {
     	if(!$this->_oModule->isLogged())
             return '';
 
-		if($iVendor == 0)
-    		return $this->_oModule->_oConfig->getUrl('URL_SUBSCRIPTIONS');
+        if($iVendor == 0)
+            return $this->_oModule->_oConfig->getUrl('URL_SUBSCRIPTIONS');
 
     	return  bx_append_url_params($this->_oModule->_oConfig->getUrl('URL_SUBSCRIPTIONS'), array('seller_id' => $iVendor));
     }
@@ -72,11 +81,14 @@ class BxBaseModPaymentSubscriptions extends BxDol
     /** 
      * @ref bx_base_payment-get_subscribe_url "get_subscribe_url"
      */
-	public function serviceGetSubscribeUrl($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount = 1)
+    public function serviceGetSubscribeUrl($iVendorId, $sVendorProvider, $mixedModuleId, $iItemId, $iItemCount = 1)
     {
     	if(!$this->_oModule->isLogged())
             return '';
 
+        if($this->_bSingleSeller)
+            $iVendorId = $this->_iSingleSeller;
+
     	return  bx_append_url_params($this->_oModule->_oConfig->getUrl('URL_SUBSCRIBE'), array(
     		'seller_id' => $iVendorId,
     		'seller_provider' => $sVendorProvider,
@@ -115,6 +127,9 @@ class BxBaseModPaymentSubscriptions extends BxDol
         if(empty($iModuleId))
             return '';
 
+        if($this->_bSingleSeller)
+            $iVendorId = $this->_iSingleSeller;
+
         return $this->_oModule->_oTemplate->displaySubscribeJs($iVendorId, $sVendorProvider, $iModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
     }
     
@@ -148,6 +163,9 @@ class BxBaseModPaymentSubscriptions extends BxDol
         if(empty($iModuleId))
             return '';
 
+        if($this->_bSingleSeller)
+            $iVendorId = $this->_iSingleSeller;
+
         return $this->_oModule->_oTemplate->displaySubscribeJsWithAddons($iVendorId, $sVendorProvider, $iModuleId, $iItemId, $iItemCount, $sItemAddons, $sRedirect, $aCustom);
     }
 
@@ -180,7 +198,10 @@ class BxBaseModPaymentSubscriptions extends BxDol
         if(empty($iModuleId))
             return '';
 
-		return $this->_oModule->_oTemplate->displaySubscribeLink($iVendorId, $sVendorProvider, $iModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
+        if($this->_bSingleSeller)
+            $iVendorId = $this->_iSingleSeller;
+
+        return $this->_oModule->_oTemplate->displaySubscribeLink($iVendorId, $sVendorProvider, $iModuleId, $iItemId, $iItemCount, $sRedirect, $aCustom);
     }
 }
 

+ 6 - 2
modules/boonex/market/classes/BxMarketTemplate.php

@@ -284,8 +284,12 @@ class BxMarketTemplate extends BxBaseModTextTemplate
                 $sVoting = $oVoting->getElementBlock(array('show_counter' => false));
 
             $sPricing = "";
-            if(!$oPayment->isCreditsOnly() && (float)$aData[$CNF['FIELD_PRICE_RECURRING']] != 0)
-                $sPricing = _t('_bx_market_txt_price_recurring_short', $this->_aCurrency['sign'], $aData[$CNF['FIELD_PRICE_RECURRING']], _t($CNF['T']['txt_per_' . $aData[$CNF['FIELD_DURATION_RECURRING']] . '_short']));
+            if((float)$aData[$CNF['FIELD_PRICE_RECURRING']] != 0) {
+                $sDuration = 'txt_per_' . $aData[$CNF['FIELD_DURATION_RECURRING']] . '_short';
+                $sDuration = _t(!empty($CNF['T'][$sDuration]) ? $CNF['T'][$sDuration] : '_bx_market_txt_per_' . $aData[$CNF['FIELD_DURATION_RECURRING']] . '_short');
+
+                $sPricing = _t('_bx_market_txt_price_recurring_short', $this->_aCurrency['sign'], $aData[$CNF['FIELD_PRICE_RECURRING']], $sDuration);
+            }
 
             if(empty($sPricing) && (float)$aData[$CNF['FIELD_PRICE_SINGLE']] != 0)
                 $sPricing = _t('_bx_market_txt_price_single_short', $this->_aCurrency['sign'], $aData[$CNF['FIELD_PRICE_SINGLE']]);

+ 16 - 0
modules/boonex/payment/classes/BxPaymentConfig.php

@@ -13,6 +13,9 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
 {
     protected $_bCreditsOnly;
 
+    protected $_iPayAttemptsMax;
+    protected $_iPayAttemptsInterval;
+
     function __construct($aModule)
     {
         parent::__construct($aModule);
@@ -118,6 +121,9 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
         );
 
         $this->_bCreditsOnly = false;
+
+        $this->_iPayAttemptsMax = 3;
+        $this->_iPayAttemptsInterval = 86400; //--- in sec
     }
 
     public function init(&$oDb)
@@ -134,6 +140,16 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
         return $this->_bCreditsOnly;
     }
 
+    public function getPayAttemptsMax()
+    {
+        return $this->_iPayAttemptsMax;
+    }
+
+    public function getPayAttemptsInterval()
+    {
+        return $this->_iPayAttemptsInterval;
+    }
+
     public function getPrice($sType, $aItem)
     {
     	$fPrice = 0;

+ 37 - 9
modules/boonex/payment/classes/BxPaymentDb.php

@@ -381,6 +381,16 @@ class BxPaymentDb extends BxBaseModPaymentDb
                 $sWhereClause = " AND `tt`.`seller_id`=:seller_id";
                 break;
 
+            case 'authors':
+                $aMethod['name'] = 'getColumn';
+                $aMethod['params'][1] = array(
+                    'seller_id' => $aParams['seller_id']
+                );
+
+                $sSelectClause = "DISTINCT `tt`.`author_id`";
+                $sWhereClause = " AND `tt`.`seller_id`=:seller_id";
+                break;
+
             case 'license':
                 $aMethod['name'] = 'getAll';
                 $aMethod['params'][1] = array(
@@ -438,7 +448,7 @@ class BxPaymentDb extends BxBaseModPaymentDb
         return $this->getOrderProcessed($aParams);
     }
 
-	/*
+    /*
      * Subscriptions methods
      */
     public function getOrderSubscription($aParams)
@@ -472,6 +482,20 @@ class BxPaymentDb extends BxBaseModPaymentDb
                 $sJoinClause = "LEFT JOIN `" . $this->_sPrefix . "transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id`";
                 $sWhereClause = " AND " . $this->arrayToSQL($aParams['conditions'], ' AND ');
                 break;
+
+            case 'time_tracker':
+                $aMethod['params'][1] = array(
+                    'status_active' => $aParams['status_active'],
+                    'status_trial' => $aParams['status_trial'],
+                    'status_unpaid' => $aParams['status_unpaid'],
+                    'pay_attempts_max' => $aParams['pay_attempts_max'],
+                    'pay_attempts_interval' => $aParams['pay_attempts_interval']
+                );
+
+                $sSelectClause .= ", `ttp`.`client_id`, `ttp`.`seller_id`, `ttp`.`type`, `ttp`.`provider`, `ttp`.`amount`, `ttp`.`order`";
+                $sJoinClause = "LEFT JOIN `" . $this->_sPrefix . "transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id` INNER JOIN `" . $this->_sPrefix . "providers` AS `tp` ON `ttp`.`provider`=`tp`.`name` AND `tp`.`time_tracker`='1'";
+                $sWhereClause = " AND `ts`.`date_next`<=UNIX_TIMESTAMP() AND (`ts`.`status`=:status_active OR `ts`.`status`=:status_trial OR (`ts`.`status`=:status_unpaid AND `ts`.`pay_attempts`<:pay_attempts_max AND DATE_ADD(FROM_UNIXTIME(`ts`.`date_next`), INTERVAL `ts`.`pay_attempts`*:pay_attempts_interval SECOND)<=NOW()))";
+                break;
         }
 
         $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $this->_sPrefix . "subscriptions` AS `ts` " . $sJoinClause . " WHERE 1 " . $sWhereClause . $sLimitClause;
@@ -481,8 +505,8 @@ class BxPaymentDb extends BxBaseModPaymentDb
     public function isSubscriptionByPending($iPending)
     {
     	$aSubscription = $this->getSubscription(array(
-    		'type' => 'pending_id', 
-    		'pending_id' => $iPending
+            'type' => 'pending_id', 
+            'pending_id' => $iPending
     	));
 
     	return !empty($aSubscription) && is_array($aSubscription);
@@ -493,10 +517,14 @@ class BxPaymentDb extends BxBaseModPaymentDb
         if(empty($aValues) || !is_array($aValues))
             return false;
 
-        return (int)$this->query("INSERT INTO `" . $this->_sPrefix . "subscriptions` SET " . $this->arrayToSQL($aValues) . ", `date`=UNIX_TIMESTAMP()") > 0;
+        $sSetClause = $this->arrayToSQL($aValues);
+        if(empty($aValues['date_add']))
+            $sSetClause .= ", `date_add`=UNIX_TIMESTAMP()";
+
+        return (int)$this->query("INSERT INTO `" . $this->_sPrefix . "subscriptions` SET " . $sSetClause) > 0;
     }
 
-	public function updateSubscription($aValues, $aWhere)
+    public function updateSubscription($aValues, $aWhere)
     {
         if(empty($aValues) || !is_array($aValues) || empty($aWhere) || !is_array($aWhere))
             return false;
@@ -507,13 +535,13 @@ class BxPaymentDb extends BxBaseModPaymentDb
     public function deleteSubscription($mixedId, $sReason)
     {
     	if(!is_array($mixedId))
-    		$mixedId = array($mixedId);
+            $mixedId = array($mixedId);
 
         //--- Move to deleted subscriptions table.   
     	$sQuery = "INSERT IGNORE INTO `" . $this->_sPrefix . "subscriptions_deleted` SELECT *, :reason AS `reason`, UNIX_TIMESTAMP() AS `deleted` FROM `" . $this->_sPrefix . "subscriptions` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")";
-		$this->query($sQuery, array(
-		    'reason' => $sReason
-		));
+        $this->query($sQuery, array(
+            'reason' => $sReason
+        ));
 
         return (int)$this->query("DELETE FROM `" . $this->_sPrefix . "subscriptions` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")") > 0;
     }

+ 49 - 19
modules/boonex/payment/classes/BxPaymentGridProcessed.php

@@ -140,17 +140,35 @@ class BxPaymentGridProcessed extends BxBaseModPaymentGridOrders
     {
         $aIds = $this->_oModule->_oDb->getOrderProcessed(array('type' => 'clients', 'seller_id' => $this->_aQueryAppend['seller_id']));
 
-        $aValues = array(
-            array('key' => '', 'value' => _t('_bx_payment_txt_all_clients'))
-        );
+        $aValues = array();
         foreach($aIds as $iId)
-            if(($oClient = BxDolProfile::getInstanceMagic($iId)) !== false)
+            if(!empty($iId) && ($oClient = BxDolProfile::getInstanceMagic($iId)) !== false)
                 $aValues[] = array('key' => $iId, 'value' => $oClient->getDisplayName());
 
         $this->_oModule->_oConfig->sortByColumn('value', $aValues);
 
         return $this->_getFilterSelectAll('client', array(
-            'values' => $aValues
+            'values' => array_merge(array(
+                array('key' => '', 'value' => _t('_bx_payment_txt_all_clients'))
+            ), $aValues)
+        ));
+    }
+
+    protected function _getFilterAuthors()
+    {
+        $aIds = $this->_oModule->_oDb->getOrderProcessed(array('type' => 'authors', 'seller_id' => $this->_aQueryAppend['seller_id']));
+
+        $aValues = array();
+        foreach($aIds as $iId)
+            if(!empty($iId) && ($oClient = BxDolProfile::getInstanceMagic($iId)) !== false)
+                $aValues[] = array('key' => $iId, 'value' => $oClient->getDisplayName());
+
+        $this->_oModule->_oConfig->sortByColumn('value', $aValues);
+
+        return $this->_getFilterSelectAll('author', array(
+            'values' => array_merge(array(
+                array('key' => '', 'value' => _t('_bx_payment_txt_all_authors'))
+            ), $aValues)
         ));
     }
 
@@ -158,9 +176,7 @@ class BxPaymentGridProcessed extends BxBaseModPaymentGridOrders
     {
         $aModules = $this->_oModule->_oDb->getModules();
         
-        $aValues = array(
-            array('key' => '', 'value' => _t('_bx_payment_txt_all_modules'))
-        );
+        $aValues = array();
         foreach($aModules as $aModule) {
             $sLangKey = '_' . $aModule['name'];
             $sLangValue = _t($sLangKey);
@@ -172,19 +188,19 @@ class BxPaymentGridProcessed extends BxBaseModPaymentGridOrders
 
         return $this->_getFilterSelectAll('module', array(
             'js_onchange' => 'onChangeFilterModule(this, ' . $this->_aQueryAppend['seller_id'] . ')',
-            'values' => $aValues
+            'values' => array_merge(array(
+                array('key' => '', 'value' => _t('_bx_payment_txt_all_modules'))
+            ), $aValues)
         ));
     }
 
     protected function _getFilterItems()
     {
-        $aValues = array(
-            array('key' => '', 'value' => _t('_bx_payment_txt_all_items'))
-        );
-
         return $this->_getFilterSelectAll('item', array(
             'attrs' => array('disabled' => 'disabled'),
-            'values' => $aValues
+            'values' => array(
+                array('key' => '', 'value' => _t('_bx_payment_txt_all_items'))
+            )
         ));
     }
 
@@ -192,7 +208,12 @@ class BxPaymentGridProcessed extends BxBaseModPaymentGridOrders
     {
         parent::_getFilterControls();
 
-        return $this->_getFilterClients() . $this->_getFilterModules() . $this->_getFilterItems() . $this->_getSearchInput();
+        $sResult = $this->_getFilterClients();
+        if($this->_bSingleSeller && $this->_aQueryAppend['seller_id'] == $this->_iSingleSeller)
+            $sResult .= $this->_getFilterAuthors();
+        $sResult .= $this->_getFilterModules() . $this->_getFilterItems() . $this->_getSearchInput();
+
+        return $sResult;
     }
 
     protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
@@ -202,16 +223,25 @@ class BxPaymentGridProcessed extends BxBaseModPaymentGridOrders
 
         $sParamsDivider = $this->_oModule->_oConfig->getDivider('DIVIDER_GRID_FILTERS');
 
-        $iClient = $iModule = $iItem = 0;
-        if(strpos($sFilter, $sParamsDivider) !== false)
-            list($iClient, $iModule, $iItem, $sFilter) = explode($sParamsDivider, $sFilter);
+        $iClient = $iAuthor = $iModule = $iItem = 0;
+        if(strpos($sFilter, $sParamsDivider) !== false) {
+            $aFilters = explode($sParamsDivider, $sFilter);
+            if($this->_bSingleSeller && $this->_aQueryAppend['seller_id'] = $this->_iSingleSeller)
+                list($iClient, $iAuthor, $iModule, $iItem, $sFilter) = $aFilters;
+            else
+                list($iClient, $iModule, $iItem, $sFilter) = $aFilters;
+        }
 
         $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND (`tt`.`seller_id`=? OR `tt`.`author_id`=?)", $this->_aQueryAppend['seller_id'], $this->_aQueryAppend['seller_id']);
 
         $iClient = (int)$iClient;
         if($iClient != 0)
             $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `tt`.`client_id`=?", $iClient);
-        
+
+        $iAuthor = (int)$iAuthor;
+        if($iAuthor != 0)
+            $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `tt`.`author_id`=?", $iAuthor);
+
         $iModule = (int)$iModule;
         if($iModule != 0)
             $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `tt`.`module_id`=?", $iModule);

+ 16 - 4
modules/boonex/payment/classes/BxPaymentGridSbsAdministration.php

@@ -67,11 +67,23 @@ class BxPaymentGridSbsAdministration extends BxBaseModPaymentGridOrders
 
         return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
     }
-        
-    protected function _getCellPaid($mixedValue, $sKey, $aField, $aRow)
+
+    protected function _getCellDateAdd($mixedValue, $sKey, $aField, $aRow)
+    {
+        return $this->_getCellDefaultDateTime($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellDateNext($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(empty($mixedValue))
+            return parent::_getCellDefault(_t('_uknown'), $sKey, $aField, $aRow);
+
+        return $this->_getCellDefaultDateTime($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellStatus($mixedValue, $sKey, $aField, $aRow)
     {
-        $aStates = array(0 => 'no', 1 => 'yes');
-        return parent::_getCellDefault(_t('_bx_payment_txt_' . $aStates[(int)$mixedValue]), $sKey, $aField, $aRow);
+        return parent::_getCellDefault(_t('_bx_payment_txt_status_' . $mixedValue), $sKey, $aField, $aRow);
     }
 
     protected function _addJsCss()

+ 104 - 66
modules/boonex/payment/classes/BxPaymentModule.php

@@ -16,6 +16,9 @@ define('BX_PAYMENT_ORDERS_TYPE_HISTORY', 'history');
 
 define('BX_PAYMENT_RESULT_SUCCESS', 0);
 
+/*
+ * Subscriptions: statuses.
+ */
 define('BX_PAYMENT_SBS_STATUS_SCHEDULED', 'scheduled');
 define('BX_PAYMENT_SBS_STATUS_TRIAL', 'trial');
 define('BX_PAYMENT_SBS_STATUS_ACTIVE', 'active');
@@ -24,6 +27,17 @@ define('BX_PAYMENT_SBS_STATUS_PAUSED', 'paused');
 define('BX_PAYMENT_SBS_STATUS_CANCELED', 'canceled');
 define('BX_PAYMENT_SBS_STATUS_UNKNOWN', 'unknown');
 
+/*
+ * Subscriptions: period units.
+ */
+define('BX_PAYMENT_SBS_PU_YEAR', 'year');
+define('BX_PAYMENT_SBS_PU_MONTH', 'month');
+define('BX_PAYMENT_SBS_PU_WEEK', 'week');
+define('BX_PAYMENT_SBS_PU_DAY', 'day');
+define('BX_PAYMENT_SBS_PU_HOUR', 'hour');
+define('BX_PAYMENT_SBS_PU_MINUTE', 'minute');
+
+
 /**
  * Payment module by BoonEx
  *
@@ -363,7 +377,7 @@ class BxPaymentModule extends BxBaseModPaymentModule
 
         $aIds = $this->_oDb->getAdminsIds();
         foreach($aIds as $iId) {
-        	$aUser = $this->getProfileInfo($iId);
+            $aUser = $this->getProfileInfo($iId);
 
             $aResult[] = array(
                 'key' => $iId,
@@ -579,15 +593,15 @@ class BxPaymentModule extends BxBaseModPaymentModule
     	if(!$this->isLogged())
             return $this->_oTemplate->displayPageCodeError($this->_sLangsPrefix . 'err_required_login');
 
-		if(bx_get('seller_id') !== false && bx_get('provider') !== false && bx_get('items') !== false) {
-			$iSellerId = bx_process_input(bx_get('seller_id'), BX_DATA_INT);
-			$sProvider = bx_process_input(bx_get('provider'));
-			$aItems = bx_process_input(bx_get('items'));
+        if(bx_get('seller_id') !== false && bx_get('provider') !== false && bx_get('items') !== false) {
+            $iSellerId = bx_process_input(bx_get('seller_id'), BX_DATA_INT);
+            $sProvider = bx_process_input(bx_get('provider'));
+            $aItems = bx_process_input(bx_get('items'));
 
-			$mixedResult = $this->serviceInitializeCheckout(BX_PAYMENT_TYPE_SINGLE, $iSellerId, $sProvider, $aItems);
-			if($mixedResult !== true)
-	    		return $this->_oTemplate->displayPageCodeError($mixedResult);
-		}
+            $mixedResult = $this->serviceInitializeCheckout(BX_PAYMENT_TYPE_SINGLE, $iSellerId, $sProvider, $aItems);
+            if($mixedResult !== true)
+                return $this->_oTemplate->displayPageCodeError($mixedResult);
+        }
 
         header('Location: ' . $this->_oConfig->getUrl('URL_CART'));
         exit;
@@ -748,7 +762,7 @@ class BxPaymentModule extends BxBaseModPaymentModule
                 'subscription_id' => $aResult['subscription_id']
             );
 
-            if(!$this->registerSubscription($aPending, $aSubscription))
+            if(!$this->getObjectSubscriptions()->register($aPending, $aSubscription))
                 return $this->_oTemplate->displayPageCodeError($this->_sLangsPrefix . 'err_already_registered');
         }
 
@@ -797,9 +811,9 @@ class BxPaymentModule extends BxBaseModPaymentModule
     {
     	$oProvider = $this->getObjectProvider($sProvider, $mixedVendorId);
         if($oProvider === false || !$oProvider->isActive())
-        	return $this->_oTemplate->displayPageCodeError($this->_sLangsPrefix . 'err_incorrect_provider');
+            return $this->_oTemplate->displayPageCodeError($this->_sLangsPrefix . 'err_incorrect_provider');
 
-		$oProvider->notify();
+        $oProvider->notify();
     }
     
     public function actionCall($sProvider, $sAction)
@@ -1004,59 +1018,76 @@ class BxPaymentModule extends BxBaseModPaymentModule
         return $bResult;
     }
 
+    /**
+     * @deprecated since version 12.0.0
+     * 
+     * @use BxPaymentSubscriptions::register instead.
+     */
     public function registerSubscription($aPending, $aParams = array())
     {
-            if($this->_oDb->isSubscriptionByPending($aPending['id']))
-                    return false;
-
-            $aSubscription = array(
-            'pending_id' => $aPending['id'],
-            'customer_id' => $aParams['customer_id'],
-            'subscription_id' => $aParams['subscription_id']
-        );
-        if(!$this->_oDb->insertSubscription($aSubscription))
-            return false;
-
-        $this->onSubscriptionCreate($aPending);
+        return $this->getObjectSubscriptions()->register($aPending, $aParams);
+    }
 
-        return true;
+    /**
+     * @deprecated since version 12.0.0
+     * 
+     * @use BxPaymentSubscriptions::cancelLocal instead.
+     */
+    public function cancelSubscription($mixedPending)
+    {
+        return $this->getObjectSubscriptions()->cancelLocal($mixedPending);
     }
 
-    public function updateSubscription($aPending, $aParams = array())
+    public function processTimeTracker()
     {
-        $this->_oDb->updateSubscription($aParams, array(
-            'pending_id' => $aPending['id']
-        ));
+        if(!defined('BX_DOL_CRON_EXECUTE'))
+            return;
 
-        $this->onSubscriptionUpdate($aPending);
+        $aSubscriptions = $this->_oDb->getSubscription(array(
+            'type' => 'time_tracker',
+            'status_active' => BX_PAYMENT_SBS_STATUS_ACTIVE,
+            'status_trial' => BX_PAYMENT_SBS_STATUS_TRIAL,
+            'status_unpaid' => BX_PAYMENT_SBS_STATUS_UNPAID,
+            'pay_attempts_max' => $this->_oConfig->getPayAttemptsMax(),
+            'pay_attempts_interval' => $this->_oConfig->getPayAttemptsInterval()
+        ));
 
-        return true;
-    }
+        if(empty($aSubscriptions) || !is_array($aSubscriptions))
+            return;
 
-    public function cancelSubscription($mixedPending)
-    {
-        $aPending = is_array($mixedPending) ? $mixedPending : $this->_oDb->getOrderPending(array('type' => 'id', 'id' => (int)$mixedPending));
-        if(empty($aPending) || !is_array($aPending) || $aPending['type'] != BX_PAYMENT_TYPE_RECURRING)
-            return false;
+        foreach($aSubscriptions as $aSubscription) {
+            $aPending = $this->_oDb->getOrderPending(array('type' => 'id', 'id' => (int)$aSubscription['pending_id']));
+            if(empty($aPending) || !is_array($aPending) || $aPending['type'] != BX_PAYMENT_TYPE_RECURRING) {
+                $this->log($aSubscription, 'Time Tracker', 'Cannot process subscription.');
+                continue;
+            }
 
-        $aItems = $this->_oConfig->descriptorsM2A($aPending['items']);
-        foreach($aItems as $aItem)
-            $this->callCancelSubscriptionItem((int)$aItem['module_id'], array($aPending['client_id'], $aPending['seller_id'], $aItem['item_id'], $aItem['item_count'], $aPending['order']));
+            $oProvider = $this->getObjectProvider($aPending['provider'], $aPending['seller_id']);
+            if($oProvider === false || !$oProvider->isActive() || !method_exists($oProvider, 'makePayment')) {
+                $this->log($aSubscription, 'Time Tracker', 'Payment provider unavailable or incorrect.');
+                continue;
+            }
 
-        $aSubscription = $this->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
-        if(!empty($aSubscription) && is_array($aSubscription) && !$this->_oDb->deleteSubscription($aSubscription['id'], 'cancel'))
-            return false;
+            if(!$oProvider->makePayment($aPending)) {
+                $this->log($aSubscription, 'Time Tracker', 'Payment cannot be processed.');
+                continue;
+            }
 
-        $this->onSubscriptionCancel($aPending);
+            if(!$this->getObjectSubscriptions()->prolong($aPending)) {
+                $this->log($aSubscription, 'Time Tracker', 'Cannot prolong subscription.');
+                continue;
+            }
 
-        return true;
+            if(!$this->registerPayment($aPending)) {
+                $this->log($aSubscription, 'Time Tracker', 'Cannot register payment.');
+                continue;
+            }
+        }
     }
 
     public function onPaymentRegisterBefore($aPending, $aResult = array())
     {
-        //--- 'System' -> 'Before Register Payment' for Alerts Engine ---//
         bx_alert('system', 'before_register_payment', 0, $aPending['client_id'], array('pending' => $aPending));
-        //--- 'System' -> 'Before Register Payment' for Alerts Engine ---//
     }
 
     public function onPaymentRegister($aPending, $aResult = array())
@@ -1064,45 +1095,52 @@ class BxPaymentModule extends BxBaseModPaymentModule
         $bTypeSingle = $aPending['type'] == BX_PAYMENT_TYPE_SINGLE;
 
         if($bTypeSingle) {
-        $aItems = $this->_oConfig->descriptorsM2A($aPending['items']);
-        foreach($aItems as $aItem)
-            $this->isAllowedPurchase(array('module_id' => $aItem['module_id'], 'item_id' => $aItem['item_id']), true);
+            $aItems = $this->_oConfig->descriptorsM2A($aPending['items']);
+            foreach($aItems as $aItem)
+                $this->isAllowedPurchase(array('module_id' => $aItem['module_id'], 'item_id' => $aItem['item_id']), true);
         }
 
-        //--- 'System' -> 'Register Payment' for Alerts Engine ---//
         bx_alert('system', 'register_payment', 0, $aPending['client_id'], array('pending' => $aPending));
-        //--- 'System' -> 'Register Payment' for Alerts Engine ---//
     }
 
     public function onPaymentRefund($aPending, $aResult = array())
     {
-        //--- 'System' -> 'Refund Payment' for Alerts Engine ---//
         bx_alert('system', 'refund_payment', 0, $aPending['client_id'], array('pending' => $aPending));
-        //--- 'System' -> 'Refund Payment' for Alerts Engine ---//
     }
 
-    public function onSubscriptionCreate($aPending, $aResult = array())
+    public function onSubscriptionCreate($aPending, $aSubscription, $aResult = array())
     {
         $aItems = $this->_oConfig->descriptorsM2A($aPending['items']);
         $this->isAllowedPurchase(array('module_id' => $aItems[0]['module_id'], 'item_id' => $aItems[0]['item_id']), true);
 
-        //--- 'System' -> 'Create Subscription' for Alerts Engine ---//
-        bx_alert('system', 'create_subscription', 0, $aPending['client_id'], array('pending' => $aPending));
-        //--- 'System' -> 'Create Subscription' for Alerts Engine ---//
+        bx_alert('system', 'create_subscription', 0, $aPending['client_id'], array(
+            'pending' => $aPending,
+            'subscription' => $aSubscription
+        ));
+    }
+
+    public function onSubscriptionProlong($aPending, $aSubscription, $aResult = array())
+    {
+        bx_alert('system', 'prolong_subscription', 0, $aPending['client_id'], array(
+            'pending' => $aPending,
+            'subscription' => $aSubscription
+        ));
     }
 
-    public function onSubscriptionUpdate($aPending, $aResult = array())
+    public function onSubscriptionOverdue($aPending, $aSubscription, $aResult = array())
     {
-        //--- 'System' -> 'Update Subscription' for Alerts Engine ---//
-        bx_alert('system', 'update_subscription', 0, $aPending['client_id'], array('pending' => $aPending));
-        //--- 'System' -> 'Update Subscription' for Alerts Engine ---//
+        bx_alert('system', 'overdue_subscription', 0, $aPending['client_id'], array(
+            'pending' => $aPending,
+            'subscription' => $aSubscription
+        ));
     }
 
-    public function onSubscriptionCancel($aPending, $aResult = array())
+    public function onSubscriptionCancel($aPending, $aSubscription, $aResult = array())
     {
-        //--- 'System' -> 'Cancel Subscription' for Alerts Engine ---//
-        bx_alert('system', 'cancel_subscription', 0, $aPending['client_id'], array('pending' => $aPending));
-        //--- 'System' -> 'Cancel Subscription' for Alerts Engine ---//
+        bx_alert('system', 'cancel_subscription', 0, $aPending['client_id'], array(
+            'pending' => $aPending,
+            'subscription' => $aSubscription
+        ));
     }
 
     public function setSiteSubmenu($sSubmenu, $sSelModule, $sSelName)

+ 1 - 3
modules/boonex/payment/classes/BxPaymentProviderAppleInApp.php

@@ -76,9 +76,7 @@ class BxPaymentProviderAppleInApp extends BxBaseModPaymentProvider implements iB
             return false;
 
         if($aPending['type'] == BX_PAYMENT_TYPE_RECURRING)
-            $this->_oModule->updateSubscription($aPending, array(
-                'paid' => 1
-            ));
+            $this->_oModule->getObjectSubscriptions()->prolong($aPending);
 
         return $this->_oModule->registerPayment($aPending);
     }

+ 2 - 4
modules/boonex/payment/classes/BxPaymentProviderChargebee.php

@@ -400,9 +400,7 @@ class BxPaymentProviderChargebee extends BxBaseModPaymentProvider implements iBx
             return false;
 
         if($aPending['type'] == BX_PAYMENT_TYPE_RECURRING)
-            $this->_oModule->updateSubscription($aPending, array(
-                'paid' => 1
-            ));
+            $this->_oModule->getObjectSubscriptions()->prolong($aPending);
 
         return $this->_oModule->registerPayment($aPending);
     }
@@ -426,7 +424,7 @@ class BxPaymentProviderChargebee extends BxBaseModPaymentProvider implements iBx
         list($aPending, $aSubscription) = $mixedResult;
         $this->onDeleteSubscription($aSubscription['id'], $aSubscription);
 
-        return $this->_oModule->cancelSubscription($aPending);
+        return $this->_oModule->getObjectSubscriptions()->cancelLocal($aPending);
     }
 
     protected function _getDataTransaction(&$aEvent, $sWithStatusCheck = '')

+ 1 - 1
modules/boonex/payment/classes/BxPaymentProviderRecurly.php

@@ -307,7 +307,7 @@ class BxPaymentProviderRecurly extends BxBaseModPaymentProvider implements iBxBa
 			return false;
 
 		list($aPending) = $mixedResult;
-		return $this->_oModule->cancelSubscription($aPending);
+		return $this->_oModule->getObjectSubscriptions()->cancelLocal($aPending);
 	}
 
 	protected function _getData(&$aEvent, $bWithStatusCheck = false)

+ 465 - 466
modules/boonex/payment/classes/BxPaymentProviderStripe.php

@@ -93,118 +93,118 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
     	$sToken = bx_process_input(bx_get('token'));
 
     	if(empty($aCartInfo['items']) || !is_array($aCartInfo['items']))
-    		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']);
 
-		$aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
-		if(!empty($aPending['order']) || !empty($aPending['error_code']) || !empty($aPending['error_msg']) || (int)$aPending['processed'] != 0)
+        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
+        if(!empty($aPending['order']) || !empty($aPending['error_code']) || !empty($aPending['error_msg']) || (int)$aPending['processed'] != 0)
             return $this->_sLangsPrefix . 'err_already_processed';
 
-		switch($aPending['type']) {
-			case BX_PAYMENT_TYPE_SINGLE:
-				$aCartInfo['items_title'] = '';
-				foreach($aCartInfo['items'] as $aItem)
-		            $aCartInfo['items_title'] .= ' ' . $aItem['title'] . ',';
-				$aCartInfo['items_title'] = trim($aCartInfo['items_title'], ', ');
-
-				$mixedResult = $this->_createCharge($sToken, $iPendingId, $aClient, $aCartInfo);
-				if($mixedResult === false)
-					return $this->_sLangsPrefix . 'err_cannot_perform';
-
-				header("Location: " . $this->getReturnDataUrl($aVendor['id'], array(
-					'order_id' => $mixedResult['order'],
-					'customer_id' => $mixedResult['customer'], 
-					'pending_id' => $aPending['id'],
-				    'redirect' => $sRedirect
-				)));
-				exit;
-
-			case BX_PAYMENT_TYPE_RECURRING:
-				$mixedResult = $this->_createSubscription($sToken, $iPendingId, $aClient, $aCartInfo);
-				if($mixedResult === false)
-					return $this->_sLangsPrefix . 'err_cannot_perform';
-
-				return array(
-					'code' => 0,
-					'eval' => $this->_oModule->_oConfig->getJsObject('cart') . '.onSubscribeSubmit(oData);',
-					'redirect' => $this->getReturnDataUrl($aVendor['id'], array(
-						'order_id' => $mixedResult['order'],
-						'customer_id' => $mixedResult['customer'],
-						'pending_id' => $aPending['id'],
-						'redirect' => $sRedirect
-					))
-				);
-		}
+        switch($aPending['type']) {
+            case BX_PAYMENT_TYPE_SINGLE:
+                $aCartInfo['items_title'] = '';
+                foreach($aCartInfo['items'] as $aItem)
+                    $aCartInfo['items_title'] .= ' ' . $aItem['title'] . ',';
+                $aCartInfo['items_title'] = trim($aCartInfo['items_title'], ', ');
+
+                $mixedResult = $this->_createCharge($sToken, $iPendingId, $aClient, $aCartInfo);
+                if($mixedResult === false)
+                    return $this->_sLangsPrefix . 'err_cannot_perform';
+
+                header("Location: " . $this->getReturnDataUrl($aVendor['id'], array(
+                    'order_id' => $mixedResult['order'],
+                    'customer_id' => $mixedResult['customer'], 
+                    'pending_id' => $aPending['id'],
+                    'redirect' => $sRedirect
+                )));
+                exit;
+
+            case BX_PAYMENT_TYPE_RECURRING:
+                $mixedResult = $this->_createSubscription($sToken, $iPendingId, $aClient, $aCartInfo);
+                if($mixedResult === false)
+                    return $this->_sLangsPrefix . 'err_cannot_perform';
+
+                return array(
+                    'code' => 0,
+                    'eval' => $this->_oModule->_oConfig->getJsObject('cart') . '.onSubscribeSubmit(oData);',
+                    'redirect' => $this->getReturnDataUrl($aVendor['id'], array(
+                        'order_id' => $mixedResult['order'],
+                        'customer_id' => $mixedResult['customer'],
+                        'pending_id' => $aPending['id'],
+                        'redirect' => $sRedirect
+                    ))
+                );
+        }
     }
 
     public function finalizeCheckout(&$aData)
     {
     	$sOrderId = bx_process_input($aData['order_id']);
     	$sCustomerId = bx_process_input($aData['customer_id']);
-		$iPendingId = bx_process_input($aData['pending_id'], BX_DATA_INT);
+        $iPendingId = bx_process_input($aData['pending_id'], BX_DATA_INT);
         if(empty($iPendingId))
-        	return array('code' => 1, 'message' => $this->_sLangsPrefix . 'err_wrong_data');
+            return array('code' => 1, 'message' => $this->_sLangsPrefix . 'err_wrong_data');
 
         $sRedirect = bx_process_input($aData['redirect']);
 
-		$aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
+        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
         if(!empty($aPending['order']) || !empty($aPending['error_code']) || !empty($aPending['error_msg']) || (int)$aPending['processed'] != 0)
             return array('code' => 3, 'message' => $this->_sLangsPrefix . 'err_already_processed');
 
-		$aResult = array(
-			'code' => BX_PAYMENT_RESULT_SUCCESS,
-        	'message' => '',
-			'pending_id' => $iPendingId,
-		    'customer_id' => '',
-		    'subscription_id' => '',
-			'client_name' => '',
-			'client_email' => '',
-			'paid' => false,
-			'trial' => false,
-		    'redirect' => $sRedirect
-		);
-
-		switch($aPending['type']) {
-			case BX_PAYMENT_TYPE_SINGLE:
-			    $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_SINGLE, $sCustomerId);
-				$oCharge = $this->_retrieveCharge($sOrderId);
-				if($oCustomer === false || $oCharge === false)
-				    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
-				
-				$aCustomer = $oCustomer->jsonSerialize();
-				$aCharge = $oCharge->jsonSerialize();
-				if(empty($aCustomer) || !is_array($aCustomer) || empty($aCharge) || !is_array($aCharge))
-					return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
-
-				$aResult = array_merge($aResult, array(
-					'message' => $this->_sLangsPrefix . 'strp_msg_charged',
-					'client_email' => $aCustomer['email'],
-					'paid' => (bool)$aCharge['paid']
-				));
-				break;
-
-			case BX_PAYMENT_TYPE_RECURRING:
-			    $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
-				$oSubscription = $this->_retrieveSubscription($sCustomerId, $sOrderId);
-				if($oCustomer === false || $oSubscription === false)
-					return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
-
-				$aCustomer = $oCustomer->jsonSerialize();
-				$aSubscription = $oSubscription->jsonSerialize();
-				if(empty($aCustomer) || !is_array($aCustomer) || empty($aSubscription) || !is_array($aSubscription))
-					return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
-
-				$aResult = array_merge($aResult, array(
-					'message' => $this->_sLangsPrefix . 'strp_msg_subscribed',
+        $aResult = array(
+            'code' => BX_PAYMENT_RESULT_SUCCESS,
+            'message' => '',
+            'pending_id' => $iPendingId,
+            'customer_id' => '',
+            'subscription_id' => '',
+            'client_name' => '',
+            'client_email' => '',
+            'paid' => false,
+            'trial' => false,
+            'redirect' => $sRedirect
+        );
+
+        switch($aPending['type']) {
+            case BX_PAYMENT_TYPE_SINGLE:
+                $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_SINGLE, $sCustomerId);
+                $oCharge = $this->_retrieveCharge($sOrderId);
+                if($oCustomer === false || $oCharge === false)
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aCustomer = $oCustomer->jsonSerialize();
+                $aCharge = $oCharge->jsonSerialize();
+                if(empty($aCustomer) || !is_array($aCustomer) || empty($aCharge) || !is_array($aCharge))
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aResult = array_merge($aResult, array(
+                    'message' => $this->_sLangsPrefix . 'strp_msg_charged',
+                    'client_email' => $aCustomer['email'],
+                    'paid' => (bool)$aCharge['paid']
+                ));
+                break;
+
+            case BX_PAYMENT_TYPE_RECURRING:
+                $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
+                $oSubscription = $this->_retrieveSubscription($sCustomerId, $sOrderId);
+                if($oCustomer === false || $oSubscription === false)
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aCustomer = $oCustomer->jsonSerialize();
+                $aSubscription = $oSubscription->jsonSerialize();
+                if(empty($aCustomer) || !is_array($aCustomer) || empty($aSubscription) || !is_array($aSubscription))
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aResult = array_merge($aResult, array(
+                    'message' => $this->_sLangsPrefix . 'strp_msg_subscribed',
                     'customer_id' => $sCustomerId,
-				    'subscription_id' => $sOrderId,
-					'client_email' => $aCustomer['email'],
-				    'trial' => $aSubscription['status'] == 'trialing'
-				));
-				break;
-		}
+                    'subscription_id' => $sOrderId,
+                    'client_email' => $aCustomer['email'],
+                    'trial' => $aSubscription['status'] == 'trialing'
+                ));
+                break;
+        }
 
         //--- Update pending transaction ---//
         $this->_oModule->_oDb->updateOrderPending($iPendingId, array(
@@ -218,8 +218,8 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
 
     public function notify()
     {
-		$iResult = $this->_processEvent();
-		http_response_code($iResult);
+        $iResult = $this->_processEvent();
+        http_response_code($iResult);
     }
 
     public function getButtonSingle($iClientId, $iVendorId, $aParams = array())
@@ -312,12 +312,12 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
             'plan' => $sName,
             'cost' => _t('_bx_payment_strp_txt_cost_mask', (int)$aSubscription['plan']['amount'] / 100, $aSubscription['plan']['currency'], $aSubscription['plan']['interval']),
             'status' => $aSubscription['status'],
-        	'created' => bx_time_js($aSubscription['created']),
-        	'started' => !empty($aSubscription['start']) ? bx_time_js($aSubscription['start']) : $sNone,
-        	'trial_start' => !empty($aSubscription['trial_start']) ? bx_time_js($aSubscription['trial_start']) : $sNone,
-        	'trial_end' => !empty($aSubscription['trial_end']) ? bx_time_js($aSubscription['trial_end']) : $sNone,
+            'created' => bx_time_js($aSubscription['created']),
+            'started' => !empty($aSubscription['start']) ? bx_time_js($aSubscription['start']) : $sNone,
+            'trial_start' => !empty($aSubscription['trial_start']) ? bx_time_js($aSubscription['trial_start']) : $sNone,
+            'trial_end' => !empty($aSubscription['trial_end']) ? bx_time_js($aSubscription['trial_end']) : $sNone,
             'cperiod_start' => !empty($aSubscription['current_period_start']) ? bx_time_js($aSubscription['current_period_start']) : $sNone,
-        	'cperiod_end' => !empty($aSubscription['current_period_end']) ? bx_time_js($aSubscription['current_period_end']) : $sNone,
+            'cperiod_end' => !empty($aSubscription['current_period_end']) ? bx_time_js($aSubscription['current_period_end']) : $sNone,
         ));
     }
 
@@ -360,7 +360,7 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         }
 
         return $this->_oModule->_oTemplate->parseHtmlByName('strp_details_change_recuring.html', array(
-        	'object' => $this->_oModule->_oConfig->getJsObject('subscription'),
+            'object' => $this->_oModule->_oConfig->getJsObject('subscription'),
             'form' => $oForm->getCode(),
             'form_id' => $oForm->aFormAttrs['id'],
         ));
@@ -488,393 +488,392 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         return $oToken->jsonSerialize();
     }
 
-	protected function _createCustomer($sType, $sToken, $aClient)
-	{
-	    $oCustomer = null;
-	    $aCustomer = array(
-			'card' => $sToken,
-			'email' => !empty($aClient['email']) ? $aClient['email'] : ''
-		);
-
-	    bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_create_customer', 0, $aClient['id'], array(
-	    	'type' => $sType,
-			'customer_object' => &$oCustomer, 
-			'customer_params' => &$aCustomer
-		));
-
-		try {
-			$this->_oCustomer = !empty($oCustomer) ? $oCustomer : \Stripe\Customer::create($aCustomer);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Create Customer Error: ', $oException);
-		}
-
-		return $this->_oCustomer->jsonSerialize();
-	}
-
-	protected function _retrieveCustomer($sType, $sId)
-	{
-	    $oCustomer = null;
-	    bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_customer', 0, false, array(
-	    	'type' => $sType,
-	    	'customer_id' => &$sId,
-			'customer_object' => &$oCustomer
-		));
-
-		try {
-		    if(empty($oCustomer))
-			    $oCustomer = \Stripe\Customer::retrieve($sId);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Retrieve Customer Error: ', $oException);
-		}
-
-		return $oCustomer;
-	}
-
-        protected function _retrieveProduct($sId)
-	{
-	    $oProduct = null;
-	    bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_product', 0, false, array(
-	    	'product_id' => &$sId,
-                'product_object' => &$oProduct
-            ));
+    protected function _createCustomer($sType, $sToken, $aClient)
+    {
+        $oCustomer = null;
+        $aCustomer = array(
+            'card' => $sToken,
+            'email' => !empty($aClient['email']) ? $aClient['email'] : ''
+        );
 
-            if(!empty($oProduct))
-                return $oProduct;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_create_customer', 0, $aClient['id'], array(
+            'type' => $sType,
+            'customer_object' => &$oCustomer, 
+            'customer_params' => &$aCustomer
+        ));
 
-            try {
-                $oProduct = \Stripe\Product::retrieve($sId);
-            }
-            catch (Exception $oException) {
-                return $this->_processException('Retrieve Product Error: ', $oException);
-            }
+        try {
+            $this->_oCustomer = !empty($oCustomer) ? $oCustomer : \Stripe\Customer::create($aCustomer);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Create Customer Error: ', $oException);
+        }
+
+        return $this->_oCustomer->jsonSerialize();
+    }
 
+    protected function _retrieveCustomer($sType, $sId)
+    {
+        $oCustomer = null;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_customer', 0, false, array(
+            'type' => $sType,
+            'customer_id' => &$sId,
+            'customer_object' => &$oCustomer
+        ));
+
+        try {
+            if(empty($oCustomer))
+                $oCustomer = \Stripe\Customer::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Customer Error: ', $oException);
+        }
+
+        return $oCustomer;
+    }
+
+    protected function _retrieveProduct($sId)
+    {
+        $oProduct = null;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_product', 0, false, array(
+            'product_id' => &$sId,
+            'product_object' => &$oProduct
+        ));
+
+        if(!empty($oProduct))
             return $oProduct;
-	}
-
-	protected function _createCharge($sToken, $iPendingId, &$aClient, &$aCartInfo) {
-		if(empty($this->_oCustomer))
-			$this->_createCustomer(BX_PAYMENT_TYPE_SINGLE, $sToken, $aClient);
-
-		if(empty($this->_oCustomer))
-			return false;
-
-		$fAmount = 100 * (float)$aCartInfo['items_price'];
-
-		$oCharge = null;
-		$aCharge = array(
-			'customer' => $this->_oCustomer->id,
-			'amount' => $fAmount,
-			'currency' => $aCartInfo['vendor_currency_code'],
-			'description' => $aCartInfo['items_title'],
-			'metadata' => array(
-				'vendor' => $aCartInfo['vendor_id'],
-				'client' => $aClient['id'],
-				'product' => $iPendingId,
-				'verification' => $this->_getVerificationCodeCharge($aCartInfo['vendor_id'], $aClient['id'], $fAmount, $aCartInfo['vendor_currency_code'])
-			)
-		);
-
-		bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_create_charge', $iPendingId, false, array(
-			'charge_object' => &$oCharge, 
-			'charge_params' => &$aCharge
-		));
-
-		try {
-		    if(empty($oCharge))
-			    $oCharge = \Stripe\Charge::create($aCharge);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Create Charge Error: ', $oException);
-		}
-
-		$aResult = $oCharge->jsonSerialize();
-		if(empty($aResult) || !is_array($aResult) || empty($aResult['paid']))
-			return false;
-
-		$aMetadata = $aResult['metadata'];
-		if(empty($aMetadata['verification']) || $aMetadata['verification'] != $this->_getVerificationCodeCharge($aCartInfo['vendor_id'], $aClient['id'], $aResult['amount'], $aResult['currency']))
-			return false;
-
-		return array(
-			'pending' => $iPendingId,
-			'amount' =>(float)$aResult['amount'] / 100,
-			'customer' => $this->_oCustomer->id,
-			'order' => $aResult['id']
-		);
-	}
-
-	protected function _retrieveCharge($sId) {
-	    $oCharge = null;
-	    bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_charge', 0, false, array(
-	    	'charge_id' => &$sId,
-			'charge_object' => &$oCharge
-		));
-
-		try {
-		    if(empty($oCharge))
-			    $oCharge = \Stripe\Charge::retrieve($sId);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Retrieve Charge Error: ', $oException);
-		}
-
-		return $oCharge;
-	}
-
-	protected function _createSubscription($sToken, $iPendingId, &$aClient, &$aCartInfo)
-	{
-		if(empty($this->_oCustomer))
-			$this->_createCustomer(BX_PAYMENT_TYPE_RECURRING, $sToken, $aClient);
-
-		if(empty($this->_oCustomer))
-			return false;
-
-		$aItem = array_shift($aCartInfo['items']);
-		if(empty($aItem) || !is_array($aItem))
-			return false;
-
-		$iTrial = $this->_oModule->_oConfig->getTrial(BX_PAYMENT_TYPE_RECURRING, $aItem);
-		$bTrial = !empty($iTrial);
-
-		$oSubscription = null;
-		$aSubscription = array(
-			'plan' => $aItem['name'],
-			'metadata' => array(
-				'vendor' => $aCartInfo['vendor_id'],
-				'client' => $aClient['id'],
-				'product' => $iPendingId,
-				'verification' => $this->_getVerificationCodeSubscription($aCartInfo['vendor_id'], $aClient['id'], $aItem['name'], $aCartInfo['vendor_currency_code'])
-			)
-		);
-
-		bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_create_subscription', $iPendingId, false, array(
-		    'customer' => &$this->_oCustomer,
-			'subscription_object' => &$oSubscription, 
-			'subscription_params' => &$aSubscription
-		));
-
-		try {
-             if(empty($oSubscription))
-    			$oSubscription = $this->_oCustomer->subscriptions->create($aSubscription);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Create Subscription Error: ', $oException);
-		}
-
-		$aResult = $oSubscription->jsonSerialize();
-		if(empty($aResult) || !is_array($aResult) || (!$bTrial && $aResult['status'] != 'active') || ($bTrial && !in_array($aResult['status'], array('active', 'trialing'))))
-			return false;
-
-		$aMetadata = $aResult['metadata'];
-		if(empty($aMetadata['verification']) || $aMetadata['verification'] != $this->_getVerificationCodeSubscription($aCartInfo['vendor_id'], $aClient['id'], $aResult['plan']['id'], $aResult['plan']['currency']))
-			return false;
-
-		return array(
-			'pending' => $iPendingId,
-			'amount' =>(float)$aCartInfo['items_price'],
-			'customer' => $this->_oCustomer->id, 
-			'order' => $aResult['id'],
-			'trial' => $bTrial
-		);
-	}
-
-	protected function _retrieveSubscription($sCustomerId, $sSubscriptionId)
-	{
-		try {
-			$oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
-			$oSubscription = $oCustomer->subscriptions->retrieve($sSubscriptionId);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Retrieve Subscription Error: ', $oException);
-		}
-
-		return $oSubscription;
-	}
-
-	protected function _cancelSubscription($sCustomerId, $sSubscriptionId)
-	{
-	    try {
-	        $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
-	        $oSubscription = $oSubscription->cancel();
-	    }
-	    catch (Exception $oException) {
-			return $this->_processException('Cancel Subscription Error: ', $oException);
-		}
-
-		bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_cancel_subscription', 0, false, array(
-		    'subscription_id' => $sSubscriptionId,
-			'subscription_object' => &$oSubscription
-		));
-
-		return $oSubscription;
-	}
+
+        try {
+            $oProduct = \Stripe\Product::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Product Error: ', $oException);
+        }
+
+        return $oProduct;
+    }
+
+    protected function _createCharge($sToken, $iPendingId, &$aClient, &$aCartInfo)
+    {
+        if(empty($this->_oCustomer))
+            $this->_createCustomer(BX_PAYMENT_TYPE_SINGLE, $sToken, $aClient);
+
+        if(empty($this->_oCustomer))
+            return false;
+
+        $fAmount = 100 * (float)$aCartInfo['items_price'];
+
+        $oCharge = null;
+        $aCharge = array(
+            'customer' => $this->_oCustomer->id,
+            'amount' => $fAmount,
+            'currency' => $aCartInfo['vendor_currency_code'],
+            'description' => $aCartInfo['items_title'],
+            'metadata' => array(
+                'vendor' => $aCartInfo['vendor_id'],
+                'client' => $aClient['id'],
+                'product' => $iPendingId,
+                'verification' => $this->_getVerificationCodeCharge($aCartInfo['vendor_id'], $aClient['id'], $fAmount, $aCartInfo['vendor_currency_code'])
+            )
+        );
+
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_create_charge', $iPendingId, false, array(
+            'charge_object' => &$oCharge, 
+            'charge_params' => &$aCharge
+        ));
+
+        try {
+            if(empty($oCharge))
+                $oCharge = \Stripe\Charge::create($aCharge);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Create Charge Error: ', $oException);
+        }
+
+        $aResult = $oCharge->jsonSerialize();
+        if(empty($aResult) || !is_array($aResult) || empty($aResult['paid']))
+            return false;
+
+        $aMetadata = $aResult['metadata'];
+        if(empty($aMetadata['verification']) || $aMetadata['verification'] != $this->_getVerificationCodeCharge($aCartInfo['vendor_id'], $aClient['id'], $aResult['amount'], $aResult['currency']))
+            return false;
+
+        return array(
+            'pending' => $iPendingId,
+            'amount' =>(float)$aResult['amount'] / 100,
+            'customer' => $this->_oCustomer->id,
+            'order' => $aResult['id']
+        );
+    }
+
+    protected function _retrieveCharge($sId) {
+        $oCharge = null;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_charge', 0, false, array(
+            'charge_id' => &$sId,
+            'charge_object' => &$oCharge
+        ));
+
+        try {
+        if(empty($oCharge))
+            $oCharge = \Stripe\Charge::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Charge Error: ', $oException);
+        }
+
+        return $oCharge;
+    }
+
+    protected function _createSubscription($sToken, $iPendingId, &$aClient, &$aCartInfo)
+    {
+        if(empty($this->_oCustomer))
+            $this->_createCustomer(BX_PAYMENT_TYPE_RECURRING, $sToken, $aClient);
+
+        if(empty($this->_oCustomer))
+            return false;
+
+        $aItem = array_shift($aCartInfo['items']);
+        if(empty($aItem) || !is_array($aItem))
+            return false;
+
+        $iTrial = $this->_oModule->_oConfig->getTrial(BX_PAYMENT_TYPE_RECURRING, $aItem);
+        $bTrial = !empty($iTrial);
+
+        $oSubscription = null;
+        $aSubscription = array(
+            'plan' => $aItem['name'],
+            'metadata' => array(
+                'vendor' => $aCartInfo['vendor_id'],
+                'client' => $aClient['id'],
+                'product' => $iPendingId,
+                'verification' => $this->_getVerificationCodeSubscription($aCartInfo['vendor_id'], $aClient['id'], $aItem['name'], $aCartInfo['vendor_currency_code'])
+            )
+        );
+
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_create_subscription', $iPendingId, false, array(
+            'customer' => &$this->_oCustomer,
+            'subscription_object' => &$oSubscription, 
+            'subscription_params' => &$aSubscription
+        ));
+
+        try {
+            if(empty($oSubscription))
+                $oSubscription = $this->_oCustomer->subscriptions->create($aSubscription);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Create Subscription Error: ', $oException);
+        }
+
+        $aResult = $oSubscription->jsonSerialize();
+        if(empty($aResult) || !is_array($aResult) || (!$bTrial && $aResult['status'] != 'active') || ($bTrial && !in_array($aResult['status'], array('active', 'trialing'))))
+            return false;
+
+        $aMetadata = $aResult['metadata'];
+        if(empty($aMetadata['verification']) || $aMetadata['verification'] != $this->_getVerificationCodeSubscription($aCartInfo['vendor_id'], $aClient['id'], $aResult['plan']['id'], $aResult['plan']['currency']))
+            return false;
+
+        return array(
+            'pending' => $iPendingId,
+            'amount' =>(float)$aCartInfo['items_price'],
+            'customer' => $this->_oCustomer->id, 
+            'order' => $aResult['id'],
+            'trial' => $bTrial
+        );
+    }
+
+    protected function _retrieveSubscription($sCustomerId, $sSubscriptionId)
+    {
+        try {
+            $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
+            $oSubscription = $oCustomer->subscriptions->retrieve($sSubscriptionId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Subscription Error: ', $oException);
+        }
+
+        return $oSubscription;
+    }
+
+    protected function _cancelSubscription($sCustomerId, $sSubscriptionId)
+    {
+        try {
+            $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
+            $oSubscription = $oSubscription->cancel();
+        }
+        catch (Exception $oException) {
+                return $this->_processException('Cancel Subscription Error: ', $oException);
+        }
+
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_cancel_subscription', 0, false, array(
+            'subscription_id' => $sSubscriptionId,
+            'subscription_object' => &$oSubscription
+        ));
+
+        return $oSubscription;
+    }
 
     protected function _listPlans($iLimit = 100)
-	{
-	    if($iLimit <= 0)
-	        $iLimit = 1;
+    {
+        if($iLimit <= 0)
+            $iLimit = 1;
         if($iLimit > 100)
             $iLimit = 100;
 
-		try {
-			$oPlans = \Stripe\Plan::all(array('limit' => $iLimit));
-		}
-		catch (Exception $oException) {
-			return $this->_processException('List Plans Error: ', $oException);
-		}
+        try {
+            $oPlans = \Stripe\Plan::all(array('limit' => $iLimit));
+        }
+        catch (Exception $oException) {
+            return $this->_processException('List Plans Error: ', $oException);
+        }
 
-		return $oPlans;
-	}
+        return $oPlans;
+    }
 
     protected function _createCard($sType, $sCustomerId, $sToken)
-	{
-		try {
-			$oCard = $this->_retrieveCustomer($sType, $sCustomerId)->sources->create(array(
-            	'source' => $sToken
+    {
+        try {
+            $oCard = $this->_retrieveCustomer($sType, $sCustomerId)->sources->create(array(
+                'source' => $sToken
             ));
-		}
-		catch (Stripe\Error\Base $oException) {
-			return $this->_processException('Create Card Error: ', $oException);
-		}
+        }
+        catch (Stripe\Error\Base $oException) {
+            return $this->_processException('Create Card Error: ', $oException);
+        }
 
-		return $oCard->jsonSerialize();
-	}
+        return $oCard->jsonSerialize();
+    }
 
     protected function _retrieveCard($sCustomerId, $sCardId = '') {
-		try {
-			$oCustomer = \Stripe\Customer::retrieve($sCustomerId);
-			$oCard = $oCustomer->sources->retrieve(!empty($sCardId) ? $sCardId : $oCustomer->default_source);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Retrieve Card Error: ', $oException);
-		}
-
-		return $oCard;
-	}
-
-	protected function _retrieveCoupon($sId) {
-		try {
-			$oCoupon = \Stripe\Coupon::retrieve($sId);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Retrieve Coupon Error: ', $oException);
-		}
-
-		return $oCoupon;
-	}
-
-	protected function _retrieveEvent($sId) {
-		try {
-			$oEvent = \Stripe\Event::retrieve($sId);
-		}
-		catch (Exception $oException) {
-			return $this->_processException('Retrieve Event Error: ', $oException);
-		}
-
-		return $oEvent;
-	}
-
-	protected function _processEvent()
-	{
-    	$sInput = @file_get_contents("php://input");
-		$aEvent = json_decode($sInput, true);
-		if(empty($aEvent) || !is_array($aEvent)) 
-			return 404;
-
-		$sType = $aEvent['type'];
-		if(!in_array($sType, array('invoice.payment_succeeded', 'charge.refunded', 'customer.subscription.deleted')))
-			return 200;
-
-		$this->log('Webhooks: ' . (!empty($sType) ? $sType : ''));
-		$this->log($aEvent);
-
-		$sMethod = '_processEvent' . bx_gen_method_name($sType, array('.', '_', '-'));
+        try {
+            $oCustomer = \Stripe\Customer::retrieve($sCustomerId);
+            $oCard = $oCustomer->sources->retrieve(!empty($sCardId) ? $sCardId : $oCustomer->default_source);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Card Error: ', $oException);
+        }
+
+        return $oCard;
+    }
+
+    protected function _retrieveCoupon($sId) {
+        try {
+            $oCoupon = \Stripe\Coupon::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Coupon Error: ', $oException);
+        }
+
+        return $oCoupon;
+    }
+
+    protected function _retrieveEvent($sId) {
+        try {
+            $oEvent = \Stripe\Event::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Event Error: ', $oException);
+        }
+
+        return $oEvent;
+    }
+
+    protected function _processEvent()
+    {
+        $sInput = @file_get_contents("php://input");
+        $aEvent = json_decode($sInput, true);
+        if(empty($aEvent) || !is_array($aEvent)) 
+            return 404;
+
+        $sType = $aEvent['type'];
+        if(!in_array($sType, array('invoice.payment_succeeded', 'charge.refunded', 'customer.subscription.deleted')))
+            return 200;
+
+        $this->log('Webhooks: ' . (!empty($sType) ? $sType : ''));
+        $this->log($aEvent);
+
+        $sMethod = '_processEvent' . bx_gen_method_name($sType, array('.', '_', '-'));
     	if(!method_exists($this, $sMethod))
-    		return 200;
+            return 200;
 
     	return $this->$sMethod($aEvent) ? 200 : 403;
     }
 
-	protected function _processEventInvoicePaymentSucceeded(&$aEvent)
-	{
-		$mixedResult = $this->_getData($aEvent);
-		if($mixedResult === false)
-			return false;
+    protected function _processEventInvoicePaymentSucceeded(&$aEvent)
+    {
+        $mixedResult = $this->_getData($aEvent);
+        if($mixedResult === false)
+            return false;
 
-		list($aPending, $oCharge) = $mixedResult;
-		if(empty($aPending) || !is_array($aPending) || empty($oCharge))
-			return false;
+        list($aPending, $oCharge) = $mixedResult;
+        if(empty($aPending) || !is_array($aPending) || empty($oCharge))
+            return false;
 
-		$fChargeAmount = (float)$oCharge->amount / 100;
-		$sChargeCurrency = strtoupper($oCharge->currency);
-		if($this->_bCheckAmount && ((float)$aPending['amount'] != $fChargeAmount || strcasecmp($this->_oModule->_oConfig->getDefaultCurrencyCode(), $sChargeCurrency) !== 0))
-			return false;
+        $fChargeAmount = (float)$oCharge->amount / 100;
+        $sChargeCurrency = strtoupper($oCharge->currency);
+        if($this->_bCheckAmount && ((float)$aPending['amount'] != $fChargeAmount || strcasecmp($this->_oModule->_oConfig->getDefaultCurrencyCode(), $sChargeCurrency) !== 0))
+            return false;
 
         if($aPending['type'] == BX_PAYMENT_TYPE_RECURRING)
-            $this->_oModule->updateSubscription($aPending, array(
-                'paid' => 1
-            ));
+            $this->_oModule->getObjectSubscriptions()->prolong($aPending);
 
-		return $this->_oModule->registerPayment($aPending);
-	}
+        return $this->_oModule->registerPayment($aPending);
+    }
 
-	protected function _processEventChargeRefunded(&$aEvent)
-	{
-		$mixedResult = $this->_getData($aEvent);
-		if($mixedResult === false)
-			return false;
+    protected function _processEventChargeRefunded(&$aEvent)
+    {
+        $mixedResult = $this->_getData($aEvent);
+        if($mixedResult === false)
+            return false;
 
-		list($aPending) = $mixedResult;
-		if(empty($aPending) || !is_array($aPending))
-			return false;
+        list($aPending) = $mixedResult;
+        if(empty($aPending) || !is_array($aPending))
+            return false;
 
-		return $this->_oModule->refundPayment($aPending);
-	}
+        return $this->_oModule->refundPayment($aPending);
+    }
 
-	protected function _processEventCustomerSubscriptionDeleted(&$aEvent)
-	{
-		$mixedResult = $this->_getData($aEvent);
-		if($mixedResult === false)
-			return false;
+    protected function _processEventCustomerSubscriptionDeleted(&$aEvent)
+    {
+        $mixedResult = $this->_getData($aEvent);
+        if($mixedResult === false)
+            return false;
 
-		list($aPending) = $mixedResult;
-		if(empty($aPending) || !is_array($aPending))
-			return true;
+        list($aPending) = $mixedResult;
+        if(empty($aPending) || !is_array($aPending))
+            return true;
 
-		return $this->_oModule->cancelSubscription($aPending);
-	}
+        return $this->_oModule->getObjectSubscriptions()->cancelLocal($aPending);
+    }
 
-	protected function _processException($sMessage, &$oException)
-	{
-		$aError = $oException->getJsonBody();
+    protected function _processException($sMessage, &$oException)
+    {
+        $aError = $oException->getJsonBody();
 
-		$sMessage = $aError['error']['message'];
-		if(empty($sMessage))
-			$sMessage = $oException->getMessage();
+        $sMessage = $aError['error']['message'];
+        if(empty($sMessage))
+            $sMessage = $oException->getMessage();
 
-		$this->log($sMessage . $aError['error']['message']);
-		$this->log($aError);
+        $this->log($sMessage . $aError['error']['message']);
+        $this->log($aError);
 
-		return false;
-	}
+        return false;
+    }
 
-	protected function _getData(&$aEvent, $bRetrieve = true)
-	{
-		if($bRetrieve)
-			$oEvent = $this->_retrieveEvent($aEvent['id']);
-		else 
-			$oEvent = \Stripe\Util\Util::convertToStripeObject($aEvent, array());
+    protected function _getData(&$aEvent, $bRetrieve = true)
+    {
+        if($bRetrieve)
+            $oEvent = $this->_retrieveEvent($aEvent['id']);
+        else 
+            $oEvent = \Stripe\Util\Util::convertToStripeObject($aEvent, array());
 
-		if(empty($oEvent))
-			return false;
+        if(empty($oEvent))
+            return false;
 
-		$aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'order', 'order' => $oEvent->data->object->subscription));
-		$oCharge = $this->_retrieveCharge($oEvent->data->object->charge);
+        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'order', 'order' => $oEvent->data->object->subscription));
+        $oCharge = $this->_retrieveCharge($oEvent->data->object->charge);
 
-		return array($aPending, $oCharge);
-	}
+        return array($aPending, $oCharge);
+    }
 
     protected function _getDataChangeDetailsRecurring($iPendingId)
     {
@@ -916,23 +915,23 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         return $aResult;
     }
 
-	protected function _getVerificationCodeCharge($iVendorId, $iCustomerId, $fAmount, $sCurrency) {
-		return md5(implode('#-#', array(
-			(int)$iVendorId,
-			(int)$iCustomerId,
-			(float)$fAmount,
-			strtoupper($sCurrency)
-		)));
-	}
-
-	protected function _getVerificationCodeSubscription($iVendorId, $iCustomerId, $sSubscription, $sCurrency) {
-		return md5(implode('#-#', array(
-			(int)$iVendorId,
-			(int)$iCustomerId,
-			strtoupper($sSubscription),
-			strtoupper($sCurrency)
-		)));
-	}
+    protected function _getVerificationCodeCharge($iVendorId, $iCustomerId, $fAmount, $sCurrency) {
+        return md5(implode('#-#', array(
+            (int)$iVendorId,
+            (int)$iCustomerId,
+            (float)$fAmount,
+            strtoupper($sCurrency)
+        )));
+    }
+
+    protected function _getVerificationCodeSubscription($iVendorId, $iCustomerId, $sSubscription, $sCurrency) {
+        return md5(implode('#-#', array(
+            (int)$iVendorId,
+            (int)$iCustomerId,
+            strtoupper($sSubscription),
+            strtoupper($sCurrency)
+        )));
+    }
 
     protected function _getButton($sType, $iClientId, $iVendorId, $aParams = array())
     {

+ 176 - 13
modules/boonex/payment/classes/BxPaymentSubscriptions.php

@@ -323,25 +323,153 @@ class BxPaymentSubscriptions extends BxBaseModPaymentSubscriptions
         }
     }
 
-    public function cancel($iPendingId)
+    public function register($aPending, $aParams = array())
+    {
+        if($this->_oModule->_oDb->isSubscriptionByPending($aPending['id']) || empty($aPending['items']))
+            return false;
+
+        $aInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_RECURRING, $aPending['client_id'], $aPending['seller_id'], $aPending['items']);
+        if(empty($aInfo['items']) || !is_array($aInfo['items']))
+            return false;
+
+        $aItem = array_shift($aInfo['items']);
+
+        $iPeriod = (int)$aItem['period_recurring'];
+        $iTrial = (int)$aItem['trial_recurring'];
+
+        $oDate = date_create();
+        $iNow = $iNext = date_format($oDate, 'U');
+
+        if($iTrial > 0) {
+            $sInterval = $this->_getInterval($iPeriod, $aItem['period_unit_recurring'], $iTrial);
+            date_add($oDate, new DateInterval($sInterval));
+            $iNext = date_format($oDate, 'U');
+        }       
+
+        $sUnique = genRndPwd(9, false);
+
+        $aSubscription = array(
+            'pending_id' => $aPending['id'],
+            'customer_id' => !empty($aParams['customer_id']) ? $aParams['customer_id'] : 'bx_cus_' . $sUnique,
+            'subscription_id' => !empty($aParams['subscription_id']) ? $aParams['subscription_id'] : 'bx_sub_' . $sUnique,
+            'period' => $iPeriod,
+            'period_unit' => $aItem['period_unit_recurring'],
+            'trial' => $iTrial,
+            'date_add' => $iNow,
+            'date_next' => $iNext,
+            'status' => $iTrial > 0 ? BX_PAYMENT_SBS_STATUS_TRIAL : BX_PAYMENT_SBS_STATUS_UNPAID
+        );
+
+        if(!$this->_oModule->_oDb->insertSubscription($aSubscription))
+            return false;
+
+        $this->_oModule->onSubscriptionCreate($aPending, $aSubscription);
+
+        return true;
+    }
+
+    public function prolong($aPending, $aParams = array())
     {
-    	$aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $iPendingId));
+        if(empty($aPending['items']))
+            return false;
+
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
         if(empty($aSubscription) || !is_array($aSubscription))
-            return true;
+            return false;
 
-        $aOrder = $this->_oModule->_oDb->getOrderSubscription(array('type' => 'id', 'id' => $iPendingId));
-        if(empty($aOrder) || !is_array($aOrder))
+        $aInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_RECURRING, $aPending['client_id'], $aPending['seller_id'], $aPending['items']);
+        if(empty($aInfo['items']) || !is_array($aInfo['items']))
             return false;
 
-        $iSellerId = (int)$aOrder['seller_id'];
-        $oProvider = $this->_oModule->getObjectProvider($aOrder['provider'], $iSellerId);
+        $aItem = array_shift($aInfo['items']);
+
+        $sInterval = $this->_getInterval((int)$aItem['period_recurring'], $aItem['period_unit_recurring']);
+
+        $oDate = date_create('@' . $aSubscription['date_next']);
+        date_add($oDate, new DateInterval($sInterval));
+
+        $this->_oModule->_oDb->updateSubscription(array_merge(array(
+            'date_next' => date_format($oDate, 'U'),
+            'pay_attempts' => 0,
+            'status' => BX_PAYMENT_SBS_STATUS_ACTIVE
+        ), $aParams), array(
+            'id' => $aSubscription['id']
+        ));
+
+        $this->_oModule->onSubscriptionProlong($aPending, $aSubscription);
+
+        return true;
+    }
+
+    public function overdue($aPending, $aParams = array())
+    {
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        $this->_oModule->_oDb->updateSubscription(array_merge(array(
+            'status' => BX_PAYMENT_SBS_STATUS_UNPAID
+        ), $aParams), array(
+            'id' => $aSubscription['id']
+        ));
+
+        $this->_oModule->onSubscriptionOverdue($aPending, $aSubscription);
+
+        return true;
+    }
+
+    public function cancel($iPendingId)
+    {
+        if(!$this->cancelRemote($iPendingId))
+            return false;
+
+        if(!$this->cancelLocal($iPendingId))
+            return false;
+
+        return true;
+    }
+
+    public function cancelRemote($mixedPending)
+    {
+        $aPending = is_array($mixedPending) ? $mixedPending : $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => (int)$mixedPending));
+        if(empty($aPending) || !is_array($aPending) || $aPending['type'] != BX_PAYMENT_TYPE_RECURRING)
+            return false;
+
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        $iSellerId = (int)$aPending['seller_id'];
+        $oProvider = $this->_oModule->getObjectProvider($aPending['provider'], $iSellerId);
         if($oProvider === false || !$oProvider->isActive())
             return false;
 
-        if(!$oProvider->cancelRecurring($iPendingId, $aSubscription['customer_id'], $aSubscription['subscription_id']))
+        if(!$oProvider->cancelRecurring($aPending['id'], $aSubscription['customer_id'], $aSubscription['subscription_id']))
             return false;
 
-        return $this->_oModule->cancelSubscription($aOrder);
+        return true;
+    }
+
+    public function cancelLocal($mixedPending)
+    {
+        $aPending = is_array($mixedPending) ? $mixedPending : $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => (int)$mixedPending));
+        if(empty($aPending) || !is_array($aPending) || $aPending['type'] != BX_PAYMENT_TYPE_RECURRING)
+            return false;
+
+        $aSubscription = $this->_oModule->_oDb->getSubscription(array('type' => 'pending_id', 'pending_id' => $aPending['id']));
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return false;
+
+        $aItems = $this->_oModule->_oConfig->descriptorsM2A($aPending['items']);
+        foreach($aItems as $aItem)
+            $this->_oModule->callCancelSubscriptionItem((int)$aItem['module_id'], array($aPending['client_id'], $aPending['seller_id'], $aItem['item_id'], $aItem['item_count'], $aPending['order']));
+
+        if(!$this->_oModule->_oDb->deleteSubscription($aSubscription['id'], 'cancel'))
+            return false;
+
+        $this->_oModule->onSubscriptionCancel($aPending, $aSubscription);
+
+        return true;
     }
 
     protected function _getBlock($sType)
@@ -351,13 +479,13 @@ class BxPaymentSubscriptions extends BxBaseModPaymentSubscriptions
         $sMethod = 'displayBlockSbs' . bx_gen_method_name($sType);
         if(!$this->_oModule->_oTemplate->isMethodExists($sMethod))
             return array(
-        		'content' => MsgBox(_t('_Empty'))
+                'content' => MsgBox(_t('_Empty'))
             );
 
     	$iUserId = $this->_oModule->getProfileId();
         if(empty($iUserId))
             return array(
-        		'content' => MsgBox(_t($CNF['T']['ERR_REQUIRED_LOGIN']))
+                'content' => MsgBox(_t($CNF['T']['ERR_REQUIRED_LOGIN']))
             );
 
         $this->_oModule->setSiteSubmenu('menu_dashboard', 'system', 'dashboard-subscriptions');
@@ -368,10 +496,45 @@ class BxPaymentSubscriptions extends BxBaseModPaymentSubscriptions
             $oBlockSubmenu->setSelected($this->MODULE, 'sbs-' . str_replace('_', '-', $sType));     
 
         return array(
-        	'content' => $this->_oModule->_oTemplate->$sMethod($iUserId),
-        	'menu' => $this->_oModule->_oConfig->getObject('menu_sbs_submenu')
+            'content' => $this->_oModule->_oTemplate->$sMethod($iUserId),
+            'menu' => $this->_oModule->_oConfig->getObject('menu_sbs_submenu')
         );
     }
+
+    private function _getInterval($iPeriod, $sPeriodUnit, $iTrial = 0)
+    {
+        if(!empty($iTrial))
+            return 'P' . $iTrial . 'D';
+
+        $sInterval = '';
+        switch($sPeriodUnit) {
+            case BX_PAYMENT_SBS_PU_YEAR:
+                $sInterval = 'P' . $iPeriod . 'Y';
+                break;
+
+            case BX_PAYMENT_SBS_PU_MONTH:
+                $sInterval = 'P' . $iPeriod . 'M';
+                break;
+
+            case BX_PAYMENT_SBS_PU_WEEK:
+                $sInterval = 'P' . (7 * $iPeriod) . 'D';
+                break;
+
+            case BX_PAYMENT_SBS_PU_DAY:
+                $sInterval = 'P' . $iPeriod . 'D';
+                break;
+
+            case BX_PAYMENT_SBS_PU_HOUR:
+                $sInterval = 'PT' . $iPeriod . 'H';
+                break;
+
+            case BX_PAYMENT_SBS_PU_MINUTE:
+                $sInterval = 'PT' . $iPeriod . 'I';
+                break;
+        }
+
+        return $sInterval;
+    }
 }
 
 /** @} */

+ 16 - 3
modules/boonex/payment/install/langs/en.xml

@@ -96,8 +96,11 @@
     <string name="_bx_payment_grid_column_title_pdrs_for_visitor"><![CDATA[For visitors]]></string>
     <string name="_bx_payment_grid_column_title_pdrs_for_single"><![CDATA[One Time]]></string>
     <string name="_bx_payment_grid_column_title_pdrs_for_recurring"><![CDATA[Recurring]]></string>
+    <string name="_bx_payment_grid_column_title_pdrs_single_seller"><![CDATA[Single Seller]]></string>
+    <string name="_bx_payment_grid_column_title_pdrs_time_tracker"><![CDATA[Time Tracker]]></string>
     <string name="_bx_payment_grid_column_title_ods_client_id"><![CDATA[Client]]></string>
     <string name="_bx_payment_grid_column_title_ods_seller_id"><![CDATA[Seller]]></string>
+    <string name="_bx_payment_grid_column_title_ods_author_id"><![CDATA[Author]]></string>
     <string name="_bx_payment_grid_column_title_ods_transaction"><![CDATA[Transaction]]></string>
     <string name="_bx_payment_grid_column_title_ods_license"><![CDATA[License]]></string>
     <string name="_bx_payment_grid_column_title_ods_item"><![CDATA[Product]]></string>
@@ -112,9 +115,10 @@
     <string name="_bx_payment_grid_column_title_sbs_license"><![CDATA[License]]></string>
     <string name="_bx_payment_grid_column_title_sbs_amount"><![CDATA[Amount]]></string>
     <string name="_bx_payment_grid_column_title_sbs_provider"><![CDATA[Provider]]></string>
-    <string name="_bx_payment_grid_column_title_sbs_paid"><![CDATA[Paid]]></string>
+    <string name="_bx_payment_grid_column_title_sbs_status"><![CDATA[Status]]></string>
     <string name="_bx_payment_grid_column_title_sbs_date"><![CDATA[Date]]></string>
-    <string name="_bx_payment_grid_column_title_sbs_date_created"><![CDATA[Created]]></string>
+    <string name="_bx_payment_grid_column_title_sbs_date_add"><![CDATA[Created]]></string>
+    <string name="_bx_payment_grid_column_title_sbs_date_next"><![CDATA[Next Payment]]></string>
     <string name="_bx_payment_grid_column_title_crts_vendor_id"><![CDATA[Vendor]]></string>
     <string name="_bx_payment_grid_column_title_crts_items_count"><![CDATA[Count]]></string>
     <string name="_bx_payment_grid_column_title_crts_items_price"><![CDATA[Price]]></string>
@@ -280,16 +284,25 @@
 	<string name="_bx_payment_txt_no"><![CDATA[No]]></string>
         <string name="_bx_payment_txt_unsubscribe_yes"><![CDATA[Cancel automatically]]></string>
         <string name="_bx_payment_txt_unsubscribe_no"><![CDATA[Do not cancel]]></string>
+        <string name="_bx_payment_txt_all_authors"><![CDATA[All Authors]]></string>
         <string name="_bx_payment_txt_all_clients"><![CDATA[All Clients]]></string>
         <string name="_bx_payment_txt_all_modules"><![CDATA[All Modules]]></string>
         <string name="_bx_payment_txt_all_items"><![CDATA[All Items]]></string>
+        <string name="_bx_payment_txt_status_scheduled"><![CDATA[Scheduled]]></string>
+        <string name="_bx_payment_txt_status_trial"><![CDATA[Trial]]></string>
+        <string name="_bx_payment_txt_status_active"><![CDATA[Active]]></string>
+        <string name="_bx_payment_txt_status_unpaid"><![CDATA[Unpaid]]></string>
+        <string name="_bx_payment_txt_status_paused"><![CDATA[Paused]]></string>
+        <string name="_bx_payment_txt_status_canceled"><![CDATA[Canceled]]></string>
+        <string name="_bx_payment_txt_status_unknown"><![CDATA[Unknown]]></string>
 
         <!-- Credits payment provider -->
 	<string name="_bx_payment_cdt_cpt"><![CDATA[Credits]]></string>
 	<string name="_bx_payment_cdt_dsc"><![CDATA[Credits payment provider]]></string>
 	<string name="_bx_payment_cdt_active_cpt"><![CDATA[Active]]></string>
 	<string name="_bx_payment_cdt_active_dsc"><![CDATA[Activate(Deactivate) provider]]></string>
-        <string name="_bx_payment_cdt_msg_processed"><![CDATA[Payment was successfully processed.]]></string>
+        <string name="_bx_payment_cdt_msg_charged"><![CDATA[Payment was successfully processed.]]></string>
+        <string name="_bx_payment_cdt_msg_subscribed"><![CDATA[Thank you! Subscription created. Please, check your email for further instructions.]]></string>
         <string name="_bx_payment_cdt_err_not_available"><![CDATA[Checkout with Credits isn't available now.]]></string>
         <string name="_bx_payment_cdt_err_wrong_transaction"><![CDATA[Transaction verification failed.]]></string>
         <string name="_bx_payment_txt_name_credits"><![CDATA[Credits]]></string>

+ 4 - 0
modules/boonex/payment/install/sql/disable.sql

@@ -35,5 +35,9 @@ DELETE FROM `sys_acl_actions` WHERE `Module` = @sName;
 DELETE FROM `sys_email_templates` WHERE `Module` = @sName;
 
 
+-- CRON
+DELETE FROM `sys_cron_jobs` WHERE `name` IN ('bx_payment_time_tracker');
+
+
 -- PAYMENTS
 DELETE FROM `sys_objects_payments` WHERE `object` = @sName;

+ 5 - 0
modules/boonex/payment/install/sql/enable.sql

@@ -161,6 +161,11 @@ INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `B
 (@sName, '_bx_payment_et_txt_name_expiration_notification_client', 'bx_payment_expiration_notification_client', '_bx_payment_et_txt_subject_expiration_notification_client', '_bx_payment_et_txt_body_expiration_notification_client');
 
 
+-- CRON
+INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
+('bx_payment_time_tracker', '* * * * *', 'BxPaymentCronTimeTracker', 'modules/boonex/payment/classes/BxPaymentCronTimeTracker.php', '');
+
+
 -- PAYMENTS
 INSERT INTO `sys_objects_payments` (`object`, `title`, `uri`) VALUES
 (@sName, '_bx_payment', 'payment');

+ 58 - 40
modules/boonex/payment/install/sql/install.sql

@@ -12,6 +12,8 @@ CREATE TABLE IF NOT EXISTS `bx_payment_providers` (
   `for_owner_only` tinyint(4) NOT NULL default '0',
   `for_single` tinyint(4) NOT NULL default '0',
   `for_recurring` tinyint(4) NOT NULL default '0',
+  `single_seller` tinyint(4) NOT NULL default '0',
+  `time_tracker` tinyint(4) NOT NULL default '0',
   `active` tinyint(4) NOT NULL default '0',
   `order` tinyint(4) NOT NULL default '0',
   `class_name` varchar(128) NOT NULL default '',
@@ -70,8 +72,13 @@ CREATE TABLE IF NOT EXISTS `bx_payment_subscriptions` (
   `pending_id` int(11) NOT NULL default '0',
   `customer_id` varchar(32) NOT NULL default '',
   `subscription_id` varchar(32) NOT NULL default '',
-  `paid` tinyint(4) NOT NULL default '0',
-  `date` int(11) NOT NULL default '0',
+  `period` int(11) unsigned NOT NULL default '1',
+  `period_unit` varchar(32) NOT NULL default '',
+  `trial` int(11) unsigned NOT NULL default '0',
+  `date_add` int(11) NOT NULL default '0',
+  `date_next` int(11) NOT NULL default '0',
+  `pay_attempts` tinyint(4) NOT NULL default '0',
+  `status` varchar(32) NOT NULL default 'unpaid',
   PRIMARY KEY(`id`),
   UNIQUE KEY `pending_id` (`pending_id`)
 );
@@ -81,8 +88,13 @@ CREATE TABLE IF NOT EXISTS `bx_payment_subscriptions_deleted` (
   `pending_id` int(11) NOT NULL default '0',
   `customer_id` varchar(32) NOT NULL default '',
   `subscription_id` varchar(32) NOT NULL default '',
-  `paid` tinyint(4) NOT NULL default '0',
-  `date` int(11) NOT NULL default '0',
+  `period` int(11) unsigned NOT NULL default '1',
+  `period_unit` varchar(32) NOT NULL default '',
+  `trial` int(11) unsigned NOT NULL default '0',
+  `date_add` int(11) NOT NULL default '0',
+  `date_next` int(11) NOT NULL default '0',
+  `pay_attempts` tinyint(4) NOT NULL default '0',
+  `status` varchar(32) NOT NULL default 'unpaid',
   `reason` varchar(16) NOT NULL default '',
   `deleted` int(11) NOT NULL default '0',
   PRIMARY KEY(`id`),
@@ -115,8 +127,8 @@ CREATE TABLE IF NOT EXISTS `bx_payment_modules` (
 
 
 -- Credits payment provider
-INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('credits', '_bx_payment_cdt_cpt', '_bx_payment_cdt_dsc', 'cdt_', 0, 1, 0, 1, 0, 'BxPaymentProviderCredits');
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `single_seller`, `time_tracker`, `active`, `order`, `class_name`) VALUES
+('credits', '_bx_payment_cdt_cpt', '_bx_payment_cdt_dsc', 'cdt_', 0, 1, 1, 1, 1, 1, 0, 'BxPaymentProviderCredits');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -124,8 +136,8 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 
 -- PayPal payment provider
-INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('paypal', '_bx_payment_pp_cpt', '_bx_payment_pp_dsc', 'pp_', 1, 1, 0, 1, 1, 'BxPaymentProviderPayPal');
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `single_seller`, `active`, `order`, `class_name`) VALUES
+('paypal', '_bx_payment_pp_cpt', '_bx_payment_pp_dsc', 'pp_', 1, 1, 0, 1, 1, 1, 'BxPaymentProviderPayPal');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -256,8 +268,8 @@ INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `fie
 ('bx_payment_grid_carts', 'Array', '', '', 'vendor_id', '', '', '', 20, NULL, 'start', '', '', '', 'like', '', '', 2147483647, 'BxPaymentGridCarts', 'modules/boonex/payment/classes/BxPaymentGridCarts.php'),
 ('bx_payment_grid_cart', 'Array', '', '', 'descriptor', '', '', '', 20, NULL, 'start', '', 'title,description', '', 'like', '', '', 2147483647, 'BxPaymentGridCart', 'modules/boonex/payment/classes/BxPaymentGridCart.php'),
 
-('bx_payment_grid_sbs_list_my', 'Sql', 'SELECT `ttp`.`id` AS `id`, `ttp`.`seller_id` AS `seller_id`, `ts`.`customer_id` AS `customer_id`, `ts`.`subscription_id` AS `subscription_id`, `ttp`.`provider` AS `provider`, `ttp`.`items` AS `items`, `ts`.`date` AS `date` FROM `bx_payment_subscriptions` AS `ts` LEFT JOIN `bx_payment_transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id` WHERE 1 AND `ttp`.`type`=''recurring'' ', 'bx_payment_subscriptions', 'id', 'date', '', '', 100, NULL, 'start', '', 'ts`.`customer_id,ts`.`subscription_id,ts`.`date', '', 'auto', '', '', 2147483647, 'BxPaymentGridSbsList', 'modules/boonex/payment/classes/BxPaymentGridSbsList.php'),
-('bx_payment_grid_sbs_list_all', 'Sql', 'SELECT `ttp`.`id` AS `id`, `ttp`.`client_id` AS `client_id`, `tac`.`email` AS `client_email`, `ttp`.`seller_id` AS `seller_id`, `ts`.`customer_id` AS `customer_id`, `ts`.`subscription_id` AS `subscription_id`, `ttp`.`provider` AS `provider`, `ttp`.`items` AS `items`, `ts`.`paid` AS `paid`, `ts`.`date` AS `date` FROM `bx_payment_subscriptions` AS `ts` LEFT JOIN `bx_payment_transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id` LEFT JOIN `sys_profiles` AS `tpc` ON `ttp`.`client_id`=`tpc`.`id` LEFT JOIN `sys_accounts` AS `tac` ON `tpc`.`account_id`=`tac`.`id` WHERE 1 AND `ttp`.`type`=''recurring'' ', 'bx_payment_subscriptions', 'id', 'date', '', '', 100, NULL, 'start', '', 'tac`.`email,ts`.`customer_id,ts`.`subscription_id,ts`.`date', '', 'auto', '', '', 192, 'BxPaymentGridSbsAdministration', 'modules/boonex/payment/classes/BxPaymentGridSbsAdministration.php'),
+('bx_payment_grid_sbs_list_my', 'Sql', 'SELECT `ttp`.`id` AS `id`, `ttp`.`seller_id` AS `seller_id`, `ts`.`customer_id` AS `customer_id`, `ts`.`subscription_id` AS `subscription_id`, `ttp`.`provider` AS `provider`, `ttp`.`items` AS `items`, `ts`.`date_add` AS `date_add`, `ts`.`date_next` AS `date_next`, `ts`.`status` AS `status` FROM `bx_payment_subscriptions` AS `ts` LEFT JOIN `bx_payment_transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id` WHERE 1 AND `ttp`.`type`=''recurring'' ', 'bx_payment_subscriptions', 'id', 'date_add', '', '', 100, NULL, 'start', '', 'ts`.`customer_id,ts`.`subscription_id,ts`.`date_add', '', 'auto', '', '', 2147483647, 'BxPaymentGridSbsList', 'modules/boonex/payment/classes/BxPaymentGridSbsList.php'),
+('bx_payment_grid_sbs_list_all', 'Sql', 'SELECT `ttp`.`id` AS `id`, `ttp`.`client_id` AS `client_id`, `tac`.`email` AS `client_email`, `ttp`.`seller_id` AS `seller_id`, `ts`.`customer_id` AS `customer_id`, `ts`.`subscription_id` AS `subscription_id`, `ttp`.`provider` AS `provider`, `ttp`.`items` AS `items`, `ts`.`date_add` AS `date_add`, `ts`.`date_next` AS `date_next`, `ts`.`status` AS `status` FROM `bx_payment_subscriptions` AS `ts` LEFT JOIN `bx_payment_transactions_pending` AS `ttp` ON `ts`.`pending_id`=`ttp`.`id` LEFT JOIN `sys_profiles` AS `tpc` ON `ttp`.`client_id`=`tpc`.`id` LEFT JOIN `sys_accounts` AS `tac` ON `tpc`.`account_id`=`tac`.`id` WHERE 1 AND `ttp`.`type`=''recurring'' ', 'bx_payment_subscriptions', 'id', 'date_add', '', '', 100, NULL, 'start', '', 'tac`.`email,ts`.`customer_id,ts`.`subscription_id,ts`.`date', '', 'auto', '', '', 192, 'BxPaymentGridSbsAdministration', 'modules/boonex/payment/classes/BxPaymentGridSbsAdministration.php'),
 ('bx_payment_grid_sbs_history', 'Sql', 'SELECT `tt`.`id` AS `id`, `tt`.`seller_id` AS `seller_id`, `ttp`.`order` AS `transaction`, `tt`.`license` AS `license`, `tt`.`amount` AS `amount`, `tt`.`date` AS `date` FROM `bx_payment_transactions` AS `tt` LEFT JOIN `bx_payment_transactions_pending` AS `ttp` ON `tt`.`pending_id`=`ttp`.`id` WHERE 1 AND `ttp`.`type`=''recurring'' ', 'bx_payment_transactions', 'id', 'date', '', '', 100, NULL, 'start', '', 'ttp`.`order,tt`.`license,tt`.`amount,tt`.`date', '', 'auto', '', '', 2147483647, 'BxPaymentGridSbsHistory', 'modules/boonex/payment/classes/BxPaymentGridSbsHistory.php'),
 
 ('bx_payment_grid_orders_history', 'Sql', 'SELECT `tt`.`id` AS `id`, `tt`.`seller_id` AS `seller_id`, `tt`.`module_id` AS `module_id`, `tt`.`item_id` AS `item_id`, `ttp`.`order` AS `transaction`, `tt`.`license` AS `license`, `tt`.`amount` AS `amount`, `tt`.`date` AS `date` FROM `bx_payment_transactions` AS `tt` LEFT JOIN `bx_payment_transactions_pending` AS `ttp` ON `tt`.`pending_id`=`ttp`.`id` WHERE 1 AND `ttp`.`type`=''single'' ', 'bx_payment_transactions', 'id', 'date', '', '', 100, NULL, 'start', '', 'ttp`.`order,tt`.`license,tt`.`amount,tt`.`date', '', 'auto', '', '', 2147483647, 'BxPaymentGridHistory', 'modules/boonex/payment/classes/BxPaymentGridHistory.php'),
@@ -265,13 +277,15 @@ INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `fie
 ('bx_payment_grid_orders_pending', 'Sql', 'SELECT `tt`.`id` AS `id`, `tt`.`client_id` AS `client_id`, `tt`.`seller_id` AS `seller_id`, `tt`.`items` AS `items`, `tt`.`amount` AS `amount`, `tt`.`order` AS `transaction`, `tt`.`error_msg` AS `error_msg`, `tt`.`provider` AS `provider`, `tt`.`date` AS `date` FROM `bx_payment_transactions_pending` AS `tt` WHERE 1 AND (ISNULL(`tt`.`order`) OR (NOT ISNULL(`tt`.`order`) AND `tt`.`error_code`<>''0'')) ', 'bx_payment_transactions_pending', 'id', 'date', '', '', 100, NULL, 'start', '', 'tt`.`order,tt`.`amount,tt`.`date', '', 'auto', '', '', 2147483647, 'BxPaymentGridPending', 'modules/boonex/payment/classes/BxPaymentGridPending.php');
 
 INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
-('bx_payment_grid_providers', 'order', '', '2%', 0, '', '', 1),
-('bx_payment_grid_providers', 'switcher', '', '8%', 0, '', '', 2),
-('bx_payment_grid_providers', 'caption', '_bx_payment_grid_column_title_pdrs_caption', '25%', 1, '16', '', 3),
-('bx_payment_grid_providers', 'description', '_bx_payment_grid_column_title_pdrs_description', '35%', 1, '32', '', 4),
-('bx_payment_grid_providers', 'for_visitor', '_bx_payment_grid_column_title_pdrs_for_visitor', '10%', 0, '8', '', 5),
-('bx_payment_grid_providers', 'for_single', '_bx_payment_grid_column_title_pdrs_for_single', '10%', 0, '8', '', 6),
-('bx_payment_grid_providers', 'for_recurring', '_bx_payment_grid_column_title_pdrs_for_recurring', '10%', 0, '8', '', 7),
+('bx_payment_grid_providers', 'order', '', '2%', 0, '0', '', 1),
+('bx_payment_grid_providers', 'switcher', '', '8%', 0, '0', '', 2),
+('bx_payment_grid_providers', 'caption', '_bx_payment_grid_column_title_pdrs_caption', '20%', 1, '16', '', 3),
+('bx_payment_grid_providers', 'description', '_bx_payment_grid_column_title_pdrs_description', '30%', 1, '32', '', 4),
+('bx_payment_grid_providers', 'for_visitor', '_bx_payment_grid_column_title_pdrs_for_visitor', '8%', 0, '8', '', 5),
+('bx_payment_grid_providers', 'for_single', '_bx_payment_grid_column_title_pdrs_for_single', '8%', 0, '8', '', 6),
+('bx_payment_grid_providers', 'for_recurring', '_bx_payment_grid_column_title_pdrs_for_recurring', '8%', 0, '8', '', 7),
+('bx_payment_grid_providers', 'single_seller', '_bx_payment_grid_column_title_pdrs_single_seller', '8%', 0, '8', '', 8),
+('bx_payment_grid_providers', 'time_tracker', '_bx_payment_grid_column_title_pdrs_time_tracker', '8%', 0, '8', '', 9),
 
 ('bx_payment_grid_carts', 'checkbox', '', '2%', 0, '', '', 1),
 ('bx_payment_grid_carts', 'vendor_id', '_bx_payment_grid_column_title_crts_vendor_id', '40%', 0, '36', '', 2),
@@ -286,48 +300,52 @@ INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable
 ('bx_payment_grid_cart', 'price_single', '_bx_payment_grid_column_title_crt_price', '10%', 0, '16', '', 5),
 ('bx_payment_grid_cart', 'actions', '', '18%', 0, '', '', 6),
 
-('bx_payment_grid_sbs_list_my', 'checkbox', '', '2%', 0, '', '', 1),
-('bx_payment_grid_sbs_list_my', 'seller_id', '_bx_payment_grid_column_title_sbs_seller_id', '20%', 0, '24', '', 2),
-('bx_payment_grid_sbs_list_my', 'customer_id', '_bx_payment_grid_column_title_sbs_customer_id', '20%', 0, '24', '', 3),
-('bx_payment_grid_sbs_list_my', 'subscription_id', '_bx_payment_grid_column_title_sbs_subscription_id', '20%', 0, '32', '', 4),
+('bx_payment_grid_sbs_list_my', 'checkbox', '', '2%', 0, '0', '', 1),
+('bx_payment_grid_sbs_list_my', 'seller_id', '_bx_payment_grid_column_title_sbs_seller_id', '16%', 0, '0', '', 2),
+('bx_payment_grid_sbs_list_my', 'customer_id', '_bx_payment_grid_column_title_sbs_customer_id', '10%', 0, '8', '', 3),
+('bx_payment_grid_sbs_list_my', 'subscription_id', '_bx_payment_grid_column_title_sbs_subscription_id', '10%', 0, '0', '', 4),
 ('bx_payment_grid_sbs_list_my', 'provider', '_bx_payment_grid_column_title_sbs_provider', '10%', 0, '16', '', 5),
-('bx_payment_grid_sbs_list_my', 'date', '_bx_payment_grid_column_title_sbs_date_created', '14%', 0, '10', '', 6),
-('bx_payment_grid_sbs_list_my', 'actions', '', '14%', 0, '', '', 7),
+('bx_payment_grid_sbs_list_my', 'date_add', '_bx_payment_grid_column_title_sbs_date_add', '14%', 0, '10', '', 6),
+('bx_payment_grid_sbs_list_my', 'date_next', '_bx_payment_grid_column_title_sbs_date_next', '14%', 0, '10', '', 7),
+('bx_payment_grid_sbs_list_my', 'status', '_bx_payment_grid_column_title_sbs_status', '6%', 0, '8', '', 8),
+('bx_payment_grid_sbs_list_my', 'actions', '', '18%', 0, '0', '', 9),
 
-('bx_payment_grid_sbs_list_all', 'seller_id', '_bx_payment_grid_column_title_sbs_seller_id', '10%', 0, '24', '', 1),
-('bx_payment_grid_sbs_list_all', 'client_id', '_bx_payment_grid_column_title_sbs_client_id', '10%', 0, '24', '', 2),
+('bx_payment_grid_sbs_list_all', 'seller_id', '_bx_payment_grid_column_title_sbs_seller_id', '12%', 0, '0', '', 1),
+('bx_payment_grid_sbs_list_all', 'client_id', '_bx_payment_grid_column_title_sbs_client_id', '12%', 0, '0', '', 2),
 ('bx_payment_grid_sbs_list_all', 'client_email', '_bx_payment_grid_column_title_sbs_client_email', '15%', 0, '24', '', 3),
-('bx_payment_grid_sbs_list_all', 'customer_id', '_bx_payment_grid_column_title_sbs_customer_id', '15%', 0, '18', '', 4),
-('bx_payment_grid_sbs_list_all', 'subscription_id', '_bx_payment_grid_column_title_sbs_subscription_id', '15%', 0, '32', '', 5),
+('bx_payment_grid_sbs_list_all', 'customer_id', '_bx_payment_grid_column_title_sbs_customer_id', '5%', 0, '8', '', 4),
+('bx_payment_grid_sbs_list_all', 'subscription_id', '_bx_payment_grid_column_title_sbs_subscription_id', '5%', 0, '0', '', 5),
 ('bx_payment_grid_sbs_list_all', 'provider', '_bx_payment_grid_column_title_sbs_provider', '5%', 0, '16', '', 6),
-('bx_payment_grid_sbs_list_all', 'paid', '_bx_payment_grid_column_title_sbs_paid', '4%', 0, '4', '', 7),
-('bx_payment_grid_sbs_list_all', 'date', '_bx_payment_grid_column_title_sbs_date_created', '10%', 0, '10', '', 8),
-('bx_payment_grid_sbs_list_all', 'actions', '', '16%', 0, '', '', 9),
+('bx_payment_grid_sbs_list_all', 'date_add', '_bx_payment_grid_column_title_sbs_date_add', '12%', 0, '10', '', 7),
+('bx_payment_grid_sbs_list_all', 'date_next', '_bx_payment_grid_column_title_sbs_date_next', '12%', 0, '10', '', 8),
+('bx_payment_grid_sbs_list_all', 'status', '_bx_payment_grid_column_title_sbs_status', '6%', 0, '8', '', 9),
+('bx_payment_grid_sbs_list_all', 'actions', '', '16%', 0, '0', '', 10),
 
-('bx_payment_grid_sbs_history', 'seller_id', '_bx_payment_grid_column_title_sbs_seller_id', '24%', 0, '20', '', 1),
+('bx_payment_grid_sbs_history', 'seller_id', '_bx_payment_grid_column_title_sbs_seller_id', '24%', 0, '0', '', 1),
 ('bx_payment_grid_sbs_history', 'transaction', '_bx_payment_grid_column_title_sbs_transaction', '22%', 0, '18', '', 2),
 ('bx_payment_grid_sbs_history', 'license', '_bx_payment_grid_column_title_sbs_license', '22%', 0, '18', '', 3),
 ('bx_payment_grid_sbs_history', 'amount', '_bx_payment_grid_column_title_sbs_amount', '10%', 1, '10', '', 4),
 ('bx_payment_grid_sbs_history', 'date', '_bx_payment_grid_column_title_sbs_date', '10%', 0, '10', '', 5),
 ('bx_payment_grid_sbs_history', 'actions', '', '12%', 0, '', '', 6),
 
-('bx_payment_grid_orders_history', 'seller_id', '_bx_payment_grid_column_title_ods_seller_id', '24%', 0, '20', '', 1),
+('bx_payment_grid_orders_history', 'seller_id', '_bx_payment_grid_column_title_ods_seller_id', '24%', 0, '0', '', 1),
 ('bx_payment_grid_orders_history', 'transaction', '_bx_payment_grid_column_title_ods_transaction', '22%', 0, '18', '', 2),
 ('bx_payment_grid_orders_history', 'item', '_bx_payment_grid_column_title_ods_item', '22%', 0, '18', '', 3),
 ('bx_payment_grid_orders_history', 'amount', '_bx_payment_grid_column_title_ods_amount', '10%', 1, '10', '', 4),
 ('bx_payment_grid_orders_history', 'date', '_bx_payment_grid_column_title_ods_date', '10%', 0, '10', '', 5),
 ('bx_payment_grid_orders_history', 'actions', '', '12%', 0, '', '', 6),
 
-('bx_payment_grid_orders_processed', 'checkbox', '', '2%', 0, '', '', 1),
-('bx_payment_grid_orders_processed', 'client_id', '_bx_payment_grid_column_title_ods_client_id', '22%', 0, '18', '', 2),
-('bx_payment_grid_orders_processed', 'transaction', '_bx_payment_grid_column_title_ods_transaction', '20%', 0, '18', '', 3),
-('bx_payment_grid_orders_processed', 'item', '_bx_payment_grid_column_title_ods_item', '20%', 0, '18', '', 4),
-('bx_payment_grid_orders_processed', 'amount', '_bx_payment_grid_column_title_ods_amount', '10%', 1, '10', '', 5),
-('bx_payment_grid_orders_processed', 'date', '_bx_payment_grid_column_title_ods_date', '10%', 0, '10', '', 6),
-('bx_payment_grid_orders_processed', 'actions', '', '16%', 0, '', '', 7),
+('bx_payment_grid_orders_processed', 'checkbox', '', '2%', 0, '0', '', 1),
+('bx_payment_grid_orders_processed', 'client_id', '_bx_payment_grid_column_title_ods_client_id', '16%', 0, '0', '', 2),
+('bx_payment_grid_orders_processed', 'author_id', '_bx_payment_grid_column_title_ods_author_id', '16%', 0, '0', '', 3),
+('bx_payment_grid_orders_processed', 'transaction', '_bx_payment_grid_column_title_ods_transaction', '10%', 0, '8', '', 4),
+('bx_payment_grid_orders_processed', 'item', '_bx_payment_grid_column_title_ods_item', '20%', 0, '', '', 5),
+('bx_payment_grid_orders_processed', 'amount', '_bx_payment_grid_column_title_ods_amount', '10%', 1, '10', '', 6),
+('bx_payment_grid_orders_processed', 'date', '_bx_payment_grid_column_title_ods_date', '10%', 0, '10', '', 7),
+('bx_payment_grid_orders_processed', 'actions', '', '16%', 0, '0', '', 8),
 
 ('bx_payment_grid_orders_pending', 'checkbox', '', '2%', 0, '', '', 1),
-('bx_payment_grid_orders_pending', 'client_id', '_bx_payment_grid_column_title_ods_client_id', '25%', 0, '25', '', 2),
+('bx_payment_grid_orders_pending', 'client_id', '_bx_payment_grid_column_title_ods_client_id', '25%', 0, '0', '', 2),
 ('bx_payment_grid_orders_pending', 'transaction', '_bx_payment_grid_column_title_ods_transaction', '25%', 0, '25', '', 3),
 ('bx_payment_grid_orders_pending', 'amount', '_bx_payment_grid_column_title_ods_amount', '14%', 1, '14', '', 4),
 ('bx_payment_grid_orders_pending', 'date', '_bx_payment_grid_column_title_ods_date', '14%', 0, '14', '', 5),

+ 6 - 0
modules/boonex/payment/js/orders.js

@@ -45,6 +45,10 @@ BxPaymentOrders.prototype.onChangeFilter = function(oElement) {
     var bClient = oClient.length > 0;
     var sClient = bClient ? oClient.val() : '';
 
+    var oAuthor = $('#bx-grid-author-' + this._sObjNameGrid);
+    var bAuthor = oAuthor.length > 0;
+    var sAuthor = bAuthor ? oAuthor.val() : '';
+    
     var oModule = $('#bx-grid-module-' + this._sObjNameGrid);
     var bModule = oModule.length > 0;
     var sModule = bModule ? oModule.val() : '';
@@ -62,6 +66,8 @@ BxPaymentOrders.prototype.onChangeFilter = function(oElement) {
         var sValue = '';
         if(bClient)
             sValue += sClient + $this._sParamsDivider;
+        if(bAuthor)
+            sValue += sAuthor + $this._sParamsDivider;
         if(bModule)
             sValue += sModule + $this._sParamsDivider;
         if(bItem)

+ 2 - 0
modules/boonex/payment/template/css/orders.css

@@ -80,10 +80,12 @@ div.bx-payment-vo-items {
     width: 165px;
 }
 #bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-client,
+#bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-author,
 #bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-module,
 #bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-item {
     margin-right: 20px;
 }
+#bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-author,
 #bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-module,
 #bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-item,
 #bx-grid-wrap-bx_payment_grid_orders_processed div.bx-grid-header div#bx-form-element-keyword {

+ 89 - 62
modules/boonex/russian/data/langs/bx_payment/ru.xml

@@ -95,9 +95,12 @@
     <string name="_bx_payment_grid_column_title_pdrs_description"><![CDATA[Описание]]></string>
     <string name="_bx_payment_grid_column_title_pdrs_for_visitor"><![CDATA[Для посетителей]]></string>
     <string name="_bx_payment_grid_column_title_pdrs_for_single"><![CDATA[Разовые]]></string>
-    <string name="_bx_payment_grid_column_title_pdrs_for_recurring"><![CDATA[Периодические]]></string>
+    <string name="_bx_payment_grid_column_title_pdrs_for_recurring"><![CDATA[Подписки]]></string>
+    <string name="_bx_payment_grid_column_title_pdrs_single_seller"><![CDATA[Единый продавец]]></string>
+    <string name="_bx_payment_grid_column_title_pdrs_time_tracker"><![CDATA[Счетчик времени]]></string>
     <string name="_bx_payment_grid_column_title_ods_client_id"><![CDATA[Покупатель]]></string>
     <string name="_bx_payment_grid_column_title_ods_seller_id"><![CDATA[Продавец]]></string>
+    <string name="_bx_payment_grid_column_title_ods_author_id"><![CDATA[Автор]]></string>
     <string name="_bx_payment_grid_column_title_ods_transaction"><![CDATA[Транзакция]]></string>
     <string name="_bx_payment_grid_column_title_ods_license"><![CDATA[Лицензия]]></string>
     <string name="_bx_payment_grid_column_title_ods_item"><![CDATA[Продукт]]></string>
@@ -113,8 +116,9 @@
     <string name="_bx_payment_grid_column_title_sbs_amount"><![CDATA[Сумма]]></string>
     <string name="_bx_payment_grid_column_title_sbs_provider"><![CDATA[Поставщик]]></string>
     <string name="_bx_payment_grid_column_title_sbs_date"><![CDATA[Дата]]></string>
-    <string name="_bx_payment_grid_column_title_sbs_date_created"><![CDATA[Дата создания]]></string>
-    <string name="_bx_payment_grid_column_title_sbs_paid"><![CDATA[Оплачено]]></string>
+    <string name="_bx_payment_grid_column_title_sbs_date_add"><![CDATA[Дата создания]]></string>
+    <string name="_bx_payment_grid_column_title_sbs_date_next"><![CDATA[Дата сл. оплаты]]></string>
+    <string name="_bx_payment_grid_column_title_sbs_status"><![CDATA[Статус]]></string>
     <string name="_bx_payment_grid_column_title_crts_vendor_id"><![CDATA[Продавец]]></string>
     <string name="_bx_payment_grid_column_title_crts_items_count"><![CDATA[Количество]]></string>
     <string name="_bx_payment_grid_column_title_crts_items_price"><![CDATA[Цена]]></string>
@@ -188,15 +192,15 @@
     <!-- Predefined Values -->
     <string name="_bx_payment_pre_lists_currencies"><![CDATA[Валюты]]></string>
 
-	<!-- ACL -->
-	<string name="_bx_payment_acl_action_purchase"><![CDATA[Покупать]]></string>
-	<string name="_bx_payment_acl_action_sell"><![CDATA[Продавать]]></string>
-	<string name="_bx_payment_acl_action_manage_any_purchase"><![CDATA[Управлять любыми покупками]]></string>
+    <!-- ACL -->
+    <string name="_bx_payment_acl_action_purchase"><![CDATA[Покупать]]></string>
+    <string name="_bx_payment_acl_action_sell"><![CDATA[Продавать]]></string>
+    <string name="_bx_payment_acl_action_manage_any_purchase"><![CDATA[Управлять любыми покупками]]></string>
 
-	<!-- EMAIL TEMPLATES -->
-	<string name="_bx_payment_et_txt_name_paid_need_join"><![CDATA[Заплатил - присоединяйся.]]></string>
-	<string name="_bx_payment_et_txt_subject_paid_need_join"><![CDATA[Оплата была принята.]]></string>
-	<string name="_bx_payment_et_txt_body_paid_need_join"><![CDATA[{email_header}
+    <!-- EMAIL TEMPLATES -->
+    <string name="_bx_payment_et_txt_name_paid_need_join"><![CDATA[Заплатил - присоединяйся.]]></string>
+    <string name="_bx_payment_et_txt_subject_paid_need_join"><![CDATA[Оплата была принята.]]></string>
+    <string name="_bx_payment_et_txt_body_paid_need_join"><![CDATA[{email_header}
 <p>Привет, {RealName}.</p>
 <p>Ваша оплата была принята. Если вы ещё не заполняли регистрационную форму, то Вы можете сделать это используя следующую ссылку.</p>
 <p><a href="{JoinLink}">Присоединяйтесь</a></p>
@@ -228,57 +232,80 @@
 <p><b>Внимание</b>. Данные, связанные с подпиской, будут удалены в ближайшее время.</p>
 {email_footer}]]></string>
 
-	<!-- MESSAGES, WARNINGS and ERRORS -->
-	<string name="_bx_payment_msg_no_results"><![CDATA[Пусто]]></string>
-	<string name="_bx_payment_msg_redirecting"><![CDATA[Пересылаю... Пожалуйста подождите.]]></string>
-	<string name="_bx_payment_msg_item_added"><![CDATA[Товар был успешно добавлен в вашу корзину.]]></string>
-	<string name="_bx_payment_msg_item_deleted"><![CDATA[Товар был успешно удален из вашей корзины.]]></string>
-	<string name="_bx_payment_msg_successfully_performed"><![CDATA[Действие было успешно выполнено.]]></string>
-	<string name="_bx_payment_msg_successfully_processed_free"><![CDATA[Бесплатный товар был успешно обработан.]]></string>
-	<string name="_bx_payment_msg_cancelation_request_sent"><![CDATA[Запрос на отмену был отправлен. Пожалуйста, ждите.]]></string>
-        <string name="_bx_payment_msg_confirm_cancelation"><![CDATA[Вы уверены, что хотите отменить вашу подписку?]]></string>
-        <string name="_bx_payment_msg_single_seller"><![CDATA[Режим единого продавца активирован. <a href="{0}">Перейти</a> в единую корзину.]]></string>
-	<string name="_bx_payment_err_access_db"><![CDATA[Ошибка обращения к базе данных.]]></string>
-	<string name="_bx_payment_err_cannot_validate"><![CDATA[Не могу присоединиться к удаленному хосту для проверки данных.]]></string>
-	<string name="_bx_payment_err_empty_items"><![CDATA[Товары не выбраны.]]></string>
-	<string name="_bx_payment_err_empty_order"><![CDATA[Заказ пуст.]]></string>
-	<string name="_bx_payment_err_inactive_vendor"><![CDATA[Профиль продавца не активен.]]></string>
-	<string name="_bx_payment_err_incorrect_provider"><![CDATA[Неправильная или недоступная платежная система.]]></string>
-	<string name="_bx_payment_err_not_accept_payments"><![CDATA[Продавец не принимает платежи.]]></string>
-	<string name="_bx_payment_err_nothing_selected"><![CDATA[Вам нужно выбрать как минимум один товар.]]></string>
-	<string name="_bx_payment_err_self_purchase"><![CDATA[Покупка из собственного магазина не возможна.]]></string>
-	<string name="_bx_payment_err_required_login"><![CDATA[Вам нужно войти.]]></string>
-	<string name="_bx_payment_err_unknown_vendor"><![CDATA[Неизвестный продавец.]]></string>
-	<string name="_bx_payment_err_wrong_data"><![CDATA[Неверные входные данные.]]></string>
-	<string name="_bx_payment_err_not_found_pending"><![CDATA[Обрабатываемый заказ не найден.]]></string>
-	<string name="_bx_payment_err_cannot_perform"><![CDATA[Действие не может быть выполнено. Свяжитесь с нами.]]></string>
-	<string name="_bx_payment_err_wrong_quantity"><![CDATA[Указано неправильное количество.]]></string>
-	<string name="_bx_payment_err_already_processed"><![CDATA[Этот платеж уже обработан.]]></string>
-	<string name="_bx_payment_err_already_registered"><![CDATA[Подписка с аналогичными данными уже была зарегистрирована.]]></string>
-
-	<!-- Other -->
-	<string name="_bx_payment_txt_add_to_cart"><![CDATA[В корзину]]></string>
-	<string name="_bx_payment_txt_subscribe"><![CDATA[Подписаться]]></string>
-	<string name="_bx_payment_txt_payment_to"><![CDATA[Платеж для {0}]]></string>
-	<string name="_bx_payment_txt_payment_to_for"><![CDATA[Платеж для {0} за:]]></string>
-	<string name="_bx_payment_txt_checkout_to"><![CDATA[Продолжить оплату в пользу {0}]]></string>
-	<string name="_bx_payment_txt_checkout_with"><![CDATA[Заплатить используя {0}]]></string>
-	<string name="_bx_payment_txt_client"><![CDATA[Покупатель]]></string>
-        <string name="_bx_payment_txt_license"><![CDATA[Лицензия]]></string>
-	<string name="_bx_payment_txt_seller"><![CDATA[Продавец]]></string>
-        <string name="_bx_payment_txt_author"><![CDATA[Автор]]></string>
-	<string name="_bx_payment_txt_order"><![CDATA[ID заказа]]></string>
-	<string name="_bx_payment_txt_processed_with"><![CDATA[Обработан используя]]></string>
-	<string name="_bx_payment_txt_message"><![CDATA[Результат]]></string>
-	<string name="_bx_payment_txt_date"><![CDATA[Дата]]></string>
-	<string name="_bx_payment_txt_products"><![CDATA[Продукты]]></string>
-	<string name="_bx_payment_txt_buyer_name_mask"><![CDATA[{0} {1}]]></string>
-	<string name="_bx_payment_txt_none"><![CDATA[нет]]></string>
-	<string name="_bx_payment_txt_name_manual"><![CDATA[Вручную]]></string>
-	<string name="_bx_payment_txt_yes"><![CDATA[Да]]></string>
-        <string name="_bx_payment_txt_no"><![CDATA[Нет]]></string>
-        <string name="_bx_payment_txt_unsubscribe_yes"><![CDATA[Отменить автоматически]]></string>
-        <string name="_bx_payment_txt_unsubscribe_no"><![CDATA[Не отменять]]></string>
+    <!-- MESSAGES, WARNINGS and ERRORS -->
+    <string name="_bx_payment_msg_no_results"><![CDATA[Пусто]]></string>
+    <string name="_bx_payment_msg_redirecting"><![CDATA[Пересылаю... Пожалуйста подождите.]]></string>
+    <string name="_bx_payment_msg_item_added"><![CDATA[Товар был успешно добавлен в вашу корзину.]]></string>
+    <string name="_bx_payment_msg_item_deleted"><![CDATA[Товар был успешно удален из вашей корзины.]]></string>
+    <string name="_bx_payment_msg_successfully_performed"><![CDATA[Действие было успешно выполнено.]]></string>
+    <string name="_bx_payment_msg_successfully_processed_free"><![CDATA[Бесплатный товар был успешно обработан.]]></string>
+    <string name="_bx_payment_msg_cancelation_request_sent"><![CDATA[Запрос на отмену был отправлен. Пожалуйста, ждите.]]></string>
+    <string name="_bx_payment_msg_confirm_cancelation"><![CDATA[Вы уверены, что хотите отменить вашу подписку?]]></string>
+    <string name="_bx_payment_msg_single_seller"><![CDATA[Режим единого продавца активирован. <a href="{0}">Перейти</a> в единую корзину.]]></string>
+    <string name="_bx_payment_err_access_db"><![CDATA[Ошибка обращения к базе данных.]]></string>
+    <string name="_bx_payment_err_cannot_validate"><![CDATA[Не могу присоединиться к удаленному хосту для проверки данных.]]></string>
+    <string name="_bx_payment_err_empty_items"><![CDATA[Товары не выбраны.]]></string>
+    <string name="_bx_payment_err_empty_order"><![CDATA[Заказ пуст.]]></string>
+    <string name="_bx_payment_err_inactive_vendor"><![CDATA[Профиль продавца не активен.]]></string>
+    <string name="_bx_payment_err_incorrect_provider"><![CDATA[Неправильная или недоступная платежная система.]]></string>
+    <string name="_bx_payment_err_not_accept_payments"><![CDATA[Продавец не принимает платежи.]]></string>
+    <string name="_bx_payment_err_nothing_selected"><![CDATA[Вам нужно выбрать как минимум один товар.]]></string>
+    <string name="_bx_payment_err_self_purchase"><![CDATA[Покупка из собственного магазина не возможна.]]></string>
+    <string name="_bx_payment_err_required_login"><![CDATA[Вам нужно войти.]]></string>
+    <string name="_bx_payment_err_unknown_vendor"><![CDATA[Неизвестный продавец.]]></string>
+    <string name="_bx_payment_err_wrong_data"><![CDATA[Неверные входные данные.]]></string>
+    <string name="_bx_payment_err_not_found_pending"><![CDATA[Обрабатываемый заказ не найден.]]></string>
+    <string name="_bx_payment_err_cannot_perform"><![CDATA[Действие не может быть выполнено. Свяжитесь с нами.]]></string>
+    <string name="_bx_payment_err_wrong_quantity"><![CDATA[Указано неправильное количество.]]></string>
+    <string name="_bx_payment_err_already_processed"><![CDATA[Этот платеж уже обработан.]]></string>
+    <string name="_bx_payment_err_already_registered"><![CDATA[Подписка с аналогичными данными уже была зарегистрирована.]]></string>
+
+    <!-- Other -->
+    <string name="_bx_payment_txt_add_to_cart"><![CDATA[В корзину]]></string>
+    <string name="_bx_payment_txt_subscribe"><![CDATA[Подписаться]]></string>
+    <string name="_bx_payment_txt_payment_to"><![CDATA[Платеж для {0}]]></string>
+    <string name="_bx_payment_txt_payment_to_for"><![CDATA[Платеж для {0} за:]]></string>
+    <string name="_bx_payment_txt_checkout_to"><![CDATA[Продолжить оплату в пользу {0}]]></string>
+    <string name="_bx_payment_txt_checkout_with"><![CDATA[Заплатить используя {0}]]></string>
+    <string name="_bx_payment_txt_client"><![CDATA[Покупатель]]></string>
+    <string name="_bx_payment_txt_license"><![CDATA[Лицензия]]></string>
+    <string name="_bx_payment_txt_seller"><![CDATA[Продавец]]></string>
+    <string name="_bx_payment_txt_author"><![CDATA[Автор]]></string>
+    <string name="_bx_payment_txt_order"><![CDATA[ID заказа]]></string>
+    <string name="_bx_payment_txt_processed_with"><![CDATA[Обработан используя]]></string>
+    <string name="_bx_payment_txt_message"><![CDATA[Результат]]></string>
+    <string name="_bx_payment_txt_date"><![CDATA[Дата]]></string>
+    <string name="_bx_payment_txt_products"><![CDATA[Продукты]]></string>
+    <string name="_bx_payment_txt_buyer_name_mask"><![CDATA[{0} {1}]]></string>
+    <string name="_bx_payment_txt_none"><![CDATA[нет]]></string>
+    <string name="_bx_payment_txt_name_manual"><![CDATA[Вручную]]></string>
+    <string name="_bx_payment_txt_yes"><![CDATA[Да]]></string>
+    <string name="_bx_payment_txt_no"><![CDATA[Нет]]></string>
+    <string name="_bx_payment_txt_unsubscribe_yes"><![CDATA[Отменить автоматически]]></string>
+    <string name="_bx_payment_txt_unsubscribe_no"><![CDATA[Не отменять]]></string>
+    <string name="_bx_payment_txt_all_authors"><![CDATA[Все авторы]]></string>
+    <string name="_bx_payment_txt_all_clients"><![CDATA[Все клиенты]]></string>
+    <string name="_bx_payment_txt_all_modules"><![CDATA[Все модули]]></string>
+    <string name="_bx_payment_txt_all_items"><![CDATA[Все продукты]]></string>
+    <string name="_bx_payment_txt_status_scheduled"><![CDATA[Запланированно]]></string>
+    <string name="_bx_payment_txt_status_trial"><![CDATA[Пробный]]></string>
+    <string name="_bx_payment_txt_status_active"><![CDATA[Активно]]></string>
+    <string name="_bx_payment_txt_status_unpaid"><![CDATA[Не оплачено]]></string>
+    <string name="_bx_payment_txt_status_paused"><![CDATA[Приостановлено]]></string>
+    <string name="_bx_payment_txt_status_canceled"><![CDATA[Отменено]]></string>
+    <string name="_bx_payment_txt_status_unknown"><![CDATA[Неизвестно]]></string>
+
+
+        <!-- Credits payment provider -->
+	<string name="_bx_payment_cdt_cpt"><![CDATA[Кредиты]]></string>
+	<string name="_bx_payment_cdt_dsc"><![CDATA[Платежная система Кредиты]]></string>
+	<string name="_bx_payment_cdt_active_cpt"><![CDATA[Активировать]]></string>
+	<string name="_bx_payment_cdt_active_dsc"><![CDATA[Активировать(деактивировать) платежную систему.]]></string>
+        <string name="_bx_payment_cdt_msg_charged"><![CDATA[Платеж успешно принят.]]></string>
+        <string name="_bx_payment_cdt_err_not_available"><![CDATA[В данное время оплата Кредитами не доступна.]]></string>
+        <string name="_bx_payment_cdt_err_wrong_transaction"><![CDATA[Не удалось верифицировать транзакцию.]]></string>
+        <string name="_bx_payment_txt_name_credits"><![CDATA[Кредиты]]></string>
+	<string name="_bx_payment_txt_cart_credits"><![CDATA[Кредиты]]></string>
 
 	<!-- PayPal payment provider -->
 	<string name="_bx_payment_pp_cpt"><![CDATA[PayPal]]></string>