diff --git a/Api/Data/PaymentLinkInterface.php b/Api/Data/PaymentLinkInterface.php index 347d4abe..465c87ab 100644 --- a/Api/Data/PaymentLinkInterface.php +++ b/Api/Data/PaymentLinkInterface.php @@ -2,16 +2,6 @@ declare(strict_types=1); -/** - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade this extension to newer - * version in the future. - * - * @category Vindi - * @package Vindi_Payment - */ - namespace Vindi\Payment\Api\Data; use Magento\Framework\Api\ExtensibleDataInterface; @@ -25,6 +15,8 @@ interface PaymentLinkInterface extends ExtensibleDataInterface const CUSTOMER_ID = 'customer_id'; const CREATED_AT = 'created_at'; const STATUS = 'status'; + const EXPIRED_AT = 'expired_at'; + const SUCCESS_PAGE_ACCESSED = 'success_page_accessed'; /** * @return int @@ -32,7 +24,7 @@ interface PaymentLinkInterface extends ExtensibleDataInterface public function getEntityId(); /** - * @param int $id + * @param int $entityId */ public function setEntityId(int $entityId); @@ -95,4 +87,32 @@ public function getStatus(); * @param string $status */ public function setStatus(string $status); + + /** + * Get the expiration date of the payment link + * + * @return string|null + */ + public function getExpiredAt(); + + /** + * Set the expiration date of the payment link + * + * @param string|null $expiredAt + */ + public function setExpiredAt($expiredAt); + + /** + * Check if the success page has been accessed + * + * @return bool + */ + public function getSuccessPageAccessed(); + + /** + * Set the success page accessed flag + * + * @param bool $successPageAccessed + */ + public function setSuccessPageAccessed(bool $successPageAccessed); } diff --git a/Block/InfoTrait.php b/Block/InfoTrait.php index 76bce5f7..cd41aea0 100644 --- a/Block/InfoTrait.php +++ b/Block/InfoTrait.php @@ -69,7 +69,7 @@ public function getCcValue($totalQtyCard = 1, $cardPosition = 1) */ public function getCcBrand() { - $brands = $this->paymentMethod->getCreditCardTypes(); + $brands = $this->paymentMethod->getCreditCardCodes(); $CardCode = $this->getOrder()->getPayment()->getCcType(); return isset($brands[$CardCode]) ? $brands[$CardCode] : null; diff --git a/Block/Product/ProductRecurrence.php b/Block/Product/ProductRecurrence.php index 335cfb69..9d449fde 100644 --- a/Block/Product/ProductRecurrence.php +++ b/Block/Product/ProductRecurrence.php @@ -72,6 +72,23 @@ public function __construct( $this->_localeFormat = $localeFormat; } + /** + * Get cache key informative items + * + * @return array + */ + public function getCacheKeyInfo() + { + return [ + 'BLOCK_TPL', + $this->_storeManager->getStore()->getCode(), + $this->getTemplateFile(), + 'base_url' => $this->getBaseUrl(), + 'template' => $this->getTemplate(), + 'current_product' => $this->getCurrentProduct()->getId(), + ]; + } + /** * Returns the current product from the registry. * @@ -82,6 +99,14 @@ public function getCurrentProduct() return $this->_registry->registry('current_product'); } + public function getProductId(): int + { + try { + return $this->getCurrentProduct()->getId(); + } catch (\Exception $e) { + return 0; + } + } /** * Returns the name of a plan by its ID. * diff --git a/CHANGELOG.md b/CHANGELOG.md index abe8a12c..25d75748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [2.2.0 - 14/01/2025](https://github.com/vindi/vindi-magento2/releases/tag/2.2.0) - Novo layout do cartão de crédito na página de checkout +- Nova Configuração de Templates Personalizados para Notificação de Link de Pagamento +- Novo Recurso de cancelamento de Pedidos com Link de Pagamento Expirado Após 30 Dias ## [2.1.0 - 30/10/2024](https://github.com/vindi/vindi-magento2/releases/tag/2.1.0) diff --git a/Console/Command/RunCancelOrdersWithExpiredLinks.php b/Console/Command/RunCancelOrdersWithExpiredLinks.php new file mode 100644 index 00000000..a39031e8 --- /dev/null +++ b/Console/Command/RunCancelOrdersWithExpiredLinks.php @@ -0,0 +1,67 @@ +cancelOrdersWithExpiredLinks = $cancelOrdersWithExpiredLinks; + $this->logger = $logger; + parent::__construct(); + } + + /** + * Configure the command + */ + protected function configure() + { + $this->setName('vindi:payment:cancel-orders-with-expired-links'); + $this->setDescription('Manually run the cron to cancel orders with expired payment links'); + parent::configure(); + } + + /** + * Execute the command + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + try { + $this->cancelOrdersWithExpiredLinks->execute(); + $output->writeln('Orders with expired payment links have been canceled successfully.'); + return Command::SUCCESS; + } catch (\Exception $e) { + $this->logger->error('Error while canceling orders with expired payment links: ' . $e->getMessage()); + $output->writeln('An error occurred while canceling orders with expired payment links.'); + return Command::FAILURE; + } + } +} diff --git a/Controller/Checkout/Success.php b/Controller/Checkout/Success.php index eb9c9e03..67d5f775 100644 --- a/Controller/Checkout/Success.php +++ b/Controller/Checkout/Success.php @@ -12,7 +12,6 @@ * @category Vindi * @package Vindi_Payment * - * */ namespace Vindi\Payment\Controller\Checkout; @@ -24,6 +23,7 @@ use Magento\Framework\Message\ManagerInterface; use Vindi\Payment\Helper\Data; use Vindi\Payment\Model\PaymentLinkService; +use Magento\Sales\Model\OrderRepository; class Success implements HttpGetActionInterface { @@ -57,6 +57,11 @@ class Success implements HttpGetActionInterface */ private ManagerInterface $messageManager; + /** + * @var OrderRepository + */ + private OrderRepository $orderRepository; + /** * @param PageFactory $resultPageFactory * @param PaymentLinkService $paymentLinkService @@ -64,6 +69,7 @@ class Success implements HttpGetActionInterface * @param RedirectFactory $redirectFactory * @param Data $helperData * @param ManagerInterface $messageManager + * @param OrderRepository $orderRepository */ public function __construct( PageFactory $resultPageFactory, @@ -71,7 +77,8 @@ public function __construct( RequestInterface $request, RedirectFactory $redirectFactory, Data $helperData, - ManagerInterface $messageManager + ManagerInterface $messageManager, + OrderRepository $orderRepository ) { $this->resultPageFactory = $resultPageFactory; @@ -80,6 +87,7 @@ public function __construct( $this->redirectFactory = $redirectFactory; $this->helperData = $helperData; $this->messageManager = $messageManager; + $this->orderRepository = $orderRepository; } /** @@ -98,6 +106,20 @@ public function execute() return $this->redirectFactory->create()->setPath('/'); } + + $paymentLink = $this->paymentLinkService->getPaymentLinkByOrderId($orderId); + + if ($paymentLink && $paymentLink->getSuccessPageAccessed()) { + $this->messageManager->addWarningMessage( + __('The payment success page has already been accessed.') + ); + + return $this->redirectFactory->create()->setPath('/'); + } + + $paymentLink->setSuccessPageAccessed(true); + $this->paymentLinkService->savePaymentLink($paymentLink); + } catch (\Exception $e) { $this->messageManager->addErrorMessage( __('An error occurred while processing your request. Please try again later.') diff --git a/Controller/PaymentProfile/Save.php b/Controller/PaymentProfile/Save.php index 4cf3dc66..9c74dd00 100644 --- a/Controller/PaymentProfile/Save.php +++ b/Controller/PaymentProfile/Save.php @@ -10,6 +10,7 @@ use Magento\Framework\App\ResponseInterface; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\View\Result\PageFactory; +use Vindi\Payment\Model\Payment\PaymentMethod; use Vindi\Payment\Model\Payment\Profile as PaymentProfileManager; use Vindi\Payment\Model\PaymentProfileFactory; use Vindi\Payment\Model\PaymentProfileRepository; @@ -80,6 +81,11 @@ class Save extends Action */ protected $api; + /** + * @var PaymentMethod + */ + protected $paymentMethod; + /** * @param Context $context * @param PageFactory $resultPageFactory @@ -93,6 +99,7 @@ class Save extends Action * @param SubscriptionFactory $subscriptionFactory * @param SubscriptionResource $subscriptionResource * @param Api $api + * @param PaymentMethod $paymentMethod */ public function __construct( Context $context, @@ -106,7 +113,8 @@ public function __construct( VindiCustomer $vindiCustomer, SubscriptionFactory $subscriptionFactory, SubscriptionResource $subscriptionResource, - Api $api + Api $api, + PaymentMethod $paymentMethod ) { parent::__construct($context); $this->resultPageFactory = $resultPageFactory; @@ -120,6 +128,7 @@ public function __construct( $this->subscriptionFactory = $subscriptionFactory; $this->subscriptionResource = $subscriptionResource; $this->api = $api; + $this->paymentMethod = $paymentMethod; } /** @@ -210,7 +219,7 @@ public function execute() * @param int $customerId * @return array */ - private function formatPaymentProfileData($data, $customerId) + private function formatPaymentProfileData(array $data, $customerId): array { $cardNumber = preg_replace('/\D/', '', $data['cc_number']); @@ -219,17 +228,16 @@ private function formatPaymentProfileData($data, $customerId) $cardExpiration = $expirationParts[0] . '/' . $expirationYear; $paymentCompanyCode = $data['cc_type']; - $formattedData = [ + $ccTypeCode = $this->paymentMethod->getCreditCardApiCode($paymentCompanyCode); + return [ 'holder_name' => $data['cc_name'], 'card_expiration' => $cardExpiration, 'card_number' => $cardNumber, 'card_cvv' => $data['cc_cvv'], 'customer_id' => $customerId, - 'payment_company_code' => $paymentCompanyCode, + 'payment_company_code' => $ccTypeCode, 'payment_method_code' => 'credit_card', ]; - - return $formattedData; } /** diff --git a/Cron/CancelOrdersWithExpiredLinks.php b/Cron/CancelOrdersWithExpiredLinks.php new file mode 100644 index 00000000..821f1dc0 --- /dev/null +++ b/Cron/CancelOrdersWithExpiredLinks.php @@ -0,0 +1,91 @@ +paymentLinkFactory = $paymentLinkFactory; + $this->orderRepository = $orderRepository; + $this->orderManagement = $orderManagement; + $this->dateTime = $dateTime; + $this->logger = $logger; + } + + /** + * Execute the cron job + */ + public function execute(): void + { + try { + $currentDate = $this->dateTime->gmtDate(); + $paymentLinkCollection = $this->paymentLinkFactory->create()->getCollection() + ->addFieldToFilter('expired_at', ['notnull' => true]) + ->addFieldToFilter('expired_at', ['lt' => date('Y-m-d H:i:s', strtotime('-30 days', strtotime($currentDate)))]); + + foreach ($paymentLinkCollection as $paymentLink) { + $orderId = $paymentLink->getOrderId(); + $order = $this->orderRepository->get($orderId); + + if ($order && $order->canCancel()) { + $this->orderManagement->cancel($orderId); + $this->logger->info(sprintf('Order ID %s has been canceled due to expired payment link.', $orderId)); + + if ($paymentLink->getStatus() !== 'processed') { + $paymentLink->setStatus('processed'); + $paymentLink->save(); + $this->logger->info(sprintf('Payment link for order ID %s has been updated to "processed".', $orderId)); + } + } + } + } catch (\Exception $e) { + $this->logger->error('Error in canceling orders with expired links: ' . $e->getMessage()); + } + } +} diff --git a/Model/Config/Source/CardImages.php b/Model/Config/Source/CardImages.php index e134c863..233314c6 100644 --- a/Model/Config/Source/CardImages.php +++ b/Model/Config/Source/CardImages.php @@ -11,16 +11,39 @@ public function toOptionArray() return [ [ 'value' => 'mc.png', + 'code' => 'MC', 'label' => __('mastercard') ], [ 'value' => 'vi.png', + 'code' => 'VI', 'label' => __('visa') ], [ 'value' => 'ae.png', + 'code' => 'AE', 'label' => __('american_express') - ] + ], + [ + 'value' => 'elo.png', + 'code' => 'ELO', + 'label' => __('elo') + ], + [ + 'value' => 'hc.png', + 'code' => 'HC', + 'label' => __('hipercard') + ], + [ + 'value' => 'dn.png', + 'code' => 'DN', + 'label' => __('diners_club') + ], + [ + 'value' => 'jcb.png', + 'code' => 'JCB', + 'label' => __('jcb') + ], ]; } } diff --git a/Model/Config/Source/EmailTemplate.php b/Model/Config/Source/EmailTemplate.php index fe795a70..0437d4bd 100644 --- a/Model/Config/Source/EmailTemplate.php +++ b/Model/Config/Source/EmailTemplate.php @@ -1,47 +1,52 @@ emailTemplateConfig = $emailTemplateConfig; + public function __construct( + TemplateCollectionFactory $templateCollectionFactory + ) { + $this->templateCollectionFactory = $templateCollectionFactory; } /** + * Get available email templates including custom templates based on 'vindi_vr_payment_link_template' + * * @return array */ public function toOptionArray() { - return $this->emailTemplateConfig->getAvailableTemplates(); + $options = []; + + $collection = $this->templateCollectionFactory->create(); + $collection->load(); + + $options[] = [ + 'value' => 'vindi_vr_payment_link_template', + 'label' => __('Payment Link Notification (Default)'), + ]; + + foreach ($collection as $template) { + if ($template->getOrigTemplateCode() == 'vindi_vr_payment_link_template') { + $options[] = [ + 'value' => $template->getTemplateId(), + 'label' => $template->getTemplateCode(), + ]; + } + } + + return $options; } } diff --git a/Model/ConfigProvider.php b/Model/ConfigProvider.php index 421be890..2fe2b368 100644 --- a/Model/ConfigProvider.php +++ b/Model/ConfigProvider.php @@ -10,7 +10,9 @@ use Magento\Customer\Model\Session as CustomerSession; use Magento\Directory\Model\Currency; use Magento\Framework\View\Asset\Source; +use Magento\Payment\Helper\Data as PaymentHelper; use Magento\Payment\Model\CcConfig; +use Magento\Payment\Model\CcGenericConfigProvider; use Vindi\Payment\Helper\Data; use Vindi\Payment\Model\Config\Source\CardImages as CardImagesSource; use Vindi\Payment\Model\Payment\PaymentMethod; @@ -20,55 +22,65 @@ * Class ConfigProvider * @package Vindi\Payment\Model */ -class ConfigProvider implements ConfigProviderInterface +class ConfigProvider extends CcGenericConfigProvider implements ConfigProviderInterface { - private $helperData; + public const CODE = 'vindi'; + + /** + * @var string + */ + protected $_methodCode = 'vindi'; + + protected $icons = []; + + protected $helperData; /** * @var CcConfig */ - private $ccConfig; + protected $ccConfig; /** * @var Source */ - private $assetSource; + protected $assetSource; /** * @var CheckoutSession */ - private $checkoutSession; + protected $checkoutSession; /** * @var Currency */ - private $currency; + protected $currency; /** * @var PaymentMethod */ - private $paymentMethod; + protected $paymentMethod; /** * @var ProductRepositoryInterface */ - private $productRepository; + protected $productRepository; /** * @var CustomerSession */ - private $customerSession; + protected $customerSession; /** * @var PaymentProfileCollection */ - private $paymentProfileCollection; + protected $paymentProfileCollection; /** * @var CardImagesSource */ - private $creditCardTypeSource; + protected $creditCardTypeSource; public function __construct( CcConfig $ccConfig, + PaymentHelper $paymentHelper, Source $assetSource, Data $data, CheckoutSession $checkoutSession, @@ -79,7 +91,7 @@ public function __construct( PaymentProfileCollection $paymentProfileCollection, CardImagesSource $creditCardTypeSource ) { - + parent::__construct($ccConfig, $paymentHelper, [self::CODE]); $this->ccConfig = $ccConfig; $this->assetSource = $assetSource; $this->helperData = $data; @@ -92,11 +104,6 @@ public function __construct( $this->creditCardTypeSource = $creditCardTypeSource; } - /** - * @var string - */ - protected $_methodCode = 'vindi_cc'; - /** * {@inheritdoc} */ @@ -104,8 +111,8 @@ public function getConfig() { return [ 'payment' => [ - 'vindi_cc' => [ - 'availableTypes' => [$this->_methodCode => $this->paymentMethod->getCreditCardTypes()], + 'vindi' => [ + 'availableTypes' => $this->paymentMethod->getCreditCardCodes(), 'months' => [$this->_methodCode => $this->ccConfig->getCcMonths()], 'years' => [$this->_methodCode => $this->ccConfig->getCcYears()], 'hasVerification' => [$this->_methodCode => $this->ccConfig->hasVerification()], @@ -115,7 +122,8 @@ public function getConfig() 'hasPlanInCart' => (int) $this->hasPlanInCart(), 'planIntervalCountMaxInstallments' => (int) $this->planIntervalCountMaxInstallments(), 'saved_cards' => $this->getPaymentProfiles(), - 'credit_card_images' => $this->getCreditCardImages() + 'credit_card_images' => $this->getCreditCardImages(), + 'icons' => $this->getIcons(), ] ] ]; @@ -218,6 +226,7 @@ public function getCreditCardImages(): array foreach ($creditCardOptionArray as $creditCardOption) { $ccImages[] = [ + 'code' => $creditCardOption['code'], 'label' => $creditCardOption['label'], 'value' => $creditCardOption['value'] ]; @@ -225,4 +234,39 @@ public function getCreditCardImages(): array return $ccImages; } + + + /** + * Get icons for available payment methods + * + * @return array + */ + public function getIcons() + { + if (!empty($this->icons)) { + return $this->icons; + } + + $types = $this->getCreditCardImages(); + foreach ($types as $type) { + $code = $type['code']; + $label = $type['label']; + + if (!array_key_exists($code, $this->icons)) { + $asset = $this->ccConfig->createAsset('Vindi_Payment::images/cc/' . strtolower($code) . '.png'); + $placeholder = $this->assetSource->findSource($asset); + if ($placeholder) { + list($width, $height) = getimagesize($asset->getSourceFile()); + $this->icons[$code] = [ + 'url' => $asset->getUrl(), + 'width' => $width, + 'height' => $height, + 'title' => $label, + ]; + } + } + } + + return $this->icons; + } } diff --git a/Model/Payment/PaymentMethod.php b/Model/Payment/PaymentMethod.php index 7e3142d4..d43ed4ba 100644 --- a/Model/Payment/PaymentMethod.php +++ b/Model/Payment/PaymentMethod.php @@ -2,27 +2,35 @@ namespace Vindi\Payment\Model\Payment; - use Exception; use Vindi\Payment\Helper\Api; class PaymentMethod { + public const BANK_SLIP = 'bank_slip'; - const BANK_SLIP = 'bank_slip'; - - const BANK_SLIP_PIX = 'pix_bank_slip'; + public const BANK_SLIP_PIX = 'pix_bank_slip'; - const PIX = 'pix'; - const CREDIT_CARD = 'credit_card'; - const DEBIT_CARD = 'debit_card'; + public const PIX = 'pix'; + public const CREDIT_CARD = 'credit_card'; + public const DEBIT_CARD = 'debit_card'; /** * @var \Vindi\Payment\Helper\Api */ - private $api; + protected $api; + + protected $methods = []; - private $acceptBankSlip; + protected $methodsCodes = [ + 'mastercard' => 'MC', + 'visa' => 'VI', + 'american_express' => 'AE', + 'elo' => 'ELO', + 'hipercard' => 'HC', + 'diners_club' => 'DN', + 'jcb' => 'JCB', + ]; /** * @param Api $api @@ -35,14 +43,16 @@ public function __construct(Api $api) /** * @return array */ - public function getCreditCardTypes() + public function getCreditCardCodes(): array { $methods = $this->get(); $types = []; - if ($methods) { + if (!empty($methods)) { foreach ($methods['credit_card'] as $type) { - $types[$type['code']] = $type['name']; + if (isset($this->methodsCodes[$type['code']])) { + $types[$this->methodsCodes[$type['code']]] = $type['name']; + } } } @@ -50,47 +60,78 @@ public function getCreditCardTypes() } /** - * Make an API request to retrieve Payment Methods. - * - * @return array|bool + * @return string */ - public function get() + public function getCreditCardApiCode(string $ccType): string { - $paymentMethods = [ - 'credit_card' => [], - 'debit_card' => [], - 'bank_slip' => false, - ]; - - $response = $this->api->request('payment_methods', 'GET'); - - if (false === $response) { - return $this->acceptBankSlip = false; + $methods = $this->get(); + if ($methods) { + foreach ($methods['credit_card'] as $type) { + if (isset($this->methodsCodes[$type['code']]) && $ccType == $this->methodsCodes[$type['code']]) { + return $type['code']; + } + } } - foreach ($response['payment_methods'] as $method) { - if ('active' !== $method['status']) { - continue; - } + return $ccType; + } - if ('PaymentMethod::CreditCard' === $method['type']) { - $paymentMethods['credit_card'] = array_merge( - $paymentMethods['credit_card'], - $method['payment_companies'] - ); - } elseif ('PaymentMethod::DebitCard' === $method['type']) { - $paymentMethods['debit_card'] = array_merge( - $paymentMethods['debit_card'], - $method['payment_companies'] - ); - } elseif ('PaymentMethod::BankSlip' === $method['type']) { - $paymentMethods['bank_slip'] = true; + /** + * @return array + */ + public function getCreditCardTypes(): array + { + $methods = $this->get(); + $types = []; + + if ($methods) { + foreach ($methods['credit_card'] as $type) { + $types[$type['code']] = $type['name']; } } - $this->acceptBankSlip = $paymentMethods['bank_slip']; + return $types; + } - return $paymentMethods; + /** + * Make an API request to retrieve Payment Methods. + * + * @return array + */ + public function get(): array + { + if (empty($this->methods)) { + $this->methods = [ + 'credit_card' => [], + 'debit_card' => [], + 'bank_slip' => false, + ]; + + $response = $this->api->request('payment_methods', 'GET'); + + if ($response && isset($response['payment_methods'])) { + foreach ($response['payment_methods'] as $method) { + if ('active' !== $method['status']) { + continue; + } + + if ('PaymentMethod::CreditCard' === $method['type']) { + $this->methods['credit_card'] = array_merge( + $this->methods['credit_card'], + $method['payment_companies'] + ); + } elseif ('PaymentMethod::DebitCard' === $method['type']) { + $paymentMethods['debit_card'] = array_merge( + $this->methods['debit_card'], + $method['payment_companies'] + ); + } elseif ('PaymentMethod::BankSlip' === $method['type']) { + $this->methods['bank_slip'] = true; + } + } + } + } + return $this->methods; } /** @@ -101,7 +142,7 @@ public function get() */ public function isCcTypeValid($ccType) { - $validCreditCardTypes = $this->getCreditCardTypes(); + $validCreditCardTypes = $this->getCreditCardCodes(); $fullName = $this->getCcTypeFullName($ccType); $fullTrimmedName = strtolower(str_replace(' ', '', $fullName)); @@ -124,7 +165,7 @@ public function isCcTypeValid($ccType) */ private function getCcTypeFullName($ccType) { - $fullNames = $this->getCreditCardTypes(); + $fullNames = $this->getCreditCardCodes(); if (isset($fullNames[$ccType])) { return $fullNames[$ccType]; diff --git a/Model/Payment/Profile.php b/Model/Payment/Profile.php index 00f463d3..d413e09f 100644 --- a/Model/Payment/Profile.php +++ b/Model/Payment/Profile.php @@ -7,12 +7,16 @@ class Profile { private $api; + private $helperData; private $paymentMethod; - public function __construct(\Vindi\Payment\Helper\Api $api, Data $helperData, PaymentMethod $paymentMethod) - { + public function __construct( + \Vindi\Payment\Helper\Api $api, + Data $helperData, + PaymentMethod $paymentMethod + ) { $this->api = $api; $this->helperData = $helperData; $this->paymentMethod = $paymentMethod; @@ -20,6 +24,7 @@ public function __construct(\Vindi\Payment\Helper\Api $api, Data $helperData, Pa public function create($payment, $customerId, $paymentMethodCode) { + $ccTypeCode = $this->paymentMethod->getCreditCardApiCode($payment->getCcType()); $creditCardData = [ 'holder_name' => $payment->getCcOwner(), 'card_expiration' => str_pad($payment->getCcExpMonth(), 2, '0', STR_PAD_LEFT) @@ -27,7 +32,7 @@ public function create($payment, $customerId, $paymentMethodCode) 'card_number' => $payment->getCcNumber(), 'card_cvv' => $payment->getCcCid() ?: '', 'customer_id' => $customerId, - 'payment_company_code' => $payment->getCcType(), + 'payment_company_code' => $ccTypeCode, 'payment_method_code' => $paymentMethodCode ]; diff --git a/Model/PaymentLink.php b/Model/PaymentLink.php index 3a3c60f4..c396881c 100644 --- a/Model/PaymentLink.php +++ b/Model/PaymentLink.php @@ -2,16 +2,6 @@ declare(strict_types=1); -/** - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade this extension to newer - * version in the future. - * - * @category Vindi - * @package Vindi_Payment - */ - namespace Vindi\Payment\Model; use Vindi\Payment\Api\Data\PaymentLinkInterface; @@ -157,4 +147,39 @@ public function setStatus(string $status) $this->setData(self::STATUS, $status); return $this; } + + /** + * @inheritdoc + */ + public function getExpiredAt() + { + return $this->getData(self::EXPIRED_AT); + } + + /** + * @inheritdoc + */ + public function setExpiredAt($expiredAt) + { + $this->setData(self::EXPIRED_AT, $expiredAt); + return $this; + } + + /** + * @inheritdoc + */ + public function getSuccessPageAccessed() + { + return $this->getData(self::SUCCESS_PAGE_ACCESSED); + } + + /** + * @inheritdoc + */ + public function setSuccessPageAccessed(bool $successPageAccessed) + { + $this->setData(self::SUCCESS_PAGE_ACCESSED, $successPageAccessed); + return $this; + } } + diff --git a/Model/PaymentLinkService.php b/Model/PaymentLinkService.php index 0b379dae..9f8ab797 100644 --- a/Model/PaymentLinkService.php +++ b/Model/PaymentLinkService.php @@ -33,6 +33,11 @@ class PaymentLinkService */ public const SALES_NAME = 'trans_email/ident_sales/name'; + /** + * Payment link template path + */ + public const PAYMENT_LINK_TEMPLATE_PATH = 'vindiconfiguration/general/vindi_vr_payment_link_template'; + /** * @var PaymentLinkCollectionFactory */ @@ -189,7 +194,8 @@ public function sendPaymentLinkEmail($orderId): bool 'name' => $this->scopeConfig->getValue(self::SALES_NAME, ScopeInterface::SCOPE_STORE) ]; - $emailTemplateId = 'payment_link_template'; + $emailTemplateId = $this->scopeConfig->getValue(self::PAYMENT_LINK_TEMPLATE_PATH, ScopeInterface::SCOPE_STORE) + ?: 'vindi_vr_payment_link_template'; $this->sendEmailService->sendEmailTemplate($emailTemplateId, $order->getCustomerEmail(), $order->getCustomerFirstname(), $from, $templateVars); return true; @@ -292,6 +298,8 @@ public function updatePaymentLinkStatusToExpired(PaymentLink $paymentLink): void { try { $paymentLink->setStatus('expired'); + $paymentLink->setExpiredAt($this->dateTimeFactory->create()->format('Y-m-d H:i:s')); + $this->linkRepository->save($paymentLink); } catch (\Exception $e) { $this->logger->error('Error updating payment link status to expired: ' . $e->getMessage()); @@ -321,6 +329,8 @@ public function updateExpiredPaymentLinks(): void foreach ($paymentLinks as $paymentLink) { if ($this->isLinkExpired($paymentLink->getCreatedAt())) { $paymentLink->setStatus('expired'); + $paymentLink->setExpiredAt($this->dateTimeFactory->create()->format('Y-m-d H:i:s')); + $this->savePaymentLink($paymentLink); } } diff --git a/Observer/InvoicePaymentLinkObserver.php b/Observer/InvoicePaymentLinkObserver.php new file mode 100644 index 00000000..5af5029a --- /dev/null +++ b/Observer/InvoicePaymentLinkObserver.php @@ -0,0 +1,78 @@ +paymentLinkService = $paymentLinkService; + $this->orderRepository = $orderRepository; + $this->logger = $logger; + } + + /** + * Execute observer to update payment link status after invoice creation + * + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer): void + { + try { + $invoice = $observer->getEvent()->getInvoice(); + $order = $invoice->getOrder(); + + if ($order->hasInvoices()) { + $this->logger->info(sprintf('Order ID %s has already been invoiced.', $order->getEntityId())); + + $orderId = $order->getEntityId(); + $paymentLink = $this->paymentLinkService->getPaymentLink($orderId); + + if ($paymentLink && $paymentLink->getId()) { + if ($paymentLink->getStatus() !== 'processed') { + $paymentLink->setStatus('processed'); + $this->paymentLinkService->savePaymentLink($paymentLink); + + $this->logger->info(sprintf('Payment link for order ID %s has been updated to "processed" after invoice generation.', $orderId)); + } + } + } else { + $this->logger->info(sprintf('Order ID %s has not been invoiced yet.', $order->getEntityId())); + } + } catch (\Exception $e) { + $this->logger->error('Error while updating payment link after invoice generation: ' . $e->getMessage()); + } + } +} diff --git a/Setup/InstallData.php b/Setup/InstallData.php deleted file mode 100644 index 6090ce21..00000000 --- a/Setup/InstallData.php +++ /dev/null @@ -1,52 +0,0 @@ - 'default', - 'scope_id' => 0, - 'path' => 'vindiconfiguration/general/webhook_key', - 'value' => self::generateRandomHash(), - ]; - $setup->getConnection() - ->insertOnDuplicate($setup->getTable('core_config_data'), $data, ['value']); - } - - /** - * Generate a random hash string - * - * @return string - * @throws \Exception - */ - public static function generateRandomHash() - { - $length = 15; - $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $encoding = '8bit'; - - if (false === ($max = mb_strlen($characters, $encoding))) { - throw new \BadMethodCallException('Invalid encoding passed'); - } - - $string = ''; - $max--; - for ($i = 0; $i < $length; ++$i) { - $string .= $characters[random_int(0, $max)]; - } - - return $string; - } -} diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index e47a039b..de16d75e 100644 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -42,4 +42,9 @@ + + + Magento\Email\Model\ResourceModel\Template\CollectionFactory + + diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml index 124804d4..893f2bd0 100644 --- a/etc/adminhtml/events.xml +++ b/etc/adminhtml/events.xml @@ -22,4 +22,7 @@ + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 7e49a80b..1efe33df 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -55,6 +55,12 @@ Message that will be presented to the customer on the success screen, after completing the order. checkout/vindi_pix/info_message_onepage_success + + + + Vindi\Payment\Model\Config\Source\EmailTemplate + vindiconfiguration/general/vindi_vr_payment_link_template + diff --git a/etc/config.xml b/etc/config.xml index a18bfb80..c0b7170a 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -78,7 +78,7 @@ 2 values pending - payment_link_template + vindi_vr_payment_link_template diff --git a/etc/crontab.xml b/etc/crontab.xml index ccc43669..ed191a55 100644 --- a/etc/crontab.xml +++ b/etc/crontab.xml @@ -17,5 +17,8 @@ 30 3 * * * + + 0 3 * * * + diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 8794c41e..9504d23b 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -158,6 +158,8 @@ + + diff --git a/etc/di.xml b/etc/di.xml index c91d39f6..6671ed41 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -19,6 +19,7 @@ Vindi\Payment\Console\Command\ProcessOrderCreationQueueCommand Vindi\Payment\Console\Command\ProcessOrderPaidQueueCommand Vindi\Payment\Console\Command\RunUpdateExpiredLinks + Vindi\Payment\Console\Command\RunCancelOrdersWithExpiredLinks diff --git a/etc/email_templates.xml b/etc/email_templates.xml index 2255c700..ca8925d5 100644 --- a/etc/email_templates.xml +++ b/etc/email_templates.xml @@ -1,5 +1,5 @@