@flittpayments/js-sdk 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -354,8 +354,134 @@ const jsonParse = (value, defaults) => {
354
354
  }
355
355
  };
356
356
 
357
+ const PromiseWithResolvers = () => {
358
+ let resolve;
359
+ let reject;
360
+ const promise = new Promise((promiseResolve, promiseReject) => {
361
+ resolve = promiseResolve;
362
+ reject = promiseReject;
363
+ });
364
+ return { promise, resolve, reject }
365
+ };
366
+
367
+ /**
368
+ * @typedef PaymentRequestMethod
369
+ * @property {{[string]:any}} data
370
+ */
371
+
372
+ /**
373
+ * @typedef PaymentRequestOptions
374
+ * @property {boolean} [requestPayerEmail]
375
+ * @property {boolean} [requestPayerPhone]
376
+ * @property {boolean} [requestPayerName]
377
+ * @property {boolean} [requestShipping]
378
+ */
379
+
380
+ /**
381
+ * @typedef PaymentRequestConfig
382
+ * @property [PaymentRequestMethod] methods
383
+ * @property {{[string]:any}} options
384
+ */
385
+
386
+ /**
387
+ * @param {PaymentRequestConfig} config
388
+ */
389
+ const getAppleSessionConfig = (config) => {
390
+ const { methods, details, options } = config;
391
+ const method = methods.find((item) => item.supportedMethods === 'https://apple.com/apple-pay');
392
+ const requiredShippingContactFields = [];
393
+ const requiredBillingContactFields = [];
394
+ if (options.requestShipping) requiredBillingContactFields.push('postalAddress');
395
+ if (options.requestPayerName) requiredShippingContactFields.push('name');
396
+ if (options.requestPayerEmail) requiredShippingContactFields.push('email');
397
+ if (options.requestPayerPhone) requiredShippingContactFields.push('phone');
398
+ //method.data.recurringPaymentRequest = recurringPaymentRequest
399
+ //method.data.deferredPaymentRequest = deferredPaymentRequest
400
+ return {
401
+ version: method.data.version,
402
+ countryCode: method.data.countryCode,
403
+ supportedNetworks: method.data.supportedNetworks,
404
+ merchantCapabilities: method.data.merchantCapabilities,
405
+ merchantIdentifier: method.data.merchantIdentifier,
406
+ lineItems: method.data.lineItems || details.displayItems,
407
+ additionalLineItems: method.data.additionalLineItems,
408
+ recurringPaymentRequest: method.data.recurringPaymentRequest,
409
+ deferredPaymentRequest: method.data.deferredPaymentRequest,
410
+ //
411
+ currencyCode: method.data.currencyCode || details.total.amount.currency,
412
+ total: {
413
+ label: details.total.label,
414
+ amount: details.total.amount.value,
415
+ },
416
+ //
417
+ requiredBillingContactFields,
418
+ requiredShippingContactFields,
419
+ }
420
+ };
421
+
422
+ // export const deferredPaymentRequest = {
423
+ // paymentDescription: 'Бронювання готельного номеру',
424
+ // deferredBilling: {
425
+ // label: 'Бронювання',
426
+ // amount: '50.00',
427
+ // type: 'final',
428
+ // paymentTiming: 'deferred',
429
+ // deferredPaymentDate: new Date('2026-01-05'),
430
+ // },
431
+ // managementURL: 'https://flitt.ksv.app/manage-booking.html',
432
+ // }
433
+
434
+ // export const recurringPaymentRequest = {
435
+ // paymentDescription: 'Місячна підписка',
436
+ // regularBilling: {
437
+ // label: 'Підписка на місяць',
438
+ // amount: '20.00',
439
+ // type: 'final',
440
+ // paymentTiming: 'recurring',
441
+ // recurringPaymentStartDate: new Date('2026-01-01T00:00:00'),
442
+ // recurringPaymentIntervalUnit: 'month',
443
+ // recurringPaymentIntervalCount: 6,
444
+ // recurringPaymentEndDate: new Date('2028-01-01T00:00:00'),
445
+ // },
446
+ // managementURL: 'https://flitt.ksv.app/manage-subscription.html',
447
+ // }
448
+
449
+ const getFunction = (path) => {
450
+ const props = path.split('.');
451
+ const value = props.reduce((acc, cur) => {
452
+ if (acc && acc.hasOwnProperty(cur)) {
453
+ acc = acc[cur];
454
+ return acc
455
+ }
456
+ return null
457
+ }, window);
458
+ return isFunction(value) ? value : value
459
+ };
460
+
461
+ const loadExternalApi = (url, path) => {
462
+ const [existScript] = toArray(document.scripts).filter((script) => script.src === url);
463
+ if (existScript) return Promise.resolve(getFunction(path))
464
+ const { promise, resolve } = PromiseWithResolvers();
465
+ const script = document.createElement('script');
466
+ script.async = true;
467
+ script.src = url;
468
+ script.addEventListener('load', () => {
469
+ const value = getFunction(path);
470
+ console.log('api loading', 'success', url);
471
+ resolve(value);
472
+ });
473
+ script.addEventListener('error', (e) => {
474
+ const value = getFunction(path);
475
+ console.log('api loading', 'error', url);
476
+ resolve(value);
477
+ });
478
+ document.head.appendChild(script);
479
+ return promise
480
+ };
481
+
357
482
  var Utils = /*#__PURE__*/Object.freeze({
358
483
  __proto__: null,
484
+ PromiseWithResolvers: PromiseWithResolvers,
359
485
  addAttr: addAttr,
360
486
  addClass: addClass,
361
487
  cleanObject: cleanObject,
@@ -364,6 +490,8 @@ var Utils = /*#__PURE__*/Object.freeze({
364
490
  deparam: deparam,
365
491
  extend: extend,
366
492
  forEach: forEach,
493
+ getAppleSessionConfig: getAppleSessionConfig,
494
+ getFunction: getFunction,
367
495
  getPath: getPath,
368
496
  getPaymentRequest: getPaymentRequest,
369
497
  getStyle: getStyle,
@@ -381,6 +509,7 @@ var Utils = /*#__PURE__*/Object.freeze({
381
509
  isRegexp: isRegexp,
382
510
  isString: isString,
383
511
  jsonParse: jsonParse,
512
+ loadExternalApi: loadExternalApi,
384
513
  map: map,
385
514
  param: param,
386
515
  querySelector: querySelector,
@@ -418,6 +547,36 @@ const GoogleBaseRequest = {
418
547
  ],
419
548
  };
420
549
 
550
+ const PaymentMethodsConfig = [
551
+ {
552
+ url: 'https://pay.google.com/gp/p/js/pay.js',
553
+ name: 'google',
554
+ path: 'google.payments.api.PaymentsClient',
555
+ callback(PaymentsClient) {
556
+ if (PaymentsClient) {
557
+ return new PaymentsClient({ environment: 'TEST' })
558
+ .isReadyToPay(GoogleBaseRequest)
559
+ .catch((e) => {
560
+ console.log(e);
561
+ return false
562
+ })
563
+ }
564
+ return false
565
+ },
566
+ },
567
+ {
568
+ url: 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js',
569
+ name: 'apple',
570
+ path: 'ApplePaySession',
571
+ callback(ApplePaySession) {
572
+ if (ApplePaySession) {
573
+ return ApplePaySession.supportsVersion(3)
574
+ }
575
+ return false
576
+ },
577
+ },
578
+ ];
579
+
421
580
  const GooglePayLanguages = [
422
581
  'ar',
423
582
  'bg',
@@ -587,6 +746,7 @@ var Config = /*#__PURE__*/Object.freeze({
587
746
  GoogleBaseRequest: GoogleBaseRequest,
588
747
  GooglePayApi: GooglePayApi,
589
748
  GooglePayLanguages: GooglePayLanguages,
749
+ PaymentMethodsConfig: PaymentMethodsConfig,
590
750
  PaymentRequestDetails: PaymentRequestDetails,
591
751
  ProxyUrl: ProxyUrl
592
752
  });
@@ -642,28 +802,31 @@ function Deferred() {
642
802
  const promise = {
643
803
  done(callback) {
644
804
  if (isFunction(callback)) {
645
- if (status === RESOLVED) {
805
+ if (this.isResolved()) {
646
806
  callback.apply(resultContext, resultArgs);
807
+ } else {
808
+ resolveCallbacks.push(callback);
647
809
  }
648
- resolveCallbacks.push(callback);
649
810
  }
650
811
  return this
651
812
  },
652
813
  fail(callback) {
653
814
  if (isFunction(callback)) {
654
- if (status === REJECTED) {
815
+ if (this.isRejected()) {
655
816
  callback.apply(resultContext, resultArgs);
817
+ } else {
818
+ rejectCallbacks.push(callback);
656
819
  }
657
- rejectCallbacks.push(callback);
658
820
  }
659
821
  return this
660
822
  },
661
823
  progress(callback) {
662
824
  if (isFunction(callback)) {
663
- if (status === PROGRESS) {
825
+ if (this.isProgress()) {
664
826
  callback.apply(resultContext, resultArgs);
827
+ } else {
828
+ notifyCallbacks.push(callback);
665
829
  }
666
- notifyCallbacks.push(callback);
667
830
  }
668
831
  return this
669
832
  },
@@ -703,33 +866,31 @@ function Deferred() {
703
866
  isResolved() {
704
867
  return status === RESOLVED
705
868
  },
706
- resetState() {
707
- status = PENDING;
708
- return this
869
+ isProgress() {
870
+ return status === PROGRESS
709
871
  },
710
872
  resolveWith(context, args) {
711
- if (status === PENDING) {
873
+ if (this.isPending()) {
712
874
  status = RESOLVED;
713
875
  executeCallback(context, args, resolveCallbacks);
714
876
  }
715
877
  return this
716
878
  },
717
879
  rejectWith(context, args) {
718
- if (status === PENDING) {
880
+ if (this.isPending()) {
719
881
  status = REJECTED;
720
882
  executeCallback(context, args, rejectCallbacks);
721
883
  }
722
884
  return this
723
885
  },
724
886
  notifyWith(context, args) {
725
- if (status === PENDING) {
887
+ if (this.isPending()) {
726
888
  status = PROGRESS;
727
889
  executeCallback(context, args, notifyCallbacks);
728
890
  status = PENDING;
729
891
  }
730
892
  return this
731
893
  },
732
-
733
894
  resolve() {
734
895
  return this.resolveWith(this, Array.from(arguments))
735
896
  },
@@ -1466,6 +1627,7 @@ const Api = Module.extend({
1466
1627
  defaults: {
1467
1628
  origin: ApiOrigin,
1468
1629
  endpoint: ApiEndpoint,
1630
+ container: 'body',
1469
1631
  messages: {
1470
1632
  modalHeader:
1471
1633
  'Now you will be redirected to your bank 3DSecure. If you are not redirected please refer',
@@ -1473,6 +1635,7 @@ const Api = Module.extend({
1473
1635
  },
1474
1636
  },
1475
1637
  init(params) {
1638
+ this.iframeDeferred = Deferred();
1476
1639
  this.initParams(params);
1477
1640
  },
1478
1641
  url(type, url) {
@@ -1486,8 +1649,6 @@ const Api = Module.extend({
1486
1649
  this.params = this.utils.extend({}, this.defaults);
1487
1650
  this.extendParams(params);
1488
1651
  this.setOrigin(this.params.origin);
1489
- this.loaded = false;
1490
- this.created = false;
1491
1652
  },
1492
1653
  setOrigin(origin) {
1493
1654
  if (this.utils.isString(origin)) {
@@ -1497,10 +1658,21 @@ const Api = Module.extend({
1497
1658
  },
1498
1659
  scope(callback) {
1499
1660
  callback = this.proxy(callback);
1500
- if (this.createFrame().loaded === true) {
1501
- callback();
1661
+ this.domReady(function () {
1662
+ this.createFrame();
1663
+ if (this.iframeDeferred.isPending()) {
1664
+ this.iframeDeferred.done(callback);
1665
+ } else {
1666
+ callback();
1667
+ }
1668
+ });
1669
+ },
1670
+ domReady(callback) {
1671
+ callback = this.proxy(callback);
1672
+ if (document.readyState === 'loading') {
1673
+ window.addEventListener('DOMContentLoaded', callback);
1502
1674
  } else {
1503
- this.on('checkout.api', callback);
1675
+ callback();
1504
1676
  }
1505
1677
  },
1506
1678
  request(model, method, params) {
@@ -1530,186 +1702,84 @@ const Api = Module.extend({
1530
1702
  );
1531
1703
  return defer
1532
1704
  },
1533
- loadFrame(url) {
1705
+ createFrame() {
1706
+ if (this.iframe) return this
1534
1707
  this.iframe = this.utils.createElement('iframe');
1535
1708
  this.addAttr(this.iframe, {
1536
1709
  allowtransparency: true,
1537
1710
  frameborder: 0,
1538
1711
  scrolling: 'no',
1539
1712
  });
1540
- this.addAttr(this.iframe, { src: url });
1713
+ this.addAttr(this.iframe, { src: this.url('gateway') });
1541
1714
  this.addCss(this.iframe, ApiFrameCss);
1542
- this.body = this.utils.querySelector('body');
1543
- if (this.body.firstChild) {
1544
- this.body.insertBefore(this.iframe, this.body.firstChild);
1545
- } else {
1546
- this.body.appendChild(this.iframe);
1715
+ if (this.utils.isElement(this.params.container)) {
1716
+ this.container = this.params.container;
1547
1717
  }
1548
- return this.iframe
1549
- },
1550
- createFrame() {
1551
- if (this.created === false) {
1552
- this.created = true;
1553
- this.iframe = this.loadFrame(this.url('gateway'));
1718
+ if (this.utils.isString(this.params.container)) {
1719
+ this.container = this.utils.querySelector(this.params.container);
1720
+ }
1721
+ if (this.container) {
1722
+ if (this.container.firstChild) {
1723
+ this.container.insertBefore(this.iframe, this.container.firstChild);
1724
+ } else {
1725
+ this.container.appendChild(this.iframe);
1726
+ }
1554
1727
  this.connector = new Connector({
1555
1728
  target: this.iframe.contentWindow,
1556
1729
  origin: this.params.origin,
1557
1730
  });
1558
1731
  this.connector.on('load', this.proxy('onLoadConnector'));
1559
1732
  this.connector.on('modal', this.proxy('onOpenModal'));
1733
+ } else {
1734
+ throw Error(`container element not found: document.querySelector("${this.params.container}")`)
1560
1735
  }
1561
1736
  return this
1562
1737
  },
1563
1738
  onOpenModal(xhr, model) {
1564
- this.modal = new Modal({
1565
- checkout: this,
1566
- model: model,
1567
- });
1739
+ this.modal = new Modal({ checkout: this, model: model });
1568
1740
  this.modal.on('close', this.proxy('onCloseModal'));
1569
1741
  },
1570
1742
  onCloseModal(modal, data) {
1571
1743
  this.trigger('modal.close', modal, data);
1572
1744
  },
1573
1745
  onLoadConnector() {
1574
- this.loaded = true;
1746
+ this.iframeDeferred.resolve(true);
1575
1747
  this.connector.off('load');
1576
- this.trigger('checkout.api');
1577
- this.off('checkout.api');
1578
1748
  },
1579
1749
  });
1580
1750
 
1581
- const GooglePayClient = Module.extend({
1582
- id: 'google-payments-api',
1583
- init() {
1584
- this.client = null;
1585
- this.wrapper = this.utils.querySelector('head');
1586
- this.defer = Deferred();
1587
- },
1588
- load() {
1589
- if (this.utils.getPath('google.payments.api.PaymentsClient')) {
1590
- return this.defer.resolveWith(this)
1591
- }
1592
- if (this.utils.querySelector('#'.concat(this.id))) {
1593
- return this.defer
1594
- }
1595
- this.script = this.utils.createElement('script');
1596
- this.addAttr(this.script, {
1597
- id: this.id,
1598
- async: true,
1599
- src: GooglePayApi,
1600
- });
1601
- this.utils.isElement(this.wrapper) && this.wrapper.appendChild(this.script);
1602
- this.addEvent(this.script, 'load', 'onLoadSuccess');
1603
- this.addEvent(this.script, 'error', 'onLoadError');
1604
- return this.defer
1605
- },
1606
- show(methods) {
1607
- const method = methods.find(function (item) {
1608
- return item.supportedMethods === 'https://google.com/pay'
1609
- });
1610
- const client = this.getClient({ environment: method.data.environment });
1611
- return client.loadPaymentData(method.data)
1612
- },
1613
- readyToPay(cx, response) {
1614
- if (response.result) {
1615
- this.defer.resolveWith(this);
1616
- }
1617
- },
1618
- onError(cx, error) {
1619
- this.defer.rejectWith(this, error);
1620
- },
1621
- onLoadSuccess() {
1622
- const client = this.getClient();
1623
- if (client)
1624
- client
1625
- .isReadyToPay(GoogleBaseRequest)
1626
- .then(this.proxy('readyToPay'))
1627
- .catch(this.proxy('onError'));
1628
- },
1629
- onLoadError() {
1630
- this.defer.rejectWith(this);
1631
- },
1632
- getClient(options) {
1633
- if (options || this.client === null) {
1634
- const PaymentClient = this.utils.getPath('google.payments.api.PaymentsClient');
1635
- if (PaymentClient) {
1636
- this.client = new PaymentClient(options);
1637
- } else {
1638
- this.onError(null, new Error('Google Client Error'));
1639
- }
1640
- }
1641
- return this.client
1642
- },
1643
- });
1644
-
1645
- const GooglePay = new GooglePayClient();
1646
-
1647
- const getPaymentMethods = () => {
1648
- return [
1649
- [
1650
- 'google',
1651
- {
1652
- supportedMethods: 'https://google.com/pay',
1653
- data: GoogleBaseRequest,
1654
- },
1655
- ],
1656
- [
1657
- 'apple',
1658
- {
1659
- supportedMethods: 'https://apple.com/apple-pay',
1660
- },
1661
- () => hasProp(window, 'ApplePaySession'),
1662
- ],
1663
- ]
1664
- };
1665
-
1666
- let requestDeferred = null;
1667
-
1668
- let requestSupported = {
1669
- fallback: false,
1670
- provider: [],
1671
- };
1751
+ let supportedMethodsDeferred = null;
1672
1752
 
1673
1753
  const getSupportedMethods = () => {
1674
- if (requestDeferred) return requestDeferred
1675
- requestDeferred = Deferred();
1676
- const methods = getPaymentMethods();
1677
- const details = PaymentRequestDetails
1678
- ;(function check() {
1679
- const item = methods.shift();
1680
- if (item === undefined) {
1681
- if (requestSupported.provider.indexOf('google') === -1) {
1682
- requestSupported.fallback = true;
1683
- requestSupported.provider.push('google');
1684
- }
1685
- return requestDeferred.resolve(requestSupported)
1686
- }
1687
- const method = item.shift();
1688
- const config = item.shift();
1689
- const callback = item.shift();
1690
- if (isFunction(callback) && callback() === false) {
1691
- setTimeout(check, 25);
1692
- return false
1693
- }
1694
- const request = getPaymentRequest([config], details, {});
1695
- if (request) {
1696
- request
1697
- .canMakePayment()
1698
- .then(function (status) {
1699
- if (status === true) requestSupported.provider.push(method);
1700
- setTimeout(check, 25);
1754
+ if (supportedMethodsDeferred) return supportedMethodsDeferred
1755
+ supportedMethodsDeferred = Deferred();
1756
+ const promises = [];
1757
+ const supported = { provider: [] };
1758
+ forEach(PaymentMethodsConfig, (method) => {
1759
+ promises.push(
1760
+ loadExternalApi(method.url, method.path)
1761
+ .then(method.callback)
1762
+ .then((state) => {
1763
+ if (state) {
1764
+ supported.provider.push(method.name);
1765
+ } else {
1766
+ console.log('error loading api', google);
1767
+ }
1701
1768
  })
1702
- .catch(function () {
1703
- setTimeout(check, 25);
1704
- });
1705
- } else {
1706
- setTimeout(check, 25);
1769
+ );
1770
+ });
1771
+ Promise.all(promises).then(
1772
+ () => {
1773
+ supportedMethodsDeferred.resolve(supported);
1774
+ },
1775
+ () => {
1776
+ supportedMethodsDeferred.resolve(supported);
1707
1777
  }
1708
- })();
1709
- return requestDeferred
1778
+ );
1779
+ return supportedMethodsDeferred
1710
1780
  };
1711
1781
 
1712
- const PaymentRequestInterface = Module.extend({
1782
+ const PaymentRequestApi = Module.extend({
1713
1783
  config: {
1714
1784
  payment_system: '',
1715
1785
  fallback: false,
@@ -1748,6 +1818,16 @@ const PaymentRequestInterface = Module.extend({
1748
1818
  setMerchant(merchant) {
1749
1819
  this.merchant = merchant;
1750
1820
  },
1821
+ setBeforeCallback(beforeCallback) {
1822
+ if (isFunction(beforeCallback)) {
1823
+ this.params.before = beforeCallback;
1824
+ }
1825
+ },
1826
+ setAfterCallback(afterCallback) {
1827
+ if (isFunction(afterCallback)) {
1828
+ this.params.after = afterCallback;
1829
+ }
1830
+ },
1751
1831
  setApi(api) {
1752
1832
  if (api instanceof Api) this.api = api;
1753
1833
  return this
@@ -1761,171 +1841,151 @@ const PaymentRequestInterface = Module.extend({
1761
1841
  isFallbackMethod(method) {
1762
1842
  return method === 'google' && this.supported.fallback
1763
1843
  },
1764
- });
1765
-
1766
- const PaymentRequestApi = PaymentRequestInterface.extend({
1767
- request(method, params, success, failure) {
1768
- if (this.api) {
1769
- this.api.scope(
1770
- this.proxy(function () {
1771
- this.api
1772
- .request('api.checkout.pay', method, params)
1773
- .done(this.proxy(success))
1774
- .fail(this.proxy(failure));
1775
- })
1776
- );
1844
+ request(model, method, params, success, failure) {
1845
+ const context = this;
1846
+ if (context.api) {
1847
+ context.api.scope(function () {
1848
+ context.api.request(model, method, params).done(success).fail(failure);
1849
+ });
1777
1850
  }
1778
1851
  },
1852
+ session(params) {
1853
+ const defer = Deferred();
1854
+ this.request(
1855
+ 'api.checkout.pay',
1856
+ 'session',
1857
+ params,
1858
+ function (model) {
1859
+ defer.resolve(model.serialize());
1860
+ },
1861
+ function (model) {
1862
+ defer.reject(model);
1863
+ }
1864
+ );
1865
+ return defer
1866
+ },
1779
1867
  update(data) {
1868
+ if (this.isPending()) return
1869
+ this.setPending(true);
1870
+ const context = this;
1780
1871
  const defer = Deferred();
1872
+ this.params.data = data;
1781
1873
  this.request(
1874
+ 'api.checkout.pay',
1782
1875
  'methods',
1783
- data,
1784
- function (cx, model) {
1785
- this.onUpdate(cx, model);
1786
- defer.resolveWith(this, [model]);
1876
+ this.params.data,
1877
+ function (model) {
1878
+ context.setPending(false);
1879
+ context.setPayload(model.serialize());
1880
+ defer.resolve(model);
1787
1881
  },
1788
- function (cx, model) {
1789
- this.onError(cx, model);
1790
- defer.rejectWith(this, [model]);
1882
+ function (model) {
1883
+ context.setPending(false);
1884
+ context.trigger('error', model);
1885
+ defer.reject(model);
1791
1886
  }
1792
1887
  );
1793
1888
  return defer
1794
1889
  },
1795
- onUpdate(cx, model) {
1796
- this.setPayload(model.serialize());
1797
- },
1798
- onError(cx, model) {
1799
- this.trigger('error', model);
1800
- },
1801
1890
  isPending() {
1802
1891
  return this.pendingState === true
1803
1892
  },
1804
1893
  setPending(state) {
1805
1894
  this.pendingState = state;
1806
1895
  clearTimeout(this.pendingTimeoutEvent);
1807
- this.pendingTimeoutEvent = setTimeout(() => {
1808
- this.trigger('pending', state);
1809
- }, 100);
1810
- },
1811
- beforeCallback(defer) {
1812
- defer.resolve();
1813
- },
1814
- setBeforeCallback(callback) {
1815
- if (isFunction(callback)) {
1816
- this.beforeCallback = callback;
1817
- }
1818
- return this
1896
+ this.pendingTimeoutEvent = setTimeout(
1897
+ function (context) {
1898
+ context.trigger('pending', state);
1899
+ },
1900
+ 50,
1901
+ this
1902
+ );
1819
1903
  },
1820
1904
  before() {
1821
1905
  if (this.isPending()) return
1822
1906
  this.setPending(true);
1823
1907
  const defer = Deferred();
1824
- defer.always(
1825
- this.proxy(function () {
1826
- this.setPending(false);
1827
- })
1828
- );
1829
- this.beforeCallback(defer);
1908
+ const context = this;
1909
+ defer.always(function () {
1910
+ context.setPending(false);
1911
+ });
1912
+ this.params.before(defer);
1913
+ return defer
1914
+ },
1915
+ after(params) {
1916
+ const defer = Deferred();
1917
+ this.params.after(defer, params);
1830
1918
  return defer
1831
1919
  },
1920
+ withApplePay(response, config) {
1921
+ const sessionConfig = getAppleSessionConfig(config);
1922
+ const request = new ApplePaySession(sessionConfig.version, sessionConfig);
1923
+ const merchant = this.merchant;
1924
+ const context = this;
1925
+ request.onvalidatemerchant = function (event) {
1926
+ context
1927
+ .session({
1928
+ url: event.validationURL,
1929
+ domain: location.host,
1930
+ merchant_id: merchant,
1931
+ })
1932
+ .done((session) => {
1933
+ try {
1934
+ request.completeMerchantValidation(session.data);
1935
+ } catch (error) {
1936
+ response.reject({ code: error.code, message: error.message });
1937
+ }
1938
+ });
1939
+ };
1940
+ request.oncancel = function () {
1941
+ response.reject({ code: 20, message: 'cancel' });
1942
+ };
1943
+ request.onpaymentauthorized = function (event) {
1944
+ request.completePayment(ApplePaySession.STATUS_SUCCESS);
1945
+ response.resolve(event.payment);
1946
+ };
1947
+ request.begin();
1948
+ },
1949
+ withGooglePay(response, config) {
1950
+ const { methods } = config;
1951
+ const method = methods.find((item) => item.supportedMethods === 'https://google.com/pay');
1952
+ const client = new google.payments.api.PaymentsClient({ environment: method.data.environment });
1953
+ client
1954
+ .loadPaymentData(method.data)
1955
+ .then((paymentData) => {
1956
+ response.resolve(paymentData);
1957
+ })
1958
+ .catch((error) => {
1959
+ response.reject({ code: error.code, message: error.message });
1960
+ });
1961
+ },
1832
1962
  pay(method) {
1833
1963
  if (this.isPending()) return
1834
1964
  this.setPending(true);
1835
- const payload = this.getProviderPayload(method);
1965
+ const context = this;
1836
1966
  const response = Deferred();
1967
+ const payload = this.getProviderPayload(method);
1837
1968
  response.always(function () {
1838
- this.setPending(false);
1969
+ context.setPending(false);
1839
1970
  });
1840
- if (this.isMethodSupported(method) === false) {
1841
- return response.rejectWith(this, [{ test: true }])
1842
- }
1843
- if (this.isFallbackMethod(method)) {
1844
- this.makePaymentFallback(response, payload.methods);
1845
- } else {
1846
- this.makeNativePayment(response, payload);
1971
+ switch (method) {
1972
+ case 'google':
1973
+ this.withGooglePay(response, payload);
1974
+ break
1975
+ case 'apple':
1976
+ this.withApplePay(response, payload);
1977
+ break
1847
1978
  }
1848
1979
  return response
1849
- .done(function (details) {
1850
- this.trigger('details', {
1851
- payment_system: this.payload.payment_system,
1852
- data: details,
1980
+ .done(function (data) {
1981
+ context.trigger('details', {
1982
+ payment_system: context.payload.payment_system,
1983
+ data,
1853
1984
  });
1854
1985
  })
1855
1986
  .fail(function (error) {
1856
- this.trigger('error', error);
1857
- if (this.params.embedded === true) {
1858
- location.reload();
1859
- this.trigger('reload', this.params);
1860
- }
1861
- })
1862
- },
1863
- makeNativePayment(defer, payload) {
1864
- const self = this;
1865
- const request = getPaymentRequest(payload.methods, payload.details, payload.options);
1866
- this.addEvent(request, 'merchantvalidation', 'merchantValidation');
1867
- request
1868
- .canMakePayment()
1869
- .then(function () {
1870
- request
1871
- .show()
1872
- .then(function (response) {
1873
- response.complete('success').then(function () {
1874
- defer.resolveWith(self, [response.details]);
1875
- });
1876
- })
1877
- .catch(function (e) {
1878
- defer.rejectWith(self, [{ code: e.code, message: e.message }]);
1879
- });
1987
+ context.trigger('error', error);
1880
1988
  })
1881
- .catch(function (e) {
1882
- defer.rejectWith(self, [{ code: e.code, message: e.message }]);
1883
- });
1884
- },
1885
- makePaymentFallback(defer, methods) {
1886
- const self = this;
1887
- GooglePay.load().then(() => {
1888
- GooglePay.show(methods)
1889
- .then((details) => {
1890
- defer.resolveWith(self, [details]);
1891
- })
1892
- .catch((e) => {
1893
- defer.rejectWith(self, [{ code: e.code, message: e.message }]);
1894
- });
1895
- });
1896
- },
1897
- appleSession(params) {
1898
- const defer = Deferred();
1899
- this.request(
1900
- 'session',
1901
- params,
1902
- function (c, model) {
1903
- defer.resolveWith(this, [model.serialize()]);
1904
- },
1905
- function (c, model) {
1906
- defer.rejectWith(this, [model]);
1907
- }
1908
- );
1909
- return defer
1910
- },
1911
- merchantValidation(cx, event) {
1912
- const { validationURL } = event;
1913
- const { host } = location;
1914
- this.appleSession({
1915
- url: validationURL,
1916
- domain: host,
1917
- merchant_id: this.merchant,
1918
- })
1919
- .done(function (session) {
1920
- try {
1921
- event.complete(session.data);
1922
- } catch (error) {
1923
- this.trigger('error', error);
1924
- }
1925
- })
1926
- .fail(function (error) {
1927
- this.trigger('error', error);
1928
- });
1929
1989
  },
1930
1990
  });
1931
1991
 
@@ -1942,11 +2002,22 @@ const PaymentElement = Module.extend({
1942
2002
  height: 38,
1943
2003
  },
1944
2004
  init(params) {
2005
+ this.state = {
2006
+ isSupported: false,
2007
+ isAllowed: false,
2008
+ isMounted: false,
2009
+ };
1945
2010
  this.params = {};
1946
2011
  this.utils.extend(this.params, this.defaults);
1947
2012
  this.utils.extend(this.params, params);
1948
2013
  this.initElement();
1949
2014
  },
2015
+ timeout(callback, timeout) {
2016
+ this.timeoutMap = this.timeoutMap || {};
2017
+ clearTimeout(this.timeoutMap[timeout]);
2018
+ this.timeoutMap[callback] = setTimeout(callback, timeout);
2019
+ return this
2020
+ },
1950
2021
  getElementUrl() {
1951
2022
  return [this.params.origin, this.params.endpoint].join('')
1952
2023
  },
@@ -2005,18 +2076,19 @@ const PaymentElement = Module.extend({
2005
2076
  },
2006
2077
  onClickFail() {},
2007
2078
  onSupported(cx, supported) {
2008
- if (supported.provider.includes(this.params.method)) {
2009
- this.mount();
2010
- }
2079
+ this.state.isSupported = supported.provider.includes(this.params.method);
2011
2080
  },
2012
2081
  onPayload(cx, payload) {
2013
- if (payload.allowed.includes(this.params.method)) {
2014
- this.show();
2015
- }
2082
+ this.state.isAllowed = payload.allowed.includes(this.params.method);
2083
+ this.render();
2016
2084
  },
2017
2085
  onPending(cx, state) {
2018
2086
  this.pending = state;
2019
- this.send('pending', { state: state });
2087
+ this.addCss(this.element, {
2088
+ transition: 'height 0.2s ease-out, opacity 0.4s ease-out',
2089
+ pointerEvents: this.pending ? 'none' : '',
2090
+ opacity: this.pending ? '0.5' : '1',
2091
+ });
2020
2092
  },
2021
2093
  initEvents() {
2022
2094
  this.addEvent(this.button, 'mouseenter', 'onEvent');
@@ -2043,36 +2115,50 @@ const PaymentElement = Module.extend({
2043
2115
  this.initEvents();
2044
2116
  return this
2045
2117
  },
2046
- mount() {
2047
- this.appendTo(this.params.appendTo);
2118
+ render() {
2119
+ if (this.state.isSupported === false) return this
2120
+ if (this.state.isAllowed === false) return this
2121
+ this.mount();
2122
+ this.show();
2123
+ },
2124
+ unmount() {
2125
+ if (this.element.parentNode) {
2126
+ this.element.parentNode.removeChild(this.element);
2127
+ }
2128
+ return this
2048
2129
  },
2049
- isMounted() {
2050
- return document.body.contains(this.element)
2130
+ mount() {
2131
+ if (document.body.contains(this.element) === false) {
2132
+ this.appendTo(this.params.appendTo);
2133
+ }
2134
+ return this
2051
2135
  },
2052
2136
  show() {
2053
- if (this.isMounted() === false) return this
2054
- this.addCss(this.iframe, {
2055
- transition: 'opacity 0.6s 0.4s ease-out',
2056
- opacity: this.utils.cssUnit(1),
2057
- });
2058
- this.addCss(this.element, {
2059
- transition: 'height 0.2s ease-out',
2060
- height: this.utils.cssUnit(this.params.height, 'px'),
2061
- });
2062
- this.trigger('show', {});
2137
+ this.timeout(() => {
2138
+ this.addCss(this.iframe, {
2139
+ transition: 'opacity 0.6s 0.4s ease-out',
2140
+ opacity: this.utils.cssUnit(1),
2141
+ });
2142
+ this.addCss(this.element, {
2143
+ transition: 'height 0.2s ease-out',
2144
+ height: this.utils.cssUnit(this.params.height, 'px'),
2145
+ });
2146
+ this.trigger('show', {});
2147
+ }, 25);
2063
2148
  return this
2064
2149
  },
2065
2150
  hide() {
2066
- if (this.isMounted() === false) return this
2067
- this.addCss(this.iframe, {
2068
- transition: 'opacity 0.4s ease-out',
2069
- opacity: this.utils.cssUnit(0),
2070
- });
2071
- this.addCss(this.element, {
2072
- transition: 'height 0.2s 0.4s ease-out',
2073
- height: this.utils.cssUnit(0, 'px'),
2074
- });
2075
- this.trigger('hide', {});
2151
+ this.timeout(() => {
2152
+ this.addCss(this.iframe, {
2153
+ transition: 'opacity 0.4s ease-out',
2154
+ opacity: this.utils.cssUnit(0),
2155
+ });
2156
+ this.addCss(this.element, {
2157
+ transition: 'height 0.2s 0.4s ease-out',
2158
+ height: this.utils.cssUnit(0, 'px'),
2159
+ });
2160
+ this.trigger('hide', {});
2161
+ }, 25);
2076
2162
  return this
2077
2163
  },
2078
2164
  });
@@ -2095,6 +2181,9 @@ const PaymentButton = Module.extend({
2095
2181
  before(defer) {
2096
2182
  defer.resolve();
2097
2183
  },
2184
+ after(defer, params) {
2185
+ defer.resolve(params);
2186
+ },
2098
2187
  },
2099
2188
  init(params) {
2100
2189
  this.elements = {};
@@ -2105,7 +2194,8 @@ const PaymentButton = Module.extend({
2105
2194
  origin: params.origin || this.defaults.origin,
2106
2195
  endpoint: this.utils.extend({}, this.defaults.endpoint, params.endpoint),
2107
2196
  style: this.utils.extend({}, this.defaults.style, params.style),
2108
- before: params.before || this.defaults.before,
2197
+ before: isFunction(params.before) ? params.before : this.defaults.before,
2198
+ after: isFunction(params.after) ? params.after : this.defaults.after,
2109
2199
  data: this.utils.extend({}, this.defaults.data, params.data),
2110
2200
  };
2111
2201
  this.initApi(params.api);
@@ -2130,10 +2220,12 @@ const PaymentButton = Module.extend({
2130
2220
  }
2131
2221
  },
2132
2222
  initPaymentRequestApi() {
2133
- this.request = new PaymentRequestApi();
2223
+ this.request = new PaymentRequestApi({
2224
+ before: this.params.before,
2225
+ after: this.params.after,
2226
+ });
2134
2227
  this.request.setApi(this.api);
2135
2228
  this.request.setMerchant(this.params.data.merchant_id);
2136
- this.request.setBeforeCallback(this.params.before);
2137
2229
  this.request.on('details', this.proxy('onDetails'));
2138
2230
  this.request.on('error', this.proxy('onError'));
2139
2231
  },
@@ -2170,10 +2262,16 @@ const PaymentButton = Module.extend({
2170
2262
  },
2171
2263
  onDetails(cx, data) {
2172
2264
  this.api.scope(() => {
2173
- this.api
2174
- .request('api.checkout.form', 'request', this.utils.extend({}, this.params.data, data))
2175
- .done(this.proxy('onSuccess'))
2176
- .fail(this.proxy('onError'));
2265
+ this.request.after(this.params.data).done((extendParams) => {
2266
+ this.api
2267
+ .request(
2268
+ 'api.checkout.form',
2269
+ 'request',
2270
+ this.utils.extend({}, this.params.data, extendParams || {}, data)
2271
+ )
2272
+ .done(this.proxy('onSuccess'))
2273
+ .fail(this.proxy('onError'));
2274
+ });
2177
2275
  });
2178
2276
  },
2179
2277
  onSuccess(cx, data) {