Статьи

Токенизация кредитных карт PayPal в Magento

Являясь надежной платформой электронной коммерции, Magento уже давно поддерживает интеграцию с различными платежными решениями PayPal для кредитных карт. Недавно выпущенное расширение токенизации кредитной карты PayPal от Classy Llama добавляет функцию, которой очень не хватает продавцам, использующим PayPal, — возможность покупателям сохранять кредитную карту для повторного использования.

Создание этого расширения стало новым опытом для Classy Llama во многих отношениях, особенно потому, что мы в первую очередь агентство, а не разработчик распространяемых расширений. Фактически, это была наша первая попытка разработать расширение для Magento Connect, официального рынка платформы. Эта статья предлагает краткий обзор того, как развивался проект, и несколько основных моментов процесса разработки.

Недостающий кусок

«Но мне действительно нужно разрешить клиентам сохранять свои кредитные карты». Мы с определенной частотой слышали это мнение от клиентов, выражая серьезное препятствие на пути выбора PayPal по сравнению с конкурентом Authorize.Net в качестве их платежного шлюза или процессора.

Видите ли, то, о чем мы говорим, на самом деле вовсе не хранит данные кредитной карты. Соответствие PCI DSS, которое обеспечивается хранением конфиденциальных данных о платежах, невозможно для многих онлайн-продавцов, и существуют альтернативы безопасных токенов как в интеграции PayPal, так и Authorize.Net API. Эти решения обеспечивают полное использование сохраненных карт для клиента, а сами данные кредитной карты хранятся у поставщика платежных услуг, а не на сайте продавца.

Однако базовая кодовая база Magento еще не реализовала поддержку этих функций ни в одной из этих служб. И хотя существует несколько платных сторонних расширений для улучшения интеграции Authorize.Net с этой функциональностью, до сих пор не было готовых вариантов для PayPal.

Мы обнаружили, что клиенты не хотят нести расходы на разработку этой функциональности для своего сайта. Когда перспективы снова возникли при планировании недавнего клиентского сайта, мы решили обратиться к PayPal: захотят ли они разделить расходы на разработку сохраненных функций кредитной карты для сайта нашего нового клиента, если в результате получится распространяемое расширение, которое может добавить ценность PayPal для других продавцов?

Ответ был восторженным. Отсутствие такого расширения сильно ощущалось среди специалистов по интеграции компании и отдела продаж, особенно когда многие продавцы ошибочно ожидали, что PayPal будет иметь прямой контроль над тем, что было и не было включено в Magento (обе компании являются частью семейства брендов eBay) ,

Наконец-то у нас появился жизнеспособный план по завершению ключевой части функциональности, которая принесет большую пользу нынешним и будущим клиентам. И самая лучшая часть для продавцов: расширение будет полностью бесплатным, что позволит PayPal полностью противостоять платным альтернативам Authorize.Net.

Объем работ вскоре обрел форму, включая еще несколько наворотов помимо простой сохраненной функциональности кредитной карты. Поскольку у нас был конкретный целевой клиент для первоначальной сборки, его специфические потребности частично определяли функции, которые будут включены (поддержка соглашений о выставлении счетов с интеграцией шлюза PayFlow — одна из таких функций). Однако в этой статье основное внимание будет уделено сохраненным функциям кредитных карт.

развитие

Окружающая обстановка

Несколько особых проблем связаны с разработкой распространяемого расширения, которого нет в обычной работе агентства. Как самостоятельный продукт, в модуль VCS войдет только сам модуль, а не вся кодовая база сайта. Тем не менее, вам понадобится активная среда, интегрирующая файлы вашего модуля во время разработки, без необходимости копировать эти файлы снова и снова.

Как правило, во время разработки также необходимо легко тестировать код между различными версиями платформы. Эти проблемы можно решить, просто исключив большое количество рабочих файлов из вашей VCS, но это не идеально, особенно в Magento, где ваш пользовательский код должен быть в некоторой степени разбросан по основным каталогам.

К счастью, в средах Linux / Unix или Mac OSX есть инструмент для решения этих проблем без особых усилий. Облегченный и превосходный скрипт Modman Колина Молленхура был специально разработан для разработки Magento и сводит работу по сопровождению проекта к нескольким рабочим базам кода до нескольких простых команд (инструмент работает, храня файлы вашего проекта в центральном скрытом каталоге и размещая символические ссылки в вашем актуальная кодовая база). В прошлом мы использовали модман в прошлом, и это был очевидный выбор для управления средами разработки для нашего расширения PayPal.

Проблема с версией

