@economist/web-apple-pay 1.0.1 → 1.1.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/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # @economist/web-apple-pay
2
+
3
+ Framework-agnostic Apple Pay UI components implemented as Web Components.
4
+
5
+ This package provides:
6
+
7
+ - `teg-apple-pay-button`
8
+ - `teg-apple-pay-modal`
9
+
10
+ It is intentionally UI-focused. Consumer applications own backend payment orchestration, merchant validation, and secret handling.
11
+
12
+ ## Requirements
13
+
14
+ - Node.js `20.x` or later
15
+ - npm `10.x` or later
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ npm install --save @economist/web-apple-pay
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ### 1) Configure runtime values
26
+
27
+ Call `configureApplePay(...)` once during app startup, before rendering Apple Pay UI:
28
+
29
+ ```ts
30
+ import { configureApplePay, defineApplePayElements } from '@economist/web-apple-pay';
31
+
32
+ configureApplePay({
33
+ merchantId: 'merchant.com.economist.your-app',
34
+ debugBypassEnabled: false,
35
+ });
36
+
37
+ defineApplePayElements();
38
+ ```
39
+
40
+ ### 2) Create and mount a button
41
+
42
+ ```ts
43
+ import type { Offer, ApplePayButton } from '@economist/web-apple-pay';
44
+
45
+ const button = document.createElement('teg-apple-pay-button') as ApplePayButton;
46
+ button.offer = offer as Offer;
47
+ button.country = 'US';
48
+ button.dataset.loggedIn = 'true';
49
+
50
+ container.appendChild(button);
51
+ ```
52
+
53
+ When clicked, the button creates and opens `teg-apple-pay-modal` automatically.
54
+
55
+ ## Runtime configuration
56
+
57
+ ### `configureApplePay(config)`
58
+
59
+ Sets package-level runtime configuration used by Apple Pay availability checks.
60
+
61
+ `config` shape:
62
+
63
+ - `merchantId` (`string`, required): Apple Pay merchant identifier used for active-card checks.
64
+ - `debugBypassEnabled` (`boolean`, optional): allows `?BYPASS_APPLE_PAY_CHECKS=1` query param to bypass capability checks in non-production-style debugging flows.
65
+
66
+ If `merchantId` is not configured, Apple Pay availability checks return `false` and the button hides itself.
67
+
68
+ ## Component behavior
69
+
70
+ ### `ApplePayButton`
71
+
72
+ - Requires `offer` and `country` before click flow is meaningful.
73
+ - Checks Apple Pay availability on connect; if unavailable, it hides.
74
+ - Skips rendering for excluded variants (`bundle`, `insider_print`).
75
+ - Forwards `data-logged-in` from button to modal (`modal.dataset.loggedIn`).
76
+
77
+ ### `ApplePayModal`
78
+
79
+ - Opened by `ApplePayButton` click.
80
+ - Emits `apple-pay-confirmed` when CTA is accepted.
81
+ - `Escape` closes the modal and prevents propagation to other global handlers.
82
+
83
+ ## Events
84
+
85
+ ### `apple-pay-confirmed`
86
+
87
+ Dispatched from `ApplePayModal` as a `CustomEvent` with:
88
+
89
+ - `detail.skuId` (`string`)
90
+ - `detail.country` (`string`)
91
+
92
+ Consumers should listen for this event and continue payment orchestration.
93
+
94
+ ## Public API
95
+
96
+ - `defineApplePayElements()`: registers both custom elements.
97
+ - `configureApplePay(config)`: sets required runtime config (merchant ID and optional debug bypass).
98
+ - `ApplePayButton`: button component class.
99
+ - `ApplePayModal`: modal component class.
100
+ - `ensureApplePayLogoSymbol()`: logo symbol injection helper.
101
+ - `Offer`: offer contract type used by component props.
102
+
103
+ ## Troubleshooting
104
+
105
+ ### Button does not appear
106
+
107
+ Check:
108
+
109
+ - `configureApplePay(...)` was called before rendering (with a valid `merchantId`)
110
+ - device/browser supports Apple Pay checks (Safari/Apple device logic)
111
+ - query string/feature flag gating for your page is satisfied
112
+ - offer variant is not excluded (`bundle`, `insider_print`)
113
+
114
+ ### Logged-in checkbox copy looks wrong
115
+
116
+ Set `button.dataset.loggedIn = 'true'` before appending the button. The button forwards this flag to the modal automatically.
117
+
118
+ ## Security and responsibilities
119
+
120
+ Merchant setup must be handled by the consuming application:
121
+
122
+ - inject merchant config at runtime
123
+ - perform merchant validation on trusted backend services
124
+ - keep certificates/secrets outside this package
125
+
126
+ ## Local development
127
+
128
+ From `shared-packages`:
129
+
130
+ ```sh
131
+ cd packages/web-apple-pay
132
+ npm run build
133
+ yalc publish --sig
134
+ ```
135
+
136
+ Then in a consuming app:
137
+
138
+ ```sh
139
+ yalc add @economist/web-apple-pay
140
+ ```
package/dist/index.js CHANGED
@@ -739,8 +739,54 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
739
739
  // src/templates/apple-pay-button.template.html
