Как пользоваться PHP библиотекой Omnipay от The League

Omnipay — простая в использовании, согласованная библиотека обработки платежей для PHP. Она была разработан на основе идей Active Merchant, плюс опыт внедрения десятков шлюзов для CI Merchant. Omnipay имеет ясный и последовательный API, полностью покрыт модульными тестами и даже поставляется с примером приложения.

Зачем использовать Omnipay?

Итак, зачем использовать Omnipay вместо библиотеки или примеров кода предоставляемыми шлюзом?

  • вам нужно изучить всего один API и использовать его в нескольких проектах, используя разные платёжные шлюзы
  • если вам нужно изменить платежные шлюзы, вам не нужно переписывать код
  • большинство официальных PHP-библиотек для шлюза это полный пиздец
  • большинство платёжных шлюзов имеют плохую документацию
  • вы разрабатываете корзину покупок и должны поддерживать много шлюзов

Обновление до 3.0

Если вы обновляете версию Omnipay до версии 3.0, обратите внимание, что в отношении HTTP-клиента есть некоторые изменения. Также были внесены некоторые изменения в порядок создания экземпляров шлюза. Подробные сведения см. В полной версии.

Поддержка

Если у вас есть проблемы с Omnipay, мы предлагаем разместить вопрос на Stack Overflow. Не забудьте добавить тег omnipay, чтобы его можно было легко найти.

Если вы хотите быть в курсе последних новостей, обсуждать идеи для проекта или задавать более подробные вопросы, есть также список рассылки, на который вы можете подписаться.

Если вы считаете, что обнаружили ошибку, сообщите об этом с помощью средства отслеживания проблем GitHub для соответствующего пакета или, ещё лучше, сделайте форк библиотеки, внесите правки и отправьте pull request.

Простой пример

Вот простой пример использования Omnipay. Как вы можете видеть, Omnipay имеет последовательный, продуманный API. Насколько это возможно, чтобы абстрагировать все различия между платёжными шлюзами.

use Omnipay\Omnipay;
// Настройка платёжного шлюза
$gateway = Omnipay::create('Stripe');
$gateway->setApiKey('abc123');
// Пример заполнения формы
$formData = [
    'number' => '4242424242424242',
    'expiryMonth' => '6',
    'expiryYear' => '2016',
    'cvv' => '123'
];
// Отправить запрос на покупку
$response = $gateway->purchase(
    [
        'amount' => '10.00',
        'currency' => 'USD',
        'card' => $formData
    ]
)->send();
// Обработка ответа
if ($response->isSuccessful()) {
    // Оплата прошла успешно
    print_r($response);
} elseif ($response->isRedirect()) {
    // Перенаправление на внешний шлюз
    $response->redirect();
} else {
    // Платеж не прошёл
    echo $response->getMessage();
}

Пример применения

Пример приложения также предоставляется в репозитории omnipay/example.

# Склонируйте проект
git clone https://github.com/thephpleague/omnipay-example.git omnipay-example
# Перейдите в директорию с проектом
cd omnipay-example
# Установите зависимости
composer install
# Запустите встроенный в PHP веб-сервер
php -S localhost:8000

Установка

Omnipay устанавливается через Composer. Для большинства целей вам понадобится пакет league/omnipay и необходимые шлюзы:

composer require league/omnipay:^3 omnipay/paypal

Если вы хотите использовать свой собственный HTTP-клиент вместо Guzzle (который является значением по умолчанию для league/omnipay), вам может потребоваться omnipay/common и любая реализация php-http/client-implementation.

composer require omnipay/common:^3 omnipay/paypal php-http/buzz-adapter

Создайте собственный драйвер шлюза

Omnipay — это набор пакетов, все из которых зависят от пакета omnipay/common, чтобы обеспечить согласованный интерфейс. Никаких зависимостей от официальных PHP-пакетов шлюзов, в Omnipay предпочтительно напрямую работать с HTTP API. Под капотом используется популярная и мощная PHP библиотека для создания HTTP-запросов. Адаптер Guzzle требуется по умолчанию при использовании league/omnipay.

