From 4c2f96c4b04a2ac55593f12a286d1d83da7f841e Mon Sep 17 00:00:00 2001 From: Afterpay Plugins Date: Wed, 24 Mar 2021 16:29:21 +1100 Subject: [PATCH] Release version 3.4.0 --- Block/Catalog/Installments.php | 122 ++-- Block/Info.php | 2 +- Block/JsConfig.php | 26 +- CHANGELOG.md | 23 +- Controller/Payment/Express.php | 527 ++++++++++++++++++ Model/Adapter/Afterpay/Call.php | 11 +- Model/Adapter/AfterpayExpressPayment.php | 422 ++++++++++++++ Model/Adapter/V2/AfterpayOrderAuthRequest.php | 10 +- .../Adapter/V2/AfterpayOrderDirectCapture.php | 9 +- Model/Adapter/V2/AfterpayOrderTokenV2.php | 148 ++++- Model/Config/Payovertime.php | 29 +- Model/Config/Source/CartMode.php | 31 ++ composer.json | 4 +- etc/adminhtml/system.xml | 8 +- etc/module.xml | 2 +- view/frontend/templates/afterpay/cart.phtml | 66 ++- .../frontend/templates/afterpay/product.phtml | 7 +- view/frontend/web/css/afterpay.css | 8 + .../web/js/view/cart/afterpay-cart.js | 92 ++- .../web/js/view/product/afterpay-products.js | 29 +- 20 files changed, 1430 insertions(+), 146 deletions(-) create mode 100644 Controller/Payment/Express.php create mode 100644 Model/Adapter/AfterpayExpressPayment.php create mode 100644 Model/Config/Source/CartMode.php diff --git a/Block/Catalog/Installments.php b/Block/Catalog/Installments.php index 482ffe4..cb01f20 100644 --- a/Block/Catalog/Installments.php +++ b/Block/Catalog/Installments.php @@ -7,28 +7,42 @@ */ namespace Afterpay\Afterpay\Block\Catalog; -use Magento\Framework\Registry as Registry; +use Afterpay\Afterpay\Block\JsConfig; use Afterpay\Afterpay\Model\Config\Payovertime as AfterpayConfig; use Afterpay\Afterpay\Model\Payovertime as AfterpayPayovertime; -use Magento\Framework\View\Element\Template\Context; use Magento\Framework\Locale\Resolver as Resolver; +use Magento\Framework\Registry as Registry; +use Magento\Framework\View\Element\Template\Context; -class Installments extends \Afterpay\Afterpay\Block\JsConfig +class Installments extends JsConfig { - - protected $registry; - protected $afterpayConfig; - protected $afterpayPayovertime; + /** + * @var Registry + */ + private $registry; + + /** + * @var AfterpayConfig + */ + private $afterpayConfig; + + /** + * @var AfterpayPayovertime + */ + private $afterpayPayovertime; + + /** + * @var Resolver + */ private $localeResolver; /** - * Installments constructor. * @param Context $context - * @param AfterpayConfig $afterpayConfig - * @param AfterpayPayovertime $afterpayPayovertime * @param Registry $registry * @param AfterpayConfig $afterpayConfig + * @param AfterpayPayovertime $afterpayPayovertime * @param array $data + * @param Resolver $localeResolver */ public function __construct( Context $context, @@ -42,63 +56,46 @@ public function __construct( $this->afterpayConfig = $afterpayConfig; $this->afterpayPayovertime = $afterpayPayovertime; $this->localeResolver = $localeResolver; - parent::__construct($afterpayConfig,$context, $localeResolver,$data); + parent::__construct($afterpayConfig, $context, $localeResolver, $data); } /** * @return bool */ - protected function _getPaymentIsActive() + public function canShow(): bool { - return $this->afterpayConfig->isActive(); - } - - /** - * @return bool - */ - public function canShow() - { // check if payment is active - if (!$this->_getPaymentIsActive()) { - return false; + if ($this->_getPaymentIsActive() && + $this->afterpayConfig->getCurrencyCode() && + $this->afterpayPayovertime->canUseForCurrency($this->afterpayConfig->getCurrencyCode()) + ) { + $excluded_categories = $this->afterpayConfig->getExcludedCategories(); + if ($excluded_categories != "") { + $excluded_categories_array = explode(",", $excluded_categories); + $product = $this->registry->registry('product'); + $categoryids = $product->getCategoryIds(); + foreach ($categoryids as $k) { + if (in_array($k, $excluded_categories_array)) { + return false; + } + } + } + + return true; } - else{ - if($this->afterpayConfig->getCurrencyCode()){ - if($this->afterpayPayovertime->canUseForCurrency($this->afterpayConfig->getCurrencyCode())){ - $excluded_categories=$this->afterpayConfig->getExcludedCategories(); - if($excluded_categories!=""){ - $excluded_categories_array = explode(",",$excluded_categories); - $product = $this->registry->registry('product'); - $categoryids = $product->getCategoryIds(); - foreach($categoryids as $k) - { - if(in_array($k,$excluded_categories_array)){ - return false; - } - } - } - return true; - } - else{ - return false; - } - } - else { - return false; - } - } - } - - /** + return false; + } + + /** * @return string */ public function getTypeOfProduct() { $product = $this->registry->registry('product'); - return $product->getTypeId(); + return $product->getTypeId(); } - + /** * @return string */ @@ -106,13 +103,13 @@ public function getFinalAmount() { // get product $product = $this->registry->registry('product'); - + // set if final price is exist - $price = $product->getFinalPrice(); - - return !empty($price)?number_format($price, 2,".",""):"0.00"; - - } + $price = $product->getPriceInfo()->getPrice('final_price')->getValue(); + + return !empty($price) ? number_format($price, 2, ".", "") : "0.00"; + } + /** * @return boolean */ @@ -120,13 +117,10 @@ public function canUseCurrency() { $canUse=false; //Check for Supported currency - if($this->afterpayConfig->getCurrencyCode()) - { + if ($this->afterpayConfig->getCurrencyCode()) { $canUse= $this->afterpayPayovertime->canUseForCurrency($this->afterpayConfig->getCurrencyCode()); - } - + } + return $canUse; - } - } diff --git a/Block/Info.php b/Block/Info.php index c35df35..48886a4 100644 --- a/Block/Info.php +++ b/Block/Info.php @@ -25,7 +25,7 @@ protected function _prepareSpecificInformation($transport = null) $info = $this->getInfo(); // load the data available on additional informations - if ($this->_appState->getAreaCode() === \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE + if ($this->getArea() === \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE && $info->getAdditionalInformation() ) { foreach ($info->getAdditionalInformation() as $field => $value) { diff --git a/Block/JsConfig.php b/Block/JsConfig.php index 000362a..d18057b 100644 --- a/Block/JsConfig.php +++ b/Block/JsConfig.php @@ -72,7 +72,13 @@ protected function _getPaymentIsActive() */ public function getCurrentLocale() { - return $this->localeResolver->getLocale(); // eg. fr_CA + $currentLocale=$this->localeResolver->getLocale(); + $country_code=$this->_payOverTime->getCurrentCountryCode(); + if(!empty($country_code) && stripos($currentLocale,$country_code)=== false){ + $currentLocale="en_".strtoupper($country_code); + } + + return $currentLocale; // eg. fr_CA } /** @@ -124,15 +130,21 @@ public function isDisplayOnProductPage() /** * check if payment is active for cart page * - * @return bool + * @return int */ public function isDisplayOnCartPage() { - $isEnabledForCartPage=true; - if (!$this->_payOverTime->isEnabledForCartPage()) { - $isEnabledForCartPage= false; - } - return $isEnabledForCartPage; + return $this->_payOverTime->isEnabledForCartPage(); } + /** + * Get Express Checkout JS URL + * + * @return bool|string + */ + public function getAfterpayECJsUrl() + { + $express_checkout_key=$this->_payOverTime->getExpressCheckoutKey(); + return $this->_payOverTime->getWebUrl('afterpay.js',array("merchant_key"=>!empty($express_checkout_key)?$express_checkout_key:"")); + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index c943afa..7c6ab1b 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Afterpay Magento 2 Extension Changelog +## Version 3.4.0 + +_Wed 24 Mar 2021_ + +### Supported Editions & Versions + +Tested and verified in clean installations of Magento 2: + +- Magento Enterprise Edition (EE) version 2.4.2 + +### Highlights + +- Introduced an implementation of Express Checkout. +- Improved instalment calculations by adding coverage for additional scenarios. +- Improved the asset placement on PDP and cart pages. +- Improved invoice payment context to remove unnecessary details from consumer order notifications. + +--- + ## Version 3.3.0 _Wed 13 Jan 2021_ @@ -14,8 +33,8 @@ Tested and verified in clean installations of Magento 2: ### Highlights - Implemented the JS Library for asset placement on PDP and Cart page. -- Added new admin options to enable/disable display of Afterpay/Clearpay assets on PDP and Cart page. -- Moved the Afterpay/Clearpay PDP assets for “Bundle” products. +- Added new admin options to enable/disable display of Afterpay assets on PDP and Cart page. +- Moved the Afterpay PDP assets for “Bundle” products. - Improved Cross Border Trade (CBT) configuration to update automatically in sync with the nominated Merchant account. - Improved support for multi-currency configurations. - Improved compatibility with PHP 7.4. diff --git a/Controller/Payment/Express.php b/Controller/Payment/Express.php new file mode 100644 index 0000000..6bf395c --- /dev/null +++ b/Controller/Payment/Express.php @@ -0,0 +1,527 @@ +_checkoutSession = $checkoutSession; + $this->_objectManager = $objectManager; + $this->_quoteFactory = $quoteFactory; + $this->_afterpayConfig = $afterpayConfig; + $this->_afterpayOrderTokenV2 = $afterpayOrderTokenV2; + $this->_tokenCheck = $tokenCheck; + $this->_jsonHelper = $jsonHelper; + $this->_helper = $helper; + $this->_quoteRepository = $quoteRepository; + $this->_jsonResultFactory = $jsonResultFactory; + $this->_quoteValidator = $quoteValidator; + $this->_directCapture = $directCapture; + $this->_authRequest = $authRequest; + $this->_afterpayApiPayment = $afterpayApiPayment; + $this->_quoteManagement = $quoteManagement; + $this->_expressPayment=$expressPayment; + $this->_timezone = $timezone; + + parent::__construct($context); + } + + public function execute() + { + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => 1, + 'message' => "Invalid Request." + ]); + if ($this->_afterpayConfig->getPaymentAction() == AbstractMethod::ACTION_AUTHORIZE_CAPTURE) { + $action = strtolower($this->getRequest()->getParam('action')); + switch ($action) { + case "start": + $result = $this->_start(); + break; + case "change": + $result = $this->_change(); + break; + case "confirm": + $result = $this->_confirm(); + break; + } + } + return $result; + } + + /** + * Initialize the Express Checkout + */ + public function _start() + { + $this->_helper->debug("================= In Initialiazation========="); + // need to load the correct quote by store + $data = $this->_checkoutSession->getData(); + + $quote = $this->_checkoutSession->getQuote(); + $websiteId = $this->_afterpayConfig->getStoreObjectFromRequest()->getWebsiteId(); + + if ($websiteId > 1) { + $quote = $this->_quoteFactory->create()->loadByIdWithoutStore($data["quote_id_" . $websiteId]); + } + + $customerSession = $this->_objectManager->get('Magento\Customer\Model\Session'); + $customerRepository = $this->_objectManager->get('Magento\Customer\Api\CustomerRepositoryInterface'); + + if ($customerSession->isLoggedIn()) { + $customerId = $customerSession->getCustomer()->getId(); + $customer = $customerRepository->getById($customerId); + + // customer login + $quote->setCustomer($customer); + } else { + $quote->setCustomerIsGuest(true)->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID); + } + + $payment = $quote->getPayment(); + + $payment->setMethod(\Afterpay\Afterpay\Model\Payovertime::METHOD_CODE); + + $quote->reserveOrderId(); + + try { + $payment = $this->_expressPayment->getAfterPayExpressOrderToken($this->_afterpayOrderTokenV2, $payment, $quote); + } catch (\Exception $e) { + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => 1, + 'message' => $e->getMessage() + ]); + + return $result; + } + + $quote->setPayment($payment); + + $this->_quoteRepository->save($quote); + $this->_checkoutSession->replaceQuote($quote); + + $token = $payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ADDITIONAL_INFORMATION_KEY_TOKEN); + + $result = $this->_jsonResultFactory->create()->setData([ + 'success' => true, + 'token' => $token + ]); + + return $result; + } + + /** + * Shipping Address Change callback + */ + public function _change() + { + $this->_helper->debug("================= In Shipping Change========="); + $data = $this->_checkoutSession->getData(); + $customerData = $this->getRequest()->getPostValue(); + $quote = $this->_checkoutSession->getQuote(); + $websiteId = $this->_afterpayConfig->getStoreObjectFromRequest()->getWebsiteId(); + + if ($websiteId > 1) { + $quote = $this->_objectManager->get('Magento\Checkout\Model\Session') + ->getQuote() + ->load($data["quote_id_" . $websiteId]); + } + + $shippingAddress = $quote->getShippingAddress(); + + if (! empty($customerData) && ! $quote->isVirtual()) { + // Set first name & lastname in shipping address + $fullName = explode(' ', $customerData["name"]); + $lastName = array_pop($fullName); + if (count($fullName) == 0) { + // if $customerData["name"] contains only one word + $firstName = $lastName; + } else { + + $firstName = implode(' ', $fullName); + } + + $shippingAddress->setFirstName($firstName); + $shippingAddress->setLastName($lastName); + $shippingAddress->setStreet(array( + $customerData['address1'], + $customerData['address2'] + )); + $shippingAddress->setCountryId($customerData['countryCode']); + $shippingAddress->setCity($customerData['suburb']); + $shippingAddress->setPostcode($customerData['postcode']); + $shippingAddress->setRegionId($this->_expressPayment->getRegionId($customerData['state'], $customerData['countryCode'])); + $shippingAddress->setRegion($customerData['state']); + $shippingAddress->setTelephone($customerData['phoneNumber']); + $shippingAddress->setCollectShippingRates(true); + + $this->_quoteRepository->save($quote); + $this->_checkoutSession->replaceQuote($quote); + } + + $shippingData = $this->_expressPayment->getShippingDetails($quote); + $shippingList = array(); + if (! empty($shippingData)) { + foreach ($shippingData as $rateData) { + $shippingAmount = $rateData->getBaseAmount(); + $taxAmount = $shippingAddress->getBaseTaxAmount(); + $orderAmount = $quote->getBaseSubtotalWithDiscount() + $shippingAmount + $taxAmount; + if($this->_expressPayment->isValidOrderAmount($orderAmount)){ + $carrierCode = $rateData->getCarrierCode(); + $methodCode = $rateData->getMethodCode(); + $shippingOptions['id'] = $carrierCode . "_" . $methodCode; + $shippingOptions['name'] = $rateData->getCarrierTitle(); + $shippingOptions['description'] = $rateData->getCarrierTitle(); + + + $shippingOptions['shippingAmount'] = array( + 'amount' => $this->_expressPayment->formatAmount($shippingAmount), + 'currency' => $quote->getStoreCurrencyCode() + ); + + + $shippingOptions['taxAmount'] = array( + 'amount' => $this->_expressPayment->formatAmount($taxAmount), + 'currency' => $quote->getBaseCurrencyCode() + ); + + + $shippingOptions['orderAmount'] = array( + 'amount' => $this->_expressPayment->formatAmount($orderAmount), + 'currency' => $quote->getBaseCurrencyCode() + ); + + $shippingList[] = $shippingOptions; + } + } + } + $this->_helper->debug("Shipping Estimation Rates", $shippingList); + if (! empty($shippingList)) { + $result = $result = $this->_jsonResultFactory->create()->setData([ + 'success' => true, + 'shippingOptions' => $shippingList + ]); + } elseif ($quote->isVirtual()) { + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => true, + 'message' => "Shipping option is not required for virtual product." + ]); + } else { + + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => true, + 'message' => "Shipping is unavailable for this address, or all options exceed Afterpay order limit." + ]); + } + return $result; + } + + + /** + * Place the order + */ + public function _confirm() + { + $this->_helper->debug("================= In Confirm============"); + try { + $responseData = $this->getRequest()->getParams(); + $this->_helper->debug("EC Response: ", $responseData); + switch ($responseData['status']) { + case \Afterpay\Afterpay\Model\Response::RESPONSE_STATUS_CANCELLED: + $this->messageManager->addError(__('You have cancelled your Afterpay payment. Please select an alternative payment method.')); + break; + case \Afterpay\Afterpay\Model\Response::RESPONSE_STATUS_SUCCESS: + + $quote = $this->_checkoutSession->getQuote(); + + $payment = $quote->getPayment(); + + $token = $responseData["orderToken"]; + $merchant_order_id = $quote->getReservedOrderId(); + + $orderResponse = $this->_tokenCheck->generate($token); + $orderData = $this->_jsonHelper->jsonDecode($orderResponse->getBody()); + + /** + * Validation to check between session and post request + */ + if (! $orderData || ! empty($orderData['errorCode'])) { + // Check the order token being use + throw new \Magento\Framework\Exception\LocalizedException(__('There are issues when processing your payment. Invalid Token')); + } elseif ($this->_expressPayment->isCartUpdated($quote, $orderData['items'])) { + // Check cart Items + throw new \Magento\Framework\Exception\LocalizedException(__('There are issues when processing your payment. Invalid Cart Items')); + } + $this->_expressPayment->setOrderData($orderData); + $quote = $this->_checkoutSession->getQuote(); + $this->_quoteValidator->validateBeforeSubmit($quote); + + $baseOrderTotal = $quote->getBaseGrandTotal(); + $orderAmount = array( + 'amount' => $this->_expressPayment->formatAmount($baseOrderTotal), + 'currency' => $quote->getBaseCurrencyCode() + ); + // Process payment + if (! $this->_helper->getConfig('payment/afterpaypayovertime/payment_flow') || $this->_helper->getConfig('payment/afterpaypayovertime/payment_flow') == "immediate" || $quote->getIsVirtual()) { + + $this->_helper->debug("Starting Payment Capture request."); + + $paymentResponse = $this->_directCapture->generate($token, $merchant_order_id, $orderAmount); + } else { + + $this->_helper->debug("Starting Auth request."); + $paymentResponse = $this->_authRequest->generate($token, $merchant_order_id, $orderAmount); + } + + $paymentResponse = $this->_jsonHelper->jsonDecode($paymentResponse->getBody()); + + if (empty($paymentResponse['status'])) { + $paymentResponse['status'] = \Afterpay\Afterpay\Model\Response::RESPONSE_STATUS_DECLINED; + $this->_helper->debug("_confirm: Transaction Exception (Empty Response): " . json_encode($paymentResponse)); + } + + switch ($paymentResponse['status']) { + case \Afterpay\Afterpay\Model\Response::RESPONSE_STATUS_APPROVED: + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ADDITIONAL_INFORMATION_KEY_ORDERID, $paymentResponse['id']); + + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::PAYMENT_STATUS, $paymentResponse['paymentState']); + + if ($paymentResponse['paymentState'] == \Afterpay\Afterpay\Model\Response::PAYMENT_STATUS_AUTH_APPROVED && array_key_exists('events', $paymentResponse)) { + try { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::AUTH_EXPIRY, $this->_timezone->date($paymentResponse['events'][0]['expires']) + ->format('Y-m-d H:i T')); + } catch (\Exception $e) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::AUTH_EXPIRY, $this->_timezone->date($paymentResponse['events'][0]['expires'], null, false) + ->format('Y-m-d H:i T')); + $this->_helper->debug($e->getMessage()); + } + } + + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::OPEN_TOCAPTURE_AMOUNT, array_key_exists('openToCaptureAmount', $paymentResponse) && ! empty($paymentResponse['openToCaptureAmount']) ? $paymentResponse['openToCaptureAmount']['amount'] : "0.00"); + + $this->_checkoutSession->setLastQuoteId($quote->getId()) + ->setLastSuccessQuoteId($quote->getId()) + ->clearHelperData(); + + // Store Customer email address in temporary variable + $customerEmailAddress = $quote->getCustomerEmail(); + + // Create Order From Quote + + $quote->collectTotals(); + + // Restore Customer email address if it becomes null/blank + if (empty($quote->getCustomerEmail())) { + $quote->setCustomerEmail($customerEmailAddress); + } + + // Catch the deadlock exception while creating the order and retry 3 times + + $tries = 0; + do { + $retry = false; + + try { + // Create order in Magento + $this->_helper->debug("Trying Order Creation. Try number:" . $tries); + $order = $this->_quoteManagement->submit($quote); + } catch (\Exception $e) { + + if (preg_match('/SQLSTATE\[40001\]: Serialization failure: 1213 Deadlock found/', $e->getMessage()) && $tries < 2) { + $this->_helper->debug("Waiting for a second before retrying the Order Creation"); + $retry = true; + sleep(1); + } else { + // Reverse or void the order + $orderId = $payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ADDITIONAL_INFORMATION_KEY_ORDERID); + $paymentStatus = $payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::PAYMENT_STATUS); + + if ($paymentStatus == \Afterpay\Afterpay\Model\Response::PAYMENT_STATUS_AUTH_APPROVED) { + $voidResponse = $this->_afterpayApiPayment->voidOrder($orderId); + $voidResponse = $this->_jsonHelper->jsonDecode($voidResponse->getBody()); + + if (! array_key_exists("errorCode", $voidResponse)) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::PAYMENT_STATUS, $voidResponse['paymentState']); + + if (array_key_exists('openToCaptureAmount', $voidResponse) && ! empty($voidResponse['openToCaptureAmount'])) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::OPEN_TOCAPTURE_AMOUNT, $voidResponse['openToCaptureAmount']['amount']); + } + + $this->_helper->debug('Order Exception : There was a problem with order creation. Afterpay Order ' . $orderId . ' Voided.' . $e->getMessage()); + throw new \Magento\Framework\Exception\LocalizedException(__('There was a problem placing your order. Your Afterpay order ' . $orderId . ' is refunded.')); + } else { + $this->_helper->debug("_confirm:Transaction Exception : " . json_encode($voidResponse)); + throw new \Magento\Framework\Exception\LocalizedException(__('There was a problem placing your order.')); + } + } else { + // $orderTotal = $quote->getGrandTotal(); + $refundResponse = $this->_afterpayApiPayment->refund($this->_expressPayment->formatAmount($baseOrderTotal), $orderId, $quote->getBaseCurrencyCode()); + + $refundResponse = $this->_jsonHelper->jsonDecode($refundResponse->getBody()); + + if (! empty($refundResponse['refundId'])) { + $this->_helper->debug('Order Exception : There was a problem with order creation. Afterpay Order ' . $orderId . ' refunded.' . $e->getMessage()); + throw new \Magento\Framework\Exception\LocalizedException(__('There was a problem placing your order. Your Afterpay order ' . $orderId . ' is refunded.')); + } else { + $this->_helper->debug("_confirm:Transaction Exception : " . json_encode($refundResponse)); + throw new \Magento\Framework\Exception\LocalizedException(__('There was a problem placing your order.')); + } + } + } + } + $tries ++; + } while ($tries < 3 && $retry); + + if ($order) { + + $payment = $order->getPayment(); + + if ($payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::PAYMENT_STATUS) == \Afterpay\Afterpay\Model\Response::PAYMENT_STATUS_AUTH_APPROVED) { + $totalDiscount = $this->_expressPayment->calculateTotalDiscount($order); + if ($totalDiscount > 0) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ROLLOVER_DISCOUNT, $this->_expressPayment->calculateTotalDiscount($order)); + } + $this->_expressPayment->captureVirtual($order, $payment); + } + + $this->_checkoutSession->setLastOrderId($order->getId()) + ->setLastRealOrderId($order->getIncrementId()) + ->setLastOrderStatus($order->getStatus()); + + $this->_expressPayment->createTransaction($order, $paymentResponse, $payment); + + $this->_helper->debug("Afterpay Transaction Completed"); + $result = $this->_jsonResultFactory->create()->setData([ + 'success' => true + ]); + } else { + $result = $this->_jsonResultFactory->create()->setData([ + 'success' => true + ]); + $this->_helper->debug("Order Exception : There was a problem with order creation."); + } + break; + case \Afterpay\Afterpay\Model\Response::RESPONSE_STATUS_DECLINED: + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => true, + 'message' =>'Afterpay payment declined. Please select an alternative payment method.' + ]); + $this->messageManager->addError(__('Afterpay payment declined. Please select an alternative payment method.')); + break; + default: + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => true, + 'message' =>$paymentResponse + ]); + $this->messageManager->addError($paymentResponse); + break; + } + break; + } + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => true, + 'message' =>$e->getMessage() + ]); + $this->_helper->debug("_confirm : Transaction Exception: " . $e->getMessage()); + $this->messageManager->addError($e->getMessage()); + } catch (\Exception $e) { + $result = $this->_jsonResultFactory->create()->setData([ + 'error' => true, + 'message' =>"There was a problem in placing your order." + ]); + $this->_helper->debug("_confirm : Transaction Exception: " . $e->getMessage()); + $this->messageManager->addError("There was a problem in placing your order."); + } + return $result; + } + + + + +} diff --git a/Model/Adapter/Afterpay/Call.php b/Model/Adapter/Afterpay/Call.php index d932442..5bb1bf5 100644 --- a/Model/Adapter/Afterpay/Call.php +++ b/Model/Adapter/Afterpay/Call.php @@ -112,9 +112,9 @@ public function send($url, $body = false, $method = \Magento\Framework\HTTP\Zend if (!empty($override['website_id'])) { - $url = $this->getWebsiteUrl($override['website_id']); + $storeUrl = $this->getWebsiteUrl($override['website_id']); } else { - $url = $this->getWebsiteUrl(); + $storeUrl = $this->getWebsiteUrl(); } // set configurations @@ -122,7 +122,7 @@ public function send($url, $body = false, $method = \Magento\Framework\HTTP\Zend [ 'timeout' => 80, 'maxredirects' => 0, - 'useragent' => 'AfterpayMagento2Plugin ' . $this->helper->getModuleVersion() . ' (' . $description . ' ' . $version . ')' . ' PHPVersion: PHP/' . phpversion() . ' MerchantID: ' . trim($this->afterpayConfig->getMerchantId($override) . ' URL: ' . $url) + 'useragent' => 'AfterpayMagento2Plugin ' . $this->helper->getModuleVersion() . ' (' . $description . ' ' . $version . ')' . ' PHPVersion: PHP/' . phpversion() . ' MerchantID: ' . trim($this->afterpayConfig->getMerchantId($override) . ' URL: ' . $storeUrl) ] ); @@ -133,7 +133,8 @@ public function send($url, $body = false, $method = \Magento\Framework\HTTP\Zend 'url' => $url, 'body' => $this->obfuscateCustomerData($body) ]; - $this->helper->debug($this->jsonHelper->jsonEncode($requestLog)); + + $this->helper->debug('Request', $requestLog); // do the request with catch try { @@ -158,7 +159,7 @@ public function send($url, $body = false, $method = \Magento\Framework\HTTP\Zend 'httpStatusCode' => $response->getStatus(), 'body' => $this->obfuscateCustomerData($responseBody) ]; - $this->helper->debug($this->jsonHelper->jsonEncode($responseLog)); + $this->helper->debug('Response', $responseLog); } catch (\Exception $e) { $this->helper->debug($e->getMessage()); diff --git a/Model/Adapter/AfterpayExpressPayment.php b/Model/Adapter/AfterpayExpressPayment.php new file mode 100644 index 0000000..9f248c7 --- /dev/null +++ b/Model/Adapter/AfterpayExpressPayment.php @@ -0,0 +1,422 @@ +_checkoutSession = $checkoutSession; + $this->_orderRepository = $orderRepository; + $this->_quoteFactory = $quoteFactory; + $this->_afterpayOrderTokenV2 = $afterpayOrderTokenV2; + $this->_jsonHelper = $jsonHelper; + $this->_helper = $helper; + $this->_quoteRepository = $quoteRepository; + $this->_totalsCollector = $totalsCollector; + $this->_converter = $converter; + $this->_paymentCapture = $paymentCapture; + $this->_transactionRepository = $transactionRepository; + $this->_objectManager = $objectManager; + $this->_transactionBuilder = $transactionBuilder; + $this->_paymentRepository = $paymentRepository; + $this->_payOverTime = $payovertime; + + } + /** + * + * @param $afterpayOrderToken + * @param $payment + * @param $targetObject + * @return bool + * @throws LocalizedException + * + */ + public function getAfterPayExpressOrderToken($afterpayOrderToken, $payment, $targetObject) + { + if ($targetObject && $targetObject->getReservedOrderId()) { + $result = $afterpayOrderToken->generate($targetObject, [ + 'merchantOrderId' => $targetObject->getReservedOrderId(), + 'mode' => \Afterpay\Afterpay\Model\Config\Source\CartMode::EXPRESS_CHECKOUT + ]); + } elseif ($targetObject) { + $result = $afterpayOrderToken->generate($targetObject, [ + 'mode' => \Afterpay\Afterpay\Model\Config\Source\CartMode::EXPRESS_CHECKOUT + ]); + } + + $result = $this->_jsonHelper->jsonDecode($result->getBody(), true); + $orderToken = array_key_exists('token', $result) ? $result['token'] : false; + + if ($orderToken) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ADDITIONAL_INFORMATION_KEY_TOKEN, $orderToken); + } else { + $this->_helper->debug('No Token response from API'); + throw new \Magento\Framework\Exception\LocalizedException(__('There is an issue processing your order.')); + } + return $payment; + } + + /** + * Get Region Id + * + * @param string $stateCode + * @param string $countryCode + * @return int + */ + public function getRegionId($stateCode, $countryCode) + { + $region = $this->_objectManager->create('Magento\Directory\Model\Region'); + + return $region->loadByCode($stateCode, $countryCode)->getId(); + } + + /** + * Get list of available shipping methods + * + * @param \Magento\Quote\Model\Quote $quote + * @return \Magento\Quote\Api\Data\ShippingMethodInterface[] + */ + + public function getShippingDetails($quote) + { + $output = []; + if (! $quote->isVirtual()) { + $shippingAddress = $quote->getShippingAddress(); + $shippingAddress->setCollectShippingRates(true); + + $this->_totalsCollector->collectAddressTotals($quote, $shippingAddress); + $shippingRates = $shippingAddress->getGroupedAllShippingRates(); + foreach ($shippingRates as $carrierRates) { + foreach ($carrierRates as $rate) { + $output[] = $this->_converter->modelToDataObject($rate, $quote->getQuoteCurrencyCode()); + } + } + } + return $output; + + } + + + + /** + * Format amount upto 2 decimal + * + * @param + * $amount + * @return float + */ + public function formatAmount($amount) + { + return number_format($amount, 2, '.', ''); + } + + /* + * Is cart Updated + * @param \Magento\Quote\Model\Quote $quoteData + * @param Array $responseItems + * + * @return bool + */ + public function isCartUpdated($quoteData, $responseItems) + { + $isCartupdated = false; + $quoteItems = $quoteData->getAllItems(); + if ($quoteData->getItemsCount()!= count($responseItems)) { + $isCartupdated = true; + $this->_helper->debug('Cart Items count does not match. Quote Count : '.$quoteData->getItemsCount().' & Response count : '.count($responseItems)); + } else { + foreach ($quoteItems as $items) { + $itemFound = array_search($items->getSku(), array_column($responseItems, 'sku')); + if ($itemFound === false) { + $this->_helper->debug('Cart Items '.$items->getSku().' does not match.'); + $isCartupdated = true; + break; + } + continue; + } + } + return $isCartupdated; + } + + /** + * Update Order databased on response + */ + public function setOrderData($data) + { + $quote = $this->_checkoutSession->getQuote(); + $billingAddress = $quote->getBillingAddress(); + $shippingAddress = $quote->getShippingAddress(); + + // Set first name & lastname in shipping address + $fullName = explode(' ', $data['shipping']['name']); + $lastName = array_pop($fullName); + if (count($fullName) == 0) { + // if $order['shipping']['name'] contains only one word + $firstName = $lastName; + } else { + $firstName = implode(' ', $fullName); + } + + // Set Customer Data + $customerSession = $this->_objectManager->get('Magento\Customer\Model\Session'); + $customerRepository = $this->_objectManager->get('Magento\Customer\Api\CustomerRepositoryInterface'); + + if ($customerSession->isLoggedIn()) { + $customerId = $customerSession->getCustomer()->getId(); + $customer = $customerRepository->getById($customerId); + + // customer login + $quote->setCustomer($customer); + + // Set Billing Details + if (empty($billingAddress) || empty($billingAddress->getStreetLine(1)) || empty($billingAddress->getFirstname())) { + $billingID = $customerSession->getCustomer()->getDefaultBilling(); + $this->_helper->debug("No billing address found. Adding the Customer's default billing address."); + $address = $this->_objectManager->create('Magento\Customer\Model\Address')->load($billingID); + $billingAddress->addData($address->getData()); + } + } else { + + $quote->setCustomerEmail($data['consumer']['email']) + ->setCustomerIsGuest(true) + ->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID) + ->setCustomerFirstname($data['consumer']['givenNames']) + ->setCustomerLastname($data['consumer']['surname']); + + // Set Billing Details + + $billingAddress->setFirstname($firstName) + ->setLastname($lastName) + ->setEmail($data['consumer']['email']) + ->setTelephone($data['shipping']['phoneNumber']) + ->setStreet(array( + $data['shipping']['line1'], + isset($data['shipping']['line2']) ? $data['shipping']['line2'] : null + )) + ->setCity($data['shipping']['area1']) + ->setRegion($data['shipping']['region']) + ->setRegionId($this->getRegionId($data['shipping']['region'], $data['shipping']['countryCode'])) + ->setPostcode($data['shipping']['postcode']) + ->setCountryId($data['shipping']['countryCode']); + + // Set flag for same billing and shipping address + $shippingAddress->setSameAsBilling(1); + } + + // Set Shipping Details + if (! $quote->isVirtual()) { + $shippingAddress->setFirstname($firstName) + ->setLastname($lastName) + ->setEmail($data['consumer']['email']) + ->setTelephone($data['shipping']['phoneNumber']) + ->setStreet(array( + $data['shipping']['line1'], + isset($data['shipping']['line2']) ? $data['shipping']['line2'] : null + )) + ->setCity($data['shipping']['area1']) + ->setRegion($data['shipping']['region']) + ->setRegionId($this->getRegionId($data['shipping']['region'], $data['shipping']['countryCode'])) + ->setPostcode($data['shipping']['postcode']) + ->setCountryId($data['shipping']['countryCode']) + ->setShippingMethod($data['shippingOptionIdentifier']) + ->setAddressType('shipping') + ->setPaymentMethod(\Afterpay\Afterpay\Model\Payovertime::METHOD_CODE); + } else { + $billingAddress->setPaymentMethod(\Afterpay\Afterpay\Model\Payovertime::METHOD_CODE); + } + + $quote->collectTotals(); + + $this->_quoteRepository->save($quote); + $this->_checkoutSession->replaceQuote($quote); + } + + /* + * Calculate Total Discount for the given order + */ + public function calculateTotalDiscount($order) + { + $storeCredit = $order->getCustomerBalanceAmount(); + $giftCardAmount = $order->getGiftCardsAmount(); + $totalDiscountAmount = $storeCredit + $giftCardAmount; + return number_format($totalDiscountAmount, 2, '.', ''); + } + + /* + * Capture payment for Virtal products + */ + public function captureVirtual($order = null, $payment = null) + { + $totalCaptureAmount = 0.00; + + foreach ($order->getAllItems() as $items) { + if ($items->getIsVirtual()) { + $itemPrice = ($items->getQtyOrdered() * $items->getPrice()) + $items->getBaseTaxAmount(); + $totalCaptureAmount = $totalCaptureAmount + ($itemPrice - $items->getDiscountAmount()); + } + } + + $totalDiscountAmount = $payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ROLLOVER_DISCOUNT); + + if ($totalDiscountAmount != 0) { + if ($totalCaptureAmount >= $totalDiscountAmount) { + $totalCaptureAmount = $totalCaptureAmount - $totalDiscountAmount; + $totalDiscountAmount = 0.00; + } else if ($totalCaptureAmount < $totalDiscountAmount) { + $totalDiscountAmount = $totalDiscountAmount - $totalCaptureAmount; + $totalCaptureAmount = 0.00; + } + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ROLLOVER_DISCOUNT, number_format($totalDiscountAmount, 2, '.', '')); + } + + if ($totalCaptureAmount >= 1) { + $afterpay_order_id = $payment->getAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ADDITIONAL_INFORMATION_KEY_ORDERID); + $merchant_order_id = $order->getIncrementId(); + $currencyCode = $order->getOrderCurrencyCode(); + + $totalAmount = [ + 'amount' => number_format($totalCaptureAmount, 2, '.', ''), + 'currency' => $currencyCode + ]; + + $response = $this->_paymentCapture->send($totalAmount, $merchant_order_id, $afterpay_order_id); + $response = $this->_jsonHelper->jsonDecode($response->getBody()); + + if (! array_key_exists("errorCode", $response)) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::PAYMENT_STATUS, $response['paymentState']); + if (array_key_exists('openToCaptureAmount', $response) && ! empty($response['openToCaptureAmount'])) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::OPEN_TOCAPTURE_AMOUNT, $response['openToCaptureAmount']['amount']); + } + } else { + $this->_helper->debug("_captureVirtual : Transaction Exception : " . json_encode($response)); + } + } else { + if ($totalCaptureAmount < 1 && $totalCaptureAmount > 0) { + $payment->setAdditionalInformation(\Afterpay\Afterpay\Model\Payovertime::ROLLOVER_AMOUNT, number_format($totalCaptureAmount, 2, '.', '')); + } + } + } + + /* + * Create Transaction + */ + public function createTransaction($order = null, $paymentData = [], $payment = null) + { + try { + $payment->setLastTransId($paymentData['id']); + $payment->setTransactionId($paymentData['id']); + $formatedPrice = $order->getBaseCurrency()->formatTxt($order->getGrandTotal()); + + $message = __('The authorized amount is %1.', $formatedPrice); + // get the object of builder class + $trans = $this->_transactionBuilder; + $transaction = $trans->setPayment($payment) + ->setOrder($order) + ->setTransactionId($paymentData['id']) + ->setFailSafe(true) + -> + // build method creates the transaction and returns the object + build(\Magento\Sales\Model\Order\Payment\Transaction::TYPE_CAPTURE); + + $payment->addTransactionCommentsToOrder($transaction, $message); + $payment->setParentTransactionId(null); + $this->_paymentRepository->save($payment); + + $order->setBaseCustomerBalanceInvoiced(null); + $order->setCustomerBalanceInvoiced(null); + $this->_orderRepository->save($order); + + $transaction = $this->_transactionRepository->save($transaction); + return $transaction->getTransactionId(); + } catch (\Exception $e) { + // log errors here + $this->_helper->debug("_createTransaction : Transaction Exception: There was a problem with creating the transaction. " . $e->getMessage()); + } + } + + /* + * Validate order amount + */ + public function isValidOrderAmount($orderAmount) + { + $max_limit=$this->formatAmount($this->_payOverTime->getMaxOrderLimit()); + $min_limit= $this->formatAmount($this->_payOverTime->getMinOrderLimit()); + $orderTotal=$this->formatAmount($orderAmount); + return ($orderTotal<=$max_limit && $orderTotal>=$min_limit); + + } + +} diff --git a/Model/Adapter/V2/AfterpayOrderAuthRequest.php b/Model/Adapter/V2/AfterpayOrderAuthRequest.php index c419a40..8bcbec4 100644 --- a/Model/Adapter/V2/AfterpayOrderAuthRequest.php +++ b/Model/Adapter/V2/AfterpayOrderAuthRequest.php @@ -58,9 +58,9 @@ public function __construct( * @return mixed|\Zend_Http_Response * @throws \Magento\Framework\Exception\LocalizedException */ - public function generate($token, $merchant_order_id) + public function generate($token, $merchant_order_id,$orderAmount=null) { - $requestData = $this->_buildAuthRequest($token, $merchant_order_id); + $requestData = $this->_buildAuthRequest($token, $merchant_order_id,$orderAmount); try { $response = $this->afterpayApiCall->send( @@ -83,12 +83,14 @@ public function generate($token, $merchant_order_id) * @param $merchant_order_id * @return array */ - protected function _buildAuthRequest($token, $merchant_order_id) + protected function _buildAuthRequest($token, $merchant_order_id,$orderAmount=null) { $params['requestId'] = uniqid(); $params['merchantReference'] = $merchant_order_id; $params['token'] = $token; - + if(!is_null($orderAmount)){ + $params['amount'] = $orderAmount; + } return $params; } } diff --git a/Model/Adapter/V2/AfterpayOrderDirectCapture.php b/Model/Adapter/V2/AfterpayOrderDirectCapture.php index 2c7e909..3da9157 100644 --- a/Model/Adapter/V2/AfterpayOrderDirectCapture.php +++ b/Model/Adapter/V2/AfterpayOrderDirectCapture.php @@ -58,9 +58,9 @@ public function __construct( * @return mixed|\Zend_Http_Response * @throws \Magento\Framework\Exception\LocalizedException */ - public function generate($token, $merchant_order_id) + public function generate($token, $merchant_order_id,$orderAmount=null) { - $requestData = $this->_buildDirectCaptureRequest($token, $merchant_order_id); + $requestData = $this->_buildDirectCaptureRequest($token, $merchant_order_id,$orderAmount); try { $response = $this->afterpayApiCall->send( @@ -84,10 +84,13 @@ public function generate($token, $merchant_order_id) * @param $merchant_order_id * @return array */ - protected function _buildDirectCaptureRequest($token, $merchant_order_id) + protected function _buildDirectCaptureRequest($token, $merchant_order_id,$orderAmount=null) { $params['merchantReference'] = $merchant_order_id; $params['token'] = $token; + if(!is_null($orderAmount)){ + $params['amount'] = $orderAmount; + } return $params; } diff --git a/Model/Adapter/V2/AfterpayOrderTokenV2.php b/Model/Adapter/V2/AfterpayOrderTokenV2.php index fed1061..3fbf125 100644 --- a/Model/Adapter/V2/AfterpayOrderTokenV2.php +++ b/Model/Adapter/V2/AfterpayOrderTokenV2.php @@ -44,6 +44,7 @@ class AfterpayOrderTokenV2 protected $_countryFactory; protected $_scopeConfig; protected $listStateRequired; + private $expressCheckout; /** * AfterpayOrderToken constructor. @@ -78,6 +79,8 @@ public function __construct( $this->_countryFactory = $countryFactory; $this->_scopeConfig = $scopeConfig; $this->listStateRequired = $this->_getStateRequired(); + $this->expressCheckout=\Afterpay\Afterpay\Model\Config\Source\CartMode::EXPRESS_CHECKOUT; + } /** @@ -89,10 +92,20 @@ public function __construct( */ public function generate($object,$override = []) { - $requestData = $this->_buildOrderTokenRequest($object, $override); - $targetUrl = $this->_afterpayConfig->getApiUrl('v2/checkouts/', null, $override); - $requestData = $this->constructPayload($requestData, $this->listStateRequired); - $this->handleValidation($requestData); + if(isset($override["mode"]) && ($override["mode"]==$this->expressCheckout)){ + $requestData = $this->_buildExpressOrderTokenRequest($object, $override); + }else{ + $requestData = $this->_buildOrderTokenRequest($object, $override); + } + $targetUrl = $this->_afterpayConfig->getApiUrl('v2/checkouts/', null, $override); + $this->_helper->debug("Request :",$requestData); + if(isset($override["mode"]) && ($override["mode"]==$this->expressCheckout)){ + $this->handleExpressValidation($requestData); + }else{ + $requestData = $this->constructPayload($requestData, $this->listStateRequired); + $this->handleValidation($requestData); + } + $response = $this->performTransaction($requestData, $targetUrl); return $response; } @@ -183,6 +196,26 @@ public function handleValidation($requestData) } } + public function handleExpressValidation($requestData) + { + $errors = []; + + if (empty($requestData['amount'])) { + $errors[] = 'Amount is required'; + } + if (empty($requestData['mode'])) { + $errors[] = 'Mode is required'; + } + if(empty($requestData['merchant']['popupOriginUrl'])){ + $errors[] = "Merchant's origin url is required"; + } + + if (count($errors)) { + throw new \Magento\Framework\Exception\LocalizedException(__(implode(' ; ', $errors))); + } else { + return true; + } + } /** * Build object for order token @@ -296,4 +329,111 @@ protected function _buildOrderTokenRequest($object, $override = []) return $params; } + + /** + * Build object for Express order token + * + * @param \Magento\Sales\Model\Order $object Order to get token for + * @param $code + * @param array $override + * @return array + */ + protected function _buildExpressOrderTokenRequest($object, $override = []) + { + $precision = self::DECIMAL_PRECISION; + $data = $object->getData(); + $billingAddress = $object->getBillingAddress(); + $shippingAddress = $object->getShippingAddress(); + + $email = $object->getCustomerEmail(); + + $params['mode'] ="express"; + + $params['merchantReference'] = array_key_exists('merchantOrderId', $override) ? $override['merchantOrderId'] : $object->getIncrementId(); + + $params['merchant'] = [ + 'popupOriginUrl' => $this->_storeManagerInterface->getStore($object->getStore()->getId())->getBaseUrl() . 'checkout/cart' + ]; + + foreach ($object->getAllVisibleItems() as $item) { + if (!$item->getParentItem()) { + + $product = $this->_productRepositoryInterface->getById($item->getProductId()); + $category_ids = $product->getCategoryIds(); + $imageHelper = $this->_objectManagerInterface->get('\Magento\Catalog\Helper\Image'); + + $categories=[]; + if(count($category_ids) > 0){ + foreach($category_ids as $category){ + $cat = $this->_objectManagerInterface->create('Magento\Catalog\Model\Category')->load($category); + array_push($categories,$cat->getName()); + } + } + $params['items'][] = [ + 'name' => (string)$item->getName(), + 'sku' => (string)$item->getSku(), + 'pageUrl' => $product->getProductUrl(), + 'imageUrl' => $imageHelper->init($product, 'product_page_image_small')->setImageFile($product->getFile())->getUrl(), + 'quantity' => (int)$item->getQty(), + 'price' => [ + 'amount' => round((float)$item->getBasePriceInclTax(), $precision), + 'currency' => (string)$data['store_currency_code'] + ], + 'categories' => [$categories] + ]; + } + } + if ($object->getShippingInclTax()) { + $params['shippingAmount'] = [ + 'amount' => round((float)$object->getBaseShippingInclTax(), $precision), // with tax + 'currency' => (string)$data['store_currency_code'] + ]; + } + if (isset($data['discount_amount'])) { + $params['discounts']['displayName'] = 'Discount'; + $params['orderDetail']['amount'] = [ + 'amount' => round((float)$data['base_discount_amount'], $precision), + 'currency' => (string)$data['store_currency_code'] + ]; + } + $taxAmount = array_key_exists('base_tax_amount', $data) ? $data['base_tax_amount'] : $shippingAddress->getBaseTaxAmount(); + $params['taxAmount'] = [ + 'amount' => isset($taxAmount) ? round((float)$taxAmount, $precision) : 0, + 'currency' => (string)$data['store_currency_code'] + ]; + + if(!empty($shippingAddress) && !empty($shippingAddress->getStreetLine(1))) + { + $params['shipping'] = [ + 'name' => (string)$shippingAddress->getFirstname() . ' ' . $shippingAddress->getLastname(), + 'line1' => (string)$shippingAddress->getStreetLine(1), + 'line2' => (string)$shippingAddress->getStreetLine(2), + 'area1' => (string)$shippingAddress->getCity(), + 'area2' => "", + 'postcode' => (string)$shippingAddress->getPostcode(), + 'region' => (string)$shippingAddress->getRegion(), + 'countryCode' => (string)$shippingAddress->getCountryId(), + 'phoneNumber' => (string)$shippingAddress->getTelephone(), + ]; + } + if(!empty($billingAddress) && !empty($billingAddress->getStreetLine(1))){ + $params['billing'] = [ + 'name' => (string)$billingAddress->getFirstname() . ' ' . $billingAddress->getLastname(), + 'line1' => (string)$billingAddress->getStreetLine(1), + 'line2' => (string)$billingAddress->getStreetLine(2), + 'area1' => (string)$billingAddress->getCity(), + 'area2' => "", + 'postcode' => (string)$billingAddress->getPostcode(), + 'region' => (string)$billingAddress->getRegion(), + 'countryCode' => (string)$billingAddress->getCountryId(), + 'phoneNumber' => (string)$billingAddress->getTelephone(), + ]; + } + $params['amount'] = [ + 'amount' => round((float)$object->getBaseGrandTotal(), $precision), + 'currency' => (string)$data['store_currency_code'], + ]; + + return $params; + } } diff --git a/Model/Config/Payovertime.php b/Model/Config/Payovertime.php index b7227ff..2ae82d5 100644 --- a/Model/Config/Payovertime.php +++ b/Model/Config/Payovertime.php @@ -39,6 +39,7 @@ class Payovertime const CBT_COUNTRY = 'cbt_country'; const ENABLE_FOR_PRODUCT_PAGE= "enable_for_product_page"; const ENABLE_FOR_CART_PAGE = "enable_for_cart_page"; + const EXPRESS_CHECKOUT_KEY = "express_checkout_key"; /** * @var ApiMode @@ -397,10 +398,34 @@ public function isEnabledForProductDisplayPage() } /** - * @return bool + * @return int */ public function isEnabledForCartPage() { - return (bool)$this->_getConfigData(self::ENABLE_FOR_CART_PAGE); + return $this->_getConfigData(self::ENABLE_FOR_CART_PAGE); } + + /** + * Calculated the currency code + * + * @return $text + */ + public function getCurrentCountryCode() + { + $websiteId=$this->getWebsiteId();; + $countryCode = $this->scopeConfig->getValue('general/country/default', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES,$websiteId); + return $countryCode; + } + + /** + * Get config for express checkout key + * + * @return string + */ + public function getExpressCheckoutKey() + { + return $this->_getConfigData(self::EXPRESS_CHECKOUT_KEY); + } + + } diff --git a/Model/Config/Source/CartMode.php b/Model/Config/Source/CartMode.php new file mode 100644 index 0000000..6f6648a --- /dev/null +++ b/Model/Config/Source/CartMode.php @@ -0,0 +1,31 @@ + self::MAGENTO_CHECKOUT, 'label' => __('Yes - Magento Checkout')], + ['value' => self::EXPRESS_CHECKOUT, 'label' => __('Yes - Express Checkout')], + ['value' => self::DISABLED, 'label' => __('No')], + ]; + } +} diff --git a/composer.json b/composer.json index 74822ee..5f98038 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "license" : "OSL-3.0", "type" : "magento2-module", "description" : "Magento 2 Afterpay Payment Module", - "version" : "3.3.0", + "version" : "3.4.0", "authors" : [{ "name" : "Afterpay", "homepage" : "https://www.afterpay.com" @@ -17,4 +17,4 @@ "Afterpay\\Afterpay\\" : "" } } -} +} \ No newline at end of file diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 6c59ece..4379895 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -132,9 +132,15 @@ - Magento\Config\Model\Config\Source\Yesno + Afterpay\Afterpay\Model\Config\Source\CartMode payment/afterpaypayovertime/enable_for_cart_page + + + + + payment/afterpaypayovertime/express_checkout_key + diff --git a/etc/module.xml b/etc/module.xml index 4d29068..219a96d 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -8,7 +8,7 @@ */ --> - + diff --git a/view/frontend/templates/afterpay/cart.phtml b/view/frontend/templates/afterpay/cart.phtml index 86e6dfa..0f91c47 100644 --- a/view/frontend/templates/afterpay/cart.phtml +++ b/view/frontend/templates/afterpay/cart.phtml @@ -5,43 +5,61 @@ * @author Afterpay * @copyright 2016-2020 Afterpay https://www.afterpay.com */ - -if($block->isPaymentMethodActive() && $block->isDisplayOnCartPage() && $block->canUseCurrency() ){ +$cartDisplayMode = (int) $block->isDisplayOnCartPage(); +$data_amount = $block->getFinalAmount(); +if ($block->isPaymentMethodActive() && $cartDisplayMode != \Afterpay\Afterpay\Model\Config\Source\CartMode::DISABLED && $block->canUseCurrency() && $data_amount>0) { $afterpay_eligible = "true"; if (($block->canShow() === false)) { $afterpay_eligible = "false"; } - - $min_limit=$block->getMinOrderLimit(); - $max_limit=$block->getMaxOrderLimit(); - $show_lower_limit="true"; - if((float)$min_limit<1){ - $show_lower_limit="false"; + + $min_limit = $block->getMinOrderLimit(); + $max_limit = $block->getMaxOrderLimit(); + $show_lower_limit = "true"; + if ((float) $min_limit < 1) { + $show_lower_limit = "false"; } - ?> - + + +getCurrentCurrency(); $data_locale = $block->getCurrentLocale(); - $data_amount = $block->getFinalAmount(); + $enable_cbt = $this->helper('Afterpay\Afterpay\Helper\Data')->getConfig('payment/afterpaypayovertime/enable_cbt'); $data_enable_cbt = ! empty($enable_cbt) ? "true" : "false"; ?> - -=$min_limit && $data_amount<=$max_limit && $afterpay_eligible!="false" ){?> - - -= $min_limit && $data_amount <= $max_limit && $afterpay_eligible != "false" ) { + if ($cartDisplayMode == \Afterpay\Afterpay\Model\Config\Source\CartMode::EXPRESS_CHECKOUT) { +?> + + + + + + +} +?> diff --git a/view/frontend/templates/afterpay/product.phtml b/view/frontend/templates/afterpay/product.phtml index 207568e..110c0f8 100644 --- a/view/frontend/templates/afterpay/product.phtml +++ b/view/frontend/templates/afterpay/product.phtml @@ -5,9 +5,10 @@ * @author Afterpay * @copyright 2016-2020 Afterpay https://www.afterpay.com */ +/** @var \Afterpay\Afterpay\Block\Catalog\Installments $block */ $product_type = $block->getTypeOfProduct(); - -if($block->isPaymentMethodActive() && $block->isDisplayOnProductPage() && $product_type != "grouped" && $block->canUseCurrency()){ +$data_amount = $block->getFinalAmount(); +if($block->isPaymentMethodActive() && $block->isDisplayOnProductPage() && $product_type != "grouped" && $block->canUseCurrency() && $data_amount>0){ $afterpay_eligible = "true"; if (($block->canShow() === false)) { $afterpay_eligible = "false"; @@ -25,7 +26,7 @@ if($block->isPaymentMethodActive() && $block->isDisplayOnProductPage() && $produ getCurrentCurrency(); $data_locale = $block->getCurrentLocale(); - $data_amount = $block->getFinalAmount(); + $enable_cbt = $this->helper('Afterpay\Afterpay\Helper\Data')->getConfig('payment/afterpaypayovertime/enable_cbt'); $data_enable_cbt = ! empty($enable_cbt) ? "true" : "false"; ?> diff --git a/view/frontend/web/css/afterpay.css b/view/frontend/web/css/afterpay.css index eeae4c1..cca9a56 100644 --- a/view/frontend/web/css/afterpay.css +++ b/view/frontend/web/css/afterpay.css @@ -162,3 +162,11 @@ fieldset[disabled] .afterpay.primary { cursor: default; pointer-events: none; } + +.checkout-methods-items button.express-button { + float:none; + width: 267px; + max-width: 100%; + margin-top: 10px; + cursor: pointer; +} \ No newline at end of file diff --git a/view/frontend/web/js/view/cart/afterpay-cart.js b/view/frontend/web/js/view/cart/afterpay-cart.js index 194ad57..950c8b8 100644 --- a/view/frontend/web/js/view/cart/afterpay-cart.js +++ b/view/frontend/web/js/view/cart/afterpay-cart.js @@ -6,14 +6,92 @@ require( [ "jquery", "Magento_Catalog/js/price-utils", - "Magento_Checkout/js/model/quote" + "Magento_Checkout/js/model/quote", + 'mage/url', + 'Magento_Customer/js/customer-data' ], - function ( $, priceUtils, quote ) { - + function ( $, priceUtils, quote,mageUrl,customerData) { + // Afterpay Express Checkout + function initAfterpayExpress() { + + var afterpayData = window.checkoutConfig.payment.afterpay; + + //CountryCode Object to pass in initialize function. + var countryCurrencyMapping ={AUD:"AU", NZD:"NZ", USD:"US",CAD:"CA"}; + var countryCode = (afterpayData.currencyCode in countryCurrencyMapping)? countryCurrencyMapping[afterpayData.currencyCode]:''; + var isShippingRequired= (!quote.isVirtual())?true:false; + if( $("#afterpay-express-button").length && countryCode!=""){ + AfterPay.initializeForPopup({ + countryCode: countryCode, // fetch + shippingOptionRequired: isShippingRequired, //fetch for virtual type + buyNow: true, + target: '#afterpay-express-button', + onCommenceCheckout: function(actions){ + $.ajax({ + url: mageUrl.build("afterpay/payment/express")+'?action=start', + success: function(data){ + if (!data.success) { + actions.reject(data.message); + } else { + actions.resolve(data.token); + } + } + }); + }, + onShippingAddressChange: function (shippingData, actions) { + $.ajax({ + url: mageUrl.build("afterpay/payment/express")+'?action=change', + method: 'POST', + data: shippingData, + success: function(options){ + if (options.hasOwnProperty('error')) { + actions.reject(AfterPay.constants.SERVICE_UNAVAILABLE, options.message); + } else { + actions.resolve(options.shippingOptions); + } + } + }); + + }, + onComplete: function (orderData) { + $("body").trigger('processStart'); + if (orderData.data.status == 'SUCCESS') { + + $.ajax({ + url: mageUrl.build("afterpay/payment/express")+'?action=confirm', + method: 'POST', + data: orderData.data, + success: function(result){ + $("body").trigger('processStop'); + if (result.success) { + //To Clear mini-cart + var sections = ['cart']; + customerData.invalidate(sections); + customerData.reload(sections, true); + + window.location.href = mageUrl.build("checkout/onepage/success"); + } + } + }); + } + $("body").trigger('processStop'); + + }, + pickup: false, + }); + + } + } + + $(document).ready(function() { + initAfterpayExpress(); + }); + $(".cart-totals").bind("DOMSubtreeModified", function() { var totals = quote.getTotals()(); - $('afterpay-placement').attr('data-amount',totals['base_grand_total']); - + const epsilon = Number.EPSILON || Math.pow(2, -52); + $('afterpay-placement').attr('data-amount',(Math.round((parseFloat(totals['base_grand_total']) + epsilon) * 100) / 100).toFixed(2)); + }); - } -); \ No newline at end of file + +}); diff --git a/view/frontend/web/js/view/product/afterpay-products.js b/view/frontend/web/js/view/product/afterpay-products.js index bc0275c..8457ec5 100644 --- a/view/frontend/web/js/view/product/afterpay-products.js +++ b/view/frontend/web/js/view/product/afterpay-products.js @@ -8,10 +8,10 @@ require( "Magento_Catalog/js/price-utils" ], function ( $, priceUtils, quote ) { - + $(document).ready(function($) { - setFinalAmount(); - + setFinalAmount(); + $('body').on('click change', $('form#product_addtocart_form'), function (e) { setFinalAmount(); }); @@ -23,17 +23,14 @@ require( }); function setFinalAmount(){ - - if($("[data-price-type=finalPrice]:first").text()!=""){ - var price_raw = $("[data-price-type=finalPrice]:first").text(); - } - else{ - var price_raw = $('span.price-final_price > span.price-wrapper > span.price:first').text(); - } - + + let price_raw = $(".page-main [data-price-type=finalPrice]:first").text() || ''; + if (!price_raw) price_raw = $('.page-main .product-info-price .price-final_price .price-wrapper:not([data-price-type="oldPrice"]) span.price:first').text(); + + var price = price_raw.match(/[\d\.]+/g); - - var product_variant_price=parseFloat($('span.price-final_price > span[data-price-type="finalPrice"]').attr('data-price-amount')); + + var product_variant_price=parseFloat($('.page-main .product-info-price span.price-final_price > span[data-price-type="finalPrice"]').attr('data-price-amount')); if(price != null){ if (price[1]) { product_variant_price = price[0]+price[1]; @@ -41,11 +38,11 @@ require( product_variant_price = price[0]; } } - + $('afterpay-placement').attr('data-amount',product_variant_price); - + } - + } );