Имея хорошую систему для тестирования версий, какие версии мы бы на самом деле поддерживали с нашим расширением Tokenization кредитной карты? Текущая версия Magento Enterprise на тот момент была 1.12.0.2; это было приоритетом не только в соответствии со здравым смыслом, но и потому, что клиентский сайт, который получит первые плоды проекта, был новой версией этой версии. Масштаб проекта был совершенно ясен, что изначально совместимость с предыдущими версиями будет ограничена теми, которые требуют небольшой дополнительной разработки. После небольшого анализа мы узнали ответ на вопрос с редкой и неудачной окончательностью. Наше начальное расширение вообще не было бы обратно совместимым, даже с выпуском 1.12.0.0.

Причина кроется в недавнем пересмотре основных параметров конфигурации Magento для интеграции PayPal, что, как ни странно, произошло между 1.12.0.0 и 1.12.0.2. В Magento такие настройки очень легко определяются в файле XML с определенным расширением. Естественно, нашим потребовалось бы несколько собственных интегрированных с основными опциями PayPal, и недавний пересмотр означал, что структура узла XML, с которой мы должны работать, кардинально отличалась от любой предыдущей версии Magento.

Это был момент, когда ползучесть области могла начаться довольно легко и рано. Базовый код интеграции PayPal в Magento (даже код, который фактически обращался к указанным опциям конфигурации) мало менялся в нескольких выпусках, поэтому было несколько других барьеров для более широкой совместимости. Однако дополнительная разработка двух версий файла конфигурации расширения, скорее всего, также приведет к увеличению времени тестирования и отладки.

И хотя определение версии обычно было бы небольшим подвигом в коде приложения, файлы конфигурации XML — это одна из областей в Magento, где нет реальной возможности для такой вещи; поэтому два расходящихся пакета расширения выглядели как вероятный сценарий, добавляя еще большую сложность в перспективу.

Столкнувшись со всеми этими соображениями, как бы нам ни хотелось выпустить с более широкой совместимостью, мы остановились на этом. Наше расширение изначально поддерживало бы Magento Enterprise 1.12.0.2, параллельное Сообщество 1.7.0.2 и (на тот момент) готовящуюся к выпуску Enterprise 1.13.0.0. И в результате мы выпустили функциональное продление в срок и в рамках бюджета.

Базовая Архитектура

Учитывая простоту области действия, было фактически мало вариантов технической реализации, которые могли быть сделаны с этим расширением. Один из немногих связан с нашим подходом к пользовательскому интерфейсу и последствиями этого решения для структуры кода. Основной код Magento определяет различные способы оплаты и классы PHP, которые управляют ими в одном из многих типов системных XML-файлов конфигурации. Способы оплаты PayPal не являются исключением:

<payment>
    <paypal_express>
        <model>paypal/express</model>
        <title>PayPal Express Checkout</title>
        ...
    </paypal_express>
    <paypal_direct>
        <model>paypal/direct</model>
        <title>PayPal Payments Pro</title>
        ...
    </paypal_direct>
    ...
    <verisign>
        <model>paypal/payflowpro</model>
        <title>Payflow Pro</title>
        ...
    </verisign>
    ...
    <payflow_link>
        <model>paypal/payflowlink</model>
        ...
        <title>Credit Card</title>
        ...
    </payflow_link>
    <payflow_advanced>
        <model>paypal/payflowadvanced</model>
        ...
        <title>Credit Card</title>
        ...
    </payflow_advanced>
    ...
</payment>

Многое из XML было усечено выше, но то, что осталось, показывает, как определяются PayPal Express и четыре решения для прямых кредитных карт, поддерживаемые расширением. Каждый узел под соответствует элементу в списке переключателей способов оплаты, с которым клиент представлен на кассе (за исключением PayPal Express, только один из перечисленных вариантов будет активирован за один раз, в зависимости от выбранной интеграции PayPal продавца). узел под каждым определяет класс PHP, отвечающий за поведение метода оплаты.

Учитывая эту структуру одного основного класса, отвечающего за один метод оплаты, нам нужно было решить, имеет ли смысл переопределять функциональность существующих методов оплаты — что позволит обеспечить большую гибкость при представлении интерфейса сохраненной карты — или расширить их с помощью отдельных методов оплаты.

В конце концов, последнее имело смысл. Сохранение логики использования сохраненной карты в ее собственном наборе классов и привязке к ним собственного набора параметров конфигурации было более чистой структурой.

 