Новые шлюзы могут быть созданы путём клонирования существующего пакета. При выборе имени для пакета, не используйте префикс omnipay, так как это означает, что он официально поддерживается. Вы должны использовать своё собственное имя пользователя в качестве префикса и добавить всплывающее имя к имени пакета, чтобы было ясно, что ваш пакет работает с Omnipay. Например, если ваше имя пользователя на GitHub santa, и вы использовали библиотеку платежей giftpay, хорошим именем для вашего пакета будет santa/omnipay-giftpay.

Терминология Omnipay

Merchant Site — веб-сайт или приложение, которое инициирует платёж. Обычно это будет интернет-магазин или какая-либо другая онлайн-система, которая должна принимать платежи от клиентов.

Merchant — владелец или оператор Merchant Site.

Payment Gateway — система обработки удалённых платежей, которая обрабатывает обмен и передачу средств между Merchant Site, банком клиента и банком Merchant. Обычно это крупные компании, которые ежедневно обрабатывают тысячи платежей.

Driver — код, написанный для расширения функциональности ядра Omnipay, чтобы он мог связываться со специальным шлюзом платежей. Существует несколько «официальных» драйверов Omnipay и многие другие, написанные сообществом. Если в Payment Gateway, который вы хотите использовать, в настоящее время нет Omnipay-драйвера, вы можете создать свой собственный и поделиться им с сообществом.

Transaction — единственная попытка (успешная или что-то другое) произвести платеж.

Соглашения в Omnipay

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

transactionId и transactionReference

transactionId — идентификатор на транзакцию в Merchant системе — обычно это идентификатор платежа в базе данных Merchant Site. transactionReference — это идентификатор в Payment Gateway на транзакцию. Обычно платёжные шлюзы генерируют уникальный идентификатор для каждой попытки платежа, которую делает клиент. Общепринятой практикой является сохранение этого значения в базе данных Merchant Site, чтобы транзакции можно было соотнести с платежами в Payment Gateway. Некоторые драйверы Omnipay также полагаются на то, что это значение доступно для обработки возвратов или повторных платежей.

returnUrl и notifyUrl

returnUrl используется драйверами, когда им нужно сообщить в Payment Gateway, куда необходимо перенаправить клиента после выполнения транзакции. Как правило, это используется для перенаправления пользователя. notifyUrl используется для того, чтобы сообщить в Payment Gateway, куда отправлять уведомление от сервера к серверу, сообщая Merchant Site об итогах транзакции. notifyUrl обычно является скриптом на Merchant Site, который выполняет обновление базы данных был ли платеж успешным или нет.

Настройка шлюзов

Все платёжные шлюзы должны реализовывать GatewayInterface и обычно расширяют AbstractGateway для основных функций.

Инициализировать шлюз

Шлюзы создаются и инициализируются так:

use Omnipay\Omnipay;
$gateway = Omnipay::create('PayPal_Express');
$gateway->setUsername('adrian');
$gateway->setPassword('12345');

В качестве альтернативы сразу несколько параметров могут быть инициализированы непосредственно из данных:

...
$gateway->initialize([
    'username' => 'adrian',
    'password' => '12345',
]);

Настройка параметров таким образом начнётся с использования параметров по умолчанию, а затем слияния ваших поставленных параметров сверху.

Настройки шлюза

Большинство настроек зависят от шлюза. Если вам нужно запросить шлюз, чтобы получить список доступных параметров, вы можете вызвать getDefaultParameters():

$settings = $gateway->getDefaultParameters();
// значения по умолчанию в виде массива:
array(
    'username' => '', // строковое значение
    'testMode' => false, // булево значение
    'landingPage' => ['billing', 'login'], // перечисление возможных значений, первое значение будет значением по умолчанию
);

Типы шлюзов

Как правило, большинство платёжных шлюзов можно классифицировать как один из двух типов:

