From 135f24f6a7832651e279d472aec59d58e02a016f Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Mon, 4 Nov 2024 23:57:30 -0300 Subject: [PATCH 01/15] feat: Init 3DS implementation --- Controller/Tds/Enrollment.php | 189 ++++++++++++++++++ Gateway/Http/Client/Api.php | 18 +- Gateway/Http/Client/Api/Tds.php | 48 +++++ Gateway/Request/Tds/EnrollmentRequest.php | 108 ++++++++++ Gateway/Request/Tds/SetupRequest.php | 66 ++++++ Gateway/Request/Tds/TdsRequest.php | 97 +++++++++ Helper/Tds.php | 61 ++++++ Model/Ui/CreditCard/ConfigProvider.php | 12 +- etc/adminhtml/system/credit_card.xml | 29 +++ etc/config.xml | 4 + etc/di.xml | 7 + .../web/js/view/payment/method-renderer/cc.js | 66 ++++++ 12 files changed, 703 insertions(+), 2 deletions(-) create mode 100644 Controller/Tds/Enrollment.php create mode 100644 Gateway/Http/Client/Api/Tds.php create mode 100644 Gateway/Request/Tds/EnrollmentRequest.php create mode 100644 Gateway/Request/Tds/SetupRequest.php create mode 100644 Gateway/Request/Tds/TdsRequest.php create mode 100644 Helper/Tds.php diff --git a/Controller/Tds/Enrollment.php b/Controller/Tds/Enrollment.php new file mode 100644 index 0000000..b695dea --- /dev/null +++ b/Controller/Tds/Enrollment.php @@ -0,0 +1,189 @@ +checkoutSession = $checkoutSession; + $this->session = $session; + $this->resultJsonFactory = $resultJsonFactory; + $this->json = $json; + $this->helperData = $helperData; + $this->enrollmentRequest = $enrollmentRequest; + $this->setupRequest = $setupRequest; + $this->api = $api; + $this->copy = $copy; + $this->orderFactory = $orderFactory; + + parent::__construct($context); + } + + public function execute() + { + $result = $this->resultJsonFactory->create(); + + try { + $content = $this->getRequest()->getContent(); + $bodyParams = ($content) ? $this->json->unserialize($content) : []; + $paymentData = $this->getPaymentData($bodyParams); + + $setup = $this->createSetup($paymentData); + if ($setup['response']['chargeId']) { + $enrollment = $this->startEnrollment($setup['response']['chargeId'], $paymentData); + } + + $result->setJsonData($this->json->serialize($setup)); + $responseCode = 200; + } catch (\Exception $e) { + $responseCode = 500; + } + + $result->setHttpResponseCode($responseCode); + return $result; + } + + protected function getPaymentData($params) + { + $paymentData = new \Magento\Framework\DataObject(); + $paymentData->setData($params); + + $this->_eventManager->dispatch( + 'payment_method_assign_data_picpay_checkout_cc', + [ + AbstractDataAssignObserver::METHOD_CODE => 'picpay_checkout_cc', + AbstractDataAssignObserver::MODEL_CODE => $this->checkoutSession->getQuote()->getPayment(), + AbstractDataAssignObserver::DATA_CODE => $paymentData + ] + ); + return $paymentData; + } + + public function createSetup($paymentData) + { + $quote = $this->checkoutSession->getQuote(); + + $transaction = $this->setupRequest->build([ + 'payment' => $paymentData, + 'quote' => $quote, + 'amount' => $quote->getGrandTotal() + ]); + + $result = $this->api->tds()->setup($transaction['request']); + + if ($result['status'] == 200) { + $this->checkoutSession->setPicPayTdsChargeId($result['response']['chargeId']); + $this->checkoutSession->setPicPayTdsSetupTransaction($result['response']['transaction']); + return $result; + } + + throw new \Exception('Error trying to create 3DS setup on PicPay'); + } + + public function startEnrollment($chargeId, $paymentData) + { + $quote = $this->checkoutSession->getQuote(); + $transaction = $this->enrollmentRequest->build([ + 'payment' => $paymentData, + 'quote' => $quote, + 'amount' => $quote->getGrandTotal(), + 'chargeId' => $chargeId + ]); + + $result = $this->api->tds()->enrollment($transaction['request']); + + if ($result['status'] == 200) { + $this->checkoutSession->setPicPayTdsChargeId($result['response']['chargeId']); + $this->checkoutSession->setPicPayTdsSetupTransaction($result['response']['transaction']); + return $result; + } + + throw new \Exception('Error trying to create 3DS setup on PicPay'); + } + + public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException + { + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); + $result->setHttpResponseCode(403); + return new InvalidRequestException( + $result + ); + } + + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } +} diff --git a/Gateway/Http/Client/Api.php b/Gateway/Http/Client/Api.php index 2d9e3b4..bb9d940 100644 --- a/Gateway/Http/Client/Api.php +++ b/Gateway/Http/Client/Api.php @@ -24,6 +24,7 @@ use PicPay\Checkout\Gateway\Http\Client\Api\Refund; use PicPay\Checkout\Gateway\Http\Client\Api\Capture; use PicPay\Checkout\Gateway\Http\Client\Api\Token; +use PicPay\Checkout\Gateway\Http\Client\Api\Tds; use PicPay\Checkout\Gateway\Http\ClientInterface; use PicPay\Checkout\Helper\Data; @@ -74,6 +75,11 @@ class Api */ private $token; + /** + * @var Tds + */ + private $tds; + /** * @var string */ @@ -88,7 +94,8 @@ public function __construct( Refund $refund, Capture $capture, Card $card, - Query $query + Query $query, + Tds $tds ) { $this->helper = $helper; $this->token = $token; @@ -99,6 +106,7 @@ public function __construct( $this->capture = $capture; $this->card = $card; $this->query = $query; + $this->tds = $tds; } /** @@ -186,6 +194,14 @@ public function card(): ClientInterface return $this->getClient($this->card); } + /** + * @throws \Exception + */ + public function tds(): ClientInterface + { + return $this->getClient($this->tds); + } + /** * @throws \Exception */ diff --git a/Gateway/Http/Client/Api/Tds.php b/Gateway/Http/Client/Api/Tds.php new file mode 100644 index 0000000..879c4b1 --- /dev/null +++ b/Gateway/Http/Client/Api/Tds.php @@ -0,0 +1,48 @@ +getEndpointPath('payments/tds_setup'); + $method = Request::METHOD_POST; + return $this->makeRequest($path, $method, 'payments', $data); + } + + public function enrollment(array $data): array + { + $path = $this->getEndpointPath('payments/tds_enrollment'); + $method = Request::METHOD_POST; + return $this->makeRequest($path, $method, 'payments', $data); + } + + public function challengeStatus($chargeId): array + { + $path = $this->getEndpointPath('payments/tds_challenge_status'); + $method = Request::METHOD_GET; + return $this->makeRequest($path, $method); + } + + public function authorization($data): array + { + $path = $this->getEndpointPath('payments/tds_authorization'); + $method = Request::METHOD_POST; + return $this->makeRequest($path, $method, 'payments', $data); + } +} diff --git a/Gateway/Request/Tds/EnrollmentRequest.php b/Gateway/Request/Tds/EnrollmentRequest.php new file mode 100644 index 0000000..fb97142 --- /dev/null +++ b/Gateway/Request/Tds/EnrollmentRequest.php @@ -0,0 +1,108 @@ +getEnrollmentTransactions($quote, $buildSubject['chargeId'], $buildSubject['amount']); + return ['request' => $request, 'client_config' => ['store_id' => $quote->getStoreId()]]; + } + + protected function getEnrollmentTransactions(Quote $quote, string $chargeId, float $amount): array + { + return [ + 'chargeId' => $chargeId, + 'customer' => $this->getTdsCustomerData($quote), + 'browser' => $this->getBrowserData(), + 'transactions' => $this->getTdsTransactionInfo($quote, $amount), + 'shipping' => $this->getShipping($quote) + ]; + } + + protected function getTdsCustomerData(Quote $quote): array + { + $payment = $quote->getPayment(); + $taxvat = (string) $payment->getAdditionalInformation('picpay_customer_taxvat'); + + $address = $quote->getBillingAddress(); + $fullName = $this->getCustomerFullName($quote); + $phoneNumber = $this->helper->formatPhoneNumber($address->getTelephone()); + + $customerData = [ + 'name' => $fullName, + 'email' => $quote->getCustomerEmail(), + 'documentType' => $this->getDocumentType($taxvat), + 'document' => $taxvat, + 'phone' => $phoneNumber, +// 'threeDomainSecureSettings' => $this->getTdsSecureSettings() + ]; + + if ($quote->getCustomerDob()) { + $customerData['birth_date'] = $this->helper->formatDate($quote->getCustomerDob()); + } + + return $customerData; + } + + protected function getBrowserData() + { + return [ + 'httpAcceptBrowserValue' => $_SERVER['HTTP_ACCEPT'] , + 'httpAcceptContent' => $_SERVER['HTTP_ACCEPT'] , + 'httpBrowserLanguage' => '', + 'httpBrowserJavaEnabled' => '', + 'httpBrowserJavaScriptEnabled' => '', + 'httpBrowserColorDepth' => '', + 'httpBrowserScreenHeight' => '', + 'httpBrowserTimeDifference' => '', + 'userAgentBrowserValue' => '', + ]; + } + + /** + * @param Quote $quote + * @param float $orderAmount + * @return array + */ + protected function getTdsTransactionInfo(Quote $quote, float $orderAmount): array + { + $cardData = $this->getCardData($quote); + $response = $this->api->card()->execute($cardData); + + if (isset($response['response']) && isset($response['response']['cardId'])) { + $cardData['cardId'] = $response['response']['cardId']; + } + $test['cardId'] = $cardData['cardId']; + $test['billingAddress'] = $cardData['billingAddress']; + + $transactionInfo = [ + 'amount' => $orderAmount * 100, + 'paymentType' => 'CREDIT', + 'card' => $test + ]; + return [$transactionInfo]; + } +} diff --git a/Gateway/Request/Tds/SetupRequest.php b/Gateway/Request/Tds/SetupRequest.php new file mode 100644 index 0000000..315984f --- /dev/null +++ b/Gateway/Request/Tds/SetupRequest.php @@ -0,0 +1,66 @@ +getSetupTransactions($quote, $buildSubject['amount']); + return ['request' => $request, 'client_config' => ['store_id' => $quote->getStoreId()]]; + } + + protected function getSetupTransactions(Quote $quote, float $amount): array + { + return [ + 'paymentSource' => 'GATEWAY', + 'transactions' => $this->getTdsTransactionInfo($quote, $amount) + ]; + } + + /** + * @param Quote $quote + * @param float $orderAmount + * @return array + */ + protected function getTdsTransactionInfo(Quote $quote, float $orderAmount): array + { + $cardData = $this->getCardData($quote); + $response = $this->api->card()->execute($cardData); + + if (isset($response['response']) && isset($response['response']['cardId'])) { + $cardData['cardId'] = $response['response']['cardId']; + } + $test['cardId'] = $cardData['cardId']; + $test['billingAddress'] = $cardData['billingAddress']; + + $transactionInfo = [ + 'amount' => $orderAmount * 100, + 'paymentType' => 'CREDIT', + 'card' => $test + ]; + return [$transactionInfo]; + } +} diff --git a/Gateway/Request/Tds/TdsRequest.php b/Gateway/Request/Tds/TdsRequest.php new file mode 100644 index 0000000..33ae7ae --- /dev/null +++ b/Gateway/Request/Tds/TdsRequest.php @@ -0,0 +1,97 @@ + 'GATEWAY', + 'transactions' => $this->getTdsTransactionInfo($quote, $amount) + ]; + } + + protected function getCardData(Quote $quote): array + { + $payment = $quote->getPayment(); + $installments = (int) $payment->getAdditionalInformation('cc_installments') ?: 1; + $taxvat = (string) $payment->getAdditionalInformation('picpay_customer_taxvat'); + return [ + 'cardType' => ConfigProvider::DEFAULT_TYPE, + 'cardNumber' => $payment->getCcNumber(), + 'cvv' => $payment->getCcCid(), + 'brand' => 'MASTERCARD', + 'cardholderName' => $payment->getCcOwner(), + 'cardholderDocument' => $this->helper->digits($taxvat), + 'expirationMonth' => (int) $payment->getCcExpMonth(), + 'expirationYear' => (int) $payment->getCcExpYear(), + 'installmentNumber' => $installments, + 'installmentType' => $installments > 1 ? 'MERCHANT' : 'NONE', + 'billingAddress' => $this->getQuoteBillingAddress($quote) + ]; + } + + /** + * @param Quote $quote + * @return array + */ + protected function getQuoteBillingAddress(Quote $quote): array + { + $billingAddress = $quote->getBillingAddress(); + $number = $billingAddress->getStreetLine($this->getStreetField('number')) ?: 0; + $complement = $billingAddress->getStreetLine($this->getStreetField('complement')); + $address = [ + 'street' => $billingAddress->getStreetLine($this->getStreetField('street')), + 'number' => $number, + 'neighborhood' => $billingAddress->getStreetLine($this->getStreetField('district')), + 'city' => $billingAddress->getCity(), + 'state' => $billingAddress->getRegionCode(), + 'country' => $billingAddress->getCountryId(), + 'zipCode' => $this->helper->clearNumber($billingAddress->getPostcode()), + ]; + + if ($complement) { + $address['complement'] = $complement; + } + + return $address; + } + + /** + * @param Quote $quote + * @param float $orderAmount + * @return array[] + * @throws \Exception + */ + protected function getTdsTransactionInfo(Quote $quote, float $orderAmount): array + { + $cardData = $this->getCardData($quote); + $response = $this->api->card()->execute($cardData); + + if (isset($response['response']) && isset($response['response']['cardId'])) { + $cardData['cardId'] = $response['response']['cardId']; + } + $test['cardId'] = $cardData['cardId']; + $test['billingAddress'] = $cardData['billingAddress']; + + $transactionInfo = [ + 'amount' => $orderAmount * 100, + 'paymentType' => 'CREDIT', + 'card' => $test + ]; + return [$transactionInfo]; + } + + protected function getCustomerFullName(Quote $quote): string + { + $firstName = $quote->getCustomerFirstname(); + $lastName = $quote->getCustomerLastname(); + return $firstName . ' ' . $lastName; + } +} diff --git a/Helper/Tds.php b/Helper/Tds.php new file mode 100644 index 0000000..d610524 --- /dev/null +++ b/Helper/Tds.php @@ -0,0 +1,61 @@ +priceCurrency = $priceCurrency; + $this->helper = $helper; + parent::__construct($context); + } + + public function addCardDataToPayment() + { + + } + + + +} diff --git a/Model/Ui/CreditCard/ConfigProvider.php b/Model/Ui/CreditCard/ConfigProvider.php index ca464fe..b3986c1 100644 --- a/Model/Ui/CreditCard/ConfigProvider.php +++ b/Model/Ui/CreditCard/ConfigProvider.php @@ -86,7 +86,9 @@ public function getConfig() 'customer_taxvat' => $customerTaxvat, 'sandbox' => (int) $this->helper->getGeneralConfig('use_sandbox'), 'icons' => $this->getPaymentIcons(), - 'availableTypes' => $this->getCcAvailableTypes($methodCode) + 'availableTypes' => $this->getCcAvailableTypes($methodCode), + 'use_tds' => (int) $this->canUseTds($grandTotal), + 'place_not_authorized_order' => (int) $this->helper->getConfig('place_not_authorized_order'), ], 'ccform' => [ 'grandTotal' => [$methodCode => $grandTotal], @@ -104,6 +106,14 @@ public function getConfig() ]; } + public function canUseTds($amount) + { + $isActive = $this->helper->getConfig('tds_active'); + $minAmount = $this->helper->getConfig('min_tds_order_total'); + + return $isActive && $minAmount <= $amount; + } + /** * Get icons for available payment methods * diff --git a/etc/adminhtml/system/credit_card.xml b/etc/adminhtml/system/credit_card.xml index 3c277ff..ca305de 100644 --- a/etc/adminhtml/system/credit_card.xml +++ b/etc/adminhtml/system/credit_card.xml @@ -217,5 +217,34 @@ payment/picpay_checkout_cc/max_order_total + + + Magento\Config\Block\System\Config\Form\Fieldset + + + Magento\Config\Model\Config\Source\Yesno + payment/picpay_checkout_cc/tds_active + + + + Magento\Config\Model\Config\Source\Yesno + payment/picpay_checkout_cc/place_not_authorized_tds + + 1 + + + + validate-number validate-zero-or-greater + + + payment/picpay_checkout_cc/min_tds_order_total + + 1 + + + + 1 + + diff --git a/etc/config.xml b/etc/config.xml index ef82277..51aa017 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -40,6 +40,10 @@ api/v1/charge/{order_id}/refund api/v1/charge/{order_id}/refund api/v1/cards/verification + api/v1/charge/3ds/setup + api/v1/charge/3ds/enrollment + api/v1/charge/3ds/:chargeId/status + api/v1/charge/3ds/authorization diff --git a/etc/di.xml b/etc/di.xml index f6e8016..52f35d6 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -230,6 +230,13 @@ + + + + PicPayCreditCardConfig + + + PicPayPixConfig diff --git a/view/frontend/web/js/view/payment/method-renderer/cc.js b/view/frontend/web/js/view/payment/method-renderer/cc.js index cc242b6..4cd3038 100644 --- a/view/frontend/web/js/view/payment/method-renderer/cc.js +++ b/view/frontend/web/js/view/payment/method-renderer/cc.js @@ -27,6 +27,7 @@ define([ 'Magento_SalesRule/js/action/cancel-coupon', 'Magento_Customer/js/model/customer', 'Magento_Payment/js/view/payment/cc-form', + 'mage/url', 'PicPay_Checkout/js/model/credit-card-validation/credit-card-number-validator', 'Magento_Payment/js/model/credit-card-validation/credit-card-data', 'picpay-cc-form', @@ -44,6 +45,7 @@ define([ cancelCouponCodeAction, customer, Component, + urlBuilder, cardNumberValidator, creditCardData, creditCardForm @@ -253,6 +255,70 @@ define([ }); }, 500); } + }, + + canUseTds: function () { + return window.checkoutConfig.payment[this.getCode()].use_tds; + }, + + placeNotAuthorizedOrder: function () { + return window.checkoutConfig.payment[this.getCode()].place_not_authorized_order; + }, + + placeOrder: function (data, event) { + if (event) { + event.preventDefault(); + } + + let formData = this.getData(); + formData.additional_data.browser_data = this.getBrowserData(); + if (this.canUseTds()) { + $.ajax({ + url: urlBuilder.build('picpay_checkout/tds/enrollment'), + global: true, + data: JSON.stringify(this.getData()), + contentType: 'application/json', + type: 'POST', + async: true + }).done(function (data) { + alert('success') + console.log(data) + }).fail(function (response) { + alert('fail') + console.log(response) + }); + } else { + return this._super(data, event); + } + + + // @todo Verifica se deve usar 3DS + // @todo Chama a criação de sessão + // @todo Verifica se tem challenge, se tiver, exibe para o usuário + + + + // Add custom logic before the order is placed + alert('placeOrder') + + // Custom logic: for example, validating specific fields or sending data to an external service + console.log("Custom placeOrder logic executed"); + + // Call the original placeOrder function + // return this._super(data, event); + }, + + getBrowserData: function () { + return { + httpBrowserJavaEnabled: navigator.javaEnabled(), + httpBrowserJavaScriptEnabled: true, + httpBrowserColorDepth: screen.colorDepth, + httpBrowserScreenHeight: screen.height, + httpBrowserScreenWidth: screen.width, + httpBrowserTimeDifference: new Date().getTimezoneOffset(), + httpBrowserLanguage: navigator.language, + userAgentBrowserValue: navigator.userAgent + } } }); }); From 1d7e955abcebe316b2031af78121b6bbb606e571 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Sun, 17 Nov 2024 13:07:26 -0300 Subject: [PATCH 02/15] fix: add taxvat to pix and wallet --- Model/Ui/Pix/ConfigProvider.php | 6 +- etc/events.xml | 8 +++ .../js/view/payment/method-renderer/pix.js | 24 +++++-- .../js/view/payment/method-renderer/wallet.js | 17 ++++- .../web/template/payment/form/pix.html | 66 +++++++++++++------ .../web/template/payment/form/wallet.html | 24 +++++++ 6 files changed, 117 insertions(+), 28 deletions(-) diff --git a/Model/Ui/Pix/ConfigProvider.php b/Model/Ui/Pix/ConfigProvider.php index 1802155..081b0e6 100644 --- a/Model/Ui/Pix/ConfigProvider.php +++ b/Model/Ui/Pix/ConfigProvider.php @@ -73,12 +73,16 @@ public function __construct( */ public function getConfig() { + $customer = $this->customerSession->getCustomer(); + $customerTaxvat = ($customer && $customer->getTaxvat()) ? $customer->getTaxvat() : ''; + return [ 'payment' => [ self::CODE => [ 'grand_total' => $this->checkoutSession->getQuote()->getGrandTotal(), 'sandbox' => (int) $this->helper->getGeneralConfig('use_sandbox'), - 'checkout_instructions' => $this->helper->getConfig('checkout_instructions', self::CODE) + 'checkout_instructions' => $this->helper->getConfig('checkout_instructions', self::CODE), + 'customer_taxvat' => $customerTaxvat, ] ] ]; diff --git a/etc/events.xml b/etc/events.xml index 099fe03..70ec813 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -19,6 +19,14 @@ + + + + + + + + diff --git a/view/frontend/web/js/view/payment/method-renderer/pix.js b/view/frontend/web/js/view/payment/method-renderer/pix.js index 7218c4b..f6cacbb 100644 --- a/view/frontend/web/js/view/payment/method-renderer/pix.js +++ b/view/frontend/web/js/view/payment/method-renderer/pix.js @@ -19,27 +19,43 @@ */ define( [ + 'jquery', + 'ko', 'Magento_Checkout/js/view/payment/default', - 'mage/url' + 'Magento_Customer/js/model/customer' ], - function (Component, url) { + function ($, ko, Component, customer) { 'use strict'; return Component.extend({ defaults: { - template: 'PicPay_Checkout/payment/form/pix' + template: 'PicPay_Checkout/payment/form/pix', }, + taxvat: ko.observable(), + getCode: function() { return 'picpay_checkout_pix'; }, + validate: function () { + const $form = $('#' + 'form_' + this.getCode()); + return ($form.validation() && $form.validation('isValid')); + }, + getData: function() { return { - 'method': this.item.method + 'method': this.item.method, + 'additional_data': { + 'taxvat': this.taxvat() + } }; }, + isLoggedIn: function () { + return customer.isLoggedIn(); + }, + hasInstructions: function () { return (window.checkoutConfig.payment.picpay_checkout_pix.checkout_instructions.length > 0); }, diff --git a/view/frontend/web/js/view/payment/method-renderer/wallet.js b/view/frontend/web/js/view/payment/method-renderer/wallet.js index ac38813..4a4a152 100644 --- a/view/frontend/web/js/view/payment/method-renderer/wallet.js +++ b/view/frontend/web/js/view/payment/method-renderer/wallet.js @@ -19,10 +19,12 @@ */ define( [ + 'jquery', + 'ko', 'Magento_Checkout/js/view/payment/default', - 'mage/url' + 'Magento_Customer/js/model/customer' ], - function (Component, url) { + function ($, ko, Component, customer) { 'use strict'; return Component.extend({ @@ -30,16 +32,25 @@ define( template: 'PicPay_Checkout/payment/form/wallet' }, + taxvat: ko.observable(), + getCode: function() { return 'picpay_checkout_wallet'; }, getData: function() { return { - 'method': this.item.method + 'method': this.item.method, + 'additional_data': { + 'taxvat': this.taxvat() + } }; }, + isLoggedIn: function () { + return customer.isLoggedIn(); + }, + hasInstructions: function () { return (window.checkoutConfig.payment.picpay_checkout_wallet.checkout_instructions.length > 0); }, diff --git a/view/frontend/web/template/payment/form/pix.html b/view/frontend/web/template/payment/form/pix.html index 4ecda2a..6955187 100644 --- a/view/frontend/web/template/payment/form/pix.html +++ b/view/frontend/web/template/payment/form/pix.html @@ -32,31 +32,57 @@
-
-
-
- -
- -
-
+
+
+
+
+ +
+ +
+
- - - + +
+ +
+ +
+
+ -
- + -
-
- - - -
-
+
+ + + +
+ +
+ + + +
+
+
diff --git a/view/frontend/web/template/payment/form/wallet.html b/view/frontend/web/template/payment/form/wallet.html index 16d2205..217f6ef 100644 --- a/view/frontend/web/template/payment/form/wallet.html +++ b/view/frontend/web/template/payment/form/wallet.html @@ -41,6 +41,30 @@ + +
+ +
+ +
+
+ + From 15638eec97544b4c0f4470bf9056b9f26ee6c6a4 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Wed, 20 Nov 2024 16:46:08 -0300 Subject: [PATCH 03/15] fix: save request data when transaction rollback --- Helper/Data.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Helper/Data.php b/Helper/Data.php index 9b27971..655c6b3 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -14,6 +14,7 @@ namespace PicPay\Checkout\Helper; +use Magento\Framework\App\ResourceConnection; use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\HTTP\Header; @@ -122,6 +123,9 @@ class Data extends \Magento\Payment\Helper\Data /** @var SessionManager */ protected $sessionManager; + /** @var ResourceConnection */ + protected $resourceConnection; + public function __construct( Context $context, LayoutFactory $layoutFactory, @@ -141,6 +145,7 @@ public function __construct( OrderInterface $order, Header $httpHeader, SessionManager $sessionManager, + ResourceConnection $resourceConnection, ComponentRegistrar $componentRegistrar, DateTime $dateTime, DirectoryData $helperDirectory, @@ -168,6 +173,7 @@ public function __construct( $this->order = $order; $this->httpHeader = $httpHeader; $this->sessionManager = $sessionManager; + $this->resourceConnection = $resourceConnection; $this->componentRegistrar = $componentRegistrar; $this->dateTime = $dateTime; $this->helperDirectory = $helperDirectory; @@ -311,6 +317,7 @@ public function saveRequest( $request = $this->serializeAndMask($request); $response = $this->serializeAndMask($response); + $connection = $this->resourceConnection->getConnection(); $requestModel = $this->requestFactory->create(); $requestModel->setRequest($request); $requestModel->setResponse($response); @@ -318,6 +325,7 @@ public function saveRequest( $requestModel->setStatusCode($statusCode); $this->requestRepository->save($requestModel); + $connection->commit(); } catch (\Exception $e) { $this->log($e->getMessage()); } From 4996ff908af6d1907822381fcc7aae954f50c033 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Thu, 7 Nov 2024 22:33:06 -0300 Subject: [PATCH 04/15] feat: add enrollment --- .../web/js/view/payment/method-renderer/cc.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/view/frontend/web/js/view/payment/method-renderer/cc.js b/view/frontend/web/js/view/payment/method-renderer/cc.js index 4cd3038..081de32 100644 --- a/view/frontend/web/js/view/payment/method-renderer/cc.js +++ b/view/frontend/web/js/view/payment/method-renderer/cc.js @@ -290,22 +290,6 @@ define([ } else { return this._super(data, event); } - - - // @todo Verifica se deve usar 3DS - // @todo Chama a criação de sessão - // @todo Verifica se tem challenge, se tiver, exibe para o usuário - - - - // Add custom logic before the order is placed - alert('placeOrder') - - // Custom logic: for example, validating specific fields or sending data to an external service - console.log("Custom placeOrder logic executed"); - - // Call the original placeOrder function - // return this._super(data, event); }, getBrowserData: function () { From 80edd8195e8c508574bbfc7d7385c7783c97d768 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 10 Jan 2025 18:05:54 -0300 Subject: [PATCH 05/15] feat: implement 3DS authorization steps --- Controller/Callback.php | 8 + Controller/Callback/Payments.php | 19 +- Controller/Tds/Challenge.php | 85 ++++++++ Controller/Tds/Enrollment.php | 141 +++---------- Gateway/Http/Client/Transaction.php | 7 +- Gateway/Request/CaptureRequest.php | 2 +- .../Request/CreditCard/TransactionRequest.php | 23 ++- Gateway/Request/PaymentsRequest.php | 9 +- Gateway/Request/RefundRequest.php | 2 +- Gateway/Request/Tds/AuthorizationRequest.php | 61 ++++++ Gateway/Request/Tds/EnrollmentRequest.php | 53 ++--- Gateway/Request/Tds/SetupRequest.php | 15 +- Gateway/Request/Tds/TdsRequest.php | 58 +++--- .../CreditCard/TransactionHandler.php | 8 +- Gateway/Response/PaymentsHandler.php | 5 +- Helper/Order.php | 15 +- Helper/Tds.php | 57 ++++-- Model/CheckoutTds.php | 185 ++++++++++++++++++ Model/Ui/CreditCard/ConfigProvider.php | 2 +- Observer/CreditCardAssignObserver.php | 7 +- etc/adminhtml/system/credit_card.xml | 6 +- etc/csp_whitelist.xml | 90 +++++++++ etc/db_schema.xml | 6 + etc/fieldset.xml | 12 ++ i18n/en_US.csv | 1 + i18n/pt_BR.csv | 1 + view/base/web/js/view/info/pix.js | 2 +- view/frontend/web/js/credit-card/tds.js | 155 +++++++++++++++ .../web/js/view/payment/method-renderer/cc.js | 74 +++---- .../web/template/payment/form/cc.html | 10 + 30 files changed, 849 insertions(+), 270 deletions(-) create mode 100644 Controller/Tds/Challenge.php create mode 100644 Gateway/Request/Tds/AuthorizationRequest.php create mode 100644 Model/CheckoutTds.php create mode 100644 etc/csp_whitelist.xml create mode 100644 view/frontend/web/js/credit-card/tds.js diff --git a/Controller/Callback.php b/Controller/Callback.php index 035df08..9fa69bb 100644 --- a/Controller/Callback.php +++ b/Controller/Callback.php @@ -4,6 +4,7 @@ use PicPay\Checkout\Helper\Data as HelperData; use PicPay\Checkout\Helper\Order as HelperOrder; +use PicPay\Checkout\Helper\Tds as HelperTds; use PicPay\Checkout\Model\CallbackFactory; use PicPay\Checkout\Model\ResourceModel\Callback as CallbackResourceModel; use Magento\Framework\App\Action\Action; @@ -33,6 +34,11 @@ abstract class Callback extends Action implements \Magento\Framework\App\CsrfAwa */ protected $helperOrder; + /** + * @var HelperTds + */ + protected $helperTds; + /** * @var CallbackFactory */ @@ -70,6 +76,7 @@ public function __construct( ResultFactory $resultFactory, HelperData $helperData, HelperOrder $helperOrder, + HelperTds $helperTds, CallbackFactory $callbackFactory, CallbackResourceModel $callbackResourceModel, ManagerInterface $eventManager, @@ -78,6 +85,7 @@ public function __construct( $this->resultFactory = $resultFactory; $this->helperData = $helperData; $this->helperOrder = $helperOrder; + $this->helperTds = $helperTds; $this->callbackFactory = $callbackFactory; $this->callbackResourceModel = $callbackResourceModel; $this->eventManager = $eventManager; diff --git a/Controller/Callback/Payments.php b/Controller/Callback/Payments.php index dc42f80..a183e6a 100644 --- a/Controller/Callback/Payments.php +++ b/Controller/Callback/Payments.php @@ -11,11 +11,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Controller\ResultFactory; use PicPay\Checkout\Controller\Callback; -use PicPay\Checkout\Gateway\Http\Client\Api; -use PicPay\Checkout\Helper\Order; use Laminas\Http\Response; -use PicPay\Checkout\Helper\Order as HelperOrder; -use Magento\Sales\Model\Order as SalesOrder; class Payments extends Callback { @@ -47,13 +43,18 @@ public function execute() $orderIncrementId = ''; try { - $content = $this->getContent($this->getRequest()); - $this->logParams($content); + $webhookData = $this->getContent($this->getRequest()); + $this->logParams($webhookData); $method = 'picpay-payments'; + $content = isset($webhookData['type']) ? $webhookData['data'] : $webhookData; - $content = isset($content['type']) ? $content['data'] : $content; - - if (isset($content['status'])) { + if (isset($webhookData['type']) && $webhookData['type'] == 'THREE_DS_CHALLENGE') { + $quote = $this->helperTds->loadQuoteByChargeId($content['chargeId']); + if ($quote->getId()) { + $this->helperTds->updateQuote($quote, $content); + $statusCode = Response::STATUS_CODE_200; + } + } else if (isset($content['status'])) { $chargeId = $content['merchantChargeId']; if (isset($content['status'])) { $picpayStatus = $content['status']; diff --git a/Controller/Tds/Challenge.php b/Controller/Tds/Challenge.php new file mode 100644 index 0000000..2619ab2 --- /dev/null +++ b/Controller/Tds/Challenge.php @@ -0,0 +1,85 @@ +checkoutSession = $checkoutSession; + $this->json = $json; + $this->resultJsonFactory = $resultJsonFactory; + $this->quoteRepository = $quoteRepository; + + parent::__construct($context); + } + + public function execute() + { + $result = $this->resultJsonFactory->create(); + + $quoteId = $this->checkoutSession->getQuoteId(); + + if ($quoteId) { + $quote = $this->quoteRepository->get($quoteId); + + if ($quote->getPicpayChargeId()) { + $tdsChallengeStatus = $quote->getPicpayChallengeStatus(); + return $result->setData([ + 'challenge_status' => $tdsChallengeStatus, + 'charge_id' => $quote->getPicpayChargeId() + ]); + } + } + + return $result->setData(['error' => true, 'message' => __('No orders found for this user.')]); + } + + public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException + { + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); + $result->setHttpResponseCode(403); + return new InvalidRequestException( + $result + ); + } + + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } +} diff --git a/Controller/Tds/Enrollment.php b/Controller/Tds/Enrollment.php index b695dea..cc70aa7 100644 --- a/Controller/Tds/Enrollment.php +++ b/Controller/Tds/Enrollment.php @@ -3,7 +3,6 @@ namespace PicPay\Checkout\Controller\Tds; use Magento\Backend\App\Action\Context; -use Magento\Checkout\Model\Session; use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\CsrfAwareActionInterface; @@ -11,42 +10,21 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\Controller\ResultFactory; -use Magento\Framework\DataObject\Copy; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\Session\SessionManagerInterface; -use Magento\Payment\Observer\AbstractDataAssignObserver; -use Magento\Sales\Api\Data\OrderInterfaceFactory as OrderFactory; use PicPay\Checkout\Gateway\Http\Client\Api; -use PicPay\Checkout\Gateway\Request\Tds\SetupRequest; -use PicPay\Checkout\Gateway\Request\Tds\EnrollmentRequest; -use PicPay\Checkout\Helper\Data as HelperData; +use PicPay\Checkout\Model\CheckoutTds; class Enrollment extends Action implements HttpPostActionInterface, CsrfAwareActionInterface { - /** @var JsonFactory */ - protected $resultJsonFactory; - - /** @var Json */ - protected $json; - - /** @var Session */ - protected $checkoutSession; - - /** @var SessionManagerInterface */ - protected $session; - - /** @var HelperData */ - protected $helperData; - /** - * @var SetupRequest + * @var JsonFactory */ - protected $setupRequest; + protected $resultJsonFactory; /** - * @var EnrollmentRequest + * @var Json */ - protected $enrollmentRequest; + protected $json; /** * @var Api @@ -54,39 +32,25 @@ class Enrollment extends Action implements HttpPostActionInterface, CsrfAwareAct protected $api; /** - * @var Copy + * @var CheckoutTds */ - protected $copy; + protected $tds; /** - * @var OrderFactory + * @param Context $context + * @param CheckoutTds $tds + * @param JsonFactory $resultJsonFactory + * @param Json $json */ - protected $orderFactory; - public function __construct( Context $context, - Session $checkoutSession, - SessionManagerInterface $session, + CheckoutTds $tds, JsonFactory $resultJsonFactory, - Json $json, - HelperData $helperData, - SetupRequest $setupRequest, - EnrollmentRequest $enrollmentRequest, - Api $api, - Copy $copy, - OrderFactory $orderFactory - ) - { - $this->checkoutSession = $checkoutSession; - $this->session = $session; + Json $json + ) { $this->resultJsonFactory = $resultJsonFactory; $this->json = $json; - $this->helperData = $helperData; - $this->enrollmentRequest = $enrollmentRequest; - $this->setupRequest = $setupRequest; - $this->api = $api; - $this->copy = $copy; - $this->orderFactory = $orderFactory; + $this->tds = $tds; parent::__construct($context); } @@ -98,81 +62,26 @@ public function execute() try { $content = $this->getRequest()->getContent(); $bodyParams = ($content) ? $this->json->unserialize($content) : []; - $paymentData = $this->getPaymentData($bodyParams); + $response = $this->tds->runTdsRequest($bodyParams); - $setup = $this->createSetup($paymentData); - if ($setup['response']['chargeId']) { - $enrollment = $this->startEnrollment($setup['response']['chargeId'], $paymentData); + if ($response['response']['chargeId']) { + $result->setJsonData($this->json->serialize($response['response']['transactions'][0])); } - $result->setJsonData($this->json->serialize($setup)); $responseCode = 200; } catch (\Exception $e) { $responseCode = 500; + $this->messageManager->addErrorMessage($e->getMessage()); } $result->setHttpResponseCode($responseCode); return $result; } - protected function getPaymentData($params) - { - $paymentData = new \Magento\Framework\DataObject(); - $paymentData->setData($params); - - $this->_eventManager->dispatch( - 'payment_method_assign_data_picpay_checkout_cc', - [ - AbstractDataAssignObserver::METHOD_CODE => 'picpay_checkout_cc', - AbstractDataAssignObserver::MODEL_CODE => $this->checkoutSession->getQuote()->getPayment(), - AbstractDataAssignObserver::DATA_CODE => $paymentData - ] - ); - return $paymentData; - } - - public function createSetup($paymentData) - { - $quote = $this->checkoutSession->getQuote(); - - $transaction = $this->setupRequest->build([ - 'payment' => $paymentData, - 'quote' => $quote, - 'amount' => $quote->getGrandTotal() - ]); - - $result = $this->api->tds()->setup($transaction['request']); - - if ($result['status'] == 200) { - $this->checkoutSession->setPicPayTdsChargeId($result['response']['chargeId']); - $this->checkoutSession->setPicPayTdsSetupTransaction($result['response']['transaction']); - return $result; - } - - throw new \Exception('Error trying to create 3DS setup on PicPay'); - } - - public function startEnrollment($chargeId, $paymentData) - { - $quote = $this->checkoutSession->getQuote(); - $transaction = $this->enrollmentRequest->build([ - 'payment' => $paymentData, - 'quote' => $quote, - 'amount' => $quote->getGrandTotal(), - 'chargeId' => $chargeId - ]); - - $result = $this->api->tds()->enrollment($transaction['request']); - - if ($result['status'] == 200) { - $this->checkoutSession->setPicPayTdsChargeId($result['response']['chargeId']); - $this->checkoutSession->setPicPayTdsSetupTransaction($result['response']['transaction']); - return $result; - } - - throw new \Exception('Error trying to create 3DS setup on PicPay'); - } - + /** + * @param RequestInterface $request + * @return InvalidRequestException|null + */ public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException { $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); @@ -182,6 +91,10 @@ public function createCsrfValidationException(RequestInterface $request): ?Inval ); } + /** + * @param RequestInterface $request + * @return bool|null + */ public function validateForCsrf(RequestInterface $request): ?bool { return true; diff --git a/Gateway/Http/Client/Transaction.php b/Gateway/Http/Client/Transaction.php index 3d74c2e..3c0b317 100644 --- a/Gateway/Http/Client/Transaction.php +++ b/Gateway/Http/Client/Transaction.php @@ -71,7 +71,12 @@ public function placeRequest(TransferInterface $transferObject) break; default: - $transaction = $this->api->create()->execute($requestBody, $config['store_id']); + if ($config['use_tds']) { + $transaction = $this->api->tds()->authorization($requestBody); + $transaction['response'] = $transaction['response']['charge'] ?? $transaction['response']; + } else { + $transaction = $this->api->create()->execute($requestBody, $config['store_id']); + } } $this->api->logResponse($transaction, self::LOG_NAME); diff --git a/Gateway/Request/CaptureRequest.php b/Gateway/Request/CaptureRequest.php index 0f75bce..0cec75c 100644 --- a/Gateway/Request/CaptureRequest.php +++ b/Gateway/Request/CaptureRequest.php @@ -45,7 +45,7 @@ public function build(array $buildSubject) ]; $clientConfig = [ - 'order_id' => $payment->getAdditionalInformation('merchantChargeId'), + 'order_id' => $payment->getAdditionalInformation('merchantChargeId') ?? $order->getPicpayMerchantId(), 'status' => $payment->getAdditionalInformation('status'), 'store_id' => $order->getStoreId() ]; diff --git a/Gateway/Request/CreditCard/TransactionRequest.php b/Gateway/Request/CreditCard/TransactionRequest.php index 6e15339..74cff28 100644 --- a/Gateway/Request/CreditCard/TransactionRequest.php +++ b/Gateway/Request/CreditCard/TransactionRequest.php @@ -2,17 +2,24 @@ namespace PicPay\Checkout\Gateway\Request\CreditCard; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\Event\ManagerInterface; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Payment\Gateway\ConfigInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; +use PicPay\Checkout\Gateway\Http\Client\Api; use PicPay\Checkout\Gateway\Request\PaymentsRequest; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Gateway\Request\BuilderInterface; +use PicPay\Checkout\Helper\Data; use PicPay\Checkout\Model\Ui\CreditCard\ConfigProvider; class TransactionRequest extends PaymentsRequest implements BuilderInterface { - /** * Builds ENV request * @@ -32,10 +39,18 @@ public function build(array $buildSubject) $payment = $buildSubject['payment']->getPayment(); $order = $payment->getOrder(); - $this->validateCard($order, $payment); - $request = $this->getTransactions($order, $buildSubject['amount']); + if ($payment->getAdditionalInformation('use_tds_authorization')) { + $request = $this->authorizationRequest->getRequest($order); + } else { + $this->validateCard($order, $payment); + $request = $this->getTransactions($order, $buildSubject['amount']); + } - return ['request' => $request, 'client_config' => ['store_id' => $order->getStoreId()]]; + $clientConfig = [ + 'store_id' => $order->getStoreId(), + 'use_tds' => $payment->getAdditionalInformation('use_tds_authorization') + ]; + return ['request' => $request, 'client_config' => $clientConfig]; } /** diff --git a/Gateway/Request/PaymentsRequest.php b/Gateway/Request/PaymentsRequest.php index 678ef27..a9ca6fb 100644 --- a/Gateway/Request/PaymentsRequest.php +++ b/Gateway/Request/PaymentsRequest.php @@ -21,6 +21,7 @@ namespace PicPay\Checkout\Gateway\Request; use PicPay\Checkout\Gateway\Http\Client\Api; +use PicPay\Checkout\Gateway\Request\Tds\AuthorizationRequest; use PicPay\Checkout\Helper\Data; use Magento\Customer\Model\Session as CustomerSession; use Magento\Framework\Event\ManagerInterface; @@ -57,6 +58,10 @@ class PaymentsRequest */ protected $date; + /** + * @var AuthorizationRequest + */ + protected $authorizationRequest; /** * @var DateTime @@ -92,7 +97,8 @@ public function __construct( CustomerSession $customerSession, CategoryRepositoryInterface $categoryRepository, ProductRepositoryInterface $productRepository, - Api $api + Api $api, + AuthorizationRequest $authorizationRequest ) { $this->eventManager = $eventManager; $this->helper = $helper; @@ -103,6 +109,7 @@ public function __construct( $this->categoryRepository = $categoryRepository; $this->productRepository = $productRepository; $this->api = $api; + $this->authorizationRequest = $authorizationRequest; } protected function getTransactions(Order $order, float $amount): array diff --git a/Gateway/Request/RefundRequest.php b/Gateway/Request/RefundRequest.php index 95cc03b..9793234 100644 --- a/Gateway/Request/RefundRequest.php +++ b/Gateway/Request/RefundRequest.php @@ -71,7 +71,7 @@ public function build(array $buildSubject) ]; $clientConfig = [ - 'order_id' => $payment->getAdditionalInformation('merchantChargeId'), + 'order_id' => $payment->getAdditionalInformation('merchantChargeId') ?? $order->getPicpayMerchantId(), 'store_id' => $order->getStoreId() ]; diff --git a/Gateway/Request/Tds/AuthorizationRequest.php b/Gateway/Request/Tds/AuthorizationRequest.php new file mode 100644 index 0000000..4c64ada --- /dev/null +++ b/Gateway/Request/Tds/AuthorizationRequest.php @@ -0,0 +1,61 @@ +helper = $helper; + } + + /** + * @param Order $order + * @return array + */ + public function getRequest(Order $order): array + { + return [ + 'chargeId' => $order->getPicpayChargeId(), + 'capture' => !$this->helper->isLateCapture(), + 'transactions' => $this->getAuthTransactionInfo($order) + ]; + } + + /** + * @param Order $order + * @return array + */ + protected function getAuthTransactionInfo(Order $order): array + { + $installments = (int) $order->getPayment()->getAdditionalInformation('cc_installments') ?: 1; + + $transactionInfo = [ + 'installmentNumber' => $installments, + 'installmentType' => $installments > 1 ? 'MERCHANT' : 'NONE', + 'card' => $this->getCardData($order, $order->getPayment()), + ]; + return [$transactionInfo]; + } + + + protected function getCardData(Order $order, Payment $payment): array + { + return [ + 'cvv' => $payment->getCcCid(), + 'cardholderAuthenticationId' => $order->getPicpayCardholderAuthId() + ]; + } +} diff --git a/Gateway/Request/Tds/EnrollmentRequest.php b/Gateway/Request/Tds/EnrollmentRequest.php index fb97142..e4a71f3 100644 --- a/Gateway/Request/Tds/EnrollmentRequest.php +++ b/Gateway/Request/Tds/EnrollmentRequest.php @@ -5,8 +5,6 @@ use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Payment; -use PicPay\Checkout\Gateway\Request\PaymentsRequest; -use PicPay\Checkout\Model\Ui\CreditCard\ConfigProvider; class EnrollmentRequest extends TdsRequest implements BuilderInterface { @@ -26,22 +24,32 @@ public function build(array $buildSubject) /** @var Payment $payment */ $payment = $buildSubject['payment']; + $quote = $buildSubject['quote']; - $request = $this->getEnrollmentTransactions($quote, $buildSubject['chargeId'], $buildSubject['amount']); + $request = $this->getEnrollmentTransactions($quote, $buildSubject, $payment->getAdditionalData()); return ['request' => $request, 'client_config' => ['store_id' => $quote->getStoreId()]]; } - protected function getEnrollmentTransactions(Quote $quote, string $chargeId, float $amount): array + /** + * @param Quote $quote + * @param $paymentData + * @param $additionalData + * @return array + */ + protected function getEnrollmentTransactions(Quote $quote, $paymentData, $additionalData): array { return [ - 'chargeId' => $chargeId, + 'chargeId' => $paymentData['chargeId'], 'customer' => $this->getTdsCustomerData($quote), - 'browser' => $this->getBrowserData(), - 'transactions' => $this->getTdsTransactionInfo($quote, $amount), - 'shipping' => $this->getShipping($quote) + 'browser' => $this->getBrowserData($additionalData['browser_data']), + 'transactions' => $this->getTdsTransactionInfo($quote, $paymentData['amount']) ]; } + /** + * @param Quote $quote + * @return array + */ protected function getTdsCustomerData(Quote $quote): array { $payment = $quote->getPayment(); @@ -57,7 +65,6 @@ protected function getTdsCustomerData(Quote $quote): array 'documentType' => $this->getDocumentType($taxvat), 'document' => $taxvat, 'phone' => $phoneNumber, -// 'threeDomainSecureSettings' => $this->getTdsSecureSettings() ]; if ($quote->getCustomerDob()) { @@ -67,19 +74,17 @@ protected function getTdsCustomerData(Quote $quote): array return $customerData; } - protected function getBrowserData() + /** + * @param $data + * @return array + */ + protected function getBrowserData($data) { - return [ + $browserData = [ 'httpAcceptBrowserValue' => $_SERVER['HTTP_ACCEPT'] , 'httpAcceptContent' => $_SERVER['HTTP_ACCEPT'] , - 'httpBrowserLanguage' => '', - 'httpBrowserJavaEnabled' => '', - 'httpBrowserJavaScriptEnabled' => '', - 'httpBrowserColorDepth' => '', - 'httpBrowserScreenHeight' => '', - 'httpBrowserTimeDifference' => '', - 'userAgentBrowserValue' => '', ]; + return array_merge($browserData, $data); } /** @@ -90,18 +95,14 @@ protected function getBrowserData() protected function getTdsTransactionInfo(Quote $quote, float $orderAmount): array { $cardData = $this->getCardData($quote); - $response = $this->api->card()->execute($cardData); + unset($cardData['cardType']); - if (isset($response['response']) && isset($response['response']['cardId'])) { - $cardData['cardId'] = $response['response']['cardId']; - } - $test['cardId'] = $cardData['cardId']; - $test['billingAddress'] = $cardData['billingAddress']; + $cardData['number'] = $cardData['cardNumber']; + unset($cardData['cardNumber']); $transactionInfo = [ 'amount' => $orderAmount * 100, - 'paymentType' => 'CREDIT', - 'card' => $test + 'card' => $cardData ]; return [$transactionInfo]; } diff --git a/Gateway/Request/Tds/SetupRequest.php b/Gateway/Request/Tds/SetupRequest.php index 315984f..ea6c43d 100644 --- a/Gateway/Request/Tds/SetupRequest.php +++ b/Gateway/Request/Tds/SetupRequest.php @@ -25,8 +25,6 @@ public function build(array $buildSubject) throw new \InvalidArgumentException('Payment data object should be provided'); } - /** @var Payment $payment */ - $payment = $buildSubject['payment']; $quote = $buildSubject['quote']; $request = $this->getSetupTransactions($quote, $buildSubject['amount']); return ['request' => $request, 'client_config' => ['store_id' => $quote->getStoreId()]]; @@ -43,23 +41,18 @@ protected function getSetupTransactions(Quote $quote, float $amount): array /** * @param Quote $quote * @param float $orderAmount - * @return array + * @return array[] + * @throws \Exception */ protected function getTdsTransactionInfo(Quote $quote, float $orderAmount): array { $cardData = $this->getCardData($quote); - $response = $this->api->card()->execute($cardData); - - if (isset($response['response']) && isset($response['response']['cardId'])) { - $cardData['cardId'] = $response['response']['cardId']; - } - $test['cardId'] = $cardData['cardId']; - $test['billingAddress'] = $cardData['billingAddress']; + unset($cardData['cardType']); $transactionInfo = [ 'amount' => $orderAmount * 100, 'paymentType' => 'CREDIT', - 'card' => $test + 'card' => $cardData ]; return [$transactionInfo]; } diff --git a/Gateway/Request/Tds/TdsRequest.php b/Gateway/Request/Tds/TdsRequest.php index 33ae7ae..85d5c15 100644 --- a/Gateway/Request/Tds/TdsRequest.php +++ b/Gateway/Request/Tds/TdsRequest.php @@ -9,30 +9,24 @@ class TdsRequest extends PaymentsRequest { - protected function getSetupTransactions(Quote $quote, float $amount): array - { - return [ - 'paymentSource' => 'GATEWAY', - 'transactions' => $this->getTdsTransactionInfo($quote, $amount) - ]; - } - + /** + * @param Quote $quote + * @return array + */ protected function getCardData(Quote $quote): array { $payment = $quote->getPayment(); - $installments = (int) $payment->getAdditionalInformation('cc_installments') ?: 1; $taxvat = (string) $payment->getAdditionalInformation('picpay_customer_taxvat'); + return [ - 'cardType' => ConfigProvider::DEFAULT_TYPE, + 'cardType' => 'CREDIT', 'cardNumber' => $payment->getCcNumber(), 'cvv' => $payment->getCcCid(), - 'brand' => 'MASTERCARD', + 'brand' => $this->getCardType($payment->getCcType()), 'cardholderName' => $payment->getCcOwner(), 'cardholderDocument' => $this->helper->digits($taxvat), 'expirationMonth' => (int) $payment->getCcExpMonth(), 'expirationYear' => (int) $payment->getCcExpYear(), - 'installmentNumber' => $installments, - 'installmentType' => $installments > 1 ? 'MERCHANT' : 'NONE', 'billingAddress' => $this->getQuoteBillingAddress($quote) ]; } @@ -65,33 +59,27 @@ protected function getQuoteBillingAddress(Quote $quote): array /** * @param Quote $quote - * @param float $orderAmount - * @return array[] - * @throws \Exception + * @return string */ - protected function getTdsTransactionInfo(Quote $quote, float $orderAmount): array - { - $cardData = $this->getCardData($quote); - $response = $this->api->card()->execute($cardData); - - if (isset($response['response']) && isset($response['response']['cardId'])) { - $cardData['cardId'] = $response['response']['cardId']; - } - $test['cardId'] = $cardData['cardId']; - $test['billingAddress'] = $cardData['billingAddress']; - - $transactionInfo = [ - 'amount' => $orderAmount * 100, - 'paymentType' => 'CREDIT', - 'card' => $test - ]; - return [$transactionInfo]; - } - protected function getCustomerFullName(Quote $quote): string { $firstName = $quote->getCustomerFirstname(); $lastName = $quote->getCustomerLastname(); return $firstName . ' ' . $lastName; } + + /** + * @param $type + * @return string + */ + protected function getCardType($type) + { + $types = [ + 'MC' => 'MASTERCARD', + 'VI' => 'VISA', + 'ELO' => 'ELO', + ]; + + return $types[$type] ?? ''; + } } diff --git a/Gateway/Response/CreditCard/TransactionHandler.php b/Gateway/Response/CreditCard/TransactionHandler.php index 49b6373..ea64de7 100644 --- a/Gateway/Response/CreditCard/TransactionHandler.php +++ b/Gateway/Response/CreditCard/TransactionHandler.php @@ -73,11 +73,15 @@ public function handle(array $handlingSubject, array $response) $payment = $this->helperOrder->updateDefaultAdditionalInfo($payment, $transaction); $payment = $this->helperOrder->updatePaymentAdditionalInfo($payment, $transaction['transactions'], 'credit'); - if ($transaction['chargeStatus'] == HelperOrder::STATUS_PRE_AUTHORIZED) { + if ( + $transaction['chargeStatus'] == HelperOrder::STATUS_PRE_AUTHORIZED + || $transaction['chargeStatus'] == HelperOrder::STATUS_CHARGE_PRE_AUTHORIZED + ) { $payment->getOrder()->setState('new'); $payment->setSkipOrderProcessing(true); } - $payment->getOrder()->setData('picpay_charge_id', $transaction['merchantChargeId']); + $merchantChargeId = $transaction['merchantChargeId'] ?? $payment->getOrder()->getPicpayMerchantId(); + $payment->getOrder()->setData('picpay_charge_id', $merchantChargeId); } } diff --git a/Gateway/Response/PaymentsHandler.php b/Gateway/Response/PaymentsHandler.php index 5e21339..ed7c941 100644 --- a/Gateway/Response/PaymentsHandler.php +++ b/Gateway/Response/PaymentsHandler.php @@ -44,7 +44,10 @@ public function validateResponse(array $handlingSubject, array $response): array } $transaction = $response['transaction']; - if (!isset($transaction['merchantChargeId']) || !isset($transaction['chargeStatus'])) { + if ( + (!isset($transaction['merchantChargeId']) && !isset($transaction['id'])) + || !isset($transaction['chargeStatus']) + ) { throw new LocalizedException(__('There was an error processing your request.')); } return array($handlingSubject['payment'], $transaction); diff --git a/Helper/Order.php b/Helper/Order.php index ddf2101..b0b2ac2 100644 --- a/Helper/Order.php +++ b/Helper/Order.php @@ -9,17 +9,11 @@ namespace PicPay\Checkout\Helper; -use BaconQrCode\Renderer\ImageRenderer as QrCodeImageRenderer; -use BaconQrCode\Renderer\Image\ImagickImageBackEnd as QrCodeImagickImageBackEnd; -use BaconQrCode\Renderer\RendererStyle\RendererStyle as QrCodeRendererStyle; -use BaconQrCode\Writer as QrCodeWritter; use Magento\Framework\Exception\LocalizedException; use PicPay\Checkout\Helper\Data as HelperData; use PicPay\Checkout\Gateway\Http\Client; use PicPay\Checkout\Gateway\Http\Client\Api; -use PicPay\Checkout\Model\Ui\CreditCard\ConfigProvider as CcConfigProvider; use Magento\Framework\App\Config\Initial; -use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\Helper\Context; use Magento\Framework\Filesystem; use Magento\Framework\Stdlib\DateTime\DateTime; @@ -45,6 +39,8 @@ class Order extends \Magento\Payment\Helper\Data public const STATUS_PRE_AUTHORIZED = 'PRE_AUTHORIZED'; + public const STATUS_CHARGE_PRE_AUTHORIZED = 'PreAuthorized'; + public const STATUS_PARTIAL = 'PARTIAL'; public const STATUS_ERROR = 'ERROR'; @@ -65,7 +61,7 @@ class Order extends \Magento\Payment\Helper\Data protected $orderFactory; /** - * @var OrderFactory + * @var OrderRepository */ protected $orderRepository; @@ -116,7 +112,6 @@ class Order extends \Magento\Payment\Helper\Data protected $dateTime; /** - * Order constructor. * @param Context $context * @param LayoutFactory $layoutFactory * @param Factory $paymentMethodFactory @@ -428,6 +423,10 @@ public function getStatusState(string $status): string return ''; } + /** + * @param string $chargeId + * @return SalesOrder + */ public function loadOrderByMerchantChargeId(string $chargeId): SalesOrder { $order = $this->orderFactory->create(); diff --git a/Helper/Tds.php b/Helper/Tds.php index d610524..0a9519c 100644 --- a/Helper/Tds.php +++ b/Helper/Tds.php @@ -23,39 +23,64 @@ use Magento\Framework\App\Helper\AbstractHelper; use Magento\Framework\App\Helper\Context; use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory; +use Magento\Quote\Model\QuoteRepository; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\ResourceModel\Order\Payment as ResourcePayment; -/** - * Tds data helper, prepared for PicPay Transparent - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ class Tds extends AbstractHelper { /** - * @var Data + * @var CartRepositoryInterface + */ + protected $quoteRepository; + + /** + * @var QuoteCollectionFactory */ - protected $helper; + protected $quoteCollectionFactory; /** - * @var PriceCurrencyInterface + * @var ResourcePayment */ - protected $priceCurrency; + protected $resourcePayment; public function __construct( Context $context, - PriceCurrencyInterface $priceCurrency, - Data $helper + QuoteCollectionFactory $quoteCollectionFactory, + QuoteRepository $quoteRepository, + ResourcePayment $resourcePayment ) { - $this->priceCurrency = $priceCurrency; - $this->helper = $helper; + $this->quoteRepository = $quoteRepository; + $this->quoteCollectionFactory = $quoteCollectionFactory; + $this->resourcePayment = $resourcePayment; parent::__construct($context); } - public function addCardDataToPayment() - { + /** + * @param string $chargeId + * @return \Magento\Framework\DataObject + */ + public function loadQuoteByChargeId(string $chargeId) + { + $collection = $this->quoteCollectionFactory->create(); + $collection->addFieldToFilter('picpay_charge_id', $chargeId); + return $collection->getFirstItem(); } - + /** + * @param $quote + * @param $content + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function updateQuote($quote, $content) + { + $quote->setPicpayChallengeStatus($content['status']); + $quote->setPicpayMerchantId($content['merchantChargeId']); + $this->quoteRepository->save($quote); + } } diff --git a/Model/CheckoutTds.php b/Model/CheckoutTds.php new file mode 100644 index 0000000..8ca6dbb --- /dev/null +++ b/Model/CheckoutTds.php @@ -0,0 +1,185 @@ +checkoutSession = $checkoutSession; + $this->helperData = $helperData; + $this->enrollmentRequest = $enrollmentRequest; + $this->setupRequest = $setupRequest; + $this->eventManager = $eventManager; + $this->api = $api; + $this->quoteRepository = $quoteRepository; + } + + /** + * @param $data + * @return mixed|void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function runTdsRequest($data) + { + $enrollment = []; + $paymentData = $this->getPaymentData($data); + $setup = $this->createSetup($paymentData); + + if ($setup['response']['chargeId']) { + $enrollment = $this->runEnrollment($setup['response']['chargeId'], $paymentData); + + if (isset($enrollment['response']['chargeId']) && isset($enrollment['response']['transactions'][0])) { + $transaction = $enrollment['response']['transactions'][0]; + $this->checkoutSession->setPicPayTdsChallengeStatus($transaction['cardholderAuthenticationStatus']); + + $quote = $this->checkoutSession->getQuote(); + $quote->setPicpayChargeId($enrollment['response']['chargeId']); + $quote->setPicpayChallengeStatus($transaction['cardholderAuthenticationStatus']); + $quote->setPicpayMerchantId($setup['response']['transactions'][0]['cardholderAuthenticationId']); + $quote->setPicpayCardholderAuthId($setup['response']['transactions'][0]['cardholderAuthenticationId']); + $this->quoteRepository->save($quote); + } + } + + return $enrollment; + } + + /** + * @param $params + * @return \Magento\Framework\DataObject + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + protected function getPaymentData($params) + { + $paymentData = new \Magento\Framework\DataObject(); + $paymentData->setData($params); + + $this->eventManager->dispatch( + 'payment_method_assign_data_picpay_checkout_cc', + [ + AbstractDataAssignObserver::METHOD_CODE => 'picpay_checkout_cc', + AbstractDataAssignObserver::MODEL_CODE => $this->checkoutSession->getQuote()->getPayment(), + AbstractDataAssignObserver::DATA_CODE => $paymentData + ] + ); + return $paymentData; + } + + /** + * @param $paymentData + * @return mixed + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function createSetup($paymentData) + { + $quote = $this->checkoutSession->getQuote(); + + $transaction = $this->setupRequest->build([ + 'payment' => $paymentData, + 'quote' => $quote, + 'amount' => $quote->getGrandTotal() + ]); + + $result = $this->api->tds()->setup($transaction['request']); + + if ($result['status'] == 200) { + $this->checkoutSession->setPicPayTdsChargeId($result['response']['chargeId']); + $this->checkoutSession->setPicPayTdsSetupTransaction($result['response']['transactions']); + return $result; + } + + throw new \Exception('Error trying to create 3DS setup on PicPay'); + } + + /** + * @param $chargeId + * @param $paymentData + * @return mixed + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function runEnrollment($chargeId, $paymentData) + { + $quote = $this->checkoutSession->getQuote(); + $transaction = $this->enrollmentRequest->build([ + 'payment' => $paymentData, + 'quote' => $quote, + 'amount' => $quote->getGrandTotal(), + 'chargeId' => $chargeId + ]); + + $result = $this->api->tds()->enrollment($transaction['request']); + + if ($result['status'] == 200) { + $this->checkoutSession->setPicPayTdsChargeStatus($result['response']['chargeStatus']); + $this->checkoutSession->setPicPayTdsEnrollmentTransaction($result['response']['transactions']); + return $result; + } + + throw new \Exception('Error trying to create 3DS setup on PicPay'); + } +} diff --git a/Model/Ui/CreditCard/ConfigProvider.php b/Model/Ui/CreditCard/ConfigProvider.php index b3986c1..4070d25 100644 --- a/Model/Ui/CreditCard/ConfigProvider.php +++ b/Model/Ui/CreditCard/ConfigProvider.php @@ -88,7 +88,7 @@ public function getConfig() 'icons' => $this->getPaymentIcons(), 'availableTypes' => $this->getCcAvailableTypes($methodCode), 'use_tds' => (int) $this->canUseTds($grandTotal), - 'place_not_authorized_order' => (int) $this->helper->getConfig('place_not_authorized_order'), + 'place_not_authenticated_order' => (int) $this->helper->getConfig('place_not_authenticated_order'), ], 'ccform' => [ 'grandTotal' => [$methodCode => $grandTotal], diff --git a/Observer/CreditCardAssignObserver.php b/Observer/CreditCardAssignObserver.php index 1e2bd47..25ccae4 100644 --- a/Observer/CreditCardAssignObserver.php +++ b/Observer/CreditCardAssignObserver.php @@ -88,7 +88,12 @@ public function execute(Observer $observer) 'cc_exp_year' => $ccExpYear ]); - $extraInfo = ['installments' => $installments, 'cc_installments' => $installments, 'cc_bin' => $ccBin]; + $extraInfo = [ + 'installments' => $installments, + 'cc_installments' => $installments, + 'cc_bin' => $ccBin, + 'use_tds_authorization' => $additionalData['use_tds_authorization'] ?? 0, + ]; foreach ($extraInfo as $key => $value) { $paymentInfo->setAdditionalInformation($key, $value); } diff --git a/etc/adminhtml/system/credit_card.xml b/etc/adminhtml/system/credit_card.xml index ca305de..def01b1 100644 --- a/etc/adminhtml/system/credit_card.xml +++ b/etc/adminhtml/system/credit_card.xml @@ -225,10 +225,10 @@ Magento\Config\Model\Config\Source\Yesno payment/picpay_checkout_cc/tds_active - - + + Magento\Config\Model\Config\Source\Yesno - payment/picpay_checkout_cc/place_not_authorized_tds + payment/picpay_checkout_cc/place_not_authenticated_tds 1 diff --git a/etc/csp_whitelist.xml b/etc/csp_whitelist.xml new file mode 100644 index 0000000..187e535 --- /dev/null +++ b/etc/csp_whitelist.xml @@ -0,0 +1,90 @@ + + + + + + *.picpay.com + *.pagar.me + *.netsgroup.com + *.sibs.pt + *.seglan.com + *.secureacs.com + *.rsa3dsauth.com + *.apata.io + *.cardinalcommerce.com + *.santander.com.br + *.bradesco.com.br + *.bradesco + *.stone.com.br + *.nubank.com.br + *.itau.com.br + *.bb.com.br + *.caixa.gov.br + *.inter.co + *.bancointer.com.br + *.c6bank.com.br + *.bancobmg.com.br + *.safra.com.br + *.sicoob.com.br + *.banrisul.com.br + *.banrisul.b.br + *.banorte.com + *.xpi.com.br + *.btgpactual.com + *.btgpactualdigital.com + *.mercadopago.com.br + *.mercadopago.com + *.amedigital.com + *.neon.tech + *.neon.com.br + *.wise.com + *.revolut.com + *.sandbox.3dsecure.io + *.google.com + + + + + *.pagar.me + *.netsgroup.com + *.sibs.pt + *.seglan.com + *.secureacs.com + *.rsa3dsauth.com + *.apata.io + *.cardinalcommerce.com + *.santander.com.br + *.bradesco.com.br + *.bradesco + *.stone.com.br + *.nubank.com.br + *.itau.com.br + *.bb.com.br + *.caixa.gov.br + *.inter.co + *.bancointer.com.br + *.c6bank.com.br + *.bancobmg.com.br + *.safra.com.br + *.sicoob.com.br + *.banrisul.com.br + *.banrisul.b.br + *.banorte.com + *.xpi.com.br + *.btgpactual.com + *.btgpactualdigital.com + *.mercadopago.com.br + *.mercadopago.com + *.picpay.com + *.amedigital.com + *.neon.tech + *.neon.com.br + *.wise.com + *.revolut.com + *.sandbox.3dsecure.io + *.google.com + + + + diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 4f3bd9f..00795e4 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -71,6 +71,8 @@ + +
@@ -86,6 +88,10 @@ + + + +
diff --git a/etc/fieldset.xml b/etc/fieldset.xml index fdf99eb..29c43e7 100644 --- a/etc/fieldset.xml +++ b/etc/fieldset.xml @@ -21,6 +21,18 @@ + + + + + + + + + + + +
diff --git a/i18n/en_US.csv b/i18n/en_US.csv index 47d254f..b2ecffa 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -135,3 +135,4 @@ Response,Response "Success Page Instructions","Success Page Instructions" "Instructions to be shown in success page, it can be HTML","Instructions to be shown in success page, it can be HTML" "Payment received.","Payment received." +"We were unable to authenticate your transaction, please try again.", "We were unable to authenticate your transaction, please try again." diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index c006b6c..5c6caf7 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -131,3 +131,4 @@ Response,Resposta "Success Page Instructions","Instruções da Página de Sucesso" "Instructions to be shown in success page, it can be HTML","Instruções para ser mostrada na página de sucesso, pode ser HTML" "Payment received.", "Pagamento recebido." +"We were unable to authenticate your transaction, please try again.", "Não foi possível autenticar sua transação. Por favor, tente novamente." diff --git a/view/base/web/js/view/info/pix.js b/view/base/web/js/view/info/pix.js index 59b6e97..96c53c0 100644 --- a/view/base/web/js/view/info/pix.js +++ b/view/base/web/js/view/info/pix.js @@ -62,7 +62,7 @@ define([ checkPaymentStatus: function () { let source = new EventSource( - urlBuilder.build('picpay_checkout/payment/status?' + 'expiration_time=' + this.expiration_time()) + urlBuilder.build('picpay_checkout/payment/status?' + 'order_id=' + this.expiration_time()) ); let self = this; source.onmessage = function(event) { diff --git a/view/frontend/web/js/credit-card/tds.js b/view/frontend/web/js/credit-card/tds.js new file mode 100644 index 0000000..99a9949 --- /dev/null +++ b/view/frontend/web/js/credit-card/tds.js @@ -0,0 +1,155 @@ +/** + * PicPay + * + * NOTICE OF LICENSE + * + * This source file is subject to the PicPay license that is + * available through the world-wide-web at this URL: + * + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade this extension to newer + * version in the future. + * + * @category PicPay + * @package PicPay_Checkout + * @copyright Copyright (c) PicPay + * + */ + +define([ + 'underscore', + 'ko', + 'jquery', + 'mage/translate', + 'mage/url', + 'Magento_Ui/js/modal/modal', + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Customer/js/customer-data' + ], function ( + _, + ko, + $, + $t, + urlBuilder, + modal, + fullScreenLoader, + customerData +) { + 'use strict'; + + return class Tds { + runTds(cardData, placeOrderCallback) { + let self = this; + cardData.additional_data.browser_data = this.getBrowserData(); + + $.ajax({ + url: urlBuilder.build('picpay_checkout/tds/enrollment'), + global: true, + data: JSON.stringify(cardData), + contentType: 'application/json', + type: 'POST', + async: true + }).done(function (data) { + if (data['cardholderAuthenticationStatus'] == 'Challenged' && data['accessToken']) { + self.setTdsIframe(data); + $('#picpay-tds-step-up-form').submit(); + $('#picpay-tds-modal').modal('openModal'); + fullScreenLoader.stopLoader(); + self.checkChallengeStatus(placeOrderCallback) + } else if (data['cardholderAuthenticationStatus'] == 'Approved' && typeof placeOrderCallback === 'function') { + $('#picpay-tds-modal').modal('closeModal'); + placeOrderCallback(data['cardholderAuthenticationStatus']); + } else { + $('#picpay-tds-modal').modal('closeModal'); + self.displayErrorMessage($t('We were unable to authenticate your transaction, please try again.')); + fullScreenLoader.stopLoader(); + } + }); + } + + initTdsModal() { + let modalOptions = { + type: 'popup', + responsive: true, + innerScroll: false, + modalClass: 'picpay-tds-modal', + buttons: [] + }; + + modal(modalOptions, $('#picpay-tds-modal')); + } + + setTdsIframe(data) { + let iframe = $('#picpay-tds-step-up-iframe'); + let modal = $('.picpay-tds-modal .modal-inner-wrap'); + + iframe.css('height', data['heightChallenge']); + modal.css('height', 'fit-content'); + + iframe.css('width', data['widthChallenge']); + modal.css('width', 'fit-content'); + + let form = $('#picpay-tds-step-up-form'); + form.attr('action', data['stepUpUrl']); + + let input = $('#picpay-tds-access-code'); + input.val(data['accessToken']); + } + + getBrowserData() { + return { + httpBrowserJavaEnabled: navigator.javaEnabled(), + httpBrowserJavaScriptEnabled: true, + httpBrowserColorDepth: screen.colorDepth, + httpBrowserScreenHeight: screen.height, + httpBrowserScreenWidth: screen.width, + httpBrowserTimeDifference: new Date().getTimezoneOffset(), + httpBrowserLanguage: navigator.language, + userAgentBrowserValue: navigator.userAgent + } + } + + checkChallengeStatus(placeOrderCallback) { + let self = this; + let challengeInterval = setInterval(function() { + $.ajax({ + url: urlBuilder.build('picpay_checkout/tds/challenge'), + type: 'GET', + dataType: 'json', + success: function (response) { + + if (response.error) { + console.error(response.message); + } else if (response.challenge_status == 'Approved') { + clearInterval(challengeInterval); + if (typeof placeOrderCallback === 'function') { + placeOrderCallback(response.challenge_status); // Continue the checkout process + } + $('#picpay-tds-modal').modal('closeModal'); + } else if (response.challenge_status == 'Rejected') { + $('#picpay-tds-modal').modal('closeModal'); + self.displayErrorMessage($t('We were unable to authenticate your transaction, please try again.')); + clearInterval(challengeInterval); + } + }, + error: function () { + clearInterval(challengeInterval); + $('#picpay-tds-modal').modal('closeModal'); + console.error('An error occurred while checking the order status.'); + } + }); + }, 2000); + } + + displayErrorMessage(message) { + customerData.set('messages', { + messages: [ + { text: message, type: 'error' } + ] + }); + customerData.reload(['messages']); + } + } +}); diff --git a/view/frontend/web/js/view/payment/method-renderer/cc.js b/view/frontend/web/js/view/payment/method-renderer/cc.js index 081de32..f327c2b 100644 --- a/view/frontend/web/js/view/payment/method-renderer/cc.js +++ b/view/frontend/web/js/view/payment/method-renderer/cc.js @@ -28,6 +28,8 @@ define([ 'Magento_Customer/js/model/customer', 'Magento_Payment/js/view/payment/cc-form', 'mage/url', + 'PicPay_Checkout/js/credit-card/tds', + 'Magento_Checkout/js/model/full-screen-loader', 'PicPay_Checkout/js/model/credit-card-validation/credit-card-number-validator', 'Magento_Payment/js/model/credit-card-validation/credit-card-data', 'picpay-cc-form', @@ -46,6 +48,8 @@ define([ customer, Component, urlBuilder, + Tds, + FullScreenLoader, cardNumberValidator, creditCardData, creditCardForm @@ -63,9 +67,11 @@ define([ showCardData: ko.observable(true), installments: ko.observableArray([]), hasInstallments: ko.observable(false), + useTdsAuthorization: ko.observable(false), installmentsUrl: '', showInstallmentsWarning: ko.observable(true), - debounceTimer: null + debounceTimer: null, + tds: '' }, /** @inheritdoc */ @@ -131,6 +137,14 @@ define([ return this; }, + initialize: function () { + this._super(); + + this.tds = new Tds(); + + return this; + }, + loadCard: function () { let ccName = document.getElementById(this.getCode() + '_cc_owner'); let ccNumber = document.getElementById(this.getCode() + '_cc_number'); @@ -152,7 +166,6 @@ define([ * @returns {Object} */ getData: function () { - let ccExpMonth = ''; let ccExpYear = ''; let ccExpDate = this.creditCardExpDate(); @@ -172,7 +185,8 @@ define([ 'cc_exp_year': ccExpYear.length === 4 ? ccExpYear : '20' + ccExpYear, 'cc_number': this.picpayCreditCardNumber(), 'cc_owner': this.creditCardOwner(), - 'installments': this.creditCardInstallments() + 'installments': this.creditCardInstallments(), + 'use_tds_authorization': this.useTdsAuthorization() } }; }, @@ -257,52 +271,44 @@ define([ } }, - canUseTds: function () { + isTdsActive: function () { return window.checkoutConfig.payment[this.getCode()].use_tds; }, - placeNotAuthorizedOrder: function () { - return window.checkoutConfig.payment[this.getCode()].place_not_authorized_order; + canUseTds: function () { + let allowedCardTypes = ['VI', 'MC', 'ELO']; + return window.checkoutConfig.payment[this.getCode()].use_tds && + allowedCardTypes.includes(this.creditCardType()); + }, + + renderTdsModal: function () { + this.tds.initTdsModal(); + }, + + canPlaceNotAuthorizedOrder: function() { + return window.checkoutConfig.payment[this.getCode()].place_not_authenticated_order; + }, + + placeOrderContinue: function(data, event, _super) { + _super(data, event); }, placeOrder: function (data, event) { + var _super = this._super.bind(this); + FullScreenLoader.startLoader(); if (event) { event.preventDefault(); } - let formData = this.getData(); - formData.additional_data.browser_data = this.getBrowserData(); if (this.canUseTds()) { - $.ajax({ - url: urlBuilder.build('picpay_checkout/tds/enrollment'), - global: true, - data: JSON.stringify(this.getData()), - contentType: 'application/json', - type: 'POST', - async: true - }).done(function (data) { - alert('success') - console.log(data) - }).fail(function (response) { - alert('fail') - console.log(response) + this.useTdsAuthorization(true); + this.tds.runTds(this.getData(), () => { + this.placeOrderContinue(data, event, _super); }); } else { + this.useTdsAuthorization(false); return this._super(data, event); } - }, - - getBrowserData: function () { - return { - httpBrowserJavaEnabled: navigator.javaEnabled(), - httpBrowserJavaScriptEnabled: true, - httpBrowserColorDepth: screen.colorDepth, - httpBrowserScreenHeight: screen.height, - httpBrowserScreenWidth: screen.width, - httpBrowserTimeDifference: new Date().getTimezoneOffset(), - httpBrowserLanguage: navigator.language, - userAgentBrowserValue: navigator.userAgent - } } }); }); diff --git a/view/frontend/web/template/payment/form/cc.html b/view/frontend/web/template/payment/form/cc.html index 14cae2f..b075713 100644 --- a/view/frontend/web/template/payment/form/cc.html +++ b/view/frontend/web/template/payment/form/cc.html @@ -222,6 +222,16 @@
+ + +
+ +
+ + +
+
+
From 18370ad925adb31a8a163fdf34076c4f7ef1dce5 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 17 Jan 2025 10:09:40 -0300 Subject: [PATCH 06/15] fix: update composer version and add translation --- composer.json | 2 +- etc/adminhtml/system/credit_card.xml | 2 +- i18n/en_US.csv | 2 ++ i18n/pt_BR.csv | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8b6697e..28d4978 100755 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "picpay/ecommerce-integration-magento2", - "version": "1.2.2", + "version": "1.3.2", "description": "N/A", "type": "magento2-module", "autoload": { diff --git a/etc/adminhtml/system/credit_card.xml b/etc/adminhtml/system/credit_card.xml index def01b1..e0c3dc7 100644 --- a/etc/adminhtml/system/credit_card.xml +++ b/etc/adminhtml/system/credit_card.xml @@ -217,7 +217,7 @@ payment/picpay_checkout_cc/max_order_total - + Magento\Config\Block\System\Config\Form\Fieldset diff --git a/i18n/en_US.csv b/i18n/en_US.csv index b2ecffa..04c1207 100644 --- a/i18n/en_US.csv +++ b/i18n/en_US.csv @@ -136,3 +136,5 @@ Response,Response "Instructions to be shown in success page, it can be HTML","Instructions to be shown in success page, it can be HTML" "Payment received.","Payment received." "We were unable to authenticate your transaction, please try again.", "We were unable to authenticate your transaction, please try again." +"Place order if authentication fails", "Place order if authentication fails" +"Order minimum amount to use 3DS", "Order minimum amount to use 3DS" diff --git a/i18n/pt_BR.csv b/i18n/pt_BR.csv index 5c6caf7..47168f1 100644 --- a/i18n/pt_BR.csv +++ b/i18n/pt_BR.csv @@ -132,3 +132,5 @@ Response,Resposta "Instructions to be shown in success page, it can be HTML","Instruções para ser mostrada na página de sucesso, pode ser HTML" "Payment received.", "Pagamento recebido." "We were unable to authenticate your transaction, please try again.", "Não foi possível autenticar sua transação. Por favor, tente novamente." +"Place order if authentication fails", "Finalizar pedido mesmo que a autenticação falhe" +"Order minimum amount to use 3DS", "Valor mínimo do pedido para usar 3DS" From 2e0b5c35bd72d1490118de3b87cc02a38d22f177 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 17 Jan 2025 15:04:26 -0300 Subject: [PATCH 07/15] fix: fix config path and not authorized validation --- Model/Ui/CreditCard/ConfigProvider.php | 2 +- view/frontend/web/js/credit-card/tds.js | 35 +++++++++++++------ .../web/js/view/payment/method-renderer/cc.js | 6 +++- .../web/template/payment/form/cc.html | 1 - 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/Model/Ui/CreditCard/ConfigProvider.php b/Model/Ui/CreditCard/ConfigProvider.php index 4070d25..a8d20d4 100644 --- a/Model/Ui/CreditCard/ConfigProvider.php +++ b/Model/Ui/CreditCard/ConfigProvider.php @@ -88,7 +88,7 @@ public function getConfig() 'icons' => $this->getPaymentIcons(), 'availableTypes' => $this->getCcAvailableTypes($methodCode), 'use_tds' => (int) $this->canUseTds($grandTotal), - 'place_not_authenticated_order' => (int) $this->helper->getConfig('place_not_authenticated_order'), + 'place_not_authenticated_order' => (int) $this->helper->getConfig('place_not_authenticated_tds'), ], 'ccform' => [ 'grandTotal' => [$methodCode => $grandTotal], diff --git a/view/frontend/web/js/credit-card/tds.js b/view/frontend/web/js/credit-card/tds.js index 99a9949..9eb1970 100644 --- a/view/frontend/web/js/credit-card/tds.js +++ b/view/frontend/web/js/credit-card/tds.js @@ -26,7 +26,8 @@ define([ 'mage/url', 'Magento_Ui/js/modal/modal', 'Magento_Checkout/js/model/full-screen-loader', - 'Magento_Customer/js/customer-data' + 'Magento_Customer/js/customer-data', + 'Magento_Ui/js/model/messageList' ], function ( _, ko, @@ -35,7 +36,8 @@ define([ urlBuilder, modal, fullScreenLoader, - customerData + customerData, + messageList ) { 'use strict'; @@ -60,7 +62,10 @@ define([ self.checkChallengeStatus(placeOrderCallback) } else if (data['cardholderAuthenticationStatus'] == 'Approved' && typeof placeOrderCallback === 'function') { $('#picpay-tds-modal').modal('closeModal'); - placeOrderCallback(data['cardholderAuthenticationStatus']); + self.placeOrder(placeOrderCallback, true); + } else if (data['cardholderAuthenticationStatus'] == 'Rejected' && self.canPlaceNotAuthorizedOrder()) { + $('#picpay-tds-modal').modal('closeModal'); + self.placeOrder(placeOrderCallback, true); } else { $('#picpay-tds-modal').modal('closeModal'); self.displayErrorMessage($t('We were unable to authenticate your transaction, please try again.')); @@ -119,15 +124,14 @@ define([ type: 'GET', dataType: 'json', success: function (response) { - if (response.error) { - console.error(response.message); + messageList.addSuccessMessage({'message': $t(response.error)}); } else if (response.challenge_status == 'Approved') { clearInterval(challengeInterval); - if (typeof placeOrderCallback === 'function') { - placeOrderCallback(response.challenge_status); // Continue the checkout process - } - $('#picpay-tds-modal').modal('closeModal'); + self.placeOrder(placeOrderCallback, true); + } else if (response.challenge_status == 'Rejected' && self.canPlaceNotAuthorizedOrder()) { + clearInterval(challengeInterval); + self.placeOrder(placeOrderCallback, false); } else if (response.challenge_status == 'Rejected') { $('#picpay-tds-modal').modal('closeModal'); self.displayErrorMessage($t('We were unable to authenticate your transaction, please try again.')); @@ -143,13 +147,24 @@ define([ }, 2000); } + placeOrder(placeOrderCallback, withTds) { + if (typeof placeOrderCallback === 'function') { + placeOrderCallback(withTds); + } + $('#picpay-tds-modal').modal('closeModal'); + } + + canPlaceNotAuthorizedOrder() { + return window.checkoutConfig.payment['picpay_checkout_cc'].place_not_authenticated_order; + } + displayErrorMessage(message) { + messageList.addErrorMessage({'message': $t(message)}); customerData.set('messages', { messages: [ { text: message, type: 'error' } ] }); - customerData.reload(['messages']); } } }); diff --git a/view/frontend/web/js/view/payment/method-renderer/cc.js b/view/frontend/web/js/view/payment/method-renderer/cc.js index f327c2b..69456ef 100644 --- a/view/frontend/web/js/view/payment/method-renderer/cc.js +++ b/view/frontend/web/js/view/payment/method-renderer/cc.js @@ -302,7 +302,11 @@ define([ if (this.canUseTds()) { this.useTdsAuthorization(true); - this.tds.runTds(this.getData(), () => { + this.tds.runTds(this.getData(), (placeOrderWithTds) => { + if (!placeOrderWithTds) { + this.useTdsAuthorization(false); + } + this.placeOrderContinue(data, event, _super); }); } else { diff --git a/view/frontend/web/template/payment/form/cc.html b/view/frontend/web/template/payment/form/cc.html index b075713..9ed1324 100644 --- a/view/frontend/web/template/payment/form/cc.html +++ b/view/frontend/web/template/payment/form/cc.html @@ -228,7 +228,6 @@
-
From bc82478d01214071327c0fdc4faac48960b75ef6 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 31 Jan 2025 09:07:05 -0300 Subject: [PATCH 08/15] fix: improve code --- Controller/Callback/Payments.php | 12 ++++++------ Controller/Tds/Challenge.php | 17 +++++++---------- Gateway/Http/Client/Transaction.php | 25 +++++++++++++++++++------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Controller/Callback/Payments.php b/Controller/Callback/Payments.php index a183e6a..24cd071 100644 --- a/Controller/Callback/Payments.php +++ b/Controller/Callback/Payments.php @@ -48,12 +48,12 @@ public function execute() $method = 'picpay-payments'; $content = isset($webhookData['type']) ? $webhookData['data'] : $webhookData; - if (isset($webhookData['type']) && $webhookData['type'] == 'THREE_DS_CHALLENGE') { - $quote = $this->helperTds->loadQuoteByChargeId($content['chargeId']); - if ($quote->getId()) { - $this->helperTds->updateQuote($quote, $content); - $statusCode = Response::STATUS_CODE_200; - } + $chargeId = $content['chargeId'] ?? $content['merchantChargeId']; + $quote = $this->helperTds->loadQuoteByChargeId($chargeId); + + if ($quote->getId()) { + $this->helperTds->updateQuote($quote, $content); + $statusCode = Response::STATUS_CODE_200; } else if (isset($content['status'])) { $chargeId = $content['merchantChargeId']; if (isset($content['status'])) { diff --git a/Controller/Tds/Challenge.php b/Controller/Tds/Challenge.php index 2619ab2..03c5cd8 100644 --- a/Controller/Tds/Challenge.php +++ b/Controller/Tds/Challenge.php @@ -53,17 +53,14 @@ public function execute() $result = $this->resultJsonFactory->create(); $quoteId = $this->checkoutSession->getQuoteId(); + $quote = $this->quoteRepository->get($quoteId); - if ($quoteId) { - $quote = $this->quoteRepository->get($quoteId); - - if ($quote->getPicpayChargeId()) { - $tdsChallengeStatus = $quote->getPicpayChallengeStatus(); - return $result->setData([ - 'challenge_status' => $tdsChallengeStatus, - 'charge_id' => $quote->getPicpayChargeId() - ]); - } + if ($quote->getPicpayChargeId()) { + $tdsChallengeStatus = $quote->getPicpayChallengeStatus(); + return $result->setData([ + 'challenge_status' => $tdsChallengeStatus, + 'charge_id' => $quote->getPicpayChargeId() + ]); } return $result->setData(['error' => true, 'message' => __('No orders found for this user.')]); diff --git a/Gateway/Http/Client/Transaction.php b/Gateway/Http/Client/Transaction.php index 3c0b317..a9e6288 100644 --- a/Gateway/Http/Client/Transaction.php +++ b/Gateway/Http/Client/Transaction.php @@ -71,12 +71,7 @@ public function placeRequest(TransferInterface $transferObject) break; default: - if ($config['use_tds']) { - $transaction = $this->api->tds()->authorization($requestBody); - $transaction['response'] = $transaction['response']['charge'] ?? $transaction['response']; - } else { - $transaction = $this->api->create()->execute($requestBody, $config['store_id']); - } + $transaction = $this->executeCardTransaction($config, $requestBody); } $this->api->logResponse($transaction, self::LOG_NAME); @@ -88,4 +83,22 @@ public function placeRequest(TransferInterface $transferObject) return ['status' => $status, 'status_code' => $statusCode, 'transaction' => $transaction['response']]; } + + /** + * @param $config + * @param $requestBody + * @return array + * @throws \Exception + */ + protected function executeCardTransaction($config, $requestBody) + { + if ($config['use_tds']) { + $transaction = $this->api->tds()->authorization($requestBody); + $transaction['response'] = $transaction['response']['charge'] ?? $transaction['response']; + } else { + $transaction = $this->api->create()->execute($requestBody, $config['store_id']); + } + + return $transaction; + } } From 04af2597606ad0cf7e5358a0c0f8ab616267258c Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 31 Jan 2025 09:23:16 -0300 Subject: [PATCH 09/15] fix: improve code --- Controller/Callback/Payments.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Controller/Callback/Payments.php b/Controller/Callback/Payments.php index 24cd071..285fb29 100644 --- a/Controller/Callback/Payments.php +++ b/Controller/Callback/Payments.php @@ -51,23 +51,21 @@ public function execute() $chargeId = $content['chargeId'] ?? $content['merchantChargeId']; $quote = $this->helperTds->loadQuoteByChargeId($chargeId); - if ($quote->getId()) { + if (isset($webhookData['type']) && $webhookData['type'] == 'THREE_DS_CHALLENGE' && $quote->getId()) { + $quote = $this->helperTds->loadQuoteByChargeId($content['chargeId']); $this->helperTds->updateQuote($quote, $content); $statusCode = Response::STATUS_CODE_200; } else if (isset($content['status'])) { - $chargeId = $content['merchantChargeId']; - if (isset($content['status'])) { - $picpayStatus = $content['status']; - $order = $this->helperOrder->loadOrderByMerchantChargeId($chargeId); - if ($order->getId()) { - $orderIncrementId = $order->getIncrementId(); - $method = $order->getPayment()->getMethod(); - $amount = $content['amount'] ? $content['amount'] / 100 : $order->getGrandTotal(); - $refundedAmount = $content['refundedAmount'] ? $content['refundedAmount'] / 100 : 0; + $picpayStatus = $content['status']; + $order = $this->helperOrder->loadOrderByMerchantChargeId($chargeId); + if ($order->getId()) { + $orderIncrementId = $order->getIncrementId(); + $method = $order->getPayment()->getMethod(); + $amount = $content['amount'] ? $content['amount'] / 100 : $order->getGrandTotal(); + $refundedAmount = $content['refundedAmount'] ? $content['refundedAmount'] / 100 : 0; - $this->helperOrder->updateOrder($order, $picpayStatus, $content, $amount, $method, true, $refundedAmount); - $statusCode = Response::STATUS_CODE_200; - } + $this->helperOrder->updateOrder($order, $picpayStatus, $content, $amount, $method, true, $refundedAmount); + $statusCode = Response::STATUS_CODE_200; } } From 5c581a55797e69c27b0c5bb9542af77b24e4042e Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Fri, 31 Jan 2025 15:48:11 -0300 Subject: [PATCH 10/15] feat: code improvments --- Controller/Callback/Payments.php | 45 +++++++++++-------- Controller/Tds/Challenge.php | 25 ++++++----- Gateway/Http/Client/Transaction.php | 7 ++- Helper/Order.php | 6 +-- Model/CheckoutTds.php | 35 ++++++++++----- view/frontend/web/js/credit-card/tds.js | 40 +++++++++++------ .../web/js/view/payment/method-renderer/cc.js | 12 +++-- 7 files changed, 101 insertions(+), 69 deletions(-) diff --git a/Controller/Callback/Payments.php b/Controller/Callback/Payments.php index 285fb29..d47c848 100644 --- a/Controller/Callback/Payments.php +++ b/Controller/Callback/Payments.php @@ -15,6 +15,7 @@ class Payments extends Callback { + public const DEFAULT_STATUS_CODE = 500; /** * @var string */ @@ -39,7 +40,7 @@ public function execute() $this->helperData->log(__('Webhook %1', __CLASS__), self::LOG_NAME); $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); - $statusCode = 500; + $statusCode = self::DEFAULT_STATUS_CODE; $orderIncrementId = ''; try { @@ -47,26 +48,21 @@ public function execute() $this->logParams($webhookData); $method = 'picpay-payments'; $content = isset($webhookData['type']) ? $webhookData['data'] : $webhookData; - $chargeId = $content['chargeId'] ?? $content['merchantChargeId']; - $quote = $this->helperTds->loadQuoteByChargeId($chargeId); - if (isset($webhookData['type']) && $webhookData['type'] == 'THREE_DS_CHALLENGE' && $quote->getId()) { - $quote = $this->helperTds->loadQuoteByChargeId($content['chargeId']); - $this->helperTds->updateQuote($quote, $content); + $statusCode = $this->processTds($webhookData, $chargeId, $content, $statusCode); + if ( + isset($content['status']) + && $statusCode === self::DEFAULT_STATUS_CODE + && $order = $this->helperOrder->loadOrderByMerchantChargeId($chargeId) + ) { + $status = $content['status']; + $orderIncrementId = $order->getIncrementId(); + $method = $order->getPayment()->getMethod(); + $amount = $content['amount'] ? $content['amount'] / 100 : $order->getGrandTotal(); + $refundedAmount = $content['refundedAmount'] ? $content['refundedAmount'] / 100 : 0; + $this->helperOrder->updateOrder($order, $status, $content, $amount, $method, true, $refundedAmount); $statusCode = Response::STATUS_CODE_200; - } else if (isset($content['status'])) { - $picpayStatus = $content['status']; - $order = $this->helperOrder->loadOrderByMerchantChargeId($chargeId); - if ($order->getId()) { - $orderIncrementId = $order->getIncrementId(); - $method = $order->getPayment()->getMethod(); - $amount = $content['amount'] ? $content['amount'] / 100 : $order->getGrandTotal(); - $refundedAmount = $content['refundedAmount'] ? $content['refundedAmount'] / 100 : 0; - - $this->helperOrder->updateOrder($order, $picpayStatus, $content, $amount, $method, true, $refundedAmount); - $statusCode = Response::STATUS_CODE_200; - } } /** @var \PicPay\Checkout\Model\Callback $callBack */ @@ -77,11 +73,22 @@ public function execute() $callBack->setPayload($this->helperData->jsonEncode($content)); $this->callbackResourceModel->save($callBack); } catch (\Exception $e) { - $statusCode = 500; + $statusCode = self::DEFAULT_STATUS_CODE; $this->helperData->getLogger()->error($e->getMessage()); } $result->setHttpResponseCode($statusCode); return $result; } + + public function processTds(array $webhookData, string $chargeId, string $content, int $statusCode): int + { + $quote = $this->helperTds->loadQuoteByChargeId($chargeId); + if (isset($webhookData['type']) && $webhookData['type'] == 'THREE_DS_CHALLENGE' && $quote->getId()) { + $quote = $this->helperTds->loadQuoteByChargeId($chargeId); + $this->helperTds->updateQuote($quote, $content); + $statusCode = Response::STATUS_CODE_200; + } + return $statusCode; + } } diff --git a/Controller/Tds/Challenge.php b/Controller/Tds/Challenge.php index 03c5cd8..6a95749 100644 --- a/Controller/Tds/Challenge.php +++ b/Controller/Tds/Challenge.php @@ -38,8 +38,7 @@ public function __construct( Json $json, JsonFactory $resultJsonFactory, QuoteRepository $quoteRepository - ) - { + ) { $this->checkoutSession = $checkoutSession; $this->json = $json; $this->resultJsonFactory = $resultJsonFactory; @@ -50,17 +49,21 @@ public function __construct( public function execute() { - $result = $this->resultJsonFactory->create(); + try { + $result = $this->resultJsonFactory->create(); - $quoteId = $this->checkoutSession->getQuoteId(); - $quote = $this->quoteRepository->get($quoteId); + $quoteId = $this->checkoutSession->getQuoteId(); + $quote = $this->quoteRepository->get($quoteId); - if ($quote->getPicpayChargeId()) { - $tdsChallengeStatus = $quote->getPicpayChallengeStatus(); - return $result->setData([ - 'challenge_status' => $tdsChallengeStatus, - 'charge_id' => $quote->getPicpayChargeId() - ]); + if ($quote->getPicpayChargeId()) { + $tdsChallengeStatus = $quote->getPicpayChallengeStatus(); + return $result->setData([ + 'challenge_status' => $tdsChallengeStatus, + 'charge_id' => $quote->getPicpayChargeId() + ]); + } + } catch (\Exception $e) { + return $result->setData(['error' => true, 'message' => $e->getMessage()]); } return $result->setData(['error' => true, 'message' => __('No orders found for this user.')]); diff --git a/Gateway/Http/Client/Transaction.php b/Gateway/Http/Client/Transaction.php index a9e6288..8f13873 100644 --- a/Gateway/Http/Client/Transaction.php +++ b/Gateway/Http/Client/Transaction.php @@ -90,15 +90,14 @@ public function placeRequest(TransferInterface $transferObject) * @return array * @throws \Exception */ - protected function executeCardTransaction($config, $requestBody) + protected function executeCardTransaction($config, $requestBody): array { if ($config['use_tds']) { $transaction = $this->api->tds()->authorization($requestBody); $transaction['response'] = $transaction['response']['charge'] ?? $transaction['response']; - } else { - $transaction = $this->api->create()->execute($requestBody, $config['store_id']); + return $transaction; } - return $transaction; + return $this->api->create()->execute($requestBody, $config['store_id']); } } diff --git a/Helper/Order.php b/Helper/Order.php index b0b2ac2..9736b7e 100644 --- a/Helper/Order.php +++ b/Helper/Order.php @@ -425,15 +425,15 @@ public function getStatusState(string $status): string /** * @param string $chargeId - * @return SalesOrder + * @return SalesOrder|false */ - public function loadOrderByMerchantChargeId(string $chargeId): SalesOrder + public function loadOrderByMerchantChargeId(string $chargeId): SalesOrder|false { $order = $this->orderFactory->create(); if ($chargeId) { $order->loadByAttribute('picpay_charge_id', $chargeId); } - return $order; + return $order->getId() ? $order : false; } /** diff --git a/Model/CheckoutTds.php b/Model/CheckoutTds.php index 8ca6dbb..31a3e61 100644 --- a/Model/CheckoutTds.php +++ b/Model/CheckoutTds.php @@ -89,18 +89,7 @@ public function runTdsRequest($data) if ($setup['response']['chargeId']) { $enrollment = $this->runEnrollment($setup['response']['chargeId'], $paymentData); - - if (isset($enrollment['response']['chargeId']) && isset($enrollment['response']['transactions'][0])) { - $transaction = $enrollment['response']['transactions'][0]; - $this->checkoutSession->setPicPayTdsChallengeStatus($transaction['cardholderAuthenticationStatus']); - - $quote = $this->checkoutSession->getQuote(); - $quote->setPicpayChargeId($enrollment['response']['chargeId']); - $quote->setPicpayChallengeStatus($transaction['cardholderAuthenticationStatus']); - $quote->setPicpayMerchantId($setup['response']['transactions'][0]['cardholderAuthenticationId']); - $quote->setPicpayCardholderAuthId($setup['response']['transactions'][0]['cardholderAuthenticationId']); - $this->quoteRepository->save($quote); - } + $this->processEnrollment($enrollment, $setup['response']['transactions'][0]['cardholderAuthenticationId']); } return $enrollment; @@ -182,4 +171,26 @@ public function runEnrollment($chargeId, $paymentData) throw new \Exception('Error trying to create 3DS setup on PicPay'); } + + /** + * @param mixed $enrollment + * @param $cardholderAuthenticationId + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function processEnrollment($enrollment, $cardholderAuthenticationId): void + { + if (isset($enrollment['response']['chargeId']) && isset($enrollment['response']['transactions'][0])) { + $transaction = $enrollment['response']['transactions'][0]; + $this->checkoutSession->setPicPayTdsChallengeStatus($transaction['cardholderAuthenticationStatus']); + + $quote = $this->checkoutSession->getQuote(); + $quote->setPicpayChargeId($enrollment['response']['chargeId']); + $quote->setPicpayChallengeStatus($transaction['cardholderAuthenticationStatus']); + $quote->setPicpayMerchantId($cardholderAuthenticationId); + $quote->setPicpayCardholderAuthId($cardholderAuthenticationId); + $this->quoteRepository->save($quote); + } + } } diff --git a/view/frontend/web/js/credit-card/tds.js b/view/frontend/web/js/credit-card/tds.js index 9eb1970..300e948 100644 --- a/view/frontend/web/js/credit-card/tds.js +++ b/view/frontend/web/js/credit-card/tds.js @@ -59,18 +59,23 @@ define([ $('#picpay-tds-step-up-form').submit(); $('#picpay-tds-modal').modal('openModal'); fullScreenLoader.stopLoader(); - self.checkChallengeStatus(placeOrderCallback) - } else if (data['cardholderAuthenticationStatus'] == 'Approved' && typeof placeOrderCallback === 'function') { - $('#picpay-tds-modal').modal('closeModal'); - self.placeOrder(placeOrderCallback, true); - } else if (data['cardholderAuthenticationStatus'] == 'Rejected' && self.canPlaceNotAuthorizedOrder()) { - $('#picpay-tds-modal').modal('closeModal'); + self.checkChallengeStatus(placeOrderCallback); + return; + } + + if (data['cardholderAuthenticationStatus'] == 'Approved') { self.placeOrder(placeOrderCallback, true); - } else { - $('#picpay-tds-modal').modal('closeModal'); - self.displayErrorMessage($t('We were unable to authenticate your transaction, please try again.')); - fullScreenLoader.stopLoader(); + return; } + + if (data['cardholderAuthenticationStatus'] == 'Rejected' && self.canPlaceNotAuthorizedOrder()) { + self.placeOrder(placeOrderCallback, false); + return; + } + + $('#picpay-tds-modal').modal('closeModal'); + self.displayErrorMessage($t('We were unable to authenticate your transaction, please try again.')); + fullScreenLoader.stopLoader(); }); } @@ -126,13 +131,22 @@ define([ success: function (response) { if (response.error) { messageList.addSuccessMessage({'message': $t(response.error)}); - } else if (response.challenge_status == 'Approved') { + return; + } + + if (response.challenge_status == 'Approved') { clearInterval(challengeInterval); self.placeOrder(placeOrderCallback, true); - } else if (response.challenge_status == 'Rejected' && self.canPlaceNotAuthorizedOrder()) { + return; + } + + if (response.challenge_status == 'Rejected' && self.canPlaceNotAuthorizedOrder()) { clearInterval(challengeInterval); self.placeOrder(placeOrderCallback, false); - } else if (response.challenge_status == 'Rejected') { + return; + } + + if (response.challenge_status == 'Rejected') { $('#picpay-tds-modal').modal('closeModal'); self.displayErrorMessage($t('We were unable to authenticate your transaction, please try again.')); clearInterval(challengeInterval); diff --git a/view/frontend/web/js/view/payment/method-renderer/cc.js b/view/frontend/web/js/view/payment/method-renderer/cc.js index 69456ef..4f4a13a 100644 --- a/view/frontend/web/js/view/payment/method-renderer/cc.js +++ b/view/frontend/web/js/view/payment/method-renderer/cc.js @@ -303,16 +303,14 @@ define([ if (this.canUseTds()) { this.useTdsAuthorization(true); this.tds.runTds(this.getData(), (placeOrderWithTds) => { - if (!placeOrderWithTds) { - this.useTdsAuthorization(false); - } - + this.useTdsAuthorization(placeOrderWithTds); this.placeOrderContinue(data, event, _super); }); - } else { - this.useTdsAuthorization(false); - return this._super(data, event); + return; } + + this.useTdsAuthorization(false); + return this._super(data, event); } }); }); From a200781c5a64f727caaca08f78ebcea5e7b9d3fc Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 31 Jan 2025 16:06:55 -0300 Subject: [PATCH 11/15] fix: change param type to array --- Controller/Callback/Payments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/Callback/Payments.php b/Controller/Callback/Payments.php index d47c848..b24dbe3 100644 --- a/Controller/Callback/Payments.php +++ b/Controller/Callback/Payments.php @@ -81,7 +81,7 @@ public function execute() return $result; } - public function processTds(array $webhookData, string $chargeId, string $content, int $statusCode): int + public function processTds(array $webhookData, string $chargeId, array $content, int $statusCode): int { $quote = $this->helperTds->loadQuoteByChargeId($chargeId); if (isset($webhookData['type']) && $webhookData['type'] == 'THREE_DS_CHALLENGE' && $quote->getId()) { From 4d10a52e3f120b3eab28dade06f68d58ba16e4c4 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Fri, 7 Mar 2025 13:39:57 -0300 Subject: [PATCH 12/15] feat: set Magento as caller-origin --- Gateway/Http/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gateway/Http/Client.php b/Gateway/Http/Client.php index e0a0d18..ab7eced 100644 --- a/Gateway/Http/Client.php +++ b/Gateway/Http/Client.php @@ -79,7 +79,7 @@ protected function getDefaultHeaders(): array { return [ 'Content-Type' => 'application/json', - 'caller-origin' => 'M2-v' . $this->helper->getModuleVersion() + 'caller-origin' => 'Magento' ]; } From 46b031c5646ad22e95c4667de7392c0520c48711 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Fri, 9 May 2025 14:47:20 -0300 Subject: [PATCH 13/15] fix: added some data to sse endpoint and fix typo --- Controller/Tds/Challenge.php | 4 ++++ view/frontend/templates/payment/info/cc.phtml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Controller/Tds/Challenge.php b/Controller/Tds/Challenge.php index 6a95749..6321a11 100644 --- a/Controller/Tds/Challenge.php +++ b/Controller/Tds/Challenge.php @@ -58,6 +58,10 @@ public function execute() if ($quote->getPicpayChargeId()) { $tdsChallengeStatus = $quote->getPicpayChallengeStatus(); return $result->setData([ + 'challenge_hash' => hash_hmac('sha256', $quote->getPicpayChargeId(), $quote->getId()), + 'challenge_customer_email' => $quote->getCustomerEmail(), + 'challenge_customer_name' => $quote->getCustomerFirstname() . ' ' . $quote->getCustomerLastname(), + 'challenge_customer_address' => $quote->getBillingAddress()->getStreet(), 'challenge_status' => $tdsChallengeStatus, 'charge_id' => $quote->getPicpayChargeId() ]); diff --git a/view/frontend/templates/payment/info/cc.phtml b/view/frontend/templates/payment/info/cc.phtml index 5ed5e70..0d0a6cf 100644 --- a/view/frontend/templates/payment/info/cc.phtml +++ b/view/frontend/templates/payment/info/cc.phtml @@ -27,7 +27,7 @@ */ $specificInfo = $block->getSpecificInformation(); $title = $block->escapeHtml($block->getMethod()->getTitle()); -?>zz +?>
From 27c5833890bc2b664590a45a34d57330446f75a7 Mon Sep 17 00:00:00 2001 From: Thiago Contardi Date: Fri, 9 May 2025 14:53:16 -0300 Subject: [PATCH 14/15] fix: change #Ds challenge verification to POST to avoid Varnish cache --- Controller/Tds/Challenge.php | 4 ++-- view/frontend/web/js/credit-card/tds.js | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Controller/Tds/Challenge.php b/Controller/Tds/Challenge.php index 6321a11..5e2f7f3 100644 --- a/Controller/Tds/Challenge.php +++ b/Controller/Tds/Challenge.php @@ -4,7 +4,7 @@ use Magento\Checkout\Model\Session; use Magento\Framework\App\Action\Action; -use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\CsrfAwareActionInterface; use Magento\Framework\App\Request\InvalidRequestException; use Magento\Framework\App\RequestInterface; @@ -14,7 +14,7 @@ use Magento\Backend\App\Action\Context; use Magento\Quote\Model\QuoteRepository; -class Challenge extends Action implements HttpGetActionInterface, CsrfAwareActionInterface +class Challenge extends Action implements HttpPostActionInterface, CsrfAwareActionInterface { /** @var Json */ protected $json; diff --git a/view/frontend/web/js/credit-card/tds.js b/view/frontend/web/js/credit-card/tds.js index 300e948..8fee69d 100644 --- a/view/frontend/web/js/credit-card/tds.js +++ b/view/frontend/web/js/credit-card/tds.js @@ -126,8 +126,11 @@ define([ let challengeInterval = setInterval(function() { $.ajax({ url: urlBuilder.build('picpay_checkout/tds/challenge'), - type: 'GET', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({}), dataType: 'json', + async: true, success: function (response) { if (response.error) { messageList.addSuccessMessage({'message': $t(response.error)}); @@ -158,7 +161,7 @@ define([ console.error('An error occurred while checking the order status.'); } }); - }, 2000); + }, 1500); } placeOrder(placeOrderCallback, withTds) { From d92074654b42b7ea45b4f6291102634d72eac8b4 Mon Sep 17 00:00:00 2001 From: Janaina Silva Date: Wed, 17 Sep 2025 12:49:46 -0300 Subject: [PATCH 15/15] fix: adjust qr code display --- view/frontend/web/css/checkout.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/frontend/web/css/checkout.less b/view/frontend/web/css/checkout.less index d1aa74a..351926c 100644 --- a/view/frontend/web/css/checkout.less +++ b/view/frontend/web/css/checkout.less @@ -23,7 +23,7 @@ margin-right: auto; } .pix-barcode-container { - text-align: center; + justify-self: center; margin: 10px 0; .pix-img-code {