swell-js 5.6.1 → 5.8.0

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.
package/dist/payment.mjs CHANGED
@@ -678,6 +678,7 @@ async function createPaymentMethod(stripe, cardElement, cart) {
678
678
  return error
679
679
  ? { error }
680
680
  : {
681
+ gateway: 'stripe',
681
682
  token: paymentMethod.id,
682
683
  last4: paymentMethod.card.last4,
683
684
  exp_month: paymentMethod.card.exp_month,
@@ -781,13 +782,13 @@ function getPaymentRequestData(cart, params) {
781
782
  tax_included_total,
782
783
  settings,
783
784
  } = cart;
784
- const { price: shippingPrice, service_name } = shipping || {};
785
+ const { price: shippingPrice, service, service_name } = shipping || {};
785
786
  const { country, name } = settings || {};
786
787
  const { require: { shipping: requireShipping } = {} } = params;
787
788
 
788
789
  const stripeCurrency = currency.toLowerCase();
789
790
  const displayItems = items.map((item) => ({
790
- label: get(item, 'product.name', 'Unknown product'),
791
+ label: item.product_name || item.product?.name || 'Unknown product',
791
792
  amount: stripeAmountByCurrency(
792
793
  currency,
793
794
  item.price_total - item.discount_total,
@@ -809,14 +810,21 @@ function getPaymentRequestData(cart, params) {
809
810
  }
810
811
 
811
812
  let shippingOptions;
812
- const services = get(shipment_rating, 'services');
813
- if (Array.isArray(services) && services.length > 0) {
813
+ const services = shipment_rating?.services;
814
+ if (requireShipping && Array.isArray(services) && services.length > 0) {
814
815
  shippingOptions = services.map((service) => ({
815
816
  id: service.id,
816
817
  label: service.name,
817
818
  detail: service.description,
818
819
  amount: stripeAmountByCurrency(currency, service.price),
819
820
  }));
821
+
822
+ const index = shippingOptions.findIndex((item) => item.id === service);
823
+
824
+ if (index > 0) {
825
+ // Move selected shipping option to the top
826
+ shippingOptions.unshift(...shippingOptions.splice(index, 1));
827
+ }
820
828
  }
821
829
 
822
830
  return {
@@ -828,7 +836,7 @@ function getPaymentRequestData(cart, params) {
828
836
  pending: true,
829
837
  },
830
838
  displayItems,
831
- ...(requireShipping && { shippingOptions }),
839
+ ...(shippingOptions && { shippingOptions }),
832
840
  };
833
841
  }
834
842
 
@@ -948,6 +956,10 @@ class StripeCardPayment extends Payment {
948
956
  throw new Error(paymentMethod.error.message);
949
957
  }
950
958
 
959
+ if (!isLiveMode(this.method.mode)) {
960
+ paymentMethod.test = true;
961
+ }
962
+
951
963
  // should save payment method data when payment amount is not chargeable
952
964
  if (!isStripeChargeableAmount(cart.capture_total, cart.currency)) {
953
965
  await this.updateCart({
@@ -1372,6 +1384,7 @@ class StripeKlarnaPayment extends Payment {
1372
1384
 
1373
1385
  /** @typedef {import('@stripe/stripe-js').Stripe} Stripe */
1374
1386
  /** @typedef {import('@stripe/stripe-js').PaymentRequestPaymentMethodEvent} PaymentRequestPaymentMethodEvent */
1387
+ /** @typedef {import('../../../types').Cart} Cart */
1375
1388
 
1376
1389
  class StripeGooglePayment extends Payment {
1377
1390
  constructor(api, options, params, methods) {
@@ -1439,6 +1452,7 @@ class StripeGooglePayment extends Payment {
1439
1452
  this.element.mount(`#${this.elementContainer.id}`);
1440
1453
  }
1441
1454
 
1455
+ /** @param {Cart} cart */
1442
1456
  _createPaymentRequest(cart) {
1443
1457
  const { require: { name, email, shipping, phone } = {} } = this.params;
1444
1458
 
@@ -1451,15 +1465,10 @@ class StripeGooglePayment extends Payment {
1451
1465
  ...getPaymentRequestData(cart, this.params),
1452
1466
  });
1453
1467
 
1454
- paymentRequest.on(
1455
- 'shippingaddresschange',
1456
- this._onShippingAddressChange.bind(this),
1457
- );
1458
- paymentRequest.on(
1459
- 'shippingoptionchange',
1460
- this._onShippingOptionChange.bind(this),
1461
- );
1462
- paymentRequest.on('paymentmethod', this._onPaymentMethod.bind(this));
1468
+ paymentRequest
1469
+ .on('shippingaddresschange', this._onShippingAddressChange.bind(this))
1470
+ .on('shippingoptionchange', this._onShippingOptionChange.bind(this))
1471
+ .on('paymentmethod', this._onPaymentMethod.bind(this));
1463
1472
 
1464
1473
  return paymentRequest;
1465
1474
  }
@@ -1467,9 +1476,9 @@ class StripeGooglePayment extends Payment {
1467
1476
  /** @param {import('@stripe/stripe-js').PaymentRequestShippingAddressEvent} event */
1468
1477
  async _onShippingAddressChange(event) {
1469
1478
  const { shippingAddress, updateWith } = event;
1470
- const shipping = this._mapShippingAddress(shippingAddress);
1479
+
1471
1480
  const cart = await this.updateCart({
1472
- shipping: { ...shipping, service: null },
1481
+ shipping: this._mapShippingAddress(shippingAddress),
1473
1482
  shipment_rating: null,
1474
1483
  });
1475
1484
 
@@ -1509,16 +1518,15 @@ class StripeGooglePayment extends Payment {
1509
1518
  shippingOption,
1510
1519
  complete,
1511
1520
  } = event;
1512
- const { require: { shipping: requireShipping } = {} } = this.params;
1513
1521
 
1514
1522
  await this.updateCart({
1515
1523
  account: {
1516
- email: payerEmail,
1524
+ email: payerEmail || billing_details?.email,
1517
1525
  },
1518
- ...(requireShipping && {
1526
+ ...(shippingAddress && {
1519
1527
  shipping: {
1520
1528
  ...this._mapShippingAddress(shippingAddress),
1521
- service: shippingOption.id,
1529
+ service: shippingOption?.id || undefined,
1522
1530
  },
1523
1531
  }),
1524
1532
  billing: {
@@ -1532,9 +1540,9 @@ class StripeGooglePayment extends Payment {
1532
1540
  exp_month: card.exp_month,
1533
1541
  exp_year: card.exp_year,
1534
1542
  last4: card.last4,
1535
- address_check: card.checks.address_line1_check,
1536
- zip_check: card.checks.address_postal_code_check,
1537
- cvc_check: card.checks.cvc_check,
1543
+ address_check: card.checks?.address_line1_check,
1544
+ zip_check: card.checks?.address_postal_code_check,
1545
+ cvc_check: card.checks?.cvc_check,
1538
1546
  },
1539
1547
  },
1540
1548
  });
@@ -1582,8 +1590,8 @@ class StripeGooglePayment extends Payment {
1582
1590
  _mapShippingAddress(address = {}) {
1583
1591
  return {
1584
1592
  name: address.recipient,
1585
- address1: address.addressLine[0],
1586
- address2: address.addressLine[1],
1593
+ address1: address.addressLine?.[0],
1594
+ address2: address.addressLine?.[1],
1587
1595
  city: address.city,
1588
1596
  state: address.region,
1589
1597
  zip: address.postalCode,
@@ -1597,18 +1605,19 @@ class StripeGooglePayment extends Payment {
1597
1605
  return {
1598
1606
  name: address.name,
1599
1607
  phone: address.phone,
1600
- address1: address.address.line1,
1601
- address2: address.address.line2,
1602
- city: address.address.city,
1603
- state: address.address.state,
1604
- zip: address.address.postal_code,
1605
- country: address.address.country,
1608
+ address1: address.address?.line1,
1609
+ address2: address.address?.line2,
1610
+ city: address.address?.city,
1611
+ state: address.address?.state,
1612
+ zip: address.address?.postal_code,
1613
+ country: address.address?.country,
1606
1614
  };
1607
1615
  }
1608
1616
  }
1609
1617
 
1610
1618
  /** @typedef {import('@stripe/stripe-js').Stripe} Stripe */
1611
1619
  /** @typedef {import('@stripe/stripe-js').PaymentRequestPaymentMethodEvent} PaymentRequestPaymentMethodEvent */
1620
+ /** @typedef {import('../../../types').Cart} Cart */
1612
1621
 
1613
1622
  class StripeApplePayment extends Payment {
1614
1623
  constructor(api, options, params, methods) {
@@ -1662,7 +1671,7 @@ class StripeApplePayment extends Payment {
1662
1671
  const paymentRequest = this._createPaymentRequest(cart);
1663
1672
  const canMakePayment = await paymentRequest.canMakePayment();
1664
1673
 
1665
- if (!canMakePayment || !canMakePayment.applePay) {
1674
+ if (!canMakePayment?.applePay) {
1666
1675
  throw new Error(
1667
1676
  'This device is not capable of making Apple Pay payments',
1668
1677
  );
@@ -1699,6 +1708,7 @@ class StripeApplePayment extends Payment {
1699
1708
  }
1700
1709
  }
1701
1710
 
1711
+ /** @param {Cart} cart */
1702
1712
  _createPaymentRequest(cart) {
1703
1713
  const { require: { name, email, shipping, phone } = {} } = this.params;
1704
1714
 
@@ -1711,15 +1721,10 @@ class StripeApplePayment extends Payment {
1711
1721
  ...getPaymentRequestData(cart, this.params),
1712
1722
  });
1713
1723
 
1714
- paymentRequest.on(
1715
- 'shippingaddresschange',
1716
- this._onShippingAddressChange.bind(this),
1717
- );
1718
- paymentRequest.on(
1719
- 'shippingoptionchange',
1720
- this._onShippingOptionChange.bind(this),
1721
- );
1722
- paymentRequest.on('paymentmethod', this._onPaymentMethod.bind(this));
1724
+ paymentRequest
1725
+ .on('shippingaddresschange', this._onShippingAddressChange.bind(this))
1726
+ .on('shippingoptionchange', this._onShippingOptionChange.bind(this))
1727
+ .on('paymentmethod', this._onPaymentMethod.bind(this));
1723
1728
 
1724
1729
  return paymentRequest;
1725
1730
  }
@@ -1727,9 +1732,9 @@ class StripeApplePayment extends Payment {
1727
1732
  /** @param {import('@stripe/stripe-js').PaymentRequestShippingAddressEvent} event */
1728
1733
  async _onShippingAddressChange(event) {
1729
1734
  const { shippingAddress, updateWith } = event;
1730
- const shipping = this._mapShippingAddress(shippingAddress);
1735
+
1731
1736
  const cart = await this.updateCart({
1732
- shipping: { ...shipping, service: null },
1737
+ shipping: this._mapShippingAddress(shippingAddress),
1733
1738
  shipment_rating: null,
1734
1739
  });
1735
1740
 
@@ -1769,16 +1774,15 @@ class StripeApplePayment extends Payment {
1769
1774
  shippingOption,
1770
1775
  complete,
1771
1776
  } = event;
1772
- const { require: { shipping: requireShipping } = {} } = this.params;
1773
1777
 
1774
1778
  await this.updateCart({
1775
1779
  account: {
1776
- email: payerEmail,
1780
+ email: payerEmail || billing_details?.email,
1777
1781
  },
1778
- ...(requireShipping && {
1782
+ ...(shippingAddress && {
1779
1783
  shipping: {
1780
1784
  ...this._mapShippingAddress(shippingAddress),
1781
- service: shippingOption.id,
1785
+ service: shippingOption?.id || undefined,
1782
1786
  },
1783
1787
  }),
1784
1788
  billing: {
@@ -1792,9 +1796,9 @@ class StripeApplePayment extends Payment {
1792
1796
  exp_month: card.exp_month,
1793
1797
  exp_year: card.exp_year,
1794
1798
  last4: card.last4,
1795
- address_check: card.checks.address_line1_check,
1796
- zip_check: card.checks.address_postal_code_check,
1797
- cvc_check: card.checks.cvc_check,
1799
+ address_check: card.checks?.address_line1_check,
1800
+ zip_check: card.checks?.address_postal_code_check,
1801
+ cvc_check: card.checks?.cvc_check,
1798
1802
  },
1799
1803
  },
1800
1804
  });
@@ -1808,8 +1812,8 @@ class StripeApplePayment extends Payment {
1808
1812
  _mapShippingAddress(address = {}) {
1809
1813
  return {
1810
1814
  name: address.recipient,
1811
- address1: address.addressLine[0],
1812
- address2: address.addressLine[1],
1815
+ address1: address.addressLine?.[0],
1816
+ address2: address.addressLine?.[1],
1813
1817
  city: address.city,
1814
1818
  state: address.region,
1815
1819
  zip: address.postalCode,
@@ -1823,12 +1827,12 @@ class StripeApplePayment extends Payment {
1823
1827
  return {
1824
1828
  name: address.name,
1825
1829
  phone: address.phone,
1826
- address1: address.address.line1,
1827
- address2: address.address.line2,
1828
- city: address.address.city,
1829
- state: address.address.state,
1830
- zip: address.address.postal_code,
1831
- country: address.address.country,
1830
+ address1: address.address?.line1,
1831
+ address2: address.address?.line2,
1832
+ city: address.address?.city,
1833
+ state: address.address?.state,
1834
+ zip: address.address?.postal_code,
1835
+ country: address.address?.country,
1832
1836
  };
1833
1837
  }
1834
1838
  }
@@ -2096,6 +2100,44 @@ function getDiscountLabel(discount, cart) {
2096
2100
  return discount.id;
2097
2101
  }
2098
2102
 
2103
+ /** @type {Map<string, number>} */
2104
+ const CURRENCIES_CENTS = new Map();
2105
+
2106
+ /**
2107
+ * @param {string} currency
2108
+ * @returns {number}
2109
+ */
2110
+ function getCurrencyCents(currency) {
2111
+ let cents = CURRENCIES_CENTS.get(currency);
2112
+
2113
+ if (cents !== undefined) {
2114
+ return cents;
2115
+ }
2116
+
2117
+ const formatter = new Intl.NumberFormat('en-US', {
2118
+ style: 'currency',
2119
+ currency,
2120
+ });
2121
+
2122
+ const options = formatter.resolvedOptions();
2123
+ const decimals = options.maximumFractionDigits ?? 2;
2124
+
2125
+ cents = 10 ** decimals;
2126
+
2127
+ CURRENCIES_CENTS.set(currency, cents);
2128
+
2129
+ return cents;
2130
+ }
2131
+
2132
+ /**
2133
+ * @param {string} currency
2134
+ * @param {number} amount
2135
+ * @returns {number}
2136
+ */
2137
+ function amountInCents(currency, amount) {
2138
+ return Math.round(amount * getCurrencyCents(currency));
2139
+ }
2140
+
2099
2141
  /** @typedef {import('./payment').default} Payment */
2100
2142
  /** @typedef {import('../../types').Cart} Cart */
2101
2143
  /** @typedef {import('../../types').Address} Address */
@@ -2183,7 +2225,7 @@ async function onPaymentDataChanged(intermediatePaymentData) {
2183
2225
  this,
2184
2226
  cart,
2185
2227
  ),
2186
- newTransactionInfo: getTransactionInfo(cart),
2228
+ newTransactionInfo: getTransactionInfo.call(this, cart),
2187
2229
  };
2188
2230
  }
2189
2231
 
@@ -2196,7 +2238,7 @@ async function onPaymentDataChanged(intermediatePaymentData) {
2196
2238
  },
2197
2239
  });
2198
2240
 
2199
- return { newTransactionInfo: getTransactionInfo(cart) };
2241
+ return { newTransactionInfo: getTransactionInfo.call(this, cart) };
2200
2242
  } catch (err) {
2201
2243
  return createError(
2202
2244
  intermediatePaymentData.callbackTrigger,
@@ -2218,7 +2260,7 @@ async function onPaymentDataChanged(intermediatePaymentData) {
2218
2260
 
2219
2261
  return {
2220
2262
  newOfferInfo: getOfferInfo(cart),
2221
- newTransactionInfo: getTransactionInfo(cart),
2263
+ newTransactionInfo: getTransactionInfo.call(this, cart),
2222
2264
  };
2223
2265
  } catch (err) {
2224
2266
  return createError(
@@ -2318,12 +2360,13 @@ function getDisplayItems(cart) {
2318
2360
 
2319
2361
  /**
2320
2362
  * Converts cart data to Google Pay transaction information.
2363
+ * @this {Payment}
2321
2364
  * @param {Cart} cart - The current cart data
2322
2365
  * @returns {google.payments.api.TransactionInfo} Transaction info for Google Pay
2323
2366
  */
2324
2367
  function getTransactionInfo(cart) {
2325
- const { settings, capture_total, currency, shipment_delivery, shipping } =
2326
- cart;
2368
+ const { require: { shipping: shipmentDelivery = false } = {} } = this.params;
2369
+ const { settings, capture_total, currency, shipping } = cart;
2327
2370
 
2328
2371
  return {
2329
2372
  countryCode: settings?.country || 'US',
@@ -2331,7 +2374,7 @@ function getTransactionInfo(cart) {
2331
2374
  totalPrice: String(capture_total ?? 0),
2332
2375
  // Set status to FINAL if shipping has been selected, otherwise ESTIMATED
2333
2376
  totalPriceStatus:
2334
- !shipment_delivery || shipping?.service ? 'FINAL' : 'ESTIMATED',
2377
+ !shipmentDelivery || shipping?.service ? 'FINAL' : 'ESTIMATED',
2335
2378
  totalPriceLabel: 'Total',
2336
2379
  displayItems: getDisplayItems(cart),
2337
2380
  };
@@ -2344,12 +2387,14 @@ function getTransactionInfo(cart) {
2344
2387
  * @returns {google.payments.api.ShippingOptionParameters | undefined} Shipping options for Google Pay
2345
2388
  */
2346
2389
  function getShippingOptionParameters(cart) {
2347
- const { shipment_delivery, shipment_rating, shipping } = cart;
2390
+ const { require: { shipping: shipmentDelivery = false } = {} } = this.params;
2348
2391
 
2349
- if (!shipment_delivery) {
2392
+ if (!shipmentDelivery) {
2350
2393
  return undefined;
2351
2394
  }
2352
2395
 
2396
+ const { shipment_rating, shipping } = cart;
2397
+
2353
2398
  if (!shipment_rating?.services?.length) {
2354
2399
  // Return placeholder when no shipping services are available
2355
2400
  return {
@@ -2430,20 +2475,11 @@ function createError(intent, reason, message) {
2430
2475
  };
2431
2476
  }
2432
2477
 
2478
+ /** @typedef {import('../../../types').Cart} Cart */
2479
+
2433
2480
  const API_VERSION$1 = 2;
2434
2481
  const API_MINOR_VERSION$1 = 0;
2435
2482
 
2436
- const ALLOWED_CARD_AUTH_METHODS = ['PAN_ONLY', 'CRYPTOGRAM_3DS'];
2437
-
2438
- const ALLOWED_CARD_NETWORKS = [
2439
- 'AMEX',
2440
- 'DISCOVER',
2441
- 'INTERAC',
2442
- 'JCB',
2443
- 'MASTERCARD',
2444
- 'VISA',
2445
- ];
2446
-
2447
2483
  class BraintreeGooglePayment extends Payment {
2448
2484
  constructor(api, options, params, methods) {
2449
2485
  if (!methods.card) {
@@ -2479,6 +2515,9 @@ class BraintreeGooglePayment extends Payment {
2479
2515
  if (this.google) {
2480
2516
  this.googleClient = new this.google.payments.api.PaymentsClient({
2481
2517
  environment: isLiveMode(this.method.mode) ? 'PRODUCTION' : 'TEST',
2518
+ paymentDataCallbacks: {
2519
+ onPaymentDataChanged: onPaymentDataChanged.bind(this),
2520
+ },
2482
2521
  });
2483
2522
  }
2484
2523
 
@@ -2494,31 +2533,12 @@ class BraintreeGooglePayment extends Payment {
2494
2533
  BraintreeGooglePayment.googleClient = googleClient;
2495
2534
  }
2496
2535
 
2497
- /** @returns {google.payments.api.PaymentMethodSpecification} */
2498
- get cardPaymentMethod() {
2499
- return {
2500
- type: 'CARD',
2501
- parameters: {
2502
- allowedAuthMethods: ALLOWED_CARD_AUTH_METHODS,
2503
- allowedCardNetworks: ALLOWED_CARD_NETWORKS,
2504
- billingAddressRequired: true,
2505
- billingAddressParameters: {
2506
- format: 'FULL',
2507
- phoneNumberRequired: true,
2508
- },
2509
- },
2510
- };
2511
- }
2512
-
2513
- /** @returns {google.payments.api.PaymentMethodSpecification[]} */
2514
- get allowedPaymentMethods() {
2515
- return [this.cardPaymentMethod];
2516
- }
2517
-
2536
+ /** @param {Cart} cart */
2518
2537
  async createElements(cart) {
2519
2538
  const {
2520
2539
  elementId = 'googlepay-button',
2521
2540
  locale = 'en',
2541
+ require: { phone } = {},
2522
2542
  style: { color = 'black', type = 'plain', sizeMode = 'fill' } = {},
2523
2543
  } = this.params;
2524
2544
 
@@ -2529,10 +2549,35 @@ class BraintreeGooglePayment extends Payment {
2529
2549
  this.setElementContainer(elementId);
2530
2550
  await this.loadScripts(this.scripts);
2531
2551
 
2552
+ const braintreeClient = await this._createBraintreeClient();
2553
+
2554
+ const googlePayment = await this.braintree.googlePayment.create({
2555
+ client: braintreeClient,
2556
+ googleMerchantId: this.method.merchant_id,
2557
+ googlePayVersion: API_VERSION$1,
2558
+ });
2559
+
2560
+ const paymentDataRequest = googlePayment.createPaymentDataRequest(
2561
+ this._createPaymentRequestData(cart),
2562
+ );
2563
+
2564
+ const cardPaymentMethod = paymentDataRequest.allowedPaymentMethods.find(
2565
+ (method) => method.type === 'CARD',
2566
+ );
2567
+
2568
+ if (cardPaymentMethod !== undefined) {
2569
+ cardPaymentMethod.parameters.billingAddressRequired = true;
2570
+
2571
+ cardPaymentMethod.parameters.billingAddressParameters = {
2572
+ format: 'FULL',
2573
+ phoneNumberRequired: Boolean(phone),
2574
+ };
2575
+ }
2576
+
2532
2577
  const isReadyToPay = await this.googleClient.isReadyToPay({
2533
2578
  apiVersion: API_VERSION$1,
2534
2579
  apiVersionMinor: API_MINOR_VERSION$1,
2535
- allowedPaymentMethods: this.allowedPaymentMethods,
2580
+ allowedPaymentMethods: paymentDataRequest.allowedPaymentMethods,
2536
2581
  existingPaymentMethodRequired: true,
2537
2582
  });
2538
2583
 
@@ -2542,16 +2587,6 @@ class BraintreeGooglePayment extends Payment {
2542
2587
  );
2543
2588
  }
2544
2589
 
2545
- const braintreeClient = await this._createBraintreeClient();
2546
- const googlePayment = await this.braintree.googlePayment.create({
2547
- client: braintreeClient,
2548
- googleMerchantId: this.method.merchant_id,
2549
- googlePayVersion: API_VERSION$1,
2550
- });
2551
- const paymentRequestData = this._createPaymentRequestData(cart);
2552
- const paymentDataRequest =
2553
- googlePayment.createPaymentDataRequest(paymentRequestData);
2554
-
2555
2590
  this.element = this.googleClient.createButton({
2556
2591
  buttonColor: color,
2557
2592
  buttonType: type,
@@ -2578,6 +2613,7 @@ class BraintreeGooglePayment extends Payment {
2578
2613
  }
2579
2614
  }
2580
2615
 
2616
+ /** @returns {Promise<braintree.Client>} */
2581
2617
  async _createBraintreeClient() {
2582
2618
  const authorization = await this.authorizeGateway({
2583
2619
  gateway: 'braintree',
@@ -2592,39 +2628,51 @@ class BraintreeGooglePayment extends Payment {
2592
2628
  });
2593
2629
  }
2594
2630
 
2595
- /** @returns {google.payments.api.PaymentDataRequest} */
2631
+ /**
2632
+ * @param {Cart} cart
2633
+ * @returns {google.payments.api.PaymentDataRequest}
2634
+ */
2596
2635
  _createPaymentRequestData(cart) {
2597
2636
  const {
2598
- settings: { name, country },
2599
- capture_total,
2600
- currency,
2637
+ settings: { name },
2601
2638
  } = cart;
2639
+
2602
2640
  const { require: { email, shipping, phone } = {} } = this.params;
2603
2641
 
2642
+ /** @type {google.payments.api.CallbackIntent[]} */
2643
+ const callbackIntents = ['OFFER'];
2644
+
2645
+ if (shipping) {
2646
+ callbackIntents.push('SHIPPING_ADDRESS', 'SHIPPING_OPTION');
2647
+ }
2648
+
2604
2649
  return {
2605
2650
  apiVersion: API_VERSION$1,
2606
2651
  apiVersionMinor: API_MINOR_VERSION$1,
2607
- transactionInfo: {
2608
- countryCode: country,
2609
- currencyCode: currency,
2610
- totalPrice: capture_total.toString(),
2611
- totalPriceStatus: 'ESTIMATED',
2612
- },
2613
- allowedPaymentMethods: this.allowedPaymentMethods,
2652
+ transactionInfo: getTransactionInfo.call(this, cart),
2653
+ /**
2654
+ * @see {@link https://developer.paypal.com/braintree/docs/guides/google-pay/client-side/javascript/v3/#requesting-a-payment}
2655
+ *
2656
+ * Braintree automatically populates the `allowedPaymentMethods` property.
2657
+ */
2614
2658
  emailRequired: Boolean(email),
2615
2659
  shippingAddressRequired: Boolean(shipping),
2616
2660
  shippingAddressParameters: {
2617
2661
  phoneNumberRequired: Boolean(phone),
2618
2662
  },
2663
+ shippingOptionRequired: Boolean(shipping),
2664
+ shippingOptionParameters: getShippingOptionParameters.call(this, cart),
2665
+ offerInfo: getOfferInfo(cart),
2619
2666
  merchantInfo: {
2620
2667
  merchantName: name,
2621
2668
  merchantId: this.method.merchant_id,
2622
2669
  },
2670
+ callbackIntents,
2623
2671
  };
2624
2672
  }
2625
2673
 
2626
2674
  /**
2627
- * @param {object} googlePayment
2675
+ * @param {braintree.GooglePayment} googlePayment
2628
2676
  * @param {google.payments.api.PaymentDataRequest} paymentDataRequest
2629
2677
  */
2630
2678
  async _onClick(googlePayment, paymentDataRequest) {
@@ -2641,34 +2689,59 @@ class BraintreeGooglePayment extends Payment {
2641
2689
  }
2642
2690
 
2643
2691
  /**
2644
- * @param {object} googlePayment
2692
+ * @param {braintree.GooglePayment} googlePayment
2645
2693
  * @param {google.payments.api.PaymentData} paymentData
2646
2694
  */
2647
2695
  async _submitPayment(googlePayment, paymentData) {
2648
- const { require: { shipping: requireShipping } = {} } = this.params;
2649
2696
  const { nonce } = await googlePayment.parseResponse(paymentData);
2650
- const { email, shippingAddress, paymentMethodData } = paymentData;
2651
- const {
2652
- info: { billingAddress },
2653
- } = paymentMethodData;
2697
+ const { email, shippingAddress, shippingOptionData, paymentMethodData } =
2698
+ paymentData;
2699
+ const { info: { billingAddress } = {} } = paymentMethodData;
2654
2700
 
2655
- await this.updateCart({
2701
+ const cart = await this.updateCart({
2656
2702
  account: {
2657
2703
  email,
2658
2704
  },
2659
2705
  billing: {
2660
2706
  method: 'google',
2707
+ account_card_id: null,
2708
+ card: null,
2661
2709
  google: {
2662
2710
  nonce,
2663
2711
  gateway: 'braintree',
2664
2712
  },
2665
2713
  ...convertToSwellAddress$2(billingAddress),
2666
2714
  },
2667
- ...(requireShipping && {
2668
- shipping: convertToSwellAddress$2(shippingAddress),
2715
+ ...(shippingAddress && {
2716
+ shipping: {
2717
+ ...convertToSwellAddress$2(shippingAddress),
2718
+ service: shippingOptionData?.id || undefined,
2719
+ },
2669
2720
  }),
2670
2721
  });
2671
2722
 
2723
+ if (cart.subscription_delivery) {
2724
+ try {
2725
+ const card = await cardApi.createToken({
2726
+ gateway: 'braintree',
2727
+ account_id: cart.account_id,
2728
+ nonce,
2729
+ });
2730
+
2731
+ delete card.nonce;
2732
+
2733
+ await this.updateCart({
2734
+ billing: {
2735
+ method: 'card',
2736
+ card,
2737
+ google: null,
2738
+ },
2739
+ });
2740
+ } catch (error) {
2741
+ console.warn('Failed to extract card data from google token', error);
2742
+ }
2743
+ }
2744
+
2672
2745
  this.onSuccess();
2673
2746
  }
2674
2747
  }
@@ -2698,7 +2771,7 @@ async function onShippingAddressChange(shippingContact) {
2698
2771
  'Shipping is not available for the provided address.';
2699
2772
 
2700
2773
  return {
2701
- newTotal: getTotal(cart, this.merchantInfo),
2774
+ newTotal: getTotal.call(this, cart, this.merchantInfo),
2702
2775
  newLineItems: getLineItems(cart),
2703
2776
  newShippingMethods: [], // REQUIRED: Must always provide this, even as empty array
2704
2777
  errors: [
@@ -2709,15 +2782,15 @@ async function onShippingAddressChange(shippingContact) {
2709
2782
 
2710
2783
  // Success: Return updated totals and shipping methods
2711
2784
  return {
2712
- newTotal: getTotal(cart, this.merchantInfo),
2785
+ newTotal: getTotal.call(this, cart, this.merchantInfo),
2713
2786
  newLineItems: getLineItems(cart),
2714
- newShippingMethods: getShippingMethods(cart),
2787
+ newShippingMethods: getShippingMethods.call(this, cart),
2715
2788
  };
2716
2789
  } catch (err) {
2717
2790
  const cartData = await this.getCart();
2718
2791
 
2719
2792
  return {
2720
- newTotal: getTotal(cartData, this.merchantInfo),
2793
+ newTotal: getTotal.call(this, cartData, this.merchantInfo),
2721
2794
  newLineItems: getLineItems(cartData),
2722
2795
  newShippingMethods: [], // REQUIRED: Must always provide this, even as empty array
2723
2796
  errors: [new ApplePayError('unknown', undefined, err.message)],
@@ -2763,12 +2836,12 @@ async function onShippingMethodChange(shippingMethod) {
2763
2836
  });
2764
2837
 
2765
2838
  return {
2766
- newTotal: getTotal(cart, this.merchantInfo),
2839
+ newTotal: getTotal.call(this, cart, this.merchantInfo),
2767
2840
  newLineItems: getLineItems(cart),
2768
2841
  };
2769
2842
  } catch (err) {
2770
2843
  return {
2771
- newTotal: getTotal(await this.getCart(), this.merchantInfo),
2844
+ newTotal: getTotal.call(this, await this.getCart(), this.merchantInfo),
2772
2845
  errors: [new ApplePayError('unknown', undefined, err.message)],
2773
2846
  };
2774
2847
  }
@@ -2808,7 +2881,7 @@ async function onCouponCodeUpdated(couponCode) {
2808
2881
 
2809
2882
  // Valid coupon applied successfully
2810
2883
  return {
2811
- newTotal: getTotal(cart, this.merchantInfo),
2884
+ newTotal: getTotal.call(this, cart, this.merchantInfo),
2812
2885
  newLineItems: getLineItems(cart),
2813
2886
  };
2814
2887
  } catch (err) {
@@ -2816,7 +2889,7 @@ async function onCouponCodeUpdated(couponCode) {
2816
2889
  const currentCart = await this.getCart();
2817
2890
 
2818
2891
  return {
2819
- newTotal: getTotal(currentCart, this.merchantInfo),
2892
+ newTotal: getTotal.call(this, currentCart, this.merchantInfo),
2820
2893
  newLineItems: getLineItems(currentCart),
2821
2894
  errors: [new ApplePayError('couponCodeInvalid', undefined, err.message)],
2822
2895
  };
@@ -2850,15 +2923,17 @@ async function onCouponCodeChanged(session, event) {
2850
2923
  * This ensures the user sees "pending" until they've selected shipping,
2851
2924
  * then sees the final amount once everything is calculated.
2852
2925
  *
2926
+ * @this {Payment}
2853
2927
  * @param {Cart} cart
2854
2928
  * @param {object} [merchantInfo]
2855
2929
  * @returns {ApplePayJS.ApplePayLineItem}
2856
2930
  */
2857
2931
  function getTotal(cart, merchantInfo) {
2858
- const { capture_total, shipment_delivery, shipping } = cart;
2932
+ const { require: { shipping: shipmentDelivery = false } = {} } = this.params;
2933
+ const { capture_total, shipping } = cart;
2859
2934
 
2860
2935
  // Use 'final' only after shipping method is selected or not needed, otherwise 'pending'
2861
- const isFinalPrice = !shipment_delivery || shipping?.service;
2936
+ const isFinalPrice = !shipmentDelivery || shipping?.service;
2862
2937
  const type = isFinalPrice ? 'final' : 'pending';
2863
2938
 
2864
2939
  return {
@@ -2927,16 +3002,19 @@ function getLineItems(cart) {
2927
3002
  *
2928
3003
  * IMPORTANT: Amounts must be formatted as strings with 2 decimals.
2929
3004
  *
3005
+ * @this {Payment}
2930
3006
  * @param {Cart} cart
2931
3007
  * @returns {ApplePayJS.ApplePayShippingMethod[]}
2932
3008
  */
2933
3009
  function getShippingMethods(cart) {
2934
- const { shipment_delivery, shipment_rating } = cart;
3010
+ const { require: { shipping: shipmentDelivery = false } = {} } = this.params;
2935
3011
 
2936
- if (!shipment_delivery) {
3012
+ if (!shipmentDelivery) {
2937
3013
  return [];
2938
3014
  }
2939
3015
 
3016
+ const { shipment_rating } = cart;
3017
+
2940
3018
  if (!shipment_rating?.services?.length) {
2941
3019
  return [];
2942
3020
  }
@@ -2987,13 +3065,38 @@ function convertToSwellAddress$1(address = {}) {
2987
3065
  };
2988
3066
  }
2989
3067
 
2990
- const VERSION$1 = 3;
3068
+ /** @param {object} params */
3069
+ function getRequiredContactFields(params) {
3070
+ const { require = {} } = params;
3071
+ /** @type {ApplePayJS.ApplePayContactField[]} */
3072
+ const requiredFields = [];
3073
+
3074
+ if (require.email) {
3075
+ requiredFields.push('email');
3076
+ }
3077
+
3078
+ if (require.name) {
3079
+ requiredFields.push('name');
3080
+ }
3081
+
3082
+ if (require.phone) {
3083
+ requiredFields.push('phone');
3084
+ }
3085
+
3086
+ /** @type {ApplePayJS.ApplePayContactField[]} */
3087
+ const requiredShippingContactFields = require.shipping
3088
+ ? [...requiredFields, 'postalAddress']
3089
+ : [];
3090
+
3091
+ /** @type {ApplePayJS.ApplePayContactField[]} */
3092
+ const requiredBillingContactFields = [...requiredFields, 'postalAddress'];
3093
+
3094
+ return { requiredBillingContactFields, requiredShippingContactFields };
3095
+ }
3096
+
3097
+ /** @typedef {import('../../../types').Cart} Cart */
2991
3098
 
2992
- const MERCHANT_CAPABILITIES = [
2993
- 'supports3DS',
2994
- 'supportsDebit',
2995
- 'supportsCredit',
2996
- ];
3099
+ const VERSION$1 = 14;
2997
3100
 
2998
3101
  class BraintreeApplePayment extends Payment {
2999
3102
  constructor(api, options, params, methods) {
@@ -3025,6 +3128,7 @@ class BraintreeApplePayment extends Payment {
3025
3128
  return window.ApplePaySession;
3026
3129
  }
3027
3130
 
3131
+ /** @param {Cart} cart */
3028
3132
  async createElements(cart) {
3029
3133
  const { elementId = 'applepay-button' } = this.params;
3030
3134
 
@@ -3038,10 +3142,12 @@ class BraintreeApplePayment extends Payment {
3038
3142
  }
3039
3143
 
3040
3144
  const braintreeClient = await this._createBraintreeClient();
3145
+
3041
3146
  const applePayment = await this.braintree.applePay.create({
3042
3147
  client: braintreeClient,
3043
3148
  });
3044
- const paymentRequest = await this._createPaymentRequest(cart, applePayment);
3149
+
3150
+ const paymentRequest = this._createPaymentRequest(cart, applePayment);
3045
3151
 
3046
3152
  this.element = this._createButton(applePayment, paymentRequest);
3047
3153
  }
@@ -3058,8 +3164,9 @@ class BraintreeApplePayment extends Payment {
3058
3164
  }
3059
3165
 
3060
3166
  /**
3061
- * @param {object} applePayment
3167
+ * @param {braintree.ApplePay} applePayment
3062
3168
  * @param {ApplePayJS.ApplePayPaymentRequest} paymentRequest
3169
+ * @returns {HTMLDivElement}
3063
3170
  */
3064
3171
  _createButton(applePayment, paymentRequest) {
3065
3172
  const { style: { type = 'plain', theme = 'black', height = '40px' } = {} } =
@@ -3080,6 +3187,7 @@ class BraintreeApplePayment extends Payment {
3080
3187
  return button;
3081
3188
  }
3082
3189
 
3190
+ /** @returns {Promise<braintree.Client>} */
3083
3191
  async _createBraintreeClient() {
3084
3192
  const authorization = await this.authorizeGateway({
3085
3193
  gateway: 'braintree',
@@ -3094,47 +3202,43 @@ class BraintreeApplePayment extends Payment {
3094
3202
  });
3095
3203
  }
3096
3204
 
3097
- /** @returns {ApplePayJS.ApplePayPaymentRequest} */
3205
+ /**
3206
+ * @param {Cart} cart
3207
+ * @param {braintree.ApplePay} applePayment
3208
+ * @returns {ApplePayJS.ApplePayPaymentRequest}
3209
+ */
3098
3210
  _createPaymentRequest(cart, applePayment) {
3099
- const { require = {} } = this.params;
3100
3211
  const {
3101
3212
  settings: { name, country },
3102
3213
  capture_total,
3103
3214
  currency,
3104
3215
  } = cart;
3105
3216
 
3106
- const requiredShippingContactFields = [];
3107
- const requiredBillingContactFields = ['postalAddress'];
3108
-
3109
- if (require.name) {
3110
- requiredShippingContactFields.push('name');
3111
- }
3112
- if (require.email) {
3113
- requiredShippingContactFields.push('email');
3114
- }
3115
- if (require.phone) {
3116
- requiredShippingContactFields.push('phone');
3117
- }
3118
- if (require.shipping) {
3119
- requiredShippingContactFields.push('postalAddress');
3120
- }
3217
+ const { requiredBillingContactFields, requiredShippingContactFields } =
3218
+ getRequiredContactFields(this.params);
3121
3219
 
3122
3220
  return applePayment.createPaymentRequest({
3123
3221
  total: {
3124
3222
  label: name,
3125
3223
  type: 'pending',
3126
- amount: capture_total.toString(),
3224
+ amount: (capture_total || 0).toFixed(2),
3127
3225
  },
3128
3226
  countryCode: country,
3129
3227
  currencyCode: currency,
3130
- merchantCapabilities: MERCHANT_CAPABILITIES,
3228
+ /**
3229
+ * @see {@link https://developer.paypal.com/braintree/docs/guides/apple-pay/client-side/javascript/v3/#create-anapplepaysession}
3230
+ *
3231
+ * Braintree automatically fills in `supportedNetworks` and `merchantCapabilities`.
3232
+ */
3131
3233
  requiredShippingContactFields,
3132
3234
  requiredBillingContactFields,
3235
+ supportsCouponCode: true,
3236
+ lineItems: getLineItems(cart),
3133
3237
  });
3134
3238
  }
3135
3239
 
3136
3240
  /**
3137
- * @param {object} applePayment
3241
+ * @param {braintree.ApplePay} applePayment
3138
3242
  * @param {ApplePayJS.ApplePayPaymentRequest} paymentRequest
3139
3243
  */
3140
3244
  _createPaymentSession(applePayment, paymentRequest) {
@@ -3155,11 +3259,23 @@ class BraintreeApplePayment extends Payment {
3155
3259
  }
3156
3260
  };
3157
3261
 
3262
+ session.onshippingcontactselected = onShippingContactSelected.bind(
3263
+ this,
3264
+ session,
3265
+ );
3266
+
3267
+ session.onshippingmethodselected = onShippingMethodSelected.bind(
3268
+ this,
3269
+ session,
3270
+ );
3271
+
3272
+ session.oncouponcodechanged = onCouponCodeChanged.bind(this, session);
3273
+
3158
3274
  session.onpaymentauthorized = async (event) => {
3159
3275
  const {
3160
3276
  payment: { token, shippingContact, billingContact },
3161
3277
  } = event;
3162
- const { require: { shipping: requireShipping } = {} } = this.params;
3278
+
3163
3279
  const payload = await applePayment
3164
3280
  .tokenize({ token })
3165
3281
  .catch(this.onError.bind(this));
@@ -3168,23 +3284,47 @@ class BraintreeApplePayment extends Payment {
3168
3284
  return session.completePayment(this.ApplePaySession.STATUS_FAILURE);
3169
3285
  }
3170
3286
 
3171
- await this.updateCart({
3287
+ const cart = await this.updateCart({
3172
3288
  account: {
3173
- email: shippingContact.emailAddress,
3289
+ email: shippingContact?.emailAddress || billingContact?.emailAddress,
3174
3290
  },
3175
3291
  billing: {
3176
3292
  method: 'apple',
3293
+ account_card_id: null,
3294
+ card: null,
3177
3295
  apple: {
3178
3296
  nonce: payload.nonce,
3179
3297
  gateway: 'braintree',
3180
3298
  },
3181
3299
  ...convertToSwellAddress$1(billingContact),
3182
3300
  },
3183
- ...(requireShipping && {
3301
+ ...(shippingContact && {
3184
3302
  shipping: convertToSwellAddress$1(shippingContact),
3185
3303
  }),
3186
3304
  });
3187
3305
 
3306
+ if (cart.subscription_delivery) {
3307
+ try {
3308
+ const card = await cardApi.createToken({
3309
+ gateway: 'braintree',
3310
+ account_id: cart.account_id,
3311
+ nonce: payload.nonce,
3312
+ });
3313
+
3314
+ delete card.nonce;
3315
+
3316
+ await this.updateCart({
3317
+ billing: {
3318
+ method: 'card',
3319
+ card,
3320
+ apple: null,
3321
+ },
3322
+ });
3323
+ } catch (error) {
3324
+ console.warn('Failed to extract card data from apple token', error);
3325
+ }
3326
+ }
3327
+
3188
3328
  this.onSuccess();
3189
3329
 
3190
3330
  return session.completePayment(this.ApplePaySession.STATUS_SUCCESS);
@@ -3290,6 +3430,8 @@ class AbstractGooglePayment extends Payment {
3290
3430
  * @returns {google.payments.api.PaymentMethodSpecification}
3291
3431
  */
3292
3432
  _getCardPaymentMethod(submit = false) {
3433
+ const { require: { phone } = {} } = this.params;
3434
+
3293
3435
  return {
3294
3436
  type: 'CARD',
3295
3437
  parameters: {
@@ -3298,7 +3440,7 @@ class AbstractGooglePayment extends Payment {
3298
3440
  billingAddressRequired: true,
3299
3441
  billingAddressParameters: {
3300
3442
  format: 'FULL',
3301
- phoneNumberRequired: true,
3443
+ phoneNumberRequired: Boolean(phone),
3302
3444
  },
3303
3445
  },
3304
3446
  ...(submit && {
@@ -3331,29 +3473,30 @@ class AbstractGooglePayment extends Payment {
3331
3473
  cart.shipment_price = 0;
3332
3474
  cart.shipping = {};
3333
3475
 
3334
- const { shipment_delivery } = cart;
3335
3476
  const { require: { email, shipping, phone } = {} } = this.params;
3336
3477
 
3478
+ /** @type {google.payments.api.CallbackIntent[]} */
3479
+ const callbackIntents = ['OFFER', 'PAYMENT_AUTHORIZATION'];
3480
+
3481
+ if (shipping) {
3482
+ callbackIntents.push('SHIPPING_ADDRESS', 'SHIPPING_OPTION');
3483
+ }
3484
+
3337
3485
  return {
3338
3486
  apiVersion: API_VERSION,
3339
3487
  apiVersionMinor: API_MINOR_VERSION,
3340
- transactionInfo: getTransactionInfo(cart),
3488
+ transactionInfo: getTransactionInfo.call(this, cart),
3341
3489
  allowedPaymentMethods: this._getAllowedPaymentMethods(true),
3342
3490
  emailRequired: Boolean(email),
3343
3491
  shippingAddressRequired: Boolean(shipping),
3344
3492
  shippingAddressParameters: {
3345
3493
  phoneNumberRequired: Boolean(phone),
3346
3494
  },
3347
- shippingOptionRequired: Boolean(shipment_delivery),
3495
+ shippingOptionRequired: Boolean(shipping),
3348
3496
  shippingOptionParameters: getShippingOptionParameters.call(this, cart),
3349
3497
  offerInfo: getOfferInfo(cart),
3350
3498
  merchantInfo,
3351
- callbackIntents: [
3352
- 'OFFER',
3353
- 'SHIPPING_ADDRESS',
3354
- 'SHIPPING_OPTION',
3355
- 'PAYMENT_AUTHORIZATION',
3356
- ],
3499
+ callbackIntents,
3357
3500
  };
3358
3501
  }
3359
3502
 
@@ -3477,6 +3620,22 @@ class AbstractGooglePayment extends Payment {
3477
3620
  /** @typedef {import('../../../types').Cart} Cart */
3478
3621
 
3479
3622
  class AuthorizeNetGooglePayment extends AbstractGooglePayment {
3623
+ /**
3624
+ * @override
3625
+ * @returns {google.payments.api.CardNetwork[]}
3626
+ */
3627
+ getAllowedCardNetworks() {
3628
+ return ['AMEX', 'DISCOVER', 'JCB', 'MASTERCARD', 'VISA'];
3629
+ }
3630
+
3631
+ /**
3632
+ * @override
3633
+ * @returns {google.payments.api.CardAuthMethod[]}
3634
+ */
3635
+ getAllowedCardAuthMethods() {
3636
+ return ['PAN_ONLY', 'CRYPTOGRAM_3DS'];
3637
+ }
3638
+
3480
3639
  /**
3481
3640
  * @override
3482
3641
  * @returns {google.payments.api.PaymentGatewayTokenizationParameters}
@@ -3494,7 +3653,6 @@ class AuthorizeNetGooglePayment extends AbstractGooglePayment {
3494
3653
  * @returns {Promise<object>}
3495
3654
  */
3496
3655
  async preparePaymentDataForCartUpdate(paymentData) {
3497
- const { require: { shipping: requireShipping } = {} } = this.params;
3498
3656
  const { email, shippingAddress, shippingOptionData, paymentMethodData } =
3499
3657
  paymentData;
3500
3658
 
@@ -3515,7 +3673,7 @@ class AuthorizeNetGooglePayment extends AbstractGooglePayment {
3515
3673
  },
3516
3674
  ...convertToSwellAddress$2(billingAddress),
3517
3675
  },
3518
- ...(requireShipping && {
3676
+ ...(shippingAddress && {
3519
3677
  shipping: {
3520
3678
  ...convertToSwellAddress$2(shippingAddress),
3521
3679
  service: shippingOptionData?.id || undefined,
@@ -3582,9 +3740,15 @@ class AbstractApplePayment extends Payment {
3582
3740
  return window.ApplePaySession;
3583
3741
  }
3584
3742
 
3585
- /** @returns {string[]} */
3743
+ /**
3744
+ * We should specify the payment methods in the order preferred by the gateway.
3745
+ *
3746
+ * @see {@link https://developer.apple.com/documentation/applepayontheweb/applepaypaymentrequest/supportednetworks}
3747
+ *
3748
+ * @returns {string[]}
3749
+ */
3586
3750
  getSupportedCardNetworks() {
3587
- return ['amex', 'discover', 'interac', 'jcb', 'masterCard', 'visa'];
3751
+ return ['visa', 'masterCard', 'amex', 'discover', 'interac', 'jcb'];
3588
3752
  }
3589
3753
 
3590
3754
  /** @returns {ApplePayJS.ApplePayMerchantCapability[]} */
@@ -3595,12 +3759,12 @@ class AbstractApplePayment extends Payment {
3595
3759
  /**
3596
3760
  * Creates the Apple Pay payment request object
3597
3761
  *
3598
- * IMPORTANT: countryCode here is the MERCHANT's country (where your business is located),
3599
- * NOT the customer's country. Customer country comes from shippingContact.countryCode.
3762
+ * IMPORTANT: `countryCode` here is the MERCHANT's country (where your business is located),
3763
+ * NOT the customer's country. Customer country comes from `shippingContact.countryCode`.
3600
3764
  *
3601
- * NOTE: We do NOT pass shippingMethods in the initial request. Shipping methods
3765
+ * NOTE: We do NOT pass `shippingMethods` in the initial request. Shipping methods
3602
3766
  * are provided dynamically after the user selects/confirms their address in the
3603
- * 'shippingcontactselected' event handler.
3767
+ * `'shippingcontactselected'` event handler.
3604
3768
  *
3605
3769
  * @param {Cart} cart
3606
3770
  * @returns {ApplePayJS.ApplePayPaymentRequest}
@@ -3608,38 +3772,21 @@ class AbstractApplePayment extends Payment {
3608
3772
  _createPaymentRequest(cart) {
3609
3773
  cart = { ...cart };
3610
3774
 
3611
- const { require = {} } = this.params;
3612
3775
  const {
3613
3776
  settings: { country },
3614
3777
  currency,
3615
3778
  } = cart;
3616
3779
 
3617
- const requiredShippingContactFields = [];
3618
- const requiredBillingContactFields = ['postalAddress'];
3619
-
3620
- if (require.name) {
3621
- requiredShippingContactFields.push('name');
3622
- }
3623
-
3624
- if (require.email) {
3625
- requiredShippingContactFields.push('email');
3626
- }
3627
-
3628
- if (require.phone) {
3629
- requiredShippingContactFields.push('phone');
3630
- }
3631
-
3632
- if (require.shipping) {
3633
- requiredShippingContactFields.push('postalAddress');
3634
- }
3635
-
3636
3780
  // When initiating a payment request,
3637
3781
  // we should not display the shipping cost, as it will be calculated later.
3638
3782
  cart.shipment_price = 0;
3639
3783
  cart.shipping = {};
3640
3784
 
3785
+ const { requiredBillingContactFields, requiredShippingContactFields } =
3786
+ getRequiredContactFields(this.params);
3787
+
3641
3788
  return {
3642
- total: getTotal(cart, this.merchantInfo),
3789
+ total: getTotal.call(this, cart, this.merchantInfo),
3643
3790
  countryCode: country, // Merchant's country
3644
3791
  currencyCode: currency,
3645
3792
  supportedNetworks: this.getSupportedCardNetworks(),
@@ -3821,6 +3968,22 @@ class AbstractApplePayment extends Payment {
3821
3968
  }
3822
3969
 
3823
3970
  class AuthorizeNetApplePayment extends AbstractApplePayment {
3971
+ /**
3972
+ * @override
3973
+ * @returns {string[]}
3974
+ */
3975
+ getSupportedCardNetworks() {
3976
+ return ['visa', 'masterCard', 'amex', 'discover'];
3977
+ }
3978
+
3979
+ /**
3980
+ * @override
3981
+ * @returns {ApplePayJS.ApplePayMerchantCapability[]}
3982
+ */
3983
+ getMerchantCapabilities() {
3984
+ return ['supports3DS', 'supportsDebit', 'supportsCredit'];
3985
+ }
3986
+
3824
3987
  /**
3825
3988
  * @override
3826
3989
  * @param {ApplePayJS.ApplePayValidateMerchantEvent} event
@@ -3854,11 +4017,9 @@ class AuthorizeNetApplePayment extends AbstractApplePayment {
3854
4017
  payment: { token, shippingContact, billingContact },
3855
4018
  } = event;
3856
4019
 
3857
- const { require: { shipping: requireShipping } = {} } = this.params;
3858
-
3859
4020
  return {
3860
4021
  account: {
3861
- email: shippingContact.emailAddress,
4022
+ email: shippingContact?.emailAddress || billingContact?.emailAddress,
3862
4023
  },
3863
4024
  billing: {
3864
4025
  method: 'apple',
@@ -3870,7 +4031,7 @@ class AuthorizeNetApplePayment extends AbstractApplePayment {
3870
4031
  },
3871
4032
  ...convertToSwellAddress$1(billingContact),
3872
4033
  },
3873
- ...(requireShipping && {
4034
+ ...(shippingContact && {
3874
4035
  shipping: convertToSwellAddress$1(shippingContact),
3875
4036
  }),
3876
4037
  };
@@ -3883,7 +4044,7 @@ class QuickpayCardPayment extends Payment {
3883
4044
  }
3884
4045
 
3885
4046
  get orderId() {
3886
- return Math.random().toString(36).substr(2, 9);
4047
+ return Math.random().toString(36).slice(2, 11);
3887
4048
  }
3888
4049
 
3889
4050
  async tokenize() {
@@ -3945,6 +4106,12 @@ class QuickpayCardPayment extends Payment {
3945
4106
  throw new Error(card.error.message);
3946
4107
  }
3947
4108
 
4109
+ card.gateway = 'quickpay';
4110
+
4111
+ if (!isLiveMode(this.method.mode)) {
4112
+ card.test = true;
4113
+ }
4114
+
3948
4115
  await this.updateCart({
3949
4116
  billing: {
3950
4117
  method: 'card',
@@ -3984,7 +4151,7 @@ async function getConvesioPaymentSettings(mode, settings, country) {
3984
4151
  merchantAccount: false,
3985
4152
  countryCode: country || 'US',
3986
4153
  shopperLocale: 'en-US',
3987
- // shopperReference: email ? md5(email) : undefined,
4154
+ // shopperReference: email ? md5(email.toLowerCase()) : undefined,
3988
4155
  }),
3989
4156
  },
3990
4157
  ).then((res) => res.json());
@@ -4112,15 +4279,20 @@ class ConvesioCardPayment extends Payment {
4112
4279
  }
4113
4280
  }
4114
4281
 
4282
+ function getBaseConvesioVaultUrl(mode) {
4283
+ return isLiveMode(mode)
4284
+ ? 'https://vault.convesiopay.com'
4285
+ : 'https://qa-vault.convesiopay.com';
4286
+ }
4287
+
4115
4288
  function createConvesioCardPaymentToken(settings, data) {
4116
- const baseUrl = getBaseConvesioApiUrl(settings.mode);
4289
+ const baseUrl = getBaseConvesioVaultUrl(settings.mode);
4117
4290
 
4118
4291
  return fetch(`${baseUrl}/v1/create-token`, {
4119
4292
  method: 'POST',
4120
4293
  headers: {
4121
4294
  Accept: 'application/json',
4122
4295
  'Content-Type': 'application/json',
4123
- 'X-Requested-With': 'convesio-pay-dashboard',
4124
4296
  'X-Api-Key': settings.public_key,
4125
4297
  },
4126
4298
  body: JSON.stringify(data),
@@ -4235,8 +4407,6 @@ class ConvesioPayApplePayment extends AbstractApplePayment {
4235
4407
  payment: { token, shippingContact, billingContact },
4236
4408
  } = event;
4237
4409
 
4238
- const { require: { shipping: requireShipping } = {} } = this.params;
4239
-
4240
4410
  const payment = await createConvesioApplePaymentToken(
4241
4411
  this.method.mode,
4242
4412
  this.convesiopay,
@@ -4258,7 +4428,7 @@ class ConvesioPayApplePayment extends AbstractApplePayment {
4258
4428
 
4259
4429
  return {
4260
4430
  account: {
4261
- email: shippingContact.emailAddress,
4431
+ email: shippingContact?.emailAddress || billingContact?.emailAddress,
4262
4432
  },
4263
4433
  billing: {
4264
4434
  method: 'apple',
@@ -4273,7 +4443,7 @@ class ConvesioPayApplePayment extends AbstractApplePayment {
4273
4443
  },
4274
4444
  ...convertToSwellAddress$1(billingContact),
4275
4445
  },
4276
- ...(requireShipping && {
4446
+ ...(shippingContact && {
4277
4447
  shipping: convertToSwellAddress$1(shippingContact),
4278
4448
  }),
4279
4449
  };
@@ -4403,7 +4573,6 @@ class ConvesioPayGooglePayment extends AbstractGooglePayment {
4403
4573
  * @returns {Promise<object>}
4404
4574
  */
4405
4575
  async preparePaymentDataForCartUpdate(paymentData) {
4406
- const { require: { shipping: requireShipping } = {} } = this.params;
4407
4576
  const { email, shippingAddress, shippingOptionData, paymentMethodData } =
4408
4577
  paymentData;
4409
4578
 
@@ -4446,7 +4615,7 @@ class ConvesioPayGooglePayment extends AbstractGooglePayment {
4446
4615
  },
4447
4616
  ...convertToSwellAddress$2(billingAddress),
4448
4617
  },
4449
- ...(requireShipping && {
4618
+ ...(shippingAddress && {
4450
4619
  shipping: {
4451
4620
  ...convertToSwellAddress$2(shippingAddress),
4452
4621
  service: shippingOptionData?.id || undefined,
@@ -5342,20 +5511,29 @@ class SezzleDirectPayment extends Payment {
5342
5511
  settings: { name },
5343
5512
  } = cart;
5344
5513
 
5345
- const shipmentDelivery = Boolean(cart.shipment_delivery);
5514
+ const { require: { shipping } = {} } = this.params;
5346
5515
  const cartCurrency = getCartCurrency(cart);
5347
5516
 
5348
5517
  return {
5349
5518
  checkout_payload: {
5350
- express_checkout_type: shipmentDelivery ? 'multi-step' : 'no-shipping',
5519
+ express_checkout_type: shipping ? 'multi-step' : 'no-shipping',
5520
+ customer: {
5521
+ email: cart.account?.email,
5522
+ first_name: cart.account?.first_name,
5523
+ last_name: cart.account?.last_name,
5524
+ phone: cart.account?.phone,
5525
+ // TODO: recurring payments https://docs.sezzle.com/docs/api/tokenization/intro
5526
+ // tokenize: cart.subscription_delivery,
5527
+ // recurring: cart.subscription_delivery,
5528
+ },
5351
5529
  order: {
5352
5530
  intent: 'AUTH',
5353
5531
  reference_id: cart.checkout_id,
5354
5532
  description: `${name} #${cart.number}`,
5355
- requires_shipping_info: shipmentDelivery,
5533
+ requires_shipping_info: shipping,
5356
5534
  items: getItems(cart),
5357
5535
  discounts: getDiscounts(cart),
5358
- tax_amount: shipmentDelivery
5536
+ tax_amount: shipping
5359
5537
  ? undefined
5360
5538
  : getAmountInCents(cartCurrency, cart.tax_total),
5361
5539
  order_amount: getAmountInCents(cartCurrency, cart.capture_total),
@@ -5615,15 +5793,6 @@ function convertToSwellAddress(address) {
5615
5793
  };
5616
5794
  }
5617
5795
 
5618
- /**
5619
- * @param {string} currency
5620
- * @param {number} amount
5621
- * @returns {number}
5622
- */
5623
- function amountInCents(currency, amount) {
5624
- return Math.round(amount * 100);
5625
- }
5626
-
5627
5796
  class PaymentController {
5628
5797
  constructor(api, options) {
5629
5798
  this.api = api;