Off-site шлюзы, такие как PayPal Express, где клиент перенаправляется на сторонний сайт для ввода платёжных реквизитов.

On-site шлюзы (merchant-hosted), такие как PayPal Pro, где клиент вводит данные своей кредитной карты на вашем сайте

Тем не менее, есть некоторые шлюзы, такие как Sage Pay Direct, где вы берёте данные о кредитной карте на сайте, а затем в случае необходимости перенаправляете, если карта клиента поддерживает 3D Secure аутентификацию. Поэтому нет разницы между двумя типами шлюзов (кроме тех, которые они поддерживают).

Пластиковые карты

Ввод формы пользователя относится к объекту CreditCard. Это обеспечивает безопасный способ принять пользовательский ввод.

Объект CreditCard имеет следующие поля:

[
    'firstName',
    'lastName',
    'number',
    'expiryMonth',
    'expiryYear',
    'startMonth',
    'startYear',
    'cvv',
    'issueNumber',
    'type',
    'billingAddress1',
    'billingAddress2',
    'billingCity',
    'billingPostcode',
    'billingState',
    'billingCountry',
    'billingPhone',
    'shippingAddress1',
    'shippingAddress2',
    'shippingCity',
    'shippingPostcode',
    'shippingState',
    'shippingCountry',
    'shippingPhone',
    'company',
    'email'
]

Шлюзы за пределами площадки используют объект CreditCard, потому что вам часто приходится передавать данные о выставлении счетов или доставки клиентам через шлюз.

Объект CreditCard можно инициализировать с помощью ненадёжного ввода пользователем через конструктор. Любые поля, переданные в конструктор, которые не распознаются, будут игнорироваться.

$formInputData = array(
    'firstName' => 'Bobby',
    'lastName' => 'Tables',
    'number' => '4111111111111111',
);
$card = new CreditCard($formInputData);

Вы также можете просто передать массив данных формы непосредственно на шлюз, и для вас будет создан объект CreditCard.

Получить доступ к значениям CreditCard можно с помощью геттеров и сеттеров:

$number = $card->getNumber();
$card->setFirstName('Adrian');

Если вы предоставите недействительные данные кредитной карты (отсутствуют обязательные поля или номер, который не прошёл Luhn check), будет выброшено исключение InvalidCreditCardException. Вы должны самостоятельно проверить данные карты, прежде чем отправлять данные в платёжный шлюз, чтобы избежать лишних API-вызовов.

Для on-site платёжных шлюзов обычно требуются следующие поля:

  • firstName
  • lastName
  • number
  • expiryMonth
  • expiryYear
  • cvv

Вы также можете проверить номер карты с помощью алгоритма Луна:

Helper::validateLuhn($number)

Авторизация

Основные методы, реализуемые шлюзами:

authorize($options) — авторизовать сумму на карточке клиента

completeAuthorize($options) — обработка callback из off-site шлюза после авторизации. В on-site шлюзах не требуется реализовать метод completeAuthorize и будет исключение BadMethodCallException.

capture($options) — захват суммы, которую вы ранее разрешили.

Все методы шлюза, кроме acceptNotification, принимают массив $options в качестве аргумента. Метод acceptNotification не принимает никаких параметров и неявно обращается к переменным URL HTTP или данным POST. Каждый шлюз отличается тем, какие параметры являются обязательными, и шлюз выдаст InvalidRequestException, если пропущены какие-либо обязательные параметры. Все шлюзы принимают подмножество этих параметров:

  • card
  • token
  • amount
  • currency
  • description
  • transactionId
  • clientIp
  • returnUrl
  • cancelUrl

Передайте параметры до метода следующим образом:

$card = new CreditCard($formData);
$request = $gateway->authorize([
    'amount' => '10.00',
    'card' => $card,
    'returnUrl' => 'https://www.example.com/return',
]);

