AntonLV 5 лет назад
Родитель
Сommit
84dae4f57e

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

@@ -345,7 +345,7 @@ class BxBaseModPaymentModule extends BxBaseModGeneralModule
 
         $sTitle .= "\n";
 
-        bx_log('sys_payments', '[' . $sSection . '] ' . $sTitle . $mixedContents);
+        bx_log('sys_payments', ":\n[" . $sSection . "] " . $sTitle . $mixedContents);
     }
 }
 

+ 5 - 0
modules/base/payment/classes/BxBaseModPaymentProvider.php

@@ -153,6 +153,11 @@ class BxBaseModPaymentProvider extends BxDol
 
     public function finalizedCheckout() {}
 
+    public function isSubscriptionStatus($sStatus, $aSubscription, $sStatusKey = 'status')
+    {
+        return isset($this->_aSbsStatuses[$aSubscription[$sStatusKey]]) && $this->_aSbsStatuses[$aSubscription[$sStatusKey]] == $sStatus;
+    }
+
     public function getSubscription($iPendingId, $sCustomerId, $sSubscriptionId)
     {
         return array();

+ 9 - 2
modules/boonex/payment/classes/BxPaymentConfig.php

@@ -23,11 +23,16 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
         $sBaseUrl = BX_DOL_URL_ROOT . $this->getBaseUri();
 
         $this->CNF = array_merge($this->CNF, array(
+            // module icon
+            'ICON' => 'credit-card col-gray-dark',
+
+            // page URIs
             'URL_JOIN' => 'page.php?i=payment-join',
             'URL_CARTS' => 'page.php?i=payment-carts',
             'URL_CART' => 'page.php?i=payment-cart',
             'URL_CART_CHECKOUT' => $sBaseUrl . 'initialize_checkout/' . BX_PAYMENT_TYPE_SINGLE . '/',
             'URL_SUBSCRIBE' => $sBaseUrl . 'subscribe/',
+            'URL_SUBSCRIBE_JSON' => $sBaseUrl . 'subscribe_json/',
             'URL_SUBSCRIPTIONS' => 'page.php?i=payment-sbs-list-my',
             'URL_HISTORY' => 'page.php?i=payment-history',
             'URL_ORDERS' => 'page.php?i=payment-orders',
@@ -107,7 +112,8 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
             'pending' => 'BxPaymentOrders',
             'processed' => 'BxPaymentOrders',
             'chargebee_v3' => 'BxPaymentProviderChargebeeV3',
-            'stripe' => 'BxPaymentProviderStripe'
+            'stripe' => 'BxPaymentProviderStripe',
+            'stripe_v3' => 'BxPaymentProviderStripeV3'
         );
 
         $this->_aJsObjects = array(
@@ -117,7 +123,8 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
             'pending' => 'oPaymentOrders',
             'processed' => 'oPaymentOrders',
             'chargebee_v3' => 'oPaymentProviderChargebeeV3',
-            'stripe' => 'oPaymentProviderStripe'
+            'stripe' => 'oPaymentProviderStripe',
+            'stripe_v3' => 'oPaymentProviderStripeV3'
         );
 
         $this->_bCreditsOnly = false;

+ 18 - 14
modules/boonex/payment/classes/BxPaymentGridCart.php

@@ -24,27 +24,30 @@ class BxPaymentGridCart extends BxBaseModPaymentGridCarts
     public function performActionCheckout()
     {
     	$aParams = array(
-			'seller_id' => bx_process_input(bx_get('seller_id'), BX_DATA_INT), 
-			'provider' => bx_process_input(bx_get('provider')), 
-			'items' => bx_process_input(bx_get('ids'))    		
+            'seller_id' => bx_process_input(bx_get('seller_id'), BX_DATA_INT), 
+            'provider' => bx_process_input(bx_get('provider')), 
+            'items' => bx_process_input(bx_get('ids'))
     	);
+
     	if(empty($aParams['seller_id']) || empty($aParams['provider']))
-    		return echoJson(array());
+            return echoJson(array());
 
         if(empty($aParams['items']) || !is_array($aParams['items'])) 
-        	return echoJson(array('msg' => _t('_bx_payment_err_nothing_selected')));
+            return echoJson(array('msg' => _t('_bx_payment_err_nothing_selected')));
 
-		$oProvider = $this->_oModule->getObjectProvider($aParams['provider'], $aParams['seller_id']);
-		if($oProvider !== false && method_exists($oProvider, 'getCheckoutParamsSingle'))
-			$aParams = $oProvider->getCheckoutParamsSingle($aParams, $this);
+        $oProvider = $this->_oModule->getObjectProvider($aParams['provider'], $aParams['seller_id']);
+        if($oProvider !== false) {
+            if(method_exists($oProvider, 'overwriteCheckoutParamsSingle'))
+                return echoJson($oProvider->overwriteCheckoutParamsSingle($aParams, $this));
 
-		$sLink = $this->_oModule->_oConfig->getUrl('URL_CART_CHECKOUT');
-		$sLink = $sLink . (strpos($sLink, '?') === false ? '?' : '&') . http_build_query($aParams);
+            if(method_exists($oProvider, 'getCheckoutParamsSingle'))
+                $aParams = $oProvider->getCheckoutParamsSingle($aParams, $this);
+        }
 
-		echoJson(array(
-			'eval' => $this->_oModule->_oConfig->getJsObject('cart') . '.onCartCheckout(oData);', 
-			'link' => $sLink
-		));
+        echoJson(array(
+            'eval' => $this->_oModule->_oConfig->getJsObject('cart') . '.onCartCheckout(oData);', 
+            'link' => bx_append_url_params($this->_oModule->_oConfig->getUrl('URL_CART_CHECKOUT'), $aParams)
+        ));
     }
 
     protected function _getCellTitle($mixedValue, $sKey, $aField, $aRow)
@@ -125,6 +128,7 @@ class BxPaymentGridCart extends BxBaseModPaymentGridCarts
                     $aParams = array(
                         'sObjNameGrid' => $this->getObject(),
                         'aCartInfo' => $aCartInfo,
+                        'sAction' => $sAction
                     );
 
                     if(!empty($aCartInfo['items_price']) && !empty($aCartInfo['items']) && is_array($aCartInfo['items'])) {

+ 14 - 4
modules/boonex/payment/classes/BxPaymentModule.php

@@ -757,9 +757,16 @@ class BxPaymentModule extends BxBaseModPaymentModule
         $aPending = $this->_oDb->getOrderPending(array('type' => 'id', 'id' => (int)$aResult['pending_id']));
         $bTypeRecurring = $aPending['type'] == BX_PAYMENT_TYPE_RECURRING;
         if($bTypeRecurring) {
+            $sStatus = BX_PAYMENT_SBS_STATUS_UNPAID;
+            if(isset($aResult['paid']) && $aResult['paid'])
+                $sStatus = BX_PAYMENT_SBS_STATUS_ACTIVE;
+            else if(isset($aResult['trial']) && $aResult['trial'])
+                $sStatus = BX_PAYMENT_SBS_STATUS_TRIAL;
+
             $aSubscription = array(
                 'customer_id' => $aResult['customer_id'], 
-                'subscription_id' => $aResult['subscription_id']
+                'subscription_id' => $aResult['subscription_id'],
+                'status' => $sStatus
             );
 
             if(!$this->getObjectSubscriptions()->register($aPending, $aSubscription))
@@ -801,7 +808,7 @@ class BxPaymentModule extends BxBaseModPaymentModule
     {
         $oProvider = $this->getObjectProvider($sProvider, $mixedVendorId);
         if($oProvider === false || !$oProvider->isActive())
-        	return $this->_oTemplate->displayPageCodeError($this->_sLangsPrefix . 'err_incorrect_provider');
+            return $this->_oTemplate->displayPageCodeError($this->_sLangsPrefix . 'err_incorrect_provider');
 
         $aResult = $oProvider->finalizedCheckout();
         $this->_oTemplate->displayPageCodeResponse($aResult['message']);
@@ -816,9 +823,12 @@ class BxPaymentModule extends BxBaseModPaymentModule
         $oProvider->notify();
     }
     
-    public function actionCall($sProvider, $sAction)
+    /**
+     * The method is needed to pass direct action calls to necessary Payment Provider.
+     */
+    public function actionCall($sProvider, $sAction, $mixedVendorId = BX_PAYMENT_EMPTY_ID)
     {
-        $oProvider = $this->getObjectProvider($sProvider);
+        $oProvider = $this->getObjectProvider($sProvider, $mixedVendorId);
         if($oProvider === false)
             BxDolRequest::methodNotFound($sAction, $this->getName());
 

+ 3 - 592
modules/boonex/payment/classes/BxPaymentProviderStripe.php

@@ -9,83 +9,18 @@
  * @{
  */
 
-require_once(BX_DIRECTORY_PATH_PLUGINS . 'stripe/init.php');
+require_once('BxPaymentProviderStripeBasic.php');
 
-define('STRP_MODE_LIVE', 1);
-define('STRP_MODE_TEST', 2);
-
-class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBaseModPaymentProvider
+class BxPaymentProviderStripe extends BxPaymentProviderStripeBasic implements iBxBaseModPaymentProvider
 {
-    protected $_aIncludeJs;
-    protected $_aIncludeCss;
-
-    protected $_sFormDetails;
-    protected $_sFormDisplayDetailsEdit;
-
-    protected $_sFormCard;
-    protected $_sFormDisplayCardAdd;
-
-    protected $_iMode;
-    protected $_bCheckAmount;
-    protected $_bProrate;
-
-    protected $_oCustomer;
-
-
     function __construct($aConfig)
     {
-    	$this->MODULE = 'bx_payment';
-
         parent::__construct($aConfig);
 
-        $this->_aSbsStatuses = array(
-            'incomplete' => BX_PAYMENT_SBS_STATUS_UNPAID, 
-            'incomplete_expired' => BX_PAYMENT_SBS_STATUS_UNPAID, 
-            'trialing' => BX_PAYMENT_SBS_STATUS_TRIAL, 
-            'active' => BX_PAYMENT_SBS_STATUS_ACTIVE, 
-            'past_due' => BX_PAYMENT_SBS_STATUS_UNPAID,
-            'unpaid' => BX_PAYMENT_SBS_STATUS_UNPAID,
-            'canceled' => BX_PAYMENT_SBS_STATUS_CANCELED,
-        );
-
-        $this->_sFormDetails = 'bx_payment_form_strp_details';
-        $this->_sFormDisplayDetailsEdit = 'bx_payment_form_strp_details_edit';
-
-        $this->_sFormCard = 'bx_payment_form_strp_card';
-        $this->_sFormDisplayCardAdd = 'bx_payment_form_strp_card_add';
-
-        $this->_bProrate = false;
-
         $this->_aIncludeJs = array(
             'https://checkout.stripe.com/checkout.js',
-            'main.js',
             'stripe.js'
         );
-        $this->_aIncludeCss = array(
-            'stripe.css'
-        );
-
-        $this->_oCustomer = null;
-
-        \Stripe\Stripe::setApiKey($this->_getSecretKey());
-    }
-
-    public function initOptions($aOptions)
-    {
-    	parent::initOptions($aOptions);
-
-    	$this->_iMode = (int)$this->getOption('mode');
-    	$this->_bCheckAmount = $this->getOption('check_amount') == 'on';
-    	$this->_bUseSsl = $this->getOption('ssl') == 'on';
-    }
-
-    public function addJsCss()
-    {
-    	if(!$this->isActive())
-    		return;
-
-        $this->_oModule->_oTemplate->addJs($this->_aIncludeJs);
-        $this->_oModule->_oTemplate->addCss($this->_aIncludeCss);
     }
 
     public function initializeCheckout($iPendingId, $aCartInfo, $sRedirect = '')
@@ -216,225 +151,6 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         return $aResult;
     }
 
-    public function notify()
-    {
-        $iResult = $this->_processEvent();
-        http_response_code($iResult);
-    }
-
-    public function getButtonSingle($iClientId, $iVendorId, $aParams = array())
-    {
-        $aItems = array();
-        $aCartInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_SINGLE, $iClientId, (int)$iVendorId, $aItems);
-        if(empty($aCartInfo) || !is_array($aCartInfo))
-            return '';
-
-    	return $this->_getButton(BX_PAYMENT_TYPE_SINGLE, $iClientId, $iVendorId, array_merge($aParams, array(
-            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aCartInfo['vendor_name']),
-            'sVendorCurrency' => $aCartInfo['vendor_currency_code'],
-            'sVendorIcon' => $aCartInfo['vendor_avatar'],
-    	)));
-    }
-
-    public function getButtonSingleJs($iClientId, $iVendorId, $aParams = array())
-    {
-        $aItems = array();
-        $aCartInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_SINGLE, $iClientId, (int)$iVendorId, $aItems);
-        if(empty($aCartInfo) || !is_array($aCartInfo))
-            return '';
-
-    	return $this->_getButtonJs(BX_PAYMENT_TYPE_SINGLE, $iClientId, $iVendorId, array_merge($aParams, array(
-            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aCartInfo['vendor_name']),
-            'sVendorCurrency' => $aCartInfo['vendor_currency_code'],
-            'sVendorIcon' => $aCartInfo['vendor_avatar'],
-    	)));
-    }
-
-    public function getButtonRecurring($iClientId, $iVendorId, $aParams = array())
-    {
-    	$aVendor = $this->_oModule->getVendorInfo((int)$iVendorId);
-
-        return $this->_getButton(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iVendorId, array_merge($aParams, array(
-            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aVendor['name']),
-            'sVendorCurrency' => $aVendor['currency_code'],
-            'sVendorIcon' => $aVendor['avatar'],
-        )));
-    }
-    
-    public function getButtonRecurringJs($iClientId, $iVendorId, $aParams = array())
-    {
-    	$aVendor = $this->_oModule->getVendorInfo((int)$iVendorId);
-
-        return $this->_getButtonJs(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iVendorId, array_merge($aParams, array(
-            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aVendor['name']),
-            'sVendorCurrency' => $aVendor['currency_code'],
-            'sVendorIcon' => $aVendor['avatar'],
-        )));
-    }
-
-    public function getMenuItemsActionsRecurring($iClientId, $iVendorId, $aParams = array())
-    {
-        if(empty($aParams['order']))
-            return array();
-
-        $sJsObject = $this->_oModule->_oConfig->getJsObject(BX_PAYMENT_ORDERS_TYPE_SUBSCRIPTION);
-
-        $sPrefix = 'bx-payment-strp-';
-        return array(
-            array('id' => $sPrefix . 'details', 'name' => $sPrefix . 'details', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".getDetails(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_details')),
-            array('id' => $sPrefix . 'details_change', 'name' => $sPrefix . 'details_change', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changeDetails(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_details_change')),
-            array('id' => $sPrefix . 'billing', 'name' => $sPrefix . 'billing', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".getBilling(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_billing')),
-            array('id' => $sPrefix . 'billing_change', 'name' => $sPrefix . 'billing_change', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changeBilling(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_billing_change')),
-        );
-    }
-
-    public function getDetailsRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
-    {
-        $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
-        if($oSubscription === false)
-            return '';
-
-        $aSubscription = $oSubscription->jsonSerialize();
-        if(empty($aSubscription) || !is_array($aSubscription))
-            return '';
-
-        $sName = '';
-        if(!empty($aSubscription['plan']['name']))
-            $sName = $aSubscription['plan']['name'];
-        else if(!empty($aSubscription['plan']['product'])){
-            $oProduct = $this->_retrieveProduct($aSubscription['plan']['product']);
-            if($oProduct !== false) 
-                $sName = $oProduct->name;
-        }
-
-        $sNone = _t('_bx_payment_txt_none');
-        return $this->_oModule->_oTemplate->parseHtmlByName('strp_details_recurring.html', array(
-            'plan' => $sName,
-            'cost' => _t('_bx_payment_strp_txt_cost_mask', (int)$aSubscription['plan']['amount'] / 100, $aSubscription['plan']['currency'], $aSubscription['plan']['interval']),
-            'status' => $aSubscription['status'],
-            'created' => bx_time_js($aSubscription['created']),
-            'started' => !empty($aSubscription['start']) ? bx_time_js($aSubscription['start']) : $sNone,
-            'trial_start' => !empty($aSubscription['trial_start']) ? bx_time_js($aSubscription['trial_start']) : $sNone,
-            'trial_end' => !empty($aSubscription['trial_end']) ? bx_time_js($aSubscription['trial_end']) : $sNone,
-            'cperiod_start' => !empty($aSubscription['current_period_start']) ? bx_time_js($aSubscription['current_period_start']) : $sNone,
-            'cperiod_end' => !empty($aSubscription['current_period_end']) ? bx_time_js($aSubscription['current_period_end']) : $sNone,
-        ));
-    }
-
-    public function changeDetailsRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
-    {
-        $oForm = BxDolForm::getObjectInstance($this->_sFormDetails, $this->_sFormDisplayDetailsEdit, $this->_oModule->_oTemplate);
-        $oForm->aFormAttrs['id'] = $this->_oModule->_oConfig->getHtmlIds('subscription', 'form_subscription_change_details');
-        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oModule->_oConfig->getBaseUri() . 'subscription_change_details/' . $iPendingId;
-
-        $oForm->aInputs['item_id']['values'] = $this->_getDataChangeDetailsRecurring($iPendingId);
-
-        $oForm->initChecker();
-        if($oForm->isSubmittedAndValid()) {
-            $aResultError = array('code' => 1, 'message' => _t('_bx_payment_strp_err_details_changed'));
-
-            $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
-            list($iVendorId, $iModuleId, $iItemId, $iItemCount) = $this->_oModule->_oConfig->descriptorS2A($aPending['items']);
-
-            $aItem = $this->_oModule->callGetCartItem($iModuleId, array($oForm->getCleanValue('item_id')));
-    		if(empty($aItem) || !is_array($aItem))
-    			return $aResultError;
-
-            $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
-            if($oSubscription === false)
-                return $aResultError;
-
-            $oSubscription->plan = $aItem['name'];
-            $oSubscription->prorate = $this->_bProrate;
-            $oSubscription = $oSubscription->save();
-            if(strcmp($oSubscription->plan->id, $aItem['name']) !== 0)
-                return $aResultError;
-
-            $sItems = $this->_oModule->_oConfig->descriptorA2S(array($iVendorId, $iModuleId, $aItem['id'], $iItemCount));
-            if(!$this->_oModule->_oDb->updateOrderPending($iPendingId, array('items' => $sItems)))
-                return $aResultError;
-
-            $this->_oModule->callReregisterSubscriptionItem($iModuleId, array($aPending['client_id'], $aPending['seller_id'], $iItemId, $aItem['id'], $aPending['order']));
-
-            return array('code' => 0, 'message' => _t('_bx_payment_strp_msg_details_changed'));
-        }
-
-        return $this->_oModule->_oTemplate->parseHtmlByName('strp_details_change_recuring.html', array(
-            'object' => $this->_oModule->_oConfig->getJsObject('subscription'),
-            'form' => $oForm->getCode(),
-            'form_id' => $oForm->aFormAttrs['id'],
-        ));
-    }
-
-    public function getBillingRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
-    {
-        $aCard = $this->_retrieveCard($sCustomerId)->jsonSerialize();
-
-        return $this->_oModule->_oTemplate->parseHtmlByName('strp_billing_recurring.html', array(
-            'brand' => $aCard['brand'],
-            'origin' => $aCard['country'],
-            'type' => $aCard['funding'],
-            'number' => _t('_bx_payment_strp_txt_card_number_mask', $aCard['last4']),
-            'expires' => _t('_bx_payment_strp_txt_card_expires_mask', $aCard['exp_month'], $aCard['exp_year']),
-            'cvc' => _t(strcmp($aCard['cvc_check'], 'pass') === 0 ? '_bx_payment_strp_txt_card_cvc_passed' : '_bx_payment_txt_none'),
-        ));
-    }
-
-    public function changeBillingRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
-    {
-        $oForm = BxDolForm::getObjectInstance($this->_sFormCard, $this->_sFormDisplayCardAdd, $this->_oModule->_oTemplate);
-        $oForm->aFormAttrs['id'] = $this->_oModule->_oConfig->getHtmlIds('subscription', 'form_subscription_change_billing');
-        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oModule->_oConfig->getBaseUri() . 'subscription_change_billing/' . $iPendingId;
-
-        $oForm->initChecker();
-        if($oForm->isSubmittedAndValid()) {
-            $aResultError = array('code' => 1, 'message' => _t('_bx_payment_strp_err_billing_changed'));
-
-            $aMatch = array();
-            if(!preg_match('/^([0-9]{2})\D([0-9]{4})$/i', $oForm->getCleanValue('card_expire'), $aMatch))
-                return $aResultError;
-
-            list($iMonth, $iYear) = array_slice($aMatch, 1);
-
-        	$aToken = $this->_createToken(array(
-                'number' => $oForm->getCleanValue('card_number'),
-                'exp_month' => $iMonth,
-                'exp_year' => $iYear,
-                'cvc' => $oForm->getCleanValue('card_cvv')
-            ));
-            if(empty($aToken) || !is_array($aToken))
-                return $aResultError;
-
-            $aCard = $this->_createCard(BX_PAYMENT_TYPE_RECURRING, $sCustomerId, $aToken['id']);
-            if(empty($aCard) || !is_array($aCard))
-                return $aResultError;
-
-            $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
-            if($oCustomer === false)
-                return $aResultError;
-
-            $oCustomer->default_source = $aCard['id'];
-            $oCustomer->save();
-
-            return array('code' => 0, 'message' => _t('_bx_payment_strp_msg_billing_changed'));
-        }
-
-        return $this->_oModule->_oTemplate->parseHtmlByName('strp_billing_change_recuring.html', array(
-            'object' => $this->_oModule->_oConfig->getJsObject('subscription'),
-            'form' => $oForm->getCode(),
-            'form_id' => $oForm->aFormAttrs['id'],
-        ));
-    }
-
-    public function cancelRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
-    {
-        $oSubscription = $this->_cancelSubscription($sCustomerId, $sSubscriptionId);
-        if($oSubscription === false)
-            return false;
-
-        return true;
-    }
-
     public function getCheckoutParamsSingle($aParams, &$oGrid)
     {
     	if(bx_get('token') !== false)
@@ -443,39 +159,6 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
     	return $aParams;
     }
 
-    public function getSubscription($iPendingId, $sCustomerId, $sSubscriptionId)
-    {
-        $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
-        if($oSubscription === false)
-            return array();
-
-        $aSubscription = $oSubscription->jsonSerialize();
-        if(empty($aSubscription) || !is_array($aSubscription))
-            return array();
-
-        $sStatus = isset($this->_aSbsStatuses[$aSubscription['status']]) ? $this->_aSbsStatuses[$aSubscription['status']] : BX_PAYMENT_SBS_STATUS_UNKNOWN;
-
-        return array(
-            'status' => $sStatus,
-            'created' => $aSubscription['created'],
-            'started' => !empty($aSubscription['start']) ? $aSubscription['start'] : 0,
-            'trial_start' => !empty($aSubscription['trial_start']) ? $aSubscription['trial_start'] : 0,
-            'trial_end' => !empty($aSubscription['trial_end']) ? $aSubscription['trial_end'] : 0,
-            'cperiod_start' => !empty($aSubscription['current_period_start']) ? $aSubscription['current_period_start'] : 0,
-            'cperiod_end' => !empty($aSubscription['current_period_end']) ? $aSubscription['current_period_end'] : 0,
-        );
-    }
-
-    protected function _getPublicKey()
-    {
-        return $this->_iMode == STRP_MODE_LIVE ? $this->getOption('live_pub_key') : $this->getOption('test_pub_key');
-    }
-
-    protected function _getSecretKey()
-    {
-        return $this->_iMode == STRP_MODE_LIVE ? $this->getOption('live_sec_key') : $this->getOption('test_sec_key');
-    }
-
     protected function _createToken($aCard)
     {
         try {
@@ -512,47 +195,6 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         return $this->_oCustomer->jsonSerialize();
     }
 
-    protected function _retrieveCustomer($sType, $sId)
-    {
-        $oCustomer = null;
-        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_customer', 0, false, array(
-            'type' => $sType,
-            'customer_id' => &$sId,
-            'customer_object' => &$oCustomer
-        ));
-
-        try {
-            if(empty($oCustomer))
-                $oCustomer = \Stripe\Customer::retrieve($sId);
-        }
-        catch (Exception $oException) {
-            return $this->_processException('Retrieve Customer Error: ', $oException);
-        }
-
-        return $oCustomer;
-    }
-
-    protected function _retrieveProduct($sId)
-    {
-        $oProduct = null;
-        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_product', 0, false, array(
-            'product_id' => &$sId,
-            'product_object' => &$oProduct
-        ));
-
-        if(!empty($oProduct))
-            return $oProduct;
-
-        try {
-            $oProduct = \Stripe\Product::retrieve($sId);
-        }
-        catch (Exception $oException) {
-            return $this->_processException('Retrieve Product Error: ', $oException);
-        }
-
-        return $oProduct;
-    }
-
     protected function _createCharge($sToken, $iPendingId, &$aClient, &$aCartInfo)
     {
         if(empty($this->_oCustomer))
@@ -681,54 +323,6 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         );
     }
 
-    protected function _retrieveSubscription($sCustomerId, $sSubscriptionId)
-    {
-        try {
-            $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
-            $oSubscription = $oCustomer->subscriptions->retrieve($sSubscriptionId);
-        }
-        catch (Exception $oException) {
-            return $this->_processException('Retrieve Subscription Error: ', $oException);
-        }
-
-        return $oSubscription;
-    }
-
-    protected function _cancelSubscription($sCustomerId, $sSubscriptionId)
-    {
-        try {
-            $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
-            $oSubscription = $oSubscription->cancel();
-        }
-        catch (Exception $oException) {
-                return $this->_processException('Cancel Subscription Error: ', $oException);
-        }
-
-        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_cancel_subscription', 0, false, array(
-            'subscription_id' => $sSubscriptionId,
-            'subscription_object' => &$oSubscription
-        ));
-
-        return $oSubscription;
-    }
-
-    protected function _listPlans($iLimit = 100)
-    {
-        if($iLimit <= 0)
-            $iLimit = 1;
-        if($iLimit > 100)
-            $iLimit = 100;
-
-        try {
-            $oPlans = \Stripe\Plan::all(array('limit' => $iLimit));
-        }
-        catch (Exception $oException) {
-            return $this->_processException('List Plans Error: ', $oException);
-        }
-
-        return $oPlans;
-    }
-
     protected function _createCard($sType, $sCustomerId, $sToken)
     {
         try {
@@ -766,192 +360,8 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         return $oCoupon;
     }
 
-    protected function _retrieveEvent($sId) {
-        try {
-            $oEvent = \Stripe\Event::retrieve($sId);
-        }
-        catch (Exception $oException) {
-            return $this->_processException('Retrieve Event Error: ', $oException);
-        }
-
-        return $oEvent;
-    }
-
-    protected function _processEvent()
-    {
-        $sInput = @file_get_contents("php://input");
-        $aEvent = json_decode($sInput, true);
-        if(empty($aEvent) || !is_array($aEvent)) 
-            return 404;
-
-        $sType = $aEvent['type'];
-        if(!in_array($sType, array('invoice.payment_succeeded', 'charge.refunded', 'customer.subscription.deleted')))
-            return 200;
-
-        $this->log('Webhooks: ' . (!empty($sType) ? $sType : ''));
-        $this->log($aEvent);
-
-        $sMethod = '_processEvent' . bx_gen_method_name($sType, array('.', '_', '-'));
-    	if(!method_exists($this, $sMethod))
-            return 200;
-
-    	return $this->$sMethod($aEvent) ? 200 : 403;
-    }
-
-    protected function _processEventInvoicePaymentSucceeded(&$aEvent)
-    {
-        $mixedResult = $this->_getData($aEvent);
-        if($mixedResult === false)
-            return false;
-
-        list($aPending, $oCharge) = $mixedResult;
-        if(empty($aPending) || !is_array($aPending) || empty($oCharge))
-            return false;
-
-        $fChargeAmount = (float)$oCharge->amount / 100;
-        $sChargeCurrency = strtoupper($oCharge->currency);
-        if($this->_bCheckAmount && ((float)$aPending['amount'] != $fChargeAmount || strcasecmp($this->_oModule->_oConfig->getDefaultCurrencyCode(), $sChargeCurrency) !== 0))
-            return false;
-
-        if($aPending['type'] == BX_PAYMENT_TYPE_RECURRING)
-            $this->_oModule->getObjectSubscriptions()->prolong($aPending);
-
-        return $this->_oModule->registerPayment($aPending);
-    }
-
-    protected function _processEventChargeRefunded(&$aEvent)
-    {
-        $mixedResult = $this->_getData($aEvent);
-        if($mixedResult === false)
-            return false;
-
-        list($aPending) = $mixedResult;
-        if(empty($aPending) || !is_array($aPending))
-            return false;
-
-        return $this->_oModule->refundPayment($aPending);
-    }
-
-    protected function _processEventCustomerSubscriptionDeleted(&$aEvent)
-    {
-        $mixedResult = $this->_getData($aEvent);
-        if($mixedResult === false)
-            return false;
-
-        list($aPending) = $mixedResult;
-        if(empty($aPending) || !is_array($aPending))
-            return true;
-
-        return $this->_oModule->getObjectSubscriptions()->cancelLocal($aPending);
-    }
-
-    protected function _processException($sMessage, &$oException)
-    {
-        $aError = $oException->getJsonBody();
-
-        $sMessage = $aError['error']['message'];
-        if(empty($sMessage))
-            $sMessage = $oException->getMessage();
-
-        $this->log($sMessage . $aError['error']['message']);
-        $this->log($aError);
-
-        return false;
-    }
-
-    protected function _getData(&$aEvent, $bRetrieve = true)
-    {
-        if($bRetrieve)
-            $oEvent = $this->_retrieveEvent($aEvent['id']);
-        else 
-            $oEvent = \Stripe\Util\Util::convertToStripeObject($aEvent, array());
-
-        if(empty($oEvent))
-            return false;
-
-        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'order', 'order' => $oEvent->data->object->subscription));
-        $oCharge = $this->_retrieveCharge($oEvent->data->object->charge);
-
-        return array($aPending, $oCharge);
-    }
-
-    protected function _getDataChangeDetailsRecurring($iPendingId)
-    {
-        $aResult = array();
-
-        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
-        list($iVendorId, $iModuleId, $iItemId) = $this->_oModule->_oConfig->descriptorS2A($aPending['items']);
-        if(empty($iModuleId) || empty($iVendorId) || (int)$iVendorId != (int)$aPending['seller_id'])
-            return $aResult;
-
-        $aItems = $this->_oModule->callGetCartItems((int)$iModuleId, array($iVendorId));
-        if(empty($aItems) || !is_array($aItems))
-            return $aResult;
-
-        $oPlans = $this->_listPlans();
-        if($oPlans === false)
-            return $aResult;
-
-        $aPlans = $oPlans->jsonSerialize();
-        if(empty($aPlans) || !is_array($aPlans) || empty($aPlans['data']) || !is_array($aPlans['data']))
-            return $aResult;
-
-        $aPlans = $aPlans['data'];
-
-        $aPlanNames = array();
-        foreach($aPlans as $aPlan)
-            $aPlanNames[] = $aPlan['id'];
-        if(empty($aPlanNames) || !is_array($aPlanNames))
-            return $aResult;
-
-        foreach($aItems as $aItem) {
-            $fPrice = $this->_oModule->_oConfig->getPrice(BX_PAYMENT_TYPE_RECURRING, $aItem);
-            if($fPrice == 0 || (int)$aItem['id'] == (int)$iItemId || !in_array($aItem['name'], $aPlanNames))
-                continue;
-
-            $aResult[] = array('key' => $aItem['id'], 'value' => $aItem['title']);
-        }
-
-        return $aResult;
-    }
-
-    protected function _getVerificationCodeCharge($iVendorId, $iCustomerId, $fAmount, $sCurrency) {
-        return md5(implode('#-#', array(
-            (int)$iVendorId,
-            (int)$iCustomerId,
-            (float)$fAmount,
-            strtoupper($sCurrency)
-        )));
-    }
-
-    protected function _getVerificationCodeSubscription($iVendorId, $iCustomerId, $sSubscription, $sCurrency) {
-        return md5(implode('#-#', array(
-            (int)$iVendorId,
-            (int)$iCustomerId,
-            strtoupper($sSubscription),
-            strtoupper($sCurrency)
-        )));
-    }
-
-    protected function _getButton($sType, $iClientId, $iVendorId, $aParams = array())
-    {
-        list($sJsCode, $sJsMethod) = $this->_getButtonJs($sType, $iClientId, $iVendorId, $aParams);
-
-    	return $this->_oModule->_oTemplate->parseHtmlByName('strp_button_' . $sType . '.html', array(
-            'type' => $sType,
-            'caption' => _t($this->_sLangsPrefix . 'strp_txt_checkout_with_' . $sType, $this->_sCaption),  
-            'onclick' => $sJsMethod,
-            'js_object' => $this->_oModule->_oConfig->getJsObject($this->_sName),
-            'js_code' => $sJsCode,
-    	));
-    }
-
     protected function _getButtonJs($sType, $iClientId, $iVendorId, $aParams = array())
     {
-        $sJsObject = $this->_oModule->_oConfig->getJsObject($this->_sName);
-        if(isset($aParams['iModuleId'], $aParams['iSellerId'], $aParams['iItemId']))
-            $sJsObject .= '_' . md5($aParams['iModuleId'] . '-' . $aParams['iSellerId'] . '-' . $aParams['iItemId']);        
-
         $sClientEmail = '';
     	if(!empty($iClientId) && ($oClient = BxDolProfile::getInstance($iClientId)) !== false)
             $sClientEmail = $oClient->getAccountObject()->getEmail();
@@ -963,6 +373,7 @@ class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBas
         ));
 
         $sJsMethod = '';
