AntonLV 10 лет назад
Родитель
Сommit
a0f990043b

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

@@ -83,6 +83,7 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
         	'history' => 'BxPaymentOrders',
         	'pending' => 'BxPaymentOrders',
         	'processed' => 'BxPaymentOrders',
+        	'stripe' => 'BxPaymentProviderStripe'
         );
 
         $this->_aJsObjects = array(
@@ -90,6 +91,7 @@ class BxPaymentConfig extends BxBaseModPaymentConfig
         	'history' => 'oPaymentOrders',
         	'pending' => 'oPaymentOrders',
         	'processed' => 'oPaymentOrders',
+        	'stripe' => 'oPaymentProviderStripe'
         );
     }
 

+ 497 - 0
modules/boonex/payment/classes/BxPaymentProviderStripe.php

@@ -0,0 +1,497 @@
+<?php use Symfony\Component\Finder\Tests\FakeAdapter\DummyAdapter;
+defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Payment Payment
+ * @ingroup     TridentModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_PLUGINS . 'stripe/init.php');
+
+define('STRP_MODE_LIVE', 1);
+define('STRP_MODE_TEST', 2);
+
+class BxPaymentProviderStripe extends BxBaseModPaymentProvider implements iBxBaseModPaymentProvider
+{
+	protected $_aIncludeJs;
+	protected $_aIncludeCss;
+
+	protected $_iMode;
+	protected $_bCheckAmount;
+
+	protected $_oCustomer;
+
+    function __construct($aConfig)
+    {
+    	$this->MODULE = 'bx_payment';
+
+        parent::__construct($aConfig);
+
+        $this->_bRedirectOnResult = false;
+        $this->_bUseSsl = $this->getOption('ssl') == 'on';
+        $this->_sLogFile = BX_DIRECTORY_PATH_LOGS . 'bx_pp_' . $this->_sName . '.log';
+
+        $this->_aIncludeJs = array(
+        	'https://checkout.stripe.com/checkout.js',
+        	'main.js',
+        	'stripe.js'
+        );
+        $this->_aIncludeCss = array(
+        	'stripe.css'
+        );
+
+        $this->_iMode = (int)$this->getOption('mode');
+        $this->_bCheckAmount = false; // Disabled for easier processing of discounted subscriptions.
+
+        $this->_oCustomer = null;
+
+        \Stripe\Stripe::setApiKey($this->_getSecretKey());
+    }
+
+	public function addJsCss()
+    {
+    	if(!$this->isActive())
+    		return;
+
+		$this->_oModule->_oTemplate->addJs($this->_aIncludeJs);
+		$this->_oModule->_oTemplate->addCss($this->_aIncludeCss);
+    }
+
+    public function initializeCheckout($iPendingId, $aCartInfo)
+    {
+    	$sToken = bx_process_input(bx_get('token'));
+
+    	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:
+				$aCartInfo['items_title'] = '';
+				foreach($aCartInfo['items'] as $aItem)
+		            $aCartInfo['items_title'] .= ' ' . $aItem['title'] . ',';
+				$aCartInfo['items_title'] = trim($aCartInfo['items_title'], ', ');
+
+				$mixedResult = $this->_createCharge($sToken, $iPendingId, $aClient, $aCartInfo);
+				if($mixedResult === false)
+					return $this->_sLangsPrefix . 'err_cannot_perform';
+
+				header("Location: " . $this->getReturnDataUrl($aVendor['id'], array(
+					'order_id' => $mixedResult['order'],
+					'customer_id' => $mixedResult['customer'], 
+					'pending_id' => $aPending['id']
+				)));
+				exit;
+
+			case BX_PAYMENT_TYPE_RECURRING:
+				$mixedResult = $this->_createSubscription($sToken, $iPendingId, $aClient, $aCartInfo);
+				if($mixedResult === false)
+					return $this->_sLangsPrefix . 'err_cannot_perform';
+
+				return array(
+					'code' => 0,
+					'eval' => $this->_oModule->_oConfig->getJsObject('cart') . '.onSubscribeSubmit(oData);',
+					'redirect' => $this->getReturnDataUrl($aVendor['id'], array(
+						'order_id' => $mixedResult['order'],
+						'customer_id' => $mixedResult['customer'],
+						'pending_id' => $aPending['id']
+					))
+				);
+		}
+    }
+
+    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');
+
+		$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,
+			'client_name' => '',
+			'client_email' => '',
+			'paid' => false
+		);
+
+		switch($aPending['type']) {
+			case BX_PAYMENT_TYPE_SINGLE:
+				$aCustomer = $this->_retrieveCustomer($sCustomerId)->jsonSerialize();
+				$aCharge = $this->_retrieveCharge($sOrderId)->jsonSerialize();
+				if(empty($aCustomer) || !is_array($aCustomer) || empty($aCharge) || !is_array($aCharge))
+					return array('code' => 4, 'message' => $this->_sLangsPrefix . 'err_cannot_perform');
+
+				$aResult = array_merge($aResult, array(
+					'message' => $this->_sLangsPrefix . 'strp_msg_charged',
+					'client_email' => $aCustomer['email'],
+					'paid' => (bool)$aCharge['paid']
+				));
+				break;
+
+			case BX_PAYMENT_TYPE_RECURRING:
+				$aCustomer = $this->_retrieveCustomer($sCustomerId)->jsonSerialize();
+				$aSubscription = $this->_retrieveSubscription($sCustomerId, $sOrderId)->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',
+					'client_email' => $aCustomer['email']
+				));
+				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 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);
+
+    	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_icon'],
+    	)));
+    }
+
+    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['icon'],
+		)));
+    }
+
+    public function getCheckoutParamsSingle($aParams, &$oGrid)
+    {
+    	if(bx_get('token') !== false)
+    		$aParams['token'] = bx_process_input(bx_get('token'));
+
+    	return $aParams;
+    }
+
+	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 _createCustomer($sToken, $aClient) {
+		try {
+			$aClientParams = array(
+				'card'  => $sToken,
+				'email' => !empty($aClient['email']) ? $aClient['email'] : ''
+			);
+
+			$this->_oCustomer = \Stripe\Customer::create($aClientParams);
+		}
+		catch (Stripe\Error\Base $oException) {
+			return $this->_processException('Create Customer Error: ', $oException);
+		}
+
+		return $this->_oCustomer->jsonSerialize();
+	}
+
+	protected function _retrieveCustomer($sId) {
+		try {
+			$oCustomer = \Stripe\Customer::retrieve($sId);
+		}
+		catch (Exception $oException) {
+			return $this->_processException('Retrieve Customer Error: ', $oException);
+		}
+
+		return $oCustomer;
+	}
+
+	protected function _createCharge($sToken, $iPendingId, &$aClient, &$aCartInfo) {
+		if(empty($this->_oCustomer))
+			$this->_createCustomer($sToken, $aClient);
+
+		if(empty($this->_oCustomer))
+			return false;
+
+		$fAmount = 100 * (float)$aCartInfo['items_price'];
+
+		try {
+			$oCharge = \Stripe\Charge::create(array(
+				'customer' => $this->_oCustomer->id,
+				'amount' => $fAmount,
+				'currency' => $aCartInfo['vendor_currency_code'],
+				'description' => $aCartInfo['items_title'],
+				'metadata' => array(
+					'vendor' => $aCartInfo['vendor_id'],
+					'customer' => $aClient['id'],
+					'product' => $iPendingId,
+					'verification' => $this->_getVerificationCodeCharge($aCartInfo['vendor_id'], $aClient['id'], $fAmount, $aCartInfo['vendor_currency_code'])
+				)
+			));
+		}
+		catch (Exception $oException) {
+			return $this->_processException('Create Charge Error: ', $oException);
+		}
+
+		$aResult = $oCharge->jsonSerialize();
+		if(empty($aResult) || !is_array($aResult) || empty($aResult['paid']))
+			return false;
+
+		$aMetadata = $aResult['metadata'];
+		if(empty($aMetadata['verification']) || $aMetadata['verification'] != $this->_getVerificationCodeCharge($aMetadata['vendor'], $aMetadata['customer'], $aResult['amount'], $aResult['currency']))
+			return false;
+
+		return array(
+			'pending' => $iPendingId,
+			'amount' =>(float)$aResult['amount'] / 100,
+			'customer' => $this->_oCustomer->id,
+			'order' => $aResult['id']
+		);
+	}
+
+	protected function _retrieveCharge($sId) {
+		try {
+			$oCharge = \Stripe\Charge::retrieve($sId);
+		}
+		catch (Exception $oException) {
+			return $this->_processException('Retrieve Charge Error: ', $oException);
+		}
+
+		return $oCharge;
+	}
+
+	protected function _createSubscription($sToken, $iPendingId, &$aClient, &$aCartInfo)
+	{
+		$bTrial = false;
+
+		if(empty($this->_oCustomer))
+			$this->_createCustomer($sToken, $aClient);
+
+		if(empty($this->_oCustomer))
+			return false;
+
+		try {
+			$aItem = array_shift($aCartInfo['items']);
+			if(empty($aItem) || !is_array($aItem))
+				return false;
+
+			//TODO: "Trial" wasn't finaly relized. It's just a draft.
+			if(isset($aItem['trial']) && $aItem['trial'] === true)
+				$bTrial = true;
+
+			$oSubscription = $this->_oCustomer->subscriptions->create(array(
+				'plan' => $aItem['name'],
+				'metadata' => array(
+					'vendor' => $aCartInfo['vendor_id'],
+					'customer' => $aClient['id'],
+					'product' => $iPendingId,
+					'verification' => $this->_getVerificationCodeSubscription($aCartInfo['vendor_id'], $aClient['id'], $aItem['name'], $aCartInfo['vendor_currency_code'])
+				)
+			));
+		}
+		catch (Exception $oException) {
+			return $this->_processException('Create Subscription Error: ', $oException);
+		}
+
+		$aResult = $oSubscription->jsonSerialize();
+		if(empty($aResult) || !is_array($aResult) || (!$bTrial && $aResult['status'] != 'active') || ($bTrial && !in_array($aResult['status'], array('active', 'trialing'))))
+			return false;
+
+		$aMetadata = $aResult['metadata'];
+		if(empty($aMetadata['verification']) || $aMetadata['verification'] != $this->_getVerificationCodeSubscription($aCartInfo['vendor_id'], $aClient['id'], $aResult['plan']['id'], $aResult['plan']['currency']))
+			return false;
+
+		return array(
+			'pending' => $iPendingId,
+			'amount' =>(float)$aCartInfo['items_price'],
+			'customer' => $this->_oCustomer->id, 
+			'order' => $aResult['id'],
+			'trial' => $bTrial
+		);
+	}
+
+	protected function _retrieveSubscription($sCustomerId, $sSubscriptionId) {
+		try {
+			$oCustomer = \Stripe\Customer::retrieve($sCustomerId);
+			$oSubscription = $oCustomer->subscriptions->retrieve($sSubscriptionId);
+		}
+		catch (Exception $oException) {
+			return $this->_processException('Retrieve Subscription Error: ', $oException);
+		}
+
+		return $oSubscription;
+	}
+
+	//TODO: Work with WEBHOOKS
+	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;
+
+		$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;
+
+		return $this->_oModule->registerPayment($aPending);
+	}
+
+	protected function _processEventChargeRefunded(&$aEvent)
+	{
+		$mixedResult = $this->_getData($aEvent);
+		if($mixedResult === false)
+			return false;
+
+		list($aPending) = $mixedResult;
+		return $this->_oModule->refundPayment($aPending);
+	}
+
+	protected function _processEventCustomerSubscriptionDeleted(&$aEvent)
+	{
+		$mixedResult = $this->_getData($aEvent);
+		if($mixedResult === false)
+			return false;
+
+		list($aPending) = $mixedResult;
+		return $this->_oModule->cancelSubscription($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));
+		if(empty($aPending) || !is_array($aPending))
+			return false;
+
+		$oCharge = $this->_retrieveCharge($oEvent->data->object->charge);
+		if(empty($oCharge))
+			return false;
+
+		return array($aPending, $oCharge);
+	}
+
+	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())
+    {
+    	$sClientEmail = '';
+    	if(!empty($iClientId)) {
+    		$oClient = BxDolProfile::getInstance($iClientId);
+    		if($oClient)
+    			$sClientEmail = $oClient->getAccountObject()->getEmail();
+    	}
+
+    	return $this->_oModule->_oTemplate->parseHtmlByName('strp_button_' . $sType . '.html', array(
+    		'type' => $sType,
+    		'caption' => _t($this->_sLangsPrefix . 'strp_txt_checkout_with_' . $sType, $this->_sCaption),
+    		'js_object' => $this->_oModule->_oConfig->getJsObject($this->_sName),
+    		'js_code' => $this->_oModule->_oTemplate->getJsCode($this->_sName, array_merge(array(
+	    		'sProvider' => $this->_sName,
+	    		'sPublicKey' => $this->_getPublicKey(),
+	    		'sVendorName' => '',
+	    		'sVendorCurrency' => '',
+	    		'sVendorIcon' => '',
+	    		'sClientEmail' => $sClientEmail,
+	    	), $aParams))
+    	));
+    }
+}
+
+/** @} */

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

