From 3f41f890af61ca16fa9001e2a6b438af70517c24 Mon Sep 17 00:00:00 2001 From: Elisei Date: Wed, 4 Dec 2024 10:25:28 -0300 Subject: [PATCH] =?UTF-8?q?PagBank=20=F0=9F=98=8D=20Magento=20-=20Adiciona?= =?UTF-8?q?=20salvamento=20de=20cart=C3=A3o=20utilizando=20a=20API=20de=20?= =?UTF-8?q?Zero=20Dollar.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Data/PagBankVaultTokenInterface.php | 153 ++++++++ Api/PagBankVaultManagementInterface.php | 33 ++ Block/Customer/Vault/Cards/CreateForm.php | 143 +++++++ Controller/Customer/SaveCard.php | 68 ++++ Model/Api/Data/PagBankVaultToken.php | 150 ++++++++ Model/Api/PagBankVaultManagement.php | 364 ++++++++++++++++++ README.md | 1 + etc/acl.xml | 1 + etc/di.xml | 4 + etc/webapi.xml | 9 + i18n/en_US.csv | 22 +- i18n/pt_BR.csv | 22 +- .../layout/vault_cards_listaction.xml | 3 + .../vault/cards/button-add-cards.phtml | 39 ++ .../customer/vault/cards/create-form.phtml | 86 +++++ view/frontend/web/css/source/_module.less | 20 + .../js/view/customer/vault/cards/add-card.js | 33 ++ .../js/view/customer/vault/cards/save-card.js | 190 +++++++++ view/frontend/web/js/view/payment/cc-form.js | 2 +- 19 files changed, 1338 insertions(+), 5 deletions(-) create mode 100644 Api/Data/PagBankVaultTokenInterface.php create mode 100644 Api/PagBankVaultManagementInterface.php create mode 100644 Block/Customer/Vault/Cards/CreateForm.php create mode 100644 Controller/Customer/SaveCard.php create mode 100644 Model/Api/Data/PagBankVaultToken.php create mode 100644 Model/Api/PagBankVaultManagement.php create mode 100644 view/frontend/templates/customer/vault/cards/button-add-cards.phtml create mode 100644 view/frontend/templates/customer/vault/cards/create-form.phtml create mode 100644 view/frontend/web/js/view/customer/vault/cards/add-card.js create mode 100644 view/frontend/web/js/view/customer/vault/cards/save-card.js diff --git a/Api/Data/PagBankVaultTokenInterface.php b/Api/Data/PagBankVaultTokenInterface.php new file mode 100644 index 0000000..597bd5d --- /dev/null +++ b/Api/Data/PagBankVaultTokenInterface.php @@ -0,0 +1,153 @@ + + * @license See LICENSE for license details. + */ + +declare(strict_types=1); + +namespace PagBank\PaymentMagento\Api\Data; + +/** + * Interface PagBank Vault Token. + * + * @api + */ +interface PagBankVaultTokenInterface +{ + /** + * Constants for keys of data array. + */ + public const PAGBANK_TOKEN = 'pagbank_token'; + public const PUBLIC_HASH = 'public_hash'; + public const CARD_BRAND = 'card_brand'; + public const LAST_DIGITS = 'last_digits'; + public const EXPIRATION_DATE = 'expiration_date'; + public const CREATED_AT = 'created_at'; + public const IS_ACTIVE = 'is_active'; + public const WEBSITE_ID = 'website_id'; + + /** + * Get PagBank Token. + * + * @return string|null + */ + public function getPagBankToken(); + + /** + * Set PagBank Token. + * + * @param string $token + * @return $this + */ + public function setPagBankToken($token); + + /** + * Get Website Id. + * + * @return int|null + */ + public function getWebsiteId(); + + /** + * Set Website Id. + * + * @param int $websiteId + * @return $this + */ + public function setWebsiteId($websiteId); + + /** + * Get public hash. + * + * @return string|null + */ + public function getPublicHash(); + + /** + * Set public hash. + * + * @param string $hash + * @return $this + */ + public function setPublicHash($hash); + + /** + * Get card brand. + * + * @return string|null + */ + public function getCardBrand(); + + /** + * Set card brand. + * + * @param string $brand + * @return $this + */ + public function setCardBrand($brand); + + /** + * Get last digits. + * + * @return string|null + */ + public function getLastDigits(); + + /** + * Set last digits. + * + * @param string $lastDigits + * @return $this + */ + public function setLastDigits($lastDigits); + + /** + * Get expiration date. + * + * @return string|null + */ + public function getExpirationDate(); + + /** + * Set expiration date. + * + * @param string $expirationDate + * @return $this + */ + public function setExpirationDate($expirationDate); + + /** + * Get created at. + * + * @return string|null + */ + public function getCreatedAt(); + + /** + * Set created at. + * + * @param string $createdAt + * @return $this + */ + public function setCreatedAt($createdAt); + + /** + * Get is active. + * + * @return bool|null + */ + public function getIsActive(); + + /** + * Set is active. + * + * @param bool $isActive + * @return $this + */ + public function setIsActive($isActive); +} diff --git a/Api/PagBankVaultManagementInterface.php b/Api/PagBankVaultManagementInterface.php new file mode 100644 index 0000000..a922e28 --- /dev/null +++ b/Api/PagBankVaultManagementInterface.php @@ -0,0 +1,33 @@ + + * @license See LICENSE for license details. + */ + +declare(strict_types=1); + +namespace PagBank\PaymentMagento\Api; + +/** + * Interface for creating token vault from zero dollar transaction. + * + * @api + */ +interface PagBankVaultManagementInterface +{ + /** + * Criar token vault a partir de cartão criptografado. + * + * @param int $customerId + * @param string $encryptedCard + * @return \PagBank\PaymentMagento\Api\Data\PagBankVaultTokenInterface + */ + public function createVaultToken( + int $customerId, + string $encryptedCard + ); +} \ No newline at end of file diff --git a/Block/Customer/Vault/Cards/CreateForm.php b/Block/Customer/Vault/Cards/CreateForm.php new file mode 100644 index 0000000..b08276a --- /dev/null +++ b/Block/Customer/Vault/Cards/CreateForm.php @@ -0,0 +1,143 @@ + + * @license See LICENSE for license details. + */ + +namespace PagBank\PaymentMagento\Block\Customer\Vault\Cards; + +use Magento\Framework\View\Element\Template; +use Magento\Payment\Model\CcConfig; +use PagBank\PaymentMagento\Gateway\Config\Config; +use PagBank\PaymentMagento\Gateway\Config\ConfigCc; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\View\Asset\Source; + +/** + * Class CreateForm - Block to handle credit card form. + */ +class CreateForm extends Template +{ + /** + * @var Config + */ + private $config; + + /** + * @var ConfigCc + */ + private $configCc; + + /** + * @var CcConfig + */ + private $ccConfig; + + /** + * @var Json + */ + private $serializer; + + /** + * @var Source + */ + private $assetSource; + + /** + * @param Template\Context $context + * @param Config $config + * @param ConfigCc $configCc + * @param CcConfig $ccConfig + * @param Json $serializer + * @param Source $assetSource + * @param array $data + */ + public function __construct( + Template\Context $context, + Config $config, + ConfigCc $configCc, + CcConfig $ccConfig, + Json $serializer, + Source $assetSource, + array $data = [] + ) { + parent::__construct($context, $data); + $this->config = $config; + $this->configCc = $configCc; + $this->ccConfig = $ccConfig; + $this->serializer = $serializer; + $this->assetSource = $assetSource; + } + + /** + * Get credit card expiration months + * + * @return array + */ + public function getCcMonths() + { + return $this->ccConfig->getCcMonths(); + } + + /** + * Get credit card expiration years + * + * @return array + */ + public function getCcYears() + { + return $this->ccConfig->getCcYears(); + } + + /** + * Get CC Available Types + * + * @return array + */ + public function getCcAvailableTypes() + { + $storeId = $this->_storeManager->getStore()->getId(); + $types = $this->configCc->getCcAvailableTypes($storeId); + $ccTypesMapper = $this->configCc->getCcTypesMapper($storeId); + $availableTypes = array_filter(explode(',', $types)); + + $result = []; + foreach ($availableTypes as $type) { + $code = trim($type); + if (isset($ccTypesMapper[$code])) { + $result[$code] = $ccTypesMapper[$code]; + } + } + + return $result; + } + + /** + * Get Public Key + * + * @return string + */ + public function getPublicKey() + { + return $this->config->getMerchantGatewayPublicKey($this->_storeManager->getStore()->getId()); + } + + /** + * Get JS Form Config + * + * @return string + */ + public function getJsFormConfig() + { + $storeId = $this->_storeManager->getStore()->getId(); + $config = [ + 'publicKey' => $this->getPublicKey(), + 'ccTypesMapper' => $this->configCc->getCcTypesMapper($storeId) + ]; + return $this->serializer->serialize($config); + } +} \ No newline at end of file diff --git a/Controller/Customer/SaveCard.php b/Controller/Customer/SaveCard.php new file mode 100644 index 0000000..6d7ed16 --- /dev/null +++ b/Controller/Customer/SaveCard.php @@ -0,0 +1,68 @@ +vaultManagement = $vaultManagement; + $this->jsonFactory = $jsonFactory; + $this->customerSession = $customerSession; + } + + /** + * @inheritDoc + */ + public function execute() + { + $encryptedCard = $this->getRequest()->getParam('encrypted_card'); + $customerId = $this->customerSession->getCustomerId(); + + try { + $vaultToken = $this->vaultManagement->createVaultToken($customerId, $encryptedCard); + return $this->jsonFactory->create()->setData([ + 'success' => true, + 'message' => __('Card saved successfully.'), + 'token' => $vaultToken->getPagBankToken() + ]); + } catch (LocalizedException $e) { + return $this->jsonFactory->create()->setData([ + 'success' => false, + 'message' => $e->getMessage() + ]); + } + } +} diff --git a/Model/Api/Data/PagBankVaultToken.php b/Model/Api/Data/PagBankVaultToken.php new file mode 100644 index 0000000..38f0d9a --- /dev/null +++ b/Model/Api/Data/PagBankVaultToken.php @@ -0,0 +1,150 @@ + + * @license See LICENSE for license details. + */ + +declare(strict_types=1); + +namespace PagBank\PaymentMagento\Model\Api\Data; + +use Magento\Framework\Api\AbstractSimpleObject; +use PagBank\PaymentMagento\Api\Data\PagBankVaultTokenInterface; + +/** + * Class PagBank Vault Token - Model data. + */ +class PagBankVaultToken extends AbstractSimpleObject implements PagBankVaultTokenInterface +{ + /** + * @inheritdoc + */ + public function getPagBankToken() + { + return $this->_get(self::PAGBANK_TOKEN); + } + + /** + * @inheritdoc + */ + public function setPagBankToken($token) + { + return $this->setData(self::PAGBANK_TOKEN, $token); + } + + /** + * @inheritdoc + */ + public function getWebsiteId() + { + return $this->_get(self::WEBSITE_ID); + } + + /** + * @inheritdoc + */ + public function setWebsiteId($websiteId) + { + return $this->setData(self::WEBSITE_ID, $websiteId); + } + + /** + * @inheritdoc + */ + public function getPublicHash() + { + return $this->_get(self::PUBLIC_HASH); + } + + /** + * @inheritdoc + */ + public function setPublicHash($hash) + { + return $this->setData(self::PUBLIC_HASH, $hash); + } + + /** + * @inheritdoc + */ + public function getCardBrand() + { + return $this->_get(self::CARD_BRAND); + } + + /** + * @inheritdoc + */ + public function setCardBrand($brand) + { + return $this->setData(self::CARD_BRAND, $brand); + } + + /** + * @inheritdoc + */ + public function getLastDigits() + { + return $this->_get(self::LAST_DIGITS); + } + + /** + * @inheritdoc + */ + public function setLastDigits($lastDigits) + { + return $this->setData(self::LAST_DIGITS, $lastDigits); + } + + /** + * @inheritdoc + */ + public function getExpirationDate() + { + return $this->_get(self::EXPIRATION_DATE); + } + + /** + * @inheritdoc + */ + public function setExpirationDate($expirationDate) + { + return $this->setData(self::EXPIRATION_DATE, $expirationDate); + } + + /** + * @inheritdoc + */ + public function getCreatedAt() + { + return $this->_get(self::CREATED_AT); + } + + /** + * @inheritdoc + */ + public function setCreatedAt($createdAt) + { + return $this->setData(self::CREATED_AT, $createdAt); + } + + /** + * @inheritdoc + */ + public function getIsActive() + { + return $this->_get(self::IS_ACTIVE); + } + + /** + * @inheritdoc + */ + public function setIsActive($isActive) + { + return $this->setData(self::IS_ACTIVE, $isActive); + } +} diff --git a/Model/Api/PagBankVaultManagement.php b/Model/Api/PagBankVaultManagement.php new file mode 100644 index 0000000..342c20b --- /dev/null +++ b/Model/Api/PagBankVaultManagement.php @@ -0,0 +1,364 @@ + + * @license See LICENSE for license details. + */ + +declare(strict_types=1); + +namespace PagBank\PaymentMagento\Model\Api; + +use Magento\Framework\HTTP\ZendClient; +use Magento\Framework\HTTP\ZendClientFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\HTTP\LaminasClient; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; +use Magento\Vault\Api\PaymentTokenManagementInterface; +use Magento\Vault\Api\PaymentTokenRepositoryInterface; +use PagBank\PaymentMagento\Api\PagBankVaultManagementInterface; +use PagBank\PaymentMagento\Api\Data\PagBankVaultTokenInterface; +use PagBank\PaymentMagento\Api\Data\PagBankVaultTokenInterfaceFactory; +use PagBank\PaymentMagento\Gateway\Config\Config as ConfigBase; +use PagBank\PaymentMagento\Gateway\Config\ConfigCc; + +/** + * Class PagBank Vault Management - Create token vault from zero dollar transaction. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class PagBankVaultManagement implements PagBankVaultManagementInterface +{ + /** + * @var PaymentTokenManagementInterface + */ + protected $payTokenManagement; + + /** + * @var PaymentTokenRepositoryInterface + */ + protected $payTokenRepository; + + /** + * @var PaymentTokenFactoryInterface + */ + protected $paymentTokenFactory; + + /** + * @var PagBankVaultTokenInterfaceFactory + */ + protected $vaultTokenFactory; + + /** + * @var ConfigCc + */ + protected $configCc; + + /** + * @var ConfigBase + */ + protected $configBase; + + /** + * @var Json + */ + protected $serializer; + + /** + * @var StoreManagerInterface + */ + protected $storeManager; + + /** + * @var ZendClientFactory + */ + protected $httpClientFactory; + + /** + * @var EncryptorInterface + */ + private $encryptor; + + /** + * Constructor + * + * @param PaymentTokenManagementInterface $payTokenManagement + * @param PaymentTokenRepositoryInterface $payTokenRepository + * @param PaymentTokenFactoryInterface $paymentTokenFactory + * @param PagBankVaultTokenInterfaceFactory $vaultTokenFactory + * @param ConfigBase $configBase + * @param ConfigCc $configCc + * @param Json $serializer + * @param StoreManagerInterface $storeManager + * @param ZendClientFactory $httpClientFactory + * @param EncryptorInterface $encryptor + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + PaymentTokenManagementInterface $payTokenManagement, + PaymentTokenRepositoryInterface $payTokenRepository, + PaymentTokenFactoryInterface $paymentTokenFactory, + PagBankVaultTokenInterfaceFactory $vaultTokenFactory, + ConfigBase $configBase, + ConfigCc $configCc, + Json $serializer, + StoreManagerInterface $storeManager, + ZendClientFactory $httpClientFactory, + EncryptorInterface $encryptor + ) { + $this->payTokenManagement = $payTokenManagement; + $this->payTokenRepository = $payTokenRepository; + $this->paymentTokenFactory = $paymentTokenFactory; + $this->vaultTokenFactory = $vaultTokenFactory; + $this->configBase = $configBase; + $this->configCc = $configCc; + $this->serializer = $serializer; + $this->storeManager = $storeManager; + $this->httpClientFactory = $httpClientFactory; + $this->encryptor = $encryptor; + } + + /** + * Create vault token for customer's credit card. + * + * @param int $customerId + * @param string $encryptedCard + * @return PagBankVaultTokenInterface + * @throws LocalizedException + */ + public function createVaultToken(int $customerId, string $encryptedCard) + { + try { + $storeId = (int) $this->storeManager->getStore()->getId(); + $websiteId = (int) $this->storeManager->getStore()->getWebsiteId(); + + $cardData = $this->createPagBankToken($storeId, $encryptedCard); + if (!$cardData) { + throw new LocalizedException(__('Invalid card data')); + } + + $ccType = $this->getCreditCardType($cardData['cc_type']); + $publicHash = $this->generatePublicHash($cardData); + + $existingToken = $this->payTokenManagement->getByPublicHash($publicHash, $customerId); + if ($existingToken !== null) { + return $this->handleExistingToken($existingToken); + } + + $paymentToken = $this->createPaymentToken($customerId, $websiteId, $publicHash, $cardData, $ccType); + $this->payTokenRepository->save($paymentToken); + + return $this->createVaultTokenResponse($paymentToken, $cardData, $ccType, $websiteId); + } catch (\Exception $e) { + throw new LocalizedException(__($e->getMessage())); + } + } + + /** + * Handle existing token - reactivate if inactive. + * + * @param PaymentTokenInterface $existingToken + * @return PagBankVaultTokenInterface + * @throws LocalizedException + */ + private function handleExistingToken(PaymentTokenInterface $existingToken) + { + if (!$existingToken->getIsActive() || !$existingToken->getIsVisible()) { + return $this->reactivateToken($existingToken); + } + throw new LocalizedException(__('Card already exists and is active.')); + } + + /** + * Reactivate existing token. + * + * @param PaymentTokenInterface $existingToken + * @return PagBankVaultTokenInterface + */ + private function reactivateToken(PaymentTokenInterface $existingToken) + { + $existingToken->setIsActive(true); + $existingToken->setIsVisible(true); + $this->payTokenRepository->save($existingToken); + + $details = $this->serializer->unserialize($existingToken->getTokenDetails()); + $ccType = $this->getCreditCardType($details['cc_type']); + + $vaultToken = $this->vaultTokenFactory->create(); + $vaultToken->setPagBankToken($existingToken->getGatewayToken()) + ->setWebsiteId($existingToken->getWebsiteId()) + ->setPublicHash($existingToken->getPublicHash()) + ->setCardBrand($ccType) + ->setLastDigits($details['cc_last4']) + ->setCreatedAt($existingToken->getCreatedAt()) + ->setIsActive(true); + + return $vaultToken; + } + + /** + * Create new payment token. + * + * @param int $customerId + * @param int $websiteId + * @param string $publicHash + * @param array $cardData + * @param string $ccType + * @return PaymentTokenInterface + */ + private function createPaymentToken($customerId, $websiteId, $publicHash, array $cardData, string $ccType) + { + $paymentToken = $this->paymentTokenFactory->create(PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD); + $paymentToken->setCustomerId($customerId) + ->setWebsiteId($websiteId) + ->setPaymentMethodCode('pagbank_paymentmagento_cc') + ->setPublicHash($publicHash) + ->setExpiresAt( + $this->getExpirationDate($cardData['cc_exp_month'], $cardData['cc_exp_year']) + ) + ->setGatewayToken($cardData['token']) + ->setIsVisible(true) + ->setIsActive(true) + ->setTokenDetails($this->convertDetailsToString([ + 'cc_bin' => $cardData['cc_bin'], + 'cc_last4' => $cardData['cc_last4'], + 'cc_exp_year' => $cardData['cc_exp_year'], + 'cc_exp_month' => $cardData['cc_exp_month'], + 'cc_type' => $ccType + ])); + + return $paymentToken; + } + + /** + * Create vault token response. + * + * @param PaymentTokenInterface $paymentToken + * @param array $cardData + * @param string $ccType + * @param int $websiteId + * @return PagBankVaultTokenInterface + */ + private function createVaultTokenResponse(PaymentTokenInterface $paymentToken, array $cardData, string $ccType, int $websiteId) + { + /** @var PagBankVaultTokenInterfaceFactory $vault */ + $vaultToken = $this->vaultTokenFactory->create(); + $vaultToken->setPagBankToken($cardData['token']) + ->setWebsiteId($websiteId) + ->setPublicHash($paymentToken->getPublicHash()) + ->setCardBrand($ccType) + ->setLastDigits($cardData['cc_last4']) + ->setCreatedAt($paymentToken->getCreatedAt()) + ->setIsActive(true); + + return $vaultToken; + } + + /** + * Create PagBank Token. + * + * @param int $storeId + * @param string $encryptedCard + * @return array + * @throws LocalizedException + */ + private function createPagBankToken($storeId, $encryptedCard) + { + /** @var ZendClient $client */ + $client = $this->httpClientFactory->create(); + $url = $this->configBase->getApiUrl($storeId); + $apiConfigs = $this->configBase->getApiConfigs(); + $headers = $this->configBase->getApiHeaders($storeId); + $uri = $url.'tokens/cards'; + + $data = [ + 'encrypted' => $encryptedCard + ]; + + try { + $client->setUri($uri); + $client->setHeaders($headers); + $client->setMethod(ZendClient::POST); + $client->setConfig($apiConfigs); + $client->setRawData($this->serializer->serialize($data), 'application/json'); + + $responseBody = $client->request()->getBody(); + + $response = $this->serializer->unserialize($responseBody); + + if (isset($response['error_messages'])) { + throw new LocalizedException( + __('PagBank API error: %1', $response['error_messages'][0]['description']) + ); + } + + return [ + 'token' => $response['id'], + 'cc_bin' => $response['first_digits'], + 'cc_last4' => $response['last_digits'], + 'cc_exp_month' => $response['exp_month'], + 'cc_exp_year' => $response['exp_year'], + 'cc_type' => $response['brand'] + ]; + + } catch (\Exception $e) { + throw new LocalizedException(__('Error creating card token: %1', $e->getMessage())); + } + } + + /** + * Generate public hash for payment token. + * + * @param array $cardData + * @return string + */ + private function generatePublicHash($cardData): string + { + $hashData = $cardData['cc_bin'] . $cardData['cc_last4'] . $cardData['cc_type']; + return $this->encryptor->getHash($hashData); + } + + /** + * Get expiration date. + * + * @param string $month + * @param string $year + * @return string + */ + private function getExpirationDate(string $month, string $year): string + { + return sprintf('%s-%s-01 00:00:00', $year, $month); + } + + /** + * Convert payment token details to string. + * + * @param array $details + * @return string + */ + private function convertDetailsToString(array $details): string + { + return $this->serializer->serialize($details); + } + + /** + * Get Credit Card Type. + * + * @param string $type + * @return string + */ + private function getCreditCardType(string $type): ?string + { + $type = strtoupper($type); + $mapper = $this->configCc->getCcTypesMapper(); + return $mapper[$type] ?? $type; + } +} diff --git a/README.md b/README.md index cbe3e14..4e94803 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Principais Recursos: - Método transparente - Autenticação 3DS +- Salvamento de cartão via api de Zero Dollar - AntiFraude aprimorado com revisão manual - Captura automatizada ou tardia - Reembolso online total ou parcial diff --git a/etc/acl.xml b/etc/acl.xml index 875ffd5..d5b6419 100644 --- a/etc/acl.xml +++ b/etc/acl.xml @@ -12,6 +12,7 @@ + diff --git a/etc/di.xml b/etc/di.xml index f055185..dd96ea7 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -39,6 +39,10 @@ + + + + diff --git a/etc/webapi.xml b/etc/webapi.xml index fe81ed2..51bfdde 100644 --- a/etc/webapi.xml +++ b/etc/webapi.xml @@ -51,4 +51,13 @@ + + + + + + + %customer_id% + + \ No newline at end of file diff --git a/i18n/en_US.csv b/i18n/en_US.csv index 590fa20..bfd93b5 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -10,6 +10,7 @@ Production,Production "You are diconnected to PagBank.","You are diconnected to PagBank." "You are connected to PagBank. =)","You are connected to PagBank. =)" "Unable to get the code, try again. =(","Unable to get the code, try again. =(" +"Card saved successfully.","Card saved successfully." "You should not be here...","You should not be here..." "Not apply.","Not apply." "PagBank, error when checking order status.","PagBank, error when checking order status." @@ -68,6 +69,10 @@ Close,Close "Receive within 30 days","Receive within 30 days" "Cart %1 doesn't contain products","Cart %1 doesn't contain products" "Unable to save interest.","Unable to save interest." +"Invalid card data","Invalid card data" +"Card already exists and is active.","Card already exists and is active." +"PagBank API error: %1","PagBank API error: %1" +"Error creating card token: %1","Error creating card token: %1" "Refresh Token for Default Store.","Refresh Token for Default Store." "Refresh Token for Web Site Id %1.","Refresh Token for Web Site Id %1." Finished,Finished @@ -127,6 +132,14 @@ Pix:,Pix: "With receipt within 2 business days.","With receipt within 2 business days." "With 3.05% fee.","With 3.05% fee." "* The fees advertised here may vary, please check our page for updated values.","* The fees advertised here may vary, please check our page for updated values." +"Add New Card","Add New Card" +"Card Number","Card Number" +"Security Code","Security Code" +"Expiration Date","Expiration Date" +Month,Month +Year,Year +"Card Holder Name","Card Holder Name" +"Save Card","Save Card" "The Boleto barcode is","The Boleto barcode is" Copy,Copy Copied,Copied @@ -155,8 +168,6 @@ Waiting...,Waiting... "Save for later use.","Save for later use." "A 3-digit number in italics on the back of your credit card.","A 3-digit number in italics on the back of your credit card." "Card Type","Card Type" -Month,Month -Year,Year "Information for paying with PagBank","Information for paying with PagBank" "Payer Information","Payer Information" "Payer Tax id","Payer Tax id" @@ -177,6 +188,13 @@ expires,expires "Please enter a valid credit card number.","Please enter a valid credit card number." "Please enter a valid credit card verification number.","Please enter a valid credit card verification number." "Please provide a valid CPF/CNPJ.","Please provide a valid CPF/CNPJ." +"Please enter a valid card holder name.","Please enter a valid card holder name." +"Card has expired. Please use a valid expiration date.","Card has expired. Please use a valid expiration date." +Error,Error +"Unable to complete the card validation.","Unable to complete the card validation." +"An error occurred while processing the card. Please try again.","An error occurred while processing the card. Please try again." +Success,Success +"An error occurred while saving the card.","An error occurred while saving the card." Credit,Credit Debit,Debit "Unable to complete the payment with this card, please verify the information and try again.","Unable to complete the payment with this card, please verify the information and try again." diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 0833a51..1eea20d 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -10,6 +10,7 @@ Production,Produção "You are diconnected to PagBank.","Você foi desconectado do PagBank." "You are connected to PagBank. =)","Você foi conectado ao PagBank. =)" "Unable to get the code, try again. =(","Não foi possível obter o código, tente novamente. =(" +"Card saved successfully.","Cartão salvo com sucesso." "You should not be here...","Não é para você estar aqui..." "Not apply.","Não Aplicado." "PagBank, error when checking order status.","PagBank, erro ao verificar o status do pedido." @@ -67,6 +68,10 @@ Close,Fechar "Receive within 14 days","Receba em até 14 dias" "Receive within 30 days","Receba em até 30 dias" "Cart %1 doesn't contain products","O carrinho %1 não contém produtos" +"Invalid card data","Dados do cartão inválidos" +"Card already exists and is active.","Cartão já existe e está habilitado." +"PagBank API error: %1","PagBank API erro: %1" +"Error creating card token: %1","Error creating card token: %1" "Unable to save interest.","Não foi possível salvar o juros." "Refresh Token for Default Store.","Refresh Token para Loja Padrão." "Refresh Token for Web Site Id %1.","Refresh Token para Web Site Id %1." @@ -127,6 +132,14 @@ Pix:,Pix: "With receipt within 2 business days.","Com recebimento em até 2 dias úteis." "With 3.05% fee.","Com taxa de 3.05%." "* The fees advertised here may vary, please check our page for updated values.","* As taxas aqui anunciadas podem sofrer variações, consulte nossa página para os valores atualizados." +"Add New Card","Adicionar Novo Cartão" +"Card Number","Número do Cartão" +"Security Code","Código de Segurança (CVV)" +"Expiration Date","Data de Expiração" +Month,Mês +Year,Ano +"Card Holder Name","Nome Impresso no Cartão" +"Save Card","Salvar Cartão" "The Boleto barcode is","O código de barras do boleto é" Copy,Copiar Copied,Copiado @@ -156,8 +169,6 @@ Waiting...,Aguardando... "Save for later use.","Salvar para uso futuro." "A 3-digit number in italics on the back of your credit card.","Um número de 3 dígitos em itálico no verso do seu cartão de crédito." "Card Type","Tipo de Cartão" -Month,Mês -Year,Ano "Information for paying with PagBank","Informações para pagar com PagBank" "Payer Information","Informação do Pagador" "Payer Tax id","CPF/CNPJ do Pagador" @@ -178,6 +189,13 @@ expires,expirando "Please enter a valid credit card number.","Por favor digite um número de cartão de crédito válido." "Please enter a valid credit card verification number.","Insira um número de verificação de cartão de crédito válido (CVV)." "Please provide a valid CPF/CNPJ.","Forneça um CPF/CNPJ válido." +"Please enter a valid card holder name.","Por favor, informe um nome válido." +"Card has expired. Please use a valid expiration date.","Cartão expirado. Por favor use um cartão dentro da validade." +Error,Erro +"Unable to complete the card validation.","Não foi possível validar o cartão." +"An error occurred while processing the card. Please try again.","Ocorreu um erro ao salvar o cartão. Por favor, tente novamente." +Success,Sucesso +"An error occurred while saving the card.","Erro ao salvar o cartão." "Credit","Crédito" "Debit","Débito" "Unable to complete the payment with this card, please verify the information and try again.","Não foi possível concluir o pagamento com este cartão, por favor verifique as informações e tente novamente." diff --git a/view/frontend/layout/vault_cards_listaction.xml b/view/frontend/layout/vault_cards_listaction.xml index 4ce3f01..a910600 100644 --- a/view/frontend/layout/vault_cards_listaction.xml +++ b/view/frontend/layout/vault_cards_listaction.xml @@ -15,6 +15,9 @@ + + + \ No newline at end of file diff --git a/view/frontend/templates/customer/vault/cards/button-add-cards.phtml b/view/frontend/templates/customer/vault/cards/button-add-cards.phtml new file mode 100644 index 0000000..e2918f5 --- /dev/null +++ b/view/frontend/templates/customer/vault/cards/button-add-cards.phtml @@ -0,0 +1,39 @@ + + * @license See LICENSE for license details. + */ + +/** @var \PagBank\PaymentMagento\Block\Customer\Vault\Cards\CreateForm $block */ +/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */ +?> +
+
+ +
+
+ +
+
+ getChildHtml('pagbank.vault.create.form') ?> +
+
+ +renderStyleAsTag( + "display:none", + '#pagbank-form-container' +) ?> + + \ No newline at end of file diff --git a/view/frontend/templates/customer/vault/cards/create-form.phtml b/view/frontend/templates/customer/vault/cards/create-form.phtml new file mode 100644 index 0000000..f6a1733 --- /dev/null +++ b/view/frontend/templates/customer/vault/cards/create-form.phtml @@ -0,0 +1,86 @@ + + * @license See LICENSE for license details. + */ + +/** @var \PagBank\PaymentMagento\Block\Customer\Vault\Cards\CreateForm $block */ +?> +
+
+ + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/view/frontend/web/css/source/_module.less b/view/frontend/web/css/source/_module.less index 845386c..3c2d1a5 100644 --- a/view/frontend/web/css/source/_module.less +++ b/view/frontend/web/css/source/_module.less @@ -12,6 +12,26 @@ @pagbank-checkout__margin-adjust: 21px; & when (@media-common =true) { + aside { + &.modal-sm { + &.pagbank-add-new-card { + max-width: 450px; + margin: 0 auto; + .field { + &.month { + padding-left: 0; + } + &.year { + padding-right: 0; + } + } + } + } + } + + .action-add-new-vault { + margin-top: 20px; + } .form-pagbank { .checkout-payment-method { diff --git a/view/frontend/web/js/view/customer/vault/cards/add-card.js b/view/frontend/web/js/view/customer/vault/cards/add-card.js new file mode 100644 index 0000000..229e70d --- /dev/null +++ b/view/frontend/web/js/view/customer/vault/cards/add-card.js @@ -0,0 +1,33 @@ +/** + * PagBank Payment Magento Module. + * + * Copyright © 2023 PagBank. All rights reserved. + * + * @author Bruno Elisei + * @license See LICENSE for license details. + */ + +define([ + 'jquery', + 'Magento_Ui/js/modal/modal' +], function ($, modal) { + 'use strict'; + + return function (config, element) { + var options = { + type: 'popup', + responsive: true, + innerScroll: true, + modalClass: 'modal-sm pagbank-add-new-card', + title: $.mage.__('Add New Card'), + focus: '#card_number', + buttons: [] + }; + + modal(options, $('#pagbank-form-container')); + + $(element).click(function () { + $('#pagbank-form-container').modal('openModal'); + }); + }; +}); diff --git a/view/frontend/web/js/view/customer/vault/cards/save-card.js b/view/frontend/web/js/view/customer/vault/cards/save-card.js new file mode 100644 index 0000000..5eb5de6 --- /dev/null +++ b/view/frontend/web/js/view/customer/vault/cards/save-card.js @@ -0,0 +1,190 @@ +/** + * PagBank Payment Magento Module. + * + * Copyright © 2024 PagBank. All rights reserved. + * + * @author Bruno Elisei + * @license See LICENSE for license details. + */ + +define([ + 'jquery', + 'mage/url', + 'Magento_Ui/js/modal/alert', + 'pagBankCardJs', + 'mage/translate', + 'PagBank_PaymentMagento/js/model/credit-card-validation/credit-card-number-validator', + 'PagBank_PaymentMagento/js/validation/custom-credit-card-validation', + 'jquery/validate' +], function ($, url, alert, _pagBankCardJs, $t, creditCardNumberValidator) { + 'use strict'; + + return (config, element) => { + // Card holder validation + $.validator.addMethod( + 'validate-card-holder', + (value) => { + return value.length > 0 && /^[a-zA-Z\s]+$/.test(value); + }, + $t('Please enter a valid card holder name.') + ); + + $.validator.addMethod( + 'validate-expiration-date', + (_value, el) => { + const currentDate = new Date(); + const currentMonth = currentDate.getMonth() + 1; + const currentYear = currentDate.getFullYear() % 100; + const $form = $(el).closest('form'); + const selectedMonth = parseInt($form.find('#expiration_month').val(), 10); + const selectedYear = parseInt($form.find('#expiration_year').val(), 10); + + if (selectedYear < currentYear) { + return false; + } + + if (selectedYear === currentYear && selectedMonth < currentMonth) { + return false; + } + + return true; + }, + $t('Card has expired. Please use a valid expiration date.') + ); + + $(element).validate({ + rules: { + 'card_holder': { + required: true, + 'validate-card-holder': true + }, + 'card_number': { + required: true, + 'validate-card-number-pagbank': true, + 'validate-card-type-math-pagbank': '#cc_type' + }, + 'security_code': { + required: true, + 'validate-card-cvv-pagbank': true + }, + 'expiration_month': { + required: true, + 'validate-expiration-date': true + }, + 'expiration_year': { + required: true, + 'validate-expiration-date': true + } + }, + errorClass: 'mage-error', + errorElement: 'div', + focusInvalid: false + }); + + $('#card_number').on('input', function() { + let value = $(this).val().replace(/\D/g, ''); + const cardType = $('#cc_type').val(); + + if (cardType === 'DN') { + value = value.replace(/(\d{4})(\d{6})(\d{5})/g, '$1 $2 $3'); + } else { + value = value.replace(/(\d{4})/g, '$1 ').trim(); + } + + $(this).val(value); + }); + + $('#card_number').on('keyup', function () { + const number = $(this).val().replace(/\s/g, ''); + const result = creditCardNumberValidator(number); + + if (result.card) { + $('#cc_type').val(result.card.type); + $('.credit-card-types li').removeClass('_active'); + $(`.credit-card-types li[data-type="${result.card.type}"]`).addClass('_active'); + } + }); + + function encryptCardData() { + if (!$(element).valid()) { + return false; + } + + const cardData = { + publicKey: config.publicKey, + holder: $('#card_holder').val(), + number: $('#card_number').val().replace(/\s/g, ''), + expMonth: $('#expiration_month').val(), + expYear: '20' + $('#expiration_year').val(), + securityCode: $('#security_code').val() + }; + + try { + const cardPs = window.PagSeguro.encryptCard(cardData); + + if (cardPs.hasErrors) { + alert({ + title: $t('Error'), + content: $t('Unable to complete the card validation.') + }); + return false; + } + + return cardPs.encryptedCard; + } catch (e) { + alert({ + title: $t('Error'), + content: $t('An error occurred while processing the card. Please try again.') + }); + return false; + } + } + + $(element).submit((e) => { + e.preventDefault(); + + const encryptedCard = encryptCardData(); + + if (!encryptedCard) { + return; + } + + const formData = new FormData(); + + formData.append('encrypted_card', encryptedCard); + + $.ajax({ + url: url.build('pagbank/customer/savecard'), + type: 'POST', + data: formData, + processData: false, + contentType: false, + showLoader: true, + success: (response) => { + if (response.success) { + alert({ + title: $t('Success'), + content: response.message, + actions: { + always: () => { + location.reload(); + } + } + }); + } else { + alert({ + title: $t('Error'), + content: response.message + }); + } + }, + error: () => { + alert({ + title: $t('Error'), + content: $t('An error occurred while saving the card.') + }); + } + }); + }); + }; +}); diff --git a/view/frontend/web/js/view/payment/cc-form.js b/view/frontend/web/js/view/payment/cc-form.js index 559cbf9..5b04a32 100644 --- a/view/frontend/web/js/view/payment/cc-form.js +++ b/view/frontend/web/js/view/payment/cc-form.js @@ -40,7 +40,7 @@ define([ creditCardNumber: '', //5200000000001096 creditCardVerificationNumber: '', //123 creditCardType: '', //MC - creditCardExpYear: '', //2024 + creditCardExpYear: '', //2031 creditCardExpMonth: '', //10 creditCardHolderName: '', //Test tres ds creditCardInstallment: '',