740
740
  var apple_pay_button_template_default = '<style>\n /* Use CSS variables for layout so parent walls can easily override placement and sizing */\n teg-apple-pay-button {\n display: block;\n margin: var(--apple-pay-margin, 0);\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-container {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n width: 100%;\n min-width: 0;\n height: 100%;\n min-height: var(--apple-pay-height, 2.75rem);\n background-color: #000;\n color: #fff;\n border: none;\n border-radius: var(--apple-pay-border-radius, 1.375rem);\n cursor: pointer;\n font-family: var(--wall-type-system-sans, system-ui, sans-serif);\n font-size: 17px;\n font-weight: 500;\n }\n\n teg-apple-pay-button .apple-pay-btn svg {\n flex-shrink: 0;\n display: block;\n }\n</style>\n\n<div class="apple-pay-container" aria-label="Express checkout with Apple Pay">\n <button\n class="apple-pay-btn"\n type="button"\n aria-label="Pay with Apple Pay"\n data-analytics="paywall:apple-pay-checkout"\n >\n <svg width="51" height="22" aria-label="Apple Pay" role="img">\n <use href="#apple-pay-logo" />\n </svg>\n </button>\n</div>\n';
741
741
 
742
+ // src/clients/payment-checkout/config.ts
743
+ var runtimeConfig = null;
744
+ var configureApplePay = (config) => {
745
+ runtimeConfig = {
746
+ ...config
747
+ };
748
+ };
749
+ var getApplePayConfig = () => runtimeConfig;
750
+
751
+ // src/clients/payment-checkout/detects.ts
752
+ var isAppleDeviceOrSafari = () => {
753
+ const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
754
+ const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
755
+ return isIOSOrMacTouchDevice || isSafariBrowser;
756
+ };
757
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
758
+ var isApplePayAvailable = async () => {
759
+ if (bypassApplePayChecks()) {
760
+ return true;
761
+ }
762
+ if (!isAppleDeviceOrSafari()) {
763
+ return false;
764
+ }
765
+ const applePaySession = window.ApplePaySession;
766
+ if (!applePaySession) {
767
+ return false;
768
+ }
769
+ try {
770
+ if (typeof applePaySession.canMakePayments !== "function") {
771
+ return false;
772
+ }
773
+ if (!applePaySession.canMakePayments()) {
774
+ return false;
775
+ }
776
+ const merchantId = getApplePayConfig()?.merchantId;
777
+ if (!merchantId) {
778
+ return false;
779
+ }
780
+ return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
781
+ } catch (error) {
782
+ console.error("Error checking Apple Pay active card status:", error);
783
+ return false;
784
+ }
785
+ };
786
+
742
787
  // src/apple-pay-button.ts
743
788
  var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