При вызове методов completeAuthorize или completePurchase необходимо указать те же аргументы, что и при первоначальном вызове authorize или purchase (некоторые шлюзы должны будут подтвердить, например, что фактически оплаченная сумма равна запрашиваемой сумме). Единственным параметром, который вы можете пропустить, является card.

Чтобы обобщить различные параметры, которые вам доступны:

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

Параметры метода используются для любых опций, связанных с платежами, которые не заданы клиентом. Например, сумма платежа, валюта, transactionId и returnUrl.

Параметры CreditCard — это данные, которые предоставляет пользователь. Например, вы хотите, чтобы пользователь указывал свои firstName и billingCountry, но вы не хотите, чтобы пользователь указывал валюту платежа или returnUrl.

Параметры можно задать двумя способами:

  • Как массив при создании объекта запроса, как показано выше.
  • Использование методов setter на объекте запроса после аналогичного соглашения об именах.

Например, карта и сумма могут быть установлены после того, как запрос авторизации был создан следующим образом:

$request = $gateway->authorize(['returnUrl' => 'https://www.example.com/return']);
$request->setCard($card);
$request->setAmount('10.00');

Оплата

Основными методами, реализуемыми шлюзами, являются:

purchase($options) — авторизация и немедленное получение суммы с карточки клиента

completePurchase($options) — обработка callback из off-site шлюзов после покупки

В on-site шлюзах не нужно реализовывать метод completePurchase, он будет выбрасывать исключение BadMethodCallException при вызове.

При вызове методов completeAuthorize или completePurchase необходимо указать те же аргументы, что и при первоначальном вызове authorize или purchase (некоторые шлюзы должны будут подтвердить, например, что фактически оплаченная сумма равна запрашиваемой сумме). Единственным параметром, который вы можете пропустить, является card.

Возвраты

Основным методом, реализованным шлюзами, является:

refund($options) — возврат уже обработанной транзакции.

Если какой-либо шлюз не поддерживает определённые функции (например, возврат средств), он выкинет BadMethodCallException.

Аннулирование

Основным методом, реализованным шлюзами, является:

void($options) — обычно может быть вызван в течении 24 часов после отправки транзакции

Ответы

Ответ на платёж

Ответ на платёж должен реализовывать ResponseInterface. Существует два основных типа ответа:

  • Оплата прошла успешно (стандартный ответ)
  • Веб-сайт требует перенаправления на внеплатежную форму (перенаправление ответа)

Успешный ответ

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

$response = $gateway->purchase(['amount' => '10.00', 'card' => $card])->send();
// ответ успешный?
$response->isSuccessful();
// является ли ответ перенаправлением?
$response->isRedirect();
// идентификатор, сгенерированная платёжным шлюзом
$response->getTransactionReference();
// сообщение, генерируемое платёжным шлюзом
$response->getMessage();

Кроме того, большинство шлюзов переопределяют объект ответа и предоставляют доступ к любым дополнительным полям, возвращаемым шлюзом.

Ответ с перенаправлением

Ответ на перенаправление далее разбивается на то, должен ли браузер клиента перенаправляться с использованием GET (объект RedirectResponse) или POST (объект FormRedirectResponse). Их можно объединить в один класс ответа с помощью метода getRedirectMethod().

После обработки платежа нужно проверить, требуется ли перенаправление, и если да, перенаправлять соответственно:

$response = $gateway->purchase(['amount' => '10.00', 'card' => $card])->send();
if ($response->isSuccessful()) {
    // оплата завершена
} elseif ($response->isRedirect()) {
    // автоматически перенаправить клиента
    $response->redirect();
} else {
    // не успешный платёж
}

Клиент не перенаправляется автоматически, потому что часто корзина или разработчик захотят настроить метод перенаправления (или если обработка платежей происходит внутри вызова AJAX, они захотят вернуть JS в браузер).

Чтобы отобразить вашу собственную страницу переадресации, просто вызовите getRedirectUrl() в ответ, а затем отобразите её соответственно:

$url = $response->getRedirectUrl();
// для перенаправления формы вы также можете вызвать следующий метод:
// ассоциативный массив полей, который должен быть отправлен в redirectUrl
$data = $response->getRedirectData();

