Рассмотрим пример создания модуля для вымышленного типа оплаты CustomPaymentType. Структура папок модуля рекомендуется следующая:
- custompaymenttype
- config
- file.inc.php
- handlers.inc.php
- module.xml
- model
Непосредственно функциональность модуля оплаты реализована в классе PaymentType. Класс PaymentType должен быть унаследован от базового класса Shop::Model::PaymentType::AbstractType.
Класс, описывающий тип оплаты (paymenttype.inc.php)
Рассмотрим методы базового класса Shop::Model::PaymentType::AbstractType, которые необходимо реализовать/унаследовать в нашем классе PaymentType.
Основная информация о типе оплаты
abstract public function getTitle();
abstract public function getDescription();
abstract public function getShortName();
abstract public function canOnlinePay();
public function cashOnDelivery()
{
return !$this->canOnlinePay();
}
public function getTypeUnique(): string
{
return '';
}
Дополнительная форма для способа оплаты
public function getFormObject()
{}
Пример готовой формы:
public function getFormObject()
{
$properties = new \RS\Orm\PropertyIterator([
'api_key' => new \RS\Orm\Type\Varchar([
'description' => t('API ключ'),
]),
...
]);
$form_object = new FormObject($properties);
$form_object->setParentObject($this);
$form_object->setParentParamMethod('Form');
return $form_object;
}
Методы для классов, поддерживающих печатные формы
Например: если ваш тип оплаты выставляет "счёт на оплату", то печатной формой будет являтся "счёт на оплату"
public function getDocsName()
{
return [
'bill' => [
'title' => t('Счет')
],
];
}
public function getDocHtml($dockey = null)
{
$view = new \RS\View\Engine();
if($this->order){
$view->assign('order', $this->order);
return $view->fetch('%shop%/payment/bill.tpl');
}
if($this->transaction){
$view->assign('transaction', $this->transaction);
return $view->fetch('%shop%/payment/bill_transaction.tpl');
}
throw new \Exception(t('Невозможно сформировать документ. Не передан ни объект заказа, ни объект транзакции'));
}
Методы для классов, поддерживающих операции с заказом
Например: тип оплаты "с лицевого счёта" при просмотре заказа в административной панели отображает кнопку "оплатить заказ с лицевого счёта"
public function getAdminHTML(Order $order)
{
return "";
}
public function actionOrderQuery(Order $order)
{}
Методы для классов, поддерживающих оплату онлайн (интернет эквайринг)
Под "онлайн оплатой" подразумевается интеграция с сервисами, способными присылать мгновенные уведомления о статусе платежа. Например: оплта банковской картой.
public function isPostQuery()
{
return false;
}
public function getPayUrl(Transaction $transaction)
{}
public function getTransactionIdFromRequest(HttpRequest $request)
{
return false;
}
public function onResult(Transaction $transaction, HttpRequest $request)
{}
public function wrapOnResultArray($result_array)
{}
Методы для классов, поддерживающих действия с транзакциями
public function getAvailableTransactionActions(Transaction $transaction): array
{
return [];
}
public function executeTransactionAction(Transaction $transaction, string $action): string
{
throw new RSException(t('Данный тип оплаты не поддерживает действий с транзакциями'));
}
- Заметки
- Изменения в транзакцию следует вносить при помощи объекта Shop::Model::ChangeTransaction
Пример:
$change = new \Shop\Model\ChangeTransaction($transaction);
$change->setNewStatus(Transaction::STATUS_SUCCESS);
$change->setChangelog(t('Оплата успешно завершена'));
$change->applyChanges();
Справочники
protected static function handbookNds()
{
static $nds = [
TaxApi::TAX_NDS_NONE => TaxApi::TAX_NDS_NONE,
TaxApi::TAX_NDS_0 => TaxApi::TAX_NDS_0,
TaxApi::TAX_NDS_10 => TaxApi::TAX_NDS_10,
TaxApi::TAX_NDS_20 => TaxApi::TAX_NDS_20,
TaxApi::TAX_NDS_110 => TaxApi::TAX_NDS_110,
TaxApi::TAX_NDS_120 => TaxApi::TAX_NDS_120,
];
return $nds;
}
Регистрация нового типа оплаты в системе
Для регистрации в системе нового типа оплаты, необходимо обработать событие payment.gettypes. Согласно формату моделй в ReadyScript, регистрация обработчика событий производится в файле /config/handlers.inc.php.
Пример регистрации нового модуля-адаптера для телефонии CustomPaymentType.
<?php
namespace CustomPaymentType\Config;
{
function init()
{
$this->bind('payment.gettypes');
}
public static function paymentGetTypes($list)
{
$list[] = new \CustomPaymentType\Model\PaymentType\PaymentType();
return $list;
}
}
Холдирование платежей
Когда платёж холдируется следует перевести транзакцию в статус Transaction::STATUS_HOLDING
Для завершения/отмены холдирования в административной панели, необходимо реализовать следующие методы:
public function getAvailableTransactionActions(Transaction $transaction): array
{
$result = [];
if ($transaction['status'] == Transaction::STATUS_HOLD) {
$result[] = (new TransactionAction($transaction, 'close_hold', t('Завершить оплату')))
->setConfirmText(t('Вы действительно хотите завершить оплату?'))
->setCssClass(' btn-primary');
}
return $result;
}
public function executeTransactionAction(Transaction $transaction, string $action): string
{
...
switch ($action) {
case 'close_hold':
...
Производим необходимые действия
...
return t('Оплата успешно завершена');
default:
throw new RSException(t('Вызванное действие не поддерживается данным типом оплаты'));
}
}
Рекуррентные платежи
Рекуррентные платежи подразумавают получение и сохранение в системе идентификатора способа платежа, после его ввода покупателем на стороне платежной системы. Далее, сохраненный способ платежа можно использовать при последующих оплатах для списания средств без ввода данных по способу платежа (например, карты).
Чтобы способ оплаты поддерживал рекуррентные платежи, он должен имплементировать интерфейс Shop::Model::PaymentType::InterfaceRecurringPayments и использовать трейт Shop::Model::PaymentType::TraitInterfaceRecurringPayments. Трейт создан для упрощения разработки, в нем уже реализованы некоторые методы интерфейса, но при необходимости вы сможете их перегрузить в вашем классе платежной системы.
Чтобы в административной панели у способа оплаты появились соответствующие опции, необходимо в вашем расчетном классе оплаты в методе getFormObject предусмотреть добавление настроек, связанных с рекуррентными платежами.
Пример кода:
<?php
use RS\Orm\Type;
class YandexKassaApi extends AbstractType implements InterfaceRecurringPayments
{
public function getFormObject()
{
$properties = [
->setDescription(t(''))
->setVisible(true)
->setTemplate('%shop%/form/payment/yandexkassaapi/help.tpl'),
->setDescription(t('Идентификатор магазина (shopId)'))
->setHint(t('Можно узнать в личном кабинете ЮКассы'))
->setChecker(Type\Checker::CHECK_EMPTY, t('Не указан "Идентификатор магазина (shopId)"')),
'key_secret' => (new Type\Varchar())
->setDescription(t('Секретный ключ'))
->setHint(t('Можно узнать в личном кабинете ЮКассы'))
->setChecker(Type\Checker::CHECK_EMPTY, t('Не указан "Секретный ключ"')),
'is_holding' => (new Type\Integer())
->setDescription(t('Холдирование платежей'))
->setMaxLength(1)
->setDefault(0)
->setCheckboxView(1, 0),
->setDescription(t('Вести лог запросов?'))
->setMaxLength(1)
->setDefault(0)
->setCheckboxView(1, 0),
];
$properties += $this->getFormCommonProperties();
$form_object = new FormObject(new PropertyIterator($properties));
$form_object->setParentObject($this);
$form_object->setParentParamMethod('Form');
return $form_object;
}
}
В этом случае у вашего расчетного класса автоматически появятся следующие опции:
- Режим работы "рекуррентных платежей": Рекуррентные платежи не доступны, Сохранять способ платежа, Только привязывать способ платежа
- Не отправлять чек при привязке способа платежа
Обычно, платежные системы возвращают идентификатор способа платежа в уведомлении об успешной оплате. Соответственно необходимо принимать его там и сохранять в БД, с помощью объекта класса Shop::Model::Orm::SavedPaymentMethod. Ниже предлагаем схематично посмотреть, как мог бы выглядеть код, который выпоняет сохранение способа платежа. Различные платежные системы могут совершенно по-разному возвращать данные о способе платежа, поэтому мы показываем на примере ЮКассы.
<?php
class YooKassaApi extends AbstractType implements InterfaceRecurringPayments
{
const TRANSACTION_EXTRA_KEY_PAYMENT_ID = 'payment_id';
public function onResult(Transaction $transaction, HttpRequest $request)
{
$payment_id = $transaction->getExtra(self::TRANSACTION_EXTRA_KEY_PAYMENT_ID);
if ($payment_id) {
$response = $this->apiRequest("payments/$payment_id", 'GET');
if (!empty($response['payment_method']['saved'])) {
$payment_method_data = $response['payment_method'];
$saved_method = SavedPaymentMethod::loadByWhere(['external_id' => $payment_method_data['id']]);
if (empty($saved_method['id'])) {
$payment_method = new SavedPaymentMethod();
$payment_method['external_id'] = $payment_method_data['id'];
$payment_method['type'] = SavedPaymentMethod::TYPE_CARD;
$payment_method['subtype'] = $payment_method_data['card']['card_type'];
$payment_method['title'] = "*{$payment_method_data['card']['last4']}";
$payment_method['user_id'] = $transaction->getUser()['id'];
$payment_method['payment_type'] = $this->getShortName();
$payment_method['payment_type_unique'] = $this->getTypeUnique();
$payment_method['data'] = $payment_method_data['card'];
if ($payment_method->insert()) {
$transaction->setExtra(InterfaceRecurringPayments::TRANSACTION_EXTRA_KEY_SAVED_METHOD, $payment_method['id']);
$transaction->update();
return true;
}
}
}
} else {
throw (new ResultException(t('Транзакция не содержит идентификатор платежа')))
->setUpdateTransaction(false);
}
}
}
Рассмотрим методы интерфейса Shop::Model::PaymentType::InterfaceRecurringPayments, базовая реализация которых отсутствует в трейте, а соответственно их необходимо реализовать:
public function recurringPayOrder(Order $order, SavedPaymentMethod $saved_payment_method): void;
public function recurringPayBalanceFounds(User $user, float $cost, SavedPaymentMethod $saved_payment_method): void;
public function refundBindingTransaction(Transaction $transaction): void;
public function deleteSavedPaymentMethod(SavedPaymentMethod $saved_payment_method): void;
Готовый пример реализации поддержки рекуррентных платежей можно найти в классе Shop::Model::PaymentType::YandexKassaApi.