From 5797d9149e01f292493dc2589ee5faa96d3b53fc Mon Sep 17 00:00:00 2001 From: yourpayments <133095074+yourpayments@users.noreply.github.com> Date: Wed, 10 May 2023 11:02:37 +0300 Subject: [PATCH 001/151] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f8f8d4b..a4192da 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# php-payu4 -Примеры использования PayU API v4. +# PHP API Client для платёжноц системы YourPayments +Примеры использования YourPayments API -PayU - многофункциональная платёжная система, поддерживающая не только простые платежи с банковских карт, но и множество +YourPayments (Твои Платежи) - многофункциональная платёжная система, поддерживающая не только простые платежи с банковских карт, но и множество форм оплаты, а также подписки и выплаты на карты. Данный репозиторий написан по принципам SOLID, и каждый программный интерфейс снабжен подробной документацией на @@ -291,7 +291,7 @@ try { ```php print_r($_POST); ``` -### Получить номер транзакции в PayU (GetStatus) +### Получить номер транзакции в YourPayments (GetStatus) ```php sendRefundRequest($refund, $merchant); ``` ## Ссылки -- [Докуметация по API](https://dev.payu.ru/ru/documents/apiv4/) -- [Основной сайт PayU Россия](https://payu.ru/) +- [Докуметация по API](https://dev.YPMN.ru/ru/documents/apiv4/) +- [Основной сайт Твои Платежи](https://YPMN.ru/) - Начните знакомство с кодом с этих файлов: [example.php](https://github.com/payuru/php-payu4/blob/main/example.php) и класса [PaymentInterface.php](https://github.com/payuru/php-payu4/blob/main/src/PaymentInterface.php) - [Задать вопрос или сообщить о проблеме](https://github.com/payuru/php-payu4/issues/new) ------------- -![](https://www.nco-payu.ru/media/images/global/payu@2x.png) +![](https://ypmn.ru/s/img/logo/ru/dark/horizontal_logo.svg) -[PayU.ru](https://PayU.ru/ "Платёжная система для сайтов и не только") +[YPMN.ru](https:/YPMN.ru/ "Платёжная система для сайтов и не только") From 4aaef28b606b2c554dc83ef3bbcfdb1a6f89a4f8 Mon Sep 17 00:00:00 2001 From: yourpayments <133095074+yourpayments@users.noreply.github.com> Date: Wed, 10 May 2023 11:05:30 +0300 Subject: [PATCH 002/151] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4192da..8ed7007 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PHP API Client для платёжноц системы YourPayments +# PHP API Client для платёжной системы YourPayments Примеры использования YourPayments API YourPayments (Твои Платежи) - многофункциональная платёжная система, поддерживающая не только простые платежи с банковских карт, но и множество From 9aa8d57af38e1bdc930a0672d54186dd82f41365 Mon Sep 17 00:00:00 2001 From: yourpayments <133095074+yourpayments@users.noreply.github.com> Date: Wed, 10 May 2023 15:50:28 +0300 Subject: [PATCH 003/151] Update composer.json --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 10ecf7c..984754a 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "payuru/php-payu4", - "description": "PayU - powerful payment gateway PHP integration", + "name": "yourpayments/php-api-client ", + "description": "Your Payments - powerful payment gateway PHP integration", "type": "package", "keywords": ["payments", "processing"], "require": { @@ -12,9 +12,9 @@ "license": "mit", "authors": [ { - "name": "PayU Team", - "email": "help@payu.ru", - "homepage": "https://dev.payu.ru/ru/documents/apiv4/" + "name": "YPMN Team", + "email": "itsupport@ypmn.ru", + "homepage": "https://dev.ypmn.ru/ru/documents/apiv4/" } ], "minimum-stability": "dev", From 1c7bc58c4f6e38c9015eb4b4e8553de472289628 Mon Sep 17 00:00:00 2001 From: yourpayments <133095074+yourpayments@users.noreply.github.com> Date: Wed, 10 May 2023 16:15:25 +0300 Subject: [PATCH 004/151] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 984754a..90a319d 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "yourpayments/php-api-client ", + "name": "yourpayments/php-api-client", "description": "Your Payments - powerful payment gateway PHP integration", "type": "package", "keywords": ["payments", "processing"], From 2ab226e5ee4a657df28f9a3ee092a54376287f96 Mon Sep 17 00:00:00 2001 From: whoiann Date: Thu, 11 May 2023 17:10:47 +0300 Subject: [PATCH 005/151] Change namespace --- README.md | 52 ++++++++++++++-------------- composer.json | 2 +- example.php | 54 +++++++++++++++--------------- src/AirlineInfoInterface.php | 4 +-- src/ApiRequest.php | 12 +++---- src/ApiRequestInterface.php | 4 +-- src/Authorization.php | 8 ++--- src/AuthorizationInterface.php | 2 +- src/Billing.php | 2 +- src/BillingInterface.php | 2 +- src/Capture.php | 4 +-- src/CaptureApiRequest.php | 2 +- src/CaptureInterface.php | 12 +++---- src/CardDetails.php | 2 +- src/CardDetailsInterface.php | 2 +- src/Client.php | 2 +- src/ClientInterface.php | 2 +- src/Delivery.php | 6 ++-- src/DeliveryInterface.php | 2 +- src/IdentityDocument.php | 2 +- src/IdentityDocumentInterface.php | 2 +- src/Merchant.php | 2 +- src/MerchantInterface.php | 4 +-- src/OrderData.php | 4 +-- src/OrderDataInterface.php | 10 +++--- src/Payment.php | 2 +- src/PaymentException.php | 2 +- src/PaymentInterface.php | 2 +- src/PaymentResult.php | 2 +- src/PaymentResultInterface.php | 2 +- src/Product.php | 2 +- src/ProductInterface.php | 2 +- src/Refund.php | 4 +-- src/RefundInterface.php | 12 +++---- src/Std.php | 10 +++--- src/StoredCredentials.php | 2 +- src/StoredCredentialsInterface.php | 2 +- src/TransactionInterface.php | 2 +- src/Webhook.php | 2 +- src/WebhookInterface.php | 4 +-- 40 files changed, 126 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 8ed7007..0432fca 100644 --- a/README.md +++ b/README.md @@ -27,24 +27,24 @@ Eclipse, Netbeans, etc), чтобы получать подробные подс [Composer](https://getcomposer.org/) - это инструмент для управления зависимостями в PHP. Он позволяет вам объявить библиотеки, от которых зависит ваш проект, и он будет управлять ими (устанавливать/обновлять) за вас. ```shell -composer require payuru/php-payu4 +composer require Ypmn ``` ```php // Для использования классов, например: -use payuru\phpPayu4\Authorization; -use payuru\phpPayu4\Delivery; -use payuru\phpPayu4\IdentityDocument; -use payuru\phpPayu4\Merchant; -use payuru\phpPayu4\Payment; -use payuru\phpPayu4\Client; -use payuru\phpPayu4\Billing; -use payuru\phpPayu4\ApiRequest; -use payuru\phpPayu4\PaymentException; -use payuru\phpPayu4\Product; -use payuru\phpPayu4\Capture; -use payuru\phpPayu4\Refund; -use payuru\phpPayu4\Std; +use Ypmn\Authorization; +use Ypmn\Delivery; +use Ypmn\IdentityDocument; +use Ypmn\Merchant; +use Ypmn\Payment; +use Ypmn\Client; +use Ypmn\Billing; +use Ypmn\ApiRequest; +use Ypmn\PaymentException; +use Ypmn\Product; +use Ypmn\Capture; +use Ypmn\Refund; +use Ypmn\Std; // Подключите загрузчик классов от Composer require vendor/autoload.php; @@ -60,13 +60,13 @@ require vendor/autoload.php; $merchant = new Merchant('rudevru1', 'hE9I1?3@|C8@w[1I&=y)'); ``` ### Создание (авторизация) платежа -Метод создаёт платёж (транзакцию) в системе PayU. +Метод создаёт платёж (транзакцию) в системе Ypmn. В зависимости от настройки, средства списываются либо сразу, либо после отправки метода "capture". #### Упрощённая интеграция, минимальный набор полей ```php setFirstName('Иван'); // Установим Фамилия Плательщика $billing->setLastName('Петров'); // Установим Email Плательщика -$billing->setEmail('test1@payu.ru'); +$billing->setEmail('test1@ypmn.ru'); // Установим Телефон Плательщика $billing->setPhone('+7-800-555-35-35'); // Установим Город @@ -150,7 +150,7 @@ try { #### Расширенные возможности, полный набор полей ```php setState('Центральный регион'); // Установим Адрес Плательщика (первая строка) $billing->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Плательщика (вторая строка) -$billing->setAddressLine1('Офис PayU'); +$billing->setAddressLine1('Офис Ypmn'); // Установим Почтовый Индекс Плательщика $billing->setZipCode('121000'); // Установим Имя Плательщика @@ -196,7 +196,7 @@ $billing->setLastName('Петров'); // Установим Телефон Плательщика $billing->setPhone('+79670660742'); // Установим Email Плательщика -$billing->setEmail('test1@payu.ru'); +$billing->setEmail('test1@ypmn.ru'); // (необязательно) Опишем Доствку и принимающее лицо $delivery = new Delivery; @@ -213,7 +213,7 @@ $delivery->setState('Центральный регион'); // Установим Адрес Лица, принимающего заказ (первая строка) $delivery->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Лица, принимающего заказ (вторая строка) -$delivery->setAddressLine1('Офис PayU'); +$delivery->setAddressLine1('Офис Ypmn'); // Установим Почтовый Индекс Лица, принимающего заказ $delivery->setZipCode('121000'); // Установим Имя Лица, принимающего заказ @@ -223,7 +223,7 @@ $delivery->setLastName('Петрова'); // Установим Телефон Лица, принимающего заказ $delivery->setPhone('+79670660743'); // Установим Email Лица, принимающего заказ -$delivery->setEmail('test2@payu.ru'); +$delivery->setEmail('test2@ypmn.ru'); // Установим Название Компании, в которой можно оставить заказ $delivery->setCompanyName('ООО "Вектор"'); @@ -294,7 +294,7 @@ print_r($_POST); ### Получить номер транзакции в YourPayments (GetStatus) ```php sendStatusRequest($merchantPaymentReference); ``` ### Списание средств (Capture) -В зависимости от настройки мерчанта, PayU может списывать денежные средства автоматически, +В зависимости от настройки мерчанта, Ypmn может списывать денежные средства автоматически, // Либо с помощью дополнительного запроса, описанного ниже. ```php sendStatusRequest($merchantPaymentReference); // Создадим такой запрос: $capture = (new Capture); -// Номер платежа PayU (возвращается в ответ на запрос на авторизацию в JSON Response) +// Номер платежа Ypmn (возвращается в ответ на запрос на авторизацию в JSON Response) $capture->setPayuPaymentReference(2297597); // Cумма исходной операции на авторизацию @@ -345,7 +345,7 @@ $responseData = $apiRequest->sendCaptureRequest($capture, $merchant); // Создадим запрос $refund = (new Refund); -// Установим номер платежа PayU - возвращается в ответ на запрос на авторизацию платежа в JSON Response +// Установим номер платежа Ypmn - возвращается в ответ на запрос на авторизацию платежа в JSON Response // См. пример с запросом Payment выше $refund->setPayuPaymentReference(2297597); // Cумма исходной операции на списание (Capture) diff --git a/composer.json b/composer.json index 90a319d..8946cc9 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "minimum-stability": "dev", "autoload": { "psr-4": { - "payuru\\phpPayu4\\": "src/" + "Ypmn\\": "src/" } } } diff --git a/example.php b/example.php index b9aeee5..704fc16 100644 --- a/example.php +++ b/example.php @@ -1,6 +1,6 @@ setLastName('Петров'); // Установим Email Плательщика - $billing->setEmail('test1@payu.ru'); + $billing->setEmail('test1@ypmn.ru'); // Установим Телефон Плательщика $billing->setPhone('+7-800-555-35-35'); // Установим Город @@ -94,7 +94,7 @@ $responseData = json_decode((string) $responseData["response"], true); // Нарисуем кнопку оплаты - echo Std::drawPayuButton([ + echo Std::drawYpmnButton([ 'url' => $responseData["paymentResult"]['url'], 'sum' => $payment->sumProductsAmount(), ]); @@ -116,7 +116,7 @@ } break; case 'getPaymentLink': - // Оплата по ссылке PayU + // Оплата по ссылке Ypmn // Представим, что нам надо оплатить пару позиций: Синий Мяч и Жёлтый Круг // Опишем первую позицию @@ -152,7 +152,7 @@ // Установим Адрес Плательщика (первая строка) $billing->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Плательщика (вторая строка) - $billing->setAddressLine1('Офис PayU'); + $billing->setAddressLine1('Офис Ypmn'); // Установим Почтовый Индекс Плательщика $billing->setZipCode('121000'); // Установим Имя Плательщика @@ -162,7 +162,7 @@ // Установим Телефон Плательщика $billing->setPhone('+79670660742'); // Установим Email Плательщика - $billing->setEmail('test1@payu.ru'); + $billing->setEmail('test1@ypmn.ru'); // (необязательно) Опишем Доствку и принимающее лицо $delivery = new Delivery; @@ -179,7 +179,7 @@ // Установим Адрес Лица, принимающего заказ (первая строка) $delivery->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Лица, принимающего заказ (вторая строка) - $delivery->setAddressLine1('Офис PayU'); + $delivery->setAddressLine1('Офис Ypmn'); // Установим Почтовый Индекс Лица, принимающего заказ $delivery->setZipCode('121000'); // Установим Имя Лица, принимающего заказ @@ -189,7 +189,7 @@ // Установим Телефон Лица, принимающего заказ $delivery->setPhone('+79670660743'); // Установим Email Лица, принимающего заказ - $delivery->setEmail('test2@payu.ru'); + $delivery->setEmail('test2@ypmn.ru'); // Установим Название Компании, в которой можно оставить заказ $delivery->setCompanyName('ООО "Вектор"'); @@ -233,7 +233,7 @@ $responseData = json_decode((string) $responseData["response"], true); // Нарисуем кнопку оплаты - echo Std::drawPayuButton([ + echo Std::drawYpmnButton([ 'url' => $responseData["paymentResult"]['url'], 'sum' => $payment->sumProductsAmount(), ]); @@ -257,13 +257,13 @@ case 'paymentCapture': // Запрос на списание денег - // В зависимости от настройки мерчанта, PayU может списывать денежные средства автоматически, + // В зависимости от настройки мерчанта, Ypmn может списывать денежные средства автоматически, // Либо с помощью дополнительного запроса, описанного ниже. // Создадим такой запрос: $capture = (new Capture); - // Номер платежа PayU (возвращается в ответ на запрос на авторизацию в JSON Response) + // Номер платежа Ypmn (возвращается в ответ на запрос на авторизацию в JSON Response) $capture->setPayuPaymentReference(2297597); // Cумма исходной операции на авторизацию @@ -284,7 +284,7 @@ break; case 'paymentGetStatus': - // Получить номер транзакции в PayU + // Получить номер транзакции в Ypmn // Номер заказа $merchantPaymentReference = 'primer_nomer__184'; @@ -307,7 +307,7 @@ // Создадим запрос $refund = (new Refund); - // Установим номер платежа PayU - возвращается в ответ на запрос на авторизацию платежа в JSON Response + // Установим номер платежа Ypmn - возвращается в ответ на запрос на авторизацию платежа в JSON Response // См. пример с запросом Payment выше $refund->setPayuPaymentReference(2297597); // Cумма исходной операции на авторизацию diff --git a/src/AirlineInfoInterface.php b/src/AirlineInfoInterface.php index fcf06d8..346cb91 100644 --- a/src/AirlineInfoInterface.php +++ b/src/AirlineInfoInterface.php @@ -1,9 +1,9 @@ getDebugMode()) { - $this->echoDebugMessage('Запрос к серверу PayU:'); + $this->echoDebugMessage('Запрос к серверу Ypmn:'); $this->echoDebugMessage($encodedJsonData); - $this->echoDebugMessage('Ответ от сервера PayU:'); + $this->echoDebugMessage('Ответ от сервера Ypmn:'); $this->echoDebugMessage(json_encode(json_decode($response), JSON_PRETTY_PRINT)); if (mb_strlen($err) > 0) { diff --git a/src/ApiRequestInterface.php b/src/ApiRequestInterface.php index 8f74a30..e603133 100644 --- a/src/ApiRequestInterface.php +++ b/src/ApiRequestInterface.php @@ -1,6 +1,6 @@ PayU Оплатить diff --git a/src/StoredCredentials.php b/src/StoredCredentials.php index 3e14485..c52eb65 100644 --- a/src/StoredCredentials.php +++ b/src/StoredCredentials.php @@ -1,6 +1,6 @@ Date: Fri, 12 May 2023 10:16:13 +0300 Subject: [PATCH 006/151] Change payment button to SVG --- example_template.html | 13 +++++++------ index.php | 2 +- src/Std.php | 19 +++++++++++++++++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/example_template.html b/example_template.html index a5e2ff2..0696655 100644 --- a/example_template.html +++ b/example_template.html @@ -1,11 +1,12 @@

    -
  1. simpleGetPaymentLink -- самый простой платёж
  2. -
  3. getPaymentLink -- платёж со всеми полями
  4. -
  5. paymentCapture -- списание средств
  6. +
  7. simpleGetPaymentLink -- самый простой платёж
  8. +
  9. getPaymentLink -- платёж со всеми полями
  10. +
  11. paymentCapture -- списание средств
  12. -
  13. paymentRefund -- запрос на возврат
  14. -
  15. paymentGetStatus -- проверка статуса платежа
  16. -
  17. returnPage -- страница после оплаты
  18. +
  19. paymentRefund -- запрос на возврат
  20. +
  21. paymentGetStatus -- проверка статуса платежа
  22. +
  23. returnPage -- страница после оплаты
