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

Ticket #4113 - Credits: Add multi-currency support: Changes in Payments module.

AntonLV 3 лет назад
Родитель
Сommit
67ed14ac74

+ 9 - 0
inc/classes/BxDolPayments.php

@@ -104,6 +104,15 @@ class BxDolPayments extends BxDolFactory implements iBxDolSingleton
         return bx_srv($this->_sActive, 'get_currency_sign', $aSrvParams);
     }
 
+    public function convert($fAmount, $sCurrencyFrom, $sCurrencyTo)
+    {
+        if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'convert'))
+            return false;
+
+    	$aSrvParams = [$fAmount, $sCurrencyFrom, $sCurrencyTo];
+        return bx_srv($this->_sActive, 'convert', $aSrvParams);
+    }
+
     public function isAcceptingPayments($iVendorId, $sPaymentType = '')
     {
     	if(empty($this->_sActive) || !BxDolRequest::serviceExists($this->_sActive, 'is_accepting_payments'))

+ 3 - 1
modules/base/payment/classes/BxBaseModPaymentConfig.php

@@ -31,6 +31,8 @@ class BxBaseModPaymentConfig extends BxBaseModGeneralConfig
 
         $this->CNF = array_merge($this->CNF, array(
             // some params
+            'PARAM_CURRENCY_CODE' => $this->_sName . '_default_currency_code',
+
             'PARAM_CMSN_INVOICE_ISSUE_DAY' => 1, //the first day of month
             'PARAM_CMSN_INVOICE_LIFETIME' => 4, //in days
             'PARAM_CMSN_INVOICE_EXPIRATION_NOTIFY' => 1, //in days, before expiration date
@@ -101,7 +103,7 @@ class BxBaseModPaymentConfig extends BxBaseModGeneralConfig
 
         $sPrefix = $this->getPrefix('options');
 
-        $this->_sCurrencyCode = (string)$this->_oDb->getParam($sPrefix . 'default_currency_code');
+        $this->_sCurrencyCode = (string)$this->_oDb->getParam($this->CNF['PARAM_CURRENCY_CODE']);
         $this->_sCurrencySign = $this->retrieveCurrencySign($this->_sCurrencyCode);
 
         $this->_iSiteAdmin = (int)$this->_oDb->getParam($sPrefix . 'site_admin');

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

@@ -53,6 +53,7 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
             'KEY_ARRAY_TRIAL_RECURRING' => 'trial_recurring',
 
             // some params
+            'PARAM_CURRENCY_EXCHANGE_API' => 'bx_payment_currency_exchange_api',
             'PARAM_CMSN_INVOICE_ISSUE_DAY' => 'bx_payment_inv_issue_day', //some day of month
             'PARAM_CMSN_INVOICE_LIFETIME' => 'bx_payment_inv_lifetime', //in days
             'PARAM_CMSN_INVOICE_EXPIRATION_NOTIFY' => 'bx_payment_inv_expiraction_notify', //in days, before expiration date

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

@@ -164,6 +164,27 @@ class BxPaymentDb extends BxBaseModPaymentDb
         return $this->query($sQuery);
     }
 
+    public function getCurrencyExchangeRate($sCode)
+    {
+        return $this->getOne("SELECT `rate` FROM `" . $this->_sPrefix . "currencies` WHERE `code`=:code LIMIT 1", [
+            'code' => $sCode
+        ]);
+    }
+
+    public function updateCurrencyExchangeRate($sCode, $fRate)
+    {
+        return $this->query("INSERT INTO `" . $this->_sPrefix . "currencies`(`code`, `rate`) VALUES(:code, :rate) ON DUPLICATE KEY UPDATE `rate`=:rate", [
+            'code' => $sCode,
+            'rate' => $fRate
+        ]);
+    }
+
+    public function deleteCurrencyExchangeRate($sCode)
+    {
+        return $this->query("DELETE FROM `" . $this->_sPrefix . "currencies` WHERE `code`=:code", [
+            'code' => $sCode
+        ]) !== false;
+    }
 
     /**
      * Shopping cart methods.
@@ -307,6 +328,13 @@ class BxPaymentDb extends BxBaseModPaymentDb
                 $sItems .= $this->_oConfig->descriptorA2S(array($aAddon['author_id'], $aAddon['module_id'], $aAddon['id'], $aAddon['quantity'])) . ':';
         }
 
+        $sCurrency = $aCartInfo['vendor_currency_code'];
+        $sCurrencyDefault = $this->_oConfig->getDefaultCurrencyCode();
+
+        $aData = ['cur_def' => $sCurrencyDefault];
+        if(strcmp($sCurrency, $sCurrencyDefault) != 0)
+            $aData['cur_rate'] = $this->getCurrencyExchangeRate($sCurrency);
+
         $aSet = [
             'client_id' => $iClientId,
             'seller_id' => $aCartInfo['vendor_id'],
@@ -315,7 +343,8 @@ class BxPaymentDb extends BxBaseModPaymentDb
             'items' => trim($sItems, ':'),
             'customs' => !empty($aCustom) && is_array($aCustom) ? serialize($aCustom) : '',
             'amount' => $aCartInfo['items_price'],
-            'currency' => $aCartInfo['vendor_currency_code'],
+            'currency' => $sCurrency,
+            'data' => !empty($aData) ? serialize($aData) : ''
         ];
 
         return (int)$this->query("INSERT INTO `" . $this->_sPrefix . "transactions_pending` SET " . $this->arrayToSQL($aSet) . ", `date`=UNIX_TIMESTAMP()") > 0 ? $this->lastId() : 0;

+ 52 - 1
modules/boonex/payment/classes/BxPaymentModule.php

@@ -1963,7 +1963,58 @@ class BxPaymentModule extends BxBaseModPaymentModule
 
     	return true;
     }
-    
+
+    public function serviceConvert($fAmount, $sCurrencyFrom, $sCurrencyTo, $fRate = false)
+    {
+        $sCurrencyDefault = $this->_oConfig->getDefaultCurrencyCode();
+        $bFromDefault = strcmp($sCurrencyFrom, $sCurrencyDefault) == 0;
+        $bToDefault = strcmp($sCurrencyTo, $sCurrencyDefault) == 0;
+
+        if(!$bFromDefault && !$bToDefault)
+            return false;
+
+        if($fRate === false)
+            $fRate = $this->_oDb->getCurrencyExchangeRate($bFromDefault ? $sCurrencyTo : $sCurrencyFrom);
+        if($fRate === false)
+            return false;
+
+        $fRate = (float)$fRate;
+        if($bToDefault)
+            $fRate = 1/$fRate;
+
+        return $fAmount * $fRate;
+    }
+
+    public function updateCurrencyExchangeRates()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sApiKey = getParam($CNF['PARAM_CURRENCY_EXCHANGE_API']);
+        if(empty($sApiKey))
+            return;
+
+        $sCurrency = $this->_oDb->getParam($CNF['PARAM_CURRENCY_CODE'], false);
+        $aCurrencies = BxDolForm::getDataItems($CNF['OBJECT_FORM_PRELISTS_CURRENCIES'], false, BX_DATA_VALUES_ADDITIONAL);
+        unset($aCurrencies[$sCurrency]);
+
+        $sApiEndpoint = 'https://api.apilayer.com/exchangerates_data/latest';
+        $aApiParams = [
+            'symbols' => implode(',', array_keys($aCurrencies)), 
+            'base' => $sCurrency
+        ];
+        $sResponse = bx_file_get_contents($sApiEndpoint, $aApiParams, 'get', [
+            'apikey: ' . $sApiKey
+        ]);
+
+        $aResponse = json_decode($sResponse, true);
+        if(empty($aResponse['rates']) || !is_array($aResponse['rates']))
+            return;
+
+        $this->_oDb->deleteCurrencyExchangeRate($sCurrency);
+
+        foreach($aResponse['rates'] as $sCode => $fRate)
+            $this->_oDb->updateCurrencyExchangeRate($sCode, $fRate);
+    }
 }
 
 /** @} */

+ 29 - 5
modules/boonex/payment/classes/BxPaymentProviderCredits.php

@@ -130,9 +130,11 @@ class BxPaymentProviderCredits extends BxBaseModPaymentProvider implements iBxBa
         if(!$oClient || !$oSeller)
             return ['code' => 3, 'message' => $this->_sLangsPrefix . 'err_wrong_data'];
 
-        $fAmount = (float)$mixedPending['amount'];
+        $fAmountM = $this->_getAmountByPending($mixedPending);
+        $fAmountC = bx_srv($this->_sModuleCredits, 'convert_money_to_credits', [$fAmountM]);
+
         $fClientBalance = bx_srv($this->_sModuleCredits, 'get_profile_balance', [$iClient]);
-        if($fAmount > $fClientBalance) {
+        if($fAmountC > $fClientBalance) {
             $aSubscription = $this->_oModule->_oDb->getSubscription([
                 'type' => 'pending_id', 
                 'pending_id' => $mixedPending['id']
@@ -155,7 +157,7 @@ class BxPaymentProviderCredits extends BxBaseModPaymentProvider implements iBxBa
             return ['code' => 4, 'message' => $this->_sLangsPrefix . 'cdt_err_wrong_balance'];
         }
 
-        if(!bx_srv($this->_sModuleCredits, 'make_payment', [$iClient, $fAmount, $iSeller, $mixedPending['order']]))
+        if(!bx_srv($this->_sModuleCredits, 'make_payment', [$iClient, $fAmountC, $iSeller, $mixedPending['order']]))
             return ['code' => 5, 'message' => $this->_sLangsPrefix . 'err_cannot_perform'];
 
         return true;        
@@ -196,12 +198,13 @@ class BxPaymentProviderCredits extends BxBaseModPaymentProvider implements iBxBa
                     return array('code' => 3, 'message' => $this->_sLangsPrefix . 'err_already_processed');
 
                 $sOrder = $aData['o'];
-                if(!bx_srv($this->_sModuleCredits, 'validate_checkout', array((int)$aPending['seller_id'], (int)$aPending['client_id'], (float)$aPending['amount'], $sOrder)))
+                $fAmount = $this->_getAmountByPending($aPending);
+                if(!bx_srv($this->_sModuleCredits, 'validate_checkout', array((int)$aPending['seller_id'], (int)$aPending['client_id'], $fAmount, $sOrder)))
                     return array('code' => 4, 'message' => $this->_sLangsPrefix . 'cdt_err_wrong_transaction', 'pending_id' => $iPendingId);
 
                 $aResult = array(
                     'message' => $this->_sLangsPrefix . 'cdt_msg_charged',
-                    'order' => !empty($aData['o']) ? $aData['o'] : '',
+                    'order' => $sOrder,
                     'paid' => true
                 );
                 break;
@@ -240,6 +243,27 @@ class BxPaymentProviderCredits extends BxBaseModPaymentProvider implements iBxBa
     {
         return explode('|', base64_decode(urldecode($data)));
     }
+
+    protected function _getAmountByPending($aPending)
+    {
+        $fCurrencyRate = false;
+        $sCurrencyDefault = $this->_oModule->_oConfig->getDefaultCurrencyCode();
+        if(!empty($aPending['data'])) {
+            $aPendingData = unserialize($aPending['data']);
+
+            if(!empty($aPendingData['cur_rate']))
+                $fCurrencyRate = (float)$aPendingData['cur_rate'];
+
+            if(!empty($aPendingData['cur_def']))
+                $sCurrencyDefault = $aPendingData['cur_def'];
+        }
+
+        $fAmount = (float)$aPending['amount'];
+        if(strcmp($aPending['currency'], $sCurrencyDefault) != 0)
+            $fAmount = $this->_oModule->serviceConvert($fAmount, $aPending['currency'], $sCurrencyDefault, $fCurrencyRate);
+
+        return $fAmount;
+    }
 }
 
 /** @} */

+ 34 - 18
modules/boonex/payment/classes/BxPaymentResponse.php

@@ -11,37 +11,53 @@
 
 class BxPaymentResponse extends BxDolAlertsResponse
 {
-	protected $MODULE;
+    protected $_sModule;
     protected $_oModule;
 
-	public function __construct()
+    public function __construct()
     {
-    	$this->MODULE = 'bx_payment';
-
         parent::__construct();
 
-        $this->_oModule = BxDolModule::getInstance($this->MODULE);
+        $this->_sModule = 'bx_payment';
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
     }
 
-	/**
+    /**
      * Overwtire the method of parent class.
      *
      * @param BxDolAlerts $oAlert an instance of alert.
      */
     public function response($oAlert)
     {
-    	if($oAlert->sUnit != 'profile' || !in_array($oAlert->sAction, array('join', 'delete')))
-    		return;
-
-		switch($oAlert->sAction) {
-			case 'join':
-				$this->_oModule->onProfileJoin($oAlert->iObject);
-				break;
-
-			case 'delete':
-				$this->_oModule->onProfileDelete($oAlert->iObject);
-				break;
-		}
+        $sMethod = '_process' . bx_gen_method_name($oAlert->sUnit . '_' . $oAlert->sAction);           	
+        if(method_exists($this, $sMethod))
+            return $this->$sMethod($oAlert);
+
+    	if($oAlert->sUnit != 'profile' || !in_array($oAlert->sAction, ['join', 'delete']))
+            return;
+
+        switch($oAlert->sAction) {
+            case 'join':
+                $this->_oModule->onProfileJoin($oAlert->iObject);
+                break;
+
+            case 'delete':
+                $this->_oModule->onProfileDelete($oAlert->iObject);
+                break;
+        }
+    }
+
+    protected function _processSystemSaveSetting($oAlert)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if($oAlert->aExtras['option'] != $CNF['PARAM_CURRENCY_CODE'])
+            return;
+        
+        if(strcmp($oAlert->aExtras['value'], $oAlert->aExtras['value_prior']) == 0)
+            return;
+
+        $this->_oModule->updateCurrencyExchangeRates();
     }
 }
 

+ 2 - 0
modules/boonex/payment/install/langs/en.xml

@@ -4,9 +4,11 @@
 
     <!-- OPTIONS -->
     <string name="_bx_payment_options_category_general"><![CDATA[General]]></string>
+    <string name="_bx_payment_options_category_currency"><![CDATA[Сurrency]]></string>
     <string name="_bx_payment_options_category_commissions"><![CDATA[Commissions]]></string>
     <string name="_bx_payment_option_default_currency_code"><![CDATA[Currency]]></string>
     <string name="_bx_payment_option_err_default_currency_code"><![CDATA[Currency code cannot be empty.]]></string>
+    <string name="_bx_payment_option_currency_exchange_api"><![CDATA[Exchange currency API key (by exchangeratesapi.io)]]></string>
     <string name="_bx_payment_option_site_admin"><![CDATA[Main seller (To sell on behalf of the site)]]></string>
     <string name="_bx_payment_option_credits_only"><![CDATA[Pay with Credits only mode ('Credits' app should be installed).]]></string>
     <string name="_bx_payment_option_single_seller"><![CDATA[Single seller mode (To sell everything on behalf of the 'Main seller').]]></string>

+ 11 - 2
modules/boonex/payment/install/sql/enable.sql

@@ -12,11 +12,18 @@ VALUES (@iTypeId, 'bx_payment_general', '_bx_payment_options_category_general',
 SET @iCategoryId = LAST_INSERT_ID();
 
 INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_params`, `check_error`, `extra`, `order`) VALUES
-('bx_payment_default_currency_code', 'USD', @iCategoryId, '_bx_payment_option_default_currency_code', 'select', 'Avail', '', '_bx_payment_option_err_default_currency_code', 'a:2:{s:6:"module";s:10:"bx_payment";s:6:"method";s:33:"get_options_default_currency_code";}', 0),
-('bx_payment_site_admin', '', @iCategoryId, '_bx_payment_option_site_admin', 'select', '', '', '', 'a:2:{s:6:"module";s:10:"bx_payment";s:6:"method";s:22:"get_options_site_admin";}', 1), 
+('bx_payment_site_admin', '', @iCategoryId, '_bx_payment_option_site_admin', 'select', '', '', '', 'a:2:{s:6:"module";s:10:"bx_payment";s:6:"method";s:22:"get_options_site_admin";}', 1),
 ('bx_payment_credits_only', '', @iCategoryId, '_bx_payment_option_credits_only', 'checkbox', '', '', '', '', 10),
 ('bx_payment_single_seller', '', @iCategoryId, '_bx_payment_option_single_seller', 'checkbox', '', '', '', '', 11);
 
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order`)
+VALUES (@iTypeId, 'bx_payment_currency', '_bx_payment_options_category_currency', 5);
+SET @iCategoryId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_params`, `check_error`, `extra`, `order`) VALUES
+('bx_payment_default_currency_code', 'USD', @iCategoryId, '_bx_payment_option_default_currency_code', 'select', 'Avail', '', '_bx_payment_option_err_default_currency_code', 'a:2:{s:6:"module";s:10:"bx_payment";s:6:"method";s:33:"get_options_default_currency_code";}', 1),
+('bx_payment_currency_exchange_api', '', @iCategoryId, '_bx_payment_option_currency_exchange_api', 'digit', '', '', '', '', 10);
+
 INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order`)
 VALUES (@iTypeId, 'bx_payment_commissions', '_bx_payment_options_category_commissions', 10);
 SET @iCategoryId = LAST_INSERT_ID();
@@ -132,6 +139,7 @@ INSERT INTO `sys_alerts_handlers`(`name`, `class`, `file`, `service_call`) VALUE
 SET @iHandler := LAST_INSERT_ID();
 
 INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('system', 'save_setting', @iHandler),
 ('profile', 'join', @iHandler),
 ('profile', 'delete', @iHandler);
 
@@ -193,6 +201,7 @@ INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `B
 -- CRON
 INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
 ('bx_payment_commissions', '0 * * * *', 'BxPaymentCronCommissions', 'modules/boonex/payment/classes/BxPaymentCronCommissions.php', ''),
+('bx_payment_currency', '0 0 * * *', 'BxPaymentCronCurrency', 'modules/boonex/payment/classes/BxPaymentCronCurrency.php', ''),
 ('bx_payment_time_tracker', '* * * * *', 'BxPaymentCronTimeTracker', 'modules/boonex/payment/classes/BxPaymentCronTimeTracker.php', '');
 
 

+ 10 - 1
modules/boonex/payment/install/sql/install.sql

@@ -2,6 +2,14 @@ SET @sName = 'bx_payment';
 
 
 -- TABLES
+CREATE TABLE IF NOT EXISTS `bx_payment_currencies` (
+  `id` int(11) NOT NULL auto_increment,
+  `code` varchar(4) NOT NULL default '',
+  `rate` float NOT NULL default '0',
+  PRIMARY KEY(`id`),
+  UNIQUE KEY `code`(`code`)
+);
+
 CREATE TABLE IF NOT EXISTS `bx_payment_providers` (
   `id` int(11) NOT NULL auto_increment,
   `name` varchar(64) NOT NULL default '',
@@ -115,6 +123,7 @@ CREATE TABLE IF NOT EXISTS `bx_payment_transactions_pending` (
   `amount` float NOT NULL default '0',
   `currency` varchar(4) NOT NULL default '',
   `order` varchar(32) NOT NULL default '',
+  `data` text NOT NULL,
   `error_code` varchar(16) NOT NULL default '',
   `error_msg` varchar(255) NOT NULL default '',
   `date` int(11) NOT NULL default '0',
@@ -646,7 +655,7 @@ INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALU
 ('bx_payment_currencies', 'EUR', 3, 'EUR', '&#8364;'),
 ('bx_payment_currencies', 'GBP', 4, 'GBP', '&#163;'),
 ('bx_payment_currencies', 'USD', 5, 'USD', '&#36;'),
-('bx_payment_currencies', 'YEN', 6, 'YEN', '&#165;');
+('bx_payment_currencies', 'JPY', 6, 'YEN', '&#165;');
 
 -- STUDIO PAGE & WIDGET
 INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES

+ 1 - 1
modules/boonex/payment/install/sql/uninstall.sql

@@ -2,7 +2,7 @@ SET @sName = 'bx_payment';
 
 
 -- TABLES
-DROP TABLE IF EXISTS `bx_payment_providers`, `bx_payment_providers_options`, `bx_payment_user_values`;
+DROP TABLE IF EXISTS `bx_payment_currencies`, `bx_payment_providers`, `bx_payment_providers_options`, `bx_payment_user_values`;
 DROP TABLE IF EXISTS `bx_payment_cart`;
 DROP TABLE IF EXISTS `bx_payment_transactions`, `bx_payment_transactions_pending`;
 DROP TABLE IF EXISTS `bx_payment_subscriptions`, `bx_payment_subscriptions_deleted`;

+ 2 - 0
modules/boonex/russian/data/langs/bx_payment/ru.xml

@@ -4,9 +4,11 @@
 
     <!-- OPTIONS -->
     <string name="_bx_payment_options_category_general"><![CDATA[Общие]]></string>
+    <string name="_bx_payment_options_category_currency"><![CDATA[Валюта]]></string>
     <string name="_bx_payment_options_category_commissions"><![CDATA[Комиссии]]></string>
     <string name="_bx_payment_option_default_currency_code"><![CDATA[Код валюты]]></string>
     <string name="_bx_payment_option_err_default_currency_code"><![CDATA[Код валюты не может быть пуст.]]></string>
+    <string name="_bx_payment_option_currency_exchange_api"><![CDATA[API-ключ для конвертации валют (от exchangeratesapi.io)]]></string>
     <string name="_bx_payment_option_site_admin"><![CDATA[Главный продавец (для продажи от имени сайта)]]></string>
     <string name="_bx_payment_option_credits_only"><![CDATA[Режим оплаты только кредитами (приложение 'Кредиты' должно быть установлено).]]></string>
     <string name="_bx_payment_option_single_seller"><![CDATA[Режим единого продавца (все продается от имени 'Главный продавец').]]></string>