@@ -155,11 +155,13 @@
 	<string name="_bx_payment_err_not_found_pending"><![CDATA[Pending order was not found.]]></string>
 	<string name="_bx_payment_err_cannot_perform"><![CDATA[Cannot perform the action. Please report.]]></string>
 	<string name="_bx_payment_err_wrong_quantity"><![CDATA[Incorrect quantity specified.]]></string>
+	<string name="_bx_payment_err_already_processed"><![CDATA[This payment has already processed.]]></string>
 
 	<!-- Other -->
 	<string name="_bx_payment_txt_add_to_cart"><![CDATA[Add to Cart]]></string>
 	<string name="_bx_payment_txt_subscribe"><![CDATA[Subscribe]]></string>
-	<string name="_bx_payment_txt_payment_to"><![CDATA[Payment to {0} for:]]></string>
+	<string name="_bx_payment_txt_payment_to"><![CDATA[Payment to {0}]]></string>
+	<string name="_bx_payment_txt_payment_to_for"><![CDATA[Payment to {0} for:]]></string>
 	<string name="_bx_payment_txt_checkout_to"><![CDATA[Continue checkouting to {0}]]></string>
 	<string name="_bx_payment_txt_checkout_with"><![CDATA[Checkout with {0}]]></string>
 	<string name="_bx_payment_txt_client"><![CDATA[Client]]></string>
