netfantom / yii2-robokassa
PHP 8.1 的 Robokassa Yii2 框架扩展
dev-main
2023-07-27 09:22 UTC
Requires
- php: >=8.1
- ext-mbstring: *
- netfantom/robokassa-api: @dev
- yiisoft/yii2: ~2.0.45
Requires (Dev)
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^10.2
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^3.7
- symfony/http-client: ^6.3
- vimeo/psalm: ^5.12
This package is auto-updated.
Last update: 2024-09-27 11:41:53 UTC
README
该组件旨在与Robokassa一起使用
- 是
netFantom/robokassa-api
的包装器,并完全实现其接口netFantom\robokassa-api\RobokassaApiInterface
(见 https://github.com/igor-netFantom/robokassa-api ) - 以
Yii 2 framework
组件的形式实现,并添加了方便的方法和小部件
需要 PHP 8.1+
使用 Composer 安装
composer require igor-netfantom/yii2-robokassa:@dev
连接组件
对象
[ // ... 'components' => [ 'robokassa' => [ 'class' => 'netFantom\Yii2Robokassa\Yii2Robokassa', 'robokassaApi' => new \netFantom\RobokassaApi\RobokassaApi( merchantLogin: 'robo-demo', password1: 'password_1', password2: 'password_2', isTest: !YII_ENV_PROD, psr18Client: new \Http\Discovery\Psr18Client(), // необязательно ), ], // ... ], ];
...或数组
[ // ... 'components' => [ 'robokassa' => [ 'class' => 'netFantom\Yii2Robokassa\Yii2Robokassa', 'merchantLogin' => 'robo-demo', 'password1' => 'password1', 'password2' => 'password2', 'isTest' => !YII_ENV_PROD, 'psr18Client' =>'Http\Discovery\Psr18Client', // необязательно ], // ... ], ];
方法
重定向到付款页面
/** @var \netFantom\Yii2Robokassa\Yii2Robokassa $robokassa */ $robokassa = Yii::$app->get('robokassa'); /** * @var \models\Invoice $invoice смотрите пример модели счета в разделе ниже * @see https://github.com/igor-netFantom/yii2-robokassa#пример-модели-счета */ /** @var bool $setReturnUrl по умолчанию TRUE {@see \yii\web\User::setReturnUrl()} */ $response = $robokassa->redirectToPaymentUrl($invoice->getInvoiceOptions(), $setReturnUrl); /** @var \yii\web\Response $response */ return $response;
将付款参数转换为表单字段
将付款参数 InvoiceOptions
转换为隐藏表单字段 Html::hiddenInput()
,以便通过 POST 请求将用户发送到付款
/** @var \netFantom\Yii2Robokassa\Yii2Robokassa $robokassa */ $robokassa = Yii::$app->get('robokassa'); /** * @var \models\Invoice $invoice смотрите пример модели счета в разделе ниже * @see https://github.com/igor-netFantom/yii2-robokassa#пример-модели-счета */ echo $robokassa->getHiddenInputsHtml($invoice->getInvoiceOptions());
从 Yii HTTP 请求获取 Robokassa 付款结果
use netFantom\Yii2Robokassa\Yii2Robokassa; use netFantom\RobokassaApi\Results\InvoicePayResult; /** @var \yii\web\Request $request */ $request = Yii::$app->request /** @var InvoicePayResult $invoicePayResult */ $invoicePayResult = Yii2Robokassa::getInvoicePayResultFromYiiWebRequest($request);
模块 netFantom/robokassa-api
的方法
该组件是 netFantom/robokassa-api
的包装器,并完全实现其接口 netFantom\robokassa-api\RobokassaApiInterface
(见 https://github.com/igor-netFantom/robokassa-api )
组件使用示例
账单模型示例
class InvoiceStatus { public const STATUS_CREATED = 1; public const STATUS_PAYED = 2; public const STATUS_FAILED = 3; }
class PaymentSystem { public const SYSTEM_ROBOKASSA = 1; }
use models\InvoiceStatus; use DateInterval; use DateTimeImmutable; use netFantom\RobokassaApi\Options\InvoiceOptions; use netFantom\RobokassaApi\Params\Option\{Culture, OutSumCurrency, Receipt}; use netFantom\RobokassaApi\Params\Item\{PaymentMethod, PaymentObject}; use netFantom\RobokassaApi\Params\Receipt\{Item, Sno, Tax}; use yii\db\ActiveRecord; class Invoice extends ActiveRecord { public int $id; public string $sum; public int $status_id = InvoiceStatus::STATUS_CREATED; public int $payment_system_id; public int $user_id; public function getInvoiceOptions(): InvoiceOptions { return new InvoiceOptions( outSum: $this->sum, invId: $this->id, description: 'Description', receipt: new Receipt( items: [ new Item( name: "Название товара 1", quantity: 1, sum: 100, tax: Tax::vat10, payment_method: PaymentMethod::full_payment, payment_object: PaymentObject::commodity, ), new Item( name: "Название товара 2", quantity: 3, sum: 450, tax: Tax::vat10, payment_method: PaymentMethod::full_payment, payment_object: PaymentObject::service, cost: 150, nomenclature_code: '04620034587217', ), ], sno: Sno::osn ), expirationDate: (new DateTimeImmutable())->add(new DateInterval('PT48H')), email: 'user@email.com', outSumCurrency: OutSumCurrency::USD, userIP: '127.0.0.1', incCurrLabel: null, userParameters: [ 'user_id' => '123', 'parameter2' => 'parameter2_value', // ... ], encoding: 'utf-8', culture: Culture::ru, ); } //... }
控制器动作示例,用于处理 Robokassa 请求
use models\{Invoice, InvoiceStatus, PaymentSystem}; use netFantom\Yii2Robokassa\Assets\PopupIframeAsset; use netFantom\Yii2Robokassa\Yii2Robokassa; use Yii; use yii\web\{BadRequestHttpException, Controller, Response}; class PaymentController extends Controller { /** * В случае отказа от исполнения платежа покупатель перенаправляется по данному адресу. * Необходим для того, чтобы продавец мог, например, разблокировать заказанный товар на складе. * * Переход пользователя по данному адресу, строго говоря, не означает окончательного отказа покупателя от оплаты, * нажав кнопку «Назад» в браузере он может вернуться на страницу оплаты Robokassa. * Поэтому в случае блокировки товара на складе под заказ, для его разблокирования желательно проверять * факт отказа от платежа запросом XML-интерфейса получения состояния оплаты счета, используя в запросе * номер счета InvId имеющийся в базе данных магазина/продавца. * @link https://docs.robokassa.ru/pay-interface/ */ public function actionFail(): Response { $invoicePayResult = Yii2Robokassa::getInvoicePayResultFromYiiWebRequest(Yii::$app->request); $invoice = $this->loadInvoice($invoicePayResult->invId); if ($invoice->status_id === InvoiceStatus::STATUS_CREATED) { $invoice->updateAttributes(['status' => InvoiceStatus::STATUS_FAILED]); } return $this->goBack(); } /** * ВАРИАНТ: Загрузка Popup виджета оплаты AJAX запросом */ public function actionInvoiceAjax(): string { $invoice = new Invoice(); $invoice->payment_system_id = PaymentSystem::SYSTEM_ROBOKASSA; $invoice->status_id = InvoiceStatus::STATUS_CREATED; $invoice->user_id = Yii::$app->user->id; if (Yii::$app->request->isAjax && $invoice->load(Yii::$app->request->post()) && $invoice->save()) { return $this->renderAjax('invoice-ajax-response', compact('invoice')); } PopupIframeAsset::register($this->view); return $this->render('invoice-ajax', [ 'invoice' => $invoice, ]); } /** * ВАРИАНТ: Переход на оплату формой с POST запросом */ public function actionInvoiceForm(int $id = null): string { if (isset($id)) { $invoice = $this->loadInvoice($id); if ($invoice->user_id !== Yii::$app->user->id || $invoice->status_id !== InvoiceStatus::STATUS_CREATED) { throw new BadRequestHttpException('Подходящий по условиям счёт не найден'); } return $this->render('invoice-form', compact('invoice')); } $invoice = new Invoice(); $invoice->payment_system_id = PaymentSystem::SYSTEM_ROBOKASSA; $invoice->status_id = InvoiceStatus::STATUS_CREATED; $invoice->user_id = Yii::$app->user->id; if ($invoice->load(Yii::$app->request->post()) && $invoice->save()) { $this->redirect(['payment/invoice-form', 'id' => $invoice->id]); } return $this->render('invoice-create', [ 'invoice' => $invoice, ]); } /** * ВАРИАНТ: Формирование Popup виджета */ public function actionInvoicePopup(int $id = null): string { if (isset($id)) { $invoice = $this->loadInvoice($id); if ($invoice->user_id !== Yii::$app->user->id || $invoice->status_id !== InvoiceStatus::STATUS_CREATED) { throw new BadRequestHttpException('Подходящий по условиям счёт не найден'); } return $this->render('invoice-popup', compact('invoice')); } $invoice = new Invoice(); $invoice->payment_system_id = PaymentSystem::SYSTEM_ROBOKASSA; $invoice->status_id = InvoiceStatus::STATUS_CREATED; $invoice->user_id = Yii::$app->user->id; if ($invoice->load(Yii::$app->request->post()) && $invoice->save()) { $this->redirect(['payment/invoice-popup', 'id' => $invoice->id]); } return $this->render('invoice-create', [ 'invoice' => $invoice, ]); } /** * ResultURL предназначен для получения Вашим сайтом оповещения об успешном платеже в автоматическом режиме. * В случае успешного проведения оплаты Robokassa делает запрос на ResultURL (см. раздел Технические настройки). * Данные всегда передаются в кодировке UTF-8. * * Ваш скрипт, находящийся по ResultURL, обязан проверить равенство полученной контрольной суммы * и контрольной суммы, рассчитанной Вашим скриптом по параметрам, полученным от Robokassa, * а не по локальным данным магазина. * * Если контрольные суммы совпали, то Ваш скрипт должен ответить Robokassa, чтобы мы поняли, * что Ваш скрипт работает правильно и повторное уведомление с нашей стороны не требуется. * Результат должен содержать текст OK и параметр InvId. * Например, для номера счёта 5 должен быть вот такой ответ: OK5. * * Если контрольные суммы не совпали, то полученное оповещение некорректно, и ситуация требует разбора магазином. * @link https://docs.robokassa.ru/pay-interface/ */ public function actionResult(): string { /** @var Yii2Robokassa $robokassa */ $robokassa = Yii::$app->get('robokassa'); $invoicePayResult = Yii2Robokassa::getInvoicePayResultFromYiiWebRequest(Yii::$app->request); if (!$robokassa->checkSignature($invoicePayResult)) { throw new BadRequestHttpException(); } if (!$this->loadInvoice($invoicePayResult->invId)->updateAttributes(['status' => InvoiceStatus::STATUS_PAYED])) { throw new BadRequestHttpException(); } return $invoicePayResult->formatOkAnswer(); } /** * В случае успешного исполнения платежа Покупатель сможет перейти по адресу, * указанному вами в Технических настройках, там же вы указали метод (GET или POST). * * Переход пользователя по данному адресу с корректными параметрами (правильной Контрольной суммой) означает, * что оплата вашего заказа успешно выполнена. * * Однако для дополнительной защиты желательно, чтобы факт оплаты проверялся скриптом, * исполняемым при переходе на SuccessURL, или путем запроса XML-интерфейса получения состояния оплаты счета, * и только при реальном наличии счета с номером InvId в базе данных магазина. * * На самом деле, переход пользователя по ссылке SuccessURL – это формальность, которая нужна только для того, * чтобы пользователь вернулся обратно к Вам и получил информацию о том, что он сделал всё правильно, * и его заказ ждёт его там-то и там-то. Проводить подтверждение оплаты у себя по базе и все остальные действия, * связанные с выдачей покупки, Вам нужно при получении уведомления на ResultUrl, * потому что именно на него Robokassa передаёт подтверждающие данные об оплате в автоматическом режиме * (т. е. в любом случае и без участия пользователя). * @link https://docs.robokassa.ru/pay-interface/ */ public function actionSuccess(): Response|string { $invoicePayResult = Yii2Robokassa::getInvoicePayResultFromYiiWebRequest(Yii::$app->request); $invoice = $this->loadInvoice($invoicePayResult->invId); return $this->render("success", compact('invoice')); } protected function loadInvoice(int $id): Invoice { $invoice = Invoice::find() ->andWhere(['id' => $id]) ->andWhere(['payment_system_id' => PaymentSystem::SYSTEM_ROBOKASSA]) ->one(); if ($invoice === null) { throw new BadRequestHttpException('Подходящий по условиям счёт не найден'); } return $invoice; } }
视图示例,用于创建账单并发送用户到付款
选项:使用 AJAX 请求加载付款弹出窗口小部件
invoice-ajax
视图示例,用于 actionInvoiceAjax
动作
use models\Invoice; use yii\helpers\Html; use yii\helpers\Url; use yii\web\View; use yii\widgets\ActiveForm; /* @var $this View */ /* @var $invoice Invoice */ $url = Url::current(); $this->registerJs( <<<JS $('form').on('beforeSubmit', function(){ var data = $(this).serialize(); $.ajax({ url: '$url', type: 'POST', data: data, success: function(res){ let responsePayForm=$(res).find('#pay-form'); if(responsePayForm.length>0) { $('#pay-form').html(responsePayForm); } else { $('#pay-form').append(res); } }, error: function(){ alert('Error!'); } }); return false; }); JS ); $form = ActiveForm::begin([ 'id' => 'pay-form', 'enableClientValidation' => false, ]); echo $form->field($invoice, 'sum')->textInput(); echo Html::submitButton('Pay', ['class' => 'btn btn-success']); ActiveForm::end();
invoice-ajax-response
视图示例,用于 actionInvoiceAjax
动作
use models\Invoice; use netFantom\Yii2Robokassa\Widgets\PopupIframeWidget; use netFantom\Yii2Robokassa\Yii2Robokassa; use yii\helpers\Html; use yii\web\View; /* @var $this View */ /* @var $invoice Invoice */ /** @var Yii2Robokassa $robokassa */ $robokassa = Yii::$app->get('robokassa'); PopupIframeWidget::widget([ 'yii2Robokassa' => $robokassa, 'invoiceOptions' => $invoice->getInvoiceOptions(), 'registerAsset' => false, ]); echo Html::encode("Сформирован счет №$invoice->id на сумму $invoice->sum руб. и ждет оплаты"); echo Html::button('оплатить', [ 'onClick' => PopupIframeWidget::SHOW_ROBOKASSA_POPUP_IFRAME_ACTION, 'class' => 'btn btn-primary', ]);
选项:使用 POST 请求进行表单付款
invoice-form
视图示例,用于 actionInvoiceForm
动作
use models\Invoice; use netFantom\Yii2Robokassa\Widgets\PopupIframeWidget; use netFantom\Yii2Robokassa\Yii2Robokassa; use yii\helpers\Html; use yii\web\View; use yii\widgets\ActiveForm; /* @var $this View */ /* @var $invoice Invoice */ /** @var Yii2Robokassa $robokassa */ $robokassa = Yii::$app->get('robokassa'); echo Html::encode("Сформирован счет №$invoice->id на сумму $invoice->sum руб. и ждет оплаты"); $form = ActiveForm::begin([ 'id' => 'pay-form', 'method' => 'POST', 'action' => $robokassa->paymentUrl, ]); echo $robokassa->getHiddenInputsHtml($invoice->getInvoiceOptions()); echo Html::submitButton('оплатить', [ 'onClick' => PopupIframeWidget::SHOW_ROBOKASSA_POPUP_IFRAME_ACTION, 'class' => 'btn btn-primary btn-lg', ]); ActiveForm::end();
选项:生成弹出窗口小部件
invoice-popup
视图示例,用于 actionInvoicePopup
动作
use models\Invoice; use netFantom\Yii2Robokassa\Widgets\PopupIframeWidget; use netFantom\Yii2Robokassa\Yii2Robokassa; use yii\helpers\Html; use yii\web\View; /* @var $this View */ /* @var $invoice Invoice */ /** @var Yii2Robokassa $robokassa */ $robokassa = Yii::$app->get('robokassa'); PopupIframeWidget::widget([ 'yii2Robokassa' => $robokassa, 'invoiceOptions' => $invoice->getInvoiceOptions(), 'showOnLoad' => true, ]); echo Html::encode("Сформирован счет №$invoice->id на сумму $invoice->sum руб. и ждет оплаты"); echo Html::button('оплатить', [ 'onClick' => PopupIframeWidget::SHOW_ROBOKASSA_POPUP_IFRAME_ACTION, 'class' => 'btn btn-primary btn-lg', ]);
invoice-create
视图示例,用于 actionInvoicePopup
和 actionInvoiceForm
动作
use models\Invoice; use yii\helpers\Html; use yii\web\View; use yii\widgets\ActiveForm; /* @var $this View */ /* @var $invoice Invoice */ $form = ActiveForm::begin(); echo $form->field($invoice, 'sum')->textInput(); echo Html::submitButton('Пополнить баланс', ['class' => 'btn btn-success']); ActiveForm::end();