Bladeren bron

Ticket #277 - integration.

AntonLV 10 jaren geleden
bovenliggende
commit
c886d79a93

+ 324 - 0
modules/boonex/payment/classes/BxPaymentProviderRecurly.php

@@ -0,0 +1,324 @@
+<?php 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 . 'recurly/recurly.php');
+
+define('RCRL_MODE_LIVE', 1);
+define('RCRL_MODE_TEST', 2);
+
+class BxPaymentProviderRecurly extends BxBaseModPaymentProvider implements iBxBaseModPaymentProvider
+{
+	protected $_aIncludeJs;
+	protected $_aIncludeCss;
+
+	protected $_sFormCard;
+	protected $_sFormDisplayCardAdd;
+
+	protected $_iMode;
+	protected $_bCheckAmount;
+
+    function __construct($aConfig)
+    {
+    	$this->MODULE = 'bx_payment';
+
+        parent::__construct($aConfig);
+
+        $this->_aIncludeJs = array(
+        	'https://js.recurly.com/v4/recurly.js'
+        );
+        $this->_aIncludeCss = array(
+        	'recurly.css'
+        );
+
+        $this->_sFormCard = 'bx_payment_form_rcrl_card';
+        $this->_sFormDisplayCardAdd = 'bx_payment_form_rcrl_card_add';
+
+        $this->_bRedirectOnResult = false;
+        $this->_bUseSsl = true;
+        $this->_sLogFile = BX_DIRECTORY_PATH_LOGS . 'bx_pp_' . $this->_sName . '.log';        
+
+        $this->_iMode = (int)$this->getOption('mode');
+        $this->_bCheckAmount = false; // Disabled for easier processing of discounted subscriptions.
+    }
+
+	public function addJsCss()
+    {
+    	if(!$this->isActive())
+    		return;
+
+		$this->_oModule->_oTemplate->addJs($this->_aIncludeJs);
+		$this->_oModule->_oTemplate->addCss($this->_aIncludeCss);
+    }
+
+    public function initializeCheckout($iPendingId, $aCartInfo)
+    {
+    	$aItem = array_shift($aCartInfo['items']);
+    	if(empty($aItem) || !is_array($aItem))
+    		return $this->_sLangsPrefix . 'err_empty_items';
+
+		$aClient = $this->_oModule->getProfileInfo();
+		$aVendor = $this->_oModule->getProfileInfo($aCartInfo['vendor_id']);
+
+		$mixedResult = $this->createCardForm($iPendingId, $aItem, $aClient, $aVendor);
+		if($mixedResult === false)
+			return $this->_sLangsPrefix . 'err_cannot_perform';
+
+		return array_merge(array('code' => 0), $mixedResult);
+    }
+
+    public function finalizeCheckout(&$aData)
+    {
+    	$iPendingId = bx_process_input($aData['pending_id'], BX_DATA_INT);
+    	$sItem = bx_process_input($aData['item']);
+    	$sToken = bx_process_input($aData['token']);
+    	$aClient = array(
+    		'first_name' => bx_process_input($aData['first_name']),
+    		'last_name' => bx_process_input($aData['last_name']),
+    		'email' => bx_process_input($aData['email'])
+    	);
+
+        if(empty($sToken) || empty($iPendingId))
+        	return array('code' => 1, 'message' => $this->_sLangsPrefix . 'pp_err_no_data_given');
+		
+		$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 . 'pp_err_already_processed');
+
+		$oSubscription = $this->createSubscription($sToken, $sItem, $aClient);
+		if($oSubscription === false)
+			return $this->_sLangsPrefix . 'err_cannot_perform';
+
+		/*
+		 * TODO: Need to get subscription ID to save it in DB as Order
+		 */
+		$sSubscription = '';
+
+		$aResult = array(
+			'code' => BX_PAYMENT_RESULT_SUCCESS,
+        	'message' => $this->_sLangsPrefix . 'rcrl_msg_subscribed',
+			'pending_id' => $iPendingId,
+			'client_name' => _t($this->_sLangsPrefix . 'txt_buyer_name_mask', $aClient['first_name'], $aClient['last_name']),
+			'client_email' => $aClient['email'],
+			'paid' => false
+		);
+
+        //--- Update pending transaction ---//
+        $this->_oModule->_oDb->updateOrderPending($iPendingId, array(
+            'order' => $sSubscription,
+            'error_code' => $aResult['code'],
+            'error_msg' => _t($aResult['message'])
+        ));
+
+        return $aResult;
+    }
+
+    public function notify()
+    {
+    	/*
+    	 * TODO: Integrate necessary Webhook events. 
+    	 */
+		//$iResult = $this->_processEvent();
+		//http_response_code($iResult);
+    }
+
+    public function createCardForm($iPendingId, $aItem, $aClient, $aVendor)
+    {
+		$oForm = BxDolForm::getObjectInstance($this->_sFormCard, $this->_sFormDisplayCardAdd, $this->_oModule->_oTemplate);
+        $oForm->aFormAttrs['action'] = bx_append_url_params($this->getReturnDataUrl($aVendor['id']), array(
+			'pending_id' => $iPendingId
+		));
+
+        $oForm->aInputs['pending_id']['value'] = $iPendingId;
+        $oForm->aInputs['item']['value'] = $aItem['name'];
+
+		$aCardFields = array('card_number', 'card_cvv');
+		foreach($aCardFields as $sCardField) {
+			$oForm->aInputs[$sCardField]['content'] = $this->_oModule->_oTemplate->parseHtmlByName('rcrl_card_field.html', array(
+				'attrs' => bx_convert_array2attrs($oForm->aInputs[$sCardField]['attrs'])
+			));
+		}
+		$oForm->aInputs['card_expire']['content'] = $this->_oModule->_oTemplate->parseHtmlByName('rcrl_card_field_date.html', array(
+			'attrs_month' => bx_convert_array2attrs(array('data-recurly' => 'month')),
+			'attrs_year' => bx_convert_array2attrs(array('data-recurly' => 'year'))
+		));
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+        	$sFormMethod = $oForm->aFormAttrs['method'];
+
+        	/*
+        	 * TODO: Need to reload form on internal error or update the code to work via this check and then redirect to Finalize.
+        	 * Issue with "form reloading". Currently Recurly doesn't allow to "call payment form" multiple times (https://github.com/recurly/recurly-js/issues/239). 
+        	 */ 
+        }
+
+        $aTmplConfig = BxTemplConfig::getInstance()->__get('aLessConfig');
+        
+        $sId = 'bx-payment-rcrl-card-add';
+    	$sTitle = _t($this->_sLangsPrefix . 'popup_title_rcrl_card_add');
+		$sContent = BxTemplStudioFunctions::getInstance()->popupBox($sId, $sTitle, $this->_oModule->_oTemplate->parseHtmlByName('rcrl_card_popup_add.html', array(
+			'public_key' => $this->getOption('api_key_public'),
+			'font_family' => $aTmplConfig['bx-font-family'],
+			'font_size' => $aTmplConfig['bx-font-size-default'],
+			'font_color' => $aTmplConfig['bx-font-color-default'],
+			'form_id' => $oForm->aFormAttrs['id'],
+			'form' => $oForm->getCode(true),
+		)));
+
+		return array('popup' => array(
+			'html' => $sContent, 
+			'options' => array(
+				'closeOnOuterClick' => false, 
+				'onBeforeShow' => 'bxRecurlyFieldsInit();', 
+				'onHide' => 'bxRecurlyFieldsDestroy();'
+			)
+		));
+    }
+
+	public function createSubscription($sToken, $sItem, $aClient)
+	{
+		$oSubscription = false;
+
+		try {
+			$oSubscription = new Recurly_Subscription();
+			$oSubscription->plan_code = $sItem;
+
+			$oSubscription->account = new Recurly_Account();
+			$oSubscription->account->account_code = 'pat_smith';
+			$oSubscription->account->first_name = $aClient['first_name'];
+			$oSubscription->account->last_name = $aClient['last_name'];
+			$oSubscription->account->email = $aClient['email'];
+
+			$oSubscription->account->billing_info = new Recurly_BillingInfo();
+			$oSubscription->account->billing_info->token_id = $sToken;
+
+			$oSubscription->create();
+		}
+		catch (Exception $oException) {
+			$iError = $oException->getCode();
+			$sError = $oException->getMessage();
+
+			$this->log('Create Subscription Error: ' . $sError . '(' . $iError . ')');
+
+			return false;
+		}
+
+		return $oSubscription;
+	}
+
+	public function retrieveSubscription($sSubscriptionId)
+	{
+		$oSubscription = null;
+
+		try {
+			
+		}
+		catch (Exception $oException) {
+			$iError = $oException->getCode();
+			$sError = $oException->getMessage();
+
+			$this->log('Retrieve Subscription Error: ' . $sError . '(' . $iError . ')');
+
+			return false;
+		}
+
+		return $oSubscription;
+	}
+
+	public function deleteSubscription($sSubscriptionId)
+	{
+		try {
+
+		}
+		catch (Exception $oException) {
+			$aError = $oException->getJsonBody();
+
+			$this->log('Delete Subscription Error: ' . $aError['error']['message']);
+			$this->log($aError);
+
+			return false;
+		}
+
+		return true;
+	}
+
+	protected function _processEvent()
+	{
+    	$sInput = @file_get_contents("php://input");
+		$aEvent = json_decode($sInput, true);
+		if(empty($aEvent) || !is_array($aEvent)) 
+			return 404;
+
+		$sType = $aEvent['event_type'];
+		if(!in_array($sType, array('payment_succeeded', ' payment_refunded', 'subscription_cancelled')))
+			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 _processEventPaymentSucceeded(&$aEvent)
+	{
+		$mixedResult = $this->_getData($aEvent, true);
+		if($mixedResult === false)
+			return false;
+
+		list($aPending, $aTransaction) = $mixedResult;
+
+		$fTransactionAmount = (float)$aTransaction['amount'] / 100;
+		$sTransactionCurrency = strtoupper($aTransaction['currency_code']);
+		if($this->_bCheckAmount && ((float)$aPending['amount'] != $fTransactionAmount || strcasecmp($this->_oModule->_oConfig->getCurrencyCode(), $sTransactionCurrency) != 0))
+			return false;
+
+		return $this->_oModule->registerPayment($aPending);
+	}
+
+	protected function _processEventPaymentRefunded(&$aEvent)
+	{
+		$mixedResult = $this->_getData($aEvent);
+		if($mixedResult === false)
+			return false;
+
+		list($aPending) = $mixedResult;
+		return $this->_oModule->refundPayment($aPending);
+	}
+
+	protected function _processEventSubscriptionCancelled(&$aEvent)
+	{
+		$mixedResult = $this->_getData($aEvent);
+		if($mixedResult === false)
+			return false;
+
+		list($aPending) = $mixedResult;
+		return $this->_oModule->cancelSubscription($aPending);
+	}
+
+	protected function _getData(&$aEvent, $bWithStatusCheck = false)
+	{
+		$aTransaction = $aEvent['content']['transaction'];
+		if(empty($aTransaction) || ($bWithStatusCheck && $aTransaction['status'] != 'success'))
+			return false;
+
+		$aPending = $this->_oModule->_oDb->getOrderPending(array('type' => 'order', 'order' => $aTransaction['subscription_id']));
+		if(empty($aPending) || !is_array($aPending))
+			return false;
+
+		return array($aPending, $aTransaction);
+	}
+}
+
+/** @} */

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