@@ -169,6 +171,7 @@
 	<string name="_bx_payment_txt_message"><![CDATA[Result]]></string>
 	<string name="_bx_payment_txt_date"><![CDATA[Date]]></string>
 	<string name="_bx_payment_txt_products"><![CDATA[Products]]></string>
+	<string name="_bx_payment_txt_buyer_name_mask"><![CDATA[{0} {1}]]></string>
 
 	<!-- PayPal payment provider -->
 	<string name="_bx_payment_pp_cpt"><![CDATA[PayPal]]></string>
@@ -192,14 +195,12 @@
 	<string name="_bx_payment_pp_sandbox_dsc"><![CDATA[Sandbox email address]]></string>
 	<string name="_bx_payment_pp_return_data_url_cpt"><![CDATA[Data return URL]]></string>
 	<string name="_bx_payment_pp_err_no_data_given"><![CDATA[No data given.]]></string>
-	<string name="_bx_payment_pp_err_no_vendor_given"><![CDATA[Unknown vendor.]]></string>
 	<string name="_bx_payment_pp_err_not_completed"><![CDATA[Payment is not completed.]]></string>
 	<string name="_bx_payment_pp_err_wrong_business"><![CDATA[Wrong receiver email.]]></string>
 	<string name="_bx_payment_pp_err_wrong_transaction"><![CDATA[Transaction verification failed.]]></string>
 	<string name="_bx_payment_pp_err_wrong_verification_status"><![CDATA[No verification status received.]]></string>
 	<string name="_bx_payment_pp_err_wrong_amount"><![CDATA[Wrong amount.]]></string>
 	<string name="_bx_payment_pp_err_wrong_custom_data"><![CDATA[Incorrect custom data.]]></string>