+ diff --git a/index.php b/index.php index 25425dc..d7f34ab 100644 --- a/index.php +++ b/index.php @@ -3,7 +3,7 @@ ini_set('display_startup_errors', '1'); error_reporting(E_ALL); -// Эта фукнция подключает клас +// Эта фукнция подключает классы spl_autoload_register(function ($className) { $className = explode('\\', $className); $className = end($className); diff --git a/src/Std.php b/src/Std.php index 3fd0a46..202d3df 100644 --- a/src/Std.php +++ b/src/Std.php @@ -107,11 +107,26 @@ public static function drawYpmnButton(array $params): string text-decoration: none; " > - Ypmn + />*/ + + + + + + Оплатить '.(isset($params['sum']) ? '
' . number_format($params['sum'], 2, '.', ' ') From 646ed629ab6706e7356f9c6c82997934fa0b1efa Mon Sep 17 00:00:00 2001 From: whoiann Date: Fri, 12 May 2023 11:06:46 +0300 Subject: [PATCH 007/151] Delete old payment button Resized max-width from 100px to 160px --- src/Std.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Std.php b/src/Std.php index 202d3df..a4ee0f3 100644 --- a/src/Std.php +++ b/src/Std.php @@ -94,7 +94,7 @@ public static function drawYpmnButton(array $params): string rel="noindex nofollow" title="Оплатить" style=" - max-width: 100px; + max-width: 160px; display: inline-block; justify-content: center; text-align: center; @@ -107,11 +107,6 @@ public static function drawYpmnButton(array $params): string text-decoration: none; " > - /*Ypmn*/
|WS+k!%z#P?W~^>ZURg z8-DDu+1so4BvUi*uGE%+u3!pjy=+y*7Yf&4IhJWHw03iEcM0B(#9Y0SyY`tPLQfCy zdCz%@HgrF25n=Jk+1Ite*e-iCnup}w#fLY%%qZrzM=UABl+q_9JbPL${N#Tf3 zdfrlhJX$JJ4$BTdGCsHc25v^jFl4Et#`2DPIPh!juJbtLCRDVSlYXB=I{i%Xon$wW zvl<}^hiMKGgO zOJt? zI|ds^WZFqHs5An-ux6n`RU8~SU0nha+t>ddO;0GX;nIZ?rO-i*f&jbL(7_prxD!LW zV9(ud)!3d8FMfT#KuQh8#IigT@=i^@lX%-?LCb8;*|xj5Yc4Y;^~&4Vr?^47sXuew z)n)y_R~hTAu?ncV3cQh7_xmw7iy*{V1f|(fhA^isSbQthP(g(MG-*@e@}rrD`&p3Q zGgsb=;0{7ftsc7VNP|pt?D(&f_1@t@-dQs98@C(K;n15F^dkR(UuQ(#q90@Ydf#!x zup4O!Yu)#WQ$#RR6QgemOTh4D;RQ+4-ze4?q4sNe&s-F%nP{^-N6w3UMku#((-0N% zoocx=6z=81tySc(k%s2xmtn7C7s2m23f~Ai{klw(mqMs&*nI7wGPuCSMW!Z`*VnmENWBapAU+MqG*JgV=-l{ngOp~84S^|hV;6igtUaF2o=iD zk#-<$syGe>w;&){L(QRp%^EI}@btCuU6F9~IeDBzGNp_(0<-}%hDShaf`0Ml z6V&J(kp7Kq?NjchcDbJ2?$^3^W?<*MX-j%b>NMMTm9K<@@*wax>q!#ZN%SB$yn?$! zI@Qz?asecuFsGD!Bb{FWL6?ZnMY^rHQ<1db z{X}<_L@-V3yJ+-aP)@WhP!vB4yp9e%wK%f49(K8E*=5|sT%s0->3Nm?DxYu9d3Wu1 zs?WP07D#zaV)uK)+02@BXlC_>ggCd3m_HiU*CE>)3r21?1IeC$-#C_90c3aFFgg{M zZ=J3$wbk!7b`RB~%rhZT@T`i_W6-QlV0O4p01iY-Dn^xDS#r0TUE)vNrE8{AH%I7< zE&x(n&N**CO%Ue4n<$3;asy=RWV6~{4Rk}8avavpWvEALGl0H-;A+>ex1Z<`Rb*O* z&Y43O;_tfMh$Q9KzVp6Th*8E!jnm?o{}fCe=5Vs(9hT=s1d5NTY45kPg5Jq6R;tih z9%Ff8Cqk+!A~IikieUxERiZ#WC^6iBhi^01FV*@s$DqkW$z}}T?)gE!;K1i;0!jcV zTupK6SGdAQufbBKCUMwr24e##-Q6pZk~NZi=>U(NVC!sc5dN7qG85>sIV}pG5}$_IgPIl1{daGA61P`BV;g}#_>0M*u+drXC&)!}2Pa6R}%hJ1@I5KGL z*+Q47*TP29lcWu`8Yavi@xaN>hTe3Z(W)7X4xXkyLZX8q-7Uoy53VU&mOJcpp08;Z zad7><%jHn}-o`Y5_#qb;?;Yr1MJlBT@AZ+~X+=1S%we*K;Mgb`Hs;y2_}0ZuC{VQc zl|YF&8-PqfQ}=QaduuC5y&U9XYs^+*#{3 z(%8t5s=#yYyjg(WW2)BD@;{9mZFSR>>&OWl(#7Q8lLfIUgfU#z8Ixc1Y4UT z5C<1a(IlFy3m7{29uYBv_(ztZe9Xeo1ia2~csBDSGT_8dsyt+35&U5@2bSt^emmQx zz20r8kF$8wM}qsP#VgJL8lME5f-rw%--z^0V(#mCt0x2A%SCeYbg)igA+H|e78;x%rB ztgs;QWhT){@r2&t3^r|$DE!kce5~GZ)>D-KMW^Fea?8A4P}BXkT@|VdiL3~fc#hT8 z!)3i}9)+{xyI@o~+=Q@*pnFSE*511G*pUPsF1wyrk?MO`^Qh-lZ07-IfcdY_jlXH5 zEEc>f3(fhl)21DaN+9)9psKEdt~Wc6tvl=61is-Bd}U9lSX|hH(mL@3lutb@VkL#X z1F54=b|*KptZlHh>76au!AFwQYOyg_Fdol5-TymTAfDuJa)MbK2(!Q(LZh`LpG&{C zEP3TTtE1;YQ#cPjHn*L3QB@G6W8s)lSmxqJeMKLqpV-jc_3L5ktK8X^!m~aF&EsAI zfRdqY58CM`7zyo5G8qT)mR;W-Q#QhykICI#+yCFHuoU;)iaz0OK?H7ZD|IU1`D}e$ zAH}a=!tr%>m|t4L0<f~rT$PGj&B?tmY(KF=TC3Qwx&^@RT$7x|<5|B7rOSUu2&@#*C*(qETP zow$B0fiQMmBJaAI%=^Kg9{w_N>dK60JpOaNa`W`-`@@HTpMkHt z{a=0Fl=!T6o;y`~H)TEd`|$ry1@4b3_iv|8J|K;TANq|+>x!;m!f0mnI^>M6=);@} zmJH;E0}N{2u?UzgP&qY9wQ{!ZKIBMFe(@P#m~TW2wz>A3iBMsOyQFl8wp>b-~ zFZxM?_#+afE5s)rU7Ymg?hGaGF+^+}>_#~2opb98U*W=%E6IM28*)TSo+fH0)bNh* zTI{X2EcU$q2Y?!J^Xm1^#eL7V*UpH=g=Yj2ZQb@(upxRK;K2;r`SqzCntj?<@#`(i zXQnTG((|l{=8i|3Bk_2>r{si5^$9MHB`i%L6zC(gCGxnaBXQ_+Ep6_^QB!QF@4bn? zFQRDj6wTX4m1N+_)q(a?3-+xbjB2cYJx6)E$(}dWTnQAp{3AyN+F{>dA;rAF*&O?y-s8wG=WVDY-@={;! z7Ev=}vs6HUGr40&xgB6YRr1O#>5S!KR!p}EXQw&mt35eh!L^Sfj2Z$JW3qj zg-wG~5Z#(<`IrY;bV=#j)zY#h+p2hkj>!>y87c*2nZWu#?`;W!#LT?jDU2%eGLR!c zi^`cytNlb)Nzb?|t2UGs3r9)9O6T-t%Usu_djUFcoG8F-s&*NWm}RU?qF3AxBqNd8 z_;HjSqiB?UyDZ8wYu43l`h{f8jozPG+x1e@#6Az)wme&tf(lXV?gyB+YHl$ctwNCRrd>n`Tmm4xcB*(U<$7;waw z+F1BmGVwi83uTDh#A6?%c8qVMUdZWKZD4tTlo#UiIkxZ{C`i*p8f7rA)+a@0f+-`V zu9u!&t$(JPu#G7v3E)!4hJ>F~K+d9c&S8xd6u81 za1*?OMi8rnuPF7ebT;h*w(#p3YOV*Tq7M@U@%XjCh*r21fKZv>!%JaguX7*uF^FlO1a=ek!SKL^AS@Zge55;HN z6g|TY@OxiK!n~Pv?5(;`!Yz6zE=t_bO*Q@m*YD<1+DVl(2}vVR#W!DR)=yv)Eh&38 z5hhsAMHDb25@Uzpc0b}7ITdT2zXxN#Ayv@JQ7xbHrtTYrJ1!xu8Y53B)+JxDHRVlt zm>)aIK-xgPxrm#SJu~RK-DPZ$PO1n!);Y1$mk_J&``j8XxK|gx|&0=VChod_!HDG>Wp2Z3mbL1&0kXH;o{?9pk7R~0|}oJ&C7?IkcdURp!AL62em$`wBm4WW<{`mN0OUBg--1_v6_ zc>Wu^@MBQnyffA5&ia-VSK9zlotTr0f&a)Kof*F7!R(xatcRAZM2@JQcMKqO#-#_d zRKbdxk8c`P#eQ;YU)4JQr&m)$qZ5BpqDZgl!~>#@^FWJ5mIQ|+cO-yv`gu03Euqi% zgrV&o;(a5-kiG#0jUuu__{wT#STd$oZ>f{I_zt8M??i@el|pi(o=>+5jOR4ABQde| z81&I)S`kMWwiP=8O2bSd?>f!Mvnt**C0%Y$98?|(Q{l_m$t+A44DayU^LZvbnp2a- z;QaQ&tiAYSB(s}7oxAGean7)pa9 zRJuV(GaMQkwx%Z6*%xptE=Ba>fera4$jo$4y293>SRvsXHJVZ9NR zf!t>2M{QBf$5^KFDCh4#-1L56{C&YDhhZ7KZT;biiQ?+@$uL-l@P)f4{29v5>tBeTq(AKBL2-&glMg=+8NAI}aB1F_cD5;~+KGqGwJY@tLFb01 zofD*%xLAXV`Q*tT@E<=0%$6T;@IZVVu69~yM}Sg$LUE>4EuL8Pgn?qor%~`XZ)iPG zve+QPa=c1Pm#wu%{r!|@9_lv4`GcP0upLYeU)e#fyP(k=6CRozqQYAIr4ho;jWxS1 z@jE_xN0q!BhOCa6$uoSkqwnr)l1uV5eeX-ubP!OliTn-<5(2@tR zD(Pr{WIHRbi!v)=iMQ6_I_)yKKg(iwB zbX-;ogYrk=!0xZ>f=Tn#pyQgxtr5SujsKuZv)b={LcfG zMy8iHR9Ht-OJL#gG}}CE3?CDnT~&{3<84Au=@I%i-~D#GCT|c_gmmE(Hj8dPWFi5z z=!3@_hJ4L#N*H!ZDzxX69JYr)gQzxU(QszvCoK2>AG&;Pd9DvJ{P`@q0wbW25p!KjbAeIi zoQflroQ);!;cHrOJ`dYTG15W&(;bAGJ9CX0Jwjlc$sJ7T~fB&~)Iw|19u(bL1(tlbk-cur9Or39`T#e|nl|S+WTr zsTXcsyjU>`4+Y1D?+??_LYbD|%o*Fqu8kG&BTR*$OH>+m*I85mH=08VKq96>N#gg- zPD#>`bPzy$;gFmI4>AySO%+8J?Vih;<-hoSPRc0XnflW~H1~^!=?j6SR!l>-(3W7= zv0GQm5k`JOqfwmj6h*^NG5&%7y9~8dqJppB%*9}`Wt@8OhPv@A_Gup)0kyy>DMY)t zq`Faljwri~?OKVJkU@*cmM9=z95$E-E@nFO859#vdnUL!MeV@u*kOP8-dzr(77OAt zQ>%~4w@>%wJqPw zKOw=wvh%mWAw3T#V*9;vgw5prLkqul@A9BbQk^e^{jeyBR|$+eeO=p4z_s?>rXOsG zub7VYDb&&kD&cLM#$tlQ25+>f^Vkd>xw&g8XQ7EsWbB&~TPO+zpUAGXj3&{x8px|b z&0-JbI{M-Df(o7+%nv=eciW~C?lIE}coSM4ftNAyVx**>#eqYt6B<%oVtlUPkLoU= zh(a0(YKG?G`2k8V7tHnB&Ix4;5C*eRcWY*ODBWV)V$oYFJjx903d&#c?gv+&qVrcr zZ_{~yoEbjdUA^u?j}$=*BpH+{-g7s_QXHQ*{tC21|QhEL;1wG__RC2x@l@Vmr)Q}0O*fC+15 zsa>s5Y#1TxS#5y~-Gys3LkfDNm$2T-MJCr83A(M?tg>SfP!Wt>HM2n}Ad7ypDK)l8 zK6RFd-B70&6bRcPW#RttXFR&z*{>$yI`rjq9H#&iVYJuJjuV-;k}1c{E9-sqCiFGi zoOM~ptprm5D;&EK?1YaUV1K?8!8J;*`;I}n-=dH%kX#GDd$`Ya!eE7yMnogPMU)Vv z86`8nIDt^WL(@=H9YW)%;<4}tn%eT)(NG_}KO#<;Jh>nEkW*8zr6eja9Q*u{t|>H6 z5302rDlE)&L=>_|q!b?{$`8Rgps5Y(%kL}1m(+|Z*fr&mm&GX((S9C=aJ6($5~_dr z!v6_6%RZa!vf~-a!IVp;kfO<3ls!S^$($FFE4b@-Dq$v1KJsJzSDSr`LToHG4aCMh zq9yq|S&(7prmBdtD+TIvbHt z^TA&AizRIUirE}pVFiN-rU7}>&^IbdyhP#F%-B^O%5IMxC2N`E)~o7k_bykP&@7a_sFvd) z>JbhQ(7X46ZOJhb3UO5zI_l??DuM%+0c!eqkz2uqPJLz9tw1wC5WBQurFgfyIRJp% z$68Io;`_%iJe_@KU!%Q*y;9Y9@po-z%3e4d2_N;vbD84SG2+^P?z{?Pn}XDLeJcq_ z=ASK^sg*wmDH|oDG`HWymIQesOwr8028GWrXh)Lf144P(rgZ_9Rc0vQ?L38x8UD{2 zALN3_NV&iE0SR?5rsx#QKma@+6gGO3)i0`Z{;zUi9wtd9!M3}G1M_+4)U@_-(#B?h zzN{Idy>0#+iJdsfvA7HL4QVu*Y$lt&v$h(J*oa9J^mMm>0QrvzPxCmi#ySqNwIi3x z6xPROfOdYgQ8Sm>|tVce7`u5 z(EG?%b(GPAToiF;{tF8KthNwx&2`jSZgams*3^>fF?O^sZplvozQ(zE9yPiG)hkt* zEY-=VsKuPDPe`RX20aO3LtBLH$!(^KMovRX!U=Ys9o`IE^5j38JqWv=VAT z+SEhiciSn>8^0ur9>hkgXFS9O4jxW1);#y2Egzf%e(9way*LX9!kIX0sX>`?XCR+n z#p+|N?4teggNIu_JyR3EU3l|Jn0?oXP9mLfC0=rzfGlRIYNo?VhR~;ZO27ZgC!oXs zle57Yk}zP>v&#%VD*T0x!pQ$n^lm<_o5t&Ik(qKvG|KqU5drR5C%^O#eIEXsJs38zKtrhjw4` zxR!C;(%?{X%&bL71RBABWli#LfHieey^6W7Ndskk)9ERG$)9KR5RPKv3q)~OX)Ib0 zzo6($XoZp@sVIC+F2|}W8W@^?P_u4Y;wnB%>!YcQ&e$854~p|RvXX_~XHdU%wJK}^ z-M@@gW*hO>ZrWoV!umE$4+2e^8MRWb=sF|h2l8_32cNZ~CRk6y65QRPi7}(Z(E_6% zq~s^-RPf@P-iNQFV*^MvgoQ67tP4%fpfUav5bBR3Oh$Vdd>|M{Ml>(}#5Rg8KP%|y z#!9jwA4HmZ?MB>U&o!;*^n1+0GW-=|qdvB07rv+PeLV~@8>{e{HzP^ERAeW!w99FN zpShjs%Zj~=;l@JmUw&b%)cR$s-DTL}FFpe2t^#Lkv~ zbNB~EU&kh{g7RV@FarxetCFuFe@gle?+Uabul#Mo*MgTK#6_3@_<&p>Oj`#r)1YnX zPlY`FuU78;p9uvD0QlIvR>QpW977tH4|%s`o;6Y{q{cj!%4HIFnxPm%60JyVDmRbh zSqhD zgq?*GJ1w(5XDN^C+f~}}p7tYh2q4GsR$Sn)2!I&%AcH(>Qos0*gpte+idk`6I?cJ> z(sj;Kpez6ndyu&OuF4tFoC2jdT{sH=KDUq^h){9DRB&m*%VM6uk%4*&wJ#> zD?6yKC}bH)!)47tUJS1qIc`Krjk!`1PFMjMU<#(8_9U92s~-0~UH1}rmHG;FBt`+- z9L=W;Wy?d6L=~?six;rSR*hDtY{6712G$L%fbB09u;+E{Yl$A{w+@WsT)N zj)pc~+aq|pcfD=TiDaA|O18+bYIc@>6;C{j?UChQCYTcQVKPIO5^yoP%@I_rJ!p9^~jGlHc}Y5Yw2$*I6$dDK$@pq7blk^ zxza~{y7hxU%})Xz${R0vuIdJmF>HYR?yZrDv!}s9OJhK1Q;2V3s@Bn&y-vQd7sknF zRU)w`PSeI>i~FS*%J}eB*3rq(a=#gt@|pgP*L@ox-V|;rTQvg;^$VbMsoxlXM`2MJ zSbn_WOK|AB7)oX*bGgz!k#fQqcv~zp{rkUblifi;Qk*$ z9JcJap9J!x2}`)ph~A+dRchv`cpxB-Z8E$M9QeB5#n)7WqSBty#vIn)FGDiDZ3?T% z)V-H~F(qYB}cX%)1Et6IZpd*a=EH=_{v_zNeGPfxoi=hO6+0+e(O34b2wc(w0|0P;R&5{vsmU;Y+4u{1%RqWXf~#vhnGNFJL?8O_ug#M+B-3s=K$@8wNsjxoG!>&ac0(ap8|_N#BVBw zQhWcmkb9f7KTL1arEBZ-rpvPhR#gXILohnvs`AZKup&a-Sfs81=W@cZKhg{#$&_z- z{EpeI5JhHx8SqJ?@tg(`sDmw*YcS2X!pz~U;$K3}S){u;BN`(Cqsm^izymb`S zfvE3%-DIJ@^77x{Q(KOGuc(#ac&+))9-^V^eFatd2}R{cPOn4W?l|xeJH#msT3b-P z52^=qqL$>e9;4|kN_v=rJ++aZgF3rYbpt^{_82ZLXhf(Y_75CuQ^G0T+K=L%8vHQt zLtnYnxbJ4!*Rbjda}d-Dho4G77nL7_gaCv{i<=3mZzM3=kfA|lrgJ#tM6qdM5d{rc l*CG<4sP(sPB=Xd1EXMWTEwtm}bM`V}`@Ua*p449>{{lA4FXR9K literal 57877 zcmeFY1yI}Hw=WuswZ&VY#VKAKiaTww@9&cA-KC+fIxz~ zy99svo%812-#K&V{_nl>=FGf#@0;(Q$xL?kUZ1^}?X}i^n0Z(N5Py(Yk_TX5U;sSO zAHV|=@D70a=+WOV`oTiK*pIQXv9Pe8;^N{w#(#>BkM|T0kAUzQF##b7As!wvIWfs| zQZh0!e4-aG$w^;6BPApKdk_pv^nb9hpI~D@Atk^gApPHd9@+pTk1>E)7MK_efJY=4 zm?RhvT>v@&00SFM?cW6d-w(zkG>tg8kDolnLwA4@10G>uVm`vc{F`cYZ(sE904x&h zXRr8Vah_|K<1#pt@(09bJ!X7c*-oZ8e#G>~!X@y@Q}P!tDJYp)SlM2)3kV7ci-?N7 zdoL%ips4iWqn5UguAaVuboRXcB zo0nfuSXBI{sv25TTUX!E(b?7A)7#fSFaevKnugEJB9>QH*VZ>Sx3+hVPfpLyFD_A6 z*MH-}0AT(*tpA4WKj9)l<9dXJg^7jxH!h4v?&yX|f`$Ex59gVz2CljDa|Zr^$E0s# zvnt!4Fuu_|BC~KAe@f0Iu*`h?H?)64_P+)!@c#(ee*^a4aUlTsm>B5I!z2Mn15oVY z>_84tSG+zhGvm`yhMsWoN)gOcfr-XHj24s}MW`sx=fJ^ygPL6~7P5-4=*W+UwJYmU zDrFCVuFy7w)K4kHbh+pP7xBIIS07Q1AQW-l#P0QdVopuXeQU{<3#o?jW{8h8XjKyT zuRu17kb_=z;`xCvvJ`}A>X7B4tuBRRSbjp6(-&RCyCmzCeXz-7qT(0hXOqdw?{M5+ zImH(D)D6;#N3W&icxG)2iREPr)o}|nhBYlhk+Lm%*HPsp&d@(%j$5)DdG^0aUDGwYjH(*75?~g=^i(kB#N<^dA4rIpJThZSS?GG?d1g*%5MK&TpjYfzad_L$eKQ z6M~KR0*&sD>Q^24`BHpugKCreH4H(^aSbd{g?Lem2;pb#>sS6e7o-#;o+A$c*@^1b zWvpl#a2I*4!H-(lnT}P)X&>kC>7IIJ&}7E!>U4m=DSXaFOV^a{tLADKf+dqMm$_o< zhZ4w#o<4XG^H={;V6LTfl+JY1YfIqo2LNz8B{%7!--YXwVT4*$okd=NX?hU;>rn~k zU4>sGpTiIK@5!6sh$gPWu%(JB!vMa}JNqcyGzLs}IyOfaze82KmgJ*pV_n&CS2mEs zwJ&^jJ*@iyK)YQZ`b+gdp|9(RPZfc@o;2om{;5+QUd^{$PP}Lv=nuuS6?#%*zt|?) zqbhbizHCf%{|rPtW9#QX+cL$Pw6V8X8(r5ox~T-LkQC-GgoNFSeL5PxD6>Xh0PFUB z(lfoi>gUsrqtf{nYMD^(_!fqIr5wO<{kzINsS%{mx6`Ljz=K$uFMI9B7YA?4uL#jZ zEG#B>j~i$FD|%Jpmv7y!FB)x~i#^5MqA^k~_tukZiAg!l8{+(%XHFFiAFmdcwMd7q zpcj}s9Vh$GtX&^dZS#quxJ%o?TMzo8m!qrNf~HRphFd(vwur*XoWNy=GIivyNc*+~ zjyU(%2+yS(jg2>{+2Pwh;>+nAdf`)!&`w)VSDpm2QgR1zLK*Clb;J|NKel2zPt`HY|_%1buyYm&gW5;N9sG+@5yh|1|6k|ml?8H zuRnim;78N4DjmiB{gP_p5-k)u^YDwRG<>3FrC23x17_@(eRjdJw4vc@tBUyfjcDfzyrX++E6~8O!H5-ZhtI&#{zh+K8hw8gj zuCq)eB?qvZmd2E5@Y9iT#4g7Sc6Zageq_R&R*0tRV|MS9?%x*0iOaIA6ib@AR-Ue` zA-CAMy%s*-p?N9F{qVykyLEET3zH{|EuMuE;!AhZTH_Xzf&?ZG5-PqIFLOeM)DjVp zIG^setD@QYyt^;AT`oU$wTS{5?`Prr2T&4+f~}E3$;~+9)MxhUg+w}%RlN`e_a$hk z@i^!H;q=C!E?=s5JIP0A?u`2@31V6FIx-&`%*&rOUsDg0%>Xm-U;_IrC)a}p8JBzR z{Zf^;W^Y0#%7i;=qnCs1WDGbDD7R!!?$XVkKWVviH50nN%*lHIl<%rn zHAfSM?|kvcot?K8Y6vMPc>sKt^Awl0E3yB~Q;U&YAwPxv^}y>`y*6;O*7O;qt3y2M zxsAt^`xB*~3hjMuo04ppY1vTdFy;+quR`6Mt5yVgt1Yq$lyuXh((HLtKS~2+iFt)h zwsZgLXxA3;1@SqSFtBG)RcSNYCQA(F9_@ds9@9m7hYLT9R^uO5jqdg zAV)kRgD4-W`~{DWURl`r_cB^wNKJ~Z{Wl91Tnfw~@y1J1x6>E$H`GyN1*IO=I%7_V z=wwezDxpd5RQE{Xu~_||1SK{d;ajhcB5PB)G^)8ZLC-TDDw2G;VC16d*NQTtV`Hn}kCR+K< z^~=@gq=E3q`v~{_Q>n*@da2EF(!E3TFZHA2#yZJMu1O}-XY;D4nX-8NKcfP?SF7a3ua>F3g_bFmvs^>ybm<`~v`E#P=8x zorEWjyxw!s(mDU1?{p1(IY`NM#n9uNW@4L!DXc|aYD^z*|7P)Zn^b*H>kn!{vI&pQ zcE~b^@2iE~{b!4YSEF=Ewf*)>`7SOBqO=4W*p#<;<^!nS1wcD3$^Zz;YtnUspd1hpB%^&J45^8;Vo zYxp;diE{_;1xZ!ms>xSMD~)xLos|^f>u{zrp^R^9mZUd$^&=X_LY2$n;=W{16%L(y zSWWn{L&2iG*{9D~B><(l0^7}?eS`9&R>z8II)o?EmpLi%1^DW>Ab2=pI;lBlH;YyMkive5GK*M;x4GM|m61 z+&tV95tdt(!pP)KwMIhLM<3JtLqSsP)vA_Pzl0%AP-=_hYEEF!WWr1KdCCIxc+nwo-cL8N9b z=&a3B-+BgP)vtGeBQ0xe4%cX+*~9nFMnnGWG!RAUP+KRbZ^|?ruWl9 z_5t7%pjWLiZA#7Q;{|0Yx_l+%4QqHPxN6hJ0R7M9ZnBtoKnbg!ms|_c3S3#(PA4s4im7uD9dXX^=mExMr=Pc;-`Q~|3N_h6WLJ1d zcMKB#C{e%QKa~3kXfq>pQoOS{GN~sDxz*0MbH`(B2lB>F6XV4O!I+4qD{XR{k$SWR zrbRw7O>iG(&wRV2q@+tVPtqfa{bmg^Bi?>dtVEOflDDQbHpA{xH8q!uNSI#gyC<_3 zW*aF2?{s?;$xPLw>Gb^VLni**7vz_JZRP1R`F7fl zfFnk-dzpsb z%C|6}@|OFb?P|0{zaY6nq-Et=hA=HTesc`__{RL z2V}h(TIs>{^ILurusy+Y;(1PC12H?5h6P_f^&f{Xj8~}^L4sHn1o3S$G90~R$Znc^hc?1 zC_eMoI5$ObTG5ug2a~ReAE)h&^=qjrnx<(rdx4)P-MRP{Y)_Y4%+qLPB(JtjG~{R~ z4_-`fS}wId+s>f*ZY6xZkm_Z4xj1`o*e!OQvkl|wa`85swv0!s=sH-wnLV_a?^kq3-&+p>GXE@Zcqa%E0I~?Wy5z|$MG<>T~ z_no%1i+f*r$o&+f09P$2^H!MT?jT!EUTazA+6(vkMU?3ZlYwcd! lf%RYHRZ0v> zdHgYwMnTYa^T5XVXdy4(wrVG7*q_sJzp$90 zgE&4x+pS-;$77|gLECr;_pdbVJQzZ)LFx82S&29(L=3j&CH>uT{re|qb%=PFerw<% z^DD;14baU-r3C9Lg%3}`ZuWwAs_)y2m#cv*b4yvza4b|^_%W!hA(sAD{fAc?cS)?R z>uQj0%SzL*abu5|j+Ff0@FY#!A6M=!XcHCHJfh1riWa9l4R{mKq12WVn(~fQS#Z7) zp%75R#-Jegany+aj0SA9d0~|C07#&lb@9H}0u`J$_o72I)6?R{)+;KtVdSSOtuNxX z;tV=!2HqxfIWmT^xl&bD*DT3<5@b{_;`_X<9`_Nq`o!((z`ugfjZ@p}2v713V;r)O z#Ud0FChPeJwW`;1sW!2bJG26@yq06vTM}a`h*=XaXLuIl_j&yYv-rmK9$f149F=2e zR7T|zvNn9FH|H&5-P4l#n3Tg4pSh#XGI2Q9zadxncdD85np)LR&W(*%_pL|0YX53C zpr29o0u9Y$!Uh53OfZ9S&Ip3+ccAr6$307bNpSRhNznaZ{NhQ@4Y!*AKy;M*10aO9 zw)MPuq*e8}C)Iq}o5S8zDhp)g`q}JhHbPDG*TLuc_-Y~N+CpkrSqo!3ZPp4E600tJ z<-$Tj!WuzK*d4h-uVC^M|zmhSco1#@t*L!iWWJjp45rsFMD~n zY4Y2$+^gAhbzjW%g-uFUP5mTeWz45b;qbtf^*=K%IJz9Mk@1)EDo$D-_t&_i+$Ax) z>4>?YF?e&fZyx}9lk47vWsIpGXTNC^KK)wid>INyQt$O2zvv_=YDW1Q4hhGZghi$4 z4R)^E&u@OA0mpPSrG#fh`5R036W(~zJOF}Q7!>kQE=XlG59-0B1_k0cwmIvPO|eQs z{#GI88O`i;1RmEg#YqV{Z&&Q8NqC!*9h3R1XQioQ16`3zUzIS^zLZgh`#N$b3i~Ln zJU$Lk?5i*t9Ey3QlRPc`$J8V4|%34e6*| z^Q_byE47^<;49J@-twCB*4fr_324l~_L(w#B|st71)BJl_EFS zLGt*AKZ-XYu(VBz&B)Kt{d;C45>?gZUeS_eX;p%abhgoUG-sE^BAAhU%qU4@aN6+t z>H%O7w%w#sS{`xU$P}aAl)_u`xN+K=qMw!1`A5X)Ul~6$_$EtJkV@lc+xKu_UPqr} zV8Zhc2l}(Jn~qL6UaNw`8p?gdnsjSROTYV>d_V36x$=*Njk#EP4k^s!j}$g%tjP>6ewt5DL?kq|THV%XZ);6AQ8ccv%RK#!xt$7RCVNuv zvcK$B7CZn(7k@117A*Ei8$Ih+4NvZ!4t3u#(a4 zI=ImWCP$kixMpgRE?57`GK^dC<&tAxkBajm{(a{P0l6}brkq|iRpx&1{H;31%~tPZ z*aP5=!64z!E2`65*PFzAzd^bAdpEUXbf}95fA{&^Fo-tZm$?gzvy!VSDe{LRK40vQ zC!1qaz-I1?4n@tK32*DT~B$fU;_0s5ater(V#LBr;;s2cg2x z=;$~Rra_xk^)NwYOJ~1$rj(W(>H|Fv9GfW()d?alSf1Uy-HVStA13WHR&=Tyuzw(v z#Q83WXOOT%@OjaN#w8San|jBEO4v){kGiZ~*?$0Fo!${GkJPUaJiXMahLtmgV9S5E zgvQ>(k`V7d5xSkIN^+z%Rb{Hx$02r}aFsENn^647DLSJ+A#kT7N>ZQl;ah?5sCpK( z#yzH7K(LC>aJIFKXTBTZz*KII>7=Ev6T1i^gff|DCl0D7+FZ0KF!k)rzLY#3e!ohZ zJ)LA)!WU;0u0)Zd6YpJSM?AAVU1hQ`q!_(`#eE>d^c9zBi8jH+g9)0voFWviH&fEf zkYL4FNPi|cc9#wBrsak@2oXrPSM+Id;3a<}Uzh(Ycs{*Xwd*wU>Fzy?#{)nSE+OoY zb64z7zUy{UA{-9lnqE-jd?b;(Iuz@9Gg0HQEN2CBdjP=C?;NfCy?UQN03H*4e*i4g z-7h=; zTBCf$31cCs&i3OS%BkqeWj%-w*IM_=FrA^!1ox|GfNK= zOzb8uoR|WSc%4as;m-%&eLj4p?<}TIgnwEjE=f4*e~9EbdI7|Tk2DR~@w@4&aGmM4 z99(SAOS&p6Uk~DBGB!?0r^k8OZnl)19UTTsKR$l|L}ai9DNWdy4M~|rZ`_M3F^9lj zg(rWANfKA2U=##*v|_t9zguC6S_VBs*qIQDCH)!%7uo*dcsx!KQ3|T4yW(p(qK@QbUOm> z@(1upa4bnb_lQuR>;i9HciL*qMEKn0X?IX`9$aZ9E}3oOW^e?6uu;JhQi2;}u7YDT zOLm_})T*AR`9qMkG*26O|v4U9b2)iM9BraG2|0X+S-g^92yu@y-N~|{(nd!^Zl`t)QdtFt#*e093kLm9 zRa+O{w&A?&{p-Bk{fH(e;tL4zd|5Iy5-P&us;oiTYuo&(zB1G&*u2iHezD&y?^m!D z`n=tGagehjJ$uv3Q@l85$A$adp80!Oo-MxP@mwfJe2)x8@9)w??s75P0y=llp-Rv0 zk#D2Bpq_`o3QW!T_V>E=rD1D+Z?b>K^OaZ24}g8x@13A`4}jm9Gix`(M*qI^b}Dpj zv~0&@)$WwKA?LoT3o`30Rh5aK%ziBf7Rw^*!P|A#5Z{1o54mb!$yACODKL>>Sv=K7 z5DC$lv$=GRz+n(^NR&u0a>_b+dLT*Q5$_SNJtl>F%XvRE=~#V-xX9t~P0@(~a~STk z;-)!@T&IjAT8f>npPTPd!3rgncyl9uIh()D=VIq0l8nne;7hn)M_nQxlde zk2hj$>6_Y16Lt+Rp0QKT_^o7GUQhB=)-1#&ChW<3DL)b?C_Q_G~M_mS{u=#)p*O2?vyzsAu*@NyJuM~d-zhl=lA25gXP zHZm??Ek)s#tawcsj%Rs(wc6vFC|9p4rIjd`_9j-a15TrBF*1twfX}Q;f#6SXTuC&k zl&o=YR-P_}A+B;tS#6^(bT6)DTDN(BdY(2vwgc9stF^s7j<~5eENkGpHm%w0uBDX9 zvNpH!b+!a!C!lTcpSgZKPVk<%sU=AW!b4;WsEJ%&3_ld4YL;5;6zjUYurC1vX}&=? znScHXRj=37R-)L!kSv*f09eQ7pmsaJTP6(YjL;*u-z^+5L-*o>M#gQDze-tYbEh(c z{P-ttU$)yzi9VNek+z$&_lu&;xullT2n+V zLxb{*g`3fK7kQh|lo@+^U$i`L_&#x(OF_A*B5p@8Wu60Qtp^md42N1-|`7xmB`HlKp38KY8ns$ zLB%g$c9j!uxy&2y3X==D=-FPJl5l)$l#d@YI)Ccoj0lY?XEz!c_ggEUm~arkHuPKc zBG?dq<73cM*3@$ZG{I zodCsGUUdFydPN)LpS#o9B}U$esbcO`GmI6f`Hnia>3t1c=jGB57hd3)AF03s(a~a$ z;IVv&pkb;wp5mcEv!;^e+ylTNjCh}lBalc!SYz8b_@Xj@_SPHquGqc`CPghktd`Z0 zV=olm-830Hlh&7nb>JrXGbPz#SH7{TNf)SplQwPaxKNT;Cipfr8>!>x-f$TQ&r2MI zAf%j*af6@_fG2mEH{f@Z)u3wbE&pmRrl4Wkk|z9Ug%7)D(jj8PxAf#0ua9}Yb64v@ z@k$~LsSLT;79=1~v)o*)BI$?1aX(Hs3$JRg-+@FM(x=Y;C6s3|>T@#}#4}ciN zbV@|duuv?R0TMMZ!0@^Gt5p9-j8Lvq9$X2X7~Y~$&8Hmmr>Dv$4GX(!O#>o|b#GNo zpDQ1>u7h@Z1A(;EQ%&MU>EhZGhXTVgmO@F1hxkorS1&O~Ckp<>ew?AbNPkjhxD>k& zcTBgt(4qNnV^0#(`R5zlIT(0*-_P% zNobU{d%ljQBWMIkLI&wI(~4x>dmZ#kIdG#-m|i($%<;HB@>4 z;DNL!-zHX0{iS=Mi+P}eAC{-D<~->uOEzTq04P$+^D5hKEb+@w$fOIVPir!PLh+t0Lj(t~L3a-g$oJ0}jA8s~B$06x zevVEUS?90qle@|?zrfxR*Zdmf5BZK|4GtPBJP?s&w;C5DhXS+Azc~EFdGpxN3^!|p z&zLaN^C`+0ifeD8mz&5NNu4R2K##V)_=|Ze){w`75Qg_HdJ{Cv*>lVQMo&$jW)JbO zRXC}nbUDidVDJ$0XVb*tmz!^aJ0sq+ZPeQt%9ZD@2nIofLFyZ3|#lrH4WDL^QGlN|Xhcy?~Zt#zcRE616SWS~| zdL#EQ^XEPMylGgJ8v~e|#^>=q*Cg_n@~8d*!?wfc##}jU_HRYtFEmOzi~2@4r1^3l zUolpUcwUd^&KqnYd`sQ`bf6uGY0DJFlJ#qIW@~ssvotGF7T~ z*hX4~dGNrqZlQylJCPnDGD&W!8oqP7Cp!qM6@x)br;`SbLPetkEFM+ScC&)9W9|G2 z>nL+sko!fAE#}*cbr~ewc0}o==#nKF%*ePG`yj`iW)6e<%eQEjXje`>rVj%$q8~e* zWF#nK-c4qqtlV*c>?z=u_13 zod=(DNU7prP){aN5*7d%6ok^<@uS;Gm;!!QQ7-`$n3nFfVIJO^H8Cm^-!8us42*hw zXKdw7W>Amz4rtnxs+X#s*>fztYGqjGy4%^J347}+GqBM`W_nG3X6wOlxganP%OX)L>&@ReM@o-Ie2q5d1TpsmP;}3MzjA3AA$kJ(qhtqIhte|G)tev@* zm{l6y_sA${RO7GdX4%5QNEG+5MT%nRh2X0B%V@tzQOJ9n{A2$pt{Nj1Z!ngWmJYb| z%>aLJ#~aMixAOclg4|UqCGNL0VVhpp4}her40VL@p8HBSgw=4O9lP@Tx6~NfT8d$@ zQ-xMoR=8m!RjMAEvvf}1_m!V9flVR=hWr>%LI`Cg2n&?AOJ`@DH7!OUVL=O@n2=ZA zIegVsjnwX0ckwoC$kE}zv1xVl*-&I&g>D8vArVx|?t{*s`!a)g(7~;v+0>0Brq(2* zKGzpyR^as`Tt{ZscMNVJwu_!lp~ zpON+N(TQeC8>^(QR$}`vuqv%O7p~Q)8u7w33NqimJ+ZHL%5EvhZWHp_#vkcA@DFv$ zX)T-Y{zrIih~W&i-C7HkF{Q6+`2>y^31jq^KoFF3ZAOAyVv*3`h-!L zUe8!2+RbRw5V*Z^J7W~_ysd$^HsJx_Ye}|6xjqS}C78sjQevZYOKd}n&?)#FGH>9q zs+_Kh=(*oijsHMgx^XX9Ib)7j{mSS77X?o52sKcW6pJ4@Nb*O~ZFn+A2NmsTeCIsd zZLDVgYC~_0`*M`tt#5Qvn?)xoyn=neN%kc@+~PW6I`atrK?wc~8DFg0)wS;ORw1Z2 zADB7~)j!|Y*;P+x)IXpba(?=wG3H~8*8;yoOHZsrN$aj5sZAcj%WS+l`gizPj>Z{p z3web8>B*-ipcunp6Oof!4r%uc zCM#JsC74HbcBQy|w;0P?zum=Bak?RXLye!&k#&so-FX?}u0G*7+HH&ebK4%OESAhVVGy| zBn0b+st8WgWdTET_5(oWSUS=V7p1xR{>62+ZQPD2BFce7G(cH?aO3oc&|2ihu%=RX zOIOWR+~K_f(Nb%uupc|@TvBy|OLI`J3Yr4i=Wt;0uEX!k`u0~JZ!&Ho>r+v|pd1ft zz2g_Dck#K4b{QIv)GCn@$R}7|-GU;6Ezu=+QVF}JRS$qb3Eg5tRN%#^(xjsR5Q3*s z6QVaB$**e8PVC01M&`YAkyPrzk2HZDE;URDvNg&6&K*O>|z3CO%8N0G%j&-$5(x++h&eMY_8c*AS5Ni8H4BM2_#Yy222Vh1euwGuX8p z|CfAa&f+$U>--BOB!iCEt89s1D&dS;th-uD7^UV9F~-F5$}faCyRtrssQvrsio+P+ zwyWp=f_(EW3IxtK+49=|-ZYid5Dp^Q<((fe)|ABW%24wLx$T7qa< zlC_so$_N#^;Ny z;u8VrS{)zUFKVEgS8N@u^p}-`gNEKEe2x~33X|+v7bIF%gP+}&qkh%`ukSOroOKaa z8>wq|e@=rpuTyt}8p<|7;IfbRT8A1*Wo9si72@wK)6G->6b z>(mnrehqH_uzhXspeYfzUS*OcN2-%Ai_V*2{6E<=4ULqnok@>s`YoKcW^nNdWj7ac zivG^*m*0Wa?f@TE3cg_PzvxQxq6_;yolP?ANt$oI`Fdsuoc2|r++N3$?=z=Ye98y5 zt)#bQ3(E#xpfGNMbRMnB00z85206|lqF*G2Zd0OdSOFPCvid*OQw^Vp+XaTR<^GS- ze;DIEr;3$5r6+t(i*uOb$1rqTmuaS2w_bQY0KOjHlX|${%Mk0!7>byxW=wZU9o=Iy0Wmo1_m$`y$ zTcLE-;R9fc%KHJ}hwl0(J0TSOt1e!zzAAA1>?RBJWT2NOzfI}o+zh63+m4G}!G-Sw z;0_byf7t0C?53q*R^Nj@r!1lwRp|2mRv4vmm+6n@Q{>{!(c-dk0;wLibL;Ho1K^AR z9kFaBxF_}hM`v9tIdZ%-&_9Qy`)6meSq-_sO{#wmsqoLv|2BWWq2Z2BRc?R2QB_gj zGXu{ERLLWv6?WgZ4%S*ErPscF(Gq*nRIi@BmQ zv6G6SW%7OR*ff9UQMAYRTAGcwy#3K`L`hgeAL8WYyUi!_IP`4xDHSCcOG^in{}Rs} zu$J(Azv`aI`;zzSiXGfZm>4dZ`KvkQ;QllvUS(VtD7UQQI#4zo_XcoN|CxygKbgJm zrTuGqvk=dTGdjKpz)I3F#@iOXoyW+5J`w3ROE|c%T)M$+CgJ~m976&cOHZP2u8GN& zY@y(*534b3)Q%@3%H-1081kZ)TK%Oqx~e{W4rSYkC)rn7Q?+j%sYiWM=Y-0W_l`!i{OhC1f}O3FXSo^NRD5&dyt z#fTM|Mw?Sf$~+ojd4xpyiJY^Y*|OZcPjqe@h*SD1EQC88iI4e1T-Q}jQZB0l<+s}1 z@;vT9g;r8oHE{YR358v-3DbeJyab5}9fyik z+!q{$>cKuL{4lXis<6|IC@7uQHL;U*QlvG?C44+;e^y-6A>zeRT#o`58MLp|XLxJ2 zP}M1WSCq}V*;4XaUU^b{gj4G}91Aa%*k0#D-Ip(5D-{*c@a^hK50ib1lOIiuRIH9G z^J-(3q-7BQ-FhAObqT-lvth5^PJ`O^Gx*skVHx@oOo>84Kf{8GbV`e?i_<11D-Et) z&QdF&vmBj=rp(GzZmg@RO8ivieZ|mFOR|*g1C0#Dr3dH{dcnNFp8g44(AvzanI)UD zs`du}!8y@O+e?E$lkEo8R1iZOJOwfu`}{*EgP@40J$RzhM`;;(_+&T9OEr%;$@f*X zU+6Nt%Ei%{A{}gb)Yni~7lPDioMcJ{X}@?i;9L3*C;^a&?{XR*;0pFk-;PNPGg{&`dyI@pjT709?>6@sZLht@na0WVyEuK*72oX)WKAX=uedU%!lXY%LOCE3 zy$-jL>>ODr|2zYr33tVxme5h?j~3J=*$7G48nIV^fmeQf-mVmxis@f4Dy4Lg&S>Aw zJH>cMqd>fq$LQKYts_D9v5RA1S<0faXgq(W;2-zgl?+*ewYbaHBsFc!S*od3|Pma@E2<@q~I zMKTf`(JX=rAZnsvTyO>MaYbrS67+gTAARV5Kf@w!Ng9T(G8O>MUO+4MqDHsddipLs9x z8n2P}L|MJ##>|;tT{%jIbr1?QRq-mwiy$!f6d=u!AdqiyTQz?9pfCkto3(H1+luH$ z4By1C-g|+1=WjV;&78T}KcrT{PEB)eqVAz@_rVasJO7W#*^MzoD5C!3kQl2X-%NE_ zd7?rzP(MO}P1`Y&*MnY%yK<>LQnKoPgbiYdb^*-a=W$>7{VBhFV zqgn&~G64kzVTK z9=qw1BlyP&hr?X&REmXRa(%N9p8-aCnj1xG@Iuy-Ge^pi-vPS1?>7GC*M03fHDBNZ z0P_~U;POS+M~WJuv~=UW46${d?{|_-QO+lEm?28K^g~I?Nh`nv*uK!=-<7|2c)P2U zsF&e!@JT`C4>lH?hy5+BFwD)cR?P>g15mAxd9`?uu{7J6mX#>;OOcVOpUE=7Pxi+h| zcDfj^I`Sg0t#C4BpW>C?D*b6jI=hPL^4)z5kgF?6 z=ZykETWw}vrgOmeHg3K)=}f3{+lr<``z#3M7bPuwXBT7(b_A2^dam$<)})w*e!1y$ zjp7|KPgL@s(2EF!w0(2fj7A6B-TVl~4`0YvI^du)iGtjprx`O;D(dB#ez>H|tlkwL zM5~4UTvw@1sr3cb^xq4aSaVjf^981;K>P_Vfk%@k;T*Z+x46C#eL0sT9ic&|eVRYU zs*K(txLEYP62~K=5DcZ z$4E!X5`7gEh4z1`xajy)BMjt8y(zNndB=eNY@yq6GTBZ%QUdx3(sV3%E7f*&`yFuwM7rlMpvW&KfY{LoJr{U` z0d`5~gmY}^x0r-zej?HBiKIzqvHSh4{ZqHUb#2+|^%O4XZ+6DL&71@O)Kx0;c=`Z% zTaj^~93lQ|f8&JJP`|cXB@SmfAaZqgH$3igd7;3vVA$nUzk2LFYetV~dtLaOnB*jC zLIuAw2|{MDN$RDmKfM2Lw1QI`C;p?s1HiQSW_)nmSg;(e;!f4V*FRV3Z+x43XK@m><>XO1eSsjn2vS`q`b>RPJl)_bevXy`1tgcrfXFvO?|7 zlBuXNpejVHJQ3{{!f$ar@5m4ye^=A2a6Mbp_Wh_h!R455!{uLn5UJv{8`aKRkVWsJ zAUJj_h?2AZzu0@vsHWb3PZR|~KrAS|D!q&Jt{`2E^b#N{(g~q=h*Fg*ARt{pdXtg_ zLWxN4QX_;KdhZ?L?BAKY=AJd{f9|>SVrI>Y^J2eYE!NI{cAjs(-%oq+Q6kiu#T$$k zWyeM+N|wfyd`t4rMfWuk8vAOXKJs%TmP>@hrmrE*lzle&4kATu@ z`sz)Pu9i-w*wIKtU?+jl&+4ShAn$m)X(E zLjPp78ib!OY}!piyV85>{t{(@eqL)%Wj#zTR$TCtjFpT$ymRl8W?Wt@Z+$ZQJUQNz zHTkV}ozr%7wc0jL`7T{JS%n5`=z%3%^;e6k|503sylBoP3!(u~I%^Sn4Pq%vGtD|I zI%c3KqHFXE_cHjVd=C`fS1Rq!wq%0aCKZ#4Rf^YWKd-|Cjf(v}-X>_?Lv8|7ZfAkm zNQSG<l{zh+B*8TqJiv4%*+~2Q#2)BI3!x7A9al3Dkw}Q|)hph2;C82`nPik_fvX=cN za>)FwLRn`xf4O?eHqHnF7Chg^nFt1 z25RO5CAW_quei761aj`8|Zpp`*(ckOdR^P$ql`9&;6c5Y`Yqh5yQBBW;#>dw; zdb0LZioQH2Grz9>Do~~(TkSM3M};j$WTaKfl0WgqxMhbB>-f6HB&7?O`m)|@4TX7; zbvTp$mk131n%n>f1Em>nU|bQPbm};dVGVfRj6!0-7Nk6`tl32aZ<^uLRlkbJ9@4wH zNz)9NU2-*zHoo#u5CacMm=ZcpFm^R)+xPq{yx`ZZds9~N{lYUT46V8r6^PvuphVOS z1r3$j?c1N%JZULu^|Bl?eMv7D*3}tAWxBWWUa7j#Drfar!0PVkR(Tq^5bRxjxC#di zHOy}Gy~Y5MG>aI>AKVo7m*_UorFh~iL(qevhv{3&j?=>Da+DvSzBDn|RIUbUw#VC!JJ?P&@vi;4IsuS7A`EvsQF@>(Q(s&Kn{(M>k;;5-lrPM{Dd9JU(S zyyVB8$iDDPYJ~)5_$KtE8~09kbTF@s$AEunG(T!WAhVd5G^}E69oIw`cMRojU*in^ zGiMvQ6ky_aH8pyZ6TYHZ5kmKWdtZPC`BxbmU+wTkzyuBR2^=a3JQ6h`C9xX zn!_E4pSKG@(m|(mm(gYdaD!co?UY+?zT?=Scilu_drFpphCFF@m)@=`SKalQ`}M<6 z>x^kLi;tZ5%!SJG)5v6gI(=AGQ&UqnbNgjn*xyfDjyXCzUgyR0X^kf@bI}Z~Ti$3K zdDCdqP@U!Y+|&jcuJ_JB`fS(0_2Bl|O1>F{Y(gsRbu(dNWTDP?LR+pE#q?nFXWY*9 zm;P=t<%)S0GJI+S{M%c?NI-^N8h?Z~(vh3Sff|8~AXJN_(TN#d^!Kb8zV9Jg)XKx` zR!o7p_v$rQT{OBjb+ij7*FM{wlcnbh=eZVX@@ulE876+Z9`j9dYVL$4i|vl!iwu3O zp7Yt#rPXXBd!y=n8tzJO=gi8nyqH`xU*jVtgVA{%<>ATUVW2_6?_1<_aS{=cB!1}b zxS^l@>jc?ba@K6tT~J-N=iK`y-45uG7&>JL5A}8t2^bN0E4&r*)Fcw5eS8U@A4*h( z0lJB8-%zNhy>n1E%SP?MPqo;d5~ih%1HR6jOFo$VouWyIsTnD%E^XcPq3*5Jo2ySf z-#+blLcSF_vThPu*>w>ld3kumvmp+1hHb#RR#wX!D#@pwaea>59zJHOZyrdY9i~-H z z$TNw2*_-Z88o41+dDGPXoF;6_tUWYQUQaH}7nGs$S-!FGTc`ALLVIoHsfuvL;t+JH zG2Cvet;6?&+}Hq~c!9%iD{^ekmO<-&f{wt+$YN^tcrL#1!3^>eI^M~r>LCjYu0Y7i z34}FOg>02|!O#bL;FWOv&^v$@LQ)ZXAO{JmivV9VMjaWk2bbcOr*L^KfXg3 zV{f}p7SJ;u*qdQ>N*n9|7*rr|BS)`YFVgu0nH_@>OUXqF$%}DqO)t7aOs|qhNt4eq zLpeCF1sl6^W>~?j^xEe(COkih&kNPXzn0P#O*{e7Ti`;ha#-t5+HHOyz9;I=s-6Pt zaFP9X=21e^60(4X2IV==ROc+X5Q!hymP6Qt<7Ixe;evJrF97cRh^A^Gj;cLZBiRe> zS@y>I3^&Ukr04P4!R2)5 zg8d3Zx5az;oB@9mUWGl<6Y-@!EY^kTpRJ@$!-Wmr5pr5ist~fo-wZu4<^0}H&Ft1n zzy0JIr%knw**akx2Q)xDCyMP?Zxc~Xn4|N`Ci(|PN0<f3@ICqX z=**v1EZ5lYC&ng)n2zS@#ng{g*i(Ei4l?$?@j%xkje(UP;P|Kj9?1=I#l+%^@B>9+ z7Ks7g$Q*!v)jBPqdOvw{BcIazdvqs2vNi&JQEYmTg=lXG%sT4CT;jRf~s; zvC=P9?T-(B3hql;2j-jnzTYpdrFX`QU_zZ_l+jrp4LIG5*ZIxQM)qWQ>NBxNaSpRk|dCT30-nXU;9I=G;!$kw_=OV-5R z(?i6RPUI=W?nPf#!sm%O!X2^!VHy2#qz<&_plES9T(Nv9*m0FR`Ne$%t*W4lj6Cr$ zdRnA2%`0MW0QMmG#LW0Ozioa&k0JMS-PD7*6{-wo<4j${z{bi`++~>KKi>fWFciZs z?}5Ss#PUz!D#M4xRs;o7h0poELzuxn2P6pXXGf1=4m7oh_4#81ukwcyS^+&y@ z+#{V|{t`8-)_L=6Ut;*kmfL~;SP2VoKV7-?n93$XVfpEW>8@1NwuH!o=9(Z+$6o1$ zIMv8MIgxXZ3M}=L>^i3A+}urP=*Q%x$^%4FT4e{&0ZN{vMV|Bpjxxg3)}l@m`yMh! z$CVPfQFd7PL0toYb;cQ<>hgrvLU~N;#u_bHGyO~KQyA@QWIgujy??Z(yi#$g|9Mof zikhL${v}%&g1AGiY3oQ^hK{MIWC=~+`t|X$x*0N~KV{Qc9a#z_qNv0B09?`NGVaYD zPg_4-8|9v)$#Md5nYtBi_JerjTBvZ&r=Jgl3c2W5&8{xx-kK6b`vkC#L`Q`;_~f~O=~9$ zYE)7@fcWC;cQR`<)884G+RfaYsi;bqn*z8hR}-dr_7$FtbfCrkV!o97hQ@+vt(KIo zOS3>u{Y8mqO{`KQr$H8C8K~;k`J|kV&U(A98DkB$@B{Q6D&D~b zBWCMqfViR@-m7AC0UF+t_{bh&D0S{nbKsnZvg-*BauRUUpn;f$cgz~p$LZ81tha#g z={(x9pDHk+-SF7*piv^9S{qSM_Z-+YCV^prk~Q34}mEspxz6FyF_ zOjN5nEg?f<)nWUNMSI2{J<_j8*EmPtjvw*FGfvK#_}+j1ZK<*I=X5-PWmhWZHav{Y zlpYOzl#o)MlG{|f=3ndHOUY2DrRN=2(2Nc|MhxohskiY;=t!P}p?=nME;qx!Aa62| zzoHk<_qA-g?OWll**GjAas&PjvTmzg7ra*ypEkW@dhKaLkYxl%6A`3{;`}}}63K7P zu`0fa_38JJg~tgRA{y*G6TkaT0+BNo=_0j)RVXH}_#}Da4CCY>Ly%;fe##RR9?dmW zfNYL)DNY$G0tU60C+p=WnYCH`h=zy`DuvC&{vve>aJTp~ON(gPP$uIKTLu9S@fzh7 z73D{c4IgaX-*9KuFn1q&I^9)X*$$SyW45$f0jnHe2~)4+cdRI`mkNXfGMV`!iSv9# z&1asdo^BzTElZXH@{1&4lRL!x}hgQYYm-9uDX;N_L3uof^jPo%I9?RjaOQAy3 z88(I7@vy0J9QpL^pX39N{q99$AFA3j_vB01sqs1HXB*SaUX;S+5_Vk@8^7a5b}=a@ z*>ipok&X6+kW(1AD$bN{8LyEwO!H%(R-F1>!PCVLvEY%|Bdxzg?P&%x+V z;r^zEGuT0QwJ3_dYYD_d9C^?q;E>wnSFx|X7s!c59w%PjDqb2bE(Or?JKM*QX~-w+ znpB!v{#@l|)YXeM@4J=~!(!mZQg&WCSnuye?_%Jw716^1Ou6>wYWZAKQoM%j3r~^Uq&!>qIC-D3oO;; zK+Lj9o5M~qAMW-3`Y}c7ml)@;f6o6f9skDB?COXR6m$hFnS#0?I=a>y0qpREeS&)K z2CHbe=0Obu-tc0G-&sTkacqVHqU!6us(KwHrFI@@ z^?_bWb5saK<<=CZju-d$C8M@aB4f8u)$8Ui;@=W1geIKM(3~jQ3tT^k*{)^EEp%$l zs7F`9Gl%hQyxSmPwkF;y|8wbDIHhi%4=U-&S>D$F8Nmp#|r z+ylR0EvuF$kIlS(EPTjk^$#oz%G_zH#Q0}sx6VFz_6@f_o-oTItlb{n?tknmo1?6G z{=a!_J9dO2jN#0mnOYt04C=2Rf z&EL~+lJG{5if=SYy|GCiJ$WUE>sq~}!zcfie^vnU7T-59YrwO&hx(6}J}F+1Uzw#D za{T{%L5=H?yvv@edInY{ItsJe_IWjCLgSf&vS0ZP9sG@(%0~u!HoQ@NTf&X1h24dx z;}Uh#+&TWt8l?DqDb&cf_sI%Fji=Qp$!^xL{&wk*Ax z-nr|3(oMndINd34aFH{Rak}?g`>?gMGX2=|j>=0j6}KQWt43EN)4T11`l_Y*>gf`Q z=m84063A$AQLvbG3_NFXRE5c9#pJ9{-U;)_X0v~_p`sgm-b3%?Y&5RX-b!ZlrZ&1l z=Hkzq`FO}N{5}Lc=@Zp9ykR7)l9W(jxLi$=2NKsRxetuR<_pu6m3lGfSM-fp&A4U z+7=aw%g8H`wE|QSI=P`#Gq2sM$kOt8)nqLmnwUpVur$a3@(VXCsf=t!D}@EiH^ zU~RV!ChyN*@q%vc#?lN5uHzMilP9yFpIN{JQME~+Kh4QehvK8UjzFhs5~W*jwGSJh z0JfDjQT?2#d4l^nE-aGU`L*BC!o0G8-cuM!eDX5yx5R>OEm!J0#i3)PA zmgGhrS9%8;^>nm!?i1!vfn{9RQ1i_e1~O@m$fS4J1l0T+lDWE0nw9DF595#vmm`Y{gL}6yuf^g zh9|IzU1BKz5~Z#gPZow>>h9J&tFFhsOetFxohGUBm*JwbQJ=|qXG!;sO~T#>ddb4} zD@OZyMe{n{fhj8G-=5idL>*$UI#Cv8DLs;s;{4Dn7oWN zK!lq%&B7d+gu=tA#+YY!?KBNbhI|y~9yozq@R8->duf)9QF(C5sjQAwi&=g| zRKiuQ#-?CCiqW&pI)SIRw?2iwb9X-ofj=~y>6aLqih?bd`BfAz1k&O=@wKiq+ktN9 zW&?Eh#zHGhp`3_pqQf$1jCz9`|o<-0D4T~%s3gA3O z(jA!2D(rmyMQY8hB*M_OQ0X+%C{%31KAQb1ZCZjnQe4XTxMNK=VXop%pU+~*Z>*@Q z-@T5p=mnli{%SLvqxaCcr;K%YXdV|ztRe6v{-EvEv=2@hJ<>D%aXxrrSX%zu;*!O6 z52A1m;#hlhc$Ky7g7(*Oi?qO)?~?&k!4qx5A%!OyiP?S&Lfc%3RPJ!~aJzxU1WEoL zbr*G&7dC=zi3z@(u*qfGo@@tRKBP;iS zXbn{u;*6ul70|us108>P#o@)DEeT4~@b~^~*c7pU0W6hn&FRT@xL=$UO_8j@J!8?A}0Q4x0aTh(^bu{mzYo zvL&+v=k*Jx*2&+vQsK-+((j|YDEQ}S$jAg{$Ll8r_!bKF^uFLTM=bn$(=l!fVbO2D zfQP5i0$!V5LDvAJ#=lePn2haQ|As+gJJv{%ZrDkDS_?lhGH~(2MUH1Cph9D#!_Qbm z)m^VrFHWk)YuV`187BLxC?TyW6M(8&AmN@u!dUfG>*N8P?m=%Y3aR8U%Q_VHBKX0I zf@?;squ!ta0;|Yv@zG?kVBKMS|Az&pK{Oh9TeP+ac1q;#o7R_Gx_?Vo}wo`C>@D81mvYIzwIfcX>GTUSgcHC*V_j2r<^6RhhRpwAm z?kC>2Pb>5yv&$Rmr3LEtj}DtJ2&^hT9$5awnJc+jBv#fx&}Kkdp1WLZJGgeOCbRK<0t=qHlAH5fFBY}rH#{g!$2HfkE3lhZs2hOAeg z1I=jtn&3dqOdUV9HLM;28;?vLkLA_fJ2Or+P0isVm+$D!dIn5=kqMxCIK_H@K~$-{ z(uCVYyP#LI7Om*#lA^p1iSIR7Pb*|25pgBO9SK@PO{fjHoM%wbyGBkfKAk2=A&^e{ z?Kn$4?2$b~uCmrcD}?682b?rpk_Y2;qGw_>9&t2`#lm+K zc787xhQ>%{0Ir`qA|jlAH!xAee4+m`PyucP>;w)oX`fdZ5eR5w2S|#p8%X5%pG1w7ntR5V?_qz5HWC0Iz@Lr& z5)%NV)wure;MP~8Sonr9p}vstp7%QAf08*ZE-wdkV59($3(JpI%0kR*>9RkJ0A!VEDx@6|G)n&3A9NHoB+y4b~@_uiX{CF zKshJCXNfQE{>Qy^ho1h|4gPD1{_9)xU;E&{O2xmZrb+nUAc3SJHXELm*{*Eb1o@+;E)X1GygYh;cW`T1LR!sSPYG3v^^jCt~_80|bCH`#T$CGz-Yn#36b^|K?nXx4&&b?yXoawzSpZQA?^f{bw zc9k&RLUT^SK1@zxH{)SrLEWsC?#ye)7$NzDt)<+BKHPib3SK}-uCV$?QhXn?ONU4G6=0j!oG1oeSYW8`V@ zI<9rnIPNe|_PPI`R1p|#UP)?Fae}pLDB>AC=Q~fgncyOa8UFDjX##_aZy98|-HjvJ zX9ewTYHry@{FrB%9k84S7XVomBI(+ILouAW33moLzXUxj7vH^V%VJCc-=Uk}XwZI= zs%Uep=HeP{Md44NnF-L^HEfD0K$R&bmf6t>6rbrMadt?7@buS||If=FVp!qvvDa&Htyy?kH#c}pA501IG8Jsx9P(R&q^ao^; ztBmdI$iBZs4=Ln=wH6BZY!ZEK=OA=zlTnURK>U$}fi^lTfyLA;lqA_|0F!7UmR z5U8W8posUnma|0@ry-WVH^q1E4;HVcxH(D|@$7LhwARm59QU+bSF6AAnNkFh*y`mIXpz$_cJ2XQ80dh2)-(1Z zpn*7(BW#a;>j|}wgN2hkwcpJM^3?#8C#1!_pV(xSs2 zhWH*-1*=pqTmxj_iCFrI)}M@qF(<85dOL=m7CbuR4;#>NUW_geKMM+MZyLJ)q4qay zQZf%|Hxo`QI!#)jHD(pmcN0bO&fo2jyf#7e)ZFWOak&{}`qCHPpMcakBhUdoe;041 z(~#}n5+=4x36=dSLK|F9Rk3e0D%&dA>Ub%4eT5a$8`C@n`bFSOYzw+S@_o;%*uBJl z=rn7{8mcwFZ1Az2drtd)*ik;4FJxrTJ}55*y|r-)a84?ntCep?bM_3!Q}F3>O2lHt>4Pv~}rnkeqfcqPE49mU!J|;m~3b}R~*Bax_6aRgURVnl@WJ-fI0$l8j~t!$5Z5Aq7bl~&54ES<72f^MJ9y_$do9-8fS*VWJM3$Y#;d` zbK0TLCbaQ>g*{_X)2h!9eG3dVpJ<{fM%Jc#VU%odi~yvguA#YTQ+Aw-GQYJl=AoPVFVmV?!^hERI}6>9OtZ^AWr1S9Z!O6#VtLI+j$K_@QRaJpc7%1C; zY$d2@bV+Vo{9b2&Y)1)jp5fYgL%>%|<;GIwl+Tgfsw^{sy;*X%(dCiah>okwBWWw$ zWUrq*L|#M*E<~IkEQjz>7M55bfj>Ngs!vfUfk4i_Pm_F9)&&#&*4gJaV~Nk$8gv?! zG){>MKo09Ho}M4HR;zGysZUaM)!m_%^fRY_MFbkm#I#3-?f|_F7%0rw&Q|4u;3bgV zz&p(2PGy4Ju>wNc7q7yuHiv!=wKpj3_cb$w%T?zL_;4gc_0etPn=aTA_sOHIBL0 zcqJ_Vn*D}sEA@&r$ zzv{*dR0(x-+^LV3Ap&0?)neMsHF~^mkLo1Ev-pyyVA?)q3$OVi*$;e{^7syy%Ew6wDwMay z`&)Nq4GMi`yMrYE5#&OQajzKaG!`pV~3@w|@PB!#uc)n6D>bfq8f=VCyu$ z@aRuO;WYf$%12$uQ%A3p>Q%JW`ftbJV{F@T{QvXwMX$L0{7(VzpWMuPYc3xidkOUp zKXddlf~GK{V!X5Ui6gg=k(WjPjfEG_64u2T^=ji=B%DLH*Wj(43uo(+{k#ePYPdqZ9mINwU=) zz5p7#eC9hZ4OkGZ)W#mBZEL(4XWEi2g?=)ppE!OeCj6(xa1!^?MgdqI2mpb+a+r^2g;k1dAv7c;v-ZhJQ9i}IU#RF`>w?&rNNwTjEQ<4_M|Yxd@y=IM_`YFZWgAh%&vRV@vPukW=fd|HaL zHrj~{6Y&J#Fvy+1L|o?&7Ivh8F$LbWTOxout@6Ff-=hGR*2L9j8PF90|Vt-*}V5voYMirAw#$AXb# z&>cqd7hbyqam-Mkn1i$UA~LevT%8UT>@TJD#0qBN_I$&0Out)& zs&5h*ul;J?hPWz;7Gcoov*(0(qm#6?D+eD{nhn3@nzGZ9!TEvIK)hVt+~^zD0BlWT z)y{=^Bxk(okEN$<{=aX1FeAtg9a*>on4vYg2Ae;IwFn;{Bv4Z#l9$@zyU3sO7Vu8x z?$xIVw6c63@ism};ZxSRO3xs>utRyt$TfM&Qq}ocT4j+R9%mQqa=*~F&4d6Wb=J;g5bEmQs4-CGLsBke5rcdmk-LCvpQD<*=ls!ALfjxU(>qS6C# z6m}qQ6L}(si(zPVUAT$=(X8{z&RcHV@`#a`5GnRC^kD?sR$6)G=@T4e2uMqV zvQeo@9j|v`-l)aWTC3^Paebt0OYOZaM{T)|IXcF4aqQS%c>s#fZgCwJL~kn)o#^Fu z=Clecyqot~tj0f}1SCE6bb+Cpr~vUjllEk@+}qxlUDei+FLWsHUS@h{Pn$Qt#)-t% z$+>k_u~IBnxBh}VovJxTq3`|WjM*=8gCnnb^#%$+{2FEU1IxMFw2o7^WlmH6xHV6& zk!QMW@N6TD$hKMRr5X5qb@u4WJQm{#%%ngw?qXm(^h7dWD(nzvMOs4|#}oEN6YeUf z7|*azfBAcha+w;)6MVw(WlOr{cHt+#4c)?tkS9(TI>^*6fTiKTNd^F1kV5I z(jO%>Tws{ssRu_iJ2T9?u#Z9#ap5DN9EVq}wr^ZePc-cxm67ziJatp=Z6V9nHI#qo zE1W&Ib4%x&9eSXWmXk0Vr_rqO+BdY2&nHV|>6Z0li{LGxXgB+leXq--!{1q*iK~h< ziEWR?Kd<{)oUXn<`aNk=3+2eJ4$Ko+nvA$s3I+9%j`5>(I`CLJ$1)lV!Hwa- z?-#BamZ$@0kgVTt2G^(EJU%g$_HeELdCK)0@U8&7%nHxqpJm zQcz0k1as)%F4mB{O0sYd*0DDO3M_Fjil5?+aZX9+jtMO>g{F@E()^+LyEIF>r6tD^ zmOYnp7GmUecnKoSZR#0ptNwGEt6gsOY_x0z`MW^7eZ1oNiT7vmp29vwv%6k&uS{&O z`Coh0{eALO=_jkuZ~3u$(;H2P2SrOcQg%PAMnpq+t`MwW&huw4Cohv)&oAlLH+?c` zwzH`b;KM(%3c0>Z@=M)U*6Z-v>DBv}X@$`Z8+aN1TI=#$alK>-R z@UMc*{^xUzi2qNsOUjLyR?Plmd-%~29U)v`lrJajKl!M{wnlq&$AuBKLL!?4<72bF zX6MVZtas_>R?!~o?k1zUPl1R1*B9vR@z8V|_H(a75o|`CB$wySDt#$Y;Bi`TB+=NO z(nq$ov>FR~qe+q&b`;V`@P~3F&E5N0A~*j8kY7)3qzXX5eU0gtYjas+Wy4h0*2YUP z9qkK?qy@xrxL7f=>(*8oPPNBA{YlJSQ-N?S#W4wJ=Fh@x%06@dmLz@K_k{GNCQn+i z>a|5v%xHzDdox>Z1RM+q7$^SaC1BmC+R#$`?J9eF7I88u zb>4B`dCK2b=2Cz$QWku^&Eot(SC!~Lx@c^5fV{`_)k-1GedjlF2~V%_yN=p68Rfng zS@kxeHT$B!ZlvcRS>(;+vvPNb6s5i=HU) z_wAn^#6PYV4gKqN{*TuGfAR1Cci2pLa~sOx+Er-aU=05NXNrkVr(Tu@{uIHII?j-| z3h6@$Wx!xv4WU|1aA343X*%B0yOB)$gj*=J`Z}unVldS&Cd2T?h`1vQn`UD(T}`*o zyE`n6A3t1gp8lESw0Egglvf@xyi7K+bfJ{Jq!iy^n_9ay9lfAfx222qm|^&Azmp@_ zW4=-9%*{dkwQD-AP`V@~h-O6n%fk${S%=u`m`TYKswbi?o+f9?(sSDv(>{ySYfI>W2 zZ#0Px%G;fZES&{8-q1aO-(!)xukGoAM9NSIRKO_L;EpzqDPYg4ge83NI#cQ+59vt@ zMZKFXTUcDWCCkv^Ckg2FJ!l12ZDX>A**M-Hu$o(j=k=}cj~DD#nuuHujr9xc{G1CN zT|IAO!G%0%&cO><=!d8@!#RCt_Wr zJ$KTG(zSnYABENVTSxW2Cl*WuG4p@RxNY%%anvt4$un>2rZawj`m;vpV{14ap|#6R?!Ii7ODRF0htgmb5*M0m+(t>iC{i`*PY}z;S)+-9H{^ z7O-}88bOW|X_#&>oPg(}DtY?LQ_vt(PZ?&=LfIlnrDAm>KdgN${SOW#oc&#sv)SWm zqKj75^wee-l@QFw!cnvA`MI*5Z!kko&U)7lqc4qr3N?yyl~2ViyvOQg0z(lFwQZ&Il(|JO&OmU z-`%MjNox^kaEO@IVG}~ZXN=D#&&|)5YF)=xn{^7$?9W*P_0(a4KYIC}?>sO$U@=H~ zk#Q?)LIP`PVY=q0FgBiDs2qmSWN-h;XTIgRoYk}4dr_giPe9+HJzR>ty3&KE{+zKtVT30L zbL?NPCfl8n@lM~NVQFeaxeGpiexNn$B|Tch_Kjw`d$FvlU!qnj2ozxzPwhFC^WoK< z`LwF}^c?Q|c?(r&`6D#iKixjssH&vSoaUkOmeE;N644%55Pcg{vod^zKq>Ley0Jdd z%9awwl&4?DuRJhzFC2UWoBxA|N{VrI7GdSfQsAfjILcpaaxBK*`B}3E$X`>FiCp#< zl6p2;u_=BUA6vSl3@eXc3>Raf4g&=(WRU3C zwbwV4sdd=UfmUkIdagm8Jp{E^TwKQjXW@rC`LTMD+qDH39{Uq9iX+nK?emknj}@QZ?2=zb z#9A)ae>vPa@jYyA^5^jkSc+a6NwJ*u-&!hMfI?e4-?;OkaFqCYq;lWmiV0$(EZO*% zqqVq9R)V8IS2W#YBsTi9OHf|OsqwqX#O^#Ftp_Fn;f?XcOm?mF)^Ti)%|oJX$+FH0 z9duR794gDqixPw+?H@JiU%sLL`V0`Nyj42AVg5t>?0#tGq2KXR6l|&g1X3kHBO&5z zUpWIXSg2X6*fDYce%kqmO`$CU9o1oi;RPi6IY$+npoFgBKv9$j%3vVtjW+v79u%~B~Wom zX$o(D(Lm2OcBUIqj$WIb3A_PfoC;iBM~ zwp+b=T<7xa^4anC{S%6y;qPT=0xNt5jN2Zi71c8TVca~6Ju_d(LnWJ7Cq43x6!eWP z(JFXJ3UW#tk7$`hwB7Ayz9*9=BFk&E%pv|M*saQW1{m!Tp8r|lDM6WqXXXyhmuuOg z6OPA!F+4)Xa^0)C)$Xl{VYe-!@Sz`+_qbAyQCiSkYwnpjOhlfXdYDOC9ibDA3t5gg zj`jDE7n?2~#<@P~+Og|U;gMG+){ME`kHcAHxZFa22#$zSF_17j4Ll}qkzcm=3(U&5 z&tI=}3Z7|sr+sK}4bscBAQZdvIdxmrT@W2mmFU$>09&jD)~95h+d+@IiZ#KoQ^|`> z?3C-Mu{+e4swpm<*?#=?36}aQKWmK1@9fXy6Gh!%7qy$4_9>NqCU6}zR#PsxUlyKzTBK0S@hDdSkc=^*E4JCCB0}wsEi2~2TV&-lu=BpEca|Wb zvkGdZt?e5Eehn_MuTo!}Pq{tvi4y%o!Nk4sE*TAX&M~;$y^ZnJNLfy8_<;S}{jD(z z&gZx36{Xysow=jP@@1?qD|Aj6P|p+ zjhb_d@$C1~O};DYG`b?)+1Z~INiP(>I4&~;3sh3}UnN)Lz)VG$TD|3Gf*bxE&~qLQzt2!45o)5#xkwoE(0TF@egK35qs8EXp3pFm-SQ1Mxn_;M^gM$q5)gPeuePY`Th>ti1wJv|8Q&Y$lD80ez8vrP)gT!0Tr_S4umdO4`0o zKy7-U_lXB4(CHXr4okK-llx1wCb@RC1*beZU=(PmN!Mx`Ox$n~ER)Ru`jOvZWK07;sX`9@B|-r{P$us*86mHBPGya)1QS#ZiBquD-8}XWS#kzf>S|{MCaFS? zNmO7XMoy`GJw^B;aLo}1-_YI_H}-cFN~-v9yV9G)>%^FjTjqt|3$c#t9bt4<)g{g( zNCWqQo7d~E95-htPqM8d!Tu15#yT5w*;`k`PkdQqkIO{qA*Jn70<%ZPknj^^(}_eL zWU{*ydJek^QsoD zVYt6}+xJEnF;DJ?bBfN~4~#mM=2FKMdP=9Ml52HJB7>NvmDoV{b{$82RH@N>-&fra zybAi~<25y{1i@4T?uny!)oxe~Y3D~QJHTP|OPS?mS(JCQvj=PH(}ta_`ht~XLU8P3e*B|Lv}u01=K4Ak^@#6|_P2{^^NK;9 z-U!#HO5hRLr7X#NJIzFz{Nuon$j;Fyp&J#<}c`Km|PIl2e%}0H{ zEb*SJt%UvU-VHZ-q%>z`&YYXV4HZ@9{idBOXXdI+yH>o8+044*SA_PT3hvJIC+5<= zq9d+kJgi4Ll{^Wwp9L zILflir{ zElAV226u<1ad&sgt(m!V=X~d`Ip2@F&diy0&kq*eRa7-=SM{!UzmKKnra+1f7v!Rm z21q@b@J0h|?U3?L8HU48lsAtGB&kPT;oLL~_MYTG?yGuDL(smy;<0YN6n)A*Wu0%` zO!w`}7_dip|7aiY-FQWB21C@k$zZxa3d1o9kiwHADQ>57z;to@?c70ElAQimVqLq4 zgj(YhMeSUrDzO=ChmIikA+K>)kNK=T-JSa)j`M0EEbN>0vU}0gI-Nzl95v0s3|~%vEC%UeJ-5cN0YoED3-_6 zC@CqQoKo7m7Gcuwk+aJG7%A;0r1a=Zhh1K!R&$4uXk zdsw)+ToAH?^oT+gC4}PSx+_vIs&Q9$?9B7-SxT|}N7jT+vXV8cJ|M+uK$3CqSE{!A znll+Azo4+JAcKeV&}2qtd{xT#kGsKr6!VDq%qf^Kq(%!yQ8T#|+E46?sv#=x+;ctQ zURKIZ^sBP(g*#3$RZm}Pu`}{|oJVLkR;h0@OkC@Z zxceA<5mL7Q2!DQK-Z@P&6l06YiM#P6+DY!(d5DmAt=;`9RbnH#FJ8PsaA;s>NaP~l zs<<`>ITq{6K2WmFPyEuH?QXo)QU(qOEdEYlA+p(uZ> z{fEoxnFZs?DaZuoC%SYies*{}F6v$TJ(+B!#PiBK!iPiDXyQitVeQ)S-e zo5u7x)sLU4LbIT;M1+mm^MK-4*GB@l%Dag9EJu^L4-0eh7C&Y`o^Xacjy+PuU=}!& z?>+!%Y_+4?uTT{$UO_-cpqcKLl`ZxBygeI!HzwcXX}u`99^!r8(3_%fOo`X))~iY{ z*P=Bn%(kkcoupypKIOjI+_3Pofv8B(`n*PjtB@ro&?bD8JzhJWaudv8*Jp=iux(Ax z*)Ymu-XEy_jUqy$tE^E8uQFfPz`K-U=SOMv?#ZYHeiaCW#B=n+Tmt5TLPhqi@JSg--I})JsoIOmF(IJe2vW!eFpQSk6 zWPl!|zyIGbLPq^N?!$>7^6kd>4Z5%8xDSUmApS2G=K>?3$w~Vt@!-w|pjGhz$psH2 zRDtz%iQ)Bn+BGQsq1AuVLH+m6>hNEgHUq3jJfNuk3t#8lAfigg#XF68G6pLXQ%V!d z5aA!$vC4jKq!Xbi{&=Th22~ysOpmSu% zN6;JPqr-Pi-#qKbfPpR2`T~qo$sTu+L_0Qi;rsm5qJ}|icZC+UVMXja`yxN~QX_ls zXs;kgN#+SdG|?P=f)=VF#dpU+MguIXXE0-L_JT#cRxq!cSSXZ$ z9*E;lS(y9+shOu5pg0Nv8^VEB)}yP496d6#G{qps*zbIkY7f5SI5_&b9t#bhHV+*( zUja79@hbqXcpY+JJ1aMK+DN{FH;nxO zPNmbsA1zkH7F{XFo+ySepTjN9O$%i4Qu2MR5zGa;WsDg}IsbOB(<@o(%K?zezgCD< zx4cVzzd{@`IvwSW zXOicLOqHskC(70EI2Txmwg!KyQavdHYS%R6mPNkP12-+mOWreA%Vro{9Z7`cYl|7r zu0{~{RDc1Bwe?<#?O!kfBHr+#h50vjWGW1!tTwvo42za0B}Gc;3kIJ?m2eoUm+ytY z*@cA-tbL4dRdn5;dD%sXGV~t~Qg&8NRMitD4Ld2m@}t~L-=pReLd#nimS3dV91Gf_ z4RQwrIB;4OUEc?~FQu3q$Tx>cy4l`PsR!UCpg$RZ{04J);*so}KE53=t^MUCdGXii z*njo=e}r|2tmrTJ2eRj}>ydugG96G*3z?YTBS7Ls(hP?@vx#e0uDjtvEy7QpC4Bq_ zWC?mKbE}{Mu3fbXRiGz*t<_`V3UUISl8F{Fzd${kY%ExK1rC?mUkYGBW8M8lG&t6? z&E>ciqdu_KdU7HXJS3R3A>Obd&R-^SGM|wd+4sbT-$4Prd`^M#CvLe=w}h0SfNH7` zP*&!|=#HB;$!|2*d5)9n1|@_Az;Tl=3e{Af_$>S)&&f}gD9~?XG8z540Xf0i*2RoD zD-3t$mY|f<pkOzWac zA?pA=v5EGN)o3T#?951L+EUe2-2AJ`)ONXeow{#~W>s*zN}4(N5E)_`a?|UYOhl=f zKaB)mD}^(`BMhh5InJn1H)m(IpK{r7$`EUWkP&jSuEa^SKKuNr7PWyo#9rOruye7> z?Y_!7HGY}nF*s4FT@-Jobboihv<$+~%-SE*>rCnr)pUd&Q>%o2hW=TfB!oA#5nuW8 z8^n~ex=!n~B_{o91wfO73*lLK(l$rTFY&uCu-*61_{t~jejq>fp0w5PJzpH+*>M`F z%(xlgj|}%0GYx}+lP(kPU_d$u(--k2TI*T$lY@qZYDy%YNlUyq->&I_b9iI99&son zPY2lzk;f|AOXSL6+xPnRq~f8u2~_xrf3{I-FuLIp|F_IZJC{AN&Yr4079}Yy+Gu;B zw}WroqR#Uz81O>UK^Ou(jEz%gMaERH`zrZ+EX$?j#9v}UqcrWu64VtH1h(v?%rdh| z`#r$piDlerPFm;UfcjFWb3yNr8eXvVb5o2pMtv+D4A!2=ta)92FiUD$Zfg9~Gy;e# z^@8ap+J}~wc&TK8`AzrAa%F8lh|$kc3cr2+dSy+S+u!pMLG~_D1ob@PT#{PDU^B35?y5g2_{f)duJM7^rCZMY^kx&`$UetD?-1TLxhWR8 zWu?4SzuDIhoMee_F`VQ_8p0W^xA++)KU zxTZw=+UJI4SN9JmmFDYC?!^0!#G)?UElrPD+7LWKVclhm-m=*V+4O8CXadx?okgcQ z7HLOJAL=euSB8iYq|4)sQ_FD}iG=YzG<8^k4pQo`K`YSAhB}&WlXXh%H)(jA9_SwG z>lx&d1=~Se>z+A!hmXUglTZAmu&$>bsF!hHd0!|rr!;d~XTfjzstsB0rDw*#W5`>x zn!}w|>${kOO&Z(A;N7021ew{|SqdnjU8f^-BI=NT7a)1!R3(Ur+WR_tu~TjjZ(;QH zM+Kx5i6b5gKRTU$(<%~jMHd3}we)pwVDRqLg(-R8RJt2yzI^*6I9s&& z>)7t0UMpeHMC;ebBpa_KEJ?d5O<}HlK6KGTD@}fxT6UOUMMQC?LY`>u5#&eaF<8<% zyz&4Ewv<8hGpw7>_Gj-Ot^qZ88O-+*bGUAF$M>-4o;HGn|5l;7x;StaH_h#d(fz^N z&5y0yohcXf$+2bP)HqB9!(e(vni+or{DJ3#Rx*b^Qv9vvtz)dwo9ibbS7IUdx>qNFoo6v)-qNLS!kL~&P}*{d>Ee3*a$1GF zyXS-9xOJ-uVr?~!?-Xb>UaNRXNzkeVD*jO2#fx7AD`?i|2UV@-kbvI#>U$^Kq4--- z*MqC3bMSfu?zV=96wJ;of2*iyMoM>}!X3v%omq+>B$w(O$;H{>D#m5Za;(2AoQPhKo#JUGxb&2j(dkp2 z+_>+1wPI8QWwdJZtymB-g&$z%eDWq-J21T7PFu1QMjf3uTm|&U1{=eX1KF~Ui}Ejt z$#z{X#&>N)E2Lr+qte4exnduz_o4Q_X63?TX2Vw*F9j&_!rqz^leYGwx}8BM9VD5{ z)&#p7oGHVdUg^`c)l%{+jh`L>=`X(w55HXo&8ppqBN}3(yh?7koA?xKMENcAY;+Iq zd|Mync3Cf5OvNm%t@?1?=q&hJN!-Oyr$3BRHNCo57Jb$gd*SmwuuHD(&@h-c=_^K# z<>JDn+@68bOWod7^>0(?{?8z-?Hmi}Pi9LTPgQmJew}HE*7Eehjx57?MJY5U&pdx* z!?o>B$woLBxY)P2d92=cP=x215HY-H`y2&_;01!0BdDmX`L?3pWDki^%9XGb@?DW} zHogyL??y4wf+~3JNiya%1TXZTmk7%pd3khwg~(r>)t+g6!JZ3>Fu`LEn=_NnOpHK0v6>+wCcg{)6(i824tKUE&6EMJ1u{*OwZnfxzTw;+i> zNz%j!{-ZEoGyd|A0hBziGDBwVA8fb!KQxYZ`rOgrrrhw0XzrJB9|6Ftn0WZ>Uzawe zdRtD2tx3(&Zj(CpCu%9}#-NP|kGy~5_z>AEkFJfGR=R7G>c3`E_;Q#X70_k)eaxP< zsk3@xD9Bgn8T&KuSuUmC`S30!$Jf2%Dw3#=m?|)K#8=$`O3VEfQNa=-!Jg-tXUES5Usla`Ff&hd#(G+e6`#93hBb|F z^$@&_aGRTp>h!cM;dHslg`ad(b>ikUw35yGy=D^gixht%AFUreK(+ryQi(VXBKSqx zjwi_WYjvkm0<_-SaZjmk)V?@t!O`3i)lOrM^?ep@N?z3P;>r|8gr%^v_O#%Nr(Thy zweF_VP~N;A)lr{4x#Bac?h`r-gdWXnx${lgAtGF8c)pFULQ43+w27Mg8iU!CI48DFT;*ig|Xrpt9B^H1IOg>MlqOV zmFH<)+(hw5jdfa_$RAolXkkxly0^tS6^8S($ecC-1L$tKVhTq9Al+8Wtgc#kG>81; zKPJ~4vLYY7l01ZhLxR5gveSc{{glFDltN|9p0PeeFb>P42L8CaxFwbr)94MNz0^ra zy_n4sQ%s-e#!%i+;kxw8jfagK|K3(9VsE%3vx?nIxNv5Gke}Rq_31!O7iHXj@gC}p<-YC^i$Zg6Qej@Mr*`tbnW7lw4h2AN$Prm9wG<*tNM|AV_C*+veHK%$jW%MB-C$ddr`_YK)-etsE3k%K&m$2EQ(UTY; zOtFA!`F>HI_S~vSQonZij6yiOx@K%Yy%xg$vj>|c7N9wYtpr?=CyL{2R~Tv|_|hOa zQy-WI1Zhc@oP4cZ_^vB;pr_yZ_3lr0Bmxly0nV`IYVCftMqp z2J?W+hw0qG!gn%mo+wkI<$5VrLjVM_J&YJAH0ZN=t%?(m*YkPUj)}BLad7!qSpTgZ z^@}qq6S|t3@wKr2IxkHtLXetW)HwTYc{iEBHs_#5x}8f2`-;lwt}20H56TUsLdNbp zb8Lj^zOpP^Pfy#5DEPp#Hds$I%vGqgBu|^AKg~h`u3<&Yw%xJz%=JrN7 zp~Rw7q?_Tzw5ZgZ0>Gt~lq2AdFEE&kulUq$d*PK&+N#UR`wJn1r~=6x1ad*< zV|ui}9!OeU{aL>is5e;T^DS*^Z1==h2unRgGGyB-mfub-(yu;WkGRh89(+>+{>!UGI+@svDjFjIBg1 zsnJzbA}tr==o;J(C*teuYJ~Y(NFnLRM1EJ={RjM}l)ph$HYuLi6r^vTs5Hi=+?iL* zmL(p~-#fRJPZVCAPn5n0R|BbvZR`jG)w7}Z@4Th{~tOJl5 zyVXi0y{9ajzfCTLY$tTs%59sMHN&EiDJ|`CY@tXnL_g{-HIX=yi`9zpi;457%QCQg6{5LMDa~2XI6GO8dUm1r{B|%)cj{U6n4!Gq zggXPYcWza)3nBMXo7BxX>bBA5J{EF5ao2I`h{VUe;^2&@AXTVh;Xk~smlj1KTW(k` zwZ6`-vvvFSGz&W)xBdWqNggd~$nd**JC{=u;4iJ~=5?Tm=v#e%Xe|+k4_}SnboC&b ztB!D9DiZFi*GxpLFI$sYYC=ipUaPU^*)z(vh**wK5Ss4H7Hr7B>(B%* zgH`f$7Ie4Ek~TQ)#cH5X`fNxl&Cr4)I2DCVrCvp03o^A;Z&Cmb2+ zLjR2;qF*}waTi$htyH})dqO_6>Q3*`-oH~zjLYCF@@5;q@#=ZFBt`U^^bw{J2!#Je zp9aCkZk6M*V$V?GT@0&o-96{j689L`D!Dy?d&4Y0OuAn4rIZ3;Sc7{OsRWe7Q&s~! zCLd%U$j)|^(Tg%`dr4+(CmPhP53WzjJ_`{@=O5Vv$?#>{&{1-H5M)3bR0My`tc?>= zTP_$2QIRdOx@dyK6)nJLGCd~>b8}*|x;eMdJf$dLAt6)miv2v^jq+(F&6UYeE$I!$ zJokRp!sE*j;c$617z_@pgw@sAi!D!iR&6pcJk_2JyPeiU>q;+m68>fkRfB4VGwN3` zm!ovPX+0wY-4jv0G-X)KBHG!=G^ood^Oo0w46fcArftAYhtH&bj=2~|CubJc|p{B@;kon_UyAiAcH;dWa-`f1kNxsK#E<`a1YxAwUl07S}QArz}xO zFle>N5_5Vb5ni|*Z8MhD;Mhv(_%$==@mr~YbTWoBhSg|UhAGMS9tDcBEY{|0#iiG5 zYKgN$8DCyOp8{95xmMeIOZ?IqR(FINL2?OQ;w3s>9W(QPsbL&+hgeeE7W9` zxJGWGu78rqoNvq35C}{L1y)D))-pv(aVdfWGvm)wl&tsI7LMXy-Dlw`%AdmLNNC0n zY$ED5Ue`opz`+TK8cTILE&1@`R*(e2juI_uLJ=Nz z+z=#_FK*-W{~ozcf07nUG6xb`&k<|z+JcW%#?hk zRF{;r5RhLc*-H1s5cn1eD-8__oNYi~pb9asHHGHS%Z#)dLqtN>`LvcSp7}8pfFLa* zp5(M65Q-u}_NAY7aiby!sRG(jjy-)SpF<>Gj%cio94u8pkL6juR7EbalRtk_E*fBp zne)cco-KO_=SYP6S-Uh{thv}1>hHg#|IfOX$nyPIDQ~#kWak7@4XZn7>1LnGw z+&*aTj#EB_&mze+%o2IGDqJZeVuc|;cUB(ii)k-Js0d{$(d}xyQhJr?bS6U z5a*$?^kpA>N9HbLjCwV5IN)F@U!e(-q2pz>`;d&35F>D<6(W?!38d;L+36Ft)us0@ zbUWV)Me^l4hY_sfS)ba&Z^bt$s(>Y+VZpkZCZyO($f9Ja4Buv0NB7cV{eIiiGCcP? zqQP-b%;SK45w$}R+pG*tL|yz+10IhtQ_RaIt_rX7Uk1DHg=Lt3B5{e#|1{AsP(w=u z0zCH)R;i#;AzQcHhZibGAGSIXo-!25^`63JHBsN|89e7|R0BczLq+&rx&Q_UrUg9u zU_e)Yq`4y66fC+KZ4#;E_s;yCE;1^a$ENQ%Es5}#W0j^Rf4k?6sJuG7siFL5)tC-% zno7c4x|mVFE+4(HJ*6LsKo70ac+@R-Gnb8pzJx+`<=C?Sviyv>@5`m6JH2mTSX=zX z^y_Ook*gobx>XE_y?Y94-&&r5Y+{P3;-SkOTCR1X5p6TPpko1h4#7768EeE9c&@eLX9^0j6i?h!&RJ{JW#ty+E+DrQs;>tJWyxhod2Xa>RiVK01gb+?W zTBKo9&R|!L8R-O_3Y4;kl*Zw~h4c66)AUDpzrrZ2TxJD~wCSxU%)ZclW-t-ix9f77 zL#F~_gNkrmuVK~Scr;zB%XO4!Q`nib5R6<0=r0A0gYF-Dkgsg!c?yT-K;c^BD|RIM zK@{2IR+Az_Yz#CF5qJ`&bsLnYTe3y@;|k$tIeRtb@Jc8*8nW2wojNCjF&1(by8oK? zU}v&ch*={oc(Iu8;LaU+(4bJn8odqIaJ0r4y&}?B`iLCt)|M;y+Ur`e2ziY$FRV#X zEY0eX(>vrEe}eg})HJfYf=0QkY_ecP(tk;kQ1vkoaD-Bs;oBy@Mzfc;C(gb1YGqf< zKqM@U|G<};Z*OmkcX-oH2QS#vtz>HqS(*d3V|XI*>1dz-X+51Rd6|Gr#jn%|IHE@9 zg$FRLaP0J3LKbOAtbNETS-d?92C#c|cHR+3DVcg?Su4-3Qc%!_FOim^J8SF2hp?53 zaHdBvW>!+2IsJv_^G$Cc7 zG;=MD(7KF$p6^AEm*8Fk_UMfDW$l9$Qua8RaoS2M@2hzQx~@W<(|&wj=LIWFlvU}b zRasxe6Z2EZxmS-?*;j=80!(k9E5iDN-J1H-lq7Ko7+*{7rQqt(Jm@6S-HTJ?Xp`l#taX|7B4re*P3)nj4sHwdtX(_qYr2m27;J(=GU z6B_a2e;#aZ#IX;sdqYEe_StD=a^tj3j$5>Tg%PO%&1f9@FQhODKx%^1zy zA2c-#e1XsIKjk0*Bd7zsSI|eBtDcWTv>B?Rh4Z86U}62#3Z;|@STEAeRY5TW?h``R z*)<7ku?SiHP~lp}AE~lTwYifj5zI29AL?y^>aXI@DzJZ~{r@1lM1G)3G3x)Tfn1FAuh}f#RvSbAHw7PQ{(r&mk|U+@3@VT$IEL${GmW$8kHW zxW)xCGXr{#gFM*rG?F(YnIejSv~^JIoO2RRd+D3sRcP&s@Rv2V_sR(Dd%Y?h*=3E6 z|Kq_Rx2|g2RN?3s>grq9iHn2seQ!aO@hjhf;=a%y-8H+E259F zu?tG*2XDSES%f=qIkgxWsSn|6BqJ&H0f!sBrZ1gUjMQemYU(?p*dNB}7r=RQdby)F z8K0BwFwqZ%%uI?Um-H3AQD9&9%sm++Gt9#yKu-+?I!D3?QmeAw7RQe3mw4B?VQM71 zIO#W8C=gA|ec~J8V6VFIR@8i1q@$zTK+_4@i^M<2>i7bX{BX%XH_1;pQ$MQwm=2oOippbVE z&#gt|Xd{(if}##1I4S9<&hA z-`uGt(Lu<0<;rE8I zRR?p)c3pZKVIR(&M`j&Qr1tcnAxXRZ6qfdtebva%+_nN?F@6+dse*Y5dHS=uT_Tf} zlITBX=sVw5OG#l-9u2T->zoe@X2E(-)nTLdiCcRby^F|nYfCz|UnaMUcJXr*$CAqV7I!X@Gi_N9{MrxRyYIWM=Ke;*p6$u*>JBEtLh) z^2waK5g0aw*8WG}EgkAI-6oEHoP*vh+DqGGNnbMe z!&Y>XGqY2}G9=~gtwmRe;6bu8Qp@ETCK#`Ws8@X;Z1XYRd2?*AiIfglpQ$bPyWR~Z zG*H-ywv$4sSryNE_@1|1mBwV+;+)BEkXGpXn+F}R@eng?N9*qT zolA?gLGtcnK_P0)eG8H9L?5y~JlXiJqw6)oS%4*`Ro~d1Ut)QfT11@)L9R`a^0c;c#0$wtCP-dlaseuFQwY$v0q# zy+@{ltn-?PAY%YG858+kC;*KlgpgE*tObGXCT z*gmTPO_6wQpFPDg%w3n7gK2Eh29xV^FeMyA@4^|oWE)=RSPO`jra{^TAIC&yW)vX5$}& z{E9F0nvwfwO=XEU?zVzPD@*_jqbN5%vm7x8j$n-V_O4=PRM{f`Hz3EDPHaa{3ddkR_eZda*IU<-yuS%LodeXYn4PtCe$&g!c@q6ZS z4!Fq2cO625=%f;Fi|TTN?~XPU7cpH|pHz@SyGVBsC15Ar1?wi-pvh(AkF(%fb?(fA zP&~BfN<*`OJ*%1&l14`r->C%I$l=K7-=JBQvWa-EXm9&V{%AjJHK}lEsn^N9D*_SH6ud)o#7J2l`3MVBsCB>m0-Ip}%uLd)wnuyu zkmk$gE2fKQ4Gg*-2lpz5H#Wj4{CX9Qb_D7TR%V))&rtHmILE)>=beF5}!{5x*mQ5MWe|ZyU)RI9YS7 zb@^SMEd|yecV4~3y3Sg`T;Y#KY;vjM|3*|eWH~>FwaqC+V{>XgyBW`N>Wt9AF?0dG zWPdM7{W;GDkkFc25lpvdHy}J-#q3?!W4F^fb@+c%6EKpV(_liUZmTex44JvqH>uSM zY#4n%B6az={70l?sz8^)MDK{gI(MD5>n)|>J)#~eAduIrc?nFI(Uy*-?m`nKdjNL^ zOd(yQH^A@#nGN8xsDeau{y#ylS_H2ukrUGp^PA`4ym{x66N71u=85NL%P!w#w7M}9 zQ%!!-uCfsL;~N`5Bn8&-xSUibvYQVnbfy$%!IxU`0gB8b2#zUWscHCWi>5zn!6tWE zae@-xSl=2`W9(MsTqhisxpfp9`@BX*2TP=_Q<>EN>VZ;U%<;--?IV^@{IBOEqZjbF z!JX?n#lr86ImLVS$K_L(F?#UEQ3#4DfBb{aa@OSscGR7)BrkZAmZ*x4io6!- zE91J9A3pZ;4!2S&+B(tWt(cq5HlUSCJ%am1%9v81rOukqcZq)GT|fPxog&|4mFH=A z1dtJ1N)NVaR04}r;CE~n+PcGgw8v-Sl)4LVAeb{HXB1$qXO?#>o!QJ>We%t~X^uOJ zr7epS9g$u?Q#cpsA(9B$BO?_RqAZ-}-(Ic3={O9Ag^H)A?i*G*>C99e$N zj=o}m$1A0hB6}Y-(O(f#F5b2n;VqYSQ!nToHs$bT*;9qbhFA7b#y=_zAz&>c6Hani zSwr_ip7O3wII9ANp4p$*)l_kw%$BxDsgr<`Q(+bTDaqN5`{s-nJv%3!5bii^waimk zGd4vcODQxlaNKS3h<84T<9oTnN#}b%Td}a$OlUBhOR!D_tn%=%wkCKW%wfx}LGwAH+zUAj2D9GEb znd?PVJGlphw=&#!9?rqPy3N=n?co%ymI-wFoV#C1Gg+OMai{+`^eKcrzp=5?r9Piw zRivT-Eq(45nJl|~PM&vr9t>ES0*}~b>#$P~mO=5QR}uc^;vbKZZ28mkSk+KRNV<3Z@#Izjb-f)}W~2`P1}(B0-YSrw zjck?yPOX33N^|(bChY`4bNe6^AiHKRdWaHj{Bc{>Jpr!jy{E))kYBm)5sx0ebY{cK zAGi6o85jBvS@|9kbKE}<{k(aqnf!-M5tr|Im9F;i?@>DdGR264D0RrmUv2yAL14%H z=g|D`;ri#${PV8)=NIbV=bt}JEdQKq{&H&m=g|D)J)zFR0TChY&j^P>Eoxqn*1aqqhXq<{jw;}sX*HuW4pu6HYb@vHgvqaW{P zam1xuRr3`P<8yx*5A+IvHVl>q@Io9&_E~uP8+23$n#!yYUt$It2^;q`H_L|i{L;6K zI)(tJs2y;7l>ZIt+(a`70el$06ajD2E`1Na`CiV^9L8Gv0@^YByCaU@Ac}Nc4;K1= z{xAO=m;agf%g(K=_YzhENrpt$^xT`}7r#NP&qTuH#3@&zg%-n5K2J7m{K?I|^W|vc z_KThP4ibnKo?!t(CI0Yraq{69XM<7fSi%x4-vxuYicso3tHq4)r3iljqRfBs=()HT xX96a_@Y`+j|9HLmry0b6(YoLNcRznIK$(8eGDH5`?MMEMXZT;eAMN+d{{kra>Q?{& diff --git a/src/CaptureInterface.php b/src/CaptureInterface.php index 577c199..be40564 100644 --- a/src/CaptureInterface.php +++ b/src/CaptureInterface.php @@ -23,7 +23,7 @@ public function setSandboxMode(bool $isOn) : self; /** * Установить Номер платежа Ypmn - * Используйте значение из JSON-ответа на запрос на авторизацию платежа (ключ 'payuPaymentReference') + * Используйте значение из JSON-ответа на запрос на авторизацию платежа (ключ 'ypmnPaymentReference') * @param string $paymentIdString Номер платежа Ypmn * @return $this */ From 1c97e50b4c5bf8216620dc18f5009afbabd0273f Mon Sep 17 00:00:00 2001 From: whoiann Date: Tue, 16 May 2023 18:01:04 +0300 Subject: [PATCH 012/151] Downgraded variable names --- example.php | 4 +-- src/Capture.php | 8 ++--- src/CaptureInterface.php | 2 +- src/OrderData.php | 6 ++-- src/Refund.php | 8 ++--- src/RefundInterface.php | 2 +- src/Std.php | 77 +++++++++++++++++++++++++--------------- 7 files changed, 64 insertions(+), 43 deletions(-) diff --git a/example.php b/example.php index d86a1ed..cf2d6d8 100644 --- a/example.php +++ b/example.php @@ -77,7 +77,7 @@ // Установим номер заказа (должен быть уникальным в вашей системе) $payment->setMerchantPaymentReference($merchantPaymentReference); // Установим адрес перенаправления пользователя после оплаты - $payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); + $payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); // Установим клиентское подключение $payment->setClient($client); @@ -216,7 +216,7 @@ // Установим номер заказа (должен быть уникальным в вашей системе) $payment->setMerchantPaymentReference('primer_nomer__' . time()); // Установим адрес перенаправления пользователя после оплаты - $payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); + $payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); // Установим клиентское подключение $payment->setClient($client); diff --git a/src/Capture.php b/src/Capture.php index 0a36fd9..edb71f8 100644 --- a/src/Capture.php +++ b/src/Capture.php @@ -9,7 +9,7 @@ class Capture implements CaptureInterface, JsonSerializable, TransactionInterfac /** * @var string Номер платежа Ypmn */ - private string $ypmnPaymentReference; + private string $payuPaymentReference; /** * @var float Cумма исходной операции на авторизацию @@ -29,7 +29,7 @@ class Capture implements CaptureInterface, JsonSerializable, TransactionInterfac /** @inheritDoc */ public function setYpmnPaymentReference(string $paymentIdString): CaptureInterface { - $this->ypmnPaymentReference = $paymentIdString; + $this->payuPaymentReference = $paymentIdString; return $this; } @@ -37,7 +37,7 @@ public function setYpmnPaymentReference(string $paymentIdString): CaptureInterfa /** @inheritDoc */ public function getYpmnPaymentReference(): string { - return $this->ypmnPaymentReference; + return $this->payuPaymentReference; } /** @inheritDoc */ @@ -110,7 +110,7 @@ public function jsonSerialize() { //TODO: проверка необходимых параметров $requestData = [ - 'ypmnPaymentReference' => $this->getYpmnPaymentReference(), + 'payuPaymentReference' => $this->getYpmnPaymentReference(), 'originalAmount' => $this->getOriginalAmount(), 'amount' => $this->getAmount(), 'currency' => $this->getCurrency() diff --git a/src/CaptureInterface.php b/src/CaptureInterface.php index be40564..577c199 100644 --- a/src/CaptureInterface.php +++ b/src/CaptureInterface.php @@ -23,7 +23,7 @@ public function setSandboxMode(bool $isOn) : self; /** * Установить Номер платежа Ypmn - * Используйте значение из JSON-ответа на запрос на авторизацию платежа (ключ 'ypmnPaymentReference') + * Используйте значение из JSON-ответа на запрос на авторизацию платежа (ключ 'payuPaymentReference') * @param string $paymentIdString Номер платежа Ypmn * @return $this */ diff --git a/src/OrderData.php b/src/OrderData.php index e4d079a..c0ebaa8 100644 --- a/src/OrderData.php +++ b/src/OrderData.php @@ -8,7 +8,7 @@ class OrderData implements OrderDataInterface private string $orderDate; /** @var string Номер платежа Ypmn */ - private string $ypmnPaymentReference; + private string $payuPaymentReference; /** @var string */ private string $merchantPaymentReference; @@ -50,13 +50,13 @@ public function setOrderDate(string $orderDate): self /** @inheritDoc */ public function getUpmnPaymentReference(): string { - return $this->ypmnPaymentReference; + return $this->payuPaymentReference; } /** @inheritDoc */ public function setYpmnPaymentReference(string $ypmnPaymentReference): self { - $this->ypmnPaymentReference = $ypmnPaymentReference; + $this->payuPaymentReference = $payuPaymentReference; return $this; } diff --git a/src/Refund.php b/src/Refund.php index b6dfcf0..cf8b6b2 100644 --- a/src/Refund.php +++ b/src/Refund.php @@ -12,7 +12,7 @@ class Refund implements RefundInterface, JsonSerializable, TransactionInterface /** * @var string Номер платежа Ypmn */ - private string $ypmnPaymentReference; + private string $payuPaymentReference; /** * @var float Cумма исходной операции на авторизацию @@ -54,7 +54,7 @@ public function setSandboxMode(bool $isOn) : self */ public function setYpmnPaymentReference(string $paymentIdString): RefundInterface { - $this->ypmnPaymentReference = $paymentIdString; + $this->payuPaymentReference = $paymentIdString; return $this; } @@ -64,7 +64,7 @@ public function setYpmnPaymentReference(string $paymentIdString): RefundInterfac */ public function getYpmnPaymentReference(): string { - return $this->ypmnPaymentReference; + return $this->payuPaymentReference; } /** @@ -133,7 +133,7 @@ public function jsonSerialize() { //TODO: проверка необходимых параметров $requestData = [ - 'ypmnPaymentReference' => $this->getYpmnPaymentReference(), + 'payuPaymentReference' => $this->getYpmnPaymentReference(), 'originalAmount' => $this->getOriginalAmount(), 'amount' => $this->getAmount(), 'currency' => $this->getCurrency() diff --git a/src/RefundInterface.php b/src/RefundInterface.php index 2fc6e15..0a84c01 100644 --- a/src/RefundInterface.php +++ b/src/RefundInterface.php @@ -23,7 +23,7 @@ public function setSandboxMode(bool $isOn) : self; /** * Установить Номер платежа Ypmn - * Используйте значение из JSON-ответа на запрос на авторизацию платежа (ключ 'ypmnPaymentReference') + * Используйте значение из JSON-ответа на запрос на авторизацию платежа (ключ 'payuPaymentReference') * @param string $paymentIdString Номер платежа Ypmn * @return $this */ diff --git a/src/Std.php b/src/Std.php index a4ee0f3..5fb9ad4 100644 --- a/src/Std.php +++ b/src/Std.php @@ -87,47 +87,68 @@ public static function drawYpmnButton(array $params): string return ' - - - - - - - Оплатить - '.(isset($params['sum']) ? '
' - . number_format($params['sum'], 2, '.', ' ') - . ' ' - . ( isset($params['currency']) ? htmlspecialchars($params['currency']) : '₽' ) .'' : '').' -
+ +
Оплатить + '.(isset($params['sum']) ? '
' + . number_format($params['sum'], 2, '.', ' ') + . ' ' + . ( isset($params['currency']) ? htmlspecialchars($params['currency']) : '₽' ) .'' : '').' +
+ '; } } From 79bf370c8c5e6f359f5e5c3f73e2e18cce95daae Mon Sep 17 00:00:00 2001 From: whoiann Date: Wed, 17 May 2023 10:46:12 +0300 Subject: [PATCH 013/151] Added token payment menu point --- README.md | 1 + example.php | 140 ++++++++++++++++++++++++++++++++++++++++++ example_template.html | 1 + 3 files changed, 142 insertions(+) diff --git a/README.md b/README.md index 346466a..f13adca 100644 --- a/README.md +++ b/README.md @@ -370,6 +370,7 @@ $responseData = $apiRequest->sendRefundRequest($refund, $merchant); - [Основной сайт Твои Платежи](https://YPMN.ru/) - Начните знакомство с кодом с этих файлов: [example.php](https://github.com/yourpayments/php-api-client/blob/main/example.php) и класса [PaymentInterface.php](https://github.com/yourpayments/php-api-client/blob/main/src/PaymentInterface.php) +- [Реквизиты тестовых банковских карт](https://dev.payu.ru/ru/documents/rest-api/testing/#menu-2) - [Задать вопрос или сообщить о проблеме](https://github.com/yourpayments/php-api-client/issues/new) ------------- diff --git a/example.php b/example.php index cf2d6d8..83d090a 100644 --- a/example.php +++ b/example.php @@ -255,6 +255,146 @@ } break; + case 'paymentByToken': + // Оплата по токену + // Представим, что нам надо оплатить пару позиций: Синий Мяч и Жёлтый Круг + + // Опишем первую позицию + $product1 = new Product; + // Установим Наименование (название товара или услуги) + $product1->setName('Синий Квадрат'); + // Установим Артикул + $product1->setSku('ball-05'); + // Установим Стоимость за единицу + $product1->setUnitPrice('500'); + // Установим Количество + $product1->setQuantity(1); + // Установим НДС + $product1->setVat(20); + + //Опишем вторую позицию с помощью сокращённого синтаксиса: + $product2 = new Product([ + 'name' => 'Оранжевый Круг', + 'sku' => 'toy-15', + 'unitPrice' => 160000, + 'quantity' => 3, + 'vat' => 0, + ]); + + // Опишем Биллинговую (платёжную) информацию + $billing = new Billing; + // Установим Код страны + $billing->setCountryCode('RU'); + // Установим Город + $billing->setCity('Москва'); + // Установим Регион + $billing->setState('Центральный регион'); + // Установим Адрес Плательщика (первая строка) + $billing->setAddressLine1('Улица Старый Арбат, дом 10'); + // Установим Адрес Плательщика (вторая строка) + $billing->setAddressLine1('Офис Ypmn'); + // Установим Почтовый Индекс Плательщика + $billing->setZipCode('121000'); + // Установим Имя Плательщика + $billing->setFirstName('Иван'); + // Установим Фамилия Плательщика + $billing->setLastName('Петров'); + // Установим Телефон Плательщика + $billing->setPhone('+79670660742'); + // Установим Email Плательщика + $billing->setEmail('test1@ypmn.ru'); + + // (необязательно) Опишем Доствку и принимающее лицо + $delivery = new Delivery; + // Установим документ, подтверждающий право приёма доставки + $delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') + ); + // Установим Код страны + $delivery->setCountryCode('RU'); + // Установим Город + $delivery->setCity('Москва'); + // Установим Регион + $delivery->setState('Центральный регион'); + // Установим Адрес Лица, принимающего заказ (первая строка) + $delivery->setAddressLine1('Улица Старый Арбат, дом 10'); + // Установим Адрес Лица, принимающего заказ (вторая строка) + $delivery->setAddressLine1('Офис Ypmn'); + // Установим Почтовый Индекс Лица, принимающего заказ + $delivery->setZipCode('121000'); + // Установим Имя Лица, принимающего заказ + $delivery->setFirstName('Мария'); + // Установим Фамилия Лица, принимающего заказ + $delivery->setLastName('Петрова'); + // Установим Телефон Лица, принимающего заказ + $delivery->setPhone('+79670660743'); + // Установим Email Лица, принимающего заказ + $delivery->setEmail('test2@ypmn.ru'); + // Установим Название Компании, в которой можно оставить заказ + $delivery->setCompanyName('ООО "Вектор"'); + + // Создадим клиентское подключение + $client = new Client; + // Установим биллинг + $client->setBilling($billing); + // Установим доставку + $client->setDelivery($delivery); + // Установим IP (автоматически) + $client->setCurrentClientIp(); + // И Установим время (автоматически) + $client->setCurrentClientTime(); + + // Создадим платёж + $payment = new Payment; + // Установим позиции + $payment->addProduct($product1); + $payment->addProduct($product2); + // Установим валюту + $payment->setCurrency('RUB'); + // Создадим и установим авторизацию по типу платежа + $payment->setAuthorization(new Authorization('CCVISAMC',true)); + // Установим номер заказа (должен быть уникальным в вашей системе) + $payment->setMerchantPaymentReference('primer_nomer__' . time()); + // Установим адрес перенаправления пользователя после оплаты + $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 в массив + try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); + } catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); + } + break; + case 'paymentCapture': // Запрос на списание денег // В зависимости от настройки мерчанта, Ypmn может списывать денежные средства автоматически, diff --git a/example_template.html b/example_template.html index 0696655..abf17bf 100644 --- a/example_template.html +++ b/example_template.html @@ -3,6 +3,7 @@
  1. simpleGetPaymentLink -- самый простой платёж
  2. getPaymentLink -- платёж со всеми полями
  3. +
  4. paymentByToken -- оплата по токену
  5. paymentCapture -- списание средств
  6. paymentRefund -- запрос на возврат
  7. From 2a3cc55290f0135c27173de483bec110d3596727 Mon Sep 17 00:00:00 2001 From: "nikita.ivanov" Date: Thu, 18 May 2023 15:04:07 +0300 Subject: [PATCH 014/151] Token --- example.php | 132 ++++++++++++++--------------------- example_template.html | 1 + src/AirlineInfoInterface.php | 2 +- src/ApiRequest.php | 13 ++++ src/ApiRequestInterface.php | 14 ++++ src/PaymentReference.php | 41 +++++++++++ src/Std.php | 8 +-- 7 files changed, 128 insertions(+), 83 deletions(-) create mode 100644 src/PaymentReference.php diff --git a/example.php b/example.php index 83d090a..286055f 100644 --- a/example.php +++ b/example.php @@ -21,6 +21,7 @@ use Ypmn\Capture; use Ypmn\Refund; use Ypmn\Std; +use Ypmn\PaymentReference; // TODO: нужен публичный тестовый мерчант, которого можно включить в документацию // Создадим тестового мерчанта @@ -254,107 +255,83 @@ throw new PaymentException('Платёжный метод временно недоступен'); } break; + case 'getToken': + // Хотим получить токен + // Создадим HTTP-запрос к API + $apiRequest = new ApiRequest($merchant); + // Включить режим отладки (удалите в рабочей программе!) + $apiRequest->setDebugMode(); + // Переключиться на тестовый сервер (удалите в рабочей программе!) + $apiRequest->setSandboxMode(); + // Отправим запрос + $ypmnPaymentReference = new PaymentReference(2469476); + $responseData = $apiRequest->sendTokenCreationRequest($ypmnPaymentReference); + // Преобразуем ответ из JSON в массив + try { + $responseData = json_decode((string) $responseData["response"], true); - case 'paymentByToken': - // Оплата по токену - // Представим, что нам надо оплатить пару позиций: Синий Мяч и Жёлтый Круг + // Нарисуем кнопку оплаты 5 +// echo Std::drawYpmnButton([ +// 'url' => $responseData["paymentResult"]['url'] +// ]); - // Опишем первую позицию - $product1 = new Product; - // Установим Наименование (название товара или услуги) - $product1->setName('Синий Квадрат'); - // Установим Артикул - $product1->setSku('ball-05'); - // Установим Стоимость за единицу - $product1->setUnitPrice('500'); - // Установим Количество - $product1->setQuantity(1); - // Установим НДС - $product1->setVat(20); + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); + } catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
    + Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
    +
    +
    ' . $exception->getMessage() . '
    ', + 'type' => 'danger', + ]); - //Опишем вторую позицию с помощью сокращённого синтаксиса: - $product2 = new Product([ - 'name' => 'Оранжевый Круг', - 'sku' => 'toy-15', - 'unitPrice' => 160000, - 'quantity' => 3, - 'vat' => 0, + throw new PaymentException('Платёжный метод временно недоступен'); + } + break; + case 'paymentByToken': + // Оплата по токену + $orderAsProduct = new Product([ + 'name' => 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 1.42, + 'quantity' => 2, ]); // Опишем Биллинговую (платёжную) информацию $billing = new Billing; // Установим Код страны $billing->setCountryCode('RU'); - // Установим Город - $billing->setCity('Москва'); - // Установим Регион - $billing->setState('Центральный регион'); - // Установим Адрес Плательщика (первая строка) - $billing->setAddressLine1('Улица Старый Арбат, дом 10'); - // Установим Адрес Плательщика (вторая строка) - $billing->setAddressLine1('Офис Ypmn'); - // Установим Почтовый Индекс Плательщика - $billing->setZipCode('121000'); // Установим Имя Плательщика $billing->setFirstName('Иван'); // Установим Фамилия Плательщика $billing->setLastName('Петров'); - // Установим Телефон Плательщика - $billing->setPhone('+79670660742'); // Установим Email Плательщика $billing->setEmail('test1@ypmn.ru'); - - // (необязательно) Опишем Доствку и принимающее лицо - $delivery = new Delivery; - // Установим документ, подтверждающий право приёма доставки - $delivery->setIdentityDocument( - new IdentityDocument('123456', 'PERSONALID') - ); - // Установим Код страны - $delivery->setCountryCode('RU'); + // Установим Телефон Плательщика + $billing->setPhone('+7-800-555-35-35'); // Установим Город - $delivery->setCity('Москва'); - // Установим Регион - $delivery->setState('Центральный регион'); - // Установим Адрес Лица, принимающего заказ (первая строка) - $delivery->setAddressLine1('Улица Старый Арбат, дом 10'); - // Установим Адрес Лица, принимающего заказ (вторая строка) - $delivery->setAddressLine1('Офис Ypmn'); - // Установим Почтовый Индекс Лица, принимающего заказ - $delivery->setZipCode('121000'); - // Установим Имя Лица, принимающего заказ - $delivery->setFirstName('Мария'); - // Установим Фамилия Лица, принимающего заказ - $delivery->setLastName('Петрова'); - // Установим Телефон Лица, принимающего заказ - $delivery->setPhone('+79670660743'); - // Установим Email Лица, принимающего заказ - $delivery->setEmail('test2@ypmn.ru'); - // Установим Название Компании, в которой можно оставить заказ - $delivery->setCompanyName('ООО "Вектор"'); + $billing->setCity('Москва'); // Создадим клиентское подключение $client = new Client; // Установим биллинг $client->setBilling($billing); - // Установим доставку - $client->setDelivery($delivery); - // Установим IP (автоматически) - $client->setCurrentClientIp(); - // И Установим время (автоматически) - $client->setCurrentClientTime(); // Создадим платёж $payment = new Payment; // Установим позиции - $payment->addProduct($product1); - $payment->addProduct($product2); + $payment->addProduct($orderAsProduct); // Установим валюту $payment->setCurrency('RUB'); // Создадим и установим авторизацию по типу платежа $payment->setAuthorization(new Authorization('CCVISAMC',true)); + // Установим токен транзакции + $payment->set // Установим номер заказа (должен быть уникальным в вашей системе) - $payment->setMerchantPaymentReference('primer_nomer__' . time()); + $payment->setMerchantPaymentReference($merchantPaymentReference); // Установим адрес перенаправления пользователя после оплаты $payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); // Установим клиентское подключение @@ -367,16 +344,16 @@ // Переключиться на тестовый сервер (удалите в рабочей программе!) $apiRequest->setSandboxMode(); // Отправим запрос - $responseData = $apiRequest->sendAuthRequest($payment, $merchant); + $ypmnPaymentReference = new PaymentReference(2469476); + $responseData = $apiRequest->sendTokenCreationRequest($ypmnPaymentReference); // Преобразуем ответ из JSON в массив try { $responseData = json_decode((string) $responseData["response"], true); - // Нарисуем кнопку оплаты - echo Std::drawYpmnButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); + // Нарисуем кнопку оплаты 5 +// echo Std::drawYpmnButton([ +// 'url' => $responseData["paymentResult"]['url'] +// ]); // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: // echo Std::redirect($responseData["paymentResult"]['url']); @@ -394,7 +371,6 @@ throw new PaymentException('Платёжный метод временно недоступен'); } break; - case 'paymentCapture': // Запрос на списание денег // В зависимости от настройки мерчанта, Ypmn может списывать денежные средства автоматически, diff --git a/example_template.html b/example_template.html index abf17bf..02f4693 100644 --- a/example_template.html +++ b/example_template.html @@ -3,6 +3,7 @@
    1. simpleGetPaymentLink -- самый простой платёж
    2. getPaymentLink -- платёж со всеми полями
    3. +
    4. getToken -- получить токен
    5. paymentByToken -- оплата по токену
    6. paymentCapture -- списание средств
    7. diff --git a/src/AirlineInfoInterface.php b/src/AirlineInfoInterface.php index 346cb91..404c182 100644 --- a/src/AirlineInfoInterface.php +++ b/src/AirlineInfoInterface.php @@ -3,7 +3,7 @@ namespace Ypmn; /** - * Ypmn поддерживает специальные условия транзауции + * Ypmn поддерживает специальные условия транзакции * для торговли транспортными билетами * обратитесь к Вашему менеджеру для деталей реализации */ diff --git a/src/ApiRequest.php b/src/ApiRequest.php index 5a1c845..cd4ca19 100644 --- a/src/ApiRequest.php +++ b/src/ApiRequest.php @@ -13,6 +13,7 @@ class ApiRequest implements ApiRequestInterface { const AUTHORIZE_API = '/api/v4/payments/authorize'; const CAPTURE_API = '/api/v4/payments/capture'; + const TOKEN_API = '/api/v4/token'; const REFUND_API = '/api/v4/payments/refund'; const STATUS_API = '/api/v4/payments/status'; const HOST = 'https://secure.payu.ru'; @@ -178,6 +179,18 @@ public function sendStatusRequest(string $merchantPaymentReference): array return $this->sendGetRequest(self::STATUS_API . '/' . $merchantPaymentReference); } + /** @inheritdoc */ + public function sendTokenCreationRequest(PaymentReference $payuPaymentReference): array + { + return $this->sendPostRequest($payuPaymentReference, self::TOKEN_API); + } + + /** @inheritdoc */ + public function sendTokenPaymentRequest(PaymentReference $payuPaymentReference): array + { + return $this->sendPostRequest($payuPaymentReference, self::TOKEN_API); + } + /** * Подпись запроса * @param MerchantInterface $merchant Мерчант diff --git a/src/ApiRequestInterface.php b/src/ApiRequestInterface.php index e603133..1eb386c 100644 --- a/src/ApiRequestInterface.php +++ b/src/ApiRequestInterface.php @@ -66,4 +66,18 @@ public function setDebugMode(bool $debugModeIsOn): self; * @return bool Режим отладки включен? */ public function getDebugMode(): bool; + + /** + * Отправить Запрос на Токенизацию + * @param PaymentReference $payuPaymentReference Оплата + * @return array + */ + public function sendTokenCreationRequest(PaymentReference $payuPaymentReference): array; + + /** + * Отправить Запрос на Оплату токеном + * @param PaymentReference $payuPaymentReference Оплата + * @return array + */ + public function sendTokenPaymentRequest(PaymentReference $payuPaymentReference): array; } \ No newline at end of file diff --git a/src/PaymentReference.php b/src/PaymentReference.php new file mode 100644 index 0000000..1cc1d0c --- /dev/null +++ b/src/PaymentReference.php @@ -0,0 +1,41 @@ +setPaymentReference($paymentReference); + } + + private function setPaymentReference(int $paymentReference) : self + { + $this->paymentReference = $paymentReference; + return $this; + } + + /** + * @throws PaymentException + */ + public function jsonSerialize(): string + { + if(is_null($this->paymentReference)){ + throw new PaymentException("Не хватает номера оплаты для токенизации"); + } + + $resultArray = [ + 'payuPaymentReference' => $this->paymentReference, + ]; + + return json_encode($resultArray, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); + } +} \ No newline at end of file diff --git a/src/Std.php b/src/Std.php index 5fb9ad4..3c0f9af 100644 --- a/src/Std.php +++ b/src/Std.php @@ -100,9 +100,9 @@ public static function drawYpmnButton(array $params): string max-height: 150px; display: inline-block; justify-content: space-between; - background: white; - color: dimgrey; - border: 1px solid silver; + background: #ffffff; + color: #000000; + border: 1px solid #000000; border-radius: 5px; padding: 16px; " @@ -139,7 +139,7 @@ public static function drawYpmnButton(array $params): string padding-right: 10px; font-size: 14pt; ">Оплатить - '.(isset($params['sum']) ? '
      Date: Wed, 31 May 2023 09:32:50 +0300 Subject: [PATCH 015/151] Changed the type of the field cvv from int to string --- example.php | 23 +++++++++++++++++------ src/ApiRequest.php | 4 ++-- src/ApiRequestInterface.php | 2 +- src/Authorization.php | 14 ++++++++++---- src/AuthorizationInterface.php | 2 +- src/CardDetails.php | 8 ++++---- src/CardDetailsInterface.php | 8 ++++---- src/MerchantToken.php | 26 +++++++++++++++++++++----- src/MerchantTokenInterface.php | 8 ++++---- src/Std.php | 2 +- 10 files changed, 65 insertions(+), 32 deletions(-) diff --git a/example.php b/example.php index 286055f..049a0ed 100644 --- a/example.php +++ b/example.php @@ -12,6 +12,7 @@ use Ypmn\Delivery; use Ypmn\IdentityDocument; use Ypmn\Merchant; +use Ypmn\MerchantToken; use Ypmn\Payment; use Ypmn\Client; use Ypmn\Billing; @@ -264,7 +265,7 @@ // Переключиться на тестовый сервер (удалите в рабочей программе!) $apiRequest->setSandboxMode(); // Отправим запрос - $ypmnPaymentReference = new PaymentReference(2469476); + $ypmnPaymentReference = new PaymentReference(2469883); $responseData = $apiRequest->sendTokenCreationRequest($ypmnPaymentReference); // Преобразуем ответ из JSON в массив try { @@ -293,6 +294,8 @@ break; case 'paymentByToken': // Оплата по токену + // Установим номер (ID) заказа (номер заказа в вашем магазине, должен быть уникален в вашей системе) + $merchantPaymentReference = "order_id_" . time(); $orderAsProduct = new Product([ 'name' => 'Заказ №' . $merchantPaymentReference, 'sku' => $merchantPaymentReference, @@ -326,10 +329,19 @@ $payment->addProduct($orderAsProduct); // Установим валюту $payment->setCurrency('RUB'); + + + // токен + $token = new MerchantToken(); + $token->setTokenHash("8080695611129aa71725c413bd330e9e"); + + $auth = new Authorization('CCVISAMC',false); + $auth->setMerchantToken($token); + // Создадим и установим авторизацию по типу платежа - $payment->setAuthorization(new Authorization('CCVISAMC',true)); + $payment->setAuthorization($auth); + // Установим токен транзакции - $payment->set // Установим номер заказа (должен быть уникальным в вашей системе) $payment->setMerchantPaymentReference($merchantPaymentReference); // Установим адрес перенаправления пользователя после оплаты @@ -343,9 +355,8 @@ $apiRequest->setDebugMode(); // Переключиться на тестовый сервер (удалите в рабочей программе!) $apiRequest->setSandboxMode(); - // Отправим запрос - $ypmnPaymentReference = new PaymentReference(2469476); - $responseData = $apiRequest->sendTokenCreationRequest($ypmnPaymentReference); + + $responseData = $apiRequest->sendAuthRequest($auth, $merchant); // Преобразуем ответ из JSON в массив try { $responseData = json_decode((string) $responseData["response"], true); diff --git a/src/ApiRequest.php b/src/ApiRequest.php index cd4ca19..f2f666c 100644 --- a/src/ApiRequest.php +++ b/src/ApiRequest.php @@ -186,9 +186,9 @@ public function sendTokenCreationRequest(PaymentReference $payuPaymentReference) } /** @inheritdoc */ - public function sendTokenPaymentRequest(PaymentReference $payuPaymentReference): array + public function sendTokenPaymentRequest(MerchantToken $tokenHash): array { - return $this->sendPostRequest($payuPaymentReference, self::TOKEN_API); + return $this->sendPostRequest($tokenHash, self::AUTHORIZE_API); } /** diff --git a/src/ApiRequestInterface.php b/src/ApiRequestInterface.php index 1eb386c..a1d3700 100644 --- a/src/ApiRequestInterface.php +++ b/src/ApiRequestInterface.php @@ -79,5 +79,5 @@ public function sendTokenCreationRequest(PaymentReference $payuPaymentReference) * @param PaymentReference $payuPaymentReference Оплата * @return array */ - public function sendTokenPaymentRequest(PaymentReference $payuPaymentReference): array; + public function sendTokenPaymentRequest(MerchantToken $tokenHash): array; } \ No newline at end of file diff --git a/src/Authorization.php b/src/Authorization.php index 5e3d8bd..5edffb1 100644 --- a/src/Authorization.php +++ b/src/Authorization.php @@ -25,13 +25,13 @@ class Authorization implements AuthorizationInterface /** * Создать Платёжную Авторизацию * @param string $paymentMethodType Метод оплаты (из справочника) - * @param bool $isUsed страница оплаты Ypmn включена? + * @param bool $isPaymentPageUsed страница оплаты Ypmn включена? * @return void * @throws PaymentException Ошибка оплаты */ - public function __constructor(string $paymentMethodType, bool $isUsed) { + public function __constructor(string $paymentMethodType, bool $isPaymentPageUsed) { $this->setPaymentMethod($paymentMethodType); - $this->setUsePaymentPage($isUsed); + $this->setUsePaymentPage($isPaymentPageUsed); } /** @@ -83,7 +83,7 @@ public function getPaymentMethod(): string } /** @inheritDoc */ - public function getCardDetails(): CardDetailsInterface + public function getCardDetails(): ?CardDetailsInterface { return $this->cardDetails; } @@ -112,6 +112,12 @@ public function getMerchantToken(): ?MerchantTokenInterface */ public function setMerchantToken(?MerchantTokenInterface $merchantToken): self { + if (is_null($this->getCardDetails())){ + echo "Сработало 1 условие"; + } + if ($this->getUsePaymentPage() === false){ + echo "Сработало 2 условие"; + } if (is_null($this->getCardDetails()) && $this->getUsePaymentPage() === false) { $this->merchantToken = $merchantToken; diff --git a/src/AuthorizationInterface.php b/src/AuthorizationInterface.php index 06d9cea..9163622 100644 --- a/src/AuthorizationInterface.php +++ b/src/AuthorizationInterface.php @@ -28,7 +28,7 @@ public function setUsePaymentPage(bool $isUsed) : self; * Получить Данные Карты * @return CardDetailsInterface Данные Карты */ - public function getCardDetails(): CardDetailsInterface; + public function getCardDetails(): ?CardDetailsInterface; /** * Установить Данные Карты diff --git a/src/CardDetails.php b/src/CardDetails.php index c409cb6..4fb0d78 100644 --- a/src/CardDetails.php +++ b/src/CardDetails.php @@ -13,8 +13,8 @@ class CardDetails implements CardDetailsInterface /** @var int Год прекращения действия Карты */ private int $expiryYear; - /** @var int CVV Карты */ - private int $cvv; + /** @var string CVV Карты */ + private string $cvv; /** @var string Имя Владельца Карты */ private string $owner; @@ -94,13 +94,13 @@ public function setExpiryYear(int $expiryYear): self } /** @inheritDoc */ - public function getCvv(): int + public function getCvv(): string { return $this->cvv; } /** @inheritDoc */ - public function setCvv(int $cvv): self + public function setCvv(string $cvv): self { $this->cvv = $cvv; return $this; diff --git a/src/CardDetailsInterface.php b/src/CardDetailsInterface.php index 95bac13..765dc0f 100644 --- a/src/CardDetailsInterface.php +++ b/src/CardDetailsInterface.php @@ -47,16 +47,16 @@ public function getExpiryYear() : int; /** * Установить CVV Карты - * @param int $cvv CVV Карты + * @param string $cvv CVV Карты * @return $this */ - public function setCvv(int $cvv) : self; + public function setCvv(string $cvv) : self; /** * Получить CVV Карты - * @return int CVV Карты + * @return string CVV Карты */ - public function getCvv() : int; + public function getCvv() : string; /** * Установить Имя Владельца Карты diff --git a/src/MerchantToken.php b/src/MerchantToken.php index 6eaf8f3..ab44ba3 100644 --- a/src/MerchantToken.php +++ b/src/MerchantToken.php @@ -2,13 +2,13 @@ namespace Ypmn; -class MerchantToken implements MerchantTokenInterface +class MerchantToken implements MerchantTokenInterface, \JsonSerializable { /** @var string Хэш Токен карты */ private string $tokenHash; - /** @var int CVV Карты */ - private int $cvv; + /** @var string CVV Карты */ + private string $cvv; /** @var string Имя Владельца Карты */ private string $owner; @@ -27,13 +27,13 @@ public function setTokenHash(string $tokenHash): MerchantToken } /** @inheritDoc */ - public function getCvv(): int + public function getCvv(): string { return $this->cvv; } /** @inheritDoc */ - public function setCvv(int $cvv): MerchantToken + public function setCvv(string $cvv): MerchantToken { $this->cvv = $cvv; return $this; @@ -76,4 +76,20 @@ public function toArray() : array return $resultArray; } + + /** + * @return mixed + */ + public function jsonSerialize() + { + if(is_null($this->tokenHash)){ + throw new PaymentException("Не хватает токена"); + } + + $resultArray = [ + 'tokenHash' => $this->tokenHash, + ]; + + return json_encode($resultArray, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); + } } \ No newline at end of file diff --git a/src/MerchantTokenInterface.php b/src/MerchantTokenInterface.php index 57affe9..c13e67e 100644 --- a/src/MerchantTokenInterface.php +++ b/src/MerchantTokenInterface.php @@ -19,16 +19,16 @@ public function getTokenHash() : string; /** * Установить CVV Карты - * @param int $cvv CVV Карты + * @param string $cvv CVV Карты * @return $this */ - public function setCvv(int $cvv) : self; + public function setCvv(string $cvv) : self; /** * Получить CVV Карты - * @return int CVV Карты + * @return string CVV Карты */ - public function getCvv() : int; + public function getCvv() : string; /** * Установить Имя Владельца Карты diff --git a/src/Std.php b/src/Std.php index 3c0f9af..f3b39a8 100644 --- a/src/Std.php +++ b/src/Std.php @@ -132,7 +132,7 @@ public static function drawYpmnButton(array $params): string display: inline-block; " 'Заказ №' . $merchantPaymentReference, - 'sku' => $merchantPaymentReference, - 'unitPrice' => 1.42, - 'quantity' => 2, -]); - -// Опишем Биллинговую (платёжную) информацию -$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); -// Установим валюту -$payment->setCurrency('RUB'); -// Создадим и установим авторизацию по типу платежа -$payment->setAuthorization(new Authorization('CCVISAMC',true)); -// Установим номер заказа (должен быть уникальным в вашей системе) -$payment->setMerchantPaymentReference($merchantPaymentReference); -// Установим адрес перенаправления пользователя после оплаты -$payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); -// Установим клиентское подключение -$payment->setClient($client); - -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос -$responseData = $apiRequest->sendAuthRequest($payment, $merchant); -// Преобразуем ответ из JSON в массив -try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты - echo Std::drawYpmnButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // .. или сделаем редирект на форму оплаты (опционально) - // Std::redirect($responseData["paymentResult"]['url']); -} catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
      - Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      -
      -
      ' . $exception->getMessage() . '
      ', - 'type' => 'danger', - ]); -} -``` -#### Расширенные возможности, полный набор полей -```php -setName('Синий Мяч'); -// Установим Артикул -$product1->setSku('ball-05'); -// Установим Стоимость за единицу -$product1->setUnitPrice('500'); -// Установим Количество -$product1->setQuantity(1); -// Установим НДС -$product1->setVat(20); - -//Опишем вторую позицию с помощью сокращённого синтаксиса: -$product2 = new Product([ - 'name' => 'Жёлтый Круг', - 'sku' => 'toy-15', - 'unitPrice' => '1600', - 'quantity' => '3', - 'vat' => 0, -]); - -// Опишем Биллинговую (платёжную) информацию -$billing = new Billing; -// Установим Код страны -$billing->setCountryCode('RU'); -// Установим Город -$billing->setCity('Москва'); -// Установим Регион -$billing->setState('Центральный регион'); -// Установим Адрес Плательщика (первая строка) -$billing->setAddressLine1('Улица Старый Арбат, дом 10'); -// Установим Адрес Плательщика (вторая строка) -$billing->setAddressLine1('Офис Ypmn'); -// Установим Почтовый Индекс Плательщика -$billing->setZipCode('121000'); -// Установим Имя Плательщика -$billing->setFirstName('Иван'); -// Установим Фамилия Плательщика -$billing->setLastName('Петров'); -// Установим Телефон Плательщика -$billing->setPhone('+79670660742'); -// Установим Email Плательщика -$billing->setEmail('test1@ypmn.ru'); - -// (необязательно) Опишем Доствку и принимающее лицо -$delivery = new Delivery; -// Установим документ, подтверждающий право приёма доставки -$delivery->setIdentityDocument( - new IdentityDocument('123456', 'PERSONALID') -); -// Установим Код страны -$delivery->setCountryCode('RU'); -// Установим Город -$delivery->setCity('Москва'); -// Установим Регион -$delivery->setState('Центральный регион'); -// Установим Адрес Лица, принимающего заказ (первая строка) -$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); -// Установим Адрес Лица, принимающего заказ (вторая строка) -$delivery->setAddressLine1('Офис Ypmn'); -// Установим Почтовый Индекс Лица, принимающего заказ -$delivery->setZipCode('121000'); -// Установим Имя Лица, принимающего заказ -$delivery->setFirstName('Мария'); -// Установим Фамилия Лица, принимающего заказ -$delivery->setLastName('Петрова'); -// Установим Телефон Лица, принимающего заказ -$delivery->setPhone('+79670660743'); -// Установим Email Лица, принимающего заказ -$delivery->setEmail('test2@ypmn.ru'); -// Установим Название Компании, в которой можно оставить заказ -$delivery->setCompanyName('ООО "Вектор"'); - -// Создадим клиентское подключение -$client = new Client; -// Установим биллинг -$client->setBilling($billing); -// Установим доставку -$client->setDelivery($delivery); -// Установим IP (автоматически) -$client->setCurrentClientIp(); -// И Установим время (автоматически) -$client->setCurrentClientTime(); - -// Создадим платёж -$payment = new Payment; -// Установим позиции -$payment->addProduct($product1); -$payment->addProduct($product2); -// Установим валюту -$payment->setCurrency('RUB'); -// Создадим и установим авторизацию по типу платежа -$payment->setAuthorization(new Authorization('CCVISAMC',true)); -// Установим номер заказа (должен быть уникальным в вашей системе) -$payment->setMerchantPaymentReference('primer_nomer__' . time()); -// Установим адрес перенаправления пользователя после оплаты -$payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); -// Установим клиентское подключение -$payment->setClient($client); +### Функции -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос -$responseData = $apiRequest->sendAuthRequest($payment, $merchant); -// Преобразуем ответ из JSON в массив -try { - $responseData = json_decode((string) $responseData["response"], true); - // Нарисуем кнопку оплаты - echo Std::drawYpmnButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // .. или сделаем редирект на форму оплаты (опционально) - // Std::redirect($responseData["paymentResult"]['url']); -} catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
      - Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      -
      -
      ' . $exception->getMessage() . '
      ', - 'type' => 'danger', - ]); -} -``` -### Страница пользователя после совершения платежа -Данные о состоянии платежа после его создания передаются в параметрах POST ($_POST) -```php -print_r($_POST); -``` -### Получить номер транзакции в YourPayments (GetStatus) -```php -setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос к API -$responseData = $apiRequest->sendStatusRequest($merchantPaymentReference); -``` - -### Списание средств (Capture) -В зависимости от настройки мерчанта, Ypmn может списывать денежные средства автоматически, -// Либо с помощью дополнительного запроса, описанного ниже. -```php -setPaymentReference(2297597); - -// Cумма исходной операции на авторизацию -$capture->setOriginalAmount(5300); -// Cумма фактического списания -$capture->setAmount(3700); -// Валюта -$capture->setCurrency('RUB'); - -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос к API -$responseData = $apiRequest->sendCaptureRequest($capture, $merchant); -``` -### Отмена платежа (Refund) -```php -setPaymentReference(2297597); -// Cумма исходной операции на списание (Capture) -// Пример: если сумма авторизации была 5300, а сумма списания 3700 (частичное списание), указать 3700 -$refund->setOriginalAmount(3700); -// Cумма фактического списания -$refund->setAmount(3700); -// Установим валюту -$refund->setCurrency('RUB'); -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос к API -$responseData = $apiRequest->sendRefundRequest($refund, $merchant); -``` +Для работы рекомендуется использовать любую современную IDE (VS Code, Intellij Idea/PHPStorm, +Eclipse, Netbeans, etc), чтобы получать подробные подсказки прямо во время редактирования кода. +![IDE screenshot](screenshot.jpg "IDE screenshot") ## Ссылки - [Докуметация по API](https://dev.YPMN.ru/ru/documents/apiv4/) @@ -376,4 +71,4 @@ $responseData = $apiRequest->sendRefundRequest($refund, $merchant); ------------- ![](https://ypmn.ru/s/img/logo/ru/dark/horizontal_logo.svg) -[YPMN.ru](https:/YPMN.ru/ "Платёжная система для сайтов и не только") +[YPMN.ru](https://YPMN.ru/ "Платёжная система для сайтов и не только") diff --git a/src/Documentation/getPaymentLink.md b/src/Documentation/getPaymentLink.md new file mode 100644 index 0000000..9d16954 --- /dev/null +++ b/src/Documentation/getPaymentLink.md @@ -0,0 +1,145 @@ +### Создание (авторизация) платежа +Метод создаёт платёж (транзакцию) в системе Ypmn. +В зависимости от настройки, средства списываются либо сразу, +либо после отправки метода "capture". + + +#### Расширенные возможности, полный набор полей +```php +setName('Синий Мяч'); +// Установим Артикул +$product1->setSku('ball-05'); +// Установим Стоимость за единицу +$product1->setUnitPrice('500'); +// Установим Количество +$product1->setQuantity(1); +// Установим НДС +$product1->setVat(20); + +//Опишем вторую позицию с помощью сокращённого синтаксиса: +$product2 = new Product([ + 'name' => 'Жёлтый Круг', + 'sku' => 'toy-15', + 'unitPrice' => '1600', + 'quantity' => '3', + 'vat' => 0, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Город +$billing->setCity('Москва'); +// Установим Регион +$billing->setState('Центральный регион'); +// Установим Адрес Плательщика (первая строка) +$billing->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Плательщика (вторая строка) +$billing->setAddressLine1('Офис Ypmn'); +// Установим Почтовый Индекс Плательщика +$billing->setZipCode('121000'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Телефон Плательщика +$billing->setPhone('+79670660742'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); + +// (необязательно) Опишем Доствку и принимающее лицо +$delivery = new Delivery; +// Установим документ, подтверждающий право приёма доставки +$delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') +); +// Установим Код страны +$delivery->setCountryCode('RU'); +// Установим Город +$delivery->setCity('Москва'); +// Установим Регион +$delivery->setState('Центральный регион'); +// Установим Адрес Лица, принимающего заказ (первая строка) +$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Лица, принимающего заказ (вторая строка) +$delivery->setAddressLine1('Офис Ypmn'); +// Установим Почтовый Индекс Лица, принимающего заказ +$delivery->setZipCode('121000'); +// Установим Имя Лица, принимающего заказ +$delivery->setFirstName('Мария'); +// Установим Фамилия Лица, принимающего заказ +$delivery->setLastName('Петрова'); +// Установим Телефон Лица, принимающего заказ +$delivery->setPhone('+79670660743'); +// Установим Email Лица, принимающего заказ +$delivery->setEmail('test2@ypmn.ru'); +// Установим Название Компании, в которой можно оставить заказ +$delivery->setCompanyName('ООО "Вектор"'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); +// Установим доставку +$client->setDelivery($delivery); +// Установим IP (автоматически) +$client->setCurrentClientIp(); +// И Установим время (автоматически) +$client->setCurrentClientTime(); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($product1); +$payment->addProduct($product2); +// Установим валюту +$payment->setCurrency('RUB'); +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization(new Authorization('CCVISAMC',true)); +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference('primer_nomer__' . time()); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // .. или сделаем редирект на форму оплаты (опционально) + // Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
      + Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      +
      +
      ' . $exception->getMessage() . '
      ', + 'type' => 'danger', + ]); +} +``` diff --git a/src/Documentation/paymentCapture.md b/src/Documentation/paymentCapture.md new file mode 100644 index 0000000..ec91531 --- /dev/null +++ b/src/Documentation/paymentCapture.md @@ -0,0 +1,27 @@ +### Списание средств (Capture) +В зависимости от настройки мерчанта, Ypmn может списывать денежные средства автоматически, либо с помощью дополнительного запроса, описанного ниже. +```php +setPaymentReference(2297597); + +// Cумма исходной операции на авторизацию +$capture->setOriginalAmount(5300); +// Cумма фактического списания +$capture->setAmount(3700); +// Валюта +$capture->setCurrency('RUB'); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (удалите после окончания интеграции) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (удалите после окончания интеграции) +$apiRequest->setSandboxMode(); +// Отправим запрос к API +$responseData = $apiRequest->sendCaptureRequest($capture, $merchant); + +``` diff --git a/src/Documentation/paymentGetStatus.md b/src/Documentation/paymentGetStatus.md new file mode 100644 index 0000000..60dd94a --- /dev/null +++ b/src/Documentation/paymentGetStatus.md @@ -0,0 +1,16 @@ +### Получить состояние транзакции в YourPayments (GetStatus) +```php +setDebugMode(); +// Переключиться на тестовый сервер (удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос к API +$responseData = $apiRequest->sendStatusRequest($merchantPaymentReference); +``` diff --git a/src/Documentation/paymentRefund.md b/src/Documentation/paymentRefund.md new file mode 100644 index 0000000..31766f0 --- /dev/null +++ b/src/Documentation/paymentRefund.md @@ -0,0 +1,27 @@ +### Отмена платежа и возврат средств (Refund) +```php +setPaymentReference(2297597); +// Cумма исходной операции на списание (Capture) +// Пример: если сумма авторизации была 5300, а сумма списания 3700 (частичное списание), указать 3700 +$refund->setOriginalAmount(3700); +// Cумма фактического списания +$refund->setAmount(3700); +// Установим валюту +$refund->setCurrency('RUB'); +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос к API +$responseData = $apiRequest->sendRefundRequest($refund, $merchant); +``` diff --git a/src/Documentation/returnPage.md b/src/Documentation/returnPage.md new file mode 100644 index 0000000..2db12d0 --- /dev/null +++ b/src/Documentation/returnPage.md @@ -0,0 +1,10 @@ +### Страница пользователя после совершения платежа +Данные о состоянии платежа после его создания передаются в параметрах $_GET или $_POST, в зависимости от настройки интеграции. +```php + 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 1.42, + 'quantity' => 2, +]); + +// Опишем Биллинговую (платёжную) информацию +$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); +// Установим валюту +$payment->setCurrency('RUB'); +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization(new Authorization('CCVISAMC',true)); +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference($merchantPaymentReference); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // .. или сделаем редирект на форму оплаты (опционально) + // Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
      + Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      +
      +
      ' . $exception->getMessage() . '
      ', + 'type' => 'danger', + ]); +} +``` From bda308c03c115d220047648b264994164ed9f1ce Mon Sep 17 00:00:00 2001 From: Alexey Babak Date: Wed, 31 May 2023 18:46:36 +0300 Subject: [PATCH 019/151] added docs links --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a8bdfd5..f9a1337 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,14 @@ require vendor/autoload.php; $merchant = new Merchant('rudevru1', 'hE9I1?3@|C8@w[1I&=y)'); ``` ### Функции - +1. [Cамый простой платёж](src/Documentation/simpleGetPaymentLink.md) +2. [Платёж со всеми полями](src/Documentation/getPaymentLink.md) +3. Получить токен (чтобы запомнить карту) +4. Оплата по токену +5. [Списание средств](src/Documentation/paymentCapture.md) +6. [src/Documentation/paymentRefund.md](src/Documentation/paymentRefund.md) +7. Проверка статуса платежа +8. [Страница после оплаты](src/Documentation/returnPage.md) Для работы рекомендуется использовать любую современную IDE (VS Code, Intellij Idea/PHPStorm, Eclipse, Netbeans, etc), чтобы получать подробные подсказки прямо во время редактирования кода. From 2117f08c174a447c568220d12eca2563b37f151d Mon Sep 17 00:00:00 2001 From: Alexey Babak Date: Tue, 6 Jun 2023 19:23:34 +0300 Subject: [PATCH 020/151] style fix, more payu to ypmn changes, new menu --- example.php | 278 +----------------- example_footer.html | 8 + example_header.php | 84 ++++++ example_list.php | 51 ++++ example_template.html | 14 - index.php | 4 + src/{Interfaces => }/AirlineInfoInterface.php | 2 +- src/ApiRequest.php | 30 +- src/{Interfaces => }/ApiRequestInterface.php | 5 +- src/Authorization.php | 4 - .../AuthorizationInterface.php | 4 +- src/Billing.php | 3 - src/{Interfaces => }/BillingInterface.php | 2 +- src/Capture.php | 2 - src/CaptureApiRequest.php | 2 - src/{Interfaces => }/CaptureInterface.php | 2 +- src/CardDetails.php | 2 - src/{Interfaces => }/CardDetailsInterface.php | 4 +- src/Client.php | 4 - src/{Interfaces => }/ClientInterface.php | 2 +- src/Delivery.php | 3 - src/{Interfaces => }/DeliveryInterface.php | 2 +- src/Examples/getPaymentLink.php | 152 ++++++++++ src/Examples/getToken.php | 54 ++++ src/Examples/simpleGetPaymentLink.php | 97 ++++++ src/Examples/start.php | 1 + src/IdentityDocument.php | 4 +- .../IdentityDocumentInterface.php | 4 +- src/Merchant.php | 2 - src/{Interfaces => }/MerchantInterface.php | 2 +- src/MerchantToken.php | 2 - .../MerchantTokenInterface.php | 2 +- src/OrderData.php | 2 - src/{Interfaces => }/OrderDataInterface.php | 2 +- src/Payment.php | 5 - src/{Interfaces => }/PaymentInterface.php | 5 +- src/PaymentResult.php | 3 - .../PaymentResultInterface.php | 4 +- src/Product.php | 2 - src/{Interfaces => }/ProductInterface.php | 2 +- src/Refund.php | 2 - src/{Interfaces => }/RefundInterface.php | 2 +- src/Std.php | 2 +- src/StoredCredentials.php | 2 - .../StoredCredentialsInterface.php | 2 +- src/{Interfaces => }/TransactionInterface.php | 2 +- src/Webhook.php | 3 - src/{Interfaces => }/WebhookInterface.php | 5 +- 48 files changed, 507 insertions(+), 370 deletions(-) create mode 100644 example_footer.html create mode 100644 example_header.php create mode 100644 example_list.php delete mode 100644 example_template.html rename src/{Interfaces => }/AirlineInfoInterface.php (92%) rename src/{Interfaces => }/ApiRequestInterface.php (97%) rename src/{Interfaces => }/AuthorizationInterface.php (97%) rename src/{Interfaces => }/BillingInterface.php (99%) rename src/{Interfaces => }/CaptureInterface.php (99%) rename src/{Interfaces => }/CardDetailsInterface.php (98%) rename src/{Interfaces => }/ClientInterface.php (98%) rename src/{Interfaces => }/DeliveryInterface.php (99%) create mode 100644 src/Examples/getPaymentLink.php create mode 100644 src/Examples/getToken.php create mode 100644 src/Examples/simpleGetPaymentLink.php create mode 100644 src/Examples/start.php rename src/{Interfaces => }/IdentityDocumentInterface.php (93%) rename src/{Interfaces => }/MerchantInterface.php (96%) rename src/{Interfaces => }/MerchantTokenInterface.php (97%) rename src/{Interfaces => }/OrderDataInterface.php (99%) rename src/{Interfaces => }/PaymentInterface.php (98%) rename src/{Interfaces => }/PaymentResultInterface.php (98%) rename src/{Interfaces => }/ProductInterface.php (99%) rename src/{Interfaces => }/RefundInterface.php (99%) rename src/{Interfaces => }/StoredCredentialsInterface.php (96%) rename src/{Interfaces => }/TransactionInterface.php (61%) rename src/{Interfaces => }/WebhookInterface.php (94%) diff --git a/example.php b/example.php index 049a0ed..66dd7f6 100644 --- a/example.php +++ b/example.php @@ -24,7 +24,8 @@ use Ypmn\Std; use Ypmn\PaymentReference; -// TODO: нужен публичный тестовый мерчант, которого можно включить в документацию + + // Создадим тестового мерчанта //$merchant = new Merchant('rudevru1', 'hE9I1?3@|C8@w[1I&=y)'); $merchant = new Merchant('CC1', 'SECRET_KEY'); @@ -33,265 +34,14 @@ if(isset($_GET['function'])){ try { switch ($_GET['function']) { + case 'start': case 'simpleGetPaymentLink': - // Оплата по ссылке Ypmn - // Минимальный набор полей - - // Представим, что мы не хотим передавать товары, только номер заказа и сумму - // Установим номер (ID) заказа (номер заказа в вашем магазине, должен быть уникален в вашей системе) - $merchantPaymentReference = "order_id_" . time(); - - $orderAsProduct = new Product([ - 'name' => 'Заказ №' . $merchantPaymentReference, - 'sku' => $merchantPaymentReference, - 'unitPrice' => 1.42, - 'quantity' => 2, - ]); - - // Опишем Биллинговую (платёжную) информацию - $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); - // Установим валюту - $payment->setCurrency('RUB'); - // Создадим и установим авторизацию по типу платежа - $payment->setAuthorization(new Authorization('CCVISAMC',true)); - // Установим номер заказа (должен быть уникальным в вашей системе) - $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 в массив - try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты - echo Std::drawYpmnButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // .. или сделаем редирект на форму оплаты (опционально) - // Std::redirect($responseData["paymentResult"]['url']); - } catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
      - Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      -
      -
      ' . $exception->getMessage() . '
      ', - 'type' => 'danger', - ]); - - throw new PaymentException('Платёжный метод временно недоступен'); - } - break; case 'getPaymentLink': - // Оплата по ссылке Ypmn - // Представим, что нам надо оплатить пару позиций: Синий Мяч и Жёлтый Круг - - // Опишем первую позицию - $product1 = new Product; - // Установим Наименование (название товара или услуги) - $product1->setName('Синий Квадрат'); - // Установим Артикул - $product1->setSku('ball-05'); - // Установим Стоимость за единицу - $product1->setUnitPrice('500'); - // Установим Количество - $product1->setQuantity(1); - // Установим НДС - $product1->setVat(20); - - //Опишем вторую позицию с помощью сокращённого синтаксиса: - $product2 = new Product([ - 'name' => 'Оранжевый Круг', - 'sku' => 'toy-15', - 'unitPrice' => 160000, - 'quantity' => 3, - 'vat' => 0, - ]); - - // Опишем Биллинговую (платёжную) информацию - $billing = new Billing; - // Установим Код страны - $billing->setCountryCode('RU'); - // Установим Город - $billing->setCity('Москва'); - // Установим Регион - $billing->setState('Центральный регион'); - // Установим Адрес Плательщика (первая строка) - $billing->setAddressLine1('Улица Старый Арбат, дом 10'); - // Установим Адрес Плательщика (вторая строка) - $billing->setAddressLine1('Офис Ypmn'); - // Установим Почтовый Индекс Плательщика - $billing->setZipCode('121000'); - // Установим Имя Плательщика - $billing->setFirstName('Иван'); - // Установим Фамилия Плательщика - $billing->setLastName('Петров'); - // Установим Телефон Плательщика - $billing->setPhone('+79670660742'); - // Установим Email Плательщика - $billing->setEmail('test1@ypmn.ru'); - - // (необязательно) Опишем Доствку и принимающее лицо - $delivery = new Delivery; - // Установим документ, подтверждающий право приёма доставки - $delivery->setIdentityDocument( - new IdentityDocument('123456', 'PERSONALID') - ); - // Установим Код страны - $delivery->setCountryCode('RU'); - // Установим Город - $delivery->setCity('Москва'); - // Установим Регион - $delivery->setState('Центральный регион'); - // Установим Адрес Лица, принимающего заказ (первая строка) - $delivery->setAddressLine1('Улица Старый Арбат, дом 10'); - // Установим Адрес Лица, принимающего заказ (вторая строка) - $delivery->setAddressLine1('Офис Ypmn'); - // Установим Почтовый Индекс Лица, принимающего заказ - $delivery->setZipCode('121000'); - // Установим Имя Лица, принимающего заказ - $delivery->setFirstName('Мария'); - // Установим Фамилия Лица, принимающего заказ - $delivery->setLastName('Петрова'); - // Установим Телефон Лица, принимающего заказ - $delivery->setPhone('+79670660743'); - // Установим Email Лица, принимающего заказ - $delivery->setEmail('test2@ypmn.ru'); - // Установим Название Компании, в которой можно оставить заказ - $delivery->setCompanyName('ООО "Вектор"'); - - // Создадим клиентское подключение - $client = new Client; - // Установим биллинг - $client->setBilling($billing); - // Установим доставку - $client->setDelivery($delivery); - // Установим IP (автоматически) - $client->setCurrentClientIp(); - // И Установим время (автоматически) - $client->setCurrentClientTime(); - - // Создадим платёж - $payment = new Payment; - // Установим позиции - $payment->addProduct($product1); - $payment->addProduct($product2); - // Установим валюту - $payment->setCurrency('RUB'); - // Создадим и установим авторизацию по типу платежа - $payment->setAuthorization(new Authorization('CCVISAMC',true)); - // Установим номер заказа (должен быть уникальным в вашей системе) - $payment->setMerchantPaymentReference('primer_nomer__' . time()); - // Установим адрес перенаправления пользователя после оплаты - $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 в массив - try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты - echo Std::drawYpmnButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: - // echo Std::redirect($responseData["paymentResult"]['url']); - } catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
      - Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      -
      -
      ' . $exception->getMessage() . '
      ', - 'type' => 'danger', - ]); - - throw new PaymentException('Платёжный метод временно недоступен'); - } - break; case 'getToken': - // Хотим получить токен - // Создадим HTTP-запрос к API - $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) - $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) - $apiRequest->setSandboxMode(); - // Отправим запрос - $ypmnPaymentReference = new PaymentReference(2469883); - $responseData = $apiRequest->sendTokenCreationRequest($ypmnPaymentReference); - // Преобразуем ответ из JSON в массив - try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты 5 -// echo Std::drawYpmnButton([ -// 'url' => $responseData["paymentResult"]['url'] -// ]); + include './src/Examples/'.$_GET['function'] . '.php'; + break; - // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: - // echo Std::redirect($responseData["paymentResult"]['url']); - } catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
      - Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      -
      -
      ' . $exception->getMessage() . '
      ', - 'type' => 'danger', - ]); - throw new PaymentException('Платёжный метод временно недоступен'); - } - break; case 'paymentByToken': // Оплата по токену // Установим номер (ID) заказа (номер заказа в вашем магазине, должен быть уникален в вашей системе) @@ -351,9 +101,9 @@ // Создадим HTTP-запрос к API $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) + // Включить режим отладки (закомментируйте или удалите в рабочей программе!) $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) + // Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) $apiRequest->setSandboxMode(); $responseData = $apiRequest->sendAuthRequest($auth, $merchant); @@ -402,9 +152,9 @@ // Создадим HTTP-запрос к API $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) + // Включить режим отладки (закомментируйте или удалите в рабочей программе!) $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) + // Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) $apiRequest->setSandboxMode(); // Отправим запрос к API $responseData = $apiRequest->sendCaptureRequest($capture, $merchant); @@ -417,9 +167,9 @@ $merchantPaymentReference = 'primer_nomer__184'; // Создадим HTTP-запрос к API $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) + // Включить режим отладки (закомментируйте или удалите в рабочей программе!) $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) + // Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) $apiRequest->setSandboxMode(); // Отправим запрос к API $responseData = $apiRequest->sendStatusRequest($merchantPaymentReference); @@ -445,9 +195,9 @@ $refund->setCurrency('RUB'); // Создадим HTTP-запрос к API $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) + // Включить режим отладки (закомментируйте или удалите в рабочей программе!) $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) + // Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) $apiRequest->setSandboxMode(); // Отправим запрос к API $responseData = $apiRequest->sendRefundRequest($refund, $merchant); @@ -469,5 +219,3 @@ echo $e->getHtmlMessage(); } } - -include 'example_template.html'; diff --git a/example_footer.html b/example_footer.html new file mode 100644 index 0000000..ddfe1d7 --- /dev/null +++ b/example_footer.html @@ -0,0 +1,8 @@ + + + +
      + hello +
      + + diff --git a/example_header.php b/example_header.php new file mode 100644 index 0000000..c366ed4 --- /dev/null +++ b/example_header.php @@ -0,0 +1,84 @@ + + + + + Твои Платежи | Сервис для работы с электронными платежами + + + + + + + + + + + + + + + +
      +
      +
      + + + + + + + + + + + + + + + + + + + +
      + +
      + + $examples[ $_GET['function'] ]['about'], + ]); + } diff --git a/example_list.php b/example_list.php new file mode 100644 index 0000000..6a2190c --- /dev/null +++ b/example_list.php @@ -0,0 +1,51 @@ + [ + 'name' => 'Начало работы', +// 'about' => '', + 'link' => '', + ], + 'simpleGetPaymentLink' => [ + 'name' => 'Самая простая кнопка оплаты', + 'about' => 'В этом примере показана самая простая реализация. Без детализации, просто оплата определённой суммы/заказа.', + 'link' => '', + ], + 'getPaymentLink' => [ + 'name' => 'Платёж со всеми подробностями', + 'about' => '', + 'link' => '', + ], + 'getToken' => [ + 'name' => 'Создание токена', + 'about' => 'Приложение передаёт номер успешно оплаченного заказа в YPMN API, и получает в ответ платёжный токен', + 'link' => '', + ], + 'paymentByToken' => [ + 'name' => 'Оплата токеном', + 'about' => 'Оплата с помощью токена (теперь не нужно повторно вводить данные банковской карты)', + 'link' => '', + ], + 'paymentCapture' => [ + 'name' => 'Списание средств', + 'about' => 'Списание ранее заблокированной на счету суммы. Не обязательно, если у Вас настроена оплата в 1 шаг.', + 'link' => '', + ], + 'paymentRefund' => [ + 'name' => 'Возврат средств', + 'about' => 'Запрос на полный или частичный возврат средств.', + 'link' => '', + ], + 'paymentGetStatus' => [ + 'name' => 'Проверка статуса платежа', + 'about' => 'Запрос к YPMN API о состоянии платежа.', + 'link' => '', + ], + 'returnPage' => [ + 'name' => 'Страница после оплаты', + 'about' => 'Это пример странцы, на которую плательщик возвращается после совершения платежа.', + 'link' => '', + ], +]; diff --git a/example_template.html b/example_template.html deleted file mode 100644 index 02f4693..0000000 --- a/example_template.html +++ /dev/null @@ -1,14 +0,0 @@ -
      -
      -
        -
      1. simpleGetPaymentLink -- самый простой платёж
      2. -
      3. getPaymentLink -- платёж со всеми полями
      4. -
      5. getToken -- получить токен
      6. -
      7. paymentByToken -- оплата по токену
      8. -
      9. paymentCapture -- списание средств
      10. - -
      11. paymentRefund -- запрос на возврат
      12. -
      13. paymentGetStatus -- проверка статуса платежа
      14. -
      15. returnPage -- страница после оплаты
      16. -
      - diff --git a/index.php b/index.php index d7f34ab..963deab 100644 --- a/index.php +++ b/index.php @@ -7,6 +7,7 @@ spl_autoload_register(function ($className) { $className = explode('\\', $className); $className = end($className); + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $className . '.php'; if (is_readable($filename)) { @@ -14,4 +15,7 @@ } }); +include 'example_list.php'; +include 'example_header.php'; require 'example.php'; +include 'example_footer.html'; diff --git a/src/Interfaces/AirlineInfoInterface.php b/src/AirlineInfoInterface.php similarity index 92% rename from src/Interfaces/AirlineInfoInterface.php rename to src/AirlineInfoInterface.php index d32d138..b093cd5 100644 --- a/src/Interfaces/AirlineInfoInterface.php +++ b/src/AirlineInfoInterface.php @@ -1,6 +1,6 @@ Оставить заявку на улучшение'; echo '
      Контакты'; } else { + $cpanel_url = 'https://' . ($this->getSandboxMode() ? 'sandbox' : 'secure' ). '.ypmn.ru/cpanel/'; + if ($this->getSandboxMode()) { - echo '
      Внимание! У вас включен тестовый режим (режим песочницы). Все запросы уходят на sandbox.payu.ru'; + echo Std::alert([ + 'type' => 'warning', + 'text' => ' + Внимание! + У вас включен тестовый режим. +
      Все запросы уходят на тестовый сервер sandbox.ypmn.ru +
      +
      + Когда закончите тестирование, закомментируйте или удалите строки кода: + + $apiRequest->setDebugMode(); // вывод отладки +
      $apiRequest->setSandboxMode(); // тетстовый сервер +
      + ', + ]); } - $cpanel_url = 'https://' . ($this->getSandboxMode() ? 'sandbox' : 'secure' ). '.payu.ru/cpanel/'; - echo '
      Отслеживайте состояние транзакции по адресу ' . $cpanel_url . ''; - echo '

      '; } } @@ -157,6 +165,10 @@ private function sendPostRequest(JsonSerializable $data, string $api): array throw new PaymentException($err); } + if ($response == null || strlen($response) === 0) { + throw new PaymentException('Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.'); + } + return ['response' => $response, 'error' => $err]; } diff --git a/src/Interfaces/ApiRequestInterface.php b/src/ApiRequestInterface.php similarity index 97% rename from src/Interfaces/ApiRequestInterface.php rename to src/ApiRequestInterface.php index 1b24411..9b1416a 100644 --- a/src/Interfaces/ApiRequestInterface.php +++ b/src/ApiRequestInterface.php @@ -1,9 +1,6 @@ setName('Синий Квадрат'); +// Установим Артикул +$product1->setSku('ball-05'); +// Установим Стоимость за единицу +$product1->setUnitPrice(500); +// Установим Количество +$product1->setQuantity(1); +// Установим НДС +$product1->setVat(20); + +//Опишем вторую позицию с помощью сокращённого синтаксиса: +$product2 = new Product([ + 'name' => 'Оранжевый Круг', + 'sku' => 'toy-15', + 'unitPrice' => 160000, + 'quantity' => 3, + 'vat' => 0, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Город +$billing->setCity('Москва'); +// Установим Регион +$billing->setState('Центральный регион'); +// Установим Адрес Плательщика (первая строка) +$billing->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Плательщика (вторая строка) +$billing->setAddressLine1('Офис Ypmn'); +// Установим Почтовый Индекс Плательщика +$billing->setZipCode('121000'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Телефон Плательщика +$billing->setPhone('+79670660742'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); + +// (необязательно) Опишем Доствку и принимающее лицо +$delivery = new Delivery; +// Установим документ, подтверждающий право приёма доставки +$delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') +); +// Установим Код страны +$delivery->setCountryCode('RU'); +// Установим Город +$delivery->setCity('Москва'); +// Установим Регион +$delivery->setState('Центральный регион'); +// Установим Адрес Лица, принимающего заказ (первая строка) +$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Лица, принимающего заказ (вторая строка) +$delivery->setAddressLine1('Офис Ypmn'); +// Установим Почтовый Индекс Лица, принимающего заказ +$delivery->setZipCode('121000'); +// Установим Имя Лица, принимающего заказ +$delivery->setFirstName('Мария'); +// Установим Фамилия Лица, принимающего заказ +$delivery->setLastName('Петрова'); +// Установим Телефон Лица, принимающего заказ +$delivery->setPhone('+79670660743'); +// Установим Email Лица, принимающего заказ +$delivery->setEmail('test2@ypmn.ru'); +// Установим Название Компании, в которой можно оставить заказ +$delivery->setCompanyName('ООО "Вектор"'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); +// Установим доставку +$client->setDelivery($delivery); +// Установим IP (автоматически) +$client->setCurrentClientIp(); +// И Установим время (автоматически) +$client->setCurrentClientTime(); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($product1); +$payment->addProduct($product2); +// Установим валюту +$payment->setCurrency('RUB'); +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization(new Authorization('CCVISAMC',true)); +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference('primer_nomer__' . time()); +// Установим адрес перенаправления пользователя после оплаты +$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 в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
      + Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      +
      +
      ' . $exception->getMessage() . '
      ', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/getToken.php b/src/Examples/getToken.php new file mode 100644 index 0000000..c1fcc49 --- /dev/null +++ b/src/Examples/getToken.php @@ -0,0 +1,54 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$ypmnPaymentReference = new PaymentReference(2469883); +$responseData = $apiRequest->sendTokenCreationRequest($ypmnPaymentReference); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты 5 +// echo Std::drawYpmnButton([ +// 'url' => $responseData["paymentResult"]['url'] +// ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
      + Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      +
      +
      ' . $exception->getMessage() . '
      ', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/simpleGetPaymentLink.php b/src/Examples/simpleGetPaymentLink.php new file mode 100644 index 0000000..5948e0d --- /dev/null +++ b/src/Examples/simpleGetPaymentLink.php @@ -0,0 +1,97 @@ + 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 200.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); +// Установим валюту +$payment->setCurrency('RUB'); +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization(new Authorization('CCVISAMC',true)); +// Установим номер заказа (должен быть уникальным в вашей системе) +$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 в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + if ($responseData) { + // Выведем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // .. или сделаем редирект на форму оплаты (опционально) + // Std::redirect($responseData["paymentResult"]['url']); + } +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
      + Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
      +
      +
      ' . $exception->getMessage() . '
      ', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/start.php b/src/Examples/start.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/src/Examples/start.php @@ -0,0 +1 @@ +setNumber($number) ->setType($type); diff --git a/src/Interfaces/IdentityDocumentInterface.php b/src/IdentityDocumentInterface.php similarity index 93% rename from src/Interfaces/IdentityDocumentInterface.php rename to src/IdentityDocumentInterface.php index f6ea7db..76efb8b 100644 --- a/src/Interfaces/IdentityDocumentInterface.php +++ b/src/IdentityDocumentInterface.php @@ -1,6 +1,6 @@ ' . $params['text'] . ' - +
      '; } diff --git a/src/StoredCredentials.php b/src/StoredCredentials.php index ae138c2..c52eb65 100644 --- a/src/StoredCredentials.php +++ b/src/StoredCredentials.php @@ -2,8 +2,6 @@ namespace Ypmn; -use Ypmn\Interfaces\StoredCredentialsInterface; - class StoredCredentials implements StoredCredentialsInterface { /** @var string */ diff --git a/src/Interfaces/StoredCredentialsInterface.php b/src/StoredCredentialsInterface.php similarity index 96% rename from src/Interfaces/StoredCredentialsInterface.php rename to src/StoredCredentialsInterface.php index 38de4ef..ba8e193 100644 --- a/src/Interfaces/StoredCredentialsInterface.php +++ b/src/StoredCredentialsInterface.php @@ -1,6 +1,6 @@ Date: Tue, 6 Jun 2023 19:28:45 +0300 Subject: [PATCH 021/151] style fix, more payu to ypmn changes, new menu --- example_header.php | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/example_header.php b/example_header.php index c366ed4..724e625 100644 --- a/example_header.php +++ b/example_header.php @@ -33,24 +33,25 @@
      - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + +
      @@ -77,7 +78,7 @@
      $examples[ $_GET['function'] ]['about'], ]); From 2bc03da2ab01d36f057db4a63161f78ecb80f325 Mon Sep 17 00:00:00 2001 From: Alexey Babak Date: Tue, 6 Jun 2023 19:43:45 +0300 Subject: [PATCH 022/151] added test cards, footer --- example_footer.html | 8 ++++++-- example_header.php | 7 ++++++- example_list.php | 4 ++-- src/ApiRequest.php | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/example_footer.html b/example_footer.html index ddfe1d7..be78c5b 100644 --- a/example_footer.html +++ b/example_footer.html @@ -1,8 +1,12 @@
      -