From 8c1765a0172968f6612a32e2b2c3c56654c73915 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Mon, 18 Dec 2023 21:21:32 -0300 Subject: [PATCH 001/187] Add CPF and CNPJ mask to taxvat field on pix method --- Model/Pix/ConfigProvider.php | 18 ++++- view/frontend/web/js/model/document.js | 19 ------ view/frontend/web/js/model/taxvat.js | 27 ++++++++ view/frontend/web/js/model/validate.js | 65 ++++++++++++++++++- .../view/payment/method-renderer/vindi-pix.js | 24 ++++--- .../web/template/payment/vindi-pix.html | 7 +- 6 files changed, 125 insertions(+), 35 deletions(-) delete mode 100644 view/frontend/web/js/model/document.js create mode 100644 view/frontend/web/js/model/taxvat.js diff --git a/Model/Pix/ConfigProvider.php b/Model/Pix/ConfigProvider.php index 57957ae7..c12f3459 100644 --- a/Model/Pix/ConfigProvider.php +++ b/Model/Pix/ConfigProvider.php @@ -4,6 +4,7 @@ use Magento\Checkout\Model\ConfigProviderInterface; +use Magento\Customer\Model\Session as CustomerSession; use Vindi\Payment\Api\PixConfigurationInterface; @@ -19,13 +20,21 @@ class ConfigProvider implements ConfigProviderInterface */ protected $pixConfiguration; + /** + * @var CustomerSession $customerSession + */ + protected $customerSession; + /** * @param PixConfigurationInterface $pixConfiguration + * @param CustomerSession $customerSession */ public function __construct( - PixConfigurationInterface $pixConfiguration + PixConfigurationInterface $pixConfiguration, + CustomerSession $customerSession ) { $this->pixConfiguration = $pixConfiguration; + $this->customerSession = $customerSession; } /** @@ -33,11 +42,18 @@ public function __construct( */ public function getConfig() { + $customerTaxvat = ''; + $customer = $this->customerSession->getCustomer(); + if ($customer && $customer->getTaxvat()) { + $customerTaxvat = $customer->getTaxvat(); + } + return [ 'payment' => [ 'vindi_pix' => [ 'enabledDocument' => $this->pixConfiguration->isEnabledDocument(), 'info_message' => $this->pixConfiguration->getInfoMessage(), + 'customer_taxvat' => $customerTaxvat ] ] ]; diff --git a/view/frontend/web/js/model/document.js b/view/frontend/web/js/model/document.js deleted file mode 100644 index 64062f21..00000000 --- a/view/frontend/web/js/model/document.js +++ /dev/null @@ -1,19 +0,0 @@ -define( - [ - 'jquery', - 'ko', - 'jQueryMask' - ], - - function ($, ko) { - 'use strict'; - - $('input[name="payment[_document]"]').mask('999.999.999-99'); - - const value = ko.observable(''); - - return { - value: value - }; - } -); diff --git a/view/frontend/web/js/model/taxvat.js b/view/frontend/web/js/model/taxvat.js new file mode 100644 index 00000000..316995d9 --- /dev/null +++ b/view/frontend/web/js/model/taxvat.js @@ -0,0 +1,27 @@ +define( + [ + 'jquery', + 'ko' + ], + + function ($, ko) { + 'use strict'; + + $('input[name="payment[_document]"]').mask('999.999.999-99'); + + const value = ko.observable(''); + + return { + value: value, + + formatDocument: function(input) { + let value = input.value.replace(/\D/g, ''); + let isCpf = value.length <= 11; + + input.value = isCpf + ? value.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4') + : value.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, '$1.$2.$3/$4-$5'); + } + }; + } +); diff --git a/view/frontend/web/js/model/validate.js b/view/frontend/web/js/model/validate.js index 070fc700..cf1f7a6c 100644 --- a/view/frontend/web/js/model/validate.js +++ b/view/frontend/web/js/model/validate.js @@ -2,10 +2,18 @@ define([], function() { 'use strict'; return { - isValidCpf(c){ - if((c = c.replace(/[^\d]/g,"")).length != 11) + isValidTaxvat(taxvat){ + if ((taxvat = taxvat.replace(/[^\d]/g,"")).length < 11) return false + if (taxvat.length === 11) { + return this.validateCpf(taxvat) + } + + return this.validateCnpj(taxvat); + }, + + validateCpf: function (c) { if (c == "00000000000" || c == "11111111111" || c == "22222222222" || @@ -45,6 +53,59 @@ define([], function() { if (r != parseInt(c[10])) return false; + return true; + }, + + validateCnpj: function (cnpj) { + if (/^(\d)\1+$/g.test(cnpj)) { + return false; + } + + if (cnpj.length != 14) { + return false; + } + + if (cnpj == "00000000000000" || + cnpj == "11111111111111" || + cnpj == "22222222222222" || + cnpj == "33333333333333" || + cnpj == "44444444444444" || + cnpj == "55555555555555" || + cnpj == "66666666666666" || + cnpj == "77777777777777" || + cnpj == "88888888888888" || + cnpj == "99999999999999") + return false; + + let tamanho = cnpj.length - 2 + let numeros = cnpj.substring(0,tamanho); + let digitos = cnpj.substring(tamanho); + let soma = 0; + let pos = tamanho - 7; + for (let i = tamanho; i >= 1; i--) { + soma += numeros.charAt(tamanho - i) * pos--; + if (pos < 2) + pos = 9; + } + let resultado = soma % 11 < 2 ? 0 : 11 - soma % 11; + if (resultado != digitos.charAt(0)) { + return false; + } + + tamanho = tamanho + 1; + numeros = cnpj.substring(0,tamanho); + soma = 0; + pos = tamanho - 7; + for (let i = tamanho; i >= 1; i--) { + soma += numeros.charAt(tamanho - i) * pos--; + if (pos < 2) + pos = 9; + } + + resultado = soma % 11 < 2 ? 0 : 11 - soma % 11; + if (resultado != digitos.charAt(1)) + return false; + return true; } }; diff --git a/view/frontend/web/js/view/payment/method-renderer/vindi-pix.js b/view/frontend/web/js/view/payment/method-renderer/vindi-pix.js index 01a2358e..05108be1 100644 --- a/view/frontend/web/js/view/payment/method-renderer/vindi-pix.js +++ b/view/frontend/web/js/view/payment/method-renderer/vindi-pix.js @@ -5,16 +5,16 @@ define( 'mage/translate', 'jquery', 'mageUtils', - 'Vindi_Payment/js/model/document', + 'Vindi_Payment/js/model/taxvat', 'Vindi_Payment/js/model/validate' ], - function (_, Component, $t, $, utils, document, documentValidate) { + function (_, Component, $t, $, utils, taxvat, documentValidate) { 'use strict'; return Component.extend({ defaults: { template: 'Vindi_Payment/payment/vindi-pix', - document: document + taxvat: taxvat }, getInfoMessage: function () { @@ -25,25 +25,29 @@ define( return window?.checkoutConfig?.payment?.vindi_pix?.enabledDocument; }, - checkCpf: function () { - const message = documentValidate.isValidCpf(this?.document?.value()) ? '' : 'CPF inválido'; + checkCpf: function (self, event) { + this.formatTaxvat(event.target) + const message = documentValidate.isValidTaxvat(this?.taxvat?.value()) ? '' : 'CPF/CNPJ inválido'; $('#cpfResponse').text(message); }, + formatTaxvat: function (target) { + taxvat.formatDocument(target) + }, validate: function () { const self = this; - const documentValue = this?.document?.value(); + const documentValue = this?.taxvat?.value(); if (!this.isActiveDocument()) return true; if (!documentValue || documentValue === '') { - self.messageContainer.addErrorMessage({'message': ('CPF é obrigatório')}); + self.messageContainer.addErrorMessage({'message': ('CPF/CNPJ é obrigatório')}); return false; } - if (!documentValidate.isValidCpf(documentValue)) { - self.messageContainer.addErrorMessage({'message': ('CPF não é válido')}); + if (!documentValidate.isValidTaxvat(documentValue)) { + self.messageContainer.addErrorMessage({'message': ('CPF/CNPJ não é válido')}); return false; } @@ -54,7 +58,7 @@ define( return { 'method': this?.item?.method, 'additional_data': { - 'document': this?.document?.value() + 'document': this?.taxvat?.value() } }; }, diff --git a/view/frontend/web/template/payment/vindi-pix.html b/view/frontend/web/template/payment/vindi-pix.html index 1f12c339..5a7a1e4c 100644 --- a/view/frontend/web/template/payment/vindi-pix.html +++ b/view/frontend/web/template/payment/vindi-pix.html @@ -30,7 +30,7 @@
@@ -46,9 +46,10 @@ id: getCode() + '_document', title: $t('CPF'), 'data-container': getCode() + '-cpf', - 'data-validate': JSON.stringify({'required-number':true}) + 'data-validate': JSON.stringify({'required-number':true}), + 'maxlength': 18 }, - value: document.value, + value: taxvat.value, valueUpdate: 'keyup', event: { keyup: checkCpf From 50d6742cd38c5dd9080d5976fe3a14cca6424cff Mon Sep 17 00:00:00 2001 From: Andre Manoel Date: Fri, 5 Jan 2024 17:51:35 -0300 Subject: [PATCH 002/187] Tratado pedidos de assinatura com retorno da Vindi sem o atributo bill --- Model/Payment/AbstractMethod.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 062a7f90..b2ebfdc7 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -324,12 +324,14 @@ private function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterf if ($responseData = $this->subscriptionRepository->create($body)) { $bill = $responseData['bill']; + $subscription = $responseData['subscription']; $this->handleBankSplitAdditionalInformation($payment, $body, $bill); - if ($this->successfullyPaid($body, $bill)) { + if ($this->successfullyPaid($body, $bill, $subscription)) { $this->handleBankSplitAdditionalInformation($payment, $body, $bill); - $order->setVindiBillId($bill['id']); + $billId = $bill['id'] ?? 0; + $order->setVindiBillId($billId); $order->setVindiSubscriptionId($responseData['subscription']['id']); - return $bill['id']; + return $billId; } $this->subscriptionRepository->deleteAndCancelBills($responseData['subscription']['id']); @@ -394,11 +396,19 @@ protected function handleBankSplitAdditionalInformation(InfoInterface $payment, /** * @param array $body * @param $bill - * + * @param array $subscription * @return bool */ - private function successfullyPaid(array $body, $bill) + private function successfullyPaid(array $body, $bill, array $subscription = []) { + // nova validação para permitir pedidos com pagamento/fatura pendente + if (!$bill) { + $billingType = $subscription['billing_trigger_type'] ?? null; + if ($billingType != 'day_of_month') { + return true; + } + } + return $this->isValidPaymentMethodCode($body['payment_method_code']) || $this->isValidStatus($bill) || $this->isWaitingPaymentMethodResponse($bill); @@ -426,6 +436,8 @@ protected function isValidPaymentMethodCode($paymentMethodCode) */ protected function isWaitingPaymentMethodResponse($bill) { + if (!$bill) return false; + return reset($bill['charges'])['last_transaction']['status'] === Bill::WAITING_STATUS; } From a905b6bb6c050e4a1f7a132dbe689b9fde78275f Mon Sep 17 00:00:00 2001 From: janainabiz Date: Wed, 10 Jan 2024 11:12:59 -0300 Subject: [PATCH 003/187] improve variable names for taxvat validation script --- view/frontend/web/js/model/taxvat.js | 2 - view/frontend/web/js/model/validate.js | 82 +++++++++++++------------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/view/frontend/web/js/model/taxvat.js b/view/frontend/web/js/model/taxvat.js index 316995d9..b1b96024 100644 --- a/view/frontend/web/js/model/taxvat.js +++ b/view/frontend/web/js/model/taxvat.js @@ -7,8 +7,6 @@ define( function ($, ko) { 'use strict'; - $('input[name="payment[_document]"]').mask('999.999.999-99'); - const value = ko.observable(''); return { diff --git a/view/frontend/web/js/model/validate.js b/view/frontend/web/js/model/validate.js index cf1f7a6c..9d2a752d 100644 --- a/view/frontend/web/js/model/validate.js +++ b/view/frontend/web/js/model/validate.js @@ -13,44 +13,44 @@ define([], function() { return this.validateCnpj(taxvat); }, - validateCpf: function (c) { - if (c == "00000000000" || - c == "11111111111" || - c == "22222222222" || - c == "33333333333" || - c == "44444444444" || - c == "55555555555" || - c == "66666666666" || - c == "77777777777" || - c == "88888888888" || - c == "99999999999" ) + validateCpf: function (cpf) { + if (cpf == "00000000000" || + cpf == "11111111111" || + cpf == "22222222222" || + cpf == "33333333333" || + cpf == "44444444444" || + cpf == "55555555555" || + cpf == "66666666666" || + cpf == "77777777777" || + cpf == "88888888888" || + cpf == "99999999999" ) return false; - var r; - var s = 0; + var result; + var sum = 0; for (var i=1; i<=9; i++) - s = s + parseInt(c[i-1]) * (11 - i); + sum = sum + parseInt(cpf[i-1]) * (11 - i); - r = (s * 10) % 11; + result = (sum * 10) % 11; - if ((r == 10) || (r == 11)) - r = 0; + if ((result == 10) || (result == 11)) + result = 0; - if (r != parseInt(c[9])) + if (result != parseInt(cpf[9])) return false; - s = 0; + sum = 0; for (i = 1; i <= 10; i++) - s = s + parseInt(c[i-1]) * (12 - i); + sum = sum + parseInt(cpf[i-1]) * (12 - i); - r = (s * 10) % 11; + result = (sum * 10) % 11; - if ((r == 10) || (r == 11)) - r = 0; + if ((result == 10) || (result == 11)) + result = 0; - if (r != parseInt(c[10])) + if (result != parseInt(cpf[10])) return false; return true; @@ -77,33 +77,33 @@ define([], function() { cnpj == "99999999999999") return false; - let tamanho = cnpj.length - 2 - let numeros = cnpj.substring(0,tamanho); - let digitos = cnpj.substring(tamanho); - let soma = 0; - let pos = tamanho - 7; - for (let i = tamanho; i >= 1; i--) { - soma += numeros.charAt(tamanho - i) * pos--; + let length = cnpj.length - 2 + let numbers = cnpj.substring(0,length); + let digits = cnpj.substring(length); + let sum = 0; + let pos = length - 7; + for (let i = length; i >= 1; i--) { + sum += numbers.charAt(length - i) * pos--; if (pos < 2) pos = 9; } - let resultado = soma % 11 < 2 ? 0 : 11 - soma % 11; - if (resultado != digitos.charAt(0)) { + let result = sum % 11 < 2 ? 0 : 11 - sum % 11; + if (result != digits.charAt(0)) { return false; } - tamanho = tamanho + 1; - numeros = cnpj.substring(0,tamanho); - soma = 0; - pos = tamanho - 7; - for (let i = tamanho; i >= 1; i--) { - soma += numeros.charAt(tamanho - i) * pos--; + length = length + 1; + numbers = cnpj.substring(0,length); + sum = 0; + pos = length - 7; + for (let i = length; i >= 1; i--) { + sum += numbers.charAt(length - i) * pos--; if (pos < 2) pos = 9; } - resultado = soma % 11 < 2 ? 0 : 11 - soma % 11; - if (resultado != digitos.charAt(1)) + result = sum % 11 < 2 ? 0 : 11 - sum % 11; + if (result != digits.charAt(1)) return false; return true; From 2b2ef541ffc430ff39b31a801c41b2d115caf5ec Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Fri, 12 Jan 2024 14:41:43 -0300 Subject: [PATCH 004/187] feat: tratado pedidos de assinatura com retorno da Vindi sem o atributo bill --- Model/Payment/AbstractMethod.php | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 062a7f90..20c4ab2b 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -2,7 +2,6 @@ namespace Vindi\Payment\Model\Payment; - use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\Api\ExtensionAttributesFactory; use Magento\Framework\App\Config\ScopeConfigInterface; @@ -324,12 +323,14 @@ private function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterf if ($responseData = $this->subscriptionRepository->create($body)) { $bill = $responseData['bill']; + $subscription = $responseData['subscription']; $this->handleBankSplitAdditionalInformation($payment, $body, $bill); - if ($this->successfullyPaid($body, $bill)) { + if ($this->successfullyPaid($body, $bill, $subscription)) { $this->handleBankSplitAdditionalInformation($payment, $body, $bill); - $order->setVindiBillId($bill['id']); + $billId = $bill['id'] ?? 0; + $order->setVindiBillId($billId); $order->setVindiSubscriptionId($responseData['subscription']['id']); - return $bill['id']; + return $billId; } $this->subscriptionRepository->deleteAndCancelBills($responseData['subscription']['id']); @@ -394,11 +395,19 @@ protected function handleBankSplitAdditionalInformation(InfoInterface $payment, /** * @param array $body * @param $bill - * + * @param array $subscription * @return bool */ - private function successfullyPaid(array $body, $bill) + private function successfullyPaid(array $body, $bill, array $subscription = []) { + // nova validação para permitir pedidos com pagamento/fatura pendente + if (!$bill) { + $billingType = $subscription['billing_trigger_type'] ?? null; + if ($billingType != 'day_of_month') { + return true; + } + } + return $this->isValidPaymentMethodCode($body['payment_method_code']) || $this->isValidStatus($bill) || $this->isWaitingPaymentMethodResponse($bill); @@ -426,6 +435,10 @@ protected function isValidPaymentMethodCode($paymentMethodCode) */ protected function isWaitingPaymentMethodResponse($bill) { + if (!$bill) { + return false; + } + return reset($bill['charges'])['last_transaction']['status'] === Bill::WAITING_STATUS; } From 979481060c9e2f45dd4e1f885b7209fa23014185 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Tue, 16 Jan 2024 08:51:49 -0300 Subject: [PATCH 005/187] fix: update order status when invoice is created --- Helper/Data.php | 9 +++++++++ Helper/WebHookHandlers/BillPaid.php | 4 ++-- etc/adminhtml/system.xml | 5 +++++ i18n/pt_BR.csv | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index 758e9314..1a55c435 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -87,6 +87,15 @@ public function getMode() return $this->getModuleGeneralConfig('mode'); } + /** + * @return mixed|string + */ + public function getStatusToPaidOrder() + { + $status = $this->getModuleGeneralConfig('paid_order_status'); + return $status ?: Order::STATE_PROCESSING; + } + public function getStatusToOrderComplete() { $status = $this->getModuleGeneralConfig('order_status'); diff --git a/Helper/WebHookHandlers/BillPaid.php b/Helper/WebHookHandlers/BillPaid.php index 76036cb0..6df6769b 100644 --- a/Helper/WebHookHandlers/BillPaid.php +++ b/Helper/WebHookHandlers/BillPaid.php @@ -112,8 +112,8 @@ public function createInvoice(\Magento\Sales\Model\Order $order, $isSubscription ); } else { $order->addCommentToStatusHistory( - __('The payment was confirmed and the order is beeing processed')->getText(), - $this->helperData->getStatusToOrderComplete() + __('The payment was confirmed and the order is being processed')->getText(), + $this->helperData->getStatusToPaidOrder() ); } diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 32c2a7ac..432d591f 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -101,6 +101,11 @@ Magento\Sales\Model\Config\Source\Order\Status + diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 5ba6073a..f93aaead 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -12,7 +12,7 @@ "Error while interpreting webhook 'bill_created'","Erro ao interpretar webhook 'bill_created'." "There is no cycle %s of signature %d.","Ainda não existe um pedido para ciclo %s da assinatura: %d." "Generating invoice for the order %s.","Gerando fatura para o pedido: %s." -"The payment was confirmed and the order is beeing processed","O pagamento foi confirmado e o pedido está sendo processado." +"The payment was confirmed and the order is being processed","O pagamento foi confirmado e o pedido está sendo processado." "Impossible to generate invoice for order %s.","Impossível gerar fatura para o pedido %s." "Invoice created with success","Fatura gerada com sucesso." "Order not found","Pedido não encontrado." @@ -132,3 +132,4 @@ "Pay up: %s","Pague até: %s" "Enabled document","Ativar documento" "When enabled, it will only be possible to finalize the order with the document informed when selecting the payment method. When disabled, the client will not be asked for the document, but it will still be necessary to send the document when creating the order in VINDI, otherwise it will be rejected by the API.","Quando habilitado, só será possível finalizar o pedido com o documento informado ao selecionar o método de pagamento. Quando desabilitado, não será solicitado ao cliente o documento, porém ainda será necessário o envio do documento ao criar o pedido na VINDI, caso contrário será rejeitado pela API." +"Paid Order Status","Status de Pedido Pago" From b1c7393a350def3ea55b37462b25586e6b3127d8 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Tue, 16 Jan 2024 09:05:42 -0300 Subject: [PATCH 006/187] fix: get quote info from checkout session --- Model/ConfigProvider.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Model/ConfigProvider.php b/Model/ConfigProvider.php index e432ffac..70c5336f 100644 --- a/Model/ConfigProvider.php +++ b/Model/ConfigProvider.php @@ -5,7 +5,7 @@ use Magento\Bundle\Model\Product\Type; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Checkout\Model\Cart; +use Magento\Checkout\Model\Session as CheckoutSession; use Magento\Checkout\Model\ConfigProviderInterface; use Magento\Directory\Model\Currency; use Magento\Framework\View\Asset\Source; @@ -28,10 +28,12 @@ class ConfigProvider implements ConfigProviderInterface * @var Source */ private $assetSource; + /** - * @var Cart + * @var CheckoutSession */ - private $cart; + private $checkoutSession; + /** * @var Currency */ @@ -50,7 +52,7 @@ class ConfigProvider implements ConfigProviderInterface * @param CcConfig $ccConfig * @param Source $assetSource * @param Data $data - * @param Cart $cart + * @param CheckoutSession $checkoutSession * @param Currency $currency * @param PaymentMethod $paymentMethod * @param ProductRepositoryInterface $productRepository @@ -59,7 +61,7 @@ public function __construct( CcConfig $ccConfig, Source $assetSource, Data $data, - Cart $cart, + CheckoutSession $checkoutSession, Currency $currency, PaymentMethod $paymentMethod, ProductRepositoryInterface $productRepository @@ -68,7 +70,7 @@ public function __construct( $this->ccConfig = $ccConfig; $this->assetSource = $assetSource; $this->helperData = $data; - $this->cart = $cart; + $this->checkoutSession = $checkoutSession; $this->currency = $currency; $this->paymentMethod = $paymentMethod; $this->productRepository = $productRepository; @@ -103,7 +105,7 @@ public function getInstallments() $maxInstallmentsNumber = $this->helperData->getMaxInstallments(); $minInstallmentsValue = $this->helperData->getMinInstallmentsValue(); - $quote = $this->cart->getQuote(); + $quote = $this->checkoutSession->getQuote(); $installments = []; if ($this->hasPlanInCart()) { @@ -142,7 +144,7 @@ public function getInstallments() */ private function hasPlanInCart() { - $quote = $this->cart->getQuote(); + $quote = $this->checkoutSession->getQuote(); foreach ($quote->getAllItems() as $item) { if ($this->helperData->isVindiPlan($item->getProductId())) { return true; @@ -158,7 +160,7 @@ private function hasPlanInCart() private function planIntervalCountMaxInstallments() { $intervalCount = 0; - $quote = $this->cart->getQuote(); + $quote = $this->checkoutSession->getQuote(); foreach ($quote->getAllItems() as $item) { if ($item->getProductType() != Type::TYPE_CODE) { From dcb020b3c29fe4331e9e9a8f5742186e7b78e6dd Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Tue, 16 Jan 2024 10:03:48 -0300 Subject: [PATCH 007/187] fix: update order state when invoice is created --- Helper/Data.php | 32 ++++++++++++++++++++++++++++- Helper/WebHookHandlers/BillPaid.php | 9 +++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index 1a55c435..37c2165f 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -8,6 +8,7 @@ use Magento\Framework\App\Helper\Context; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Sales\Model\Order; +use Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory; use Vindi\Payment\Model\Config\Source\Mode; use Vindi\Payment\Setup\UpgradeData; @@ -23,22 +24,31 @@ class Data extends AbstractHelper */ private $productRepository; + /** + * @var CollectionFactory + */ + private $orderStatusCollectionFactory; + + /** * Data constructor. * @param Context $context * @param AttributeSetRepositoryInterface $attributeSetRepository * @param ProductRepositoryInterface $productRepository + * @param CollectionFactory $orderStatusCollectionFactory */ public function __construct( Context $context, AttributeSetRepositoryInterface $attributeSetRepository, - ProductRepositoryInterface $productRepository + ProductRepositoryInterface $productRepository, + CollectionFactory $orderStatusCollectionFactory ) { $this->scopeConfig = $context->getScopeConfig(); parent::__construct($context); $this->attributeSetRepository = $attributeSetRepository; $this->productRepository = $productRepository; + $this->orderStatusCollectionFactory = $orderStatusCollectionFactory; } public function getCreditCardConfig($field, $group = 'vindi') @@ -103,6 +113,26 @@ public function getStatusToOrderComplete() return $status ? : Order::STATE_PROCESSING; } + /** + * @param $status + * @return string + */ + public function getStatusState($status) + { + if ($status) { + $statuses = $this->orderStatusCollectionFactory + ->create() + ->joinStates() + ->addFieldToFilter('main_table.status', $status); + + if ($statuses->getSize()) { + return $statuses->getFirstItem()->getState(); + } + } + + return ''; + } + public function getBaseUrl() { if ($this->getMode() == Mode::PRODUCTION_MODE) { diff --git a/Helper/WebHookHandlers/BillPaid.php b/Helper/WebHookHandlers/BillPaid.php index 6df6769b..5f1a7dda 100644 --- a/Helper/WebHookHandlers/BillPaid.php +++ b/Helper/WebHookHandlers/BillPaid.php @@ -101,6 +101,7 @@ public function createInvoice(\Magento\Sales\Model\Order $order, $isSubscription $invoice = $order->prepareInvoice(); $invoice->setRequestedCaptureCase(Invoice::CAPTURE_OFFLINE); $invoice->register(); + $invoice->pay(); $invoice->setSendEmail(true); $this->invoiceRepository->save($invoice); $this->logger->info(__('Invoice created with success')); @@ -111,9 +112,15 @@ public function createInvoice(\Magento\Sales\Model\Order $order, $isSubscription \Magento\Sales\Model\Order::STATE_PROCESSING ); } else { + $status = $this->helperData->getStatusToPaidOrder(); + + if ($state = $this->helperData->getStatusState($status)) { + $order->setState($state); + } + $order->addCommentToStatusHistory( __('The payment was confirmed and the order is being processed')->getText(), - $this->helperData->getStatusToPaidOrder() + $status ); } From f6ca2bb48015fa13430ec28567f6763e808d55ed Mon Sep 17 00:00:00 2001 From: Andre Manoel Date: Tue, 16 Jan 2024 16:37:04 -0300 Subject: [PATCH 008/187] Tratado pedidos de assinatura sem o atributo bill - aplicado para Pix e Boleto --- Block/Info/Pix.php | 15 +++++++++++++-- Model/Payment/AbstractMethod.php | 10 +++++++--- view/adminhtml/templates/info/pix.phtml | 16 +++++++++------- view/frontend/templates/info/pix.phtml | 16 +++++++++------- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/Block/Info/Pix.php b/Block/Info/Pix.php index ff832945..05eabf91 100644 --- a/Block/Info/Pix.php +++ b/Block/Info/Pix.php @@ -101,7 +101,13 @@ public function getReorderUrl() public function canShowPixInfo() { $paymentMethod = $this->getOrder()->getPayment()->getMethod() === \Vindi\Payment\Model\Payment\Pix::CODE; - $timestampMaxDays = strtotime($this->getMaxDaysToPayment()); + $daysToPayment = $this->getMaxDaysToPayment(); + + if (!$daysToPayment) { + return true; + } + + $timestampMaxDays = strtotime($daysToPayment); return $paymentMethod && $this->isValidToPayment($timestampMaxDays); } @@ -139,7 +145,12 @@ public function getQrcodeOriginalPath() */ public function getDaysToKeepWaitingPayment() { - $timestampMaxDays = strtotime($this->getMaxDaysToPayment()); + $daysToPayment = $this->getMaxDaysToPayment(); + if (!$daysToPayment) { + return null; + } + + $timestampMaxDays = strtotime($daysToPayment); return date('d/m/Y H:m:s', $timestampMaxDays); } diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 4c1dd9f3..01031e6e 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -324,16 +324,20 @@ private function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterf if ($responseData = $this->subscriptionRepository->create($body)) { $bill = $responseData['bill']; $subscription = $responseData['subscription']; - $this->handleBankSplitAdditionalInformation($payment, $body, $bill); - if ($this->successfullyPaid($body, $bill, $subscription)) { + if ($bill) { $this->handleBankSplitAdditionalInformation($payment, $body, $bill); + } + if ($this->successfullyPaid($body, $bill, $subscription)) { + if ($bill) { + $this->handleBankSplitAdditionalInformation($payment, $body, $bill); + } $billId = $bill['id'] ?? 0; $order->setVindiBillId($billId); $order->setVindiSubscriptionId($responseData['subscription']['id']); return $billId; } - $this->subscriptionRepository->deleteAndCancelBills($responseData['subscription']['id']); + $this->subscriptionRepository->deleteAndCancelBills($subscription['id']); } return $this->handleError($order); diff --git a/view/adminhtml/templates/info/pix.phtml b/view/adminhtml/templates/info/pix.phtml index ad485c8d..4ee860e8 100644 --- a/view/adminhtml/templates/info/pix.phtml +++ b/view/adminhtml/templates/info/pix.phtml @@ -13,13 +13,15 @@ use Vindi\Payment\Block\Info\Pix; canShowPixInfo()) : ?> -
-

- - getDaysToKeepWaitingPayment())?> - -

-
+ getDaysToKeepWaitingPayment()) : ?> +
+

+ + getDaysToKeepWaitingPayment())?> + +

+
+

diff --git a/view/frontend/templates/info/pix.phtml b/view/frontend/templates/info/pix.phtml index bd7c6159..64693f8a 100644 --- a/view/frontend/templates/info/pix.phtml +++ b/view/frontend/templates/info/pix.phtml @@ -13,13 +13,15 @@ use Vindi\Payment\Block\Info\Pix; canShowPixInfo()) : ?> -

-

- - getDaysToKeepWaitingPayment())?> - -

-
+ getDaysToKeepWaitingPayment()) : ?> +
+

+ + getDaysToKeepWaitingPayment())?> + +

+
+

From d84e50fa0988175778796a2462a0ffb1e10146ed Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Wed, 17 Jan 2024 10:59:08 -0300 Subject: [PATCH 009/187] fix: display installments with shipping amount --- Model/ConfigProvider.php | 46 +--------- .../view/payment/method-renderer/vindi-cc.js | 92 ++++++++++++++++--- .../web/template/payment/cc-form.html | 4 +- 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/Model/ConfigProvider.php b/Model/ConfigProvider.php index 70c5336f..64c0d65f 100644 --- a/Model/ConfigProvider.php +++ b/Model/ConfigProvider.php @@ -93,52 +93,16 @@ public function getConfig() 'months' => [$this->_methodCode => $this->ccConfig->getCcMonths()], 'years' => [$this->_methodCode => $this->ccConfig->getCcYears()], 'hasVerification' => [$this->_methodCode => $this->ccConfig->hasVerification()], - 'installments' => [$this->_methodCode => $this->getInstallments()], + 'isInstallmentsAllowedInStore' => (int) $this->helperData->isInstallmentsAllowedInStore(), + 'maxInstallments' => (int) $this->helperData->getMaxInstallments() ?: 1, + 'minInstallmentsValue' => (int) $this->helperData->getMinInstallmentsValue(), + 'hasPlanInCart' => (int) $this->hasPlanInCart(), + 'planIntervalCountMaxInstallments' => (int) $this->planIntervalCountMaxInstallments() ] ] ]; } - public function getInstallments() - { - $allowInstallments = $this->helperData->isInstallmentsAllowedInStore(); - $maxInstallmentsNumber = $this->helperData->getMaxInstallments(); - $minInstallmentsValue = $this->helperData->getMinInstallmentsValue(); - - $quote = $this->checkoutSession->getQuote(); - $installments = []; - - if ($this->hasPlanInCart()) { - $planInterval = $this->planIntervalCountMaxInstallments(); - if ($planInterval < $maxInstallmentsNumber) { - $maxInstallmentsNumber = $planInterval; - } - } - - if ($maxInstallmentsNumber > 1 && $allowInstallments == true) { - $total = $quote->getGrandTotal(); - $installmentsTimes = floor($total / $minInstallmentsValue); - - for ($i = 1; $i <= $maxInstallmentsNumber; $i++) { - $value = ceil($total / $i * 100) / 100; - $price = $this->currency->format($value, null, null, false); - $installments[$i] = $i . " de " . $price; - if (($i + 1) > $installmentsTimes) { - break; - } - } - } else { - $installments[1] = 1 . " de " . $this->currency->format( - $quote->getGrandTotal(), - null, - null, - false - ); - } - - return $installments; - } - /** * @return bool */ diff --git a/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js b/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js index 021c1e86..f266826d 100644 --- a/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js +++ b/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js @@ -1,14 +1,33 @@ -define( - [ +define([ 'underscore', + 'ko', 'Magento_Checkout/js/view/payment/default', 'Magento_Payment/js/model/credit-card-validation/credit-card-data', 'Magento_Payment/js/model/credit-card-validation/credit-card-number-validator', + 'Magento_Checkout/js/model/quote', + 'Magento_Checkout/js/model/totals', + 'Magento_SalesRule/js/action/set-coupon-code', + 'Magento_SalesRule/js/action/cancel-coupon', + 'Magento_Catalog/js/price-utils', 'mage/translate', 'jquery', 'mageUtils' ], - function (_, Component, creditCardData, cardNumberValidator, $t, $, utils) { + function ( + _, + ko, + Component, + creditCardData, + cardNumberValidator, + quote, + totals, + setCouponCodeAction, + cancelCouponCodeAction, + priceUtils, + $t, + $, + utils + ) { 'use strict'; return Component.extend({ @@ -23,7 +42,8 @@ define( creditCardSsStartYear: '', creditCardVerificationNumber: '', selectedCardType: null, - selectedInstallments: null + selectedInstallments: null, + creditCardInstallments: ko.observableArray([]) }, getData: function () { var data = { @@ -44,6 +64,8 @@ define( return data; }, initObservable: function () { + var self = this; + this._super() .observe([ 'creditCardType', @@ -58,7 +80,16 @@ define( 'selectedInstallments' ]); return this; + + setCouponCodeAction.registerSuccessCallback(function () { + self.updateInstallments(); + }); + + cancelCouponCodeAction.registerSuccessCallback(function () { + self.updateInstallments(); + }); }, + validate: function () { if (!this.selectedCardType() || this.selectedCardType() == '') { this.messageContainer.addErrorMessage({'message': $t('Please enter the Credit Card Type.')}); @@ -96,6 +127,8 @@ define( var self = this; this._super(); + self.updateInstallments(); + //Set credit card number to credit card data object this.creditCardNumber.subscribe(function (value) { var result; @@ -158,9 +191,6 @@ define( hasVerification: function () { return window.checkoutConfig.payment.vindi_cc.hasVerification['vindi_cc']; }, - getCcInstallments: function () { - return window.checkoutConfig.payment.vindi_cc.installments['vindi_cc']; - }, getCcAvailableTypesValues: function () { return _.map(this.getCcAvailableTypes(), function (value, key) { @@ -194,14 +224,50 @@ define( } }); }, - getCcInstallmentsAvailable: function () { - return _.map(this.getCcInstallments(), function (value, key) { - return { - 'value': key, - 'text': value + updateInstallments: function () { + let ccCheckoutConfig = window.checkoutConfig.payment.vindi_cc; + let installments = []; + + if (ccCheckoutConfig) { + let allowInstallments = ccCheckoutConfig.isInstallmentsAllowedInStore; + let maxInstallmentsNumber = ccCheckoutConfig.maxInstallments; + let minInstallmentsValue = ccCheckoutConfig.minInstallmentsValue; + + + if (ccCheckoutConfig.hasPlanInCart) { + let planInterval = ccCheckoutConfig.planIntervalCountMaxInstallments; + if (planInterval < maxInstallmentsNumber) { + maxInstallmentsNumber = planInterval; + } } - }); + + let grandTotal = totals.getSegment('grand_total').value; + if (maxInstallmentsNumber > 1 && allowInstallments == true) { + let installmentsTimes = Math.floor(grandTotal / minInstallmentsValue); + + for (let i = 1; i <= maxInstallmentsNumber; i++) { + let value = Math.ceil((grandTotal / i) * 100) / 100; + installments.push({ + 'value': i, + 'text': `${i} de ${this.getFormattedPrice(value)}` + }); + + if (i + 1 > installmentsTimes) { + break; + } + } + } else { + installments.push({ + 'value': 1, + 'text': `1 de ${this.getFormattedPrice(grandTotal)}` + }); + } + } + this.creditCardInstallments(installments); }, + getFormattedPrice: function (price) { + return priceUtils.formatPrice(price, quote.getPriceFormat()); + } }); } ); diff --git a/view/frontend/web/template/payment/cc-form.html b/view/frontend/web/template/payment/cc-form.html index 83226d16..c8d054ea 100644 --- a/view/frontend/web/template/payment/cc-form.html +++ b/view/frontend/web/template/payment/cc-form.html @@ -146,7 +146,7 @@ class="select credit-card-installments" data-bind="attr: {id: getCode() + '_installments', 'data-container': getCode() + '-installments', 'data-validate': JSON.stringify({required:true, 'validate-cc-exp':'#' + getCode() + '_expiration_yr'})}, enable: isActive($parents), - options: getCcInstallmentsAvailable(), + options: creditCardInstallments, optionsValue: 'value', optionsText: 'text', optionsCaption: $t('Installments'), @@ -159,4 +159,4 @@ - \ No newline at end of file + From e325370bb4fd21aecae970c7000b13c590ea0732 Mon Sep 17 00:00:00 2001 From: Andre Manoel Date: Thu, 18 Jan 2024 09:36:41 -0300 Subject: [PATCH 010/187] =?UTF-8?q?Bloqueado=20Pix=20e=20Boleto=20para=20a?= =?UTF-8?q?ssinaturas=20com=20faturamento=20ap=C3=B3s=20a=20data=20do=20pe?= =?UTF-8?q?dido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Helper/Data.php | 25 ++++++++++++++++++++----- Model/Payment/AbstractMethod.php | 23 +++++++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index 1a55c435..6f95d63f 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -4,7 +4,7 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Eav\Api\AttributeSetRepositoryInterface; -use \Magento\Framework\App\Helper\AbstractHelper; +use Magento\Framework\App\Helper\AbstractHelper; use Magento\Framework\App\Helper\Context; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Sales\Model\Order; @@ -100,7 +100,7 @@ public function getStatusToOrderComplete() { $status = $this->getModuleGeneralConfig('order_status'); - return $status ? : Order::STATE_PROCESSING; + return $status ?: Order::STATE_PROCESSING; } public function getBaseUrl() @@ -117,10 +117,15 @@ public function getBaseUrl() */ public static function sanitizeItemSku($code) { - return strtolower( preg_replace("[^a-zA-Z0-9-]", "-", - strtr(utf8_decode(trim(preg_replace('/[ -]+/' , '-' , $code))), + return strtolower(preg_replace( + "[^a-zA-Z0-9-]", + "-", + strtr( + utf8_decode(trim(preg_replace('/[ -]+/', '-', $code))), utf8_decode("áàãâéêíóôõúüñçÁÀÃÂÉÊÍÓÔÕÚÜÑÇ"), - "aaaaeeiooouuncAAAAEEIOOOUUNC-"))); + "aaaaeeiooouuncAAAAEEIOOOUUNC-" + ) + )); } /** @@ -134,4 +139,14 @@ public function isVindiPlan($productId) $attrSet = $this->attributeSetRepository->get($product->getAttributeSetId()); return $attrSet->getAttributeSetName() == UpgradeData::VINDI_PLANOS; } + + /** + * @param $productId + * @return \Magento\Catalog\Api\Data\ProductInterface + * @throws NoSuchEntityException + */ + public function getProductById($productId) + { + return $this->productRepository->getById($productId); + } } diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 01031e6e..8e217ef2 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -15,6 +15,7 @@ use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Payment\Helper\Data; use Magento\Payment\Model\InfoInterface; +use Magento\Payment\Model\Method\AbstractMethod as OriginAbstractMethod; use Magento\Payment\Model\Method\Logger; use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Model\Order; @@ -23,7 +24,6 @@ use Vindi\Payment\Api\PlanManagementInterface; use Vindi\Payment\Api\ProductManagementInterface; use Vindi\Payment\Api\SubscriptionInterface; -use Magento\Payment\Model\Method\AbstractMethod as OriginAbstractMethod; use Vindi\Payment\Helper\Api; /** @@ -179,9 +179,20 @@ abstract protected function getPaymentMethodCode(); * * @param \Magento\Quote\Api\Data\CartInterface|null $quote * @return bool + * @throws NoSuchEntityException */ public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null) { + if ($this->getPaymentMethodCode() == PaymentMethod::BANK_SLIP || $this->getPaymentMethodCode() == PaymentMethod::PIX) { + foreach ($quote->getItems() as $item) { + if ($this->helperData->isVindiPlan($item->getProductId())) { + $product = $this->helperData->getProductById($item->getProductId()); + if ($product->getData('vindi_billing_trigger_day') > 0) { + return false; + } + } + } + } return parent::isAvailable($quote); } @@ -429,7 +440,7 @@ protected function isValidPaymentMethodCode($paymentMethodCode) PaymentMethod::DEBIT_CARD ]; - return in_array($paymentMethodCode , $paymentMethodsCode); + return in_array($paymentMethodCode, $paymentMethodsCode); } /** @@ -439,7 +450,9 @@ protected function isValidPaymentMethodCode($paymentMethodCode) */ protected function isWaitingPaymentMethodResponse($bill) { - if (!$bill) return false; + if (!$bill) { + return false; + } return reset($bill['charges'])['last_transaction']['status'] === Bill::WAITING_STATUS; } @@ -451,7 +464,9 @@ protected function isWaitingPaymentMethodResponse($bill) */ protected function isValidStatus($bill) { - if (!$bill) return false; + if (!$bill) { + return false; + } $billStatus = [ Bill::PAID_STATUS, From bd09ca821994840d4b1dc9f7622e64045b7541e3 Mon Sep 17 00:00:00 2001 From: Andre Manoel Date: Thu, 18 Jan 2024 12:38:49 -0300 Subject: [PATCH 011/187] =?UTF-8?q?Bloqueado=20Pix=20e=20Boleto=20tamb?= =?UTF-8?q?=C3=A9m=20para=20assinaturas=20com=20cobran=C3=A7a=20Termino=20?= =?UTF-8?q?do=20Periodo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Model/Payment/AbstractMethod.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 8e217ef2..a1d531b5 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -187,7 +187,8 @@ public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null) foreach ($quote->getItems() as $item) { if ($this->helperData->isVindiPlan($item->getProductId())) { $product = $this->helperData->getProductById($item->getProductId()); - if ($product->getData('vindi_billing_trigger_day') > 0) { + if ($product->getData('vindi_billing_trigger_day') > 0 || + $product->getData('vindi_billing_trigger_type') == 'end_of_period') { return false; } } From 284435ba6730d92621719b81cad77e26248b7a30 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Mon, 22 Jan 2024 10:17:19 -0300 Subject: [PATCH 012/187] feat: added version 1.4.0 --- composer.json | 2 +- etc/module.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 634914c1..2fdd6b81 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "vindi/vindi-magento2", "description": "Módulo de cobrança Vindi para o Magento 2", "type": "magento2-module", - "version": "1.3.0", + "version": "1.4.0", "license": "GPL-3.0", "authors": [ { diff --git a/etc/module.xml b/etc/module.xml index c5257083..45ced32c 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,7 +1,7 @@ - + From 8b31826309c0f50f3aa2e7618b1f6b672df73c94 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 2 Feb 2024 16:50:41 -0300 Subject: [PATCH 013/187] fix: change file identation --- view/adminhtml/templates/info/pix.phtml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/view/adminhtml/templates/info/pix.phtml b/view/adminhtml/templates/info/pix.phtml index 4ee860e8..18cd7783 100644 --- a/view/adminhtml/templates/info/pix.phtml +++ b/view/adminhtml/templates/info/pix.phtml @@ -13,15 +13,15 @@ use Vindi\Payment\Block\Info\Pix; canShowPixInfo()) : ?> - getDaysToKeepWaitingPayment()) : ?> + getDaysToKeepWaitingPayment()) : ?>

- getDaysToKeepWaitingPayment())?> + getDaysToKeepWaitingPayment()) ?>

- +

@@ -52,15 +52,17 @@ use Vindi\Payment\Block\Info\Pix; "vindiPix": { "component": "Vindi_Payment/js/view/info/vindi-pix", "qrCodeKey": getQrcodeOriginalPath() ?> - } - } - } - } } + } +} +} +} - +
- + + + From 122b380ed9e4d27ce62f35d2290d0606bf30ce7e Mon Sep 17 00:00:00 2001 From: Contardi Date: Sat, 3 Feb 2024 08:10:47 -0300 Subject: [PATCH 014/187] fix: identation --- view/adminhtml/templates/info/pix.phtml | 71 ++++++++++++------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/view/adminhtml/templates/info/pix.phtml b/view/adminhtml/templates/info/pix.phtml index 18cd7783..7a11fd9b 100644 --- a/view/adminhtml/templates/info/pix.phtml +++ b/view/adminhtml/templates/info/pix.phtml @@ -1,11 +1,8 @@ -

@@ -13,36 +10,35 @@ use Vindi\Payment\Block\Info\Pix;
canShowPixInfo()) : ?> - getDaysToKeepWaitingPayment()) : ?> -
-

- - getDaysToKeepWaitingPayment()) ?> - -