Результат для пользовательского интерфейса был немного более жестким, но совершенно ясным для клиента: «Сохраненная кредитная карта» будет отображаться как отдельная опция в списке переключателей при оформлении заказа, если клиент ранее выбрал сохранение карты.

Варианты Magento PayPal

Конечно, некоторое переопределение базовых классов методов кредитных карт было все еще необходимо. В конце концов, покупателям должна была быть предложена возможность сохранить карту. Но фактически отличающаяся логика обработки платежей будет четко разделена, без вмешательства в классы базовой интеграции с условным переходом на каждом шагу.

Незначительные удары

Одной из проблем со структурой классов, определенной чуть выше, была типичная проблема наследования. Случилось так, что основные классы, отвечающие за три из наших четырех поддерживаемых решений — PayFlow Pro, PayFlow Link и Payments Advanced — уже сформировали цепочку наследования с небольшими изменениями. Наши версии «сохраненных карточек» всех трех, естественно, также были бы почти идентичны, и нам бы не хотелось больше ничего, кроме как использовать наследование для нашей собственной логики.

Увы, каждый из наших классов должен был расширить свой аналог в основном коде. Решение, которое мы получили в итоге, было одним из лучших в обоих мирах, с базовым классом, содержащим основную логику обработки, на которую ссылаются как на свойство, а не расширяют классы метода оплаты. Методы в последнем случае почти повсеместно просто вызывались до основного класса Усеченный пример одного из классов платежей должен прояснить суть:

 

Другие проблемы возникли и были побеждены здесь и там, но это не имело большого значения. На самом деле, мы не могли быть довольны тем, как гладко шла разработка расширения. Однако одним из недостатков готового расширения была жесткость, необходимая из-за того, что информация о различных способах оплаты PayPal была жестко запрограммирована в основном модуле.

Вы видели выше, как легко определить новые методы оплаты через XML, и если бы наши новые методы были действительно автономными, они были бы готовы к работе с минимальным вмешательством в основной код. Однако, полагаясь на существующий код интеграции PayPal, они подвергаются многочисленным случаям констант классов, напрямую ссылающихся на методы оплаты, операторам дел, определяющих, как должна быть получена информация конфигурации, и даже жестко заданной логике, для которой страны используют метод оплаты. опоры.

Ниже приведены некоторые фрагменты нашего собственного переписывания вездесущего класса PayPal Config, демонстрирующие несколько мест, где мы были вынуждены добавить наши новые способы оплаты:

 <payment>
    <paypal_direct_customerstored>
        <model>cls_paypal/paypal_stored_customerstored_direct</model>
        ...
        <title>Saved Credit Card</title>
        ...
    </paypal_direct_customerstored>        
    <verisign_customerstored>
        <model>cls_paypal/paypal_stored_customerstored_payflowpro</model>
        ...
        <title>Saved Credit Card</title>
        ...
    </verisign_customerstored>
    <payflow_link_customerstored>
        <model>cls_paypal/paypal_stored_customerstored_payflowlink</model>
        ...
        <title>Saved Credit Card</title>
        ...
    </payflow_link_customerstored>
    <payflow_advanced_customerstored>
        <model>cls_paypal/paypal_stored_customerstored_payflowadvanced</model>
        ...
        <title>Saved Credit Card</title>
        ...
    </payflow_advanced_customerstored>
    ...
</payment>

В конце я не могу с уверенностью сказать, что я мог бы спроектировать такую ​​сложную интеграцию более гибким способом. Однако, это может упасть, однако, конечный результат жестко закодированных элементов, подобных этим, повышает вероятность того, что для поддержки будущих выпусков Magento потребуется незначительный повторный набор инструментов.

Нет проблем с API

Будучи в основном интеграцией с веб-сервисом, это расширение опирается на вызовы API для своей основной функциональности. В этой области, однако, наша задача стала чрезвычайно легкой благодаря тому, что мы просто усиливали существующую интеграцию. Ядро Magento уже определило классы для выдачи соответствующих запросов, и на самом деле нам не нужно было вообще определять низкоуровневый код для создания вызовов API.

Наше расширение было связано с двумя API-интерфейсами PayPal — имя-значение-пара (NVP) — прямым API и API шлюза PayFlow. Класс Magento Mage_Paypal_Model_Api_Nvp определяет логику для первого, в то время как последний обрабатывается непосредственно в классе метода оплаты PayFlow Pro и его потомках. Очень краткий снимок некоторых классов прямого API:

 class CLS_Paypal_Model_Paypal_Stored_Customerstored_Payflowpro extends CLS_Paypal_Model_Paypal_Payflowpro
{
    //...

    protected $_commonPayflowMethod;