+        $sJsObject = $this->getJsObject($aParams);
         switch($sType) {
             case BX_PAYMENT_TYPE_SINGLE:
                 $sJsMethod = $sJsObject . '.checkout(this)';

+ 621 - 0
modules/boonex/payment/classes/BxPaymentProviderStripeBasic.php

@@ -0,0 +1,621 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Payment Payment
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_PLUGINS . 'stripe/init.php');
+
+define('STRP_MODE_LIVE', 1);
+define('STRP_MODE_TEST', 2);
+
+class BxPaymentProviderStripeBasic extends BxBaseModPaymentProvider
+{
+    protected $_aIncludeJs;
+    protected $_aIncludeCss;
+
+    protected $_sFormDetails;
+    protected $_sFormDisplayDetailsEdit;
+
+    protected $_sFormCard;
+    protected $_sFormDisplayCardAdd;
+
+    protected $_iMode;
+    protected $_bCheckAmount;
+    protected $_bProrate;
+
+    protected $_oCustomer;
+
+
+    function __construct($aConfig)
+    {
+    	$this->MODULE = 'bx_payment';
+
+        parent::__construct($aConfig);
+
+        $this->_aSbsStatuses = array(
+            'incomplete' => BX_PAYMENT_SBS_STATUS_UNPAID, 
+            'incomplete_expired' => BX_PAYMENT_SBS_STATUS_UNPAID, 
+            'trialing' => BX_PAYMENT_SBS_STATUS_TRIAL, 
+            'active' => BX_PAYMENT_SBS_STATUS_ACTIVE, 
+            'past_due' => BX_PAYMENT_SBS_STATUS_UNPAID,
+            'unpaid' => BX_PAYMENT_SBS_STATUS_UNPAID,
+            'canceled' => BX_PAYMENT_SBS_STATUS_CANCELED,
+        );
+
+        $this->_sFormDetails = 'bx_payment_form_strp_details';
+        $this->_sFormDisplayDetailsEdit = 'bx_payment_form_strp_details_edit';
+
+        $this->_sFormCard = 'bx_payment_form_strp_card';
+        $this->_sFormDisplayCardAdd = 'bx_payment_form_strp_card_add';
+
+        $this->_bProrate = false;
+
+        $this->_aIncludeJs = array(
+            'main.js',
+        );
+
+        $this->_aIncludeCss = array(
+            'stripe.css'
+        );
+
+        $this->_oCustomer = null;
+
+        \Stripe\Stripe::setApiKey($this->_getSecretKey());
+    }
+
+    public function initOptions($aOptions)
+    {
+    	parent::initOptions($aOptions);
+
+    	$this->_iMode = (int)$this->getOption('mode');
+    	$this->_bCheckAmount = $this->getOption('check_amount') == 'on';
+    	$this->_bUseSsl = $this->getOption('ssl') == 'on';
+    }
+
+    public function addJsCss()
+    {
+    	if(!$this->isActive())
+    		return;
+
+        $this->_oModule->_oTemplate->addJs($this->_aIncludeJs);
+        $this->_oModule->_oTemplate->addCss($this->_aIncludeCss);
+    }
+
+    public function getJsObject($aParams = array())
+    {
+        $sJsObject = $this->_oModule->_oConfig->getJsObject($this->_sName);
+        if(isset($aParams['iModuleId'], $aParams['iSellerId'], $aParams['iItemId']))
+            $sJsObject .= '_' . md5($aParams['iModuleId'] . '-' . $aParams['iSellerId'] . '-' . $aParams['iItemId']);
+        
+        return $sJsObject;
+    }
+
+    public function notify()
+    {
+        $iResult = $this->_processEvent();
+        http_response_code($iResult);
+    }
+
+    public function getButtonSingle($iClientId, $iVendorId, $aParams = array())
+    {
+        $aItems = array();
+        $aCartInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_SINGLE, $iClientId, (int)$iVendorId, $aItems);
+        if(empty($aCartInfo) || !is_array($aCartInfo))
+            return '';
+
+    	return $this->_getButton(BX_PAYMENT_TYPE_SINGLE, $iClientId, $iVendorId, array_merge($aParams, array(
+            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aCartInfo['vendor_name']),
+            'sVendorCurrency' => $aCartInfo['vendor_currency_code'],
+            'sVendorIcon' => $aCartInfo['vendor_avatar'],
+    	)));
+    }
+
+    public function getButtonSingleJs($iClientId, $iVendorId, $aParams = array())
+    {
+        $aItems = array();
+        $aCartInfo = $this->_oModule->getObjectCart()->getInfo(BX_PAYMENT_TYPE_SINGLE, $iClientId, (int)$iVendorId, $aItems);
+        if(empty($aCartInfo) || !is_array($aCartInfo))
+            return '';
+
+    	return $this->_getButtonJs(BX_PAYMENT_TYPE_SINGLE, $iClientId, $iVendorId, array_merge($aParams, array(
+            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aCartInfo['vendor_name']),
+            'sVendorCurrency' => $aCartInfo['vendor_currency_code'],
+            'sVendorIcon' => $aCartInfo['vendor_avatar'],
+    	)));
+    }
+
+    public function getButtonRecurring($iClientId, $iVendorId, $aParams = array())
+    {
+    	$aVendor = $this->_oModule->getVendorInfo((int)$iVendorId);
+
+        return $this->_getButton(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iVendorId, array_merge($aParams, array(
+            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aVendor['name']),
+            'sVendorCurrency' => $aVendor['currency_code'],
+            'sVendorIcon' => $aVendor['avatar'],
+        )));
+    }
+    
+    public function getButtonRecurringJs($iClientId, $iVendorId, $aParams = array())
+    {
+    	$aVendor = $this->_oModule->getVendorInfo((int)$iVendorId);
+
+        return $this->_getButtonJs(BX_PAYMENT_TYPE_RECURRING, $iClientId, $iVendorId, array_merge($aParams, array(
+            'sVendorName' => _t($this->_sLangsPrefix . 'txt_payment_to', $aVendor['name']),
+            'sVendorCurrency' => $aVendor['currency_code'],
+            'sVendorIcon' => $aVendor['avatar'],
+        )));
+    }
+
+    public function getMenuItemsActionsRecurring($iClientId, $iVendorId, $aParams = array())
+    {
+        if(empty($aParams['order']))
+            return array();
+
+        $sJsObject = $this->_oModule->_oConfig->getJsObject(BX_PAYMENT_ORDERS_TYPE_SUBSCRIPTION);
+
+        $sPrefix = 'bx-payment-strp-';
+        return array(
+            array('id' => $sPrefix . 'details', 'name' => $sPrefix . 'details', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".getDetails(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_details')),
+            array('id' => $sPrefix . 'details_change', 'name' => $sPrefix . 'details_change', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changeDetails(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_details_change')),
+            array('id' => $sPrefix . 'billing', 'name' => $sPrefix . 'billing', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".getBilling(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_billing')),
+            array('id' => $sPrefix . 'billing_change', 'name' => $sPrefix . 'billing_change', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changeBilling(this, '" . $aParams['id'] . "')", 'target' => '_self', 'title' => _t('_bx_payment_strp_menu_item_title_billing_change')),
+        );
+    }
+
+    public function getDetailsRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
+    {
+        $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
+        if($oSubscription === false)
+            return '';
+
+        $aSubscription = $oSubscription->jsonSerialize();
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return '';
+
+        $sName = '';
+        if(!empty($aSubscription['plan']['name']))
+            $sName = $aSubscription['plan']['name'];
+        else if(!empty($aSubscription['plan']['product'])){
+            $oProduct = $this->_retrieveProduct($aSubscription['plan']['product']);
+            if($oProduct !== false) 
+                $sName = $oProduct->name;
+        }
+
+        $sNone = _t('_bx_payment_txt_none');
+        return $this->_oModule->_oTemplate->parseHtmlByName('strp_details_recurring.html', array(
+            'plan' => $sName,
+            'cost' => _t('_bx_payment_strp_txt_cost_mask', (int)$aSubscription['plan']['amount'] / 100, $aSubscription['plan']['currency'], $aSubscription['plan']['interval']),
+            'status' => $aSubscription['status'],
+            'created' => bx_time_js($aSubscription['created']),
+            'started' => !empty($aSubscription['start']) ? bx_time_js($aSubscription['start']) : $sNone,
+            'trial_start' => !empty($aSubscription['trial_start']) ? bx_time_js($aSubscription['trial_start']) : $sNone,
+            'trial_end' => !empty($aSubscription['trial_end']) ? bx_time_js($aSubscription['trial_end']) : $sNone,
+            'cperiod_start' => !empty($aSubscription['current_period_start']) ? bx_time_js($aSubscription['current_period_start']) : $sNone,
+            'cperiod_end' => !empty($aSubscription['current_period_end']) ? bx_time_js($aSubscription['current_period_end']) : $sNone,
+        ));
+    }
+
+    public function changeDetailsRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
+    {
+        $oForm = BxDolForm::getObjectInstance($this->_sFormDetails, $this->_sFormDisplayDetailsEdit, $this->_oModule->_oTemplate);
+        $oForm->aFormAttrs['id'] = $this->_oModule->_oConfig->getHtmlIds('subscription', 'form_subscription_change_details');
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oModule->_oConfig->getBaseUri() . 'subscription_change_details/' . $iPendingId;
+
+        $oForm->aInputs['item_id']['values'] = $this->_getDataChangeDetailsRecurring($iPendingId);
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $aResultError = array('code' => 1, 'message' => _t('_bx_payment_strp_err_details_changed'));
+
+            $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
+            list($iVendorId, $iModuleId, $iItemId, $iItemCount) = $this->_oModule->_oConfig->descriptorS2A($aPending['items']);
+
+            $aItem = $this->_oModule->callGetCartItem($iModuleId, array($oForm->getCleanValue('item_id')));
+    		if(empty($aItem) || !is_array($aItem))
+    			return $aResultError;
+
+            $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
+            if($oSubscription === false)
+                return $aResultError;
+
+            $oSubscription->plan = $aItem['name'];
+            $oSubscription->prorate = $this->_bProrate;
+            $oSubscription = $oSubscription->save();
+            if(strcmp($oSubscription->plan->id, $aItem['name']) !== 0)
+                return $aResultError;
+
+            $sItems = $this->_oModule->_oConfig->descriptorA2S(array($iVendorId, $iModuleId, $aItem['id'], $iItemCount));
+            if(!$this->_oModule->_oDb->updateOrderPending($iPendingId, array('items' => $sItems)))
+                return $aResultError;
+
+            $this->_oModule->callReregisterSubscriptionItem($iModuleId, array($aPending['client_id'], $aPending['seller_id'], $iItemId, $aItem['id'], $aPending['order']));
+
+            return array('code' => 0, 'message' => _t('_bx_payment_strp_msg_details_changed'));
+        }
+
+        return $this->_oModule->_oTemplate->parseHtmlByName('strp_details_change_recuring.html', array(
+            'object' => $this->_oModule->_oConfig->getJsObject('subscription'),
+            'form' => $oForm->getCode(),
+            'form_id' => $oForm->aFormAttrs['id'],
+        ));
+    }
+
+    public function getBillingRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
+    {
+        $aCard = $this->_retrieveCard($sCustomerId)->jsonSerialize();
+
+        return $this->_oModule->_oTemplate->parseHtmlByName('strp_billing_recurring.html', array(
+            'brand' => $aCard['brand'],
+            'origin' => $aCard['country'],
+            'type' => $aCard['funding'],
+            'number' => _t('_bx_payment_strp_txt_card_number_mask', $aCard['last4']),
+            'expires' => _t('_bx_payment_strp_txt_card_expires_mask', $aCard['exp_month'], $aCard['exp_year']),
+            'cvc' => _t(strcmp($aCard['cvc_check'], 'pass') === 0 ? '_bx_payment_strp_txt_card_cvc_passed' : '_bx_payment_txt_none'),
+        ));
+    }
+
+    public function changeBillingRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
+    {
+        $oForm = BxDolForm::getObjectInstance($this->_sFormCard, $this->_sFormDisplayCardAdd, $this->_oModule->_oTemplate);
+        $oForm->aFormAttrs['id'] = $this->_oModule->_oConfig->getHtmlIds('subscription', 'form_subscription_change_billing');
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oModule->_oConfig->getBaseUri() . 'subscription_change_billing/' . $iPendingId;
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $aResultError = array('code' => 1, 'message' => _t('_bx_payment_strp_err_billing_changed'));
+
+            $aMatch = array();
+            if(!preg_match('/^([0-9]{2})\D([0-9]{4})$/i', $oForm->getCleanValue('card_expire'), $aMatch))
+                return $aResultError;
+
+            list($iMonth, $iYear) = array_slice($aMatch, 1);
+
+        	$aToken = $this->_createToken(array(
+                'number' => $oForm->getCleanValue('card_number'),
+                'exp_month' => $iMonth,
+                'exp_year' => $iYear,
+                'cvc' => $oForm->getCleanValue('card_cvv')
+            ));
+            if(empty($aToken) || !is_array($aToken))
+                return $aResultError;
+
+            $aCard = $this->_createCard(BX_PAYMENT_TYPE_RECURRING, $sCustomerId, $aToken['id']);
+            if(empty($aCard) || !is_array($aCard))
+                return $aResultError;
+
+            $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
+            if($oCustomer === false)
+                return $aResultError;
+
+            $oCustomer->default_source = $aCard['id'];
+            $oCustomer->save();
+
+            return array('code' => 0, 'message' => _t('_bx_payment_strp_msg_billing_changed'));
+        }
+
+        return $this->_oModule->_oTemplate->parseHtmlByName('strp_billing_change_recuring.html', array(
+            'object' => $this->_oModule->_oConfig->getJsObject('subscription'),
+            'form' => $oForm->getCode(),
+            'form_id' => $oForm->aFormAttrs['id'],
+        ));
+    }
+
+    public function cancelRecurring($iPendingId, $sCustomerId, $sSubscriptionId)
+    {
+        $oSubscription = $this->_cancelSubscription($sCustomerId, $sSubscriptionId);
+        if($oSubscription === false)
+            return false;
+
+        return true;
+    }
+
+    public function getSubscription($iPendingId, $sCustomerId, $sSubscriptionId)
+    {
+        $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
+        if($oSubscription === false)
+            return array();
+
+        $aSubscription = $oSubscription->jsonSerialize();
+        if(empty($aSubscription) || !is_array($aSubscription))
+            return array();
+
+        $sStatus = isset($this->_aSbsStatuses[$aSubscription['status']]) ? $this->_aSbsStatuses[$aSubscription['status']] : BX_PAYMENT_SBS_STATUS_UNKNOWN;
+
+        return array(
+            'status' => $sStatus,
+            'created' => $aSubscription['created'],
+            'started' => !empty($aSubscription['start']) ? $aSubscription['start'] : 0,
+            'trial_start' => !empty($aSubscription['trial_start']) ? $aSubscription['trial_start'] : 0,
+            'trial_end' => !empty($aSubscription['trial_end']) ? $aSubscription['trial_end'] : 0,
+            'cperiod_start' => !empty($aSubscription['current_period_start']) ? $aSubscription['current_period_start'] : 0,
+            'cperiod_end' => !empty($aSubscription['current_period_end']) ? $aSubscription['current_period_end'] : 0,
+        );
+    }
+
+    protected function _getPublicKey()
+    {
+        return $this->_iMode == STRP_MODE_LIVE ? $this->getOption('live_pub_key') : $this->getOption('test_pub_key');
+    }
+
+    protected function _getSecretKey()
+    {
+        return $this->_iMode == STRP_MODE_LIVE ? $this->getOption('live_sec_key') : $this->getOption('test_sec_key');
+    }
+
+    protected function _retrieveCustomer($sType, $sId)
+    {
+        $oCustomer = null;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_customer', 0, false, array(
+            'type' => $sType,
+            'customer_id' => &$sId,
+            'customer_object' => &$oCustomer
+        ));
+
+        try {
+            if(empty($oCustomer))
+                $oCustomer = \Stripe\Customer::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Customer Error: ', $oException);
+        }
+
+        return $oCustomer;
+    }
+
+    protected function _retrieveSubscription($sCustomerId, $sSubscriptionId)
+    {
+        try {
+            $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
+            $oSubscription = $oCustomer->subscriptions->retrieve($sSubscriptionId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Subscription Error: ', $oException);
+        }
+
+        return $oSubscription;
+    }
+
+    protected function _cancelSubscription($sCustomerId, $sSubscriptionId)
+    {
+        try {
+            $oSubscription = $this->_retrieveSubscription($sCustomerId, $sSubscriptionId);
+            $oSubscription = $oSubscription->cancel();
+        }
+        catch (Exception $oException) {
+                return $this->_processException('Cancel Subscription Error: ', $oException);
+        }
+
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_cancel_subscription', 0, false, array(
+            'subscription_id' => $sSubscriptionId,
+            'subscription_object' => &$oSubscription
+        ));
+
+        return $oSubscription;
+    }
+
+    protected function _retrieveProduct($sId)
+    {
+        $oProduct = null;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_product', 0, false, array(
+            'product_id' => &$sId,
+            'product_object' => &$oProduct
+        ));
+
+        if(!empty($oProduct))
+            return $oProduct;
+
+        try {
+            $oProduct = \Stripe\Product::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Product Error: ', $oException);
+        }
+
+        return $oProduct;
+    }
+
+    protected function _listPlans($iLimit = 100)
+    {
+        if($iLimit <= 0)
+            $iLimit = 1;
+        if($iLimit > 100)
+            $iLimit = 100;
+
+        try {
+            $oPlans = \Stripe\Plan::all(array('limit' => $iLimit));
+        }
+        catch (Exception $oException) {
+            return $this->_processException('List Plans Error: ', $oException);
+        }
+
+        return $oPlans;
+    }
+
+    protected function _retrieveEvent($sId) {
+        try {
+            $oEvent = \Stripe\Event::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Event Error: ', $oException);
+        }
+
+        return $oEvent;
+    }
+
+    protected function _processEvent()
+    {
+        $sInput = @file_get_contents("php://input");
+        $aEvent = json_decode($sInput, true);
+        if(empty($aEvent) || !is_array($aEvent)) 
+            return 404;
+
+        $sType = $aEvent['type'];
+        if(!in_array($sType, array('invoice.payment_succeeded', 'charge.refunded', 'customer.subscription.deleted')))
+            return 200;
+
+        $this->log('Webhooks: ' . (!empty($sType) ? $sType : ''));
+        $this->log($aEvent);
+
+        $sMethod = '_processEvent' . bx_gen_method_name($sType, array('.', '_', '-'));
+    	if(!method_exists($this, $sMethod))
+            return 200;
+
+    	return $this->$sMethod($aEvent) ? 200 : 403;
+    }
+
+    protected function _processEventInvoicePaymentSucceeded(&$aEvent)
+    {
+        $mixedResult = $this->_getData($aEvent);
+        if($mixedResult === false)
+            return false;
+
+        list($aPending, $oCharge) = $mixedResult;
+        if(empty($aPending) || !is_array($aPending) || empty($oCharge))
+            return false;
+
+        $fChargeAmount = (float)$oCharge->amount / 100;
+        $sChargeCurrency = strtoupper($oCharge->currency);
+        if($this->_bCheckAmount && ((float)$aPending['amount'] != $fChargeAmount || strcasecmp($this->_oModule->_oConfig->getDefaultCurrencyCode(), $sChargeCurrency) !== 0))
+            return false;
+
+        if($aPending['type'] == BX_PAYMENT_TYPE_RECURRING)
+            $this->_oModule->getObjectSubscriptions()->prolong($aPending);
+
+        return $this->_oModule->registerPayment($aPending);
+    }
+
+    protected function _processEventChargeRefunded(&$aEvent)
+    {
+        $mixedResult = $this->_getData($aEvent);
+        if($mixedResult === false)
+            return false;
+
+        list($aPending) = $mixedResult;
+        if(empty($aPending) || !is_array($aPending))
+            return false;
+
+        return $this->_oModule->refundPayment($aPending);
+    }
+
+    protected function _processEventCustomerSubscriptionDeleted(&$aEvent)
+    {
+        $mixedResult = $this->_getData($aEvent);
+        if($mixedResult === false)
+            return false;
+
+        list($aPending) = $mixedResult;
+        if(empty($aPending) || !is_array($aPending))
+            return true;
+
+        return $this->_oModule->getObjectSubscriptions()->cancelLocal($aPending);
+    }
+
+    protected function _processException($sMessage, &$oException)
+    {
+        $aError = $oException->getJsonBody();
+
+        $sMessage = $aError['error']['message'];
+        if(empty($sMessage))
+            $sMessage = $oException->getMessage();
+
+        $this->log($sMessage . $aError['error']['message']);
+        $this->log($aError);
+
+        return false;
+    }
+
+    protected function _getData(&$aEvent, $bRetrieve = true)
+    {
+        if($bRetrieve)
+            $oEvent = $this->_retrieveEvent($aEvent['id']);
+        else 
+            $oEvent = \Stripe\Util\Util::convertToStripeObject($aEvent, array());
+
+        if(empty($oEvent))
+            return false;
+
+        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'order', 'order' => $oEvent->data->object->subscription));
+        $oCharge = $this->_retrieveCharge($oEvent->data->object->charge);
+
+        return array($aPending, $oCharge);
+    }
+
+    protected function _getDataChangeDetailsRecurring($iPendingId)
+    {
+        $aResult = array();
+
+        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
+        list($iVendorId, $iModuleId, $iItemId) = $this->_oModule->_oConfig->descriptorS2A($aPending['items']);
+        if(empty($iModuleId) || empty($iVendorId) || (int)$iVendorId != (int)$aPending['seller_id'])
+            return $aResult;
+
+        $aItems = $this->_oModule->callGetCartItems((int)$iModuleId, array($iVendorId));
+        if(empty($aItems) || !is_array($aItems))
+            return $aResult;
+
+        $oPlans = $this->_listPlans();
+        if($oPlans === false)
+            return $aResult;
+
+        $aPlans = $oPlans->jsonSerialize();
+        if(empty($aPlans) || !is_array($aPlans) || empty($aPlans['data']) || !is_array($aPlans['data']))
+            return $aResult;
+
+        $aPlans = $aPlans['data'];
+
+        $aPlanNames = array();
+        foreach($aPlans as $aPlan)
+            $aPlanNames[] = $aPlan['id'];
+        if(empty($aPlanNames) || !is_array($aPlanNames))
+            return $aResult;
+
+        foreach($aItems as $aItem) {
+            $fPrice = $this->_oModule->_oConfig->getPrice(BX_PAYMENT_TYPE_RECURRING, $aItem);
+            if($fPrice == 0 || (int)$aItem['id'] == (int)$iItemId || !in_array($aItem['name'], $aPlanNames))
+                continue;
+
+            $aResult[] = array('key' => $aItem['id'], 'value' => $aItem['title']);
+        }
+
+        return $aResult;
+    }
+
+    protected function _getVerificationCodeCharge($iVendorId, $iCustomerId, $fAmount, $sCurrency) {
+        return md5(implode('#-#', array(
+            (int)$iVendorId,
+            (int)$iCustomerId,
+            (float)$fAmount,
+            strtoupper($sCurrency)
+        )));
+    }
+
+    protected function _getVerificationCodeSubscription($iVendorId, $iCustomerId, $sSubscription, $sCurrency) {
+        return md5(implode('#-#', array(
+            (int)$iVendorId,
+            (int)$iCustomerId,
+            strtoupper($sSubscription),
+            strtoupper($sCurrency)
+        )));
+    }
+
+    protected function _getButton($sType, $iClientId, $iVendorId, $aParams = array())
+    {
+        list($sJsCode, $sJsMethod) = $this->_getButtonJs($sType, $iClientId, $iVendorId, $aParams);
+
+    	return $this->_oModule->_oTemplate->parseHtmlByName('strp_button_' . $sType . '.html', array(
+            'type' => $sType,
+            'caption' => _t($this->_sLangsPrefix . 'strp_txt_checkout_with_' . $sType, $this->_sCaption),  
+            'onclick' => $sJsMethod,
+            'js_object' => $this->_oModule->_oConfig->getJsObject($this->_sName),
+            'js_code' => $sJsCode,
+    	));
+    }
+}
+
+/** @} */

