Symfony2: интеграция с платежными системами с помощью JMSPaymentCoreBundle
10.03.2013Вступление
JMSPaymentCoreBundle предоставляет базу для различных бэкендов платежных систем. Пакет представляет собой набор абстракций, унифицированный API для финансовых транзакций.
Краткий список возможностей:
- Простой, унифицированный API
- Возможность сохранения сущностей финансовых операций
- Управление транзакциями
- Шифрование данных
Лицензия
Документация - Attribution-NonCommercial-NoDerivs 3.0 Unported
license.
Установка
Для установки JMSPaymentCoreBundle можно воспользоваться Composer. Добавьте следующую строку в файл composer.json:
// composer.json
{
// ...
require: {
// ...
"jms/payment-core-bundle": "master-dev"
}
}
$ php composer.phar update
Этой командой Composer загрузит и установит все необходимые файлы. Далее необходимо обновить
AppKernel.php добавив следующие строки:
<?php
// in AppKernel::registerBundles()
$bundles = array(
// ...
new JMSPaymentCoreBundleJMSPaymentCoreBundle(),
// ...
);
Первоначальная настройка
Настройка пакета JMSPaymentCoreBundle заключается в выборе "секретной" строки с помобщью которой будут шифроваться финансовый данные.
Следующие строки нужно добавить в configuration.yml проекта:
jms_payment_core:
secret: someS3cretP4ssw0rd
Настройка бэкендов
Некоторые бэкенды платежных систем предоставляют свои пакеты, которые также требуют некоторые настройки.
Модель
Перед тем как принимать/обрабатывать платежи, давайте кратко ознакомимся с моделью платежей.
PaymentInstruction
PaymentInstruction это первый объект который вам нужно создать. Он содержит такую иниформацию как общее количество, способ оплат, валюта, и другие данные, которые необходимы для определенного типа платежа, например информацию кредитной карты.
Платеж (Patment)
Каждый объект PaymentInstruction может быть разделен на несколько платежей.
Payment всегда содержит общее количество и текущее состояние объекта, такие как initiated, approved, deposited, итд.
Это позволяет, например, запросить часть от общей суммы перед предоставлением "товара", и остальное после этого.
Финансовая операция (FinancialTransaction)
Каждый платеж (Payment) может иметь несколько транзакций. Каждая транзакция (FinancialTransaction)представляет специфическое взаимодействие с бэкендом конкретной платежной системы. Например, в случае оплаты кредитной картой, это может быть авторизация.
Ниже приведены состояния объекта FinancialTransaction:
Использование
<?php
use DoctrineORMMapping as ORM;
use JMSPaymentCoreBundleEntityPaymentInstruction;
class Order
{
/** @ORMOneToOne(targetEntity="JMSPaymentCore:PaymentInstruction") */
private $paymentInstruction;
/** @ORMColumn(type="string", unique = true) */
private $orderNumber;
/** @ORMColumn(type="decimal", precision = 2) */
private $amount;
// ...
public function __construct($amount, $orderNumber)
{
$this->amount = $amount;
$this->orderNumber = $orderNumber;
}
public function getOrderNumber()
{
return $this->orderNumber;
}
public function getAmount()
{
return $this->amount;
}
public function getPaymentInstruction()
{
return $this->paymentInstruction;
}
public function setPaymentInstruction(PaymentInstruction $instruction)
{
$this->paymentInstruction = $instruction;
}
// ...
}
Выбор способа оплаты
Обычно, вы желаете предоставить потенциальному покупателю выбор способа оплаты. Для этих целей JMSPaymentCoreBundle поставляеться с специальной формой jms_choose_payment_method.
Следующий пример использует JMSDiExtraBundle, и SensioFrameworkExtraBundle но они не обязательны.
Заметка: в примере не учтены вопросы безопасности приложения. Для полноценной реализации следует учитывать права доступа на действия (detailsAction и другие).
<?php
use JMSDiExtraBundleAnnotation as DI;
use JMSPaymentCoreBundleEntityPayment;
use JMSPaymentCoreBundlePluginControllerResult;
use JMSPaymentCoreBundlePluginExceptionActionRequiredException;
use JMSPaymentCoreBundlePluginExceptionActionVisitUrl;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SensioBundleFrameworkExtraBundleConfigurationTemplate;
use SymfonyComponentHttpFoundationRedirectResponse;
/**
* @Route("/payments")
*/
class PaymentController
{
/** @DIInject */
private $request;
/** @DIInject */
private $router;
/** @DIInject("doctrine.orm.entity_manager") */
private $em;
/** @DIInject("payment.plugin_controller") */
private $ppc;
/**
* @Route("/{orderNumber}/details", name = "payment_details")
* @Template
*/
public function detailsAction(Order $order)
{
$form = $this->getFormFactory()->create('jms_choose_payment_method', null, array(
'amount' => $order->getAmount(),
'currency' => 'EUR',
'default_method' => 'payment_paypal', // Optional
'predefined_data' => array(
'paypal_express_checkout' => array(
'return_url' => $this->router->generate('payment_complete', array(
'orderNumber' => $order->getOrderNumber(),
), true),
'cancel_url' => $this->router->generate('payment_cancel', array(
'orderNumber' => $order->getOrderNumber(),
), true)
),
),
));
if ('POST' === $this->request->getMethod()) {
$form->bindRequest($this->request);
if ($form->isValid()) {
$this->ppc->createPaymentInstruction($instruction = $form->getData());
$order->setPaymentInstruction($instruction);
$this->em->persist($order);
$this->em->flush($order);
return new RedirectResponse($this->router->generate('payment_complete', array(
'orderNumber' => $order->getOrderNumber(),
)));
}
}
return array(
'form' => $form->createView()
);
}
// ...
/** @DILookupMethod("form.factory") */
protected function getFormFactory() { }
}
Тип форм jms_choose_payment_method
автоматически отбражает форму с доступными способами оплаты. После связывания, форма проверит данные для выбраной платежной системы, и в случае успеха возвратит объект PaymentInstruction.
Перевод денег
В предыдущей главе мы получили объект PaymentInstruction. Давайте посмотрим как мы можем вносить деньги на счет. Если посмотреть на detailsAction, указанный в предыдущем примере, мы перенаправляем пользователя на маршрут payment_complete для которого мы сейчас создадим соответствующее действие в контроллере:
<?php
use JMSDiExtraBundleAnnotation as DI;
use JMSPaymentCoreBundleEntityPayment;
use JMSPaymentCoreBundlePluginControllerResult;
use JMSPaymentCoreBundlePluginExceptionActionRequiredException;
use JMSPaymentCoreBundlePluginExceptionActionVisitUrl;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SensioBundleFrameworkExtraBundleConfigurationTemplate;
use SymfonyComponentHttpFoundationRedirectResponse;
/**
* @Route("/payments")
*/
class PaymentController
{
/** @DIInject */
private $request;
/** @DIInject */
private $router;
/** @DIInject("doctrine.orm.entity_manager") */
private $em;
/** @DIInject("payment.plugin_controller") */
private $ppc;
// ... see previous section
/**
* @Route("/{orderNumber}/complete", name = "payment_complete")
*/
public function completeAction(Order $order)
{
$instruction = $order->getPaymentInstruction();
if (null === $pendingTransaction = $instruction->getPendingTransaction()) {
$payment = $this->ppc->createPayment($instruction->getId(), $instruction->getAmount() - $instruction->getDepositedAmount());
} else {
$payment = $pendingTransaction->getPayment();
}
$result = $this->ppc->approveAndDeposit($payment->getId(), $payment->getTargetAmount());
if (Result::STATUS_PENDING === $result->getStatus()) {
$ex = $result->getPluginException();
if ($ex instanceof ActionRequiredException) {
$action = $ex->getAction();
if ($action instanceof VisitUrl) {
return new RedirectResponse($action->getUrl());
}
throw $ex;
}
} else if (Result::STATUS_SUCCESS !== $result->getStatus()) {
throw new RuntimeException('Transaction was not successful: '.$result->getReasonCode());
}
// payment was successful, do something interesting with the order
}
}
События
Заметка: Список возможных событий можно найти в классе JMSPaymentCoreBundlePluginControllerEventEvents.
События при изменении статуса платежа
Имя: payment.state_change
Класс события: JMSPaymentCoreBundlePluginControllerEventPaymentStateChangeEvent
Это событие возникает сразу после изменения статуса платежа. Все связанные сущности будут обновлены. Вы можете получить доступ к объектам Payment, PaymentInstruction, получить старое и новое состояние платежа.
Источник:
Свободный перевод документации: http://jmsyst.com/bundles/JMSPaymentCoreBundle
Комментарии