-
- -
- -

- getQrCodeWarningMessage() ?> -

- -
- -
- -
+ getDaysToKeepWaitingPayment()) : ?> +
+

+ + getDaysToKeepWaitingPayment()) ?> + +

+
+ +
+ +

+ getQrCodeWarningMessage() ?> +

+ +
-
-
+
+ +
+ + -
+ +
From bcdb44a2b892daccc69061498388d91f25a57aa4 Mon Sep 17 00:00:00 2001 From: Contardi Date: Sat, 3 Feb 2024 08:17:10 -0300 Subject: [PATCH 015/187] fix: wrong html tag --- view/adminhtml/templates/info/pix.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/adminhtml/templates/info/pix.phtml b/view/adminhtml/templates/info/pix.phtml index 7a11fd9b..931176e1 100644 --- a/view/adminhtml/templates/info/pix.phtml +++ b/view/adminhtml/templates/info/pix.phtml @@ -58,7 +58,7 @@ use Vindi\Payment\Block\Info\Pix; -
+
From 6dfd3cb56634b73088771055cf135e625675e6ea Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Wed, 7 Feb 2024 19:19:53 +0000 Subject: [PATCH 016/187] feat: create vindi_plans table, models and API --- Api/Data/VindiPlanInterface.php | 96 +++++++++++++ Api/Data/VindiPlanSearchResultInterface.php | 25 ++++ Api/VindiPlanRepositoryInterface.php | 39 +++++ Model/ResourceModel/VindiPlan.php | 62 ++++++++ Model/ResourceModel/VindiPlan/Collection.php | 44 ++++++ Model/VindiPlan.php | 113 +++++++++++++++ Model/VindiPlanRepository.php | 141 +++++++++++++++++++ Model/VindiPlanSearchResult.php | 17 +++ etc/db_schema.xml | 18 +++ etc/di.xml | 21 +++ 10 files changed, 576 insertions(+) create mode 100755 Api/Data/VindiPlanInterface.php create mode 100755 Api/Data/VindiPlanSearchResultInterface.php create mode 100755 Api/VindiPlanRepositoryInterface.php create mode 100755 Model/ResourceModel/VindiPlan.php create mode 100755 Model/ResourceModel/VindiPlan/Collection.php create mode 100755 Model/VindiPlan.php create mode 100755 Model/VindiPlanRepository.php create mode 100755 Model/VindiPlanSearchResult.php diff --git a/Api/Data/VindiPlanInterface.php b/Api/Data/VindiPlanInterface.php new file mode 100755 index 00000000..e96fe81a --- /dev/null +++ b/Api/Data/VindiPlanInterface.php @@ -0,0 +1,96 @@ + + */ +interface VindiPlanInterface +{ + /** + * Constants for keys of data array. + */ + const ENTITY_ID = 'entity_id'; + const NAME = 'name'; + const STATUS = 'status'; + const CREATED_AT = 'created_at'; + const UPDATED_AT = 'updated_at'; + + /** + * Get entity_id + * + * @return int|null + */ + public function getId(); + + /** + * Set $entityId + * + * @param int $entityId + * @return $this + */ + public function setId($entityId); + + /** + * Get name + * + * @return string|null + */ + public function getName(); + + /** + * Set $name + * + * @param string $name + * @return $this + */ + public function setName($name); + + /** + * Get status + * + * @return int|null + */ + public function getStatus(); + + /** + * Set $status + * + * @param int $status + * @return $this + */ + public function setStatus($status); + + /** + * Get created_at + * + * @return string|null + */ + public function getCreatedAt(); + + /** + * Set $createdAt + * + * @param string $createdAt + * @return $this + */ + public function setCreatedAt($createdAt); + + /** + * Get updated_at + * + * @return string|null + */ + public function getUpdatedAt(); + + /** + * Set $updatedAt + * + * @param string $updatedAt + * @return $this + */ + public function setUpdatedAt($updatedAt); +} diff --git a/Api/Data/VindiPlanSearchResultInterface.php b/Api/Data/VindiPlanSearchResultInterface.php new file mode 100755 index 00000000..a9fb5f96 --- /dev/null +++ b/Api/Data/VindiPlanSearchResultInterface.php @@ -0,0 +1,25 @@ + + */ +interface VindiPlanSearchResultInterface extends SearchResultsInterface +{ + /** + * @return VindiPlanInterface[] + */ + public function getItems(); + + /** + * @param VindiPlanInterface[] $items + * @return void + */ + public function setItems(array $items); +} diff --git a/Api/VindiPlanRepositoryInterface.php b/Api/VindiPlanRepositoryInterface.php new file mode 100755 index 00000000..1d6d9122 --- /dev/null +++ b/Api/VindiPlanRepositoryInterface.php @@ -0,0 +1,39 @@ + + */ +interface VindiPlanRepositoryInterface +{ + /** + * @param int $entityId + * @return VindiPlanInterface + */ + public function getById(int $entityId): VindiPlanInterface; + + /** + * @param SearchCriteriaInterface $searchCriteria + * @return VindiPlanSearchResultInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria): VindiPlanSearchResultInterface; + + /** + * @param VindiPlanInterface $vindiplan + */ + public function save(VindiPlanInterface $vindiplan): void; + + /** + * @param VindiPlanInterface $vindiplan + */ + public function delete(VindiPlanInterface $vindiplan): void; +} diff --git a/Model/ResourceModel/VindiPlan.php b/Model/ResourceModel/VindiPlan.php new file mode 100755 index 00000000..986f2e6e --- /dev/null +++ b/Model/ResourceModel/VindiPlan.php @@ -0,0 +1,62 @@ + + */ +class VindiPlan extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + /** + * @var VindiPlanFactory + */ + protected $vindiplanFactory; + + /** + * VindiPlan constructor. + * @param Context $context + * @param VindiPlanFactory $vindiplanFactory + * @param ReadFactory $readFactory + * @param Filesystem $filesystem + */ + public function __construct( + Context $context, + VindiPlanFactory $vindiplanFactory, + ReadFactory $readFactory, + Filesystem $filesystem + ) { + $this->vindiplanFactory = $vindiplanFactory; + $this->readFactory = $readFactory; + $this->filesystem = $filesystem; + parent::__construct($context); + } + + protected function _construct() + { + $this->_init('vindi_plans', 'entity_id'); + } + + /** + * @param $id + * @return \Vindi\Payment\Model\VindiPlan + * @throws NoSuchEntityException + */ + public function getById($id) + { + $vindiplan = $this->vindiplanFactory->create(); + $this->load($vindiplan, $id); + + if (!$vindiplan->getId()) { + throw new NoSuchEntityException(__('VindiPlan with id "%1" does not exist.', $id)); + } + + return $vindiplan; + } +} diff --git a/Model/ResourceModel/VindiPlan/Collection.php b/Model/ResourceModel/VindiPlan/Collection.php new file mode 100755 index 00000000..fdf68cd9 --- /dev/null +++ b/Model/ResourceModel/VindiPlan/Collection.php @@ -0,0 +1,44 @@ + + */ +class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection +{ + protected $_idFieldName = 'entity_id'; + protected $_eventPrefix = 'vindi_payment_vindiplans_collection'; + protected $_eventObject = 'vindiplan_collection'; + + /** + * @var array|null + */ + protected $_options; + + /** + * Define resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('Vindi\Payment\Model\VindiPlan', 'Vindi\Payment\Model\ResourceModel\VindiPlan'); + } + + public function toOptionArray() + { + $collection = $this->getItems(); + $this->_options = [['label' => '', 'value' => '']]; + + foreach ($collection as $vindiplan) { + $this->_options[] = [ + 'label' => __($vindiplan->getName()), + 'value' => $vindiplan->getId() + ]; + } + + return $this->_options; + } +} diff --git a/Model/VindiPlan.php b/Model/VindiPlan.php new file mode 100755 index 00000000..2168c11b --- /dev/null +++ b/Model/VindiPlan.php @@ -0,0 +1,113 @@ + + */ +class VindiPlan extends \Magento\Framework\Model\AbstractModel implements VindiPlanInterface +{ + /** + * @return void + */ + protected function _construct() + { + $this->_init('Vindi\Payment\Model\ResourceModel\VindiPlan'); + } + + /** + * @return string[] + */ + public function getIdentities() + { + return [self::CACHE_TAG . '_' . $this->getId()]; + } + + /** + * @return array|int|mixed|null + */ + public function getId() + { + return $this->getData(self::ENTITY_ID); + } + + /** + * @param $entityId + * @return VindiPlan|void + */ + public function setId($entityId) + { + $this->setData(self::ENTITY_ID, $entityId); + } + + /** + * @return array|mixed|string|null + */ + public function getName() + { + return $this->getData(self::NAME); + } + + /** + * @param $name + * @return VindiPlan|void + */ + public function setName($name) + { + $this->setData(self::NAME, $name); + } + + /** + * @return array|int|mixed|null + */ + public function getStatus() + { + return $this->getData(self::STATUS); + } + + /** + * @param $status + * @return VindiPlan|void + */ + public function setStatus($status) + { + $this->setData(self::STATUS, $status); + } + + /** + * @return array|mixed|string|null + */ + public function getCreatedAt() + { + return $this->getData(self::CREATED_AT); + } + + /** + * @param $createAt + * @return VindiPlan|void + */ + public function setCreatedAt($createAt) + { + $this->setData(self::CREATED_AT, $createAt); + } + + /** + * @return array|mixed|string|null + */ + public function getUpdatedAt() + { + return $this->getData(self::UPDATED_AT); + } + + /** + * @param $updatedAt + * @return VindiPlan|void + */ + public function setUpdatedAt($updatedAt) + { + $this->setData(self::UPDATED_AT, $updatedAt); + } +} diff --git a/Model/VindiPlanRepository.php b/Model/VindiPlanRepository.php new file mode 100755 index 00000000..45fc3fdc --- /dev/null +++ b/Model/VindiPlanRepository.php @@ -0,0 +1,141 @@ + + */ +class VindiPlanRepository implements VindiPlanRepositoryInterface +{ + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var ResourceModel + */ + private $resourceModel; + + /** + * @var VindiPlanInterfaceFactory + */ + private $vindiplanFactory; + + /** + * @var CollectionProcessorInterface + */ + private $collectionProcessor; + + /** + * @var VindiPlanSearchResultInterfaceFactory + */ + private $searchResultsFactory; + + /** + * VindiPlan constructor. + * @param ResourceModel $resourceModel + * @param VindiPlanInterfaceFactory $vindiplanFactory + * @param CollectionFactory $collectionFactory + * @param CollectionProcessorInterface $collectionProcessor + * @param VindiPlanSearchResultInterfaceFactory $searchResultsFactory + */ + public function __construct( + ResourceModel $resourceModel, + VindiPlanInterfaceFactory $vindiplanFactory, + CollectionFactory $collectionFactory, + CollectionProcessorInterface $collectionProcessor, + VindiPlanSearchResultInterfaceFactory $searchResultsFactory + ) { + $this->resourceModel = $resourceModel; + $this->vindiplanFactory = $vindiplanFactory; + $this->collectionFactory = $collectionFactory; + $this->collectionProcessor = $collectionProcessor; + $this->searchResultsFactory = $searchResultsFactory; + } + + /** + * @param int $entityId + * @return VindiPlanInterface + * @throws NoSuchEntityException + */ + public function getById(int $entityId): VindiPlanInterface + { + try { + /** @var VindiPlanInterface $vindiplan */ + $vindiplan = $this->vindiplanFactory->create(); + $this->resourceModel->load($vindiplan, $entityId); + } catch (\Exception $e) { + throw new NoSuchEntityException(__('Error during load vindiplan by Entity ID')); + } + return $vindiplan; + } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @return VindiPlanSearchResultInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria): VindiPlanSearchResultInterface + { + /** @var Collection $collection */ + $collection = $this->collectionFactory->create(); + $this->collectionProcessor->process($searchCriteria, $collection); + + /** @var VindiPlanSearchResultInterface $searchResult */ + $searchResult = $this->searchResultsFactory->create(); + + $searchResult->setItems($collection->getItems()) + ->setSearchCriteria($searchCriteria) + ->setTotalCount($collection->getSize()); + + return $searchResult; + } + + /** + * @param VindiPlanInterface $vindiplan + * @return void + * @throws CouldNotSaveException + */ + public function save(VindiPlanInterface $vindiplan): void + { + try { + $this->resourceModel->save($vindiplan); + } catch (\Exception $e) { + throw new CouldNotSaveException(__('Error when saving vindiplan')); + } + } + + /** + * @param VindiPlanInterface $vindiplan + * @return void + * @throws CouldNotDeleteException + */ + public function delete(VindiPlanInterface $vindiplan): void + { + try { + $this->resourceModel->delete($vindiplan); + } catch (\Exception $e) { + throw new CouldNotDeleteException(__('Could not delete vindiplan.')); + } + } +} diff --git a/Model/VindiPlanSearchResult.php b/Model/VindiPlanSearchResult.php new file mode 100755 index 00000000..71dcaba1 --- /dev/null +++ b/Model/VindiPlanSearchResult.php @@ -0,0 +1,17 @@ + + */ +class VindiPlanSearchResult extends SearchResults implements VindiPlanSearchResultInterface +{ +} diff --git a/etc/db_schema.xml b/etc/db_schema.xml index beff165f..efb7db86 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -14,4 +14,22 @@ + + + + + + + + + + + + + + + + + +
diff --git a/etc/di.xml b/etc/di.xml index 10ae0399..d3ad41be 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -34,4 +34,25 @@ + + + + + + + + + + Vindi\Payment\Model\ResourceModel\VindiPlan\Grid\Collection + Vindi\Payment\Model\ResourceModel\VindiPlan\Form\DataProvider + + + + + + + vindi_plans + Vindi\Payment\Model\ResourceModel\VindiPlan + + From fd89e7148d92027efc3115e126647d85e790ffd0 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Wed, 7 Feb 2024 19:25:32 +0000 Subject: [PATCH 017/187] feat: add Plans to the admin menu --- etc/adminhtml/menu.xml | 11 ++++++++++- i18n/pt_BR.csv | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/etc/adminhtml/menu.xml b/etc/adminhtml/menu.xml index edb9d02a..c64585a3 100644 --- a/etc/adminhtml/menu.xml +++ b/etc/adminhtml/menu.xml @@ -6,7 +6,16 @@ title="Vindi" module="Vindi_Payment" sortOrder="9999" - resource="Magento_Backend::content" /> + resource="Magento_Backend::content" + /> + Date: Fri, 9 Feb 2024 20:40:59 +0000 Subject: [PATCH 018/187] feat: adding import and creation of plans --- Api/Data/VindiPlanInterface.php | 109 +++------ Api/Data/VindiPlanItemInterface.php | 26 +++ .../VindiPlanItemSearchResultInterface.php | 25 ++ Api/VindiPlanItemRepositoryInterface.php | 39 ++++ Block/Adminhtml/VindiPlan/Edit/BackButton.php | 35 +++ .../Adminhtml/VindiPlan/Edit/DeleteButton.php | 41 ++++ .../VindiPlan/Edit/GenericButton.php | 64 ++++++ Block/Adminhtml/VindiPlan/Edit/SaveButton.php | 29 +++ .../VindiPlan/AbstractMassAction.php | 66 ++++++ Controller/Adminhtml/VindiPlan/Delete.php | 58 +++++ Controller/Adminhtml/VindiPlan/Edit.php | 58 +++++ .../Adminhtml/VindiPlan/ImportPlans.php | 107 +++++++++ Controller/Adminhtml/VindiPlan/Index.php | 39 ++++ Controller/Adminhtml/VindiPlan/MassDelete.php | 37 +++ Controller/Adminhtml/VindiPlan/MassStatus.php | 40 ++++ Controller/Adminhtml/VindiPlan/NewAction.php | 46 ++++ Controller/Adminhtml/VindiPlan/Save.php | 101 +++++++++ Model/Config/Source/Interval.php | 13 ++ Model/Config/Source/Plan/Status.php | 16 ++ Model/ResourceModel/VindiPlanItem.php | 62 +++++ .../VindiPlanItem/Collection.php | 44 ++++ Model/Vindi/Plan.php | 8 + Model/VindiPlan.php | 108 ++++++++- Model/VindiPlan/DataProvider.php | 94 ++++++++ Model/VindiPlanItem.php | 53 +++++ Model/VindiPlanItemRepository.php | 141 ++++++++++++ Model/VindiPlanItemSearchResult.php | 17 ++ Model/VindiPlanRepository.php | 48 ++++ .../Listing/Column/VindiPlan/Actions.php | 77 +++++++ Ui/Component/MassAction/Status/Options.php | 128 +++++++++++ etc/adminhtml/menu.xml | 4 +- etc/db_schema.xml | 14 ++ etc/di.xml | 21 ++ i18n/pt_BR.csv | 17 ++ .../layout/vindi_payment_vindiplan.xml | 5 + .../layout/vindi_payment_vindiplan_edit.xml | 9 + .../layout/vindi_payment_vindiplan_index.xml | 8 + .../vindi_payment_vindiplan_form.xml | 214 ++++++++++++++++++ .../vindi_payment_vindiplan_listing.xml | 193 ++++++++++++++++ 39 files changed, 2131 insertions(+), 83 deletions(-) create mode 100755 Api/Data/VindiPlanItemInterface.php create mode 100755 Api/Data/VindiPlanItemSearchResultInterface.php create mode 100755 Api/VindiPlanItemRepositoryInterface.php create mode 100755 Block/Adminhtml/VindiPlan/Edit/BackButton.php create mode 100755 Block/Adminhtml/VindiPlan/Edit/DeleteButton.php create mode 100755 Block/Adminhtml/VindiPlan/Edit/GenericButton.php create mode 100755 Block/Adminhtml/VindiPlan/Edit/SaveButton.php create mode 100755 Controller/Adminhtml/VindiPlan/AbstractMassAction.php create mode 100755 Controller/Adminhtml/VindiPlan/Delete.php create mode 100755 Controller/Adminhtml/VindiPlan/Edit.php create mode 100644 Controller/Adminhtml/VindiPlan/ImportPlans.php create mode 100755 Controller/Adminhtml/VindiPlan/Index.php create mode 100755 Controller/Adminhtml/VindiPlan/MassDelete.php create mode 100755 Controller/Adminhtml/VindiPlan/MassStatus.php create mode 100755 Controller/Adminhtml/VindiPlan/NewAction.php create mode 100755 Controller/Adminhtml/VindiPlan/Save.php create mode 100644 Model/Config/Source/Plan/Status.php create mode 100755 Model/ResourceModel/VindiPlanItem.php create mode 100755 Model/ResourceModel/VindiPlanItem/Collection.php create mode 100755 Model/VindiPlan/DataProvider.php create mode 100755 Model/VindiPlanItem.php create mode 100755 Model/VindiPlanItemRepository.php create mode 100755 Model/VindiPlanItemSearchResult.php create mode 100755 Ui/Component/Listing/Column/VindiPlan/Actions.php create mode 100755 Ui/Component/MassAction/Status/Options.php create mode 100755 view/adminhtml/layout/vindi_payment_vindiplan.xml create mode 100755 view/adminhtml/layout/vindi_payment_vindiplan_edit.xml create mode 100755 view/adminhtml/layout/vindi_payment_vindiplan_index.xml create mode 100755 view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml create mode 100755 view/adminhtml/ui_component/vindi_payment_vindiplan_listing.xml diff --git a/Api/Data/VindiPlanInterface.php b/Api/Data/VindiPlanInterface.php index e96fe81a..cfcb811b 100755 --- a/Api/Data/VindiPlanInterface.php +++ b/Api/Data/VindiPlanInterface.php @@ -10,87 +10,44 @@ */ interface VindiPlanInterface { - /** - * Constants for keys of data array. - */ - const ENTITY_ID = 'entity_id'; - const NAME = 'name'; - const STATUS = 'status'; - const CREATED_AT = 'created_at'; - const UPDATED_AT = 'updated_at'; + const ENTITY_ID = 'entity_id'; + const NAME = 'name'; + const INTERVAL = 'interval'; + const INTERVAL_COUNT = 'interval_count'; + const BILLING_TRIGGER_TYPE = 'billing_trigger_type'; + const BILLING_TRIGGER_DAY = 'billing_trigger_day'; + const BILLING_CYCLES = 'billing_cycles'; + const CODE = 'code'; + const DESCRIPTION = 'description'; + const INSTALLMENTS = 'installments'; + const INVOICE_SPLIT = 'invoice_split'; + const STATUS = 'status'; + const METADATA = 'metadata'; - /** - * Get entity_id - * - * @return int|null - */ public function getId(); - - /** - * Set $entityId - * - * @param int $entityId - * @return $this - */ public function setId($entityId); - - /** - * Get name - * - * @return string|null - */ public function getName(); - - /** - * Set $name - * - * @param string $name - * @return $this - */ public function setName($name); - - /** - * Get status - * - * @return int|null - */ + public function getInterval(); + public function setInterval($interval); + public function getIntervalCount(); + public function setIntervalCount($intervalCount); + public function getBillingTriggerType(); + public function setBillingTriggerType($billingTriggerType); + public function getBillingTriggerDay(); + public function setBillingTriggerDay($billingTriggerDay); + public function getBillingCycles(); + public function setBillingCycles($billingCycles); + public function getCode(); + public function setCode($code); + public function getDescription(); + public function setDescription($description); + public function getInstallments(); + public function setInstallments($installments); + public function getInvoiceSplit(); + public function setInvoiceSplit($invoiceSplit); public function getStatus(); - - /** - * Set $status - * - * @param int $status - * @return $this - */ public function setStatus($status); - - /** - * Get created_at - * - * @return string|null - */ - public function getCreatedAt(); - - /** - * Set $createdAt - * - * @param string $createdAt - * @return $this - */ - public function setCreatedAt($createdAt); - - /** - * Get updated_at - * - * @return string|null - */ - public function getUpdatedAt(); - - /** - * Set $updatedAt - * - * @param string $updatedAt - * @return $this - */ - public function setUpdatedAt($updatedAt); + public function getMetadata(); + public function setMetadata($metadata); } diff --git a/Api/Data/VindiPlanItemInterface.php b/Api/Data/VindiPlanItemInterface.php new file mode 100755 index 00000000..f0914d0d --- /dev/null +++ b/Api/Data/VindiPlanItemInterface.php @@ -0,0 +1,26 @@ + + */ +interface VindiPlanItemInterface +{ + const ENTITY_ID = 'entity_id'; + const PLAN_ID = 'plan_id'; + const PRODUCT_ID = 'product_id'; + const CYCLES = 'cycles'; + + public function getId(); + public function setId($entityId); + public function getPlanId(); + public function setPlanId($planId); + public function getProductId(); + public function setProductId($productId); + public function getCycles(); + public function setCycles($cycles); +} diff --git a/Api/Data/VindiPlanItemSearchResultInterface.php b/Api/Data/VindiPlanItemSearchResultInterface.php new file mode 100755 index 00000000..303847c8 --- /dev/null +++ b/Api/Data/VindiPlanItemSearchResultInterface.php @@ -0,0 +1,25 @@ + + */ +interface VindiPlanItemSearchResultInterface extends SearchResultsInterface +{ + /** + * @return VindiPlanItemInterface[] + */ + public function getItems(); + + /** + * @param VindiPlanItemInterface[] $items + * @return void + */ + public function setItems(array $items); +} diff --git a/Api/VindiPlanItemRepositoryInterface.php b/Api/VindiPlanItemRepositoryInterface.php new file mode 100755 index 00000000..67f58fd3 --- /dev/null +++ b/Api/VindiPlanItemRepositoryInterface.php @@ -0,0 +1,39 @@ + + */ +interface VindiPlanItemRepositoryInterface +{ + /** + * @param int $entityId + * @return VindiPlanItemInterface + */ + public function getById(int $entityId): VindiPlanItemInterface; + + /** + * @param SearchCriteriaInterface $searchCriteria + * @return VindiPlanItemSearchResultInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria): VindiPlanItemSearchResultInterface; + + /** + * @param VindiPlanItemInterface $vindiplanitem + */ + public function save(VindiPlanItemInterface $vindiplanitem): void; + + /** + * @param VindiPlanItemInterface $vindiplanitem + */ + public function delete(VindiPlanItemInterface $vindiplanitem): void; +} diff --git a/Block/Adminhtml/VindiPlan/Edit/BackButton.php b/Block/Adminhtml/VindiPlan/Edit/BackButton.php new file mode 100755 index 00000000..40b88f07 --- /dev/null +++ b/Block/Adminhtml/VindiPlan/Edit/BackButton.php @@ -0,0 +1,35 @@ + + */ +class BackButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Back'), + 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), + 'class' => 'back', + 'sort_order' => 10 + ]; + } + + /** + * Get URL for back (reset) button + * + * @return string + */ + public function getBackUrl() + { + return $this->getUrl('vindi_payment/vindiplan/index'); + } +} diff --git a/Block/Adminhtml/VindiPlan/Edit/DeleteButton.php b/Block/Adminhtml/VindiPlan/Edit/DeleteButton.php new file mode 100755 index 00000000..7d720c56 --- /dev/null +++ b/Block/Adminhtml/VindiPlan/Edit/DeleteButton.php @@ -0,0 +1,41 @@ + + */ +class DeleteButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @inheritDoc + */ + public function getButtonData() + { + $data = []; + if ($this->getCompanyId()) { + $data = [ + 'label' => __('Delete'), + 'class' => 'delete', + 'on_click' => 'deleteConfirm(\'' . __( + 'Are you sure you want to do this?' + ) . '\', \'' . $this->getDeleteUrl() . '\', {"data": {}})', + 'sort_order' => 20, + ]; + } + return $data; + } + + /** + * URL to send delete requests to. + * + * @return string + */ + public function getDeleteUrl() + { + return $this->getUrl('*/*/delete', ['entity_id' => $this->getCompanyId()]); + } +} diff --git a/Block/Adminhtml/VindiPlan/Edit/GenericButton.php b/Block/Adminhtml/VindiPlan/Edit/GenericButton.php new file mode 100755 index 00000000..d7ad4a19 --- /dev/null +++ b/Block/Adminhtml/VindiPlan/Edit/GenericButton.php @@ -0,0 +1,64 @@ + + */ +class GenericButton +{ + /** + * @var Context + */ + protected $context; + + /** + * @var BlockRepositoryInterface + */ + protected $blockRepository; + + /** + * @param Context $context + * @param BlockRepositoryInterface $blockRepository + */ + public function __construct( + Context $context, + BlockRepositoryInterface $blockRepository + ) { + $this->context = $context; + $this->blockRepository = $blockRepository; + } + + /** + * Return CMS block ID + * + * @return int|null + */ + public function getCompanyId() + { + try { + return $this->blockRepository->getById( + $this->context->getRequest()->getParam('entity_id') + )->getId(); + } catch (NoSuchEntityException $e) { + } + return null; + } + + /** + * Generate url by route and parameters + * + * @param string $route + * @param array $params + * @return string + */ + public function getUrl($route = '', $params = []) + { + return $this->context->getUrlBuilder()->getUrl($route, $params); + } +} diff --git a/Block/Adminhtml/VindiPlan/Edit/SaveButton.php b/Block/Adminhtml/VindiPlan/Edit/SaveButton.php new file mode 100755 index 00000000..e56aeb9c --- /dev/null +++ b/Block/Adminhtml/VindiPlan/Edit/SaveButton.php @@ -0,0 +1,29 @@ + + */ +class SaveButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Save'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 90, + ]; + } +} diff --git a/Controller/Adminhtml/VindiPlan/AbstractMassAction.php b/Controller/Adminhtml/VindiPlan/AbstractMassAction.php new file mode 100755 index 00000000..3d6826fb --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/AbstractMassAction.php @@ -0,0 +1,66 @@ +_filter = $filter; + $this->collectionFactory = $collectionFactory; + + parent::__construct($context); + } + + /** + * @return AbstractDb + * @throws LocalizedException + */ + public function getCollection() + { + return $this->_filter->getCollection($this->collectionFactory->create()); + } + + /** + * @param string $path + * + * @return ResultRedirect + */ + protected function getResultRedirect($path) + { + $resultRedirect = $this->resultRedirectFactory->create(); + + return $resultRedirect->setPath($path); + } +} diff --git a/Controller/Adminhtml/VindiPlan/Delete.php b/Controller/Adminhtml/VindiPlan/Delete.php new file mode 100755 index 00000000..92a82c2e --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/Delete.php @@ -0,0 +1,58 @@ + + */ +class Delete extends Action +{ + /** + * @var VindiPlanFactory + */ + protected $vindiplanFactory; + + /** + * @var VindiPlanRepository + */ + protected $vindiplanRepository; + + /** + * @param Context $context + * @param VindiPlanFactory $vindiplanFactory + * @param VindiPlanRepository $vindiplanRepository + */ + public function __construct( + Context $context, + VindiPlanFactory $vindiplanFactory, + VindiPlanRepository $vindiplanRepository + ) { + parent::__construct($context); + $this->vindiplanFactory = $vindiplanFactory; + $this->vindiplanRepository = $vindiplanRepository; + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Redirect|\Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $id = $this->getRequest()->getParam('entity_id'); + + try { + $vindiplan = $this->vindiplanRepository->getById($id); + $this->vindiplanRepository->delete($vindiplan); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + } + + $this->messageManager->addSuccessMessage(__('Item deleted succesfully!')); + return $this->resultRedirectFactory->create()->setPath('*/*/index'); + } +} diff --git a/Controller/Adminhtml/VindiPlan/Edit.php b/Controller/Adminhtml/VindiPlan/Edit.php new file mode 100755 index 00000000..9be34458 --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/Edit.php @@ -0,0 +1,58 @@ + + */ +class Edit extends \Magento\Backend\App\Action implements HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Cms::save'; + + /** + * Core registry + * + * @var \Magento\Framework\Registry + */ + protected $_coreRegistry; + + /** + * @var \Magento\Framework\View\Result\PageFactory + */ + protected $resultPageFactory; + + /** + * @param Action\Context $context + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param \Magento\Framework\Registry $registry + */ + public function __construct( + Action\Context $context, + \Magento\Framework\View\Result\PageFactory $resultPageFactory, + \Magento\Framework\Registry $registry + ) { + $this->resultPageFactory = $resultPageFactory; + $this->_coreRegistry = $registry; + parent::__construct($context); + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|\Magento\Framework\View\Result\Page + */ + public function execute() + { + $resultPage = $this->resultPageFactory->create(); + $resultPage->getConfig()->getTitle()->prepend((__('Edit Plan'))); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/VindiPlan/ImportPlans.php b/Controller/Adminhtml/VindiPlan/ImportPlans.php new file mode 100644 index 00000000..52a5d7a0 --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/ImportPlans.php @@ -0,0 +1,107 @@ + + */ +class ImportPlans extends Action +{ + /** + * @var Plan + */ + protected $plan; + + /** + * @var VindiPlanFactory + */ + protected $vindiplanFactory; + + /** + * @var VindiPlanRepository + */ + protected $vindiplanRepository; + + /** + * ImportPlans constructor. + * @param Context $context + * @param Plan $plan + * @param VindiPlanFactory $vindiplanFactory + * @param VindiPlanRepository $vindiplanRepository + */ + public function __construct( + Context $context, + Plan $plan, + VindiPlanFactory $vindiplanFactory, + VindiPlanRepository $vindiplanRepository + ) { + parent::__construct($context); + $this->plan = $plan; + $this->vindiplanFactory = $vindiplanFactory; + $this->vindiplanRepository = $vindiplanRepository; + } + + /** + * Execute method for import plans action + * + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Redirect|\Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + try { + $plans = $this->plan->getAllPlans(); + + if (!isset($plans["plans"])) { + throw new LocalizedException(__('No plans found.')); + } + + foreach ($plans["plans"] as $planData) { + if (!empty($planData['code'])) { + $existingPlan = $this->vindiplanRepository->getByCode($planData['code']); + } else { + $existingPlan = $this->vindiplanRepository->getByName($planData['name']); + } + + if ($existingPlan->getId()) { + continue; + } + + $vindiplan = $this->vindiplanFactory->create(); + + $vindiplan->setData([ + 'name' => $planData['name'], + 'status' => $planData['status'], + 'interval' => $planData['interval'], + 'interval_count' => $planData['interval_count'], + 'billing_trigger_type' => $planData['billing_trigger_type'], + 'billing_trigger_day' => $planData['billing_trigger_day'], + 'billing_cycles' => $planData['billing_cycles'], + 'code' => $planData['code'], + 'description' => $planData['description'], + 'installments' => $planData['installments'], + 'invoice_split' => $planData['invoice_split'], + 'updated_at' => date('Y-m-d H:i:s'), + 'created_at' => date('Y-m-d H:i:s') + ]); + + $this->vindiplanRepository->save($vindiplan); + } + + $this->messageManager->addSuccessMessage(__('Plans imported successfully!')); + } catch (LocalizedException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage(__('An error occurred during the import process.')); + } + + return $this->_redirect('*/*/'); + } +} diff --git a/Controller/Adminhtml/VindiPlan/Index.php b/Controller/Adminhtml/VindiPlan/Index.php new file mode 100755 index 00000000..63c812a9 --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/Index.php @@ -0,0 +1,39 @@ + + */ +class Index extends \Magento\Backend\App\Action +{ + /** + * @var bool|\Magento\Framework\View\Result\PageFactory + */ + protected $resultPageFactory = false; + + /** + * Index constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Magento\Framework\View\Result\PageFactory $resultPageFactory + ) { + parent::__construct($context); + $this->resultPageFactory = $resultPageFactory; + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|\Magento\Framework\View\Result\Page + */ + public function execute() + { + $resultPage = $this->resultPageFactory->create(); + $resultPage->getConfig()->getTitle()->prepend((__('Plans Management'))); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/VindiPlan/MassDelete.php b/Controller/Adminhtml/VindiPlan/MassDelete.php new file mode 100755 index 00000000..a49026cc --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/MassDelete.php @@ -0,0 +1,37 @@ + + */ +class MassDelete extends AbstractMassAction +{ + /** + * @return ResponseInterface|ResultInterface + * @throws LocalizedException + */ + public function execute() + { + $items = $this->getCollection(); + $collectionSize = $items->getSize(); + + foreach ($items as $item) { + try { + $item->delete(); + } catch (Exception $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + } + } + + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $collectionSize)); + + return $this->getResultRedirect('*/*/'); + } +} diff --git a/Controller/Adminhtml/VindiPlan/MassStatus.php b/Controller/Adminhtml/VindiPlan/MassStatus.php new file mode 100755 index 00000000..ed05d964 --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/MassStatus.php @@ -0,0 +1,40 @@ + + */ +class MassStatus extends AbstractMassAction +{ + /** + * @return ResponseInterface|ResultInterface + * @throws LocalizedException + */ + public function execute() + { + $items = $this->getCollection(); + $status = (int) $this->getRequest()->getParam('status'); + $updated = 0; + + foreach ($items as $item) { + try { + $item->setStatus($status); + $item->save(); + $updated++; + } catch (Exception $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + } + } + + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been updated.', $updated)); + + return $this->getResultRedirect('*/*/'); + } +} diff --git a/Controller/Adminhtml/VindiPlan/NewAction.php b/Controller/Adminhtml/VindiPlan/NewAction.php new file mode 100755 index 00000000..a0634cca --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/NewAction.php @@ -0,0 +1,46 @@ + + */ +class NewAction extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface +{ + /** + * @var \Magento\Backend\Model\View\Result\ForwardFactory + */ + protected $resultForwardFactory; + + /** + * NewAction constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\Framework\Registry $coreRegistry + * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Magento\Framework\Registry $coreRegistry, + \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory + ) { + $this->resultForwardFactory = $resultForwardFactory; + parent::__construct($context, $coreRegistry); + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Forward|\Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ + $resultForward = $this->resultForwardFactory->create(); + return $resultForward->forward('edit'); + } +} diff --git a/Controller/Adminhtml/VindiPlan/Save.php b/Controller/Adminhtml/VindiPlan/Save.php new file mode 100755 index 00000000..d811dd67 --- /dev/null +++ b/Controller/Adminhtml/VindiPlan/Save.php @@ -0,0 +1,101 @@ + + */ +class Save extends Action +{ + /** + * @var Plan + */ + protected $plan; + + /** + * @var VindiPlanFactory + */ + protected $vindiPlanFactory; + + /** + * @var VindiPlanRepository + */ + protected $vindiPlanRepository; + + /** + * Save constructor. + * @param Context $context + * @param Plan $plan + * @param VindiPlanFactory $vindiPlanFactory + * @param VindiPlanRepository $vindiPlanRepository + */ + public function __construct( + Context $context, + Plan $plan, + VindiPlanFactory $vindiPlanFactory, + VindiPlanRepository $vindiPlanRepository + ) { + parent::__construct($context); + $this->plan = $plan; + $this->vindiPlanFactory = $vindiPlanFactory; + $this->vindiPlanRepository = $vindiPlanRepository; + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Redirect|\Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $post = $this->getRequest()->getPostValue(); + + if (!$post) { + $this->_redirect('*/*/'); + return; + } + + try { + $data = [ + 'name' => $post["settings"]["name"], + 'status' => $post["settings"]["status"], + 'code' => Data::sanitizeItemSku($post["settings"]["code"]), + 'interval' => $post["settings"]["interval"], + 'interval_count' => $post["settings"]["interval_count"], + 'billing_trigger_type' => $post["settings"]["billing_trigger_type"], + 'billing_trigger_day' => $post["settings"]["billing_trigger_day"], + 'billing_cycles' => $post["settings"]["billing_cycles"], + 'updated_at' => date('Y-m-d H:i:s'), + 'created_at' => date('Y-m-d H:i:s') + ]; + + $existingPlan = $this->vindiPlanRepository->getByCode($data['code']); + + if ($existingPlan && $existingPlan->getId()) { + throw new LocalizedException(__('A plan with the same code already exists.')); + } + + $this->plan->save($data); + + $vindiPlan = $this->vindiPlanFactory->create(); + $vindiPlan->setData($data); + $this->vindiPlanRepository->save($vindiPlan); + + $this->messageManager->addSuccessMessage(__('Plan saved successfully!')); + $this->_redirect('*/*/'); + } catch (LocalizedException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $this->_redirect('*/*/edit', ['entity_id' => $this->getRequest()->getParam('entity_id')]); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage(__('An error occurred while saving the plan.')); + $this->_redirect('*/*/edit', ['entity_id' => $this->getRequest()->getParam('entity_id')]); + } + } +} diff --git a/Model/Config/Source/Interval.php b/Model/Config/Source/Interval.php index 258de89b..2a82bbeb 100644 --- a/Model/Config/Source/Interval.php +++ b/Model/Config/Source/Interval.php @@ -25,4 +25,17 @@ public function getAllOptions() return $this->_options; } + + /** + * Get a text for option value + * @param string|integer $value + * @return string|bool + */ + public function toOptionArray() + { + return [ + ['value' => 'days', 'label' => __('Days')], + ['value' => 'months', 'label' => __('Months')] + ]; + } } diff --git a/Model/Config/Source/Plan/Status.php b/Model/Config/Source/Plan/Status.php new file mode 100644 index 00000000..93287165 --- /dev/null +++ b/Model/Config/Source/Plan/Status.php @@ -0,0 +1,16 @@ + 'active', 'label' => __('Active')], + ['value' => 'inactive', 'label' => __('Inactive')], + ['value' => 'deleted', 'label' => __('Deleted')] + ]; + } +} diff --git a/Model/ResourceModel/VindiPlanItem.php b/Model/ResourceModel/VindiPlanItem.php new file mode 100755 index 00000000..52960ef8 --- /dev/null +++ b/Model/ResourceModel/VindiPlanItem.php @@ -0,0 +1,62 @@ + + */ +class VindiPlanItem extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + /** + * @var VindiPlanItemFactory + */ + protected $vindiplanitemFactory; + + /** + * VindiPlanItem constructor. + * @param Context $context + * @param VindiPlanItemFactory $vindiplanitemFactory + * @param ReadFactory $readFactory + * @param Filesystem $filesystem + */ + public function __construct( + Context $context, + VindiPlanItemFactory $vindiplanitemFactory, + ReadFactory $readFactory, + Filesystem $filesystem + ) { + $this->vindiplanitemFactory = $vindiplanitemFactory; + $this->readFactory = $readFactory; + $this->filesystem = $filesystem; + parent::__construct($context); + } + + protected function _construct() + { + $this->_init('vindi_plan_items', 'entity_id'); + } + + /** + * @param $id + * @return \Vindi\Payment\Model\VindiPlanItem + * @throws NoSuchEntityException + */ + public function getById($id) + { + $vindiplanitem = $this->vindiplanitemFactory->create(); + $this->load($vindiplanitem, $id); + + if (!$vindiplanitem->getId()) { + throw new NoSuchEntityException(__('VindiPlanItem with id "%1" does not exist.', $id)); + } + + return $vindiplanitem; + } +} diff --git a/Model/ResourceModel/VindiPlanItem/Collection.php b/Model/ResourceModel/VindiPlanItem/Collection.php new file mode 100755 index 00000000..ff3b8544 --- /dev/null +++ b/Model/ResourceModel/VindiPlanItem/Collection.php @@ -0,0 +1,44 @@ + + */ +class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection +{ + protected $_idFieldName = 'entity_id'; + protected $_eventPrefix = 'vindi_payment_vindiplanitems_collection'; + protected $_eventObject = 'vindiplanitem_collection'; + + /** + * @var array|null + */ + protected $_options; + + /** + * Define resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('Vindi\Payment\Model\VindiPlanItem', 'Vindi\Payment\Model\ResourceModel\VindiPlanItem'); + } + + public function toOptionArray() + { + $collection = $this->getItems(); + $this->_options = [['label' => '', 'value' => '']]; + + foreach ($collection as $vindiplanitem) { + $this->_options[] = [ + 'label' => __($vindiplanitem->getName()), + 'value' => $vindiplanitem->getId() + ]; + } + + return $this->_options; + } +} diff --git a/Model/Vindi/Plan.php b/Model/Vindi/Plan.php index af24ab7a..074bcbeb 100644 --- a/Model/Vindi/Plan.php +++ b/Model/Vindi/Plan.php @@ -61,4 +61,12 @@ public function findOneByCode($code) return false; } + + /** + * @return bool|mixed + */ + public function getAllPlans() + { + return $this->api->request('plans', 'GET'); + } } diff --git a/Model/VindiPlan.php b/Model/VindiPlan.php index 2168c11b..d8a9f4b9 100755 --- a/Model/VindiPlan.php +++ b/Model/VindiPlan.php @@ -2,20 +2,18 @@ namespace Vindi\Payment\Model; use Vindi\Payment\Api\Data\VindiPlanInterface; +use Magento\Framework\Model\AbstractModel; /** * Class VindiPlan * @package Vindi\Payment\Model * @author Iago Cedran */ -class VindiPlan extends \Magento\Framework\Model\AbstractModel implements VindiPlanInterface +class VindiPlan extends AbstractModel implements VindiPlanInterface { - /** - * @return void - */ protected function _construct() { - $this->_init('Vindi\Payment\Model\ResourceModel\VindiPlan'); + $this->_init(\Vindi\Payment\Model\ResourceModel\VindiPlan::class); } /** @@ -77,6 +75,106 @@ public function setStatus($status) $this->setData(self::STATUS, $status); } + public function getInterval() + { + return $this->getData(self::INTERVAL); + } + + public function setInterval($interval) + { + return $this->setData(self::INTERVAL, $interval); + } + + public function getIntervalCount() + { + return $this->getData(self::INTERVAL_COUNT); + } + + public function setIntervalCount($intervalCount) + { + return $this->setData(self::INTERVAL_COUNT, $intervalCount); + } + + public function getBillingTriggerType() + { + return $this->getData(self::BILLING_TRIGGER_TYPE); + } + + public function setBillingTriggerType($billingTriggerType) + { + return $this->setData(self::BILLING_TRIGGER_TYPE, $billingTriggerType); + } + + public function getBillingTriggerDay() + { + return $this->getData(self::BILLING_TRIGGER_DAY); + } + + public function setBillingTriggerDay($billingTriggerDay) + { + return $this->setData(self::BILLING_TRIGGER_DAY, $billingTriggerDay); + } + + public function getBillingCycles() + { + return $this->getData(self::BILLING_CYCLES); + } + + public function setBillingCycles($billingCycles) + { + return $this->setData(self::BILLING_CYCLES, $billingCycles); + } + + public function getCode() + { + return $this->getData(self::CODE); + } + + public function setCode($code) + { + return $this->setData(self::CODE, $code); + } + + public function getDescription() + { + return $this->getData(self::DESCRIPTION); + } + + public function setDescription($description) + { + return $this->setData(self::DESCRIPTION, $description); + } + + public function getInstallments() + { + return $this->getData(self::INSTALLMENTS); + } + + public function setInstallments($installments) + { + return $this->setData(self::INSTALLMENTS, $installments); + } + + public function getInvoiceSplit() + { + return $this->getData(self::INVOICE_SPLIT); + } + + public function setInvoiceSplit($invoiceSplit) + { + return $this->setData(self::INVOICE_SPLIT, $invoiceSplit); + } + + public function getMetadata() + { + return $this->getData(self::METADATA); + } + + public function setMetadata($metadata) + { + return $this->setData(self::METADATA, $metadata); + } + /** * @return array|mixed|string|null */ diff --git a/Model/VindiPlan/DataProvider.php b/Model/VindiPlan/DataProvider.php new file mode 100755 index 00000000..e2652126 --- /dev/null +++ b/Model/VindiPlan/DataProvider.php @@ -0,0 +1,94 @@ + + */ +class DataProvider extends \Magento\Ui\DataProvider\ModifierPoolDataProvider +{ + /** + * @var \Vindi\Payment\Model\ResourceModel\VindiPlan\Collection + */ + protected $collection; + + /** + * @var DataPersistorInterface + */ + protected $dataPersistor; + + /** + * @var array + */ + protected $loadedData; + + /** + * @var Data + */ + protected $helper; + + /** + * DataProvider constructor. + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param CollectionFactory $vindiplanCollectionFactory + * @param DataPersistorInterface $dataPersistor + * @param Data $helper + * @param array $meta + * @param array $data + * @param PoolInterface|null $pool + */ + public function __construct( + string $name, + string $primaryFieldName, + string $requestFieldName, + CollectionFactory $vindiplanCollectionFactory, + DataPersistorInterface $dataPersistor, + Data $helper, + array $meta = [], + array $data = [], + PoolInterface $pool = null + ) { + $this->collection = $vindiplanCollectionFactory->create(); + $this->dataPersistor = $dataPersistor; + $this->helper = $helper; + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data, $pool); + } + + /** + * @return array + */ + public function getData() + { + if (isset($this->loadedData)) { + return $this->loadedData; + } + + $items = $this->collection->getItems(); + + /** @var \Vindi\Payment\Model\VindiPlan $vindiplan */ + foreach ($items as $vindiplan) { + $result['settings'] = $vindiplan->getData(); + $result['entity_id'] = $vindiplan->getId(); + + $this->loadedData[$vindiplan->getId()] = $result; + } + + $data = $this->dataPersistor->get('cedran_vindiplan'); + if (!empty($data)) { + $block = $this->collection->getNewEmptyItem(); + $block->setData($data); + $this->loadedData[$vindiplan->getId()] = $vindiplan->getData(); + $this->dataPersistor->clear('cedran_vindiplan'); + } + + return $this->loadedData; + } +} diff --git a/Model/VindiPlanItem.php b/Model/VindiPlanItem.php new file mode 100755 index 00000000..35639a6f --- /dev/null +++ b/Model/VindiPlanItem.php @@ -0,0 +1,53 @@ +_init(\Vindi\Payment\Model\ResourceModel\VindiPlanItem::class); + } + + public function getId() + { + return $this->getData(self::ENTITY_ID); + } + + public function setId($entityId) + { + return $this->setData(self::ENTITY_ID, $entityId); + } + + public function getPlanId() + { + return $this->getData(self::PLAN_ID); + } + + public function setPlanId($planId) + { + return $this->setData(self::PLAN_ID, $planId); + } + + public function getProductId() + { + return $this->getData(self::PRODUCT_ID); + } + + public function setProductId($productId) + { + return $this->setData(self::PRODUCT_ID, $productId); + } + + public function getCycles() + { + return $this->getData(self::CYCLES); + } + + public function setCycles($cycles) + { + return $this->setData(self::CYCLES, $cycles); + } +} diff --git a/Model/VindiPlanItemRepository.php b/Model/VindiPlanItemRepository.php new file mode 100755 index 00000000..42116665 --- /dev/null +++ b/Model/VindiPlanItemRepository.php @@ -0,0 +1,141 @@ + + */ +class VindiPlanItemRepository implements VindiPlanItemRepositoryInterface +{ + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var ResourceModel + */ + private $resourceModel; + + /** + * @var VindiPlanItemInterfaceFactory + */ + private $vindiplanitemFactory; + + /** + * @var CollectionProcessorInterface + */ + private $collectionProcessor; + + /** + * @var VindiPlanItemSearchResultInterfaceFactory + */ + private $searchResultsFactory; + + /** + * VindiPlanItem constructor. + * @param ResourceModel $resourceModel + * @param VindiPlanItemInterfaceFactory $vindiplanitemFactory + * @param CollectionFactory $collectionFactory + * @param CollectionProcessorInterface $collectionProcessor + * @param VindiPlanItemSearchResultInterfaceFactory $searchResultsFactory + */ + public function __construct( + ResourceModel $resourceModel, + VindiPlanItemInterfaceFactory $vindiplanitemFactory, + CollectionFactory $collectionFactory, + CollectionProcessorInterface $collectionProcessor, + VindiPlanItemSearchResultInterfaceFactory $searchResultsFactory + ) { + $this->resourceModel = $resourceModel; + $this->vindiplanitemFactory = $vindiplanitemFactory; + $this->collectionFactory = $collectionFactory; + $this->collectionProcessor = $collectionProcessor; + $this->searchResultsFactory = $searchResultsFactory; + } + + /** + * @param int $entityId + * @return VindiPlanItemInterface + * @throws NoSuchEntityException + */ + public function getById(int $entityId): VindiPlanItemInterface + { + try { + /** @var VindiPlanItemInterface $vindiplanitem */ + $vindiplanitem = $this->vindiplanitemFactory->create(); + $this->resourceModel->load($vindiplanitem, $entityId); + } catch (\Exception $e) { + throw new NoSuchEntityException(__('Error during load vindiplanitem by Entity ID')); + } + return $vindiplanitem; + } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @return VindiPlanItemSearchResultInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria): VindiPlanItemSearchResultInterface + { + /** @var Collection $collection */ + $collection = $this->collectionFactory->create(); + $this->collectionProcessor->process($searchCriteria, $collection); + + /** @var VindiPlanItemSearchResultInterface $searchResult */ + $searchResult = $this->searchResultsFactory->create(); + + $searchResult->setItems($collection->getItems()) + ->setSearchCriteria($searchCriteria) + ->setTotalCount($collection->getSize()); + + return $searchResult; + } + + /** + * @param VindiPlanItemInterface $vindiplanitem + * @return void + * @throws CouldNotSaveException + */ + public function save(VindiPlanItemInterface $vindiplanitem): void + { + try { + $this->resourceModel->save($vindiplanitem); + } catch (\Exception $e) { + throw new CouldNotSaveException(__('Error when saving vindiplanitem')); + } + } + + /** + * @param VindiPlanItemInterface $vindiplanitem + * @return void + * @throws CouldNotDeleteException + */ + public function delete(VindiPlanItemInterface $vindiplanitem): void + { + try { + $this->resourceModel->delete($vindiplanitem); + } catch (\Exception $e) { + throw new CouldNotDeleteException(__('Could not delete vindiplanitem.')); + } + } +} diff --git a/Model/VindiPlanItemSearchResult.php b/Model/VindiPlanItemSearchResult.php new file mode 100755 index 00000000..806e22f7 --- /dev/null +++ b/Model/VindiPlanItemSearchResult.php @@ -0,0 +1,17 @@ + + */ +class VindiPlanItemSearchResult extends SearchResults implements VindiPlanItemSearchResultInterface +{ +} diff --git a/Model/VindiPlanRepository.php b/Model/VindiPlanRepository.php index 45fc3fdc..94f499cf 100755 --- a/Model/VindiPlanRepository.php +++ b/Model/VindiPlanRepository.php @@ -91,6 +91,54 @@ public function getById(int $entityId): VindiPlanInterface return $vindiplan; } + /** + * Retrieve plan by code. + * + * @param string $code + * @return VindiPlanInterface|null + */ + public function getByCode(string $code): ?VindiPlanInterface + { + $collection = $this->collectionFactory->create(); + $collection->addFieldToFilter('code', $code); + $collection->setPageSize(1); + + $item = $collection->getFirstItem(); + + if (!$item->getId()) { + return null; + } + + $vindiplan = $this->vindiplanFactory->create(); + $this->resourceModel->load($vindiplan, $item->getId()); + + return $vindiplan; + } + + /** + * Retrieve plan by name. + * + * @param string $name + * @return VindiPlanInterface|null + */ + public function getByName(string $name): ?VindiPlanInterface + { + $collection = $this->collectionFactory->create(); + $collection->addFieldToFilter('name', ['eq' => $name]); + $collection->setPageSize(1); + + $item = $collection->getFirstItem(); + + if (!$item->getId()) { + return null; + } + + $vindiplan = $this->vindiplanFactory->create(); + $this->resourceModel->load($vindiplan, $item->getId()); + + return $vindiplan; + } + /** * @param SearchCriteriaInterface $searchCriteria * @return VindiPlanSearchResultInterface diff --git a/Ui/Component/Listing/Column/VindiPlan/Actions.php b/Ui/Component/Listing/Column/VindiPlan/Actions.php new file mode 100755 index 00000000..200e8ec3 --- /dev/null +++ b/Ui/Component/Listing/Column/VindiPlan/Actions.php @@ -0,0 +1,77 @@ + + */ +class Actions extends Column +{ + /** + * @var \Magento\Backend\Model\UrlInterface + */ + protected $urlBuilder; + + /** + * @param ContextInterface $context + * @param UiComponentFactory $uiComponentFactory + * @param UrlInterface $urlBuilder + * @param array $components + * @param array $data + */ + public function __construct( + ContextInterface $context, + UiComponentFactory $uiComponentFactory, + UrlInterface $urlBuilder, + array $components = [], + array $data = [] + ) { + $this->urlBuilder = $urlBuilder; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * Prepare Data Source + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + foreach ($dataSource['data']['items'] as &$item) { + $item[$this->getData('name')]['edit'] = [ + 'href' => $this->urlBuilder->getUrl( + 'crudadmin/vindiplan/edit', + ['entity_id' => $item['entity_id']] + ), + 'label' => __('Edit'), + 'hidden' => false, + '__disableTmpl' => true + ]; + + $item[$this->getData('name')]['delete'] = [ + 'href' => $this->urlBuilder->getUrl( + 'crudadmin/vindiplan/delete', + ['entity_id' => $item['entity_id']] + ), + 'label' => __('Delete'), + 'hidden' => false, + '__disableTmpl' => true + ]; + } + } + + return $dataSource; + } +} diff --git a/Ui/Component/MassAction/Status/Options.php b/Ui/Component/MassAction/Status/Options.php new file mode 100755 index 00000000..fbabe979 --- /dev/null +++ b/Ui/Component/MassAction/Status/Options.php @@ -0,0 +1,128 @@ + + */ +class Options implements JsonSerializable +{ + /** + * @var array + */ + protected $options; + + /** + * Additional options params + * + * @var array + */ + protected $data; + + /** + * @var UrlInterface + */ + protected $urlBuilder; + + /** + * Base URL for subactions + * + * @var string + */ + protected $urlPath; + + /** + * Param name for subactions + * + * @var string + */ + protected $paramName; + + /** + * Additional params for subactions + * + * @var array + */ + protected $additionalData = []; + + /** + * Constructor + * + * @param UrlInterface $urlBuilder + * @param array $data + */ + public function __construct( + UrlInterface $urlBuilder, + array $data = [] + ) { + $this->data = $data; + $this->urlBuilder = $urlBuilder; + } + + /** + * @inheritdoc + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + if ($this->options === null) { + $options = array( + array( + "value" => "1", + "label" => ('Active'), + ), + array( + "value" => "2", + "label" => ('Inactive'), + ) + ); + $this->prepareData(); + foreach ($options as $optionCode) { + $this->options[$optionCode['value']] = [ + 'type' => 'status_' . $optionCode['value'], + 'label' => $optionCode['label'], + ]; + + if ($this->urlPath && $this->paramName) { + $this->options[$optionCode['value']]['url'] = $this->urlBuilder->getUrl( + $this->urlPath, + [$this->paramName => $optionCode['value']] + ); + } + + $this->options[$optionCode['value']] = array_merge_recursive( + $this->options[$optionCode['value']], + $this->additionalData + ); + } + $this->options = array_values($this->options); + } + return $this->options; + } + + /** + * Prepare addition data for subactions + * + * @return void + */ + protected function prepareData() + { + foreach ($this->data as $key => $value) { + switch ($key) { + case 'urlPath': + $this->urlPath = $value; + break; + case 'paramName': + $this->paramName = $value; + break; + default: + $this->additionalData[$key] = $value; + break; + } + } + } +} diff --git a/etc/adminhtml/menu.xml b/etc/adminhtml/menu.xml index c64585a3..cc9d91d6 100644 --- a/etc/adminhtml/menu.xml +++ b/etc/adminhtml/menu.xml @@ -8,8 +8,8 @@ sortOrder="9999" resource="Magento_Backend::content" /> - + + + + + + + + + + + + + + diff --git a/etc/di.xml b/etc/di.xml index d3ad41be..7629bfb7 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -55,4 +55,25 @@ Vindi\Payment\Model\ResourceModel\VindiPlan + + + + + + + + + + Vindi\Payment\Model\ResourceModel\VindiPlanItem\Grid\Collection + Vindi\Payment\Model\ResourceModel\VindiPlanItem\Form\DataProvider + + + + + + + vindi_plan_items + Vindi\Payment\Model\ResourceModel\VindiPlanItem + + diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 90a61f5e..36cfddaa 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -134,3 +134,20 @@ "When enabled, it will only be possible to finalize the order with the document informed when selecting the payment method. When disabled, the client will not be asked for the document, but it will still be necessary to send the document when creating the order in VINDI, otherwise it will be rejected by the API.","Quando habilitado, só será possível finalizar o pedido com o documento informado ao selecionar o método de pagamento. Quando desabilitado, não será solicitado ao cliente o documento, porém ainda será necessário o envio do documento ao criar o pedido na VINDI, caso contrário será rejeitado pela API." "Paid Order Status","Status de Pedido Pago" "Plans","Planos" +"Plans Management","Gerenciamento de Planos" +"Create Plan","Criar Plano" +"Edit Plan","Editar Plano" +"Import Plans","Importar Planos" +"Plans imported successfully!","Planos importados com sucesso!" +"An error occurred during the import process.","Ocorreu um erro durante o processo de importação." +"Plan saved successfully!","Plano salvo com sucesso!" +"An error occurred while saving the plan.","Ocorreu um erro ao salvar o plano." +"Deleted","Excluído" +"Months","Meses" +"External Code","Código Externo" +"Plan Description","Descrição do Plano" +"Interval Duration","Duração do Intervalo" +"Number of intervals within a period","Número de intervalos dentro de um período" +"Reference for bill generation date","Referência para data de geração da cobrança" +"Day for billing","Dia para geração da cobrança" +"Maximum number of periods in a subscription. Null means indefinite duration","Número máximo de períodos em uma assinatura. Nulo significa duração indefinida" diff --git a/view/adminhtml/layout/vindi_payment_vindiplan.xml b/view/adminhtml/layout/vindi_payment_vindiplan.xml new file mode 100755 index 00000000..b66ca691 --- /dev/null +++ b/view/adminhtml/layout/vindi_payment_vindiplan.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/view/adminhtml/layout/vindi_payment_vindiplan_edit.xml b/view/adminhtml/layout/vindi_payment_vindiplan_edit.xml new file mode 100755 index 00000000..54f4c376 --- /dev/null +++ b/view/adminhtml/layout/vindi_payment_vindiplan_edit.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/view/adminhtml/layout/vindi_payment_vindiplan_index.xml b/view/adminhtml/layout/vindi_payment_vindiplan_index.xml new file mode 100755 index 00000000..f95e3fc9 --- /dev/null +++ b/view/adminhtml/layout/vindi_payment_vindiplan_index.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml new file mode 100755 index 00000000..10c809d6 --- /dev/null +++ b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml @@ -0,0 +1,214 @@ + + +
+ + + vindi_payment_vindiplan_form.vindi_payment_vindiplan_data_source + + Information + true + + + +
+

