braintree.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /*browser:true*/
  6. /*global define*/
  7. define([
  8. 'jquery',
  9. 'uiComponent',
  10. 'Magento_Ui/js/modal/alert',
  11. 'Magento_Ui/js/lib/view/utils/dom-observer',
  12. 'mage/translate',
  13. 'Magento_Braintree/js/validator'
  14. ], function ($, Class, alert, domObserver, $t, validator) {
  15. 'use strict';
  16. return Class.extend({
  17. defaults: {
  18. $selector: null,
  19. selector: 'edit_form',
  20. container: 'payment_form_braintree',
  21. active: false,
  22. scriptLoaded: false,
  23. braintree: null,
  24. selectedCardType: null,
  25. checkout: null,
  26. imports: {
  27. onActiveChange: 'active'
  28. }
  29. },
  30. /**
  31. * Set list of observable attributes
  32. * @returns {exports.initObservable}
  33. */
  34. initObservable: function () {
  35. var self = this;
  36. validator.setConfig(this);
  37. self.$selector = $('#' + self.selector);
  38. this._super()
  39. .observe([
  40. 'active',
  41. 'scriptLoaded',
  42. 'selectedCardType'
  43. ]);
  44. // re-init payment method events
  45. self.$selector.off('changePaymentMethod.' + this.code)
  46. .on('changePaymentMethod.' + this.code, this.changePaymentMethod.bind(this));
  47. // listen block changes
  48. domObserver.get('#' + self.container, function () {
  49. if (self.scriptLoaded()) {
  50. self.$selector.off('submit');
  51. self.initBraintree();
  52. }
  53. });
  54. return this;
  55. },
  56. /**
  57. * Enable/disable current payment method
  58. * @param {Object} event
  59. * @param {String} method
  60. * @returns {exports.changePaymentMethod}
  61. */
  62. changePaymentMethod: function (event, method) {
  63. this.active(method === this.code);
  64. return this;
  65. },
  66. /**
  67. * Triggered when payment changed
  68. * @param {Boolean} isActive
  69. */
  70. onActiveChange: function (isActive) {
  71. if (!isActive) {
  72. this.$selector.off('submitOrder.braintree');
  73. return;
  74. }
  75. this.disableEventListeners();
  76. window.order.addExcludedPaymentMethod(this.code);
  77. if (!this.clientToken) {
  78. this.error($.mage.__('This payment is not available'));
  79. return;
  80. }
  81. this.enableEventListeners();
  82. if (!this.scriptLoaded()) {
  83. this.loadScript();
  84. }
  85. },
  86. /**
  87. * Load external Braintree SDK
  88. */
  89. loadScript: function () {
  90. var self = this,
  91. state = self.scriptLoaded;
  92. $('body').trigger('processStart');
  93. require([this.sdkUrl], function (braintree) {
  94. state(true);
  95. self.braintree = braintree;
  96. self.initBraintree();
  97. $('body').trigger('processStop');
  98. });
  99. },
  100. /**
  101. * Retrieves client token and setup Braintree SDK
  102. */
  103. initBraintree: function () {
  104. var self = this;
  105. try {
  106. $('body').trigger('processStart');
  107. $.getJSON(self.clientTokenUrl).done(function (response) {
  108. self.clientToken = response.clientToken;
  109. self._initBraintree();
  110. }).fail(function (response) {
  111. var failed = JSON.parse(response.responseText);
  112. $('body').trigger('processStop');
  113. self.error(failed.message);
  114. });
  115. } catch (e) {
  116. $('body').trigger('processStop');
  117. self.error(e.message);
  118. }
  119. },
  120. /**
  121. * Setup Braintree SDK
  122. */
  123. _initBraintree: function () {
  124. var self = this;
  125. this.disableEventListeners();
  126. if (self.checkout) {
  127. self.checkout.teardown(function () {
  128. self.checkout = null;
  129. });
  130. }
  131. self.braintree.setup(self.clientToken, 'custom', {
  132. id: self.selector,
  133. hostedFields: self.getHostedFields(),
  134. /**
  135. * Triggered when sdk was loaded
  136. */
  137. onReady: function (checkout) {
  138. self.checkout = checkout;
  139. $('body').trigger('processStop');
  140. self.enableEventListeners();
  141. },
  142. /**
  143. * Callback for success response
  144. * @param {Object} response
  145. */
  146. onPaymentMethodReceived: function (response) {
  147. if (self.validateCardType()) {
  148. self.setPaymentDetails(response.nonce);
  149. self.placeOrder();
  150. }
  151. },
  152. /**
  153. * Error callback
  154. * @param {Object} response
  155. */
  156. onError: function (response) {
  157. self.error(response.message);
  158. }
  159. });
  160. },
  161. /**
  162. * Get hosted fields configuration
  163. * @returns {Object}
  164. */
  165. getHostedFields: function () {
  166. var self = this,
  167. fields = {
  168. number: {
  169. selector: self.getSelector('cc_number')
  170. },
  171. expirationMonth: {
  172. selector: self.getSelector('cc_exp_month'),
  173. placeholder: $t('MM')
  174. },
  175. expirationYear: {
  176. selector: self.getSelector('cc_exp_year'),
  177. placeholder: $t('YY')
  178. },
  179. /**
  180. * Triggered when hosted field is changed
  181. * @param {Object} event
  182. */
  183. onFieldEvent: function (event) {
  184. return self.fieldEventHandler(event);
  185. }
  186. };
  187. if (self.useCvv) {
  188. fields.cvv = {
  189. selector: self.getSelector('cc_cid')
  190. };
  191. }
  192. return fields;
  193. },
  194. /**
  195. * Function to handle hosted fields events
  196. * @param {Object} event
  197. * @returns {Boolean}
  198. */
  199. fieldEventHandler: function (event) {
  200. var self = this,
  201. $cardType = $('#' + self.container).find('.icon-type');
  202. if (event.isEmpty === false) {
  203. self.validateCardType();
  204. }
  205. if (event.type !== 'fieldStateChange') {
  206. return false;
  207. }
  208. // Handle a change in validation or card type
  209. if (event.target.fieldKey === 'number') {
  210. self.selectedCardType(null);
  211. }
  212. // remove previously set classes
  213. $cardType.attr('class', 'icon-type');
  214. if (event.card) {
  215. $cardType.addClass('icon-type-' + event.card.type);
  216. self.selectedCardType(
  217. validator.getMageCardType(event.card.type, self.getCcAvailableTypes())
  218. );
  219. }
  220. },
  221. /**
  222. * Show alert message
  223. * @param {String} message
  224. */
  225. error: function (message) {
  226. alert({
  227. content: message
  228. });
  229. },
  230. /**
  231. * Enable form event listeners
  232. */
  233. enableEventListeners: function () {
  234. this.$selector.on('submitOrder.braintree', this.submitOrder.bind(this));
  235. },
  236. /**
  237. * Disable form event listeners
  238. */
  239. disableEventListeners: function () {
  240. this.$selector.off('submitOrder');
  241. this.$selector.off('submit');
  242. },
  243. /**
  244. * Store payment details
  245. * @param {String} nonce
  246. */
  247. setPaymentDetails: function (nonce) {
  248. var $container = $('#' + this.container);
  249. $container.find('[name="payment[payment_method_nonce]"]').val(nonce);
  250. },
  251. /**
  252. * Trigger order submit
  253. */
  254. submitOrder: function () {
  255. this.$selector.validate().form();
  256. this.$selector.trigger('afterValidate.beforeSubmit');
  257. $('body').trigger('processStop');
  258. // validate parent form
  259. if (this.$selector.validate().errorList.length) {
  260. return false;
  261. }
  262. $('#' + this.container).find('[type="submit"]').trigger('click');
  263. },
  264. /**
  265. * Place order
  266. */
  267. placeOrder: function () {
  268. $('#' + this.selector).trigger('realOrder');
  269. },
  270. /**
  271. * Get list of currently available card types
  272. * @returns {Array}
  273. */
  274. getCcAvailableTypes: function () {
  275. var types = [],
  276. $options = $(this.getSelector('cc_type')).find('option');
  277. $.map($options, function (option) {
  278. types.push($(option).val());
  279. });
  280. return types;
  281. },
  282. /**
  283. * Validate current entered card type
  284. * @returns {Boolean}
  285. */
  286. validateCardType: function () {
  287. var $input = $(this.getSelector('cc_number'));
  288. $input.removeClass('braintree-hosted-fields-invalid');
  289. if (!this.selectedCardType()) {
  290. $input.addClass('braintree-hosted-fields-invalid');
  291. return false;
  292. }
  293. $(this.getSelector('cc_type')).val(this.selectedCardType());
  294. return true;
  295. },
  296. /**
  297. * Get jQuery selector
  298. * @param {String} field
  299. * @returns {String}
  300. */
  301. getSelector: function (field) {
  302. return '#' + this.code + '_' + field;
  303. }
  304. });
  305. });