789
+ var APPLE_PAY_EXCLUDED_VARIANTS = ["bundle", "insider_print"];
744
790
  var ApplePayButton = class _ApplePayButton extends HTMLElement {
745
791
  static tag = "teg-apple-pay-button";
746
792
  static define() {
@@ -753,6 +799,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
753
799
  if (!offer) {
754
800
  return null;
755
801
  }
802
+ const isExcludedVariant = APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant);
803
+ if (isExcludedVariant) {
804
+ return null;
805
+ }
756
806
  if (!new URLSearchParams(window.location.search).has(
757
807
  FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
758
808
  )) {
@@ -766,12 +816,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
766
816
  }
767
817
  #offer = null;
768
818
  #country = "";
819
+ #rendering = false;
769
820
  get offer() {
770
821
  return this.#offer;
771
822
  }
772
823
  set offer(value) {
773
824
  this.#offer = value;
774
- this.updateDisabledState();
825
+ this.#updateDisabledState();
775
826
  }
776
827
  get country() {
777
828
  return this.#country;
@@ -779,34 +830,47 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
779
830
  set country(value) {
780
831
  this.#country = value;
781
832
  }
782
- connectedCallback() {
783
- if (this.querySelector(".apple-pay-container")) {
833
+ async connectedCallback() {
834
+ if (this.#rendering || this.querySelector(".apple-pay-container")) {
784
835
  return;
785
836
  }
786
- ensureApplePayLogoSymbol();
787
- const template = document.createElement("template");
788
- template.innerHTML = apple_pay_button_template_default;
789
- this.appendChild(template.content.cloneNode(true));
790
- this.updateDisabledState();
791
- const button = this.querySelector(".apple-pay-btn");
792
- button.addEventListener("click", () => {
793
- if (!this.#offer) {
794
- return;
795
- }
796
- if (document.querySelector(ApplePayModal.tag)) {
837
+ this.#rendering = true;
838
+ try {
839
+ const available = await isApplePayAvailable();
840
+ if (!available) {
841
+ this.style.display = "none";
797
842
  return;
798
843
  }
799
- ApplePayModal.define();
800
- const modal = document.createElement(ApplePayModal.tag);
801
- modal.offer = this.#offer;
802
- modal.country = this.#country;
803
- if (this.dataset.loggedIn !== void 0) {
804
- modal.dataset.loggedIn = this.dataset.loggedIn;
805
- }
806
- modal.open();
807
- });
844
+ this.style.removeProperty("display");
845
+ ensureApplePayLogoSymbol();
846
+ const template = document.createElement("template");
847
+ template.innerHTML = apple_pay_button_template_default;
848
+ this.appendChild(template.content.cloneNode(true));
849
+ this.#updateDisabledState();
850
+ const button = this.querySelector(".apple-pay-btn");
851
+ button.addEventListener("click", () => {
852
+ if (!this.#offer) {
853
+ return;
854
+ }
855
+ if (document.querySelector(ApplePayModal.tag)) {
856
+ return;
857
+ }
858
+ ApplePayModal.define();
859
+ const modal = document.createElement(ApplePayModal.tag);
860
+ modal.offer = this.#offer;
861
+ modal.country = this.#country;
862
+ if (this.dataset.loggedIn !== void 0) {
863
+ modal.dataset.loggedIn = this.dataset.loggedIn;
864
+ }
865
+ modal.open();
866
+ });
867
+ } catch {
868
+ this.style.display = "none";
869
+ } finally {
870
+ this.#rendering = false;
871
+ }
808
872
  }
809
- updateDisabledState() {
873
+ #updateDisabledState() {
810
874
  const button = this.querySelector(".apple-pay-btn");
811
875
  if (!button) {
812
876
  return;
@@ -829,6 +893,7 @@ function defineApplePayElements() {
829
893
  export {
830
894
  ApplePayButton,
831
895
  ApplePayModal,
896
+ configureApplePay,
832
897
  defineApplePayElements,
833
898
  ensureApplePayLogoSymbol
834
899
  };
@@ -11,6 +11,5 @@ export declare class ApplePayButton extends HTMLElement {
11
11
  set offer(value: Offer | null);
12
12
  get country(): string;
13
13
  set country(value: string);
14
- connectedCallback(): void;
15
- private updateDisabledState;
14
+ connectedCallback(): Promise<void>;
16
15
  }
@@ -739,8 +739,49 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
739
739
  // src/templates/apple-pay-button.template.html
740
740
  var apple_pay_button_template_default = '<style>\n /* Use CSS variables for layout so parent walls can easily override placement and sizing */\n teg-apple-pay-button {\n display: block;\n margin: var(--apple-pay-margin, 0);\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-container {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n width: 100%;\n min-width: 0;\n height: 100%;\n min-height: var(--apple-pay-height, 2.75rem);\n background-color: #000;\n color: #fff;\n border: none;\n border-radius: var(--apple-pay-border-radius, 1.375rem);\n cursor: pointer;\n font-family: var(--wall-type-system-sans, system-ui, sans-serif);\n font-size: 17px;\n font-weight: 500;\n }\n\n teg-apple-pay-button .apple-pay-btn svg {\n flex-shrink: 0;\n display: block;\n }\n</style>\n\n<div class="apple-pay-container" aria-label="Express checkout with Apple Pay">\n <button\n class="apple-pay-btn"\n type="button"\n aria-label="Pay with Apple Pay"\n data-analytics="paywall:apple-pay-checkout"\n >\n <svg width="51" height="22" aria-label="Apple Pay" role="img">\n <use href="#apple-pay-logo" />\n </svg>\n </button>\n</div>\n';
741
741
 
742
+ // src/clients/payment-checkout/config.ts
743
+ var runtimeConfig = null;
744
+ var getApplePayConfig = () => runtimeConfig;
745
+
746
+ // src/clients/payment-checkout/detects.ts
747
+ var isAppleDeviceOrSafari = () => {
748
+ const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
749
+ const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
750
+ return isIOSOrMacTouchDevice || isSafariBrowser;
751
+ };
752
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
753
+ var isApplePayAvailable = async () => {
754
+ if (bypassApplePayChecks()) {
755
+ return true;
756
+ }
757
+ if (!isAppleDeviceOrSafari()) {
758
+ return false;
759
+ }
760
+ const applePaySession = window.ApplePaySession;
761
+ if (!applePaySession) {
762
+ return false;
763
+ }
764
+ try {
765
+ if (typeof applePaySession.canMakePayments !== "function") {
766
+ return false;
767
+ }
768
+ if (!applePaySession.canMakePayments()) {
769
+ return false;
770
+ }
771
+ const merchantId = getApplePayConfig()?.merchantId;
772
+ if (!merchantId) {
773
+ return false;
774
+ }
775
+ return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
776
+ } catch (error) {
777
+ console.error("Error checking Apple Pay active card status:", error);
778
+ return false;
779
+ }
780
+ };
781
+
742
782
  // src/apple-pay-button.ts
743
783
  var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
784
+ var APPLE_PAY_EXCLUDED_VARIANTS = ["bundle", "insider_print"];
744
785
  var ApplePayButton = class _ApplePayButton extends HTMLElement {
745
786
  static tag = "teg-apple-pay-button";
746
787
  static define() {
@@ -753,6 +794,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
753
794
  if (!offer) {
754
795
  return null;
755
796
  }
797
+ const isExcludedVariant = APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant);
798
+ if (isExcludedVariant) {
799
+ return null;
800
+ }
756
801
  if (!new URLSearchParams(window.location.search).has(
757
802
  FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
758
803
  )) {
@@ -766,12 +811,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
766
811
  }
767
812
  #offer = null;
768
813
  #country = "";
814
+ #rendering = false;
769
815
  get offer() {
770
816
  return this.#offer;
771
817
  }
772
818
  set offer(value) {
773
819
  this.#offer = value;
774
- this.updateDisabledState();
820
+ this.#updateDisabledState();
775
821
  }
776
822
  get country() {
777
823
  return this.#country;
@@ -779,34 +825,47 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
779
825
  set country(value) {
780
826
  this.#country = value;
781
827
  }
782
- connectedCallback() {
783
- if (this.querySelector(".apple-pay-container")) {
828
+ async connectedCallback() {
829
+ if (this.#rendering || this.querySelector(".apple-pay-container")) {
784
830
  return;
785
831
  }
786
- ensureApplePayLogoSymbol();
787
- const template = document.createElement("template");
788
- template.innerHTML = apple_pay_button_template_default;
789
- this.appendChild(template.content.cloneNode(true));
790
- this.updateDisabledState();
791
- const button = this.querySelector(".apple-pay-btn");
792
- button.addEventListener("click", () => {
793
- if (!this.#offer) {
832
+ this.#rendering = true;
833
+ try {
834
+ const available = await isApplePayAvailable();
835
+ if (!available) {
836
+ this.style.display = "none";
794
837
  return;
795
838
  }
796
- if (document.querySelector(ApplePayModal.tag)) {
797
- return;
798
- }
799
- ApplePayModal.define();
800
- const modal = document.createElement(ApplePayModal.tag);
801
- modal.offer = this.#offer;
802
- modal.country = this.#country;
803
- if (this.dataset.loggedIn !== void 0) {
804
- modal.dataset.loggedIn = this.dataset.loggedIn;
805
- }
806
- modal.open();
807
- });
839
+ this.style.removeProperty("display");
840
+ ensureApplePayLogoSymbol();
841
+ const template = document.createElement("template");
842
+ template.innerHTML = apple_pay_button_template_default;
843
+ this.appendChild(template.content.cloneNode(true));
844
+ this.#updateDisabledState();
845
+ const button = this.querySelector(".apple-pay-btn");
846
+ button.addEventListener("click", () => {
847
+ if (!this.#offer) {
848
+ return;
849
+ }
850
+ if (document.querySelector(ApplePayModal.tag)) {
851
+ return;
852
+ }
853
+ ApplePayModal.define();
854
+ const modal = document.createElement(ApplePayModal.tag);
855
+ modal.offer = this.#offer;
856
+ modal.country = this.#country;
857
+ if (this.dataset.loggedIn !== void 0) {
858
+ modal.dataset.loggedIn = this.dataset.loggedIn;
859
+ }
860
+ modal.open();
861
+ });
862
+ } catch {
863
+ this.style.display = "none";
864
+ } finally {
865
+ this.#rendering = false;
866
+ }
808
867
  }
809
- updateDisabledState() {
868
+ #updateDisabledState() {
810
869
  const button = this.querySelector(".apple-pay-btn");
811
870
  if (!button) {
812
871
  return;
@@ -0,0 +1,7 @@
1
+ export interface ApplePayRuntimeConfig {
2
+ merchantId: string;
3
+ debugBypassEnabled?: boolean;
4
+ }
5
+ export declare const configureApplePay: (config: ApplePayRuntimeConfig) => void;
6
+ export declare const getApplePayConfig: () => ApplePayRuntimeConfig | null;
7
+ export declare const resetApplePayConfig: () => void;
@@ -0,0 +1,16 @@
1
+ // src/clients/payment-checkout/config.ts
2
+ var runtimeConfig = null;
3
+ var configureApplePay = (config) => {
4
+ runtimeConfig = {
5
+ ...config
6
+ };
7
+ };
8
+ var getApplePayConfig = () => runtimeConfig;
9
+ var resetApplePayConfig = () => {
10
+ runtimeConfig = null;
11
+ };
12
+ export {
13
+ configureApplePay,
14
+ getApplePayConfig,
15
+ resetApplePayConfig
16
+ };
@@ -0,0 +1,3 @@
1
+ export declare const isAppleDeviceOrSafari: () => boolean;
2
+ export declare const bypassApplePayChecks: () => boolean;
3
+ export declare const isApplePayAvailable: () => Promise<boolean>;
@@ -0,0 +1,44 @@
1
+ // src/clients/payment-checkout/config.ts
2
+ var runtimeConfig = null;
3
+ var getApplePayConfig = () => runtimeConfig;
4
+
5
+ // src/clients/payment-checkout/detects.ts
6
+ var isAppleDeviceOrSafari = () => {
7
+ const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
8
+ const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
9
+ return isIOSOrMacTouchDevice || isSafariBrowser;
10
+ };
11
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
12
+ var isApplePayAvailable = async () => {
13
+ if (bypassApplePayChecks()) {
14
+ return true;
15
+ }
16
+ if (!isAppleDeviceOrSafari()) {
17
+ return false;
18
+ }
19
+ const applePaySession = window.ApplePaySession;
20
+ if (!applePaySession) {
21
+ return false;
22
+ }
23
+ try {
24
+ if (typeof applePaySession.canMakePayments !== "function") {
25
+ return false;
26
+ }
27
+ if (!applePaySession.canMakePayments()) {
28
+ return false;
29
+ }
30
+ const merchantId = getApplePayConfig()?.merchantId;
31
+ if (!merchantId) {
32
+ return false;
33
+ }
34
+ return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
35
+ } catch (error) {
36
+ console.error("Error checking Apple Pay active card status:", error);
37
+ return false;
38
+ }
39
+ };
40
+ export {
41
+ bypassApplePayChecks,
42
+ isAppleDeviceOrSafari,
43
+ isApplePayAvailable
44
+ };
@@ -1,5 +1,7 @@
1
1
  export { ApplePayButton } from './apple-pay-button';
2
2
  export { ApplePayModal } from './apple-pay-modal';
3
3
  export { ensureApplePayLogoSymbol } from './apple-pay-logo';
4
+ export { configureApplePay } from './clients/payment-checkout/config';
5
+ export type { ApplePayRuntimeConfig } from './clients/payment-checkout/config';
4
6
  export type { Offer } from './types/offer.types';
5
7
  export declare function defineApplePayElements(): void;
package/dist/src/index.js CHANGED
@@ -739,8 +739,54 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
739
739
  // src/templates/apple-pay-button.template.html
740
740
  var apple_pay_button_template_default = '<style>\n /* Use CSS variables for layout so parent walls can easily override placement and sizing */\n teg-apple-pay-button {\n display: block;\n margin: var(--apple-pay-margin, 0);\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-container {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n width: 100%;\n height: 100%;\n }\n\n teg-apple-pay-button .apple-pay-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n width: 100%;\n min-width: 0;\n height: 100%;\n min-height: var(--apple-pay-height, 2.75rem);\n background-color: #000;\n color: #fff;\n border: none;\n border-radius: var(--apple-pay-border-radius, 1.375rem);\n cursor: pointer;\n font-family: var(--wall-type-system-sans, system-ui, sans-serif);\n font-size: 17px;\n font-weight: 500;\n }\n\n teg-apple-pay-button .apple-pay-btn svg {\n flex-shrink: 0;\n display: block;\n }\n</style>\n\n<div class="apple-pay-container" aria-label="Express checkout with Apple Pay">\n <button\n class="apple-pay-btn"\n type="button"\n aria-label="Pay with Apple Pay"\n data-analytics="paywall:apple-pay-checkout"\n >\n <svg width="51" height="22" aria-label="Apple Pay" role="img">\n <use href="#apple-pay-logo" />\n </svg>\n </button>\n</div>\n';
741
741
 
742
+ // src/clients/payment-checkout/config.ts
743
+ var runtimeConfig = null;
744
+ var configureApplePay = (config) => {
745
+ runtimeConfig = {
746
+ ...config
747
+ };
748
+ };
749
+ var getApplePayConfig = () => runtimeConfig;
750
+
751
+ // src/clients/payment-checkout/detects.ts
752
+ var isAppleDeviceOrSafari = () => {
753
+ const isIOSOrMacTouchDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);
754
+ const isSafariBrowser = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1;
755
+ return isIOSOrMacTouchDevice || isSafariBrowser;
756
+ };
757
+ var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
758
+ var isApplePayAvailable = async () => {
759
+ if (bypassApplePayChecks()) {
760
+ return true;
761
+ }
762
+ if (!isAppleDeviceOrSafari()) {
763
+ return false;
764
+ }
765
+ const applePaySession = window.ApplePaySession;
766
+ if (!applePaySession) {
767
+ return false;
768
+ }
769
+ try {
770
+ if (typeof applePaySession.canMakePayments !== "function") {
771
+ return false;
772
+ }
773
+ if (!applePaySession.canMakePayments()) {
774
+ return false;
775
+ }
776
+ const merchantId = getApplePayConfig()?.merchantId;
777
+ if (!merchantId) {
778
+ return false;
779
+ }
780
+ return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
781
+ } catch (error) {
782
+ console.error("Error checking Apple Pay active card status:", error);
783
+ return false;
784
+ }
785
+ };
786
+
742
787
  // src/apple-pay-button.ts
743
788
  var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
789
+ var APPLE_PAY_EXCLUDED_VARIANTS = ["bundle", "insider_print"];
744
790
  var ApplePayButton = class _ApplePayButton extends HTMLElement {
745
791
  static tag = "teg-apple-pay-button";
746
792
  static define() {
@@ -753,6 +799,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
753
799
  if (!offer) {
754
800
  return null;
755
801
  }
802
+ const isExcludedVariant = APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant);
803
+ if (isExcludedVariant) {
804
+ return null;
805
+ }
756
806
  if (!new URLSearchParams(window.location.search).has(
757
807
  FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
758
808
  )) {
@@ -766,12 +816,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
766
816
  }
767
817
  #offer = null;
768
818
  #country = "";
819
+ #rendering = false;
769
820
  get offer() {
770
821
  return this.#offer;
771
822
  }
772
823
  set offer(value) {
773
824
  this.#offer = value;
774
- this.updateDisabledState();
825
+ this.#updateDisabledState();
775
826
  }
776
827
  get country() {
777
828
  return this.#country;
@@ -779,34 +830,47 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
779
830
  set country(value) {
780
831
  this.#country = value;
781
832
  }
782
- connectedCallback() {
783
- if (this.querySelector(".apple-pay-container")) {
833
+ async connectedCallback() {
834
+ if (this.#rendering || this.querySelector(".apple-pay-container")) {
784
835
  return;
785
836
  }
786
- ensureApplePayLogoSymbol();
787
- const template = document.createElement("template");
788
- template.innerHTML = apple_pay_button_template_default;
789
- this.appendChild(template.content.cloneNode(true));
790
- this.updateDisabledState();
791
- const button = this.querySelector(".apple-pay-btn");
792
- button.addEventListener("click", () => {
793
- if (!this.#offer) {
794
- return;
795
- }
796
- if (document.querySelector(ApplePayModal.tag)) {
837
+ this.#rendering = true;
838
+ try {
839
+ const available = await isApplePayAvailable();
840
+ if (!available) {
841
+ this.style.display = "none";
797
842
  return;
798
843
  }
799
- ApplePayModal.define();
800
- const modal = document.createElement(ApplePayModal.tag);
801
- modal.offer = this.#offer;
802
- modal.country = this.#country;
803
- if (this.dataset.loggedIn !== void 0) {
804
- modal.dataset.loggedIn = this.dataset.loggedIn;
805
- }
806
- modal.open();
807
- });
844
+ this.style.removeProperty("display");
845
+ ensureApplePayLogoSymbol();
846
+ const template = document.createElement("template");
847
+ template.innerHTML = apple_pay_button_template_default;
848
+ this.appendChild(template.content.cloneNode(true));
849
+ this.#updateDisabledState();
850
+ const button = this.querySelector(".apple-pay-btn");
851
+ button.addEventListener("click", () => {
852
+ if (!this.#offer) {
853
+ return;
854
+ }
855
+ if (document.querySelector(ApplePayModal.tag)) {
856
+ return;
857
+ }
858
+ ApplePayModal.define();
859
+ const modal = document.createElement(ApplePayModal.tag);
860
+ modal.offer = this.#offer;
861
+ modal.country = this.#country;
862
+ if (this.dataset.loggedIn !== void 0) {
863
+ modal.dataset.loggedIn = this.dataset.loggedIn;
864
+ }
865
+ modal.open();
866
+ });
867
+ } catch {
868
+ this.style.display = "none";
869
+ } finally {
870
+ this.#rendering = false;
871
+ }
808
872
  }
809
- updateDisabledState() {
873
+ #updateDisabledState() {
810
874
  const button = this.querySelector(".apple-pay-btn");
811
875
  if (!button) {
812
876
  return;
@@ -829,6 +893,7 @@ function defineApplePayElements() {
829
893
  export {
830
894
  ApplePayButton,
831
895
  ApplePayModal,
896
+ configureApplePay,
832
897
  defineApplePayElements,
833
898
  ensureApplePayLogoSymbol
834
899
  };
@@ -71,3 +71,13 @@ export type Offer = {
71
71
  content?: Content;
72
72
  meta_data?: MetaData;
73
73
  };
74
+ /** Total number of items matching the relevant lookup. */
75
+ export type ResponseModelMetaTotal = {
76
+ total: number;
77
+ };
78
+ export type ResponsePostPricedSkuOffer = {
79
+ /** Offer list. */
80
+ data: Offer[] | null;
81
+ /** Set of key value pairs. */
82
+ meta: ResponseModelMetaTotal | null;
83
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@economist/web-apple-pay",
3
3
  "description": "Web component package for Apple Pay checkout UI",
4
- "version": "1.0.1",
4
+ "version": "1.1.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",