diff --git a/CHANGELOG.md b/CHANGELOG.md index dece706..49f7e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,11 @@ +1.1.0, 2024-11-22: +- Added compatibility with OpenMage and PHP latest versions. +- [technical] Enabled some features by plugin variant. +- Improve refund management. +- Update payment method default logo. +- Update list of supported payment means. +- Update list of supported currencies. +- Set return mode to POST by default. + 1.0.0, 2021-07-13: - Initial plugin version compatible with OpenMage 19 and 20. \ No newline at end of file diff --git a/COPYING.md b/COPYING.md index 83ab037..a6ff87e 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,4 +1,4 @@ -Copyright © 2021 Lyra Network. +Copyright © 2021-2024 Lyra Network. PayZen plugin for OpenMage is licensed under the Open Software License version 3.0 that is bundled with diff --git a/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/ContactUs.php b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/ContactUs.php index 41b6dcd..8448d59 100644 --- a/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/ContactUs.php +++ b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/ContactUs.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + /** * Custom renderer for the contact us element. */ @@ -21,7 +23,7 @@ class Lyranetwork_Payzen_Block_Adminhtml_System_Config_Field_ContactUs extends M */ public function render(Varien_Data_Form_Element_Abstract $element) { - $comment = Lyranetwork_Payzen_Model_Api_Api::formatSupportEmails('support@payzen.eu'); + $comment = PayzenApi::formatSupportEmails('support@payzen.eu'); $element->setComment($comment); return parent::render($element); diff --git a/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Multi/PaymentOptions.php b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Multi/PaymentOptions.php index 6697510..110c3cf 100644 --- a/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Multi/PaymentOptions.php +++ b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Multi/PaymentOptions.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + /** * Custom renderer for the multi payment options field. */ @@ -38,7 +40,7 @@ public function __construct() ) ); - $cards = Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes(); + $cards = PayzenApi::getSupportedCardTypes(); if (isset($cards['CB'])) { // If CB is available, we allow contract override. $this->addColumn( diff --git a/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Other/PaymentMeans.php b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Other/PaymentMeans.php index e556600..ddc367d 100644 --- a/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Other/PaymentMeans.php +++ b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/Other/PaymentMeans.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + /** * Custom renderer for the other payment means field. */ @@ -24,7 +26,7 @@ public function __construct() ) ); - $defaultCards = Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes(); + $defaultCards = PayzenApi::getSupportedCardTypes(); $addedCards = Mage::getModel('payzen/payment_other')->getAddedMeans(); $cards = array_merge ($defaultCards, $addedCards); diff --git a/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/PluginDoc.php b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/PluginDoc.php new file mode 100644 index 0000000..4c84f2d --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Block/Adminhtml/System/Config/Field/PluginDoc.php @@ -0,0 +1,45 @@ + 'Français', + 'en' => 'English', + 'es' => 'Español', + 'pt' => 'Português' + // Complete when other languages are managed. + ); + + $docs = ""; + foreach (PayzenApi::getOnlineDocUri() as $lang => $docUri) { + $docs .= '' . $languages[$lang] . ''; + } + + $element->setComment($docs); + + return parent::render($element); + } +} \ No newline at end of file diff --git a/app/code/community/Lyranetwork/Payzen/Block/Info.php b/app/code/community/Lyranetwork/Payzen/Block/Info.php index 9188c05..84936bc 100644 --- a/app/code/community/Lyranetwork/Payzen/Block/Info.php +++ b/app/code/community/Lyranetwork/Payzen/Block/Info.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Response as PayzenResponse; + class Lyranetwork_Payzen_Block_Info extends Mage_Payment_Block_Info { protected function _construct() @@ -80,7 +82,7 @@ public function getResultDescription() } if ($key === 'result' && $allResults[$key] === '30') { // Append form error if any. - $label .= ' ' . Lyranetwork_Payzen_Model_Api_Response::extraMessage($allResults['extra_result']); + $label .= ' ' . PayzenResponse::extraMessage($allResults['extra_result']); } $labels[] = $label; @@ -92,6 +94,6 @@ public function getResultDescription() public function translate($code, $type, $appendCode = false) { $lang = strtolower(substr(Mage::app()->getLocale()->getLocaleCode(), 0, 2)); - return Lyranetwork_Payzen_Model_Api_Response::translate($code, $type, $lang, $appendCode); + return PayzenResponse::translate($code, $type, $lang, $appendCode); } } diff --git a/app/code/community/Lyranetwork/Payzen/Helper/Payment.php b/app/code/community/Lyranetwork/Payzen/Helper/Payment.php index 6a4bdb9..a21c1cd 100644 --- a/app/code/community/Lyranetwork/Payzen/Helper/Payment.php +++ b/app/code/community/Lyranetwork/Payzen/Helper/Payment.php @@ -8,6 +8,10 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; +use Lyranetwork\Payzen\Model\Api\Rest\Api as PayzenRest; +use Lyranetwork\Payzen\Model\Api\Form\Response as PayzenResponse; + class Lyranetwork_Payzen_Helper_Payment extends Mage_Core_Helper_Abstract { const IDENTIFIER = 'payzen_identifier'; // Key to save if payment is by identifier. @@ -108,7 +112,7 @@ public function doPaymentReturn($controller) $storeId = $order->getStore()->getId(); // Load API response. - $response = new Lyranetwork_Payzen_Model_Api_Response( + $response = new PayzenResponse( $request, $this->_getHelper()->getCommonConfigData('ctx_mode', $storeId), $this->_getHelper()->getCommonConfigData('key_test', $storeId), @@ -227,7 +231,7 @@ public function doPaymentCheck($controller) Mage::app()->init($storeId, 'store'); // Load API response. - $response = new Lyranetwork_Payzen_Model_Api_Response( + $response = new PayzenResponse( $post, $this->_getHelper()->getCommonConfigData('ctx_mode', $storeId), $this->_getHelper()->getCommonConfigData('key_test', $storeId), @@ -311,13 +315,13 @@ public function doPaymentCheck($controller) $transactionId = $response->get('trans_id') . '-' . $response->get('sequence_number'); // Save paid amount. - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($response->get('currency')); + $currency = PayzenApi::findCurrencyByNumCode($response->get('currency')); $amount = number_format($currency->convertAmountToFloat($response->get('amount')), $currency->getDecimals(), ',', ' '); $amountDetail = $amount . ' ' . $currency->getAlpha3(); if ($response->get('effective_currency') && ($response->get('currency') !== $response->get('effective_currency'))) { - $effectiveCurrency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($response->get('effective_currency')); + $effectiveCurrency = PayzenApi::findCurrencyByNumCode($response->get('effective_currency')); $effectiveAmount = number_format( $effectiveCurrency->convertAmountToFloat($response->get('effective_amount')), @@ -390,8 +394,8 @@ public function doPaymentRestReturn($controller) // Wrap payment result to use traditional order creation tunnel. $data = $this->_getRestHelper()->convertRestResult($answer); - /** @var Lyranetwork_Payzen_Model_Api_Response $response */ - $response = new Lyranetwork_Payzen_Model_Api_Response($data, null, null, null); + /** @var PayzenResponse $response */ + $response = new PayzenResponse($data, null, null, null); $quoteId = (int) $response->getExtInfo('quote_id'); // Quote ID is sent to platform as ext_info. $quote = Mage::getModel('sales/quote'); @@ -530,7 +534,7 @@ public function doPaymentRestCheck($controller) // Wrap payment result to use traditional order creation tunnel. $data = $this->_getRestHelper()->convertRestResult($answer); - $response = new Lyranetwork_Payzen_Model_Api_Response($data, null, null, null); + $response = new PayzenResponse($data, null, null, null); $quoteId = (int) $response->getExtInfo('quote_id'); // Quote ID is sent to platform as ext_info. $quote = Mage::getModel('sales/quote'); @@ -678,7 +682,7 @@ public function deleteIdentifier($attribute, $maskedAttribute) ); // Perform REST request to cancel identifier. - $client = new Lyranetwork_Payzen_Model_Api_Rest( + $client = new PayzenRest( $this->_getHelper()->getCommonConfigData('rest_url'), $this->_getHelper()->getCommonConfigData('site_id'), $this->_getRestHelper()->getPassword() @@ -729,7 +733,7 @@ private function deleteIdentifierAttribute($customer, $attribute, $maskedAttribu $this->_getHelper()->log("Identifier for customer {$customer->getEmail()} successfully deleted."); } - private function _isPaymentSuccessfullyProcessed(Lyranetwork_Payzen_Model_Api_Response $response) + private function _isPaymentSuccessfullyProcessed(PayzenResponse $response) { if ($response->isAcceptedPayment()) { return true; @@ -742,9 +746,9 @@ private function _isPaymentSuccessfullyProcessed(Lyranetwork_Payzen_Model_Api_Re * Update order status and eventually create invoice. * * @param Mage_Sales_Model_Order $order - * @param Lyranetwork_Payzen_Model_Api_Response $response + * @param PayzenResponse $response */ - protected function _registerOrder(Mage_Sales_Model_Order $order, Lyranetwork_Payzen_Model_Api_Response $response) + protected function _registerOrder(Mage_Sales_Model_Order $order, PayzenResponse $response) { $this->_getHelper()->log("Saving payment for order #{$order->getIncrementId()}."); @@ -798,7 +802,7 @@ protected function _registerOrder(Mage_Sales_Model_Order $order, Lyranetwork_Pay * Update order payment information. * * @param Mage_Sales_Model_Order $order - * @param Lyranetwork_Payzen_Model_Api_Response $response + * @param PayzenResponse $response */ public function updatePaymentInfo(Mage_Sales_Model_Order $order, $response) { @@ -853,7 +857,7 @@ public function updatePaymentInfo(Mage_Sales_Model_Order $order, $response) // Set is_fraud_detected flag. $order->getPayment()->setIsFraudDetected($response->isSuspectedFraud()); - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($response->get('currency')); + $currency = PayzenApi::findCurrencyByNumCode($response->get('currency')); if ($response->get('card_brand') === 'MULTI') { // Multi brand. $data = Mage::helper('core')->jsonDecode($response->get('payment_seq'), Zend_Json::TYPE_OBJECT); @@ -961,7 +965,7 @@ public function updatePaymentInfo(Mage_Sales_Model_Order $order, $response) && ($response->get('currency') !== $response->get('effective_currency')) ) { // Effective amount. - $effectiveCurrency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($response->get('effective_currency')); + $effectiveCurrency = PayzenApi::findCurrencyByNumCode($response->get('effective_currency')); $effectiveAmount= number_format( $effectiveCurrency->convertAmountToFloat((int) ($amount / $rate)), @@ -1002,7 +1006,7 @@ public function updatePaymentInfo(Mage_Sales_Model_Order $order, $response) if ($response->get('effective_currency') && ($response->get('currency') !== $response->get('effective_currency'))) { // Effective amount. - $effectiveCurrency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($response->get('effective_currency')); + $effectiveCurrency = PayzenApi::findCurrencyByNumCode($response->get('effective_currency')); $effectiveAmount = number_format( $effectiveCurrency->convertAmountToFloat($response->get('effective_amount')), @@ -1059,7 +1063,7 @@ private function getThreedsStatus($status) } } - private function _saveIdentifier(Mage_Sales_Model_Order $order, Lyranetwork_Payzen_Model_Api_Response $response) + private function _saveIdentifier(Mage_Sales_Model_Order $order, PayzenResponse $response) { if (! $order->getCustomerId()) { return; @@ -1109,7 +1113,7 @@ private function _saveIdentifier(Mage_Sales_Model_Order $order, Lyranetwork_Payz } } - private function _saveSepaIdentifier(Mage_Sales_Model_Order $order, Lyranetwork_Payzen_Model_Api_Response $response) + private function _saveSepaIdentifier(Mage_Sales_Model_Order $order, PayzenResponse $response) { if (! $order->getCustomerId()) { return; @@ -1196,9 +1200,9 @@ public function createInvoice(Mage_Sales_Model_Order $order) * Cancel order. * * @param Mage_Sales_Model_Order $order - * @param Lyranetwork_Payzen_Model_Api_Response $response + * @param PayzenResponse $response */ - protected function _cancelOrder(Mage_Sales_Model_Order $order, Lyranetwork_Payzen_Model_Api_Response $response) + protected function _cancelOrder(Mage_Sales_Model_Order $order, PayzenResponse $response) { $this->_getHelper()->log("Canceling order #{$order->getIncrementId()}."); @@ -1296,7 +1300,7 @@ public function addTransaction($payment, $type, $transactionId, $additionalInfo, /** * Get new order state and status according to the gateway response. * - * @param Lyranetwork_Payzen_Model_Api_Response $response + * @param PayzenResponse $response * @param Mage_Sales_Model_Order $order * @param boolean $ignoreFraud * @return Varien_Object @@ -1369,8 +1373,8 @@ public function convertTxnType($payzenType) $type = false; $successStatuses = array_merge( - Lyranetwork_Payzen_Model_Api_Api::getSuccessStatuses(), - Lyranetwork_Payzen_Model_Api_Api::getPendingStatuses() + PayzenApi::getSuccessStatuses(), + PayzenApi::getPendingStatuses() ); switch (true) { diff --git a/app/code/community/Lyranetwork/Payzen/Helper/Refund.php b/app/code/community/Lyranetwork/Payzen/Helper/Refund.php new file mode 100644 index 0000000..9c17192 --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Helper/Refund.php @@ -0,0 +1,140 @@ +payment = $payment; + return $this; + } + + /** + * Action to do in case of error during refund process. + * + */ + public function doOnError($errorCode, $message) + { + if ($errorCode === 'PSP_100') { + // Merchant has not subscribed to REST WS option, let Magento refund payment offline. + $notice = $this->translate('You are not authorized to do this action online. Please, do not forget to update payment in PayZen Back Office.'); + $this->_getAdminSession()->addWarning($notice); + } else { + $this->_getAdminSession()->addWarning($message); + } + } + + public function doOnSuccess($operationResponse, $operationType) + { + $order = Mage::getModel('sales/order'); + $orderId = $operationResponse['orderDetails']['orderId']; + $order->loadByIncrementId($orderId); + + if ($operationType == 'refund') { // Actual refund. + $this->_createRefundTransaction($this->payment, $operationResponse); + } elseif ($operationType == 'cancel') { // Cancellation. + $order->cancel(); + } + } + + protected function _createRefundTransaction($payment, $refundResponse) + { + $response = $this->_getRestHelper()->convertRestResult($refundResponse, true); + + // Save transaction details to sales_payment_transaction. + $transactionId = $response['vads_trans_id'] . '-' . $response['vads_sequence_number']; + + $expiry = ''; + if ($response['vads_expiry_month'] && $response['vads_expiry_year']) { + $expiry = str_pad($response['vads_expiry_month'], 2, '0', STR_PAD_LEFT) . ' / ' . + $response['vads_expiry_year']; + } + + // Save paid amount. + $currency = PayzenApi::findCurrencyByNumCode($response['vads_currency']); + $amount = number_format($currency->convertAmountToFloat($response['vads_amount']), $currency->getDecimals(), ',', ' '); + + $amountDetail = $amount . ' ' . $currency->getAlpha3(); + + if ($response['vads_effective_currency'] && + ($response['vads_currency'] !== $response['vads_effective_currency'])) { + $effectiveCurrency = _PayzenApi::findCurrencyByNumCode($response['vads_effective_currency']); + + $effectiveAmount = number_format( + $effectiveCurrency->convertAmountToFloat($response['vads_effective_amount']), + $effectiveCurrency->getDecimals(), + ',', + ' ' + ); + + $amountDetail = $effectiveAmount . ' ' . $effectiveCurrency->getAlpha3() . ' (' . $amountDetail . ')'; + } + + $additionalInfo = array( + 'Transaction Type' => 'CREDIT', + 'Amount' => $amountDetail, + 'Transaction ID' => $transactionId, + 'Transaction UUID' => $response['vads_trans_uuid'], + 'Transaction Status' => $response['vads_trans_status'], + 'Means of payment' => $response['vads_card_brand'], + 'Card Number' => $response['vads_card_number'], + 'Expiration Date' => $expiry + ); + + $transactionType = Mage_Sales_Model_Order_Payment_Transaction::TYPE_REFUND; + $this->_getPaymentHelper()->addTransaction($payment, $transactionType, $transactionId, $additionalInfo); + } + + /** + * Action to do after failed refund process. + * + */ + public function doOnFailure($errorCode, $message) + { + Mage::throwException($message, $errorCode); + } + + /** + * Translate given message. + * + */ + public function translate($message) + { + return $this->_getHelper()->__($message); + } + + public function log($message, $level = Zend_Log::INFO) + { + $this->_getHelper()->log($message, $level); + } + + protected function _getRestHelper() + { + return Mage::helper('payzen/rest'); + } + + protected function _getPaymentHelper() + { + return Mage::helper('payzen/payment'); + } + protected function _getHelper() + { + return Mage::helper('payzen'); + } + + protected function _getAdminSession() + { + return Mage::getSingleton('adminhtml/session'); + } +} \ No newline at end of file diff --git a/app/code/community/Lyranetwork/Payzen/Helper/Rest.php b/app/code/community/Lyranetwork/Payzen/Helper/Rest.php index 5a01a6e..6ef02d3 100644 --- a/app/code/community/Lyranetwork/Payzen/Helper/Rest.php +++ b/app/code/community/Lyranetwork/Payzen/Helper/Rest.php @@ -8,6 +8,9 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; +use Lyranetwork\Payzen\Model\Api\Rest\Api as PayzenRest; + class Lyranetwork_Payzen_Helper_Rest extends Mage_Core_Helper_Abstract { public function convertRestResult($answer, $isTransaction = false) @@ -45,8 +48,8 @@ public function convertRestResult($answer, $isTransaction = false) $response['vads_amount'] = $this->getProperty($transaction, 'amount'); - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrency($this->getProperty($transaction, 'currency')); - $response['vads_currency'] = Lyranetwork_Payzen_Model_Api_Api::getCurrencyNumCode($currency->getAlpha3()); + $currency = PayzenApi::findCurrency($this->getProperty($transaction, 'currency')); + $response['vads_currency'] = PayzenApi::getCurrencyNumCode($currency->getAlpha3()); if ($paymentToken = $this->getProperty($transaction, 'paymentMethodToken')) { $response['vads_identifier'] = $paymentToken; @@ -67,7 +70,7 @@ public function convertRestResult($answer, $isTransaction = false) $response['vads_sequence_number'] = $this->getProperty($transactionDetails, 'sequenceNumber'); $effectiveAmount = $this->getProperty($transactionDetails, 'effectiveAmount'); - $effectiveCurrency = Lyranetwork_Payzen_Model_Api_Api::getCurrencyNumCode($this->getProperty($transactionDetails, 'effectiveCurrency')); + $effectiveCurrency = PayzenApi::getCurrencyNumCode($this->getProperty($transactionDetails, 'effectiveCurrency')); // Workarround to adapt to REST API behavior. if ($effectiveAmount && $effectiveCurrency) { @@ -207,7 +210,7 @@ public function checkIdentifier($identifier, $customerEmail) ); // Perform REST request to check identifier. - $client = new Lyranetwork_Payzen_Model_Api_Rest( + $client = new PayzenRest( $this->_getHelper()->getCommonConfigData('rest_url'), $this->_getHelper()->getCommonConfigData('site_id'), $this->getPassword() diff --git a/app/code/community/Lyranetwork/Payzen/Helper/Util.php b/app/code/community/Lyranetwork/Payzen/Helper/Util.php index 63c7afd..1e398b8 100644 --- a/app/code/community/Lyranetwork/Payzen/Helper/Util.php +++ b/app/code/community/Lyranetwork/Payzen/Helper/Util.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + class Lyranetwork_Payzen_Helper_Util extends Mage_Core_Helper_Abstract { const ORDER_ID_REGEX = '#^[a-zA-Z0-9]{1,9}$#u'; @@ -231,7 +233,7 @@ public function setCartData($order, &$payzenRequest, $needsCartData = false) $notAllowed = '#[^A-Z0-9ÁÀÂÄÉÈÊËÍÌÎÏÓÒÔÖÚÙÛÜÇ ]#ui'; // Used currency. - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($payzenRequest->get('currency')); + $currency = PayzenApi::findCurrencyByNumCode($payzenRequest->get('currency')); $subtotal = 0; diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Api.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Api.php new file mode 100644 index 0000000..708eb52 --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Api.php @@ -0,0 +1,446 @@ + 'German', + 'en' => 'English', + 'zh' => 'Chinese', + 'es' => 'Spanish', + 'fr' => 'French', + 'it' => 'Italian', + 'ja' => 'Japanese', + 'nl' => 'Dutch', + 'pl' => 'Polish', + 'pt' => 'Portuguese', + 'ru' => 'Russian', + 'sv' => 'Swedish', + 'tr' => 'Turkish' + ); + } + + /** + * Returns true if the entered language (ISO code) is supported. + * + * @param string $lang + * @return boolean + */ + public static function isSupportedLanguage($lang) + { + $supportedLanguages = self::getSupportedLanguages(); + return isset($supportedLanguages[strtolower($lang)]); + } + + /** + * Return the list of currencies recognized by the payment gateway. + * + * @return array[int][Lyranetwork\Payzen\Model\Api\Form\Currency] + */ + public static function getSupportedCurrencies() + { + $currencies = array( + array('ARS', '032', 2), array('AUD', '036', 2), array('BRL', '986', 2), array('CAD', '124', 2), + array('CHF', '756', 2), array('CNY', '156', 2), array('COP', '170', 2), array('CZK', '203', 2), + array('DKK', '208', 2), array('EUR', '978', 2), array('GBP', '826', 2), array('HKD', '344', 2), + array('HUF', '348', 2), array('IDR', '360', 0), array('INR', '356', 2), array('JPY', '392', 0), + array('KHR', '116', 0), array('KRW', '410', 0), array('KWD', '414', 3), array('MAD', '504', 2), + array('MXN', '484', 2), array('MYR', '458', 2), array('NOK', '578', 2), array('NZD', '554', 2), + array('PEN', '604', 2), array('PHP', '608', 2), array('PLN', '985', 2), array('RUB', '643', 2), + array('SEK', '752', 2), array('SGD', '702', 2), array('THB', '764', 2), array('TND', '788', 3), + array('TRY', '949', 2), array('TWD', '901', 2), array('USD', '840', 2), array('XOF', '952', 0), + array('XPF', '953', 0), array('ZAR', '710', 2) + ); + + $supported_currencies = array(); + + foreach ($currencies as $currency) { + $supported_currencies[] = new Currency($currency[0], $currency[1], $currency[2]); + } + + return $supported_currencies; + } + + /** + * Return a currency from its 3-letters ISO code. + * + * @param string $alpha3 + * @return \Lyranetwork\Payzen\Model\Api\Form\Currency|null + */ + public static function findCurrencyByAlphaCode($alpha3) + { + $list = self::getSupportedCurrencies(); + foreach ($list as $currency) { + /** + * @var \Lyranetwork\Payzen\Model\Api\Form\Currency $currency + */ + if ($currency->getAlpha3() === $alpha3) { + return $currency; + } + } + + return null; + } + + /** + * Returns a currency form its numeric ISO code. + * + * @param int $numeric + * @return \Lyranetwork\Payzen\Model\Api\Form\Currency|null + */ + public static function findCurrencyByNumCode($numeric) + { + $list = self::getSupportedCurrencies(); + foreach ($list as $currency) { + /** + * @var \Lyranetwork\Payzen\Model\Api\Form\Currency $currency + */ + if ($currency->getNum() == $numeric) { + return $currency; + } + } + + return null; + } + + /** + * Return a currency from its 3-letters or numeric ISO code. + * + * @param string $code + * @return \Lyranetwork\Payzen\Model\Api\Form\Currency|null + */ + public static function findCurrency($code) + { + $list = self::getSupportedCurrencies(); + foreach ($list as $currency) { + /** + * @var \Lyranetwork\Payzen\Model\Api\Form\Currency $currency + */ + if ($currency->getNum() === $code || $currency->getAlpha3() === $code) { + return $currency; + } + } + + return null; + } + + /** + * Returns currency numeric ISO code from its 3-letters code. + * + * @param string $alpha3 + * @return string|null + */ + public static function getCurrencyNumCode($alpha3) + { + $currency = self::findCurrencyByAlphaCode($alpha3); + return ($currency instanceof Currency) ? $currency->getNum() : null; + } + + /** + * Returns an array of card types accepted by the payment gateway. + * + * @return array[string][string] + */ + public static function getSupportedCardTypes() + { + return array( + 'CB' => 'CB', 'E-CARTEBLEUE' => 'e-Carte Bleue', 'MAESTRO' => 'Maestro', 'MASTERCARD' => 'Mastercard', + 'VISA' => 'Visa', 'VISA_ELECTRON' => 'Visa Electron', 'VPAY' => 'V PAY', 'AMEX' => 'American Express', + 'ACCORD_STORE' => 'Cartes Enseignes Partenaires', 'ACCORD_STORE_SB' => 'Cartes Enseignes Partenaires (sandbox)', + 'ALINEA' => 'Carte myalinea', 'ALINEA_CDX' => 'Carte Cadeau Alinéa', 'ALINEA_CDX_SB' => 'Carte Cadeau Alinéa (sandbox)', + 'ALINEA_SB' => 'Carte myalinea (sandbox)', 'ALIPAY' => 'Alipay', 'ALLOBEBE_CDX' => 'Carte Cadeau Allobébé', + 'ALLOBEBE_CDX_SB' => 'Carte Cadeau Allobébé (sandbox)', 'ALMA' => 'Alma en 1 fois', 'ALMA_10X' => 'Alma en 10 fois', + 'ALMA_12X' => 'Alma en 12 fois', 'ALMA_2X' => 'Alma en 2 fois', 'ALMA_3X' => 'Alma en 3 fois', + 'ALMA_4X' => 'Alma en 4 fois', 'APETIZ' => 'Apetiz', 'APPLE_PAY' => 'Apple Pay', + 'AUCHAN' => 'Carte Auchan', 'AUCHAN_SB' => 'Carte Auchan (sandbox)', 'AURORE-MULTI' => 'Cpay Aurore', + 'BANCONTACT' => 'Bancontact Mistercash', 'BIZUM' => 'Bizum', 'BIZZBEE_CDX' => 'Carte Cadeau Bizzbee', + 'BIZZBEE_CDX_SB' => 'Carte Cadeau Bizzbee (sandbox)', 'BOULANGER' => 'Carte b+', + 'BOULANGER_SB' => 'Carte b+ (sandbox)', 'BRICE_CDX' => 'Carte Cadeau Brice', 'BRICE_CDX_SB' => 'Carte Cadeau Brice (sandbox)', + 'BUT' => 'But', 'CABAL' => 'Cabal', 'CARNET' => 'Carnet', 'CASINO' => 'Banque Casino', 'CA_DO_CARTE' => 'CA DO Carte', + 'CDGP' => 'Carte Privilège', 'CDISCOUNT' => 'CDiscount', 'CHQ_DEJ' => 'Chèque Déjeuner', 'COF3XCB' => 'Cofinoga 3 fois CB', + 'COF3XCB_SB' => 'Cofinoga 3 fois CB Sandbox', 'COFIDIS_3X_BE' => 'Cofidis en 3 fois', 'COFIDIS_3X_FR' => 'Cofidis en 3 fois', + 'COFIDIS_4X_ES' => 'Cofidis en 4 vencimientos', 'COFIDIS_4X_FR' => 'Cofidis en 4 fois', 'COFIDIS_DFPAY_FR' => 'Cofidis Pay Later', + 'COFIDIS_LOAN_BE' => 'Cofidis en 6-12-18 fois', 'COFIDIS_LOAN_CB' => 'Cofidis en 5-12 fois', 'COFIDIS_LOAN_BE' => 'Cofidis en 6-12-18 fois', + 'COFIDIS_LOAN_ES' => 'Cofidis en 6-12-24 vencimientos', 'COFIDIS_LOAN_FR' => 'Amortissable', 'COFIDIS_LOAN_IT' => 'Cofidis Pagodil', + 'COFIDIS_PAY_FR' => 'Cofidis Pay', 'COFINOGA' => 'Cofinoga', 'COM_BARRY_CDX' => 'Carte Cadeau Comtesse du Barry', + 'COM_BARRY_CDX_SB' => 'Carte Cadeau Comtesse du Barry (sandbox)', 'CONECS' => 'Conecs', 'CONFORAMA' => 'Conforama', + 'CORA' => 'Cora', 'CORA_BLANCHE' => 'Cora blanche', 'CORA_PREM' => 'Cora Visa Premier', 'CORA_VISA' => 'Cora Visa', + 'CVCO' => 'Chèque-Vacances Connect', 'DINERS' => 'Diners', 'DISCOVER' => 'Discover', 'ECCARD' => 'EC Card', + 'EDENRED' => 'Ticket Restaurant', 'EDENRED_CC' => 'Ticket Cheque Consommation', 'EDENRED_EC' => 'Ticket EcoCheque', + 'EDENRED_SC' => 'Ticket Sport & Culture', 'EDENRED_TC' => 'Ticket Compliments', 'EDENRED_TR' => 'Ticket Restaurant', 'ELO' => 'Elo', + 'ELV' => 'ELV', 'FRANFINANCE_3X' => 'Paiement en 3 fois', 'FRANFINANCE_4X' => 'Paiement en 4 fois', 'FULLCB3X' => 'Paiement en 3 fois CB', + 'FULLCB4X' => 'Paiement en 4 fois CB', 'GEMO_CDX' => 'Carte Cadeau Gémo', 'GEMO_CDX_SB' => 'Carte Cadeau Gémo (sandbox)', + 'GIROPAY' => 'Giropay', 'GOOGLEPAY' => 'Google Pay', 'HIPER' => 'Hiper', 'HIPERCARD' => 'Hipercard', 'IDEAL' => 'iDEAL', + 'ILLICADO' => 'Carte Illicado', 'ILLICADO_SB' => 'Carte Illicado (sandbox)', 'IP_WIRE' => 'Virement SEPA', + 'IP_WIRE_INST' => 'Virement SEPA Instantané', 'JCB' => 'JCB', 'JOUECLUB_CDX' => 'Carte Cadeau Joué Club', + 'JOUECLUB_CDX_SB' => 'Carte Cadeau Joué Club (sandbox)', 'JULES_CDX' => 'Carte Cadeau Jules', + 'JULES_CDX_SB' => 'Carte Cadeau Jules (sandbox)', 'KADEOS_CULTURE' => 'Carte Kadéos Culture', + 'KADEOS_GIFT' => 'Carte Kadéos Zénith', 'KLARNA' => 'Klarna', 'LECLERC' => 'Carte Reglo', + 'LEROY-MERLIN' => 'Carte Maison Financement', 'LEROY-MERLIN_SB' => 'Carte Maison Financement (sandbox)', + 'MASTERPASS' => 'MasterPass', 'MC_CORDOBESA' => 'Mastercard Cordobesa', 'MULTIBANCO' => 'Multibanco', 'MYBANK' => 'MyBank', + 'NARANJA' => 'Naranja', 'NORAUTO' => 'Carte Norauto option Financement', 'NORAUTO_SB' => 'Carte Norauto option Financement (sandbox)', + 'ONEY' => 'Paiement en 3 ou 4 fois par CB', 'ONEY_10X_12X' => 'Paiement en 10 ou 12 fois Oney', + 'ONEY_3X_4X' => 'Paiement en 3 ou 4 fois Oney', 'ONEY_ENSEIGNE' => 'Cartes enseignes Oney', 'ONEY_PAYLATER' => 'Pay Later Oney', + 'ONEY_SANDBOX' => 'Paiement en 3 ou 4 fois par CB (sandbox)', 'PASS_BEAU_CDX' => 'Carte Cadeau Passion Beauté', + 'PASS_BEAU_CDX_SB' => 'Carte Cadeau Passion Beauté (sandbox)', 'PAYBOX' => 'Paybox', 'PAYDIREKT' => 'Paydirekt', 'PAYPAL' => 'PayPal', + 'PAYPAL_BNPL' => 'PayPal Pay Later', 'PAYPAL_BNPL_SB' => 'PayPal Pay Later Sandbox', + 'PAYPAL_SB' => 'PayPal Sandbox', 'PICWIC' => 'Carte Picwic', 'PICWIC_SB' => 'Carte Picwic (sandbox)', + 'POSTFINANCE' => 'PostFinance Card', 'POSTFINANCE_EFIN' => 'PostFinance E-Finance', 'PRESTO' => 'Presto', + 'PRZELEWY24' => 'Przelewy24', 'S-MONEY' => 'S-money', 'SCT' => 'Virement SEPA', 'SDD' => 'Prélèvement SEPA', + 'SODEXO' => 'Pass Restaurant', 'SOFICARTE' => 'Soficarte', 'SOFORT_BANKING' => 'Sofort', 'SOROCRED' => 'Sorocred', + 'SYGMA' => 'Sygma', 'TRUFFAUT_CDX' => 'Carte Cadeau Truffaut', 'VILLAVERDE' => 'Carte Cadeau VillaVerde', + 'VILLAVERDE_SB' => 'Carte Cadeau VillaVerde (sandbox)', 'WECHAT' => 'WeChat Pay' + ); + } + + /** + * Return the statuses list of finalized successful payments (authorized or captured). + * @return string[] + */ + public static function getSuccessStatuses() + { + return array( + 'AUTHORISED', + 'CAPTURED', + 'ACCEPTED', + 'PARTIALLY_AUTHORISED' + ); + } + + /** + * Return the statuses list of payments that are waiting confirmation (successful but + * the amount has not been transfered and is not yet guaranteed). + * @return string[] + */ + public static function getPendingStatuses() + { + return array( + 'INITIAL', + 'WAITING_AUTHORISATION', + 'WAITING_AUTHORISATION_TO_VALIDATE', + 'UNDER_VERIFICATION', + 'PRE_AUTHORISED', + 'WAITING_FOR_PAYMENT', + 'AUTHORISED_TO_VALIDATE', + 'SUSPENDED', + 'PENDING', + 'REFUND_TO_RETRY' + ); + } + + /** + * Return the statuses list of payments interrupted by the buyer. + * @return string[] + */ + public static function getCancelledStatuses() + { + return array( + 'ABANDONED', + 'NOT_CREATED', + 'CANCELLED' + ); + } + + /** + * Return the statuses list of payments waiting manual validation from the gateway Back Office. + * @return string[] + */ + public static function getToValidateStatuses() + { + return array( + 'WAITING_AUTHORISATION_TO_VALIDATE', + 'AUTHORISED_TO_VALIDATE' + ); + } + + /** + * Compute the signature. Parameters must be in UTF-8. + * + * @param array[string][string] $parameters payment gateway request/response parameters + * @param string $key shop certificate + * @param string $algo signature algorithm + * @param boolean $hashed set to false to get the unhashed signature + * @return string + */ + public static function sign($parameters, $key, $algo, $hashed = true) + { + ksort($parameters); + + $sign = ''; + foreach ($parameters as $name => $value) { + if (strpos($name, 'vads_') === 0) { + $sign .= $value . '+'; + } + } + + $sign .= $key; + + if (! $hashed) { + return $sign; + } + + switch ($algo) { + case self::ALGO_SHA1: + return sha1($sign); + case self::ALGO_SHA256: + return base64_encode(hash_hmac('sha256', $sign, $key, true)); + default: + throw new \InvalidArgumentException("Unsupported algorithm passed : {$algo}."); + } + } + + /** + * Get current PHP version without build info. + * @return string + */ + public static function shortPhpVersion() + { + $version = PHP_VERSION; + + $match = array(); + if (preg_match(self::PATTERN_SHORT_PHP_VERSION, $version, $match) === 1) { + $version = $match[0]; + } + + return $version; + } + + /** + * Format a given list of e-mails separated by commas and render them as HTML links. + * @param string $emails + * @return string + */ + public static function formatSupportEmails($emails) + { + $formatted = ''; + + $parts = explode(', ', $emails); + foreach ($parts as $part) { + $elts = explode(':', $part); + if (count($elts) === 2) { + $label = trim($elts[0]) . ': '; + $email = $elts[1]; + } elseif (count($elts) === 1) { + $label = ''; + $email = $elts[0]; + } else { + throw new \InvalidArgumentException("Invalid support e-mails string passed: {$emails}."); + } + + $email = trim($email); + + if (! empty($formatted)) { + $formatted .= '
'; + } + + $formatted .= $label . '' . $email . ''; + } + + return $formatted; + } + + /** + * Return the list of SEPA countries. + * + * @return array[string] + */ + public static function getSepaCountries() + { + return array( + 'AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', + 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HR', 'HU', + 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', + 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'SM' + ); + } + + /** + * Return the list of Overseas countries. + * + * @return array[string] + */ + public static function getOverseasCountries() + { + return array( + 'BL', 'GF', 'GP', 'MF', 'MQ', 'NC', 'PF', 'PM', 'RE', + 'TF', 'WF', 'YT' + ); + } + + /** + * Returns an array of the online documentation URI of the payment module. + * + * @return array[string][string] + */ + public static function getOnlineDocUri() + { + return array( + 'fr' => 'https://payzen.io/fr-FR/plugins/', + 'en' => 'https://payzen.io/en-EN/plugins/', + 'es' => 'https://payzen.io/es-ES/plugins/' + ); + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Currency.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Currency.php new file mode 100644 index 0000000..ac7bafb --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Currency.php @@ -0,0 +1,79 @@ +alpha3 = $alpha3; + $this->num = $num; + $this->decimals = $decimals; + } + + /** + * @param float $float + * @return int + */ + public function convertAmountToInteger($float) + { + $coef = 10 ** $this->decimals; + + $amount = $float * $coef; + return (int) (string) $amount; // Cast amount to string (to avoid rounding) than return it as int. + } + + /** + * @param int $integer + * @return float|int + */ + public function convertAmountToFloat($integer) + { + $coef = 10 ** $this->decimals; + + return ((float) $integer) / $coef; + } + + /** + * @return string + */ + public function getAlpha3() + { + return $this->alpha3; + } + + /** + * @return string + */ + public function getNum() + { + return $this->num; + } + + /** + * @return int + */ + public function getDecimals() + { + return $this->decimals; + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Field.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Field.php new file mode 100644 index 0000000..95bdd13 --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Field.php @@ -0,0 +1,173 @@ +name = $name; + $this->label = $label; + $this->regex = $regex; + $this->required = $required; + $this->length = $length; + } + + /** + * Checks the current value. + * + * @return boolean + */ + public function isValid() + { + if ($this->value === null && $this->required) { + return false; + } + + if ($this->value !== null && ! preg_match($this->regex, $this->value)) { + return false; + } + + return true; + } + + /** + * Setter for value. + * + * @param mixed $value + * @return boolean + */ + public function setValue($value) + { + $value = ($value === null) ? null : (string) $value; + // We save value even if invalid but we return "false" as warning. + $this->value = $value; + + return $this->isValid(); + } + + /** + * Return the current value of the field. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Is the field required in the payment request ? + * + * @return boolean + */ + public function isRequired() + { + return $this->required; + } + + /** + * Return the name (HTML attribute) of the field. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return the english human-readable name of the field. + * + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * Return the length of the field value. + * + * @return int + */ + public function getLength() + { + return $this->length; + } + + /** + * Has a value been set ? + * + * @return boolean + */ + public function isFilled() + { + return ! is_null($this->value); + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Request.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Request.php new file mode 100644 index 0000000..7699e20 --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Request.php @@ -0,0 +1,722 @@ +encoding = in_array(strtoupper($encoding), Api::$SUPPORTED_ENCODINGS, true) ? + strtoupper($encoding) : 'UTF-8'; + + // Parameters' regular expressions. + $ans = '[^<>]'; // Any character (except the dreadful "<" and ">"). + $an63 = '#^[A-Za-z0-9]{0,63}$#u'; + $ans255 = '#^' . $ans . '{0,255}$#u'; + $ans127 = '#^' . $ans . '{0,127}$#u'; + $supzero = '[1-9]\d*'; + $regex_payment_cfg = '#^(SINGLE|MULTI:first=\d+;count=' . $supzero . ';period=' . $supzero . ')$#u'; + // AAAAMMJJhhmmss + $regex_trans_date = '#^\d{4}(1[0-2]|0[1-9])(3[01]|[1-2]\d|0[1-9])(2[0-3]|[0-1]\d)([0-5]\d){2}$#u'; + $regex_sub_effect_date = '#^\d{4}(1[0-2]|0[1-9])(3[01]|[1-2]\d|0[1-9])$#u'; + $regex_mail = '#^[^@]+@[^@]+\.\w{2,4}$#u'; + $regex_params = '#^([^&=]+=[^&=]*)?(&[^&=]+=[^&=]*)*$#u'; // name1=value1&name2=value2.... + $regex_ship_type = '#^RECLAIM_IN_SHOP|RELAY_POINT|RECLAIM_IN_STATION|PACKAGE_DELIVERY_COMPANY|ETICKET$#u'; + $regex_payment_option = '#^[a-zA-Z0-9]{0,32}$|^COUNT=([1-9][0-9]{0,2})?;RATE=[0-9]{0,4}(\\.[0-9]{1,4})?;DESC=.{0,64};?$#'; + + // Defining all parameters and setting formats and default values. + $this->addField('signature', 'Signature', '#^[0-9a-f]{40}$#u', true); + + $this->addField('vads_acquirer_transient_data', 'Acquirer transient data', $ans255); + $this->addField('vads_action_mode', 'Action mode', '#^INTERACTIVE|SILENT$#u', true, 11); + $this->addField('vads_amount', 'Amount', '#^' . $supzero . '$#u', true); + $this->addField('vads_available_languages', 'Available languages', '#^(|[A-Za-z]{2}(;[A-Za-z]{2})*)$#u', false, 2); + $this->addField('vads_capture_delay', 'Capture delay', '#^\d*$#u'); + $this->addField('vads_card_number', 'Card number', '#^\d{13,19}$#u'); + $this->addField('vads_contracts', 'Contracts', $ans255); + $this->addField('vads_contrib', 'Contribution', $ans255); + $this->addField('vads_ctx_mode', 'Mode', '#^TEST|PRODUCTION$#u', true); + $this->addField('vads_currency', 'Currency', '#^\d{3}$#u', true, 3); + $this->addField('vads_cust_address', 'Customer address', $ans255); + $this->addField('vads_cust_antecedents', 'Customer history', '#^NONE|NO_INCIDENT|INCIDENT$#u'); + $this->addField('vads_cust_cell_phone', 'Customer cell phone', $an63, false, 63); + $this->addField('vads_cust_city', 'Customer city', '#^' . $ans . '{0,63}$#u', false, 63); + $this->addField('vads_cust_country', 'Customer country', '#^[A-Za-z]{2}$#u', false, 2); + $this->addField('vads_cust_email', 'Customer email', $regex_mail, false, 127); + $this->addField('vads_cust_first_name', 'Customer first name', $an63, false, 63); + $this->addField('vads_cust_id', 'Customer id', $an63, false, 63); + $this->addField('vads_cust_last_name', 'Customer last name', $an63, false, 63); + $this->addField('vads_cust_legal_name', 'Customer legal name', '#^' . $ans . '{0,100}$#u', false, 100); + $this->addField('vads_cust_name', 'Customer name', $ans127, false, 127); + $this->addField('vads_cust_phone', 'Customer phone', $an63, false, 63); + $this->addField('vads_cust_state', 'Customer state/region', '#^' . $ans . '{0,63}$#u', false, 63); + $this->addField('vads_cust_status', 'Customer status (private or company)', '#^PRIVATE|COMPANY$#u', false, 7); + $this->addField('vads_cust_title', 'Customer title', '#^' . $ans . '{0,63}$#u', false, 63); + $this->addField('vads_cust_zip', 'Customer zip code', $an63, false, 63); + $this->addField('vads_cust_national_id', 'Customer national id', $ans255); + $this->addField('vads_cust_address_number', 'Customer address number', '#^' . $ans . '{0,64}$#u', false, 64); + $this->addField('vads_cust_district', 'Customer district', $ans127, false, 127); + $this->addField('vads_cvv', 'Card verification number', '#^\d{3,4}$#u'); + $this->addField('vads_expiry_month', 'Month of card expiration', '#^\d[0-2]{1}$#u'); + $this->addField('vads_expiry_year', 'Year of card expiration', '#^20[0-9]{2}$#u'); + $this->addField('vads_identifier', 'Identifier', '#^'.$ans.'{0,50}$#u', false, 50); + $this->addField('vads_insurance_amount', 'The amount of insurance', '#^' . $supzero . '$#u', false, 12); + $this->addField('vads_language', 'Language', '#^[A-Za-z]{2}$#u', false, 2); + $this->addField('vads_nb_products', 'Number of products', '#^' . $supzero . '$#u', false); + $this->addField('vads_order_id', 'Order id', '#^[A-za-z0-9]{0,12}$#u', false, 12); + $this->addField('vads_order_info', 'Order info', $ans255); + $this->addField('vads_order_info2', 'Order info 2', $ans255); + $this->addField('vads_order_info3', 'Order info 3', $ans255); + $this->addField('vads_page_action', 'Page action', '#^PAYMENT$#u', true, 7); + $this->addField('vads_payment_cards', 'Payment cards', '#^([A-Za-z0-9\-_]+;)*[A-Za-z0-9\-_]*$#u', false, 127); + $this->addField('vads_payment_config', 'Payment config', $regex_payment_cfg, true); + $this->addField('vads_payment_option_code', 'Payment option to use', $regex_payment_option, false); + $this->addField('vads_payment_src', 'Payment source', '#^$#u', false, 0); + $this->addField('vads_redirect_error_message', 'Redirection error message', $ans255, false); + $this->addField('vads_redirect_error_timeout', 'Redirection error timeout', $ans255, false); + $this->addField('vads_redirect_success_message', 'Redirection success message', $ans255, false); + $this->addField('vads_redirect_success_timeout', 'Redirection success timeout', $ans255, false); + $this->addField('vads_return_get_params', 'GET return parameters', $regex_params, false); + $this->addField('vads_return_mode', 'Return mode', '#^NONE|GET|POST$#u', false, 4); + $this->addField('vads_return_post_params', 'POST return parameters', $regex_params, false); + $this->addField('vads_ship_to_city', 'Shipping city', '#^' . $ans . '{0,63}$#u', false, 63); + $this->addField('vads_ship_to_country', 'Shipping country', '#^[A-Za-z]{2}$#u', false, 2); + $this->addField('vads_ship_to_delay', 'Delay of shipping', '#^INFERIOR_EQUALS|SUPERIOR|IMMEDIATE|ALWAYS$#u', false, 15); + $this->addField('vads_ship_to_delivery_company_name', 'Name of the delivery company', $ans127, false, 127); + $this->addField('vads_ship_to_first_name', 'Shipping first name', $an63, false, 63); + $this->addField('vads_ship_to_last_name', 'Shipping last name', $an63, false, 63); + $this->addField('vads_ship_to_legal_name', 'Shipping legal name', '#^' . $ans . '{0,100}$#u', false, 100); + $this->addField('vads_ship_to_name', 'Shipping name', '#^' . $ans . '{0,127}$#u', false, 127); + $this->addField('vads_ship_to_phone_num', 'Shipping phone', $ans255, false, 63); + $this->addField('vads_ship_to_speed', 'Speed of the shipping method', '#^STANDARD|EXPRESS|PRIORITY$#u', false, 8); + $this->addField('vads_ship_to_state', 'Shipping state', $an63, false, 63); + $this->addField('vads_ship_to_status', 'Shipping status (private or company)', '#^PRIVATE|COMPANY$#u', false, 7); + $this->addField('vads_ship_to_street', 'Shipping street', $ans127, false, 127); + $this->addField('vads_ship_to_street2', 'Shipping street (2)', $ans127, false, 127); + $this->addField('vads_ship_to_type', 'Type of the shipping method', $regex_ship_type, false, 24); + $this->addField('vads_ship_to_zip', 'Shipping zip code', $an63, false, 63); + $this->addField('vads_shipping_amount', 'The amount of shipping', '#^' . $supzero . '$#u', false, 12); + $this->addField('vads_shop_name', 'Shop name', $ans127); + $this->addField('vads_shop_url', 'Shop URL', '#^https?://(\w+(:\w*)?@)?(\S+)(:[0-9]+)?[\w\#!:.?+=&%@`~;,|!\-/]*$#u'); + $this->addField('vads_site_id', 'Shop ID', '#^\d{8}$#u', true, 8); + $this->addField('vads_tax_amount', 'The amount of tax', '#^' . $supzero . '$#u', false, 12); + $this->addField('vads_tax_rate', 'The rate of tax', '#^\d{1,2}\.\d{1,4}$#u', false, 6); + $this->addField('vads_theme_config', 'Theme configuration', '#^[^;=]+=[^;=]*(;[^;=]+=[^;=]*)*;?$#u'); + $this->addField('vads_totalamount_vat', 'The total amount of VAT', '#^' . $supzero . '$#u', false, 12); + $this->addField('vads_threeds_mpi', 'Enable / disable 3D Secure', '#^[0-2]$#u', false); + $this->addField('vads_trans_date', 'Transaction date', $regex_trans_date, true, 14); + $this->addField('vads_trans_id', 'Transaction ID', '#^[0-8]\d{5}$#u', true, 6); + $this->addField('vads_url_cancel', 'Cancel URL', $ans127, false, 127); + $this->addField('vads_url_error', 'Error URL', $ans127, false, 127); + $this->addField('vads_url_referral', 'Referral URL', $ans127, false, 127); + $this->addField('vads_url_refused', 'Refused URL', $ans127, false, 127); + $this->addField('vads_url_return', 'Return URL', $ans127, false, 127); + $this->addField('vads_url_success', 'Success URL', $ans127, false, 127); + $this->addField('vads_user_info', 'User info', $ans255); + $this->addField('vads_validation_mode', 'Validation mode', '#^[01]?$#u', false, 1); + $this->addField('vads_version', 'Gatway version', '#^V2$#u', true, 2); + + // Subscription payment fields. + $this->addField('vads_sub_amount', 'Subscription amount', '#^' . $supzero . '$#u'); + $this->addField('vads_sub_currency', 'Subscription currency', '#^\d{3}$#u', false, 3); + $this->addField('vads_sub_desc', 'Subscription description', $ans255); + $this->addField('vads_sub_effect_date', 'Subscription effect date', $regex_sub_effect_date); + $this->addField('vads_sub_init_amount', 'Subscription initial amount', '#^' . $supzero . '$#u'); + $this->addField('vads_sub_init_amount_number', 'subscription initial amount number', '#^\d+$#u'); + + // Set some default values. + $this->set('vads_version', 'V2'); + $this->set('vads_page_action', 'PAYMENT'); + $this->set('vads_action_mode', 'INTERACTIVE'); + $this->set('vads_payment_config', 'SINGLE'); + + $timestamp = time(); + $this->set('vads_trans_id', Api::generateTransId($timestamp)); + $this->set('vads_trans_date', gmdate('YmdHis', $timestamp)); + } + + /** + * Shortcut function used in constructor to build requestParameters. + * + * @param string $name + * @param string $label + * @param string $regex + * @param boolean $required + * @param mixed $value + * @return boolean + */ + private function addField($name, $label, $regex, $required = false, $length = 255, $value = null) + { + $this->requestParameters[$name] = new Field($name, $label, $regex, $required, $length); + + if ($value !== null) { + return $this->set($name, $value); + } + + return true; + } + + /** + * Shortcut for setting multiple values with one array. + * + * @param array[string][mixed] $parameters + * @return boolean + */ + public function setFromArray($parameters) + { + $ok = true; + foreach ($parameters as $name => $value) { + $ok &= $this->set($name, $value); + } + + return $ok; + } + + /** + * General getter that retrieves a request parameter with its name. + * Adds "vads_" to the name if necessary. + * Example : $site_id = $request->get('site_id'); + * + * @param string $name + * @return mixed + */ + public function get($name) + { + if (! $name || ! is_string($name)) { + return null; + } + + // Shortcut notation compatibility. + $name = (strpos($name, 'vads_') !== 0) ? 'vads_' . $name : $name; + + if ($name === 'vads_key_test') { + return $this->keyTest; + } + + if ($name === 'vads_key_prod') { + return $this->keyProd; + } + + if ($name === 'vads_platform_url') { + return $this->platformUrl; + } + + if ($name === 'vads_redirect_enabled') { + return $this->redirectEnabled; + } + + if (array_key_exists($name, $this->requestParameters)) { + return $this->requestParameters[$name]->getValue(); + } + + return null; + } + + /** + * Set a request parameter with its name and the provided value. + * Adds "vads_" to the name if necessary. + * Example : $request->set('site_id', '12345678'); + * + * @param string $name + * @param mixed $value + * @return boolean + */ + public function set($name, $value) + { + if (! $name || ! is_string($name)) { + return false; + } + + // Shortcut notation compatibility. + $name = (strpos($name, 'vads_') !== 0) ? 'vads_' . $name : $name; + + if (is_string($value)) { + // Trim value before set. + $value = trim($value); + + // Convert the parameters' values if they are not encoded in UTF-8. + if ($this->encoding !== 'UTF-8') { + $value = iconv($this->encoding, 'UTF-8', $value); + } + + // Delete < and > characters from $value and replace multiple spaces by one. + $value = preg_replace(array('#[<>]+#u', '#\s+#u'), array('', ' '), $value); + } + + // Search appropriate setter. + if ($name === 'vads_key_test') { + return $this->setCertificate($value, 'TEST'); + } + + if ($name === 'vads_key_prod') { + return $this->setCertificate($value, 'PRODUCTION'); + } + + if ($name === 'vads_platform_url') { + return $this->setPlatformUrl($value); + } + + if ($name === 'vads_redirect_enabled') { + return $this->setRedirectEnabled($value); + } + + if ($name === 'vads_sign_algo') { + return $this->setSignAlgo($value); + } + + if (array_key_exists($name, $this->requestParameters)) { + return $this->requestParameters[$name]->setValue($value); + } + + return false; + } + + /** + * Set multi payment configuration. + * + * @param $total_in_cents total order amount in cents + * @param $first_in_cents amount of the first payment in cents + * @param $count total number of payments + * @param $period number of days between 2 payments + * @return boolean + */ + public function setMultiPayment($total_in_cents = null, $first_in_cents = null, $count = 3, $period = 30) + { + $result = true; + + if (is_numeric($count) && $count > 1 && is_numeric($period) && $period > 0) { + // Default values for first and total. + $total_in_cents = ($total_in_cents === null) ? $this->get('amount') : $total_in_cents; + $first_in_cents = ($first_in_cents === null) ? round($total_in_cents / $count) : $first_in_cents; + + // Check parameters. + if (is_numeric($total_in_cents) && $total_in_cents > $first_in_cents + && $total_in_cents > 0 && is_numeric($first_in_cents) && $first_in_cents > 0) { + // Set value to payment_config. + $payment_config = 'MULTI:first=' . $first_in_cents . ';count=' . $count . ';period=' . $period; + $result &= $this->set('amount', $total_in_cents); + $result &= $this->set('payment_config', $payment_config); + } + } + + return $result; + } + + /** + * Set target URL of the payment form. + * + * @param string $url + * @return boolean + */ + public function setPlatformUrl($url) + { + if (preg_match('#^https?://([^/]+/)+$#u', $url)) { + $this->platformUrl = $url; + return true; + } + + return false; + } + + /** + * Enable/disable vads_redirect_* parameters. + * + * @param mixed $enabled false, 0, null or 'false' to disable + * @return boolean + */ + public function setRedirectEnabled($enabled) + { + $this->redirectEnabled = ($enabled && (! is_string($enabled) || strtolower($enabled) !== 'false')); + return true; + } + + /** + * Set TEST or PRODUCTION certificate. + * + * @param string $key + * @param string $mode + * @return boolean + */ + public function setCertificate($key, $mode) + { + if ($mode === 'TEST') { + $this->keyTest = $key; + } + elseif ($mode === 'PRODUCTION') { + $this->keyProd = $key; + } + else { + return false; + } + + return true; + } + + /** + * Set signature algorithm. + * + * @param string $algo + * @return boolean + */ + public function setSignAlgo($algo) + { + if (in_array($algo, Api::$SUPPORTED_ALGOS, true)) { + $this->algo = $algo; + return true; + } + + return false; + } + + /** + * Add a product info as request parameters. + * + * @param string $label + * @param int $amount + * @param int $qty + * @param string $ref + * @param string $type + * @param float vat + * @return boolean + */ + public function addProduct($label, $amount, $qty, $ref, $type = null, $vat = null) + { + $index = $this->get('nb_products') ? $this->get('nb_products') : 0; + $ok = true; + + // Add product info as request parameters. + $ok &= $this->addField('vads_product_label' . $index, 'Product label', '#^[^<>"+-]{0,255}$#u', false, 255, $label); + $ok &= $this->addField('vads_product_amount' . $index, 'Product amount', '#^[1-9]\d*$#u', false, 12, $amount); + $ok &= $this->addField('vads_product_qty' . $index, 'Product quantity', '#^[1-9]\d*$#u', false, 255, $qty); + $ok &= $this->addField('vads_product_ref' . $index, 'Product reference', '#^[A-Za-z0-9]{0,64}$#u', false, 64, $ref); + $ok &= $this->addField('vads_product_type' . $index, 'Product type', '#^' . implode('|', self::$ACCORD_CATEGORIES) . '$#u', false, 30, $type); + $ok &= $this->addField('vads_product_vat' . $index, 'Product tax rate', '#^((\d{1,12})|(\d{1,2}\.\d{1,4}))$#u', false, 12, $vat); + + // Increment the number of products. + $ok &= $this->set('nb_products', $index + 1); + + return $ok; + } + + /** + * Add extra info as a request parameter. + * + * @param string $key + * @param string $value + * @return boolean + */ + public function addExtInfo($key, $value) + { + return $this->addField('vads_ext_info_' . $key, 'Extra info ' . $key, '#^.{0,255}$#u', false, 255, $value); + } + + /** + * Return certificate according to current mode, false if mode was not set. + * + * @return string|boolean + */ + private function getCertificate() + { + switch ($this->requestParameters['vads_ctx_mode']->getValue()) { + case 'TEST': + return $this->keyTest; + + case 'PRODUCTION': + return $this->keyProd; + + default: + return false; + } + } + + /** + * Generate signature from a list of fields. + * + * @param array[string][Lyranetwork\Payzen\Model\Api\Form\Field] $fields already filtered fields list + * @param bool $hashed + * @return string + */ + private function generateSignature($fields, $hashed = true) + { + $params = array(); + foreach ($fields as $field) { + $params[$field->getName()] = $field->getValue(); + } + + return Api::sign($params, $this->getCertificate(), $this->algo, $hashed); + } + + /** + * Unset the value of optionnal fields if they are invalid. + */ + public function clearInvalidOptionnalFields() + { + $fields = $this->getRequestFields(); + foreach ($fields as $field) { + if (! $field->isValid() && ! $field->isRequired()) { + $field->setValue(null); + } + } + } + + /** + * Check all payment fields. + * + * @param array[string] $errors will be filled with the names of invalid fields + * @return boolean + */ + public function isRequestReady(&$errors = null) + { + $errors = is_array($errors) ? $errors : array(); + + foreach ($this->getRequestFields() as $field) { + if (! $field->isValid()) { + $errors[] = $field->getName(); + } + } + + return count($errors) === 0; + } + + /** + * Return the list of fields to send to the payment gateway. + * + * @return array[string][Lyranetwork\Payzen\Model\Api\Form\Field] a list of fields + */ + public function getRequestFields() + { + $fields = $this->requestParameters; + + // Filter redirect_* parameters if redirect is disabled. + if (! $this->redirectEnabled) { + $redirect_fields = array( + 'vads_redirect_success_timeout', + 'vads_redirect_success_message', + 'vads_redirect_error_timeout', + 'vads_redirect_error_message' + ); + + foreach ($redirect_fields as $field_name) { + unset($fields[$field_name]); + } + } + + foreach ($fields as $field_name => $field) { + if (! $field->isFilled() && ! $field->isRequired()) { + unset($fields[$field_name]); + } + } + + // Compute signature. + $fields['signature']->setValue($this->generateSignature($fields)); + + // Return the list of fields. + return $fields; + } + + /** + * Return the URL of the payment page with urlencoded parameters (GET-like URL). + * + * @return string + */ + public function getRequestUrl() + { + $fields = $this->getRequestFields(); + + $url = $this->platformUrl . '?'; + foreach ($fields as $field) { + if (! $field->isFilled()) { + continue; + } + + $url .= $field->getName() . '=' . rawurlencode($field->getValue()) . '&'; + } + + $url = substr($url, 0, - 1); // Remove last &. + return $url; + } + + /** + * Return the HTML form to send to the payment gateway. + * + * @param string $form_add + * @param string $input_type + * @param string $input_add + * @param string $btn_type + * @param string $btn_value + * @param string $btn_add + * @return string + */ + public function getRequestHtmlForm( + $form_add = '', + $input_type = 'hidden', + $input_add = '', + $btn_type = 'submit', + $btn_value = 'Pay', + $btn_add = '', + $escape = true + ) { + $html = '
'; + $html .= "\n"; + $html .= $this->getRequestHtmlFields($input_type, $input_add, $escape); + $html .= ''; + $html .= "\n"; + $html .= '
'; + + return $html; + } + + /** + * Return the HTML inputs of fields to send to the payment page. + * + * @param string $input_type + * @param string $input_add + * @return string + */ + public function getRequestHtmlFields($input_type = 'hidden', $input_add = '', $escape = true) + { + $fields = $this->getRequestFields(); + + $html = ''; + $format = '\n"; + foreach ($fields as $field) { + if (! $field->isFilled()) { + continue; + } + + // Convert special chars to HTML entities to avoid data truncation. + if ($escape) { + $value = htmlspecialchars($field->getValue(), ENT_QUOTES, 'UTF-8'); + } + + $html .= sprintf($format, $field->getName(), $value); + } + + return $html; + } + + /** + * Return the html fields to send to the payment page as a key/value array. + * + * @param bool $for_log + * @return array[string][string] + */ + public function getRequestFieldsArray($for_log = false, $escape = true) + { + $fields = $this->getRequestFields(); + + $sensitive_data = array('vads_card_number', 'vads_cvv', 'vads_expiry_month', 'vads_expiry_year', 'vads_cust_national_id'); + + $result = array(); + foreach ($fields as $field) { + if (! $field->isFilled()) { + continue; + } + + $value = $field->getValue(); + if ($for_log && in_array($field->getName(), $sensitive_data, true)) { + $value = str_repeat('*', strlen($value)); + } + + // Convert special chars to HTML entities to avoid data truncation. + if ($escape) { + $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); + } + + $result[$field->getName()] = $value; + } + + return $result; + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Response.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Response.php new file mode 100644 index 0000000..a1b2976 --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Form/Response.php @@ -0,0 +1,837 @@ +rawResponse = $params; + $this->certificate = trim(($ctx_mode === 'PRODUCTION') ? $key_prod : $key_test); + + if (in_array($algo, Api::$SUPPORTED_ALGOS, true)) { + $this->algo = $algo; + } + + // Payment results. + $this->result = self::findInArray('vads_result', $this->rawResponse, null); + $this->extraResult = self::findInArray('vads_extra_result', $this->rawResponse, null); + $this->authResult = self::findInArray('vads_auth_result', $this->rawResponse, null); + $this->warrantyResult = self::findInArray('vads_warranty_result', $this->rawResponse, null); + + $this->transStatus = self::findInArray('vads_trans_status', $this->rawResponse, null); + } + + /** + * Check response signature. + * @return bool + */ + public function isAuthentified() + { + return $this->getComputedSignature() === $this->getSignature(); + } + + /** + * Return the signature computed from the received parameters, for log/debug purposes. + * @param bool $hashed + * @return string + */ + public function getComputedSignature($hashed = true) + { + return Api::sign($this->rawResponse, $this->certificate, $this->algo, $hashed); + } + + /** + * Check if the payment was successful (waiting confirmation or captured). + * @return bool + */ + public function isAcceptedPayment() + { + return in_array($this->transStatus, Api::getSuccessStatuses(), true) || $this->isPendingPayment(); + } + + /** + * Check if the payment is waiting confirmation (successful but the amount has not been + * transfered and is not yet guaranteed). + * @return bool + */ + public function isPendingPayment() + { + return in_array($this->transStatus, Api::getPendingStatuses(), true); + } + + /** + * Check if the payment process was interrupted by the buyer. + * @return bool + */ + public function isCancelledPayment() + { + return in_array($this->transStatus, Api::getCancelledStatuses(), true); + } + + /** + * Check if the payment is to validate manually in the gateway Back Office. + * @return bool + */ + public function isToValidatePayment() + { + return in_array($this->transStatus, Api::getToValidateStatuses(), true); + } + + /** + * Check if the payment is suspected to be fraudulent. + * @return bool + */ + public function isSuspectedFraud() + { + // At least one control failed... + $riskControl = $this->getRiskControl(); + if (in_array('WARNING', $riskControl, true) || in_array('ERROR', $riskControl, true)) { + return true; + } + + // Or there was an alert from risk assessment module. + $riskAssessment = $this->getRiskAssessment(); + if (in_array('INFORM', $riskAssessment, true)) { + return true; + } + + return false; + } + + /** + * Return the risk control result. + * @return array[string][string] + */ + public function getRiskControl() + { + $riskControl = $this->get('risk_control'); + if (! isset($riskControl) || ! trim($riskControl)) { + return array(); + } + + // Get a URL-like string. + $riskControl = str_replace(';', '&', $riskControl); + + $result = array(); + parse_str($riskControl, $result); + + return $result; + } + + /** + * Return the risk assessment result. + * @return array[string] + */ + public function getRiskAssessment() + { + $riskAssessment = $this->get('risk_assessment_result'); + if (! isset($riskAssessment) || ! trim($riskAssessment)) { + return array(); + } + + return explode(';', $riskAssessment); + } + + /** + * Return the value of a response parameter. + * @param string $name + * @return string + */ + public function get($name, $hasPrefix = true) + { + if ($hasPrefix) { + // Manage shortcut notations by adding 'vads_' prefix. + $name = (strpos($name, 'vads_') !== 0) ? 'vads_' . $name : $name; + } + + return array_key_exists($name, $this->rawResponse) ? $this->rawResponse[$name] : null; + } + + /** + * Shortcut for getting ext_info_* fields. + * @param string $key + * @return string + */ + public function getExtInfo($key) + { + return $this->get("ext_info_$key"); + } + + /** + * Return the expected signature received from gateway. + * @return string + */ + public function getSignature() + { + return $this->get('signature', false); + } + + /** + * Return the paid amount converted from cents (or currency equivalent) to a decimal value. + * @return float + */ + public function getFloatAmount() + { + $currency = Api::findCurrencyByNumCode($this->get('currency')); + return $currency->convertAmountToFloat($this->get('amount')); + } + + /** + * Return the payment response result. + * @return string + */ + public function getResult() + { + return $this->result; + } + + /** + * Return the payment response extra result. + * @return string + */ + public function getExtraResult() + { + return $this->extraResult; + } + + /** + * Return the payment response authentication result. + * @return string + */ + public function getAuthResult() + { + return $this->authResult; + } + + /** + * Return the payment response warranty result. + * @return string + */ + public function getWarrantyResult() + { + return $this->warrantyResult; + } + + /** + * Return all the payment response results as array. + * @return array[string][string] + */ + public function getAllResults() + { + return array( + 'result' => $this->result, + 'extra_result' => $this->extraResult, + 'auth_result' => $this->authResult, + 'warranty_result' => $this->warrantyResult + ); + } + + /** + * Return the payment transaction status. + * @return string + */ + public function getTransStatus() + { + return $this->transStatus; + } + + /** + * Return the response message translated to the payment langauge. + * @param $type string + * @return string + */ + public function getMessage($type = self::TYPE_RESULT) + { + $text = self::translate($this->get($type), $type, $this->get('language'), true); + + if ($type === self::TYPE_RESULT && $this->get($type) === '30' /* form error */) { + $text .= ' ' . self::extraMessage($this->extraResult); + } + + return $text; + } + + /** + * Return the complete response message translated to the payment langauge. + * @param $type string + * @return string + */ + public function getCompleteMessage($sep = ' ') + { + $text = $this->getMessage(self::TYPE_RESULT); + $text .= $sep . $this->getMessage(self::TYPE_AUTH_RESULT); + $text .= $sep . $this->getMessage(self::TYPE_WARRANTY_RESULT); + + return $text; + } + + /** + * Return a short description of the payment result, useful for logging. + * @return string + */ + public function getLogMessage() + { + $text = self::translate($this->result, self::TYPE_RESULT, 'en', true); + if ($this->result === '30' /* form error */) { + $text .= ' ' . self::extraMessage($this->extraResult); + } + + $text .= ' ' . self::translate($this->authResult, self::TYPE_AUTH_RESULT, 'en', true); + $text .= ' ' . self::translate($this->warrantyResult, self::TYPE_WARRANTY_RESULT, 'en', true); + + return $text; + } + + public function getOutputForPlatform() + { + return call_user_func_array(array($this, 'getOutputForGateway'), func_get_args()); + } + + /** + * Return a formatted string to output as a response to the notification URL call. + * + * @param string $case shortcut code for current situations. Most useful : payment_ok, payment_ko, auth_fail + * @param string $extra_message some extra information to output to the payment gateway + * @param string $original_encoding some extra information to output to the payment gateway + * @return string + */ + public function getOutputForGateway($case = '', $extra_message = '', $original_encoding = 'UTF-8') + { + // Predefined response messages according to case. + $cases = array( + 'payment_ok' => array(true, 'Accepted payment, order has been updated.'), + 'payment_ko' => array(true, 'Payment failure, order has been cancelled.'), + 'payment_ko_bis' => array(true, 'Payment failure.'), + 'payment_ok_already_done' => array(true, 'Accepted payment, already registered.'), + 'payment_ko_already_done' => array(true, 'Payment failure, already registered.'), + 'order_not_found' => array(false, 'Order not found.'), + 'payment_ko_on_order_ok' => array(false, 'Order status does not match the payment result.'), + 'auth_fail' => array(false, 'An error occurred while computing the signature.'), + 'empty_cart' => array(false, 'Empty cart detected before order processing.'), + 'unknown_status' => array(false, 'Unknown order status.'), + 'amount_error' => array(false, 'Total paid is different from order amount.'), + 'ok' => array(true, ''), + 'ko' => array(false, '') + ); + + $success = array_key_exists($case, $cases) ? $cases[$case][0] : false; + $message = array_key_exists($case, $cases) ? $cases[$case][1] : ''; + + if (! empty($extra_message)) { + $message .= ' ' . $extra_message; + } + + $message = str_replace("\n", ' ', $message); + + // Set original CMS encoding to convert if necessary response to send to gateway. + $encoding = in_array(strtoupper($original_encoding), Api::$SUPPORTED_ENCODINGS, true) ? + strtoupper($original_encoding) : 'UTF-8'; + if ($encoding !== 'UTF-8') { + $message = iconv($encoding, 'UTF-8', $message); + } + + $content = $success ? 'OK-' : 'KO-'; + $content .= "$message\n"; + + $response = ''; + $response .= htmlspecialchars($content, ENT_COMPAT, 'UTF-8'); + $response .= ''; + return $response; + } + + /** + * Return a translated short description of the payment result for a specified language. + * @param string $result + * @param string $type + * @param string $lang + * @param boolean $appendCode + * @return string + */ + public static function translate($result, $type = self::TYPE_RESULT, $lang = 'en', $appendCode = false) + { + // If language is not supported, use the domain default language. + if (! array_key_exists($lang, self::$RESPONSE_TRANS)) { + $lang = 'en'; + } + + $translations = self::$RESPONSE_TRANS[$lang]; + + $default = isset($translations[$type]['UNKNOWN']) ? $translations[$type]['UNKNOWN'] : + $translations['UNKNOWN']; + $text = self::findInArray($result ? $result : 'empty', $translations[$type], $default); + + if ($text && $appendCode) { + $text = self::appendResultCode($text, $result); + } + + return $text; + } + + public static function appendResultCode($message, $result_code) + { + if ($result_code) { + $message .= ' (' . $result_code . ')'; + } + + return $message . '.'; + } + + public static function extraMessage($extra_result) + { + $error = self::findInArray($extra_result, self::$FORM_ERRORS, 'OTHER'); + return self::appendResultCode($error, $extra_result); + } + + public static function findInArray($key, $array, $default) + { + if (is_array($array) && array_key_exists($key, $array)) { + return $array[$key]; + } + + return $default; + } + + /** + * Associative array containing human-readable translations of response codes. + * + * @var array + * @access private + */ + public static $RESPONSE_TRANS = array( + 'fr' => array( + 'UNKNOWN' => 'Inconnu', + + 'result' => array( + 'empty' => '', + '00' => 'Action réalisée avec succès', + '02' => 'Le marchand doit contacter la banque du porteur', + '05' => 'Action refusée', + '17' => 'Action annulée', + '30' => 'Erreur de format de la requête', + '96' => 'Erreur technique' + ), + 'auth_result' => array( + 'empty' => '', + '00' => 'Transaction approuvée ou traitée avec succès', + 'UNKNOWN' => 'Voir le détail de la transaction pour plus d\'information' + ), + 'warranty_result' => array( + 'empty' => 'Garantie de paiement non applicable', + 'YES' => 'Le paiement est garanti', + 'NO' => 'Le paiement n\'est pas garanti', + 'UNKNOWN' => 'Suite à une erreur technique, le paiment ne peut pas être garanti' + ), + 'risk_control' => array ( + 'CARD_FRAUD' => 'Contrôle du numéro de carte', + 'SUSPECT_COUNTRY' => 'Contrôle du pays émetteur de la carte', + 'IP_FRAUD' => 'Contrôle de l\'adresse IP', + 'CREDIT_LIMIT' => 'Contrôle de l\'encours', + 'BIN_FRAUD' => 'Contrôle du code BIN', + 'ECB' => 'Contrôle e-carte bleue', + 'COMMERCIAL_CARD' => 'Contrôle carte commerciale', + 'SYSTEMATIC_AUTO' => 'Contrôle carte à autorisation systématique', + 'INCONSISTENT_COUNTRIES' => 'Contrôle de cohérence des pays (IP, carte, adresse de facturation)', + 'NON_WARRANTY_PAYMENT' => 'Contrôle le transfert de responsabilité', + 'SUSPECT_IP_COUNTRY' => 'Contrôle Pays de l\'IP' + ), + 'risk_assessment' => array( + 'ENABLE_3DS' => '3D Secure activé', + 'DISABLE_3DS' => '3D Secure désactivé', + 'MANUAL_VALIDATION' => 'La transaction est créée en validation manuelle', + 'REFUSE' => 'La transaction est refusée', + 'RUN_RISK_ANALYSIS' => 'Appel à un analyseur de risques externes', + 'INFORM' => 'Une alerte est remontée' + ) + ), + + 'en' => array( + 'UNKNOWN' => 'Unknown', + + 'result' => array( + 'empty' => '', + '00' => 'Action successfully completed', + '02' => 'The merchant must contact the cardholder\'s bank', + '05' => 'Action rejected', + '17' => 'Action canceled', + '30' => 'Request format error', + '96' => 'Technical issue' + ), + 'auth_result' => array( + 'empty' => '', + '00' => 'Approved or successfully processed transaction', + 'UNKNOWN' => 'See the transaction details for more information' + ), + 'warranty_result' => array( + 'empty' => 'Payment guarantee not applicable', + 'YES' => 'The payment is guaranteed', + 'NO' => 'The payment is not guaranteed', + 'UNKNOWN' => 'Due to a technical error, the payment cannot be guaranteed' + ), + 'risk_control' => array ( + 'CARD_FRAUD' => 'Card number control', + 'SUSPECT_COUNTRY' => 'Card country control', + 'IP_FRAUD' => 'IP address control', + 'CREDIT_LIMIT' => 'Card outstanding control', + 'BIN_FRAUD' => 'BIN code control', + 'ECB' => 'E-carte bleue control', + 'COMMERCIAL_CARD' => 'Commercial card control', + 'SYSTEMATIC_AUTO' => 'Systematic authorization card control', + 'INCONSISTENT_COUNTRIES' => 'Countries consistency control (IP, card, shipping address)', + 'NON_WARRANTY_PAYMENT' => 'Transfer of responsibility control', + 'SUSPECT_IP_COUNTRY' => 'IP country control' + ), + 'risk_assessment' => array( + 'ENABLE_3DS' => '3D Secure enabled', + 'DISABLE_3DS' => '3D Secure disabled', + 'MANUAL_VALIDATION' => 'The transaction has been created via manual validation', + 'REFUSE' => 'The transaction is refused', + 'RUN_RISK_ANALYSIS' => 'Call for an external risk analyser', + 'INFORM' => 'A warning message appears' + ) + ), + + 'es' => array( + 'UNKNOWN' => 'Desconocido', + + 'result' => array( + 'empty' => '', + '00' => 'Accion procesada con exito', + '02' => 'El mercante debe contactar el banco del portador', + '05' => 'Accion rechazada', + '17' => 'Accion cancelada', + '30' => 'Error de formato de solicitutd', + '96' => 'Problema technico' + ), + 'auth_result' => array( + 'empty' => '', + '00' => 'Transacción aceptada o procesada con exito', + 'UNKNOWN' => 'Vea los detalles de la transacción para más información' + ), + 'warranty_result' => array( + 'empty' => 'Garantia de pago no aplicable', + 'YES' => 'El pago es garantizado', + 'NO' => 'El pago no es garantizado', + 'UNKNOWN' => 'Debido a un problema tecnico, el pago no puede ser garantizado' + ), + 'risk_control' => array ( + 'CARD_FRAUD' => 'Control de numero de tarjeta', + 'SUSPECT_COUNTRY' => 'Control de pais de tarjeta', + 'IP_FRAUD' => 'Control de direccion IP', + 'CREDIT_LIMIT' => 'Control de saldo de vivo de tarjeta', + 'BIN_FRAUD' => 'Control de codigo BIN', + 'ECB' => 'Control de E-carte bleue', + 'COMMERCIAL_CARD' => 'Control de tarjeta comercial', + 'SYSTEMATIC_AUTO' => 'Control de tarjeta a autorizacion sistematica', + 'INCONSISTENT_COUNTRIES' => 'Control de coherencia de pais (IP, tarjeta, direccion de envio)', + 'NON_WARRANTY_PAYMENT' => 'Control de transferencia de responsabilidad', + 'SUSPECT_IP_COUNTRY' => 'Control del pais de la IP' + ), + 'risk_assessment' => array( + 'ENABLE_3DS' => '3D Secure activado', + 'DISABLE_3DS' => '3D Secure desactivado', + 'MANUAL_VALIDATION' => 'La transaccion ha sido creada con validacion manual', + 'REFUSE' => 'La transaccion ha sido rechazada', + 'RUN_RISK_ANALYSIS' => 'Llamada a un analisador de riesgos exterior', + 'INFORM' => 'Un mensaje de advertencia aparece' + ) + ), + + 'pt' => array ( + 'UNKNOWN' => 'Desconhecido', + + 'result' => array ( + 'empty' => '', + '00' => 'Ação realizada com sucesso', + '02' => 'O comerciante deve contactar o banco do portador', + '05' => 'Ação recusada', + '17' => 'Ação cancelada', + '30' => 'Erro no formato dos dados', + '96' => 'Erro técnico durante o pagamento' + ), + 'auth_result' => array ( + 'empty' => '', + '00' => 'Transação aprovada ou tratada com sucesso', + 'UNKNOWN' => 'Veja os detalhes da transação para mais informações' + ), + 'warranty_result' => array ( + 'empty' => 'Garantia de pagamento não aplicável', + 'YES' => 'O pagamento foi garantido', + 'NO' => 'O pagamento não foi garantido', + 'UNKNOWN' => 'Devido à un erro técnico, o pagamento não pôde ser garantido' + ), + 'risk_control' => array ( + 'CARD_FRAUD' => 'Card number control', + 'SUSPECT_COUNTRY' => 'Card country control', + 'IP_FRAUD' => 'IP address control', + 'CREDIT_LIMIT' => 'Card outstanding control', + 'BIN_FRAUD' => 'BIN code control', + 'ECB' => 'E-carte bleue control', + 'COMMERCIAL_CARD' => 'Commercial card control', + 'SYSTEMATIC_AUTO' => 'Systematic authorization card control', + 'INCONSISTENT_COUNTRIES' => 'Countries consistency control (IP, card, shipping address)', + 'NON_WARRANTY_PAYMENT' => 'Transfer of responsibility control', + 'SUSPECT_IP_COUNTRY' => 'IP country control' + ), + 'risk_assessment' => array( + 'ENABLE_3DS' => '3D Secure enabled', + 'DISABLE_3DS' => '3D Secure disabled', + 'MANUAL_VALIDATION' => 'The transaction has been created via manual validation', + 'REFUSE' => 'The transaction is refused', + 'RUN_RISK_ANALYSIS' => 'Call for an external risk analyser', + 'INFORM' => 'A warning message appears' + ) + ), + + 'de' => array ( + 'UNKNOWN' => 'Unbekannt', + + 'result' => array ( + 'empty' => '', + '00' => 'Aktion erfolgreich ausgeführt', + '02' => 'Der Händler muss die Bank des Karteninhabers kontaktieren', + '05' => 'Aktion abgelehnt', + '17' => 'Aktion abgebrochen', + '30' => 'Fehler im Format der Anfrage', + '96' => 'Technischer Fehler bei der Zahlung' + ), + 'auth_result' => array ( + 'empty' => '', + '00' => 'Zahlung durchgeführt oder mit Erfolg bearbeitet', + 'UNKNOWN' => 'Weitere Informationen finden Sie in den Transaktionsdetails' + ), + 'warranty_result' => array ( + 'empty' => 'Zahlungsgarantie nicht anwendbar', + 'YES' => 'Die Zahlung ist garantiert', + 'NO' => 'Die Zahlung ist nicht garantiert', + 'UNKNOWN' => 'Die Zahlung kann aufgrund eines technischen Fehlers nicht gewährleistet werden' + ), + 'risk_control' => array ( + 'CARD_FRAUD' => 'Card number control', + 'SUSPECT_COUNTRY' => 'Card country control', + 'IP_FRAUD' => 'IP address control', + 'CREDIT_LIMIT' => 'Card outstanding control', + 'BIN_FRAUD' => 'BIN code control', + 'ECB' => 'E-carte bleue control', + 'COMMERCIAL_CARD' => 'Commercial card control', + 'SYSTEMATIC_AUTO' => 'Systematic authorization card control', + 'INCONSISTENT_COUNTRIES' => 'Countries consistency control (IP, card, shipping address)', + 'NON_WARRANTY_PAYMENT' => 'Transfer of responsibility control', + 'SUSPECT_IP_COUNTRY' => 'IP country control' + ), + 'risk_assessment' => array( + 'ENABLE_3DS' => '3D Secure enabled', + 'DISABLE_3DS' => '3D Secure disabled', + 'MANUAL_VALIDATION' => 'The transaction has been created via manual validation', + 'REFUSE' => 'The transaction is refused', + 'RUN_RISK_ANALYSIS' => 'Call for an external risk analyser', + 'INFORM' => 'A warning message appears' + ) + ) + ); + + // Relative to https://payzen.io/fr-FR/form-payment/error-code/sitemap.html + public static $FORM_ERRORS = array( + '00' => 'SIGNATURE', + '01' => 'VERSION', + '02' => 'SITE_ID', + '03' => 'TRANS_ID', + '04' => 'TRANS_DATE', + '05' => 'VALIDATION_MODE', + '06' => 'CAPTURE_DELAY', + '07' => 'PAYMENT_CONFIG', + '08' => 'PAYMENT_CARDS', + '09' => 'AMOUNT', + '10' => 'CURRENCY', + '11' => 'CTX_MODE', + '12' => 'LANGUAGE', + '13' => 'ORDER_ID', + '14' => 'ORDER_INFO', + '15' => 'CUST_EMAIL', + '16' => 'CUST_ID', + '17' => 'CUST_TITLE', + '18' => 'CUST_NAME', + '19' => 'CUST_ADDRESS', + '20' => 'CUST_ZIP', + '21' => 'CUST_CITY', + '22' => 'CUST_COUNTRY', + '23' => 'CUST_PHONE', + '24' => 'URL_SUCCESS', + '25' => 'URL_REFUSED', + '26' => 'URL_REFERRAL', + '27' => 'URL_CANCEL', + '28' => 'URL_RETURN', + '29' => 'URL_ERROR', + '30' => 'IDENTIFIER', + '31' => 'CONTRIB', + '32' => 'THEME_CONFIG', + '33' => 'URL_CHECK', + '34' => 'REDIRECT_SUCCESS_TIMEOUT', + '35' => 'REDIRECT_SUCCESS_MESSAGE', + '36' => 'REDIRECT_ERROR_TIMEOUT', + '37' => 'REDIRECT_ERROR_MESSAGE', + '38' => 'RETURN_POST_PARAMS', + '39' => 'RETURN_GET_PARAMS', + '40' => 'CARD_NUMBER', + '41' => 'CARD_EXP_MONTH', + '42' => 'CARD_EXP_YEAR', + '43' => 'CARD_CVV', + '44' => 'CARD_CVV_AND_BIRTH', + '46' => 'PAGE_ACTION', + '47' => 'ACTION_MODE', + '48' => 'RETURN_MODE', + '49' => 'ABSTRACT_INFO', + '50' => 'SECURE_MPI', + '51' => 'SECURE_ENROLLED', + '52' => 'SECURE_CAVV', + '53' => 'SECURE_ECI', + '54' => 'SECURE_XID', + '55' => 'SECURE_CAVV_ALG', + '56' => 'SECURE_STATUS', + '60' => 'PAYMENT_SRC', + '61' => 'USER_INFO', + '62' => 'CONTRACTS', + '63' => 'RECURRENCE', + '64' => 'RECURRENCE_DESC', + '65' => 'RECURRENCE_AMOUNT', + '66' => 'RECURRENCE_REDUCED_AMOUNT', + '67' => 'RECURRENCE_CURRENCY', + '68' => 'RECURRENCE_REDUCED_AMOUNT_NUMBER', + '69' => 'RECURRENCE_EFFECT_DATE', + '70' => 'EMPTY_PARAMS', + '71' => 'AVAILABLE_LANGUAGES', + '72' => 'SHOP_NAME', + '73' => 'SHOP_URL', + '74' => 'OP_COFINOGA', + '75' => 'OP_CETELEM', + '76' => 'BIRTH_DATE', + '77' => 'CUST_CELL_PHONE', + '79' => 'TOKEN_ID', + '80' => 'SHIP_TO_NAME', + '81' => 'SHIP_TO_STREET', + '82' => 'SHIP_TO_STREET2', + '83' => 'SHIP_TO_CITY', + '84' => 'SHIP_TO_STATE', + '85' => 'SHIP_TO_ZIP', + '86' => 'SHIP_TO_COUNTRY', + '87' => 'SHIP_TO_PHONE_NUM', + '88' => 'CUST_STATE', + '89' => 'REQUESTOR', + '90' => 'PAYMENT_TYPE', + '91' => 'EXT_INFO', + '92' => 'CUST_STATUS', + '93' => 'SHIP_TO_STATUS', + '94' => 'SHIP_TO_TYPE', + '95' => 'SHIP_TO_SPEED', + '96' => 'SHIP_TO_DELIVERY_COMPANY_NAME', + '97' => 'PRODUCT_LABEL', + '98' => 'PRODUCT_TYPE', + '99' => 'OTHER', + '100' => 'PRODUCT_REF', + '101' => 'PRODUCT_QTY', + '102' => 'PRODUCT_AMOUNT', + '103' => 'PAYMENT_OPTION_CODE', + '104' => 'CUST_FIRST_NAME', + '105' => 'CUST_LAST_NAME', + '106' => 'SHIP_TO_FIRST_NAME', + '107' => 'SHIP_TO_LAST_NAME', + '108' => 'TAX_AMOUNT', + '109' => 'SHIPPING_AMOUNT', + '110' => 'INSURANCE_AMOUNT', + '111' => 'PAYMENT_ENTRY', + '112' => 'CUST_ADDRESS_NUMBER', + '113' => 'CUST_DISTRICT', + '114' => 'SHIP_TO_STREET_NUMBER', + '115' => 'SHIP_TO_DISTRICT', + '116' => 'SHIP_TO_USER_INFO', + '118' => 'STEP_UP_DATA', + '201' => 'PAYMENT_AUTH_CODE', + '202' => 'PAYMENT_CUST_CONTRACT_NUM', + '888' => 'ROBOT_REQUEST', + '999' => 'SENSITIVE_DATA' + ); +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/Api.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/Api.php new file mode 100644 index 0000000..6eababd --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/Api.php @@ -0,0 +1,528 @@ +refundProcessor = $refundProcessor; + $this->privateKey = $privateKey; + $this->restServerUrl = $restServerUrl; + $this->siteId = $siteId; + $this->cmsName = $cmsName; + } + + /** + * Refund money. + * + * @param \Lyranetwork\Payzen\Model\Api\Refund\OrderInfo $orderInfo + * @param float $amount + */ + public function refund($orderInfo, $amount) + { + // Client has not configured private key in module backend, let CMS do offline refund. + if (! $this->privateKey) { + $this->refundProcessor->log("Impossible to make online refund for order #{$orderInfo->getOrderId()}: private key is not configured. Let {$this->cmsName} do offline refund.", 'WARNING'); + $this->refundProcessor->doOnError('privateKey', sprintf($this->refundProcessor->translate('Impossible to make online refund for order #%1$s: password is not configured. Let %2$s do offline refund.'), $orderInfo->getOrderId(), $this->cmsName)); + return true; + } + + // Get currency. + $currency = PayzenApi::findCurrencyByAlphaCode($orderInfo->getOrderCurrencyIsoCode()); + $amount = round($amount, $currency->getDecimals()); + $amountInCents = $currency->convertAmountToInteger($amount); + + $this->refundProcessor->log("Start refund of {$amount} {$orderInfo->getOrderCurrencySign()} for order #{$orderInfo->getOrderId()} on payment gateway.", 'INFO'); + + try { + // Get payment details of all transactions, including ones in unpaid status to manage refund of split payment. + $getPaymentDetails = $this->getPaymentDetails($orderInfo, true); + + if (count($getPaymentDetails) > 1) { + // Payment in installments, refund the desired amount from last installment to first one. + // Check if we can refund amount. + $refundableAmount = 0; + $transactionRefundedAmount = 0; + $split = true; + + foreach ($getPaymentDetails as $key => $transaction) { + // Get the refundable and the already refunded amount of each transaction. + $transactionRefundableAmount = $this->getTransactionRefundableAmount($transaction, $orderInfo->getOrderCurrencyIsoCode()); + $transactionRefundedAmount += $this->getTransactionRefundedAmount($transaction, $orderInfo->getOrderCurrencyIsoCode()); + $getPaymentDetails[$key]['transactionRefundableAmount'] = $transactionRefundableAmount; + $refundableAmount += $transactionRefundableAmount; + + if (isset($transaction['metadata']['module_id']) && $transaction['metadata']['module_id'] == "multi") { + $split = false; + } + } + + if ($split) { + if (! $transactionRefundedAmount || $transactionRefundedAmount == 0) { + $msg = sprintf($this->refundProcessor->translate('Refund of split payment is not supported. Please, consider making necessary changes in %1$s Back Office.'), 'PayZen'); + throw new \Exception($msg); + } + + $refundTransactions = $this->getRefundDetails($orderInfo, false); + $response = array( + 'orderDetails' => array( + 'orderId' => $orderInfo->getOrderId() + ), + 'amount' => $amountInCents, + 'refundedAmount' => $transactionRefundedAmount, + 'refundTransactions' => $refundTransactions + ); + + $this->refundProcessor->doOnSuccess($response, "frac_update"); + } elseif ($amountInCents > $refundableAmount) { + // Unable to refund more than the sum of the refundable amount of each installment. + $msg = sprintf( + $this->refundProcessor->translate('Remaining amount (%1$s %2$s) is less than requested refund amount (%3$s %2$s).'), + $currency->convertAmountToFloat($refundableAmount), + $orderInfo->getOrderCurrencySign(), + $amount + ); + + throw new \Exception($msg); + } else { + // Extract paid transactions to manage normal refund. + $getPaymentDetailsPaid = array_filter( + $getPaymentDetails, + function ($trx) { + return $trx["status"] !== "UNPAID"; + } + ); + + $AmountStillToRefund = $amountInCents; + $refundedAmount = 0; + foreach ($getPaymentDetailsPaid as $transaction) { + if ($transaction['transactionRefundableAmount'] > 0) { + $transactionAmountRefund = min($transaction['transactionRefundableAmount'], $AmountStillToRefund); + $AmountStillToRefund -= $transactionAmountRefund; + + $refundedAmount += $transactionAmountRefund; + $transaction['refundedAmount'] = $refundedAmount; + + // Do not update order status till we refund all the amount. + $this->refundFromOneTransaction($orderInfo, $transactionAmountRefund, $transaction, $currency); + + if ($AmountStillToRefund == 0) { + break; + } + } + } + } + } else { + // Extract paid transactions to manage normal refund. + $getPaymentDetailsPaid = array_filter( + $getPaymentDetails, + function ($trx) { + return $trx["status"] !== "UNPAID"; + } + ); + + // Standard payment, refund on the only transaction. + $this->refundFromOneTransaction($orderInfo, $amountInCents, reset($getPaymentDetailsPaid), $currency); + } + + return true; + } catch (\Exception $e) { + $this->refundProcessor->log("{$e->getMessage()}" . ($e->getCode() > 0 ? ' (' . $e->getCode() . ').' : ''), 'ERROR'); + + $errorCode = ($e->getCode() <= -1) ? -1 : $e->getCode(); + switch ((string) $errorCode) { + case 'PSP_100': + // Merchant don't have offer allowing REST WS. + // Allow offline refund and display warning message. + $this->refundProcessor->doOnError($errorCode, sprintf($this->refundProcessor->translate('Payment is refunded/canceled only in %1$s. Please, consider making necessary changes in %2$s Back Office.'), $this->cmsName, 'PayZen')); + return true; + + case 'PSP_083': + $message = $this->refundProcessor->translate('Chargebacks cannot be refunded.'); + break; + + case '-1': // Manage cUrl errors. + $message = sprintf($this->refundProcessor->translate('Error occurred when refunding payment for order #%1$s. Please consult the payment module log for more details.'), $orderInfo->getOrderReference()); + break; + + case '0': + $message = sprintf($this->refundProcessor->translate('Cannot refund payment for order #%1$s.'), $orderInfo->getOrderReference()) . ' ' . $e->getMessage(); + break; + + default: + $message = $this->refundProcessor->translate('Refund error') . ': ' . $e->getMessage(); + break; + } + + $this->refundProcessor->doOnFailure($errorCode, $message); + + return false; + } + } + + /** + * Get payment details for the passed order info. + * + * @param \Lyranetwork\Payzen\Model\Api\Refund\OrderInfo $orderInfo + * @param boolean $unpaid to include or not unpaid transactions + * @return array + */ + private function getPaymentDetails($orderInfo, $unpaid = false) + { + /** + * @var Lyranetwork\Payzen\Model\Api\Rest\Api $client + * */ + $client = new PayzenRest( + $this->restServerUrl, + $this->siteId, + $this->privateKey + ); + + $requestData = array( + 'orderId' => $orderInfo->getOrderRemoteId(), + 'operationType' => 'DEBIT' + ); + + $getOrderResponse = $client->post('V4/Order/Get', json_encode($requestData)); + self::checkRestResult($getOrderResponse); + + // Order transactions organized by sequence numbers. + $transBySequence = array(); + foreach ($getOrderResponse['answer']['transactions'] as $transaction) { + $sequenceNumber = $transaction['transactionDetails']['sequenceNumber']; + if (($transaction['status'] !== 'UNPAID' || $unpaid) && $transaction['detailedStatus'] !== 'REFUSED') { + $transBySequence[$sequenceNumber] = $transaction; + } + } + + ksort($transBySequence); + return array_reverse($transBySequence); + } + + /** + * Get refund details for the passed order info. + * + * @param \Lyranetwork\Payzen\Model\Api\Refund\OrderInfo $orderInfo + * @param boolean $unpaid to include or not unpaid transactions + * @return array + */ + private function getRefundDetails($orderInfo, $unpaid = false) + { + /** + * @var Lyranetwork\Payzen\Model\Api\Rest\Api $client + * */ + $client = new PayzenRest( + $this->restServerUrl, + $this->siteId, + $this->privateKey + ); + + $requestData = array( + 'orderId' => $orderInfo->getOrderRemoteId(), + 'operationType' => 'CREDIT' + ); + + $getOrderResponse = $client->post('V4/Order/Get', json_encode($requestData)); + self::checkRestResult($getOrderResponse); + + $transactions = array(); + foreach ($getOrderResponse['answer']['transactions'] as $transaction) { + $uuid = $transaction['uuid']; + if (($transaction['status'] !== 'UNPAID' || $unpaid) && $transaction['detailedStatus'] !== 'REFUSED') { + $transactions[$uuid] = $transaction; + } + } + + return $transactions; + } + + // Check REST WS response. + private function checkRestResult($response, $expectedStatuses = array()) + { + $answer = $response['answer']; + + if ($response['status'] !== 'SUCCESS') { + $errorMessage = $response['answer']['errorMessage'] . ' (' . $answer['errorCode'] . ').'; + + if (isset($answer['detailedErrorMessage']) && ! empty($answer['detailedErrorMessage'])) { + $errorMessage .= ' Detailed message: ' . $answer['detailedErrorMessage'] . ' (' . $answer['detailedErrorCode'] . ').'; + } + + throw new \Lyranetwork\Payzen\Model\Api\Refund\WsException($errorMessage, $answer['errorCode']); + } elseif (! empty($expectedStatuses) && ! in_array($answer['detailedStatus'], $expectedStatuses, true)) { + throw new \Exception(sprintf($this->refundProcessor->translate('Unexpected transaction status received (%1$s).'), $answer['detailedStatus'])); + } + } + + private function getTransactionRefundedAmount($transaction, $orderCurrencyIsoCode) + { + $refundedAmount = 0; + if ($transaction['detailedStatus'] === 'CAPTURED') { + // Get transaction amount and already refunded amount. + if ($orderCurrencyIsoCode !== $transaction['currency']) { + $refundedAmount = $transaction['transactionDetails']['cardDetails']['captureResponse']['effectiveRefundAmount']; + } else { + $refundedAmount = $transaction['transactionDetails']['cardDetails']['captureResponse']['refundAmount']; + } + + if (empty($refundedAmount)) { + $refundedAmount = 0; + } + } elseif ($transaction['detailedStatus'] === 'CANCELLED') { + // Get transaction amount and already refunded amount. + if ($orderCurrencyIsoCode !== $transaction['currency']) { + $refundedAmount = $transaction['transactionDetails']['effectiveAmount']; + } else { + $refundedAmount = $transaction['amount']; + } + + if (empty($refundedAmount)) { + $refundedAmount = 0; + } + } + + return $refundedAmount; + } + + private function getTransactionRefundableAmount($transaction, $orderCurrencyIsoCode) + { + $refundedAmount = $this->getTransactionRefundedAmount($transaction, $orderCurrencyIsoCode); + if ($transaction['detailedStatus'] === 'CAPTURED') { + // Get transaction amount and already refunded amount. + if ($orderCurrencyIsoCode !== $transaction['currency']) { + $transAmount = $transaction['transactionDetails']['effectiveAmount']; + } else { + $transAmount = $transaction['amount']; + } + + $refundableAmount = $transAmount - $refundedAmount; + } else { + $refundableAmount = ($orderCurrencyIsoCode !== $transaction['currency']) ? + $transaction['transactionDetails']['effectiveAmount'] : $transaction['amount']; + } + + return $refundableAmount; + } + + private function refundFromOneTransaction($orderInfo, $amountInCents, $transaction, $currency) + { + if (! $transaction) { + $msg = sprintf( + $this->refundProcessor->translate("No paid transaction found for order ID [%s]"), + $orderInfo->getOrderId() + ); + + throw new \Exception($msg); + } + + $amount = $currency->convertAmountToFloat($amountInCents, $currency->getDecimals()); + $successStatuses = array_merge( + PayzenApi::getSuccessStatuses(), + PayzenApi::getPendingStatuses() + ); + + $transStatus = $transaction['detailedStatus']; + $uuid = $transaction['uuid']; + $commentText = $orderInfo->getOrderUserInfo(); + + /** + * @var Lyranetwork\Payzen\Model\Api\Rest\Api $client + */ + $client = new PayzenRest( + $this->restServerUrl, + $this->siteId, + $this->privateKey + ); + + if ($transStatus === 'CAPTURED') { // Transaction captured, we can do refund. + $real_refund_amount = $amountInCents; + + // Get transaction amount and already transaction refunded amount. + if ($orderInfo->getOrderCurrencyIsoCode() != $transaction['currency']) { + $currency_conversion = true; + $transAmount = $transaction['transactionDetails']['effectiveAmount']; + $refundedAmount = $transaction['transactionDetails']['cardDetails']['captureResponse']['effectiveRefundAmount']; + } else { + $currency_conversion = false; + $transAmount = $transaction['amount']; + $refundedAmount = $transaction['transactionDetails']['cardDetails']['captureResponse']['refundAmount']; + } + + if (empty($refundedAmount)) { + $refundedAmount = 0; + } + + $remainingAmount = $transAmount - $refundedAmount; // Calculate remaining amount. + $currency_alpha3 = $currency->getAlpha3(); + + if ($remainingAmount < $amountInCents) { + if (! $currency_conversion) { + $remainingAmountFloat = $currency->convertAmountToFloat($remainingAmount); + $msg = sprintf( + $this->refundProcessor->translate('Remaining amount (%1$s %2$s) is less than requested refund amount (%3$s %2$s).'), + $remainingAmountFloat, + $orderInfo->getOrderCurrencySign(), + $amount + ); + + throw new \Exception($msg); + } else { + // It may be caused by currency conversion. + // We refund all the transaction refundable remaining amount in the gateway currency to avoid also conversions rounding. + $amountInCents = $transaction['amount'] - $transaction['transactionDetails']['cardDetails']['captureResponse']['refundAmount']; + $currency_alpha3 = $transaction['currency']; + } + } + + $requestData = array( + 'uuid' => $uuid, + 'amount' => $amountInCents, + 'currency' => $currency_alpha3, + 'resolutionMode' => 'REFUND_ONLY', + 'comment' => $commentText + ); + + $refundPaymentResponse = $client->post('V4/Transaction/CancelOrRefund', json_encode($requestData)); + + self::checkRestResult( + $refundPaymentResponse, + $successStatuses + ); + + // Check operation type. + $transType = $refundPaymentResponse['answer']['operationType']; + + if ($transType !== 'CREDIT') { + throw new \Exception(sprintf($this->refundProcessor->translate('Unexpected transaction type received (%1$s).'), $transType)); + } + + if (isset($transaction['refundedAmount'])) { // For payment in installment refund. + $refundPaymentResponse['answer']['refundedAmountMulti'] = $transaction['refundedAmount']; + } + + // Refund success do after refund function. + $this->refundProcessor->log("Online refund $amount {$orderInfo->getOrderCurrencySign()} for transaction with uuid #$uuid for order #{$orderInfo->getOrderId()} is successful.", 'INFO'); + $this->refundProcessor->doOnSuccess($refundPaymentResponse['answer'], 'refund'); + } else { + $transAmount = $transaction['amount']; + + // If order currency different than transaction currency we use transaction effective amount. + if ($orderInfo->getOrderCurrencyIsoCode() != $transaction['currency']) { + $transAmount = $transaction['transactionDetails']['effectiveAmount']; + } + + if ($amountInCents > $transAmount) { + $transAmountFloat = $currency->convertAmountToFloat($transAmount); + $msg = sprintf($this->refundProcessor->translate('Transaction amount (%1$s %2$s) is less than requested refund amount (%3$s %2$s).'), $transAmountFloat, $orderInfo->getOrderCurrencySign(), $amount); + throw new \Exception($msg); + } + + if ($amountInCents == $transAmount) { // Transaction cancel in gateway. + $requestData = array( + 'uuid' => $uuid, + 'resolutionMode' => 'CANCELLATION_ONLY', + 'comment' => $commentText + ); + + $cancelPaymentResponse = $client->post('V4/Transaction/CancelOrRefund', json_encode($requestData)); + self::checkRestResult($cancelPaymentResponse, array('CANCELLED')); + + if (isset($transaction['refundedAmount'])) { // For payment in installment refund. + $cancelPaymentResponse['answer']['refundedAmountMulti'] = $transaction['refundedAmount']; + } + + // Refund success do after refund function. + $this->refundProcessor->log("Online transaction with uuid #$uuid cancel for order #{$orderInfo->getOrderId()} is successful.", 'INFO'); + $this->refundProcessor->doOnSuccess($cancelPaymentResponse['answer'], 'cancel'); + } else { + // Partial transaction cancel, call update WS. + $new_transaction_amount = $transAmount - $amountInCents; + $requestData = array( + 'uuid' => $uuid, + 'cardUpdate' => array( + 'amount' => $new_transaction_amount, + 'currency' => $currency->getAlpha3() + ), + 'comment' => $commentText + ); + + $updatePaymentResponse = $client->post('V4/Transaction/Update', json_encode($requestData)); + + self::checkRestResult( + $updatePaymentResponse, + array( + 'AUTHORISED', + 'AUTHORISED_TO_VALIDATE', + 'WAITING_AUTHORISATION', + 'WAITING_AUTHORISATION_TO_VALIDATE' + ) + ); + + if (isset($transaction['refundedAmount'])) { // For payment in installment refund. + $updatePaymentResponse['answer']['refundedAmountMulti'] = $transaction['refundedAmount']; + } + + // Refund success do after refund function. + $this->refundProcessor->log("Online transaction with uuid #$uuid update for order #{$orderInfo->getOrderId()} is successful.", 'INFO'); + $this->refundProcessor->doOnSuccess($updatePaymentResponse['answer'], 'update'); + } + } + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/OrderInfo.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/OrderInfo.php new file mode 100644 index 0000000..31a744e --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/OrderInfo.php @@ -0,0 +1,128 @@ +orderRemoteId; + } + + /** + * @return string + */ + public function getOrderId() + { + return $this->orderId; + } + + /** + * @return string + */ + public function getOrderReference() + { + return $this->orderReference; + } + + /** + * @return string + */ + public function getOrderCurrencyIsoCode() + { + return $this->orderCurrencyIsoCode; + } + + /** + * @return string + */ + public function getOrderCurrencySign() + { + return $this->orderCurrencySign; + } + + /** + * @return string + */ + public function getOrderUserInfo() + { + return $this->orderUserInfo; + } + + /** + * @param string $orderRemoteId + */ + public function setOrderRemoteId($orderRemoteId) + { + $this->orderRemoteId = $orderRemoteId; + + return $this; + } + + /** + * @param string $orderId + */ + public function setOrderId($orderId) + { + $this->orderId = $orderId; + + return $this; + } + + /** + * @param string $orderReference + */ + public function setOrderReference($orderReference) + { + $this->orderReference = $orderReference; + + return $this; + } + + /** + * @param string $orderCurrencyIsoCode + */ + public function setOrderCurrencyIsoCode($orderCurrencyIsoCode) + { + $this->orderCurrencyIsoCode = $orderCurrencyIsoCode; + + return $this; + } + + /** + * @param string $orderCurrencySign + */ + public function setOrderCurrencySign($orderCurrencySign) + { + $this->orderCurrencySign = $orderCurrencySign; + + return $this; + } + + /** + * @param string $orderUserInfo + */ + public function setOrderUserInfo($orderUserInfo) + { + $this->orderUserInfo = $orderUserInfo; + + return $this; + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/Processor.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/Processor.php new file mode 100644 index 0000000..0fffd16 --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Refund/Processor.php @@ -0,0 +1,43 @@ +code = $code; + } + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Api/Rest/Api.php b/app/code/community/Lyranetwork/Payzen/Model/Api/Rest/Api.php new file mode 100644 index 0000000..897655f --- /dev/null +++ b/app/code/community/Lyranetwork/Payzen/Model/Api/Rest/Api.php @@ -0,0 +1,235 @@ +endpoint = $endpoint; + $this->privateKey = $site_id . ':' . $password; + } + + /** + * @param null|string $host + * @param string|int $port + * @return $this + */ + public function setProxy($host, $port) + { + $this->proxyHost = $host; + $this->proxyPort = $port; + + return $this; + } + + /** + * @param int $connectionTimeout Maximum amount of time in seconds that is allowed to make the + * connection to the server. It can be set to 0 to disable this limit, but this is inadvisable + * in a production environment. + * @param int $timeout Maximum amount of time in seconds to which the execution of individual + * cURL extension function calls will be limited. Note that the value for this setting should + * include the value for CURLOPT_CONNECTTIMEOUT. + * In other words, CURLOPT_CONNECTTIMEOUT is a segment of the time represented by + * CURLOPT_TIMEOUT, so the value of the CURLOPT_TIMEOUT should be greater than the value of + * the CURLOPT_CONNECTTIMEOUT. + * @return $this + */ + public function setTimeouts($connectionTimeout, $timeout) + { + $this->connectionTimeout = $connectionTimeout; + $this->timeout = $timeout; + + return $this; + } + + /** + * @param string $target + * @param mixed $data + * @return array + * @throws Exception + */ + public function post($target, $data) + { + if (extension_loaded('curl')) { + return $this->curlPost($target, $data); + } + + return $this->fallbackPost($target, $data); + } + + /** + * @param string $target + * @param mixed $data + * @return array + * @throws Exception + */ + protected function curlPost($target, $data) + { + $url = $this->endpoint . $target; + + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type: application/json')); + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_USERAGENT, 'Plugins REST API PHP SDK'); + curl_setopt($curl, CURLOPT_USERPWD, $this->privateKey); + curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout); + curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout); + + // We disable SSL validation for test key because there is a lot of WAMP installations that do not handle certificates well. + $test_mode = strpos($this->privateKey, 'testpassword_') !== false; + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $test_mode ? 0 : 2); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ! $test_mode); + + $raw_response = curl_exec($curl); + + $info = curl_getinfo($curl); + if (! in_array($info['http_code'], array(200, 401), true)) { + $error = curl_error($curl); + $errno = curl_errno($curl); + curl_close($curl); + + $msg = "Call to URL $url failed with unexpected status: {$info['http_code']}"; + + if ($raw_response) { + $msg .= ", raw response: $raw_response"; + } + + if ($errno) { + $msg .= ", cURL error: $error ($errno)"; + } + + $msg .= ", cURL info: " . print_r($info, true); + + throw new \Exception($msg, '-1'); + } + + $response = json_decode($raw_response, true); + if (! is_array($response)) { + $error = curl_error($curl); + $errno = curl_errno($curl); + curl_close($curl); + + $msg = "Call to URL $url returned an unexpected response, raw response: $raw_response"; + + if ($errno) { + $msg .= ", cURL error: $error ($errno)"; + } + + $msg .= ", cURL info: " . print_r($info, true); + + throw new \Exception($msg, '-1'); + } + + curl_close($curl); + + return $response; + } + + /** + * @param string $target + * @param mixed $data + * @return array + * @throws Exception + */ + protected function fallbackPost($target, $data) + { + $url = $this->endpoint . $target; + + $http = array( + 'method' => 'POST', + 'header' => 'Authorization: Basic ' . base64_encode($this->privateKey) . "\r\n". + 'Content-Type: application/json', + 'content' => $data, + 'user_agent' => 'Plugins REST API PHP SDK', + 'timeout' => $this->timeout + ); + + if ($this->proxyHost && $this->proxyPort) { + $http['proxy'] = $this->proxyHost . ':' . $this->proxyPort; + } + + $ssl = array(); + + // We disable SSL validation for test key because there is a lot of WAMP installations that do not handle certificates well. + $test_mode = strpos($this->privateKey, 'testpassword_') !== false; + $ssl['verify_peer'] = ! $test_mode; + $ssl['verify_peer_name'] = ! $test_mode; + + $context = stream_context_create(array('http' => $http, 'ssl' => $ssl)); + $raw_response = file_get_contents($url, false, $context); + + if (! $raw_response) { + throw new \Exception("Error: call to URL $url failed.", '-1'); + } + + $response = json_decode($raw_response, true); + if (! is_array($response)) { + throw new \Exception("Error: call to URL $url failed, response $raw_response.", '-1'); + } + + return $response; + } +} diff --git a/app/code/community/Lyranetwork/Payzen/Model/Field/Other/AddedPaymentMeans.php b/app/code/community/Lyranetwork/Payzen/Model/Field/Other/AddedPaymentMeans.php index 0e25035..ff11533 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Field/Other/AddedPaymentMeans.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Field/Other/AddedPaymentMeans.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + class Lyranetwork_Payzen_Model_Field_Other_AddedPaymentMeans extends Lyranetwork_Payzen_Model_Field_Array { protected $_eventPrefix = 'payzen_field_added_payment_means'; @@ -15,12 +17,16 @@ class Lyranetwork_Payzen_Model_Field_Other_AddedPaymentMeans extends Lyranetwork protected function _beforeSave() { $values = $this->getValue(); - $usedCards = Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes(); + $usedCards = PayzenApi::getSupportedCardTypes(); if (! is_array($values) || empty($values)) { $this->setValue(array()); } else { foreach ($values as $key => $value) { + if (empty($value)) { + continue; + } + $code = trim($value['code']); $title = $value['title']; if (empty($code) diff --git a/app/code/community/Lyranetwork/Payzen/Model/Field/Other/PaymentMeans.php b/app/code/community/Lyranetwork/Payzen/Model/Field/Other/PaymentMeans.php index a55f3d9..0ef17d6 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Field/Other/PaymentMeans.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Field/Other/PaymentMeans.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + class Lyranetwork_Payzen_Model_Field_Other_PaymentMeans extends Lyranetwork_Payzen_Model_Field_Array { protected $_eventPrefix = 'payzen_field_other_payment_means'; @@ -22,7 +24,7 @@ protected function _beforeSave() } else { $i = 0; $means = array(); - $supportedCards = Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes(); + $supportedCards = PayzenApi::getSupportedCardTypes(); $addedCards = Mage::getModel('payzen/payment_other')->getAddedMeans(); $cards = array_merge($supportedCards, $addedCards); foreach ($values as $key => $value) { diff --git a/app/code/community/Lyranetwork/Payzen/Model/Field/Standard/RestPlaceholders.php b/app/code/community/Lyranetwork/Payzen/Model/Field/Standard/RestPlaceholders.php index 5c47bf3..5781837 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Field/Standard/RestPlaceholders.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Field/Standard/RestPlaceholders.php @@ -18,15 +18,6 @@ protected function _beforeSave() if (! is_array($values) || empty($values)) { $this->setValue(array()); - } else { - $i = 0; - foreach ($values as $value) { - $i++; - - if (empty($value)) { - continue; - } - } } return parent::_beforeSave(); diff --git a/app/code/community/Lyranetwork/Payzen/Model/Observer.php b/app/code/community/Lyranetwork/Payzen/Model/Observer.php index 4899b2c..8adb19a 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Observer.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Observer.php @@ -355,30 +355,6 @@ public function doPaymentButtonsManage($observer) } } - public function doAfterPaymentSectionEdit($observer) - { - if (Mage::app()->getRequest()->getParam('section') !== 'payment') { - return; - } - - // Response content. - $output = Mage::app()->getLayout()->getOutput(); - - $preferedMaxInputVars = 0; - $preferedMaxInputVars += substr_count($output, 'name="groups['); - $preferedMaxInputVars += substr_count($output, 'name="config_state['); - $preferedMaxInputVars += 100; // To take account of dynamically created inputs. - - $block = Mage::app()->getLayout()->getMessagesBlock(); - if ((ini_get('suhosin.post.max_vars') && ini_get('suhosin.post.max_vars') < $preferedMaxInputVars) - || (ini_get('suhosin.request.max_vars') && ini_get('suhosin.request.max_vars') < $preferedMaxInputVars) - ) { - $block->addWarning($this->_getHelper()->__('Warning, please increase the suhosin patch for PHP post and request limits to save module configurations correctly. Recommended value is %s.', $preferedMaxInputVars)); - } elseif (ini_get('max_input_vars') && ini_get('max_input_vars') < $preferedMaxInputVars) { - $block->addWarning($this->_getHelper()->__('Warning, please increase the value of the max_input_vars directive in php.ini to save module configurations correctly. Recommended value is %s.', $preferedMaxInputVars)); - } - } - public function doReplacePrototypeLibrary() { $quote = Mage::getSingleton('checkout/session')->getQuote(); diff --git a/app/code/community/Lyranetwork/Payzen/Model/Payment/Abstract.php b/app/code/community/Lyranetwork/Payzen/Model/Payment/Abstract.php index 311e1d6..c6319b8 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Payment/Abstract.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Payment/Abstract.php @@ -8,6 +8,13 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; +use Lyranetwork\Payzen\Model\Api\Rest\Api as PayzenRest; +use Lyranetwork\Payzen\Model\Api\Form\Response as PayzenResponse; +use Lyranetwork\Payzen\Model\Api\Form\Request as PayzenRequest; +use Lyranetwork\Payzen\Model\Api\Refund\OrderInfo as PayzenOrderInfo; +use Lyranetwork\Payzen\Model\Api\Refund\Api as PayzenRefund; + abstract class Lyranetwork_Payzen_Model_Payment_Abstract extends Mage_Payment_Model_Method_Abstract { protected $_infoBlockType = 'payzen/info'; @@ -36,7 +43,7 @@ public function __construct() { parent::__construct(); - $this->_payzenRequest = new Lyranetwork_Payzen_Model_Api_Request(); + $this->_payzenRequest = new PayzenRequest(); } /** @@ -53,10 +60,10 @@ public function getFormFields($order) $amount = $order->getGrandTotal(); // Set currency. - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByAlphaCode($order->getOrderCurrencyCode()); + $currency = PayzenApi::findCurrencyByAlphaCode($order->getOrderCurrencyCode()); if ($currency == null) { // If currency is not supported, use base currency. - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByAlphaCode($order->getBaseCurrencyCode()); + $currency = PayzenApi::findCurrencyByAlphaCode($order->getBaseCurrencyCode()); // ... and order total in base currency $amount = $order->getBaseGrandTotal(); @@ -70,7 +77,7 @@ public function getFormFields($order) $contrib = $this->_getHelper()->getCommonConfigData('cms_identifier') . '_' . $this->_getHelper()->getCommonConfigData('plugin_version') . '/'; - $this->_payzenRequest->set('contrib', $contrib . Mage::getOpenMageVersion() . '/' . Lyranetwork_Payzen_Model_Api_Api::shortPhpVersion()); + $this->_payzenRequest->set('contrib', $contrib . Mage::getOpenMageVersion() . '/' . PayzenApi::shortPhpVersion()); // Set config parameters. $configFields = array('site_id', 'key_test', 'key_prod', 'ctx_mode', 'capture_delay', 'validation_mode', @@ -102,7 +109,7 @@ public function getFormFields($order) // Set the language code. $lang = strtolower(substr(Mage::app()->getLocale()->getLocaleCode(), 0, 2)); - if (! Lyranetwork_Payzen_Model_Api_Api::isSupportedLanguage($lang)) { + if (! PayzenApi::isSupportedLanguage($lang)) { $lang = $this->_getHelper()->getCommonConfigData('language'); } @@ -283,7 +290,7 @@ public function acceptPayment(Mage_Payment_Model_Info $payment) $requestData = array('uuid' => $uuid); // Perform our request. - $client = new Lyranetwork_Payzen_Model_Api_Rest( + $client = new PayzenRest( $this->_getHelper()->getCommonConfigData('rest_url', $storeId), $this->_getHelper()->getCommonConfigData('site_id', $storeId), $this->_getRestHelper()->getPassword($storeId) @@ -293,8 +300,8 @@ public function acceptPayment(Mage_Payment_Model_Info $payment) } $successStatuses = array_merge( - Lyranetwork_Payzen_Model_Api_Api::getSuccessStatuses(), - Lyranetwork_Payzen_Model_Api_Api::getPendingStatuses() + PayzenApi::getSuccessStatuses(), + PayzenApi::getPendingStatuses() ); $this->_getRestHelper()->checkResult($getPaymentDetails, $successStatuses); @@ -315,7 +322,7 @@ public function acceptPayment(Mage_Payment_Model_Info $payment) $data = $this->_getRestHelper()->convertRestResult($getPaymentDetails['answer'], true); // Load API response. - $response = new Lyranetwork_Payzen_Model_Api_Response($data, null, null, null); + $response = new PayzenResponse($data, null, null, null); $stateObject = $this->_getPaymentHelper()->nextOrderState($response, $order, true); @@ -396,7 +403,7 @@ public function denyPayment(Mage_Payment_Model_Info $payment) ); // Load API response. - $client = new Lyranetwork_Payzen_Model_Api_Rest( + $client = new PayzenRest( $this->_getHelper()->getCommonConfigData('rest_url', $storeId), $this->_getHelper()->getCommonConfigData('site_id', $storeId), $this->_getRestHelper()->getPassword($storeId) @@ -499,7 +506,7 @@ public function validatePayment(Mage_Payment_Model_Info $payment) ); // Perform our request. - $client = new Lyranetwork_Payzen_Model_Api_Rest( + $client = new PayzenRest( $this->_getHelper()->getCommonConfigData('rest_url', $storeId), $this->_getHelper()->getCommonConfigData('site_id', $storeId), $this->_getRestHelper()->getPassword($storeId) @@ -513,7 +520,7 @@ public function validatePayment(Mage_Payment_Model_Info $payment) $data = $this->_getRestHelper()->convertRestResult($validatePaymentResponse['answer'], true); // Load API response. - $response = new Lyranetwork_Payzen_Model_Api_Response($data, null, null, null); + $response = new PayzenResponse($data, null, null, null); $transId = $order->getPayment()->getCcTransId() . '-' . $response->get('sequence_number'); @@ -599,7 +606,7 @@ protected function _validatePaymentOffline($order, $payment) { } // Load API response. - $response = new Lyranetwork_Payzen_Model_Api_Response($data, null, null, null); + $response = new PayzenResponse($data, null, null, null); $stateObject = $this->_getPaymentHelper()->nextOrderState($response, $order, true); @@ -684,14 +691,14 @@ public function canUseForCurrency($baseCurrencyCode) return in_array($this->currentCurrencyCode, $this->_currencies); } - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByAlphaCode($this->currentCurrencyCode); + $currency = PayzenApi::findCurrencyByAlphaCode($this->currentCurrencyCode); if ($currency) { return true; } } // Check base currency support. - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByAlphaCode($baseCurrencyCode); + $currency = PayzenApi::findCurrencyByAlphaCode($baseCurrencyCode); if ($currency) { return true; } @@ -781,166 +788,54 @@ public function refund(Varien_Object $payment, $amount) $storeId = $order->getStore()->getId(); $successStatuses = array_merge( - Lyranetwork_Payzen_Model_Api_Api::getSuccessStatuses(), - Lyranetwork_Payzen_Model_Api_Api::getPendingStatuses() + PayzenApi::getSuccessStatuses(), + PayzenApi::getPendingStatuses() ); $this->_getHelper()->log("Start refund of {$amount} {$order->getOrderCurrencyCode()} for order #{$order->getIncrementId()} with {$this->_code} payment method."); try { - // Get currency. - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByAlphaCode($order->getOrderCurrencyCode()); - $amountInCents = $currency->convertAmountToInteger($amount); - - // Retrieve transaction UUID. - $uuid = $payment->getAdditionalInformation(Lyranetwork_Payzen_Helper_Payment::TRANS_UUID); - if (! $uuid) { // Retro compatibility. - // Get UUID from Order. - $uuidArray = $this->_getPaymentDetails($order); - $uuid = reset($uuidArray); - } - - $requestData = array('uuid' => $uuid); - - // Perform our request. - $client = new Lyranetwork_Payzen_Model_Api_Rest( - $this->_getHelper()->getCommonConfigData('rest_url', $storeId), - $this->_getHelper()->getCommonConfigData('site_id', $storeId), - $this->_getRestHelper()->getPassword($storeId) - ); - - $getPaymentDetails = $client->post('V4/Transaction/Get', json_encode($requestData)); - $this->_getRestHelper()->checkResult($getPaymentDetails); - - $transStatus = $getPaymentDetails['answer']['detailedStatus']; - - if (! in_array($transStatus, $successStatuses)) { - $msg = $this->_getHelper()->__('Error occurred when refunding payment for order #%s. Unexpected transaction status: %s.', $order->getIncrementId(), $transStatus); - Mage::throwException($msg); - } - $commentText = $this->_getUserInfo(); foreach ($payment->getCreditmemo()->getCommentsCollection() as $comment) { $commentText .= '; ' . $comment->getComment(); } - if ($transStatus === 'CAPTURED') { // Transaction captured. - $requestData = array( - 'uuid' => $uuid, - 'amount' => $amountInCents, - 'resolutionMode' => 'REFUND_ONLY', - 'currency' => $currency->getAlpha3(), - 'comment' => $commentText - ); - - $refundPaymentResponse = $client->post('V4/Transaction/CancelOrRefund', json_encode($requestData)); - - $this->_getRestHelper()->checkResult( - $refundPaymentResponse, - array( - 'INITIAL', - 'AUTHORISED', - 'AUTHORISED_TO_VALIDATE', - 'WAITING_AUTHORISATION', - 'WAITING_AUTHORISATION_TO_VALIDATE', - 'CAPTURED', - 'UNDER_VERIFICATION' - ) - ); - - // Check operation type. - $transType = $refundPaymentResponse['answer']['operationType']; - - if ($transType !== 'CREDIT') { - $msg = $this->_getHelper()->__("Unexpected transaction type received (%s).", $transType); - throw new UnexpectedValueException($msg); - } - - // Create refund transaction in OpenMage. - $this->_createRefundTransaction($payment, $refundPaymentResponse['answer']); - - $this->_getHelper()->log("Online money refund for order #{$order->getIncrementId()} is successful."); - } else { - $transAmount = $getPaymentDetails['answer']['amount']; - if ($getPaymentDetails['answer']['transactionDetails']['effectiveCurrency'] && ($getPaymentDetails['answer']['transactionDetails']['effectiveCurrency'] !== $getPaymentDetails['answer']['currency'])) { - $transAmount = $getPaymentDetails['answer']['transactionDetails']['effectiveAmount']; // Use effective amount to get modified amount. - } - - if ($amountInCents > $transAmount) { - $transAmountFloat = $currency->convertAmountToFloat($transAmount); - $msg = $this->_getHelper()->__("Cannot refund payment for order #%s. Transaction amount (%s %s) is less than requested refund amount (%s %s).", $order->getIncrementId(), $transAmountFloat, $currency->getAlpha3(), $amount, $currency->getAlpha3()); - Mage::throwException($msg); - } - - if ($amountInCents == $transAmount) { // Transaction cancel. - $requestData = array( - 'uuid' => $uuid, - 'resolutionMode' => 'CANCELLATION_ONLY', - 'comment' => $commentText - ); - - $cancelPaymentResponse = $client->post('V4/Transaction/CancelOrRefund', json_encode($requestData)); - - $this->_getRestHelper()->checkResult($cancelPaymentResponse, array('CANCELLED')); - - $order->cancel(); - $this->_getHelper()->log("Online payment cancel for order #{$order->getIncrementId()} is successful."); - } else { - // Partial transaction cancel, call update WS. - $newTransactionAmount = $transAmount - $amountInCents; - $requestData = array( - 'uuid' => $uuid, - 'cardUpdate' => array( - 'amount' => $newTransactionAmount, - 'currency' => $currency->getAlpha3() - ), - 'comment' => $commentText - ); - - $updatePaymentResponse = $client->post('V4/Transaction/Update', json_encode($requestData)); - - $successStatuses = array_merge( - Lyranetwork_Payzen_Model_Api_Api::getSuccessStatuses(), - Lyranetwork_Payzen_Model_Api_Api::getPendingStatuses() - ); - - $this->_getRestHelper()->checkResult($updatePaymentResponse, $successStatuses); - - $this->_getHelper()->log("Online payment update for order #{$order->getIncrementId()} is successful."); - } + $online_transactions_currency = $this->_getHelper()->getCommonConfigData('online_transactions_currency', $storeId); + $amountInCurrencyCode = $payment->getCreditmemo()->getGrandTotal(); + $currencyCode = $order->getOrderCurrencyCode(); + $currency = PayzenApi::findCurrencyByAlphaCode($currencyCode); + if (($online_transactions_currency == '2') || (! $currency)) { + $currencyCode = $order->getBaseCurrencyCode(); + $amountInCurrencyCode = $payment->getCreditmemo()->getBaseGrandTotal(); } - } catch (UnexpectedValueException $e) { - $this->_getHelper()->log("Refund payment error: {$e->getMessage()}.", Zend_Log::ERR); - Mage::throwException($e->getMessage()); - } catch (Exception $e) { - $this->_getHelper()->log( - "Refund payment xception with code {$e->getCode()}: {$e->getMessage()}", - Zend_Log::ERR - ); - - if ($e->getCode() === 'PSP_083') { - Mage::throwException(__('Chargebacks cannot be refunded.')); - } elseif ($e->getCode() === 'PSP_100') { - // Merchant does not subscribe to REST WS option, refund payment offline. - $notice = __('You are not authorized to do this action online. Please, do not forget to update payment in PayZen Back Office.'); - $this->_getAdminSession()->addWarning($notice); - // OpenMage will do an offline refund. - } else { - $message = __('Refund error') . ': '; - if ($e->getCode() <= -1) { - // Manage cUrl errors. - $message .= __('Please consult the PayZen logs for more details.'); - } else { - $message .= $e->getMessage(); - } + $payzenOrderInfo = new PayzenOrderInfo(); + $payzenOrderInfo->setOrderRemoteId($order->getIncrementId()); + $payzenOrderInfo->setOrderId($order->getIncrementId()); + $payzenOrderInfo->setOrderReference($order->getIncrementId()); + $payzenOrderInfo->setOrderCurrencyIsoCode($currencyCode); + $payzenOrderInfo->setOrderCurrencySign($currencyCode); + $payzenOrderInfo->setOrderUserInfo($commentText); + + $refundApi = new PayzenRefund( + $this->_getRefundHelper()->setPayment($payment), + $this->_getRestHelper()->getPassword($storeId), + $this->_getHelper()->getCommonConfigData('rest_url', $storeId), + $this->_getHelper()->getCommonConfigData('site_id', $storeId), + 'OpenMage' + ); - Mage::throwException($message); - } + // Do online refund. + $order->setPayment($payment); + $refundApi->refund($payzenOrderInfo, $amountInCurrencyCode); + } catch (Exception $e) { + throw new \Exception($e->getMessage()); } $order->save(); + $this->_getHelper()->log("Refunded order #{$order->getIncrementId()} has been saved."); + return $this; } @@ -958,14 +853,14 @@ protected function _createRefundTransaction($payment, $refundResponse) } // Save paid amount. - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($response['vads_currency']); + $currency = PayzenApi::findCurrencyByNumCode($response['vads_currency']); $amount = number_format($currency->convertAmountToFloat($response['vads_amount']), $currency->getDecimals(), ',', ' '); $amountDetail = $amount . ' ' . $currency->getAlpha3(); if ($response['vads_effective_currency'] && ($response['vads_currency'] !== $response['vads_effective_currency'])) { - $effectiveCurrency = _Lyranetwork_Payzen_Model_Api_Api::findCurrencyByNumCode($response['vads_effective_currency']); + $effectiveCurrency = _PayzenApi::findCurrencyByNumCode($response['vads_effective_currency']); $effectiveAmount = number_format( $effectiveCurrency->convertAmountToFloat($response['vads_effective_amount']), @@ -1042,12 +937,22 @@ protected function _getRestHelper() return Mage::helper('payzen/rest'); } + /** + * Return payzen payment method refund helper. + * + * @return Mage_Payzen_Helper_Refund + */ + protected function _getRefundHelper() + { + return Mage::helper('payzen/refund'); + } + protected function _getPaymentDetails($order, $uuidOnly = true) { $storeId = $order->getStore()->getId(); // Get UUIDs from Order. - $client = new Lyranetwork_Payzen_Model_Api_Rest( + $client = new PayzenRest( $this->_getHelper()->getCommonConfigData('rest_url', $storeId), $this->_getHelper()->getCommonConfigData('site_id', $storeId), $this->_getRestHelper()->getPassword($storeId) diff --git a/app/code/community/Lyranetwork/Payzen/Model/Payment/Standard.php b/app/code/community/Lyranetwork/Payzen/Model/Payment/Standard.php index 9b41f6c..2cbb914 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Payment/Standard.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Payment/Standard.php @@ -8,6 +8,9 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; +use Lyranetwork\Payzen\Model\Api\Rest\Api as PayzenRest; + class Lyranetwork_Payzen_Model_Payment_Standard extends Lyranetwork_Payzen_Model_Payment_Abstract { protected $_code = 'payzen_standard'; @@ -86,17 +89,17 @@ protected function _proposeOney() private function _getFormTokenData($quote, $useIdentifier = false) { $amount = $quote->getGrandTotal(); - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByAlphaCode($quote->getOrderCurrencyCode()); + $currency = PayzenApi::findCurrencyByAlphaCode($quote->getOrderCurrencyCode()); if ($currency == null) { // If currency is not supported, use base currency - $currency = Lyranetwork_Payzen_Model_Api_Api::findCurrencyByAlphaCode($quote->getBaseCurrencyCode()); + $currency = PayzenApi::findCurrencyByAlphaCode($quote->getBaseCurrencyCode()); // ... and order total in base currency. $amount = $quote->getBaseGrandTotal(); } $language = strtolower(substr(Mage::app()->getLocale()->getLocaleCode(), 0, 2)); - if (! Lyranetwork_Payzen_Model_Api_Api::isSupportedLanguage($language)) { + if (! PayzenApi::isSupportedLanguage($language)) { $language = $this->_getHelper()->getCommonConfigData('language'); } @@ -107,12 +110,6 @@ private function _getFormTokenData($quote, $useIdentifier = false) $captureDelay = $this->_getHelper()->getCommonConfigData('capture_delay'); } - if ($this->getConfigData('validation_mode') !== '-1') { - $validationMode = $this->getConfigData('validation_mode'); - } else { - $validationMode = $this->_getHelper()->getCommonConfigData('validation_mode'); - } - // Activate 3DS ? $strongAuth = 'AUTO'; $threedsMinAmount = $this->_getHelper()->getCommonConfigData('threeds_min_amount'); @@ -162,18 +159,17 @@ private function _getFormTokenData($quote, $useIdentifier = false) $shippingAddress->getRegionCode() : $shippingAddress->getRegion(), 'phoneNumber' => $shippingAddress->getTelephone(), 'country' => $shippingAddress->getCountryId(), - 'shippingMethod' => $methodCode['type'], - 'shippingSpeed' => $methodCode['speed'] + 'shippingMethod' => $methodCode['type'] ?? null, + 'shippingSpeed' => $methodCode['speed'] ?? null ) ), 'transactionOptions' => array( 'cardOptions' => array( 'captureDelay' => $captureDelay, - 'manualValidation' => $validationMode ? 'YES' : 'NO', 'paymentSource' => 'EC' ) ), - 'contrib' => $contrib . Mage::getOpenMageVersion() . '/' . Lyranetwork_Payzen_Model_Api_Api::shortPhpVersion(), + 'contrib' => $contrib . Mage::getOpenMageVersion() . '/' . PayzenApi::shortPhpVersion(), 'strongAuthentication' => $strongAuth, 'currency' => $currency->getAlpha3(), 'amount' => $currency->convertAmountToInteger($amount), @@ -182,6 +178,15 @@ private function _getFormTokenData($quote, $useIdentifier = false) ) ); + $validationMode = $this->getConfigData('validation_mode'); + if (! is_null($validationMode)) { + $validationMode = ($validationMode === '-1') ? $this->_getHelper()->getCommonConfigData('validation_mode') : $validationMode; + + if (! is_null($validationMode)) { + $data['transactionOptions']['cardOptions']['manualValidation'] = ($validationMode === '1') ? 'YES' : 'NO'; + } + } + // Set Number of attempts in case of rejected payment. if ($this->getConfigData('rest_attempts')) { $data['transactionOptions']['cardOptions']['retry'] = $this->getConfigData('rest_attempts'); @@ -242,7 +247,7 @@ public function getFormToken($useIdentifier, $renew = false) try { // Perform our request. - $client = new Lyranetwork_Payzen_Model_Api_Rest( + $client = new PayzenRest( $this->_getHelper()->getCommonConfigData('rest_url'), $login, $this->_getRestHelper()->getPassword() @@ -292,7 +297,7 @@ public function getFormToken($useIdentifier, $renew = false) public function getAvailableCcTypes() { // All cards. - $allCards = Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes(); + $allCards = PayzenApi::getSupportedCardTypes(); // Selected cards from module configuration. $cards = $this->getConfigData('payment_cards'); diff --git a/app/code/community/Lyranetwork/Payzen/Model/Source/Languages.php b/app/code/community/Lyranetwork/Payzen/Model/Source/Languages.php index d339435..6f44eae 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Source/Languages.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Source/Languages.php @@ -8,13 +8,14 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; class Lyranetwork_Payzen_Model_Source_Languages { public function toOptionArray() { $options = array(); - foreach (Lyranetwork_Payzen_Model_Api_Api::getSupportedLanguages() as $code => $name) { + foreach (PayzenApi::getSupportedLanguages() as $code => $name) { $options[] = array ( 'value' => $code, 'label' => Mage::helper('payzen')->__($name) diff --git a/app/code/community/Lyranetwork/Payzen/Model/Source/Multi/PaymentCards.php b/app/code/community/Lyranetwork/Payzen/Model/Source/Multi/PaymentCards.php index 805659b..7bc4cf8 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Source/Multi/PaymentCards.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Source/Multi/PaymentCards.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + class Lyranetwork_Payzen_Model_Source_Multi_PaymentCards { protected $_allMultiCards = array( @@ -22,7 +24,7 @@ public function toOptionArray() // Add ALL value at the beginning. $options[] = array('value' => '', 'label' => Mage::helper('payzen')->__('ALL')); - foreach (Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes() as $code => $name) { + foreach (PayzenApi::getSupportedCardTypes() as $code => $name) { if (! in_array($code, $this->_allMultiCards)) { continue; } @@ -37,7 +39,7 @@ public function getMultiCards() { $options = array(); - foreach (Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes() as $code => $name) { + foreach (PayzenApi::getSupportedCardTypes() as $code => $name) { if (! in_array($code, $this->_allMultiCards)) { continue; } diff --git a/app/code/community/Lyranetwork/Payzen/Model/Source/PaymentCards.php b/app/code/community/Lyranetwork/Payzen/Model/Source/PaymentCards.php index 591103e..380cfa8 100644 --- a/app/code/community/Lyranetwork/Payzen/Model/Source/PaymentCards.php +++ b/app/code/community/Lyranetwork/Payzen/Model/Source/PaymentCards.php @@ -8,6 +8,8 @@ * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +use Lyranetwork\Payzen\Model\Api\Form\Api as PayzenApi; + class Lyranetwork_Payzen_Model_Source_PaymentCards { public function toOptionArray() @@ -18,7 +20,7 @@ public function toOptionArray() 'label' => Mage::helper('payzen')->__('ALL') ); - foreach (Lyranetwork_Payzen_Model_Api_Api::getSupportedCardTypes() as $code => $name) { + foreach (PayzenApi::getSupportedCardTypes() as $code => $name) { if ($code === 'ONEY_SANDBOX' || $code === 'ONEY') { continue; } diff --git a/app/code/community/Lyranetwork/Payzen/etc/config.xml b/app/code/community/Lyranetwork/Payzen/etc/config.xml index c92b10b..cdfc9de 100644 --- a/app/code/community/Lyranetwork/Payzen/etc/config.xml +++ b/app/code/community/Lyranetwork/Payzen/etc/config.xml @@ -14,7 +14,7 @@ - 1.0.0 + 1.1.0 @@ -411,16 +411,6 @@ - - - - - singleton - payzen/observer - doAfterPaymentSectionEdit - - -          @@ -499,7 +489,7 @@ Lyra Network - 1.0.0 + 1.1.0 V2 OpenMage_19-20 1 @@ -524,7 +514,7 @@ Redirection to shop in a few seconds... 5 Redirection to shop in a few seconds... - GET + POST processing 1 diff --git a/app/code/community/Lyranetwork/Payzen/etc/system.xml b/app/code/community/Lyranetwork/Payzen/etc/system.xml index 5233d57..6715ae7 100644 --- a/app/code/community/Lyranetwork/Payzen/etc/system.xml +++ b/app/code/community/Lyranetwork/Payzen/etc/system.xml @@ -79,13 +79,25 @@ 1 + + + label + payzen/adminhtml_system_config_field_label + payzen/adminhtml_system_config_field_pluginDoc + payment/payzen/plugin_doc + 15 + 1 + 1 + 1 + + Reset payzen/adminhtml_system_config_field_initButton - 15 + 16 1 1 1 @@ -97,7 +109,7 @@ select adminhtml/system_config_source_yesno payment/payzen/enable_logs - 16 + 17 1 1 1 diff --git a/app/locale/de_DE/Lyranetwork_Payzen.csv b/app/locale/de_DE/Lyranetwork_Payzen.csv index a9fba88..9755553 100644 --- a/app/locale/de_DE/Lyranetwork_Payzen.csv +++ b/app/locale/de_DE/Lyranetwork_Payzen.csv @@ -16,6 +16,7 @@ "Reset","Resetten" "Logs","Logdaten" "Enable / disable module logs.","Logdateien des Moduls aktivieren." +"Click to view the module configuration documentation","Klicken Sie, um die Modul-Konfigurationsdokumentation zu finden" "PAYMENT GATEWAY ACCESS","ZUGANG ZAHLUNGSSCHNITTSTELLE" "Store identifier","Shop ID" "The identifier provided by PayZen.","Die Kennung von PayZen bereitgestellt." @@ -283,8 +284,6 @@ "This field must agree to the regular expression %s.","Dieses Feld muss mit dem regulären Ausdruck %s zustimmen." "Payment in 3 or 4 times Oney cannot be used.","Die Zahlung im 3 oder 4 mal Oney kann nicht benutzt werden." "Gift card logos cannot be uploaded. See module logs for more details.","Geschenkkarten Logos können nicht hochgeladen werden. Weitere Informationen finden Sie unter Modul-Logdaten." -"Warning, please increase the suhosin patch for PHP post and request limits to save module configurations correctly. Recommended value is %s.","Achtung, bitte das suhosin patch für PHP post erhöhen und Beschränkungen bieten, um Modulkonfigurationen richtig zu speichern. Empfohlender Wert ist %s." -"Warning, please increase the value of the max_input_vars directive in php.ini to save module configurations correctly. Recommended value is %s.","Achtung, bitte den Wert vom post_max_size Direktive unter php.ini erhöhen, um Modulkonfigurationen richtig zu speichern. Empfohlender Wert ist %s." "Use 127 alphanumeric characters, accentuated characters and these special characters: space, slash, hyphen, apostrophe.","127 alphanumerische Zeichen benutzen, Akzente und Sonderzeichen: Leertaste, Querstrick, Bindestrich, Auslassungszeichen." "Payment Method","Zahlungsmethode" diff --git a/app/locale/en_US/Lyranetwork_Payzen.csv b/app/locale/en_US/Lyranetwork_Payzen.csv index 20c1e96..baa7ffc 100644 --- a/app/locale/en_US/Lyranetwork_Payzen.csv +++ b/app/locale/en_US/Lyranetwork_Payzen.csv @@ -16,6 +16,7 @@ "Reset","Reset" "Logs","Logs" "Enable / disable module logs.","Enable / disable module logs." +"Click to view the module configuration documentation","Click to view the module configuration documentation" "PAYMENT GATEWAY ACCESS","PAYMENT GATEWAY ACCESS" "Store identifier","Shop ID" "The identifier provided by PayZen.","The identifier provided by PayZen." @@ -282,8 +283,6 @@ "This field must agree to the regular expression %s.","This field must agree to the regular expression %s." "Payment in 3 or 4 times Oney cannot be used.","Payment in 3 or 4 times Oney cannot be used" "Gift card logos cannot be uploaded. See module logs for more details.","Gift card logos cannot be uploaded. See module logs for more details." -"Warning, please increase the suhosin patch for PHP post and request limits to save module configurations correctly. Recommended value is %s.","Warning, please increase the suhosin patch for PHP post and request limits to save module configurations correctly. Recommended value is %s." -"Warning, please increase the value of the max_input_vars directive in php.ini to save module configurations correctly. Recommended value is %s.","Warning, please increase the value of the max_input_vars directive in php.ini to save module configurations correctly. Recommended value is %s." "Use 127 alphanumeric characters, accentuated characters and these special characters: space, slash, hyphen, apostrophe.","Use 127 alphanumeric characters, accentuated characters and these special characters: space, slash, hyphen, apostrophe." "Payment Method","Payment Method" diff --git a/app/locale/es_ES/Lyranetwork_Payzen.csv b/app/locale/es_ES/Lyranetwork_Payzen.csv index 58b4d4e..49880a5 100644 --- a/app/locale/es_ES/Lyranetwork_Payzen.csv +++ b/app/locale/es_ES/Lyranetwork_Payzen.csv @@ -16,6 +16,7 @@ "Reset","Restablecer" "Logs","Registros" "Enable / disable module logs.","Habilitar/deshabilitar registros del módulo." +"Click to view the module configuration documentation","Haga clic para ver la documentación de la configuración del módulo" "PAYMENT GATEWAY ACCESS","ACCESO AL PORTAL DE PAGO" "Store identifier","Identificador de tienda" "The identifier provided by PayZen.","El identificador proporcionado por PayZen." @@ -283,8 +284,6 @@ "This field must agree to the regular expression %s.","Este campo debe aceptar la expresión regular %s." "Payment in 3 or 4 times Oney cannot be used.","No se puede usar el pago 3 o 4 veces Oney." "Gift card logos cannot be uploaded. See module logs for more details.","No se pueden cargar los logotipos de la tarjeta regalo. Consulte los registros del módulo para más detalles." -"Warning, please increase the suhosin patch for PHP post and request limits to save module configurations correctly. Recommended value is %s.","Advertencia: aumente el parche suhosin para PHP post y solicite límites para guardar la configuración del módulo correctamente. El valor recomendado es %s." -"Warning, please increase the value of the max_input_vars directive in php.ini to save module configurations correctly. Recommended value is %s.","Advertencia: aumente el valor de la directriz max_input_vars en php.ini para guardar la configuración del módulo correctamente. El valor recomendado es %s." "Use 127 alphanumeric characters, accentuated characters and these special characters: space, slash, hyphen, apostrophe.","Use 127 caracteres alfanuméricos, caracteres acentuados y estos caracteres especiales: espacio, barra diagonal, guión, apóstrofe." "Payment Method","Método de pago" diff --git a/app/locale/fr_FR/Lyranetwork_Payzen.csv b/app/locale/fr_FR/Lyranetwork_Payzen.csv index fa766f9..1bd85c1 100644 --- a/app/locale/fr_FR/Lyranetwork_Payzen.csv +++ b/app/locale/fr_FR/Lyranetwork_Payzen.csv @@ -16,6 +16,7 @@ "Reset","Réinitialiser" "Logs","Logs" "Enable / disable module logs.","Activer / désactiver les logs pour ce module." +"Click to view the module configuration documentation","Cliquer pour accéder à la documentation de configuration du module" "PAYMENT GATEWAY ACCESS","ACCÈS À LA PLATEFORME" "Store identifier","Identifiant de la boutique" "The identifier provided by PayZen.","Identifiant fourni par PayZen." @@ -282,8 +283,6 @@ "This field must agree to the regular expression %s.","Ce champ doit respecter l'expression régulière %s." "Payment in 3 or 4 times Oney cannot be used.","Le paiement 3 ou 4 Oney ne peut pas être utilisé." "Gift card logos cannot be uploaded. See module logs for more details.","Les logos des cartes cadeaux ne peuvent pas être téléchargés. Veuillez vérifier les logs du module pour plus de détails." -"Warning, please increase the suhosin patch for PHP post and request limits to save module configurations correctly. Recommended value is %s.","Avertissement, veuillez augmenter les limites de POST et REQUEST dans le patch suhosin de PHP pour enregistrer correctement la configuration du module. La valeur recommandée est %s." -"Warning, please increase the value of the max_input_vars directive in php.ini to save module configurations correctly. Recommended value is %s.","Avertissement, veuillez augmenter la valeur de la directive max_input_vars dans php.ini pour enregistrer correctement les configurations du module. La valeur recommandée est %s." "Use 127 alphanumeric characters, accentuated characters and these special characters: space, slash, hyphen, apostrophe.","Utiliser 127 caractères alphanumériques, caractères accentués et les caractères spéciaux suivants: espace, slash, tiret, apostrophe." "Payment Method","Méthode de paiement" diff --git a/media/payzen/logos/multi.png b/media/payzen/logos/multi.png index bd23abf..c4bdc34 100644 Binary files a/media/payzen/logos/multi.png and b/media/payzen/logos/multi.png differ diff --git a/media/payzen/logos/standard.png b/media/payzen/logos/standard.png index bd23abf..c4bdc34 100644 Binary files a/media/payzen/logos/standard.png and b/media/payzen/logos/standard.png differ