Quellcode durchsuchen

Ticket #2507 - Module itself.

AntonLV vor 6 Jahren
Ursprung
Commit
b00a6b9858
36 geänderte Dateien mit 3632 neuen und 0 gelöschten Zeilen
  1. 30 0
      modules/boonex/credits/classes/BxCreditsAlertsResponse.php
  2. 280 0
      modules/boonex/credits/classes/BxCreditsConfig.php
  3. 361 0
      modules/boonex/credits/classes/BxCreditsDb.php
  4. 29 0
      modules/boonex/credits/classes/BxCreditsFormBundle.php
  5. 89 0
      modules/boonex/credits/classes/BxCreditsFormWithdraw.php
  6. 158 0
      modules/boonex/credits/classes/BxCreditsGridBundles.php
  7. 172 0
      modules/boonex/credits/classes/BxCreditsGridHistoryAdministration.php
  8. 50 0
      modules/boonex/credits/classes/BxCreditsGridHistoryCommon.php
  9. 77 0
      modules/boonex/credits/classes/BxCreditsGridOrdersAdministration.php
  10. 32 0
      modules/boonex/credits/classes/BxCreditsGridOrdersCommon.php
  11. 854 0
      modules/boonex/credits/classes/BxCreditsModule.php
  12. 25 0
      modules/boonex/credits/classes/BxCreditsPageBrowse.php
  13. 35 0
      modules/boonex/credits/classes/BxCreditsPageHistory.php
  14. 35 0
      modules/boonex/credits/classes/BxCreditsPageOrders.php
  15. 60 0
      modules/boonex/credits/classes/BxCreditsStudioPage.php
  16. 127 0
      modules/boonex/credits/classes/BxCreditsTemplate.php
  17. 77 0
      modules/boonex/credits/install/config.php
  18. 37 0
      modules/boonex/credits/install/installer.php
  19. 230 0
      modules/boonex/credits/install/langs/en.xml
  20. 33 0
      modules/boonex/credits/install/sql/disable.sql
  21. 229 0
      modules/boonex/credits/install/sql/enable.sql
  22. 161 0
      modules/boonex/credits/install/sql/install.sql
  23. 16 0
      modules/boonex/credits/install/sql/uninstall.sql
  24. 62 0
      modules/boonex/credits/js/checkout.js
  25. 42 0
      modules/boonex/credits/js/studio.js
  26. 49 0
      modules/boonex/credits/js/withdraw.js
  27. 18 0
      modules/boonex/credits/request.php
  28. 19 0
      modules/boonex/credits/template/bundle_form.html
  29. 1 0
      modules/boonex/credits/template/bundle_link.html
  30. 27 0
      modules/boonex/credits/template/bundles.html
  31. 36 0
      modules/boonex/credits/template/checkout.html
  32. 19 0
      modules/boonex/credits/template/credit_form.html
  33. 42 0
      modules/boonex/credits/template/css/checkout.css
  34. 89 0
      modules/boonex/credits/template/css/main.css
  35. 4 0
      modules/boonex/credits/template/css/withdraw.css
  36. 27 0
      modules/boonex/credits/template/images/icons/std-icon.svg

+ 30 - 0
modules/boonex/credits/classes/BxCreditsAlertsResponse.php

@@ -0,0 +1,30 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCreditsAlertsResponse extends BxBaseModGeneralAlertsResponse
+{
+    public function __construct()
+    {
+        $this->MODULE = 'bx_credits';
+
+        parent::__construct();
+    }
+
+    public function response($oAlert)
+    {
+        parent::response($oAlert);
+
+        if($oAlert->sUnit == 'profile' && $oAlert->sAction == 'delete' && !empty($oAlert->aExtras['delete_with_content']))
+            return BxDolService::call($this->MODULE, 'delete_entities_by_author', array($oAlert->iObject));
+    }
+}
+
+/** @} */

+ 280 - 0
modules/boonex/credits/classes/BxCreditsConfig.php

@@ -0,0 +1,280 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCreditsConfig extends BxBaseModGeneralConfig
+{
+    protected $_iAuthor;
+    protected $_aCurrency;
+
+    protected $_sCheckoutSessionKey;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->CNF = array (
+
+            // module icon
+            'ICON' => 'money-bill-alt col-green3',
+
+            // database tables
+            'TABLE_BUNDLES' => $aModule['db_prefix'] . 'bundles',
+            'TABLE_ORDERS' => $aModule['db_prefix'] . 'orders',
+            'TABLE_ORDERS_DELETED' => $aModule['db_prefix'] . 'orders_deleted',
+            'TABLE_PROFILES' => $aModule['db_prefix'] . 'profiles',
+            'TABLE_HISTORY' => $aModule['db_prefix'] . 'history',
+
+            // database fields
+            'FIELD_ID' => 'id',
+            'FIELD_AUTHOR' => '',
+            'FIELD_ADDED' => 'added',
+            'FIELD_CHANGED' => '',
+            'FIELD_NAME' => 'name',
+            'FIELD_TITLE' => 'title',
+            'FIELD_AMOUNT' => 'amount',
+            'FIELD_BONUS' => 'bonus',
+            'FIELD_PRICE' => 'price',
+            'FIELD_STATUS' => 'active',
+
+            // page URIs
+            'URI_HOME' => 'credits-home',
+            'URL_HOME' => 'page.php?i=credits-home',
+            'URL_CHECKOUT' => 'page.php?i=credits-checkout',
+            'URL_MANAGE_COMMON' => 'page.php?i=credits-manage',
+            'URL_MANAGE_ADMINISTRATION' => 'page.php?i=credits-administration',
+            'URL_ORDERS_COMMON' => 'page.php?i=credits-orders',
+            'URL_ORDERS_ADMINISTRATION' => 'page.php?i=credits-orders-administration',
+
+            // some params
+            'PARAM_WITHDRAW' => 'bx_credits_enable_withdraw',
+            'PARAM_WITHDRAW_EMAIL' => 'bx_credits_withdraw_email',
+            'PARAM_PRECISION' => 'bx_credits_precision',
+            'PARAM_CR_USE' => 'bx_credits_conversion_rate_use',
+            'PARAM_CR_WITHDRAW' => 'bx_credits_conversion_rate_withdraw',
+            'PARAM_CODE' => 'bx_credits_code',
+            'PARAM_ICON' => 'bx_credits_icon',
+
+            // objects
+            'OBJECT_FORM_CREDIT' => 'bx_credits_credit',
+            'OBJECT_FORM_CREDIT_DISPLAY_GRANT' => 'bx_credits_credit_grant',
+            'OBJECT_FORM_CREDIT_DISPLAY_WITHDRAW_REQUEST' => 'bx_credits_credit_withdraw_request',
+            'OBJECT_FORM_CREDIT_DISPLAY_WITHDRAW_CONFIRM' => 'bx_credits_credit_withdraw_confirm',
+            'OBJECT_FORM_BUNDLE' => 'bx_credits_bundle',
+            'OBJECT_FORM_BUNDLE_DISPLAY_ADD' => 'bx_credits_bundle_add',
+            'OBJECT_FORM_BUNDLE_DISPLAY_EDIT' => 'bx_credits_bundle_edit',
+            'OBJECT_MENU_SUBMENU' => 'bx_credits_submenu', 
+            'OBJECT_MENU_MANAGE_SUBMENU' => 'bx_credits_manage_submenu',
+            'OBJECT_GRID_BUNDLES' => 'bx_credits_bundles',
+            'OBJECT_GRID_ORDERS_ADMINISTRATION' => 'bx_credits_orders_administration',
+            'OBJECT_GRID_ORDERS_COMMON' => 'bx_credits_orders_common',
+            'OBJECT_GRID_HISTORY_ADMINISTRATION' => 'bx_credits_history_administration',
+            'OBJECT_GRID_HISTORY_COMMON' => 'bx_credits_history_common',
+
+            // email templates
+            'ETEMPLATE_GRANTED' => 'bx_credits_granted',
+            'ETEMPLATE_PURCHASED' => 'bx_credits_purchased',
+            'ETEMPLATE_IN' => 'bx_credits_in',
+            'ETEMPLATE_OUT' => 'bx_credits_out',
+            'ETEMPLATE_WITHDRAW_REQUESTED' => 'bx_credits_withdraw_requested',
+            'ETEMPLATE_WITHDRAW_SENT' => 'bx_credits_withdraw_sent',
+
+            // some language keys
+            'T' => array (
+                'txt_sample_single' => '_bx_credits_txt_sample_single',
+                'grant_popup' => '_bx_credits_grid_popup_title_htr_grant',
+                'withdraw_request_popup' => '_bx_credits_grid_popup_title_htr_withdraw_request',
+                'withdraw_confirm_popup' => '_bx_credits_grid_popup_title_htr_withdraw_confirm',
+            ),
+        );
+
+        $this->_aJsClasses = array_merge($this->_aJsClasses, array(
+            'studio' => 'BxCreditsStudio',
+            'checkout' => 'BxCreditsCheckout',
+            'withdraw' => 'BxCreditsWithdraw',
+        ));
+
+        $this->_aJsObjects = array_merge($this->_aJsObjects, array(
+            'studio' => 'oBxCreditsStudio',
+            'checkout' => 'oBxCreditsCheckout',
+            'withdraw' => 'oBxCreditsWithdraw',
+        ));
+
+        $sPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = array(
+            'add_bundle_popup' =>  $sPrefix . '-add-bundle-popup',
+            'edit_bundle_popup' =>  $sPrefix . '-edit-bundle-popup',
+
+            'grant_popup' =>  $sPrefix . '-grant-popup',
+            'withdraw_request_popup' =>  $sPrefix . '-withdraw-request-popup',
+            'withdraw_confirm_popup' =>  $sPrefix . '-withdraw-confirm-popup',
+            'withdraw_field_rate' => $sPrefix . '-wff-rate',
+            'withdraw_field_amount' => $sPrefix . '-wff-amount',
+            'withdraw_field_result' => $sPrefix . '-wff-result',
+        );
+
+        $this->_aPrefixes = array(
+            'style' => 'bx-credits',
+        );
+
+        $oPayments = BxDolPayments::getInstance();
+        $this->_iAuthor = (int)$oPayments->getOption('site_admin');
+        $this->_aCurrency = array(
+            'code' => $oPayments->getOption('default_currency_code'),
+            'sign' => $oPayments->getOption('default_currency_sign')
+        );
+        
+        $this->_sCheckoutSessionKey = $this->_sName . '_checkout';
+    }
+
+    public function getHtmlIds($sKey = '')
+    {
+        if(empty($sKey))
+            return $this->_aHtmlIds;
+
+        return isset($this->_aHtmlIds[$sKey]) ? $this->_aHtmlIds[$sKey] : '';
+    }
+
+    public function getAuthor()
+    {
+    	return $this->_iAuthor;
+    }
+
+    public function getCurrency()
+    {
+    	return $this->_aCurrency;
+    }
+
+    public function getUnit()
+    {
+        $sUnit = '';
+        if(($sCode = getParam($this->CNF['PARAM_CODE'])) != '')
+            $sUnit = $sCode;
+        else if(($sIcon = getParam($this->CNF['PARAM_ICON'])) != '')
+            $sUnit = $this->_oTemplate->parseIcon($sIcon, array('class' => $this->getPrefix('style') . '-mti-balance bx-def-margin-thd-right'));
+
+        return $sUnit;
+    }
+
+    public function isWithdraw()
+    {
+        return getParam($this->CNF['PARAM_WITHDRAW']) == 'on';
+    }
+
+    public function getWithdrawEmail()
+    {
+        $sEmail = getParam($this->CNF['PARAM_WITHDRAW_EMAIL']);
+        if(empty($sEmail))
+            $sEmail = getParam('site_email');
+
+        return $sEmail;
+    }
+
+    public function getPrecision()
+    {
+        return (int)getParam($this->CNF['PARAM_PRECISION']);
+    }
+
+    public function getConversionRateUse()
+    {
+        return $this->_getConversionRate('use');
+    }
+
+    public function getConversionRateWithdraw()
+    {
+        return $this->_getConversionRate('withdraw');
+    }
+
+    public function getCheckoutUrl()
+    {
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($this->CNF['URL_CHECKOUT']);
+    }
+
+    public function getBundleUrl($aBundle)
+    {
+        return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink($this->CNF['URL_HOME']);
+    }
+
+    public function getBundleDescription($aBundle)
+    {
+        if(empty($aBundle[$this->CNF['FIELD_BONUS']]))
+            return $aBundle[$this->CNF['FIELD_AMOUNT']];
+
+        return _t('_bx_credits_txt_n_plus_m_for_free', $aBundle[$this->CNF['FIELD_AMOUNT']], $aBundle[$this->CNF['FIELD_BONUS']]);
+    }
+
+    public function getCheckoutData()
+    {
+        return BxDolSession::getInstance()->getUnsetValue($this->_sCheckoutSessionKey);
+    }
+
+    public function setCheckoutData($aData)
+    {
+        return BxDolSession::getInstance()->setValue($this->_sCheckoutSessionKey, $aData);
+    }
+
+    public function getOrder()
+    {
+        return genRndPwd(16, false);
+    }
+
+    /**
+     * Convert Credits to Money
+     */
+    public function convertC2M($fCredits, $fRate = false, $iPrecision = false)
+    {
+        if($fRate === false)
+            $fRate = $this->getConversionRateUse();
+        if($iPrecision === false)
+            $iPrecision = $this->getPrecision();
+
+        return round((float)$fCredits * $fRate, $iPrecision);
+    }
+
+    /**
+     * Convert Money to Credits
+     */
+    public function convertM2C($fMoney, $fRate = false, $iPrecision = false)
+    {
+        if($fRate === false)
+            $fRate = $this->getConversionRateUse();
+        if($iPrecision === false)
+            $iPrecision = $this->getPrecision();
+
+        return round((float)$fMoney / $fRate, $iPrecision);
+    }
+
+    /**
+     * Convert Credits to String
+     */
+    public function convertC2S($fCredits, $bWithUnit = true)
+    {
+        $sResult = '';
+        if($bWithUnit)
+            $sResult .= $this->getUnit();
+
+        $iPrecision = $this->getPrecision();
+        $sResult .= sprintf("%01." . $iPrecision . "f", round((float)$fCredits, $iPrecision));
+
+        return $sResult;
+    }
+
+    /*
+     * Internal methods
+     */
+    protected function _getConversionRate($sType)
+    {
+        $fRate = (float)getParam($this->CNF['PARAM_CR_' . strtoupper($sType)]);
+
+        return $fRate > 0 && $fRate <= 1 ? $fRate : 1;
+    }
+}
+
+/** @} */

+ 361 - 0
modules/boonex/credits/classes/BxCreditsDb.php

