From 5e879ee327ff4953072626c9952815b575bc2199 Mon Sep 17 00:00:00 2001 From: Ruslan Plotnikov Date: Mon, 7 Jul 2025 10:56:59 +0300 Subject: [PATCH 01/25] YP-1522 Add intcard to binding readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ff08af..db137d1 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ docker compose up --detach 1. [Создание подписки СБП](src/Examples/getBindingFasterPayment.php) 2. [Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) -##### 4. Подписки SberPay, T-Pay +##### 4. Подписки SberPay, T-Pay, картой не РФ 1. [Создание подписки](src/Examples/getBindingPays.php) 2. [Оплата по подписке](src/Examples/paymentByBindingPays.php) From 492da82232dfe6b96186e29f9397e3fe85117780 Mon Sep 17 00:00:00 2001 From: Ruslan Plotnikov Date: Mon, 7 Jul 2025 14:51:34 +0300 Subject: [PATCH 02/25] YP-1522 Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db137d1..c7d3134 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ docker compose up --detach 1. [Создание подписки СБП](src/Examples/getBindingFasterPayment.php) 2. [Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) -##### 4. Подписки SberPay, T-Pay, картой не РФ +##### 4. Подписки SberPay, T-Pay, Картой не РФ 1. [Создание подписки](src/Examples/getBindingPays.php) 2. [Оплата по подписке](src/Examples/paymentByBindingPays.php) From 9b9c6b55c844b626215412a1e8aa7308026661c5 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Tue, 15 Jul 2025 10:44:55 +0300 Subject: [PATCH 03/25] FIX: Webhook.php Fixed CardDetails mapping --- src/Webhook.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Webhook.php b/src/Webhook.php index d5bc1e3..48ba312 100644 --- a/src/Webhook.php +++ b/src/Webhook.php @@ -39,11 +39,11 @@ public function catchJsonRequest(): self $this->orderData->setLoyaltyPointsDetails((array) $request['orderData']['loyaltyPointsDetails']); $cardDetails = new CardDetails; - $cardDetails->setBin($request['paymentResult']['paymentMethod']['cardDetails']['bin']); - $cardDetails->setOwner($request['paymentResult']['paymentMethod']['cardDetails']['owner']); - $cardDetails->setPan($request['paymentResult']['paymentMethod']['cardDetails']['pan']); - $cardDetails->setType($request['paymentResult']['paymentMethod']['cardDetails']['type']); - $cardDetails->setCardIssuerBank($request['paymentResult']['paymentMethod']['cardDetails']['cardIssuerBank']); + $cardDetails->setBin($request['paymentResult']['cardDetails']['bin']); + $cardDetails->setOwner($request['paymentResult']['cardDetails']['owner']); + $cardDetails->setPan($request['paymentResult']['cardDetails']['pan']); + $cardDetails->setType($request['paymentResult']['cardDetails']['type']); + $cardDetails->setCardIssuerBank($request['paymentResult']['cardDetails']['cardIssuerBank']); $this->paymentResult = new PaymentResult; $this->paymentResult->setCardDetails($cardDetails); From cb6397177ca2e9de3cef068f771d572472d1d64f Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Tue, 15 Jul 2025 20:32:16 +0300 Subject: [PATCH 04/25] ADD: webhook example --- example_list.php | 6 ++++++ src/Examples/createWebhook.php | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/Examples/createWebhook.php diff --git a/example_list.php b/example_list.php index 9d410b5..5215a94 100644 --- a/example_list.php +++ b/example_list.php @@ -202,4 +202,10 @@ 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/widget-integration', 'link' => '', ], + 'createWebhook' => [ + 'name' => 'Обработка вебхука', + 'about' => 'В этом примере показана обработка вебхука IPN', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/webhooks', + 'link' => '', + ] ]; diff --git a/src/Examples/createWebhook.php b/src/Examples/createWebhook.php new file mode 100644 index 0000000..0417b16 --- /dev/null +++ b/src/Examples/createWebhook.php @@ -0,0 +1,32 @@ +catchJsonRequest(); + + $paymentResult = $webhookHandler->getPaymentResult(); + $orderData = $webhookHandler->getOrderData(); + $authorization = $webhookHandler->getAuthorization(); + +} catch (\Ypmn\PaymentException $e) { + // YourLogger::log(...); +} + From 8ce8ff421ac9b128a73b9c5a316281cc1a7ef0a0 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Tue, 15 Jul 2025 20:52:20 +0300 Subject: [PATCH 05/25] ADD: webhook example --- example_list.php | 2 +- src/Examples/{createWebhook.php => webhookProcessing.php} | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) rename src/Examples/{createWebhook.php => webhookProcessing.php} (93%) diff --git a/example_list.php b/example_list.php index 5215a94..c407395 100644 --- a/example_list.php +++ b/example_list.php @@ -202,7 +202,7 @@ 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/widget-integration', 'link' => '', ], - 'createWebhook' => [ + 'webhookProcessing' => [ 'name' => 'Обработка вебхука', 'about' => 'В этом примере показана обработка вебхука IPN', 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/webhooks', diff --git a/src/Examples/createWebhook.php b/src/Examples/webhookProcessing.php similarity index 93% rename from src/Examples/createWebhook.php rename to src/Examples/webhookProcessing.php index 0417b16..d6f3a55 100644 --- a/src/Examples/createWebhook.php +++ b/src/Examples/webhookProcessing.php @@ -1,7 +1,7 @@ Date: Wed, 16 Jul 2025 11:00:17 +0300 Subject: [PATCH 06/25] ADD: webhook example --- example.php | 1 + src/Examples/webhookProcessing.php | 47 +++++++++++++++++++++++++++++- src/PaymentResult.php | 2 +- src/PaymentResultInterface.php | 5 ++-- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/example.php b/example.php index ccf56a7..2469c14 100644 --- a/example.php +++ b/example.php @@ -50,6 +50,7 @@ case 'qstPrint': case 'SOMGetPaymentLink': case 'qstList': + case 'webhookProcessing': require './src/Examples/start.php'; @include './src/Examples/'.$_GET['function'] . '__prepend.php'; require './src/Examples/'.$_GET['function'] . '.php'; diff --git a/src/Examples/webhookProcessing.php b/src/Examples/webhookProcessing.php index d6f3a55..76a9315 100644 --- a/src/Examples/webhookProcessing.php +++ b/src/Examples/webhookProcessing.php @@ -11,12 +11,19 @@ declare(strict_types=1); +// Подключим файл, в котором заданы параметры мерчанта +include_once 'start.php'; + use Ypmn\Webhook; +// Обрабатываем только POST запросы +if (empty($_POST)) { + die(); +} + // Создадим обработчик вебхука $webhookHandler = new Webhook(); - // Обработаем вебхук try { $webhookHandler->catchJsonRequest(); @@ -25,6 +32,44 @@ $orderData = $webhookHandler->getOrderData(); $authorization = $webhookHandler->getAuthorization(); + + // Обозначим путь до папки и запишем вебхук в лог + $folder = __DIR__ . "/../../webhooks_log/"; + $filename = $folder . "ypmn" . date("Y-m-d H:i:s") ."_" . uniqid() . ".log"; + + if (!file_exists($folder)) { + mkdir($folder, 0777, true); + } + + file_put_contents($filename, json_encode([ + "orderData" => [ + "orderDate" => $orderData->getOrderDate(), + "payuPaymentReference" => $orderData->getPayuPaymentReference(), + "merchantPaymentReference" => $orderData->getMerchantPaymentReference(), + "status" => $orderData->getStatus(), + "currency" => $orderData->getCurrency(), + "amount" => $orderData->getAmount() ?? "null", + "commission" => $orderData->getCommission() ?? "null", + "loyaltyPointsAmount" => $orderData->getLoyaltyPointsAmount() ?? "null", + "loyaltyPointsDetails" => $orderData->getLoyaltyPointsDetails() ?? "null", + ], + + "paymentResult" => [ + "cardDetails" => [ + "bin" => $paymentResult->getCardDetails()->getBin(), + "owner" => $paymentResult->getCardDetails()->getOwner(), + "pan" => $paymentResult->getCardDetails()->getPan(), + "type" => $paymentResult->getCardDetails()->getType(), + "cardIssuerBank" => $paymentResult->getCardDetails()->getCardIssuerBank(), + ], + "paymentMethod" => $paymentResult->getPaymentMethod(), + "paymentDate" => $paymentResult->getPaymentDate(), + "authCode" => $paymentResult->getAuthCode(), + "merchantId" => $paymentResult->getMerchantId(), + ], + ])); + + } catch (\Ypmn\PaymentException $e) { // YourLogger::log(...); } diff --git a/src/PaymentResult.php b/src/PaymentResult.php index 917c93b..b4ba12b 100644 --- a/src/PaymentResult.php +++ b/src/PaymentResult.php @@ -152,7 +152,7 @@ public function setCardDetails(CardDetailsInterface $cardDetails): self } /** @inheritDoc */ - public function getCardDetails($cardDetails) : CardDetailsInterface + public function getCardDetails() : CardDetailsInterface { return $this->cardDetails; } diff --git a/src/PaymentResultInterface.php b/src/PaymentResultInterface.php index 75132a5..5eb318b 100644 --- a/src/PaymentResultInterface.php +++ b/src/PaymentResultInterface.php @@ -110,11 +110,10 @@ public function setInstallmentsNumber(string $installmentsNumber): self; public function setCardDetails(CardDetailsInterface $cardDetails): self; /** - * Установить Информацию о Карте - * @param $cardDetails + * Получить информацию о Карте * @return CardDetailsInterface */ - public function getCardDetails($cardDetails) : CardDetailsInterface; + public function getCardDetails() : CardDetailsInterface; /** From 5eb41bd8e810556d4c09d9e6be9ecfc4fc222ba0 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Wed, 16 Jul 2025 15:14:42 +0300 Subject: [PATCH 07/25] ADD: autoload script --- README.md | 2 +- src/Examples/autoload.php | 125 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/Examples/autoload.php diff --git a/README.md b/README.md index c7d3134..2c41797 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ composer require yourpayments/php-api-client ```shell composer update yourpayments/php-api-client ``` -(если на вашем проекте нет composer, слонируйте или скачайте, а затем подключите ([require](https://www.php.net/manual/ru/function.require.php)) файлы этого репозитория) +(если на вашем проекте нет composer, слонируйте или скачайте, а затем подключите файлы этого репозитория ([файл автозагрузки](src/Examples/autoload.php))) ## Запуск в контейнере docker Создайте и запустите docker контейнер следующей командой: diff --git a/src/Examples/autoload.php b/src/Examples/autoload.php new file mode 100644 index 0000000..74eb8b6 --- /dev/null +++ b/src/Examples/autoload.php @@ -0,0 +1,125 @@ +/src"); + +if (!$baseDir) { + throw new RuntimeException("Папка src не найдена!"); +} + +// Перечисление всех файлов в SDK +$files = [ + 'AirlineInfoInterface', + 'AmountInterface', + 'ApiCpanelArraySerializeInterface', + 'ApiRequestInterface', + 'AuthorizationInterface', + 'BillingInterface', + 'CaptureInterface', + 'ClientInterface', + 'DeliveryInterface', + 'DestinationInterface', + 'DetailsInterface', + 'CardDetailsInterface', + 'IdentityDocumentInterface', + 'MarketplaceSubmerchantInterface', + 'MerchantInterface', + 'MerchantTokenInterface', + 'OrderDataInterface', + 'PaymentDetailsInterface', + 'PaymentInterface', + 'PaymentPageOptionsInterface', + 'PaymentResultInterface', + 'PayoutDestinationInterface', + 'PayoutInterface', + 'PayoutSourceInterface', + 'ProductInterface', + 'QstInterface', + 'QstToArrayInterface', + 'QstSchemaAddressInterface', + 'QstSchemaBankAccountInterface', + 'QstSchemaCeoInterface', + 'QstSchemaIdentityDocInterface', + 'QstSchemaInterface', + 'QstSchemaOwnerInterface', + 'RefundInterface', + 'StoredCredentialsInterface', + 'TransactionInterface', + 'WebhookAuthorizationInterface', + 'WebhookInterface', + 'WebhookStoredCredentialsInterface', + 'Payout/PayoutCommissionInterface', + 'Payout/PayoutOrderDataInterface', + 'Payout/PayoutPaymentResultInterface', + 'Payout/PayoutRecipientInterface', + 'Payout/PayoutWebhookInterface', + 'ApiRequest', + 'Amount', + 'ApiCpanelUserException', + 'ApiCpanelUserRequest', + 'Authorization', + 'Billing', + 'Capture', + 'CaptureApiRequest', + 'CardDetails', + 'Client', + 'CpanelUser', + 'Delivery', + 'Details', + 'Group', + 'IdentityDocument', + 'MarketplaceSubmerchant', + 'Merchant', + 'MerchantToken', + 'OneTimeUseToken', + 'OrderData', + 'Payment', + 'PaymentException', + 'PaymentMethods', + 'PaymentPageOptions', + 'PaymentReference', + 'PaymentResult', + 'Payout', + 'PayoutDestination', + 'PayoutMobileDestination', + 'PayoutSource', + 'PhoneDetails', + 'PodeliMerchant', + 'PodeliMerchantAddress', + 'PodeliMerchantBankDetails', + 'Privilege', + 'Product', + 'Qst', + 'QstSchema', + 'QstSchemaAddressAbstract', + 'QstSchemaCheckableTrait', + 'QstSchemaActualAddress', + 'QstSchemaBankAccount', + 'QstSchemaCeo', + 'QstSchemaIdentityDoc', + 'QstSchemaLegalAddress', + 'QstSchemaOwner', + 'QstSchemaPostAddress', + 'Refund', + 'Role', + 'SessionRequest', + 'Std', + 'StoredCredentials', + 'SubmerchantReceipt', + 'Webhook', + 'WebhookAuthorization', + 'WebhookStoredCredentials', + 'Widget', + 'Payout/PayoutCommission', + 'Payout/PayoutPaymentResult', + 'Payout/PayoutPayoutOrderData', + 'Payout/PayoutRecipient', + 'Payout/PayoutWebhook' +]; + +// Подключение файлов к проекту +foreach ($files as $file) { + require_once $baseDir . "/" . $file . ".php"; +} + +unset ($files); +unset ($file); \ No newline at end of file From 52c2d4f6f7d18b25986e64d121d2a59bf61a718f Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Wed, 16 Jul 2025 15:18:09 +0300 Subject: [PATCH 08/25] ADD: autoload script --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c41797..fc3ef2d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ composer require yourpayments/php-api-client ```shell composer update yourpayments/php-api-client ``` -(если на вашем проекте нет composer, слонируйте или скачайте, а затем подключите файлы этого репозитория ([файл автозагрузки](src/Examples/autoload.php))) +(если на вашем проекте нет composer, склонируйте или скачайте, а затем подключите файлы этого репозитория ([файл автозагрузки](src/Examples/autoload.php))) ## Запуск в контейнере docker Создайте и запустите docker контейнер следующей командой: From 226b76fd1bde42345e467ee28b06036edf9d6937 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Fri, 18 Jul 2025 12:44:41 +0300 Subject: [PATCH 09/25] DRAFT: Webhook: OrderData FIX --- src/OrderData.php | 12 +-- src/OrderDataInterface.php | 30 +++--- src/Webhook.php | 192 +++++++++++++++++++++++++++---------- 3 files changed, 163 insertions(+), 71 deletions(-) diff --git a/src/OrderData.php b/src/OrderData.php index d6ad7d2..e62892a 100644 --- a/src/OrderData.php +++ b/src/OrderData.php @@ -35,7 +35,7 @@ class OrderData implements OrderDataInterface private array $loyaltyPointsDetails; /** @inheritDoc */ - public function getOrderDate(): string + public function getOrderDate(): ?string { return $this->orderDate; } @@ -57,7 +57,7 @@ public function setYpmnPaymentReference(string $ypmnPaymentReference): self } /** @inheritDoc */ - public function getYpmnPaymentReference(): string + public function getYpmnPaymentReference(): ?string { return $this->payUPaymentReference; } @@ -71,13 +71,13 @@ public function setPayUPaymentReference(string $payUPaymentReference): self } /** @inheritDoc */ - public function getPayUPaymentReference(): string + public function getPayUPaymentReference(): ?string { return $this->payUPaymentReference; } /** @inheritDoc */ - public function getMerchantPaymentReference(): string + public function getMerchantPaymentReference(): ?string { return $this->merchantPaymentReference; } @@ -91,7 +91,7 @@ public function setMerchantPaymentReference(string $merchantPaymentReference): s } /** @inheritDoc */ - public function getStatus(): string + public function getStatus(): ?string { return $this->status; } @@ -105,7 +105,7 @@ public function setStatus(string $status): self } /** @inheritDoc */ - public function getCurrency(): string + public function getCurrency(): ?string { return $this->currency; } diff --git a/src/OrderDataInterface.php b/src/OrderDataInterface.php index 6921b88..f2cc060 100644 --- a/src/OrderDataInterface.php +++ b/src/OrderDataInterface.php @@ -7,9 +7,9 @@ interface OrderDataInterface /** * Получить Дату Заказа - * @return string Дата Заказа + * @return string|null Дата Заказа */ - public function getOrderDate(): string; + public function getOrderDate(): ?string; /** * Установть Дату Заказа @@ -20,11 +20,11 @@ public function setOrderDate(string $orderDate): self; /** * Получить Номер Заказа в Ypmn - * @return string Номер Заказа в Ypmn + * @return string|null Номер Заказа в Ypmn * * @deprecated Используйте getPayUPaymentReference */ - public function getYpmnPaymentReference(): string; + public function getYpmnPaymentReference(): ?string; /** * Установить Номер Заказа в Ypmn @@ -37,9 +37,9 @@ public function setYpmnPaymentReference(string $ypmnPaymentReference): self; /** * Получить Номер Заказа в Ypmn - * @return string Номер Заказа в Ypmn + * @return string|null Номер Заказа в Ypmn */ - public function getPayUPaymentReference(): string; + public function getPayUPaymentReference(): ?string; /** * Установить Номер Заказа в Ypmn * @param string $payUPaymentReference Номер Заказа в Ypmn @@ -49,9 +49,9 @@ public function setPayUPaymentReference(string $payUPaymentReference): self; /** * Получить Номер Заказа у Мерчанта - * @return string Номер Заказа у Мерчанта + * @return string|null Номер Заказа у Мерчанта */ - public function getMerchantPaymentReference(): string; + public function getMerchantPaymentReference(): ?string; /** * Установить Номер Заказа у Мерчанта @@ -62,9 +62,9 @@ public function setMerchantPaymentReference(string $merchantPaymentReference): s /** * Получить Состояние Платежа - * @return string Состояние Платежа + * @return string|null Состояние Платежа */ - public function getStatus(): string; + public function getStatus(): ?string; /** * Установить Состояние Платежа @@ -75,9 +75,9 @@ public function setStatus(string $status): self; /** * Получить Валюту Платежа - * @return string Валюта Платежа + * @return string|null Валюта Платежа */ - public function getCurrency(): string; + public function getCurrency(): ?string; /** * Установить Валюту Платежа @@ -113,13 +113,13 @@ public function getCommission(): ?float; public function setCommission(float $commission): self; /** - * Получить - * @return string + * Получить ID запроса возврата + * @return string|null */ public function getRefundRequestId(): ?string; /** - * Установить + * Установить ID запроса возврата * @param string $refundRequestId * @return $this */ diff --git a/src/Webhook.php b/src/Webhook.php index 48ba312..7b22a81 100644 --- a/src/Webhook.php +++ b/src/Webhook.php @@ -28,62 +28,154 @@ public function catchJsonRequest(): self } $this->orderData = new OrderData; - $this->orderData->setOrderDate($request['orderData']['orderDate']); - $this->orderData->setPayUPaymentReference($request['orderData']['payuPaymentReference']); - $this->orderData->setMerchantPaymentReference($request['orderData']['merchantPaymentReference']); - $this->orderData->setStatus($request['orderData']['status']); - $this->orderData->setCurrency($request['orderData']['currency']); - $this->orderData->setAmount($request['orderData']['amount']); - $this->orderData->setCommission((float) $request['orderData']['commission']); - $this->orderData->setLoyaltyPointsAmount((int) $request['orderData']['loyaltyPointsAmount']); - $this->orderData->setLoyaltyPointsDetails((array) $request['orderData']['loyaltyPointsDetails']); - - $cardDetails = new CardDetails; - $cardDetails->setBin($request['paymentResult']['cardDetails']['bin']); - $cardDetails->setOwner($request['paymentResult']['cardDetails']['owner']); - $cardDetails->setPan($request['paymentResult']['cardDetails']['pan']); - $cardDetails->setType($request['paymentResult']['cardDetails']['type']); - $cardDetails->setCardIssuerBank($request['paymentResult']['cardDetails']['cardIssuerBank']); - - $this->paymentResult = new PaymentResult; - $this->paymentResult->setCardDetails($cardDetails); - $this->paymentResult->setPaymentMethod($request['paymentResult']['paymentMethod']); - $this->paymentResult->setPaymentDate($request['paymentResult']['paymentDate']); - $this->paymentResult->setAuthCode((string) $request['paymentResult']['authCode']); - $this->paymentResult->setMerchantId($request['paymentResult']['merchantId']); - - if (isset($request['paymentResult']['captureDate'])) { - $this->paymentResult->setCaptureDate($request['paymentResult']['captureDate']); + + if (!empty($request['orderData']['orderDate'])) { + $this->orderData->setOrderDate($request['orderData']['orderDate']); + } + + if (!empty($request['orderData']['payuPaymentReference'])) { + $this->orderData->setPayUPaymentReference($request['orderData']['payuPaymentReference']); + } + + if (!empty($request['orderData']['merchantPaymentReference'])) { + $this->orderData->setMerchantPaymentReference($request['orderData']['merchantPaymentReference']); + } + + if (!empty($request['orderData']['status'])) { + $this->orderData->setStatus($request['orderData']['status']); + } + + if (!empty($request['orderData']['currency'])) { + $this->orderData->setCurrency($request['orderData']['currency']); } - if (isset($request['paymentResult']['rrn'])) { - $this->paymentResult->setRrn((int) $request['paymentResult']['rrn']); + if (!empty($request['orderData']['amount'])) { + $this->orderData->setAmount($request['orderData']['amount']); } - if (isset($request['paymentResult']['cardProgramName'])) { - $this->paymentResult->setCardProgramName($request['paymentResult']['cardProgramName']); + if (isset($request['orderData']['commission'])) { + $this->orderData->setCommission((float) $request['orderData']['commission']); } - if (isset($request['paymentResult']['installmentsNumber'])) { - $this->paymentResult->setInstallmentsNumber($request['paymentResult']['installmentsNumber']); + if (isset($request['orderData']['loyaltyPointsAmount'])) { + $this->orderData->setLoyaltyPointsAmount((int) $request['orderData']['loyaltyPointsAmount']); } - if (isset($request['client']) && count($request['client']) > 0) { + if (!empty($request['orderData']['loyaltyPointsDetails'])) { + $this->orderData->setLoyaltyPointsDetails((array) $request['orderData']['loyaltyPointsDetails']); + } + + if (!empty($request['paymentResult']['cardDetails'])) { + $cardDetails = new CardDetails; + + if (!empty($request['paymentResult']['cardDetails']['bin'])) { + $cardDetails->setBin($request['paymentResult']['cardDetails']['bin']); + } + + if (!empty($request['paymentResult']['cardDetails']['owner'])) { + $cardDetails->setOwner($request['paymentResult']['cardDetails']['owner']); + } + + if (!empty($request['paymentResult']['cardDetails']['pan'])) { + $cardDetails->setPan($request['paymentResult']['cardDetails']['pan']); + } + + if (!empty($request['paymentResult']['cardDetails']['type'])) { + $cardDetails->setType($request['paymentResult']['cardDetails']['type']); + } + + if (!empty($request['paymentResult']['cardDetails']['cardIssuerBank'])) { + $cardDetails->setCardIssuerBank($request['paymentResult']['cardDetails']['cardIssuerBank']); + } + + $this->paymentResult = new PaymentResult; + $this->paymentResult->setCardDetails($cardDetails); + + if (!empty($request['paymentResult']['paymentMethod'])) { + $this->paymentResult->setPaymentMethod($request['paymentResult']['paymentMethod']); + } + + if (!empty($request['paymentResult']['paymentDate'])) { + $this->paymentResult->setPaymentDate($request['paymentResult']['paymentDate']); + } + + if (isset($request['paymentResult']['authCode'])) { + $this->paymentResult->setAuthCode((string) $request['paymentResult']['authCode']); + } + + if (!empty($request['paymentResult']['merchantId'])) { + $this->paymentResult->setMerchantId($request['paymentResult']['merchantId']); + } + + if (!empty($request['paymentResult']['captureDate'])) { + $this->paymentResult->setCaptureDate($request['paymentResult']['captureDate']); + } + + if (isset($request['paymentResult']['rrn'])) { + $this->paymentResult->setRrn((int) $request['paymentResult']['rrn']); + } + + if (!empty($request['paymentResult']['cardProgramName'])) { + $this->paymentResult->setCardProgramName($request['paymentResult']['cardProgramName']); + } + + if (isset($request['paymentResult']['installmentsNumber'])) { + $this->paymentResult->setInstallmentsNumber($request['paymentResult']['installmentsNumber']); + } + } + + if (!empty($request['client']) && count($request['client']) > 0) { $billing = new Billing; - $billing->setFirstName($request['client']['billing']['firstName']); - $billing->setLastName($request['client']['billing']['lastName']); - $billing->setEmail($request['client']['billing']['email']); - $billing->setPhone($request['client']['billing']['phone']); - $billing->setCountryCode($request['client']['billing']['countryCode']); - $billing->setCity($request['client']['billing']['city']); - $billing->setState($request['client']['billing']['state']); - $billing->setCompanyName($request['client']['billing']['companyName']); - $billing->setTaxId($request['client']['billing']['taxId']); - $billing->setAddressLine1($request['client']['billing']['addressLine1']); - $billing->setAddressLine1($request['client']['billing']['addressLine2']); - $billing->setZipCode($request['client']['billing']['zipCode']); - - if (isset($request['client']['billing']['identityDocument']) && count($request['client']['billing']['identityDocument']) > 0) { + + if (!empty($request['client']['billing']['firstName'])) { + $billing->setFirstName($request['client']['billing']['firstName']); + } + + if (!empty($request['client']['billing']['lastName'])) { + $billing->setLastName($request['client']['billing']['lastName']); + } + + if (!empty($request['client']['billing']['email'])) { + $billing->setEmail($request['client']['billing']['email']); + } + + if (!empty($request['client']['billing']['phone'])) { + $billing->setPhone($request['client']['billing']['phone']); + } + + if (!empty($request['client']['billing']['countryCode'])) { + $billing->setCountryCode($request['client']['billing']['countryCode']); + } + + if (!empty($request['client']['billing']['city'])) { + $billing->setCity($request['client']['billing']['city']); + } + + if (!empty($request['client']['billing']['state'])) { + $billing->setState($request['client']['billing']['state']); + } + + if (!empty($request['client']['billing']['companyName'])) { + $billing->setCompanyName($request['client']['billing']['companyName']); + } + + if (!empty($request['client']['billing']['taxId'])) { + $billing->setTaxId($request['client']['billing']['taxId']); + } + + if (!empty($request['client']['billing']['addressLine1'])) { + $billing->setAddressLine1($request['client']['billing']['addressLine1']); + } + + if (!empty($request['client']['billing']['addressLine2'])) { + $billing->setAddressLine2($request['client']['billing']['addressLine2']); + } + + if (!empty($request['client']['billing']['zipCode'])) { + $billing->setZipCode($request['client']['billing']['zipCode']); + } + + if (!empty($request['client']['billing']['identityDocument']) && count($request['client']['billing']['identityDocument']) > 0) { $identityDocument = new IdentityDocument( (int) $request['client']['billing']['identityDocument']['number'], $request['client']['billing']['identityDocument']['type'] @@ -98,16 +190,16 @@ public function catchJsonRequest(): self $client->setDelivery($delivery); } - if (isset($request['authorization']['storedCredentials'])) { + if (!empty($request['authorization']['storedCredentials'])) { $storedCredentialsArray = $request['authorization']['storedCredentials']; $storedCredentials = new WebhookStoredCredentials; - if (isset($storedCredentialsArray['ypmnBindingId'])) { + if (!empty($storedCredentialsArray['ypmnBindingId'])) { $storedCredentials->setYpmnBindingId($storedCredentialsArray['ypmnBindingId']); } - if (isset($storedCredentialsArray['useId'])) { + if (!empty($storedCredentialsArray['useId'])) { $storedCredentials->setUseId($storedCredentialsArray['useId']); } From 6243e102dd1c0a9ab3855e8b11f392bd1f88df09 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Fri, 18 Jul 2025 13:11:41 +0300 Subject: [PATCH 10/25] DRAFT: Webhook: CardDetails FIX --- src/CardDetails.php | 24 ++++++++++---------- src/CardDetailsInterface.php | 44 ++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/CardDetails.php b/src/CardDetails.php index a3994ff..a09616e 100644 --- a/src/CardDetails.php +++ b/src/CardDetails.php @@ -38,7 +38,7 @@ class CardDetails implements CardDetailsInterface private string $cardIssuerBank; /** @inheritDoc */ - public function getNumber(): string + public function getNumber(): ?string { return $this->number; } @@ -53,7 +53,7 @@ public function setNumber(string $number): self } /** @inheritDoc */ - public function getExpiryMonth(): int + public function getExpiryMonth(): ?int { return $this->expiryMonth; } @@ -66,7 +66,7 @@ public function setExpiryMonth(int $expiryMonth): self } /** @inheritDoc */ - public function getYear(): int + public function getYear(): ?int { return $this->year; } @@ -83,7 +83,7 @@ public function setYear(int $year): self } /** @inheritDoc */ - public function getExpiryYear(): int + public function getExpiryYear(): ?int { return $this->expiryYear; } @@ -96,7 +96,7 @@ public function setExpiryYear(int $expiryYear): self } /** @inheritDoc */ - public function getCvv(): string + public function getCvv(): ?string { return $this->cvv; } @@ -109,7 +109,7 @@ public function setCvv(string $cvv): self } /** @inheritDoc */ - public function getOwner(): string + public function getOwner(): ?string { return $this->owner; } @@ -122,7 +122,7 @@ public function setOwner(string $owner): self } /** @inheritDoc */ - public function getTimeSpentTypingNumber(): int + public function getTimeSpentTypingNumber(): ?int { return $this->timeSpentTypingNumber; } @@ -135,7 +135,7 @@ public function setTimeSpentTypingNumber(int $timeSpentTypingNumber): self } /** @inheritDoc */ - public function getTimeSpentTypingOwner(): int + public function getTimeSpentTypingOwner(): ?int { return $this->timeSpentTypingOwner; } @@ -148,7 +148,7 @@ public function setTimeSpentTypingOwner(int $timeSpentTypingOwner): self } /** @inheritDoc */ - public function getBin(): int + public function getBin(): ?int { return $this->bin; } @@ -161,7 +161,7 @@ public function setBin(int $bin): self } /** @inheritDoc */ - public function getPan(): string + public function getPan(): ?string { return $this->pan; } @@ -174,7 +174,7 @@ public function setPan(string $pan): self } /** @inheritDoc */ - public function getType(): string + public function getType(): ?string { return $this->type; } @@ -187,7 +187,7 @@ public function setType(string $type): self } /** @inheritDoc */ - public function getCardIssuerBank(): string + public function getCardIssuerBank(): ?string { return $this->cardIssuerBank; } diff --git a/src/CardDetailsInterface.php b/src/CardDetailsInterface.php index 13f3e35..73fae78 100644 --- a/src/CardDetailsInterface.php +++ b/src/CardDetailsInterface.php @@ -14,9 +14,9 @@ public function setExpiryMonth(int $expiryMonth) : self; /** * Получить Месяц прекращения действия Карты - * @return int + * @return int|null */ - public function getExpiryMonth() : int; + public function getExpiryMonth() : ?int; /** * Установить Год прекращения действия Карты @@ -28,9 +28,9 @@ public function setExpiryYear(int $expiryYear) : self; /** * Получить Год прекращения действия Карты - * @return int Год прекращения действия Карты + * @return int|null Год прекращения действия Карты */ - public function getExpiryYear() : int; + public function getExpiryYear() : ?int; /** * Установить CVV Карты @@ -41,9 +41,9 @@ public function setCvv(string $cvv) : self; /** * Получить CVV Карты - * @return string CVV Карты + * @return string|null CVV Карты */ - public function getCvv() : string; + public function getCvv() : ?string; /** * Установить Имя Владельца Карты @@ -54,9 +54,9 @@ public function setOwner(string $owner) : self; /** * Получить Имя Владельца Карты - * @return string Имя Владельца Карты + * @return string|null Имя Владельца Карты */ - public function getOwner() : string; + public function getOwner() : ?string; /** * Установить Время набора Номера Карты (сек) @@ -67,9 +67,9 @@ public function setTimeSpentTypingNumber(int $timeSpentTypingNumber) : self; /** * Получить Время набора номера Карты (сек) - * @return int Время набора Номера Карты (сек) + * @return int|null Время набора Номера Карты (сек) */ - public function getTimeSpentTypingNumber() : int; + public function getTimeSpentTypingNumber() : ?int; /** * Установить Время набора Имени Владельца Карты (сек) @@ -80,9 +80,9 @@ public function setTimeSpentTypingOwner(int $timeSpentTypingOwner) : self; /** * Получить Время набора Имени Владельца Карты - * @return int Время набора Имени Владельца Карты (сек) + * @return int|null Время набора Имени Владельца Карты (сек) */ - public function getTimeSpentTypingOwner() : int; + public function getTimeSpentTypingOwner() : ?int; /** * Установить BIN (Bank Identification Number) @@ -93,9 +93,9 @@ public function setBin(int $bin) : self; /** * Получить BIN (Bank Identification Number) - * @return int BIN (Bank Identification Number) + * @return int|null BIN (Bank Identification Number) */ - public function getBin() : int; + public function getBin() : ?int; /** * Установить PAN (Permanent Account Number) @@ -106,9 +106,9 @@ public function setPan(string $pan) : self; /** * Получить PAN (Permanent Account Number) - * @return string PAN (Permanent Account Number) + * @return string|null PAN (Permanent Account Number) */ - public function getPan() : string; + public function getPan() : ?string; /** * Установить Тип Карты (например, Visa, MIR) @@ -119,9 +119,9 @@ public function setType(string $type) : self; /** * Получить Тип Карты (например, Visa, MIR) - * @return string Тип Карты + * @return string|null Тип Карты */ - public function getType() : string; + public function getType() : ?string; /** * Установить Банк, выпустивший карту @@ -132,15 +132,15 @@ public function setCardIssuerBank(string $cardIssuerBank) : self; /** * Получить Банк, выпустивший карту - * @return string Банк, выпустивший карту + * @return string|null Банк, выпустивший карту */ - public function getCardIssuerBank() : string; + public function getCardIssuerBank() : ?string; /** * Получить Год Карты - * @return int Год Карты + * @return int|null Год Карты */ - public function getYear(): int; + public function getYear(): ?int; /** * From d59e41301578d358fc1f9ad18b2fb0797687eb62 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Fri, 18 Jul 2025 13:14:19 +0300 Subject: [PATCH 11/25] DRAFT: Webhook: CardDetails FIX --- src/PaymentResult.php | 20 ++++++++--------- src/PaymentResultInterface.php | 40 +++++++++++++++++----------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/PaymentResult.php b/src/PaymentResult.php index b4ba12b..a6d4c24 100644 --- a/src/PaymentResult.php +++ b/src/PaymentResult.php @@ -41,7 +41,7 @@ class PaymentResult implements PaymentResultInterface private CardDetailsInterface $cardDetails; /** @inheritDoc */ - public function getPaymentMethod(): string + public function getPaymentMethod(): ?string { return $this->paymentMethod; } @@ -54,7 +54,7 @@ public function setPaymentMethod(string $paymentMethod): self } /** @inheritDoc */ - public function getPaymentDate(): string + public function getPaymentDate(): ?string { return $this->paymentDate; } @@ -67,7 +67,7 @@ public function setPaymentDate(string $paymentDate): self } /** @inheritDoc */ - public function getCaptureDate(): string + public function getCaptureDate(): ?string { return $this->captureDate; } @@ -80,7 +80,7 @@ public function setCaptureDate(string $captureDate): self } /** @inheritDoc */ - public function getCardProgramName(): string + public function getCardProgramName(): ?string { return $this->cardProgramName; } @@ -93,7 +93,7 @@ public function setCardProgramName(string $cardProgramName): self } /** @inheritDoc */ - public function getAuthCode(): string + public function getAuthCode(): ?string { return $this->authCode; } @@ -106,7 +106,7 @@ public function setAuthCode(string $authCode): self } /** @inheritDoc */ - public function getMerchantId(): string + public function getMerchantId(): ?string { return $this->merchantId; } @@ -119,7 +119,7 @@ public function setMerchantId(string $merchantId): self } /** @inheritDoc */ - public function getRrn(): int + public function getRrn(): ?int { return $this->rrn; } @@ -132,7 +132,7 @@ public function setRrn(int $rrn): self } /** @inheritDoc */ - public function getInstallmentsNumber(): string + public function getInstallmentsNumber(): ?string { return $this->installmentsNumber; } @@ -158,7 +158,7 @@ public function getCardDetails() : CardDetailsInterface } /** @inheritDoc */ - public function getPaymentBankShortName(): string + public function getPaymentBankShortName(): ?string { return $this->paymentBankShortName; } @@ -171,7 +171,7 @@ public function setPaymentBankShortName(string $paymentBankShortName): self } /** @inheritDoc */ - public function getServiceProcessingType(): string + public function getServiceProcessingType(): ?string { return $this->serviceProcessingType; } diff --git a/src/PaymentResultInterface.php b/src/PaymentResultInterface.php index 5eb318b..f82cfbc 100644 --- a/src/PaymentResultInterface.php +++ b/src/PaymentResultInterface.php @@ -6,9 +6,9 @@ interface PaymentResultInterface { /** * Получить Метод Оплаты - * @return string Метод Оплаты + * @return string|null Метод Оплаты */ - public function getPaymentMethod(): string; + public function getPaymentMethod(): ?string; /** * Установить Метод Оплаты @@ -19,9 +19,9 @@ public function setPaymentMethod(string $paymentMethod): self; /** * Получить Дату Авторизации платежа - * @return string Дата Авторизации платежа + * @return string|null Дата Авторизации платежа */ - public function getPaymentDate(): string; + public function getPaymentDate(): ?string; /** * Установить Дату Авторизации платежа @@ -32,9 +32,9 @@ public function setPaymentDate(string $paymentDate): self; /** * Получить Дату Списания денежных средств - * @return string Дата Списания денежных средств + * @return string|null Дата Списания денежных средств */ - public function getCaptureDate(): string; + public function getCaptureDate(): ?string; /** * Установить Дату Списания денежных средств @@ -44,9 +44,9 @@ public function getCaptureDate(): string; public function setCaptureDate(string $captureDate): self; /** - * @return string + * @return string|null */ - public function getCardProgramName(): string; + public function getCardProgramName(): ?string; /** * @param string $cardProgramName @@ -56,9 +56,9 @@ public function setCardProgramName(string $cardProgramName): self; /** * Получить Код Авторизации - * @return string Код Авторизации + * @return string|null Код Авторизации */ - public function getAuthCode(): string; + public function getAuthCode(): ?string; /** * Установить Код Авторизации @@ -69,9 +69,9 @@ public function setAuthCode(string $authCode): self; /** * Получить Идентификатор марчанта (Merchant ID) - * @return string Идентификатор марчанта (Merchant ID) + * @return string|null Идентификатор марчанта (Merchant ID) */ - public function getMerchantId(): string; + public function getMerchantId(): ?string; /** * Установить Идентификатор марчанта (Merchant ID) @@ -81,9 +81,9 @@ public function getMerchantId(): string; public function setMerchantId(string $merchantId): self; /** - * @return int + * @return int|null */ - public function getRrn(): int; + public function getRrn(): ?int; /** * @param int $rrn @@ -92,9 +92,9 @@ public function getRrn(): int; public function setRrn(int $rrn): self; /** - * @return string + * @return string|null */ - public function getInstallmentsNumber(): string; + public function getInstallmentsNumber(): ?string; /** * @param string $installmentsNumber @@ -118,9 +118,9 @@ public function getCardDetails() : CardDetailsInterface; /** * Получить Краткую запись Названия Банка Плательщика - * @return string Краткая запись Названия Банка Плательщика + * @return string|null Краткая запись Названия Банка Плательщика */ - public function getPaymentBankShortName(): string; + public function getPaymentBankShortName(): ?string; /** * Установить Краткую запись Названия Банка Плательщика @@ -131,9 +131,9 @@ public function setPaymentBankShortName(string $paymentBankShortName): self; /** * Получить Тип Сервиса Процессинга - * @return string Тип Сервиса Процессинга + * @return string|null Тип Сервиса Процессинга */ - public function getServiceProcessingType(): string; + public function getServiceProcessingType(): ?string; /** * Установить Тип Сервиса Процессинга From 9b5a0050a206a7be5bdd9e45185618bc704f7af6 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Fri, 18 Jul 2025 13:15:57 +0300 Subject: [PATCH 12/25] DRAFT: Webhook: Billing FIX --- src/Billing.php | 6 +++--- src/BillingInterface.php | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Billing.php b/src/Billing.php index 3f4c0a9..879b9d4 100644 --- a/src/Billing.php +++ b/src/Billing.php @@ -47,7 +47,7 @@ class Billing implements BillingInterface private ?IdentityDocumentInterface $identityDocument = null; /** @inheritDoc */ - public function getFirstName(): string + public function getFirstName(): ?string { return $this->firstName; } @@ -60,7 +60,7 @@ public function setFirstName(string $firstName): self } /** @inheritDoc */ - public function getLastName(): string + public function getLastName(): ?string { return $this->lastName; } @@ -218,7 +218,7 @@ public function getIdentityDocument(): ?IdentityDocumentInterface } /** @inheritdoc */ - public function getType(): string + public function getType(): ?string { return $this->type; } diff --git a/src/BillingInterface.php b/src/BillingInterface.php index 45f1aa3..18d6fd3 100644 --- a/src/BillingInterface.php +++ b/src/BillingInterface.php @@ -13,9 +13,9 @@ public function setFirstName(string $firstName) : self; /** * Получить Имя - * @return string + * @return string|null */ - public function getFirstName() : string; + public function getFirstName() : ?string; /** * Установить Фамилия @@ -26,9 +26,9 @@ public function setLastName(string $lastName) : self; /** * Получить Фамилия - * @return string + * @return string|null */ - public function getLastName() : string; + public function getLastName() : ?string; /** * Установить Email @@ -52,7 +52,7 @@ public function setPhone(string $phone) : self; /** * Получить Номер Телефона - * @return string + * @return string|null */ public function getPhone() : ?string; @@ -65,7 +65,7 @@ public function setCountryCode(string $countryCode) : self; /** * Получить Код Страны - * @return string + * @return string|null */ public function getCountryCode() : ?string; @@ -78,7 +78,7 @@ public function setCity(string $city) : self; /** * Получить Город - * @return string + * @return string|null */ public function getCity() : ?string; @@ -91,7 +91,7 @@ public function setState(string $state) : self; /** * Получить Регион - * @return string + * @return string|null */ public function getState() : ?string; @@ -176,9 +176,9 @@ public function getIdentityDocument(): ?IdentityDocumentInterface; /** * Получить тип биллинга - * @return string + * @return string|null */ - public function getType(): string; + public function getType(): ?string; /** * Установить тип биллинга From c843103a9d5939dd26b2f34a06b0aba33ae248d4 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Fri, 18 Jul 2025 13:29:46 +0300 Subject: [PATCH 13/25] DRAFT: Webhook: Details FIX --- src/DetailsInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DetailsInterface.php b/src/DetailsInterface.php index 175e52f..28c1baa 100644 --- a/src/DetailsInterface.php +++ b/src/DetailsInterface.php @@ -13,7 +13,7 @@ public function setNumber(string $number) : self; /** * Получить Номер карты - * @return string Номер карты + * @return string|null Номер карты */ - public function getNumber() : string; + public function getNumber() : ?string; } From 0dea1c9aef79d5acab1154e6da2bf74696be1561 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Fri, 18 Jul 2025 15:27:43 +0300 Subject: [PATCH 14/25] DRAFT: Webhook: non required fields --- src/CardDetails.php | 22 +++++++++++----------- src/OrderData.php | 22 +++++++++++----------- src/PaymentResult.php | 22 +++++++++++----------- src/Webhook.php | 10 +++++----- src/WebhookAuthorization.php | 2 +- src/WebhookStoredCredentials.php | 8 ++++---- src/WebhookStoredCredentialsInterface.php | 8 ++++---- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/CardDetails.php b/src/CardDetails.php index a09616e..1f0515e 100644 --- a/src/CardDetails.php +++ b/src/CardDetails.php @@ -55,7 +55,7 @@ public function setNumber(string $number): self /** @inheritDoc */ public function getExpiryMonth(): ?int { - return $this->expiryMonth; + return $this->expiryMonth ?? null; } /** @inheritDoc */ @@ -68,7 +68,7 @@ public function setExpiryMonth(int $expiryMonth): self /** @inheritDoc */ public function getYear(): ?int { - return $this->year; + return $this->year ?? null; } /** @inheritDoc */ @@ -85,7 +85,7 @@ public function setYear(int $year): self /** @inheritDoc */ public function getExpiryYear(): ?int { - return $this->expiryYear; + return $this->expiryYear ?? null; } /** @inheritDoc */ @@ -98,7 +98,7 @@ public function setExpiryYear(int $expiryYear): self /** @inheritDoc */ public function getCvv(): ?string { - return $this->cvv; + return $this->cvv ?? null; } /** @inheritDoc */ @@ -111,7 +111,7 @@ public function setCvv(string $cvv): self /** @inheritDoc */ public function getOwner(): ?string { - return $this->owner; + return $this->owner ?? null; } /** @inheritDoc */ @@ -124,7 +124,7 @@ public function setOwner(string $owner): self /** @inheritDoc */ public function getTimeSpentTypingNumber(): ?int { - return $this->timeSpentTypingNumber; + return $this->timeSpentTypingNumber ?? null; } /** @inheritDoc */ @@ -137,7 +137,7 @@ public function setTimeSpentTypingNumber(int $timeSpentTypingNumber): self /** @inheritDoc */ public function getTimeSpentTypingOwner(): ?int { - return $this->timeSpentTypingOwner; + return $this->timeSpentTypingOwner ?? null; } /** @inheritDoc */ @@ -150,7 +150,7 @@ public function setTimeSpentTypingOwner(int $timeSpentTypingOwner): self /** @inheritDoc */ public function getBin(): ?int { - return $this->bin; + return $this->bin ?? null; } /** @inheritDoc */ @@ -163,7 +163,7 @@ public function setBin(int $bin): self /** @inheritDoc */ public function getPan(): ?string { - return $this->pan; + return $this->pan ?? null; } /** @inheritDoc */ @@ -176,7 +176,7 @@ public function setPan(string $pan): self /** @inheritDoc */ public function getType(): ?string { - return $this->type; + return $this->type ?? null; } /** @inheritDoc */ @@ -189,7 +189,7 @@ public function setType(string $type): self /** @inheritDoc */ public function getCardIssuerBank(): ?string { - return $this->cardIssuerBank; + return $this->cardIssuerBank ?? null; } /** @inheritDoc */ diff --git a/src/OrderData.php b/src/OrderData.php index e62892a..8ebdc96 100644 --- a/src/OrderData.php +++ b/src/OrderData.php @@ -37,7 +37,7 @@ class OrderData implements OrderDataInterface /** @inheritDoc */ public function getOrderDate(): ?string { - return $this->orderDate; + return $this->orderDate ?? null; } /** @inheritDoc */ @@ -59,7 +59,7 @@ public function setYpmnPaymentReference(string $ypmnPaymentReference): self /** @inheritDoc */ public function getYpmnPaymentReference(): ?string { - return $this->payUPaymentReference; + return $this->payUPaymentReference ?? null; } /** @inheritDoc */ @@ -73,13 +73,13 @@ public function setPayUPaymentReference(string $payUPaymentReference): self /** @inheritDoc */ public function getPayUPaymentReference(): ?string { - return $this->payUPaymentReference; + return $this->payUPaymentReference ?? null; } /** @inheritDoc */ public function getMerchantPaymentReference(): ?string { - return $this->merchantPaymentReference; + return $this->merchantPaymentReference ?? null; } /** @inheritDoc */ @@ -93,7 +93,7 @@ public function setMerchantPaymentReference(string $merchantPaymentReference): s /** @inheritDoc */ public function getStatus(): ?string { - return $this->status; + return $this->status ?? null; } /** @inheritDoc */ @@ -107,7 +107,7 @@ public function setStatus(string $status): self /** @inheritDoc */ public function getCurrency(): ?string { - return $this->currency; + return $this->currency ?? null; } /** @inheritDoc */ @@ -121,7 +121,7 @@ public function setCurrency(string $currency): self /** @inheritDoc */ public function getAmount(): ?float { - return $this->amount; + return $this->amount ?? null; } /** @inheritDoc */ @@ -135,7 +135,7 @@ public function setAmount(float $amount): self /** @inheritDoc */ public function getCommission(): ?float { - return $this->commission; + return $this->commission ?? null; } /** @inheritDoc */ @@ -151,7 +151,7 @@ public function setCommission(float $commission): self /** @inheritDoc */ public function getRefundRequestId(): ?string { - return $this->refundRequestId; + return $this->refundRequestId ?? null; } /** @inheritDoc */ @@ -164,7 +164,7 @@ public function setRefundRequestId(string $refundRequestId): self /** @inheritDoc */ public function getLoyaltyPointsAmount(): ?int { - return $this->loyaltyPointsAmount; + return $this->loyaltyPointsAmount ?? null; } /** @inheritDoc */ @@ -178,7 +178,7 @@ public function setLoyaltyPointsAmount(int $loyaltyPointsAmount): self /** @inheritDoc */ public function getLoyaltyPointsDetails(): ?array { - return $this->loyaltyPointsDetails; + return $this->loyaltyPointsDetails ?? null; } /** @inheritDoc */ diff --git a/src/PaymentResult.php b/src/PaymentResult.php index a6d4c24..9cc21fc 100644 --- a/src/PaymentResult.php +++ b/src/PaymentResult.php @@ -43,7 +43,7 @@ class PaymentResult implements PaymentResultInterface /** @inheritDoc */ public function getPaymentMethod(): ?string { - return $this->paymentMethod; + return $this->paymentMethod ?? null; } /** @inheritDoc */ @@ -56,7 +56,7 @@ public function setPaymentMethod(string $paymentMethod): self /** @inheritDoc */ public function getPaymentDate(): ?string { - return $this->paymentDate; + return $this->paymentDate ?? null; } /** @inheritDoc */ @@ -69,7 +69,7 @@ public function setPaymentDate(string $paymentDate): self /** @inheritDoc */ public function getCaptureDate(): ?string { - return $this->captureDate; + return $this->captureDate ?? null; } /** @inheritDoc */ @@ -82,7 +82,7 @@ public function setCaptureDate(string $captureDate): self /** @inheritDoc */ public function getCardProgramName(): ?string { - return $this->cardProgramName; + return $this->cardProgramName ?? null; } /** @inheritDoc */ @@ -95,7 +95,7 @@ public function setCardProgramName(string $cardProgramName): self /** @inheritDoc */ public function getAuthCode(): ?string { - return $this->authCode; + return $this->authCode ?? null; } /** @inheritDoc */ @@ -108,7 +108,7 @@ public function setAuthCode(string $authCode): self /** @inheritDoc */ public function getMerchantId(): ?string { - return $this->merchantId; + return $this->merchantId ?? null; } /** @inheritDoc */ @@ -121,7 +121,7 @@ public function setMerchantId(string $merchantId): self /** @inheritDoc */ public function getRrn(): ?int { - return $this->rrn; + return $this->rrn ?? null; } /** @inheritDoc */ @@ -134,7 +134,7 @@ public function setRrn(int $rrn): self /** @inheritDoc */ public function getInstallmentsNumber(): ?string { - return $this->installmentsNumber; + return $this->installmentsNumber ?? null; } /** @inheritDoc */ @@ -154,13 +154,13 @@ public function setCardDetails(CardDetailsInterface $cardDetails): self /** @inheritDoc */ public function getCardDetails() : CardDetailsInterface { - return $this->cardDetails; + return $this->cardDetails ?? new CardDetails(); } /** @inheritDoc */ public function getPaymentBankShortName(): ?string { - return $this->paymentBankShortName; + return $this->paymentBankShortName ?? null; } /** @inheritDoc */ @@ -173,7 +173,7 @@ public function setPaymentBankShortName(string $paymentBankShortName): self /** @inheritDoc */ public function getServiceProcessingType(): ?string { - return $this->serviceProcessingType; + return $this->serviceProcessingType ?? null; } /** @inheritDoc */ diff --git a/src/Webhook.php b/src/Webhook.php index 7b22a81..4e29cc1 100644 --- a/src/Webhook.php +++ b/src/Webhook.php @@ -50,7 +50,7 @@ public function catchJsonRequest(): self } if (!empty($request['orderData']['amount'])) { - $this->orderData->setAmount($request['orderData']['amount']); + $this->orderData->setAmount((float) $request['orderData']['amount']); } if (isset($request['orderData']['commission'])) { @@ -69,7 +69,7 @@ public function catchJsonRequest(): self $cardDetails = new CardDetails; if (!empty($request['paymentResult']['cardDetails']['bin'])) { - $cardDetails->setBin($request['paymentResult']['cardDetails']['bin']); + $cardDetails->setBin((int) $request['paymentResult']['cardDetails']['bin']); } if (!empty($request['paymentResult']['cardDetails']['owner'])) { @@ -213,7 +213,7 @@ public function catchJsonRequest(): self /** @inheritDoc */ public function getPaymentResult(): PaymentResultInterface { - return $this->paymentResult; + return $this->paymentResult ?? new PaymentResult(); } /** @inheritDoc */ @@ -226,7 +226,7 @@ public function setPaymentResult(PaymentResultInterface $paymentResult): self /** @inheritDoc */ public function getOrderData(): OrderDataInterface { - return $this->orderData; + return $this->orderData ?? new OrderData(); } /** @inheritDoc */ @@ -246,6 +246,6 @@ public function setAuthorization(WebhookAuthorizationInterface $authorization): /** @inheritDoc */ public function getAuthorization(): WebhookAuthorizationInterface { - return $this->authorization; + return $this->authorization ?? new WebhookAuthorization(); } } diff --git a/src/WebhookAuthorization.php b/src/WebhookAuthorization.php index 2f80b2a..f70eb17 100644 --- a/src/WebhookAuthorization.php +++ b/src/WebhookAuthorization.php @@ -16,6 +16,6 @@ public function setStoredCredentials(WebhookStoredCredentialsInterface $storedCr /** @inheritDoc */ public function getStoredCredentials(): WebhookStoredCredentialsInterface { - return $this->storedCredentials; + return $this->storedCredentials ?? new WebhookStoredCredentials(); } } diff --git a/src/WebhookStoredCredentials.php b/src/WebhookStoredCredentials.php index 99afbb7..4112ffb 100644 --- a/src/WebhookStoredCredentials.php +++ b/src/WebhookStoredCredentials.php @@ -13,9 +13,9 @@ public function setYpmnBindingId(string $ypmnBindingId): self return $this; } - public function getYpmnBindingId(): string + public function getYpmnBindingId(): ?string { - return $this->ypmnBindingId; + return $this->ypmnBindingId ?? null; } public function setUseId(string $useId): self @@ -24,8 +24,8 @@ public function setUseId(string $useId): self return $this; } - public function getUseId(): string + public function getUseId(): ?string { - return $this->useId; + return $this->useId ?? null; } } diff --git a/src/WebhookStoredCredentialsInterface.php b/src/WebhookStoredCredentialsInterface.php index 63a9eb3..2cfc70b 100644 --- a/src/WebhookStoredCredentialsInterface.php +++ b/src/WebhookStoredCredentialsInterface.php @@ -13,9 +13,9 @@ public function setYpmnBindingId(string $ypmnBindingId): self; /** * Получить токен подписки SberPay - * @return string + * @return string|null */ - public function getYpmnBindingId(): string; + public function getYpmnBindingId(): ?string; /** * Установить идентификатор первоначальной операции @@ -26,7 +26,7 @@ public function setUseId(string $useId): self; /** * Получить идентификатор первоначальной операции - * @return string + * @return string|null */ - public function getUseId(): string; + public function getUseId(): ?string; } From 286b2742577bfeb1d48e8e612b20fcea8b60a217 Mon Sep 17 00:00:00 2001 From: Pavel Chugunov Date: Fri, 18 Jul 2025 15:44:15 +0300 Subject: [PATCH 15/25] DRAFT: Webhook: Billing --- src/Billing.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Billing.php b/src/Billing.php index 879b9d4..faa2594 100644 --- a/src/Billing.php +++ b/src/Billing.php @@ -49,7 +49,7 @@ class Billing implements BillingInterface /** @inheritDoc */ public function getFirstName(): ?string { - return $this->firstName; + return $this->firstName ?? null; } /** @inheritDoc */ @@ -62,7 +62,7 @@ public function setFirstName(string $firstName): self /** @inheritDoc */ public function getLastName(): ?string { - return $this->lastName; + return $this->lastName ?? null; } /** @inheritDoc */ @@ -75,7 +75,7 @@ public function setLastName(string $lastName): self /** @inheritDoc */ public function getEmail(): ?string { - return $this->email; + return $this->email ?? null; } /** @inheritDoc */ From f32e04d6173504217be53806430032023153dfe1 Mon Sep 17 00:00:00 2001 From: Oleg Pavlov Date: Mon, 21 Jul 2025 13:04:11 +0300 Subject: [PATCH 16/25] ADDED: CI/CD pipeline files --- .gitlab-ci.yml | 11 +++++++++ ci/code-approve-reset.yml | 7 ++++++ ci/mr-approve-check.yml | 47 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 ci/code-approve-reset.yml create mode 100644 ci/mr-approve-check.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..7568f7d --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,11 @@ +# Main project pipeline +variables: + GIT_STRATEGY: none + +stages: + - code-approve-reset + - mr-approve-check + +include: + - local: 'ci/code-approve-reset.yml' + - local: 'ci/mr-approve-check.yml' diff --git a/ci/code-approve-reset.yml b/ci/code-approve-reset.yml new file mode 100644 index 0000000..776db79 --- /dev/null +++ b/ci/code-approve-reset.yml @@ -0,0 +1,7 @@ +## Removes all previously applied approvers in MR +clear_code_approved: + stage: code-approve-reset + rules: + - if: $CI_MERGE_REQUEST_ID + script: + - 'curl -X PUT -H "PRIVATE-TOKEN: $REMOVE_APPROVE_TOKEN" -H "Content-Type: application/json" "http://gtl01.dev.ruo.payudc.net/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/reset_approvals"' diff --git a/ci/mr-approve-check.yml b/ci/mr-approve-check.yml new file mode 100644 index 0000000..492ced9 --- /dev/null +++ b/ci/mr-approve-check.yml @@ -0,0 +1,47 @@ +## Blocks the pipeline if MR is not approved by any +## of defined gitlab users (ALLOWED_APPROVERS) +mr-approve-check: + stage: mr-approve-check + needs: ["clear_code_approved"] + tags: + - mr-check + variables: + TARGET_BRANCH: main + script: + - | + # Fetch the merge request ID from the environment variable + # $merge_request_iid variable is set in gitlab webhook payload + if [[ "$CI_PIPELINE_SOURCE" == "trigger" ]]; then + MR_ID=${merge_request_iid} + else + MR_ID=${CI_MERGE_REQUEST_IID} + fi + + # Check if MR_ID is set + if [ -z "$MR_ID" ]; then + echo "This job is not running in a merge request context." + exit 0 + fi + + # Get the list of approvals for the merge request + APPROVALS=$(curl --silent -H "PRIVATE-TOKEN: $REMOVE_APPROVE_TOKEN" -H "Content-Type: application/json" "http://gtl01.dev.ruo.payudc.net/api/v4/projects/$CI_PROJECT_ID/merge_requests/$MR_ID/approvals") + + # Define a list of allowed approvers + ALLOWED_APPROVERS=("oleg.pavlov" "alexander.viktorchik" "alexey.babak" "roman.zimin") + + # Check if any of the allowed users have approved the merge request + APPROVED=false + for USER in "${ALLOWED_APPROVERS[@]}"; do + if echo "$APPROVALS" | grep -q "$USER"; then + APPROVED=true + echo "Merge request approved by allowed user: $USER." + break + fi + done + + if [ "$APPROVED" = false ]; then + echo "Merge request not approved by any allowed users. Blocking the merge request." + exit 1 + fi + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $TARGET_BRANCH' From b57d2f5917d09968ba45664bad1b2cc611a50ae7 Mon Sep 17 00:00:00 2001 From: Oleg Pavlov Date: Mon, 21 Jul 2025 13:20:02 +0300 Subject: [PATCH 17/25] FIX: some debugging --- ci/code-approve-reset.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci/code-approve-reset.yml b/ci/code-approve-reset.yml index 776db79..01400d8 100644 --- a/ci/code-approve-reset.yml +++ b/ci/code-approve-reset.yml @@ -4,4 +4,7 @@ clear_code_approved: rules: - if: $CI_MERGE_REQUEST_ID script: + - echo "$REMOVE_APPROVE_TOKEN" + - echo "$CI_PROJECT_ID" + - echo "$CI_MERGE_REQUEST_IID" - 'curl -X PUT -H "PRIVATE-TOKEN: $REMOVE_APPROVE_TOKEN" -H "Content-Type: application/json" "http://gtl01.dev.ruo.payudc.net/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/reset_approvals"' From d9f63eb2846709a6224eb1fe61558a0ae6f8eb3e Mon Sep 17 00:00:00 2001 From: Oleg Pavlov Date: Mon, 21 Jul 2025 13:39:23 +0300 Subject: [PATCH 18/25] FIX: Curl error stops the pipeline --- ci/code-approve-reset.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/code-approve-reset.yml b/ci/code-approve-reset.yml index 01400d8..47eeb45 100644 --- a/ci/code-approve-reset.yml +++ b/ci/code-approve-reset.yml @@ -7,4 +7,4 @@ clear_code_approved: - echo "$REMOVE_APPROVE_TOKEN" - echo "$CI_PROJECT_ID" - echo "$CI_MERGE_REQUEST_IID" - - 'curl -X PUT -H "PRIVATE-TOKEN: $REMOVE_APPROVE_TOKEN" -H "Content-Type: application/json" "http://gtl01.dev.ruo.payudc.net/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/reset_approvals"' + - 'curl -f -X PUT -H "PRIVATE-TOKEN: $REMOVE_APPROVE_TOKEN" -H "Content-Type: application/json" "http://gtl01.dev.ruo.payudc.net/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/reset_approvals"' From 3adee6229e507dd5f0851aa99d5b914ab21aa5ce Mon Sep 17 00:00:00 2001 From: Oleg Pavlov Date: Mon, 21 Jul 2025 13:42:56 +0300 Subject: [PATCH 19/25] FIX: oleg.pavlov removed from approvers list --- ci/code-approve-reset.yml | 3 --- ci/mr-approve-check.yml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ci/code-approve-reset.yml b/ci/code-approve-reset.yml index 47eeb45..e994d37 100644 --- a/ci/code-approve-reset.yml +++ b/ci/code-approve-reset.yml @@ -4,7 +4,4 @@ clear_code_approved: rules: - if: $CI_MERGE_REQUEST_ID script: - - echo "$REMOVE_APPROVE_TOKEN" - - echo "$CI_PROJECT_ID" - - echo "$CI_MERGE_REQUEST_IID" - 'curl -f -X PUT -H "PRIVATE-TOKEN: $REMOVE_APPROVE_TOKEN" -H "Content-Type: application/json" "http://gtl01.dev.ruo.payudc.net/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/reset_approvals"' diff --git a/ci/mr-approve-check.yml b/ci/mr-approve-check.yml index 492ced9..e718b1c 100644 --- a/ci/mr-approve-check.yml +++ b/ci/mr-approve-check.yml @@ -27,7 +27,7 @@ mr-approve-check: APPROVALS=$(curl --silent -H "PRIVATE-TOKEN: $REMOVE_APPROVE_TOKEN" -H "Content-Type: application/json" "http://gtl01.dev.ruo.payudc.net/api/v4/projects/$CI_PROJECT_ID/merge_requests/$MR_ID/approvals") # Define a list of allowed approvers - ALLOWED_APPROVERS=("oleg.pavlov" "alexander.viktorchik" "alexey.babak" "roman.zimin") + ALLOWED_APPROVERS=("alexander.viktorchik" "alexey.babak" "roman.zimin") # Check if any of the allowed users have approved the merge request APPROVED=false From 43d1b3cf35bbbf53e5e58f99726b5d56748e8b4f Mon Sep 17 00:00:00 2001 From: Oleg Pavlov Date: Mon, 21 Jul 2025 13:46:06 +0300 Subject: [PATCH 20/25] FIX: Empty. Final pipeline run check --- ci/mr-approve-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/mr-approve-check.yml b/ci/mr-approve-check.yml index e718b1c..592fbb8 100644 --- a/ci/mr-approve-check.yml +++ b/ci/mr-approve-check.yml @@ -1,5 +1,6 @@ ## Blocks the pipeline if MR is not approved by any ## of defined gitlab users (ALLOWED_APPROVERS) + mr-approve-check: stage: mr-approve-check needs: ["clear_code_approved"] From 2325efe972eaaa07ebd089512ce0a80aa561cc23 Mon Sep 17 00:00:00 2001 From: Alexey Babak Date: Wed, 23 Jul 2025 17:53:19 +0300 Subject: [PATCH 21/25] readme update --- README.md | 151 ++++++++++++++++++++++++++++++++++++------------ screenshot2.jpg | Bin 0 -> 77365 bytes 2 files changed, 115 insertions(+), 36 deletions(-) create mode 100644 screenshot2.jpg diff --git a/README.md b/README.md index fc3ef2d..d97333d 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,168 @@ # «Твои Платежи»: Интеграция на PHP -Готовая библиотека + подробные примеры с комментариями. Требования: [PHP 7.4 и выше](https://github.com/yourpayments/php-api-client/blob/main/composer.json) +Готовая библиотека PHP API Client для YourPayments + +подробные примеры с комментариями ![](https://repository-images.githubusercontent.com/638835276/2067d028-b541-4355-b069-3c12c8a28042) -[Пакет Composer](https://packagist.org/packages/yourpayments/php-api-client) может -использоваться с любыми фреймворками, платформами и CMS, включая, но не ограничиваясь: Laravel, Bitrix, Wordpress, Yii, Symfony, и др. +## Оглавление +- [Описание](#описание) +- [Требования](#требования) +- [Установка](#установка) + - [Запуск встроенного сервера](#запуск-встроенного-сервера) + - [Запуск в контейнере docker](#запуск-в-контейнере-docker) +- [Примеры использования](#примеры-использования) + - [Начало работы: настройка интеграции](#1-начало-работы--настройка-интеграции) + - [Приём платежей](#2-приём-платежей) + - [Подписки](#3-подписки) (рекуррентные платежи) + - [Токенизация](#4-токенизация) (запомнить данные плательщика, чтобы не запрашивать и не вводить их повторно) + - [Отчёты и статусы платежей](#5-отчёты) + - [Возврат средств плательщику](#6-возврат-средств-плательщику-refund) (refunds, рефанды) + - [Выплаты](#7-выплаты) (отправка денег по номеру карты или телефона) + - [Подключение продавцов](#8-подключение-продавцов) + - [Обработка вебхуков](#9-обработка-вебхуков) + - [Страница после оплаты](#10-страница-после-оплаты) + - [Безопасные поля](#11-безопасные-поля--secure-fields-) (отдельный вид интеграции карточной формы) + - [Обработка ошибок](#12-обработка-ошибок) +- [Обновление библиотеки](#обновление) +- [Поддержка и контакты](#ссылки-поддержка-и-контакты) + +## Описание +`yourpayments/php-api-client` — это PHP библиотека для быстрой и удобной интеграции с платежным шлюзом YourPayments. +С её помощью можно принимать оплаты и создавать выплаты, получать отчёты, делать возвраты и работать с подпискам. + +Библиотека ориентирована на простое и надёжное использование, подходит как для опытных, так и для начинающих разработчиков. + +Особенностями этой системы являются: +- мульти-эквайринг (работа сразу со многими банками) +- поддержка сплитования (много получателей платежа в одном чеке) +- безопасность и точность расчётов + +Библиотека содержит: +- Сам клиент API +- Простой встроенный сервер с примерами +- Описание контейнера для запуска в Docker + +--- + +## Требования + +- PHP 7.4 и выше (рекомендуется PHP 8.1+) +- Расширения PHP: `curl`, `json`, `mbstring` +- Рекомендуется: Composer для управления зависимостями + +--- + +## Установка +Установка с [пакета composer](https://packagist.org/packages/yourpayments/php-api-client) -- самый простой и рекомендуемый способ: -## Установка и обновление за 1 минуту ```shell composer require yourpayments/php-api-client ``` + +Если на вашем проекте нет Composer, +склонируйте или скачайте, а затем подключите файлы этого репозитория, +([пример](src/Examples/autoload.php)) + +### Запуск встроенного сервера ```shell -composer update yourpayments/php-api-client +php -S localhost:8081 index.php ``` -(если на вашем проекте нет composer, склонируйте или скачайте, а затем подключите файлы этого репозитория ([файл автозагрузки](src/Examples/autoload.php))) -## Запуск в контейнере docker +После запуска по адресу http://localhost:8081 будут доступны интерактивные примеры в следующем виде: +![скриншот встроенного сервера с примерами](/screenshot2.jpg) + + +### Запуск в контейнере docker Создайте и запустите docker контейнер следующей командой: ```shell docker compose up ``` -либо в фоновом режиме командой: + +Либо в фоновом режиме командой: ```shell docker compose up --detach ``` После выполнения сервис с документацией и примерами будет доступен по адресу http://localhost:8080/ + +--- -## Примеры с комментариями на русском языке: +## Примеры использования: ##### 1. [Начало работы: настройка интеграции](src/Examples/start.php) -##### 2. Платежи +##### 2. Приём платежей 1. [Cамый простой платёж](src/Examples/simpleGetPaymentLink.php) 2. [Подробный платёж](src/Examples/getPaymentLink.php) +4. [Платёж со сплитом (разделением платежа для нескольких получателей)](src/Examples/getPaymentLinkMarketplace.php) 3. [Платёж через СБП (Систему Быстрых Платежей)](src/Examples/getFasterPayment.php) -4. [Платёж со сплитом (разделением платежа)](src/Examples/getPaymentLinkMarketplace.php) -5. [Списание средств](src/Examples/paymentCapture.php) +5. [Списание средств (только для двустадийной оплаты)](src/Examples/paymentCapture.php) -##### 3. Подписки СБП +##### 3. Подписки +Рекуррентные платежи 1. [Создание подписки СБП](src/Examples/getBindingFasterPayment.php) 2. [Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) - -##### 4. Подписки SberPay, T-Pay, Картой не РФ -1. [Создание подписки](src/Examples/getBindingPays.php) -2. [Оплата по подписке](src/Examples/paymentByBindingPays.php) +1. [Создание подписки SberPay, T-Pay, Картой не РФ](src/Examples/getBindingPays.php) +2. [Оплата по подписке SberPay, T-Pay, Картой не РФ](src/Examples/paymentByBindingPays.php) -##### 5. Токенизация карты (чтобы запомнить карту клиента и не вводить повторно) +##### 4. Токенизация +Запомнить данные клиента, чтобы не запрашивать и не вводить их повторно 1. [Создание платёжного токена ](src/Examples/getToken.php) 2. [Оплата токеном](src/Examples/paymentByToken.php) -##### 6. Отчёты +##### 5. Отчёты 1. [Проверка статуса платежа](src/Examples/paymentGetStatus.php) 2. [Запрос детального отчета по заказу](src/Examples/getReportOrderDetails.php) 3. [Запрос быстрого отчёта по заказам для сверки](src/Examples/getReportOrder.php) 4. [Запрос отчёта по заказам](src/Examples/getReportGeneral.php) 5. [Запрос отчёта в виде графика](src/Examples/getReportChart.php) -##### 7. Возврат средств плательщику (Refund) +##### 6. Возврат средств плательщику (Refund) 1. [Возврат средств](src/Examples/paymentRefund.php) 2. [Возврат средств со сплитом (разделением платежа)](src/Examples/paymentRefundMarketplace.php) -##### 8. Выплаты +##### 7. Выплаты 1. [Выплаты на банковские карты](src/Examples/payoutCreate.php) 2. [Запрос баланса для выплаты](src/Examples/payoutGetBalance.php) -##### 9. [Безопасные поля (Secure fields)](src/Examples/secureFields.php) -2. [Создание сессии](src/Examples/getSession.php) -3. [Оплата одноразовым токеном](src/Examples/oneTimeTokenPayment.php) - -##### 10. [Страница после оплаты](src/Examples/returnPage.php) - -##### 11. Подключение продавцов (сабмерчантов маркетплейсов) +##### 8. Подключение продавцов +Добавление сабмерчантов маркетплейсов по API 1. [Подключение продавца-юридического лица (отправка анкеты)](src/Examples/qstCreateOrg.php) 2. [Подключение продавца-ИП (отправка анкеты)](src/Examples/qstCreateIp.php) 3. [Получение статуса анкеты](src/Examples/qstStatus.php) 4. [Печать анкеты](src/Examples/qstPrint.php) 5. [Список анкет](src/Examples/qstList.php) -##### 12. Виджет -- [Подключение виджета](src/Examples/getWidget.php) +##### 9. [Обработка вебхуков](src/Examples/webhookProcessing.php) +Вебхуки -- HTTP запросы, оповещающие ваш сервер о событиях (успешные и неуспешные оплаты, списания) + +##### 10. [Страница после оплаты](src/Examples/returnPage.php) + +##### 11. [Безопасные поля (Secure fields)](src/Examples/secureFields.php) +2. [Создание сессии](src/Examples/getSession.php) +3. [Оплата одноразовым токеном](src/Examples/oneTimeTokenPayment.php) + +##### 12. Обработка ошибок +Библиотека выбрасывает один вид исключений: [Ypmn\PaymentException](/src/PaymentException.php). + +Пример перехвата исключения можно посмотреть в примере: [Cамый простой платёж](src/Examples/simpleGetPaymentLink.php) + +--- + +## Обновление + +Обновления библиотеки позволяют быстро исправлять ошибки и получать доступ к новым функциям + +```shell +composer update yourpayments/php-api-client +``` + +--- -## Ссылки -- [НКО «Твои Платежи»](https://YPMN.ru/) -- [Докуметация API](https://ypmn.ru/ru/documentation/) -- [Тестовые банковские карты](https://ypmn.ru/ru/documentation/#tag/testing) +## Ссылки, поддержка и контакты +- [НКО «Твои Платежи»](https://YPMN.ru/?utm_source=php-api-client) +- [Докуметация API](https://ypmn.ru/ru/documentation/?utm_source=php-api-client) +- [Тестовые банковские карты](https://ypmn.ru/ru/documentation/?utm_source=php-api-client#tag/testing) +- [FAQ, ответы на частые вопросы](https://ypmn.ru/ru/support/?utm_source=php-api-client) - [Задать вопрос или сообщить о проблеме](https://github.com/yourpayments/php-api-client/issues/new) -------------- -🟢 [«Твои Платежи»](https://YPMN.ru/ "Платёжная система для сайтов, платформ и приложений") -- финтех-составляющая для сайтов, платформ и приложений +--- +🟢 [«Твои Платежи»](https://YPMN.ru/ "Платёжная система для сайтов, платформ и приложений") -- финтех для сайтов, платформ и приложений diff --git a/screenshot2.jpg b/screenshot2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2c5e9b480984ae9b00c19234156794ab0784218f GIT binary patch literal 77365 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%0A+*kS2Y<_l@SfFw>bfBFBCT?a|bf8Um-cb z_uiFvoI1;ymEGyA566au?Q)P)k#)0s!0o8j8kxVfb*Q$NI5;Rk1Lbzi5T>KQqQp2G zCytL{X1UZ8H!vjd3xJd?R$AMVWD>%1${5CkM{Z$h@VWVITq~YrfGu`LnsGdxOi-76 z!jEY~+}tANfp= zHX{+j&+E0+zN_~`hO&|%c#?}IVk4Gzg0l)*?BtU2{N}ejgQ5Z_II+!1UrD1o*|Cs; z1sd}{&KA$=SZr!Htu$$?zgu+BrbcsQ^PF%Ci^?WO z7&-ecef_>v7iasej&;DlY1sjrb7eT2)5#;k&Y5JXA2d)Sgq9~Gv!6KHAvbho!}1RHu;C5 z4n+-uWC}p}T{4DmkQ#}L8^ALXPawzJJZscUtBvZY-hsBz@y>1uuf@RxDqs2%SFpOm{ERp|OWLtdQJuH-)`YQUg7 zM|`}##ObsZ1ZlkNlj!qQb*gk6fRnE8n1q$6Yix_f0SBI;#{YQtgZLM~Z|>f@m;Q%! z?=OqlW^;;-xyj>nbz+*p5o+V82oY$@z>-;%gF9>^hAa(tf{k+AfM(G>wON5n#@n5YZ>f^^o?(#o- zzy20pAiSf+4%6-;Pv5_r|9F`63vlZ@!~275MeTXbAA)N8pTeKWgO08H4%buN3L+NOc%eT`6&{C)!$aX~ZuS*=&4L(+s}Lr_p#R`J zM-15E7)h*2vA5hAk}c>5mL{|L_n6~&<4jZOoSq8d=fWc6ie%a6g^<+KV?#m84L!@a z7a*tieP509NqV&EqXrqbO?J1VVhx>#=M?u-ct9$3^Z6J7;;aw^Vf`zC{ z9{bls`8~^t;Aw?8acm(A3=2ob4u2!27}by8QL>M-0CLVrhjoy@--haiEI4w9U%&7W z`5sDjPA_ydecYtv#vyG1^or}L+aljah7azeZdTC8>5@;{Fud(UX$f7-VY9-L;Kwbd zoU}`oscs$V_jfX0-m+8|;MG)-y>9eq5}7okF>#qJS|-sq3%)SAH#;xtrtE>fG=KaB zAZ`eOmMw*w25)%>;FB`bqn#U}*q-VH@ z*R#8(=rg!=epX({K0239$z@0WWb2$OQr^70K06_P;Ix*vlR4e{*!qh$IjXGQ7@MCqBb1it(NkYCLt>NO{TJNLTM z%wTz5P(UBWQ{^}w*4ZJ5nsUpA{-ChRcD3zC@U6PGpF({6IMb{xE`K>k+oOgVjxNJT z7sBU(%wef#TiYo=C&m!-x|5CR**?8gNBOg?Pu-l~oXKqzE$79lB7SbY?tQ+_fADX5 zSzAja`tmpo$b`N%s6YIW%%u(mm>C`H`e}iroH+Ya2y_@e+S}o-n%k*`SfCRgpa~3M z>zX&`z?N`R%$9rG)i1(;%>}@T!$RZVkWQ#mBI<*km<1K62)~sU{DQ)HSIm*A2@K@?MwGkW%?`FxXhPMMUM$?2|gIRuvx`PNVGAd_E)g9wE4bH|4>~qxOG+%`XdSYr;OrQia$`0$1I!W8 zX@3UbSXfZXVt-fuod2r5`ipPj``0c?)MbvYRvmGRs2>G`bNvGZ`aM0&qpH=$vExJ) zb4o*umBhsokIR;Ph9RC#dre6e;nxYpcC<9>0l4fRRXpg4_!1#BJvxh}RB;i4)KLE$ zGA=`%Wrs#=Hbi2*b<)^kgwG0d$U98 zE9hh8ucg{Ip&x!T62x*ptJhpTd|7Z$V*iT-xhH||Npc7^|4RC;i5{Wp-;m-Fn!=bo zbBx{o>Z60s76Mu8J+l%9r|d=0et}-k9IO|3n9IQU3d2*f_$CV48BX%CBQHjI^bUU^ zGqVD(0aK)+ha7#uMLNqEPb$;-n(MaGFk&R7@wE!2~nk5y#)iv zz3WZ@TQ~YlMHNJVlxVTv>QSV#XFg_y$Ivb9#0Zhr`LZELYVc`?wt0ebhCCclS|fVH z#rl&}VApJi=a4LKyJ_%jWnTE<&)OlE4*;wgPDWk>1BfTV4CfZ1v_m?Wkdb2jB+74z zJJYnmyr#XWI^scd#EnO&+eCYe|>Rw2u_bnbqKO{&6m8(mfQ7Oxu3 z-K-u?W}y_!Bx0B84eRjrHfC!H20Q7B#Krp)KIa3SDs#=09%9X&72vNf8yUzZq)f@1 z=OL?Si#&KTS)dg}2BYjQ;jj7yP;X;!0O4bsZfDlgMxW!!J$1FhT3dLk0P_U7D!_mR ziRjFwW*h;jsM{}PY!jjRx@!d&{G+#3B1?stpPaNwMCAt#znkq3?)?b-V{|T>b?o}{ z4UE6mYa<~a+}hwFi@?z1oQJCaJO+Bmz%7uq)|G zBLcpqx{SIY@kLEo!e@#QCdsCIiQ?x{u$645S(mW6ESyZ7MX@#S)H_WTMV;Q>m^{EH z_z0vKhB~&XPi1VDWtZ-=BtBFt-^867pAm=Cu}-9QeZa7*0Jl-+>%hrIY=fwYbg5Zm zCL*h$V+xmJ)oG0Gg!C!V>L!)H(iv^D*wI*FW{|f_96jfbnBgE~-Xd(RA3*NYv)qJi z^A7VZJBzR`70`Ppy-N?e+ADqraOKZ8!raCgrQpJ&WJ79>B|=4f;ugYRvl8JpnImX?iVB0A|@biU0ON!&y(ixA6&d9mnJRB;~qmE80|sy7TfWT3jaI>e$WpD1%AN~j}WC6K?bIYTjEV~D?E+Ie91 z)YPdK!^kB+N`wZMgzt84SGH7)7Aa+CpJt^l<4R9zfrQS(W36wXz*vl)IfyE^y=p?S z$t(8BRD#FBf(do2DAI|2w47%c3nH4WDhIrJThw%A9VSibqpp5DfH zd_-DAGNPWO*V|A#QImpZSE^S6=19}keLDjrFkfem%IKf$vD=A-^)=+HGKSoerQp~p zEi#n^eF#NO%NiN3%F4DOwJHN43lA4&paRFtSc;=Nn>4I4LC{VIhZ8lD=v3V~)Eu|A zAe&{oG2zI&A#*7#&tPm+Y>e{ck+5yLEIza|jUhyvA^JKLX)8@7g|#z8j_b{8yXm_# z%FD&kZwmi9PydXj3dp-q2Hdxr!kTCjYCKda}MksXm^fA&0DJ0u|<UJGWy6#_?Z2+7pl{NFm|dMQbFr>$j9$G>g# zQN(S{G2+*IRX}1&#xQ`AiX@}AituDEAU$G^S z$>O*6JZG6iN`cohAM{2$dWl2id6L#hy2M5V(|P0UAguukVkYeJqr|0(v$8nUE2=!QQq2P*mD^CA3&Iq|U50C(_smAZoJnHFnS6qXg~&8FRCRD;>;QYzR#$EFv~4lS0cw zUA*^Z43%jpl_w44NBZ;-UV%CkwY@3cv~}q^GsO`4ccHw@$t-3OOwpk;7XA|X>>eqW zd(ky#+Xf;KX4E}kWP1AM24gj1^Q3GR9eG#1xQ;R$mzCCR?(`^-$k#1fmNDduc{0&Z z$llMZKgKtug#XmF%50v!U;SSQK9_m_UBT!7c~yn1o^DhxL(6(y4w!xc;HR(=n&`TI z=ht6aYTuOE}R1QH_sKntTo7{{eg_!= zaE_BJs;ub9i^G=S=?0p1Z|nV@LL6PlqBzYgS34Cqs;S;-KsSiLwCeX=bUN}tfx`kj zS%JZ5fPIy$4@frd7TUvK1Qu8Gd{LTQ(2$y^pPnRb!>ouCl}LK>v|^ME-B3K}`&=;4 zK2~UdgP@9-E^aU;(XH>fD70pTD@g$$s8%q&BQ0~Ar{=tUG;qT3iFdKp34VhUDI5W8 zFXk$*pk_dahK4sRAsVVexqC>Dr(F~!DtI*oweYp`mzN}*sqpKGMA}J2Cl@sLk^K3x z3FLST7TGJhGNqi!lGp}8dM^9O$XFZRRL0Yk8RNLeUr9ZF$~d(tf=ua_u2I@eU^%r> zm+8K$q|=<7fikr5rzQ;%TVYWuP~xFop6++te++{`>H54qy`TsvJZT~nOp_UCNOJI~ zk^0I@qrhX{XJ$h)e#zvsR`5$XSm*~s5}SXMr2F4b`ZL$RC^Y;-;lJj}zy4iT>{2eG zEZyf0_hnh%wcJ-D|9bzv8vm8_|Dm&?Rp5cRC?oeS5oIF?x3SP=-7^7!&2w~Ri9YVJ zzt)-R?wFsel>F=Oy-~^Z1)r^4?i;}BN6(?7)d*YN9uX+nwe54Og9le~aAw6DCoC{&Q+rcoN)bA0 zvEMePSMLa9Kj|Hkk9~|y>upw?_TJd4y%}of^{R+@FKuOp8`VMLZS;3qTfM?;vS=LB zzMZs@sv0i|Gf|0mFU+x~&0{F&c`ys>;W0F*=-VNl;-y{u9QB#ga|%vVqKs<;4$}Jr zB|UAj&-VSm#Xf+xUZZ{s^U9K`;j9S#bqD&KH$-YlkEO4jNL*2 z5$T6Qrz9(a^D8U_{n3+8Q_th0yE*4|o0~-#z0j9{MVsJ+Gjf@*pK5mJu(V!Vdz0oy zGW;P}F+nZ4LZ2XUM`B1J+-j`XP_e=u#4CHP#lo7}Bd=v9OWsVBtAWCitscex?Mtud z781v#R6UgOB|a6pn4gO+G*TAdc^krEX+lYk`w4G9STHr@ByH4iIos8NTEYVh1JzR2 z$!v(uS&Mz1xE9^NOV}%mI3f48&ln|&I%(iR$|>2g>rWJ^qFJM^S4MyNB)-}G*>p|K zp$Cc7nELGQm8bXL*n110Hn;9?G`N;RaJOJV3c(AdI0^0!#UZ#9Nue!X91`3uxVx6( z4lPnzAW$Sgp+J!uRLT|k_mb5FxjrsV8=E`i|dBpO&LUbzigd^pHE_7nV@CNM{i47oH~^y(i2vfJfv(}PIuE1hliWBIDv(P{u{G4P#SjgIxDqG|IsPY{tKur$8~olj}F(7a3jN zjjfcFA`?G9c!karS~ieTxbP}=JKiKbuA42%Ri+8NN}JiX$u-=Kf|mw6@Ar`RX>e%h zJlIeM9<4v)0_o1t4l+l5jOG_OvObZImwfRoHEwbTkQ;BeCn@sl9eh5Ll73I*CKw&3Qe8)8**ppsu{rbgKHH%4$Qk z0hc;3KtOKGt6C~%kfmAOUq^jP!mM@3t%1roG$6!5Je%~JSe4?Mul!}2JF`c2sxDl+ z`eXQyU=y()aXSNp5Ydt%2Q=Zm`(ffO`s6p^m7R&7Ep`QuEEDH-#$tw+SNPzfKA8#g zRX^SOFOTLGY2|eoLR+)V++X>9GmKGO=S*XXdex`(sirQlKlt@$^RS{1QfCwR!V(5z zPRXf~fJpZpczI_VN*r`}FeRDCxv9PslV8MGkY1$RNT=nIYiZ?BPKNzSmG)RJeB(F3 zi`n045wvbzngVLVq?VSJ28xD@i;J%+Xv#7S6AW&o{Qqk|(!LUQ`9bv8AFxu?Gu zavm?LhxTs3(>-qaUZ=R!C-mg#mBi@`dvE(Kxz>r(e^B(Z-^>aufln(wcgcQ?PUDhNaE(7mPCg_gWEh86aTa&e~gq@9V?;5DJAZ^3IUzjtgUH*l`V8!Q)T=d&Bd--f@>`(w&~=G|Pt z;a?XJx%t}PuJ_kazm`1>58%%x2(RjDJ9}Xq8bRa58Sg@$210WPhhGpVgj499W*-~< z!n09;-PuyO)7<_?&UyWB06p3xGP3pD(maf+bWh3(yr+jR%h(PeXdtbBbPaS*s+3uj z6R=n&=H@TS{il=caDH>r*&o%Tgsy`-Z-WB$>mEafsXRMVMrz4m~{%S!W3F#^}anuJoofs zCEogOsdKA%ZTIi{lc}LK*5pqQZ#Z7ySx-!YeI%Ekeoh{y9BGp)j`h2R+HOZ485k^80o85nM$fji+>8=gN)=vL>x2Fw`5tG0G%jlkjhg}?&)?x_Tr@XkLl;`4n`I&QMiP;@3sFU=ms$})Ivyrl_=~O*< z@4E`_nII$#l>CKVcPy{);VN7$(cfHD!RHU<>9V=s4(=Jn06(r!4Q6f zg*JXL+_fL8H5$tOgkf0GE}!t7?WuW#VjWm}%$5W>pz*95FiqljZc78lmumGXNgUR<;n0}ZVJfok+;0aY1;3r=5*h(vaM&a*fwuu%Is7Eki)(H(+F+^?lD>&TBldwS%I6pcxE ze{nLdZ_+T;2*Ska@hJPntL%v?hK4V}Q=}bOL6){LFX{CB?C6(Fo+8yGpONZ2hl`sQ zyWwjX)p_n@o~aEs12OOw*6YCRf>C%6T1dANl4}$$m+T#tq&Sa@m?R@8_SSNn>dZze!C_eBIiG*d+6hRE~p8Kxp-8 z0wkSUCxXc@IZ_N=j3cQ9`*<;)L?ne9Lf~DU?ymmcgf)qdWuO_#0*cGu|BB7N2yXdE z&ClNJ)+(Y3mm0qn8s5h>Fg~i*iF~C7w}rtUd+{t|V81LEiS5PZ+jtlDMF29gGo*22 z+(VMfE2rOiBl%Ue4PFoO`ty-j_YnB2m}Yo4+1nkGVbGx^S}k5eDe7A>l;@eZrIf*w z7Moi5KKGh4bcQlB=u#hTDJ;$S9{vXK|C#V~&;xxF%h5|b{ueg+{?eEa^Auio%!ZQN zYDOu)o)SkIw1IA%H*3!u>)e?njuDv5NRypZ=+aS~+vDdh@Us?*Ba~{!#}##-Jr2rD zIDddYGp(%t^v$m(8;hK1z0o8M!D@b>Ft%ZoS_zPL?_KqDb{DH!AG#K9jS znF#B~FATzwkDztI;-_~;#i8cn{K^3Z*y@QNCUrU32(Jy9MNiR2#iXQ5rSu3>X0KiH zmT{18I30OVxGxR|Q=*en;TLR?_gGHBHB9#Y-~REe*{mxhc0}=9VFi;06FC}CM^kT_ zjfVXt`T8>F>?B4d2LMGt#B^Ck{#$Kn{w^Tezea@-lQ0zQC9{#VlJin{sXrZpdC7Lk zrN->)n4;$O%JG{?Iv#(HxX@xkwD^3 z6OY7Af3yvI-C8x~_K{~un4fyOr7!1Ji~r3HJDcBlI}ctnO<{KJFkl^h^&>i8OMP6* z=BC6%lxb3#R%WlWxTaY`>-h-4Jm&Ih%fCT)5Iz=bcOei~?jjo@S^#*g;cQ_|^FRO+j%zC@wpakP&Hfe{ekhjVu zrIUN6v3zN>y3WYgAs4x5;>J-9S6NQQy2DZ5H63qZlem~QS9J)qXXNhjFpCrJyVL(* z1o{5jJAbNr>LvFb{%&3O*IMwGr5BxrKifZ^)UY@FdyM{<-thj7s)PIQQ~#~HnF_kv zEdIuJr@iCOJZvXemkZ*Z+}V4|FkP%f8yMi{=qpmyy&OoJ_j9Ct9Qcmk$mQ(VBxlR3 zfs?zReh$CsUd$UpnH02F=4B>D2wwc;>w#?Fyf9EdmDPN!txTuuZ-6QXEcsNs7stg7 z8&DR?x2&&wu%-C%u+rcvrCMe;FUNwmPcXb^m_uOL@`^= z1%mwcm)lU@(o@H|1}3Bvw6wj8E2H|7Yt_ji;lGP7uuaw7fRD=>_#am zy(cGYwnMn6rp98v){byP>YF|r?-tjgms7C@qpge^ni`S$^CX9Bw+l2l5GI))nL;+* z%C^JmMwnt<@m>*bi6XGOG@ycaB3_F;mj#{>*ZFZmB3Lz_0qDX?D23(@oy=@f_q-B- z&B-8(-;`TmvtT-IQEAc@6mN#xa2iBr6`JWA(Z#bMNNHkd-j=4wP#NL_%5u~8C#OX* ztQew`QcJcH;J46A4ZU>A6kqehA-8|kLY|``?P}5576}8hwGmnPKUp3wP5FRfV0pt6 zQ(K@=5~6@za)=o=?{;^%i+4RG=?*+bUo5S58T=h zfw5wy6B&ZUR=Z)kG)0kb@zBfYV(!qu4e8mwTTZ47c^X1vt7iK7 zvc6{NS#o01*^p6GdWs!hs*bNXxxQ{ceekRqpxs4hvd=mK6(w{2`{J;`XCI`g%X6Dm)CyVs% zU5hufmaj|zLeLrRK#S)`oRJ31Bg}Dc>-$2SGn%JeeJ5qgIpg}{WSXbQyyK^(^ZAXE zQZyL2b81lh*@tq?CXAoc4NtO8O%n)6&HKQNw-s-Y!v%Rg-J%_n?UW6LGqwmN#%*T( zSbGjOKf@lfmlI+r=2eG;O(-|w_3Q|L0D~vz^#R#6^#o_E3QZ}4m^t|~gFUnW zoFXXboM)J1$$TQ8|EObI4YhF&3hril*!M1>wx0N+uzOepz|vwljyivS_B`;e=Qnt$ znWREy6Y;0_1*bdsOO@*!kX+xv4YpXv8~Cfd;A2htEI|t+!B8Q8+SPHs3afbv;=KsV z(;Q^4W&vo)#hAq{TF6(Vr?3R4V5gs>t*21L@H}R`KwEuB!YwKqmd@s~sfx^;3xQQl zP{ztwa#m>@tZ*o&=a)x);AX7*AX{n$ZOBmZ0Nshp%@G%$6M=~Y>|Vcn8#1uC{6dy5 zg`rW{)ALS4G*I9r-4T@4SsmZYX{&&0mRip>SfMmvk4&O-!OK5eeTVZdKEbL+?(^If z@g(CFIw>kWo{97kGDOmvQ=b0>n^bt>i$Ke@)$Z6MRe7!-a#!8V-n;l$DE_DlUzluN zKBE2vEyyBFWiJ7A*Lun|dz(8g0xZL^*nYx$pc{*k%Qh*3qL~80<;c*t8AKlFo=C2@wLRb`NA7sZR;v9cMfEb5M2fw`a$2wdvf~eF1au zvnO7%#6XiiOvu?xuCXe~cp5)4$q+vh@Dpx$-X=sf$YEZ{NowTL#`|;BSZbzTtNJ-6 zTUB(Ib3i&$))#Ry|C&$EeP3`U(T51VB#h57-XK)c{<0%}eUuY`UqDbJTQFE&MXSDMtyK0`U2g-z8sh#u$;d7vYxs(zE;ugDU)oc4SprAw;mvB1uYOfBL1PAr~aAGwb;E)++R zHo#S?U{RLt1m_8tR!y?fip>N*rIbU%!xLTNhuG%CX*+@j2U~EfB7-Q^WD0Fo`*S9u zh0nwyqvwiT5C&t;FfgK3laOV(Q;6qB~xxa!EoZG0W#?WVm)bf%G7UHl*{~4EGmF zeFK+ybjv$6cNC|LjG5Hz%P#z^8n2H}NAC!)xp_5sm7>fY2dJxaTD>wrcAP+>I_QbR z(uqUt=G!>=vUf3~Mi(t(b1t`fkfi*e*ZmNZJLUALGSd@oI1s95hAf|W@d6Ink6-*d z_2;i`eevOrHnd2UUWo9cP|om8#_O884!onG;-1YQkr|swS_5T|U0KeN zQOY=#hhLFz*C7ITgyKo3Bw{Yxt6jHG@W7M)PyL?$8{YbtOT_1J=PdRYgVy*5d_PMG zw}mt%BO#FuZuy(AZ%s5rC||}@j`sDp@air5u+y-*VPPn7w0l&Ha~EYx$hZsx7sS8V zY{ByR$%?@Q#kSWM0p!;fTN*brJ~Up`Z-A`7tu4cH@to?(WI*`|Xa6dEZ=1=6s2L8X zg_Ds7+!aGZGj)?LV1?=(u`r5Q&`L4w@H0r*fV0pzo~IT)s70(rrRav_73eu_X{a^5 zZ<9aM2b_f_tHa&{jH&6K=Lwt=_vFx_=*W{qf5DZ@8OFs-EuJSw#TRJ=L5P!Z)iJt< zGOjL{2VQ&nv(2lMpw46w7n8V%_8Tk_I7Rb0z_bvkxp?jmQSM2b*2MMA4H6341!0#3 zZ}sy7WtLF2DdFG^j?UDe$t6_stoe6XO~G$~1RITDu#{*kn#3nlp{(W3>7C!b)V4Q3 z43hK5|AAbY9B)p3??6bmDxBEr9jEr0rH68#zWzfP`iD^GKSJzZGyh)@$_DhIclpvj zJC1$Hs<@iEum1XFG)dJQYkirz!lUtFv6hr!|4k&UZp87XxHoi#%%qia?p}CT*Zh9g zrX4bu3#Mh77unOt03hZ@cFiv=(z=#VKbbS8I*e%^;ghd(o%^+N#w zwl=ob4>}IgReZmm4bi}W2$porkWg!V)x+o?S+doBmOruS@=Fz+d7Uj>Bd!GBt)_xK z_>kI8{1NwU=JGQY*&kexJuG?%N0JG7-ky?4W#5<9#M66tNtexg1d6pLA!anc!bwRr zujWmCz&%4la)1;;$6$-Y&p!@XXxHD5)u=X$X2M3^18K0n?RwD-(X@L4+KnE63+u+J zPwM*8wSOqbGR1m&H}dss^r}%!O>AvI7l)-I`=$BbWBu=Uv;U*TTyk~`WGv$Jx50Na zB;g&0n0HNGhaXrvceH0MjLGYGJ$%Etom*&>*emnR|0jjc>IOIk4vKZq+-}Cw%z4erCXk#{vl~Rx_WXBe__-t{|+D zh2IVgr%^*7gH{+h5doPhlpl^sqS>CvbBDFaCj+4z8RM+h6CK6g%RI4!JK~prv-9fn z$|x$oEbdnj#i^1VS*1%ShKJ5P77XOc(@I329LAFR*oww>Ma85U&LL@Ic7PkJ#nT zE8T#E7Z(O%DqxWn;4uS1RGr;!mKjT(k({H2AwU^2*oy4tcan?1^C>PMedGOlM@(Lz zlbcjN0HLm#F;8B|=_?fuHm|Z!CH)|3if=XTY%|I)0+XKs>M$(Qb@fLWh|-A#9Uo7M zO=TlUj%d78v@d#$fd25}(6nupg7jDB34Ksr)VU9S%T`93GRGhV1Es!Q0h5}Rip~uBve#oeG+maC(3NCg+|jWDwzJ$`7&izg|{}RUWWLpntme z#%SVNXeAHchSB;_#`m%`lbzJ%e1-NXG5|H|A06#*0PtJv0JmN}-C9muB+^5_9)05I z=_!iU6Jj^0BFH`jAX1C|TC7vG+6S}{i~F>_5xi8ox$9g+L>`R@(I&i48cd1tce>zb z>|o#TBRZK^%FIQU{p<^dr}L9*RxePBJdsOqBT^P;t|4x>JxhgrKbEh4nB$(V_|a@= zgrT;KEYd{r)*_&?t!OODYC}y{Bl;P$wtwv8&^)okdv@=wT*ft7r`smhT0&xNzR`WK z6o`LIWT}6(EIIaiPKUYoF^?&J6GS%Rhem>$Z@ochn(qjb=vykr=O1sgWOtbb+=!9_Bil&^-yLG3V;~K8eB$BKCrHT|1)dCIPEiZ3d_sg}L zVi?*h!Dc!%ZSt|Wty7UzKBO=T8UW#A_IrN?gBKQe(yj8fdOBi#X*9J|C#xBoi58m( zJ|`=xY~$Kmj`#)I^UTHEP@amo3g0c4>j~Cnd|c}>*Z{+>2s?Ew<)>Cf_uO$44o<>2 z(@^z)99p*}IWm0%3E`}^R0|dQ!p`{^f^Jq7lY?YBfydN`Uq207G0TQT@O_IG#bg?| z=2C}_zq#E#P0$-4*F(Cv$*Oz3q6EqkW-&98I^|Eov)h4gjXPw?6U_u$&Ux|QFDE7rdO zrvD}VQ}D;6|3PZ|U`+0{7hlIscjy0Ew_Krn8kO|vw%MOH?nz;N7ycZ1DA)KFPa0p)24mED+GwwOOFVtjy2{1x%4r5Lh=vZ6;Pml5A)vWu>;NUqMF5 z7T*~^TyE1Lc#bheN%r67r-n)6xpCNKKG8{uQr{C%Tc+C#`y?znENZ=CJyoSNf-&3c+Ih+m*j0s=cg@d(~F~9tjj@fc$aG~T#=4xe!*0!5dGf( z=iej#%8C6G*ZLs;r^QVs9+J&efT;~&6E38DJ&z*^VJtTZP7)QVy;_4F0#0NkANzQ_ zK1PyM7(=9MRH)Kl{sy@7*E62eohO)|D03oRIWl6NyJ~|cz#NhG32R#QxEcnhD985- z;n>ehYMJA=CpUsSj@cEb^^H7@WM3 z&N$iGZq{F=&jdRx8B->;=}M9)DzCDCdb7cL$)F&ZKRNp*SlVARgt`sp=f^rUjnUwu ztmLRA9<*HkEY0+|r7MPEohr(Ur$%<`RMFHhh?AsPP1PY$zhA3|3x`eu>2*`v);*yc zUzy|=Xr=m%{!LAZtXIBSk{xmCH6!>=15hE@r{b@_0shf+nkz4^VlX79qLHc*4Lphv z-!ZLbESP3M`pXK{%V4h-viVkRn^l`wprjpOKWK}5QR6Nq4&ReEngMA9kIzy!1js(I zk!>(UcvnW5y<|Ghd1cdd49+bg3Ph2*#hpdRRtJ33t|_Fls@~;V=2!WHg-|>Ic1y zibYMkBX8fs_4}&4u;qz$b;f@LO#-@KeExWAR)Bi#T?$TTXij9UGH7^UDRN9h{<6W= zBUG}xuncH>rMBp=%NR`0Z|%LQ+}1ndc3xUy2cAi;Yf4Lx{5<=;@*mHTMm8Q`vAGk-}Q!Z_z zYdzwf+qar@;u9dNNs#b&;!BRCY%v6%jx`>Ce!ojs-FL%G04ONcMUDYtp4upRRKN6d z4Db!Y2!V2rE1#>A&E{Z~>HQ-KJ%-i;?%Hf9-v6)@D{$)t8F7=}FpGr@x^|_*OUQ4% zznQmWVRg;w*fnhCt{`Q;g>efJPJp3jkTUHc5u6*zi1o9jvwnGRAi3tq#m~coG4mvZ zlj=?Eh-*PW7h!v!yj#}SrDN%q_GgnDgBIM%w&LK%Eog(t<`=7wq*NadU!C3RwygJ) zX(!i=NgBmgrp3;FH0mFnXX$fqNoypSkgWJ21i}dzkVh;?F$t;wpLH zaixL_w;U!MB}ztIX>fFIG-w|c(pSS z6*jDbi_@U~_CY3vuy)-zSc&6!-dDMpuG9NY?b524|6gG`iHxy)_Lwe$Q6W?VvL^Xo@C* zk@d0mZD?t@piz6fcsEpSbc&v83eVk#n-4^O`|G=Z`osSQnENYE_-O}5b619CBt5-i+m^>iUWFm8;AlFCV5@kqtAi>CSc*yt2!t?+<}Yl=9L z+~lIxU`oZn0&VV@LBr)la4t=iYe&iR5}$E> zoJLr8<;lXB^PrbBhlwa4J-mto`5y*k$g3)3}B@(Hodf*cMs{zxVH55qM= zGOuiyLj&sG{RUW?5=M)T+Wslw!a0FNz6hF|Cl<5+vX8q+QPS1XPpm)1!)en8NC4&1ldMc8MV)73 z1@)xPtT1s66?eKZ6C(9&ox|z9Qk8ArWZ}!GPpouTi$~mo`yv9`` z*O?ufg?H%6z|cgzy~2Uo5zdSDpMIZBFAhtTGDGR3I6jGNnorU`+MQoYu&P*!x3DNs zn0Z=uc!*3@PI&$48`5j_bai~Jr9iW`^Ah+Yx7Cb~W!%qb&Ba`p)zSLAVwaxcyz!$f zy_+mc_<`Aq{|#R~tkD*GksIBc6&|fT(@)id`Xz@As!N5)a%bzfi>!8SeBY9#+2=O`dWF0p)>Jv@Y4Z5x_SjS;}=%8qjMBBqZ z%jImHEUf^tPFK~FJlr?K*xK`sbiDd>F@?>5Mzbow&hGW=Au-Jb|JI(%4IWX^#MvvG zM$vWggvAvv()~@@bi<4AHR^W3%5}pU<7^IAI0Sm`%sK-_^C2b_^yktj;D=4N{-g2Md$l0s zk~?_^)Op@lKpp@xi|TcCO5CudC_-@)YG}E=&U_}SgomWv`WcbJun2-Eq7;^K)Gdbi z01xxdg0+v4p>^tQ;RU#7$}I{wM2w0Q5JANyNXaPyY8eVrws zw36FjoBnClsWz$w(;Owl+$}5Bt^Qv%&hF-6gqO;{%Yh;FBr|Ot!&$5!8@(I?nE`W!zV;u2P);)Gvtl<9I@IgBcN_6Oxb17H zbl0wg;`=_IgtP1cN@#MhhadkEOX%E1{U9oP*t3W=>Dkj-s!{D z#my7ayJuJwPp-N-U{!-i5c%sjz(1eR1QY$L9$4#Wh7> zJm@afd75@5)455HU6UW!!<2r@0evFPX(%u}Ja6WY6|!mL#3;V&PQ%^JdgP&@?AjK( z&cWvj7v6Oyx7N5}<9LG)e#YCLmMGj0)DlA}xAd=v>kQw^CGd-Nc06hj;P7~Um-)NI zMQldzq_HQL%nZ>CIf1FLrM7{Ujpg&9;KI>rE_>#h}{4z{+mb~qxX_!>sNux zh8Pv9emNN`&nZ``0$4Vl8~#(Kc8iK-qo$^X=W79icrZGLi55 z>eJL0rHo<;-$^8$Hi*YANpV5Q8SU{%!Y%6=2rG z2gJDC;J4@)#%ZYMa>$oswLGdFi|D~-5B>R_W$cJ2?-5q5WkX;zTuyGF;J9YH8h%rd zCtrDqOO@#Qc}Y}=M*5xzh)P{FyvTFVbUj(&*D7yPFC{3}83Red86ql4aPCMkV(ma1 zD31{prIq0@rIpYFOzT|bk5Q9c2@h{-SQH2_5P-JX=X?zIc5}vj#v^YHG5#{#QHv*} zy!lC_88dK%5A7_F)@g&LmetB5TwUqC0zY>gj9YU@lB`!l)xQY9`S)%oQkmy&ni zH#{UWGACfhk&m_pGcu%}qQD|m*TQfL7Nh6Tv7Ne;X*V|>d#lyajVIa-jjHQxa(SFG zt1{yC@(CWUs>KKZr+U>{*neDCdv1^8*{wd#huq@0Ua=Y4dRxe_ifX^0H7B6a4BO)``Sdi}7YGlY^#&z(JiQMXVr@x{fVYp(1#z<6DeJ+4 ze+9>PIkSn|1k-PD6yTa_b&D|V0sZvQFJl{aZDy`u%R{S82%WMJuO#n-jzpkPdPw{wBlmwRu{Z)vA55^KrxcA%}_6t(ur zdWJlfi$ZxKCvhw4pkTQ(IKbNN>2~Lv^wE$N{PlK`&MP9yfq-^OwHwyHDaXC<-jxyL zHz0I-0r{9kW#>4xE`EJva)tYsg_WN?>QLeGSPglH^Y$ht&L>QiQ3%JyfbTK?)!^Nf zptksx8E~gXuH(OUeE%T(r+I(KexDq8=gwB)T2D4xKVMLz^Ou{>>G$rBPyg3lPRquk zUs1%-zo-rudOGO3R$4pCJO!lLFp(d+TY%oClK@=YV$44t0MFr`XGf5J<@b}d)l=l@ zcKkEcaaRX;h43KCF^9Bgc`Mgi#aS6X+oBIp9f!FtGpsg*ABxVyY0iaXVAuN=i*&H>C?KW?DVcY*P)1$!@8w_4(Xmq>1>jA zsjxu=W!Sq1WrexYy{wayL&$_?&Twx0u!-M^{r?qx-^-{#cbW&k@u|UU^a<<+T-ER`%hWo5t zBWNlQE%N21c@UMN1J6qW8r1oBmQUzbOhoCN6zhA8`U5WK+}(wX9{+-##dD_R2R?hs z)p%P^c+@O<95(gLYc{L;Ba1J}0N|jX)UrQqey!u-ml-Luo5kstQ^4yx3*%ufz7`cU zOu%ZmdU)ICVPGsUDh;<+b-Y*?C6v0yu(;shs{v4_>E^5SwgA^)4!>}951RzmZ{q6> zO;g%KQiNXf=9CIGw2fTCD2wqDE8JExfsmF{%xKDEIkCJD^!*+2js87Sd|-C4PA*T> zM}BpUJda@m+Ww396&$x%8Rs1mt;&nhy;$GTgqtdMUZ+`m=4*vxE8YqeAUx(ev*bjW zP~5GSD{;xm-gIyFpV|P~mzOW(IoC)*&fEz<~cqY zVmq^L!;APZ=H9YavF_r>+PN(PSbq`su@BDNia_c5gU@WF>0mNtoK%k7^s;0#=%S?C zIe*2>-mji_yge=y?waEoJ*^(1&GxkhzUA%Ed2-><)*U*>%WvzkC+6EALOr#+Z7C|G zUH(yU2A%k%@~mdb&e_1nv(>6(7*`bq?0`QNspRfKt=ZecxE64Q(pSD{V(VzN3QZ4Qq(fV_ZKG?_vWjVyD1Ji4F%Rv1R z8KN|TI=$tw(c1`iHaWW};U{!>)V?CYFt4DB07V*PY%PQ2SyKvPF&GDu^ z$+q^QeamygA?Qv1+q;kLGTo8-;Tn81-jTDi6cnTrUETz+hYJ%SiGVzUWLk1!XiRoN zd^gJcas#Q4d75%dB&5M97EarRhSZ1B>Pov9Md{V+$ge!=$SfcJ8cZzG2*j+(Aec0i zKE)%~sRp+5VitG4vIN0xhDKX;CdD|z4)e?Z8)u9_`dj$Fn_E-!65n5KLC?NY&%Uf^ zM7wB2?Otr||3qsk+)VvX8jCU(SGf@~`iKxp+poc|;E{9}f~rqaXiZbPnqfw461^kS zV(LkL?q<%yPMbk^#yjLUV#}{r_kwZa^!7i&l0u5?5l{Pv=@`jFR9)q;cn&CYKTa4w-Ez7zd*SyRJA zMuBoj6!0nj^=e(W`RIBeuuJke**&P5J3ab)g+a%! zn$JwV@oA1ijikwgR3}6SPyXZG*-0*l0degLX_Pocr0e*2x-5I&=`haxnwjD-&k;jI zLj>{$E8%ZWFv6S>0wWohNG8kPU}ezi}328 z@gQy4(To>Q!zU|SD+|UVM3H%7pEYXu-jzbD;E;|bTszwv^&Bf?Yy$=Hb9&pH5E#WZ<#rpmE9Ztm6Dwo+Ti6BPfQ$~ zopv6(aM(on1)bNhlI?V9%LTf@GC%LjB{mhSp~|vV&0Dfh8HQD9ZC%C|Oi^S@UL|mp z!<9Oom`PTmyQJQ<4sp-k4(_KQhYGpTXEmmv3YN%rJF7dKU7)Kmrj$;{-bihsb zf!$`Y3FVb?mo>?`0!Y1>`utcSiBM}{j}bDdy3P+j#hPKXj-T1QX;D{0rY2FtqlVes zk+jqR2zshG1$rT0j7j|`S4Exd^Omo*^FE%BkJ|Fr*&o&Kf0@{<6X;#|TD$nCu)gp{ z^WSu>e<~XO{k?y@*#C?$t!CquA9C_h+rKDn@7*QqdoI9BXB~nviP;NzmykcS4$g^P zp)EJZz`H0ebX+C-eSEn43tU{2BTRYbcc6y{mTWm)Q-H3>2fT6G&7(2+Zf_^m%s6l+ zaXp5F87$T@6&R_VVH}!Z;!-+eoo*mFiI8?{02$0%(%kR?W1%8w9VhBYO>flCONjYo z45Ja_Li?4)7@IJVmiPlq+VvHqGgCmsQGQc{);OpDvn~j4sc+XUAHLf$G!eMejK;SQ z2pGxrPBs3~Fg0?+T>aC^MQ?Evh$u%bD&ntH!2@{;#+j?PazP=^?C;YAP1e^d5}5U%6&VtbF^09jwN|2O%3q~ zWy-Qe;tX9m)g%b(+zu$Rt*z$L0SW=h@|g^Puw zFUD|ZCXmk<2xqtjwEe?KJ70uRNp|#oM41{;V&ZIJkIelEYTuc+i(@Qa3J>-EAI-f5 zR9sDyFg(EE?izv)PH-o$f=1jsk!dEeb{ zpMT#y|2hBJ-M>!XzTH(lxBGVW?R%%Iy6RaCD@rWASAmD-4|2ouNouc0I<_ zvj~m~8Q2E zmcK2evDu1~800{?!-Z*K05s;h z3@3Q=3QvQIXKBIS8)d%*Yt>ovC1?UKaA$qe$KW8HvGbHNlB3@_1F2JrhKQrF=B=$- zdmEu*iT(;WNGem?_1L1Dvd(!ArQ*1vuwJoXbW>0-FLja6>zacJ#OzcV@<3dv*DyUW z7{$$cY~5mNzy3}$h~UjNT2Kd*X%x`WAHG_h3JW-^9iADkU7ldTKbd!4d(x52;BG2Z zsVpS7?xO?nP?dt&&-z9m1_6-8nk_-D-15bG}iZAwYqBe2p zx?PM(N~##y{iz75xEEMDpE(+8!w6(|$Q6g_f@^Mo>FT3HcTq>?kW+2@ z9vwV>*ebzG=SHHfV7esF+GoPvS~<>GB!>|Ur<*c0EU{9NcU9^FVEF264BD3Blo`m2 zVr!A_=f|6k!S!Q~z<}77^#*(=Lo84y65WWZqc~&+l-GfQ4?ALi+pPT80+K3AFjo;# zZ!G!d_#iz4;qYdPHipSRkbh4yZ#n$)uitai%UkklPV7GMjy%DSWAVB>0!VB@c7YdV z5BFDPwM%U@icB^*WbWq}VhJ1%(Zl?nCAw91VHCsN&GVM!twE-QPvdVg z==uZupF14&kx~8JoVy^-y41~FWo(EGJ#f3=~rH1eio~oKQfvnvF->YD3WMtJFrEsAxBxUgIgz zoTH-?bBr}M#HB6{t_447PVg3PBs2!C_wq(sIe2eY9Um^T3ELUMH3N#F4%A!8zK*wv zJ!E{R!9Kriw-MPa+Dc)D0TA~ z&j*Qggp)i7#@x_&({(ws*EW3xEOok|0p>o2#F}~AFi?XFzJK&~-MuOFfGo&|lPi6G zneXZ9PLg3qALl@tQn8dJSrXo%(0jZc&1d{F*NZ0Zd{v!9RCaS%%WS(2kG8j%9;Z+L zxY(ubAe(jbhKq$%0YyMDF~~%$8(jz(J095KViUI?ftpr30Q?QuIb1|Sq5)99!{FA> zyt~4%Ab^PIJ2Qq`=LdhIktRk^)NzPpi#8M+*tLb$wuHUFcV$k~%;1w=O2Ki^^#@d{ z7G)*H^ccUtTyTwQVPL$drKWLIwM z<84tpyS*{y+Lr*)hGW&h)@L?+WN|hh1~ShiY8s0y zQ9?!FePQQPz_$LRAW8vs?nS2Qens`OmwF!hjXllQv-*;;EVyFncLSBwBWr5!ULL4L zof;g~_J6sOUwb#GX@`TCIOx4$s}Ztk?WXH!l~|P*q7+sm2v7`t$%9T4OAhm99FH3%$w3CMnkjSy z3DwuxE#;$~O-ZdWUlWDQ9rLG;al+lcHz$!(uq9I|X$5YWg**o#VSQBfF1ns_zWG6DnBYYV=QOYO#$SCJ<_?WAY88qGoN zOKDkyh;S?iFMsucMZc~&S~S-}Ln4h%E|F1aY^7DBBW-ik-cl^jIFSrr3Z-h)h-+Fn zFz!_dWpF=kIsmS0 z?a9La8k>wSlbZoh_(|J$8dv*O$YN5b31?f9XXvenToD;#Ow}+am~=wpSJZbIqQ#Sr z8FxmR0Ffaw>y@EO+e^0SZrev^R)49?RF6IUF`sn!((`9$W1j~9E~X1Q4ox`68tdtL z>9#-X>3&O_j^>+9GlpRPRM43jeKz)|EXi)nrYe2>Q@S3kj}&dhluh>ZZt16J`$)tk z{n^*qtlCI4bu7$^frJ?jsFdlnpHjB*U88=Yg^-YCzqvR=s zdQ-|#wZ4k{>@wBia->}g_4hcVtC(CVth_r2vI>`A$ed5zGGNU?*k|JBFL08-vSn<# z6a*;H=o&dpH5z(n#Aif;mo*_A=P@ewbWdsw*f$6~oXD!qcgUchJl-NhmMVJ3Kn-|o zB1XcVBjq)a4Pw^@EGuzhue4ye&xUmggg7AwVlzi^1wQyv+e^`|nT%OVW0fWIENyZ? z!514)|L!zZoJNkZ@72Gr+NpbOM8c>{3XF3)D)32}rbi`_rr%iaK+W~&k_2#~qIk#^ zx1^|g%2RTAM`(i-H?ZPj6lEt=@X))sG;`q^o}dZMzidtX(ae>Q=^5kM z+*EZ;@=+{^t>F^nMO5=*%=aMkX*6?4+D0UM!LNH6yhv(I){R8Gp7imn$uV3d93rW6buI3%9B+V`M z8%=%P@j7qnrPdsQ{5$?5`j7M<(R2uj<73OeB$2txw)Ed7e)$19)d~A%_hHjgQWr&t z*e$l;_M9Ta1?I{(O0PSo=k)?Hw+@HP7CejHlud-MC)r#lhsmJHr&>2ahQASE6?aNw zTJ=S-(qa~?mb2JBmBc1+sa~Zqm4$4n3WWP`^Soi>@*HtU7+b4i*6wLP7EpHh+Nn``T5e19cSvMnAu zWEb`Rhbr_IHVk4(rs3MN5NB1W0PlQCGufX>250Ewe_u$Op{=cd$l>y)XSg$$UfEilYOKP{6%emQ~y!e z9$xrgWussB{H>TW5y}>zd+(2i&=~CR8bS}1=yq?N!#(Repi2?&2;}%jU)B}Yw#SjA zF)$m_BiByLX$TA#;f?%U(qJ7NyiK;JhQt%QV}?RciGZR@JS7Oc zE%y{tTg`QF4{}5`ysuS+aA`zMf-!eZ%K->X9xP8TY3)mRhLD8d6VSA+@foCda#V9T z+MZXkJAu8EWly5T?@{Rar)Mj9Y}khxRP$M#oNVuw#09=Az<2nCGs>J9H?SAD>_cV` zCE(r&pC0M2OaLJ=#A7kgXG~miUUiD&8Ia&xO3HmRx<<6D`H`7B@w{g1GY8WbQsk~J z&!s?(Lo@5mtQ=5-Fu6WrEPeYuMM>Xf7VY7TFUfXILr{>b%bgCVf2(f4ESI$Nc>sT* ze(uM47*_HN`%FPD#*yAZ0R*W*Ts0eyT7w%ACBo01G%+K6ccsL%PK6rNG&L8NIZ~yr zMj)IWo~5S1frWF4xy;s~eEz~dfgA=C#+p|1&=O#Xk}BlPA8z$1t(gabw~8ockUz4< zxdkD~m=t*Hs;`5%J_1;B+rbVd3*j`yO{76V5EmvdC-dOh+#_G}RaY15>FW>Mhp{^E zRkBVRqF+Yewqm!-5((VjVc)=(nk{U&3&X&p)xzJ{$eJ?-OXQKfgFckgvX9v^M3cwl zC?z1)lCZkA#0lbr2L{`5Y`UewL<@b7Bg>%jqqrgsJ`5SdFS(vsSYNUTTRJiU9ex88 zG5n&~)J!N#taya50xdUln2pVsJpwX2ncVec5ICj03cX`M%Yoy3!V`4d`lf}gx&uAD z)C`VvP!DERkz65DJ&h8InHD6yAT;PPaJGS|`8#b>ZF^3tX zJ}<%3bRZ;Oip{{AS@zcEqE8XiYI$3ByK!uPRL~?n#{HVAT?N|G!;mA)UkbxX&4aL? zED>cXI1tGTl2%xwKBJ2x@+=eACW?-f^Ru~_}>0t@T=^{jpK27*LGLie-lo^176X; zM~_t=-Y@-ss27s;SHlnQm*FW1Po}o2i51v!dHt1(&%d~ve@XZkxBll+f4_d$HoTI5 zk0u_o6OaAoslw4v5>Y#-7vnD+-on8iqZlqAv@@bmR^9)DYTp$l8 z03stJARqw#2?+Rvh)6&@B4iW-Vj4|JuHTCQGz3Hh1Vn%l>K$qCwGjqs?}9H!a&Pbh z+urdflFu7bH=^+Df-ked)5JQ)oPfZXo?$bp{hvQ;A3;5tl$7uha32Bl51^u=B0WO& z!Rvk`pG2^x(s4{(?$)q%O{_d#) zJOXt|%2`ktM{bR7h7E zp`nm0A+Zq*XBmpqYgd#F^lvf^Wp%Tnh?T5ov$@q#5~Qo399v53~W}&0iAO@{Tkd zd}q1|ih6~zw7&E&J08?&u3}9B&Bi34FrCRM=+0ExAru~EhPUnTj3n%Clc9?``p~mL}xRa&b7%BGsJhV1k^%oh4frY%yZcq zhaWTt5S4n(u&BQj#WFL?2rab-4VmIgn)ot>{Y?LhS}KOudHNV zzYQ8$Qlg3sXOu%mhi}`o>33#Uq>T-_(?)6BkYB-5Nae9Qi#J0-b77-0LNxxu9%kQy zrUX)!8WHBl^NByT#5RS!YqmD|HWcoMPK))4uctgng>w<;V%bwF_gX*fjq9#Yycs9W zXhj#I@mxpMBJf3FCb{NDB9_kT*79Ku8CTaQ<2fk;J&%d2cvmzjor!f-io#eTdw&Hr zzB16Pl=#ihU-7O$G~&92oOQ=}M_qwR}QX21tR~Z(h2xt+X1#rEQ!sloVz;F#w;f4t-u#_;2JtMF)#fv~M z9VER{rT7?&cw|5%=JoSi-*R?5*Bk0rZxDrD)jJv$-H~QRpSeikFVJ4cBYVlJ*mKEg zFaghR67X%N_P>fd+pIVz7JS{{ZL5r8d~q*F)nn+U)$kk8k)Gmz7-B(?AcfW?r>dy+ zGDzi!O)144SsUx>d(oeJ8YVMZ5m8bSvUNyWT1+2{UtF?E#SyA??vnsI(a-^J)R+lX;m#s3bXN=o0u;T1T zFjxnEz*G3}MI#kFnlgF$Ch%#~i#ueORxh^1k?q3(NYyKvS8OqCiNnMr+v`=YN48x~ zUjGKbr(aoo00I&M5;6uBG7#y{9E<=!#7CmliUksIzsSWSq@kmiGBI-wAmZZT)6_O~ zfyU+4B1`I+PciVi_7F?UI>rBEdPb6f+n!uU-u6*hd_8@c+|XHgRBhwqL)z5Y8L{+w z@pAsJ#OlD&;p=~k3{4LNIP3q*sQHol&&cC3^INXG-3@Q06JF5`i?yGr3IQ6y zmd<7xp2~MdJ*Uw(YGri3)F7M>&vQwa41pO=oM0iq~Rnd15f%D#XAfhTku< zv3b(S5+Mf={lVS(#@|K#a;m62Ddi#@26Dx?F=b+Tpx;JKiQok5092khO1t_N#V(4$ zbt-b0F=j95V8B zYzBYXFmRuYqy-d#qfy6(fhS|f(pzjss3LcuKg2oDq^*q${AQX6LYaok)!g50z8n-! z!hx60!QfG}fPJJEYl`pB`Byn*BW7z;(VJW= zn;t3QFF`bGB%mc)Bz)62W62mOiKOJ9br3DqYC55@-W&W}Omon3I*MFrlU~oM=say4 ze3j+C#c1vNWsofj-X4m@AdNKn)CP8FYS0_*aeiJ1C6|Kg>zsg3swq?XO|wXWo+rNT zX0PO#`KP*|3}FbC{&L3V4c1-i7;=p{J=|1RHk#CO9vR)}v4jgfo6R*eAZe1XK@o)J zR4Y9c;;37mRWfj!S+sE8Ri9ixu=$|o`9@s~o3Cw2zjRLEi;i-cR zpGaj|Op)2>pciMxBT|l<(rwG|S6Hf2OrGqersyCUZ>^`ySjJD}YVoc1#Wlo@PO3V; zRS4bWZN)a{kD1B{siPRSrL71G$Ouw*k0^E`B!_1KyPOF?l|=u*n`rt@7jnT*kG#5L zr7tM%3|@~waH9+eHsWjN0|3+Cn!Iuy;DR{=_B&r8iH4;U3ilE#jd5YMcs47$gBr(a zU{QVWX`u_h-^y4rm8NLi!@ji0qG*L##GF!F5jun3^woYkwajrqV4OvDjxFCgKcy&> zrksvY5fKXkjncRhfII5_8n^dNwSS23SzllVQZ~XR{&VF!@d8C3|NR+L2cI*s zRPKegQv_=_)@D)Sm%FBA%ZFft%v9c(?af23^V9TDl<=z7$~*#R=1s z^03aiNr{{;+xhUrVT{h=Rdd{Xj4pA~vopiOmws`uQ}2rgCa#@!RitGvNV+HIZ94D7EF_{yEdrRu^H3$AKku?a4M}ZHq}mPB7TADLt~pb-73<*d&ivS6_HU zrvx4BPfS4BLFE;oAAU0uG$3US_(?fh*GnZh*cU`*W>3am_z~MSg^pu&LBOroT_)o? z1+j+&hZHnEFesj$v?ogzwEywgz?u~nOp}T>T-UcpE#b1|;^%wc^4j^NTsrT&%au&d zSx@R@9b}#mGK>g_u-AL;>v*S2vS1SNQUf>bOEhQ-3S*}xF6ci)j?LH~35C-2SnDuA zf2MOn)jCO#)mq!v-qrVREqrbHvKMrBpctf;j<1k*Zj~#t&`Q3i@aA1D1^eCAKkt~0 zC;Nj_e^10UXXN1wX)(FKs!bH7{vZ_V6103s=-`3gEDBu8gxZhy2<#fCyVj_+e*K>y z(CjK^V*Z2U6TOwlJSoQRfYXJ*47p$~y>++Y9Ud!L5bQa+`n0+@tU*O1uyLW5d{92M zL9R6ZIlN7PD6q^_k4fG$@POeU@Ibp8$ zdn}tmhhYAN05E@gfCk0u0ZsI04#?my4#+z2a)fSlw;th>+NT^7Q`GY&0_rBcPHPCz5G9y_2$_WO=gr?g}-?+?nDzQ zXd9`JyAuuOC3aYwEB!{nj{3g=DP^mTzX9CfPTL35^0)JM1LD0@SNPLjYOWOOS<|V` z13cftEry6bN3kSRX>$q{L7Y`vHvc=(frg0NFwLuprQlr&3;>LPrp#>>a;fM~htze5 zD2##@R$UR9!hv4@z6fIfe7UYXVz)eYyK zZpramAQvy4GzAI&kQUxOgS>xiQZ&3tMi}u3BBdd4nN2}&HTlv^9zvEi8*`!PHYorN zI3&CSmQ}$n>*y}n1l!PPgFt3E%GC^x>M?hHmLDMHJ0Bd?19JuwU#oc5)Ae$K1FZQn zISJUONi+vrf53gm=cz{7d|Xw_3~T+SFTfImMd?vxqlSViUQ<|Lg-2ZC<^N#^KC8#& z|9IMrIsOTFm14{=B!I~dgK-q6JJ{FALb2GJV)@?eK!NgRcE}bQHnv&I)8>(b{%ZH- z%+5B9NpM5G!oQMq_{8&i#X`<1N3x38zwu4pRdGPlRUT>qBqeH2$)(BRW1b8`7)2AJv2+M#AZYDk z8ajp~KJ_~3IM`4(8MkS!Ax>E0ciXS=L%)C+X-yy8S%JpK2K|*N5DA3J6cNyv1eT}4 zeYuntZf{Yems?q%|J?AN?+#kZ!zfY-{H#DSx*xV59J3%SwRHEauWu1YXY(_Ie;wqS z*rGLWd9NAy`n?T3vs)P!$11B;An&HIfCq>8YUsBsPUYwhuh;G;EHzAGefr8Cr9GlL z#SpEQ>nyd*@CEf9Zqq5h+?GEKpeD_{LkpyJ%r6C{}@xo`x_um&&``@=UFTY zPqM5bD}A$hvVUZ44cnLHd&;@IdZD90uPy~7cMJU?-}wscsy}TyxQ9pI?_zhj93$Hg z<(8_#ji(v!CUGJ~dYF+5o*E6*2kucqe*+|c{z_(2SrvOGdW{D{4?spuR+?N0826tt zEHD_g9N@G*Ni#LIw9^JR3|OX)H6RhFvW$fvHn}Z4DXlEHg*H}3vEZqu{G>`36(Y~i z30uO554CaRVyPlOoU}`{lP(_T`whUbKhBnP{{NSBFgThzE;uhBWS~xZ=$gUPJ zeI18TpDjJJhXa2H@B0h|@A@GIatjbEhrPeC~nN8`GY)e<(f0&VNfgtQR(Q%9wA7E4-4-Thv%t`QDJ!Hi-LV` zsx@0a6(kd=1{74}0H!p%!BJ^Jmy6|4!%zBdDHt_)PZ6yo!4=>3ri{VVys!5O#qX%q zGBeQdr*Y8qCM&btF@U&_JSE8P>^++ZKyQ{MmJ#~VEsK^qL+9qk?!U9U%}t}8&eZyz zk|CpXp^gtJ6IFv}xZl!3cv;#_2y1^T*IZ0>aSO&<94^|O7#o7L!L#{u0O5-OM-yK~ zAYV4Ra9`Sz{h?7v64hO0&vs=f(BFG!l$N`Znx$RXzfbL3AgsRLNs4m^S!J=u{41wE zUUQv}|0`+0ki*#Lq225I&tV$y;U;!Xl!87!DO@O_vz}R|eW8wiE2ECSIoSS#Vsi(c+4-wNgcY{JwN%xy&NXx)mP*7lDM$m)FF(m75;%Ik*Z&dodlWkc8fsZ8m&e%okLhGd6lenDNc6f)lsNj?) zVjKgS=B_?=^CjSQDgv$VW}f7BS4p@e)i($4cA-XZEiAN~)V+*bwJ@%i29BQ7_~dd= zqop%cu}6J{tVkSjrR>rjb^H4R5%SL%I_kVC33Q|ou@q@ZLt4%nIN3xsFelz4=W5$# zenJHp5DdrKEwItm@pux7-Q>QfW<~X4;lLlD@nfUI9p}q30La_A1;-47B7X7eBj$of zS6M6pU&WaoXN(mt6J<|PEqYn9U48s`_CtlUfg#~GQ;d=WfnF@y{M1}$y`|p#nE8^C z9hNzO6-0Vi5oqD|$*GPa`I71E-PkY>q^1Wydn zVEFnCTj7(L7Eg@l@LBa|^j|U(L;wmB0^qL|I=Gw!0UrPtkq{E$5zruWNoo?K{8@y9 zlMw+33^9*_6oxoS8h{c*oDu+02@nSW#wjttk;&)`af$#uI28ayM1Yq8{Dp@Xg9B<% ziRR=a30=Q#1P6z>IJZp+2ftiZRZa6e$?Y{Vf(#%(=mk-*m{)%$F!OCL>$=ktp~ul0 zNEKS8XB9GC8+{${_W8+;Q?66)TS)X^XEo-Dh^7V_ZnVvV(M|S_U;2aN&DySC_QU&- zkf^Anq^zu>qN=8*PoJ>JoHhrdpQ>lRt)e_y>+((I*&a)OJ~%k|me?DBd$ZS3dg`!b z-?gWIvS@_JI+-u!)KzHz8GsuIOw0VWP52%onezW7knr1Yv`G5W$Qj63+9M|s#V$PaDz&pyOG>Sq6nLSuqfSZ?r1-SExNqZG=74fM z#FGCkJ?ua$eKZS*gB$Q`cKupX*XhoKX)@$LRA=9&>C0#~VBlS`TVMW$y{I8+`r}hN zXHB&=n(Wrnc#vu;k2&l=J;i05Ic>0UGF9{Mz*IpyfQf5=1`d{z7{Z22muUugqoH!sySP&Ms~#q= zMF9ujSW;w@FZ}TdMTHvo*Q)`cim9;#DSn0OY<(N7_+n{p`wzlG$f~jh1YH5@Y$qEC z%AoL?Hx#YN`n*1IxTh=wLmaG5O6gq=D_y{t{%4yr5h+g7acJd=cQUmWF48E@P)HyE64a&;yr8wK`2Zr`D%Y)o*jU?8y}0B0PKV3mW7O&={8ey zl_SrcCgYd0K!K<|fxl)qPpSBdpl`r}E-Mr6^&CBwt&DyIE;M}(FEoD4M0~aPFW&U# zFK86_iu?)N+r}%tCj zwnzIK1WssyBL}^T4^v4Q?+7;hk%u?l3k%~5`O^@x9$;mE&1YABp6pM&MqEIIjijj6 zRD7&KC>03E{WbfYBJOGNI)Ky0G)qdXfO;iIV^Q~DzM|pQZGWa(z4L8B-1*YGc8{Z_ zuJ`x9uHYjK((rw4S;of2TP(ZVH_Pa8lrC_1LcB&3nja*oT*o5JJb(F~A;649I0A`|mUyO8k7ILY>${9_XSBMF&Asih%9BIqjJ z`YW{DC0~d=hNp4gP9e4*ovZkVwp!+&#%)4W!kjr7`&*I;;K{eEk22`h=DVXc?9VRC z%^aVDovMg?1KW(gzbC!M{pnkM_aimS&J}Vs)pgp{)AgNotxw^`Q#`-?!)4;b)A8vC z#Qp_=!*`;|*Yh`pMw_kUR?2hL#qQZ{t8b0^&u%^JdMO>1DIFOERu%1iV-{~{8&756 zwsYrU*wt~AMhpU3iuPhLi{8(^KL*-{Q^Ii+$#>V5 zgF(`EtAjx@#s-89-d0@xo}HFg7)^$r%E#be#4c2eob^cM1g(ZvNv9#=Iivj7p?g8k zzZEapUG$MGbqG0;7ncG5`&TwhV}n~i1v16MeqJcWWcnuR=q3l^+q?wr`bUmlFu{^pGaV03!44lHpS4Qp zGQ}W0c~ZLGggx{2-J;A}FBC{7q9F*4<=g!{lfS)aD}0aRX_=w`^j#@uQt=|!K~>OA z`YWc<50YU1Ih7Yw>}AdSABWc_KUHtMK8CX*|MCu*?nE{Qh_hfc%~R2VNiaiNuAPeq zx_L1#G(!_1=rQt1xMKu|%O=+ORF;gWK%GZtAx%MXV1>X)6Nxlo-%g@aC%aMvOKd$$ z)-`O#HGapt4=t*z*67tqHGC*&{d$kIjwgj1Tl5=n^R!{@5BB?SK=Znhb_Nc@yb)>H zK4(fmlW(Y0VE_uFTkIs}EtSaXMj9Nx4nMSL4pQC<#0~G)H<@!w0^fWzM zc!$s<%LFO#t``;cCTQxT23J7OHlI|JqZ%(U*Wd+h6>f|&53mSEaw20WXtRSSB+iH4 z>=Mi2wssrw{egJ5(A{sbcA1eos?G|JSBGMK`d}oF;d_H%~C zf{;kygmuaduybnm=1lE92TvM+1fmJDOHKIlj-90`q9Qd_wT(;)A6bB|CoEwaP-6^A zO`@Cx@KY z?*AE9Ky4By$f@Qq~${%oi?@HbrjdLo(@YPU}ItNGzi-tt$3}JZ8Q|zMQ&-cP-ef3{^U+ zs@UDi+g(SIIBZD-ro-qxk+>q7OkLlU=FhZ0Ar{u22dkLvV5`1K`NGm@4FN{%Sg^0~(aeCD{|oE{xI8sJ|~x*(9CFLZhl5r*W#&RB(~ zg%%_Dt2Yq!Y3+V{i`#Me_4SLg&0YOCx?Ma{CMQ7S%4K935QdYrB&WsMV6QHr1<44l zGqvO(h82i>E^sE68gH;4m(WuyqU|hLwLF`i!jvs90#8?^N+UGm#8eNR=JXQ;q;Ed& zD>~koX{-LW0j{RauP@t!%Ch*r)sKt!XL%Z5aU$MK< zP&eGGON3Myfqxi{QdGJ>bjUm^zfM7GfxhH0cGg4jyC{SElQ@wm*b{~S5yHn}f@w~& z^W)NM-Qvy(jtYG$U?{Q<{lotHF@pG3_9j)fE}m`DxPUBE^n*&Fj+5irus`zHp*kSS zEA@buLNZXZ9qTQ4837^2lUT1m!1QR~RWErgc!xHA8Eogq!`j--OIJQB!_-oiPf7iz z8Aa~p`f%AYIk7u6sJK|JqwK2&F}T z27Q}0`y#d*qSPHI1a*O{GW^8c5jS-qRPNZkIf+^YP(ZKw@G z2ICi{3+t?cSPeke@Ps2gA+SZvk?w+#1p9=SGQ(kv3inCTXEQCk>GSf+@_#492W6lz zLPTCkoP^w!}szT#Ok2sEPbL$-Ac78<8tUCZ`Ccn*v!AQcMj3BiSC3h#fIVOU%Y*1?g;~MleG=r3B zm2iZD6yP5T(#+(EzQ;iZKzDLY8n&&6=*Rw8O&nJ%VQ*1{WLxcMvj~g{bl;@WukYi% zog(011|~9nKR}@TF^EB`pucwcM9>pCZ#8j%Y)S)3`h8V}iOcX*0Vzk_Uf$BH{wlCJ zCEh*{eL4SU4)lU0?za`25AI2_E2n(Yg7P=NWaUI|1Ci$+esYe*+E)Vd)HcO56m>2^wGB zR+dR3d~S8~fc_B_X!xLKvJv}uK40ilJ-BBJgO?XbV}Tcd+BD7p(qiF5n_}0Z6U*oF zp?gZbHxgf2^O5G!*UuaUBQ^k_x9}Z^i*Kh$sT5HUDgHDTu9h_Q1kFwMhr-xvN12Lz z4!h-d-I^3qaq5ciyWm}H-P~17#|ZZ&QnZd;b1=`r*fn7dfSNjDCIx6@lub(#0#{$L zJ5QB;`PhG11(x{U0JS2Z)v*8xlpl#vPO@1a>?z3f#F2n(**k})`I!&pUt zMbtx@{tfVU4jpl2OC4F;Cs6<3R)qeWSFjM|Q;cN$n7g!KQ~`w}LBYgVK6sY}icYBD z2EL~4l^w}--s#QhMj}VZL*CW`sStBWiYDR#YMpd0d`I$2N^2Hvjr-l)6r>fMhnA%~ zvgWAYztKsRFnMe!`;UF&NbAiq-3Q3%h4gleUpzN=@PlpAFrxp#;T5E0tPJ1lu&=t( zrCmKuk`J>vW1b}r`{`x+@eu#Wak#|Nip!*2IG{;&)MSpdIEio8PAFnXLDm;5bbx^B z1_k&-6aB~OwSs!fc>cFg0<<0i12)A$!b>&yGm$fXHhqhaD49X{wO1cFq5qI7xb{Rvv0=)6 zSa_pjDd-~6BazqE#WqkwV1udBy0$m|V`+OGQH{`)`w#4>_Wlt7sX#<%V9N-BWqjVO za#A2!bX4fIq|*`pNu5=) z{{~EIs$CaL#y+u*9xV=OdJ<44(e~-O8#i!0EBEceHGz^)_N@Psp?T>laatqFdIQUE z!0`E}=!xF^G~v93MiFgl6}X4Ju_x27I61tC7tvE_(v#WQ>sWcrQkgYu8DisQp&GWYQq?JDC4TT*)Yl z@Ji2 Date: Mon, 4 Aug 2025 20:05:48 +0300 Subject: [PATCH 22/25] QR code example --- README.md | 18 ++--- example.php | 1 + example_list.php | 22 +++--- src/Authorization.php | 4 +- src/AuthorizationInterface.php | 4 +- src/Examples/payQrCode.php | 119 +++++++++++++++++++++++++++++++++ src/PaymentMethods.php | 7 +- src/Std.php | 70 ++++++++++--------- 8 files changed, 192 insertions(+), 53 deletions(-) create mode 100644 src/Examples/payQrCode.php diff --git a/README.md b/README.md index d97333d..155f952 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - [Запуск встроенного сервера](#запуск-встроенного-сервера) - [Запуск в контейнере docker](#запуск-в-контейнере-docker) - [Примеры использования](#примеры-использования) - - [Начало работы: настройка интеграции](#1-начало-работы--настройка-интеграции) + - [Начало работы: настройка интеграции](#1-начало-работы-настройка-интеграции) - [Приём платежей](#2-приём-платежей) - [Подписки](#3-подписки) (рекуррентные платежи) - [Токенизация](#4-токенизация) (запомнить данные плательщика, чтобы не запрашивать и не вводить их повторно) @@ -21,7 +21,7 @@ - [Подключение продавцов](#8-подключение-продавцов) - [Обработка вебхуков](#9-обработка-вебхуков) - [Страница после оплаты](#10-страница-после-оплаты) - - [Безопасные поля](#11-безопасные-поля--secure-fields-) (отдельный вид интеграции карточной формы) + - [Безопасные поля](#11-безопасные-поля-secure-fields) (отдельный вид интеграции карточной формы) - [Обработка ошибок](#12-обработка-ошибок) - [Обновление библиотеки](#обновление) - [Поддержка и контакты](#ссылки-поддержка-и-контакты) @@ -91,17 +91,19 @@ docker compose up --detach ##### 2. Приём платежей 1. [Cамый простой платёж](src/Examples/simpleGetPaymentLink.php) -2. [Подробный платёж](src/Examples/getPaymentLink.php) -4. [Платёж со сплитом (разделением платежа для нескольких получателей)](src/Examples/getPaymentLinkMarketplace.php) -3. [Платёж через СБП (Систему Быстрых Платежей)](src/Examples/getFasterPayment.php) -5. [Списание средств (только для двустадийной оплаты)](src/Examples/paymentCapture.php) +1. [Подробный платёж](src/Examples/getPaymentLink.php) +1. [Платёж со сплитом (разделением платежа для нескольких получателей)](src/Examples/getPaymentLinkMarketplace.php) +1. [Платёж через СБП (Систему Быстрых Платежей)](src/Examples/getFasterPayment.php) +1. [Списание средств (только для двустадийной оплаты)](src/Examples/paymentCapture.php) +1. [Встраивание QR-кода без страницы оплаты](/src/Examples/payQrCode.php) ##### 3. Подписки Рекуррентные платежи 1. [Создание подписки СБП](src/Examples/getBindingFasterPayment.php) -2. [Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) +1[Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) 1. [Создание подписки SberPay, T-Pay, Картой не РФ](src/Examples/getBindingPays.php) -2. [Оплата по подписке SberPay, T-Pay, Картой не РФ](src/Examples/paymentByBindingPays.php) +1[Оплата по подписке SberPay, T-Pay, Картой не РФ](src/Examples/paymentByBindingPays.php) +1 ##### 4. Токенизация Запомнить данные клиента, чтобы не запрашивать и не вводить их повторно diff --git a/example.php b/example.php index 2469c14..92f5b53 100644 --- a/example.php +++ b/example.php @@ -51,6 +51,7 @@ case 'SOMGetPaymentLink': case 'qstList': case 'webhookProcessing': + case 'payQrCode': require './src/Examples/start.php'; @include './src/Examples/'.$_GET['function'] . '__prepend.php'; require './src/Examples/'.$_GET['function'] . '.php'; diff --git a/example_list.php b/example_list.php index c407395..2ab2467 100644 --- a/example_list.php +++ b/example_list.php @@ -31,13 +31,19 @@ 'getFasterPayment' => [ 'name' => 'Оплата через СБП', 'about' => 'Пример платежа через систему быстрых платежей', - 'docLink' => 'https://ypmn.ru/ru/documentation/', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1authorize/post', 'link' => '', ], 'getFasterPaymentWithReceipts' => [ 'name' => 'Оплата через СБП с регистрацией чека', 'about' => 'Пример платежа через систему быстрых платежей с регистрацией чека', - 'docLink' => 'https://ypmn.ru/ru/documentation/', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1authorize/post', + 'link' => '', + ], + 'payQrCode' => [ + 'name' => 'QR-код на примере TPay', + 'about' => 'Пример отображение QR-кода Pay-метода без платёжной формы', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1authorize/post', 'link' => '', ], 'getBindingFasterPayment' => [ @@ -196,12 +202,12 @@ 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/qst-api/paths/~1v4~1qst~1list/get', 'link' => '', ], - 'getWidget' => [ - 'name' => 'Получение виджета', - 'about' => 'В этом примере показано получение виджета.', - 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/widget-integration', - 'link' => '', - ], +// 'getWidget' => [ +// 'name' => 'Получение виджета', +// 'about' => 'В этом примере показано получение виджета.', +// 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/widget-integration', +// 'link' => '', +// ], 'webhookProcessing' => [ 'name' => 'Обработка вебхука', 'about' => 'В этом примере показана обработка вебхука IPN', diff --git a/src/Authorization.php b/src/Authorization.php index 791a76a..7cbf549 100644 --- a/src/Authorization.php +++ b/src/Authorization.php @@ -49,7 +49,7 @@ public function setPaymentMethod(?string $paymentMethod = null) : self switch ($paymentMethod) { case PaymentMethods::CCVISAMC: case PaymentMethods::FASTER_PAYMENTS: - case PaymentMethods::SOM: + case PaymentMethods::INTCARD: case PaymentMethods::MIRPAY: case PaymentMethods::ALFAPAY: case PaymentMethods::TPAY: @@ -70,7 +70,7 @@ public function setPaymentMethod(?string $paymentMethod = null) : self } /** @inheritDoc */ - public function setUsePaymentPage(bool $isUsed) : self + public function setUsePaymentPage(?bool $isUsed) : self { if ($isUsed === true) { if (is_null($this->merchantToken) && is_null($this->cardDetails)) { diff --git a/src/AuthorizationInterface.php b/src/AuthorizationInterface.php index f51e3ff..8f5d7df 100644 --- a/src/AuthorizationInterface.php +++ b/src/AuthorizationInterface.php @@ -20,10 +20,10 @@ public function getPaymentMethod(): ?string; /** * Установить Использование платёжной страницы - * @param bool $isUsed Использовать платёжную страницу + * @param null|bool $isUsed Использовать платёжную страницу * @return AuthorizationInterface */ - public function setUsePaymentPage(bool $isUsed): self; + public function setUsePaymentPage(?bool $isUsed): self; /** * Получить Данные Карты diff --git a/src/Examples/payQrCode.php b/src/Examples/payQrCode.php new file mode 100644 index 0000000..4b1e022 --- /dev/null +++ b/src/Examples/payQrCode.php @@ -0,0 +1,119 @@ + 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 20.42, + 'quantity' => 1, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Email Плательщика (необязательно) +$billing->setEmail('test1@ypmn.ru'); +// Установим Телефон Плательщика +$billing->setPhone('+7-800-555-35-35'); +// Установим Город +$billing->setCity('Москва'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); + +// Создадим платёж +$payment = new Payment; +// Присвоим товарные позиции +$payment->addProduct($orderAsProduct); +// Создадим запрос на авторизацию платежа +// здесь первым параметром можно передать конкретный способ оплаты из справочника +// PaymentMethods.php +$payment->setAuthorization( + (new Authorization()) + ->setPaymentMethod(PaymentMethods::TPAY) + ->setUsePaymentPage(false) +); +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference($merchantPaymentReference); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +// TODO: перенести валидацию в функцию ApiClient +try { + $responseData = json_decode((string) $responseData["response"], true); + + if (isset($responseData['code']) + && $responseData['code'] === 429 + && $responseData['status'] === 'LIMIT_CALLS_EXCEEDED' + ) { + throw new PaymentException('YPMN-002: LIMIT_CALLS_EXCEEDED (превышена частота запросов к серверу)'); + } + + if (isset($responseData["paymentResult"])) { + if (!empty($responseData['paymentResult']['bankResponseDetails']['customBankNode']['qr'])) { + $qr = $responseData['paymentResult']['bankResponseDetails']['customBankNode']['qr']; + } + + // Выведем кнопку оплаты + echo Std::drawYpmnButton([ + 'qr' => ($qr ?? null), + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + 'newpage' => true, + ]); + + // .. или сделаем редирект на форму оплаты (опционально) + // Std::redirect($responseData["paymentResult"]['url']); + } +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+

' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/PaymentMethods.php b/src/PaymentMethods.php index 809f6c6..06995ab 100644 --- a/src/PaymentMethods.php +++ b/src/PaymentMethods.php @@ -11,10 +11,11 @@ class PaymentMethods public const FASTER_PAYMENTS = 'FASTER_PAYMENTS'; // СБП public const PAYOUT = 'PAYOUT'; // Выплата по номеру карты public const PAYOUT_FP = 'PAYOUT_FP'; // Выплата по номеру телефона - public const MIRPAY = 'MIRPAY'; // MIR PAY public const BNPL = 'BNPL'; // Рассрочка - public const SOM = 'SOM'; + public const INTCARD = 'INTCARD'; + + public const ALFAPAY = 'ALFAPAY'; public const SBERPAY = 'SBERPAY'; + public const MIRPAY = 'MIRPAY'; // MIR PAY public const TPAY = 'TPAY'; - public const ALFAPAY = 'ALFAPAY'; } diff --git a/src/Std.php b/src/Std.php index c53d323..25f1cb9 100644 --- a/src/Std.php +++ b/src/Std.php @@ -75,6 +75,7 @@ public static function drawYpmnButton(array $params): string } $allowedParams = [ + 'qr', 'url', 'shadow', 'currency', @@ -89,6 +90,41 @@ public static function drawYpmnButton(array $params): string } } + if (!empty($params['qr'])) { + $inner_block = ' + QR Code + '; + } else { + $inner_block = ' + +
+ Оплатить
+ ' . number_format($params['sum'], 2, '.', ' ') . ' '. ( isset($params['currency']) ? htmlspecialchars($params['currency']) : '₽' ) . ' +
+ '; + } + return '
- -
- Оплатить
- ' . number_format($params['sum'], 2, '.', ' ') . ' '. ( isset($params['currency']) ? htmlspecialchars($params['currency']) : '₽' ) . ' -
+ ' . $inner_block . '
-
- - + + ' . ( empty($params['qr']) ? '
QR Code' : '' ) .'
- - -
- Оплатить
- ' . number_format($params['sum'], 2, '.', ' ') . ' '. ( isset($params['currency']) ? htmlspecialchars($params['currency']) : '₽' ) . ' -
+ @keyframes a1_t { 0% { transform: translate(100px,43.74px) rotate(0deg); animation-timing-function: cubic-bezier(0,0,.58,1); } 16.625% { transform: translate(100px,43.74px) rotate(-15deg); } 100% { transform: translate(100px,43.74px) rotate(0deg); } } + @keyframes a0_t { 0% { transform: scale(1,1) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(0,0,.58,1); } 16.625% { transform: scale(1.2,1.2) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(.42,0,1,1); } 100% { transform: scale(1,1) translate(-126.64px,-77.99px); } } + @keyframes a3_t { 0% { transform: translate(100px,43.74px) rotate(0deg); animation-timing-function: cubic-bezier(0,0,.58,1); } 33.3% { transform: translate(100px,43.74px) rotate(-15deg); } 100% { transform: translate(100px,43.74px) rotate(0deg); } } + @keyframes a2_t { 0% { transform: scale(1,1) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(0,0,.58,1); } 33.3% { transform: scale(1.2,1.2) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(.42,0,1,1); } 100% { transform: scale(1,1) translate(-126.64px,-77.99px); } } + @keyframes a5_t { 0% { transform: translate(100px,43.74px) rotate(0deg); animation-timing-function: cubic-bezier(0,0,.58,1); } 50% { transform: translate(100px,43.74px) rotate(-15deg); } 100% { transform: translate(100px,43.74px) rotate(0deg); } } + @keyframes a4_t { 0% { transform: scale(1,1) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(0,0,.58,1); } 50% { transform: scale(1.15,1.15) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(.42,0,1,1); } 100% { transform: scale(1,1) translate(-126.64px,-77.99px); } } + @keyframes a7_t { 0% { transform: translate(100px,43.74px) rotate(0deg); animation-timing-function: cubic-bezier(0,0,.58,1); } 75% { transform: translate(100px,43.74px) rotate(-12deg); animation-timing-function: cubic-bezier(0,0,.58,1); } 100% { transform: translate(100px,43.74px) rotate(0deg); } } + @keyframes a6_t { 0% { transform: scale(1,1) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(0,0,.58,1); } 75% { transform: scale(1.1,1.1) translate(-126.64px,-77.99px); animation-timing-function: cubic-bezier(.42,0,1,1); } 100% { transform: scale(1,1) translate(-126.64px,-77.99px); } } + + + +
+ Оплатить
+ ' . number_format($params['sum'], 2, '.', ' ') . ' '. ( isset($params['currency']) ? htmlspecialchars($params['currency']) : '₽' ) . ' +
'; } @@ -314,4 +312,24 @@ public static function json_fix_cyr($json_str) { } return $json_str; } + + /** + * HTML-тег, например для QR-кода + * @param string $data_img + * @return string|null + */ + public static function data_img_tag(string $data_img) : ?string + { + if (empty($data_img)) { + return null; + } + + return ' + QR Code + '; + } } From b3a6824c1eaca0ba5c95b0f3809433d00aac4f43 Mon Sep 17 00:00:00 2001 From: Alexey Babak Date: Tue, 5 Aug 2025 10:40:58 +0300 Subject: [PATCH 24/25] readme update --- example_list.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/example_list.php b/example_list.php index 2ab2467..7f9b537 100644 --- a/example_list.php +++ b/example_list.php @@ -202,12 +202,6 @@ 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/qst-api/paths/~1v4~1qst~1list/get', 'link' => '', ], -// 'getWidget' => [ -// 'name' => 'Получение виджета', -// 'about' => 'В этом примере показано получение виджета.', -// 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/widget-integration', -// 'link' => '', -// ], 'webhookProcessing' => [ 'name' => 'Обработка вебхука', 'about' => 'В этом примере показана обработка вебхука IPN', From b7c0922cdc7587e0271ec18a75d83099bed6616f Mon Sep 17 00:00:00 2001 From: Alexey Babak Date: Tue, 5 Aug 2025 10:49:30 +0300 Subject: [PATCH 25/25] readme update --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 155f952..b598cd9 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,9 @@ docker compose up --detach ##### 3. Подписки Рекуррентные платежи 1. [Создание подписки СБП](src/Examples/getBindingFasterPayment.php) -1[Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) +1. [Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) 1. [Создание подписки SberPay, T-Pay, Картой не РФ](src/Examples/getBindingPays.php) -1[Оплата по подписке SberPay, T-Pay, Картой не РФ](src/Examples/paymentByBindingPays.php) -1 +1. [Оплата по подписке SberPay, T-Pay, Картой не РФ](src/Examples/paymentByBindingPays.php) ##### 4. Токенизация Запомнить данные клиента, чтобы не запрашивать и не вводить их повторно