├── .gitignore ├── Api └── Cart │ └── TotalValidatorInterface.php ├── Controller ├── Cart │ └── Totals.php └── Checkout │ └── Index.php ├── CustomerData └── PaymentRequest.php ├── LICENSE ├── Model ├── Address │ └── Action.php ├── Cart │ ├── Total.php │ ├── Validator.php │ └── Validator │ │ └── Params.php ├── Checkout.php ├── Checkout │ └── Braintree.php └── System │ └── Config │ └── Source │ ├── ButtonMode.php │ ├── CardFlags.php │ ├── CardPaymentMethods.php │ └── CardPaymentTypes.php ├── README.MD ├── ViewModel └── Config.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── di.xml ├── frontend │ ├── di.xml │ ├── routes.xml │ └── sections.xml └── module.xml ├── registration.php └── view └── frontend ├── layout └── checkout_cart_index.xml ├── requirejs-config.js ├── templates └── checkout │ └── cart │ ├── button.phtml │ └── onepage │ └── link.phtml └── web ├── images └── google_checkout.png └── js ├── checkout ├── items.js ├── payment-type │ ├── base.js │ ├── basic-card │ │ └── braintree.js │ └── service-worker │ │ └── paypal.js ├── payment.js ├── paymentRequest.js ├── shipping.js └── totals.js └── sidebar.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* -------------------------------------------------------------------------------- /Api/Cart/TotalValidatorInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Api\Cart; 17 | 18 | /** 19 | * Interface TotalValidatorInterface 20 | * @package ImaginationMedia\PaymentRequest\Api\Cart 21 | * @api 22 | */ 23 | interface TotalValidatorInterface 24 | { 25 | /** 26 | * Validate params 27 | * @param array $params 28 | * 29 | * 30 | * Expected result: 31 | * [ 32 | * 'error' => true 33 | * 'error_message' => 'Invalid field' 34 | * ] 35 | * 36 | * @return array 37 | */ 38 | public function validate(array $params) : array; 39 | } 40 | -------------------------------------------------------------------------------- /Controller/Cart/Totals.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Controller\Cart; 17 | 18 | use ImaginationMedia\PaymentRequest\Model\Cart\Total; 19 | use Magento\Framework\App\Action\Action; 20 | use Magento\Framework\App\Action\Context; 21 | use Magento\Framework\Controller\Result\Json; 22 | use Magento\Framework\Controller\Result\JsonFactory; 23 | use Magento\Framework\Exception\NoSuchEntityException; 24 | use Magento\Framework\Exception\LocalizedException; 25 | 26 | class Totals extends Action 27 | { 28 | /** 29 | * @var JsonFactory 30 | */ 31 | protected $jsonFactory; 32 | 33 | /** 34 | * @var Total 35 | */ 36 | protected $totalCalculator; 37 | 38 | /** 39 | * Totals constructor. 40 | * @param Context $context 41 | * @param JsonFactory $jsonFactory 42 | * @param Total $totalCalculator 43 | */ 44 | public function __construct( 45 | Context $context, 46 | JsonFactory $jsonFactory, 47 | Total $totalCalculator 48 | ) { 49 | parent::__construct($context); 50 | $this->jsonFactory = $jsonFactory; 51 | $this->totalCalculator = $totalCalculator; 52 | } 53 | 54 | /** 55 | * @return Json 56 | * @throws NoSuchEntityException 57 | * @throws LocalizedException 58 | */ 59 | public function execute() : Json 60 | { 61 | $params = $this->getRequest()->getParams(); 62 | $jsonResult = $this->jsonFactory->create(); 63 | 64 | $result = $this->totalCalculator->getTotals($params); 65 | 66 | if ($result['error'] !== "") { 67 | $result['totals'] = []; 68 | } 69 | 70 | $jsonResult->setData($result); 71 | 72 | return $jsonResult; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Controller/Checkout/Index.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Controller\Checkout; 17 | 18 | use ImaginationMedia\PaymentRequest\Model\Checkout; 19 | use Magento\Checkout\Model\Session as CheckoutSession; 20 | use Magento\Framework\App\Action\Action; 21 | use Magento\Framework\App\Action\Context; 22 | use Magento\Framework\Controller\Result\Json; 23 | use Magento\Framework\Controller\Result\JsonFactory; 24 | use Magento\Framework\Exception\LocalizedException; 25 | use Magento\Framework\Exception\NoSuchEntityException; 26 | 27 | class Index extends Action 28 | { 29 | /** 30 | * @var JsonFactory 31 | */ 32 | protected $jsonFactory; 33 | 34 | /** 35 | * @var Checkout 36 | */ 37 | protected $checkout; 38 | 39 | /** 40 | * @var CheckoutSession 41 | */ 42 | protected $checkoutSession; 43 | 44 | const REQUIRED_FIELDS = [ 45 | "paymentMethod", 46 | "shippingMethod", 47 | "shippingAddress", 48 | "billingAddress", 49 | "contactInfo" 50 | ]; 51 | 52 | /** 53 | * Index constructor. 54 | * @param JsonFactory $jsonFactory 55 | * @param Context $context 56 | * @param Checkout $checkout 57 | * @param CheckoutSession $checkoutSession 58 | */ 59 | public function __construct( 60 | JsonFactory $jsonFactory, 61 | Context $context, 62 | Checkout $checkout, 63 | CheckoutSession $checkoutSession 64 | ) { 65 | parent::__construct($context); 66 | $this->jsonFactory = $jsonFactory; 67 | $this->checkout = $checkout; 68 | $this->checkoutSession = $checkoutSession; 69 | } 70 | 71 | /** 72 | * @return Json 73 | * @throws LocalizedException 74 | * @throws NoSuchEntityException 75 | */ 76 | public function execute() : Json 77 | { 78 | $params = $this->getRequest()->getParams(); 79 | $jsonResult = $this->jsonFactory->create(); 80 | 81 | $error = ""; 82 | $result = false; 83 | 84 | /** 85 | * Validate info 86 | */ 87 | foreach (self::REQUIRED_FIELDS as $REQUIRED_FIELD) { 88 | if (!isset($params[$REQUIRED_FIELD]) || 89 | in_array($params[$REQUIRED_FIELD], ["undefined", "null"])) { 90 | $error = __("Not provided " . $REQUIRED_FIELD . " field."); 91 | } 92 | } 93 | 94 | /** 95 | * Check if provided info is correct 96 | */ 97 | if ($error === "") { 98 | $quote = $this->checkoutSession->getQuote(); 99 | /** 100 | * Process Braintree 101 | */ 102 | if ($params["paymentMethod"] === "braintree") { 103 | $token = (isset($params["token"]) && 104 | !in_array($params["token"], ["undefined", "null"])) 105 | ? $params["token"] : null; 106 | 107 | $customerId = ($quote->getCustomerId()) 108 | ? (int)$quote->getCustomerId() : null; 109 | 110 | if ($token) { 111 | $result = $this->checkout->createOrder( 112 | [ 113 | "code" => $params["paymentMethod"], 114 | "token" => $token 115 | ], 116 | $params["shippingMethod"]["carrier_code"] . "_" . $params["shippingMethod"]["method_code"], 117 | (int)$quote->getId(), 118 | $params["billingAddress"], 119 | $params["shippingAddress"], 120 | $params["contactInfo"], 121 | $customerId 122 | ); 123 | } else { 124 | $error = __("No token was provided"); 125 | } 126 | } 127 | } 128 | 129 | if ($error !== "") { 130 | $result = false; 131 | } 132 | 133 | $jsonResult->setData([ 134 | "error" => $error, 135 | "result" => $result 136 | ]); 137 | return $jsonResult; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /CustomerData/PaymentRequest.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\CustomerData; 17 | 18 | use ImaginationMedia\PaymentRequest\ViewModel\Config; 19 | use Magento\Customer\CustomerData\SectionSourceInterface; 20 | use Magento\Framework\Exception\LocalizedException; 21 | use Magento\Framework\Exception\NoSuchEntityException; 22 | 23 | class PaymentRequest implements SectionSourceInterface 24 | { 25 | /** 26 | * @var Config 27 | */ 28 | protected $paymentRequestConfig; 29 | 30 | /** 31 | * @var array 32 | */ 33 | protected $paymentComponents; 34 | 35 | /** 36 | * PaymentRequest constructor. 37 | * @param Config $paymentRequestConfig 38 | * @param array $paymentComponents 39 | */ 40 | public function __construct( 41 | Config $paymentRequestConfig, 42 | array $paymentComponents = [] 43 | ) { 44 | $this->paymentRequestConfig = $paymentRequestConfig; 45 | $this->paymentComponents = $paymentComponents; 46 | } 47 | 48 | /** 49 | * Get section config 50 | * @return array 51 | * @throws LocalizedException 52 | * @throws NoSuchEntityException 53 | */ 54 | public function getSectionData() 55 | { 56 | $data = [ 57 | "paymentRequestApi" => [ 58 | "enabled" => $this->paymentRequestConfig->isEnabled(), 59 | "cardConfig" => $this->paymentRequestConfig->getCardConfig(), 60 | "paypalConfig" => $this->paymentRequestConfig->getPayPalConfig(), 61 | "urls" => $this->paymentRequestConfig->getUrls(), 62 | "buttonMode" => $this->paymentRequestConfig->getButtonMode(), 63 | "cartItems" => $this->paymentRequestConfig->getQuoteItems(), 64 | "quoteTotal" => $this->paymentRequestConfig->getQuoteTotal(), 65 | "currency" => $this->paymentRequestConfig->getCurrency(), 66 | "discount" => $this->paymentRequestConfig->getDiscount(), 67 | "customerId" => $this->paymentRequestConfig->getCustomerId(), 68 | "paymentComponents" => $this->paymentComponents 69 | ] 70 | ]; 71 | 72 | $data["paymentRequestApi"]["quoteId"] = $this->paymentRequestConfig->getQuoteId(); 73 | return $data; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Open Software License v. 3.0 (OSL-3.0) 2 | This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: 3 | 4 | Licensed under the Open Software License version 3.0 5 | 6 | 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: 7 | 8 | a) to reproduce the Original Work in copies, either alone or as part of a collective work; 9 | 10 | b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; 11 | 12 | c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; 13 | 14 | d) to perform the Original Work publicly; and 15 | 16 | e) to display the Original Work publicly. 17 | 18 | 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. 19 | 20 | 3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. 21 | 22 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. 23 | 24 | 5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). 25 | 26 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. 27 | 28 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. 29 | 30 | 8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. 31 | 32 | 9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). 33 | 34 | 10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. 35 | 36 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. 37 | 38 | 12) Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. 39 | 40 | 13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. 41 | 42 | 14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 43 | 44 | 15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. 45 | 46 | 16) Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. -------------------------------------------------------------------------------- /Model/Address/Action.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model\Address; 17 | 18 | use Magento\Directory\Model\CountryFactory; 19 | use Magento\Directory\Model\ResourceModel\Region\CollectionFactory as RegionCollectionFactory; 20 | 21 | class Action 22 | { 23 | /** 24 | * @var CountryFactory 25 | */ 26 | protected $countryFactory; 27 | 28 | /** 29 | * @var RegionCollectionFactory 30 | */ 31 | protected $regionCollectionFactory; 32 | 33 | /** 34 | * Address constructor. 35 | * @param CountryFactory $countryFactory 36 | * @param RegionCollectionFactory $regionCollectionFactory 37 | */ 38 | public function __construct( 39 | CountryFactory $countryFactory, 40 | RegionCollectionFactory $regionCollectionFactory 41 | ) { 42 | $this->countryFactory = $countryFactory; 43 | $this->regionCollectionFactory = $regionCollectionFactory; 44 | } 45 | 46 | /** 47 | * Convert payment request address to the Magento format 48 | * @param array $address 49 | * @param array $contactInfo 50 | * @param int $customerId 51 | * @return array 52 | */ 53 | public function execute( 54 | array $address, 55 | array $contactInfo = [], 56 | int $customerId = null 57 | ) : array { 58 | if (!empty($contactInfo)) { 59 | $fullName = $contactInfo["name"]; 60 | $names = explode(" ", $fullName); 61 | } else { 62 | $names = [ 63 | "payment", 64 | "request" 65 | ]; 66 | } 67 | 68 | $country = $this->countryFactory->create()->loadByCode($address["country"]); 69 | 70 | $regionCollection = $this->regionCollectionFactory->create() 71 | ->addFieldToFilter("country_id", $address["country"]) 72 | ->addFieldToFilter("code", $address["region"]); 73 | $region = $regionCollection->getFirstItem(); 74 | 75 | return [ 76 | "firstname" => (is_array($names) && isset($names[0])) ? (string)$names[0] : '', 77 | "lastname" => (is_array($names)) ? (string)end($names) : '', 78 | "country_id" => $country->getId(), 79 | "region_id" => $region->getRegionId(), 80 | "region" => isset($address["region"]) ? (string)$address["region"] : "", 81 | "city" => isset($address["city"]) ? (string)$address["city"] : "", 82 | "postcode" => isset($address["postalCode"]) ? (string)$address["postalCode"] : "", 83 | "customer_id" => $customerId, 84 | "street" => (isset($address["addressLine"]) && is_array($address)) 85 | ? (string)$address["addressLine"][0] : "", 86 | "telephone" => isset($address["phone"]) ? (string)$address["phone"] : "" 87 | ]; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Model/Cart/Total.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model\Cart; 17 | 18 | use ImaginationMedia\PaymentRequest\Model\Address\Action as Address; 19 | use ImaginationMedia\PaymentRequest\Model\Cart\Validator; 20 | use Magento\Checkout\Model\Session as CheckoutSession; 21 | use Magento\Framework\Exception\LocalizedException; 22 | use Magento\Framework\Exception\NoSuchEntityException; 23 | use Magento\Quote\Api\CartRepositoryInterface; 24 | use Magento\Quote\Model\Quote; 25 | use Magento\Quote\Model\Quote\Address\RateFactory as ShippingRateFactory; 26 | use Magento\Store\Model\StoreManagerInterface; 27 | 28 | class Total 29 | { 30 | 31 | /** 32 | * @var StoreManagerInterface 33 | */ 34 | protected $storeManager; 35 | 36 | /** 37 | * @var CartRepositoryInterface 38 | */ 39 | protected $cartRepository; 40 | 41 | /** 42 | * @var ShippingRateFactory 43 | */ 44 | protected $shippingRateFactory; 45 | 46 | /** 47 | * @var Address 48 | */ 49 | protected $address; 50 | 51 | /** 52 | * @var Validator 53 | */ 54 | protected $totalValidator; 55 | 56 | /** 57 | * @var CheckoutSession 58 | */ 59 | protected $checkoutSession; 60 | 61 | /** 62 | * Total constructor. 63 | * @param StoreManagerInterface $storeManager 64 | * @param CartRepositoryInterface $cartRepository 65 | * @param ShippingRateFactory $shippingRateFactory 66 | * @param Address $address 67 | * @param Validator $totalValidator 68 | * @param CheckoutSession $checkoutSession 69 | */ 70 | public function __construct( 71 | StoreManagerInterface $storeManager, 72 | CartRepositoryInterface $cartRepository, 73 | ShippingRateFactory $shippingRateFactory, 74 | Address $address, 75 | Validator $totalValidator, 76 | CheckoutSession $checkoutSession 77 | ) { 78 | $this->storeManager = $storeManager; 79 | $this->cartRepository = $cartRepository; 80 | $this->shippingRateFactory = $shippingRateFactory; 81 | $this->address = $address; 82 | $this->totalValidator = $totalValidator; 83 | $this->checkoutSession = $checkoutSession; 84 | } 85 | 86 | /** 87 | * Calculate totals 88 | * @param array $params 89 | * @return array 90 | * @throws NoSuchEntityException 91 | * @throws LocalizedException 92 | */ 93 | public function getTotals(array $params) : array 94 | { 95 | $error = ""; 96 | $totals = []; 97 | 98 | $validationResult = $this->totalValidator->validate($params); 99 | 100 | if (!$validationResult['error']) { 101 | $quote = $this->checkoutSession->getQuote(); 102 | $quoteId = (int)$quote->getId(); 103 | $store = $this->storeManager->getStore(); 104 | $cart = $this->cartRepository->get($quoteId); 105 | $customerId = ($quote->getCustomerId()) ? (int)$quote->getCustomerId() : null; 106 | 107 | /** 108 | * Validate if there is an active quote 109 | */ 110 | if (!$cart instanceof Quote || is_null($cart->getEntityId())) { 111 | $error = __("Invalid cart"); 112 | } 113 | 114 | /** 115 | * Check if cart is empty 116 | */ 117 | if ((int)$cart->getItemsCount() === 0) { 118 | $error = __("Empty cart"); 119 | } 120 | 121 | $cart->setStore($store); 122 | $cart->setCurrency(); 123 | 124 | /** 125 | * Set shipping addreess 126 | */ 127 | $magentoAddress = $this->address->execute( 128 | $params["shippingAddress"], 129 | [], 130 | $customerId 131 | ); 132 | $cart->getShippingAddress()->addData($magentoAddress); 133 | 134 | /** 135 | * Set shipping method 136 | */ 137 | $shippingMethod = $params["shippingMethod"]["carrier_code"] . "_" . 138 | $params["shippingMethod"]["method_code"]; 139 | 140 | $shippingRate = $this->shippingRateFactory->create(); 141 | 142 | $shippingRate 143 | ->setCode($shippingMethod) 144 | ->getPrice(); 145 | $shippingAddress = $cart->getShippingAddress(); 146 | $shippingAddress->setCollectShippingRates(true) 147 | ->collectShippingRates() 148 | ->setShippingMethod($shippingMethod); 149 | $cart->getShippingAddress()->addShippingRate($shippingRate); 150 | 151 | /** 152 | * Apply all the changes 153 | */ 154 | $cart->collectTotals(); 155 | $cart->save(); 156 | 157 | $magentoTotals = $cart->getTotals(); 158 | 159 | foreach ($magentoTotals as $total) { 160 | $totals[$total["code"]] = $total["value"]; 161 | } 162 | 163 | /** 164 | * Add discounts by coupon code 165 | */ 166 | $couponCode = (string)$cart->getCouponCode(); 167 | if ($couponCode !== "") { 168 | $totals["discount"] = [ 169 | "label" => sprintf(__("Discount (%s)"), $cart->getCouponCode()), 170 | "amount" => -($cart->getSubtotal() - $cart->getSubtotalWithDiscount()) 171 | ]; 172 | } 173 | } else { 174 | $error = $validationResult['error_message']; 175 | } 176 | 177 | return [ 178 | 'error' => $error, 179 | 'totals' => $totals 180 | ]; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Model/Cart/Validator.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model\Cart; 17 | 18 | use ImaginationMedia\PaymentRequest\Api\Cart\TotalValidatorInterface; 19 | 20 | class Validator 21 | { 22 | /** 23 | * @var array 24 | */ 25 | private $validators; 26 | 27 | /** 28 | * Validator constructor. 29 | * @param array $validators 30 | */ 31 | public function __construct( 32 | array $validators = [] 33 | ) { 34 | $this->validators = $validators; 35 | } 36 | 37 | /** 38 | * Validate the request 39 | * @param array $params 40 | * @return array 41 | */ 42 | public function validate(array $params) : array 43 | { 44 | /** 45 | * @var $validator TotalValidatorInterface 46 | */ 47 | foreach ($this->validators as $validator) { 48 | $result = $validator->validate($params); 49 | if ($result['error'] === false) { 50 | return $result; 51 | } 52 | } 53 | 54 | return [ 55 | 'error' => false, 56 | 'error_message' => '' 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Model/Cart/Validator/Params.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model\Cart\Validator; 17 | 18 | use ImaginationMedia\PaymentRequest\Api\Cart\TotalValidatorInterface; 19 | 20 | class Params implements TotalValidatorInterface 21 | { 22 | const REQUIRED_FIELDS = [ 23 | "shippingAddress", 24 | "shippingMethod" 25 | ]; 26 | 27 | /** 28 | * Validate request 29 | * @param array $params 30 | * @return array 31 | */ 32 | public function validate(array $params) : array 33 | { 34 | $result = [ 35 | 'error' => false, 36 | 'error_message' => '' 37 | ]; 38 | /** 39 | * Validate info 40 | */ 41 | foreach (self::REQUIRED_FIELDS as $REQUIRED_FIELD) { 42 | if (!isset($params[$REQUIRED_FIELD]) || 43 | in_array($params[$REQUIRED_FIELD], ["undefined", "null"])) { 44 | $result['error'] = true; 45 | $result['error_message'] = __("Not provided " . $REQUIRED_FIELD . " field."); 46 | } 47 | } 48 | 49 | return $result; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Model/Checkout.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model; 17 | 18 | use ImaginationMedia\PaymentRequest\Model\Address\Action as Address; 19 | use ImaginationMedia\PaymentRequest\Model\Checkout\Braintree as BraintreeCheckout; 20 | use Magento\Checkout\Model\Session as CheckoutSession; 21 | use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; 22 | use Magento\Quote\Api\CartManagementInterface as CartManagement; 23 | use Magento\Quote\Api\CartRepositoryInterface as CartRepository; 24 | use Magento\Quote\Model\Quote; 25 | use Magento\Quote\Model\Quote\Address\RateFactory as ShippingRateFactory; 26 | use Magento\Store\Model\StoreManagerInterface as StoreManager; 27 | 28 | class Checkout 29 | { 30 | /** 31 | * @var CheckoutSession 32 | */ 33 | protected $checkoutSession; 34 | 35 | /** 36 | * @var CartRepository 37 | */ 38 | protected $cartRepository; 39 | 40 | /** 41 | * @var StoreManager 42 | */ 43 | protected $storeManager; 44 | 45 | /** 46 | * @var CustomerRepository 47 | */ 48 | protected $customerRepository; 49 | 50 | /** 51 | * @var ShippingRateFactory 52 | */ 53 | protected $shippingRateFactory; 54 | 55 | /** 56 | * @var CartManagement 57 | */ 58 | protected $cartManagement; 59 | 60 | /** 61 | * @var BraintreeCheckout 62 | */ 63 | protected $braintreeCheckout; 64 | 65 | /** 66 | * @var Address 67 | */ 68 | protected $address; 69 | 70 | /** 71 | * Checkout constructor. 72 | * @param CheckoutSession $checkoutSession 73 | * @param CartRepository $cartRepository 74 | * @param StoreManager $storeManager 75 | * @param CustomerRepository $customerRepository 76 | * @param ShippingRateFactory $shippingRateFactory 77 | * @param CartManagement $cartManagement 78 | * @param BraintreeCheckout $braintreeCheckout 79 | * @param Address $address 80 | */ 81 | public function __construct( 82 | CheckoutSession $checkoutSession, 83 | CartRepository $cartRepository, 84 | StoreManager $storeManager, 85 | CustomerRepository $customerRepository, 86 | ShippingRateFactory $shippingRateFactory, 87 | CartManagement $cartManagement, 88 | BraintreeCheckout $braintreeCheckout, 89 | Address $address 90 | ) { 91 | $this->checkoutSession = $checkoutSession; 92 | $this->cartRepository = $cartRepository; 93 | $this->storeManager = $storeManager; 94 | $this->customerRepository = $customerRepository; 95 | $this->shippingRateFactory = $shippingRateFactory; 96 | $this->cartManagement = $cartManagement; 97 | $this->braintreeCheckout = $braintreeCheckout; 98 | $this->address = $address; 99 | } 100 | 101 | /** 102 | * Create Magento order 103 | * @param array $paymentInfo 104 | * @param string $shippingMethod 105 | * @param int $quoteId 106 | * @param array $billingAddress 107 | * @param array $shippingAddress 108 | * @param array $contactInfo 109 | * @param int $customerId 110 | * @return bool 111 | */ 112 | public function createOrder( 113 | array $paymentInfo, 114 | string $shippingMethod, 115 | int $quoteId, 116 | array $billingAddress, 117 | array $shippingAddress, 118 | array $contactInfo, 119 | int $customerId = null 120 | ): bool { 121 | try { 122 | $store = $this->storeManager->getStore(); 123 | $cart = $this->cartRepository->get($quoteId); 124 | 125 | /** 126 | * Validate if there is an active quote 127 | */ 128 | if (!$cart instanceof Quote || is_null($cart->getData("entity_id"))) { 129 | return false; 130 | } 131 | 132 | /** 133 | * Check if cart is empty 134 | */ 135 | if ((int)$cart->getItemsCount() === 0) { 136 | return false; 137 | } 138 | 139 | $cart->setStore($store); 140 | $cart->setCurrency(); 141 | 142 | /** 143 | * Assign customer to the order 144 | */ 145 | if ($customerId !== null) { 146 | $customer = $this->customerRepository->getById($customerId); 147 | $cart->assignCustomer($customer); 148 | } else { 149 | $fullName = $contactInfo["name"]; 150 | $names = explode(" ", $fullName); 151 | 152 | $cart->setCustomerEmail($contactInfo["email"]); 153 | $cart->setCustomerFirstname($names[0]); 154 | $cart->setCustomerLastname(end($names)); 155 | $cart->setCustomerIsGuest(true); 156 | } 157 | 158 | /** 159 | * Set billing and shipping address 160 | */ 161 | $magentoBillingAddress = $this->address->execute( 162 | $billingAddress, 163 | $contactInfo, 164 | $customerId 165 | ); 166 | $cart->getBillingAddress()->addData($magentoBillingAddress); 167 | 168 | $magentoShippingAddress = $this->address->execute( 169 | $shippingAddress, 170 | $contactInfo, 171 | $customerId 172 | ); 173 | $cart->getShippingAddress()->addData($magentoShippingAddress); 174 | 175 | /** 176 | * Set shipping method 177 | */ 178 | $shippingRate = $this->shippingRateFactory->create(); 179 | $shippingRate 180 | ->setCode($shippingMethod) 181 | ->getPrice(); 182 | $shippingAddress = $cart->getShippingAddress(); 183 | $shippingAddress->setCollectShippingRates(true) 184 | ->collectShippingRates() 185 | ->setShippingMethod($shippingMethod); 186 | $cart->getShippingAddress()->addShippingRate($shippingRate); 187 | 188 | /** 189 | * Set payment 190 | */ 191 | if ($paymentInfo["code"] === "braintree") { 192 | $this->braintreeCheckout->setPaymentInfo($cart->getPayment(), $paymentInfo); 193 | } 194 | 195 | /** 196 | * Apply all the changes 197 | */ 198 | $cart->collectTotals(); 199 | $cart->save(); 200 | 201 | /** 202 | * Place order but don't redirect to anywhere 203 | */ 204 | $orderId = $this->cartManagement->placeOrder($cart->getId()); 205 | 206 | if ($orderId) { 207 | return true; 208 | } 209 | } catch (\Exception $ex) { 210 | return false; 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Model/Checkout/Braintree.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model\Checkout; 17 | 18 | use Magento\Braintree\Observer\DataAssignObserver; 19 | use Magento\Framework\Exception\LocalizedException; 20 | use Magento\Quote\Model\Quote\Payment; 21 | 22 | class Braintree 23 | { 24 | /** 25 | * Set Braintree payment info 26 | * @param Payment $payment 27 | * @param array $paymentInfo 28 | * @throws LocalizedException 29 | */ 30 | public function setPaymentInfo(Payment $payment, array $paymentInfo) : void 31 | { 32 | $payment->importData([ 33 | 'method' => $paymentInfo["code"] 34 | ]); 35 | $payment->setAdditionalInformation([ 36 | DataAssignObserver::PAYMENT_METHOD_NONCE => $paymentInfo["token"] 37 | ]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Model/System/Config/Source/ButtonMode.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | namespace ImaginationMedia\PaymentRequest\Model\System\Config\Source; 15 | 16 | use Magento\Framework\Data\OptionSourceInterface; 17 | 18 | class ButtonMode implements OptionSourceInterface 19 | { 20 | /** 21 | * Return the available button modes 22 | * @return array 23 | */ 24 | public function toOptionArray() 25 | { 26 | return [ 27 | [ 28 | "value" => 1, 29 | "label" => __("Replace checkout") 30 | ], 31 | [ 32 | "value" => 2, 33 | "label" => __("Add additional button") 34 | ] 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Model/System/Config/Source/CardFlags.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model\System\Config\Source; 17 | 18 | use Magento\Framework\Data\OptionSourceInterface; 19 | 20 | class CardFlags implements OptionSourceInterface 21 | { 22 | /** 23 | * Get all the available credit/debit card flags 24 | * @return array 25 | */ 26 | public function toOptionArray() 27 | { 28 | return [ 29 | [ 30 | "value" => "amex", 31 | "label" => __("American Express") 32 | ], 33 | [ 34 | "value" => "diners", 35 | "label" => __("Diners Club") 36 | ], 37 | [ 38 | "value" => "discover", 39 | "label" => __("Discover") 40 | ], 41 | [ 42 | "value" => "jcb", 43 | "label" => __("JCB") 44 | ], 45 | [ 46 | "value" => "maestro", 47 | "label" => __("Mastercard Maestro") 48 | ], 49 | [ 50 | "value" => "mastercard", 51 | "label" => __("Mastercard") 52 | ], 53 | [ 54 | "value" => "unionpay", 55 | "label" => __("Union Pay") 56 | ], 57 | [ 58 | "value" => "visa", 59 | "label" => __("Visa") 60 | ] 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Model/System/Config/Source/CardPaymentMethods.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | namespace ImaginationMedia\PaymentRequest\Model\System\Config\Source; 15 | 16 | use Magento\Framework\Data\OptionSourceInterface; 17 | use Magento\Payment\Model\Config as PaymentConfig; 18 | use Magento\Payment\Model\MethodInterface; 19 | 20 | class CardPaymentMethods implements OptionSourceInterface 21 | { 22 | /** 23 | * @var PaymentConfig 24 | */ 25 | protected $paymentConfig; 26 | 27 | /** 28 | * @var array 29 | */ 30 | protected $paymentMethods; 31 | 32 | /** 33 | * CardPaymentMethods constructor. 34 | * @param PaymentConfig $paymentConfig 35 | * @param array $paymentMethods 36 | */ 37 | public function __construct( 38 | PaymentConfig $paymentConfig, 39 | array $paymentMethods = [] 40 | ) { 41 | $this->paymentConfig = $paymentConfig; 42 | $this->paymentMethods = $paymentMethods; 43 | } 44 | 45 | /** 46 | * Get all the supported payment methods 47 | * @return array 48 | */ 49 | public function toOptionArray() 50 | { 51 | $methods = []; 52 | $availablePaymentMethods = $this->paymentConfig->getActiveMethods(); 53 | 54 | /** 55 | * @var $paymentMethod MethodInterface 56 | */ 57 | foreach ($availablePaymentMethods as $code => $paymentMethod) { 58 | if (in_array($code, $this->paymentMethods)) { 59 | $methods[] = [ 60 | "value" => $code, 61 | "label" => "[" . $code . "] " . $paymentMethod->getTitle() 62 | ]; 63 | } 64 | } 65 | return $methods; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Model/System/Config/Source/CardPaymentTypes.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\Model\System\Config\Source; 17 | 18 | use Magento\Framework\Data\OptionSourceInterface; 19 | 20 | class CardPaymentTypes implements OptionSourceInterface 21 | { 22 | /** 23 | * Get the available card payment types 24 | * @return array 25 | */ 26 | public function toOptionArray() 27 | { 28 | return [ 29 | [ 30 | "value" => "debit", 31 | "label" => __("Debit") 32 | ], 33 | [ 34 | "value" => "credit", 35 | "label" => __("Credit") 36 | ] 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Payment Request API for Magento 2 2 | 3 | ## About 4 | This Magento Extension will allow you to use the [W3C's payment request api](https://www.w3.org/TR/payment-request/) for checkout in Magento 2. 5 | 6 | The Payment Request Api uses the in-browser stored data to provid all the required information to complete an order, it will get the available addresses, contact info and payment methods that are saved in your browser. 7 | 8 | The Payment Request API is meant to reduce the number of steps needed to complete a payment online, potentially doing away with checkout forms. This can help to improve Conversion as well as Revenue Per Visitor particularly on mobile. 9 | 10 | ![enter image description here](https://developers.google.com/web/fundamentals/payments/images/deep-dive/pr-top.png) 11 | 12 | 13 | ## Installation 14 | You can install this package using composer: 15 | ```terminal 16 | composer require imaginationmedia/magento2-payment-request 17 | ``` 18 | 19 | ## Configuration 20 | This extension adds a section inside the Sales > Payment Methods on Magento admin for configuration. 21 | 22 | You can enable the Payment Request Api using one of the available modes. The first mode will replace all the checkout buttons, opening the Payment Request popup instead of redirecting to the Magento checkout page (when the browser supports the payment request api). The second mode is just a button that is added to the cart page, allowing the customer to checkout through the payment request api. 23 | 24 | ![enter image description here](https://i.ibb.co/Swpbm23/1.png) 25 | 26 | The available payment methods at the moment are PayPal Express and Debit/Credit card. 27 | 28 | PayPal Express is not ready for production use, the integration is still under development, but at this point we can see the option as one of the available payment methods. In the near future it will be available for production use. 29 | 30 | The Debit/Credit card option can be integrated with any payment provider, at this point we have Braintree ready to be used. In the Payment Request Api config there is an option where we choose the Magento payment method that will be used to process the debit/credit cart payments. 31 | 32 | ![enter image description here](https://i.ibb.co/r74jWSz/2.png) 33 | 34 | We also have options to setup the available card flags and the card types (debit, credit, or both). 35 | 36 | ## Supported Browsers 37 | You can check a list with all the supported browsers [here.](https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API#Browser_compatibility) 38 | 39 | ## Enabling multiple payments 40 | If you enable multiple payment methods on payment request api, if one of those is credit/debit card it will show you only this option. 41 | 42 | To see multiple payment methods you need to enable the Web Payments Experimental Features in your Google Chrome. To do that access this url in your browser chrome://flags/#enable-web-payments-experimental-features and enable that option. Restart your browser and you will be able to see all multiple payment methods. 43 | 44 | You also need Chrome 79 or newest to be able to enable this option. 45 | 46 | ## Demo 47 | We have a demo website where you can test the Payment Request Api module. The demo website is available at https://innovations.imaginationmedia.com/. 48 | 49 | ## License 50 | This is a Magento Community Engineering project, lead by [Imagination Media](https://www.imaginationmedia.com/) in partnership with [PayPal](https://www.paypal.com/) and [Mobile Optimization Initiative](https://www.mobileoptimized.org/). 51 | 52 | All code is under the OSL 3.0 license. 53 | -------------------------------------------------------------------------------- /ViewModel/Config.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | namespace ImaginationMedia\PaymentRequest\ViewModel; 17 | 18 | use Magento\Braintree\Gateway\Config\Config as BraintreeConfig; 19 | use Magento\Braintree\Gateway\Request\PaymentDataBuilder; 20 | use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; 21 | use Magento\Checkout\Model\Session; 22 | use Magento\Framework\App\Config\ScopeConfigInterface; 23 | use Magento\Framework\App\ResourceConnection; 24 | use Magento\Framework\DB\Adapter\AdapterInterface; 25 | use Magento\Framework\Exception\LocalizedException; 26 | use Magento\Framework\Exception\NoSuchEntityException; 27 | use Magento\Framework\UrlInterface; 28 | use Magento\Framework\View\Element\Block\ArgumentInterface; 29 | use Magento\Store\Model\ScopeInterface; 30 | use Magento\Store\Model\StoreManagerInterface; 31 | 32 | class Config implements ArgumentInterface 33 | { 34 | const SYSTEM_CONFIG_ACTIVE = "payment/payment_request/active"; 35 | const SYSTEM_CONFIG_BUTTON_MODE = "payment/payment_request/button_mode"; 36 | 37 | const SYSTEM_CONFIG_CARD_ENABLED = "payment/payment_request/payments/card/enable"; 38 | const SYSTEM_CONFIG_CARD_TYPES = "payment/payment_request/payments/card/card_types"; 39 | const SYSTEM_CONFIG_CARD_FLAG = "payment/payment_request/payments/card/card_flags"; 40 | const SYSTEM_CONFIG_CARD_PRE_PAID = "payment/payment_request/payments/card/pre_paid"; 41 | const SYSTEM_CONFIG_CARD_PAYMENT_METHOD = "payment/payment_request/payments/card/payment_method"; 42 | const SYSTEM_CONFIG_CARD_SORT_ORDER = "payment/payment_request/payments/card/sort_order"; 43 | 44 | const SYSTEM_CONFIG_PAYPAL_ENABLED = "payment/payment_request/payments/paypal/enable"; 45 | const SYSTEM_CONFIG_PAYPAL_SORT_ORDER = "payment/payment_request/payments/paypal/sort_order"; 46 | 47 | const BUTTON_MODE_REPLACE_CHECKOUT = 1; 48 | const BUTTON_MODE_ADDITIONAL_BUTTON = 2; 49 | 50 | /** 51 | * @var ScopeConfigInterface 52 | */ 53 | protected $scopeConfig; 54 | 55 | /** 56 | * @var AdapterInterface 57 | */ 58 | protected $connection; 59 | 60 | /** 61 | * @var Session 62 | */ 63 | protected $session; 64 | 65 | /** 66 | * @var BraintreeAdapterFactory 67 | */ 68 | protected $braintreeAdapterFactory; 69 | 70 | /** 71 | * @var BraintreeConfig 72 | */ 73 | protected $braintreeConfig; 74 | 75 | /** 76 | * @var StoreManagerInterface 77 | */ 78 | protected $storeManager; 79 | 80 | /** 81 | * @var UrlInterface 82 | */ 83 | protected $url; 84 | 85 | /** 86 | * @var float 87 | */ 88 | protected $totalPrice = 0; 89 | 90 | /** 91 | * Config constructor. 92 | * @param ScopeConfigInterface $scopeConfig 93 | * @param Session $session 94 | * @param BraintreeAdapterFactory $braintreeAdapterFactory 95 | * @param BraintreeConfig $braintreeConfig 96 | * @param ResourceConnection $resourceConnection 97 | * @param StoreManagerInterface $storeManager 98 | * @param UrlInterface $url 99 | */ 100 | public function __construct( 101 | ScopeConfigInterface $scopeConfig, 102 | Session $session, 103 | BraintreeAdapterFactory $braintreeAdapterFactory, 104 | BraintreeConfig $braintreeConfig, 105 | ResourceConnection $resourceConnection, 106 | StoreManagerInterface $storeManager, 107 | UrlInterface $url 108 | ) { 109 | $this->scopeConfig = $scopeConfig; 110 | $this->session = $session; 111 | $this->braintreeAdapterFactory = $braintreeAdapterFactory; 112 | $this->braintreeConfig = $braintreeConfig; 113 | $this->storeManager = $storeManager; 114 | $this->url = $url; 115 | $this->connection = $resourceConnection->getConnection(); 116 | } 117 | 118 | /** 119 | * Is W3C Web Payments enabled 120 | * @return bool 121 | */ 122 | public function isEnabled(): bool 123 | { 124 | return $this->scopeConfig->isSetFlag( 125 | self::SYSTEM_CONFIG_ACTIVE, 126 | ScopeInterface::SCOPE_STORE 127 | ); 128 | } 129 | 130 | /** 131 | * Get the button mode (replace checkout or show additional button) 132 | * @return int 133 | */ 134 | public function getButtonMode() : int 135 | { 136 | return (int)$this->scopeConfig->getValue( 137 | self::SYSTEM_CONFIG_BUTTON_MODE, 138 | ScopeInterface::SCOPE_STORE 139 | ); 140 | } 141 | 142 | /** 143 | * =============================== CARD CONFIG =================================== 144 | */ 145 | 146 | /** 147 | * Is credit/debit card payments enabled 148 | * @return bool 149 | */ 150 | protected function isCardEnabled(): bool 151 | { 152 | $enabled = $this->scopeConfig->isSetFlag( 153 | self::SYSTEM_CONFIG_CARD_ENABLED, 154 | ScopeInterface::SCOPE_STORE 155 | ); 156 | $paymentMethod = $this->getCardPaymentMethod(); 157 | return ($enabled && $paymentMethod !== ""); 158 | } 159 | 160 | /** 161 | * Get available card types (debit, credit or both) 162 | * @return array 163 | */ 164 | protected function getCardTypes(): array 165 | { 166 | $value = (string)$this->scopeConfig->getValue( 167 | self::SYSTEM_CONFIG_CARD_TYPES, 168 | ScopeInterface::SCOPE_STORE 169 | ); 170 | return explode(',', $value); 171 | } 172 | 173 | /** 174 | * Get all the enabled credit card flags (mastercard, visa, amex etc) 175 | * @return array 176 | */ 177 | protected function getCardFlags() : array 178 | { 179 | $value = (string)$this->scopeConfig->getValue( 180 | self::SYSTEM_CONFIG_CARD_FLAG, 181 | ScopeInterface::SCOPE_STORE 182 | ); 183 | return explode(',', $value); 184 | } 185 | 186 | /** 187 | * Is pre paid payments enabled 188 | * @return bool 189 | */ 190 | protected function isPrePaidEnabled(): bool 191 | { 192 | return $this->scopeConfig->isSetFlag( 193 | self::SYSTEM_CONFIG_CARD_PRE_PAID, 194 | ScopeInterface::SCOPE_STORE 195 | ); 196 | } 197 | 198 | /** 199 | * Get the credit card payment method 200 | * @return string 201 | */ 202 | protected function getCardPaymentMethod() : string 203 | { 204 | return (string)$this->scopeConfig->getValue( 205 | self::SYSTEM_CONFIG_CARD_PAYMENT_METHOD, 206 | ScopeInterface::SCOPE_STORE 207 | ); 208 | } 209 | 210 | /** 211 | * Get additional debit/card additional info (client tokens, api keys etc) 212 | * @return array 213 | */ 214 | public function getCardAdditionalInfo() : array 215 | { 216 | $additionalData = []; 217 | 218 | try { 219 | $paymentMethod = $this->getCardPaymentMethod(); 220 | if ($paymentMethod === "braintree") { 221 | $storeId = $this->session->getQuote()->getStoreId(); 222 | $merchantAccountId = (string)$this->braintreeConfig->getMerchantAccountId($storeId); 223 | $merchantAccountId = ($merchantAccountId !== "") 224 | ? $merchantAccountId 225 | : (string)$this->braintreeConfig->getMerchantId($storeId); 226 | if ($merchantAccountId !== "") { 227 | $params[PaymentDataBuilder::MERCHANT_ACCOUNT_ID] = $merchantAccountId; 228 | $additionalData["clientToken"] = $this->braintreeAdapterFactory 229 | ->create($storeId) 230 | ->generate($params); 231 | } 232 | } 233 | } catch (\Exception $ex) { 234 | /** 235 | * Error 236 | */ 237 | } 238 | 239 | return $additionalData; 240 | } 241 | 242 | /** 243 | * Get card payments sort order 244 | * @return int 245 | */ 246 | protected function getCardSortOrder() : int 247 | { 248 | $value = (string)$this->scopeConfig->getValue( 249 | self::SYSTEM_CONFIG_CARD_SORT_ORDER, 250 | ScopeInterface::SCOPE_STORE 251 | ); 252 | return ($value !== "") ? (int)$value : 0; 253 | } 254 | 255 | /** 256 | * =============================== PAYPAL CONFIG =================================== 257 | */ 258 | 259 | /** 260 | * Is PayPal express enabled 261 | * @return bool 262 | */ 263 | protected function isPayPalEnabled() : bool 264 | { 265 | return $this->scopeConfig->isSetFlag( 266 | self::SYSTEM_CONFIG_PAYPAL_ENABLED, 267 | ScopeInterface::SCOPE_STORE 268 | ); 269 | } 270 | 271 | /** 272 | * Get card payments sort order 273 | * @return int 274 | */ 275 | protected function getPayPalSortOrder() : int 276 | { 277 | $value = (string)$this->scopeConfig->getValue( 278 | self::SYSTEM_CONFIG_PAYPAL_SORT_ORDER, 279 | ScopeInterface::SCOPE_STORE 280 | ); 281 | return ($value !== "") ? (int)$value : 0; 282 | } 283 | 284 | /** 285 | * =============================== OPERATIONS ====================================== 286 | */ 287 | 288 | /** 289 | * Get debit/credit card config 290 | * @return array 291 | */ 292 | public function getCardConfig() : array 293 | { 294 | return [ 295 | "enabled" => $this->isCardEnabled(), 296 | "types" => $this->getCardTypes(), 297 | "flags" => $this->getCardFlags(), 298 | "prePaid" => $this->isPrePaidEnabled(), 299 | "paymentMethod" => $this->getCardPaymentMethod(), 300 | "additionalInfo" => $this->getCardAdditionalInfo(), 301 | "sortOrder" => $this->getCardSortOrder() 302 | ]; 303 | } 304 | 305 | /** 306 | * Get PayPal config 307 | * @return array 308 | */ 309 | public function getPayPalConfig() : array 310 | { 311 | return [ 312 | "enabled" => $this->isPayPalEnabled(), 313 | "sortOrder" => $this->getPayPalSortOrder() 314 | ]; 315 | } 316 | 317 | /** 318 | * Generate product label using the sku and quantity 319 | * @param array $product 320 | * @return string 321 | * @throws LocalizedException 322 | * @throws NoSuchEntityException 323 | */ 324 | protected function generateProductLabel(array $product) : string 325 | { 326 | $quantity = $product["qty"]; 327 | if ($quantity > 0) { 328 | $currencyCode = $this->getCurrency(); 329 | if (filter_var($quantity, FILTER_VALIDATE_INT) === false) { 330 | return $product["name"] . " (#" . $product["sku"] . ") (" . $currencyCode . 331 | number_format((float)$product["price"], 2, '.', '') . 332 | ") x" . (int)$quantity; 333 | } else { 334 | return $product["name"] . " (#" . $product["sku"] . ") (" . $currencyCode . 335 | number_format((float)$product["price"], 2, '.', '') . ") x" . 336 | number_format((float)$quantity, 2, '.', ''); 337 | } 338 | } else { 339 | return $product["name"] . " (#" . $product["sku"] . ")"; 340 | } 341 | } 342 | 343 | /** 344 | * Get all the current cart items 345 | * @return array 346 | * @throws LocalizedException 347 | * @throws NoSuchEntityException 348 | */ 349 | public function getQuoteItems() : array 350 | { 351 | $products = []; 352 | 353 | /** 354 | * Get current quote 355 | */ 356 | $quote = $this->session->getQuote(); 357 | $quoteId = $quote->getId(); 358 | 359 | /** 360 | * Generate a quote id if null 361 | */ 362 | if ($quoteId === null) { 363 | $quote->collectTotals(); 364 | $quote->save(); 365 | $quoteId = $quote->getId(); 366 | } 367 | 368 | /** 369 | * Get quote items 370 | */ 371 | $quoteItemTable = $this->connection->getTableName("quote_item"); 372 | $query = $this->connection->select()->from( 373 | $quoteItemTable, 374 | [ 375 | "name", 376 | "sku", 377 | "price", 378 | "qty" 379 | ] 380 | )->where($quoteItemTable . ".quote_id = " . $quoteId); 381 | 382 | foreach ($this->connection->fetchAll($query) as $product) { 383 | $products[$product['sku']] = [ 384 | "label" => $this->generateProductLabel($product), 385 | "price" => (float)$product["price"] * (float)$product["qty"] 386 | ]; 387 | $this->totalPrice = $this->totalPrice + ((float)$product["price"] * (float)$product["qty"]); 388 | } 389 | 390 | return $products; 391 | } 392 | 393 | /** 394 | * Get controller urls 395 | * @return array 396 | * @throws NoSuchEntityException 397 | */ 398 | public function getUrls() : array 399 | { 400 | $maskedQuoteId = $this->getQuoteIdMask(); 401 | return [ 402 | "checkout" => $this->url->getUrl("paymentRequest/checkout/index"), 403 | "updateAddress" => $this->url->getUrl("paymentRequest/cart/updateAddress"), 404 | "success" => $this->url->getUrl("checkout/onepage/success"), 405 | "estimateShipping" => [ 406 | "guest" => $this->url->getDirectUrl( 407 | "rest/" . $this->storeManager->getStore()->getCode() . 408 | "/V1/guest-carts/" . $maskedQuoteId . "/estimate-shipping-methods" 409 | ), 410 | "customer" => $this->url->getDirectUrl( 411 | "rest/" . $this->storeManager->getStore()->getCode() . 412 | "/V1/carts/mine/estimate-shipping-methods" 413 | ) 414 | ], 415 | "totals" => $this->url->getUrl("paymentRequest/cart/totals") 416 | ]; 417 | } 418 | 419 | /** 420 | * Get current quote id 421 | * @return int 422 | */ 423 | public function getQuoteId() : int 424 | { 425 | return (int)$this->session->getQuoteId(); 426 | } 427 | 428 | /** 429 | * Get the quote total 430 | * @return float 431 | */ 432 | public function getQuoteTotal() : float 433 | { 434 | return $this->totalPrice; 435 | } 436 | 437 | /** 438 | * Get quote currency 439 | * @return string 440 | * @throws LocalizedException 441 | * @throws NoSuchEntityException 442 | */ 443 | public function getCurrency() : string 444 | { 445 | return (string)$this->session->getQuote()->getCurrency()->getQuoteCurrencyCode(); 446 | } 447 | 448 | /** 449 | * Get masked quote id 450 | * @return string 451 | */ 452 | public function getQuoteIdMask() : string 453 | { 454 | $quoteIdMaskTable = $this->connection->getTableName("quote_id_mask"); 455 | $query = $this->connection->select() 456 | ->from( 457 | $quoteIdMaskTable, 458 | [ 459 | "masked_id" 460 | ] 461 | )->where($quoteIdMaskTable . ".quote_id = " . $this->getQuoteId()); 462 | $item = $this->connection->fetchRow($query); 463 | if (is_array($item) && key_exists("masked_id", $item)) { 464 | return $item["masked_id"]; 465 | } 466 | return ""; 467 | } 468 | 469 | /** 470 | * Get discount by coupon code 471 | * @return array 472 | * @throws LocalizedException 473 | * @throws NoSuchEntityException 474 | */ 475 | public function getDiscount() : array 476 | { 477 | $coupon = (string)$this->session->getQuote()->getCouponCode(); 478 | if ($coupon !== "") { 479 | return [ 480 | "label" => sprintf(__("Discount (%s)"), $coupon), 481 | "amount" => -($this->session->getQuote()->getSubtotal() - 482 | $this->session->getQuote()->getSubtotalWithDiscount()) 483 | ]; 484 | } 485 | return []; 486 | } 487 | 488 | /** 489 | * Get current customer id 490 | * @return int 491 | * @throws LocalizedException 492 | * @throws NoSuchEntityException 493 | */ 494 | public function getCustomerId() : int 495 | { 496 | $customerId = $this->session->getQuote()->getCustomerId(); 497 | if ($customerId !== null) { 498 | return (int)$customerId; 499 | } 500 | return 0; 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imaginationmedia/magento2-payment-request", 3 | "description": "W3C Web Payments for Magento 2", 4 | "version" : "1.0.1", 5 | "require": { 6 | "magento/framework": ">=102.0", 7 | "magento/module-backend": ">=101.0", 8 | "magento/module-braintree": ">=100.3", 9 | "magento/module-checkout": ">=100.3", 10 | "magento/module-payment": ">=100.3", 11 | "magento/module-quote" : ">=100.3", 12 | "magento/module-sales": ">=102.0", 13 | "magento/module-sales-sequence": ">=100.3", 14 | "magento/module-shipping": ">=100.3", 15 | "php": ">=7.2" 16 | }, 17 | "type": "magento2-module", 18 | "license": [ 19 | "OSL-3.0" 20 | ], 21 | "authors": [ 22 | { 23 | "name": "Igor Ludgero Miura", 24 | "email": "igor@imaginationmedia.com", 25 | "homepage": "http://www.imaginationmedia.com/", 26 | "role": "Lead Developer" 27 | } 28 | ], 29 | "autoload": { 30 | "files": [ 31 | "registration.php" 32 | ], 33 | "psr-4": { 34 | "ImaginationMedia\\PaymentRequest\\": "" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | Magento\Config\Model\Config\Source\Yesno 23 | 24 | 25 | 26 | 27 | ImaginationMedia\PaymentRequest\Model\System\Config\Source\ButtonMode 28 | 29 | 30 | 31 | 32 | 33 | 34 | This payment method is not ready for production. It stills under development. 35 | 36 | 37 | Magento\Config\Model\Config\Source\Yesno 38 | 39 | 40 | 41 | 42 | 1 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Magento\Config\Model\Config\Source\Yesno 51 | 52 | 53 | 54 | ImaginationMedia\PaymentRequest\Model\System\Config\Source\CardPaymentTypes 55 | 56 | 57 | 1 58 | 59 | 60 | 61 | 62 | ImaginationMedia\PaymentRequest\Model\System\Config\Source\CardFlags 63 | 64 | 65 | 1 66 | 67 | 68 | 69 | 70 | Magento\Config\Model\Config\Source\Yesno 71 | 72 | 73 | 1 74 | 75 | 76 | 77 | 78 | ImaginationMedia\PaymentRequest\Model\System\Config\Source\CardPaymentMethods 79 | 80 | 81 | 1 82 | 83 | 84 | 85 | 86 | 87 | 1 88 | 89 | 90 | 91 | 92 | 93 |
94 |
95 |
96 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 17 | 18 | 19 | 20 | ImaginationMedia\PaymentRequest\Model\Cart\Validator\Params 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | braintree 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ImaginationMedia_PaymentRequest/js/checkout/payment-type/basic-card/braintree 37 | ImaginationMedia_PaymentRequest/js/checkout/payment-type/service-worker/paypal 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | ImaginationMedia\PaymentRequest\CustomerData\PaymentRequest 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /etc/frontend/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /etc/frontend/sections.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 17 |
18 | 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 |
27 | 28 | 29 |
30 | 31 | 32 |
33 | 34 | 35 |
36 | 37 | 38 |
39 | 40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 |
48 | 49 | 50 |
51 | 52 | 53 |
54 | 55 | 56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | declare(strict_types=1); 15 | 16 | use Magento\Framework\Component\ComponentRegistrar; 17 | 18 | ComponentRegistrar::register( 19 | ComponentRegistrar::MODULE, 20 | 'ImaginationMedia_PaymentRequest', 21 | __DIR__ 22 | ); 23 | -------------------------------------------------------------------------------- /view/frontend/layout/checkout_cart_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 18 | 19 | checkout 20 | 1 21 | ImaginationMedia\PaymentRequest\ViewModel\Config 22 | 23 | 24 | 25 | 26 | 27 | ImaginationMedia_PaymentRequest::checkout/cart/onepage/link.phtml 28 | 29 | 30 | ImaginationMedia\PaymentRequest\ViewModel\Config 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /view/frontend/requirejs-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | var config = { 13 | config: { 14 | mixins: { 15 | 'Magento_Checkout/js/sidebar': { 16 | 'ImaginationMedia_PaymentRequest/js/sidebar': true 17 | } 18 | } 19 | }, 20 | paths: { 21 | braintreeClientV2: 'https://js.braintreegateway.com/js/braintree-2.32.0.min' 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /view/frontend/templates/checkout/cart/button.phtml: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | use ImaginationMedia\PaymentRequest\ViewModel\Config; 15 | 16 | /** 17 | * @var $this \Magento\Framework\View\Element\Template 18 | * @var $viewModel Config 19 | */ 20 | $viewModel = $this->getViewModel(); 21 | ?> 22 | 23 | isEnabled() && 24 | $viewModel->getButtonMode() === Config::BUTTON_MODE_ADDITIONAL_BUTTON) : ?> 25 | 28 | 42 | 43 | -------------------------------------------------------------------------------- /view/frontend/templates/checkout/cart/onepage/link.phtml: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 11 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 12 | */ 13 | 14 | /** 15 | * @var $block \Magento\Checkout\Block\Onepage\Link 16 | * @var $viewModel \ImaginationMedia\PaymentRequest\ViewModel\Config 17 | */ 18 | 19 | $viewModel = $block->getViewModel(); 20 | 21 | ?> 22 | isPossibleOnepageCheckout() && 23 | (!$viewModel->isEnabled() || $viewModel->getButtonMode() === $viewModel::BUTTON_MODE_ADDITIONAL_BUTTON)) : ?> 24 | 35 | isEnabled() && 36 | $viewModel->getButtonMode() === $viewModel::BUTTON_MODE_REPLACE_CHECKOUT) : ?> 37 | 47 | 64 | 65 | -------------------------------------------------------------------------------- /view/frontend/web/images/google_checkout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Imagination-Media/magento2-payment-request/502a141054b0bf2507b55b7a926e836b328fb116/view/frontend/web/images/google_checkout.png -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/items.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([], function () { 13 | return { 14 | /** 15 | * Get items 16 | * @param w3cPaymentRequest 17 | * @returns {Array} 18 | */ 19 | getItems: function(w3cPaymentRequest) { 20 | var displayItems = []; 21 | 22 | /** 23 | * Add products 24 | */ 25 | var values = Object.values(w3cPaymentRequest.cartItems); 26 | for (var sku in values) { 27 | var cartItem = values[sku]; 28 | displayItems.push({ 29 | label: cartItem.label, 30 | amount: { 31 | currency: w3cPaymentRequest.currency, 32 | value: cartItem.price 33 | } 34 | }); 35 | } 36 | 37 | /** 38 | * Add discount by coupon code 39 | */ 40 | if (w3cPaymentRequest.discount.amount) { 41 | displayItems.push({ 42 | label: w3cPaymentRequest.discount.label, 43 | amount: { 44 | currency: w3cPaymentRequest.currency, 45 | value: w3cPaymentRequest.discount.amount 46 | } 47 | }); 48 | } 49 | 50 | return displayItems; 51 | } 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/payment-type/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([ 13 | 'Magento_Customer/js/customer-data', 14 | 'mage/translate', 15 | ], function (customerData, $t) { 16 | 'use strict'; 17 | 18 | return { 19 | init: function (paymentRequest, paymentResponse, w3cPaymentRequest) { 20 | if (!paymentResponse.methodName) { 21 | console.log($t("Not valid response")); 22 | paymentResponse.complete('fail'); 23 | } 24 | 25 | var paymentComponents = customerData.get('payment-request')().paymentRequestApi.paymentComponents; 26 | 27 | var cardPaymentMethod = w3cPaymentRequest.cardConfig.paymentMethod; 28 | 29 | if (!paymentComponents.hasOwnProperty(cardPaymentMethod)) { 30 | throw new Error($t("Invalid payment method provided for payment request.")); 31 | } 32 | 33 | require([paymentComponents[cardPaymentMethod]], function(cardPaymentMethod) { 34 | cardPaymentMethod.process(paymentRequest, paymentResponse, w3cPaymentRequest); 35 | }); 36 | } 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/payment-type/basic-card/braintree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([ 13 | 'braintreeClientV2', 14 | 'mage/translate', 15 | 'jquery' 16 | ], function (braintreeClient, $t, $) { 17 | 'use strict'; 18 | 19 | return { 20 | process: function (paymentRequest, paymentResponse, w3cPaymentRequest) { 21 | var details = paymentResponse.details; 22 | 23 | /** 24 | * Validate client token 25 | */ 26 | if (!w3cPaymentRequest.cardConfig.hasOwnProperty("additionalInfo") || 27 | !w3cPaymentRequest.cardConfig.additionalInfo.hasOwnProperty("clientToken")) { 28 | console.log($t("No client Braintree key was provided.")); 29 | paymentResponse.complete('fail'); 30 | } 31 | 32 | var customerFullName = details.billingAddress.recipient; 33 | var names = customerFullName.split(" "); 34 | var finalData = { 35 | number: details.cardNumber, 36 | cardholderName: details.cardholderName, 37 | expirationMonth: details.expiryMonth, 38 | expirationYear: details.expiryYear, 39 | cvv: details.cardSecurityCode, 40 | billingAddress: { 41 | firstName : names[0], 42 | lastName : names.slice(-1)[0], 43 | company : details.billingAddress.organization, 44 | streetAddress : Object.values(details.billingAddress.addressLine).length > 0 45 | ? details.billingAddress.addressLine[0] : '', 46 | extendedAddress : Object.values(details.billingAddress.addressLine).length > 1 47 | ? details.billingAddress.addressLine[1] : '', 48 | locality : details.billingAddress.city, 49 | region : details.billingAddress.region, 50 | postalCode : details.billingAddress.postalCode, 51 | countryCodeAlpha2 : details.billingAddress.country 52 | } 53 | }; 54 | var client = new braintreeClient.api.Client({clientToken: w3cPaymentRequest.cardConfig.additionalInfo.clientToken}); 55 | client.tokenizeCard(finalData, function (err, nonce) { 56 | if (!err) { 57 | var params = { 58 | paymentMethod: "braintree", 59 | token: nonce, 60 | shippingAddress: JSON.parse(JSON.stringify(paymentResponse.shippingAddress)), 61 | billingAddress: details.billingAddress, 62 | contactInfo: { 63 | name: paymentResponse.payerName, 64 | email: paymentResponse.payerEmail, 65 | phone: paymentResponse.payerPhone 66 | }, 67 | shippingMethod: JSON.parse(paymentRequest.shippingOption) 68 | }; 69 | $.ajax({ 70 | url: w3cPaymentRequest.urls.checkout, 71 | action: 'POST', 72 | cache: false, 73 | data: params, 74 | success: function (response) { 75 | if (response.result === true) { 76 | paymentResponse.complete('success').then(() => { 77 | window.location.href = w3cPaymentRequest.urls.success; 78 | }); 79 | } else { 80 | paymentResponse.complete('fail'); 81 | } 82 | } 83 | }); 84 | } else { 85 | console.log(err.toString()); 86 | console.log(nonce); 87 | paymentResponse.complete('fail'); 88 | } 89 | }); 90 | } 91 | }; 92 | }); 93 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/payment-type/service-worker/paypal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([ 13 | 'mage/translate', 14 | ], function ($t) { 15 | 'use strict'; 16 | 17 | return { 18 | process: function (paymentRequest, paymentResponse, w3cPaymentRequest) { 19 | /** 20 | * Nothing happens 21 | */ 22 | } 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/payment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([], function () { 13 | 'use strict'; 14 | 15 | return { 16 | /** 17 | * Get all available payment methods 18 | * @param w3cPaymentRequest 19 | * @returns {Array} 20 | */ 21 | getPaymentMethods : function(w3cPaymentRequest) { 22 | var methodData = []; 23 | /** 24 | * Check if credit/debit cards is available 25 | */ 26 | if (w3cPaymentRequest.cardConfig.enabled) { 27 | var networks = w3cPaymentRequest.cardConfig.flags; 28 | var types = w3cPaymentRequest.cardConfig.types; 29 | 30 | /** 31 | * Check if pre paid cards are enabled 32 | **/ 33 | if (w3cPaymentRequest.cardConfig.prePaid) { 34 | types.push("prepaid"); 35 | } 36 | 37 | methodData.push({ 38 | supportedMethods: 'basic-card', 39 | data: { 40 | supportedNetworks: networks, 41 | supportedTypes: types 42 | }, 43 | sortOrder: w3cPaymentRequest.cardConfig.sortOrder 44 | }); 45 | } 46 | 47 | /** 48 | * Check if PayPal express is available 49 | */ 50 | if (w3cPaymentRequest.paypalConfig.enabled) { 51 | methodData.push({ 52 | supportedMethods: "https://innovations.imaginationmedia.com/paypal/", 53 | sortOrder: w3cPaymentRequest.paypalConfig.sortOrder 54 | }); 55 | } 56 | 57 | /** 58 | * Sort order payment methods 59 | */ 60 | methodData.sort((a, b) => (a.sortOrder > b.sortOrder) ? 1 : -1); 61 | 62 | return methodData; 63 | } 64 | }; 65 | }); 66 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/paymentRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([ 13 | 'jquery', 14 | 'ko', 15 | 'underscore', 16 | 'mageUtils', 17 | 'Magento_Ui/js/lib/collapsible', 18 | 'mage/translate', 19 | 'Magento_Customer/js/customer-data', 20 | 'ImaginationMedia_PaymentRequest/js/checkout/payment-type/base', 21 | 'ImaginationMedia_PaymentRequest/js/checkout/shipping', 22 | 'ImaginationMedia_PaymentRequest/js/checkout/payment', 23 | 'ImaginationMedia_PaymentRequest/js/checkout/items' 24 | ], function ( 25 | $, 26 | ko, 27 | _, 28 | utils, 29 | Collapsible, 30 | $t, 31 | customerData, 32 | basePaymentHandler, 33 | shipping, 34 | payment, 35 | items 36 | ) { 37 | 'use strict'; 38 | 39 | var shippingMethods = []; 40 | var shippingAddress = []; 41 | 42 | return { 43 | init: function () { 44 | var w3cPaymentRequest = customerData.get('payment-request')().paymentRequestApi; 45 | if (w3cPaymentRequest && this.isAvailable()) { 46 | /** 47 | * Get available payment methods 48 | * @type {*|Array} 49 | */ 50 | var methodData = payment.getPaymentMethods(w3cPaymentRequest); 51 | 52 | /** 53 | * Get items (products, shipping, tax, subtotal) 54 | * @type {*|Array|observable} 55 | */ 56 | var displayItems = items.getItems(w3cPaymentRequest); 57 | 58 | /** 59 | * Load all the available shipping options 60 | * @type {Array} 61 | */ 62 | var finalShippingOptions = []; 63 | 64 | var quoteTotal = (!w3cPaymentRequest.discount.amount) 65 | ? w3cPaymentRequest.quoteTotal 66 | : w3cPaymentRequest.quoteTotal + w3cPaymentRequest.discount.amount; 67 | 68 | var details = { 69 | displayItems: displayItems, 70 | total: { 71 | label: $t("Total"), 72 | amount: {currency: w3cPaymentRequest.currency, value: quoteTotal}, 73 | }, 74 | shippingOptions: finalShippingOptions 75 | }; 76 | 77 | var options = { 78 | requestShipping: true, 79 | requestPayerEmail: true, 80 | requestPayerPhone: true, 81 | requestPayerName: true 82 | }; 83 | 84 | var paymentRequest = new PaymentRequest( 85 | methodData, 86 | details, 87 | options 88 | ); 89 | 90 | /** 91 | * When shipping address change 92 | * @param ev 93 | */ 94 | paymentRequest.onshippingaddresschange = ev => { 95 | let result = shipping.onAddressChange(ev, w3cPaymentRequest); 96 | shippingMethods = result.methods; 97 | shippingAddress = result.address; 98 | }; 99 | 100 | /** 101 | * When shipping method option change 102 | * @param ev 103 | */ 104 | paymentRequest.onshippingoptionchange = ev => { 105 | shipping.onOptionChange(paymentRequest, ev, w3cPaymentRequest, shippingMethods, shippingAddress); 106 | }; 107 | 108 | /** 109 | * Process payment 110 | */ 111 | paymentRequest.show().then(function (paymentResponse) { 112 | basePaymentHandler.init(paymentRequest, paymentResponse, w3cPaymentRequest); 113 | }); 114 | 115 | 116 | } else { 117 | console.log($t("Payment Request Api data not available.")); 118 | } 119 | }, 120 | 121 | /** 122 | * Is payment request available for the browser 123 | */ 124 | isAvailable : function() { 125 | var sUsrAg = navigator.userAgent; 126 | if (sUsrAg.indexOf("Chrome") <= -1 && sUsrAg.indexOf("Safari") > -1) { //Safari is not supported but it's also returning true without this condition 127 | return false; 128 | } else if (window.PaymentRequest) { 129 | return true; 130 | } 131 | return false; 132 | } 133 | }; 134 | }); 135 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/shipping.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([ 13 | 'ImaginationMedia_PaymentRequest/js/checkout/totals', 14 | 'jquery', 15 | 'mage/translate' 16 | ], function ( 17 | totals, 18 | $, 19 | $t 20 | ) { 21 | 'use strict'; 22 | 23 | return { 24 | /** 25 | * On shipping address change 26 | * @param ev 27 | * @param w3cPaymentRequest 28 | * @returns {{address: any, methods: Array}} 29 | */ 30 | onAddressChange : function(ev, w3cPaymentRequest) { 31 | /** 32 | * Get shipping rates from Magento 33 | */ 34 | var localShippingAddress = { 35 | region_id : null, 36 | region : ev.currentTarget.shippingAddress.region, 37 | country_id : ev.currentTarget.shippingAddress.country, 38 | postcode : ev.currentTarget.shippingAddress.postalCode 39 | }; 40 | 41 | var shippingAddress = JSON.parse(JSON.stringify(ev.currentTarget.shippingAddress)); 42 | 43 | var shippingMethods = []; 44 | $.ajax({ 45 | type: "POST", 46 | contentType: "application/json; charset=utf-8", 47 | dataType: "json", 48 | url: (w3cPaymentRequest.customerId !== 0) 49 | ? w3cPaymentRequest.urls.estimateShipping.customer 50 | : w3cPaymentRequest.urls.estimateShipping.guest, 51 | cache: false, 52 | data: JSON.stringify({ 53 | address : localShippingAddress 54 | }), 55 | async: false, 56 | success: function (rates) { 57 | var values = Object.values(rates); 58 | for (var key in values) { 59 | var rate = values[key]; 60 | shippingMethods.push({ 61 | id: JSON.stringify({ 62 | carrier_code: rate.carrier_code, 63 | method_code: rate.method_code 64 | }), 65 | label: rate.carrier_title + " - " + rate.method_title, 66 | amount: { 67 | currency: w3cPaymentRequest.currency, value: rate.price_incl_tax 68 | }, 69 | selected: false 70 | }); 71 | } 72 | }, 73 | error: function(request, status, error) { 74 | console.log($t("Error updating the shipping rates")); 75 | } 76 | }); 77 | var paymentDetails = { 78 | shippingOptions: shippingMethods, 79 | }; 80 | ev.updateWith(paymentDetails); 81 | 82 | return { 83 | 'address' : shippingAddress, 84 | 'methods' : shippingMethods 85 | }; 86 | }, 87 | 88 | /** 89 | * On shipping option change 90 | * @param paymentRequest 91 | * @param ev 92 | * @param w3cPaymentRequest 93 | * @param shippingMethods 94 | * @param shippingAddress 95 | */ 96 | onOptionChange: function(paymentRequest, ev, w3cPaymentRequest, shippingMethods, shippingAddress) { 97 | var { shippingOption } = paymentRequest; 98 | var totalsResult = totals.calculateTotals(w3cPaymentRequest, shippingOption, shippingAddress, shippingMethods); 99 | ev.updateWith(totalsResult); 100 | } 101 | }; 102 | }); 103 | -------------------------------------------------------------------------------- /view/frontend/web/js/checkout/totals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([ 13 | 'jquery', 14 | 'mage/translate' 15 | ], function ( 16 | $, 17 | $t 18 | ) { 19 | 'use strict'; 20 | 21 | return { 22 | calculateTotals: function (w3cPaymentRequest, shippingMethod, shippingAddress, shippingMethods) { 23 | var availableTotals = [ 24 | "shipping", 25 | "tax", 26 | "discount" 27 | ]; 28 | 29 | var result = {}; 30 | 31 | $.ajax({ 32 | url: w3cPaymentRequest.urls.totals, 33 | action: 'POST', 34 | cache: false, 35 | async: false, 36 | data: { 37 | "shippingMethod" : JSON.parse(shippingMethod), 38 | "shippingAddress" : shippingAddress 39 | }, 40 | success: function (response) { 41 | if (response.error === "") { 42 | var total = {}; 43 | var displayItems = []; 44 | 45 | /** 46 | * Add products 47 | */ 48 | var values = Object.values(w3cPaymentRequest.cartItems); 49 | for (var sku in values) { 50 | var cartItem = values[sku]; 51 | displayItems.push({ 52 | label: cartItem.label, 53 | amount: { 54 | currency: w3cPaymentRequest.currency, 55 | value: cartItem.price 56 | } 57 | }); 58 | } 59 | 60 | /** 61 | * Add shipping, tax, discount and total 62 | */ 63 | var keys = Object.keys(response.totals); 64 | values = Object.values(response.totals); 65 | for (var key in values) { 66 | var totalItem = values[key]; 67 | if (keys[key] === "discount") { 68 | displayItems.push({ 69 | label: totalItem.label, 70 | amount: {currency: w3cPaymentRequest.currency, value: totalItem.amount}, 71 | type: key 72 | }); 73 | } else if (keys[key] === "grand_total") { 74 | total = { 75 | label: $t("Total"), 76 | amount: {currency: w3cPaymentRequest.currency, value: totalItem} 77 | }; 78 | } else if (availableTotals.includes(key)) { 79 | displayItems.push({ 80 | label: $t(key), 81 | amount: {currency: w3cPaymentRequest.currency, value: totalItem}, 82 | type: key 83 | }); 84 | } 85 | } 86 | 87 | /** 88 | * Add shipping options 89 | */ 90 | values = Object.values(shippingMethods); 91 | for (var key in Object.values(shippingMethods)) { 92 | if (values[key].id === shippingMethod) { 93 | values[key].selected = true; 94 | } else { 95 | values[key].selected = false; 96 | } 97 | } 98 | 99 | result = { 100 | displayItems: displayItems, 101 | total: total, 102 | shippingOptions: shippingMethods 103 | }; 104 | } else { 105 | console.log($t("Error getting the totals.")); 106 | console.log(response.error); 107 | } 108 | } 109 | }); 110 | 111 | return result; 112 | } 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /view/frontend/web/js/sidebar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * W3C Payment Request (https://www.w3.org/TR/payment-request/) 3 | * 4 | * Add the W3C payment request api to Magento 2 5 | * 6 | * @package ImaginationMedia\PaymentRequest 7 | * @author Igor Ludgero Miura 8 | * @copyright Copyright (c) 2019 Imagination Media (https://www.imaginationmedia.com/) 9 | * @license https://opensource.org/licenses/OSL-3.0.php Open Software License 3.0 10 | */ 11 | 12 | define([ 13 | 'jquery', 14 | 'Magento_Customer/js/model/authentication-popup', 15 | 'Magento_Customer/js/customer-data', 16 | 'Magento_Ui/js/modal/alert', 17 | 'Magento_Ui/js/modal/confirm', 18 | 'underscore', 19 | 'ImaginationMedia_PaymentRequest/js/checkout/paymentRequest', 20 | 'jquery/ui', 21 | 'mage/decorate', 22 | 'mage/collapsible', 23 | 'mage/cookies' 24 | ], function ($, authenticationPopup, customerData, alert, confirm, _, paymentRequest) { 25 | 'use strict'; 26 | 27 | var widgetMixin = { 28 | /** 29 | * @private 30 | */ 31 | _initContent: function () { 32 | var self = this, 33 | events = {}; 34 | 35 | this.element.decorate('list', this.options.isRecursive); 36 | 37 | /** 38 | * @param {jQuery.Event} event 39 | */ 40 | events['click ' + this.options.button.close] = function (event) { 41 | event.stopPropagation(); 42 | $(self.options.targetElement).dropdownDialog('close'); 43 | }; 44 | events['click ' + this.options.button.checkout] = $.proxy(function () { 45 | var cart = customerData.get('cart'), 46 | customer = customerData.get('customer'), 47 | element = $(this.options.button.checkout); 48 | 49 | if (!customer().firstname && cart().isGuestCheckoutAllowed === false) { 50 | // set URL for redirect on successful login/registration. It's postprocessed on backend. 51 | $.cookie('login_redirect', this.options.url.checkout); 52 | 53 | if (this.options.url.isRedirectRequired) { 54 | element.prop('disabled', true); 55 | location.href = this.options.url.loginUrl; 56 | } else { 57 | authenticationPopup.showModal(); 58 | } 59 | 60 | return false; 61 | } 62 | 63 | var paymentRequestApi = customerData.get('payment-request')().paymentRequestApi; 64 | if (paymentRequestApi && 65 | paymentRequestApi.enabled && 66 | paymentRequestApi.buttonMode === 1 && 67 | paymentRequest.isAvailable()) { 68 | paymentRequest.init(); 69 | } else { 70 | element.prop('disabled', true); 71 | location.href = this.options.url.checkout; 72 | } 73 | 74 | }, this); 75 | 76 | /** 77 | * @param {jQuery.Event} event 78 | */ 79 | events['click ' + this.options.button.remove] = function (event) { 80 | event.stopPropagation(); 81 | confirm({ 82 | content: self.options.confirmMessage, 83 | actions: { 84 | /** @inheritdoc */ 85 | confirm: function () { 86 | self._removeItem($(event.currentTarget)); 87 | }, 88 | 89 | /** @inheritdoc */ 90 | always: function (e) { 91 | e.stopImmediatePropagation(); 92 | } 93 | } 94 | }); 95 | }; 96 | 97 | /** 98 | * @param {jQuery.Event} event 99 | */ 100 | events['keyup ' + this.options.item.qty] = function (event) { 101 | self._showItemButton($(event.target)); 102 | }; 103 | 104 | /** 105 | * @param {jQuery.Event} event 106 | */ 107 | events['change ' + this.options.item.qty] = function (event) { 108 | self._showItemButton($(event.target)); 109 | }; 110 | 111 | /** 112 | * @param {jQuery.Event} event 113 | */ 114 | events['click ' + this.options.item.button] = function (event) { 115 | event.stopPropagation(); 116 | self._updateItemQty($(event.currentTarget)); 117 | }; 118 | 119 | /** 120 | * @param {jQuery.Event} event 121 | */ 122 | events['focusout ' + this.options.item.qty] = function (event) { 123 | self._validateQty($(event.currentTarget)); 124 | }; 125 | 126 | this._on(this.element, events); 127 | this._calcHeight(); 128 | this._isOverflowed(); 129 | } 130 | }; 131 | 132 | return function (targetWidget) { 133 | $.widget('mage.sidebar', targetWidget, widgetMixin); 134 | return $.mage.sidebar; 135 | }; 136 | }); 137 | --------------------------------------------------------------------------------