+ 407 - 0
modules/boonex/payment/classes/BxPaymentProviderStripeV3.php

@@ -0,0 +1,407 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Payment Payment
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once('BxPaymentProviderStripeBasic.php');
+
+class BxPaymentProviderStripeV3 extends BxPaymentProviderStripeBasic implements iBxBaseModPaymentProvider
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+
+        $this->_aIncludeJs = array(
+            'https://js.stripe.com/v3/',
+            'stripe_v3.js'
+        );
+    }
+
+    public function actionGetSessionRecurring()
+    {
+        $aClient = $this->_oModule->getProfileInfo();
+
+        $aParams = array(
+            'seller_id' => (int)bx_get('seller_id'),
+            'seller_provider' => bx_process_input(bx_get('seller_provider')),
+            'module_id' => (int)bx_get('module_id'),
+            'item_id' => (int)bx_get('item_id'),
+            'item_count' => (int)bx_get('item_count'),
+            'item_addons' => bx_get('item_addons'),
+            'redirect' => bx_process_input(bx_get('redirect')),
+            'custom' => bx_process_input(bx_get('custom')),
+        );
+
+        $aItems = array($this->_oModule->_oConfig->descriptorA2S(array(
+            'seller_id' => $aParams['seller_id'],
+            'module_id' => $aParams['module_id'],
+            'item_id' => $aParams['item_id'],
+            'item_count' => $aParams['item_count'],
+        )));
+
+        $oCart = $this->_oModule->getObjectCart();
+        $aCartInfo = $oCart->getInfo(BX_PAYMENT_TYPE_RECURRING, $aClient['id'], $aParams['seller_id'], $aItems);
+        if(empty($aCartInfo) || !is_array($aCartInfo))
+            return echoJson(array('msg' => _t('_bx_payment_err_nothing_selected')));
+
+        $aItem = reset($aCartInfo['items']);
+
+        $aSessionParams = array(
+            'cancel_url' => $aItem['url'],
+            'success_url' => bx_append_url_params($this->_oModule->_oConfig->getUrl('URL_SUBSCRIBE_JSON'), array_merge($aParams, array(
+                'session_id' => '{CHECKOUT_SESSION_ID}'
+            )))
+        );
+
+        $sSessionId = $this->_createSession(BX_PAYMENT_TYPE_RECURRING, $aSessionParams, $aClient, $aCartInfo);
+        if($sSessionId === false)
+            return echoJson(array('msg' => _t('_bx_payment_err_cannot_perform')));
+
+        $sJsObject = $this->getJsObject(array(
+            'iModuleId' => $aParams['module_id'],
+            'iSellerId' => $aParams['seller_id'],
+            'iItemId' => $aParams['item_id']
+        ));
+
+        return echoJson(array(
+            'code' => 0, 
+            'session_id' => $sSessionId,
+            'eval' => $sJsObject . '.onSubscribe(oData);'
+        ));
+    }
+
+    public function initializeCheckout($iPendingId, $aCartInfo, $sRedirect = '')
+    {
+        $sSessionId = bx_process_input(bx_get('session_id'));
+
+    	if(empty($aCartInfo['items']) || !is_array($aCartInfo['items']))
+            return $this->_sLangsPrefix . 'err_empty_items';
+
+        $aClient = $this->_oModule->getProfileInfo();
+        $aVendor = $this->_oModule->getProfileInfo($aCartInfo['vendor_id']);
+
+        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
+        if(!empty($aPending['order']) || !empty($aPending['error_code']) || !empty($aPending['error_msg']) || (int)$aPending['processed'] != 0)
+            return $this->_sLangsPrefix . 'err_already_processed';
+
+        switch($aPending['type']) {
+            case BX_PAYMENT_TYPE_SINGLE:
+                $mixedResult = $this->_getSession(BX_PAYMENT_TYPE_SINGLE, $sSessionId);
+                if($mixedResult === false || $mixedResult['status'] != 'paid')
+                    return $this->_sLangsPrefix . 'err_cannot_perform';
+
+                header("Location: " . $this->getReturnDataUrl($aVendor['id'], array(
+                    'order_id' => $mixedResult['order_id'],
+                    'customer_id' => $mixedResult['customer_id'], 
+                    'pending_id' => $aPending['id'],
+                    'redirect' => $sRedirect
+                )));
+                exit;
+
+            case BX_PAYMENT_TYPE_RECURRING:
+                $mixedResult = $this->_getSession(BX_PAYMENT_TYPE_RECURRING, $sSessionId);
+                if($mixedResult === false)
+                    return $this->_sLangsPrefix . 'err_cannot_perform';
+
+                header("Location: " . $this->getReturnDataUrl($aVendor['id'], array(
+                    'order_id' => $mixedResult['order_id'],
+                    'customer_id' => $mixedResult['customer_id'],
+                    'pending_id' => $aPending['id'],
+                    'redirect' => $sRedirect
+                )));
+                exit;
+        }
+    }
+
+    public function finalizeCheckout(&$aData)
+    {
+    	$sOrderId = bx_process_input($aData['order_id']);
+    	$sCustomerId = bx_process_input($aData['customer_id']);
+        $iPendingId = bx_process_input($aData['pending_id'], BX_DATA_INT);
+        if(empty($iPendingId))
+            return array('code' => 1, 'message' => $this->_sLangsPrefix . 'err_wrong_data');
+
+        $sRedirect = bx_process_input($aData['redirect']);
+
+        $aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'id', 'id' => $iPendingId));
+        if(!empty($aPending['order']) || !empty($aPending['error_code']) || !empty($aPending['error_msg']) || (int)$aPending['processed'] != 0)
+            return array('code' => 3, 'message' => $this->_sLangsPrefix . 'err_already_processed');
+
+        $aResult = array(
+            'code' => BX_PAYMENT_RESULT_SUCCESS,
+            'message' => '',
+            'pending_id' => $iPendingId,
+            'customer_id' => '',
+            'subscription_id' => '',
+            'client_name' => '',
+            'client_email' => '',
+            'paid' => false,
+            'trial' => false,
+            'redirect' => $sRedirect
+        );
+
+        switch($aPending['type']) {
+            case BX_PAYMENT_TYPE_SINGLE:
+                $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_SINGLE, $sCustomerId);
+                $oPaymentIntent = $this->_retrievePaymentIntent($sOrderId);
+                if($oCustomer === false || $oPaymentIntent === false)
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aCustomer = $oCustomer->jsonSerialize();
+                $aPaymentIntent = $oPaymentIntent->jsonSerialize();
+                if(empty($aCustomer) || !is_array($aCustomer) || empty($aPaymentIntent) || !is_array($aPaymentIntent))
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aResult = array_merge($aResult, array(
+                    'message' => $this->_sLangsPrefix . 'strp_msg_charged',
+                    'client_email' => $aCustomer['email'],
+                    'paid' => $aPaymentIntent['status'] == 'succeeded'
+                ));
+                break;
+
+            case BX_PAYMENT_TYPE_RECURRING:
+                $oCustomer = $this->_retrieveCustomer(BX_PAYMENT_TYPE_RECURRING, $sCustomerId);
+                $oSubscription = $this->_retrieveSubscription($sCustomerId, $sOrderId);
+                if($oCustomer === false || $oSubscription === false)
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aCustomer = $oCustomer->jsonSerialize();
+                $aSubscription = $oSubscription->jsonSerialize();
+                if(empty($aCustomer) || !is_array($aCustomer) || empty($aSubscription) || !is_array($aSubscription))
+                    return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+                $aResult = array_merge($aResult, array(
+                    'message' => $this->_sLangsPrefix . 'strp_msg_subscribed',
+                    'customer_id' => $sCustomerId,
+                    'subscription_id' => $sOrderId,
+                    'client_email' => $aCustomer['email'],
+                    'paid' => $this->isSubscriptionStatus(BX_PAYMENT_SBS_STATUS_ACTIVE, $aSubscription),
+                    'trial' => $this->isSubscriptionStatus(BX_PAYMENT_SBS_STATUS_TRIAL, $aSubscription)
+                ));
+                break;
+        }
+
+        //--- Update pending transaction ---//
+        $this->_oModule->_oDb->updateOrderPending($iPendingId, array(
+            'order' => $sOrderId,
+            'error_code' => $aResult['code'],
+            'error_msg' => _t($aResult['message'])
+        ));
+
+        return $aResult;
+    }
+
+    public function getButtonSingle($iClientId, $iVendorId, $aParams = array())
+    {
+        if(!isset($aParams['sAction']))
+            return '';
+
+        return $aParams['sAction'] . parent::getButtonSingle($iClientId, $iVendorId, $aParams);
+    }
+
+    public function overwriteCheckoutParamsSingle($aParams, &$oGrid)
+    {
+        $aClient = $this->_oModule->getProfileInfo();
+
+        $oCart = $this->_oModule->getObjectCart();
+        $aCartInfo = $oCart->getInfo(BX_PAYMENT_TYPE_SINGLE, $aClient['id'], $aParams['seller_id'], $aParams['items']);
+        if(empty($aCartInfo) || !is_array($aCartInfo))
+            return array('msg' => _t('_bx_payment_err_nothing_selected'));
+
+        $aSessionParams = array(
+            'cancel_url' => $oCart->serviceGetCartUrl($aParams['seller_id']),
+            'success_url' => bx_append_url_params($this->_oModule->_oConfig->getUrl('URL_CART_CHECKOUT'), array_merge($aParams, array(
+                'session_id' => '{CHECKOUT_SESSION_ID}'
+            )))
+        );
+
+        $sSessionId = $this->_createSession(BX_PAYMENT_TYPE_SINGLE, $aSessionParams, $aClient, $aCartInfo);
+        if($sSessionId === false)
+            return array('msg' => _t('_bx_payment_err_cannot_perform'));
+
+        return array(
+            'eval' => $this->_oModule->_oConfig->getJsObject($this->_sName) . '.onCartCheckout(oData);', 
+            'session_id' => $sSessionId
+        );
+    }
+
+    protected function _createSession($sType, $aParams, &$aClient, &$aCartInfo)
+    {
+        $fAmount = 100 * (float)$aCartInfo['items_price'];
+
+        $aLineItems = array();
+        foreach($aCartInfo['items'] as $aItem) {
+            $aLineItems[] = array(
+                'price' => $aItem['name'],
+                'quantity' => $aItem['quantity'],
+            );
+        }
+
+        $oSession = null;
+        $aSession = array(
+            'payment_method_types' => ['card'],
+            'customer_email' => !empty($aClient['email']) ? $aClient['email'] : '',
+            'line_items' => $aLineItems,
+            'mode' => '',
+            'success_url' => $aParams['success_url'],
+            'cancel_url' => $aParams['cancel_url'],
+            'metadata' => array(
+                'vendor' => $aCartInfo['vendor_id'],
+                'client' => $aClient['id'],
+                'verification' => $this->_getVerificationCodeCharge($aCartInfo['vendor_id'], $aClient['id'], $fAmount, $aCartInfo['vendor_currency_code'])
+            )
+        );
+
+        switch($sType) {
+            case BX_PAYMENT_TYPE_SINGLE:
+                $aSession['mode'] = 'payment';
+                break;
+            
+            case BX_PAYMENT_TYPE_RECURRING:
+                $aSession['mode'] = 'subscription';
+                break;
+        }
+
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_create_session', 0, false, array(
+            'session_object' => &$oSession, 
+            'session_params' => &$aSession
+        ));
+
+        try {
+            if(empty($oSession))
+                $oSession = \Stripe\Checkout\Session::create($aSession);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Create Session Error: ', $oException);
+        }
+
+        $aResult = $oSession->jsonSerialize();
+        if(empty($aResult) || !is_array($aResult))
+            return false;
+
+        $aMetadata = $aResult['metadata'];
+        if(empty($aMetadata['verification']) || $aMetadata['verification'] != $this->_getVerificationCodeCharge($aCartInfo['vendor_id'], $aClient['id'], (float)$aResult['amount_total'], $aResult['currency']))
+            return false;
+
+        return $aResult['id'];
+    }
+
+    protected function _getSession($sType, $sId)
+    {
+        $oSession = $this->_retrieveSession($sId);
+        if($oSession === false)
+            return array();
+
+        $aSession = $oSession->jsonSerialize();
+        if(empty($aSession) || !is_array($aSession))
+            return array();
+
+        $aResult = array(
+            'id' => $aSession['id'],
+            'order_id' => $aSession['payment_intent'],
+            'customer_id' => $aSession['customer'],
+            'status' => $aSession['payment_status']
+        );
+
+        switch($sType) {
+            case BX_PAYMENT_TYPE_SINGLE:
+                $aResult['order_id'] = $aSession['payment_intent'];
+                break;
+
+            case BX_PAYMENT_TYPE_RECURRING:
+                $aResult['order_id'] = $aSession['subscription'];
+                break;
+        }
+
+        return $aResult;
+    }
+
+    protected function _retrieveSession($sId) {
+        $oSession = null;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_session', 0, false, array(
+            'session_id' => &$sId,
+            'session_object' => &$oSession
+        ));
+
+        try {
+            if(empty($oSession))
+                $oSession = \Stripe\Checkout\Session::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Session Error: ', $oException);
+        }
+
+        return $oSession;
+    }
+    
+    protected function _retrievePaymentIntent($sId) {
+        $oPaymentIntent = null;
+        bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_retrieve_payment_intent', 0, false, array(
+            'payment_intent_id' => &$sId,
+            'payment_intent_object' => &$oPaymentIntent
+        ));
+
+        try {
+            if(empty($oPaymentIntent))
+                $oPaymentIntent = \Stripe\PaymentIntent::retrieve($sId);
+        }
+        catch (Exception $oException) {
+            return $this->_processException('Retrieve Payment Intent Error: ', $oException);
+        }
+
+        return $oPaymentIntent;
+    }
+
+    protected function _getButton($sType, $iClientId, $iVendorId, $aParams = array())
+    {
+        list($sJsCode, $sJsMethod) = $this->_getButtonJs($sType, $iClientId, $iVendorId, $aParams);
+
+    	if($sType == BX_PAYMENT_TYPE_SINGLE)
+            return $sJsCode;
+
+        return parent::_getButton($sType, $iClientId, $iVendorId);
+    }
+
+    protected function _getButtonJs($sType, $iClientId, $iVendorId, $aParams = array())
+    {
+        $sClientEmail = '';
+    	if(!empty($iClientId) && ($oClient = BxDolProfile::getInstance($iClientId)) !== false)
+            $sClientEmail = $oClient->getAccountObject()->getEmail();
+
+        $sPublicKey = '';
+    	bx_alert($this->_oModule->_oConfig->getName(), $this->_sName . '_get_button', 0, $iClientId, array(
+            'type' => &$sType, 
+            'public_key' => &$sPublicKey
+        ));
+
+        $sJsMethod = '';
+        $sJsObject = $this->getJsObject($aParams);
+        switch($sType) {
+            case BX_PAYMENT_TYPE_SINGLE:
+                $sJsMethod = 'void(0)';
+                break;
+
+            case BX_PAYMENT_TYPE_RECURRING:
+                $sJsMethod = $sJsObject . '.subscribe(this)';
+                break;
+        }
+
+        return array($this->_oModule->_oTemplate->getJsCode($this->_sName, array_merge(array(
+            'js_object' => $sJsObject,
+            'sProvider' => $this->_sName,
+            'sPublicKey' => !empty($sPublicKey) ? $sPublicKey : $this->_getPublicKey(),
+            'sVendorName' => '',
+            'sVendorCurrency' => '',
+            'sVendorIcon' => '',
+            'sClientEmail' => $sClientEmail,
+        ), $aParams)), $sJsMethod);
+    }
+}
+
+/** @} */

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