@@ -0,0 +1,361 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Module database queries
+ */
+class BxCreditsDb extends BxBaseModGeneralDb
+{
+    function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+
+    /**
+     * Bundle related methods.
+     */
+    public function getBundle($aParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sSelectClause = "`tb`.*";
+    	$sJoinClause = $sWhereClause = $sLimitClause = "";
+        $sOrderClause = "`tb`.`order` ASC";
+        switch($aParams['type']) {
+            case 'id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `tb`.`id`=:id";
+                break;
+
+            case 'order_max':
+            	$aMethod['name'] = 'getOne';
+
+                $sSelectClause = "IFNULL(MAX(`tb`.`order`), 0)";
+                break;
+
+            case 'all':
+                break;
+        }
+
+        if(isset($aParams['active']))
+            $sWhereClause .= $this->prepareAsString(" AND `tb`.`active`=?", (bool)$aParams['active'] ? 1 : 0);
+
+        $sOrderClause = !empty($sOrderClause) ? "ORDER BY " . $sOrderClause : "";
+        $sLimitClause = !empty($sLimitClause) ? "LIMIT " . $sLimitClause : "";
+
+        $aMethod['params'][0] = "SELECT
+                " . $sSelectClause . "
+            FROM `" . $CNF['TABLE_BUNDLES'] . "` AS `tb`" . $sJoinClause . "
+            WHERE 1" . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function isBundle($iId)
+    {
+        $aSport = $this->getBundle(array('type' => 'id', 'id' => $iId));
+        return !empty($aSport) && is_array($aSport);
+    }
+
+    public function insertBundle($aSet)
+    {
+        $sQuery = "INSERT INTO `" . $this->_oConfig->CNF['TABLE_BUNDLES'] . "` SET " . $this->arrayToSQL($aSet);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function updateBundle($aSet, $aWhere)
+    {
+        $sQuery = "UPDATE `" . $this->_oConfig->CNF['TABLE_BUNDLES'] . "` SET " . $this->arrayToSQL($aSet) . " WHERE " . (!empty($aWhere) ? $this->arrayToSQL($aWhere, ' AND ') : "1");
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function deleteBundle($aWhere)
+    {
+    	if(empty($aWhere))
+            return false;
+
+        $sQuery = "DELETE FROM `" . $this->_oConfig->CNF['TABLE_BUNDLES'] . "` WHERE " . $this->arrayToSQL($aWhere, ' AND ');
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    /**
+     * Order related methods.
+     */
+    public function getOrder($aParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sSelectClause = "`to`.*";
+    	$sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+        switch($aParams['type']) {
+            case 'id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `to`.`id`=:id";
+                break;
+
+            case 'row_by':
+                $aMethod['name'] = 'getRow';
+                $sWhereClause = " AND " . $this->arrayToSQL($aParams['by'], ' AND ');
+                break;
+
+            case 'all':
+                break;
+        }
+
+        $sOrderClause = !empty($sOrderClause) ? "ORDER BY " . $sOrderClause : "";
+        $sLimitClause = !empty($sLimitClause) ? "LIMIT " . $sLimitClause : "";
+
+        $aMethod['params'][0] = "SELECT
+                " . $sSelectClause . "
+            FROM `" . $CNF['TABLE_ORDERS'] . "` AS `tb`" . $sJoinClause . "
+            WHERE 1" . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function isOrder($iId)
+    {
+        $aOrder = $this->getOrder(array('type' => 'id', 'id' => $iId));
+        return !empty($aOrder) && is_array($aOrder);
+    }
+
+    public function isOrderByPbo($iProfileId, $iBundleId, $sOrder)
+    {
+        $aOrder = $this->getOrder(array('type' => 'row_by', 'by' => array('profile_id' => $iProfileId, 'bundle_id' => $iBundleId, 'order' => $sOrder)));
+        return !empty($aOrder) && is_array($aOrder);
+    }
+
+    public function insertOrder($aSet, $sSetAddon = '')
+    {
+        $sQuery = "INSERT INTO `" . $this->_oConfig->CNF['TABLE_ORDERS'] . "` SET " . $this->arrayToSQL($aSet) . $sSetAddon;
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function insertOrderDeleted($aSet, $sSetAddon = '')
+    {
+        $sQuery = "INSERT INTO `" . $this->_oConfig->CNF['TABLE_ORDERS_DELETED'] . "` SET " . $this->arrayToSQL($aSet) . $sSetAddon;
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function registerOrder($iProfileId, $iBundleId, $iCount, $sOrder, $sLicense, $sType, $sDuration = '', $iTrial = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sSetAddon = ', `added`=UNIX_TIMESTAMP()';
+
+        return $this->insertOrder(array(
+            'profile_id' => $iProfileId,
+            'bundle_id' => $iBundleId,
+            'count' => $iCount,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'type' => $sType
+        ), $sSetAddon);
+    }
+
+    public function updateOrder($aSet, $aWhere)
+    {
+        $sQuery = "UPDATE `" . $this->_oConfig->CNF['TABLE_ORDERS'] . "` SET " . $this->arrayToSQL($aSet) . " WHERE " . (!empty($aWhere) ? $this->arrayToSQL($aWhere, ' AND ') : "1");
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    /**
+     * Note. Prolong isn't allowed for now.
+     */
+    public function prolongOrder($iProfileId, $iItemId, $iItemCount, $sOrder, $sLicense, $sType, $sDuration, $iTrial)
+    {
+        return false;
+    }
+
+    public function deleteOrder($aWhere)
+    {
+    	if(empty($aWhere))
+            return false;
+
+        $sQuery = "DELETE FROM `" . $this->_oConfig->CNF['TABLE_ORDERS'] . "` WHERE " . $this->arrayToSQL($aWhere, ' AND ');
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function unregisterOrder($iProfileId, $iItemId, $sOrder, $sLicense, $sType)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aOrder = $this->getOrder(array('type' => 'row_by', 'by' => array(
+            'profile_id' => $iProfileId,
+            'bundle_id' => $iItemId,
+            'order' => $sOrder,
+            'license' => $sLicense
+    	)));
+
+        if(empty($aOrder) || !is_array($aOrder))
+            return true;
+
+        //--- Move to deleted orders table with 'refund' as reason.
+        $this->insertOrderDeleted($aOrder, ", `reason`='refund', `deleted`=UNIX_TIMESTAMP()");
+
+        return $this->deleteOrder(array('id' => $aOrder[$CNF['FIELD_ID']]));
+    }
+
+    /**
+     * Profile related methods.
+     */
+    public function getProfile($aParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sSelectClause = "`tp`.*";
+    	$sJoinClause = $sWhereClause = $sLimitClause = "";
+        switch($aParams['type']) {
+            case 'id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `tp`.`id`=:id";
+                break;
+            
+            case 'balance':
+            	$aMethod['name'] = 'getOne';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sSelectClause = "`tp`.`balance`";
+                $sWhereClause = " AND `tp`.`id`=:id";
+                break;
+        }
+
+        $sLimitClause = !empty($sLimitClause) ? "LIMIT " . $sLimitClause : $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT
+                " . $sSelectClause . "
+            FROM `" . $CNF['TABLE_PROFILES'] . "` AS `tp`" . $sJoinClause . "
+            WHERE 1" . $sWhereClause . " " . $sLimitClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function isProfile($iId)
+    {
+        $aProfile = $this->getProfile(array('type' => 'id', 'id' => $iId));
+        return !empty($aProfile) && is_array($aProfile);
+    }
+
+    public function insertProfile($aSet)
+    {
+        $sQuery = "INSERT INTO `" . $this->_oConfig->CNF['TABLE_PROFILES'] . "` SET " . $this->arrayToSQL($aSet);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function updateProfile($aSet, $aWhere)
+    {
+        $sQuery = "UPDATE `" . $this->_oConfig->CNF['TABLE_PROFILES'] . "` SET " . $this->arrayToSQL($aSet) . " WHERE " . (!empty($aWhere) ? $this->arrayToSQL($aWhere, ' AND ') : "1");
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function updateProfileBalance($iId, $fBalance)
+    {
+        if(!$this->isProfile($iId))
+            return $this->insertProfile(array('id' => $iId, 'balance' => $fBalance));
+
+        $sQuery = "UPDATE `" . $this->_oConfig->CNF['TABLE_PROFILES'] . "` SET `balance`=`balance`+:balance WHERE `id`=:id";
+        return (int)$this->query($sQuery, array(
+            'id' => $iId,
+            'balance' => $fBalance
+        )) > 0;
+    }
+
+    public function deleteProfile($aWhere)
+    {
+    	if(empty($aWhere))
+            return false;
+
+        $sQuery = "DELETE FROM `" . $this->_oConfig->CNF['TABLE_PROFILES'] . "` WHERE " . $this->arrayToSQL($aWhere, ' AND ');
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    /**
+     * History related methods.
+     */
+    public function getHistory($aParams = array())
+    {
+    	$CNF = &$this->_oConfig->CNF;
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sSelectClause = "`th`.*";
+    	$sJoinClause = $sWhereClause = $sLimitClause = "";
+        switch($aParams['type']) {
+            case 'id':
+            	$aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `th`.`id`=:id";
+                break;
+
+            case 'row_by':
+                $aMethod['name'] = 'getRow';
+                $sWhereClause = " AND " . $this->arrayToSQL($aParams['by'], ' AND ');
+                break;
+        }
+
+        $sLimitClause = !empty($sLimitClause) ? "LIMIT " . $sLimitClause : $sLimitClause;
+
+        $aMethod['params'][0] = "SELECT
+                " . $sSelectClause . "
+            FROM `" . $CNF['TABLE_HISTORY'] . "` AS `th`" . $sJoinClause . "
+            WHERE 1" . $sWhereClause . " " . $sLimitClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function isHistory($iId)
+    {
+        $aHistory = $this->getHistory(array('type' => 'id', 'id' => $iId));
+        return !empty($aHistory) && is_array($aHistory);
+    }
+
+    public function insertHistory($aSet)
+    {
+        $sQuery = "INSERT INTO `" . $this->_oConfig->CNF['TABLE_HISTORY'] . "` SET " . $this->arrayToSQL($aSet);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function updateHistory($aSet, $aWhere)
+    {
+        $sQuery = "UPDATE `" . $this->_oConfig->CNF['TABLE_HISTORY'] . "` SET " . $this->arrayToSQL($aSet) . " WHERE " . (!empty($aWhere) ? $this->arrayToSQL($aWhere, ' AND ') : "1");
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function deleteHistory($aWhere)
+    {
+    	if(empty($aWhere))
+            return false;
+
+        $sQuery = "DELETE FROM `" . $this->_oConfig->CNF['TABLE_HISTORY'] . "` WHERE " . $this->arrayToSQL($aWhere, ' AND ');
+        return (int)$this->query($sQuery) > 0;
+    }
+}
+
+/** @} */

+ 29 - 0
modules/boonex/credits/classes/BxCreditsFormBundle.php

@@ -0,0 +1,29 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Add/Edit bundle form
+ */
+class BxCreditsFormBundle extends BxTemplStudioFormView
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_credits';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct($aInfo, $oTemplate);
+    }
+}
+
+/** @} */

+ 89 - 0
modules/boonex/credits/classes/BxCreditsFormWithdraw.php

@@ -0,0 +1,89 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Withdraw form
+ */
+class BxCreditsFormWithdraw extends BxTemplFormView
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_fRate;
+
+    public function __construct($aInfo, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_credits';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct($aInfo, $oTemplate);
+
+        $this->_fRate = $this->_oModule->_oConfig->getConversionRateWithdraw();
+    }
+
+    protected function genCustomRowRate(&$aInput)
+    {
+        if($this->_fRate == 1)
+            return '';
+
+        return $this->genRowStandard($aInput);
+    }
+
+    protected function genCustomRowResult(&$aInput)
+    {
+        if($this->_fRate == 1)
+            return '';
+
+        return $this->genRowStandard($aInput);
+    }
+    
+    protected function genCustomInputBalance(&$aInput)
+    {
+        $aInput['value'] = $this->_oModule->getProfileBalance();
+
+        return $this->genInputStandard($aInput);
+    }
+    
+    protected function genCustomInputRate(&$aInput)
+    {
+        $aInput['value'] = $this->_fRate;
+        $aInput['attrs']['id'] = $this->_oModule->_oConfig->getHtmlIds('withdraw_field_rate');
+        return $this->genInputStandard($aInput);
+    }
+
+    protected function genCustomInputAmount(&$aInput)
+    {
+        if($this->_fRate != 1)
+            $aInput['attrs']['onblur'] = $this->_oModule->_oConfig->getJsObject('withdraw') . '.getResult(this)';
+
+        $aInput['attrs']['id'] = $this->_oModule->_oConfig->getHtmlIds('withdraw_field_amount');
+        return $this->genInputStandard($aInput);
+    }
+
+    protected function genCustomInputResult(&$aInput)
+    {
+        $aInput['attrs']['id'] = $this->_oModule->_oConfig->getHtmlIds('withdraw_field_result');
+        return $this->genInputStandard($aInput);
+    }
+
+    protected function genCustomInputProfile(&$aInput)
+    {
+        if(empty($aInput['custom']) || !is_array($aInput['custom']))
+            $aInput['custom'] = array();
+        $aInput['custom']['only_once'] = 1;
+
+        $aInput['ajax_get_suggestions'] = BX_DOL_URL_ROOT . $this->_oModule->_oConfig->getBaseUri() . "get_profiles";
+
+        return $this->genCustomInputUsernamesSuggestions($aInput);
+    }
+}
+
+/** @} */

+ 158 - 0
modules/boonex/credits/classes/BxCreditsGridBundles.php

@@ -0,0 +1,158 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxCreditsGridBundles extends BxTemplGrid
+{
+    protected $_sModule;
+    protected $_oModule;
+    
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+        
+        $this->_sModule = 'bx_credits';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+    }
+
+    public function performActionAdd()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sAction = 'add';
+
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $aValsToAdd = array(
+                'active' => 1,
+                'order' => $this->_oModule->_oDb->getBundle(array('type' => 'order_max')) + 1
+            );
+
+            $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+            $aLanguages = $oLanguage->getLanguagesExt();
+            $sLanguageCurrent = $oLanguage->getCurrentLangName(false);
+
+            $sTitle = BxDolForm::getSubmittedValue('title-' . $sLanguageCurrent, $oForm->aFormAttrs['method']);
+            $sName = uriGenerate(strtolower($sTitle), $CNF['TABLE_BUNDLES'], 'name', 'cat', '_');
+
+            $aValsToAdd['name'] = $sName;
+            BxDolForm::setSubmittedValue('title', '_bx_credits_txt_bundle_title_' . $sName, $oForm->aFormAttrs['method']);
+
+            $iId = (int)$oForm->insert($aValsToAdd);
+            if($iId != 0)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            else
+                $aRes = array('msg' => _t('_bx_credits_err_cannot_perform_action'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('add_bundle_popup'), _t('_bx_credits_grid_popup_title_add_bundle'), $this->_oModule->_oTemplate->parseHtmlByName('bundle_form.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+    
+    public function performActionEdit()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sAction = 'edit';
+
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $iId = (int)bx_get('id');
+            if(!$iId)
+                return false;
+
+            $aIds = array($iId);
+        }
+
+        $iId = $aIds[0];
+
+        $aBundle = $this->_oModule->_oDb->getBundle(array('type' => 'id', 'id' => $iId));
+        if(empty($aBundle) || !is_array($aBundle))
+            return echoJson(array());
+
+        $oForm = $this->_getFormObject($sAction, $aBundle);
+        $oForm->initChecker($aBundle);
+
+        if($oForm->isSubmittedAndValid()) {
+            $aValsToAdd = array();
+
+            $iId = (int)$oForm->update($iId);
+            if($iId != 0)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            else
+                $aRes = array('msg' => _t('_bx_credits_txt_err_cannot_perform_action'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplStudioFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('edit_bundle_popup'), _t('_bx_credits_grid_popup_title_edit_bundle', _t($aBundle['title'])), $this->_oModule->_oTemplate->parseHtmlByName('bundle_form.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    protected function _getFormObject($sAction, $aBundle = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_BUNDLE'], $CNF['OBJECT_FORM_BUNDLE_DISPLAY_' . strtoupper($sAction)]);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction;
+        if(!empty($aBundle['id']))
+            $oForm->aFormAttrs['action'] .= '&id=' . $aBundle['id'];
+
+        return $oForm;
+    }
+    
+    protected function _getCellPrice ($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(_t_format_currency($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+        $this->_oModule->_oTemplate->addStudioJs(array('jquery.form.min.js', 'studio.js'));
+
+        $oForm = new BxTemplStudioFormView(array());
+        $oForm->addCssJs();
+    }
+
+    protected function _isVisibleGrid ($a)
+    {
+        return isAdmin();
+    }
+
+    protected function _delete ($mixedId)
+    {
+        $aBundle = $this->_oModule->_oDb->getBundle(array('type' => 'id', 'id' => $mixedId));
+        if(!empty($aBundle['title']))
+            BxDolStudioLanguagesUtils::getInstance()->deleteLanguageString($aBundle['title']);
+
+        return parent::_delete($mixedId);
+    }
+}
+
+/** @} */

+ 172 - 0
modules/boonex/credits/classes/BxCreditsGridHistoryAdministration.php

@@ -0,0 +1,172 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxCreditsGridHistoryAdministration extends BxTemplGrid
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_bWithdraw;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_credits';
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+    	if(!$oTemplate)
+            $oTemplate = $this->_oModule->_oTemplate;
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sDefaultSortingOrder = 'DESC';
+
+        $iProfileId = bx_get_logged_profile_id();
+        if($iProfileId !== false)
+            $this->_aQueryAppend['profile_id'] = (int)$iProfileId;
+
+        $this->_bWithdraw = $this->_oModule->_oConfig->isWithdraw();
+    }
+
+    public function getCode($isDisplayHeader = true)
+    {
+        $sResult = parent::getCode($isDisplayHeader);
+        if(!empty($sResult) && $isDisplayHeader)
+            $sResult = $this->_oModule->_oTemplate->getJsCode('withdraw') . $sResult;
+
+        return $sResult;
+    }
+
+    public function performActionGrant()
+    {
+        $sAction = 'grant';
+
+        $this->_performActionWithProfileAmount($sAction);
+    }
+
+    public function performActionWithdrawConfirm()
+    {
+        if(!$this->_bWithdraw)
+            return echoJson(array());
+
+        $sAction = 'withdraw_confirm';
+
+        $this->_performActionWithProfileAmount($sAction);
+    }
+
+    protected function _getActionWithdrawConfirm($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if(!$this->_bWithdraw)
+            $isDisabled = true;
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getCellDirection($mixedValue, $sKey, $aField, $aRow)
+    {
+        if(in_array($mixedValue, array(BX_CREDITS_DIRECTION_IN, BX_CREDITS_DIRECTION_OUT)))
+            $mixedValue = _t('_bx_credits_txt_direction_' . $mixedValue);
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellFirstPid($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_getProfile($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellSecondPid($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_getProfile($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAmount($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_oModule->_oConfig->convertC2S($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellOrder($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellDate($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(bx_time_js($mixedValue, BX_FORMAT_DATE, true), $sKey, $aField, $aRow);
+    }
+
+    protected function _getProfile($mixedValue) 
+    {
+        $oProfile = BxDolProfile::getInstanceMagic($mixedValue);
+        if(!$oProfile)
+            return $mixedValue;
+
+        return $oProfile->getUnit(0, array('template' => array('name' => 'unit', 'size' => 'icon')));
+    }
+
+    protected function _performActionWithProfileAmount($sAction)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $iProfile = (int)$oForm->getCleanValue('profile');
+            $fAmount = (float)$oForm->getCleanValue('amount');
+            $sMessage = $oForm->getCleanValue('message');
+            $aResult = $this->_oModule->{'process' . bx_gen_method_name($sAction)}($iProfile, $fAmount, $sMessage);
+
+            if((int)$aResult['code'] == 0)
+                $aRes = array('msg' => _t(!empty($aResult['msg']) ? $aResult['msg'] : '_bx_credits_msg_action_performed'));
+            else
+                $aRes = array('msg' => _t(!empty($aResult['msg']) ? $aResult['msg'] : '_bx_credits_err_cannot_perform_action'));
+
+            echoJson($aRes);
+        }
+        else {
+            $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds($sAction . '_popup'), _t($CNF['T'][$sAction . '_popup']), $this->_oModule->_oTemplate->parseHtmlByName('credit_form.html', array(
+                'form_id' => $oForm->aFormAttrs['id'],
+                'form' => $oForm->getCode(true),
+                'object' => $this->_sObject,
+                'action' => $sAction
+            )));
+
+            echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+        }
+    }
+
+    protected function _getFormObject($sAction, $aBundle = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_CREDIT'], $CNF['OBJECT_FORM_CREDIT_DISPLAY_' . strtoupper($sAction)]);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . 'grid.php?o=' . $this->_sObject . '&a=' . $sAction;
+
+        return $oForm;
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+        $this->_oModule->_oTemplate->addJs(array(
+            'jquery.form.min.js', 
+            'withdraw.js'
+        ));
+
+        $this->_oModule->_oTemplate->addCss(array(
+            'withdraw.css'
+        ));
+
+        $oForm = new BxTemplFormView(array());
+        $oForm->addCssJs();
+    }
+}
+
+/** @} */

+ 50 - 0
modules/boonex/credits/classes/BxCreditsGridHistoryCommon.php

@@ -0,0 +1,50 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+require_once('BxCreditsGridHistoryAdministration.php');
+
+class BxCreditsGridHistoryCommon extends BxCreditsGridHistoryAdministration
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+    }
+
+    public function performActionWithdrawRequest()
+    {
+        if(!$this->_bWithdraw)
+            return echoJson(array());
+
+        $sAction = 'withdraw_request';
+        
+        $this->_performActionWithProfileAmount($sAction);
+    }
+
+    protected function _getActionWithdrawRequest($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if(!$this->_bWithdraw)
+            return '';
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(empty($this->_aQueryAppend['profile_id']))
+                return array();
+
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `first_pid`=?", $this->_aQueryAppend['profile_id']);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+}
+
+/** @} */

+ 77 - 0
modules/boonex/credits/classes/BxCreditsGridOrdersAdministration.php

@@ -0,0 +1,77 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxCreditsGridOrdersAdministration extends BxTemplGrid
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+    	$this->_sModule = 'bx_credits';
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+    	if(!$oTemplate)
+            $oTemplate = $this->_oModule->_oTemplate;
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sDefaultSortingOrder = 'DESC';
+        $this->_aQueryReset = array($this->_aOptions['order_get_field'], $this->_aOptions['order_get_dir'], $this->_aOptions['paginate_get_start'], $this->_aOptions['paginate_get_per_page']);
+
+        $iProfileId = bx_get_logged_profile_id();
+        if($iProfileId !== false)
+            $this->_aQueryAppend['profile_id'] = (int)$iProfileId;
+    }
+
+    protected function _getCellProfileId($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_getProfile($mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellBundle($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = $this->_oTemplate->parseHtmlByName('bundle_link.html', array(
+            'href' => $this->_oModule->_oConfig->getBundleUrl(array('id' => $aRow['bundle_id'])),
+            'title' => bx_html_attribute($mixedValue),
+            'content' => $mixedValue
+        ));
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellType($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(_t('_bx_credits_grid_column_value_ord_type_' . $mixedValue), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellAdded($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault(bx_time_js($mixedValue, BX_FORMAT_DATE, true), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellExpired($mixedValue, $sKey, $aField, $aRow)
+    {
+    	$mixedValue = (int)$mixedValue != 0 ? bx_time_js($mixedValue, BX_FORMAT_DATE, true): _t('_bx_credits_txt_never');
+    		
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getProfile($mixedValue) 
+    {
+        $oProfile = BxDolProfile::getInstanceMagic($mixedValue);
+        if(!$oProfile)
+            return $mixedValue;
+
+        return $oProfile->getUnit(0, array('template' => array('name' => 'unit', 'size' => 'icon')));
+    }
+}
+
+/** @} */

+ 32 - 0
modules/boonex/credits/classes/BxCreditsGridOrdersCommon.php

@@ -0,0 +1,32 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+require_once('BxCreditsGridOrdersAdministration.php');
+
+class BxCreditsGridOrdersCommon extends BxCreditsGridOrdersAdministration
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(empty($this->_aQueryAppend['profile_id']))
+                return array();
+
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `to`.`profile_id`=?", $this->_aQueryAppend['profile_id']);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+}
+
+/** @} */

+ 854 - 0
modules/boonex/credits/classes/BxCreditsModule.php

@@ -0,0 +1,854 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_CREDITS_DIRECTION_IN', 'in');
+define('BX_CREDITS_DIRECTION_OUT', 'out');
+
+define('BX_CREDITS_ORDER_TYPE_SINGLE', 'single'); //--- one-time payment
+define('BX_CREDITS_ORDER_TYPE_RECURRING', 'recurring'); //--- recurring payment (subscription)
+
+
+class BxCreditsModule extends BxBaseModGeneralModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+    }
+
+    public function actionGetProfiles()
+    {
+        $sTerm = bx_get('term');
+
+        $a = BxDolService::call('system', 'profiles_search', array($sTerm), 'TemplServiceProfiles');
+
+        echoJson($a);
+    }
+
+    public function actionCheckout()
+    {
+        $iBuyerId = bx_get_logged_profile_id();
+        if(!$iBuyerId)
+            return echoJson(array('code' => 1, 'msg' => _t('_bx_credits_err_unknown_buyer')));
+
+        $aData = $this->_oConfig->getCheckoutData();
+        if(empty($aData) || !is_array($aData))
+            return echoJson(array('code' => 2, 'msg' => _t('_bx_credits_err_incorrect_data')));
+
+        $iSellerId = (int)$aData['seller'];
+        $fAmount = (float)$aData['amount'];
+
+        $fBalance = (float)$this->_oDb->getProfile(array('type' => 'balance', 'id' => $iBuyerId));
+        if($fAmount > $fBalance)
+            return echoJson(array('code' => 3, 'msg' => _t('_bx_credits_err_low_balance')));
+
+        $sOrder = $this->_oConfig->getOrder();
+        $sInfo = _t('_bx_credits_txt_history_info_checkout');
+        $sData = serialize(array(
+            'conversion' => $this->_oConfig->getConversionRateUse(),
+            'precision' => $this->_oConfig->getPrecision()
+        ));
+
+        $this->updateProfileBalance($iBuyerId, $iSellerId, -$fAmount, $sOrder, $sInfo, $sData);
+        $this->updateProfileBalance($iSellerId, $iBuyerId, $fAmount, $sOrder, $sInfo, $sData);
+
+        bx_alert($this->getName(), 'checkout', 0, false, array(
+            'seller' => $iSellerId,
+            'buyer' => $iBuyerId,
+            'amount' => $fAmount, 
+            'order' => $sOrder
+        ));
+
+        return echoJson(array(
+            'code' => 0,
+            'redirect' => bx_append_url_params($aData['return_data_url'], array('o' => $sOrder, 'c' => $aData['custom']))
+        ));
+    }
+
+    public function serviceGetCheckoutUrl()
+    {
+        return $this->_oConfig->getCheckoutUrl();
+    }
+
+    public function serviceValidateCheckout($iSeller, $iBuyer, $fAmount, $sOrder)
+    {
+        $fAmount = (float)$fAmount;
+
+        $aOut = $this->_oDb->getHistory(array('type' => 'row_by', 'by' => array('first_pid' => $iBuyer, 'direction' => BX_CREDITS_DIRECTION_OUT, 'order' => $sOrder)));
+        if(empty($aOut) || !is_array($aOut) || (int)$aOut['second_pid'] != (int)$iSeller)
+            return false;
+        
+        $aDataOut = unserialize($aOut['data']);
+        if($this->_oConfig->convertM2C($fAmount, $aDataOut['conversion'], $aDataOut['precision']) != (float)$aOut['amount'])
+            return false;
+
+        $aIn = $this->_oDb->getHistory(array('type' => 'row_by', 'by' => array('first_pid' => $iSeller, 'direction' => BX_CREDITS_DIRECTION_IN, 'order' => $sOrder)));
+        if(empty($aIn) || !is_array($aIn) || (int)$aIn['second_pid'] != (int)$iBuyer)
+            return false;
+
+        $aDataIn = unserialize($aIn['data']);
+        if($this->_oConfig->convertM2C($fAmount, $aDataIn['conversion'], $aDataIn['precision']) != (float)$aIn['amount'])
+            return false;
+
+        return true;
+    }
+
+    public function serviceGetBlockCheckout()
+    {
+        $oBuyer = BxDolProfile::getInstance();
+        if(!$oBuyer)
+            return MsgBox(_t('_bx_credits_err_unknown_buyer'));
+        
+        $iSeller = (int)bx_get('seller');
+        $oSeller = BxDolProfile::getInstance($iSeller);
+        if(!$oSeller)
+            return MsgBox(_t('_bx_credits_err_unknown_seller'));
+
+        $fAmountM = (float)bx_get('amount');
+        $fAmountC = $this->_oConfig->convertM2C($fAmountM);
+
+        $aData = array(
+            'seller' => $iSeller,
+            'currency' => array(
+                'code' => bx_process_input(bx_get('currency_code')),
+                'sign' => bx_process_input(bx_get('currency_sign')),
+            ),
+            'amountm' => $fAmountM,
+            'amountc' => $fAmountC
+        );
+
+        $iItemsCount = (int)bx_get('items_count');
+        for($i = 0; $i < $iItemsCount; $i++) {
+            $aData['items'][] = array(
+                'title' => bx_process_input(bx_get('item_title_' . $i)),
+                'quantity' => (int)bx_get('item_quantity_' . $i)
+            );
+        }
+
+        $this->_oConfig->setCheckoutData(array(
+            'seller' => $iSeller,
+            'amount' => $fAmountC,
+            'custom' => bx_process_input(bx_get('custom')),
+            'return_data_url' => bx_process_input(bx_get('return_data_url')),
+        ));
+        return $this->_oTemplate->getBlockCheckout($oBuyer, $oSeller, $aData);
+    }
+
+    public function serviceGetMenuItemAddonAmount()
+    {
+        $iProfileId = bx_get_logged_profile_id();
+        if(!$iProfileId)
+            return '';
+
+        return array(
+            'unit' => $this->_oConfig->getUnit(), 
+            'value' => $this->_oConfig->convertC2S($this->_oDb->getProfile(array('type' => 'balance', 'id' => $iProfileId)), false)
+        );
+    }
+
+    public function serviceGetBlockBundles()
+    {
+        return $this->_oTemplate->getBlockBundles();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_orders_note get_block_orders_note
+     * 
+     * @code bx_srv('bx_credits', 'get_block_orders_note', [...]); @endcode
+     * 
+     * Get page block with a notice for orders usage.
+     *
+     * @return HTML string with block content to display on the site.
+     * 
+     * @see BxCreditsModule::serviceBlockLicensesNote
+     */
+    /** 
+     * @ref bx_credits-get_block_orders_note "get_block_orders_note"
+     */
+    public function serviceGetBlockOrdersNote()
+    {
+        return MsgBox(_t('_bx_credits_page_block_content_orders_common_note'));
+    }
+    
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-block_orders get_block_orders
+     * 
+     * @code bx_srv('bx_credits', 'get_block_orders', [...]); @endcode
+     * 
+     * Get page block with a list of orders purchased by currently logged member.
+     *
+     * @return an array describing a block to display on the site or false if there is no enough input data. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxCreditsModule::serviceBlockLicenses
+     */
+    /** 
+     * @ref bx_credits-get_block_orders "get_block_orders"
+     */
+    public function serviceGetBlockOrders($sType = 'common') 
+    {
+        return $this->_getBlockOrders($sType);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-page_blocks Page Blocks
+     * @subsubsection bx_credits-get_block_history get_block_history
+     * 
+     * @code bx_srv('bx_credits', 'get_block_history', [...]); @endcode
+     * 
+     * Get page block with a list of all changes with credits.
+     *
+     * @return an array describing a block to display on the site or false if there is no enough input data. All necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
+     * 
+     * @see BxCreditsModule::serviceGetBlockHistory
+     */
+    /** 
+     * @ref bx_credits-get_block_history "get_block_history"
+     */
+    public function serviceGetBlockHistory($sType = 'common') 
+    {
+        return $this->_getBlockHistory($sType);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-get_payment_data get_payment_data
+     * 
+     * @code bx_srv('bx_credits', 'get_payment_data', [...]); @endcode
+     * 
+     * Get an array with module's description. Is needed for payments processing module.
+     * 
+     * @return an array with module's description.
+     * 
+     * @see BxCreditsModule::serviceGetPaymentData
+     */
+    /** 
+     * @ref bx_credits-get_payment_data "get_payment_data"
+     */
+    public function serviceGetPaymentData()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oPermalink = BxDolPermalinks::getInstance();
+
+        $aResult = $this->_aModule;
+        $aResult['url_browse_order_common'] = BX_DOL_URL_ROOT . $oPermalink->permalink($CNF['URL_ORDERS_COMMON'], array('filter' => '{order}'));
+        $aResult['url_browse_order_administration'] = BX_DOL_URL_ROOT . $oPermalink->permalink($CNF['URL_ORDERS_ADMINISTRATION'], array('filter' => '{order}'));
+
+        return $aResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-get_cart_item get_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'get_cart_item', [...]); @endcode
+     * 
+     * Get an array with prodict's description. Is used in Shopping Cart in payments processing module.
+     * 
+     * @param $iItemId product's ID.
+     * @return an array with prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxCreditsModule::serviceGetCartItem
+     */
+    /** 
+     * @ref bx_credits-get_cart_item "get_cart_item"
+     */
+    public function serviceGetCartItem($iItemId)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        if(!$iItemId)
+            return array();
+
+        $aItem = $this->_oDb->getBundle(array('type' => 'id', 'id' => $iItemId));
+        if(empty($aItem) || !is_array($aItem))
+            return array();
+
+        return array (
+            'id' => $aItem[$CNF['FIELD_ID']],
+            'author_id' => $this->_oConfig->getAuthor(),
+            'name' => $aItem[$CNF['FIELD_NAME']],
+            'title' => _t($aItem[$CNF['FIELD_TITLE']]),
+            'description' => $this->_oConfig->getBundleDescription($aItem),
+            'url' => $this->_oConfig->getBundleUrl($aItem),
+            'price_single' => $aItem[$CNF['FIELD_PRICE']],
+            'price_recurring' => '',
+            'period_recurring' => 1,
+            'period_unit_recurring' => '',
+            'trial_recurring' => ''
+        );
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-get_cart_items get_cart_items
+     * 
+     * @code bx_srv('bx_credits', 'get_cart_items', [...]); @endcode
+     * 
+     * Get an array with prodicts' descriptions by seller. Is used in Manual Order Processing in payments processing module.
+     * 
+     * @param $iSellerId seller ID.
+     * @return an array with prodicts' descriptions. Empty array is returned if something is wrong or seller doesn't have any products.
+     * 
+     * @see BxCreditsModule::serviceGetCartItems
+     */
+    /** 
+     * @ref bx_credits-get_cart_items "get_cart_items"
+     */
+    public function serviceGetCartItems($iAuthorId)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+        $iAuthorId = $this->_oConfig->getAuthor();
+        $aItems = $this->_oDb->getBundle(array('type' => 'all', 'active' => 1));
+
+        $aResult = array();
+        foreach($aItems as $aItem)
+            $aResult[] = array(
+                'id' => $aItem[$CNF['FIELD_ID']],
+                'author_id' => $iAuthorId,
+                'name' => $aItem[$CNF['FIELD_NAME']],
+                'title' => _t($aItem[$CNF['FIELD_TITLE']]),
+                'description' => $this->_oConfig->getBundleDescription($aItem),
+                'url' => $this->_oConfig->getBundleUrl($aItem),
+                'price_single' => $aItem[$CNF['FIELD_PRICE']],
+                'price_recurring' => '',
+                'period_recurring' => 1,
+                'period_unit_recurring' => '',
+                'trial_recurring' => ''
+            );
+
+        return $aResult;
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-register_cart_item register_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'register_cart_item', [...]); @endcode
+     * 
+     * Register a processed single time payment inside the Credits module. Is called with payment processing module after the payment was registered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return an array with purchased prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxCreditsModule::serviceRegisterCartItem
+     */
+    /** 
+     * @ref bx_credits-register_cart_item "register_cart_item"
+     */
+    public function serviceRegisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_SINGLE);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-register_subscription_item register_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'register_subscription_item', [...]); @endcode
+     * 
+     * Register a processed subscription (recurring payment) inside the Credits module. Is called with payment processing module after the subscription was registered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return an array with subscribed prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxCreditsModule::serviceRegisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-register_subscription_item "register_subscription_item"
+     */
+    public function serviceRegisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_RECURRING);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-reregister_cart_item reregister_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'reregister_cart_item', [...]); @endcode
+     * 
+     * Reregister a single time payment inside the Credits module. Is called with payment processing module after the payment was reregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemIdOld old item ID.
+     * @param $iItemIdNew new item ID.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return an array with purchased prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxCreditsModule::serviceReregisterCartItem
+     */
+    /** 
+     * @ref bx_credits-reregister_cart_item "reregister_cart_item"
+     */
+    public function serviceReregisterCartItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return array();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-reregister_subscription_item reregister_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'reregister_subscription_item', [...]); @endcode
+     * 
+     * Reregister a subscription (recurring payment) inside the Credits module. Is called with payment processing module after the subscription was reregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemIdOld old item ID.
+     * @param $iItemIdNew new item ID.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return an array with subscribed prodict's description. Empty array is returned if something is wrong.
+     * 
+     * @see BxCreditsModule::serviceReregisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-reregister_subscription_item "reregister_subscription_item"
+     */
+    public function serviceReregisterSubscriptionItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return array();
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-unregister_cart_item unregister_cart_item
+     * 
+     * @code bx_srv('bx_credits', 'unregister_cart_item', [...]); @endcode
+     * 
+     * Unregister an earlier processed single time payment inside the Credits module. Is called with payment processing module after the payment was unregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return boolean value determining where the payment was unregistered or not.
+     * 
+     * @see BxCreditsModule::serviceUnregisterCartItem
+     */
+    /** 
+     * @ref bx_credits-unregister_cart_item "unregister_cart_item"
+     */
+    public function serviceUnregisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_SINGLE);
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-unregister_subscription_item unregister_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'unregister_subscription_item', [...]); @endcode
+     * 
+     * Unregister an earlier processed subscription (recurring payment) inside the Credits module. Is called with payment processing module after the subscription was unregistered there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @param $sLicense license number genereted with payment processing module for internal usage
+     * @return boolean value determining where the subscription was unregistered or not.
+     * 
+     * @see BxCreditsModule::serviceUnregisterSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-unregister_subscription_item "unregister_subscription_item"
+     */
+    public function serviceUnregisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+    	return $this->_serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, BX_CREDITS_ORDER_TYPE_RECURRING); 
+    }
+
+    /**
+     * @page service Service Calls
+     * @section bx_credits Credits
+     * @subsection bx_credits-payments Payments
+     * @subsubsection bx_credits-cancel_subscription_item cancel_subscription_item
+     * 
+     * @code bx_srv('bx_credits', 'cancel_subscription_item', [...]); @endcode
+     * 
+     * Cancel an earlier processed subscription (recurring payment) inside the Credits module. Is called with payment processing module after the subscription was canceled there.
+     * 
+     * @param $iClientId client ID.
+     * @param $iSellerId seller ID
+     * @param $iItemId item ID.
+     * @param $iItemCount the number of purchased items.
+     * @param $sOrder order number received from payment provider (PayPal, Stripe, etc)
+     * @return boolean value determining where the subscription was canceled or not.
+     * 
+     * @see BxCreditsModule::serviceCancelSubscriptionItem
+     */
+    /** 
+     * @ref bx_credits-cancel_subscription_item "cancel_subscription_item"
+     */
+    public function serviceCancelSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+    	return true;
+    }
+
+    public function serviceGetProfileBalance($iProfileId = 0)
+    {
+        return $this->getProfileBalance($iProfileId);
+    }
+
+    public function serviceUpdateProfileBalance($iFirstPid, $fAmount, $iSecondPid = 0, $sOrder = '', $sInfo = '')
+    {
+        if(empty($sInfo))
+            $sInfo = _t('_bx_credits_txt_history_info_service');
+
+        return $this->updateProfileBalance($iFirstPid, $iSecondPid, $fAmount, $sOrder, $sInfo);
+    }
+
+    /**
+     * Delete all content by profile 
+     * @param $iProfileId profile id 
+     * @return number of deleted items
+     */
+    public function serviceDeleteEntitiesByAuthor ($iProfileId)
+    {
+        $this->_oDb->deleteProfile(array('id' => $iProfileId));
+        $this->_oDb->deleteHistory(array('first_pid' => $iProfileId));
+    }
+
+
+    /*
+     * Common methods
+     */
+    public function getProfileBalance($iProfileId = 0)
+    {
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id();
+
+        if(empty($iProfileId))
+            return 0;
+
+        return (float)$this->_oDb->getProfile(array('type' => 'balance', 'id' => $iProfileId));
+    }
+
+    public function updateProfileBalance($iFirstPid, $iSecondPid, $fAmount, $sOrder = '', $sInfo = '', $sData = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($sOrder))
+            $sOrder = $this->_oConfig->getOrder();
+
+        if(empty($sData))
+            $sData = serialize(array(
+                'conversion' => $this->_oConfig->getConversionRateUse(),
+                'precision' => $this->_oConfig->getPrecision()
+            ));
+
+        if(!$this->_oDb->updateProfileBalance($iFirstPid, $fAmount))
+            return false;
+
+        $fAmountAbs = abs($fAmount);
+        $sDirection = $fAmount > 0 ? BX_CREDITS_DIRECTION_IN : BX_CREDITS_DIRECTION_OUT;
+
+        $this->_oDb->insertHistory(array(
+            'first_pid' => $iFirstPid,
+            'second_pid' => $iSecondPid,
+            'amount' => $fAmountAbs,
+            'direction' => $sDirection,
+            'order' => $sOrder,
+            'data' => $sData,
+            'info' => $sInfo,
+            'date' => time()
+        ));
+
+        bx_alert($this->getName(), 'update_balance', 0, false, array(
+            'first_pid' => $iFirstPid,
+            'second_pid' => $iSecondPid,
+            'amount' => $fAmountAbs,
+            'direction' => $sDirection,
+            'order' => $sOrder,
+        ));
+
+        $oSecondProfile = BxDolProfile::getInstanceMagic($iSecondPid);
+        sendMailTemplate($CNF['ETEMPLATE_' . strtoupper($sDirection)], 0, $iFirstPid, array(
+            'second_profile_name' => $oSecondProfile->getDisplayName(),
+            'amount' => $fAmountAbs,
+        ));
+
+        return true;
+    }
+
+    public function processGrant($iProfileId, $fAmount, $sMessage = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return array('code' => 1, 'msg' => '_bx_credits_err_profile_not_found');
+
+        $sInfo = _t('_bx_credits_txt_history_info_grant');
+        if(!$this->updateProfileBalance($iProfileId, 0, $fAmount, '', $sInfo))
+            return array('code' => 2, 'msg' => '_bx_credits_err_cannot_update_balance');
+
+        sendMailTemplate($CNF['ETEMPLATE_GRANTED'], 0, $iProfileId, array(
+            'amount' => $fAmount,
+            'message' => $sMessage
+        ));
+
+        bx_alert($this->getName(), 'granted', 0, false, array(
+            'profile' => $iProfileId,
+            'amount' => $fAmount,
+        ));
+
+        return array('code' => 0);
+    }
+
+    public function processWithdrawRequest($iProfileId, $fAmount, $sMessage = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id ();
+
+        if(empty($iProfileId))
+            return array('code' => 1, 'msg' => '_bx_credits_err_login_required');
+
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return array('code' => 1, 'msg' => '_bx_credits_err_login_required');
+
+        $fBalance = $this->getProfileBalance($iProfileId);
+        if($fAmount > $fBalance)
+            return array('code' => 2, 'msg' => '_bx_credits_err_low_balance');
+
+        $aResult = array('code' => 3, 'msg' => '_bx_credits_err_cannot_send');
+
+        $oEmailTemplates = BxDolEmailTemplates::getInstance();
+        if(!$oEmailTemplates)
+            return $aResult;
+
+        $fRate = $this->_oConfig->getConversionRateWithdraw();
+
+        $aTemplateVars = array(
+            'profile_link' => $oProfile->getUrl(),
+            'profile_name' => $oProfile->getDisplayName(),
+            'amount' => $fAmount,
+            'rate' => $fRate,
+            'message' => $sMessage,
+        );
+        $aTemplate = $oEmailTemplates->parseTemplate($CNF['ETEMPLATE_WITHDRAW_REQUESTED'], $aTemplateVars);
+        if(!$aTemplate)
+            return $aResult;
+
+        $sEmail = $this->_oConfig->getWithdrawEmail();
+        if(!sendMail($sEmail, $aTemplate['Subject'], $aTemplate['Body'], 0, array(), BX_EMAIL_SYSTEM, 'html', false, array(), false))
+            return $aResult;
+
+        bx_alert($this->getName(), 'withdraw_requested', 0, $iProfileId, array(
+            'profile' => $iProfileId,
+            'amount' => $fAmount,
+            'rate' => $fRate
+        ));
+
+        return array('code' => 0);
+    }
+
+    public function processWithdrawConfirm($iProfileId, $fAmount, $sMessage = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+        
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+        if(!$oProfile)
+            return array('code' => 1, 'msg' => '_bx_credits_err_profile_not_found');
+
+        $fBalance = $this->getProfileBalance($iProfileId);
+        if($fAmount > $fBalance)
+            return array('code' => 2, 'msg' => '_bx_credits_err_low_balance');
+
+        $sInfo = _t('_bx_credits_txt_history_info_withdraw');
+        if(!$this->updateProfileBalance($iProfileId, 0, -$fAmount, '', $sInfo))
+            return array('code' => 3, 'msg' => '_bx_credits_err_cannot_update_balance');
+
+        sendMailTemplate($CNF['ETEMPLATE_WITHDRAW_SENT'], 0, $iProfileId, array(
+            'amount' => $fAmount,
+            'message' => $sMessage
+        ));
+
+        bx_alert($this->getName(), 'withdraw_sent', 0, false, array(
+            'profile' => $iProfileId,
+            'amount' => $fAmount,
+        ));
+
+        return array('code' => 0);
+    }
+
+    /*
+     * Internal methods
+     */
+    protected function _serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, $sType)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aItem = $this->serviceGetCartItem($iItemId);
+        if(empty($aItem) || !is_array($aItem))
+            return array();
+
+        $aBundle = $this->_oDb->getBundle(array('type' => 'id', 'id' => $iItemId));
+        if(empty($aBundle) || !is_array($aBundle))
+            return array();
+
+        $iTrial = 0;
+        $sDuration = '';
+        $sAction = 'register';
+        if($sType == BX_CREDITS_ORDER_TYPE_RECURRING && isset($CNF['FIELD_DURATION_RECURRING'], $CNF['FIELD_TRIAL_RECURRING'])) {
+            $iTrial = $aBundle[$CNF['FIELD_TRIAL_RECURRING']];
+            $sDuration = $aBundle[$CNF['FIELD_DURATION_RECURRING']];
+
+            if($this->_oDb->isOrderByPbo($iClientId, $iItemId, $sOrder))
+                $sAction = 'prolong';
+        }
+
+        if(!$this->_oDb->{$sAction . 'Order'}($iClientId, $iItemId, $iItemCount, $sOrder, $sLicense, $sType, $sDuration, $iTrial))
+            return array();
+
+        $sInfo = _t('_bx_credits_txt_history_info_purchase');
+        $fAmount = (float)$aBundle[$CNF['FIELD_AMOUNT']] + (float)$aBundle[$CNF['FIELD_BONUS']];
+        if(!$this->updateProfileBalance($iClientId, 0, $fAmount, '', $sInfo))
+            return array();
+
+        bx_alert($this->getName(), 'order_' . $sAction, 0, false, array(
+            'profile_id' => $iClientId,
+            'bundle_id' => $iItemId,
+            'product_id' => $iItemId,   //--- Alias for 'bundle_id'
+            'count' => $iItemCount,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'type' => $sType,
+            'duration' => $sDuration,
+            'trial' => $iTrial
+        ));
+
+        $oClient = BxDolProfile::getInstanceMagic($iClientId);
+        $oSeller = BxDolProfile::getInstanceMagic($iSellerId);
+        $sSellerUrl = $oSeller->getUrl();
+        $sSellerName = $oSeller->getDisplayName();
+
+        sendMailTemplate($CNF['ETEMPLATE_PURCHASED'], 0, $iClientId, array(
+            'client_name' => $oClient->getDisplayName(),
+            'bundle_name' => $aBundle[$CNF['FIELD_NAME']],
+            'bundle_title' => _t($aBundle[$CNF['FIELD_TITLE']]),
+            'bundle_url' => $this->_oConfig->getBundleUrl($aBundle),
+            'vendor_url' => $sSellerUrl,
+            'vendor_name' => $sSellerName,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'notes' => _t('_bx_credits_txt_purchased_note', $sSellerUrl, $sSellerName),
+        ));
+
+        return $aItem;
+    }
+
+    protected function _serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense, $sType)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aBundle = $this->_oDb->getBundle(array('type' => 'id', 'id' => $iItemId));
+        if(empty($aBundle) || !is_array($aBundle))
+            return false;
+
+        if(!$this->_oDb->unregisterOrder($iClientId, $iItemId, $sOrder, $sLicense, $sType))
+            return false;
+
+        $fAmount = (float)$aBundle[$CNF['FIELD_AMOUNT']] + (float)$aBundle[$CNF['FIELD_BONUS']];
+        $aProfile = $this->_oDb->getProfile(array('type' => 'id', 'id' => $iClientId));
+        if(!empty($aProfile) && is_array($aProfile))
+            $this->_oDb->updateProfile(array('balance' => (float)$aProfile['balance'] - $fAmount), array('id' => $aProfile['id']));
+
+        bx_alert($this->getName(), 'order_unregister', 0, false, array(
+            'profile_id' => $iClientId,
+            'bundle_id' => $iItemId,
+            'product_id' => $iItemId,   //--- Alias for 'bundle_id'
+            'count' => $iItemCount,
+            'order' => $sOrder,
+            'license' => $sLicense,
+            'type' => $sType,
+        ));
+
+        return true;
+    }
+
+    protected function _getBlockOrders($sType) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sGrid = $CNF['OBJECT_GRID_ORDERS_' . strtoupper($sType)];
+        $oGrid = BxDolGrid::getObjectInstance($sGrid);
+        if(!$oGrid)
+            return '';
+
+        return array(
+            'content' => $oGrid->getCode(),
+            'menu' => $CNF['OBJECT_MENU_MANAGE_SUBMENU']
+        );
+    }
+
+    protected function _getBlockHistory($sType) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sGrid = $CNF['OBJECT_GRID_HISTORY_' . strtoupper($sType)];
+        $oGrid = BxDolGrid::getObjectInstance($sGrid);
+        if(!$oGrid)
+            return '';
+
+        return array(
+            'content' => $oGrid->getCode(),
+            'menu' => $CNF['OBJECT_MENU_MANAGE_SUBMENU']
+        );
+    }
+}
+
+/** @} */

+ 25 - 0
modules/boonex/credits/classes/BxCreditsPageBrowse.php

@@ -0,0 +1,25 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Browse entries pages.
+ */
+class BxCreditsPageBrowse extends BxBaseModGeneralPageBrowse
+{
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->MODULE = 'bx_credits';
+
+        parent::__construct($aObject, $oTemplate);
+    }
+}
+
+/** @} */

+ 35 - 0
modules/boonex/credits/classes/BxCreditsPageHistory.php

@@ -0,0 +1,35 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Profile's credit history page.
+ */
+class BxCreditsPageHistory extends BxTemplPage
+{
+    protected $_sModule;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_credits';
+
+        parent::__construct($aObject, $oTemplate);
+
+        $oMenuSubmenu = BxDolMenu::getObjectInstance('sys_site_submenu');
+        if($oMenuSubmenu) {
+            $sMenuSubmenu = 'sys_account_dashboard';
+            $oMenuSubmenu->setObjectSubmenu($sMenuSubmenu, array('title' => _t('_sys_menu_item_title_account_dashboard'), 'link' => '', 'icon' => ''));
+
+            BxDolMenu::getObjectInstance($sMenuSubmenu)->setSelected($this->_sModule, 'credits-manage');
+        }
+    }
+}
+
+/** @} */

+ 35 - 0
modules/boonex/credits/classes/BxCreditsPageOrders.php

@@ -0,0 +1,35 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/**
+ * Profile's orders page.
+ */
+class BxCreditsPageOrders extends BxTemplPage
+{
+    protected $_sModule;
+
+    public function __construct($aObject, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_credits';
+
+        parent::__construct($aObject, $oTemplate);
+
+        $oMenuSubmenu = BxDolMenu::getObjectInstance('sys_site_submenu');
+        if($oMenuSubmenu) {
+            $sMenuSubmenu = 'sys_account_dashboard';
+            $oMenuSubmenu->setObjectSubmenu($sMenuSubmenu, array('title' => _t('_sys_menu_item_title_account_dashboard'), 'link' => '', 'icon' => ''));
+
+            BxDolMenu::getObjectInstance($sMenuSubmenu)->setSelected($this->_sModule, 'credits-manage');
+        }
+    }
+}
+
+/** @} */

+ 60 - 0
modules/boonex/credits/classes/BxCreditsStudioPage.php

@@ -0,0 +1,60 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_DOL_STUDIO_MOD_TYPE_BUNDLES', 'bundles');
+
+class BxCreditsStudioPage extends BxTemplStudioModule
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    function __construct($sModule = "", $sPage = "")
+    {
+    	$this->_sModule = 'bx_credits';
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct($sModule, $sPage);
+
+        $this->aMenuItems[] = array('name' => BX_DOL_STUDIO_MOD_TYPE_BUNDLES, 'icon' => 'archive', 'title' => '_bx_credits_menu_item_title_bundles');
+    }
+
+    protected function getSettings()
+    {
+        $sContent = '';
+        if($this->_oModule->_oConfig->getAuthor() == 0)
+            $sContent .= $this->_oModule->_oTemplate->getEmptyAuthor();
+
+        $sContent .= parent::getSettings();
+
+        return $sContent;
+    }
+
+    protected function getBundles()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oGrid = BxDolGrid::getObjectInstance($CNF['OBJECT_GRID_BUNDLES'], BxDolStudioTemplate::getInstance());
+        if(!$oGrid)
+            return '';
+
+        $sContent = '';
+        if($this->_oModule->_oConfig->getAuthor() == 0)
+            $sContent .= $this->_oModule->_oTemplate->getEmptyAuthor();
+
+        $sContent .= $this->_oModule->_oTemplate->getJsCode('studio');
+        $sContent .= $oGrid->getCode();
+
+        $this->_oModule->_oTemplate->addJs('studio');
+        return $sContent;
+    }
+}
+
+/** @} */

+ 127 - 0
modules/boonex/credits/classes/BxCreditsTemplate.php

@@ -0,0 +1,127 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCreditsTemplate extends BxBaseModGeneralTemplate
+{
+    function __construct(&$oConfig, &$oDb)
+    {
+        $this->MODULE = 'bx_credits';
+
+        parent::__construct($oConfig, $oDb);
+
+        $this->aMethodsToCallAddJsCss = array('getBlockBundles');
+    }
+
+    public function getJsCode($sType, $aParams = array(), $bWrap = true)
+    {
+        $aParams = array_merge(array(
+            'aHtmlIds' => $this->_oConfig->getHtmlIds()
+        ), $aParams);
+
+        return parent::getJsCode($sType, $aParams, $bWrap);
+    }
+
+    public function getEmptyAuthor()
+    {
+    	return MsgBox(_t('_bx_credits_msg_empty_author'));
+    }
+
+    public function getBlockCheckout($oBuyer, $oSeller, $aData)
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sStylePrefix = $this->_oConfig->getPrefix('style');
+        $sJsObject = $this->_oConfig->getJsObject('checkout');
+
+        $sTxtQt = _t('_bx_credits_txt_checkout_qt');
+
+        $fRate = $this->_oConfig->getConversionRateUse();
+        $aCurrency = $this->_oConfig->getCurrency();
+
+        $sCurrencySign = $aCurrency['sign'];
+        if(!empty($aData['currency']['sign']))
+            $sCurrencySign = $aData['currency']['sign'];
+
+        $aTmplVarsItems = array();
+        foreach($aData['items'] as $iIndex => $aItem)
+            $aTmplVarsItems[] = array(
+                'sp' => $sStylePrefix,
+                'item_index' => $iIndex + 1,
+                'item_title' => $aItem['title'],
+                'item_quantity' => $aItem['quantity'] . $sTxtQt
+            );
+
+        $this->addJs(array('checkout.js'));
+        $this->addCss(array('checkout.css'));
+        return $this->parseHtmlByName('checkout.html', array(
+            'sp' => $sStylePrefix,
+            'jo' => $sJsObject,
+            'title' => _t('_bx_credits_txt_checkout_to', $oSeller->getDisplayName()),
+            'bx_repeat:items' => $aTmplVarsItems,
+            'amount' => $sCurrencySign . sprintf("%.2f", (float)($aData['amountm'])),
+            'bx_if:show_rate' => array(
+                'condition' => $fRate != 1,
+                'content' => array(
+                    'sp' => $sStylePrefix,
+                    'rate' => $fRate,
+                )
+            ),
+            'credits' => $this->_oConfig->convertC2S($aData['amountc']),
+            'js_code' => $this->getJsCode('checkout')
+        ));
+    }
+
+    public function getBlockBundles()
+    {
+        $CNF = &$this->_oConfig->CNF;
+        $sStylePrefix = $this->_oConfig->getPrefix('style');
+
+        $iAuthor = $this->_oConfig->getAuthor();
+        $aCurrency = $this->_oConfig->getCurrency();
+
+        $aBundles = $this->_oDb->getBundle(array('type' => 'all', 'active' => 1));
+        if(empty($aBundles) || !is_array($aBundles))
+            return MsgBox(_t('_Empty'));
+
+        $oPayment = BxDolPayments::getInstance();
+
+        $aTmplVarsBundles = array();
+        foreach($aBundles as $aBundle) {
+            $aJs = $oPayment->getAddToCartJs($iAuthor, $this->MODULE, $aBundle[$CNF['FIELD_ID']], 1, true);
+            if(empty($aJs) || !is_array($aJs))
+                continue;
+
+            list($sJsCode, $sJsMethod) = $aJs;
+
+            $aTmplVarsBundles[] = array_merge($aBundle, array(
+                'sp' => $sStylePrefix,
+                'title' => _t($aBundle[$CNF['FIELD_TITLE']]),
+                'bx_if:show_bonus' => array(
+                    'condition' => (int)$aBundle[$CNF['FIELD_BONUS']] > 0,
+                    'content' => array(
+                        'sp' => $sStylePrefix,
+                        'bonus' => (int)$aBundle[$CNF['FIELD_BONUS']]
+                    )
+                ),
+                'currency_sign' => $aCurrency['sign'],
+                'currency_code' => $aCurrency['code'],
+                'onclick' => $sJsMethod
+            ));
+        }
+
+        return $this->parseHtmlByName('bundles.html', array(
+            'sp' => $sStylePrefix,
+            'bx_repeat:bundles' => $aTmplVarsBundles,
+            'js_code' => $oPayment->getCartJs()
+        ));
+    }
+}
+
+/** @} */

+ 77 - 0
modules/boonex/credits/install/config.php

@@ -0,0 +1,77 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_credits',
+    'title' => 'Credits',
+    'note' => 'Credits module.',
+    'version' => '10.0.0.DEV',
+    'vendor' => 'BoonEx',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '10.0.x'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/credits/',
+    'home_uri' => 'credits',
+
+    'db_prefix' => 'bx_credits_',
+    'class_prefix' => 'BxCredits',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Credits',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Relations Section
+     */
+    'relations' => array(),
+
+);
+
+/** @} */

+ 37 - 0
modules/boonex/credits/install/installer.php

@@ -0,0 +1,37 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxCreditsInstaller extends BxBaseModTextInstaller
+{
+    public function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+    
+    public function enable($aParams)
+    {
+        $aResult = parent::enable($aParams);
+
+        if($aResult['result'])
+            BxDolPayments::getInstance()->updateDependentModules($this->_aConfig['name'], true);
+
+        return $aResult;
+    }
+
+    public function disable($aParams)
+    {
+        BxDolPayments::getInstance()->updateDependentModules($this->_aConfig['name'], false);
+
+        return parent::disable($aParams);
+    }
+}
+
+/** @} */

+ 230 - 0
modules/boonex/credits/install/langs/en.xml

@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_credits"><![CDATA[Credits]]></string>
+
+
+    <!-- Options -->
+    <string name="_bx_credits_option_enable_withdraw"><![CDATA[Allow members to withdraw credits converting them to money]]></string>
+    <string name="_bx_credits_option_withdraw_email"><![CDATA[Email for withdraw requests]]></string>
+    <string name="_bx_credits_option_precision"><![CDATA[Precision (number of digits after the decimal point)]]></string>
+    <string name="_bx_credits_option_conversion_rate_use"><![CDATA[Conversion rate to pay with credits]]></string>
+    <string name="_bx_credits_option_conversion_rate_withdraw"><![CDATA[Conversion rate to withdraw credits]]></string>
+    <string name="_bx_credits_option_code"><![CDATA[Code to prefix amount]]></string>
+    <string name="_bx_credits_option_icon"><![CDATA[Icon to prefix amount]]></string>
+
+
+    <!-- Forms -->
+    <string name="_bx_credits_form_bundle"><![CDATA[Bundle]]></string>
+    <string name="_bx_credits_form_bundle_display_add"><![CDATA[Add Bundle]]></string>
+    <string name="_bx_credits_form_bundle_display_edit"><![CDATA[Edit Bundle]]></string>
+    <string name="_bx_credits_form_bundle_input_sys_title"><![CDATA[Title]]></string>
+    <string name="_bx_credits_form_bundle_input_title"><![CDATA[Title]]></string>
+    <string name="_bx_credits_form_bundle_input_title_err"><![CDATA[This field is essential. Please fill it in for all languages.]]></string>
+    <string name="_bx_credits_form_bundle_input_sys_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_credits_form_bundle_input_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_credits_form_bundle_input_amount_err"><![CDATA[This field is essential. Please fill it in.]]></string>
+    <string name="_bx_credits_form_bundle_input_sys_bonus"><![CDATA[Bonus"]]></string>
+    <string name="_bx_credits_form_bundle_input_bonus"><![CDATA[Bonus]]></string>
+    <string name="_bx_credits_form_bundle_input_sys_price"><![CDATA[Price]]></string>
+    <string name="_bx_credits_form_bundle_input_price"><![CDATA[Price]]></string>
+    <string name="_bx_credits_form_bundle_input_price_err"><![CDATA[This field is essential. Please fill it in.]]></string>
+    <string name="_bx_credits_form_bundle_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_credits_form_bundle_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_credits_form_bundle_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_credits_form_bundle_input_do_cancel"><![CDATA[Cancel]]></string>
+
+    <string name="_bx_credits_form_credit"><![CDATA[Credit]]></string>
+    <string name="_bx_credits_form_credit_display_grant"><![CDATA[Grant]]></string>
+    <string name="_bx_credits_form_credit_display_withdraw_request"><![CDATA[Request Withdraw]]></string>
+    <string name="_bx_credits_form_credit_display_withdraw_confirm"><![CDATA[Confirm withdraw]]></string>
+    <string name="_bx_credits_form_credit_input_sys_balance"><![CDATA[Balance]]></string>
+    <string name="_bx_credits_form_credit_input_balance"><![CDATA[Balance]]></string>
+    <string name="_bx_credits_form_credit_input_sys_rate"><![CDATA[Conversion rate]]></string>
+    <string name="_bx_credits_form_credit_input_rate"><![CDATA[Conversion rate]]></string>
+    <string name="_bx_credits_form_credit_input_sys_result"><![CDATA[Result]]></string>
+    <string name="_bx_credits_form_credit_input_result"><![CDATA[Result]]></string>
+    <string name="_bx_credits_form_credit_input_sys_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_credits_form_credit_input_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_credits_form_credit_input_sys_message"><![CDATA[Message]]></string>
+    <string name="_bx_credits_form_credit_input_message"><![CDATA[Message]]></string>
+    <string name="_bx_credits_form_credit_input_profile"><![CDATA[Profile]]></string>
+    <string name="_bx_credits_form_credit_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_credits_form_credit_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_credits_form_credit_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_credits_form_credit_input_do_cancel"><![CDATA[Cancel]]></string>
+
+
+    <!-- Pages -->
+    <string name="_bx_credits_page_title_sys_home"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_page_title_home"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_page_block_title_sys_bundles"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_page_block_title_bundles"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_page_title_sys_checkout"><![CDATA[Checkout]]></string>
+    <string name="_bx_credits_page_title_checkout"><![CDATA[Checkout]]></string>
+    <string name="_bx_credits_page_block_title_sys_checkout"><![CDATA[Checkout]]></string>
+    <string name="_bx_credits_page_block_title_checkout"><![CDATA[Checkout]]></string>
+    <string name="_bx_credits_page_title_sys_history_common"><![CDATA[History Own]]></string>
+    <string name="_bx_credits_page_title_history_common"><![CDATA[History]]></string>
+    <string name="_bx_credits_page_block_title_sys_history_common"><![CDATA[History Own]]></string>
+    <string name="_bx_credits_page_block_title_history_common"><![CDATA[History]]></string>    
+    <string name="_bx_credits_page_title_sys_history_administration"><![CDATA[History All]]></string>
+    <string name="_bx_credits_page_title_history_administration"><![CDATA[History]]></string>
+    <string name="_bx_credits_page_block_title_sys_history_administration"><![CDATA[History All]]></string>
+    <string name="_bx_credits_page_block_title_history_administration"><![CDATA[History]]></string>
+    <string name="_bx_credits_page_title_sys_orders_common"><![CDATA[Orders Own]]></string>
+    <string name="_bx_credits_page_title_orders_common"><![CDATA[Orders]]></string>
+    <string name="_bx_credits_page_block_title_sys_orders_common_note"><![CDATA[Note]]></string>
+    <string name="_bx_credits_page_block_title_orders_common_note"><![CDATA[Note]]></string>
+    <string name="_bx_credits_page_block_content_orders_common_note"><![CDATA[The orders are for reference only.]]></string>
+    <string name="_bx_credits_page_block_title_sys_orders_common"><![CDATA[Orders Own]]></string>
+    <string name="_bx_credits_page_block_title_orders_common"><![CDATA[Orders]]></string>
+    <string name="_bx_credits_page_title_sys_orders_administration"><![CDATA[Orders All]]></string>
+    <string name="_bx_credits_page_title_orders_administration"><![CDATA[Orders]]></string>
+    <string name="_bx_credits_page_block_title_sys_orders_administration"><![CDATA[Orders All]]></string>
+    <string name="_bx_credits_page_block_title_orders_administration"><![CDATA[Orders]]></string>
+
+
+    <!-- Menus -->
+    <string name="_bx_credits_menu_item_title_system_credits"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_menu_item_title_credits"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_menu_item_title_system_history"><![CDATA[Balance]]></string>
+    <string name="_bx_credits_menu_item_title_history"><![CDATA[{unit}{value}]]></string>
+    <string name="_bx_credits_menu_title_submenu"><![CDATA[Credits Submenu]]></string>
+    <string name="_bx_credits_menu_set_title_submenu"><![CDATA[Credits Submenu]]></string>
+    <string name="_bx_credits_menu_item_title_system_home"><![CDATA[Home]]></string>
+    <string name="_bx_credits_menu_item_title_home"><![CDATA[Home]]></string>
+    <string name="_bx_credits_menu_item_title_system_admt_credits"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_menu_item_title_admt_credits"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_menu_item_title_bundles"><![CDATA[Bundles]]></string>
+    <string name="_bx_credits_menu_item_title_system_manage"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_menu_item_title_manage"><![CDATA[Credits]]></string>
+    <string name="_bx_credits_menu_title_manage_submenu"><![CDATA[Orders Submenu]]></string>
+    <string name="_bx_credits_menu_set_title_manage_submenu"><![CDATA[Orders Submenu]]></string>
+    <string name="_bx_credits_menu_item_title_system_orders_administration"><![CDATA[All Orders]]></string>
+    <string name="_bx_credits_menu_item_title_orders_administration"><![CDATA[All Orders]]></string>
+    <string name="_bx_credits_menu_item_title_system_orders_common"><![CDATA[My Orders]]></string>
+    <string name="_bx_credits_menu_item_title_orders_common"><![CDATA[My Orders]]></string>
+    <string name="_bx_credits_menu_item_title_system_history_administration"><![CDATA[All History]]></string>
+    <string name="_bx_credits_menu_item_title_history_administration"><![CDATA[All History]]></string>
+    <string name="_bx_credits_menu_item_title_system_history_common"><![CDATA[My History]]></string>
+    <string name="_bx_credits_menu_item_title_history_common"><![CDATA[My History]]></string>
+
+
+    <!-- Grids -->
+    <string name="_bx_credits_grid_column_title_name"><![CDATA[Name]]></string>
+    <string name="_bx_credits_grid_column_title_title"><![CDATA[Title]]></string>
+    <string name="_bx_credits_grid_column_title_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_credits_grid_column_title_bonus"><![CDATA[Bonus]]></string>
+    <string name="_bx_credits_grid_column_title_price"><![CDATA[Price]]></string>
+    <string name="_bx_credits_grid_action_title_add"><![CDATA[Add]]></string>
+    <string name="_bx_credits_grid_action_title_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_credits_grid_action_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_credits_grid_popup_title_add_bundle"><![CDATA[Add Bundle]]></string>
+    <string name="_bx_credits_grid_popup_title_edit_bundle"><![CDATA[Edit '{0}' Bundle]]></string>
+
+    <string name="_bx_credits_grid_column_title_htr_pid"><![CDATA[Profile]]></string>
+    <string name="_bx_credits_grid_column_title_htr_first_pid"><![CDATA[Profile 1]]></string>
+    <string name="_bx_credits_grid_column_title_htr_second_pid"><![CDATA[Profile 2]]></string>
+    <string name="_bx_credits_grid_column_title_htr_amount"><![CDATA[Amount]]></string>
+    <string name="_bx_credits_grid_column_title_htr_order"><![CDATA[Order]]></string>
+    <string name="_bx_credits_grid_column_title_htr_direction"><![CDATA[Direction]]></string>
+    <string name="_bx_credits_grid_column_title_htr_info"><![CDATA[Notice]]></string>
+    <string name="_bx_credits_grid_column_title_htr_date"><![CDATA[Date]]></string>
+    <string name="_bx_credits_grid_action_title_htr_grant"><![CDATA[Grant]]></string>
+    <string name="_bx_credits_grid_action_title_htr_withdraw_confirm"><![CDATA[Confirm withdraw]]></string>
+    <string name="_bx_credits_grid_action_title_htr_withdraw_request"><![CDATA[Request withdraw]]></string>
+    <string name="_bx_credits_grid_popup_title_htr_grant"><![CDATA[Grant]]></string>
+    <string name="_bx_credits_grid_popup_title_htr_withdraw_confirm"><![CDATA[Confirm withdraw]]></string>
+    <string name="_bx_credits_grid_popup_title_htr_withdraw_request"><![CDATA[Request withdraw]]></string>
+
+    <string name="_bx_credits_grid_column_title_ord_profile_id"><![CDATA[Profile]]></string>
+    <string name="_bx_credits_grid_column_title_ord_bundle"><![CDATA[Bundle]]></string>
+    <string name="_bx_credits_grid_column_title_ord_transaction"><![CDATA[Transaction]]></string>
+    <string name="_bx_credits_grid_column_title_ord_type"><![CDATA[Type]]></string>
+    <string name="_bx_credits_grid_column_value_ord_type_single"><![CDATA[Single]]></string>
+    <string name="_bx_credits_grid_column_value_ord_type_recurring"><![CDATA[Recurring]]></string>
+    <string name="_bx_credits_grid_column_title_ord_added"><![CDATA[Added]]></string>
+    <string name="_bx_credits_grid_column_title_ord_expired"><![CDATA[Expired]]></string>
+
+
+    <!-- Email templates -->
+    <string name="_bx_credits_et_txt_name_granted"><![CDATA[Credits granted.]]></string>
+    <string name="_bx_credits_et_txt_subject_granted"><![CDATA[You're granted {amount} credits.]]></string>
+    <string name="_bx_credits_et_txt_body_granted"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p>You're granted <b>{amount}</b> credits.</p>
+<p>{message}</p>
+{email_footer}]]></string>
+    <string name="_bx_credits_et_txt_name_purchased"><![CDATA[Credits purchased.]]></string>
+    <string name="_bx_credits_et_txt_subject_purchased"><![CDATA[You've purchased {bundle_title} credits bundle.]]></string>
+    <string name="_bx_credits_et_txt_body_purchased"><![CDATA[{email_header}
+<p>Hello, {client_name}.</p>
+<p>You've purchased <a href="{bundle_url}">{bundle_title}</a> credits bundle.<br />Your order is <b>{order}</b>.</p>
+<p>{notes}</p>
+{email_footer}]]></string>
+    <string name="_bx_credits_et_txt_name_in"><![CDATA[Credits in.]]></string>
+    <string name="_bx_credits_et_txt_subject_in"><![CDATA[Credits in.]]></string>
+    <string name="_bx_credits_et_txt_body_in"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p>You've received <b>{amount}</b> credits from {second_profile_name}.</p>
+{email_footer}]]></string>
+    <string name="_bx_credits_et_txt_name_out"><![CDATA[Credits out.]]></string>
+    <string name="_bx_credits_et_txt_subject_out"><![CDATA[Credits out.]]></string>
+    <string name="_bx_credits_et_txt_body_out"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p>You've sent <b>{amount}</b> credits to {second_profile_name}.</p>
+{email_footer}]]></string>
+    <string name="_bx_credits_et_txt_name_withdraw_requested"><![CDATA[Withdraw request.]]></string>
+    <string name="_bx_credits_et_txt_subject_withdraw_requested"><![CDATA[Withdraw request.]]></string>
+    <string name="_bx_credits_et_txt_body_withdraw_requested"><![CDATA[{email_header}
+<p><a href="{profile_link}">{profile_name}</a> requested to withdraw <b>{amount}</b> credits from his/her balance.</p>
+<p>{message}</p>
+<p>Current withdraw conversion rate is <b>{rate}</b></p>
+{email_footer}]]></string>
+    <string name="_bx_credits_et_txt_name_withdraw_sent"><![CDATA[Withdraw was sent.]]></string>
+    <string name="_bx_credits_et_txt_subject_withdraw_sent"><![CDATA[Withdraw was sent.]]></string>
+    <string name="_bx_credits_et_txt_body_withdraw_sent"><![CDATA[{email_header}
+<p>Hello, {profile_name}.</p>
+<p>The requested amount of credits (<b>{amount}</b>) was withdrawn to you.</p>
+<p>{message}</p>
+{email_footer}]]></string>
+
+
+    <!-- Other -->
+    <string name="_bx_credits_txt_sample_single"><![CDATA[credit]]></string>
+    <string name="_bx_credits_txt_sample_single_with_article"><![CDATA[a credit]]></string>
+    <string name="_bx_credits_txt_for_free"><![CDATA[for Free]]></string>
+    <string name="_bx_credits_txt_n_plus_m_for_free"><![CDATA[{0} (+{1} for free)]]></string>
+    <string name="_bx_credits_txt_purchase"><![CDATA[Purchase]]></string>
+    <string name="_bx_credits_txt_never"><![CDATA[never]]></string>
+    <string name="_bx_credits_txt_direction_out"><![CDATA[send to]]></string>
+    <string name="_bx_credits_txt_direction_in"><![CDATA[get from]]></string>
+    <string name="_bx_credits_txt_purchased_note"><![CDATA[Contact <a href="{0}">{1}</a> if you have any questions or need support.]]></string>
+    <string name="_bx_credits_txt_history_info_grant"><![CDATA[Grant]]></string>
+    <string name="_bx_credits_txt_history_info_purchase"><![CDATA[Bundle purchase]]></string>
+    <string name="_bx_credits_txt_history_info_checkout"><![CDATA[Profile to profile checkout]]></string>
+    <string name="_bx_credits_txt_history_info_withdraw"><![CDATA[Withdraw by request]]></string>
+    <string name="_bx_credits_txt_history_info_service"><![CDATA[Artificially initiated transfer]]></string>
+
+    <string name="_bx_credits_msg_empty_author"><![CDATA['Site administrator' is empty. You need to mark some profile as 'Site administrator' in Settings of currently active Payment module.]]></string>
+    <string name="_bx_credits_msg_action_performed"><![CDATA[The action was performed successfully.]]></string>
+    <string name="_bx_credits_err_profile_not_found"><![CDATA[Profile wasn't found.]]></string>
+    <string name="_bx_credits_err_login_required"><![CDATA[Login required.]]></string>
+    <string name="_bx_credits_err_low_balance"><![CDATA[Low balance.]]></string>
+    <string name="_bx_credits_err_cannot_update_balance"><![CDATA[Cannot update balance.]]></string>
+    <string name="_bx_credits_err_cannot_send"><![CDATA[Cannot send message. Please report.]]></string>
+    <string name="_bx_credits_err_cannot_perform_action"><![CDATA[Cannot perform the action. Please report.]]></string>
+
+
+    <!-- Checkout process -->
+    <string name="_bx_credits_txt_checkout"><![CDATA[Checkout]]></string>
+    <string name="_bx_credits_txt_checkout_to"><![CDATA[Pay to {0} for:]]></string>
+    <string name="_bx_credits_txt_checkout_price_money"><![CDATA[Price:]]></string>
+    <string name="_bx_credits_txt_checkout_price_credits"><![CDATA[In Credits:]]></string>
+    <string name="_bx_credits_txt_checkout_rate"><![CDATA[Rate:]]></string>
+    <string name="_bx_credits_txt_checkout_qt"><![CDATA[qt.]]></string>
+
+    <string name="_bx_credits_err_unknown_buyer"><![CDATA[Login required.]]></string>
+    <string name="_bx_credits_err_unknown_seller"><![CDATA[Unknown seller.]]></string>
+    <string name="_bx_credits_err_incorrect_data"><![CDATA[Incorrect input data. Please report.]]></string>
+</resources>

+ 33 - 0
modules/boonex/credits/install/sql/disable.sql

@@ -0,0 +1,33 @@
+
+-- SETTINGS
+SET @iTypeId = (SELECT `ID` FROM `sys_options_types` WHERE `name` = 'bx_credits' LIMIT 1);
+SET @iCategId = (SELECT `ID` FROM `sys_options_categories` WHERE `type_id` = @iTypeId LIMIT 1);
+DELETE FROM `sys_options` WHERE `category_id` = @iCategId;
+DELETE FROM `sys_options_categories` WHERE `type_id` = @iTypeId;
+DELETE FROM `sys_options_types` WHERE `id` = @iTypeId;
+
+-- PAGES
+DELETE FROM `sys_objects_page` WHERE `module` = 'bx_credits';
+DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_credits' OR `object` IN ('bx_credits_home', 'bx_credits_checkout', 'bx_credits_orders_common', 'bx_credits_orders_administration', 'bx_credits_history_common', 'bx_credits_history_administration');
+
+-- MENU
+DELETE FROM `sys_objects_menu` WHERE `module` = 'bx_credits';
+DELETE FROM `sys_menu_sets` WHERE `module` = 'bx_credits';
+DELETE FROM `sys_menu_items` WHERE `module` = 'bx_credits' OR `set_name` IN ('bx_credits_submenu', 'bx_credits_orders_submenu');
+
+-- ACL
+DELETE `sys_acl_actions`, `sys_acl_matrix` FROM `sys_acl_actions`, `sys_acl_matrix` WHERE `sys_acl_matrix`.`IDAction` = `sys_acl_actions`.`ID` AND `sys_acl_actions`.`Module` = 'bx_credits';
+DELETE FROM `sys_acl_actions` WHERE `Module` = 'bx_credits';
+
+-- GRIDS
+DELETE FROM `sys_objects_grid` WHERE `object` LIKE 'bx_credits_%';
+DELETE FROM `sys_grid_fields` WHERE `object` LIKE 'bx_credits_%';
+DELETE FROM `sys_grid_actions` WHERE `object` LIKE 'bx_credits_%';
+
+-- ALERTS
+SET @iHandler := (SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = 'bx_credits' LIMIT 1);
+DELETE FROM `sys_alerts` WHERE `handler_id` = @iHandler;
+DELETE FROM `sys_alerts_handlers` WHERE `id` = @iHandler;
+
+-- EMAIL TEMPLATES
+DELETE FROM `sys_email_templates` WHERE `Module` = 'bx_credits';

+ 229 - 0
modules/boonex/credits/install/sql/enable.sql

@@ -0,0 +1,229 @@
+
+-- SETTINGS
+SET @iTypeOrder = (SELECT MAX(`order`) FROM `sys_options_types` WHERE `group` = 'modules');
+INSERT INTO `sys_options_types`(`group`, `name`, `caption`, `icon`, `order`) VALUES 
+('modules', 'bx_credits', '_bx_credits', 'bx_credits@modules/boonex/credits/|std-icon.svg', IF(ISNULL(@iTypeOrder), 1, @iTypeOrder + 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order`)
+VALUES (@iTypeId, 'bx_credits', '_bx_credits', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_credits_enable_withdraw', '', @iCategId, '_bx_credits_option_enable_withdraw', 'checkbox', '', '', '', 1),
+('bx_credits_withdraw_email', '', @iCategId, '_bx_credits_option_withdraw_email', 'digit', '', '', '', 2),
+
+('bx_credits_precision', '2', @iCategId, '_bx_credits_option_precision', 'digit', '', '', '', 10),
+('bx_credits_conversion_rate_use', '1.0', @iCategId, '_bx_credits_option_conversion_rate_use', 'digit', '', '', '', 11),
+('bx_credits_conversion_rate_withdraw', '1.0', @iCategId, '_bx_credits_option_conversion_rate_withdraw', 'digit', '', '', '', 12),
+
+('bx_credits_code', '&#36;', @iCategId, '_bx_credits_option_code', 'digit', '', '', '', 20),
+('bx_credits_icon', '', @iCategId, '_bx_credits_option_icon', 'digit', '', '', '', 21);
+
+
+-- PAGE: module home
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_home', 'credits-home', '_bx_credits_page_title_sys_home', '_bx_credits_page_title_home', 'bx_credits', 5, 2147483647, 1, 'page.php?i=credits-home', '', '', '', 0, 1, 0, 'BxCreditsPageBrowse', 'modules/boonex/credits/classes/BxCreditsPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_credits_home', 1, 'bx_credits', '_bx_credits_page_block_title_sys_bundles', '_bx_credits_page_block_title_bundles', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_credits";s:6:"method";s:17:"get_block_bundles";}', 0, 1, 1, 0);
+
+-- PAGE: checkout
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `type_id`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_checkout', 'credits-checkout', '_bx_credits_page_title_sys_checkout', '_bx_credits_page_title_checkout', 'bx_credits', 2, 5, 2147483647, 1, 'page.php?i=credits-checkout', '', '', '', 0, 1, 0, 'BxCreditsPageBrowse', 'modules/boonex/credits/classes/BxCreditsPageBrowse.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_credits_checkout', 1, 'bx_credits', '_bx_credits_page_block_title_sys_checkout', '_bx_credits_page_block_title_checkout', 0, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_credits";s:6:"method";s:18:"get_block_checkout";}', 0, 0, 1, 0);
+
+-- PAGE: profile's orders
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_orders_common', '_bx_credits_page_title_sys_orders_common', '_bx_credits_page_title_orders_common', 'bx_credits', 5, 2147483647, 1, 'credits-orders-common', '', '', '', '', 0, 1, 0, 'BxCreditsPageOrders', 'modules/boonex/credits/classes/BxCreditsPageOrders.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_credits_orders_common', 1, 'bx_credits', '_bx_credits_page_block_title_sys_orders_common_note', '_bx_credits_page_block_title_orders_common_note', 13, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_credits";s:6:"method";s:21:"get_block_orders_note";}', 0, 0, 1, 0),
+('bx_credits_orders_common', 1, 'bx_credits', '_bx_credits_page_block_title_sys_orders_common', '_bx_credits_page_block_title_orders_common', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_credits";s:6:"method";s:16:"get_block_orders";}', 0, 0, 1, 1);
+
+-- PAGE: orders administration
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_orders_administration', '_bx_credits_page_title_sys_orders_administration', '_bx_credits_page_title_orders_administration', 'bx_credits', 5, 192, 1, 'credits-orders-administration', '', '', '', '', 0, 1, 0, 'BxCreditsPageOrders', 'modules/boonex/credits/classes/BxCreditsPageOrders.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_credits_orders_administration', 1, 'bx_credits', '_bx_credits_page_block_title_sys_orders_administration', '_bx_credits_page_block_title_orders_administration', 11, 192, 'service', 'a:3:{s:6:"module";s:10:"bx_credits";s:6:"method";s:16:"get_block_orders";s:6:"params";a:1:{i:0;s:14:"administration";}}', 0, 0, 1, 0);
+
+-- PAGE: module manage own
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_history_common', '_bx_credits_page_title_sys_history_common', '_bx_credits_page_title_history_common', 'bx_credits', 5, 2147483647, 1, 'credits-history-common', 'page.php?i=credits-history-common', '', '', '', 0, 1, 0, 'BxCreditsPageHistory', 'modules/boonex/credits/classes/BxCreditsPageHistory.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_credits_history_common', 1, 'bx_credits', '_bx_credits_page_block_title_sys_history_common', '_bx_credits_page_block_title_history_common', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:10:"bx_credits";s:6:"method";s:17:"get_block_history";}}', 0, 1, 0);
+
+-- PAGE: module manage all
+INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_history_administration', '_bx_credits_page_title_sys_history_administration', '_bx_credits_page_title_history_administration', 'bx_credits', 5, 192, 1, 'credits-history-administration', 'page.php?i=credits-history-administration', '', '', '', 0, 1, 0, 'BxCreditsPageHistory', 'modules/boonex/credits/classes/BxCreditsPageHistory.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_credits_history_administration', 1, 'bx_credits', '_bx_credits_page_block_title_sys_history_administration', '_bx_credits_page_block_title_history_administration', 11, 192, 'service', 'a:3:{s:6:"module";s:10:"bx_credits";s:6:"method";s:17:"get_block_history";s:6:"params";a:1:{i:0;s:14:"administration";}}', 0, 1, 0);
+
+
+-- MENU: add to site menu
+SET @iSiteMenuOrder = (SELECT `order` FROM `sys_menu_items` WHERE `set_name` = 'sys_site' AND `active` = 1 ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_site', 'bx_credits', 'credits-home', '_bx_credits_menu_item_title_system_credits', '_bx_credits_menu_item_title_credits', 'page.php?i=credits-home', '', '', 'money-bill-alt col-green3', 'bx_credits_submenu', 2147483647, 1, 1, IFNULL(@iSiteMenuOrder, 0) + 1);
+
+-- MENU: add to homepage menu
+SET @iHomepageMenuOrder = (SELECT `order` FROM `sys_menu_items` WHERE `set_name` = 'sys_homepage' AND `active` = 1 ORDER BY `order` DESC LIMIT 1);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_homepage', 'bx_credits', 'credits-home', '_bx_credits_menu_item_title_system_credits', '_bx_credits_menu_item_title_credits', 'page.php?i=credits-home', '', '', 'money-bill-alt col-green3', 'bx_credits_submenu', 2147483647, 1, 1, IFNULL(@iHomepageMenuOrder, 0) + 1);
+
+-- MENU: member toolbar
+SET @iMenuToolbarMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name`='sys_toolbar_member' LIMIT 1);
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_toolbar_member', 'bx_credits', 'credits-history', '_bx_credits_menu_item_title_system_history', '_bx_credits_menu_item_title_history', 'page.php?i=credits-history-common', '', '', '', 'a:2:{s:6:"module";s:10:"bx_credits";s:6:"method";s:26:"get_menu_item_addon_amount";}', '', 0, 2147483646, 1, 1, 0);
+
+-- MENU: account dashboard
+SET @iAccountDashboardMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name`='sys_account_dashboard' LIMIT 1);
+INSERT INTO `sys_menu_items` (`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `editable`, `order`) VALUES
+('sys_account_dashboard', 'bx_credits', 'credits-manage', '_bx_credits_menu_item_title_system_manage', '_bx_credits_menu_item_title_manage', 'page.php?i=credits-orders-common', '', '', 'money-bill-alt col-green3', '', '', 2147483646, 1, 0, 1, @iAccountDashboardMenuOrder + 1);
+
+-- MENU: module sub-menu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_submenu', '_bx_credits_menu_title_submenu', 'bx_credits_submenu', 'bx_credits', 8, 0, 1, '', '');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_credits_submenu', 'bx_credits', '_bx_credits_menu_set_title_submenu', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('bx_credits_submenu', 'bx_credits', 'credits-home', '_bx_credits_menu_item_title_system_home', '_bx_credits_menu_item_title_home', 'page.php?i=credits-home', '', '', '', '', 2147483647, 0, 1, 1);
+
+-- MENU: manage submenu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_manage_submenu', '_bx_credits_menu_title_manage_submenu', 'bx_credits_manage_submenu', 'bx_credits', 6, 0, 1, '', '');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_credits_manage_submenu', 'bx_credits', '_bx_credits_menu_set_title_manage_submenu', 0);
+
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `editable`, `order`) VALUES 
+('bx_credits_manage_submenu', 'bx_credits', 'credits-orders-administration', '_bx_credits_menu_item_title_system_orders_administration', '_bx_credits_menu_item_title_orders_administration', 'page.php?i=credits-orders-administration', '', '_self', '', '', '', 192, 1, 0, 1, 1),
+('bx_credits_manage_submenu', 'bx_credits', 'credits-orders-common', '_bx_credits_menu_item_title_system_orders_common', '_bx_credits_menu_item_title_orders_common', 'page.php?i=credits-orders-common', '', '_self', '', '', '', 2147483646, 1, 0, 1, 2),
+('bx_credits_manage_submenu', 'bx_credits', 'credits-history-administration', '_bx_credits_menu_item_title_system_history_administration', '_bx_credits_menu_item_title_history_administration', 'page.php?i=credits-history-administration', '', '_self', '', '', '', 192, 1, 0, 1, 3),
+('bx_credits_manage_submenu', 'bx_credits', 'credits-history-common', '_bx_credits_menu_item_title_system_history_common', '_bx_credits_menu_item_title_history_common', 'page.php?i=credits-history-common', '', '_self', '', '', '', 2147483646, 1, 0, 1, 4);
+
+-- MENU: dashboard manage tools
+SET @iManageMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name`='sys_account_dashboard_manage_tools' LIMIT 1);
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
+('sys_account_dashboard_manage_tools', 'bx_credits', 'credits-history-administration', '_bx_credits_menu_item_title_system_admt_credits', '_bx_credits_menu_item_title_admt_credits', 'page.php?i=credits-history-administration', '', '_self', 'money-bill-alt col-green3', 'a:2:{s:6:"module";s:10:"bx_credits";s:6:"method";s:27:"get_menu_addon_manage_tools";}', '', 192, 1, 0, @iManageMenuOrder + 1);
+
+
+-- ACL
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_credits', 'view', NULL, '_bx_credits_acl_action_view_entry', '', 1, 0);
+SET @iIdActionView = LAST_INSERT_ID();
+
+SET @iUnauthenticated = 1;
+SET @iAccount = 2;
+SET @iStandard = 3;
+SET @iUnconfirmed = 4;
+SET @iPending = 5;
+SET @iSuspended = 6;
+SET @iModerator = 7;
+SET @iAdministrator = 8;
+SET @iPremium = 9;
+
+INSERT INTO `sys_acl_matrix` (`IDLevel`, `IDAction`) VALUES
+
+-- entry view
+(@iUnauthenticated, @iIdActionView),
+(@iAccount, @iIdActionView),
+(@iStandard, @iIdActionView),
+(@iUnconfirmed, @iIdActionView),
+(@iPending, @iIdActionView),
+(@iModerator, @iIdActionView),
+(@iAdministrator, @iIdActionView),
+(@iPremium, @iIdActionView);
+
+
+-- GRIDS: bundles
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_credits_bundles', 'Sql', 'SELECT * FROM `bx_credits_bundles` WHERE 1 ', 'bx_credits_bundles', 'id', 'order', 'active', '', 100, NULL, 'start', '', 'period,period_unit,price', '', 'like', '', '', 192, 'BxCreditsGridBundles', 'modules/boonex/credits/classes/BxCreditsGridBundles.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_credits_bundles', 'checkbox', '_sys_select', '1%', 0, '', '', 1),
+('bx_credits_bundles', 'order', '', '1%', 0, '', '', 2),
+('bx_credits_bundles', 'name', '_bx_credits_grid_column_title_name', '23%', 0, 32, '', 3),
+('bx_credits_bundles', 'title', '_bx_credits_grid_column_title_title', '30%', 1, 16, '', 4),
+('bx_credits_bundles', 'amount', '_bx_credits_grid_column_title_amount', '10%', 0, 16, '', 5),
+('bx_credits_bundles', 'bonus', '_bx_credits_grid_column_title_bonus', '5%', 0, 16, '', 6),
+('bx_credits_bundles', 'price', '_bx_credits_grid_column_title_price', '10%', 0, 16, '', 7),
+('bx_credits_bundles', 'actions', '', '20%', 0, '', '', 8);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_credits_bundles', 'independent', 'add', '_bx_credits_grid_action_title_add', '', 0, 0, 1),
+('bx_credits_bundles', 'single', 'edit', '_bx_credits_grid_action_title_edit', 'pencil-alt', 1, 0, 1),
+('bx_credits_bundles', 'single', 'delete', '_bx_credits_grid_action_title_delete', 'remove', 1, 1, 2),
+('bx_credits_bundles', 'bulk', 'delete', '_bx_credits_grid_action_title_delete', '', 0, 1, 1);
+
+-- GRIDS: orders
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_credits_orders_administration', 'Sql', 'SELECT `to`.`id` AS `id`, `to`.`profile_id` AS `profile_id`, `to`.`bundle_id` AS `bundle_id`, `tb`.`title` AS `bundle`, `to`.`order` AS `transaction`, `to`.`license` AS `license`, `to`.`type` AS `type`, `to`.`added` AS `added`, `to`.`expired` AS `expired` FROM `bx_credits_orders` AS `to` LEFT JOIN `bx_credits_bundles` AS `tb` ON `to`.`bundle_id`=`tb`.`id` WHERE 1 ', 'bx_credits_orders', 'id', 'added', '', '', 20, NULL, 'start', '', 'tb`.`title,to`.`order,to`.`license,to`.`type', '', 'like', '', '', 192, 'BxCreditsGridOrdersAdministration', 'modules/boonex/credits/classes/BxCreditsGridOrdersAdministration.php'),
+('bx_credits_orders_common', 'Sql', 'SELECT `to`.`id` AS `id`, `to`.`profile_id` AS `profile_id`, `to`.`bundle_id` AS `bundle_id`, `tb`.`title` AS `bundle`, `to`.`order` AS `transaction`, `to`.`license` AS `license`, `to`.`type` AS `type`, `to`.`added` AS `added`, `to`.`expired` AS `expired` FROM `bx_credits_orders` AS `to` LEFT JOIN `bx_credits_bundles` AS `tb` ON `to`.`bundle_id`=`tb`.`id` WHERE 1 ', 'bx_credits_orders', 'id', 'added', '', '', 20, NULL, 'start', '', 'tb`.`title,to`.`order,to`.`license,to`.`type', '', 'like', '', '', 2147483647, 'BxCreditsGridOrdersCommon', 'modules/boonex/credits/classes/BxCreditsGridOrdersCommon.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_credits_orders_administration', 'profile_id', '_bx_credits_grid_column_title_ord_profile_id', '10%', 0, '28', '', 1),
+('bx_credits_orders_administration', 'bundle', '_bx_credits_grid_column_title_ord_bundle', '20%', 1, '28', '', 2),
+('bx_credits_orders_administration', 'transaction', '_bx_credits_grid_column_title_ord_transaction', '10%', 0, '32', '', 3),
+('bx_credits_orders_administration', 'type', '_bx_credits_grid_column_title_ord_type', '5%', 1, '12', '', 4),
+('bx_credits_orders_administration', 'added', '_bx_credits_grid_column_title_ord_added', '10%', 1, '25', '', 5),
+('bx_credits_orders_administration', 'expired', '_bx_credits_grid_column_title_ord_expired', '10%', 1, '25', '', 6),
+
+('bx_credits_orders_common', 'bundle', '_bx_credits_grid_column_title_ord_bundle', '20%', 1, '28', '', 1),
+('bx_credits_orders_common', 'transaction', '_bx_credits_grid_column_title_ord_transaction', '10%', 0, '32', '', 2),
+('bx_credits_orders_common', 'type', '_bx_credits_grid_column_title_ord_type', '10%', 1, '12', '', 3),
+('bx_credits_orders_common', 'added', '_bx_credits_grid_column_title_ord_added', '10%', 1, '25', '', 4),
+('bx_credits_orders_common', 'expired', '_bx_credits_grid_column_title_ord_expired', '10%', 1, '25', '', 5);
+
+-- GRIDS: history
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_credits_history_administration', 'Sql', 'SELECT * FROM `bx_credits_history` WHERE 1 ', 'bx_credits_history', 'id', 'date', '', '', 20, NULL, 'start', '', 'direction,order,info', '', 'like', '', '', 192, 'BxCreditsGridHistoryAdministration', 'modules/boonex/credits/classes/BxCreditsGridHistoryAdministration.php'),
+('bx_credits_history_common', 'Sql', 'SELECT * FROM `bx_credits_history` WHERE 1 ', 'bx_credits_history', 'id', 'date', '', '', 20, NULL, 'start', '', 'direction,order,info', '', 'like', '', '', 2147483647, 'BxCreditsGridHistoryCommon', 'modules/boonex/credits/classes/BxCreditsGridHistoryCommon.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_credits_history_administration', 'first_pid', '_bx_credits_grid_column_title_htr_first_pid', '15%', 0, '', '', 1),
+('bx_credits_history_administration', 'direction', '_bx_credits_grid_column_title_htr_direction', '10%', 0, '', '', 2),
+('bx_credits_history_administration', 'second_pid', '_bx_credits_grid_column_title_htr_second_pid', '15%', 0, '', '', 3),
+('bx_credits_history_administration', 'amount', '_bx_credits_grid_column_title_htr_amount', '10%', 1, '', '', 4),
+('bx_credits_history_administration', 'order', '_bx_credits_grid_column_title_htr_order', '15%', 0, '16', '', 5),
+('bx_credits_history_administration', 'info', '_bx_credits_grid_column_title_htr_info', '20%', 0, '24', '', 6),
+('bx_credits_history_administration', 'date', '_bx_credits_grid_column_title_htr_date', '15%', 0, '', '', 7),
+
+('bx_credits_history_common', 'direction', '_bx_credits_grid_column_title_htr_direction', '10%', 0, '', '', 1),
+('bx_credits_history_common', 'second_pid', '_bx_credits_grid_column_title_htr_pid', '25%', 0, '', '', 2),
+('bx_credits_history_common', 'amount', '_bx_credits_grid_column_title_htr_amount', '15%', 1, '', '', 3),
+('bx_credits_history_common', 'order', '_bx_credits_grid_column_title_htr_order', '15%', 0, '16', '', 4),
+('bx_credits_history_common', 'info', '_bx_credits_grid_column_title_htr_info', '20%', 0, '32', '', 5),
+('bx_credits_history_common', 'date', '_bx_credits_grid_column_title_htr_date', '15%', 0, '', '', 6);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_credits_history_administration', 'independent', 'grant', '_bx_credits_grid_action_title_htr_grant', '', 0, 0, 1),
+('bx_credits_history_administration', 'independent', 'withdraw_confirm', '_bx_credits_grid_action_title_htr_withdraw_confirm', '', 0, 0, 1),
+
+('bx_credits_history_common', 'independent', 'withdraw_request', '_bx_credits_grid_action_title_htr_withdraw_request', '', 0, 0, 1);
+
+
+-- ALERTS
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+('bx_credits', 'BxCreditsAlertsResponse', 'modules/boonex/credits/classes/BxCreditsAlertsResponse.php', '');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('system', 'save_setting', @iHandler),
+('profile', 'delete', @iHandler);
+
+
+-- EMAIL TEMPLATES
+INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `Body`) VALUES 
+('bx_credits', '_bx_credits_et_txt_name_granted', 'bx_credits_granted', '_bx_credits_et_txt_subject_granted', '_bx_credits_et_txt_body_granted'),
+('bx_credits', '_bx_credits_et_txt_name_purchased', 'bx_credits_purchased', '_bx_credits_et_txt_subject_purchased', '_bx_credits_et_txt_body_purchased'),
+('bx_credits', '_bx_credits_et_txt_name_in', 'bx_credits_in', '_bx_credits_et_txt_subject_in', '_bx_credits_et_txt_body_in'),
+('bx_credits', '_bx_credits_et_txt_name_out', 'bx_credits_out', '_bx_credits_et_txt_subject_out', '_bx_credits_et_txt_body_out'),
+('bx_credits', '_bx_credits_et_txt_name_withdraw_requested', 'bx_credits_withdraw_requested', '_bx_credits_et_txt_subject_withdraw_requested', '_bx_credits_et_txt_body_withdraw_requested'),
+('bx_credits', '_bx_credits_et_txt_name_withdraw_sent', 'bx_credits_withdraw_sent', '_bx_credits_et_txt_subject_withdraw_sent', '_bx_credits_et_txt_body_withdraw_sent');

+ 161 - 0
modules/boonex/credits/install/sql/install.sql

@@ -0,0 +1,161 @@
+-- TABLE: bundles
+CREATE TABLE IF NOT EXISTS `bx_credits_bundles` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `added` int(11) NOT NULL,
+  `name` varchar(255) NOT NULL,
+  `title` varchar(255) NOT NULL,
+  `amount` int(11) NOT NULL DEFAULT '0',
+  `bonus` int(11) NOT NULL DEFAULT '0',
+  `price` float NOT NULL DEFAULT '0',
+  `active` tinyint(4) NOT NULL DEFAULT '0',
+  `order` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+);
+
+-- TABLE: orders
+CREATE TABLE IF NOT EXISTS `bx_credits_orders` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `profile_id` int(11) unsigned NOT NULL default '0',
+  `bundle_id` int(11) unsigned NOT NULL default '0',
+  `count` int(11) unsigned NOT NULL default '0',
+  `order` varchar(32) NOT NULL default '',
+  `license` varchar(32) NOT NULL default '',
+  `type` varchar(16) NOT NULL default '',
+  `added` int(11) unsigned NOT NULL default '0',
+  `expired` int(11) unsigned NOT NULL default '0',
+  `new` tinyint(1) NOT NULL default '1',
+  PRIMARY KEY (`id`),
+  KEY `order_id` (`bundle_id`, `profile_id`),
+  KEY `license` (`license`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_credits_orders_deleted` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `profile_id` int(11) unsigned NOT NULL default '0',
+  `bundle_id` int(11) unsigned NOT NULL default '0',
+  `count` int(11) unsigned NOT NULL default '0',
+  `order` varchar(32) NOT NULL default '',
+  `license` varchar(32) NOT NULL default '',
+  `type` varchar(16) NOT NULL default '',
+  `added` int(11) unsigned NOT NULL default '0',
+  `expired` int(11) unsigned NOT NULL default '0',
+  `new` tinyint(1) NOT NULL default '1',
+  `reason` varchar(16) NOT NULL default '',
+  `deleted` int(11) unsigned NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `order_id` (`bundle_id`, `profile_id`),
+  KEY `license` (`license`)
+);
+
+-- TABLE: profiles
+CREATE TABLE IF NOT EXISTS `bx_credits_profiles` (
+  `id` int(11) NOT NULL DEFAULT '0',
+  `balance` float NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+);
+
+-- TABLE: history
+CREATE TABLE IF NOT EXISTS `bx_credits_history` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `first_pid` int(11) NOT NULL DEFAULT '0',
+  `second_pid` int(11) NOT NULL DEFAULT '0',
+  `amount` float NOT NULL DEFAULT '0',
+  `direction` enum('in', 'out') NOT NULL DEFAULT 'in',
+  `order` varchar(32) NOT NULL default '',
+  `data` text NOT NULL default '',
+  `info` varchar(255) NOT NULL DEFAULT '',
+  `date` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+);
+
+
+-- FORMS: credit
+INSERT INTO `sys_objects_form`(`object`, `module`, `title`, `action`, `form_attrs`, `table`, `key`, `uri`, `uri_title`, `submit_name`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_credit', 'bx_credits', '_bx_credits_form_credit', '', 'a:1:{s:7:"enctype";s:19:"multipart/form-data";}', '', '', '', '', 'a:1:{i:0;s:9:"do_submit";}', '', 0, 1, 'BxCreditsFormWithdraw', 'modules/boonex/credits/classes/BxCreditsFormWithdraw.php');
+
+INSERT INTO `sys_form_displays`(`object`, `display_name`, `module`, `view_mode`, `title`) VALUES 
+('bx_credits_credit', 'bx_credits_credit_grant', 'bx_credits', 0, '_bx_credits_form_credit_display_grant'),
+('bx_credits_credit', 'bx_credits_credit_withdraw_request', 'bx_credits', 0, '_bx_credits_form_credit_display_withdraw_request'),
+('bx_credits_credit', 'bx_credits_credit_withdraw_confirm', 'bx_credits', 0, '_bx_credits_form_credit_display_withdraw_confirm');
+
+INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES 
+('bx_credits_credit', 'bx_credits', 'balance', '', '', 0, 'text', '_bx_credits_form_credit_input_sys_balance', '_bx_credits_form_credit_input_balance', '', 0, 0, 0, 'a:1:{s:8:"disabled";s:8:"disabled";}', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_credits_credit', 'bx_credits', 'rate', '', '', 0, 'text', '_bx_credits_form_credit_input_sys_rate', '_bx_credits_form_credit_input_rate', '', 0, 0, 0, 'a:1:{s:8:"disabled";s:8:"disabled";}', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_credits_credit', 'bx_credits', 'result', '', '', 0, 'text', '_bx_credits_form_credit_input_sys_result', '_bx_credits_form_credit_input_result', '', 0, 0, 0, 'a:1:{s:8:"disabled";s:8:"disabled";}', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_credits_credit', 'bx_credits', 'amount', '', '', 0, 'text', '_bx_credits_form_credit_input_sys_amount', '_bx_credits_form_credit_input_amount', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_credits_form_credit_input_amount_err', 'Xss', '', 1, 0),
+('bx_credits_credit', 'bx_credits', 'message', '', '', 0, 'textarea', '_bx_credits_form_credit_input_sys_message', '_bx_credits_form_credit_input_message', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_credits_credit', 'bx_credits', 'profile', '', '', 0, 'custom', '_bx_credits_form_credit_input_sys_profile', '_bx_credits_form_credit_input_profile', '', 1, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_credits_credit', 'bx_credits', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_credits_credit', 'bx_credits', 'do_submit', '_bx_credits_form_credit_input_do_submit', '', 0, 'submit', '_bx_credits_form_credit_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_credits_credit', 'bx_credits', 'do_cancel', '_bx_credits_form_credit_input_do_cancel', '', 0, 'button', '_bx_credits_form_credit_input_sys_do_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES 
+('bx_credits_credit_grant', 'profile', 2147483647, 1, 1),
+('bx_credits_credit_grant', 'amount', 2147483647, 1, 2),
+('bx_credits_credit_grant', 'message', 2147483647, 1, 3),
+('bx_credits_credit_grant', 'controls', 2147483647, 1, 4),
+('bx_credits_credit_grant', 'do_submit', 2147483647, 1, 5),
+('bx_credits_credit_grant', 'do_cancel', 2147483647, 1, 6),
+
+('bx_credits_credit_withdraw_request', 'balance', 2147483647, 1, 1),
+('bx_credits_credit_withdraw_request', 'rate', 2147483647, 1, 2),
+('bx_credits_credit_withdraw_request', 'amount', 2147483647, 1, 3),
+('bx_credits_credit_withdraw_request', 'result', 2147483647, 1, 4),
+('bx_credits_credit_withdraw_request', 'message', 2147483647, 1, 5),
+('bx_credits_credit_withdraw_request', 'controls', 2147483647, 1, 6),
+('bx_credits_credit_withdraw_request', 'do_submit', 2147483647, 1, 7),
+('bx_credits_credit_withdraw_request', 'do_cancel', 2147483647, 1, 8),
+
+('bx_credits_credit_withdraw_confirm', 'profile', 2147483647, 1, 1),
+('bx_credits_credit_withdraw_confirm', 'amount', 2147483647, 1, 2),
+('bx_credits_credit_withdraw_confirm', 'message', 2147483647, 1, 3),
+('bx_credits_credit_withdraw_confirm', 'controls', 2147483647, 1, 4),
+('bx_credits_credit_withdraw_confirm', 'do_submit', 2147483647, 1, 5),
+('bx_credits_credit_withdraw_confirm', 'do_cancel', 2147483647, 1, 6);
+
+-- FORMS: bundle
+INSERT INTO `sys_objects_form`(`object`, `module`, `title`, `action`, `form_attrs`, `table`, `key`, `uri`, `uri_title`, `submit_name`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_credits_bundle', 'bx_credits', '_bx_credits_form_bundle', '', 'a:1:{s:7:"enctype";s:19:"multipart/form-data";}', 'bx_credits_bundles', 'id', '', '', 'a:1:{i:0;s:9:"do_submit";}', '', 0, 1, 'BxCreditsFormBundle', 'modules/boonex/credits/classes/BxCreditsFormBundle.php');
+
+INSERT INTO `sys_form_displays`(`object`, `display_name`, `module`, `view_mode`, `title`) VALUES 
+('bx_credits_bundle', 'bx_credits_bundle_add', 'bx_credits', 0, '_bx_credits_form_bundle_display_add'),
+('bx_credits_bundle', 'bx_credits_bundle_edit', 'bx_credits', 0, '_bx_credits_form_bundle_display_edit');
+
+INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES 
+('bx_credits_bundle', 'bx_credits', 'title', '', '', 0, 'text_translatable', '_bx_credits_form_bundle_input_sys_title', '_bx_credits_form_bundle_input_title', '', 1, 0, 0, '', '', '', 'AvailTranslatable', 'a:1:{i:0;s:5:"title";}', '_bx_credits_form_bundle_input_title_err', 'Xss', '', 1, 0),
+('bx_credits_bundle', 'bx_credits', 'amount', '', '', 0, 'text', '_bx_credits_form_bundle_input_sys_amount', '_bx_credits_form_bundle_input_amount', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_credits_form_bundle_input_amount_err', 'Xss', '', 1, 0),
+('bx_credits_bundle', 'bx_credits', 'bonus', '', '', 0, 'text', '_bx_credits_form_bundle_input_sys_bonus', '_bx_credits_form_bundle_input_bonus', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_credits_bundle', 'bx_credits', 'price', '', '', 0, 'text', '_bx_credits_form_bundle_input_sys_price', '_bx_credits_form_bundle_input_price', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_credits_form_bundle_input_price_err', 'Xss', '', 1, 0),
+('bx_credits_bundle', 'bx_credits', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_credits_bundle', 'bx_credits', 'do_submit', '_bx_credits_form_bundle_input_do_submit', '', 0, 'submit', '_bx_credits_form_bundle_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_credits_bundle', 'bx_credits', 'do_cancel', '_bx_credits_form_bundle_input_do_cancel', '', 0, 'button', '_bx_credits_form_bundle_input_sys_do_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES 
+('bx_credits_bundle_add', 'title', 2147483647, 1, 1),
+('bx_credits_bundle_add', 'amount', 2147483647, 1, 2),
+('bx_credits_bundle_add', 'bonus', 2147483647, 1, 3),
+('bx_credits_bundle_add', 'price', 2147483647, 1, 4),
+('bx_credits_bundle_add', 'controls', 2147483647, 1, 5),
+('bx_credits_bundle_add', 'do_submit', 2147483647, 1, 6),
+('bx_credits_bundle_add', 'do_cancel', 2147483647, 1, 7),
+
+('bx_credits_bundle_edit', 'title', 2147483647, 1, 1),
+('bx_credits_bundle_edit', 'amount', 2147483647, 1, 2),
+('bx_credits_bundle_edit', 'bonus', 2147483647, 1, 3),
+('bx_credits_bundle_edit', 'price', 2147483647, 1, 4),
+('bx_credits_bundle_edit', 'controls', 2147483647, 1, 5),
+('bx_credits_bundle_edit', 'do_submit', 2147483647, 1, 6),
+('bx_credits_bundle_edit', 'do_cancel', 2147483647, 1, 7);
+
+
+-- STUDIO: page & widget
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, 'bx_credits', '_bx_credits', '_bx_credits', 'bx_credits@modules/boonex/credits/|std-icon.svg');
+SET @iPageId = LAST_INSERT_ID();
+
+SET @iParentPageId = (SELECT `id` FROM `sys_std_pages` WHERE `name` = 'home');
+SET @iParentPageOrder = (SELECT MAX(`order`) FROM `sys_std_pages_widgets` WHERE `page_id` = @iParentPageId);
+INSERT INTO `sys_std_widgets` (`page_id`, `module`, `url`, `click`, `icon`, `caption`, `cnt_notices`, `cnt_actions`) VALUES
+(@iPageId, 'bx_credits', '{url_studio}module.php?name=bx_credits', '', 'bx_credits@modules/boonex/credits/|std-icon.svg', '_bx_credits', '', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_actions";s:6:"params";a:0:{}s:5:"class";s:18:"TemplStudioModules";}');
+INSERT INTO `sys_std_pages_widgets` (`page_id`, `widget_id`, `order`) VALUES
+(@iParentPageId, LAST_INSERT_ID(), IF(ISNULL(@iParentPageOrder), 1, @iParentPageOrder + 1));

+ 16 - 0
modules/boonex/credits/install/sql/uninstall.sql

@@ -0,0 +1,16 @@
+
+-- TABLES
+DROP TABLE IF EXISTS `bx_credits_bundles`, `bx_credits_orders`, `bx_credits_orders_deleted`, `bx_credits_profiles`, `bx_credits_history`;
+
+
+-- FORMS
+DELETE FROM `sys_objects_form` WHERE `module` = 'bx_credits';
+DELETE FROM `sys_form_displays` WHERE `module` = 'bx_credits';
+DELETE FROM `sys_form_inputs` WHERE `module` = 'bx_credits';
+DELETE FROM `sys_form_display_inputs` WHERE `display_name` LIKE 'bx_credits_%';
+
+
+-- STUDIO: page & widget
+DELETE FROM `tp`, `tw`, `tpw`
+USING `sys_std_pages` AS `tp`, `sys_std_widgets` AS `tw`, `sys_std_pages_widgets` AS `tpw`
+WHERE `tp`.`id` = `tw`.`page_id` AND `tw`.`id` = `tpw`.`widget_id` AND `tp`.`name` = 'bx_credits';

+ 62 - 0
modules/boonex/credits/js/checkout.js

@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+function BxCreditsCheckout(oOptions) {
+    this._sActionsUri = oOptions.sActionUri;
+    this._sActionsUrl = oOptions.sActionUrl;
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxCreditsCheckout' : oOptions.sObjName;
+    this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'slide' : oOptions.sAnimationEffect;
+    this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+    this._aHtmlIds = oOptions.aHtmlIds == undefined ? {} : oOptions.aHtmlIds;
+}
+
+BxCreditsCheckout.prototype.checkout = function(oElement) {
+    var $this = this;
+    var oParams = this._getDefaultData();
+
+    if(oElement)
+        this.loadingInBox(oElement, true);
+
+    jQuery.get (
+        this._sActionsUrl + 'checkout',
+        oParams,
+        function(oData) {
+            if(oElement)
+                $this.loadingInBox(oElement, false);
+
+            processJsonData(oData);
+        },
+        'json'
+    );
+};
+
+BxCreditsCheckout.prototype.loadingInButton = function(e, bShow) {
+    if($(e).length)
+        bx_loading_btn($(e), bShow);
+    else
+        bx_loading($('body'), bShow);	
+};
+
+BxCreditsCheckout.prototype.loadingInBox = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('.bx-def-box:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxCreditsCheckout.prototype.loadingInBlock = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('.bx-db-container:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxCreditsCheckout.prototype._getDefaultData = function() {
+    var oDate = new Date();
+    return jQuery.extend({}, this._oRequestParams, {_t:oDate.getTime()});
+};
+
+/** @} */

+ 42 - 0
modules/boonex/credits/js/studio.js

@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+function BxCreditsStudio(oOptions) {
+    this._sActionsUri = oOptions.sActionUri;
+    this._sActionsUrl = oOptions.sActionUrl;
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxCreditsStudio' : oOptions.sObjName;
+    this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'slide' : oOptions.sAnimationEffect;
+    this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+    this._aHtmlIds = oOptions.aHtmlIds == undefined ? {} : oOptions.aHtmlIds;
+}
+
+BxCreditsStudio.prototype.loadingInButton = function(e, bShow) {
+    if($(e).length)
+        bx_loading_btn($(e), bShow);
+    else
+        bx_loading($('body'), bShow);	
+};
+
+BxCreditsStudio.prototype.loadingInBox = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('.bx-base-text-poll:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxCreditsStudio.prototype.loadingInBlock = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('.bx-db-container:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxCreditsStudio.prototype._getDefaultData = function() {
+    var oDate = new Date();
+    return jQuery.extend({}, this._oRequestParams, {_t:oDate.getTime()});
+};
+
+/** @} */

+ 49 - 0
modules/boonex/credits/js/withdraw.js

@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+function BxCreditsWithdraw(oOptions) {
+    this._sActionsUri = oOptions.sActionUri;
+    this._sActionsUrl = oOptions.sActionUrl;
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxCreditsWithdraw' : oOptions.sObjName;
+    this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'slide' : oOptions.sAnimationEffect;
+    this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+    this._aHtmlIds = oOptions.aHtmlIds == undefined ? {} : oOptions.aHtmlIds;
+}
+
+BxCreditsWithdraw.prototype.getResult = function(oElement) {
+    var fRate = parseFloat($('#' + this._aHtmlIds['withdraw_field_rate']).val());
+    var fAmount = parseFloat($(oElement).val())
+
+    $('#' + this._aHtmlIds['withdraw_field_result']).val(fRate * fAmount);
+};
+
+BxCreditsWithdraw.prototype.loadingInButton = function(e, bShow) {
+    if($(e).length)
+        bx_loading_btn($(e), bShow);
+    else
+        bx_loading($('body'), bShow);	
+};
+
+BxCreditsWithdraw.prototype.loadingInBox = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('.bx-base-text-poll:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxCreditsWithdraw.prototype.loadingInBlock = function(e, bShow) {
+    var oParent = $(e).length ? $(e).parents('.bx-db-container:first') : $('body'); 
+    bx_loading(oParent, bShow);
+};
+
+BxCreditsWithdraw.prototype._getDefaultData = function() {
+    var oDate = new Date();
+    return jQuery.extend({}, this._oRequestParams, {_t:oDate.getTime()});
+};
+
+/** @} */

+ 18 - 0
modules/boonex/credits/request.php

@@ -0,0 +1,18 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Credits Credits
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+check_logged();
+
+BxBaseModTextRequest::processAsAction($GLOBALS['aModule'], $GLOBALS['aRequest']);
+
+/** @} */

+ 19 - 0
modules/boonex/credits/template/bundle_form.html

@@ -0,0 +1,19 @@
+<div class="bx-credits-bundle-form bx-def-color-bg-block">
+    <div class="bx-credits-bundle-form-cnt">
+        __form__
+        <script type="text/javascript">
+            $(document).ready(function () {
+                $("#__form_id__").ajaxForm({ 
+                    dataType: "json",
+                    beforeSubmit: function (formData, jqForm, options) {
+                        bx_loading($("#__form_id__"), true);
+                    },
+                    success: function (data) {
+                        $(".bx-popup-applied:visible").dolPopupHide();
+                        glGrids.__object__.processJson(data, '__action__');
+                    }
+                });
+            });
+        </script>
+    </div>
+</div>

+ 1 - 0
modules/boonex/credits/template/bundle_link.html

@@ -0,0 +1 @@
+<a href="__href__" title="__title__">__content__</a>

+ 27 - 0
modules/boonex/credits/template/bundles.html

@@ -0,0 +1,27 @@
+<div class="__sp__-bundles">
+    <div class="__sp__-bundles-cnt bx-def-margin-sec-neg">
+        <bx_repeat:bundles>
+            <div class="__sp__-bundle-wrp bx-def-padding-sec bx-def-box-sizing">
+                <div class="__sp__-bundle bx-def-box bx-def-color-bg-box bx-def-border bx-def-round-corners">
+                    <div class="__sp__-bundle-cnt bx-def-padding bx-def-box-sizing bx-def-font-align-center">
+                        <div class="__sp__-b-title bx-def-font-h3">__title__</div>
+                        <div class="__sp__-b-amount bx-def-margin-top">
+                            <div class="__sp__-ba-value bx-def-font-h1"><b>__amount__</b></div>
+                            <bx_if:show_bonus>
+                                <div class="__sp__-ba-bonus"><i class="sys-icon plus"></i> <b>__bonus__</b> <bx_text:_bx_credits_txt_for_free /></div>
+                            </bx_if:show_bonus>
+                        </div>
+                        <div class="__sp__-b-price bx-def-margin-top">
+                            <div class="bx-credits-bp-currency">__currency_sign__</div>
+                            <div class="bx-credits-bp-value">__price__</div>
+                        </div>
+                        <div class="__sp__-b-buy bx-def-margin-top ">
+                            <a class="bx-btn" href="javascript:void(0)" onclick="__onclick__"><bx_text:_bx_credits_txt_purchase /></a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </bx_repeat:bundles>
+    </div>
+</div>
+__js_code__

+ 36 - 0
modules/boonex/credits/template/checkout.html

@@ -0,0 +1,36 @@
+<div class="__sp__-checkout">
+    <div class="__sp__-checkout-cnt">
+        <div class="__sp__-checkout-box bx-def-box bx-def-color-bg-box bx-def-box-round-corners bx-def-border bx-def-shadow">
+            <div class="__sp__-checkout-box-cnt bx-def-padding">
+                <div class="__sp__-cb-row __sp__-cbr-title bx-def-font-h2">__title__</div>
+                <div class="__sp__-cb-row __sp__-cbr-items bx-def-margin-top bx-def-padding-leftright">
+                    <bx_repeat:items>
+                        <div class="__sp__-cb-item">
+                            <div class="__sp__-cbi-title">__item_index__. __item_title__</div>
+                            <div class="__sp__-cbi-quantity">__item_quantity__</div>
+                        </div>
+                    </bx_repeat:items>
+                </div>
+                <div class="bx-def-hr bx-def-margin-top"></div>
+                <div class="__sp__-cb-row __sp__-cbr-amount bx-def-margin-top">
+                    <div class="__sp__-cbr-caption bx-def-font-grayed"><bx_text:_bx_credits_txt_checkout_price_money /></div>
+                    <div class="__sp__-cbr-value bx-def-font-h3">__amount__</div>
+                </div>
+                <bx_if:show_rate>
+                    <div class="__sp__-cb-row __sp__-cbr-rate bx-def-margin-sec-top">
+                        <div class="__sp__-cbr-caption bx-def-font-grayed"><bx_text:_bx_credits_txt_checkout_rate /></div>
+                        <div class="__sp__-cbr-value bx-def-font-large">__rate__</div>
+                    </div>
+                </bx_if:show_rate>
+                <div class="__sp__-cb-row __sp__-cbr-credits bx-def-margin-sec-top">
+                    <div class="__sp__-cbr-caption bx-def-font-grayed"><bx_text:_bx_credits_txt_checkout_price_credits /></div>
+                    <div class="__sp__-cbr-value bx-def-font-h3">__credits__</div>
+                </div>
+                <div class="__sp__-cb-row __sp__-cbr-actions bx-def-margin-top">
+                    <a class="bx-btn" href="javascript:void(0)" onclick="javascript:__jo__.checkout(this)"><bx_text:_bx_credits_txt_checkout /></a>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+__js_code__

+ 19 - 0
modules/boonex/credits/template/credit_form.html

@@ -0,0 +1,19 @@
+<div class="bx-credits-credit-form bx-def-color-bg-block">
+    <div class="bx-credits-credit-form-cnt">
+        __form__
+        <script type="text/javascript">
+            $(document).ready(function () {
+                $("#__form_id__").ajaxForm({ 
+                    dataType: "json",
+                    beforeSubmit: function (formData, jqForm, options) {
+                        bx_loading($("#__form_id__"), true);
+                    },
+                    success: function (data) {
+                        $(".bx-popup-applied:visible").dolPopupHide();
+                        glGrids.__object__.processJson(data, '__action__');
+                    }
+                });
+            });
+        </script>
+    </div>
+</div>

+ 42 - 0
modules/boonex/credits/template/css/checkout.css

@@ -0,0 +1,42 @@
+/*--- Checkout block ---*/
+.bx-credits-checkout {
+    position: fixed;
+    width: 100vw;
+    height: 100vh;
+}
+.bx-credits-checkout-cnt {
+    position: relative;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+}
+
+.bx-credits-checkout-box {
+    position: relative;
+    display: inline-block;
+}
+
+.bx-credits-cb-item {
+    position: relative;
+    display: inline-block;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+
+.bx-credits-cbr-actions {
+    text-align: center;
+}
+.bx-credits-cbr-actions .bx-btn {
+    display: inline-block;
+    float: none;
+    width: 80%;
+}

+ 89 - 0
modules/boonex/credits/template/css/main.css

@@ -0,0 +1,89 @@
+@import url(../../../general/template/css/main.css);
+
+/*--- Bundles ---*/
+.bx-credits-bundles,
+.bx-credits-bundles-cnt {
+    position: relative;
+}
+
+.bx-credits-bundles-cnt {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+    -webkit-align-items: stretch;
+    align-items: stretch;
+    -webkit-justify-content: center;
+    justify-content: center;
+}
+
+.bx-credits-bundle-wrp {
+    position: relative;
+    -webkit-flex: 0 1 33%; 
+    flex:  0 1 33%;
+
+    width: 33%;
+}
+
+.bx-credits-bundle {
+    position: relative;
+    width: 100%;
+    height: 100%;
+}
+
+.bx-credits-bundle-cnt {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    -webkit-align-items: center;
+    align-items: center;
+
+    width: 100%;
+    height: 100%;
+}
+.bx-credits-bundle-cnt > div {
+    -webkit-flex: 0 1 auto; 
+    flex: 0 1 auto;
+
+    width: 100%;
+}
+.bx-credits-bundle-cnt > div.bx-credits-b-amount {
+    -webkit-flex-grow: 1;
+    flex-grow: 1;
+}
+
+.bx-credits-b-title {
+    text-transform: uppercase;
+}
+
+.bx-credits-b-price {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+    -webkit-justify-content: center;
+    justify-content: center;
+
+    width: 100%;
+}
+
+.bx-credits-bp-currency {
+    margin-top: 0.5rem;
+
+    line-height: normal;
+    font-size: 1rem;
+    font-weight: 300;
+}
+
+.bx-credits-bp-value {
+    line-height: normal;
+    font-size: 2.4rem;
+    font-weight: 700;
+}
+
+.bx-credits-b-buy .bx-btn {
+    float: none;
+    display: inline-block;
+    max-width: 80%;
+}

+ 4 - 0
modules/boonex/credits/template/css/withdraw.css

@@ -0,0 +1,4 @@
+/*--- Profile Auto Complete ---*/
+.ui-autocomplete.ui-front {
+    z-index: 1001;
+}

+ 27 - 0
modules/boonex/credits/template/images/icons/std-icon.svg

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#00B356;}
+	.st1{fill:#FFFFFF;}
+</style>
+<title>posts</title>
+<desc>Created with Sketch.</desc>
+<g id="Page-1">
+	<g id="Artboard" transform="translate(-340.000000, -430.000000)">
+		<g id="posts" transform="translate(340.000000, 430.000000)">
+			<g id="Path">
+				<path id="path-1_1_" class="st0" d="M32,64c14,0,21.8,0,26.9-5.1C64,53.8,64,46,64,32s0-21.8-5.1-26.9C53.8,0,46,0,32,0
+					S10.2,0,5.1,5.1C0,10.2,0,18,0,32s0,21.8,5.1,26.9C10.2,64,18,64,32,64z"/>
+			</g>
+		</g>
+	</g>
+</g>
+<path class="st1" d="M34,34h-1v-5.4c0-0.3-0.2-0.5-0.5-0.5h-0.8c-0.3,0-0.6,0.1-0.8,0.2L29.9,29c-0.2,0.1-0.3,0.5-0.1,0.7
+	c0,0,0,0,0,0l0.5,0.8c0.1,0.2,0.5,0.3,0.7,0.1c0,0,0,0,0,0l0,0V34h-1c-0.3,0-0.5,0.2-0.5,0.5v1c0,0.3,0.2,0.5,0.5,0.5H34
+	c0.3,0,0.5-0.2,0.5-0.5v-1C34.4,34.2,34.2,34,34,34L34,34z M49.6,20.3H14.4c-1.1,0-2,0.9-2,2v19.6c0,1.1,0.9,2,2,2h35.3
+	c1.1,0,2-0.9,2-2V22.2C51.6,21.1,50.7,20.3,49.6,20.3z M15.4,40.8v-3.9c2.2,0,3.9,1.8,3.9,3.9H15.4z M15.4,27.1v-3.9h3.9
+	C19.3,25.4,17.5,27.1,15.4,27.1z M32,38.9c-3.2,0-5.9-3.1-5.9-6.9c0-3.8,2.6-6.9,5.9-6.9s5.9,3.1,5.9,6.9
+	C37.9,35.8,35.2,38.9,32,38.9z M48.6,40.8h-3.9c0-2.2,1.8-3.9,3.9-3.9V40.8z M48.6,27.1c-2.2,0-3.9-1.8-3.9-3.9h3.9V27.1z"/>
+</svg>