    public function __construct()
    {
        parent::__construct();

        // Initialize common method
        $this->_commonPayflowMethod = Mage::getModel(
            'cls_paypal/paypal_stored_customerstored_payflow',
            array(
                'caller_method' => $this,
                //... various other params
            )
        );
    }

    //...

    public function authorize(Varien_Object $payment, $amount)
    {
        return $this->_commonPayflowMethod->authorize($payment, $amount);
    }

    public function capture(Varien_Object $payment, $amount)
    {
        return $this->_commonPayflowMethod->capture($payment, $amount);
    }
    
    //...
}

Наша главная забота касалась так называемых ссылочных транзакций, поскольку это ключевая функция токенизации API PayPal, которая заставляет работать нашу версию «сохраненных кредитных карт». Как оказалось, нам повезло, по крайней мере, с прямым API, потому что ссылочные транзакции были обязательным компонентом функции, которую уже поддерживала базовая интеграция: соглашения о выставлении счетов.

class CLS_Paypal_Model_Paypal_Config extends Mage_Paypal_Model_Config
{
//...

// "Customer stored" payment methods
const METHOD_PAYPAL_DIRECT_CUSTOMERSTORED = 'paypal_direct_customerstored';
const METHOD_PAYPAL_PAYFLOWADVANCED_CUSTOMERSTORED = 'payflow_advanced_customerstored';
const METHOD_PAYPAL_PAYFLOWPRO_CUSTOMERSTORED = 'verisign_customerstored';
const METHOD_PAYPAL_PAYFLOWLINK_CUSTOMERSTORED = 'payflow_link_customerstored';

//...

public function getCountryMethods($countryCode = null)
{
//Countries where this method is available
//...

// PayPal Direct 'Stored card' methods
$countriesByMethod[self::METHOD_PAYPAL_DIRECT_CUSTOMERSTORED] = array(
'US',
'CA',
'GB'
);

// PayPal Payflow-based 'Stored card' methods (Payflow Pro defines the list of supported countries)
$countriesByMethod[self::METHOD_PAYPAL_PAYFLOWADVANCED_CUSTOMERSTORED] =
$countriesByMethod[self::METHOD_PAYPAL_PAYFLOWLINK_CUSTOMERSTORED] =
$countriesByMethod[self::METHOD_PAYPAL_PAYFLOWPRO_CUSTOMERSTORED] = array(
'US',
'CA',
'AU',
'NZ'
);

$countryMethods = parent::getCountryMethods($countryCode);

foreach ($countriesByMethod as $methodCode => $countries) {
//Add this method to the list of available methods in appropriate countries
if (is_null($countryCode)) {
foreach ($countries as $country) {
array_push($countryMethods[$country], $methodCode);
}
} elseif (in_array($countryCode, $countries)) {
array_push($countryMethods, $methodCode);
}
}

return $countryMethods;
}

protected function _getSpecificConfigPath($fieldName)
{
$path = parent::_getSpecificConfigPath($fieldName);

if (is_null($path)) {
switch ($this->_methodCode) {
//...
case self::METHOD_PAYPAL_DIRECT_CUSTOMERSTORED:
$path = $this->_mapStoredFieldset($fieldName);
break;
default:
}
}

return $path;
}

public function isMethodAvailable($methodCode = null)
{
$result = parent::isMethodAvailable($methodCode);

if (!$result) {
return false;
}

//...

switch ($methodCode) {
case self::METHOD_PAYPAL_DIRECT_CUSTOMERSTORED:
if (!$this->isMethodActive(self::METHOD_WPP_DIRECT)) {
$result = false;
}
break;
case self::METHOD_PAYPAL_PAYFLOWADVANCED_CUSTOMERSTORED:
if (!$this->isMethodActive(self::METHOD_PAYFLOWADVANCED)) {
$result = false;
}
break;
case self::METHOD_PAYPAL_PAYFLOWLINK_CUSTOMERSTORED:
if (!$this->isMethodActive(self::METHOD_PAYFLOWLINK)) {
$result = false;
}
break;
case self::METHOD_PAYPAL_PAYFLOWPRO_CUSTOMERSTORED:
if (!$this->isMethodActive(self::METHOD_PAYFLOWPRO)) {
$result = false;
}
break;
}

return $result;
}

//...
}
class Mage_Paypal_Model_Api_Nvp extends Mage_Paypal_Model_Api_Abstract
{
/**
* Paypal methods definition
*/
const DO_DIRECT_PAYMENT = 'DoDirectPayment';
const DO_CAPTURE = 'DoCapture';
const DO_AUTHORIZATION = 'DoAuthorization';
//... (other constants for API method names)

protected $_globalMap = array(
// each call
'VERSION' => 'version',
'USER' => 'api_username',
'PWD' => 'api_password',
'SIGNATURE' => 'api_signature',
//... (other API parameters and the properties they map to on this class
)

//...

protected $_doAuthorizationRequest = array('TRANSACTIONID', 'AMT', 'CURRENCYCODE');
protected $_doAuthorizationResponse = array('TRANSACTIONID', 'AMT');

//...

public function callDoAuthorization()
{
$request = $this->_exportToRequest($this->_doAuthorizationRequest);
$response = $this->call(self::DO_AUTHORIZATION, $request);
$this->_importFromResponse($this->_paymentInformationResponse, $response);
$this->_importFromResponse($this->_doAuthorizationResponse, $response);

return $this;
}
}

 callDoReferenceTransaction