@@ -357,7 +357,7 @@ class BxPaymentSubscriptions extends BxBaseModPaymentSubscriptions
             'trial' => $iTrial,
             'date_add' => $iNow,
             'date_next' => $iNext,
-            'status' => $iTrial > 0 ? BX_PAYMENT_SBS_STATUS_TRIAL : BX_PAYMENT_SBS_STATUS_UNPAID
+            'status' => !empty($aParams['status']) ? $aParams['status'] : ($iTrial > 0 ? BX_PAYMENT_SBS_STATUS_TRIAL : BX_PAYMENT_SBS_STATUS_UNPAID)
         );
 
         if(!$this->_oModule->_oDb->insertSubscription($aSubscription))

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

@@ -493,6 +493,8 @@
 	<!-- Stripe payment provider -->
 	<string name="_bx_payment_strp_cpt"><![CDATA[Stripe]]></string>
 	<string name="_bx_payment_strp_dsc"><![CDATA[Stripe payment provider]]></string>
+        <string name="_bx_payment_strp_v3_cpt"><![CDATA[Stripe]]></string>
+	<string name="_bx_payment_strp_v3_dsc"><![CDATA[Stripe payment provider with 3D Secure authentication for Strong Customer Authentication (SCA) support]]></string>
 	<string name="_bx_payment_strp_active_cpt"><![CDATA[Active]]></string>
 	<string name="_bx_payment_strp_active_dsc"><![CDATA[Activate(Deactivate) provider. Note. It's recommended to use SSL for better payment processing.]]></string>
         <string name="_bx_payment_strp_hidden_cpt"><![CDATA[Hidden]]></string>
@@ -581,6 +583,8 @@
 
     <string name="_bx_payment_txt_name_stripe"><![CDATA[Stripe]]></string>
     <string name="_bx_payment_txt_cart_stripe"><![CDATA[Stripe]]></string>
+    <string name="_bx_payment_txt_name_stripe_v3"><![CDATA[Stripe]]></string>
+    <string name="_bx_payment_txt_cart_stripe_v3"><![CDATA[Stripe]]></string>
 
     <!-- _bx_payment_aina_mode_live payment provider -->
     <string name="_bx_payment_aina_cpt"><![CDATA[Apple In-App]]></string>

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

@@ -98,7 +98,7 @@ INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `titl
 -- MENU: account settings menu
 SET @iMoAccountSettings = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name`='sys_account_settings' AND `order` < 9999 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_settings', 'bx_payment', 'payment-details', '_bx_payment_menu_item_title_system_details', '_bx_payment_menu_item_title_details', 'page.php?i=payment-details', '', '_self', '', '', '', 2147483646, 1, 0, 1, @iMoAccountSettings + 1);
+('sys_account_settings', 'bx_payment', 'payment-details', '_bx_payment_menu_item_title_system_details', '_bx_payment_menu_item_title_details', 'page.php?i=payment-details', '', '_self', 'credit-card col-gray-dark', '', '', 2147483646, 1, 0, 1, @iMoAccountSettings + 1);
 
 
 -- ALERTS

+ 27 - 8
modules/boonex/payment/install/sql/install.sql

@@ -137,7 +137,7 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 -- PayPal payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `single_seller`, `active`, `order`, `class_name`) VALUES
-('paypal', '_bx_payment_pp_cpt', '_bx_payment_pp_dsc', 'pp_', 1, 1, 0, 1, 1, 1, 'BxPaymentProviderPayPal');
+('paypal', '_bx_payment_pp_cpt', '_bx_payment_pp_dsc', 'pp_', 1, 1, 0, 1, 1, 10, 'BxPaymentProviderPayPal');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -152,7 +152,7 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 -- 2Checkout payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('2checkout', '_bx_payment_2co_cpt', '_bx_payment_2co_dsc', '2co_', 1, 1, 0, 0, 2, 'BxPaymentProvider2Checkout');
+('2checkout', '_bx_payment_2co_cpt', '_bx_payment_2co_dsc', '2co_', 1, 1, 0, 0, 20, 'BxPaymentProvider2Checkout');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -166,7 +166,7 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 -- BitPay payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('bitpay', '_bx_payment_bp_cpt', '_bx_payment_bp_dsc', 'bp_', 0, 1, 0, 0, 3, 'BxPaymentProviderBitPay');
+('bitpay', '_bx_payment_bp_cpt', '_bx_payment_bp_dsc', 'bp_', 0, 1, 0, 0, 30, 'BxPaymentProviderBitPay');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -179,7 +179,7 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 -- Chargebee payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('chargebee', '_bx_payment_cbee_cpt', '_bx_payment_cbee_dsc', 'cbee_', 1, 0, 1, 1, 4, 'BxPaymentProviderChargebee');
+('chargebee', '_bx_payment_cbee_cpt', '_bx_payment_cbee_dsc', 'cbee_', 1, 0, 1, 1, 40, 'BxPaymentProviderChargebee');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -199,7 +199,7 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 -- Chargebee V3 payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('chargebee_v3', '_bx_payment_cbee_v3_cpt', '_bx_payment_cbee_v3_dsc', 'cbee_v3_', 1, 0, 1, 1, 5, 'BxPaymentProviderChargebeeV3');
+('chargebee_v3', '_bx_payment_cbee_v3_cpt', '_bx_payment_cbee_v3_dsc', 'cbee_v3_', 1, 0, 1, 1, 41, 'BxPaymentProviderChargebeeV3');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -219,7 +219,7 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 -- Recurly payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('recurly', '_bx_payment_rcrl_cpt', '_bx_payment_rcrl_dsc', 'rcrl_', 1, 0, 1, 0, 6, 'BxPaymentProviderRecurly');
+('recurly', '_bx_payment_rcrl_cpt', '_bx_payment_rcrl_dsc', 'rcrl_', 1, 0, 1, 0, 50, 'BxPaymentProviderRecurly');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -234,7 +234,7 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 
 -- Stripe payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('stripe', '_bx_payment_strp_cpt', '_bx_payment_strp_dsc', 'strp_', 1, 1, 1, 1, 7, 'BxPaymentProviderStripe');
+('stripe', '_bx_payment_strp_cpt', '_bx_payment_strp_dsc', 'strp_', 1, 1, 1, 0, 60, 'BxPaymentProviderStripe');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
@@ -251,9 +251,28 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 (@iProviderId, 'strp_expiration_email', 'text', '_bx_payment_strp_expiration_email_cpt', '', '', 'EmailOrEmpty', '', '_sys_form_account_input_email_error', 11),
 (@iProviderId, 'strp_notify_url', 'value', '_bx_payment_strp_notify_url_cpt', '', '', '', '', '', 12);
 
+-- Stripe V3 payment provider (with 3D Secure authentication for Strong Customer Authentication (SCA) support)
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
+('stripe_v3', '_bx_payment_strp_v3_cpt', '_bx_payment_strp_v3_dsc', 'strp_v3_', 1, 1, 1, 1, 61, 'BxPaymentProviderStripeV3');
+SET @iProviderId = LAST_INSERT_ID();
+
+INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES
+(@iProviderId, 'strp_v3_active', 'checkbox', '_bx_payment_strp_active_cpt', '_bx_payment_strp_active_dsc', '', '', '', '', 1),
+(@iProviderId, 'strp_v3_hidden', 'checkbox', '_bx_payment_strp_hidden_cpt', '_bx_payment_strp_hidden_dsc', '', '', '', '', 2),
+(@iProviderId, 'strp_v3_mode', 'select', '_bx_payment_strp_mode_cpt', '_bx_payment_strp_mode_dsc', '1|_bx_payment_strp_mode_live,2|_bx_payment_strp_mode_test', '', '', '', 3),
+(@iProviderId, 'strp_v3_live_pub_key', 'text', '_bx_payment_strp_live_pub_key_cpt', '_bx_payment_strp_live_pub_key_dsc', '', '', '', '', 4),
+(@iProviderId, 'strp_v3_live_sec_key', 'text', '_bx_payment_strp_live_sec_key_cpt', '_bx_payment_strp_live_sec_key_dsc', '', '', '', '', 5),
+(@iProviderId, 'strp_v3_test_pub_key', 'text', '_bx_payment_strp_test_pub_key_cpt', '_bx_payment_strp_test_pub_key_dsc', '', '', '', '', 6),
+(@iProviderId, 'strp_v3_test_sec_key', 'text', '_bx_payment_strp_test_sec_key_cpt', '_bx_payment_strp_test_sec_key_dsc', '', '', '', '', 7),
+(@iProviderId, 'strp_v3_check_amount', 'checkbox', '_bx_payment_strp_check_amount_cpt', '_bx_payment_strp_check_amount_dsc', '', '', '', '', 8),
+(@iProviderId, 'strp_v3_ssl', 'checkbox', '_bx_payment_strp_ssl_cpt', '_bx_payment_strp_ssl_dsc', '', '', '', '', 9),
+(@iProviderId, 'strp_v3_cancellation_email', 'text', '_bx_payment_strp_cancellation_email_cpt', '', '', 'EmailOrEmpty', '', '_sys_form_account_input_email_error', 10),
+(@iProviderId, 'strp_v3_expiration_email', 'text', '_bx_payment_strp_expiration_email_cpt', '', '', 'EmailOrEmpty', '', '_sys_form_account_input_email_error', 11),
+(@iProviderId, 'strp_v3_notify_url', 'value', '_bx_payment_strp_notify_url_cpt', '', '', '', '', '', 12);
+
 -- Apple In-App payment provider
 INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_owner_only`, `for_single`, `for_recurring`, `active`, `order`, `class_name`) VALUES
-('apple_in_app', '_bx_payment_aina_cpt', '_bx_payment_aina_dsc', 'aina_', 0, 1, 1, 1, 1, 8, 'BxPaymentProviderAppleInApp');
+('apple_in_app', '_bx_payment_aina_cpt', '_bx_payment_aina_dsc', 'aina_', 0, 1, 1, 1, 1, 70, 'BxPaymentProviderAppleInApp');
 SET @iProviderId = LAST_INSERT_ID();
 
 INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `caption`, `description`, `extra`, `check_type`, `check_params`, `check_error`, `order`) VALUES

+ 67 - 67
modules/boonex/payment/js/stripe.js

@@ -9,28 +9,28 @@
  */
 
 function BxPaymentProviderStripe(oOptions) {
-	this.init(oOptions);
+    this.init(oOptions);
 }
 
 BxPaymentProviderStripe.prototype = new BxPaymentMain();
 
 BxPaymentProviderStripe.prototype.init = function(oOptions) {
-	this._sProvider = oOptions.sProvider;
-	this._sActionsUrl = oOptions.sActionUrl;
+    this._sProvider = oOptions.sProvider;
+    this._sActionsUrl = oOptions.sActionUrl;
     this._sObjName = oOptions.sObjName == undefined ? 'oPaymentProviderStripe' : oOptions.sObjName;
     this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
     this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
 
     this._rHandler = StripeCheckout.configure({
-	  key: oOptions.sPublicKey,
-	  name: oOptions.sVendorName,
-	  amount: oOptions.iAmount == undefined ? '' : oOptions.iAmount,
-	  currency: oOptions.sVendorCurrency,
-	  image: oOptions.sVendorIcon,
-	  locale: 'auto',
-	  email: oOptions.sClientEmail,
-	  description: oOptions.sItemTitle == undefined ? '' : oOptions.sItemTitle
-	});
+        key: oOptions.sPublicKey,
+        name: oOptions.sVendorName,
+        amount: oOptions.iAmount == undefined ? '' : oOptions.iAmount,
+        currency: oOptions.sVendorCurrency,
+        image: oOptions.sVendorIcon,
+        locale: 'auto',
+        email: oOptions.sClientEmail,
+        description: oOptions.sItemTitle == undefined ? '' : oOptions.sItemTitle
+    });
 
     //--- For Single payment
     this._sObjNameGrid = oOptions.sObjNameGrid;
@@ -46,66 +46,66 @@ BxPaymentProviderStripe.prototype.init = function(oOptions) {
 };
 
 BxPaymentProviderStripe.prototype.checkout = function(oLink) {
-  	oLink = jQuery(oLink);
-	if(oLink.hasClass('bx-btn-disabled'))
-		return;
-
-	oLink.toggleClass('bx-btn-disabled');
-
-	var $this = this;
-	this._rHandler.open({
-		token: function(token) {
-			glGrids[$this._sObjNameGrid]._oQueryAppend['provider'] = $this._sProvider;
-			glGrids[$this._sObjNameGrid]._oQueryAppend['token'] = token.id;
-			glGrids[$this._sObjNameGrid].actionWithSelected('', 'checkout', {}, '', false, false);
-		},
-		closed: function() {
-			oLink.toggleClass('bx-btn-disabled');
-		}
-	});
+    oLink = jQuery(oLink);
+    if(oLink.hasClass('bx-btn-disabled'))
+        return;
+
+    oLink.toggleClass('bx-btn-disabled');
+
+    var $this = this;
+    this._rHandler.open({
+        token: function(token) {
+            glGrids[$this._sObjNameGrid]._oQueryAppend['provider'] = $this._sProvider;
+            glGrids[$this._sObjNameGrid]._oQueryAppend['token'] = token.id;
+            glGrids[$this._sObjNameGrid].actionWithSelected('', 'checkout', {}, '', false, false);
+        },
+        closed: function() {
+            oLink.toggleClass('bx-btn-disabled');
+        }
+    });
 };
 
 BxPaymentProviderStripe.prototype.subscribe = function(oLink) {
-	var $this = this;
+    var $this = this;
     var oDate = new Date();
 
-  	oLink = jQuery(oLink);
-	if(oLink.hasClass('bx-btn-disabled'))
-		return;
-
-	oLink.addClass('bx-btn-disabled');
-
-	this._rHandler.open({
-		token: function(token) {
-			$this.loadingInPopup(oLink, true);
-
-		    var oParams = {
-		    	seller_id: $this._iSellerId,
-		    	seller_provider: $this._sProvider,
-		    	module_id: $this._iModuleId,
-		    	item_id: $this._iItemId,
-		    	item_count: $this._iItemCount,
-		    	redirect: $this._sRedirect,
-		    	custom: $this._sCustom,
-		    	token: token.id,
-		    	_t: oDate.getTime()
-		    };
-
-		    $.post(
-		        $this._sActionsUrl + 'subscribe_json/',
-		        oParams,
-		        function(oData){
-		        	$this.loadingInPopup(oLink, true);
-
-		        	processJsonData(oData);
-		        },
-		        'json'
-		    );
-		},
-		closed: function() {
-			oLink.removeClass('bx-btn-disabled');
-		}
-	});
+    oLink = jQuery(oLink);
+    if(oLink.hasClass('bx-btn-disabled'))
+        return;
+
+    oLink.addClass('bx-btn-disabled');
+
+    this._rHandler.open({
+        token: function(token) {
+            $this.loadingInPopup(oLink, true);
+
+            var oParams = {
+                seller_id: $this._iSellerId,
+                seller_provider: $this._sProvider,
+                module_id: $this._iModuleId,
+                item_id: $this._iItemId,
+                item_count: $this._iItemCount,
+                redirect: $this._sRedirect,
+                custom: $this._sCustom,
+                token: token.id,
+                _t: oDate.getTime()
+            };
+
+            $.post(
+                $this._sActionsUrl + 'subscribe_json/',
+                oParams,
+                function(oData){
+                    $this.loadingInPopup(oLink, true);
+
+                    processJsonData(oData);
+                },
+                'json'
+            );
+        },
+        closed: function() {
+            oLink.removeClass('bx-btn-disabled');
+        }
+    });
 };
 
 /** @} */

+ 93 - 0
modules/boonex/payment/js/stripe_v3.js

@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Payment Payment
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+function BxPaymentProviderStripeV3(oOptions) {
+    this.init(oOptions);
+}
+
+BxPaymentProviderStripeV3.prototype = new BxPaymentMain();
+
+BxPaymentProviderStripeV3.prototype.init = function(oOptions) {
+    this._sProvider = oOptions.sProvider;
+    this._sActionsUrl = oOptions.sActionUrl;
+    this._sObjName = oOptions.sObjName == undefined ? 'oPaymentProviderStripeV3' : oOptions.sObjName;
+    this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
+    this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+
+    this._rHandler = Stripe(oOptions.sPublicKey);
+
+    //--- For Single payment
+    this._sObjNameGrid = oOptions.sObjNameGrid;
+
+    //--- For Recurring payment
+    this._sObjNameCart = oOptions.sObjNameCart;
+    this._iSellerId  = oOptions.iSellerId;
+    this._iModuleId  = oOptions.iModuleId;
+    this._iItemId  = oOptions.iItemId;
+    this._iItemCount  = oOptions.iItemCount;
+    this._sRedirect  = oOptions.sRedirect;
+    this._sCustom  = oOptions.sCustom;
+};
+
+BxPaymentProviderStripeV3.prototype.onCartCheckout = function(oData) {
+    this._rHandler.redirectToCheckout({
+        sessionId: oData.session_id
+    }).then(function (oResult) {
+        if(oResult.error)
+            bx_alert(oResult.error.message);
+    }).catch(function(oError) {
+        console.error('Error:', oError);
+    });
+};
+
+BxPaymentProviderStripeV3.prototype.subscribe = function(oLink) {
+    var oDate = new Date();
+
+    oLink = jQuery(oLink);
+    if(oLink.hasClass('bx-btn-disabled'))
+        return;
+
+    oLink.addClass('bx-btn-disabled');
+
+    this.loadingInPopup(oLink, true);
+
+    var aParams = {
+        seller_id: this._iSellerId,
+        seller_provider: this._sProvider,
+        module_id: this._iModuleId,
+        item_id: this._iItemId,
+        item_count: this._iItemCount,
+        redirect: this._sRedirect,
+        custom: this._sCustom,
+        _t: oDate.getTime()
+    };
+
+    $.post(this._sActionsUrl + 'call/' + this._sProvider + '/get_session_recurring/' + this._iSellerId + '/', aParams, function(oData) {
+        if(oData && oData.code != undefined && parseInt(oData.code) == 0)
+            oData.params = aParams;
+
+        oLink.removeClass('bx-btn-disabled');
+
+        processJsonData(oData);
+    }, 'json');
+};
+
+BxPaymentProviderStripeV3.prototype.onSubscribe = function(oData) {
+    this._rHandler.redirectToCheckout({
+        sessionId: oData.session_id
+    }).then(function (oResult) {
+        if(oResult.error)
+            bx_alert(oResult.error.message);
+    }).catch(function(oError) {
+        console.error('Error:', oError);
+    });
+};
+
+/** @} */

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

@@ -577,7 +577,9 @@
     <string name="_bx_payment_form_strp_card_input_submit"><![CDATA[Отправить]]></string>
     <string name="_bx_payment_form_strp_card_input_cancel_sys"><![CDATA[Отменить]]></string>
     <string name="_bx_payment_form_strp_card_input_cancel"><![CDATA[Отменить]]></string>
-    
+
     <string name="_bx_payment_txt_name_stripe"><![CDATA[Stripe]]></string>
-	<string name="_bx_payment_txt_cart_stripe"><![CDATA[Stripe]]></string>
+    <string name="_bx_payment_txt_cart_stripe"><![CDATA[Stripe]]></string>
+    <string name="_bx_payment_txt_name_stripe_v3"><![CDATA[Stripe]]></string>
+    <string name="_bx_payment_txt_cart_stripe_v3"><![CDATA[Stripe]]></string>
 </resources>