-	<string name="_bx_payment_pp_err_already_processed"><![CDATA[This payment has already processed.]]></string>
 	<string name="_bx_payment_pp_msg_verified"><![CDATA[Payment was successfully accepted.]]></string>
 	<string name="_bx_payment_txt_cart_paypal"><![CDATA[PayPal]]></string>
 
@@ -300,7 +301,7 @@
 	<string name="_bx_payment_rcrl_notify_url_cpt"><![CDATA[Webhooks URL]]></string>
 	<string name="_bx_payment_rcrl_msg_subscribed"><![CDATA[Subscription was successfully created.]]></string>
 	<string name="_bx_payment_txt_cart_chargebee"><![CDATA[Recurly]]></string>
-	
+
 	<string name="_bx_payment_form_rcrl_card"><![CDATA[Recurly: Card Form]]></string>
 	<string name="_bx_payment_form_rcrl_card_display_add"><![CDATA[Recurly: Add Card Display]]></string>
 	<string name="_bx_payment_form_rcrl_card_input_pending_id_sys"><![CDATA[Pending ID]]></string>
@@ -332,4 +333,32 @@
 	<string name="_bx_payment_form_rcrl_card_input_cancel"><![CDATA[Cancel]]></string>
 	
 	<string name="_bx_payment_popup_title_rcrl_card_add"><![CDATA[Card details]]></string>
