From 002594e052538cb0b1c525dc7b031ae1b092d138 Mon Sep 17 00:00:00 2001 From: Mateus Bissonho Date: Thu, 30 Mar 2023 18:14:33 -0300 Subject: [PATCH 1/4] Add Boleto payment method --- .gitignore | 1 + Block/Adminhtml/Form.php | 8 + Block/Info.php | 8 + Controller/Adminhtml/Boleto/DownloadPdf.php | 33 +++ Controller/Boleto/DownloadPdf.php | 23 ++ Controller/DownloadPdfAction.php | 68 +++++ Cron/UpdatePaymentStatus.php | 145 ++++++++++ .../Boleto/Http/Client/CancelBoletoClient.php | 66 +++++ .../Boleto/Http/Client/IssueBoletoClient.php | 93 +++++++ Gateway/Boleto/Http/TransferFactory.php | 25 ++ .../Request/Builder/IssueBoletoBuilder.php | 40 +++ .../Boleto/Response/IssueResponseHandler.php | 65 +++++ .../Validator/BeforeActionValidator.php | 56 ++++ .../Validator/IssueResponseValidator.php | 45 +++ Gateway/Request/Builder/AbstractBuilder.php | 19 ++ Gateway/Request/Builder/GeneralBuilder.php | 26 ++ Gateway/Request/Builder/PayerBuilder.php | 72 +++++ Gateway/Validator/CpfCnpjValidator.php | 35 +++ Gateway/Validator/ValidatorPool.php | 59 ++++ Logger/Boleto/Logger.php | 20 ++ Logger/Logger.php | 13 + Logger/LoggerInterface.php | 6 + Model/Api/Client.php | 177 ++++++++++++ .../Api/CurrentEnvironmentBaseUrlResolver.php | 33 +++ Model/Boleto/PdfDownloader.php | 87 ++++++ Model/Config/Backend/AbstractFile.php | 27 ++ Model/Config/Backend/CertificateFile.php | 11 + Model/Config/Backend/SSLKeyFile.php | 11 + Model/Config/Source/Environment.php | 17 ++ Model/Fields/AbstractOnlyNumbersField.php | 12 + Model/Fields/CEP.php | 6 + Model/Fields/CNPJ.php | 39 +++ Model/Fields/CPF.php | 30 ++ Model/Ui/Boleto/ConfigProvider.php | 43 +++ Model/Ui/ConfigProvider.php | 132 +++++++++ .../Adminhtml/DefineDebugConfigObserver.php | 64 +++++ ...hookUrlWhenPaymentConfigChangeObserver.php | 57 ++++ .../Gateway/IssueBoletoDataAssignObserver.php | 35 +++ Observer/PaymentMethodIsActiveObserver.php | 49 ++++ composer.json | 10 +- etc/adminhtml/events.xml | 14 + etc/adminhtml/routes.xml | 9 + etc/adminhtml/system.xml | 145 ++++++++++ etc/config.xml | 31 +++ etc/crontab.xml | 9 + etc/db_schema.xml | 7 + etc/di.xml | 262 ++++++++++++++++++ etc/events.xml | 15 + etc/frontend/di.xml | 9 + etc/frontend/routes.xml | 9 + etc/mbissonho_bancointer_error_mapping.xml | 8 + etc/module.xml | 2 + etc/payment.xml | 14 + i18n/pt_BR.csv | 25 ++ view/adminhtml/templates/form/boleto.phtml | 19 ++ view/adminhtml/templates/info/boleto.phtml | 31 +++ view/base/templates/info/boleto.phtml | 24 ++ view/frontend/layout/checkout_index_index.xml | 42 +++ .../payment/method-renderer/boleto-method.js | 101 +++++++ view/frontend/web/js/view/payment/payments.js | 16 ++ .../frontend/web/template/payment/boleto.html | 55 ++++ 61 files changed, 2612 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 Block/Adminhtml/Form.php create mode 100644 Block/Info.php create mode 100644 Controller/Adminhtml/Boleto/DownloadPdf.php create mode 100644 Controller/Boleto/DownloadPdf.php create mode 100644 Controller/DownloadPdfAction.php create mode 100644 Cron/UpdatePaymentStatus.php create mode 100644 Gateway/Boleto/Http/Client/CancelBoletoClient.php create mode 100644 Gateway/Boleto/Http/Client/IssueBoletoClient.php create mode 100644 Gateway/Boleto/Http/TransferFactory.php create mode 100644 Gateway/Boleto/Request/Builder/IssueBoletoBuilder.php create mode 100644 Gateway/Boleto/Response/IssueResponseHandler.php create mode 100644 Gateway/Boleto/Validator/BeforeActionValidator.php create mode 100644 Gateway/Boleto/Validator/IssueResponseValidator.php create mode 100644 Gateway/Request/Builder/AbstractBuilder.php create mode 100644 Gateway/Request/Builder/GeneralBuilder.php create mode 100644 Gateway/Request/Builder/PayerBuilder.php create mode 100644 Gateway/Validator/CpfCnpjValidator.php create mode 100644 Gateway/Validator/ValidatorPool.php create mode 100644 Logger/Boleto/Logger.php create mode 100644 Logger/Logger.php create mode 100644 Logger/LoggerInterface.php create mode 100644 Model/Api/Client.php create mode 100644 Model/Api/CurrentEnvironmentBaseUrlResolver.php create mode 100644 Model/Boleto/PdfDownloader.php create mode 100644 Model/Config/Backend/AbstractFile.php create mode 100644 Model/Config/Backend/CertificateFile.php create mode 100644 Model/Config/Backend/SSLKeyFile.php create mode 100644 Model/Config/Source/Environment.php create mode 100644 Model/Fields/AbstractOnlyNumbersField.php create mode 100644 Model/Fields/CEP.php create mode 100644 Model/Fields/CNPJ.php create mode 100644 Model/Fields/CPF.php create mode 100644 Model/Ui/Boleto/ConfigProvider.php create mode 100644 Model/Ui/ConfigProvider.php create mode 100644 Observer/Adminhtml/DefineDebugConfigObserver.php create mode 100644 Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php create mode 100644 Observer/Gateway/IssueBoletoDataAssignObserver.php create mode 100644 Observer/PaymentMethodIsActiveObserver.php create mode 100644 etc/adminhtml/events.xml create mode 100644 etc/adminhtml/routes.xml create mode 100644 etc/adminhtml/system.xml create mode 100644 etc/config.xml create mode 100644 etc/crontab.xml create mode 100644 etc/db_schema.xml create mode 100644 etc/di.xml create mode 100644 etc/events.xml create mode 100644 etc/frontend/di.xml create mode 100644 etc/frontend/routes.xml create mode 100644 etc/mbissonho_bancointer_error_mapping.xml create mode 100644 etc/payment.xml create mode 100644 i18n/pt_BR.csv create mode 100644 view/adminhtml/templates/form/boleto.phtml create mode 100644 view/adminhtml/templates/info/boleto.phtml create mode 100644 view/base/templates/info/boleto.phtml create mode 100644 view/frontend/layout/checkout_index_index.xml create mode 100644 view/frontend/web/js/view/payment/method-renderer/boleto-method.js create mode 100644 view/frontend/web/js/view/payment/payments.js create mode 100644 view/frontend/web/template/payment/boleto.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2211df6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/Block/Adminhtml/Form.php b/Block/Adminhtml/Form.php new file mode 100644 index 0000000..c851dca --- /dev/null +++ b/Block/Adminhtml/Form.php @@ -0,0 +1,8 @@ +downloadPdfAction = $downloadPdfAction; + parent::__construct($context); + } + + public function execute() + { + return $this->downloadPdfAction->execute(); + } + + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Sales::actions_view'); + } + +} diff --git a/Controller/Boleto/DownloadPdf.php b/Controller/Boleto/DownloadPdf.php new file mode 100644 index 0000000..b05b588 --- /dev/null +++ b/Controller/Boleto/DownloadPdf.php @@ -0,0 +1,23 @@ +downloadPdfAction = $downloadPdfAction; + } + + public function execute() + { + return $this->downloadPdfAction->execute(); + } +} diff --git a/Controller/DownloadPdfAction.php b/Controller/DownloadPdfAction.php new file mode 100644 index 0000000..cb46574 --- /dev/null +++ b/Controller/DownloadPdfAction.php @@ -0,0 +1,68 @@ +logger = $logger; + $this->request = $request; + $this->redirectFactory = $redirectFactory; + $this->orderPaymentFactory = $orderPaymentFactory; + $this->orderPaymentResource = $orderPaymentResource; + $this->pdfDownloader = $pdfDownloader; + } + + public function execute() + { + try { + + $orderPaymentId = $this->request->getParam('order_payment_id'); + + if (!$orderPaymentId) { + return $this->redirectFactory->create()->setRefererUrl(); + } + + $orderPayment = $this->orderPaymentFactory->create(); + + $this->orderPaymentResource->load($orderPayment, $orderPaymentId); + + if (!$orderPayment->getEntityId()) { + return $this->redirectFactory->create()->setRefererUrl(); + } + + return $this->pdfDownloader->execute($orderPayment); + } catch (\Throwable $e) { + $this->logger->critical($e, ['order_payment_id' => $orderPaymentId]); + return $this->redirectFactory->create()->setRefererUrl(); + } + } +} diff --git a/Cron/UpdatePaymentStatus.php b/Cron/UpdatePaymentStatus.php new file mode 100644 index 0000000..62ec03d --- /dev/null +++ b/Cron/UpdatePaymentStatus.php @@ -0,0 +1,145 @@ +transactionFactory = $transactionFactory; + $this->timezone = $timezone; + $this->storeManager = $storeManager; + $this->orderCollectionFactory = $orderCollectionFactory; + $this->generalConfigProvider = $generalConfigProvider; + $this->client = $client; + $this->logger = $logger; + } + + public function execute() + { + try { + + foreach ($this->storeManager->getStores() as $storeId => $store) { + + if(!$this->generalConfigProvider->isModuleEnabled($storeId)) { + continue; + } + + $collection = $this->orderCollectionFactory->create(); + $collection + ->addFieldToSelect('*') + ->addFieldToFilter('state', ['eq' => Order::STATE_PENDING_PAYMENT]) + ->addFieldToFilter('store_id', ['eq' => $storeId ]) + ->join( + ['sop' => $collection->getTable('sales_order_payment')], + 'main_table.entity_id = sop.parent_id and sop.method IN ("mbissonho_bancointer_boleto")', + ['add_info' => 'additional_information'] + ) + ->addOrder('created_at', Collection::SORT_ORDER_ASC); + + /* @var \Magento\Sales\Model\Order $olderPendingOrder */ + /* @var \Magento\Sales\Model\Order $newestPendingOrder */ + /* @var \Magento\Sales\Model\Order $order */ + + $olderPendingOrder = $collection->getFirstItem(); + $newestPendingOrder = $collection->getLastItem(); + + $ordersRelatedPayments = $this->client->getPaymentCollection( + $this->timezone->date(new \DateTime($olderPendingOrder->getCreatedAt()))->format('Y-m-d'), + $this->timezone->date(new \DateTime($newestPendingOrder->getCreatedAt()))->format('Y-m-d'), + $storeId + ); + + foreach ($collection as $order) { + + $additionalInformation = $order->getPayment()->getAdditionalInformation(); + + try { + if(isset($ordersRelatedPayments[$additionalInformation['our_number']])) { + + $relatedPayment = $ordersRelatedPayments[$additionalInformation['our_number']]; + + if($relatedPayment->situacao !== 'PAGO') { + continue; + } + + if(!$order->hasInvoices()) { + + $message = sprintf("Pagamento %s aprovado", $relatedPayment->nossoNumero); + + $status = $order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING); + + $amountPaid = $order->getGrandTotal(); + $baseAmountPaid = $order->getBaseGrandTotal(); + + $invoice = $order->prepareInvoice(); + + $invoice->setRequestedCaptureCase(Order\Invoice::CAPTURE_OFFLINE); + + $invoice->setBaseAmountPaid($baseAmountPaid); + $invoice->setAmountPaid($amountPaid); + $invoice->setBaseGrandTotal($baseAmountPaid); + $invoice->setGrandTotal($amountPaid); + $invoice->setOrder($order); + + $invoice->register(); + + $order->setState(Order::STATE_PROCESSING) + ->setStatus($status) + ->addCommentToStatusHistory($message, true, true); + + $dbTransaction = $this->transactionFactory->create(); + + $dbTransaction + ->addObject($invoice) + ->addObject($invoice->getOrder()) + ->save(); + + $this->logger->info("{$order->getIncrementId()} - $relatedPayment->situacao"); + } + + } + } catch (\Throwable $e) { + $this->logger->critical($e->getMessage()); + } + + } + + } + + } catch (\Throwable $e) { + $this->logger->critical($e->getMessage()); + throw new LocalizedException(__("An error occurred while running cron. Check 'mbissonho_bancointer_cron.log' file")); + } + } + +} diff --git a/Gateway/Boleto/Http/Client/CancelBoletoClient.php b/Gateway/Boleto/Http/Client/CancelBoletoClient.php new file mode 100644 index 0000000..b7218c0 --- /dev/null +++ b/Gateway/Boleto/Http/Client/CancelBoletoClient.php @@ -0,0 +1,66 @@ +client = $client; + $this->orderRepository = $orderRepository; + $this->boletoConfigProvider = $boletoConfigProvider; + $this->logger = $logger; + } + + public function placeRequest(TransferInterface $transferObject) + { + try { + $requestData = $transferObject->getBody(); + + if(!$this->boletoConfigProvider->shouldCancelPaymentWhenCancelOrder($requestData['store_id'])) { + return [ + 'status_code' => 200 + ]; + } + + $order = $this->orderRepository->get($requestData['order_id']); + + $response = $this->client->cancelPayment( + $order->getPayment()->getAdditionalInformation('our_number'), + $order->getStoreId() + ); + + return [ + 'status_code' => $response->getStatusCode() + ]; + + } catch (ClientException |\Throwable $e) { + $this->logger->critical($e); + + throw new LocalizedException(__('Something went wrong when trying to cancel the payment associated with the order')); + } + } +} diff --git a/Gateway/Boleto/Http/Client/IssueBoletoClient.php b/Gateway/Boleto/Http/Client/IssueBoletoClient.php new file mode 100644 index 0000000..cc6e8f5 --- /dev/null +++ b/Gateway/Boleto/Http/Client/IssueBoletoClient.php @@ -0,0 +1,93 @@ +logger = $logger; + $this->apiClient = $apiClient; + } + + public function placeRequest(TransferInterface $transferObject): array + { + try { + $requestData = $transferObject->getBody(); + + $requestBody = [ + 'seuNumero' => $requestData['transaction']['your_number'], + 'valorNominal' => $requestData['transaction']['amount'], + 'dataVencimento' => $requestData['transaction']['expiration_date'], + 'numDiasAgenda' => $requestData['transaction']['days_to_cancel_after_expiration'], + 'pagador' => [ + 'tipoPessoa' => $requestData['payer']['person_type'], + 'cpfCnpj' => $requestData['payer']['taxvat'], + 'nome' => $requestData['payer']['name'], + 'endereco' => $requestData['payer']['address_street'], + 'cidade' => $requestData['payer']['address_city'], + 'uf' => $requestData['payer']['address_region_code'], + 'cep' => $requestData['payer']['address_postcode'] + ] + ]; + + $this->logger->debug( + [ + 'ISSUE_BOLETO_REQUEST' => [ + 'BODY' => $requestBody + ] + ] + ); + + $response = $this->apiClient->issueBoleto($requestBody); + $responseBody = $response->getBody()->getContents(); + + $this->logger->debug( + [ + 'ISSUE_BOLETO_RESPONSE' => [ + 'STATUS_CODE' => $response->getStatusCode(), + 'BODY' => $responseBody + ] + ] + ); + + return [ + 'status_code' => $response->getStatusCode(), + 'body' => $responseBody + ]; + + } catch (ClientException $e) { + $this->logger->critical( + [$e->getMessage()] + ); + + return [ + 'status_code' => $e->getResponse()->getStatusCode(), + 'body' => $e->getResponse()->getBody()->getContents() + ]; + + } catch (\Throwable $e) { + $this->logger->critical( + [$e->getMessage()] + ); + //TODO: Add client related exception message + throw new LocalizedException(__('Something went wrong. Check log file.')); + } + } + + +} diff --git a/Gateway/Boleto/Http/TransferFactory.php b/Gateway/Boleto/Http/TransferFactory.php new file mode 100644 index 0000000..a8a6b7c --- /dev/null +++ b/Gateway/Boleto/Http/TransferFactory.php @@ -0,0 +1,25 @@ +transferBuilder = $transferBuilder; + } + + public function create(array $request): TransferInterface + { + return $this->transferBuilder + ->setBody($request) + ->build(); + } +} diff --git a/Gateway/Boleto/Request/Builder/IssueBoletoBuilder.php b/Gateway/Boleto/Request/Builder/IssueBoletoBuilder.php new file mode 100644 index 0000000..494f013 --- /dev/null +++ b/Gateway/Boleto/Request/Builder/IssueBoletoBuilder.php @@ -0,0 +1,40 @@ +getOrder(); + $storeId = $order->getStoreId(); + + $methodInstance = $paymentDataObject->getPayment()->getMethodInstance(); + + $numOfDaysToExpire = $methodInstance->getConfigData('expiration_days', $storeId); + $expirationDate = (new \DateTime())->add(new \DateInterval("P{$numOfDaysToExpire}D"))->format('Y-m-d'); + + return [ + 'transaction' => [ + 'your_number' => $methodInstance->getConfigData('your_number', $storeId), + 'days_to_cancel_after_expiration' => intval($methodInstance->getConfigData('days_to_cancel_after_expiration', $storeId)), + 'expiration_date' => $expirationDate, + 'amount' => $order->getGrandTotalAmount() + ] + ]; + + } catch (\Throwable $e) { + $this->logger->critical($e); + throw new LocalizedException(__('Error processing boleto payment information')); + } + } + +} diff --git a/Gateway/Boleto/Response/IssueResponseHandler.php b/Gateway/Boleto/Response/IssueResponseHandler.php new file mode 100644 index 0000000..7a9bbca --- /dev/null +++ b/Gateway/Boleto/Response/IssueResponseHandler.php @@ -0,0 +1,65 @@ +getPayment(); + + if (!$payment instanceof Payment) { + throw new \LogicException('Order Payment should be provided'); + } + + $responseBody = json_decode($response['body']); + + $payment->getOrder()->setCanSendNewEmailFlag(true); + $baseTotalDue = $payment->getOrder()->getBaseTotalDue(); + $totalDue = $payment->getOrder()->getTotalDue(); + + $payment->setTransactionId($responseBody->nossoNumero); + $payment->setIsTransactionClosed(true); + + $stateObject->setData('state', Order::STATE_PENDING_PAYMENT); + $payment->getOrder()->setState(Order::STATE_PENDING_PAYMENT); + $payment->getOrder()->setStatus($payment->getOrder()->getConfig()->getStateDefaultStatus(Order::STATE_PENDING_PAYMENT)); + + $message = __('Authorized amount of %1.', $totalDue); + $payment->setShouldCloseParentTransaction(true); + + $isSameCurrency = $payment->isSameCurrency(); + if (!$isSameCurrency || !$payment->isCaptureFinal($totalDue)) { + $payment->setIsFraudDetected(true); + } + + $amount = $payment->formatAmount($totalDue, true); + $payment->setBaseAmountAuthorized($amount); + + $transaction = $payment->addTransaction(Transaction::TYPE_AUTH); + $message = $payment->prependMessage($message); + $payment->addTransactionCommentsToOrder($transaction, $message); + + $payment->setAmountAuthorized($totalDue); + $payment->setBaseAmountAuthorized($baseTotalDue); + + $payment->setAdditionalInformation( + [ + 'own_number' => $responseBody->seuNumero, + 'our_number' => $responseBody->nossoNumero, + 'bar_code' => $responseBody->codigoBarras, + 'digitable_line' => $responseBody->linhaDigitavel + ] + ); + } +} diff --git a/Gateway/Boleto/Validator/BeforeActionValidator.php b/Gateway/Boleto/Validator/BeforeActionValidator.php new file mode 100644 index 0000000..1815fc7 --- /dev/null +++ b/Gateway/Boleto/Validator/BeforeActionValidator.php @@ -0,0 +1,56 @@ +fieldsValidatorPool = $fieldsValidatorPool; + parent::__construct($resultFactory); + } + + + public function validate(array $validationSubject): ResultInterface + { + $valid = true; + $messages = []; + + $payment = $validationSubject['payment'] ?? null; + + if (!$payment) { + return $this->createResult(false, [__('Can\'t get the payment information')]); + } + + try { + $validators = $this->fieldsValidatorPool->all(); + } catch (\Throwable $e) { + return $this->createResult(false); + } + + foreach ($validators as $validator) { + /* @var ValidatorInterface $validator */ + + $result = $validator->validate($validationSubject); + + if(!$result->isValid()) { + $valid = false; + $messages = array_merge($result->getFailsDescription(), $messages); + } + } + + return $this->createResult($valid, $messages); + } +} diff --git a/Gateway/Boleto/Validator/IssueResponseValidator.php b/Gateway/Boleto/Validator/IssueResponseValidator.php new file mode 100644 index 0000000..fe6a9d4 --- /dev/null +++ b/Gateway/Boleto/Validator/IssueResponseValidator.php @@ -0,0 +1,45 @@ +createResult(false, [__('API Inter: Unexpected error')]); + } + + $statusCode = $validationSubject['response']['status_code']; + + if($statusCode === 400) { + $isValid = false; + $errorsCode[] = 400; + } + + if($statusCode === 403 || $statusCode === 401) { + $isValid = false; + $errorsCode[] = 403; + } + + if($statusCode === 404) { + $isValid = false; + $errorsCode[] = 404; + } + + if($statusCode === 503) { + $isValid = false; + $errorsCode[] = 503; + } + + return $this->createResult($isValid, [], $errorsCode); + } +} diff --git a/Gateway/Request/Builder/AbstractBuilder.php b/Gateway/Request/Builder/AbstractBuilder.php new file mode 100644 index 0000000..2d441eb --- /dev/null +++ b/Gateway/Request/Builder/AbstractBuilder.php @@ -0,0 +1,19 @@ +logger = $logger; + } + +} diff --git a/Gateway/Request/Builder/GeneralBuilder.php b/Gateway/Request/Builder/GeneralBuilder.php new file mode 100644 index 0000000..94d85a4 --- /dev/null +++ b/Gateway/Request/Builder/GeneralBuilder.php @@ -0,0 +1,26 @@ + $paymentDataObject->getOrder()->getStoreId(), + 'order_increment_id' => $paymentDataObject->getOrder()->getOrderIncrementId(), + 'order_id' => $paymentDataObject->getOrder()->getId() + ]; + } catch (\Throwable $e) { + $this->logger->critical($e); + throw new LocalizedException(__('Error processing payment information')); + } + } +} diff --git a/Gateway/Request/Builder/PayerBuilder.php b/Gateway/Request/Builder/PayerBuilder.php new file mode 100644 index 0000000..ce31949 --- /dev/null +++ b/Gateway/Request/Builder/PayerBuilder.php @@ -0,0 +1,72 @@ +customerRepository = $customerRepository; + parent::__construct($logger); + } + + public function build(array $buildSubject) + { + try { + $paymentDataObject = SubjectReader::readPayment($buildSubject); + + $order = $paymentDataObject->getOrder(); + + $billingAddress = $order->getBillingAddress(); + + $customer = false; + + if ($order->getCustomerId()) { + $customer = $this->customerRepository->getById($order->getCustomerId()); + } + + $taxvat = $customer && !is_null($customer->getTaxvat()) ? $customer->getTaxvat() : + $paymentDataObject->getPayment()->getAdditionalInformation('customer_taxvat'); + + $personType = $this->resolvePersonTypeByTaxvat($taxvat); + + if (!$personType) { + throw new LocalizedException(__('Cannot determinate customer person type')); + } + + return [ + 'payer' => [ + 'person_type' => $personType, + 'taxvat' => AbstractOnlyNumbersField::parseToOnlyNumbers($taxvat), + 'name' => $billingAddress->getFirstname() . ' ' . $billingAddress->getLastname(), + 'address_street' => $billingAddress->getStreetLine1(), + 'address_city' => $billingAddress->getCity(), + 'address_region_code' => $billingAddress->getRegionCode(), + 'address_postcode' => CEP::parseToOnlyNumbers($billingAddress->getPostcode()) + ] + ]; + } catch (\Throwable $e) { + $this->logger->critical($e); + throw new LocalizedException(__('Error processing payer information. Please verify address information and taxvat.')); + } + } + + private function resolvePersonTypeByTaxvat($taxvat) + { + return CPF::isValid($taxvat) ? 'FISICA' : + (CNPJ::isValid($taxvat) ? 'JURIDICA' : false) ; + } +} diff --git a/Gateway/Validator/CpfCnpjValidator.php b/Gateway/Validator/CpfCnpjValidator.php new file mode 100644 index 0000000..1b12f18 --- /dev/null +++ b/Gateway/Validator/CpfCnpjValidator.php @@ -0,0 +1,35 @@ +createResult(false, [__('Can\'t get the payment information')]); + } + + $additionalInformation = $payment->getAdditionalInformation(); + + if(!isset($additionalInformation['customer_taxvat'])) { + return $this->createResult(false, [__('Taxvat is invalid')]); + } + + $value = $additionalInformation['customer_taxvat']; + + if(CPF::isValid($value) || CNPJ::isValid($value)) { + return $this->createResult(true); + } + + return $this->createResult(false, [__('Taxvat is invalid')]); + } +} diff --git a/Gateway/Validator/ValidatorPool.php b/Gateway/Validator/ValidatorPool.php new file mode 100644 index 0000000..b0d067a --- /dev/null +++ b/Gateway/Validator/ValidatorPool.php @@ -0,0 +1,59 @@ +validators = $tmapFactory->create( + [ + 'array' => $validators, + 'type' => ValidatorInterface::class + ] + ); + } + + /** + * Returns configured validator + * + * @param string $code + * @return ValidatorInterface + * @throws NotFoundException + */ + public function get($code) + { + if (!isset($this->validators[$code])) { + throw new NotFoundException(__('The validator for the "%1" field doesn\'t exist.', $code)); + } + + return $this->validators[$code]; + } + + /** + * Returns all validators + * + * @throws \Exception + */ + public function all(): \ArrayIterator + { + return $this->validators->getIterator(); + } + +} diff --git a/Logger/Boleto/Logger.php b/Logger/Boleto/Logger.php new file mode 100644 index 0000000..ed062dc --- /dev/null +++ b/Logger/Boleto/Logger.php @@ -0,0 +1,20 @@ +logger->critical(var_export($data, true)); + } +} diff --git a/Logger/Logger.php b/Logger/Logger.php new file mode 100644 index 0000000..86c183b --- /dev/null +++ b/Logger/Logger.php @@ -0,0 +1,13 @@ +filesystem = $filesystem; + $this->generalConfigProvider = $configProvider; + $this->currentEnvironmentBaseUrlResolver = $currentEnvironmentBaseUrlResolver; + $this->client = new HttpClient( + ['base_uri' => $this->currentEnvironmentBaseUrlResolver->getApiBaseUrlByCurrentEnvironmentByScopeConfig()] + ); + + $this->_varDirectory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR); + } + + public function generateAuthToken($storeId = null): void + { + if($this->authToken != null) { + return; + } + + $this->recreateHttpClientIfNeed($storeId); + + $response = $this->client->post('/oauth/v2/token', [ + 'form_params' => [ + 'client_id' => $this->generalConfigProvider->getClientId($storeId), + 'client_secret' => $this->generalConfigProvider->getClientSecret($storeId), + 'grant_type' => 'client_credentials', + 'scope' => 'boleto-cobranca.read boleto-cobranca.write' + ], + 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getCertificateFilePath($storeId), + 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getSslKeyFilePath($storeId) + ]); + + if($response->getStatusCode() == 200) { + $this->authToken = json_decode($response->getBody()->getContents())->access_token; + } + } + + public function issueBoleto(array $requestBody, $storeId = null): ResponseInterface + { + $this->generateAuthToken($storeId); + + return $this->client->post( + '/cobranca/v2/boletos', + [ + 'json' => $requestBody, + 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getCertificateFilePath($storeId), + 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getSslKeyFilePath($storeId), + 'headers' => [ + 'Authorization' => "Bearer {$this->authToken}" + ] + ] + ); + } + + public function defineWebhook(string $webhookUrl, $storeId = null): void + { + $this->generateAuthToken($storeId); + $this->recreateHttpClientIfNeed($storeId); + + $this->client->put('/cobranca/v2/boletos/webhook', [ + 'json' => ['webhookUrl' => $webhookUrl], + 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getCertificateFilePath($storeId), + 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getSslKeyFilePath($storeId), + 'headers' => [ + 'Authorization' => "Bearer {$this->authToken}" + ] + ]); + } + + public function cancelPayment($ourNumber, $storeId = null): ResponseInterface + { + $this->generateAuthToken($storeId); + $this->recreateHttpClientIfNeed($storeId); + + return $this->client->post(sprintf("/cobranca/v2/boletos/%s/cancelar", $ourNumber), [ + 'json' => ['motivoCancelamento' => 'ACERTOS'], + 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getCertificateFilePath($storeId), + 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getSslKeyFilePath($storeId), + 'headers' => [ + 'Authorization' => "Bearer {$this->authToken}" + ] + ]); + } + + public function getBoletoPdf($ourNumber, $storeId = null): ResponseInterface + { + $this->generateAuthToken($storeId); + $this->recreateHttpClientIfNeed($storeId); + + return $this->client->get(sprintf("/cobranca/v2/boletos/%s/pdf", $ourNumber), [ + 'headers' => [ + 'Authorization' => "Bearer {$this->authToken}" + ], + 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getCertificateFilePath($storeId), + 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getSslKeyFilePath($storeId) + ]); + } + + public function getPaymentCollection($initialDate, $finalDate, $storeId = null): array + { + $this->generateAuthToken($storeId); + $this->recreateHttpClientIfNeed($storeId); + + $response = $this->client->get( + sprintf("/cobranca/v2/boletos?filtrarDataPor=EMISSAO&dataInicial=%s&dataFinal=%s", $initialDate, $finalDate), + //sprintf("/cobranca/v2/boletos?filtrarDataPor=EMISSAO&situacao=PAGO&dataInicial=%s&dataFinal=%s", $initialDate, $finalDate), + [ + 'headers' => [ + 'Authorization' => "Bearer {$this->authToken}" + ], + 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getCertificateFilePath($storeId), + 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getSslKeyFilePath($storeId) + ] + ); + + $payments = []; + + $responseAsJson = json_decode($response->getBody()->getContents()); + + foreach ($responseAsJson->content as $payment) { + $payments[$payment->nossoNumero] = $payment; + } + + return $payments; + } + + private function recreateHttpClientIfNeed($storeId = null): void + { + if(!is_null($storeId)) { + $this->client = new HttpClient( + ['base_uri' => $this->currentEnvironmentBaseUrlResolver->getApiBaseUrlByCurrentEnvironmentByScopeConfig($storeId)] + ); + } + } + +} diff --git a/Model/Api/CurrentEnvironmentBaseUrlResolver.php b/Model/Api/CurrentEnvironmentBaseUrlResolver.php new file mode 100644 index 0000000..3450fee --- /dev/null +++ b/Model/Api/CurrentEnvironmentBaseUrlResolver.php @@ -0,0 +1,33 @@ +scopeConfig = $scopeConfig; + } + + public function getApiBaseUrlByCurrentEnvironment(MethodInterface $method, $storeId) + { + return self::PRODUCTION_URL; + } + + public function getApiBaseUrlByCurrentEnvironmentByScopeConfig($storeId = null) + { + return self::PRODUCTION_URL; + } +} diff --git a/Model/Boleto/PdfDownloader.php b/Model/Boleto/PdfDownloader.php new file mode 100644 index 0000000..a81485c --- /dev/null +++ b/Model/Boleto/PdfDownloader.php @@ -0,0 +1,87 @@ +httpClient = $httpClient; + $this->fileFactory = $fileFactory; + $this->filesystem = $filesystem; + $this->orderPaymentResource = $orderPaymentResource; + $this->client = $client; + + $this->_varDirectory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR); + } + + /** + * @throws GuzzleException + * @throws LocalizedException + */ + public function execute(OrderPaymentInterface $orderPayment) + { + $ourNumber = $orderPayment->getAdditionalInformation('our_number'); + + if($base64Pdf = $orderPayment->getAdditionalInformation('pdf_as_base64')) { + return $this->fileFactory->create( + $ourNumber . '.pdf', + base64_decode($base64Pdf,true), + DirectoryList::VAR_DIR, + 'application/pdf' + ); + } + + $storeId = $orderPayment->getOrder()->getStoreId(); + + $response = $this->client->getBoletoPdf($ourNumber, $storeId); + + $responseAsObject = json_decode($response->getBody()->getContents()); + + $base64Pdf = $responseAsObject->pdf; + + $orderPayment->setAdditionalInformation('pdf_as_base64', $base64Pdf); + + $this->orderPaymentResource->save($orderPayment); + + return $this->fileFactory->create( + $ourNumber . '.pdf', + base64_decode($base64Pdf,true), + DirectoryList::VAR_DIR, + 'application/pdf' + ); + } + + +} diff --git a/Model/Config/Backend/AbstractFile.php b/Model/Config/Backend/AbstractFile.php new file mode 100644 index 0000000..970247c --- /dev/null +++ b/Model/Config/Backend/AbstractFile.php @@ -0,0 +1,27 @@ +getAllowedExtensions(); + } + + protected function getUploadDirPath($uploadDir) + { + $this->_varDirectory = $this->_filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + return $this->_varDirectory->getAbsolutePath($uploadDir); + } + + abstract protected function getAllowedExtensions(): array; +} diff --git a/Model/Config/Backend/CertificateFile.php b/Model/Config/Backend/CertificateFile.php new file mode 100644 index 0000000..8add715 --- /dev/null +++ b/Model/Config/Backend/CertificateFile.php @@ -0,0 +1,11 @@ + self::PRODUCTION, 'label' => __("Production")] + ]; + } +} diff --git a/Model/Fields/AbstractOnlyNumbersField.php b/Model/Fields/AbstractOnlyNumbersField.php new file mode 100644 index 0000000..fed081e --- /dev/null +++ b/Model/Fields/AbstractOnlyNumbersField.php @@ -0,0 +1,12 @@ +scopeConfig = $scopeConfig; + } + + public function getConfig() + { + return [ + 'payment' => [ + self::CODE => [] + ] + ]; + } + + public function shouldCancelPaymentWhenCancelOrder(int $storeId = null) + { + return $this->scopeConfig->isSetFlag( + self::SHOULD_CANCEL_PAYMENT_WHEN_CANCEL_ORDER, + 'store', + $storeId + ); + } + +} diff --git a/Model/Ui/ConfigProvider.php b/Model/Ui/ConfigProvider.php new file mode 100644 index 0000000..9b9491d --- /dev/null +++ b/Model/Ui/ConfigProvider.php @@ -0,0 +1,132 @@ +filesystem = $filesystem; + $this->scopeConfig = $scopeConfig; + $this->encryptor = $encryptor; + $this->_varDirectory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR); + } + + public function getConfig() + { + return [ + 'payment' => [ + self::CODE => [] + ] + ]; + } + + public function isModuleEnabled(int $storeId = null) + { + return (bool)$this->scopeConfig->getValue( + self::MODULE_ENABLED, + 'store', + $storeId + ); + } + + public function getWebhookUrl(int $storeId = null) + { + return $this->scopeConfig->getValue( + self::WEBHOOK_URL, + 'store', + $storeId + ); + } + + public function getClientId(int $storeId = null) + { + return $this->encryptor->decrypt($this->scopeConfig->getValue( + self::CLIENT_ID, + 'store', + $storeId + )); + } + + public function getClientSecret(int $storeId = null) + { + return $this->encryptor->decrypt($this->scopeConfig->getValue( + self::CLIENT_SECRET, + 'store', + $storeId + )); + } + + public function getCertificateFilePath(int $storeId = null) + { + return $this->scopeConfig->getValue( + self::CERTIFICATE_FILE, + 'store', + $storeId + ); + } + + public function getSslKeyFilePath(int $storeId = null) + { + return $this->scopeConfig->getValue( + self::SSL_KEY_FILE, + 'store', + $storeId + ); + } + + public function getWebhookCertificateFilePath(int $storeId = null) + { + return $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/webhook/' . + $this->scopeConfig->getValue( + self::WEBHOOK_CERTIFICATE_FILE, + 'store', + $storeId + ); + } + + public function debug(int $storeId = null) + { + return $this->scopeConfig->getValue( + self::DEBUG, + 'store', + $storeId + ); + } +} diff --git a/Observer/Adminhtml/DefineDebugConfigObserver.php b/Observer/Adminhtml/DefineDebugConfigObserver.php new file mode 100644 index 0000000..ad22917 --- /dev/null +++ b/Observer/Adminhtml/DefineDebugConfigObserver.php @@ -0,0 +1,64 @@ +generalConfigProvider = $generalConfigProvider; + $this->configFactory = $configFactory; + $this->logger = $logger; + } + + public function execute(Observer $observer) + { + try { + if(empty(array_intersect(self::PATHS_TO_OBSERVE, $observer->getData('changed_paths')))) { + return; + } + + $configModel = $this->configFactory->create(); + + foreach (self::PATHS_TO_CHANGE_CONFIG as $path) { + $configModel->setDataByPath($path, $this->generalConfigProvider->debug(intval($observer->getData('store')))); + } + + $configModel->addData([ + 'section' => 'payment', + 'website' => $observer->getData('website'), + 'store' => $observer->getData('store'), + ]); + + $configModel->save(); + + } catch (\Throwable $e) { + $this->logger->critical($e); + } + } +} diff --git a/Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php b/Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php new file mode 100644 index 0000000..33f97ea --- /dev/null +++ b/Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php @@ -0,0 +1,57 @@ +configProvider = $configProvider; + $this->apiClient = $apiClient; + $this->logger = $logger; + } + + public function execute(Observer $observer) + { + try { + + if(empty(array_intersect(self::PATHS_TO_OBSERVE, $observer->getData('changed_paths')))) { + return; + } + + $store = $observer->getData('store'); + + $webhookUrl = $this->configProvider->getWebhookUrl(intval($store)); + + if(empty($webhookUrl)) { + return; + } + + $this->apiClient->defineWebhook($webhookUrl, intval($store)); + + } catch (\Throwable|ClientException $e) { + $this->logger->critical($e); + } + } +} diff --git a/Observer/Gateway/IssueBoletoDataAssignObserver.php b/Observer/Gateway/IssueBoletoDataAssignObserver.php new file mode 100644 index 0000000..57a714e --- /dev/null +++ b/Observer/Gateway/IssueBoletoDataAssignObserver.php @@ -0,0 +1,35 @@ +logger = $logger; + } + + public function execute(Observer $observer) + { + try { + $data = $this->readDataArgument($observer); + $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA); + $payment = $this->readPaymentModelArgument($observer); + + if(isset($additionalData['customer_taxvat'])) { + $payment->setAdditionalInformation('customer_taxvat', $additionalData['customer_taxvat']); + } + } catch (\Throwable $e) { + $this->logger->critical($e); + } + } +} diff --git a/Observer/PaymentMethodIsActiveObserver.php b/Observer/PaymentMethodIsActiveObserver.php new file mode 100644 index 0000000..f46e9a7 --- /dev/null +++ b/Observer/PaymentMethodIsActiveObserver.php @@ -0,0 +1,49 @@ +generalConfigProvider = $generalConfigProvider; + $this->checkoutSession = $checkoutSession; + } + + public function execute(Observer $observer) + { + /* @var \Magento\Quote\Model\Quote $quote */ + $quote = $observer->getQuote(); + + if($quote === null) { + $quote = $this->checkoutSession->getQuote(); + } + + /* @var \Magento\Payment\Model\MethodInterface $methodInstance */ + $methodInstance = $observer->getMethodInstance(); + + if(!$this->generalConfigProvider->isModuleEnabled($quote->getStoreId()) && + in_array($methodInstance->getCode(), self::METHODS_TO_OBSERVE)) { + /* @var \Magento\Framework\DataObject $checkResult */ + $checkResult = $observer->getResult(); + $checkResult->setData('is_available', false); + } + + } +} diff --git a/composer.json b/composer.json index 0a81be1..0c85467 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,16 @@ { "name": "mbissonho/module-bancointer", - "description": "", + "description": "Magento 2 Module Banco Inter by mbissonho", "type": "magento2-module", "version": "0.1.0", + "require": { + "guzzlehttp/guzzle": "7.5.*" + }, + "authors":[ + { + "name": "Mateus Bissonho(mbissonho)" + } + ], "autoload": { "files": [ "registration.php" diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml new file mode 100644 index 0000000..a0b91be --- /dev/null +++ b/etc/adminhtml/events.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml new file mode 100644 index 0000000..369e7b2 --- /dev/null +++ b/etc/adminhtml/routes.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml new file mode 100644 index 0000000..57a3112 --- /dev/null +++ b/etc/adminhtml/system.xml @@ -0,0 +1,145 @@ + + + +
+ + + + + + + + + Magento\Config\Model\Config\Source\Yesno + mbissonho_bancointer/mbissonho_bancointer_general/active + + + + Mbissonho\BancoInter\Model\Config\Source\Environment + mbissonho_bancointer/mbissonho_bancointer_general/environment + + + + mbissonho_bancointer/mbissonho_bancointer_general/client_id + Magento\Config\Model\Config\Backend\Encrypted + required-entry no-whitespace + + 1 + + + + + mbissonho_bancointer/mbissonho_bancointer_general/client_secret + Magento\Config\Model\Config\Backend\Encrypted + required-entry no-whitespace + + 1 + + + + + mbissonho_bancointer/mbissonho_bancointer_general/certificate_file + Mbissonho\BancoInter\Model\Config\Backend\CertificateFile + mbissonho/bancointer + mbissonho/bancointer + + 1 + + + + + mbissonho_bancointer/mbissonho_bancointer_general/ssl_key_file + Mbissonho\BancoInter\Model\Config\Backend\SSLKeyFile + mbissonho/bancointer + mbissonho/bancointer + + 1 + + + + + mbissonho_bancointer/mbissonho_bancointer_general/webhook_url + required-entry + + 1 + + + + + + Magento\Config\Model\Config\Source\Yesno + mbissonho_bancointer/mbissonho_bancointer_general/debug + + + + + + + + + + + + + Magento\Config\Model\Config\Source\Yesno + payment/mbissonho_bancointer_boleto/active + + + + + payment/mbissonho_bancointer_boleto/title + + 1 + + + + + + payment/mbissonho_bancointer_boleto/your_number + required-entry integer + + 1 + + + + + + payment/mbissonho_bancointer_boleto/expiration_days + required-entry integer + + 1 + + + + + + payment/mbissonho_bancointer_boleto/days_to_cancel_after_expiration + required-entry integer + + 1 + + + + + + Magento\Config\Model\Config\Source\Yesno + payment/mbissonho_bancointer_boleto/cancel_when_cancel_order + + + + + payment/mbissonho_bancointer_boleto/sort_order + + 1 + + + + + + + + +
+
+
diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 0000000..91c7748 --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,31 @@ + + + + + + production + + + + + 0 + Banco Inter Boleto + BancoInterBoletoRemoteMethodAdapter + pending_payment + authorize + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 5 + 2 + 1 + + + + diff --git a/etc/crontab.xml b/etc/crontab.xml new file mode 100644 index 0000000..b33da90 --- /dev/null +++ b/etc/crontab.xml @@ -0,0 +1,9 @@ + + + + + */5 * * * * + + + diff --git a/etc/db_schema.xml b/etc/db_schema.xml new file mode 100644 index 0000000..9455cf5 --- /dev/null +++ b/etc/db_schema.xml @@ -0,0 +1,7 @@ + + + + +
+
diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..30c69d5 --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,262 @@ + + + + + + + + + + /var/log/mbissonho_bancointer.log + + + + + + + BancoInterGeneralLogHandler + + + + + + + + + + + + + Mbissonho\BancoInter\Model\Ui\Boleto\ConfigProvider::CODE + BancoInterBoletoValueHandlerPool + BancoInterBoletoCommandPool + Mbissonho\BancoInter\Block\Adminhtml\Form + Mbissonho\BancoInter\Block\Info + BancoInterBoletoValidatorPool + + + + + + + + + BancoInterBoletoConfigValueHandler + BancoInterGeneralClientIdConfigValueHandler + BancoInterGeneralClientSecretConfigValueHandler + BancoInterGeneralCertificatePathConfigValueHandler + BancoInterGeneralSslKeyPathConfigValueHandler + BancoInterGeneralEnvironmentConfigValueHandler + BancoInterGeneralMockedApiBaseUrlConfigValueHandler + + + + + + + BancoInterBoletoConfig + + + + + + BancoInterGeneralConfig + + + + + + BancoInterGeneralConfig + + + + + + BancoInterGeneralConfig + + + + + + BancoInterGeneralConfig + + + + + + BancoInterGeneralConfig + + + + + + BancoInterGeneralConfig + + + + + + + + Mbissonho\BancoInter\Model\Ui\Boleto\ConfigProvider::CODE + + + + + + Mbissonho\BancoInter\Model\Ui\ConfigProvider::CODE + mbissonho_bancointer/%s/%s + + + + + + + + + BancoInterBoletoInitializeCommand + BancoInterBoletoCancelCommand + BancoInterBoletoCancelCommand + + + + + + + + Mbissonho\BancoInter\Gateway\Request\Builder\GeneralBuilder + Mbissonho\BancoInter\Gateway\Boleto\Request\Builder\IssueBoletoBuilder + Mbissonho\BancoInter\Gateway\Request\Builder\PayerBuilder + + + + + + + BancoInterIssueBoletoRequestBuilder + Mbissonho\BancoInter\Gateway\Boleto\Http\TransferFactory + Mbissonho\BancoInter\Gateway\Boleto\Http\Client\IssueBoletoClient + Mbissonho\BancoInter\Gateway\Boleto\Response\IssueResponseHandler + Mbissonho\BancoInter\Gateway\Boleto\Validator\IssueResponseValidator + Mbissonho\BancoInter\Gateway\ErrorMapper\VirtualErrorMessageMapper + Mbissonho\BancoInter\Logger\Logger + + + + + + + Mbissonho\BancoInter\Gateway\Request\Builder\GeneralBuilder + + + + + + + BancoInterCancelBoletoRequestBuilder + Mbissonho\BancoInter\Gateway\Boleto\Http\TransferFactory + Mbissonho\BancoInter\Gateway\Boleto\Http\Client\CancelBoletoClient + Mbissonho\BancoInter\Logger\Logger + + + + + + + Mbissonho\BancoInter\Gateway\Boleto\Validator\BeforeActionValidator + + + + + + + BancoInterBoletoLogger + + + + + + FieldsValidatorPool + + + + + + /var/log/mbissonho_bancointer_boleto.log + + + + + + + BancoInterBoletoLogHandler + + + + + + + BancoInterBoletoConfig + BancoInterBoletoInternalLogger + + + + + + + + + Mbissonho\BancoInter\Gateway\Validator\CpfCnpjValidator + + + + + + + + + mbissonho_bancointer_error_mapping.xml + + + + + + Mbissonho\BancoInter\Gateway\ErrorMapper\VirtualConfigReader + mbissonho_bancointer_error_mapper + + + + + + Mbissonho\BancoInter\Gateway\ErrorMapper\VirtualMappingData + + + + + + + + + + /var/log/mbissonho_bancointer_cron.log + + + + + + + BancoInterCronLogHandler + + + + + + + BancoInterCronLogger + + + + + + diff --git a/etc/events.xml b/etc/events.xml new file mode 100644 index 0000000..12a1638 --- /dev/null +++ b/etc/events.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml new file mode 100644 index 0000000..8ef8022 --- /dev/null +++ b/etc/frontend/di.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/etc/frontend/routes.xml b/etc/frontend/routes.xml new file mode 100644 index 0000000..919a11b --- /dev/null +++ b/etc/frontend/routes.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/etc/mbissonho_bancointer_error_mapping.xml b/etc/mbissonho_bancointer_error_mapping.xml new file mode 100644 index 0000000..8c0e0c0 --- /dev/null +++ b/etc/mbissonho_bancointer_error_mapping.xml @@ -0,0 +1,8 @@ + + + API Inter: Invalid data. Check customer and address data. + API Inter: Not authorized. Check configured credentials. + API Inter: Requested resource not found + API Inter: Unexpected error + + diff --git a/etc/module.xml b/etc/module.xml index e9c79b1..807a42b 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -3,7 +3,9 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + + diff --git a/etc/payment.xml b/etc/payment.xml new file mode 100644 index 0000000..fcafdf7 --- /dev/null +++ b/etc/payment.xml @@ -0,0 +1,14 @@ + + + + + + + + + + 0 + + + diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv new file mode 100644 index 0000000..71a4f28 --- /dev/null +++ b/i18n/pt_BR.csv @@ -0,0 +1,25 @@ +"==== REQUEST BUILDER ====", "==== REQUEST BUILDER ====" + +"Error processing payment information", "Erro ao processar as informações do pagamento." +"Error processing boleto payment information", "Erro ao processar as informações de pagamento do boleto." +"Error processing payer information. Please verify address information and taxvat.", "Erro ao processar as informações do pagador. Por favor verifique as informações de endereços e CPF/CNPJ." + +"==== FIELDS VALIDATION ====", "==== FIELDS VALIDATION ====" + +"Cannot determinate customer person type", "Não foi possível determinar o tipo de pessoa do cliente" +"Taxvat is invalid", "CPF/CNPJ é inválido" + +"==== API INTER ERROR ====", "==== API INTER ERROR ====" + +"API Inter: Invalid data. Check customer and address data.", "API Inter: Dados inválidos. Verifique os dados de cliente e endereço." +"API Inter: Not authorized. Check configured credentials.", "API Inter: Não autorizado. Verifique as credenciais configuradas." +"API Inter: Requested resource not found", "API Inter: Recurso solicitado não encontrado." +"API Inter: Unexpected error", "API Inter: Erro não esperado." + +"==== ACTIONS ====", "==== ACTIONS ====" + +"PRINT BOLETO", "IMPRIMIR BOLETO" + +"==== LABELS ====", "==== LABELS ====" + +"Taxvat", "CPF/CNPJ" diff --git a/view/adminhtml/templates/form/boleto.phtml b/view/adminhtml/templates/form/boleto.phtml new file mode 100644 index 0000000..c1b4be5 --- /dev/null +++ b/view/adminhtml/templates/form/boleto.phtml @@ -0,0 +1,19 @@ +getMethodCode(); +?> + +
+
+ +
+ " + class="required-entry admin__control-text"/> +
+
+
diff --git a/view/adminhtml/templates/info/boleto.phtml b/view/adminhtml/templates/info/boleto.phtml new file mode 100644 index 0000000..1a35302 --- /dev/null +++ b/view/adminhtml/templates/info/boleto.phtml @@ -0,0 +1,31 @@ +escapeHtml($block->getMethod()->getTitle()); +?> +
+
+ + getInfo()->getAdditionalInformation('our_number'); + + if($ourNumber) { + echo " $block->getInfo()->getEntityId(), 'our_number' => $ourNumber ])}'> + " . __('PRINT BOLETO') . "
"; + ?> + +
+ : +
+ + + + +
+getChildHtml() ?> diff --git a/view/base/templates/info/boleto.phtml b/view/base/templates/info/boleto.phtml new file mode 100644 index 0000000..b70bdb8 --- /dev/null +++ b/view/base/templates/info/boleto.phtml @@ -0,0 +1,24 @@ +escapeHtml($block->getMethod()->getTitle()); +?> +
+
+ + getInfo()->getAdditionalInformation('our_number'); + + if($ourNumber) { + echo " $block->getInfo()->getEntityId(), 'our_number' => $ourNumber ])}'> + " . __('PRINT BOLETO') . "
"; + } + + ?> + +
+getChildHtml() ?> diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml new file mode 100644 index 0000000..4a969e7 --- /dev/null +++ b/view/frontend/layout/checkout_index_index.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + uiComponent + + + + + + + Mbissonho_BancoInter/js/view/payment/payments + + + true + + + + + + + + + + + + + + + + + + + diff --git a/view/frontend/web/js/view/payment/method-renderer/boleto-method.js b/view/frontend/web/js/view/payment/method-renderer/boleto-method.js new file mode 100644 index 0000000..1255b3c --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/boleto-method.js @@ -0,0 +1,101 @@ +/* @api */ +define([ + 'Magento_Checkout/js/view/payment/default', + 'uiLayout', + 'mageUtils', + 'ko', + 'jquery', + 'underscore' +], function (Component, layout, utils, ko, $, _) { + 'use strict'; + + ko.bindingHandlers.cpfCnpjMasked = { + + init: function(element, valueAccessor, allBindingsAccessor) { + + ko.utils.registerEventHandler(element, 'focusout', function() { + var observable = valueAccessor(); + observable($(element).val()); + }); + ko.utils.registerEventHandler(element, 'keyup', function() { + var observable = valueAccessor(); + observable($(element).val()); + }); + ko.utils.registerEventHandler(element, 'keydown', function() { + var observable = valueAccessor(); + observable($(element).val()); + }); + }, + update: function (element, valueAccessor) { + let value = ko.utils.unwrapObservable(valueAccessor()); + + if (value.length <= 14) { + value=value.replace(/\D/g,""); + value=value.replace(/(\d{3})(\d)/,"$1.$2"); + value=value.replace(/(\d{3})(\d)/,"$1.$2"); + value=value.replace(/(\d{3})(\d{1,2})$/,"$1-$2"); + }else{ + value=value.replace(/\D/g,"") + value=value.replace(/^(\d{2})(\d)/,"$1.$2"); + value=value.replace(/^(\d{2})\.(\d{3})(\d)/,"$1.$2.$3"); + value=value.replace(/\.(\d{3})(\d)/,".$1/$2"); + value=value.replace(/(\d{4})(\d)/,"$1-$2"); + } + + $(element).val(value); + } + + }; + + return Component.extend({ + defaults: { + template: 'Mbissonho_BancoInter/payment/boleto' + }, + + customerTaxvat: ko.observable(''), + + initialize: function () { + this._super(); + + this.methodCode = this.item.method; + this.initializeCustomerTaxvatField(); + + return this; + }, + + initializeCustomerTaxvatField: function () { + + try { + + let customerTaxvat = window.checkoutConfig?.quoteData?.customer_taxvat + + if(!_.isEmpty(customerTaxvat)) { + this.customerTaxvat(customerTaxvat); + return; + } + + customerTaxvat = window.checkoutConfig?.customerData?.taxvat; + + if(!_.isEmpty(customerTaxvat)) { + this.customerTaxvat(customerTaxvat); + } + + } catch (e) { + console.warn('Something went wrong on method: initializeCustomerTaxvatField', e); + } + + }, + + getData: function () { + + let data = { + 'method': this.methodCode, + 'additional_data': { + 'customer_taxvat': this.customerTaxvat() + } + }; + + return data; + } + }); +}); diff --git a/view/frontend/web/js/view/payment/payments.js b/view/frontend/web/js/view/payment/payments.js new file mode 100644 index 0000000..d0e8f06 --- /dev/null +++ b/view/frontend/web/js/view/payment/payments.js @@ -0,0 +1,16 @@ +/* @api */ +define([ + 'uiComponent', + 'Magento_Checkout/js/model/payment/renderer-list' +], function (Component, rendererList) { + 'use strict'; + + rendererList.push( + { + type: 'mbissonho_bancointer_boleto', + component: 'Mbissonho_BancoInter/js/view/payment/method-renderer/boleto-method' + }, + ); + + return Component.extend({}); +}); diff --git a/view/frontend/web/template/payment/boleto.html b/view/frontend/web/template/payment/boleto.html new file mode 100644 index 0000000..2f9a455 --- /dev/null +++ b/view/frontend/web/template/payment/boleto.html @@ -0,0 +1,55 @@ +
+
+ + +
+ +
+ + + +
+ + + +
+
+ + + +
+
+ +
+ +
+
+
+
+
+ +
+
+
+
From baf8a08c06f42aaaca2e87ffaa869ec00d9fe67c Mon Sep 17 00:00:00 2001 From: Mateus Bissonho Date: Thu, 30 Mar 2023 18:47:30 -0300 Subject: [PATCH 2/4] Add license and readme files --- LICENSE.md | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 17 +++++ 2 files changed, 218 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bf11299 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2017 Arnaud GIULIANI, Pedro Bissonho + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..394d505 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Mbissonho BancoInter Magento 2 module + +## Installation + +This module can be installed via composer: + +```shell +composer require mbissonho/module-bancointer +``` + +## Maintainers + +- [Mateus Bissonho](https://github.com/mbissonho) + +## License + +[Apache 2.0](https://github.com/mbissonho/bancointer-magento2/blob/develop/LICENSE.md) From 9c919c9e21b01ff1c86e972b8f6b04daa0bf04e9 Mon Sep 17 00:00:00 2001 From: Mateus Bissonho Date: Fri, 31 Mar 2023 18:48:03 -0300 Subject: [PATCH 3/4] Update readme --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 394d505..ad66de9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ -# Mbissonho BancoInter Magento 2 module +# BancoInter Magento 2 module + +This module allow to connect a Magento 2 store with payment methods offered by Banco Inter. + +## Available Payment Methods + +- Boleto + +## Prerequisites + +- You will need a [Inter company account](https://conta-digital-pj.bancointer.com.br/login). +- Magento Open Source version 2.4.x +- PHP 7.4+ ## Installation @@ -8,6 +20,10 @@ This module can be installed via composer: composer require mbissonho/module-bancointer ``` +## Get Started + +After installation, you need follow [this guide](https://developers.bancointer.com.br/docs/criar-aplicacao) to get the credenciais to configure the module properly. + ## Maintainers - [Mateus Bissonho](https://github.com/mbissonho) From b58a94e78be60f04921732645c1ec6bc999bc77a Mon Sep 17 00:00:00 2001 From: Mateus Bissonho Date: Fri, 31 Mar 2023 18:52:41 -0300 Subject: [PATCH 4/4] Improve code; add pt_BR translations --- .gitignore | 3 + .../Boleto/Response/IssueResponseHandler.php | 10 +- Model/Api/Client.php | 110 +++++++----------- Model/Boleto/PdfDownloader.php | 4 +- Model/Ui/ConfigProvider.php | 12 -- ...hookUrlWhenPaymentConfigChangeObserver.php | 57 --------- etc/adminhtml/events.xml | 2 - etc/adminhtml/system.xml | 19 ++- i18n/pt_BR.csv | 9 ++ 9 files changed, 67 insertions(+), 159 deletions(-) delete mode 100644 Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php diff --git a/.gitignore b/.gitignore index 2211df6..bb3b52e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ *.txt +/.idea/ +/vendor/ +*.lock diff --git a/Gateway/Boleto/Response/IssueResponseHandler.php b/Gateway/Boleto/Response/IssueResponseHandler.php index 7a9bbca..9fd2640 100644 --- a/Gateway/Boleto/Response/IssueResponseHandler.php +++ b/Gateway/Boleto/Response/IssueResponseHandler.php @@ -29,14 +29,14 @@ public function handle(array $handlingSubject, array $response) $totalDue = $payment->getOrder()->getTotalDue(); $payment->setTransactionId($responseBody->nossoNumero); - $payment->setIsTransactionClosed(true); + $payment->setIsTransactionClosed(false); - $stateObject->setData('state', Order::STATE_PENDING_PAYMENT); - $payment->getOrder()->setState(Order::STATE_PENDING_PAYMENT); - $payment->getOrder()->setStatus($payment->getOrder()->getConfig()->getStateDefaultStatus(Order::STATE_PENDING_PAYMENT)); + $stateObject->setData('state', Order::STATE_NEW); + $payment->getOrder()->setState(Order::STATE_NEW); + $payment->getOrder()->setStatus($payment->getOrder()->getConfig()->getStateDefaultStatus(Order::STATE_NEW)); $message = __('Authorized amount of %1.', $totalDue); - $payment->setShouldCloseParentTransaction(true); + $payment->setShouldCloseParentTransaction(false); $isSameCurrency = $payment->isSameCurrency(); if (!$isSameCurrency || !$payment->isCaptureFinal($totalDue)) { diff --git a/Model/Api/Client.php b/Model/Api/Client.php index de2446d..ecf7d5e 100644 --- a/Model/Api/Client.php +++ b/Model/Api/Client.php @@ -48,18 +48,16 @@ public function generateAuthToken($storeId = null): void $this->recreateHttpClientIfNeed($storeId); - $response = $this->client->post('/oauth/v2/token', [ - 'form_params' => [ - 'client_id' => $this->generalConfigProvider->getClientId($storeId), - 'client_secret' => $this->generalConfigProvider->getClientSecret($storeId), - 'grant_type' => 'client_credentials', - 'scope' => 'boleto-cobranca.read boleto-cobranca.write' - ], - 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getCertificateFilePath($storeId), - 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getSslKeyFilePath($storeId) - ]); + $response = $this->client->post('/oauth/v2/token', + array_merge([ + 'form_params' => [ + 'client_id' => $this->generalConfigProvider->getClientId($storeId), + 'client_secret' => $this->generalConfigProvider->getClientSecret($storeId), + 'grant_type' => 'client_credentials', + 'scope' => 'boleto-cobranca.read boleto-cobranca.write' + ] + ], $this->getCommonRequestData($storeId)) + ); if($response->getStatusCode() == 200) { $this->authToken = json_decode($response->getBody()->getContents())->access_token; @@ -72,51 +70,22 @@ public function issueBoleto(array $requestBody, $storeId = null): ResponseInterf return $this->client->post( '/cobranca/v2/boletos', - [ - 'json' => $requestBody, - 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getCertificateFilePath($storeId), - 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getSslKeyFilePath($storeId), - 'headers' => [ - 'Authorization' => "Bearer {$this->authToken}" - ] - ] + array_merge([ + 'json' => $requestBody + ], $this->getCommonRequestData($storeId)) ); } - public function defineWebhook(string $webhookUrl, $storeId = null): void - { - $this->generateAuthToken($storeId); - $this->recreateHttpClientIfNeed($storeId); - - $this->client->put('/cobranca/v2/boletos/webhook', [ - 'json' => ['webhookUrl' => $webhookUrl], - 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getCertificateFilePath($storeId), - 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getSslKeyFilePath($storeId), - 'headers' => [ - 'Authorization' => "Bearer {$this->authToken}" - ] - ]); - } - public function cancelPayment($ourNumber, $storeId = null): ResponseInterface { $this->generateAuthToken($storeId); $this->recreateHttpClientIfNeed($storeId); - return $this->client->post(sprintf("/cobranca/v2/boletos/%s/cancelar", $ourNumber), [ - 'json' => ['motivoCancelamento' => 'ACERTOS'], - 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getCertificateFilePath($storeId), - 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getSslKeyFilePath($storeId), - 'headers' => [ - 'Authorization' => "Bearer {$this->authToken}" - ] - ]); + return $this->client->post(sprintf("/cobranca/v2/boletos/%s/cancelar", $ourNumber), + array_merge([ + 'json' => ['motivoCancelamento' => 'ACERTOS'], + ], $this->getCommonRequestData($storeId)) + ); } public function getBoletoPdf($ourNumber, $storeId = null): ResponseInterface @@ -124,15 +93,9 @@ public function getBoletoPdf($ourNumber, $storeId = null): ResponseInterface $this->generateAuthToken($storeId); $this->recreateHttpClientIfNeed($storeId); - return $this->client->get(sprintf("/cobranca/v2/boletos/%s/pdf", $ourNumber), [ - 'headers' => [ - 'Authorization' => "Bearer {$this->authToken}" - ], - 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getCertificateFilePath($storeId), - 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getSslKeyFilePath($storeId) - ]); + return $this->client->get(sprintf("/cobranca/v2/boletos/%s/pdf", $ourNumber), + $this->getCommonRequestData($storeId) + ); } public function getPaymentCollection($initialDate, $finalDate, $storeId = null): array @@ -141,17 +104,8 @@ public function getPaymentCollection($initialDate, $finalDate, $storeId = null): $this->recreateHttpClientIfNeed($storeId); $response = $this->client->get( - sprintf("/cobranca/v2/boletos?filtrarDataPor=EMISSAO&dataInicial=%s&dataFinal=%s", $initialDate, $finalDate), - //sprintf("/cobranca/v2/boletos?filtrarDataPor=EMISSAO&situacao=PAGO&dataInicial=%s&dataFinal=%s", $initialDate, $finalDate), - [ - 'headers' => [ - 'Authorization' => "Bearer {$this->authToken}" - ], - 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getCertificateFilePath($storeId), - 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . - $this->generalConfigProvider->getSslKeyFilePath($storeId) - ] + sprintf("/cobranca/v2/boletos?filtrarDataPor=EMISSAO&situacao=PAGO&dataInicial=%s&dataFinal=%s", $initialDate, $finalDate), + $this->getCommonRequestData($storeId) ); $payments = []; @@ -174,4 +128,22 @@ private function recreateHttpClientIfNeed($storeId = null): void } } + private function getCommonRequestData($storeId = null): array + { + $data = [ + 'cert' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getCertificateFilePath($storeId), + 'ssl_key' => $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/' . + $this->generalConfigProvider->getSslKeyFilePath($storeId) + ]; + + if($this->authToken != null) { + $data['headers'] = [ + 'Authorization' => "Bearer {$this->authToken}" + ]; + } + + return $data; + } + } diff --git a/Model/Boleto/PdfDownloader.php b/Model/Boleto/PdfDownloader.php index a81485c..a718f58 100644 --- a/Model/Boleto/PdfDownloader.php +++ b/Model/Boleto/PdfDownloader.php @@ -10,10 +10,8 @@ use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadInterface; use Magento\Sales\Api\Data\OrderPaymentInterface; -use Mbissonho\BancoInter\Model\Api\Client; -use Mbissonho\BancoInter\Model\Api\CurrentEnvironmentBaseUrlResolver; -use Mbissonho\BancoInter\Model\Config\Backend\AbstractFile; use Magento\Sales\Model\ResourceModel\Order\Payment as OrderPaymentResource; +use Mbissonho\BancoInter\Model\Api\Client; class PdfDownloader { diff --git a/Model/Ui/ConfigProvider.php b/Model/Ui/ConfigProvider.php index 9b9491d..6e9839e 100644 --- a/Model/Ui/ConfigProvider.php +++ b/Model/Ui/ConfigProvider.php @@ -8,7 +8,6 @@ use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadInterface; -use Mbissonho\BancoInter\Model\Config\Backend\AbstractFile; class ConfigProvider implements ConfigProviderInterface { @@ -24,7 +23,6 @@ class ConfigProvider implements ConfigProviderInterface const ENVIRONMENT = 'mbissonho_bancointer/mbissonho_bancointer_general/environment'; const WEBHOOK_URL = 'mbissonho_bancointer/mbissonho_bancointer_general/webhook_url'; - const WEBHOOK_CERTIFICATE_FILE = 'mbissonho_bancointer/mbissonho_bancointer_general/webhook_certificate_file'; const DEBUG = 'mbissonho_bancointer/mbissonho_bancointer_general/debug'; @@ -111,16 +109,6 @@ public function getSslKeyFilePath(int $storeId = null) ); } - public function getWebhookCertificateFilePath(int $storeId = null) - { - return $this->_varDirectory->getAbsolutePath(AbstractFile::UPLOAD_DIR) . '/webhook/' . - $this->scopeConfig->getValue( - self::WEBHOOK_CERTIFICATE_FILE, - 'store', - $storeId - ); - } - public function debug(int $storeId = null) { return $this->scopeConfig->getValue( diff --git a/Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php b/Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php deleted file mode 100644 index 33f97ea..0000000 --- a/Observer/Adminhtml/DefineWebhookUrlWhenPaymentConfigChangeObserver.php +++ /dev/null @@ -1,57 +0,0 @@ -configProvider = $configProvider; - $this->apiClient = $apiClient; - $this->logger = $logger; - } - - public function execute(Observer $observer) - { - try { - - if(empty(array_intersect(self::PATHS_TO_OBSERVE, $observer->getData('changed_paths')))) { - return; - } - - $store = $observer->getData('store'); - - $webhookUrl = $this->configProvider->getWebhookUrl(intval($store)); - - if(empty($webhookUrl)) { - return; - } - - $this->apiClient->defineWebhook($webhookUrl, intval($store)); - - } catch (\Throwable|ClientException $e) { - $this->logger->critical($e); - } - } -} diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml index a0b91be..a1e8284 100644 --- a/etc/adminhtml/events.xml +++ b/etc/adminhtml/events.xml @@ -3,8 +3,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> - diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 57a3112..b0bc090 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -4,7 +4,9 @@
- + @@ -57,14 +59,6 @@ 1 - - - mbissonho_bancointer/mbissonho_bancointer_general/webhook_url - required-entry - - 1 - - @@ -87,7 +81,7 @@ - + payment/mbissonho_bancointer_boleto/title 1 @@ -125,10 +119,13 @@ Magento\Config\Model\Config\Source\Yesno payment/mbissonho_bancointer_boleto/cancel_when_cancel_order + + 1 + - + payment/mbissonho_bancointer_boleto/sort_order 1 diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 71a4f28..c104ce6 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -22,4 +22,13 @@ "==== LABELS ====", "==== LABELS ====" +"Methods", "Métodos" +"Cancel When Cancel Order", "Cancelar ao cancelar o pedido" "Taxvat", "CPF/CNPJ" +"Your number", "Seu número" +"Default expiration days", "Dias para vencimento padrão" +"Days to cancel after expiration", "Dias para cancelar após o vencimento" +"Certificate File", "Arquivo do Certificado" +"SSL Key File", "Arquivo da Chave Privada" + +