Обработка ошибок

Вы можете проверить успешный ответ, вызвав isSuccessful() в объекте ответа. Если произошла ошибка, связанная со шлюзом, или ваш запрос был явно недействителен, будет выброшено исключение. В общем случае, если шлюз не генерирует исключение, но возвращает неудачный ответ, то нужно отобразить клиенту сообщение от шлюза. Если выброшено исключение, это либо ошибка в коде (отсутствующие обязательные поля), либо ошибка связи со шлюзом.

Вы можете обрабатывать оба сценария, обернув весь запрос в блок try-catch:

try {
    $response = $gateway->purchase(['amount' => '10.00', 'card' => $card])->send();
    if ($response->isSuccessful()) {
        // пометить заказ как завершённый
    } elseif ($response->isRedirect()) {
        $response->redirect();
    } else {
        // отобразить ошибку для клиента
        exit($response->getMessage());
    }
} catch(\Exception $e) {
    // внутренняя ошибка, зафиксировать исключение и отобразить сообщение клиенту
    exit('Sorry, there was an error processing your payment. Please try again later.');
}

Token billing

Token billing позволяет хранить кредитную карту на стороне шлюза и взимать плату позже. Token billing не поддерживается всеми шлюзами. Для поддерживаемых шлюзов доступны следующие методы:

createCard($options) — возвращает объект ответа, который включает cardReference, который может использоваться для будущих транзакций

updateCard($options) — обновляет сохранённую карту, не все шлюзы поддерживают этот метод

deleteCard($options) — удалить сохранённую карту, не все шлюзы поддерживают этот метод

Если у вас есть cardReference, вы можете использовать его вместо параметра карты при создании платежа:

$gateway->purchase(['amount' => '10.00', 'cardReference' => 'abc']);

Повторяющиеся платежи

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

Тестовый режим и режим разработчика

Большинство шлюзов позволяют настроить учётную запись безопасной среды или разработчика, использующую другой url-адрес и учетные данные. Некоторые также позволяют выполнять тестовые транзакции на реальном сайте, что не приводит к реальной транзакции.

Шлюзы, реализующие только учётную запись разработчика (большинство из них), называют её тестовым режимом. Authorize.net тем не менее, реализует оба и относится к этому режиму как режим разработчика.

При реализации с несколькими шлюзами следует использовать конструкцию следующего вида:

if ($is_developer_mode) {
    if (method_exists($gateway, 'setDeveloperMode')) {
        $gateway->setDeveloperMode(TRUE);
    } else {
        $gateway->setTestMode(TRUE);
    }
}

Входящие уведомления

acceptNotification() — преобразование входящего запроса из внешнего шлюза в универсальный объект уведомления для дальнейшей обработки.

Некоторые шлюзы (например, Cybersource, GoPay) предлагают HTTP-уведомления для информирования продавца о завершении (или, вообще, статусе) платежа. Чтобы облегчить обработку таких уведомлений, метод acceptNotification() извлекает ссылку на транзакцию и состояние платежа из HTTP-запроса и возвращает общий интерфейс NotificationInterface.

$notification = $gateway->acceptNotification();
// ссылка, предоставляемая шлюзом для представления этой транзакции
$notification->getTransactionReference();
// текущий статус транзакции, один из NotificationInterface::STATUS_*
$notification->getTransactionStatus();
// дополнительное сообщение, если таковое имеется, предоставленное шлюзом
$notification->getMessage();
// обновить статус соответствующей транзакции в базе данных

Примечание: некоторые более ранние шлюзы используют completeAuthorize и сообщения completePurchase для обработки входящих уведомлений. Они конвертируются и complete* сообщения считаются устаревшими. Они не будут удалены в OmniPay 2.x, но желательно переключиться на сообщение acceptNotification, когда это удобно. Например в Sage Pay Server completeAuthorize, сейчас обрабатывается через acceptNotification.