+
+	<!-- 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_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_mode_cpt"><![CDATA[Mode]]></string>
+	<string name="_bx_payment_strp_mode_dsc"><![CDATA[Mode to process payments]]></string>
+	<string name="_bx_payment_strp_mode_live"><![CDATA[live]]></string>
+	<string name="_bx_payment_strp_mode_test"><![CDATA[test]]></string>
+	<string name="_bx_payment_strp_live_pub_key_cpt"><![CDATA[Public Key (live)]]></string>
+	<string name="_bx_payment_strp_live_pub_key_dsc"><![CDATA[Your Public Key generated in Stripe for 'live' mode]]></string>
+	<string name="_bx_payment_strp_live_sec_key_cpt"><![CDATA[Secret Key (live)]]></string>
+	<string name="_bx_payment_strp_live_sec_key_dsc"><![CDATA[Your Stripe Secret Key for 'live' mode]]></string>
+	<string name="_bx_payment_strp_test_pub_key_cpt"><![CDATA[Public Key (test)]]></string>
+	<string name="_bx_payment_strp_test_pub_key_dsc"><![CDATA[Your Public Key generated in Stripe for 'test' mode]]></string>
+	<string name="_bx_payment_strp_test_sec_key_cpt"><![CDATA[Secret Key (test)]]></string>
+	<string name="_bx_payment_strp_test_sec_key_dsc"><![CDATA[Your Stripe Secret Key for 'test' mode]]></string>
+	<string name="_bx_payment_strp_check_amount_cpt"><![CDATA[Enable amount checking]]></string>
+	<string name="_bx_payment_strp_check_amount_dsc"><![CDATA[Enable amount checking for Stripe Webhooks (recomended). However checking should be disabled when discounts and coupons are used.]]></string>
+	<string name="_bx_payment_strp_ssl_cpt"><![CDATA[Enable SSL]]></string>
+	<string name="_bx_payment_strp_ssl_dsc"><![CDATA[Enable SSL connection to communicate with Stripe. Note. URLs below will be updated after saving.]]></string>
+	<string name="_bx_payment_strp_notify_url_cpt"><![CDATA[Webhooks URL]]></string>
+	<string name="_bx_payment_strp_msg_charged"><![CDATA[Payment was successfully accepted.]]></string>
+	<string name="_bx_payment_strp_msg_subscribed"><![CDATA[Subscription was successfully created.]]></string>
+	<string name="_bx_payment_strp_txt_checkout_with_single"><![CDATA[{0}]]></string>
+	<string name="_bx_payment_strp_txt_checkout_with_recurring"><![CDATA[Checkout with {0}]]></string>
+	<string name="_bx_payment_txt_cart_stripe"><![CDATA[Stripe]]></string>
 </resources>

+ 30 - 12
modules/boonex/payment/install/sql/install.sql