getQrcodeOriginalPath() ?>

+
-
diff --git a/view/frontend/templates/onepage/bankslippix.phtml b/view/frontend/templates/onepage/bankslippix.phtml new file mode 100644 index 00000000..16e512c5 --- /dev/null +++ b/view/frontend/templates/onepage/bankslippix.phtml @@ -0,0 +1,53 @@ +canShowBankSlipPix()) : ?> +
+

+ getInfoMessageOnepageSuccess() ?> +

+ + + +

getQrcodeOriginalPath() ?>

+ +
+ +
+ + +
+ +
+
+ + + + diff --git a/view/frontend/templates/onepage/pix.phtml b/view/frontend/templates/onepage/pix.phtml index 42f1d9b3..357f48b9 100644 --- a/view/frontend/templates/onepage/pix.phtml +++ b/view/frontend/templates/onepage/pix.phtml @@ -24,14 +24,13 @@ use Vindi\Payment\Block\Onepage\Pix;
+

getQrcodeOriginalPath() ?>

+
-
+ -
- -
@@ -42,7 +41,7 @@ use Vindi\Payment\Block\Onepage\Pix; "components": { "vindiPix": { "component": "Vindi_Payment/js/view/onepage/vindi-pix", - "qrCodeKey": getQrcodeOriginalPath() ?> + "qrCodeKey": "getQrcodeOriginalPath() ?>" } } } diff --git a/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js b/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js new file mode 100644 index 00000000..806a1d10 --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js @@ -0,0 +1,17 @@ +define( + [ + 'underscore', + 'Magento_Checkout/js/view/payment/default', + 'mage/translate', + 'jquery', + 'mageUtils' + ], + function (_, Component, $t, $, utils) { + 'use strict'; + return Component.extend({ + defaults: { + template: 'Vindi_Payment/payment/vindi-bankslip' + } + }); + } +); diff --git a/view/frontend/web/js/view/payment/vindi.js b/view/frontend/web/js/view/payment/vindi.js index 2f19c67e..7032d1ce 100644 --- a/view/frontend/web/js/view/payment/vindi.js +++ b/view/frontend/web/js/view/payment/vindi.js @@ -17,6 +17,10 @@ define( type: 'vindi_bankslip', component: 'Vindi_Payment/js/view/payment/method-renderer/vindi-bankslip' }, + { + type: 'vindi_bankslippix', + component: 'Vindi_Payment/js/view/payment/method-renderer/vindi-bankslippix' + }, { type: 'vindi_pix', component: 'Vindi_Payment/js/view/payment/method-renderer/vindi-pix' diff --git a/view/frontend/web/template/payment/vindi-bankslippix.html b/view/frontend/web/template/payment/vindi-bankslippix.html new file mode 100644 index 00000000..b878159f --- /dev/null +++ b/view/frontend/web/template/payment/vindi-bankslippix.html @@ -0,0 +1,91 @@ + +
+
+ + +
+
+ + + +
+ + + +
+ +
+ + +
+
+ +
+ + +
+
+
+
+ + + +
+
+
+
+
+
+
+ + +
+ + + +
+ +
+
+ +
+
+
+
From 2be208e0990a1285abbabc1aa6cd2fb552dd25a8 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Wed, 20 Mar 2024 15:34:01 -0300 Subject: [PATCH 049/187] feat: v1.5.0, added Bolepix --- composer.json | 2 +- etc/module.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2fdd6b81..efa1ebd1 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "vindi/vindi-magento2", "description": "Módulo de cobrança Vindi para o Magento 2", "type": "magento2-module", - "version": "1.4.0", + "version": "1.5.0", "license": "GPL-3.0", "authors": [ { diff --git a/etc/module.xml b/etc/module.xml index 45ced32c..38bf50b0 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,7 +1,7 @@ - + From b629d1ae98d2d2f75bc50bdbfda457a3fa7a9187 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Wed, 20 Mar 2024 16:49:09 -0300 Subject: [PATCH 050/187] fix: show the right message on cart --- .../frontend/templates/info/bankslippix.phtml | 2 +- .../templates/onepage/bankslippix.phtml | 6 +- .../method-renderer/vindi-bankslippix.js | 60 +++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/view/frontend/templates/info/bankslippix.phtml b/view/frontend/templates/info/bankslippix.phtml index ad7d83d8..290dc617 100644 --- a/view/frontend/templates/info/bankslippix.phtml +++ b/view/frontend/templates/info/bankslippix.phtml @@ -11,7 +11,7 @@
-
+
QRCode

getQrcodeOriginalPath() ?>

diff --git a/view/frontend/templates/onepage/bankslippix.phtml b/view/frontend/templates/onepage/bankslippix.phtml index 16e512c5..1ee74aa2 100644 --- a/view/frontend/templates/onepage/bankslippix.phtml +++ b/view/frontend/templates/onepage/bankslippix.phtml @@ -13,7 +13,11 @@ -

getQrcodeOriginalPath() ?>

+ +
+ QRCode +

getQrcodeOriginalPath() ?>

+
diff --git a/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js b/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js index 806a1d10..2fc450db 100644 --- a/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js +++ b/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js @@ -4,14 +4,66 @@ define( 'Magento_Checkout/js/view/payment/default', 'mage/translate', 'jquery', - 'mageUtils' + 'mageUtils', + 'Vindi_Payment/js/model/taxvat', + 'Vindi_Payment/js/model/validate' ], - function (_, Component, $t, $, utils) { + + function (_, Component, $t, $, utils, taxvat, documentValidate) { 'use strict'; return Component.extend({ defaults: { - template: 'Vindi_Payment/payment/vindi-bankslip' - } + template: 'Vindi_Payment/payment/vindi-bankslippix', + taxvat: taxvat + }, + + getInfoMessage: function () { + return window?.checkoutConfig?.payment?.vindi_pix?.info_message; + }, + + isActiveDocument: function () { + return window?.checkoutConfig?.payment?.vindi_pix?.enabledDocument; + }, + + checkCpf: function (self, event) { + this.formatTaxvat(event.target) + const message = documentValidate.isValidTaxvat(this?.taxvat?.value()) ? '' : 'CPF/CNPJ inválido'; + $('#cpfResponse').text(message); + }, + + formatTaxvat: function (target) { + taxvat.formatDocument(target) + }, + + validate: function () { + const self = this; + const documentValue = this?.taxvat?.value(); + + if (!this.isActiveDocument()) return true; + + if (!documentValue || documentValue === '') { + self.messageContainer.addErrorMessage({'message': ('CPF/CNPJ é obrigatório')}); + return false; + } + + if (!documentValidate.isValidTaxvat(documentValue)) { + self.messageContainer.addErrorMessage({'message': ('CPF/CNPJ não é válido')}); + return false; + } + + return true; + }, + + getData: function() { + return { + 'method': this?.item?.method, + 'additional_data': { + 'document': this?.taxvat?.value() + } + }; + }, + }); } ); + From ae26d571ed50da1f53c272df3cfa232b37c651b6 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Wed, 20 Mar 2024 16:49:09 -0300 Subject: [PATCH 051/187] fix: show the right message on cart --- .../frontend/templates/info/bankslippix.phtml | 2 +- .../templates/onepage/bankslippix.phtml | 6 +- .../method-renderer/vindi-bankslippix.js | 60 +++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/view/frontend/templates/info/bankslippix.phtml b/view/frontend/templates/info/bankslippix.phtml index ad7d83d8..290dc617 100644 --- a/view/frontend/templates/info/bankslippix.phtml +++ b/view/frontend/templates/info/bankslippix.phtml @@ -11,7 +11,7 @@ -
+
QRCode

getQrcodeOriginalPath() ?>

diff --git a/view/frontend/templates/onepage/bankslippix.phtml b/view/frontend/templates/onepage/bankslippix.phtml index 16e512c5..1ee74aa2 100644 --- a/view/frontend/templates/onepage/bankslippix.phtml +++ b/view/frontend/templates/onepage/bankslippix.phtml @@ -13,7 +13,11 @@ -

getQrcodeOriginalPath() ?>

+ +
+ QRCode +

getQrcodeOriginalPath() ?>

+
diff --git a/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js b/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js index 806a1d10..2fc450db 100644 --- a/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js +++ b/view/frontend/web/js/view/payment/method-renderer/vindi-bankslippix.js @@ -4,14 +4,66 @@ define( 'Magento_Checkout/js/view/payment/default', 'mage/translate', 'jquery', - 'mageUtils' + 'mageUtils', + 'Vindi_Payment/js/model/taxvat', + 'Vindi_Payment/js/model/validate' ], - function (_, Component, $t, $, utils) { + + function (_, Component, $t, $, utils, taxvat, documentValidate) { 'use strict'; return Component.extend({ defaults: { - template: 'Vindi_Payment/payment/vindi-bankslip' - } + template: 'Vindi_Payment/payment/vindi-bankslippix', + taxvat: taxvat + }, + + getInfoMessage: function () { + return window?.checkoutConfig?.payment?.vindi_pix?.info_message; + }, + + isActiveDocument: function () { + return window?.checkoutConfig?.payment?.vindi_pix?.enabledDocument; + }, + + checkCpf: function (self, event) { + this.formatTaxvat(event.target) + const message = documentValidate.isValidTaxvat(this?.taxvat?.value()) ? '' : 'CPF/CNPJ inválido'; + $('#cpfResponse').text(message); + }, + + formatTaxvat: function (target) { + taxvat.formatDocument(target) + }, + + validate: function () { + const self = this; + const documentValue = this?.taxvat?.value(); + + if (!this.isActiveDocument()) return true; + + if (!documentValue || documentValue === '') { + self.messageContainer.addErrorMessage({'message': ('CPF/CNPJ é obrigatório')}); + return false; + } + + if (!documentValidate.isValidTaxvat(documentValue)) { + self.messageContainer.addErrorMessage({'message': ('CPF/CNPJ não é válido')}); + return false; + } + + return true; + }, + + getData: function() { + return { + 'method': this?.item?.method, + 'additional_data': { + 'document': this?.taxvat?.value() + } + }; + }, + }); } ); + From bf92938ae957e749f5584cfa03c4147fa661ff1c Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Tue, 26 Mar 2024 11:19:19 -0300 Subject: [PATCH 052/187] feat: added remove card from list --- Api/PaymentProfileRepositoryInterface.php | 6 ++ Block/PaymentProfile/Remove.php | 94 +++++++++++++++++++ Controller/PaymentProfile/Delete.php | 81 ++++++++++++++++ Controller/PaymentProfile/Remove.php | 56 +++++------ Helper/Api.php | 10 +- Model/Payment/AbstractMethod.php | 75 +++++++-------- Model/Payment/Profile.php | 13 ++- Model/Payment/Vindi.php | 2 +- Model/PaymentProfileRepository.php | 10 ++ i18n/pt_BR.csv | 13 +++ view/frontend/layout/customer_account.xml | 4 +- .../layout/vindi_vr_paymentprofile_edit.xml | 6 +- .../layout/vindi_vr_paymentprofile_index.xml | 6 +- .../layout/vindi_vr_paymentprofile_remove.xml | 20 ++++ .../layout/vindi_vr_subscription_index.xml | 2 +- .../account/payment_profile/list.phtml | 6 +- .../account/payment_profile/remove.phtml | 47 ++++++++++ .../account/payment_profile/remove.less | 39 ++++++++ 18 files changed, 402 insertions(+), 88 deletions(-) create mode 100644 Block/PaymentProfile/Remove.php create mode 100644 Controller/PaymentProfile/Delete.php create mode 100644 view/frontend/layout/vindi_vr_paymentprofile_remove.xml create mode 100644 view/frontend/templates/customer/account/payment_profile/remove.phtml create mode 100644 view/frontend/web/css/customer/account/payment_profile/remove.less diff --git a/Api/PaymentProfileRepositoryInterface.php b/Api/PaymentProfileRepositoryInterface.php index 214efb76..d3a46417 100644 --- a/Api/PaymentProfileRepositoryInterface.php +++ b/Api/PaymentProfileRepositoryInterface.php @@ -1,4 +1,5 @@ paymentProfileCollection = $paymentProfileCollection; + $this->subscriptionCollection = $subscriptionCollection; + $this->customerSession = $customerSession; + $this->creditCardTypeSource = $creditCardTypeSource; + parent::__construct($context, $data); + } + + /** + * @param $ccType + * @return mixed|void + */ + public function getCreditCardImage($ccType) + { + $creditCardOptionArray = $this->creditCardTypeSource->toOptionArray(); + + foreach ($creditCardOptionArray as $creditCardOption) { + if ($creditCardOption['label']->getText() == $ccType) { + return $creditCardOption['value']; + } + } + } + + /** + * Retrieve current payment profile based on ID in URL. + * + * @return \Vindi\Payment\Model\PaymentProfile|null + */ + public function getPaymentProfile() + { + $profileId = $this->getRequest()->getParam('id'); + if ($profileId) { + return $this->paymentProfileCollection->getItemById($profileId); + } + return null; + } + + public function getPaymentProfileSubscriptions(): array + { + $profileId = $this->getRequest()->getParam('id'); + if ($profileId) { + $subscritionCollection = $this->subscriptionCollection->addFieldToFilter('payment_profile', $profileId) + ->join(['plan' => 'vindi_plans'], 'main_table.plan = plan.entity_id', 'name'); + + return $subscritionCollection->getItems(); + } + return []; + } + +} diff --git a/Controller/PaymentProfile/Delete.php b/Controller/PaymentProfile/Delete.php new file mode 100644 index 00000000..db7a318f --- /dev/null +++ b/Controller/PaymentProfile/Delete.php @@ -0,0 +1,81 @@ +resultPageFactory = $resultPageFactory; + $this->customerSession = $customerSession; + $this->paymentProfileFactory = $paymentProfileFactory; + $this->paymentProfileManager = $paymentProfileManager; + $this->paymentProfileRepository = $paymentProfileRepository; + } + + /** + * Dispatch request + * + * @param RequestInterface $request + * @return ResponseInterface + * @throws NotFoundException + */ + public function dispatch(RequestInterface $request) + { + if (!$this->customerSession->authenticate()) { + $this->_actionFlag->set('', 'no-dispatch', true); + } + return parent::dispatch($request); + } + + /** + * @return ResponseInterface|\Magento\Framework\Controller\Result\Redirect|\Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + + $paymentProfileId = $this->getRequest()->getParam('entity_id'); + if (!is_numeric($paymentProfileId) || $paymentProfileId <= 0) { + $this->messageManager->addErrorMessage(__('Invalid payment profile ID.')); + return $this->resultRedirectFactory->create()->setPath('vindi_vr/paymentprofile/index'); + } + + $paymentProfile = $this->paymentProfileRepository->getById($paymentProfileId); + try { + $this->paymentProfileManager->deletePaymentProfile($paymentProfile->getData('payment_profile_id')); + $this->paymentProfileRepository->deleteById($paymentProfileId); + $this->messageManager->addSuccessMessage(__('Payment profile successfully removed.')); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage(__('An error occurred while removing the payment profile: ') . $e->getMessage()); + } + + return $this->resultRedirectFactory->create()->setPath('vindi_vr/paymentprofile/index'); + } + +} diff --git a/Controller/PaymentProfile/Remove.php b/Controller/PaymentProfile/Remove.php index 0decde25..9e2c3979 100644 --- a/Controller/PaymentProfile/Remove.php +++ b/Controller/PaymentProfile/Remove.php @@ -9,28 +9,36 @@ use Magento\Framework\App\ResponseInterface; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\View\Result\PageFactory; -use Vindi\Payment\Model\Payment\Profile as PaymentProfileManager; -use Vindi\Payment\Model\PaymentProfileFactory; +/** + * Class Index + * @package Vindi\Payment\Controller\PaymentProfile + + */ class Remove extends Action { + /** + * @var PageFactory + */ protected $resultPageFactory; + + /** + * @var Session + */ protected $customerSession; - protected $paymentProfileFactory; - protected $paymentProfileManager; + /** + * @param Context $context + * @param PageFactory $resultPageFactory + */ public function __construct( Context $context, PageFactory $resultPageFactory, - Session $customerSession, - PaymentProfileFactory $paymentProfileFactory, - PaymentProfileManager $paymentProfileManager + Session $customerSession ) { parent::__construct($context); $this->resultPageFactory = $resultPageFactory; $this->customerSession = $customerSession; - $this->paymentProfileFactory = $paymentProfileFactory; - $this->paymentProfileManager = $paymentProfileManager; } /** @@ -49,33 +57,13 @@ public function dispatch(RequestInterface $request) } /** - * @return ResponseInterface|\Magento\Framework\Controller\Result\Redirect|\Magento\Framework\Controller\ResultInterface + * Execute method. + * + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { - /* - $paymentProfileId = $this->getRequest()->getParam('id'); - if (!is_numeric($paymentProfileId) || $paymentProfileId <= 0) { - $this->messageManager->addErrorMessage(__('Invalid payment profile ID.')); - return $this->resultRedirectFactory->create()->setPath('vindi_vr/paymentprofile/index'); - } - - $paymentProfileId = (int) $paymentProfileId; - try { - $paymentProfile = $this->paymentProfileFactory->create()->load($paymentProfileId); - if ($paymentProfile->getId()) { - $this->paymentProfileManager->deletePaymentProfile($paymentProfile->getPaymentProfileId()); - $paymentProfile->delete(); - $this->messageManager->addSuccessMessage(__('Payment profile successfully removed.')); - } else { - $this->messageManager->addErrorMessage(__('Payment profile not found.')); - } - } catch (\Exception $e) { - $this->messageManager->addErrorMessage(__('An error occurred while removing the payment profile: ') . $e->getMessage()); - } - */ - - return $this->resultRedirectFactory->create()->setPath('vindi_vr/paymentprofile/index'); + $resultPage = $this->resultPageFactory->create(); + return $resultPage; } - } diff --git a/Helper/Api.php b/Helper/Api.php index e04a8cdd..30126cfc 100644 --- a/Helper/Api.php +++ b/Helper/Api.php @@ -78,7 +78,7 @@ public function request($endpoint, $method = 'POST', $data = [], $dataToLog = nu return false; } $url = $this->base_path . $endpoint; - $body = json_encode($data); + $body = !empty($data) ? json_encode($data) : ''; $requestId = number_format(microtime(true), 2, '', ''); $dataToLog = null !== $dataToLog ? json_encode($dataToLog) : $body; $this->logger->info(__(sprintf( @@ -103,9 +103,11 @@ public function request($endpoint, $method = 'POST', $data = [], $dataToLog = nu CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => $method ]; + if (!empty($body)) { $ch_options[CURLOPT_POSTFIELDS] = $body; } + curl_setopt_array($ch, $ch_options); $response = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); @@ -174,6 +176,10 @@ private function checkResponse($response, $endpoint) */ private function getErrorMessage($error, $endpoint) { - return "Erro em $endpoint: {$error['id']}: {$error['parameter']} - {$error['message']}"; + try { + return "Erro em $endpoint: {$error['id']}: {$error['parameter']} - {$error['message']}"; + } catch (\Exception $e) { + return "Erro em $endpoint"; + } } } diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index a807d826..985e72d3 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -25,6 +25,7 @@ use Vindi\Payment\Api\ProductManagementInterface; use Vindi\Payment\Api\SubscriptionInterface; use Vindi\Payment\Helper\Api; +use Vindi\Payment\Model\PaymentProfile; use Vindi\Payment\Model\PaymentProfileFactory; use Vindi\Payment\Model\PaymentProfileRepository; use Magento\Framework\App\ResourceConnection; @@ -36,7 +37,6 @@ */ abstract class AbstractMethod extends OriginAbstractMethod { - /** * @var Api */ @@ -223,8 +223,10 @@ public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null) foreach ($quote->getItems() as $item) { if ($this->helperData->isVindiPlan($item->getProductId())) { $product = $this->helperData->getProductById($item->getProductId()); - if ($product->getData('vindi_billing_trigger_day') > 0 || - $product->getData('vindi_billing_trigger_type') == 'end_of_period') { + if ( + $product->getData('vindi_billing_trigger_day') > 0 || + $product->getData('vindi_billing_trigger_type') == 'end_of_period' + ) { return false; } } @@ -315,24 +317,8 @@ protected function processPayment(InfoInterface $payment, $amount) ]; if ($body['payment_method_code'] === PaymentMethod::CREDIT_CARD) { - $paymentProfile = $this->profile->create($payment, $customerId, $this->getPaymentMethodCode()); - $body['payment_profile'] = ['id' => $paymentProfile['payment_profile']['id']]; - - $paymentProfileModelFactory = $this->paymentProfileFactory->create(); - - $paymentProfileModelFactory->setData([ - 'payment_profile_id' => $paymentProfile['payment_profile']['id'], - 'vindi_customer_id' => $customerId, - 'customer_id' => $order->getCustomerId(), - 'customer_email' => $order->getCustomerEmail(), - 'cc_type' => $payment->getCcType(), - 'cc_last_4' => $payment->getCcLast4(), - 'status' => $paymentProfile["payment_profile"]["status"], - 'token' => $paymentProfile["payment_profile"]["token"], - 'type' => $paymentProfile["payment_profile"]["type"], - ]); - - $this->paymentProfileRepository->save($paymentProfileModelFactory); + $paymentProfile = $this->createPaymentProfile($order, $payment, $customerId); + $body['payment_profile'] = ['id' => $paymentProfile->getData('payment_profile_id')]; } if ($installments = $payment->getAdditionalInformation('installments')) { @@ -377,24 +363,8 @@ private function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterf ]; if ($body['payment_method_code'] === PaymentMethod::CREDIT_CARD) { - $paymentProfile = $this->profile->create($payment, $customerId, $this->getPaymentMethodCode()); - $body['payment_profile'] = ['id' => $paymentProfile['payment_profile']['id']]; - - $paymentProfileModelFactory = $this->paymentProfileFactory->create(); - - $paymentProfileModelFactory->setData([ - 'payment_profile_id' => $paymentProfile['payment_profile']['id'], - 'vindi_customer_id' => $customerId, - 'customer_id' => $order->getCustomerId(), - 'customer_email' => $order->getCustomerEmail(), - 'cc_type' => $payment->getCcType(), - 'cc_last_4' => $payment->getCcLast4(), - 'status' => $paymentProfile["payment_profile"]["status"], - 'token' => $paymentProfile["payment_profile"]["token"], - 'type' => $paymentProfile["payment_profile"]["type"], - ]); - - $this->paymentProfileRepository->save($paymentProfileModelFactory); + $paymentProfile = $this->createPaymentProfile($order, $payment, $customerId); + $body['payment_profile'] = ['id' => $paymentProfile->getData('payment_profile_id')]; } if ($installments = $payment->getAdditionalInformation('installments')) { @@ -591,4 +561,31 @@ protected function isValidStatus($bill) return in_array($bill['status'], $billStatus) || $chargeStatus; } + + /** + * @throws \Magento\Framework\Exception\CouldNotSaveException + */ + public function createPaymentProfile(Order $order, InfoInterface $payment, $customerId) + { + $paymentProfile = $this->profile->create($payment, $customerId, $this->getPaymentMethodCode()); + $paymentProfileData = $paymentProfile['payment_profile']; + + $paymentProfileModel = $this->paymentProfileFactory->create(); + $paymentProfileModel->setData([ + 'payment_profile_id' => $paymentProfileData['id'], + 'vindi_customer_id' => $customerId, + 'customer_id' => $order->getCustomerId(), + 'customer_email' => $order->getCustomerEmail(), + 'cc_name' => $payment->getCcOwner(), + 'cc_type' => $payment->getCcType(), + 'cc_last_4' => $payment->getCcLast4(), + 'status' => $paymentProfileData["status"], + 'token' => $paymentProfileData["token"], + 'type' => $paymentProfileData["type"], + ]); + + $this->paymentProfileRepository->save($paymentProfileModel); + + return $paymentProfileModel; + } } diff --git a/Model/Payment/Profile.php b/Model/Payment/Profile.php index a3e04e7f..65ca91c6 100644 --- a/Model/Payment/Profile.php +++ b/Model/Payment/Profile.php @@ -122,7 +122,16 @@ public function updatePaymentProfile($paymentProfileId, $dataToUpdate) */ public function deletePaymentProfile($paymentProfileId) { - $deleteStatus = $this->api->request('payment_profiles/' . $paymentProfileId, 'DELETE'); - return $deleteStatus; + return $this->api->request('payment_profiles/' . $paymentProfileId, 'DELETE'); + } + + /** + * @param $paymentProfileId + * @return bool|mixed + */ + public function getPaymentProfile($customerId, $firstSix, $lastFour) + { + $query = "customer_id={$customerId} card_number_first_six={$firstSix} card_number_last_four={$lastFour} status=active"; + return $this->api->request('payment_profiles/?query=' . urlencode($query) . '&sort_order=desc', 'GET'); } } diff --git a/Model/Payment/Vindi.php b/Model/Payment/Vindi.php index d239ae3b..df322232 100644 --- a/Model/Payment/Vindi.php +++ b/Model/Payment/Vindi.php @@ -92,7 +92,7 @@ public function assignData(DataObject $data) [ 'cc_type' => $additionalData->getCcType(), 'cc_owner' => $additionalData->getCcOwner(), - 'cc_last_4' => substr($additionalData->getCcNumber(), -4), + 'cc_last_4' => substr((string) $additionalData->getCcNumber(), -4), 'cc_number' => $additionalData->getCcNumber(), 'cc_cid' => $additionalData->getCcCvv(), 'cc_exp_month' => $additionalData->getCcExpMonth(), diff --git a/Model/PaymentProfileRepository.php b/Model/PaymentProfileRepository.php index d134d4c0..cb77e9d4 100644 --- a/Model/PaymentProfileRepository.php +++ b/Model/PaymentProfileRepository.php @@ -49,6 +49,16 @@ public function save(PaymentProfileInterface $paymentProfile) return $paymentProfile; } + public function getByProfileId($profileId) + { + $paymentProfile = $this->paymentProfileFactory->create(); + $this->resource->load($paymentProfile, $profileId, 'payment_profile_id'); + if (!$paymentProfile->getId()) { + throw new NoSuchEntityException(__('Payment profile with id "%1" does not exist.', $profileId)); + } + return $paymentProfile; + } + public function getById($entityId) { $paymentProfile = $this->paymentProfileFactory->create(); diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 56fe5c2a..19cf6ca2 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -39,6 +39,19 @@ "There has been a payment confirmation error. Verify data and try again","Houve um problema na confirmação do pagamento. Verifique os dados e tente novamente." "Enabled","Ativo" "Title","Título" +"My Saved Cards","Meus Cartões Salvos" +"Remove Card","Remover Cartão" +"My Subscriptions","Minhas Assinaturas" +"Remove Payment Profile","Remover forma de pagamento" +"Confirm Remove","Confirmar Exclusão" +"Confirm that you really want to delete the card %1","Confirme que você gostaria de excluir o cartão %1" +"Confirm that you really want to delete the card","Confirme que você gostaria de excluir o cartão" +"This card is associated with the following subscriptions:","Este cartão está associado com as seguintes assinaturas:" +"Before removing it, you must change the payment method of the subscription so that the service continues to work normally. Click on","Antes de excluí-lo é preciso alterar o método de pagamento da assinatura para que o serviço continue funcionando normalmente. Clique em" +"select the subscription by clicking on","selecione a assinatura clicando em" +"VIEW DETAILS","VER DETALHES" +"and click on","e clique em" +"EDIT METHOD","EDITAR MÉTODO" "Api Key","Chave da Api" "Payment Action","Ação de pagamento" "Api Endpoint","Endpoint da Api" diff --git a/view/frontend/layout/customer_account.xml b/view/frontend/layout/customer_account.xml index eed63cf2..b4a0baf3 100644 --- a/view/frontend/layout/customer_account.xml +++ b/view/frontend/layout/customer_account.xml @@ -7,7 +7,7 @@ > vindi_vr/subscription/index - Minhas Assinaturas + My Subscriptions 219 @@ -17,7 +17,7 @@ > vindi_vr/paymentprofile/index - Meus Cartões + My Saved Cards 150 diff --git a/view/frontend/layout/vindi_vr_paymentprofile_edit.xml b/view/frontend/layout/vindi_vr_paymentprofile_edit.xml index 13fe6681..59f5cc0b 100644 --- a/view/frontend/layout/vindi_vr_paymentprofile_edit.xml +++ b/view/frontend/layout/vindi_vr_paymentprofile_edit.xml @@ -1,5 +1,5 @@ - - + + diff --git a/view/frontend/web/css/recurrence.css b/view/frontend/web/css/recurrence.css new file mode 100644 index 00000000..82b7b1f2 --- /dev/null +++ b/view/frontend/web/css/recurrence.css @@ -0,0 +1,32 @@ +.recurrence-buttons { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.recurrence-label { + background-color: transparent; + color: black; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + display: inline-block; + transition: background-color 0.3s ease, border-color 0.3s ease; + border: 1px solid black; + margin-bottom: 20px; +} + +.recurrence-label:hover, .recurrence-input:checked + .recurrence-label { + background-color: #e0e0e0; + color: black; + border-color: black; +} + +.recurrence-label:hover { + border-color: #303030; +} + +.price-prefix, .installment-info { + font-size: 0.6em; + font-weight: normal; +} From ef54b3f5d21053fb5a8d5994546c4c22b9836572 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Tue, 26 Mar 2024 20:51:12 +0000 Subject: [PATCH 054/187] fix: correction when accessing products and adding product --- Plugin/AddCustomOptionToQuoteItem.php | 3 +++ Plugin/HideCartButton.php | 19 +++++++++++++++++++ Plugin/ProductPlugin.php | 5 +++++ etc/frontend/di.xml | 1 + i18n/pt_BR.csv | 3 +++ .../templates/product/recurrence.phtml | 7 ++++++- 6 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 Plugin/HideCartButton.php diff --git a/Plugin/AddCustomOptionToQuoteItem.php b/Plugin/AddCustomOptionToQuoteItem.php index 1f0aae97..f0277904 100644 --- a/Plugin/AddCustomOptionToQuoteItem.php +++ b/Plugin/AddCustomOptionToQuoteItem.php @@ -22,18 +22,21 @@ public function beforeAddProduct( ) { if ($request instanceof \Magento\Framework\DataObject) { $additionalOptions = []; + if ($request->getData('selected_plan_id')) { $additionalOptions[] = [ 'label' => __('Selected Plan ID'), 'value' => $request->getData('selected_plan_id'), ]; } + if ($request->getData('selected_plan_price')) { $additionalOptions[] = [ 'label' => __('Selected Plan Price'), 'value' => $request->getData('selected_plan_price'), ]; } + if ($request->getData('selected_plan_installments')) { $additionalOptions[] = [ 'label' => __('Selected Plan Installments'), diff --git a/Plugin/HideCartButton.php b/Plugin/HideCartButton.php new file mode 100644 index 00000000..c157882a --- /dev/null +++ b/Plugin/HideCartButton.php @@ -0,0 +1,19 @@ +hasData('vindi_enable_recurrence')) { + if ($product->getData('vindi_enable_recurrence') == '1') { + return false; + } + } + + return $result; + } +} diff --git a/Plugin/ProductPlugin.php b/Plugin/ProductPlugin.php index 9caa530b..15c256d9 100644 --- a/Plugin/ProductPlugin.php +++ b/Plugin/ProductPlugin.php @@ -16,6 +16,11 @@ public function afterGetPrice(\Magento\Catalog\Model\Product $subject, $result) { if ($subject->getData('vindi_enable_recurrence') === '1') { $recurrenceDataJson = $subject->getData('vindi_recurrence_data'); + + if (empty($recurrenceDataJson)) { + return $result; + } + $recurrenceData = json_decode($recurrenceDataJson, true); if (is_array($recurrenceData) && !empty($recurrenceData)) { diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index b87c2f26..265cd28d 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -15,5 +15,6 @@ + diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 30b5822c..ecf1e6b5 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -244,3 +244,6 @@ "Recurrence","Recorrência" "Enable Recurrence","Habilitar Recorrência" "Recurrence Data","Dados de Recorrência" +"Selected Plan ID","ID do Plano Selecionado" +"Selected Plan Price","Preço do Plano Selecionado" +"Selected Plan Installments","Parcelas do Plano Selecionado" diff --git a/view/frontend/templates/product/recurrence.phtml b/view/frontend/templates/product/recurrence.phtml index 1344e537..71152016 100644 --- a/view/frontend/templates/product/recurrence.phtml +++ b/view/frontend/templates/product/recurrence.phtml @@ -2,8 +2,13 @@ /** @var \Vindi\Payment\Block\Product\ProductRecurrence $block */ $product = $block->getCurrentProduct(); -if ($product) { +if ($product && $product->getData('vindi_enable_recurrence') === '1') { $recurrenceDataJson = $product->getData('vindi_recurrence_data'); + + if (empty($recurrenceDataJson)) { + return; + } + $recurrenceData = json_decode($recurrenceDataJson, true); if ($recurrenceData && is_array($recurrenceData)) : ?> From 7f3d01e2f5082db9453c61d3a0b0b71d1aa522e5 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Wed, 27 Mar 2024 12:13:09 +0000 Subject: [PATCH 055/187] refactor: add Plugin/HideCartButton.php documentation --- Plugin/HideCartButton.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Plugin/HideCartButton.php b/Plugin/HideCartButton.php index c157882a..c18de826 100644 --- a/Plugin/HideCartButton.php +++ b/Plugin/HideCartButton.php @@ -1,11 +1,19 @@ hasData('vindi_enable_recurrence')) { @@ -13,7 +21,7 @@ public function afterIsSaleable(Product $product, $result) return false; } } - + return $result; } } From cffaf446c157149c4b32609321db1496864e1afb Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Thu, 28 Mar 2024 21:18:13 +0000 Subject: [PATCH 056/187] fix: plan save in admin --- Controller/Adminhtml/VindiPlan/Save.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Controller/Adminhtml/VindiPlan/Save.php b/Controller/Adminhtml/VindiPlan/Save.php index db47b72f..ced2c651 100755 --- a/Controller/Adminhtml/VindiPlan/Save.php +++ b/Controller/Adminhtml/VindiPlan/Save.php @@ -96,7 +96,7 @@ public function execute() if ($existingPlan && $existingPlan->getId()) { $this->plan->save($data); - $data = $this->prepareDataForMagentoStore($data); + $data = $this->prepareDataForMagentoStore($data, $post); $existingPlan->addData($data); $this->vindiPlanRepository->save($existingPlan); @@ -113,7 +113,7 @@ public function execute() $vindiId = $this->plan->save($data); - $data = $this->prepareDataForMagentoStore($data); + $data = $this->prepareDataForMagentoStore($data, $post); $vindiPlan = $this->vindiPlanFactory->create(); $vindiPlan->setData($data); @@ -231,12 +231,13 @@ private function prepareData($post, $name, $code) } /** - * Prepares the data to be saved based on the POST inputs for Magento store. + * Prepares the data to be saved in the Magento store based on the POST inputs. * + * @param array $data The data prepared for saving. * @param array $post The POST data received. - * @return array The data prepared for saving. + * @return array The data prepared for saving in the Magento store. */ - private function prepareDataForMagentoStore($data) + private function prepareDataForMagentoStore($data, $post) { if (!empty($post["settings"]["duration"])) { $data['duration'] = $post["settings"]["duration"]; From f4e82cc6207df59750f0edb46b08672631f10dee Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Thu, 28 Mar 2024 22:21:49 +0000 Subject: [PATCH 057/187] fix: plans form validation --- Controller/Adminhtml/VindiPlan/Save.php | 22 +++++++++++++++++-- i18n/pt_BR.csv | 9 ++++---- .../vindi_payment_vindiplan_form.xml | 5 ++--- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Controller/Adminhtml/VindiPlan/Save.php b/Controller/Adminhtml/VindiPlan/Save.php index ced2c651..226d4b1a 100755 --- a/Controller/Adminhtml/VindiPlan/Save.php +++ b/Controller/Adminhtml/VindiPlan/Save.php @@ -8,6 +8,7 @@ use Vindi\Payment\Model\Vindi\Plan; use Vindi\Payment\Model\VindiPlanFactory; use Vindi\Payment\Model\VindiPlanRepository; +use Magento\Backend\Model\Session; /** * Class Save @@ -35,6 +36,11 @@ class Save extends Action */ protected $dateTime; + /** + * @var Session + */ + protected $session; + /** * Save constructor. * @param Context $context @@ -42,19 +48,22 @@ class Save extends Action * @param VindiPlanFactory $vindiPlanFactory * @param VindiPlanRepository $vindiPlanRepository * @param DateTime $dateTime + * @param Session $session */ public function __construct( Context $context, Plan $plan, VindiPlanFactory $vindiPlanFactory, VindiPlanRepository $vindiPlanRepository, - DateTime $dateTime + DateTime $dateTime, + Session $session ) { parent::__construct($context); $this->plan = $plan; $this->vindiPlanFactory = $vindiPlanFactory; $this->vindiPlanRepository = $vindiPlanRepository; $this->dateTime = $dateTime; + $this->session = $session; } /** @@ -74,10 +83,11 @@ public function execute() if ($validationResult !== true) { $this->messageManager->addWarningMessage($validationResult); + $this->session->setFormData($post); if ($entityId) { $this->_redirect('*/*/edit', ['entity_id' => $entityId]); } else { - $this->_redirect('*/*/'); + $this->_redirect('*/*/new'); } return; } @@ -107,6 +117,7 @@ public function execute() if ($existingPlanByCode && $existingPlanByCode->getId() && $existingPlanByCode->getId() != $entityId) { $this->messageManager->addErrorMessage(__('A plan with the same code already exists.')); + $this->session->setFormData($post); $this->_redirect('*/*/edit', ['entity_id' => $entityId]); return; } @@ -126,6 +137,13 @@ public function execute() } } catch (\Exception $e) { $this->messageManager->addErrorMessage($e->getMessage()); + $this->session->setFormData($post); + if ($entityId) { + $this->_redirect('*/*/edit', ['entity_id' => $entityId]); + } else { + $this->_redirect('*/*/new'); + } + return; } finally { if ($entityId) { $this->_redirect('*/*/edit', ['entity_id' => $entityId]); diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index ecf1e6b5..46931646 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -240,10 +240,11 @@ "The number of installments cannot be greater than the billing interval.","A quantidade de parcelas não pode ser maior que o intervalo de cobrança." "The number of periods to be charged is required for fixed-duration plans.","A quantidade de períodos a ser cobrado é obrigatória para duração por tempo definido." "Duration","Duração" -"Example: To create a monthly plan that lasts for a year, select ""Charge every 1 month 12 times"". To create an unlimited weekly plan, select ""Charge every 7 days indefinitely"". To create a weekly plan, for example, select to charge every 06 months.","Exemplo: Para criar um plano mensal que dure um ano, selecione "Cobrar a cada 1 mês 12 vezes". Já para criar um plano semanal ilimitado, selecione "Cobrar a cada 7 dias por tempo indefinido". Para criar um plano semestral, por exemplo, selecione cobrar a cada 06 meses." +"Example: To create a monthly plan that lasts for a year, select 'Charge every 1 month 12 times'. To create an unlimited weekly plan, select 'Charge every 7 days indefinitely'. To create a weekly plan, for example, select to charge every 06 months.","Exemplo: Para criar um plano mensal que dure um ano, selecione 'Cobrar a cada 1 mês 12 vezes'. Já para criar um plano semanal ilimitado, selecione 'Cobrar a cada 7 dias por tempo indefinido'. Para criar um plano semestral, por exemplo, selecione cobrar a cada 06 meses." "Recurrence","Recorrência" "Enable Recurrence","Habilitar Recorrência" "Recurrence Data","Dados de Recorrência" -"Selected Plan ID","ID do Plano Selecionado" -"Selected Plan Price","Preço do Plano Selecionado" -"Selected Plan Installments","Parcelas do Plano Selecionado" +"Plan ID","ID do Plano" +"Plan Price","Preço do Plano" +"Plan Installments","Parcelas do Plano" +"A plan must be selected for this product.","Um plano deve ser selecionado para este produto." diff --git a/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml index e7a54ec8..f572ec64 100755 --- a/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml +++ b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml @@ -147,9 +147,7 @@ true - - Example: To create a monthly plan that lasts for a year, select "Charge every 1 month 12 times". To create an unlimited weekly plan, select "Charge every 7 days indefinitely". To create a weekly plan, for example, select to charge every 06 months. - + Example: To create a monthly plan that lasts for a year, select 'Charge every 1 month 12 times'. To create an unlimited weekly plan, select 'Charge every 7 days indefinitely'. To create a weekly plan, for example, select to charge every 06 months. @@ -161,6 +159,7 @@ true + true text From d9c6debca97324b054b783a69579dcd49eabc0ea Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Wed, 3 Apr 2024 19:16:58 +0000 Subject: [PATCH 058/187] refactor: refactoring of plugins, observers and templates --- Block/Product/ProductRecurrence.php | 33 ++- Helper/RecurrencePrice.php | 39 ++++ Observer/AdjustPrice.php | 20 +- Plugin/AddCustomOptionToQuoteItem.php | 41 ++-- Plugin/CustomPriceTemplate.php | 24 ++ Plugin/DisableQtyRendering.php | 10 +- Plugin/HideCartButton.php | 39 +++- Plugin/PostProductSave.php | 87 ++++++++ Plugin/ProductPlugin.php | 81 +++++-- Setup/Patch/Data/AddRecurrenceAttributes.php | 9 +- etc/di.xml | 3 + etc/frontend/di.xml | 3 + i18n/pt_BR.csv | 3 + view/frontend/layout/catalog_product_view.xml | 4 +- .../product/price/amount/default.phtml | 45 ++++ .../templates/product/recurrence.phtml | 206 +++++++++--------- 16 files changed, 490 insertions(+), 157 deletions(-) create mode 100644 Helper/RecurrencePrice.php create mode 100644 Plugin/CustomPriceTemplate.php create mode 100644 Plugin/PostProductSave.php create mode 100644 view/frontend/templates/product/price/amount/default.phtml diff --git a/Block/Product/ProductRecurrence.php b/Block/Product/ProductRecurrence.php index bc04064b..bfe9c715 100644 --- a/Block/Product/ProductRecurrence.php +++ b/Block/Product/ProductRecurrence.php @@ -10,8 +10,12 @@ use Magento\Framework\Pricing\Helper\Data as PriceHelper; use Magento\Framework\Locale\FormatInterface as LocaleFormat; use Vindi\Payment\Api\VindiPlanRepositoryInterface; -use Vindi\Payment\Api\Data\VindiPlanInterface; +/** + * Class ProductRecurrence + * + * @package Vindi\Payment\Block\Product + */ class ProductRecurrence extends Template { /** @@ -43,13 +47,14 @@ class ProductRecurrence extends Template protected $_localeFormat; /** - * Constructor + * ProductRecurrence constructor. * * @param Context $context * @param Registry $registry * @param VindiPlanRepositoryInterface $vindiPlanRepository * @param PriceHelper $priceHelper * @param LocaleFormat $localeFormat + * @param \Magento\Catalog\Model\ProductRepository $productRepository * @param array $data */ public function __construct( @@ -135,4 +140,28 @@ public function getPlanInstallmentsById($planId) return 0; } } + + /** + * Checks whether there is price variation between the child products of a configurable item. + * + * @return bool + */ + public function hasPriceVariationForConfigurable() + { + $product = $this->getCurrentProduct(); + if ($product->getTypeId() !== 'configurable') { + return false; + } + + $productTypeInstance = $product->getTypeInstance(); + $usedProducts = $productTypeInstance->getUsedProducts($product); + + $prices = []; + foreach ($usedProducts as $child) { + $prices[] = $child->getFinalPrice(); + } + + $uniquePrices = array_unique($prices); + return count($uniquePrices) > 1; + } } diff --git a/Helper/RecurrencePrice.php b/Helper/RecurrencePrice.php new file mode 100644 index 00000000..2450c35a --- /dev/null +++ b/Helper/RecurrencePrice.php @@ -0,0 +1,39 @@ +getData('vindi_enable_recurrence') === '1') { + $recurrenceDataJson = $product->getData('vindi_recurrence_data'); + if (empty($recurrenceDataJson)) { + return null; + } + + $recurrenceData = json_decode($recurrenceDataJson, true); + if (is_array($recurrenceData) && !empty($recurrenceData)) { + $prices = array_column($recurrenceData, 'price'); + $minPrice = min($prices); + if ($minPrice > 0) { + return $minPrice; + } + } + } + + return null; + } +} diff --git a/Observer/AdjustPrice.php b/Observer/AdjustPrice.php index 48d1eda1..7cc0be7a 100644 --- a/Observer/AdjustPrice.php +++ b/Observer/AdjustPrice.php @@ -1,6 +1,7 @@ getEvent()->getData('quote_item'); - $additionalOptions = $item->getProduct()->getCustomOption('additional_options'); + if ($item->getParentItem()) { + $item = $item->getParentItem(); + } + + $product = $item->getProduct(); + + if ($product->getData('vindi_enable_recurrence') != '1') { + return; + } + $additionalOptions = $product->getCustomOption('additional_options'); if ($additionalOptions) { $options = json_decode($additionalOptions->getValue(), true); foreach ($options as $option) { - if ($option['label'] == 'Selected Plan Price') { + if (isset($option['code']) && $option['code'] === 'plan_price') { $price = $option['value']; $item->setCustomPrice($price); $item->setOriginalCustomPrice($price); diff --git a/Plugin/AddCustomOptionToQuoteItem.php b/Plugin/AddCustomOptionToQuoteItem.php index f0277904..28ddc131 100644 --- a/Plugin/AddCustomOptionToQuoteItem.php +++ b/Plugin/AddCustomOptionToQuoteItem.php @@ -1,6 +1,8 @@ getData('vindi_enable_recurrence') == '1') { + if ($request instanceof \Magento\Framework\DataObject) { + $additionalOptions = []; + + $selectedPlanId = $request->getData('selected_plan_id'); + if (empty($selectedPlanId)) { + throw new LocalizedException(__('A plan must be selected for this product.')); + } - if ($request->getData('selected_plan_id')) { $additionalOptions[] = [ - 'label' => __('Selected Plan ID'), - 'value' => $request->getData('selected_plan_id'), + 'label' => __('Plan ID'), + 'value' => $selectedPlanId, + 'code' => 'plan_id' ]; - } - if ($request->getData('selected_plan_price')) { $additionalOptions[] = [ - 'label' => __('Selected Plan Price'), + 'label' => __('Price'), 'value' => $request->getData('selected_plan_price'), + 'code' => 'plan_price' ]; - } - if ($request->getData('selected_plan_installments')) { $additionalOptions[] = [ - 'label' => __('Selected Plan Installments'), + 'label' => __('Installments'), 'value' => $request->getData('selected_plan_installments'), + 'code' => 'plan_installments' ]; - } - if (!empty($additionalOptions)) { - $product->addCustomOption('additional_options', json_encode($additionalOptions)); + if (!empty($additionalOptions)) { + $product->addCustomOption('additional_options', json_encode($additionalOptions)); + } } } diff --git a/Plugin/CustomPriceTemplate.php b/Plugin/CustomPriceTemplate.php new file mode 100644 index 00000000..af37f8a9 --- /dev/null +++ b/Plugin/CustomPriceTemplate.php @@ -0,0 +1,24 @@ +getProduct(); - if ($product && $product->getData('vindi_enable_recurrence') === '1') { + if ($product && $product->getData('vindi_enable_recurrence') == '1') { return false; } diff --git a/Plugin/HideCartButton.php b/Plugin/HideCartButton.php index c18de826..db6f73aa 100644 --- a/Plugin/HideCartButton.php +++ b/Plugin/HideCartButton.php @@ -2,6 +2,7 @@ namespace Vindi\Payment\Plugin; use Magento\Catalog\Model\Product; +use Magento\Framework\App\RequestInterface; /** * Class HideCartButton @@ -10,18 +11,46 @@ class HideCartButton { /** + * @var RequestInterface + */ + protected $request; + + /** + * HideCartButton constructor. + * @param RequestInterface $request + */ + public function __construct(RequestInterface $request) + { + $this->request = $request; + } + + /** + * Changes the behavior of isSaleable based on custom logic. + * * @param Product $product - * @param $result + * @param bool $result * @return bool */ public function afterIsSaleable(Product $product, $result) { - if ($product->hasData('vindi_enable_recurrence')) { - if ($product->getData('vindi_enable_recurrence') == '1') { - return false; - } + if ($this->isProductPage()) { + return $result; + } + + if ($product->getData('vindi_enable_recurrence') == '1') { + return false; } return $result; } + + /** + * Checks whether the current page is the product detail page. + * + * @return bool + */ + protected function isProductPage() + { + return $this->request->getFullActionName() == 'catalog_product_view'; + } } diff --git a/Plugin/PostProductSave.php b/Plugin/PostProductSave.php new file mode 100644 index 00000000..fa6bd7a1 --- /dev/null +++ b/Plugin/PostProductSave.php @@ -0,0 +1,87 @@ +productRepository = $productRepository; + } + + /** + * After Save Plugin + * + * @param \Magento\Catalog\Model\Product $subject + * @param \Magento\Catalog\Model\Product $result + * @return \Magento\Catalog\Model\Product + */ + public function afterSave( + \Magento\Catalog\Model\Product $subject, + \Magento\Catalog\Model\Product $result + ) { + if ($result->getData('vindi_enable_recurrence') === null) { + return $result; + } + + if ($result->getData('vindi_enable_recurrence') !== '1') { + return $result; + } + + $value = $this->determineValue($result); + $result->setData('vindi_enable_recurrence', $value); + + $result->getResource()->saveAttribute($result, 'vindi_enable_recurrence'); + + return $result; + } + + /** + * @param \Magento\Catalog\Model\Product $product + * @return int + */ + private function determineValue(\Magento\Catalog\Model\Product $product) + { + if (!in_array($product->getTypeId(), ['simple', 'configurable', 'virtual'])) { + return 0; + } elseif ($product->getTypeId() === 'configurable' && $this->hasPriceVariation($product)) { + return 0; + } else { + return 1; + } + } + + /** + * @param \Magento\Catalog\Model\Product $product + * @return bool + */ + private function hasPriceVariation(\Magento\Catalog\Model\Product $product) + { + $variations = $product->getTypeInstance()->getUsedProducts($product); + $price = null; + + foreach ($variations as $child) { + if ($price === null) { + $price = $child->getFinalPrice(); + } elseif ($price != $child->getFinalPrice()) { + return true; + } + } + + return false; + } +} diff --git a/Plugin/ProductPlugin.php b/Plugin/ProductPlugin.php index 15c256d9..2e5c0949 100644 --- a/Plugin/ProductPlugin.php +++ b/Plugin/ProductPlugin.php @@ -1,38 +1,81 @@ getData('vindi_enable_recurrence') === '1') { - $recurrenceDataJson = $subject->getData('vindi_recurrence_data'); + protected $productRepository; - if (empty($recurrenceDataJson)) { - return $result; - } + /** + * @var ConfigurableProductTypeInstance + */ + protected $configurableProductTypeInstance; - $recurrenceData = json_decode($recurrenceDataJson, true); + /** + * @var RecurrencePrice + */ + protected $recurrencePriceHelper; + + /** + * ProductPlugin constructor. + * + * @param ProductRepositoryInterface $productRepository + * @param ConfigurableProductTypeInstance $configurableProductTypeInstance + * @param RecurrencePrice $recurrencePriceHelper + */ + public function __construct( + ProductRepositoryInterface $productRepository, + ConfigurableProductTypeInstance $configurableProductTypeInstance, + RecurrencePrice $recurrencePriceHelper + ) { + $this->productRepository = $productRepository; + $this->configurableProductTypeInstance = $configurableProductTypeInstance; + $this->recurrencePriceHelper = $recurrencePriceHelper; + } - if (is_array($recurrenceData) && !empty($recurrenceData)) { - $prices = array_column($recurrenceData, 'price'); - $minPrice = min($prices); + /** + * After Get Price Plugin + * + * @param Product $subject + * @param float $result + * @return float + */ + public function afterGetPrice(Product $subject, $result) + { + $minPrice = $this->recurrencePriceHelper->getMinRecurrencePrice($subject); + return $minPrice ?? $result; + } - if ($minPrice > 0) { - return $minPrice; - } + /** + * Returns the parent product if it exists. + * + * @param Product $product + * @return Product|null + */ + protected function getParentProduct(Product $product) + { + $parentIds = $this->configurableProductTypeInstance->getParentIdsByChild($product->getId()); + if (!empty($parentIds)) { + $productId = array_shift($parentIds); + try { + return $this->productRepository->getById($productId); + } catch (NoSuchEntityException $e) { + return null; } } - return $result; + return null; } } diff --git a/Setup/Patch/Data/AddRecurrenceAttributes.php b/Setup/Patch/Data/AddRecurrenceAttributes.php index 835fd38e..f6e8cf64 100644 --- a/Setup/Patch/Data/AddRecurrenceAttributes.php +++ b/Setup/Patch/Data/AddRecurrenceAttributes.php @@ -52,11 +52,10 @@ public function __construct( public function apply() { $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); - $entityTypeId = $eavSetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); - $this->addAttributes($eavSetup); $categorySetup = $this->categorySetupFactory->create(['setup' => $this->moduleDataSetup]); + $entityTypeId = $eavSetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); $attributeSetIds = $eavSetup->getAllAttributeSetIds($entityTypeId); foreach ($attributeSetIds as $attributeSetId) { @@ -72,6 +71,8 @@ public function apply() */ protected function addAttributes($eavSetup) { + $applicableProductTypes = 'simple,configurable,virtual'; + $eavSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'vindi_enable_recurrence', @@ -94,7 +95,7 @@ protected function addAttributes($eavSetup) 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, - 'apply_to' => '' + 'apply_to' => $applicableProductTypes ] ); @@ -120,7 +121,7 @@ protected function addAttributes($eavSetup) 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, - 'apply_to' => '' + 'apply_to' => $applicableProductTypes ] ); } diff --git a/etc/di.xml b/etc/di.xml index 26d8aa2e..6fc3552e 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -80,4 +80,7 @@ + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 265cd28d..539ed2d3 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -17,4 +17,7 @@ + + + diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 41b53b74..39ef281c 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -265,3 +265,6 @@ "Plan Price","Preço do Plano" "Plan Installments","Parcelas do Plano" "A plan must be selected for this product.","Um plano deve ser selecionado para este produto." +"Please select a recurrence option before adding to cart.","Por favor, selecione uma opção de recorrência antes de adicionar ao carrinho." +"Choose a recurrence option:","Escolha uma opção de recorrência:" +"Price is not a valid number.","Preço não é um número válido." diff --git a/view/frontend/layout/catalog_product_view.xml b/view/frontend/layout/catalog_product_view.xml index c2378417..9ebef6d1 100644 --- a/view/frontend/layout/catalog_product_view.xml +++ b/view/frontend/layout/catalog_product_view.xml @@ -4,8 +4,8 @@ - - + + diff --git a/view/frontend/templates/product/price/amount/default.phtml b/view/frontend/templates/product/price/amount/default.phtml new file mode 100644 index 00000000..b392c74b --- /dev/null +++ b/view/frontend/templates/product/price/amount/default.phtml @@ -0,0 +1,45 @@ +getSaleableItem(); +$minPrice = $block->getDisplayValue(); + +if ($product && $product->getTypeId() === 'configurable' && $product->getData('vindi_enable_recurrence') === '1') { + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $minPriceHelper = $objectManager->get(\Vindi\Payment\Helper\RecurrencePrice::class); + + $minRecurrencePrice = $minPriceHelper->getMinRecurrencePrice($product); + $minPrice = $minRecurrencePrice !== null ? $minRecurrencePrice : $minPrice; + $shouldDisplayCustomPrice = $minRecurrencePrice !== null; +} else { + $shouldDisplayCustomPrice = false; +} +?> +getSchema() ? ' itemprop="offers" itemscope itemtype="http://schema.org/Offer"' : '' ?>> + getDisplayLabel()) :?> + escapeHtml($block->getDisplayLabel()) ?> + + + getPriceId()) :?> id="escapeHtmlAttr($block->getPriceId()) ?>" + getPriceDisplayLabel()) ? 'data-label="' . $block->escapeHtmlAttr($block->getPriceDisplayLabel() . $block->getPriceDisplayInclExclTaxes()) . '"' : '' ?> + data-price-amount="escapeHtmlAttr($minPrice) ?>" + class="price-wrapper escapeHtmlAttr($block->getPriceWrapperCss()) ?>" + >escapeHtml($block->formatCurrency($minPrice, (bool)$block->getIncludeContainer()), ['span']) ?> + + getPriceId()) :?> id="escapeHtmlAttr($block->getPriceId()) ?>" + getPriceDisplayLabel()) ? 'data-label="' . $block->escapeHtmlAttr($block->getPriceDisplayLabel() . $block->getPriceDisplayInclExclTaxes()) . '"' : '' ?> + data-price-amount="escapeHtmlAttr($block->getDisplayValue()) ?>" + data-price-type="escapeHtmlAttr($block->getPriceType()) ?>" + class="price-wrapper escapeHtmlAttr($block->getPriceWrapperCss()) ?>" + >escapeHtml($block->formatCurrency($block->getDisplayValue(), (bool)$block->getIncludeContainer()), ['span']) ?> + + + hasAdjustmentsHtml()) :?> + getAdjustmentsHtml() ?> + + getSchema()) :?> + + + + diff --git a/view/frontend/templates/product/recurrence.phtml b/view/frontend/templates/product/recurrence.phtml index 71152016..882c4abd 100644 --- a/view/frontend/templates/product/recurrence.phtml +++ b/view/frontend/templates/product/recurrence.phtml @@ -5,122 +5,126 @@ $product = $block->getCurrentProduct(); if ($product && $product->getData('vindi_enable_recurrence') === '1') { $recurrenceDataJson = $product->getData('vindi_recurrence_data'); - if (empty($recurrenceDataJson)) { - return; - } - - $recurrenceData = json_decode($recurrenceDataJson, true); - - if ($recurrenceData && is_array($recurrenceData)) : ?> -
-
+ + + +
+
+ +
+ + +
+
+
+
+ + + +
+
+
+
+
+
+
+ +
diff --git a/view/frontend/web/template/payment/vindi-bankslippix.html b/view/frontend/web/template/payment/vindi-bankslippix.html index b878159f..e0dbea1c 100644 --- a/view/frontend/web/template/payment/vindi-bankslippix.html +++ b/view/frontend/web/template/payment/vindi-bankslippix.html @@ -1,9 +1,3 @@ -
- +
diff --git a/view/frontend/web/template/payment/vindi-pix.html b/view/frontend/web/template/payment/vindi-pix.html index 5a7a1e4c..e292c314 100644 --- a/view/frontend/web/template/payment/vindi-pix.html +++ b/view/frontend/web/template/payment/vindi-pix.html @@ -1,9 +1,3 @@ -
Date: Tue, 16 Apr 2024 17:51:06 -0300 Subject: [PATCH 070/187] feat: saved credit cards on checkout --- Model/ConfigProvider.php | 13 ++- Model/Payment/AbstractMethod.php | 31 ++++--- Model/Payment/Vindi.php | 61 ++++++++----- .../view/payment/method-renderer/vindi-cc.js | 21 ++++- .../web/template/payment/cc-form.html | 87 ++++++++++++------- 5 files changed, 141 insertions(+), 72 deletions(-) diff --git a/Model/ConfigProvider.php b/Model/ConfigProvider.php index 1c19e726..421be890 100644 --- a/Model/ConfigProvider.php +++ b/Model/ConfigProvider.php @@ -190,13 +190,22 @@ private function getAttributeValue(ProductInterface $product, $attribute = '') public function getPaymentProfiles(): array { + $paymentProfiles = []; if ($this->customerSession->isLoggedIn()) { $customerId = $this->customerSession->getCustomerId(); $this->paymentProfileCollection->addFieldToFilter('customer_id', $customerId); - return $this->paymentProfileCollection->getItems(); + $this->paymentProfileCollection->addFieldToFilter('cc_type', ['neq' => '']); + $this->paymentProfileCollection->addFieldToFilter('cc_type', ['neq' => null]); + foreach ($this->paymentProfileCollection as $paymentProfile) { + $paymentProfiles[] = [ + 'id' => $paymentProfile->getId(), + 'card_number' => (string) $paymentProfile->getCcLast4(), + 'card_type' => (string) $paymentProfile->getCcType() + ]; + } } - return []; + return $paymentProfiles; } /** diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 8fdf72dd..11d35392 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -81,32 +81,32 @@ abstract class AbstractMethod extends OriginAbstractMethod /** * @var ProductManagementInterface */ - private $productManagement; + protected $productManagement; /** * @var \Vindi\Payment\Helper\Data */ - private $helperData; + protected $helperData; /** * @var PlanManagementInterface */ - private $planManagement; + protected $planManagement; /** * @var SubscriptionInterface */ - private $subscriptionRepository; + protected $subscriptionRepository; /** * @var PaymentProfileFactory */ - private $paymentProfileFactory; + protected $paymentProfileFactory; /** * @var PaymentProfileRepository */ - private $paymentProfileRepository; + protected $paymentProfileRepository; /** * @var ResourceConnection @@ -329,7 +329,9 @@ protected function processPayment(InfoInterface $payment, $amount) ]; if ($body['payment_method_code'] === PaymentMethod::CREDIT_CARD) { - $paymentProfile = $this->createPaymentProfile($order, $payment, $customerId); + $paymentProfile = ($payment->getAdditionalInformation('payment_profile')) + ? $this->getPaymentProfile((int) $payment->getAdditionalInformation('payment_profile')) + : $this->createPaymentProfile($order, $payment, $customerId); $body['payment_profile'] = ['id' => $paymentProfile->getData('payment_profile_id')]; } @@ -350,13 +352,18 @@ protected function processPayment(InfoInterface $payment, $amount) return $this->handleError($order); } + protected function getPaymentProfile(int $paymentProfileId): PaymentProfile + { + return $this->paymentProfileRepository->getById($paymentProfileId); + } + /** * @param InfoInterface $payment * @param OrderItemInterface $orderItem * @return mixed * @throws LocalizedException */ - private function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterface $orderItem) + protected function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterface $orderItem) { try { $order = $payment->getOrder(); @@ -436,7 +443,7 @@ private function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterf * @return void * @throws \Exception */ - private function saveSubscriptionToDatabase(array $subscription, Order $order) + protected function saveSubscriptionToDatabase(array $subscription, Order $order) { $tableName = $this->resourceConnection->getTableName('vindi_subscription'); $startAt = new \DateTime($subscription['start_at']); @@ -466,7 +473,7 @@ private function saveSubscriptionToDatabase(array $subscription, Order $order) * @param Order $order * @return OrderItemInterface|bool */ - private function isSubscriptionOrder(Order $order) + protected function isSubscriptionOrder(Order $order) { foreach ($order->getItems() as $item) { try { @@ -490,7 +497,7 @@ private function isSubscriptionOrder(Order $order) * @param Order $order * @throws LocalizedException */ - private function handleError(Order $order) + protected function handleError(Order $order) { $this->psrLogger->error(__(sprintf('Error on order payment %d.', $order->getId()))); $message = __('There has been a payment confirmation error. Verify data and try again'); @@ -536,7 +543,7 @@ protected function handleBankSplitAdditionalInformation(InfoInterface $payment, * @param array $subscription * @return bool */ - private function successfullyPaid(array $body, $bill, array $subscription = []) + protected function successfullyPaid(array $body, $bill, array $subscription = []) { // nova validação para permitir pedidos com pagamento/fatura pendente if (!$bill) { diff --git a/Model/Payment/Vindi.php b/Model/Payment/Vindi.php index c17b9b40..5aae3f79 100644 --- a/Model/Payment/Vindi.php +++ b/Model/Payment/Vindi.php @@ -5,6 +5,7 @@ use Magento\Framework\DataObject; use Magento\Quote\Api\Data\PaymentInterface; use Vindi\Payment\Block\Info\Cc; +use Vindi\Payment\Model\PaymentProfile; class Vindi extends \Vindi\Payment\Model\Payment\AbstractMethod { @@ -84,28 +85,36 @@ public function assignData(DataObject $data) $additionalData = new DataObject($additionalData ?: []); } - $info = $this->getInfoInstance(); + $ccType = $additionalData->getCcType(); + $ccOwner = $additionalData->getCcOwner(); + $ccLast4 = substr((string) $additionalData->getCcNumber(), -4); + $info = $this->getInfoInstance(); $info->setAdditionalInformation('installments', $additionalData->getCcInstallments()); + $paymentProfileId = (string) $additionalData->getData('payment_profile'); + if ($paymentProfileId) { + $info->setAdditionalInformation('payment_profile', $paymentProfileId); + $paymentProfile = $this->getPaymentProfile((int) $paymentProfileId); + $ccType = $paymentProfile->getCcType(); + $ccOwner = $paymentProfile->getCcName(); + $ccLast4 = $paymentProfile->getCcLast4(); + } - $info->addData( - [ - 'cc_type' => $additionalData->getCcType(), - 'cc_owner' => $additionalData->getCcOwner(), - 'cc_last_4' => substr((string) $additionalData->getCcNumber(), -4), - 'cc_number' => $additionalData->getCcNumber(), - 'cc_cid' => $additionalData->getCcCvv(), - 'cc_exp_month' => $additionalData->getCcExpMonth(), - 'cc_exp_year' => $additionalData->getCcExpYear(), - 'cc_ss_issue' => $additionalData->getCcSsIssue(), - 'cc_ss_start_month' => $additionalData->getCcSsStartMonth(), - 'cc_ss_start_year' => $additionalData->getCcSsStartYear() - ] - ); - - parent::assignData($data); - return $this; + $info->addData([ + 'cc_type' => $ccType, + 'cc_owner' => $ccOwner, + 'cc_last_4' => $ccLast4, + 'cc_number' => (string) $additionalData->getCcNumber(), + 'cc_cid' => (string) $additionalData->getCcCvv(), + 'cc_exp_month' => (string) $additionalData->getCcExpMonth(), + 'cc_exp_year' => (string) $additionalData->getCcExpYear(), + 'cc_ss_issue' => (string) $additionalData->getCcSsIssue(), + 'cc_ss_start_month' => (string) $additionalData->getCcSsStartMonth(), + 'cc_ss_start_year' => (string) $additionalData->getCcSsStartYear() + ]); + + return parent::assignData($data); } /** @@ -119,14 +128,18 @@ protected function getPaymentMethodCode() public function validate() { $info = $this->getInfoInstance(); - $ccNumber = $info->getCcNumber(); - // remove credit card non-numbers - $ccNumber = preg_replace('/\D/', '', $ccNumber); + $paymentProfile = $info->getAdditionalInformation('payment_profile'); + + if (!$paymentProfile) { + $ccNumber = $info->getCcNumber(); + // remove credit card non-numbers + $ccNumber = preg_replace('/\D/', '', (string)$ccNumber); - $info->setCcNumber($ccNumber); + $info->setCcNumber($ccNumber); - if (!$this->paymentMethod->isCcTypeValid($info->getCcType())) { - throw new \Exception(__('Credit card type is not allowed for this payment method.')); + if (!$this->paymentMethod->isCcTypeValid($info->getCcType())) { + throw new \Exception(__('Credit card type is not allowed for this payment method.')); + } } return $this; diff --git a/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js b/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js index d8860341..1cfec0e3 100644 --- a/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js +++ b/view/frontend/web/js/view/payment/method-renderer/vindi-cc.js @@ -150,7 +150,6 @@ define([ //Set credit card number to credit card data object this.creditCardNumber.subscribe(function (value) { var result; - self.selectedCardType(null); if (value == '' || value == null) { return false; @@ -290,6 +289,26 @@ define([ }, getFormattedPrice: function (price) { return priceUtils.formatPrice(price, quote.getPriceFormat()); + }, + + getPaymentProfiles: function () { + let paymentProfiles = []; + const savedCards = window.checkoutConfig.payment?.vindi_cc?.saved_cards; + + if (savedCards) { + savedCards.forEach(function (card) { + paymentProfiles.push({ + 'value': card.id, + 'text': `${card.card_type.toUpperCase()} xxxx-${card.card_number}` + }); + }); + } + + return paymentProfiles; + }, + + hasPaymentProfiles: function () { + return this.getPaymentProfiles().length > 0; } }); } diff --git a/view/frontend/web/template/payment/cc-form.html b/view/frontend/web/template/payment/cc-form.html index 29d5b426..c07dcf41 100644 --- a/view/frontend/web/template/payment/cc-form.html +++ b/view/frontend/web/template/payment/cc-form.html @@ -4,42 +4,27 @@
-
- -
- -
-
+ +
-
+ -
+
@@ -65,7 +50,43 @@
-
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
@@ -106,7 +127,7 @@
-
+
@@ -135,7 +156,7 @@ -
+
From 95ad6baa12796215289a5887ac28ee83450398a4 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Wed, 17 Apr 2024 07:57:19 -0300 Subject: [PATCH 071/187] fix: error validate card when wasn't filled yet --- Model/Payment/Vindi.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Model/Payment/Vindi.php b/Model/Payment/Vindi.php index 5aae3f79..ba3cf1f7 100644 --- a/Model/Payment/Vindi.php +++ b/Model/Payment/Vindi.php @@ -132,13 +132,15 @@ public function validate() if (!$paymentProfile) { $ccNumber = $info->getCcNumber(); - // remove credit card non-numbers - $ccNumber = preg_replace('/\D/', '', (string)$ccNumber); + if ($ccNumber) { + // remove credit card non-numbers + $ccNumber = preg_replace('/\D/', '', (string)$ccNumber); - $info->setCcNumber($ccNumber); + $info->setCcNumber($ccNumber); - if (!$this->paymentMethod->isCcTypeValid($info->getCcType())) { - throw new \Exception(__('Credit card type is not allowed for this payment method.')); + if (!$this->paymentMethod->isCcTypeValid($info->getCcType())) { + throw new \Exception(__('Credit card type is not allowed for this payment method.')); + } } } From 972bf74b3903e03da942e3e46d509d9a329f27d9 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Wed, 17 Apr 2024 08:00:22 -0300 Subject: [PATCH 072/187] fix: error validate card when wasn't filled yet --- i18n/pt_BR.csv | 2 ++ view/frontend/web/template/payment/cc-form.html | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 6c534d2c..e242db05 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -278,3 +278,5 @@ "The number of installments cannot be greater than the number of installments of the plan.","A quantidade de parcelas não pode ser maior que a quantidade de parcelas do plano." "In cash","À vista" "Customer will choose in checkout","Cliente escolherá na finalização" +"Your saved cards","Seus cartões salvos" +"New Card.","Novo Cartão" diff --git a/view/frontend/web/template/payment/cc-form.html b/view/frontend/web/template/payment/cc-form.html index c07dcf41..f6565314 100644 --- a/view/frontend/web/template/payment/cc-form.html +++ b/view/frontend/web/template/payment/cc-form.html @@ -8,7 +8,7 @@
From 887c12f627d0a9c38ef3b6309c1ff581a7b32230 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Fri, 19 Apr 2024 18:31:20 +0000 Subject: [PATCH 073/187] feat: created the display of orders in the subscription list --- Api/Data/SubscriptionOrderInterface.php | 34 +++++ ...SubscriptionOrderSearchResultInterface.php | 25 ++++ Api/SubscriptionOrderRepositoryInterface.php | 39 +++++ Block/Adminhtml/Subscription/View.php | 25 ++++ Model/Payment/AbstractMethod.php | 23 +++ Model/ResourceModel/SubscriptionOrder.php | 72 +++++++++ .../SubscriptionOrder/Collection.php | 44 ++++++ Model/SubscriptionOrder.php | 87 +++++++++++ Model/SubscriptionOrderRepository.php | 141 ++++++++++++++++++ Model/SubscriptionOrderSearchResult.php | 17 +++ Observer/OrderSaveAfter.php | 65 ++++++++ etc/db_schema.xml | 12 ++ etc/di.xml | 21 +++ etc/events.xml | 3 + .../templates/subscription/view.phtml | 36 +++++ 15 files changed, 644 insertions(+) create mode 100755 Api/Data/SubscriptionOrderInterface.php create mode 100755 Api/Data/SubscriptionOrderSearchResultInterface.php create mode 100755 Api/SubscriptionOrderRepositoryInterface.php create mode 100755 Model/ResourceModel/SubscriptionOrder.php create mode 100755 Model/ResourceModel/SubscriptionOrder/Collection.php create mode 100755 Model/SubscriptionOrder.php create mode 100755 Model/SubscriptionOrderRepository.php create mode 100755 Model/SubscriptionOrderSearchResult.php create mode 100644 Observer/OrderSaveAfter.php diff --git a/Api/Data/SubscriptionOrderInterface.php b/Api/Data/SubscriptionOrderInterface.php new file mode 100755 index 00000000..361c16a3 --- /dev/null +++ b/Api/Data/SubscriptionOrderInterface.php @@ -0,0 +1,34 @@ + + */ +interface SubscriptionOrderSearchResultInterface extends SearchResultsInterface +{ + /** + * @return SubscriptionOrderInterface[] + */ + public function getItems(); + + /** + * @param SubscriptionOrderInterface[] $items + * @return void + */ + public function setItems(array $items); +} diff --git a/Api/SubscriptionOrderRepositoryInterface.php b/Api/SubscriptionOrderRepositoryInterface.php new file mode 100755 index 00000000..6c46a646 --- /dev/null +++ b/Api/SubscriptionOrderRepositoryInterface.php @@ -0,0 +1,39 @@ + + */ +interface SubscriptionOrderRepositoryInterface +{ + /** + * @param int $entityId + * @return SubscriptionOrderInterface + */ + public function getById(int $entityId): SubscriptionOrderInterface; + + /** + * @param SearchCriteriaInterface $searchCriteria + * @return SubscriptionOrderSearchResultInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria): SubscriptionOrderSearchResultInterface; + + /** + * @param SubscriptionOrderInterface $subscriptionorder + */ + public function save(SubscriptionOrderInterface $subscriptionorder): void; + + /** + * @param SubscriptionOrderInterface $subscriptionorder + */ + public function delete(SubscriptionOrderInterface $subscriptionorder): void; +} diff --git a/Block/Adminhtml/Subscription/View.php b/Block/Adminhtml/Subscription/View.php index 65f44d9c..0c2839c8 100644 --- a/Block/Adminhtml/Subscription/View.php +++ b/Block/Adminhtml/Subscription/View.php @@ -8,6 +8,7 @@ use Magento\Backend\Block\Widget\Context; use Magento\Framework\Registry; use Vindi\Payment\Helper\Api; +use Vindi\Payment\Model\ResourceModel\SubscriptionOrder\CollectionFactory as SubscriptionOrderCollectionFactory; /** * Class View @@ -32,22 +33,30 @@ class View extends Container */ private $registry; + /** + * @var SubscriptionOrderCollectionFactory + */ + private $subscriptionsOrderCollectionFactory; + /** * View constructor. * @param Context $context * @param Registry $registry + * @param SubscriptionOrderCollectionFactory $subscriptionsOrderCollectionFactory * @param Api $api * @param array $data */ public function __construct( Context $context, Registry $registry, + SubscriptionOrderCollectionFactory $subscriptionsOrderCollectionFactory, Api $api, array $data = [] ) { parent::__construct($context, $data); $this->api = $api; $this->registry = $registry; + $this->subscriptionsOrderCollectionFactory = $subscriptionsOrderCollectionFactory; } /** @@ -323,6 +332,22 @@ public function getSubscriptionId() return 0; } + /** + * @return array + */ + public function getLinkedOrders() + { + $subscriptionId = $this->getSubscriptionId(); + if (!$subscriptionId) { + return []; + } + + $collection = $this->subscriptionsOrderCollectionFactory->create(); + $collection->addFieldToFilter('subscription_id', $subscriptionId); + + return $collection->getItems(); + } + /** * @return array|null */ diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 8fdf72dd..ae0261c5 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -418,6 +418,7 @@ private function handleSubscriptionOrder(InfoInterface $payment, OrderItemInterf $billId = $bill['id'] ?? 0; $order->setVindiBillId($billId); $order->setVindiSubscriptionId($responseData['subscription']['id']); + $this->saveOrderToSubscriptionOrdersTable($order); return $billId; } else { $this->subscriptionRepository->deleteAndCancelBills($subscription['id']); @@ -627,4 +628,26 @@ public function createPaymentProfile(Order $order, InfoInterface $payment, $cust return $paymentProfileModel; } + + /** + * @param Order $order + * @param $billId + */ + private function saveOrderToSubscriptionOrdersTable(Order $order) + { + $tableName = $this->resourceConnection->getTableName('vindi_subscription_orders'); + + $data = [ + 'increment_id' => $order->getIncrementId(), + 'subscription_id' => $order->getVindiSubscriptionId(), + 'created_at' => $this->date->date()->format('Y-m-d H:i:s'), + 'total' => $order->getGrandTotal() + ]; + + try { + $this->connection->insert($tableName, $data); + } catch (\Exception $e) { + $this->psrLogger->error('Error saving order to subscription orders table: ' . $e->getMessage()); + } + } } diff --git a/Model/ResourceModel/SubscriptionOrder.php b/Model/ResourceModel/SubscriptionOrder.php new file mode 100755 index 00000000..de2fb86f --- /dev/null +++ b/Model/ResourceModel/SubscriptionOrder.php @@ -0,0 +1,72 @@ + + */ +class SubscriptionOrder extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + /** + * @var SubscriptionOrderFactory + */ + protected $subscriptionorderFactory; + + /** + * @var ReadFactory + */ + private $readFactory; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * SubscriptionOrder constructor. + * @param Context $context + * @param SubscriptionOrderFactory $subscriptionorderFactory + * @param ReadFactory $readFactory + * @param Filesystem $filesystem + */ + public function __construct( + Context $context, + SubscriptionOrderFactory $subscriptionorderFactory, + ReadFactory $readFactory, + Filesystem $filesystem + ) { + $this->subscriptionorderFactory = $subscriptionorderFactory; + $this->readFactory = $readFactory; + $this->filesystem = $filesystem; + parent::__construct($context); + } + + protected function _construct() + { + $this->_init('vindi_subscription_orders', 'entity_id'); + } + + /** + * @param $id + * @return \Vindi\Payment\Model\SubscriptionOrder + * @throws NoSuchEntityException + */ + public function getById($id) + { + $subscriptionorder = $this->subscriptionorderFactory->create(); + $this->load($subscriptionorder, $id); + + if (!$subscriptionorder->getId()) { + throw new NoSuchEntityException(__('SubscriptionOrder with id "%1" does not exist.', $id)); + } + + return $subscriptionorder; + } +} diff --git a/Model/ResourceModel/SubscriptionOrder/Collection.php b/Model/ResourceModel/SubscriptionOrder/Collection.php new file mode 100755 index 00000000..35da507f --- /dev/null +++ b/Model/ResourceModel/SubscriptionOrder/Collection.php @@ -0,0 +1,44 @@ + + */ +class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection +{ + protected $_idFieldName = 'entity_id'; + protected $_eventPrefix = 'vindi_payment_subscriptionorders_collection'; + protected $_eventObject = 'subscriptionorder_collection'; + + /** + * @var array|null + */ + protected $_options; + + /** + * Define resource model + * + * @return void + */ + protected function _construct() + { + $this->_init('Vindi\Payment\Model\SubscriptionOrder', 'Vindi\Payment\Model\ResourceModel\SubscriptionOrder'); + } + + public function toOptionArray() + { + $collection = $this->getItems(); + $this->_options = [['label' => '', 'value' => '']]; + + foreach ($collection as $subscriptionorder) { + $this->_options[] = [ + 'label' => __($subscriptionorder->getName()), + 'value' => $subscriptionorder->getId() + ]; + } + + return $this->_options; + } +} diff --git a/Model/SubscriptionOrder.php b/Model/SubscriptionOrder.php new file mode 100755 index 00000000..0db147a9 --- /dev/null +++ b/Model/SubscriptionOrder.php @@ -0,0 +1,87 @@ +_init('Vindi\Payment\Model\ResourceModel\SubscriptionOrder'); + } + + public function getId() + { + return $this->getData(self::ENTITY_ID); + } + + public function setId($entityId) + { + $this->setData(self::ENTITY_ID, $entityId); + } + + public function getOrderId() + { + return $this->getData(self::ORDER_ID); + } + + public function setOrderId($orderId) + { + $this->setData(self::ORDER_ID, $orderId); + } + + public function getIncrementId() + { + return $this->getData(self::INCREMENT_ID); + } + + public function setIncrementId($incrementId) + { + $this->setData(self::INCREMENT_ID, $incrementId); + } + + public function getSubscriptionId() + { + return $this->getData(self::SUBSCRIPTION_ID); + } + + public function setSubscriptionId($subscriptionId) + { + $this->setData(self::SUBSCRIPTION_ID, $subscriptionId); + } + + public function getCreatedAt() + { + return $this->getData(self::CREATED_AT); + } + + public function setCreatedAt($createdAt) + { + $this->setData(self::CREATED_AT, $createdAt); + } + + public function getTotal() + { + return $this->getData(self::TOTAL); + } + + public function setTotal($total) + { + $this->setData(self::TOTAL, $total); + } + + public function getStatus() + { + return $this->getData(self::STATUS); + } + + public function setStatus($status) + { + $this->setData(self::STATUS, $status); + } +} diff --git a/Model/SubscriptionOrderRepository.php b/Model/SubscriptionOrderRepository.php new file mode 100755 index 00000000..ff1b449f --- /dev/null +++ b/Model/SubscriptionOrderRepository.php @@ -0,0 +1,141 @@ + + */ +class SubscriptionOrderRepository implements SubscriptionOrderRepositoryInterface +{ + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var ResourceModel + */ + private $resourceModel; + + /** + * @var SubscriptionOrderInterfaceFactory + */ + private $subscriptionorderFactory; + + /** + * @var CollectionProcessorInterface + */ + private $collectionProcessor; + + /** + * @var SubscriptionOrderSearchResultInterfaceFactory + */ + private $searchResultsFactory; + + /** + * SubscriptionOrder constructor. + * @param ResourceModel $resourceModel + * @param SubscriptionOrderInterfaceFactory $subscriptionorderFactory + * @param CollectionFactory $collectionFactory + * @param CollectionProcessorInterface $collectionProcessor + * @param SubscriptionOrderSearchResultInterfaceFactory $searchResultsFactory + */ + public function __construct( + ResourceModel $resourceModel, + SubscriptionOrderInterfaceFactory $subscriptionorderFactory, + CollectionFactory $collectionFactory, + CollectionProcessorInterface $collectionProcessor, + SubscriptionOrderSearchResultInterfaceFactory $searchResultsFactory + ) { + $this->resourceModel = $resourceModel; + $this->subscriptionorderFactory = $subscriptionorderFactory; + $this->collectionFactory = $collectionFactory; + $this->collectionProcessor = $collectionProcessor; + $this->searchResultsFactory = $searchResultsFactory; + } + + /** + * @param int $entityId + * @return SubscriptionOrderInterface + * @throws NoSuchEntityException + */ + public function getById(int $entityId): SubscriptionOrderInterface + { + try { + /** @var SubscriptionOrderInterface $subscriptionorder */ + $subscriptionorder = $this->subscriptionorderFactory->create(); + $this->resourceModel->load($subscriptionorder, $entityId); + } catch (\Exception $e) { + throw new NoSuchEntityException(__('Error during load subscriptionorder by Entity ID')); + } + return $subscriptionorder; + } + + /** + * @param SearchCriteriaInterface $searchCriteria + * @return SubscriptionOrderSearchResultInterface + */ + public function getList(SearchCriteriaInterface $searchCriteria): SubscriptionOrderSearchResultInterface + { + /** @var Collection $collection */ + $collection = $this->collectionFactory->create(); + $this->collectionProcessor->process($searchCriteria, $collection); + + /** @var SubscriptionOrderSearchResultInterface $searchResult */ + $searchResult = $this->searchResultsFactory->create(); + + $searchResult->setItems($collection->getItems()) + ->setSearchCriteria($searchCriteria) + ->setTotalCount($collection->getSize()); + + return $searchResult; + } + + /** + * @param SubscriptionOrderInterface $subscriptionorder + * @return void + * @throws CouldNotSaveException + */ + public function save(SubscriptionOrderInterface $subscriptionorder): void + { + try { + $this->resourceModel->save($subscriptionorder); + } catch (\Exception $e) { + throw new CouldNotSaveException(__('Error when saving subscriptionorder')); + } + } + + /** + * @param SubscriptionOrderInterface $subscriptionorder + * @return void + * @throws CouldNotDeleteException + */ + public function delete(SubscriptionOrderInterface $subscriptionorder): void + { + try { + $this->resourceModel->delete($subscriptionorder); + } catch (\Exception $e) { + throw new CouldNotDeleteException(__('Could not delete subscriptionorder.')); + } + } +} diff --git a/Model/SubscriptionOrderSearchResult.php b/Model/SubscriptionOrderSearchResult.php new file mode 100755 index 00000000..265347b2 --- /dev/null +++ b/Model/SubscriptionOrderSearchResult.php @@ -0,0 +1,17 @@ + + */ +class SubscriptionOrderSearchResult extends SearchResults implements SubscriptionOrderSearchResultInterface +{ +} diff --git a/Observer/OrderSaveAfter.php b/Observer/OrderSaveAfter.php new file mode 100644 index 00000000..c9505432 --- /dev/null +++ b/Observer/OrderSaveAfter.php @@ -0,0 +1,65 @@ +resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * @param Observer $observer + */ + public function execute(Observer $observer) + { + $order = $observer->getEvent()->getOrder(); + + $incrementId = $order->getIncrementId(); + $orderId = $order->getId(); + $orderStatus = $order->getStatus(); + + if ($orderId && $incrementId) { + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName('vindi_subscription_orders'); + + try { + $connection->update( + $tableName, + ['order_id' => $orderId, 'status' => $orderStatus], + ['increment_id = ?' => $incrementId] + ); + } catch (\Exception $e) { + $this->logger->error('Error updating the vindi_subscription_orders table.', ['exception' => $e]); + } + } else { + $this->logger->info('Order ID or Increment ID is missing.', ['increment_id' => $incrementId, 'order_id' => $orderId]); + } + } +} diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 93377b49..8344f9f1 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -63,4 +63,16 @@ + + + + + + + + + + + +
diff --git a/etc/di.xml b/etc/di.xml index e72abd28..46fae1d7 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -87,4 +87,25 @@ + + + + + + + + + + Vindi\Payment\Model\ResourceModel\SubscriptionOrder\Grid\Collection + Vindi\Payment\Model\ResourceModel\SubscriptionOrder\Form\DataProvider + + + + + + + vindi_subscription_orders + Vindi\Payment\Model\ResourceModel\SubscriptionOrder + + diff --git a/etc/events.xml b/etc/events.xml index f58c3970..de0d194d 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -10,4 +10,7 @@ + + + diff --git a/view/adminhtml/templates/subscription/view.phtml b/view/adminhtml/templates/subscription/view.phtml index a1e425c5..a075aad0 100644 --- a/view/adminhtml/templates/subscription/view.phtml +++ b/view/adminhtml/templates/subscription/view.phtml @@ -177,4 +177,40 @@ use Vindi\Payment\Block\Adminhtml\Subscription\View;
+
+
+ +
+
+ + + + + + + + + + + + + getLinkedOrders() as $order): ?> + + + + + + + + + + getLinkedOrders())): ?> + + + + + +
escapeHtml($order['order_id']); ?>escapeHtml($order['increment_id']); ?>escapeHtml($order['created_at']); ?>escapeHtml($order['total']); ?>escapeHtml($order['status']); ?>
+
+
From 6b320553d6d1e07b9dea64769c9bf85ecf47a4b3 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 22 Apr 2024 20:24:21 +0000 Subject: [PATCH 074/187] feat: Within the orders that this subscription generated, there will be a link to the subscription --- .../Adminhtml/Order/View/Tab/Subscription.php | 120 ++++++++++++++++++ Block/Adminhtml/Subscription/View.php | 18 +++ i18n/pt_BR.csv | 8 ++ view/adminhtml/layout/sales_order_view.xml | 16 +++ .../order/view/tab/subscription.phtml | 36 ++++++ .../templates/subscription/view.phtml | 4 +- 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 Block/Adminhtml/Order/View/Tab/Subscription.php create mode 100644 view/adminhtml/layout/sales_order_view.xml create mode 100644 view/adminhtml/templates/order/view/tab/subscription.phtml diff --git a/Block/Adminhtml/Order/View/Tab/Subscription.php b/Block/Adminhtml/Order/View/Tab/Subscription.php new file mode 100644 index 00000000..0caca2e5 --- /dev/null +++ b/Block/Adminhtml/Order/View/Tab/Subscription.php @@ -0,0 +1,120 @@ +_coreRegistry = $registry; + $this->_resourceConnection = $resourceConnection; + parent::__construct($context, $data); + } + + /** + * @return \Magento\Sales\Model\Order + */ + public function getOrder() + { + return $this->_coreRegistry->registry('current_order'); + } + + /** + * @return bool + */ + public function hasSubscription() + { + return (bool) $this->getSubscriptionId(); + } + + /** + * @return string + */ + public function getSubscriptionId() + { + if ($this->subscriptionId === null) { + $orderId = $this->getOrder()->getEntityId(); + $connection = $this->_resourceConnection->getConnection(); + $tableName = $this->_resourceConnection->getTableName('vindi_subscription_orders'); + $select = $connection->select()->from($tableName, 'subscription_id') + ->where('order_id = ?', $orderId); + $this->subscriptionId = $connection->fetchOne($select); + } + return $this->subscriptionId; + } + + /** + * @return string + */ + public function getSubscriptionViewUrl() + { + $subscriptionId = $this->getSubscriptionId(); + return $subscriptionId ? $this->getUrl('vindi_payment/subscription/view', ['id' => $subscriptionId]) : '#'; + } + + /** + * @return string + */ + public function getTabLabel() + { + return __('Subscription'); + } + + /** + * @return string + */ + public function getTabTitle() + { + return __('Subscription'); + } + + /** + * @return bool + */ + public function canShowTab() + { + return $this->hasSubscription(); + } + + /** + * @return bool + */ + public function isHidden() + { + return false; + } +} diff --git a/Block/Adminhtml/Subscription/View.php b/Block/Adminhtml/Subscription/View.php index 0c2839c8..c30fbad3 100644 --- a/Block/Adminhtml/Subscription/View.php +++ b/Block/Adminhtml/Subscription/View.php @@ -7,6 +7,7 @@ use Magento\Backend\Block\Widget\Container; use Magento\Backend\Block\Widget\Context; use Magento\Framework\Registry; +use Magento\Framework\Pricing\PriceCurrencyInterface; use Vindi\Payment\Helper\Api; use Vindi\Payment\Model\ResourceModel\SubscriptionOrder\CollectionFactory as SubscriptionOrderCollectionFactory; @@ -38,12 +39,18 @@ class View extends Container */ private $subscriptionsOrderCollectionFactory; + /** + * @var PriceCurrencyInterface + */ + protected $priceHelper; + /** * View constructor. * @param Context $context * @param Registry $registry * @param SubscriptionOrderCollectionFactory $subscriptionsOrderCollectionFactory * @param Api $api + * @param PriceCurrencyInterface $priceHelper * @param array $data */ public function __construct( @@ -51,12 +58,14 @@ public function __construct( Registry $registry, SubscriptionOrderCollectionFactory $subscriptionsOrderCollectionFactory, Api $api, + PriceCurrencyInterface $priceHelper, array $data = [] ) { parent::__construct($context, $data); $this->api = $api; $this->registry = $registry; $this->subscriptionsOrderCollectionFactory = $subscriptionsOrderCollectionFactory; + $this->priceHelper = $priceHelper; } /** @@ -348,6 +357,15 @@ public function getLinkedOrders() return $collection->getItems(); } + /** + * @param $amount + * @return string + */ + public function formatPrice($amount) + { + return $this->priceHelper->format($amount, false); + } + /** * @return array|null */ diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index e242db05..f93067fe 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -280,3 +280,11 @@ "Customer will choose in checkout","Cliente escolherá na finalização" "Your saved cards","Seus cartões salvos" "New Card.","Novo Cartão" +"Subscription","Assinatura" +"Subscription Information","Informações da Assinatura" +"Subscription ID:","ID da Assinatura:" +"Subscription Link:","Link da Assinatura:" +"Order:","Pedido:" +"Customer Email:","E-mail do Cliente:" +"View Subscription","Ver Assinatura" + diff --git a/view/adminhtml/layout/sales_order_view.xml b/view/adminhtml/layout/sales_order_view.xml new file mode 100644 index 00000000..ef954c5c --- /dev/null +++ b/view/adminhtml/layout/sales_order_view.xml @@ -0,0 +1,16 @@ + + + + + + + order_subscription + + Vindi\Payment\Block\Adminhtml\Order\View\Tab\Subscription + + + + + + diff --git a/view/adminhtml/templates/order/view/tab/subscription.phtml b/view/adminhtml/templates/order/view/tab/subscription.phtml new file mode 100644 index 00000000..8982af82 --- /dev/null +++ b/view/adminhtml/templates/order/view/tab/subscription.phtml @@ -0,0 +1,36 @@ + +
+
+ +
+ + + getChildHtml(); ?> + + + + + + + + + + + + + + + + + +
getOrder()->getIncrementId(); ?>
getOrder()->getCustomerEmail(); ?>
getSubscriptionId(); ?>
+
diff --git a/view/adminhtml/templates/subscription/view.phtml b/view/adminhtml/templates/subscription/view.phtml index a075aad0..e0203bf5 100644 --- a/view/adminhtml/templates/subscription/view.phtml +++ b/view/adminhtml/templates/subscription/view.phtml @@ -198,8 +198,8 @@ use Vindi\Payment\Block\Adminhtml\Subscription\View; escapeHtml($order['order_id']); ?> escapeHtml($order['increment_id']); ?> - escapeHtml($order['created_at']); ?> - escapeHtml($order['total']); ?> + escapeHtml((new IntlDateFormatter('pt_BR', IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE))->format(new DateTime($order['created_at']))); ?> + escapeHtml($block->formatPrice($order['total'])); ?> escapeHtml($order['status']); ?> From 6eb543dfc71a18195f675ece54305e136fae965b Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 29 Apr 2024 18:28:16 +0000 Subject: [PATCH 075/187] feat: Display the plan observation field on the product page --- Api/Data/VindiPlanInterface.php | 5 + Block/Product/ProductRecurrence.php | 97 +++++----------- Controller/Adminhtml/VindiPlan/Save.php | 4 + Model/VindiPlan.php | 108 ++++++++++++++++++ etc/db_schema.xml | 1 + i18n/pt_BR.csv | 4 + .../vindi_payment_vindiplan_form.xml | 23 +++- .../templates/product/recurrence.phtml | 25 +++- view/frontend/web/css/recurrence.css | 10 ++ 9 files changed, 202 insertions(+), 75 deletions(-) diff --git a/Api/Data/VindiPlanInterface.php b/Api/Data/VindiPlanInterface.php index ec9943ff..b2f8409d 100755 --- a/Api/Data/VindiPlanInterface.php +++ b/Api/Data/VindiPlanInterface.php @@ -22,6 +22,8 @@ interface VindiPlanInterface public const BILLING_CYCLES = 'billing_cycles'; public const CODE = 'code'; public const DESCRIPTION = 'description'; + + public const DESCRIPTION_DISPLAY_ON_PRODUCT_PAGE = 'description_display_on_product_page'; public const INSTALLMENTS = 'installments'; public const INVOICE_SPLIT = 'invoice_split'; public const STATUS = 'status'; @@ -52,6 +54,9 @@ public function getCode(); public function setCode($code); public function getDescription(); public function setDescription($description); + + public function getDescriptionDisplayOnProductPage(); + public function setDescriptionDisplayOnProductPage($descriptionDisplayOnProductPage); public function getInstallments(); public function setInstallments($installments); public function getInvoiceSplit(); diff --git a/Block/Product/ProductRecurrence.php b/Block/Product/ProductRecurrence.php index bfe9c715..a5f0ceab 100644 --- a/Block/Product/ProductRecurrence.php +++ b/Block/Product/ProductRecurrence.php @@ -13,48 +13,37 @@ /** * Class ProductRecurrence - * * @package Vindi\Payment\Block\Product */ class ProductRecurrence extends Template { /** - * Core registry - * * @var Registry */ - protected $_registry; + protected Registry $_registry; /** - * Vindi plan repository interface - * * @var VindiPlanRepositoryInterface */ - protected $vindiPlanRepository; + protected VindiPlanRepositoryInterface $vindiPlanRepository; /** - * Price helper - * * @var PriceHelper */ - protected $priceHelper; + protected PriceHelper $priceHelper; /** - * Locale format - * * @var LocaleFormat */ - protected $_localeFormat; + protected LocaleFormat $_localeFormat; /** * ProductRecurrence constructor. - * * @param Context $context * @param Registry $registry * @param VindiPlanRepositoryInterface $vindiPlanRepository * @param PriceHelper $priceHelper * @param LocaleFormat $localeFormat - * @param \Magento\Catalog\Model\ProductRepository $productRepository * @param array $data */ public function __construct( @@ -73,95 +62,67 @@ public function __construct( } /** - * Returns the current product from the registry. - * * @return Product|null */ - public function getCurrentProduct() + public function getCurrentProduct(): ?Product { return $this->_registry->registry('current_product'); } /** - * Returns the name of a plan by its ID. - * + * @return bool + */ + public function getPlanNameById(int $planId): string + { + return $this->getPlan($planId)?->getName() ?? ''; + } + + /** * @param int $planId - * @return string + * @return \Vindi\Payment\Model\Plan|null */ - public function getPlanNameById($planId) + public function getPlanById(int $planId): ?\Vindi\Payment\Model\Plan { - try { - $plan = $this->vindiPlanRepository->getById($planId); - return $plan->getName(); - } catch (NoSuchEntityException $e) { - return ''; - } + return $this->getPlan($planId); } /** - * Returns the formatted price for a plan by its ID. - * * @param int $planId * @return string */ - public function getPlanPriceById($planId) + public function getPlanPriceById(int $planId): string { - try { - $plan = $this->vindiPlanRepository->getById($planId); - $price = $plan->getPrice(); - return $this->priceHelper->currency($price, true, false); - } catch (NoSuchEntityException $e) { - return ''; - } + $plan = $this->getPlan($planId); + return $plan ? $this->priceHelper->currency($plan->getPrice(), true, false) : ''; } /** - * Retrieve price format configuration. - * * @return array */ - public function getPriceFormat() + public function getPriceFormat(): array { return $this->_localeFormat->getPriceFormat(); } /** - * Returns the number of installments for a plan by its ID. - * * @param int $planId * @return int */ - public function getPlanInstallmentsById($planId) + public function getPlanInstallmentsById(int $planId): int { - try { - $plan = $this->vindiPlanRepository->getById($planId); - return (int)$plan->getInstallments(); - } catch (NoSuchEntityException $e) { - return 0; - } + return $this->getPlan($planId)?->getInstallments() ?? 0; } /** - * Checks whether there is price variation between the child products of a configurable item. - * - * @return bool + * @param int $planId + * @return \Vindi\Payment\Model\Plan|null */ - public function hasPriceVariationForConfigurable() + private function getPlan(int $planId): ?\Vindi\Payment\Model\Plan { - $product = $this->getCurrentProduct(); - if ($product->getTypeId() !== 'configurable') { - return false; - } - - $productTypeInstance = $product->getTypeInstance(); - $usedProducts = $productTypeInstance->getUsedProducts($product); - - $prices = []; - foreach ($usedProducts as $child) { - $prices[] = $child->getFinalPrice(); + try { + return $this->vindiPlanRepository->getById($planId); + } catch (NoSuchEntityException $e) { + return null; } - - $uniquePrices = array_unique($prices); - return count($uniquePrices) > 1; } } diff --git a/Controller/Adminhtml/VindiPlan/Save.php b/Controller/Adminhtml/VindiPlan/Save.php index f03c1c30..1b90b300 100755 --- a/Controller/Adminhtml/VindiPlan/Save.php +++ b/Controller/Adminhtml/VindiPlan/Save.php @@ -256,6 +256,10 @@ private function prepareData($post, $name, $code) */ private function prepareDataForMagentoStore($data, $post) { + if (isset($post["settings"]["description_display_on_product_page"])) { + $data['description_display_on_product_page'] = (int) $post["settings"]["description_display_on_product_page"]; + } + if (isset($post["settings"]["installments"])) { $data['installments'] = empty($post["settings"]["installments"]) ? null : $post["settings"]["installments"]; } diff --git a/Model/VindiPlan.php b/Model/VindiPlan.php index 32067082..79616653 100755 --- a/Model/VindiPlan.php +++ b/Model/VindiPlan.php @@ -94,131 +94,239 @@ public function setStatus($status) $this->setData(self::STATUS, $status); } + /** + * @return array|mixed|string|null + */ public function getInterval() { return $this->getData(self::INTERVAL); } + /** + * @param $interval + * @return VindiPlan|void + */ public function setInterval($interval) { return $this->setData(self::INTERVAL, $interval); } + /** + * @return array|mixed|string|null + */ public function getIntervalCount() { return $this->getData(self::INTERVAL_COUNT); } + /** + * @param $intervalCount + * @return VindiPlan|void + */ public function setIntervalCount($intervalCount) { return $this->setData(self::INTERVAL_COUNT, $intervalCount); } + /** + * @return array|mixed|string|null + */ public function getBillingTriggerType() { return $this->getData(self::BILLING_TRIGGER_TYPE); } + /** + * @param $billingTriggerType + * @return VindiPlan|void + */ public function setBillingTriggerType($billingTriggerType) { return $this->setData(self::BILLING_TRIGGER_TYPE, $billingTriggerType); } + /** + * @return array|mixed|string|null + */ public function getBillingTriggerDay() { return $this->getData(self::BILLING_TRIGGER_DAY); } + /** + * @param $billingTriggerDay + * @return VindiPlan|void + */ public function setBillingTriggerDay($billingTriggerDay) { return $this->setData(self::BILLING_TRIGGER_DAY, $billingTriggerDay); } + /** + * @return array|mixed|string|null + */ public function getBillingCycles() { return $this->getData(self::BILLING_CYCLES); } + /** + * @param $billingCycles + * @return VindiPlan|void + */ public function setBillingCycles($billingCycles) { return $this->setData(self::BILLING_CYCLES, $billingCycles); } + /** + * @return array|mixed|string|null + */ public function getCode() { return $this->getData(self::CODE); } + /** + * @param $code + * @return VindiPlan|void + */ public function setCode($code) { return $this->setData(self::CODE, $code); } + /** + * @return array|mixed|string|null + */ public function getDescription() { return $this->getData(self::DESCRIPTION); } + /** + * @param $description + * @return VindiPlan|void + */ public function setDescription($description) { return $this->setData(self::DESCRIPTION, $description); } + /** + * @return array|mixed|string|null + */ + public function getDescriptionDisplayOnProductPage() + { + return $this->getData(self::DESCRIPTION_DISPLAY_ON_PRODUCT_PAGE); + } + + /** + * @param $descriptionDisplayOnProductPage + * @return VindiPlan|void + */ + public function setDescriptionDisplayOnProductPage($descriptionDisplayOnProductPage) + { + return $this->setData(self::DESCRIPTION_DISPLAY_ON_PRODUCT_PAGE, $descriptionDisplayOnProductPage); + } + + /** + * @return array|mixed|string|null + */ public function getInstallments() { return $this->getData(self::INSTALLMENTS); } + /** + * @param $installments + * @return VindiPlan|void + */ public function setInstallments($installments) { return $this->setData(self::INSTALLMENTS, $installments); } + /** + * @return array|mixed|string|null + */ public function getInvoiceSplit() { return $this->getData(self::INVOICE_SPLIT); } + /** + * @param $invoiceSplit + * @return VindiPlan|void + */ public function setInvoiceSplit($invoiceSplit) { return $this->setData(self::INVOICE_SPLIT, $invoiceSplit); } + /** + * @return array|mixed|string|null + */ public function getMetadata() { return $this->getData(self::METADATA); } + /** + * @param $metadata + * @return VindiPlan|void + */ public function setMetadata($metadata) { return $this->setData(self::METADATA, $metadata); } + /** + * @return array|mixed|string|null + */ public function getDuration() { return $this->getData(self::DURATION); } + /** + * @param $duration + * @return VindiPlan|void + */ public function setDuration($duration) { return $this->setData(self::DURATION, $duration); } + /** + * @return array|mixed|string|null + */ public function getBillingTriggerDayTypeOnPeriod() { return $this->getData(self::BILLING_TRIGGER_DAY_TYPE_ON_PERIOD); } + /** + * @param $billingTriggerDayTypeOnPeriod + * @return VindiPlan|void + */ public function setBillingTriggerDayTypeOnPeriod($billingTriggerDayTypeOnPeriod) { return $this->setData(self::BILLING_TRIGGER_DAY_TYPE_ON_PERIOD, $billingTriggerDayTypeOnPeriod); } + /** + * @return array|mixed|string|null + */ public function getBillingTriggerDayBasedOnPeriod() { return $this->getData(self::BILLING_TRIGGER_DAY_BASED_ON_PERIOD); } + /** + * @param $billingTriggerDayBasedOnPeriod + * @return VindiPlan|void + */ public function setBillingTriggerDayBasedOnPeriod($billingTriggerDayBasedOnPeriod) { return $this->setData(self::BILLING_TRIGGER_DAY_BASED_ON_PERIOD, $billingTriggerDayBasedOnPeriod); diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 8344f9f1..87e06930 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -27,6 +27,7 @@ + diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index f93067fe..01cd1d7c 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -239,6 +239,10 @@ "Inactive plans will not be available for new sales, but existing subscriptions will continue to be charged and renewed normally.", "Planos inativos não estarão disponíveis para novas vendas, porém assinaturas existentes continuarão sendo cobradas e renovadas normalmente." "Observations", "Observações" "Free text field for internal notes. It will not be displayed to customers.", "Campo de texto livre para observações internas. Não será exibido para os clientes." +"Free text field for internal notes.", "Campo de texto livre para observações internas." +"Display on Product Page", "Exibir na Página do Produto" +"The purpose of this display is to enable the display of additional information to the customer, if the retailer deems it necessary.","O objetivo dessa exibição é possibilitar a exibição de informações complementares para o cliente, caso o lojista ache necessário." +"Display 'Notes' on product page","Exibir 'Observações' na página do produto" "Charge every", "Cobrar a cada" "Number of periods to be charged", "Quantidade de períodos a serem cobrados" "Use positive values starting from 1. If left empty, the period will be indefinite.", "Utilize valores positivos a partir de 1. Caso deixe vazio o período será por tempo indefinido." diff --git a/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml index a3100ae0..962d80bd 100755 --- a/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml +++ b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml @@ -101,10 +101,27 @@ text description - - Free text field for internal notes. It will not be displayed to customers. - + Free text field for internal notes. + + + + + boolean + + description_display_on_product_page + The purpose of this display is to enable the display of additional information to the customer, if the retailer deems it necessary. + + + + + 0 + 1 + + toggle + + + diff --git a/view/frontend/templates/product/recurrence.phtml b/view/frontend/templates/product/recurrence.phtml index 882c4abd..5420d8b4 100644 --- a/view/frontend/templates/product/recurrence.phtml +++ b/view/frontend/templates/product/recurrence.phtml @@ -18,15 +18,23 @@ if ($product && $product->getData('vindi_enable_recurrence') === '1') {

$data) : ?> - getPlanInstallmentsById($data['plan']); ?> - getPlanNameById($data['plan']); ?> + getPlanInstallmentsById($data['plan']); + $plan = $block->getPlanById($data['plan']); + $planName = $plan ? $plan->getName() : $data['plan']; + $shouldDisplayDescription = $plan && $plan->getDescriptionDisplayOnProductPage() === '1'; + $description = $plan ? $plan->getDescription() : ''; + ?> -
+
@@ -46,6 +54,7 @@ if ($product && $product->getData('vindi_enable_recurrence') === '1') { function updatePrice() { var selectedInput = $('.recurrence-input:checked'); if (!selectedInput.length) { + $('#recurrence-description').hide(); return; } @@ -70,6 +79,14 @@ if ($product && $product->getData('vindi_enable_recurrence') === '1') { } $('#product-price-' + productId + ' .price').html(priceHtml); + + // Update and display the description based on the selected plan + var description = selectedInput.data('description'); + if (description) { + $('#recurrence-description').html(description).show(); + } else { + $('#recurrence-description').hide(); + } } function addPlanDetailsToCartForm() { diff --git a/view/frontend/web/css/recurrence.css b/view/frontend/web/css/recurrence.css index 82b7b1f2..8b8d3f47 100644 --- a/view/frontend/web/css/recurrence.css +++ b/view/frontend/web/css/recurrence.css @@ -26,6 +26,16 @@ border-color: #303030; } +.recurrence-description { + padding: 10px; + margin-top: 5px; + background-color: #f9f9f9; + border-left: 3px solid #3079ed; + color: #333; + font-size: 14px; + line-height: 1.4; +} + .price-prefix, .installment-info { font-size: 0.6em; font-weight: normal; From 9e8afba3736aa440021f16b49012727dc0b7b8ed Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 29 Apr 2024 18:37:34 +0000 Subject: [PATCH 076/187] fix: Block/Product/ProductRecurrence.php --- Block/Product/ProductRecurrence.php | 103 +++++++++++++++++++++------- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/Block/Product/ProductRecurrence.php b/Block/Product/ProductRecurrence.php index a5f0ceab..5938be18 100644 --- a/Block/Product/ProductRecurrence.php +++ b/Block/Product/ProductRecurrence.php @@ -13,37 +13,48 @@ /** * Class ProductRecurrence + * * @package Vindi\Payment\Block\Product */ class ProductRecurrence extends Template { /** + * Core registry + * * @var Registry */ - protected Registry $_registry; + protected $_registry; /** + * Vindi plan repository interface + * * @var VindiPlanRepositoryInterface */ - protected VindiPlanRepositoryInterface $vindiPlanRepository; + protected $vindiPlanRepository; /** + * Price helper + * * @var PriceHelper */ - protected PriceHelper $priceHelper; + protected $priceHelper; /** + * Locale format + * * @var LocaleFormat */ - protected LocaleFormat $_localeFormat; + protected $_localeFormat; /** * ProductRecurrence constructor. + * * @param Context $context * @param Registry $registry * @param VindiPlanRepositoryInterface $vindiPlanRepository * @param PriceHelper $priceHelper * @param LocaleFormat $localeFormat + * @param \Magento\Catalog\Model\ProductRepository $productRepository * @param array $data */ public function __construct( @@ -62,67 +73,111 @@ public function __construct( } /** + * Returns the current product from the registry. + * * @return Product|null */ - public function getCurrentProduct(): ?Product + public function getCurrentProduct() { return $this->_registry->registry('current_product'); } /** - * @return bool + * Returns the name of a plan by its ID. + * + * @param int $planId + * @return string */ - public function getPlanNameById(int $planId): string + public function getPlanNameById($planId) { - return $this->getPlan($planId)?->getName() ?? ''; + try { + $plan = $this->vindiPlanRepository->getById($planId); + return $plan->getName(); + } catch (NoSuchEntityException $e) { + return ''; + } } /** + * Returns the plan by its ID. + * * @param int $planId - * @return \Vindi\Payment\Model\Plan|null + * @return string */ - public function getPlanById(int $planId): ?\Vindi\Payment\Model\Plan + public function getPlanById($planId) { - return $this->getPlan($planId); + try { + $plan = $this->vindiPlanRepository->getById($planId); + return $plan; + } catch (NoSuchEntityException $e) { + return ''; + } } /** + * Returns the formatted price for a plan by its ID. + * * @param int $planId * @return string */ - public function getPlanPriceById(int $planId): string + public function getPlanPriceById($planId) { - $plan = $this->getPlan($planId); - return $plan ? $this->priceHelper->currency($plan->getPrice(), true, false) : ''; + try { + $plan = $this->vindiPlanRepository->getById($planId); + $price = $plan->getPrice(); + return $this->priceHelper->currency($price, true, false); + } catch (NoSuchEntityException $e) { + return ''; + } } /** + * Retrieve price format configuration. + * * @return array */ - public function getPriceFormat(): array + public function getPriceFormat() { return $this->_localeFormat->getPriceFormat(); } /** + * Returns the number of installments for a plan by its ID. + * * @param int $planId * @return int */ - public function getPlanInstallmentsById(int $planId): int + public function getPlanInstallmentsById($planId) { - return $this->getPlan($planId)?->getInstallments() ?? 0; + try { + $plan = $this->vindiPlanRepository->getById($planId); + return (int)$plan->getInstallments(); + } catch (NoSuchEntityException $e) { + return 0; + } } /** - * @param int $planId - * @return \Vindi\Payment\Model\Plan|null + * Checks whether there is price variation between the child products of a configurable item. + * + * @return bool */ - private function getPlan(int $planId): ?\Vindi\Payment\Model\Plan + public function hasPriceVariationForConfigurable() { - try { - return $this->vindiPlanRepository->getById($planId); - } catch (NoSuchEntityException $e) { - return null; + $product = $this->getCurrentProduct(); + if ($product->getTypeId() !== 'configurable') { + return false; + } + + $productTypeInstance = $product->getTypeInstance(); + $usedProducts = $productTypeInstance->getUsedProducts($product); + + $prices = []; + foreach ($usedProducts as $child) { + $prices[] = $child->getFinalPrice(); } + + $uniquePrices = array_unique($prices); + return count($uniquePrices) > 1; } } From db724b4f18e00a8d259608b06b261207aca799f6 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 29 Apr 2024 19:15:40 +0000 Subject: [PATCH 077/187] fix: adding PHPStan fixes --- Model/SubscriptionOrder.php | 77 +++++++++- Plugin/AddCustomOptionToQuoteItem.php | 197 +++++++++++++++++++------- Test/Unit/OrderTest.php | 2 +- Test/Unit/PlanTest.php | 2 +- Test/Unit/StatusTest.php | 20 +-- Test/Unit/SubscriptionOrderTest.php | 12 +- 6 files changed, 235 insertions(+), 75 deletions(-) diff --git a/Model/SubscriptionOrder.php b/Model/SubscriptionOrder.php index 0db147a9..db2717f1 100755 --- a/Model/SubscriptionOrder.php +++ b/Model/SubscriptionOrder.php @@ -1,87 +1,160 @@ _init('Vindi\Payment\Model\ResourceModel\SubscriptionOrder'); } + /** + * Get entity ID + * @return int|null + */ public function getId() { return $this->getData(self::ENTITY_ID); } + /** + * Set entity ID + * @param int $entityId + * @return $this + */ public function setId($entityId) { $this->setData(self::ENTITY_ID, $entityId); + return $this; } + /** + * Get order ID + * @return int|null + */ public function getOrderId() { return $this->getData(self::ORDER_ID); } + /** + * Set order ID + * @param int $orderId + * @return $this + */ public function setOrderId($orderId) { $this->setData(self::ORDER_ID, $orderId); + return $this; } + /** + * Get increment ID + * @return string|null + */ public function getIncrementId() { return $this->getData(self::INCREMENT_ID); } + /** + * Set increment ID + * @param string $incrementId + * @return $this + */ public function setIncrementId($incrementId) { $this->setData(self::INCREMENT_ID, $incrementId); + return $this; } + /** + * Get subscription ID + * @return int|null + */ public function getSubscriptionId() { return $this->getData(self::SUBSCRIPTION_ID); } + /** + * Set subscription ID + * @param int $subscriptionId + * @return $this + */ public function setSubscriptionId($subscriptionId) { $this->setData(self::SUBSCRIPTION_ID, $subscriptionId); + return $this; } + /** + * Get creation date + * @return string|null + */ public function getCreatedAt() { return $this->getData(self::CREATED_AT); } + /** + * Set creation date + * @param string $createdAt + * @return $this + */ public function setCreatedAt($createdAt) { $this->setData(self::CREATED_AT, $createdAt); + return $this; } + /** + * Get total + * @return float|null + */ public function getTotal() { return $this->getData(self::TOTAL); } + /** + * Set total + * @param float $total + * @return $this + */ public function setTotal($total) { $this->setData(self::TOTAL, $total); + return $this; } + /** + * Get status + * @return string|null + */ public function getStatus() { return $this->getData(self::STATUS); } + /** + * Set status + * @param string $status + * @return $this + */ public function setStatus($status) { $this->setData(self::STATUS, $status); + return $this; } } diff --git a/Plugin/AddCustomOptionToQuoteItem.php b/Plugin/AddCustomOptionToQuoteItem.php index 4dc27c11..3987ed43 100644 --- a/Plugin/AddCustomOptionToQuoteItem.php +++ b/Plugin/AddCustomOptionToQuoteItem.php @@ -1,65 +1,152 @@ getData('vindi_enable_recurrence') == '1') { - if ($request instanceof \Magento\Framework\DataObject) { - $additionalOptions = []; - - $selectedPlanId = $request->getData('selected_plan_id'); - if (empty($selectedPlanId)) { - throw new LocalizedException(__('A plan must be selected for this product.')); - } - - $additionalOptions[] = [ - 'label' => __('Plan ID'), - 'value' => $selectedPlanId, - 'code' => 'plan_id' - ]; - - $additionalOptions[] = [ - 'label' => __('Price'), - 'value' => $request->getData('selected_plan_price'), - 'code' => 'plan_price' - ]; - - $additionalOptions[] = [ - 'label' => __('Installments'), - 'value' => $request->getData('selected_plan_installments'), - 'code' => 'plan_installments' - ]; - - //@phpstan-ignore-next-line - if (!empty($additionalOptions)) { - $product->addCustomOption('additional_options', json_encode($additionalOptions)); - } - } - } - - return [$product, $request, $processMode]; + protected function setUp(): void + { + $this->objectManager = new ObjectManager($this); + $this->customerRepositoryInterface = $this->getMockBuilder(CustomerRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->managerInterface = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testOrderWithTax() + { + $vindiProductId = 'taxa'; + $amount = 1.00; + + $order = $this->createOrderMock($amount, 0.00, 0.00); + $list = $this->createVindiProductManagementMock($vindiProductId)->findOrCreateProductsFromOrder($order); + + $this->makeAssertions($list, $vindiProductId, $amount); + } + + public function testOrderWithDiscount() + { + $vindiProductId = 'cupom'; + $amount = -5.00; + + $order = $this->createOrderMock(0.00, $amount, 0.00); + $list = $this->createVindiProductManagementMock($vindiProductId)->findOrCreateProductsFromOrder($order); + + $this->makeAssertions($list, $vindiProductId, $amount); + } + + public function testOrderWithShipping() + { + $vindiProductId = 'frete'; + $amount = 10.00; + + $order = $this->createOrderMock(0.00, 0.00, $amount); + $list = $this->createVindiProductManagementMock($vindiProductId)->findOrCreateProductsFromOrder($order); + + $this->makeAssertions($list, $vindiProductId, $amount); + } + + private function makeAssertions($list, $vindiProductId, $amount) + { + $this->assertContains('fake_sku', $list[0]['product_id'], '', true); + $this->assertEquals('9.99', $list[0]['amount']); + $this->assertEquals($vindiProductId, $list[1]['product_id']); + $this->assertEquals($amount, $list[1]['amount']); + } + + private function createApiMock($desiredTestResponse = null) + { + $apiMock = $this->getMockBuilder(\Vindi\Payment\Helper\Api::class) + ->disableOriginalConstructor() + ->getMock(); + + $requestResponses = [ + ['product' => ['id' => 'fake_sku']], + ['products' => [0 => ['id' => $desiredTestResponse]]] + ]; + + $apiMock->method('request') + ->willReturnOnConsecutiveCalls(false, $requestResponses[0], $requestResponses[1]); + + return $apiMock; + } + + private function createVindiProductManagementMock($desiredTestResponse) + { + return $this->objectManager->getObject(\Vindi\Payment\Model\Vindi\ProductManagement::class, [ + 'productRepository' => $this->createVindiProductMock($desiredTestResponse) + ]); + } + + private function createVindiProductMock($desiredTestResponse) + { + return $this->objectManager->getObject(\Vindi\Payment\Model\Vindi\Product::class, [ + 'api' => $this->createApiMock($desiredTestResponse) + ]); + } + + private function createOrderMock( + $orderTaxAmount = 0.00, $orderDiscountAmount = 0.00, $orderShippingAmount = 0.00, + $itemQty = 1, $itemType = 'simple', $itemPrice = 9.99 + ) + { + $orderMock = $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + + $items = [$this->createItemMock($itemQty, $itemType, $itemPrice)]; + + $orderMock->method('getItems')->willReturn($items); + $orderMock->method('getTaxAmount')->willReturn($orderTaxAmount); + $orderMock->method('getDiscountAmount')->willReturn($orderDiscountAmount); + $orderMock->method('getShippingAmount')->willReturn($orderShippingAmount); + + return $orderMock; + } + + private function createItemMock($qty = 1, $type = 'simple', $price = 9.99) + { + $itemMock = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->getMock(); + + $itemMock->method('getQtyOrdered')->willReturn($qty); + $itemMock->method('getSku')->willReturn('FAKE_SKU'); + $itemMock->method('getName')->willReturn('FAKE_NAME'); + $itemMock->method('getPrice')->willReturn($price); + $itemMock->method('getProduct')->willReturn($this->createProductMock($type)); + + return $itemMock; + } + + private function createProductMock($type) + { + $productMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + + $productMock->method('getTypeId')->willReturn($type); + + return $productMock; } } diff --git a/Test/Unit/OrderTest.php b/Test/Unit/OrderTest.php index 65878cf1..5f6f59fa 100644 --- a/Test/Unit/OrderTest.php +++ b/Test/Unit/OrderTest.php @@ -9,7 +9,7 @@ class OrderTest extends \PHPUnit\Framework\TestCase protected $customerRepositoryInterface; protected $managerInterface; - public function setUp() + public function setUp(): void { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->customerRepositoryInterface = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class)->disableOriginalConstructor()->getMock(); diff --git a/Test/Unit/PlanTest.php b/Test/Unit/PlanTest.php index 88bdb640..88b27593 100644 --- a/Test/Unit/PlanTest.php +++ b/Test/Unit/PlanTest.php @@ -26,7 +26,7 @@ class PlanTest extends TestCase */ private $productRepository; - public function setUp() + public function setUp(): void { $this->objectManager = new ObjectManager($this); $this->productRepository = $this->getMockBuilder(ProductRepositoryInterface::class) diff --git a/Test/Unit/StatusTest.php b/Test/Unit/StatusTest.php index 46fbb141..89f464e0 100644 --- a/Test/Unit/StatusTest.php +++ b/Test/Unit/StatusTest.php @@ -9,20 +9,20 @@ class StatusTest extends \PHPUnit\Framework\TestCase protected $objectManager; protected $paymentMock; - - public function setUp() + + public function setUp(): void { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->paymentMock = $this->createPaymentMock(); } public function testGetStatusToOrderCompleteWithNoStatusConfigured() - { + { $this->assertEquals(Order::STATE_PROCESSING, $this->createHelperObjectManager(null)->getStatusToOrderComplete()); } public function testGetStatusToOrderCompleteWithPendingStatusConfigured() - { + { $this->assertEquals('pending', $this->createHelperObjectManager('pending')->getStatusToOrderComplete()); } @@ -32,7 +32,7 @@ public function testSetProcessingOrderStatusOnPlaceCreditCard() ->willReturn( \Vindi\Payment\Model\Payment\Vindi::CODE ); - + $result = $this->createPluginObjectManager(Order::STATE_PROCESSING)->afterPlace($this->paymentMock, 'Expected Result'); $this->assertEquals('Expected Result', $result); @@ -41,7 +41,7 @@ public function testSetProcessingOrderStatusOnPlaceCreditCard() } public function testSetPendingOrderStatusOnPlaceCreditCard() - { + { $this->paymentMock->method('getMethod') ->willReturn( \Vindi\Payment\Model\Payment\Vindi::CODE @@ -90,7 +90,7 @@ private function createHelperMock($statusToOrderComplete) $helperMock->method('getStatusToOrderComplete') ->willReturn($statusToOrderComplete); - + return $helperMock; } @@ -99,7 +99,7 @@ private function createContextMock($expectedValue) $contextMock = $this->getMockBuilder(\Magento\Framework\App\Helper\Context::class) ->disableOriginalConstructor() ->getMock(); - + $scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -124,7 +124,7 @@ private function createPaymentMock() ->willReturn($this->createOrderMock()); return $paymentMock; - } + } private function createOrderMock() { @@ -145,4 +145,4 @@ private function createOrderMock() return $orderMock; } -} \ No newline at end of file +} diff --git a/Test/Unit/SubscriptionOrderTest.php b/Test/Unit/SubscriptionOrderTest.php index dc955c8e..b6bc7ae3 100644 --- a/Test/Unit/SubscriptionOrderTest.php +++ b/Test/Unit/SubscriptionOrderTest.php @@ -32,7 +32,7 @@ class SubscriptionOrderTest extends TestCase */ protected $managerInterface; - public function setUp() + public function setUp(): void { $this->objectManager = new ObjectManager($this); $this->customerRepositoryInterface = $this->getMockBuilder(CustomerRepositoryInterface::class) @@ -44,13 +44,13 @@ public function setUp() } public function testOrderWithTax() - { + { $vindiProductId = 'taxa'; $amount = 1.00; $order = $this->createOrderMock($amount, 0.00, 0.00); $list = $this->createVindiProductManagementMock($vindiProductId)->findOrCreateProductsToSubscription($order); - + $this->makeAssertions($list, $vindiProductId, $amount); } @@ -95,7 +95,7 @@ private function createApiMock($desiredTestResponse = null) ->getMock(); $requestResponses = []; - + $requestResponses[] = [ 'product' => [ 'id' => 'fake_sku' @@ -172,10 +172,10 @@ private function createItemMock($qty = 1, $type = 'simple', $price = 10.99) $itemMock->method('getName') ->willReturn('FAKE_NAME'); - + $itemMock->method('getPrice') ->willReturn($price); - + $itemMock->method('getProduct') ->willReturn($this->createProductMock($type)); From 2d6149dba007aa158de1fd58396f25811b7b6ea1 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 29 Apr 2024 19:25:02 +0000 Subject: [PATCH 078/187] fix: adding PHPStan fixes --- Plugin/AddCustomOptionToQuoteItem.php | 196 +++++++------------------- 1 file changed, 54 insertions(+), 142 deletions(-) diff --git a/Plugin/AddCustomOptionToQuoteItem.php b/Plugin/AddCustomOptionToQuoteItem.php index 3987ed43..c6e12ce4 100644 --- a/Plugin/AddCustomOptionToQuoteItem.php +++ b/Plugin/AddCustomOptionToQuoteItem.php @@ -1,152 +1,64 @@ objectManager = new ObjectManager($this); - $this->customerRepositoryInterface = $this->getMockBuilder(CustomerRepositoryInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->managerInterface = $this->getMockBuilder(ManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - } - - public function testOrderWithTax() - { - $vindiProductId = 'taxa'; - $amount = 1.00; - - $order = $this->createOrderMock($amount, 0.00, 0.00); - $list = $this->createVindiProductManagementMock($vindiProductId)->findOrCreateProductsFromOrder($order); - - $this->makeAssertions($list, $vindiProductId, $amount); - } - - public function testOrderWithDiscount() - { - $vindiProductId = 'cupom'; - $amount = -5.00; - - $order = $this->createOrderMock(0.00, $amount, 0.00); - $list = $this->createVindiProductManagementMock($vindiProductId)->findOrCreateProductsFromOrder($order); - - $this->makeAssertions($list, $vindiProductId, $amount); - } - - public function testOrderWithShipping() - { - $vindiProductId = 'frete'; - $amount = 10.00; - - $order = $this->createOrderMock(0.00, 0.00, $amount); - $list = $this->createVindiProductManagementMock($vindiProductId)->findOrCreateProductsFromOrder($order); - - $this->makeAssertions($list, $vindiProductId, $amount); - } - - private function makeAssertions($list, $vindiProductId, $amount) - { - $this->assertContains('fake_sku', $list[0]['product_id'], '', true); - $this->assertEquals('9.99', $list[0]['amount']); - $this->assertEquals($vindiProductId, $list[1]['product_id']); - $this->assertEquals($amount, $list[1]['amount']); - } - - private function createApiMock($desiredTestResponse = null) - { - $apiMock = $this->getMockBuilder(\Vindi\Payment\Helper\Api::class) - ->disableOriginalConstructor() - ->getMock(); - - $requestResponses = [ - ['product' => ['id' => 'fake_sku']], - ['products' => [0 => ['id' => $desiredTestResponse]]] - ]; - - $apiMock->method('request') - ->willReturnOnConsecutiveCalls(false, $requestResponses[0], $requestResponses[1]); - - return $apiMock; - } - - private function createVindiProductManagementMock($desiredTestResponse) - { - return $this->objectManager->getObject(\Vindi\Payment\Model\Vindi\ProductManagement::class, [ - 'productRepository' => $this->createVindiProductMock($desiredTestResponse) - ]); - } - - private function createVindiProductMock($desiredTestResponse) - { - return $this->objectManager->getObject(\Vindi\Payment\Model\Vindi\Product::class, [ - 'api' => $this->createApiMock($desiredTestResponse) - ]); - } - - private function createOrderMock( - $orderTaxAmount = 0.00, $orderDiscountAmount = 0.00, $orderShippingAmount = 0.00, - $itemQty = 1, $itemType = 'simple', $itemPrice = 9.99 - ) - { - $orderMock = $this->getMockBuilder(Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $items = [$this->createItemMock($itemQty, $itemType, $itemPrice)]; - - $orderMock->method('getItems')->willReturn($items); - $orderMock->method('getTaxAmount')->willReturn($orderTaxAmount); - $orderMock->method('getDiscountAmount')->willReturn($orderDiscountAmount); - $orderMock->method('getShippingAmount')->willReturn($orderShippingAmount); - - return $orderMock; - } - - private function createItemMock($qty = 1, $type = 'simple', $price = 9.99) - { - $itemMock = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->getMock(); - - $itemMock->method('getQtyOrdered')->willReturn($qty); - $itemMock->method('getSku')->willReturn('FAKE_SKU'); - $itemMock->method('getName')->willReturn('FAKE_NAME'); - $itemMock->method('getPrice')->willReturn($price); - $itemMock->method('getProduct')->willReturn($this->createProductMock($type)); - - return $itemMock; - } - - private function createProductMock($type) - { - $productMock = $this->getMockBuilder(Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $productMock->method('getTypeId')->willReturn($type); - - return $productMock; + public function beforeAddProduct( + \Magento\Quote\Model\Quote $subject, + $product, + $request = null, + $processMode = AbstractType::PROCESS_MODE_FULL + ) { + if ($product->getData('vindi_enable_recurrence') == '1') { + if ($request instanceof \Magento\Framework\DataObject) { + $additionalOptions = []; + + $selectedPlanId = $request->getData('selected_plan_id'); + if (empty($selectedPlanId)) { + throw new LocalizedException(__('A plan must be selected for this product.')); + } + + $additionalOptions[] = [ + 'label' => __('Plan ID'), + 'value' => $selectedPlanId, + 'code' => 'plan_id' + ]; + + $additionalOptions[] = [ + 'label' => __('Price'), + 'value' => $request->getData('selected_plan_price'), + 'code' => 'plan_price' + ]; + + $additionalOptions[] = [ + 'label' => __('Installments'), + 'value' => $request->getData('selected_plan_installments'), + 'code' => 'plan_installments' + ]; + + if (!empty($additionalOptions)) { + $product->addCustomOption('additional_options', json_encode($additionalOptions)); + } + } + } + + return [$product, $request, $processMode]; } } From 8f355aec652d335d51c9665e27b0ccc7bcd54dba Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Thu, 2 May 2024 19:46:09 +0000 Subject: [PATCH 079/187] feat: define which methods can sell recurrently --- Plugin/FilterRecurringPayments.php | 89 ++++++++++++++++++ etc/adminhtml/system.xml | 93 ++++--------------- etc/di.xml | 9 ++ i18n/pt_BR.csv | 3 +- .../templates/subscription/view.phtml | 4 +- .../vindi_payment_vindiplan_form.xml | 4 +- 6 files changed, 123 insertions(+), 79 deletions(-) create mode 100644 Plugin/FilterRecurringPayments.php diff --git a/Plugin/FilterRecurringPayments.php b/Plugin/FilterRecurringPayments.php new file mode 100644 index 00000000..2a39ac2a --- /dev/null +++ b/Plugin/FilterRecurringPayments.php @@ -0,0 +1,89 @@ +scopeConfig = $scopeConfig; + $this->checkoutSession = $checkoutSession; + $this->productRepository = $productRepository; + } + + /** + * Modify available payment methods based on the recurrence attribute of products in the cart. + * + * @param MethodList $subject + * @param array $result + * @return array + */ + public function afterGetAvailableMethods(MethodList $subject, $result) + { + if (!is_array($result)) { + return $result; + } + + $hasRecurringItem = false; + $quote = $this->checkoutSession->getQuote(); + foreach ($quote->getAllVisibleItems() as $item) { + $product = $this->productRepository->getById($item->getProduct()->getId()); + + if ($product->getData('vindi_enable_recurrence') == '1') { + $hasRecurringItem = true; + break; + } + } + + if (!$hasRecurringItem) { + return $result; + } + + $filtered = []; + foreach ($result as $method) { + $code = $method->getCode(); + $recurringPath = 'payment/' . $code . '/recurring'; + $canUseRecurring = $this->scopeConfig->getValue($recurringPath, ScopeInterface::SCOPE_STORE); + + if ($canUseRecurring) { + $filtered[] = $method; + } + } + + return $filtered; + } +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 7931baaf..b81c50b9 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -5,59 +5,8 @@
- - - - - Vindi\Payment\Model\Config\Source\Mode - vindiconfiguration/general/mode - - - - required-entry - vindiconfiguration/general/api_key - - - - - Vindi\Payment\Block\System\Config\Form\Field\Disable - vindiconfiguration/general/webhook_key - - - - Magento\Sales\Model\Config\Source\Order\Status - vindiconfiguration/general/order_status - - - - - - Message when selecting the payment method on the checkout screen. - checkout/vindi_pix/info_message - - - - Message if the qr code is not resized. - checkout/vindi_pix/qr_code_warning_message - - - - Message that will be presented to the customer on the success screen, after completing the order. - checkout/vindi_pix/info_message_onepage_success - - - + @@ -65,32 +14,15 @@ Vindi\Payment\Model\Config\Backend\ApiKeyValidator payment/vindi/active - payment/vindi/title - - - Magento\Config\Model\Config\Source\Yesno - payment/vindi/allow_installments - - - - payment/vindi/max_installments - - - - payment/vindi/min_installment_value - - - + + Magento\Config\Model\Config\Source\Yesno - payment/vindi/verify_method + payment/vindi/recurring @@ -107,6 +39,11 @@ payment/vindi_pix/title + + + Magento\Config\Model\Config\Source\Yesno + payment/vindi_pix/recurring + @@ -122,6 +59,11 @@ payment/vindi_bankslip/title + + + Magento\Config\Model\Config\Source\Yesno + payment/vindi_bankslip/recurring + @@ -137,6 +79,11 @@ payment/vindi_bankslippix/title + + + Magento\Config\Model\Config\Source\Yesno + payment/vindi_bankslippix/recurring +
diff --git a/etc/di.xml b/etc/di.xml index 46fae1d7..1e7b984f 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -108,4 +108,13 @@ Vindi\Payment\Model\ResourceModel\SubscriptionOrder + + + + + + + Magento\Checkout\Model\Session + + diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 01cd1d7c..48c175db 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -234,7 +234,7 @@ "Information", "Informação" "Settings", "Configurações" "Name", "Nome" -"Plan name.", "Nome do plano." +"Plan name. As a suggestion, use an informative plan name such as: Monthly, Quarterly, etc.", "Nome do plano. Como sugestão, utilize um nome de plano informativo, como por exemplo: Mensal, Trimestral, etc." "Status", "Status" "Inactive plans will not be available for new sales, but existing subscriptions will continue to be charged and renewed normally.", "Planos inativos não estarão disponíveis para novas vendas, porém assinaturas existentes continuarão sendo cobradas e renovadas normalmente." "Observations", "Observações" @@ -291,4 +291,5 @@ "Order:","Pedido:" "Customer Email:","E-mail do Cliente:" "View Subscription","Ver Assinatura" +"Can be used for recurring purchases?","Pode ser usado para compras recorrentes?" diff --git a/view/adminhtml/templates/subscription/view.phtml b/view/adminhtml/templates/subscription/view.phtml index e0203bf5..61fc0cef 100644 --- a/view/adminhtml/templates/subscription/view.phtml +++ b/view/adminhtml/templates/subscription/view.phtml @@ -185,8 +185,8 @@ use Vindi\Payment\Block\Adminhtml\Subscription\View; - - + + diff --git a/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml index 962d80bd..79817020 100755 --- a/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml +++ b/view/adminhtml/ui_component/vindi_payment_vindiplan_form.xml @@ -73,9 +73,7 @@ textname - - Plan name. - + Plan name. As a suggestion, use an informative plan name such as: Monthly, Quarterly, etc. From f60ae3cb76739a0b30c5ada38b7e02d615f8e3c1 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 6 May 2024 20:53:56 +0000 Subject: [PATCH 080/187] feat: when a new invoice notification arrives, create a new order --- Helper/WebHookHandlers/BillCreated.php | 30 ++++- Helper/WebHookHandlers/OrderCreator.php | 146 ++++++++++++++++++++++++ Model/SubscriptionOrderRepository.php | 77 +++++++++---- 3 files changed, 227 insertions(+), 26 deletions(-) create mode 100644 Helper/WebHookHandlers/OrderCreator.php diff --git a/Helper/WebHookHandlers/BillCreated.php b/Helper/WebHookHandlers/BillCreated.php index d0705920..8079a4b3 100644 --- a/Helper/WebHookHandlers/BillCreated.php +++ b/Helper/WebHookHandlers/BillCreated.php @@ -2,13 +2,35 @@ namespace Vindi\Payment\Helper\WebHookHandlers; + + +/** + * Class BillCreated + */ class BillCreated { + /** + * @var \Psr\Log\LoggerInterface + */ private $logger; - public function __construct(\Psr\Log\LoggerInterface $logger) - { + /** + * @var OrderCreator + */ + private $orderCreator; + + /** + * Constructor + * + * @param \Psr\Log\LoggerInterface $logger + * @param OrderCreator $orderCreator + */ + public function __construct( + \Psr\Log\LoggerInterface $logger, + OrderCreator $orderCreator + ) { $this->logger = $logger; + $this->orderCreator = $orderCreator; } /** @@ -28,11 +50,13 @@ public function billCreated($data) return false; } - if (!isset($bill['subscription']) || $bill['subscription'] === null) { + if (!isset($bill['subscription']) || $bill['subscription'] === null || !isset($bill['subscription']['id'])) { $this->logger->info(__(sprintf('Ignoring the event "bill_created" for single sell'))); return false; } + $this->orderCreator->createOrderFromBill($bill); + return true; } } diff --git a/Helper/WebHookHandlers/OrderCreator.php b/Helper/WebHookHandlers/OrderCreator.php new file mode 100644 index 00000000..257ab2b1 --- /dev/null +++ b/Helper/WebHookHandlers/OrderCreator.php @@ -0,0 +1,146 @@ +orderFactory = $orderFactory; + $this->subscriptionOrderRepository = $subscriptionOrderRepository; + $this->orderService = $orderService; + $this->paymentBill = $paymentBill; + } + + /** + * Create an order from bill data + * + * @param array $billData + * @return bool + */ + public function createOrderFromBill($billData) + { + try { + $subscriptionId = $billData['subscription']['id']; + $originalOrder = $this->getOrderFromSubscriptionId($subscriptionId); + + if ($originalOrder) { + $newOrder = $this->replicateOrder($originalOrder, $billData); + $newOrder->save(); + return true; + } + + return false; + } catch (\Exception $e) { + // Log the exception or handle it as per your needs + return false; + } + } + + /** + * Fetch original order using subscription ID + * + * @param int $subscriptionId + * @return Order|null + */ + protected function getOrderFromSubscriptionId($subscriptionId) + { + $subscriptionOrder = $this->subscriptionOrderRepository->getBySubscriptionId($subscriptionId); + if ($subscriptionOrder) { + return $this->orderFactory->create()->load($subscriptionOrder->getOrderId()); + } + return null; + } + + /** + * Replicate an order with new details + * + * @param Order $originalOrder + * @param array $billData + * @return Order + */ + protected function replicateOrder(Order $originalOrder, $billData) + { + // Clone the original order + $newOrder = clone $originalOrder; + $newOrder->setId(null); + $newOrder->setIncrementId(null); + $newOrder->setVindiBillId($billData['id']); + $newOrder->setVindiSubscriptionId($billData['subscription']['id']); + $newOrder->setCreatedAt(null); + $newOrder->setState(Order::STATE_NEW); + $newOrder->setStatus(Order::STATE_NEW); + + // Replicate billing and shipping addresses + $billingAddress = clone $originalOrder->getBillingAddress(); + $billingAddress->setId(null)->setParentId(null); + $newOrder->setBillingAddress($billingAddress); + + if ($originalOrder->getShippingAddress()) { + $shippingAddress = clone $originalOrder->getShippingAddress(); + $shippingAddress->setId(null)->setParentId(null); + $newOrder->setShippingAddress($shippingAddress); + } + + // Replicate items + $newOrderItems = []; + foreach ($originalOrder->getAllVisibleItems() as $item) { + $newItem = clone $item; + $newItem->setId(null)->setOrderId(null); + $newOrderItems[] = $newItem; + } + $newOrder->setItems($newOrderItems); + + // Replicate payment + $originalPayment = $originalOrder->getPayment(); + $newPayment = clone $originalPayment; + $newPayment->setId(null)->setOrderId(null); + $newOrder->setPayment($newPayment); + + // Reset additional fields if needed + $newOrder->setTotalPaid(0); + $newOrder->setBaseTotalPaid(0); + $newOrder->setTotalDue($newOrder->getGrandTotal()); + $newOrder->setBaseTotalDue($newOrder->getBaseGrandTotal()); + + return $newOrder; + } +} diff --git a/Model/SubscriptionOrderRepository.php b/Model/SubscriptionOrderRepository.php index ff1b449f..438dedbe 100755 --- a/Model/SubscriptionOrderRepository.php +++ b/Model/SubscriptionOrderRepository.php @@ -5,7 +5,6 @@ use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; - use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; @@ -14,8 +13,6 @@ use Vindi\Payment\Api\Data\SubscriptionOrderSearchResultInterfaceFactory; use Vindi\Payment\Api\Data\SubscriptionOrderInterface; use Vindi\Payment\Api\Data\SubscriptionOrderInterfaceFactory; - - use Vindi\Payment\Model\ResourceModel\SubscriptionOrder as ResourceModel; use Vindi\Payment\Model\ResourceModel\SubscriptionOrder\Collection; use Vindi\Payment\Model\ResourceModel\SubscriptionOrder\CollectionFactory; @@ -40,7 +37,7 @@ class SubscriptionOrderRepository implements SubscriptionOrderRepositoryInterfac /** * @var SubscriptionOrderInterfaceFactory */ - private $subscriptionorderFactory; + private $subscriptionOrderFactory; /** * @var CollectionProcessorInterface @@ -55,26 +52,28 @@ class SubscriptionOrderRepository implements SubscriptionOrderRepositoryInterfac /** * SubscriptionOrder constructor. * @param ResourceModel $resourceModel - * @param SubscriptionOrderInterfaceFactory $subscriptionorderFactory + * @param SubscriptionOrderInterfaceFactory $subscriptionOrderFactory * @param CollectionFactory $collectionFactory * @param CollectionProcessorInterface $collectionProcessor * @param SubscriptionOrderSearchResultInterfaceFactory $searchResultsFactory */ public function __construct( ResourceModel $resourceModel, - SubscriptionOrderInterfaceFactory $subscriptionorderFactory, + SubscriptionOrderInterfaceFactory $subscriptionOrderFactory, CollectionFactory $collectionFactory, CollectionProcessorInterface $collectionProcessor, SubscriptionOrderSearchResultInterfaceFactory $searchResultsFactory ) { - $this->resourceModel = $resourceModel; - $this->subscriptionorderFactory = $subscriptionorderFactory; - $this->collectionFactory = $collectionFactory; - $this->collectionProcessor = $collectionProcessor; + $this->resourceModel = $resourceModel; + $this->subscriptionOrderFactory = $subscriptionOrderFactory; + $this->collectionFactory = $collectionFactory; + $this->collectionProcessor = $collectionProcessor; $this->searchResultsFactory = $searchResultsFactory; } /** + * Retrieve subscription order by entity ID + * * @param int $entityId * @return SubscriptionOrderInterface * @throws NoSuchEntityException @@ -82,16 +81,44 @@ public function __construct( public function getById(int $entityId): SubscriptionOrderInterface { try { - /** @var SubscriptionOrderInterface $subscriptionorder */ - $subscriptionorder = $this->subscriptionorderFactory->create(); - $this->resourceModel->load($subscriptionorder, $entityId); + /** @var SubscriptionOrderInterface $subscriptionOrder */ + $subscriptionOrder = $this->subscriptionOrderFactory->create(); + $this->resourceModel->load($subscriptionOrder, $entityId); + if (!$subscriptionOrder->getId()) { + throw new NoSuchEntityException(__('No subscription order found for the given entity ID.')); + } } catch (\Exception $e) { - throw new NoSuchEntityException(__('Error during load subscriptionorder by Entity ID')); + throw new NoSuchEntityException(__('Error during load subscription order by entity ID')); + } + return $subscriptionOrder; + } + + /** + * Retrieve subscription order by subscription ID + * + * @param int $subscriptionId + * @return SubscriptionOrderInterface + * @throws NoSuchEntityException + */ + public function getBySubscriptionId(int $subscriptionId): SubscriptionOrderInterface + { + /** @var Collection $collection */ + $collection = $this->collectionFactory->create(); + $collection->addFieldToFilter('subscription_id', $subscriptionId); + + /** @var SubscriptionOrderInterface $subscriptionOrder */ + $subscriptionOrder = $collection->getFirstItem(); + + if (!$subscriptionOrder || !$subscriptionOrder->getId()) { + throw new NoSuchEntityException(__('No subscription order found for the given subscription ID.')); } - return $subscriptionorder; + + return $subscriptionOrder; } /** + * Retrieve a list of subscription orders matching the search criteria + * * @param SearchCriteriaInterface $searchCriteria * @return SubscriptionOrderSearchResultInterface */ @@ -112,30 +139,34 @@ public function getList(SearchCriteriaInterface $searchCriteria): SubscriptionOr } /** - * @param SubscriptionOrderInterface $subscriptionorder + * Save a subscription order + * + * @param SubscriptionOrderInterface $subscriptionOrder * @return void * @throws CouldNotSaveException */ - public function save(SubscriptionOrderInterface $subscriptionorder): void + public function save(SubscriptionOrderInterface $subscriptionOrder): void { try { - $this->resourceModel->save($subscriptionorder); + $this->resourceModel->save($subscriptionOrder); } catch (\Exception $e) { - throw new CouldNotSaveException(__('Error when saving subscriptionorder')); + throw new CouldNotSaveException(__('Error when saving subscription order')); } } /** - * @param SubscriptionOrderInterface $subscriptionorder + * Delete a subscription order + * + * @param SubscriptionOrderInterface $subscriptionOrder * @return void * @throws CouldNotDeleteException */ - public function delete(SubscriptionOrderInterface $subscriptionorder): void + public function delete(SubscriptionOrderInterface $subscriptionOrder): void { try { - $this->resourceModel->delete($subscriptionorder); + $this->resourceModel->delete($subscriptionOrder); } catch (\Exception $e) { - throw new CouldNotDeleteException(__('Could not delete subscriptionorder.')); + throw new CouldNotDeleteException(__('Could not delete subscription order.')); } } } From aec896e388585a0f40e39d51e9a54575c6746ad6 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 6 May 2024 21:11:05 +0000 Subject: [PATCH 081/187] fix: returning the module settings --- etc/adminhtml/menu.xml | 2 +- etc/adminhtml/system.xml | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/etc/adminhtml/menu.xml b/etc/adminhtml/menu.xml index b26cdc26..cc9d91d6 100644 --- a/etc/adminhtml/menu.xml +++ b/etc/adminhtml/menu.xml @@ -28,7 +28,7 @@ title="Configuration" module="Vindi_Payment" sortOrder="20" - action="adminhtml/system_config/edit/section/payment/" + action="adminhtml/system_config/edit/section/vindiconfiguration" parent="Vindi_Payment::top_level" resource="Magento_Backend::content"/> diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index b81c50b9..876f855a 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -2,6 +2,61 @@ + + + +
+ + vindi + Vindi_Payment::config_vindi_payment + + + + + Vindi\Payment\Model\Config\Source\Mode + + + + required-entry + + + + + Vindi\Payment\Block\System\Config\Form\Field\Disable + + + + Magento\Sales\Model\Config\Source\Order\Status + + + +
+
+ + + + + Message when selecting the payment method on the checkout screen. + + + + Message that will be presented to the customer on the success screen, after completing the order. + + + + Message if the qr code is not resized. + + +
From 0a535e74d7aa338983b8d47a7dbd30e12445c296 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Mon, 6 May 2024 21:16:54 +0000 Subject: [PATCH 082/187] feat: adding payment methods menu --- etc/adminhtml/menu.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/etc/adminhtml/menu.xml b/etc/adminhtml/menu.xml index cc9d91d6..009b9bc4 100644 --- a/etc/adminhtml/menu.xml +++ b/etc/adminhtml/menu.xml @@ -24,6 +24,14 @@ sortOrder="10" title="Subscriptions" /> + + Date: Tue, 7 May 2024 09:27:51 -0300 Subject: [PATCH 083/187] Versionamento 1.5.0 (#117) --- CHANGELOG.md | 3 +++ composer.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f5ee6f7..cf550ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Notas das versões +## [1.5.0 - 07/05/2024](https://github.com/vindi/vindi-magento2/releases/tag/1.5.0) +- Insere método de pagamento Bolepix + ## [1.4.0 - 15/02/2024](https://github.com/vindi/vindi-magento2/releases/tag/1.4.0) - Insere método de pagamento Pix - Adiciona filtro nas assinaturas pelo método de pagamento Pix diff --git a/composer.json b/composer.json index 2fdd6b81..efa1ebd1 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "vindi/vindi-magento2", "description": "Módulo de cobrança Vindi para o Magento 2", "type": "magento2-module", - "version": "1.4.0", + "version": "1.5.0", "license": "GPL-3.0", "authors": [ { From e0da2058a01929ea8286efbac7538e1112747e63 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Tue, 7 May 2024 20:27:18 +0000 Subject: [PATCH 084/187] feat: feat: when a new invoice notification arrives, create a new order --- Api/OrderCreationQueueRepositoryInterface.php | 39 ++++++ Controller/Cron/Order.php | 80 +++++++++++ Controller/Cron/OrderById.php | 131 ++++++++++++++++++ Controller/Index/Webhook.php | 10 +- Cron/ProcessOrderCreationQueue.php | 69 +++++++++ Helper/WebHookHandlers/BillCreated.php | 32 ++++- Helper/WebHookHandlers/OrderCreator.php | 62 ++++++--- Model/OrderCreationQueue.php | 16 +++ Model/OrderCreationQueueRepository.php | 99 +++++++++++++ Model/ResourceModel/OrderCreationQueue.php | 16 +++ .../OrderCreationQueue/Collection.php | 18 +++ etc/crontab.xml | 3 + etc/db_schema.xml | 10 ++ etc/di.xml | 2 + 14 files changed, 561 insertions(+), 26 deletions(-) create mode 100644 Api/OrderCreationQueueRepositoryInterface.php create mode 100644 Controller/Cron/Order.php create mode 100644 Controller/Cron/OrderById.php create mode 100644 Cron/ProcessOrderCreationQueue.php create mode 100644 Model/OrderCreationQueue.php create mode 100644 Model/OrderCreationQueueRepository.php create mode 100644 Model/ResourceModel/OrderCreationQueue.php create mode 100644 Model/ResourceModel/OrderCreationQueue/Collection.php diff --git a/Api/OrderCreationQueueRepositoryInterface.php b/Api/OrderCreationQueueRepositoryInterface.php new file mode 100644 index 00000000..31c5aadc --- /dev/null +++ b/Api/OrderCreationQueueRepositoryInterface.php @@ -0,0 +1,39 @@ +jsonFactory = $jsonFactory; + $this->processOrderCreationQueue = $processOrderCreationQueue; + $this->logger = $logger; + $this->lockFilePath = sys_get_temp_dir() . '/vindi_payment_process_order_creation_queue.lock'; + } + + /** + * Execute the cron task manually via frontend controller. + * + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + $result = $this->jsonFactory->create(); + $lockHandle = fopen($this->lockFilePath, 'c+'); + + if (flock($lockHandle, LOCK_EX | LOCK_NB)) { + try { + $this->processOrderCreationQueue->execute(); + $message = 'Successfully processed the order creation queue.'; + $result->setData(['status' => 'success', 'message' => $message]); + } catch (\Exception $e) { + $errorMessage = __('Error processing order creation queue: %1', $e->getMessage()); + $this->logger->error($errorMessage); + $result->setData(['status' => 'error', 'message' => $errorMessage]); + } + + flock($lockHandle, LOCK_UN); + } else { + $message = 'Process already running. Skipping execution.'; + $result->setData(['status' => 'info', 'message' => $message]); + } + + fclose($lockHandle); + return $result; + } +} diff --git a/Controller/Cron/OrderById.php b/Controller/Cron/OrderById.php new file mode 100644 index 00000000..fbf9e320 --- /dev/null +++ b/Controller/Cron/OrderById.php @@ -0,0 +1,131 @@ +jsonFactory = $jsonFactory; + $this->orderFactory = $orderFactory; + $this->logger = $logger; + } + + /** + * Load an order by its ID via frontend controller and compare with another order. + * + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + $result = $this->jsonFactory->create(); + + $orderId1 = 35; + $orderId2 = 42; + + if ($orderId1 <= 0 || $orderId2 <= 0) { + $errorMessage = __('Invalid order IDs provided.'); + $this->logger->error($errorMessage); + $result->setData(['status' => 'error', 'message' => $errorMessage]); + return $result; + } + + try { + $order1 = $this->orderFactory->create()->load($orderId1); + $order2 = $this->orderFactory->create()->load($orderId2); + + if (!$order1->getId() || !$order2->getId()) { + $errorMessage = __('One or both orders not found.'); + $this->logger->error($errorMessage); + $result->setData(['status' => 'error', 'message' => $errorMessage]); + return $result; + } + + // Compare orders and get differences + $differences = $this->compareOrders($order1->getData(), $order2->getData()); + + if (empty($differences)) { + $result->setData(['status' => 'success', 'message' => 'Orders are identical']); + } else { + $result->setData(['status' => 'error', 'message' => 'Orders have differences', 'differences' => $differences]); + } + + } catch (\Exception $e) { + $errorMessage = __('Error loading or comparing orders: %1', $e->getMessage()); + $this->logger->error($errorMessage); + $result->setData(['status' => 'error', 'message' => $errorMessage]); + } + + return $result; + } + + /** + * Compare two orders and return differences. + * + * @param array $orderData1 + * @param array $orderData2 + * @return array + */ + private function compareOrders(array $orderData1, array $orderData2) + { + $differences = []; + + foreach ($orderData1 as $key => $value) { + if (array_key_exists($key, $orderData2)) { + if ($value !== $orderData2[$key]) { + $differences[$key] = [ + 'order1' => $value, + 'order2' => $orderData2[$key] + ]; + } + } else { + $differences[$key] = [ + 'order1' => $value, + 'order2' => 'Key not found in order2' + ]; + } + } + + // Check for keys in order2 that are not in order1 + foreach ($orderData2 as $key => $value) { + if (!array_key_exists($key, $orderData1)) { + $differences[$key] = [ + 'order1' => 'Key not found in order1', + 'order2' => $value + ]; + } + } + + return $differences; + } +} diff --git a/Controller/Index/Webhook.php b/Controller/Index/Webhook.php index 077db673..560acb82 100644 --- a/Controller/Index/Webhook.php +++ b/Controller/Index/Webhook.php @@ -61,11 +61,11 @@ public function __construct( */ public function execute() { - if (!$this->validateRequest()) { - $ip = $this->webhookHandler->getRemoteIp(); - $this->logger->error(__(sprintf('Invalid webhook attempt from IP %s', $ip))); - return $this->getResponse()->setHttpResponseCode(500); - } +// if (!$this->validateRequest()) { +// $ip = $this->webhookHandler->getRemoteIp(); +// $this->logger->error(__(sprintf('Invalid webhook attempt from IP %s', $ip))); +// return $this->getResponse()->setHttpResponseCode(500); +// } $body = file_get_contents('php://input'); $this->logger->info(__(sprintf("Webhook New Event!\n%s", $body))); diff --git a/Cron/ProcessOrderCreationQueue.php b/Cron/ProcessOrderCreationQueue.php new file mode 100644 index 00000000..58a99a8f --- /dev/null +++ b/Cron/ProcessOrderCreationQueue.php @@ -0,0 +1,69 @@ +logger = $logger; + $this->orderCreationQueueRepository = $orderCreationQueueRepository; + $this->orderCreator = $orderCreator; + } + + /** + * Process the oldest pending order creation request. + */ + public function execute() + { + try { + $queueItem = $this->orderCreationQueueRepository->getOldestPending(); + if (!$queueItem) { + $this->logger->info(__('No pending order creation requests in the queue.')); + return; + } + + $billData = json_decode($queueItem->getBillData(), true); + $result = $this->orderCreator->createOrderFromBill($billData); + + if ($result) { + $queueItem->setStatus('completed'); + } else { + $queueItem->setStatus('failed'); + } + + $this->orderCreationQueueRepository->save($queueItem); + } catch (\Exception $e) { + $this->logger->error(__('Error processing order creation queue: %1', $e->getMessage())); + } + } +} diff --git a/Helper/WebHookHandlers/BillCreated.php b/Helper/WebHookHandlers/BillCreated.php index 8079a4b3..338126dc 100644 --- a/Helper/WebHookHandlers/BillCreated.php +++ b/Helper/WebHookHandlers/BillCreated.php @@ -2,7 +2,8 @@ namespace Vindi\Payment\Helper\WebHookHandlers; - +use Vindi\Payment\Api\OrderCreationQueueRepositoryInterface; +use Vindi\Payment\Model\OrderCreationQueueFactory; /** * Class BillCreated @@ -20,17 +21,31 @@ class BillCreated private $orderCreator; /** - * Constructor - * + * @var OrderCreationQueueRepositoryInterface + */ + private $orderCreationQueueRepository; + + /** + * @var OrderCreationQueueFactory + */ + private $orderCreationQueueFactory; + + /** * @param \Psr\Log\LoggerInterface $logger * @param OrderCreator $orderCreator + * @param OrderCreationQueueRepositoryInterface $orderCreationQueueRepository + * @param OrderCreationQueueFactory $orderCreationQueueFactory */ public function __construct( \Psr\Log\LoggerInterface $logger, - OrderCreator $orderCreator + OrderCreator $orderCreator, + OrderCreationQueueRepositoryInterface $orderCreationQueueRepository, + OrderCreationQueueFactory $orderCreationQueueFactory ) { $this->logger = $logger; $this->orderCreator = $orderCreator; + $this->orderCreationQueueRepository = $orderCreationQueueRepository; + $this->orderCreationQueueFactory = $orderCreationQueueFactory; } /** @@ -55,7 +70,14 @@ public function billCreated($data) return false; } - $this->orderCreator->createOrderFromBill($bill); + $queueItem = $this->orderCreationQueueFactory->create(); + + $queueItem->setData([ + 'bill_data' => json_encode($data), + 'status' => 'pending' + ]); + + $this->orderCreationQueueRepository->save($queueItem); return true; } diff --git a/Helper/WebHookHandlers/OrderCreator.php b/Helper/WebHookHandlers/OrderCreator.php index 257ab2b1..a3254883 100644 --- a/Helper/WebHookHandlers/OrderCreator.php +++ b/Helper/WebHookHandlers/OrderCreator.php @@ -1,4 +1,5 @@ getOrderFromSubscriptionId($subscriptionId); + if (empty($billData['bill']) || empty($billData['bill']['subscription'])) { + throw new LocalizedException(__('Invalid bill data structure.')); + } + + $bill = $billData['bill']; + $subscriptionId = $bill['subscription']['id']; + $originalOrder = $this->getOrderFromSubscriptionId($subscriptionId); if ($originalOrder) { $newOrder = $this->replicateOrder($originalOrder, $billData); @@ -70,7 +76,6 @@ public function createOrderFromBill($billData) return false; } catch (\Exception $e) { - // Log the exception or handle it as per your needs return false; } } @@ -91,7 +96,7 @@ protected function getOrderFromSubscriptionId($subscriptionId) } /** - * Replicate an order with new details + * Replicate an order from an existing order * * @param Order $originalOrder * @param array $billData @@ -99,17 +104,15 @@ protected function getOrderFromSubscriptionId($subscriptionId) */ protected function replicateOrder(Order $originalOrder, $billData) { - // Clone the original order $newOrder = clone $originalOrder; $newOrder->setId(null); $newOrder->setIncrementId(null); - $newOrder->setVindiBillId($billData['id']); - $newOrder->setVindiSubscriptionId($billData['subscription']['id']); + $newOrder->setVindiBillId($billData['bill']['id']); + $newOrder->setVindiSubscriptionId($billData['bill']['subscription']['id']); $newOrder->setCreatedAt(null); $newOrder->setState(Order::STATE_NEW); - $newOrder->setStatus(Order::STATE_NEW); + $newOrder->setStatus('pending'); - // Replicate billing and shipping addresses $billingAddress = clone $originalOrder->getBillingAddress(); $billingAddress->setId(null)->setParentId(null); $newOrder->setBillingAddress($billingAddress); @@ -120,27 +123,54 @@ protected function replicateOrder(Order $originalOrder, $billData) $newOrder->setShippingAddress($shippingAddress); } - // Replicate items $newOrderItems = []; - foreach ($originalOrder->getAllVisibleItems() as $item) { - $newItem = clone $item; + foreach ($originalOrder->getAllVisibleItems() as $originalItem) { + $newItem = clone $originalItem; $newItem->setId(null)->setOrderId(null); $newOrderItems[] = $newItem; } $newOrder->setItems($newOrderItems); - // Replicate payment $originalPayment = $originalOrder->getPayment(); $newPayment = clone $originalPayment; $newPayment->setId(null)->setOrderId(null); $newOrder->setPayment($newPayment); - // Reset additional fields if needed - $newOrder->setTotalPaid(0); - $newOrder->setBaseTotalPaid(0); + $newOrder->setTotalPaid(null); + $newOrder->setBaseTotalPaid(null); $newOrder->setTotalDue($newOrder->getGrandTotal()); $newOrder->setBaseTotalDue($newOrder->getBaseGrandTotal()); + $subtotal = 0; + $grandTotal = 0; + foreach ($newOrderItems as $item) { + $subtotal += $item->getRowTotal(); + $grandTotal += $item->getRowTotal() + $item->getTaxAmount() - $item->getDiscountAmount(); + } + + $taxAmount = $originalOrder->getTaxAmount(); + $baseTaxAmount = $originalOrder->getBaseTaxAmount(); + $newOrder->setTaxAmount($taxAmount); + $newOrder->setBaseTaxAmount($baseTaxAmount); + + $shippingAmount = $originalOrder->getShippingAmount(); + $baseShippingAmount = $originalOrder->getBaseShippingAmount(); + $newOrder->setShippingAmount($shippingAmount); + $newOrder->setBaseShippingAmount($baseShippingAmount); + + $discountAmount = $originalOrder->getDiscountAmount(); + $baseDiscountAmount = $originalOrder->getBaseDiscountAmount(); + $newOrder->setDiscountAmount($discountAmount); + $newOrder->setBaseDiscountAmount($baseDiscountAmount); + + $grandTotal += $taxAmount + $shippingAmount - $discountAmount; + $newOrder->setSubtotal($subtotal); + $newOrder->setBaseSubtotal($subtotal); + $newOrder->setGrandTotal($grandTotal); + $newOrder->setBaseGrandTotal($grandTotal); + $newOrder->setTotalDue($grandTotal); + $newOrder->setBaseTotalDue($grandTotal); + return $newOrder; } } diff --git a/Model/OrderCreationQueue.php b/Model/OrderCreationQueue.php new file mode 100644 index 00000000..0dce13f5 --- /dev/null +++ b/Model/OrderCreationQueue.php @@ -0,0 +1,16 @@ +_init('Vindi\Payment\Model\ResourceModel\OrderCreationQueue'); + } +} diff --git a/Model/OrderCreationQueueRepository.php b/Model/OrderCreationQueueRepository.php new file mode 100644 index 00000000..ead93419 --- /dev/null +++ b/Model/OrderCreationQueueRepository.php @@ -0,0 +1,99 @@ +resource = $resource; + $this->orderCreationQueueFactory = $orderCreationQueueFactory; + $this->collectionFactory = $collectionFactory; + } + + /** + * Save a queue item + * + * @param OrderCreationQueue $queueItem + * @return OrderCreationQueue + */ + public function save(OrderCreationQueue $queueItem) + { + $this->resource->save($queueItem); + return $queueItem; + } + + /** + * Get a queue item by ID + * + * @param int $id + * @return OrderCreationQueue + * @throws NoSuchEntityException + */ + public function getById($id) + { + $queueItem = $this->orderCreationQueueFactory->create(); + $this->resource->load($queueItem, $id); + if (!$queueItem->getId()) { + throw new NoSuchEntityException(__('Queue item with ID "%1" does not exist.', $id)); + } + return $queueItem; + } + + /** + * Get the oldest pending queue item + * + * @return OrderCreationQueue|null + */ + public function getOldestPending() + { + $collection = $this->collectionFactory->create() + ->addFieldToFilter('status', 'pending') + ->setOrder('created_at', 'ASC') + ->setPageSize(1) + ->getFirstItem(); + + return $collection->getId() ? $collection : null; + } + + /** + * Delete a queue item + * + * @param OrderCreationQueue $queueItem + * @return void + */ + public function delete(OrderCreationQueue $queueItem) + { + $this->resource->delete($queueItem); + } +} diff --git a/Model/ResourceModel/OrderCreationQueue.php b/Model/ResourceModel/OrderCreationQueue.php new file mode 100644 index 00000000..47769321 --- /dev/null +++ b/Model/ResourceModel/OrderCreationQueue.php @@ -0,0 +1,16 @@ +_init('vindi_order_creation_queue', 'queue_id'); + } +} diff --git a/Model/ResourceModel/OrderCreationQueue/Collection.php b/Model/ResourceModel/OrderCreationQueue/Collection.php new file mode 100644 index 00000000..c9e12b39 --- /dev/null +++ b/Model/ResourceModel/OrderCreationQueue/Collection.php @@ -0,0 +1,18 @@ +_init(Model::class, ResourceModel::class); + } +} diff --git a/etc/crontab.xml b/etc/crontab.xml index 50660876..1e946a79 100644 --- a/etc/crontab.xml +++ b/etc/crontab.xml @@ -5,5 +5,8 @@ 0 * * * * + + * * * * * + diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 87e06930..29b8e08e 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -76,4 +76,14 @@
+ + + + + + + + + +
diff --git a/etc/di.xml b/etc/di.xml index 1e7b984f..30b4d54d 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -117,4 +117,6 @@ Magento\Checkout\Model\Session + + From c5a352e9ba826dfd9dbf54712b2f6230b33957d0 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Wed, 8 May 2024 18:29:27 +0000 Subject: [PATCH 085/187] feat: feat: when a new invoice notification arrives, create a new order --- Cron/ProcessOrderCreationQueue.php | 15 +++++++- Helper/WebHookHandlers/OrderCreator.php | 46 ++++++++++++++++++++++--- etc/crontab.xml | 2 +- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/Cron/ProcessOrderCreationQueue.php b/Cron/ProcessOrderCreationQueue.php index 58a99a8f..ea44b336 100644 --- a/Cron/ProcessOrderCreationQueue.php +++ b/Cron/ProcessOrderCreationQueue.php @@ -5,6 +5,7 @@ use Psr\Log\LoggerInterface; use Vindi\Payment\Api\OrderCreationQueueRepositoryInterface; use Vindi\Payment\Helper\WebHookHandlers\OrderCreator; +use Magento\Framework\Exception\LocalizedException; class ProcessOrderCreationQueue { @@ -53,17 +54,29 @@ public function execute() } $billData = json_decode($queueItem->getBillData(), true); + + if (!$billData) { + $this->logger->error(__('Invalid bill data in the queue item ID %1', $queueItem->getId())); + $queueItem->setStatus('failed'); + $this->orderCreationQueueRepository->save($queueItem); + return; + } + $result = $this->orderCreator->createOrderFromBill($billData); if ($result) { $queueItem->setStatus('completed'); + $this->logger->info(__('Successfully processed order creation queue item ID %1', $queueItem->getId())); } else { $queueItem->setStatus('failed'); + $this->logger->error(__('Failed to process order creation queue item ID %1', $queueItem->getId())); } $this->orderCreationQueueRepository->save($queueItem); - } catch (\Exception $e) { + } catch (LocalizedException $e) { $this->logger->error(__('Error processing order creation queue: %1', $e->getMessage())); + } catch (\Exception $e) { + $this->logger->error(__('Unexpected error processing order creation queue: %1', $e->getMessage())); } } } diff --git a/Helper/WebHookHandlers/OrderCreator.php b/Helper/WebHookHandlers/OrderCreator.php index a3254883..061706f7 100644 --- a/Helper/WebHookHandlers/OrderCreator.php +++ b/Helper/WebHookHandlers/OrderCreator.php @@ -7,6 +7,7 @@ use Magento\Sales\Model\Service\OrderService; use Magento\Framework\Exception\LocalizedException; use Vindi\Payment\Model\SubscriptionOrderRepository; +use Vindi\Payment\Model\SubscriptionOrderFactory; use Vindi\Payment\Model\Payment\Bill as PaymentBill; class OrderCreator @@ -21,6 +22,11 @@ class OrderCreator */ protected $subscriptionOrderRepository; + /** + * @var SubscriptionOrderFactory + */ + protected $subscriptionOrderFactory; + /** * @var OrderService */ @@ -32,21 +38,23 @@ class OrderCreator protected $paymentBill; /** - * Constructor - * + * OrderCreator constructor. * @param OrderFactory $orderFactory * @param SubscriptionOrderRepository $subscriptionOrderRepository + * @param SubscriptionOrderFactory $subscriptionOrderFactory * @param OrderService $orderService * @param PaymentBill $paymentBill */ public function __construct( OrderFactory $orderFactory, SubscriptionOrderRepository $subscriptionOrderRepository, + SubscriptionOrderFactory $subscriptionOrderFactory, OrderService $orderService, PaymentBill $paymentBill ) { $this->orderFactory = $orderFactory; $this->subscriptionOrderRepository = $subscriptionOrderRepository; + $this->subscriptionOrderFactory = $subscriptionOrderFactory; $this->orderService = $orderService; $this->paymentBill = $paymentBill; } @@ -69,8 +77,11 @@ public function createOrderFromBill($billData) $originalOrder = $this->getOrderFromSubscriptionId($subscriptionId); if ($originalOrder) { - $newOrder = $this->replicateOrder($originalOrder, $billData); + $newOrder = $this->replicateOrder($originalOrder, $bill); $newOrder->save(); + + $this->registerSubscriptionOrder($newOrder, $subscriptionId); + return true; } @@ -89,9 +100,11 @@ public function createOrderFromBill($billData) protected function getOrderFromSubscriptionId($subscriptionId) { $subscriptionOrder = $this->subscriptionOrderRepository->getBySubscriptionId($subscriptionId); + if ($subscriptionOrder) { return $this->orderFactory->create()->load($subscriptionOrder->getOrderId()); } + return null; } @@ -107,8 +120,8 @@ protected function replicateOrder(Order $originalOrder, $billData) $newOrder = clone $originalOrder; $newOrder->setId(null); $newOrder->setIncrementId(null); - $newOrder->setVindiBillId($billData['bill']['id']); - $newOrder->setVindiSubscriptionId($billData['bill']['subscription']['id']); + $newOrder->setVindiBillId($billData['id']); + $newOrder->setVindiSubscriptionId($billData['subscription']['id']); $newOrder->setCreatedAt(null); $newOrder->setState(Order::STATE_NEW); $newOrder->setStatus('pending'); @@ -173,4 +186,27 @@ protected function replicateOrder(Order $originalOrder, $billData) return $newOrder; } + + /** + * Register the new order in the subscription orders table + * + * @param Order $order + * @param int $subscriptionId + */ + protected function registerSubscriptionOrder(Order $order, $subscriptionId) + { + try { + $subscriptionOrder = $this->subscriptionOrderFactory->create(); + + $subscriptionOrder->setOrderId($order->getId()); + $subscriptionOrder->setIncrementId($order->getIncrementId()); + $subscriptionOrder->setSubscriptionId($subscriptionId); + $subscriptionOrder->setCreatedAt((new \DateTime())->format('Y-m-d H:i:s')); + $subscriptionOrder->setTotal($order->getGrandTotal()); + $subscriptionOrder->setStatus($order->getStatus()); + + $this->subscriptionOrderRepository->save($subscriptionOrder); + } catch (\Exception $e) { + } + } } diff --git a/etc/crontab.xml b/etc/crontab.xml index 1e946a79..e5a4970c 100644 --- a/etc/crontab.xml +++ b/etc/crontab.xml @@ -6,7 +6,7 @@ 0 * * * * - * * * * * + */2 * * * * From 0b4c3cf773bfcee69e34fae00b89005bc3857beb Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Wed, 8 May 2024 18:31:56 +0000 Subject: [PATCH 086/187] fix: removing controllers cron --- Controller/Cron/Order.php | 80 --------------------- Controller/Cron/OrderById.php | 131 ---------------------------------- 2 files changed, 211 deletions(-) delete mode 100644 Controller/Cron/Order.php delete mode 100644 Controller/Cron/OrderById.php diff --git a/Controller/Cron/Order.php b/Controller/Cron/Order.php deleted file mode 100644 index 57b098de..00000000 --- a/Controller/Cron/Order.php +++ /dev/null @@ -1,80 +0,0 @@ -jsonFactory = $jsonFactory; - $this->processOrderCreationQueue = $processOrderCreationQueue; - $this->logger = $logger; - $this->lockFilePath = sys_get_temp_dir() . '/vindi_payment_process_order_creation_queue.lock'; - } - - /** - * Execute the cron task manually via frontend controller. - * - * @return \Magento\Framework\Controller\Result\Json - */ - public function execute() - { - $result = $this->jsonFactory->create(); - $lockHandle = fopen($this->lockFilePath, 'c+'); - - if (flock($lockHandle, LOCK_EX | LOCK_NB)) { - try { - $this->processOrderCreationQueue->execute(); - $message = 'Successfully processed the order creation queue.'; - $result->setData(['status' => 'success', 'message' => $message]); - } catch (\Exception $e) { - $errorMessage = __('Error processing order creation queue: %1', $e->getMessage()); - $this->logger->error($errorMessage); - $result->setData(['status' => 'error', 'message' => $errorMessage]); - } - - flock($lockHandle, LOCK_UN); - } else { - $message = 'Process already running. Skipping execution.'; - $result->setData(['status' => 'info', 'message' => $message]); - } - - fclose($lockHandle); - return $result; - } -} diff --git a/Controller/Cron/OrderById.php b/Controller/Cron/OrderById.php deleted file mode 100644 index fbf9e320..00000000 --- a/Controller/Cron/OrderById.php +++ /dev/null @@ -1,131 +0,0 @@ -jsonFactory = $jsonFactory; - $this->orderFactory = $orderFactory; - $this->logger = $logger; - } - - /** - * Load an order by its ID via frontend controller and compare with another order. - * - * @return \Magento\Framework\Controller\Result\Json - */ - public function execute() - { - $result = $this->jsonFactory->create(); - - $orderId1 = 35; - $orderId2 = 42; - - if ($orderId1 <= 0 || $orderId2 <= 0) { - $errorMessage = __('Invalid order IDs provided.'); - $this->logger->error($errorMessage); - $result->setData(['status' => 'error', 'message' => $errorMessage]); - return $result; - } - - try { - $order1 = $this->orderFactory->create()->load($orderId1); - $order2 = $this->orderFactory->create()->load($orderId2); - - if (!$order1->getId() || !$order2->getId()) { - $errorMessage = __('One or both orders not found.'); - $this->logger->error($errorMessage); - $result->setData(['status' => 'error', 'message' => $errorMessage]); - return $result; - } - - // Compare orders and get differences - $differences = $this->compareOrders($order1->getData(), $order2->getData()); - - if (empty($differences)) { - $result->setData(['status' => 'success', 'message' => 'Orders are identical']); - } else { - $result->setData(['status' => 'error', 'message' => 'Orders have differences', 'differences' => $differences]); - } - - } catch (\Exception $e) { - $errorMessage = __('Error loading or comparing orders: %1', $e->getMessage()); - $this->logger->error($errorMessage); - $result->setData(['status' => 'error', 'message' => $errorMessage]); - } - - return $result; - } - - /** - * Compare two orders and return differences. - * - * @param array $orderData1 - * @param array $orderData2 - * @return array - */ - private function compareOrders(array $orderData1, array $orderData2) - { - $differences = []; - - foreach ($orderData1 as $key => $value) { - if (array_key_exists($key, $orderData2)) { - if ($value !== $orderData2[$key]) { - $differences[$key] = [ - 'order1' => $value, - 'order2' => $orderData2[$key] - ]; - } - } else { - $differences[$key] = [ - 'order1' => $value, - 'order2' => 'Key not found in order2' - ]; - } - } - - // Check for keys in order2 that are not in order1 - foreach ($orderData2 as $key => $value) { - if (!array_key_exists($key, $orderData1)) { - $differences[$key] = [ - 'order1' => 'Key not found in order1', - 'order2' => $value - ]; - } - } - - return $differences; - } -} From 0479dccadcf85c6bbf8fabcd2f45d2b50b8772fb Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Thu, 9 May 2024 20:15:26 +0000 Subject: [PATCH 087/187] fix: webhook validation fix --- Controller/Index/Webhook.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Controller/Index/Webhook.php b/Controller/Index/Webhook.php index 560acb82..077db673 100644 --- a/Controller/Index/Webhook.php +++ b/Controller/Index/Webhook.php @@ -61,11 +61,11 @@ public function __construct( */ public function execute() { -// if (!$this->validateRequest()) { -// $ip = $this->webhookHandler->getRemoteIp(); -// $this->logger->error(__(sprintf('Invalid webhook attempt from IP %s', $ip))); -// return $this->getResponse()->setHttpResponseCode(500); -// } + if (!$this->validateRequest()) { + $ip = $this->webhookHandler->getRemoteIp(); + $this->logger->error(__(sprintf('Invalid webhook attempt from IP %s', $ip))); + return $this->getResponse()->setHttpResponseCode(500); + } $body = file_get_contents('php://input'); $this->logger->info(__(sprintf("Webhook New Event!\n%s", $body))); From 0a0602656c9e4c3c798ca0cee096bfbda0bf3494 Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Fri, 10 May 2024 13:30:57 +0000 Subject: [PATCH 088/187] fix: fixing billing_trigger_day saving --- Controller/Adminhtml/VindiPlan/Save.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/Adminhtml/VindiPlan/Save.php b/Controller/Adminhtml/VindiPlan/Save.php index 1b90b300..c3ec63de 100755 --- a/Controller/Adminhtml/VindiPlan/Save.php +++ b/Controller/Adminhtml/VindiPlan/Save.php @@ -268,7 +268,7 @@ private function prepareDataForMagentoStore($data, $post) $data['duration'] = $post["settings"]["duration"]; } - if (!empty($post["settings"]["billing_trigger_day_type_on_period"])) { + if (isset($post["settings"]["billing_trigger_day_type_on_period"])) { $data['billing_trigger_day_type_on_period'] = $post["settings"]["billing_trigger_day_type_on_period"]; } From 9e79258d41cac877c040a36efe1ba7215d65bdbd Mon Sep 17 00:00:00 2001 From: Iago Cedran Date: Fri, 17 May 2024 02:50:10 +0000 Subject: [PATCH 089/187] feat: For orders with pix and boleto/bolepix, the order will be created for the customer and they must view the QRCode, boleto, etc. to make the payment --- Api/Data/SubscriptionInterface.php | 8 ++ Api/Data/SubscriptionOrderInterface.php | 1 + Block/Onepage/Pix.php | 80 +++++++++++-- Controller/Adminhtml/VindiPlan/Save.php | 12 +- Helper/EmailSender.php | 104 +++++++++++++++++ Helper/WebHookHandlers/BillCreated.php | 40 +++++-- Helper/WebHookHandlers/OrderCreator.php | 47 +++++--- Model/Data/Subscription.php | 38 ++++++ Model/Payment/AbstractMethod.php | 21 +++- Plugin/AddCustomOptionToQuoteItem.php | 12 +- etc/db_schema.xml | 4 +- etc/email_templates.xml | 4 + etc/frontend/di.xml | 8 ++ i18n/pt_BR.csv | 18 ++- .../email/qrcode_available_email.html | 41 +++++++ .../templates/onepage/bankslippix.phtml | 110 +++++++++++------- view/frontend/templates/onepage/pix.phtml | 81 ++++++++----- 17 files changed, 505 insertions(+), 124 deletions(-) create mode 100644 Helper/EmailSender.php create mode 100644 etc/email_templates.xml create mode 100644 view/frontend/email/qrcode_available_email.html diff --git a/Api/Data/SubscriptionInterface.php b/Api/Data/SubscriptionInterface.php index bb6565f7..7277c873 100644 --- a/Api/Data/SubscriptionInterface.php +++ b/Api/Data/SubscriptionInterface.php @@ -15,6 +15,8 @@ interface SubscriptionInterface const STATUS = 'status'; const START_AT = 'start_at'; const PLAN = 'plan'; + const NEXT_BILLING_AT = 'next_billing_at'; + const BILL_ID = 'bill_id'; /** * Get id @@ -106,4 +108,10 @@ public function getStatus(); * @return \Vindi\Payment\Api\Data\SubscriptionInterface */ public function setStatus($status); + + public function getNextBillingAt(); + public function setNextBillingAt($nextBillingAt); + + public function getBillId(); + public function setBillId($billId); } diff --git a/Api/Data/SubscriptionOrderInterface.php b/Api/Data/SubscriptionOrderInterface.php index 361c16a3..3fd324cb 100755 --- a/Api/Data/SubscriptionOrderInterface.php +++ b/Api/Data/SubscriptionOrderInterface.php @@ -31,4 +31,5 @@ public function getTotal(); public function setTotal($total); public function getStatus(); public function setStatus($status); + } diff --git a/Block/Onepage/Pix.php b/Block/Onepage/Pix.php index f8ee56af..0a60d6a1 100644 --- a/Block/Onepage/Pix.php +++ b/Block/Onepage/Pix.php @@ -2,17 +2,21 @@ namespace Vindi\Payment\Block\Onepage; - use Magento\Checkout\Model\Session; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Element\Template\Context; use Magento\Sales\Model\Order; use Vindi\Payment\Api\PixConfigurationInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +/** + * Class Pix + * @package Vindi\Payment\Block\Onepage + */ class Pix extends Template { - /** * @var Session */ @@ -29,10 +33,23 @@ class Pix extends Template protected $json; /** + * @var ResourceConnection + */ + protected $resourceConnection; + + /** + * @var TimezoneInterface + */ + protected $timezone; + + /** + * Pix constructor. * @param PixConfigurationInterface $pixConfiguration * @param Session $checkoutSession * @param Context $context * @param Json $json + * @param ResourceConnection $resourceConnection + * @param TimezoneInterface $timezone * @param array $data */ public function __construct( @@ -40,31 +57,45 @@ public function __construct( Session $checkoutSession, Context $context, Json $json, + ResourceConnection $resourceConnection, + TimezoneInterface $timezone, array $data = [] ) { parent::__construct($context, $data); $this->checkoutSession = $checkoutSession; $this->pixConfiguration = $pixConfiguration; $this->json = $json; + $this->resourceConnection = $resourceConnection; + $this->timezone = $timezone; } /** + * Checks if the payment method is Pix and can show the Pix QR code + * * @return bool */ public function canShowPix() { - return $this->getOrder()->getPayment()->getMethod() === \Vindi\Payment\Model\Payment\Pix::CODE; + $order = $this->getOrder(); + if ($order && $order->getPayment()) { + return $order->getPayment()->getMethod() === \Vindi\Payment\Model\Payment\Pix::CODE; + } + return false; } /** - * @return string[] + * Returns the Pix QR code path + * + * @return string */ public function getQrCodePix() { - return $this->getOrder()->getPayment()->getAdditionalInformation('qrcode_path'); + return $this->getOrder()->getPayment()->getAdditionalInformation('qrcode_path') ?? ''; } /** + * Returns the Pix QR code warning message + * * @return string */ public function getQrCodeWarningMessage() @@ -73,6 +104,8 @@ public function getQrCodeWarningMessage() } /** + * Returns the information message displayed on onepage success + * * @return string */ public function getInfoMessageOnepageSuccess() @@ -81,7 +114,9 @@ public function getInfoMessageOnepageSuccess() } /** - * @return bool|string + * Returns the original Pix QR code path serialized in JSON format + * + * @return string */ public function getQrcodeOriginalPath() { @@ -90,10 +125,41 @@ public function getQrcodeOriginalPath() } /** - * @return \Magento\Sales\Model\Order + * Retrieves the last real order from the checkout session + * + * @return Order */ protected function getOrder(): Order { return $this->checkoutSession->getLastRealOrder(); } + + /** + * Returns the next billing date of the subscription + * + * @return string|null + */ + public function getNextBillingDate() + { + try { + $connection = $this->resourceConnection->getConnection(); + $subscriptionOrdersTable = $this->resourceConnection->getTableName('vindi_subscription_orders'); + $subscriptionTable = $this->resourceConnection->getTableName('vindi_subscription'); + + $select = $connection->select() + ->from(['so' => $subscriptionOrdersTable], []) + ->joinInner( + ['s' => $subscriptionTable], + 'so.subscription_id = s.id', + ['next_billing_at'] + ) + ->where('so.order_id = ?', $this->getOrder()->getId()); + + $result = $connection->fetchOne($select); + return $result ? $this->timezone->formatDate($result, \IntlDateFormatter::SHORT, false) : null; + } catch (\Exception $e) { + $this->logger->error(__('Error fetching next billing date: %1', $e->getMessage())); + return null; + } + } } diff --git a/Controller/Adminhtml/VindiPlan/Save.php b/Controller/Adminhtml/VindiPlan/Save.php index c3ec63de..8f1ae202 100755 --- a/Controller/Adminhtml/VindiPlan/Save.php +++ b/Controller/Adminhtml/VindiPlan/Save.php @@ -223,17 +223,13 @@ private function prepareData($post, $name, $code) } if (!empty($post["settings"]["billing_trigger_type"])) { - if ($post["settings"]['billing_trigger_type'] != 'day_of_month') { - $data['billing_trigger_type'] = $post["settings"]["billing_trigger_day_based_on_period"]; - } else { - $data['billing_trigger_type'] = $post["settings"]["billing_trigger_type"]; + if ($post["settings"]['billing_trigger_type'] == 'day_of_month') { + $data['billing_trigger_day'] = $post["settings"]["billing_trigger_day"] ?? null; + } elseif ($post["settings"]['billing_trigger_type'] == 'based_on_period') { + $data['billing_trigger_day'] = $post["settings"]["billing_trigger_day_type_on_period"] ?? null; } } - if (!empty($post["settings"]["billing_trigger_day"])) { - $data['billing_trigger_day'] = $post["settings"]["billing_trigger_day"]; - } - if (isset($post["settings"]["billing_cycles"]) && $post["settings"]["billing_cycles"] !== '') { $data['billing_cycles'] = $post["settings"]["billing_cycles"]; } diff --git a/Helper/EmailSender.php b/Helper/EmailSender.php new file mode 100644 index 00000000..581cfad2 --- /dev/null +++ b/Helper/EmailSender.php @@ -0,0 +1,104 @@ +transportBuilder = $transportBuilder; + $this->inlineTranslation = $inlineTranslation; + $this->storeManager = $storeManager; + $this->logger = $logger; + } + + /** + * Sends an email notifying the customer that the QR Code or Bank Slip is available for payment. + * + * @param Order $order + * @return void + */ + public function sendQrCodeAvailableEmail(Order $order) + { + try { + $this->inlineTranslation->suspend(); + + $store = $this->storeManager->getStore(); + $customerEmail = $order->getCustomerEmail(); + $customerName = $order->getCustomerFirstname() . ' ' . $order->getCustomerLastname(); + $orderId = $order->getId(); + $orderLink = $store->getUrl('sales/order/view', ['order_id' => $orderId]); + + $templateVars = [ + 'store' => $store, + 'customer_name' => $customerName, + 'order_id' => $orderId, + 'order_link' => $orderLink + ]; + + $from = [ + 'email' => $store->getConfig('trans_email/ident_sales/email'), + 'name' => $store->getConfig('trans_email/ident_sales/name') + ]; + + $this->transportBuilder + ->setTemplateIdentifier('vindi_payment_qrcode_available_email_template') // ID of Email template + ->setTemplateOptions([ + 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, + 'store' => $store->getId(), + ]) + ->setTemplateVars($templateVars) + ->setFrom($from) + ->addTo($customerEmail, $customerName); + + $transport = $this->transportBuilder->getTransport(); + $message = $transport->getMessage(); + $message->setSubject(__('Your QRCode and/or BankSlip is now available for payment.')); + + $transport->sendMessage(); + + $this->inlineTranslation->resume(); + $this->logger->info(__('Email notification for QR Code availability sent to customer.')); + } catch (\Exception $e) { + $this->inlineTranslation->resume(); + $this->logger->error(__('Error sending QR Code availability email: %1', $e->getMessage())); + } + } +} diff --git a/Helper/WebHookHandlers/BillCreated.php b/Helper/WebHookHandlers/BillCreated.php index 338126dc..c1310228 100644 --- a/Helper/WebHookHandlers/BillCreated.php +++ b/Helper/WebHookHandlers/BillCreated.php @@ -4,6 +4,8 @@ use Vindi\Payment\Api\OrderCreationQueueRepositoryInterface; use Vindi\Payment\Model\OrderCreationQueueFactory; +use Magento\Sales\Model\OrderRepository; +use Vindi\Payment\Helper\EmailSender; /** * Class BillCreated @@ -31,21 +33,32 @@ class BillCreated private $orderCreationQueueFactory; /** - * @param \Psr\Log\LoggerInterface $logger - * @param OrderCreator $orderCreator - * @param OrderCreationQueueRepositoryInterface $orderCreationQueueRepository - * @param OrderCreationQueueFactory $orderCreationQueueFactory + * @var OrderRepository + */ + private $orderRepository; + + /** + * @var EmailSender + */ + private $emailSender; + + /** + * Constructor for initializing class dependencies. */ public function __construct( \Psr\Log\LoggerInterface $logger, OrderCreator $orderCreator, OrderCreationQueueRepositoryInterface $orderCreationQueueRepository, - OrderCreationQueueFactory $orderCreationQueueFactory + OrderCreationQueueFactory $orderCreationQueueFactory, + OrderRepository $orderRepository, + EmailSender $emailSender ) { $this->logger = $logger; $this->orderCreator = $orderCreator; $this->orderCreationQueueRepository = $orderCreationQueueRepository; $this->orderCreationQueueFactory = $orderCreationQueueFactory; + $this->orderRepository = $orderRepository; + $this->emailSender = $emailSender; } /** @@ -66,17 +79,28 @@ public function billCreated($data) } if (!isset($bill['subscription']) || $bill['subscription'] === null || !isset($bill['subscription']['id'])) { - $this->logger->info(__(sprintf('Ignoring the event "bill_created" for single sell'))); + $this->logger->info(__('Ignoring the event "bill_created" for single sell')); return false; } - $queueItem = $this->orderCreationQueueFactory->create(); + $originalOrder = $this->orderCreator->getOrderFromSubscriptionId($bill['subscription']['id']); + if ($originalOrder && ($originalOrder->getPayment()->getMethod() === 'vindi_pix' || $originalOrder->getPayment()->getMethod() === 'vindi_bankslippix')) { + $vindiBillId = (int) $originalOrder->getData('vindi_bill_id'); + if ($vindiBillId === null || $vindiBillId === '' || $vindiBillId === 0) { + $originalOrder->setData('vindi_bill_id', $bill['id']); + $this->orderRepository->save($originalOrder); + $this->logger->info(__('Vindi bill ID set for the order.')); + + $this->emailSender->sendQrCodeAvailableEmail($originalOrder); + return true; + } + } + $queueItem = $this->orderCreationQueueFactory->create(); $queueItem->setData([ 'bill_data' => json_encode($data), 'status' => 'pending' ]); - $this->orderCreationQueueRepository->save($queueItem); return true; diff --git a/Helper/WebHookHandlers/OrderCreator.php b/Helper/WebHookHandlers/OrderCreator.php index 061706f7..1985f2c9 100644 --- a/Helper/WebHookHandlers/OrderCreator.php +++ b/Helper/WebHookHandlers/OrderCreator.php @@ -4,6 +4,7 @@ use Magento\Sales\Model\OrderFactory; use Magento\Sales\Model\Order; +use Magento\Sales\Model\OrderRepository; use Magento\Sales\Model\Service\OrderService; use Magento\Framework\Exception\LocalizedException; use Vindi\Payment\Model\SubscriptionOrderRepository; @@ -37,6 +38,11 @@ class OrderCreator */ protected $paymentBill; + /** + * @var OrderRepository + */ + private $orderRepository; + /** * OrderCreator constructor. * @param OrderFactory $orderFactory @@ -44,26 +50,26 @@ class OrderCreator * @param SubscriptionOrderFactory $subscriptionOrderFactory * @param OrderService $orderService * @param PaymentBill $paymentBill + * @param OrderRepository $orderRepository */ public function __construct( OrderFactory $orderFactory, SubscriptionOrderRepository $subscriptionOrderRepository, SubscriptionOrderFactory $subscriptionOrderFactory, OrderService $orderService, - PaymentBill $paymentBill + PaymentBill $paymentBill, + OrderRepository $orderRepository ) { $this->orderFactory = $orderFactory; $this->subscriptionOrderRepository = $subscriptionOrderRepository; $this->subscriptionOrderFactory = $subscriptionOrderFactory; $this->orderService = $orderService; $this->paymentBill = $paymentBill; + $this->orderRepository = $orderRepository; } /** * Create an order from bill data - * - * @param array $billData - * @return bool */ public function createOrderFromBill($billData) { @@ -78,10 +84,12 @@ public function createOrderFromBill($billData) if ($originalOrder) { $newOrder = $this->replicateOrder($originalOrder, $bill); - $newOrder->save(); + $this->orderRepository->save($newOrder); $this->registerSubscriptionOrder($newOrder, $subscriptionId); + $this->updatePaymentDetails($newOrder, $billData); + return true; } @@ -93,11 +101,8 @@ public function createOrderFromBill($billData) /** * Fetch original order using subscription ID - * - * @param int $subscriptionId - * @return Order|null */ - protected function getOrderFromSubscriptionId($subscriptionId) + public function getOrderFromSubscriptionId($subscriptionId) { $subscriptionOrder = $this->subscriptionOrderRepository->getBySubscriptionId($subscriptionId); @@ -110,10 +115,6 @@ protected function getOrderFromSubscriptionId($subscriptionId) /** * Replicate an order from an existing order - * - * @param Order $originalOrder - * @param array $billData - * @return Order */ protected function replicateOrder(Order $originalOrder, $billData) { @@ -189,9 +190,6 @@ protected function replicateOrder(Order $originalOrder, $billData) /** * Register the new order in the subscription orders table - * - * @param Order $order - * @param int $subscriptionId */ protected function registerSubscriptionOrder(Order $order, $subscriptionId) { @@ -209,4 +207,21 @@ protected function registerSubscriptionOrder(Order $order, $subscriptionId) } catch (\Exception $e) { } } + + /** + * Update payment details in the order + */ + protected function updatePaymentDetails(Order $order, $billData) + { + if (($order->getPayment()->getMethod() === 'vindi_pix' || $order->getPayment()->getMethod() === 'vindi_bankslippix') + && !empty($billData['bill']['charges'][0]['last_transaction']['gateway_response_fields'])) { + $transactionDetails = $billData['bill']['charges'][0]['last_transaction']['gateway_response_fields']; + $additionalInformation = $order->getPayment()->getAdditionalInformation(); + $additionalInformation['qrcode_original_path'] = $transactionDetails['qrcode_original_path']; + $additionalInformation['qrcode_path'] = $transactionDetails['qrcode_path']; + $additionalInformation['max_days_to_keep_waiting_payment'] = $transactionDetails['max_days_to_keep_waiting_payment']; + $order->getPayment()->setAdditionalInformation($additionalInformation); + $this->orderRepository->save($order); + } + } } diff --git a/Model/Data/Subscription.php b/Model/Data/Subscription.php index a3c7a270..36846fe0 100644 --- a/Model/Data/Subscription.php +++ b/Model/Data/Subscription.php @@ -143,4 +143,42 @@ public function setStatus($status) { return $this->setData(self::STATUS, $status); } + + /** + * Get next_billing_at + * @return string|null + */ + public function getNextBillingAt() + { + return $this->_get(self::NEXT_BILLING_AT); + } + + /** + * Set next_billing_at + * @param string $nextBillingAt + * @return SubscriptionInterface + */ + public function setNextBillingAt($nextBillingAt) + { + return $this->setData(self::NEXT_BILLING_AT, $nextBillingAt); + } + + /** + * Get end_at + * @return string|null + */ + public function getBillId() + { + return $this->_get(self::BILL_ID); + } + + /** + * Set end_at + * @param string $billId + * @return SubscriptionInterface + */ + public function setBillId($billId) + { + return $this->setData(self::BILL_ID, $billId); + } } diff --git a/Model/Payment/AbstractMethod.php b/Model/Payment/AbstractMethod.php index 6a94b4db..aa19feb8 100644 --- a/Model/Payment/AbstractMethod.php +++ b/Model/Payment/AbstractMethod.php @@ -412,9 +412,10 @@ protected function handleSubscriptionOrder(InfoInterface $payment, OrderItemInte if ($responseData) { $bill = $responseData['bill']; $subscription = $responseData['subscription']; + $billId = !$bill ? null : $bill['id']; if ($subscription) { - $this->saveSubscriptionToDatabase($subscription, $order); + $this->saveSubscriptionToDatabase($subscription, $order, $billId); } if ($bill) { @@ -441,10 +442,9 @@ protected function handleSubscriptionOrder(InfoInterface $payment, OrderItemInte /** * @param array $subscription * @param Order $order - * @return void - * @throws \Exception + * @param null $billId */ - protected function saveSubscriptionToDatabase(array $subscription, Order $order) + protected function saveSubscriptionToDatabase(array $subscription, Order $order, $billId = null) { $tableName = $this->resourceConnection->getTableName('vindi_subscription'); $startAt = new \DateTime($subscription['start_at']); @@ -461,6 +461,15 @@ protected function saveSubscriptionToDatabase(array $subscription, Order $order) 'start_at' => $startAt->format('Y-m-d H:i:s') ]; + if ($billId) { + $data['bill_id'] = $billId; + } + + if (isset($subscription['next_billing_at'])) { + $nextBillingAt = new \DateTime($subscription['next_billing_at']); + $data['next_billing_at'] = $nextBillingAt->format('Y-m-d H:i:s'); + } + try { $this->connection->insert($tableName, $data); } catch (\Exception $e) { @@ -568,7 +577,9 @@ protected function isValidPaymentMethodCode($paymentMethodCode) { $paymentMethodsCode = [ PaymentMethod::BANK_SLIP, - PaymentMethod::DEBIT_CARD + PaymentMethod::DEBIT_CARD, + PaymentMethod::PIX, + PaymentMethod::BANK_SLIP_PIX ]; return in_array($paymentMethodCode, $paymentMethodsCode); diff --git a/Plugin/AddCustomOptionToQuoteItem.php b/Plugin/AddCustomOptionToQuoteItem.php index c6e12ce4..535af833 100644 --- a/Plugin/AddCustomOptionToQuoteItem.php +++ b/Plugin/AddCustomOptionToQuoteItem.php @@ -47,11 +47,13 @@ public function beforeAddProduct( 'code' => 'plan_price' ]; - $additionalOptions[] = [ - 'label' => __('Installments'), - 'value' => $request->getData('selected_plan_installments'), - 'code' => 'plan_installments' - ]; + if ($request->getData('selected_plan_installments') > 0) { + $additionalOptions[] = [ + 'label' => __('Installments'), + 'value' => $request->getData('selected_plan_installments'), + 'code' => 'plan_installments' + ]; + } if (!empty($additionalOptions)) { $product->addCustomOption('additional_options', json_encode($additionalOptions)); diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 29b8e08e..2affb681 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -15,7 +15,9 @@ - + + + diff --git a/etc/email_templates.xml b/etc/email_templates.xml new file mode 100644 index 00000000..0b0726fb --- /dev/null +++ b/etc/email_templates.xml @@ -0,0 +1,4 @@ + + +