Мы ожидали больше работы для решений на основе PayFlow, но даже здесь все оставалось довольно просто. На самом деле API PayFlow не ожидает явного имени метода среди параметров запроса. class CLS_Paypal_Model_Paypal_Stored_Customerstored_Direct extends CLS_Paypal_Model_Paypal_Direct
{
//...

protected function _placeOrder(Mage_Sales_Model_Order_Payment $payment, $amount)
{
// Get Reference ID
$paymentStoredCardId = $this->getInfoInstance()->getData('stored_card_id');
$referenceId = null;

if ($paymentStoredCardId) {
$storedCardModel = Mage::getModel('cls_paypal/customerstored')->load($paymentStoredCardId);
if ($storedCardModel->getId()) {
$referenceId = $storedCardModel->getData('transaction_id');
}
}

//...

$order = $payment->getOrder();

$api = $this->_pro->getApi()
->setReferenceId($referenceId)
->setPaymentAction($this->getConfigData('payment_action'))
->setIpAddress(Mage::app()->getRequest()->getClientIp(false))

->setAmount($amount)
->setCurrencyCode($order->getBaseCurrencyCode())
->setInvNum($order->getIncrementId())
->setEmail($order->getCustomerEmail());

//...

// call api and import transaction and other payment information
$api->callDoReferenceTransaction();
$this->_importResultToPayment($api, $payment);

//...

return $this;
}

}ORIGID

Получив и установив соответствующий токен, мы занялись бизнесом. Главный класс был не намного дольше, чем этот:

 class CLS_Paypal_Model_Paypal_Stored_Customerstored_Payflow extends CLS_Paypal_Model_Paypal_Stored_Payflow
{

    //...

    protected function _placeOrder(Mage_Sales_Model_Order_Payment $payment, $amount, $transactionType = self::TRXTYPE_AUTH_ONLY)
    {
        // Get Reference ID
        $paymentStoredCardId = $this->_commonMethod->getInfoInstance()->getData('stored_card_id');
        $referenceId = null;

        if ($paymentStoredCardId) {
            $storedCardModel = Mage::getModel('cls_paypal/customerstored')->load($paymentStoredCardId);
            if ($storedCardModel->getId()) {
                $referenceId = $storedCardModel->getData('transaction_id');
            }
        }

        //...

        // Prepare and run 'Reference Transaction' request
        $request = $this->_buildBasicRequest($payment);
        $request->setTrxtype($transactionType);
        $request->setAmt(round($amount, 2));
        $request->setOrigid($referenceId);

        $response = $this->_postRequest($request);
        $this->_processErrors($response);

        //...
        
        return $this;
    }

}

Фактически, у нас было больше работы по сопоставлению API для нашего метода оплаты PayFlow Pro (не описанного в этой статье), чем для любого из наших сохраненных функций кредитной карты.

Дорога впереди

В настоящее время наиболее явным ограничением расширения токенизации кредитных карт PayPal являются его строгие требования совместимости. Я рад сообщить, что мы уже на пути к расширению совместимости с предыдущими версиями Magento в ближайшем будущем. (Следите за обновлениями на нашем блоге , если хотите).

Есть также множество других функций, которые мы хотели бы видеть добавленными в будущем. Например, поддержка постоянных платежных профилей Magento была бы естественной для сохраненных кредитных карт. Мы будем уделять пристальное внимание наиболее востребованным функциям, поскольку планируем будущее расширение.

Разработка нашего первого расширения для Magento Connect стала серьезным опытом для Classy Llama и чрезвычайно позитивным. Мы рады видеть, что функции токенизации PayPal могут предоставить продавцам Magento.