@@ -6,10 +6,11 @@ CREATE TABLE IF NOT EXISTS `bx_payment_providers` (
   `id` int(11) NOT NULL auto_increment,
   `name` varchar(64) NOT NULL default '',
   `caption` varchar(128) NOT NULL default '',
-  `description` text NOT NULL default '',
+  `description` varchar(128) NOT NULL default '',
   `option_prefix` varchar(32) NOT NULL default '',
   `for_visitor` tinyint(4) NOT NULL default '0',
-  `for_subscription` tinyint(4) NOT NULL default '0',
+  `for_single` tinyint(4) NOT NULL default '0',
+  `for_recurring` tinyint(4) NOT NULL default '0',
   `class_name` varchar(128) NOT NULL default '',
   `class_file` varchar(255) NOT NULL  default '',
   PRIMARY KEY(`id`)
@@ -83,8 +84,8 @@ CREATE TABLE IF NOT EXISTS `bx_payment_modules` (
 
 
 -- PayPal payment provider
-INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`,`for_subscription`, `class_name`) VALUES
-('paypal', '_bx_payment_pp_cpt', '_bx_payment_pp_dsc', 'pp_', 1, 0, 'BxPaymentProviderPayPal');
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `class_name`) VALUES
+('paypal', '_bx_payment_pp_cpt', '_bx_payment_pp_dsc', 'pp_', 1, 1, 0, '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
@@ -98,8 +99,8 @@ 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_subscription`, `class_name`) VALUES
-('2checkout', '_bx_payment_2co_cpt', '_bx_payment_2co_dsc', '2co_', 1, 0, 'BxPaymentProvider2Checkout');
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `class_name`) VALUES
+('2checkout', '_bx_payment_2co_cpt', '_bx_payment_2co_dsc', '2co_', 1, 1, 0, '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
@@ -112,8 +113,8 @@ 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_subscription`, `class_name`) VALUES
-('bitpay', '_bx_payment_bp_cpt', '_bx_payment_bp_dsc', 'bp_', 0, 0, 'BxPaymentProviderBitPay');
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `class_name`) VALUES
+('bitpay', '_bx_payment_bp_cpt', '_bx_payment_bp_dsc', 'bp_', 0, 1, 0, '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
@@ -125,8 +126,8 @@ 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_subscription`, `class_name`) VALUES
-('chargebee', '_bx_payment_cbee_cpt', '_bx_payment_cbee_dsc', 'cbee_', 1, 1, 'BxPaymentProviderChargebee');
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `class_name`) VALUES
+('chargebee', '_bx_payment_cbee_cpt', '_bx_payment_cbee_dsc', 'cbee_', 1, 0, 1, '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
@@ -141,8 +142,8 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 (@iProviderId, 'cbee_notify_url', 'value', '_bx_payment_cbee_notify_url_cpt', '', '', '', '', '', 9);
 
 -- Recurly payment provider
-INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_subscription`, `class_name`) VALUES
-('recurly', '_bx_payment_rcrl_cpt', '_bx_payment_rcrl_dsc', 'rcrl_', 1, 1, 'BxPaymentProviderRecurly');
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `class_name`) VALUES
+('recurly', '_bx_payment_rcrl_cpt', '_bx_payment_rcrl_dsc', 'rcrl_', 1, 0, 1, '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
@@ -154,6 +155,23 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 (@iProviderId, 'rcrl_return_data_url', 'value', '_bx_payment_rcrl_return_data_url_cpt', '', '', '', '', '', 6),
 (@iProviderId, 'rcrl_notify_url', 'value', '_bx_payment_rcrl_notify_url_cpt', '', '', '', '', '', 7);
 
+-- Stripe payment provider
+INSERT INTO `bx_payment_providers`(`name`, `caption`, `description`, `option_prefix`, `for_visitor`, `for_single`, `for_recurring`, `class_name`) VALUES
+('stripe', '_bx_payment_strp_cpt', '_bx_payment_strp_dsc', 'strp_', 1, 1, 1, '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
+(@iProviderId, 'strp_active', 'checkbox', '_bx_payment_strp_active_cpt', '_bx_payment_strp_active_dsc', '', '', '', '', 1),
+(@iProviderId, 'strp_mode', 'select', '_bx_payment_strp_mode_cpt', '_bx_payment_strp_mode_dsc', '1|_bx_payment_strp_mode_live,2|_bx_payment_strp_mode_test', '', '', '', 2),
+(@iProviderId, 'strp_live_pub_key', 'text', '_bx_payment_strp_live_pub_key_cpt', '_bx_payment_strp_live_pub_key_dsc', '', '', '', '', 3),
+(@iProviderId, 'strp_live_sec_key', 'text', '_bx_payment_strp_live_sec_key_cpt', '_bx_payment_strp_live_sec_key_dsc', '', '', '', '', 4),
+(@iProviderId, 'strp_test_pub_key', 'text', '_bx_payment_strp_test_pub_key_cpt', '_bx_payment_strp_test_pub_key_dsc', '', '', '', '', 5),
+(@iProviderId, 'strp_test_sec_key', 'text', '_bx_payment_strp_test_sec_key_cpt', '_bx_payment_strp_test_sec_key_dsc', '', '', '', '', 6),
+(@iProviderId, 'strp_check_amount', 'checkbox', '_bx_payment_strp_check_amount_cpt', '_bx_payment_strp_check_amount_dsc', '', '', '', '', 7),
+(@iProviderId, 'strp_ssl', 'checkbox', '_bx_payment_strp_ssl_cpt', '_bx_payment_strp_ssl_dsc', '', '', '', '', 8),
+(@iProviderId, 'strp_notify_url', 'value', '_bx_payment_strp_notify_url_cpt', '', '', '', '', '', 9);
+
+
 -- GRIDS
 INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
 ('bx_payment_grid_carts', 'Array', '', '', 'vendor_id', '', '', '', 20, NULL, 'start', '', '', '', 'like', '', '', 2147483647, 'BxPaymentGridCarts', 'modules/boonex/payment/classes/BxPaymentGridCarts.php'),

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

@@ -0,0 +1,93 @@
+function BxPaymentProviderStripe(oOptions) {
+	this.init(oOptions);
+}
+
+BxPaymentProviderStripe.prototype = new BxPaymentMain();
+
+BxPaymentProviderStripe.prototype.init = function(oOptions) {
+	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,
+	  currency: oOptions.sVendorCurrency,
+	  image: oOptions.sVendorIcon,
+	  locale: 'auto',
+	  email: oOptions.sClientEmail
+	});
+
+    //--- 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;
+};
+
+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');
+		}
+	});
+};
+
+BxPaymentProviderStripe.prototype.subscribe = function(oLink) {
+	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.loadingInButton(oLink, true);
+
+		    var oParams = {
+		    	seller_id: $this._iSellerId,
+		    	seller_provider: $this._sProvider,
+		    	module_id: $this._iModuleId,
+		    	item_id: $this._iItemId,
+		    	item_count: $this._iItemCount,
+		    	token: token.id,
+		    	_t: oDate.getTime()
+		    };
+
+		    $.post(
+		        $this._sActionsUrl + 'subscribe_json/',
+		        oParams,
+		        function(oData){
+		        	$this.loadingInButton(oLink, true);
+
+		        	$this.processResult(oData);
+		        },
+		        'json'
+		    );
+		},
+		closed: function() {
+			oLink.removeClass('bx-btn-disabled');
+		}
+	});
+};

+ 5 - 0
modules/boonex/payment/template/css/stripe.css

@@ -0,0 +1,5 @@
+/*--- Single payments ---*/
+.bx-payment-strp-button-single {
+	position: relative;
+	float: left;
+}

+ 2 - 0
modules/boonex/payment/template/strp_button_recurring.html

@@ -0,0 +1,2 @@
+<a class="bx-btn bx-def-margin-top-auto" href="javascript:void(0)" onclick="javascript:__js_object__.subscribe(this)">__caption__</a>
+__js_code__

+ 4 - 0
modules/boonex/payment/template/strp_button_single.html

@@ -0,0 +1,4 @@
+<div class="bx-payment-strp-button-__type__">
+	<button class="bx-btn bx-def-margin-thd" type="button" onclick="javascript:__js_object__.checkout(this)">__caption__</button>
+	__js_code__
+</div>

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

@@ -169,6 +169,7 @@
 	<string name="_bx_payment_txt_message"><![CDATA[Результат]]></string>
 	<string name="_bx_payment_txt_date"><![CDATA[Дата]]></string>
 	<string name="_bx_payment_txt_products"><![CDATA[Продукты]]></string>
+	<string name="_bx_payment_txt_buyer_name_mask"><![CDATA[{0} {1}]]></string>
 
 	<!-- PayPal payment provider -->
 	<string name="_bx_payment_pp_cpt"><![CDATA[PayPal]]></string>
@@ -267,7 +268,7 @@
 	<string name="_bx_payment_cbee_mode_test"><![CDATA[тестовый]]></string>
 	<string name="_bx_payment_cbee_live_site_cpt"><![CDATA[Имя сайта (рабочее)]]></string>
 	<string name="_bx_payment_cbee_live_site_dsc"><![CDATA[Имя вашего сайта зарегистрированного в Chargebee для "рабочего" режима.]]></string>
-	<string name="_bx_payment_cbee_live_api_key_cpt"><![CDATA[API ключ (рабочей)]]></string>
+	<string name="_bx_payment_cbee_live_api_key_cpt"><![CDATA[API ключ (рабочий)]]></string>
 	<string name="_bx_payment_cbee_live_api_key_dsc"><![CDATA[Ваш API ключ доступа, предоставленный Chargebee, для "рабочего" режима.]]></string>
 	<string name="_bx_payment_cbee_test_site_cpt"><![CDATA[Имя сайта (тестовое)]]></string>
 	<string name="_bx_payment_cbee_test_site_dsc"><![CDATA[Имя вашего сайта зарегистрированного в Chargebee для "тестового" режима.]]></string>
@@ -300,7 +301,7 @@
 	<string name="_bx_payment_rcrl_notify_url_cpt"><![CDATA[URL для получения webhook-ов]]></string>
 	<string name="_bx_payment_rcrl_msg_subscribed"><![CDATA[Подписка была успешно создана.]]></string>
 	<string name="_bx_payment_txt_cart_chargebee"><![CDATA[Recurly]]></string>
-	
+
 	<string name="_bx_payment_form_rcrl_card"><![CDATA[Recurly: форма ввода карты]]></string>
 	<string name="_bx_payment_form_rcrl_card_display_add"><![CDATA[Recurly: добавление карты]]></string>
 	<string name="_bx_payment_form_rcrl_card_input_pending_id_sys"><![CDATA[Обрабатываемый заказ]]></string>
@@ -332,4 +333,32 @@
 	<string name="_bx_payment_form_rcrl_card_input_cancel"><![CDATA[Отменить]]></string>
 	
 	<string name="_bx_payment_popup_title_rcrl_card_add"><![CDATA[Детали карты]]></string>
+
+	<!-- Stripe payment provider -->
+	<string name="_bx_payment_strp_cpt"><![CDATA[Stripe]]></string>
+	<string name="_bx_payment_strp_dsc"><![CDATA[Платежная система Stripe]]></string>
+	<string name="_bx_payment_strp_active_cpt"><![CDATA[Активировать]]></string>
+	<string name="_bx_payment_strp_active_dsc"><![CDATA[Активировать(деактивировать) платежную систему. Внимание. Рекомендуется использовать SSL сертификат для улучшения обработки платежей.]]></string>
+	<string name="_bx_payment_strp_mode_cpt"><![CDATA[Режим]]></string>
+	<string name="_bx_payment_strp_mode_dsc"><![CDATA[Режим обработки платежей]]></string>
+	<string name="_bx_payment_strp_mode_live"><![CDATA[рабочий]]></string>
+	<string name="_bx_payment_strp_mode_test"><![CDATA[тестовый]]></string>
+	<string name="_bx_payment_strp_live_pub_key_cpt"><![CDATA[Открытый ключ (рабочий)]]></string>
+	<string name="_bx_payment_strp_live_pub_key_dsc"><![CDATA[Ваш Открытый ключ доступа, предоставленный Stripe, для "рабочего" режима.]]></string>
+	<string name="_bx_payment_strp_live_sec_key_cpt"><![CDATA[Секретный ключ (рабочий)]]></string>
+	<string name="_bx_payment_strp_live_sec_key_dsc"><![CDATA[Ваш Секретный ключ доступа для "рабочего" режима.]]></string>
+	<string name="_bx_payment_strp_test_pub_key_cpt"><![CDATA[Открытый ключ (тестовый)]]></string>
+	<string name="_bx_payment_strp_test_pub_key_dsc"><![CDATA[Ваш Открытый ключ доступа, предоставленный Stripe, для "тестового" режима.]]></string>
+	<string name="_bx_payment_strp_test_sec_key_cpt"><![CDATA[Секретный ключ (тестовый)]]></string>
+	<string name="_bx_payment_strp_test_sec_key_dsc"><![CDATA[Ваш Секретный ключ доступа для "тестового" режима.]]></string>
+	<string name="_bx_payment_strp_check_amount_cpt"><![CDATA[Активировать проверку суммы]]></string>
+	<string name="_bx_payment_strp_check_amount_dsc"><![CDATA[Активировать проверку суммы для Stripe Webhooks (рекомендуется). Однако проверка должна быть отключена когда используются скидки и купоны.]]></string>
+	<string name="_bx_payment_strp_ssl_cpt"><![CDATA[Активировать SSL]]></string>
+	<string name="_bx_payment_strp_ssl_dsc"><![CDATA[Активировать SSL соединение для связи с Stripe. Внимание. Нижеприведенная Webhooks ссылка будет обновлена после сохранения.]]></string>
+	<string name="_bx_payment_strp_notify_url_cpt"><![CDATA[URL для получения webhook-ов]]></string>
+	<string name="_bx_payment_strp_msg_charged"><![CDATA[Платеж успешно принят.]]></string>
+	<string name="_bx_payment_strp_msg_subscribed"><![CDATA[Подписка успешно создана.]]></string>
+	<string name="_bx_payment_strp_txt_checkout_with_single"><![CDATA[{0}]]></string>
+	<string name="_bx_payment_strp_txt_checkout_with_recurring"><![CDATA[Заплатить используя {0}]]></string>
+	<string name="_bx_payment_txt_cart_stripe"><![CDATA[Stripe]]></string>
 </resources>