@@ -282,4 +282,56 @@
 	<string name="_bx_payment_cbee_msg_subscribed"><![CDATA[Subscription was successfully created.]]></string>
 	<string name="_bx_payment_txt_cart_chargebee"><![CDATA[Chargebee]]></string>
 
+	<!-- Recurly payment provider -->
+	<string name="_bx_payment_rcrl_cpt"><![CDATA[Recurly]]></string>
+	<string name="_bx_payment_rcrl_dsc"><![CDATA[Recurly payment provider]]></string>
+	<string name="_bx_payment_rcrl_active_cpt"><![CDATA[Active]]></string>
+	<string name="_bx_payment_rcrl_active_dsc"><![CDATA[Activate(Deactivate) provider.]]></string>
+	<string name="_bx_payment_rcrl_active_err"><![CDATA[You should have SSL certificate (HTTPS connection) to activate and use Recurly on your site.]]></string>
+	<string name="_bx_payment_rcrl_mode_cpt"><![CDATA[Mode]]></string>
+	<string name="_bx_payment_rcrl_mode_dsc"><![CDATA[Mode to process payments]]></string>
+	<string name="_bx_payment_rcrl_mode_live"><![CDATA[live]]></string>
+	<string name="_bx_payment_rcrl_mode_test"><![CDATA[test]]></string>
+	<string name="_bx_payment_rcrl_site_cpt"><![CDATA[Site]]></string>
+	<string name="_bx_payment_rcrl_site_dsc"><![CDATA[Your site name registered in Recurly]]></string>
+	<string name="_bx_payment_rcrl_api_key_private_cpt"><![CDATA[Private API Key]]></string>
+	<string name="_bx_payment_rcrl_api_key_private_dsc"><![CDATA[Your Recurly Private API Access key]]></string>
+	<string name="_bx_payment_rcrl_api_key_public_cpt"><![CDATA[Public API Key]]></string>
+	<string name="_bx_payment_rcrl_api_key_public_dsc"><![CDATA[Your Recurly Public API Access key]]></string>
+	<string name="_bx_payment_rcrl_return_data_url_cpt"><![CDATA[Data return URL]]></string>
+	<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>
+	<string name="_bx_payment_form_rcrl_card_input_item_sys"><![CDATA[Item]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_token_sys"><![CDATA[Token]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_first_name_sys"><![CDATA[First Name]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_first_name"><![CDATA[First Name]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_first_name_err"><![CDATA[You need to fill in your first name here.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_last_name_sys"><![CDATA[Last Name]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_last_name"><![CDATA[Last Name]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_last_name_err"><![CDATA[You need to fill in your last name here.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_email_sys"><![CDATA[Email]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_email"><![CDATA[Email]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_email_err"><![CDATA[You need to fill in your email here.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_number_sys"><![CDATA[Card Number]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_number"><![CDATA[Card Number]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_number_inf"><![CDATA[16 digits without spaces describing your card number]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_sys"><![CDATA[Expire Date]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire"><![CDATA[Expire Date]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_month"><![CDATA[Month]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_year"><![CDATA[Year]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_inf"><![CDATA[Use 'mm yyyy' format]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_cvv_sys"><![CDATA[CVV]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_cvv"><![CDATA[CVV]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_cvv_inf"><![CDATA[Three digits from the back side of your card.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_submit_sys"><![CDATA[Submit]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_submit"><![CDATA[Submit]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_cancel_sys"><![CDATA[Cancel]]></string>
+	<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>
 </resources>

+ 50 - 4
modules/boonex/payment/install/sql/install.sql

@@ -140,6 +140,20 @@ INSERT INTO `bx_payment_providers_options`(`provider_id`, `name`, `type`, `capti
 (@iProviderId, 'cbee_return_data_url', 'value', '_bx_payment_cbee_return_data_url_cpt', '', '', '', '', '', 8),
 (@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');
+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, 'rcrl_active', 'checkbox', '_bx_payment_rcrl_active_cpt', '_bx_payment_rcrl_active_dsc', '', 'https', '', '_bx_payment_rcrl_active_err', 1),
+(@iProviderId, 'rcrl_mode', 'select', '_bx_payment_rcrl_mode_cpt', '_bx_payment_rcrl_mode_dsc', '1|_bx_payment_rcrl_mode_live,2|_bx_payment_rcrl_mode_test', '', '', '', 2),
+(@iProviderId, 'rcrl_site', 'text', '_bx_payment_rcrl_site_cpt', '_bx_payment_rcrl_site_dsc', '', '', '', '', 3),
+(@iProviderId, 'rcrl_api_key_private', 'text', '_bx_payment_rcrl_api_key_private_cpt', '_bx_payment_rcrl_api_key_private_dsc', '', '', '', '', 4),
+(@iProviderId, 'rcrl_api_key_public', 'text', '_bx_payment_rcrl_api_key_public_cpt', '_bx_payment_rcrl_api_key_public_dsc', '', '', '', '', 5),
+(@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);
+
 -- 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`, `override_class_name`, `override_class_file`) VALUES
 ('bx_payment_grid_carts', 'Array', '', '', 'vendor_id', '', '', '', 20, NULL, 'start', '', '', '', 'like', '', '', 'BxPaymentGridCarts', 'modules/boonex/payment/classes/BxPaymentGridCarts.php'),
@@ -209,11 +223,15 @@ INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon
 -- FORMS
 INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
 ('bx_payment_form_pendings', @sName, '_bx_payment_form_pendings_form', '', '', 'do_submit', 'bx_payment_transactions_pending', 'id', '', '', '', 0, 1, 'BxPaymentFormView', 'modules/boonex/payment/classes/BxPaymentFormView.php'),
-('bx_payment_form_processed', @sName, '_bx_payment_form_processed_form', '', '', 'do_submit', 'bx_payment_transactions', 'id', '', '', '', 0, 1, 'BxPaymentFormView', 'modules/boonex/payment/classes/BxPaymentFormView.php');
+('bx_payment_form_processed', @sName, '_bx_payment_form_processed_form', '', '', 'do_submit', 'bx_payment_transactions', 'id', '', '', '', 0, 1, 'BxPaymentFormView', 'modules/boonex/payment/classes/BxPaymentFormView.php'),
+-- FORMS: Recurly
+('bx_payment_form_rcrl_card', @sName, '_bx_payment_form_rcrl_card', '', '', 'do_submit', '', '', '', '', '', 0, 1, '', '');
 
 INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
 ('bx_payment_form_pendings_process', @sName, 'bx_payment_form_pendings', '_bx_payment_form_pendings_display_process', 0),
-('bx_payment_form_processed_add', @sName, 'bx_payment_form_processed', '_bx_payment_form_processed_display_add', 0);
+('bx_payment_form_processed_add', @sName, 'bx_payment_form_processed', '_bx_payment_form_processed_display_add', 0),
+-- FORMS: Recurly
+('bx_payment_form_rcrl_card_add', @sName, 'bx_payment_form_rcrl_card', '_bx_payment_form_rcrl_card_display_add', 0);
 
 INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
 ('bx_payment_form_pendings', @sName, 'id', '0', '', 0, 'hidden', '_bx_payment_form_pendings_input_id_sys', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
@@ -232,7 +250,21 @@ INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `c
 ('bx_payment_form_processed', @sName, 'items', '0', '', 0, 'custom', '_bx_payment_form_processed_input_items_sys', '_bx_payment_form_processed_input_items', '', 0, 0, 0, '', '', '', '', '', '_bx_payment_form_processed_input_items_err', '', '', 0, 0),
 ('bx_payment_form_processed', @sName, 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
 ('bx_payment_form_processed', @sName, 'do_submit', '_bx_payment_form_processed_input_add', '', 0, 'submit', '_bx_payment_form_processed_input_add_sys', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
-('bx_payment_form_processed', @sName, 'do_cancel', '_bx_payment_form_processed_input_cancel', '', 0, 'button', '_bx_payment_form_processed_input_cancel_sys', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
+('bx_payment_form_processed', @sName, 'do_cancel', '_bx_payment_form_processed_input_cancel', '', 0, 'button', '_bx_payment_form_processed_input_cancel_sys', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0),
+
+-- FORMS: Recurly
+('bx_payment_form_rcrl_card', @sName, 'pending_id', '0', '', 0, 'hidden', '_bx_payment_form_rcrl_card_input_pending_id_sys', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'item', '', '', 0, 'hidden', '_bx_payment_form_rcrl_card_input_item_sys', '', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'token', '', '', 0, 'hidden', '_bx_payment_form_rcrl_card_input_token_sys', '', '', 0, 0, 0, 'a:1:{s:12:"data-recurly";s:5:"token";}', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'first_name', '', '', 0, 'text', '_bx_payment_form_rcrl_card_input_first_name_sys', '_bx_payment_form_rcrl_card_input_first_name', '', 1, 0, 0, 'a:1:{s:12:"data-recurly";s:10:"first_name";}', '', '', 'Avail', '', '_bx_payment_form_rcrl_card_input_first_name_err', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'last_name', '', '', 0, 'text', '_bx_payment_form_rcrl_card_input_last_name_sys', '_bx_payment_form_rcrl_card_input_last_name', '', 1, 0, 0, 'a:1:{s:12:"data-recurly";s:9:"last_name";}', '', '', 'Avail', '', '_bx_payment_form_rcrl_card_input_last_name_err', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'email', '', '', 0, 'text', '_bx_payment_form_rcrl_card_input_email_sys', '_bx_payment_form_rcrl_card_input_email', '', 1, 0, 0, '', '', '', 'Email', '', '', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'card_number', '', '', 0, 'custom', '_bx_payment_form_rcrl_card_input_card_number_sys', '_bx_payment_form_rcrl_card_input_card_number', '_bx_payment_form_rcrl_card_input_card_number_inf', 1, 0, 0, 'a:1:{s:12:"data-recurly";s:6:"number";}', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'card_expire', '', '', 0, 'custom', '_bx_payment_form_rcrl_card_input_card_expire_sys', '_bx_payment_form_rcrl_card_input_card_expire', '_bx_payment_form_rcrl_card_input_card_expire_inf', 1, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'card_cvv', '', '', 0, 'custom', '_bx_payment_form_rcrl_card_input_card_cvv_sys', '_bx_payment_form_rcrl_card_input_card_cvv', '_bx_payment_form_rcrl_card_input_card_cvv_inf', 1, 0, 0, 'a:1:{s:12:"data-recurly";s:3:"cvv";}', '', '', '', '', '', 'Xss', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'do_submit', '_bx_payment_form_rcrl_card_input_submit', '', 0, 'submit', '_bx_payment_form_rcrl_card_input_submit_sys', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_payment_form_rcrl_card', @sName, 'do_cancel', '_bx_payment_form_rcrl_card_input_cancel', '', 0, 'button', '_bx_payment_form_rcrl_card_input_cancel_sys', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 0, 0);
 
 INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
 ('bx_payment_form_pendings_process', 'id', 2147483647, 1, 1),
@@ -251,7 +283,21 @@ INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_fo
 ('bx_payment_form_processed_add', 'items', 2147483647, 1, 7),
 ('bx_payment_form_processed_add', 'controls', 2147483647, 1, 8),
 ('bx_payment_form_processed_add', 'do_submit', 2147483647, 1, 9),
-('bx_payment_form_processed_add', 'do_cancel', 2147483647, 1, 10);
+('bx_payment_form_processed_add', 'do_cancel', 2147483647, 1, 10),
+
+-- FORMS: Recurly
+('bx_payment_form_rcrl_card_add', 'pending_id', 2147483647, 1, 1),
+('bx_payment_form_rcrl_card_add', 'item', 2147483647, 1, 2),
+('bx_payment_form_rcrl_card_add', 'token', 2147483647, 1, 3),
+('bx_payment_form_rcrl_card_add', 'first_name', 2147483647, 1, 4),
+('bx_payment_form_rcrl_card_add', 'last_name', 2147483647, 1, 5),
+('bx_payment_form_rcrl_card_add', 'email', 2147483647, 1, 6),
+('bx_payment_form_rcrl_card_add', 'card_number', 2147483647, 1, 7),
+('bx_payment_form_rcrl_card_add', 'card_expire', 2147483647, 1, 8),
+('bx_payment_form_rcrl_card_add', 'card_cvv', 2147483647, 1, 9),
+('bx_payment_form_rcrl_card_add', 'controls', 2147483647, 1, 10),
+('bx_payment_form_rcrl_card_add', 'do_submit', 2147483647, 1, 11),
+('bx_payment_form_rcrl_card_add', 'do_cancel', 2147483647, 1, 12);
 
 
 -- STUDIO PAGE & WIDGET

+ 32 - 0
modules/boonex/payment/template/css/recurly.css

@@ -0,0 +1,32 @@
+div.bx-form-input-date-expire,
+div.bx-form-input-de-cell {
+	position: relative;
+}
+div.bx-form-input-de-cell {
+	float: left;
+	width: 50%;
+}
+
+/*--- Recurly fields styles ---*/
+.recurly-hosted-field.recurly-hosted-field-number,
+.recurly-hosted-field.recurly-hosted-field-month,
+.recurly-hosted-field.recurly-hosted-field-year,
+.recurly-hosted-field.recurly-hosted-field-cvv {
+	height: 40px;
+    height: 2.5rem;
+    width: 100%;
+
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+
+	padding: 7px 3px 7px 3px;
+    padding: 0.2rem 0.4rem;
+
+	background-color: #ffffff;
+    border: 2px solid #79bd9a;
+    border-radius: 0.3rem;
+
+    box-shadow: none;
+}

+ 1 - 0
modules/boonex/payment/template/rcrl_card_field.html

@@ -0,0 +1 @@
+<div __attrs__></div>

+ 12 - 0
modules/boonex/payment/template/rcrl_card_field_date.html

@@ -0,0 +1,12 @@
+<div class="bx-form-input-date-expire bx-clearfix">
+	<div class="bx-form-input-de-cell">
+		<div class="bx-form-input-dec-cnt bx-def-padding-sec-rigth">
+			<div __attrs_month__></div>
+		</div>
+	</div>
+	<div class="bx-form-input-de-cell">
+		<div class="bx-form-input-dec-cnt bx-def-padding-sec-left">
+			<div __attrs_year__></div>
+		</div>
+	</div>
+</div>

+ 52 - 0
modules/boonex/payment/template/rcrl_card_popup_add.html

@@ -0,0 +1,52 @@
+<div class="bx-payment-rcrl-card-add">
+    <div class="bx-payment-rcrl-card-add-cnt">
+        __form__
+        <script type="text/javascript">
+	        function bxRecurlyFieldsInit() {
+	    		recurly.configure({
+					publicKey: '__public_key__',
+					style: {
+						all: {
+							fontFamily: '__font_family__',
+							fontSize: '__font_size__',
+							fontColor: '__font_color__',
+						},
+						month: {
+							placeholder: '<bx_text:_bx_payment_form_rcrl_card_input_card_expire_month />'
+						},
+						year: {
+							placeholder: '<bx_text:_bx_payment_form_rcrl_card_input_card_expire_year />'
+						},
+					}
+				});
+	    	}
+
+	        function bxRecurlyFieldsDestroy() {
+	        	/*
+	        	 * TODO: May be it'll be usefull when Recurly fix the issue with Multiple Times Configuration.
+	        	 * http://stackoverflow.com/questions/36430647/is-it-possible-to-build-configure-the-recurly-js-form-multiple-times
+				 */
+	        }
+
+            $(document).ready(function () {
+            	$("#__form_id__").on('submit', function (oEvent) {
+            		oEvent.preventDefault();
+
+            		var oForm = this;
+            		bx_loading(oForm, true);         		
+
+            		recurly.token(oForm, function(oError, oToken) {
+            			if(oError) {
+            				if(oError.message.length > 0)
+            					alert(oError.message);
+
+            				return;
+            			}
+
+            			oForm.submit();
+                    });
+            	});
+            });
+        </script>
+    </div>
+</div>

+ 53 - 1
modules/boonex/russian/data/langs/bx_payment/ru.xml

@@ -236,7 +236,7 @@
 	<string name="_bx_payment_bp_dsc"><![CDATA[Платежная система BitPay]]></string>
 	<string name="_bx_payment_bp_active_cpt"><![CDATA[Активировать]]></string>
 	<string name="_bx_payment_bp_active_dsc"><![CDATA[Активировать(деактивировать) платежную систему.]]></string>
-	<string name="_bx_payment_bp_active_err"><![CDATA[Нужен SSL сертификат (для HTTPS соединения) чтобы активировать и использовать BitPay.]]></string>
+	<string name="_bx_payment_bp_active_err"><![CDATA[Нужен SSL сертификат (для HTTPS соединения), чтобы активировать и использовать BitPay.]]></string>
 	<string name="_bx_payment_bp_api_key_cpt"><![CDATA[API ключ]]></string>
 	<string name="_bx_payment_bp_api_key_dsc"><![CDATA[API ключ платежной системы BitPay]]></string>
 	<string name="_bx_payment_bp_transaction_speed_cpt"><![CDATA[Скорость транзакции]]></string>
@@ -282,4 +282,56 @@
 	<string name="_bx_payment_cbee_msg_subscribed"><![CDATA[Подписка была успешно создана.]]></string>
 	<string name="_bx_payment_txt_cart_chargebee"><![CDATA[Chargebee]]></string>
 
+	<!-- Recurly payment provider -->
+	<string name="_bx_payment_rcrl_cpt"><![CDATA[Recurly]]></string>
+	<string name="_bx_payment_rcrl_dsc"><![CDATA[Платежная система Recurly]]></string>
+	<string name="_bx_payment_rcrl_active_cpt"><![CDATA[Активировать]]></string>
+	<string name="_bx_payment_rcrl_active_dsc"><![CDATA[Активировать(деактивировать) платежную систему.]]></string>
+	<string name="_bx_payment_rcrl_active_err"><![CDATA[Нужен SSL сертификат (для HTTPS соединения), чтобы активировать и использовать Recurly.]]></string>
+	<string name="_bx_payment_rcrl_mode_cpt"><![CDATA[Режим]]></string>
+	<string name="_bx_payment_rcrl_mode_dsc"><![CDATA[Режим обработки платежей]]></string>
+	<string name="_bx_payment_rcrl_mode_live"><![CDATA[рабочий]]></string>
+	<string name="_bx_payment_rcrl_mode_test"><![CDATA[тестовый]]></string>
+	<string name="_bx_payment_rcrl_site_cpt"><![CDATA[Имя сайта]]></string>
+	<string name="_bx_payment_rcrl_site_dsc"><![CDATA[Имя вашего сайта зарегистрированного в Recurly]]></string>
+	<string name="_bx_payment_rcrl_api_key_private_cpt"><![CDATA[Приватный API ключ]]></string>
+	<string name="_bx_payment_rcrl_api_key_private_dsc"><![CDATA[Ваш Приватный API ключ доступа, предоставленный Recurly.]]></string>
+	<string name="_bx_payment_rcrl_api_key_public_cpt"><![CDATA[Публичный API ключ]]></string>
+	<string name="_bx_payment_rcrl_api_key_public_dsc"><![CDATA[Ваш Публичный API ключ доступа, предоставленный Recurly.]]></string>
+	<string name="_bx_payment_rcrl_return_data_url_cpt"><![CDATA[URL для возврата данных]]></string>
+	<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>
+	<string name="_bx_payment_form_rcrl_card_input_item_sys"><![CDATA[Товар]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_token_sys"><![CDATA[Токен]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_first_name_sys"><![CDATA[Имя]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_first_name"><![CDATA[Имя]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_first_name_err"><![CDATA[Введите ваше имя.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_last_name_sys"><![CDATA[Фамилия]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_last_name"><![CDATA[Фамилия]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_last_name_err"><![CDATA[Введите вашу фамилию.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_email_sys"><![CDATA[Email]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_email"><![CDATA[Email]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_email_err"><![CDATA[Введите ваш email.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_number_sys"><![CDATA[Номер карты]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_number"><![CDATA[Номер карты]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_number_inf"><![CDATA[Введите 16-ти значный номер вашей карты.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_sys"><![CDATA[Дата окончания]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire"><![CDATA[Дата окончания]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_month"><![CDATA[Месяц]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_year"><![CDATA[Год]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_expire_inf"><![CDATA[Используйте 'mm yyyy' формат ввода.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_cvv_sys"><![CDATA[CVV]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_cvv"><![CDATA[CVV]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_card_cvv_inf"><![CDATA[Три цифры, которые Вы можете найти на обратной стороне вашей карты.]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_submit_sys"><![CDATA[Отправить]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_submit"><![CDATA[Отправить]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_cancel_sys"><![CDATA[Отменить]]></string>
+	<string name="_bx_payment_form_rcrl_card_input_cancel"><![CDATA[Отменить]]></string>
+	
+	<string name="_bx_payment_popup_title_rcrl_card_add"><![CDATA[Детали карты]]></string>
 </resources>