@economist/web-apple-pay 1.0.1 → 1.2.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 +144 -0
- package/dist/index.js +163 -22
- package/dist/src/apple-pay-button.d.ts +1 -2
- package/dist/src/apple-pay-button.js +157 -22
- package/dist/src/clients/payment-checkout/config.d.ts +8 -0
- package/dist/src/clients/payment-checkout/config.js +16 -0
- package/dist/src/clients/payment-checkout/detects.d.ts +3 -0
- package/dist/src/clients/payment-checkout/detects.js +47 -0
- package/dist/src/clients/payment-checkout/index.d.ts +1 -0
- package/dist/src/clients/payment-checkout/index.js +59 -0
- package/dist/src/clients/payment-checkout/session.d.ts +2 -0
- package/dist/src/clients/payment-checkout/session.js +67 -0
- package/dist/src/clients/payment-checkout/types.d.ts +14 -0
- package/dist/src/clients/payment-checkout/types.js +14 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +163 -22
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
// required: wallet API base URL for session and checkout calls
|
|
36
|
+
walletApiBaseUrl: 'https://bff-test.economist.com',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
defineApplePayElements();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2) Create and mount a button
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import type { Offer, ApplePayButton } from '@economist/web-apple-pay';
|
|
46
|
+
|
|
47
|
+
const button = document.createElement('teg-apple-pay-button') as ApplePayButton;
|
|
48
|
+
button.offer = offer as Offer;
|
|
49
|
+
button.country = 'US';
|
|
50
|
+
button.dataset.loggedIn = 'true';
|
|
51
|
+
|
|
52
|
+
container.appendChild(button);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
When clicked, the button creates and opens `teg-apple-pay-modal` automatically.
|
|
56
|
+
|
|
57
|
+
## Runtime configuration
|
|
58
|
+
|
|
59
|
+
### `configureApplePay(config)`
|
|
60
|
+
|
|
61
|
+
Sets package-level runtime configuration used by Apple Pay availability checks.
|
|
62
|
+
Call this once during app startup.
|
|
63
|
+
|
|
64
|
+
`config` shape:
|
|
65
|
+
|
|
66
|
+
- `merchantId` (`string`, required): Apple Pay merchant identifier used for active-card checks.
|
|
67
|
+
- `debugBypassEnabled` (`boolean`, optional): allows `?BYPASS_APPLE_PAY_CHECKS=1` query param to bypass capability checks in non-production-style debugging flows.
|
|
68
|
+
- `walletApiBaseUrl` (`string`, required): base URL for wallet API requests.
|
|
69
|
+
|
|
70
|
+
If `merchantId` is not configured, Apple Pay availability checks return `false` and the button hides itself.
|
|
71
|
+
|
|
72
|
+
## Component behavior
|
|
73
|
+
|
|
74
|
+
### `ApplePayButton`
|
|
75
|
+
|
|
76
|
+
- Requires `offer` and `country` before click flow is meaningful.
|
|
77
|
+
- Checks Apple Pay availability on connect; if unavailable, it hides.
|
|
78
|
+
- Skips rendering for excluded variants (`bundle`, `insider_print`).
|
|
79
|
+
- Forwards `data-logged-in` from button to modal (`modal.dataset.loggedIn`).
|
|
80
|
+
|
|
81
|
+
### `ApplePayModal`
|
|
82
|
+
|
|
83
|
+
- Opened by `ApplePayButton` click.
|
|
84
|
+
- Emits `apple-pay-confirmed` when CTA is accepted.
|
|
85
|
+
- `Escape` closes the modal and prevents propagation to other global handlers.
|
|
86
|
+
|
|
87
|
+
## Events
|
|
88
|
+
|
|
89
|
+
### `apple-pay-confirmed`
|
|
90
|
+
|
|
91
|
+
Dispatched from `ApplePayModal` as a `CustomEvent` with:
|
|
92
|
+
|
|
93
|
+
- `detail.skuId` (`string`)
|
|
94
|
+
- `detail.country` (`string`)
|
|
95
|
+
|
|
96
|
+
Consumers should listen for this event and continue payment orchestration.
|
|
97
|
+
|
|
98
|
+
## Public API
|
|
99
|
+
|
|
100
|
+
- `defineApplePayElements()`: registers both custom elements.
|
|
101
|
+
- `configureApplePay(config)`: sets required runtime config (merchant ID and wallet API base URL, with optional debug bypass).
|
|
102
|
+
- `ApplePayButton`: button component class.
|
|
103
|
+
- `ApplePayModal`: modal component class.
|
|
104
|
+
- `ensureApplePayLogoSymbol()`: logo symbol injection helper.
|
|
105
|
+
- `Offer`: offer contract type used by component props.
|
|
106
|
+
|
|
107
|
+
## Troubleshooting
|
|
108
|
+
|
|
109
|
+
### Button does not appear
|
|
110
|
+
|
|
111
|
+
Check:
|
|
112
|
+
|
|
113
|
+
- `configureApplePay(...)` was called before rendering (with a valid `merchantId` and `walletApiBaseUrl`)
|
|
114
|
+
- device/browser supports Apple Pay checks (Safari/Apple device logic)
|
|
115
|
+
- query string/feature flag gating for your page is satisfied
|
|
116
|
+
- offer variant is not excluded (`bundle`, `insider_print`)
|
|
117
|
+
|
|
118
|
+
### Logged-in checkbox copy looks wrong
|
|
119
|
+
|
|
120
|
+
Set `button.dataset.loggedIn = 'true'` before appending the button. The button forwards this flag to the modal automatically.
|
|
121
|
+
|
|
122
|
+
## Security and responsibilities
|
|
123
|
+
|
|
124
|
+
Merchant setup must be handled by the consuming application:
|
|
125
|
+
|
|
126
|
+
- inject merchant config at runtime
|
|
127
|
+
- perform merchant validation on trusted backend services
|
|
128
|
+
- keep certificates/secrets outside this package
|
|
129
|
+
|
|
130
|
+
## Local development
|
|
131
|
+
|
|
132
|
+
From `shared-packages`:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
cd packages/web-apple-pay
|
|
136
|
+
npm run build
|
|
137
|
+
yalc publish --sig
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then in a consuming app:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
yalc add @economist/web-apple-pay
|
|
144
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -739,8 +739,118 @@ 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
|
+
console.warn(
|
|
779
|
+
"[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
|
|
780
|
+
);
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
console.error("Error checking Apple Pay active card status:", error);
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
// src/clients/payment-checkout/types.ts
|
|
791
|
+
var ApiError = class extends Error {
|
|
792
|
+
status;
|
|
793
|
+
body;
|
|
794
|
+
constructor(status, body) {
|
|
795
|
+
super(`API request failed with status ${status}`);
|
|
796
|
+
this.name = "ApiError";
|
|
797
|
+
this.status = status;
|
|
798
|
+
this.body = body;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// src/clients/payment-checkout/index.ts
|
|
803
|
+
var getWalletApiBaseUrl = () => {
|
|
804
|
+
const config = getApplePayConfig();
|
|
805
|
+
if (!config?.walletApiBaseUrl) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
"[web-apple-pay] Missing walletApiBaseUrl. Call configureApplePay({ merchantId, walletApiBaseUrl, ... }) before making wallet API requests."
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
return config.walletApiBaseUrl;
|
|
811
|
+
};
|
|
812
|
+
var apiFetch = async (path, options = {}) => {
|
|
813
|
+
const baseUrl = getWalletApiBaseUrl();
|
|
814
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
815
|
+
const hasBody = options.body !== void 0 && options.body !== null;
|
|
816
|
+
const headers = new Headers(options.headers);
|
|
817
|
+
if (!headers.has("Accept")) {
|
|
818
|
+
headers.set("Accept", "application/json");
|
|
819
|
+
}
|
|
820
|
+
if (!headers.has("Content-Type")) {
|
|
821
|
+
if (hasBody || method !== "GET" && method !== "HEAD") {
|
|
822
|
+
headers.set("Content-Type", "application/json");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
826
|
+
...options,
|
|
827
|
+
headers
|
|
828
|
+
});
|
|
829
|
+
if (!response.ok) {
|
|
830
|
+
const contentType = response.headers.get("content-type") || "";
|
|
831
|
+
if (contentType.includes("application/json")) {
|
|
832
|
+
const errorBody = await response.json();
|
|
833
|
+
throw new ApiError(response.status, errorBody);
|
|
834
|
+
}
|
|
835
|
+
const errorText = await response.text();
|
|
836
|
+
throw new Error(
|
|
837
|
+
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
return response.json();
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// src/clients/payment-checkout/session.ts
|
|
844
|
+
var getExpressCheckoutSession = () => {
|
|
845
|
+
return apiFetch("/v1/wallet/session", {
|
|
846
|
+
method: "GET",
|
|
847
|
+
credentials: "include"
|
|
848
|
+
});
|
|
849
|
+
};
|
|
850
|
+
|
|
742
851
|
// src/apple-pay-button.ts
|
|
743
852
|
var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
|
|
853
|
+
var APPLE_PAY_EXCLUDED_VARIANTS = ["bundle", "insider_print"];
|
|
744
854
|
var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
745
855
|
static tag = "teg-apple-pay-button";
|
|
746
856
|
static define() {
|
|
@@ -753,6 +863,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
753
863
|
if (!offer) {
|
|
754
864
|
return null;
|
|
755
865
|
}
|
|
866
|
+
const isExcludedVariant = APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant);
|
|
867
|
+
if (isExcludedVariant) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
756
870
|
if (!new URLSearchParams(window.location.search).has(
|
|
757
871
|
FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
|
|
758
872
|
)) {
|
|
@@ -766,12 +880,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
766
880
|
}
|
|
767
881
|
#offer = null;
|
|
768
882
|
#country = "";
|
|
883
|
+
#rendering = false;
|
|
769
884
|
get offer() {
|
|
770
885
|
return this.#offer;
|
|
771
886
|
}
|
|
772
887
|
set offer(value) {
|
|
773
888
|
this.#offer = value;
|
|
774
|
-
this
|
|
889
|
+
this.#updateDisabledState();
|
|
775
890
|
}
|
|
776
891
|
get country() {
|
|
777
892
|
return this.#country;
|
|
@@ -779,34 +894,52 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
779
894
|
set country(value) {
|
|
780
895
|
this.#country = value;
|
|
781
896
|
}
|
|
782
|
-
connectedCallback() {
|
|
783
|
-
if (this.querySelector(".apple-pay-container")) {
|
|
897
|
+
async connectedCallback() {
|
|
898
|
+
if (this.#rendering || this.querySelector(".apple-pay-container")) {
|
|
784
899
|
return;
|
|
785
900
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const button = this.querySelector(".apple-pay-btn");
|
|
792
|
-
button.addEventListener("click", () => {
|
|
793
|
-
if (!this.#offer) {
|
|
901
|
+
this.#rendering = true;
|
|
902
|
+
try {
|
|
903
|
+
const available = await isApplePayAvailable();
|
|
904
|
+
if (!available) {
|
|
905
|
+
this.style.display = "none";
|
|
794
906
|
return;
|
|
795
907
|
}
|
|
796
|
-
|
|
908
|
+
const sessionData = await this.#fetchSession();
|
|
909
|
+
if (sessionData?.loggedIn && (sessionData.userType === "active-subscription" || sessionData.userType === "b2b-user")) {
|
|
910
|
+
this.style.display = "none";
|
|
797
911
|
return;
|
|
798
912
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
913
|
+
this.style.removeProperty("display");
|
|
914
|
+
ensureApplePayLogoSymbol();
|
|
915
|
+
const template = document.createElement("template");
|
|
916
|
+
template.innerHTML = apple_pay_button_template_default;
|
|
917
|
+
this.appendChild(template.content.cloneNode(true));
|
|
918
|
+
this.#updateDisabledState();
|
|
919
|
+
const button = this.querySelector(".apple-pay-btn");
|
|
920
|
+
button.addEventListener("click", () => {
|
|
921
|
+
if (!this.#offer) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (document.querySelector(ApplePayModal.tag)) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
ApplePayModal.define();
|
|
928
|
+
const modal = document.createElement(ApplePayModal.tag);
|
|
929
|
+
modal.offer = this.#offer;
|
|
930
|
+
modal.country = this.#country;
|
|
931
|
+
if (this.dataset.loggedIn !== void 0) {
|
|
932
|
+
modal.dataset.loggedIn = this.dataset.loggedIn;
|
|
933
|
+
}
|
|
934
|
+
modal.open();
|
|
935
|
+
});
|
|
936
|
+
} catch {
|
|
937
|
+
this.style.display = "none";
|
|
938
|
+
} finally {
|
|
939
|
+
this.#rendering = false;
|
|
940
|
+
}
|
|
808
941
|
}
|
|
809
|
-
updateDisabledState() {
|
|
942
|
+
#updateDisabledState() {
|
|
810
943
|
const button = this.querySelector(".apple-pay-btn");
|
|
811
944
|
if (!button) {
|
|
812
945
|
return;
|
|
@@ -819,6 +952,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
819
952
|
button.setAttribute("disabled", "");
|
|
820
953
|
button.setAttribute("aria-disabled", "true");
|
|
821
954
|
}
|
|
955
|
+
async #fetchSession() {
|
|
956
|
+
try {
|
|
957
|
+
return await getExpressCheckoutSession();
|
|
958
|
+
} catch {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
822
962
|
};
|
|
823
963
|
|
|
824
964
|
// src/index.ts
|
|
@@ -829,6 +969,7 @@ function defineApplePayElements() {
|
|
|
829
969
|
export {
|
|
830
970
|
ApplePayButton,
|
|
831
971
|
ApplePayModal,
|
|
972
|
+
configureApplePay,
|
|
832
973
|
defineApplePayElements,
|
|
833
974
|
ensureApplePayLogoSymbol
|
|
834
975
|
};
|
|
@@ -739,8 +739,113 @@ 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
|
+
console.warn(
|
|
774
|
+
"[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
|
|
775
|
+
);
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
console.error("Error checking Apple Pay active card status:", error);
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// src/clients/payment-checkout/types.ts
|
|
786
|
+
var ApiError = class extends Error {
|
|
787
|
+
status;
|
|
788
|
+
body;
|
|
789
|
+
constructor(status, body) {
|
|
790
|
+
super(`API request failed with status ${status}`);
|
|
791
|
+
this.name = "ApiError";
|
|
792
|
+
this.status = status;
|
|
793
|
+
this.body = body;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/clients/payment-checkout/index.ts
|
|
798
|
+
var getWalletApiBaseUrl = () => {
|
|
799
|
+
const config = getApplePayConfig();
|
|
800
|
+
if (!config?.walletApiBaseUrl) {
|
|
801
|
+
throw new Error(
|
|
802
|
+
"[web-apple-pay] Missing walletApiBaseUrl. Call configureApplePay({ merchantId, walletApiBaseUrl, ... }) before making wallet API requests."
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
return config.walletApiBaseUrl;
|
|
806
|
+
};
|
|
807
|
+
var apiFetch = async (path, options = {}) => {
|
|
808
|
+
const baseUrl = getWalletApiBaseUrl();
|
|
809
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
810
|
+
const hasBody = options.body !== void 0 && options.body !== null;
|
|
811
|
+
const headers = new Headers(options.headers);
|
|
812
|
+
if (!headers.has("Accept")) {
|
|
813
|
+
headers.set("Accept", "application/json");
|
|
814
|
+
}
|
|
815
|
+
if (!headers.has("Content-Type")) {
|
|
816
|
+
if (hasBody || method !== "GET" && method !== "HEAD") {
|
|
817
|
+
headers.set("Content-Type", "application/json");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
821
|
+
...options,
|
|
822
|
+
headers
|
|
823
|
+
});
|
|
824
|
+
if (!response.ok) {
|
|
825
|
+
const contentType = response.headers.get("content-type") || "";
|
|
826
|
+
if (contentType.includes("application/json")) {
|
|
827
|
+
const errorBody = await response.json();
|
|
828
|
+
throw new ApiError(response.status, errorBody);
|
|
829
|
+
}
|
|
830
|
+
const errorText = await response.text();
|
|
831
|
+
throw new Error(
|
|
832
|
+
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
return response.json();
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// src/clients/payment-checkout/session.ts
|
|
839
|
+
var getExpressCheckoutSession = () => {
|
|
840
|
+
return apiFetch("/v1/wallet/session", {
|
|
841
|
+
method: "GET",
|
|
842
|
+
credentials: "include"
|
|
843
|
+
});
|
|
844
|
+
};
|
|
845
|
+
|
|
742
846
|
// src/apple-pay-button.ts
|
|
743
847
|
var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
|
|
848
|
+
var APPLE_PAY_EXCLUDED_VARIANTS = ["bundle", "insider_print"];
|
|
744
849
|
var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
745
850
|
static tag = "teg-apple-pay-button";
|
|
746
851
|
static define() {
|
|
@@ -753,6 +858,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
753
858
|
if (!offer) {
|
|
754
859
|
return null;
|
|
755
860
|
}
|
|
861
|
+
const isExcludedVariant = APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant);
|
|
862
|
+
if (isExcludedVariant) {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
756
865
|
if (!new URLSearchParams(window.location.search).has(
|
|
757
866
|
FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
|
|
758
867
|
)) {
|
|
@@ -766,12 +875,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
766
875
|
}
|
|
767
876
|
#offer = null;
|
|
768
877
|
#country = "";
|
|
878
|
+
#rendering = false;
|
|
769
879
|
get offer() {
|
|
770
880
|
return this.#offer;
|
|
771
881
|
}
|
|
772
882
|
set offer(value) {
|
|
773
883
|
this.#offer = value;
|
|
774
|
-
this
|
|
884
|
+
this.#updateDisabledState();
|
|
775
885
|
}
|
|
776
886
|
get country() {
|
|
777
887
|
return this.#country;
|
|
@@ -779,34 +889,52 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
779
889
|
set country(value) {
|
|
780
890
|
this.#country = value;
|
|
781
891
|
}
|
|
782
|
-
connectedCallback() {
|
|
783
|
-
if (this.querySelector(".apple-pay-container")) {
|
|
892
|
+
async connectedCallback() {
|
|
893
|
+
if (this.#rendering || this.querySelector(".apple-pay-container")) {
|
|
784
894
|
return;
|
|
785
895
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const button = this.querySelector(".apple-pay-btn");
|
|
792
|
-
button.addEventListener("click", () => {
|
|
793
|
-
if (!this.#offer) {
|
|
896
|
+
this.#rendering = true;
|
|
897
|
+
try {
|
|
898
|
+
const available = await isApplePayAvailable();
|
|
899
|
+
if (!available) {
|
|
900
|
+
this.style.display = "none";
|
|
794
901
|
return;
|
|
795
902
|
}
|
|
796
|
-
|
|
903
|
+
const sessionData = await this.#fetchSession();
|
|
904
|
+
if (sessionData?.loggedIn && (sessionData.userType === "active-subscription" || sessionData.userType === "b2b-user")) {
|
|
905
|
+
this.style.display = "none";
|
|
797
906
|
return;
|
|
798
907
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
908
|
+
this.style.removeProperty("display");
|
|
909
|
+
ensureApplePayLogoSymbol();
|
|
910
|
+
const template = document.createElement("template");
|
|
911
|
+
template.innerHTML = apple_pay_button_template_default;
|
|
912
|
+
this.appendChild(template.content.cloneNode(true));
|
|
913
|
+
this.#updateDisabledState();
|
|
914
|
+
const button = this.querySelector(".apple-pay-btn");
|
|
915
|
+
button.addEventListener("click", () => {
|
|
916
|
+
if (!this.#offer) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (document.querySelector(ApplePayModal.tag)) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
ApplePayModal.define();
|
|
923
|
+
const modal = document.createElement(ApplePayModal.tag);
|
|
924
|
+
modal.offer = this.#offer;
|
|
925
|
+
modal.country = this.#country;
|
|
926
|
+
if (this.dataset.loggedIn !== void 0) {
|
|
927
|
+
modal.dataset.loggedIn = this.dataset.loggedIn;
|
|
928
|
+
}
|
|
929
|
+
modal.open();
|
|
930
|
+
});
|
|
931
|
+
} catch {
|
|
932
|
+
this.style.display = "none";
|
|
933
|
+
} finally {
|
|
934
|
+
this.#rendering = false;
|
|
935
|
+
}
|
|
808
936
|
}
|
|
809
|
-
updateDisabledState() {
|
|
937
|
+
#updateDisabledState() {
|
|
810
938
|
const button = this.querySelector(".apple-pay-btn");
|
|
811
939
|
if (!button) {
|
|
812
940
|
return;
|
|
@@ -819,6 +947,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
819
947
|
button.setAttribute("disabled", "");
|
|
820
948
|
button.setAttribute("aria-disabled", "true");
|
|
821
949
|
}
|
|
950
|
+
async #fetchSession() {
|
|
951
|
+
try {
|
|
952
|
+
return await getExpressCheckoutSession();
|
|
953
|
+
} catch {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
822
957
|
};
|
|
823
958
|
export {
|
|
824
959
|
ApplePayButton
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ApplePayRuntimeConfig {
|
|
2
|
+
merchantId: string;
|
|
3
|
+
debugBypassEnabled?: boolean;
|
|
4
|
+
walletApiBaseUrl: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const configureApplePay: (config: ApplePayRuntimeConfig) => void;
|
|
7
|
+
export declare const getApplePayConfig: () => ApplePayRuntimeConfig | null;
|
|
8
|
+
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,47 @@
|
|
|
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
|
+
console.warn(
|
|
33
|
+
"[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
|
|
34
|
+
);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("Error checking Apple Pay active card status:", error);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export {
|
|
44
|
+
bypassApplePayChecks,
|
|
45
|
+
isAppleDeviceOrSafari,
|
|
46
|
+
isApplePayAvailable
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const apiFetch: <T>(path: string, options?: RequestInit) => Promise<T>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/clients/payment-checkout/config.ts
|
|
2
|
+
var runtimeConfig = null;
|
|
3
|
+
var getApplePayConfig = () => runtimeConfig;
|
|
4
|
+
|
|
5
|
+
// src/clients/payment-checkout/types.ts
|
|
6
|
+
var ApiError = class extends Error {
|
|
7
|
+
status;
|
|
8
|
+
body;
|
|
9
|
+
constructor(status, body) {
|
|
10
|
+
super(`API request failed with status ${status}`);
|
|
11
|
+
this.name = "ApiError";
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.body = body;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/clients/payment-checkout/index.ts
|
|
18
|
+
var getWalletApiBaseUrl = () => {
|
|
19
|
+
const config = getApplePayConfig();
|
|
20
|
+
if (!config?.walletApiBaseUrl) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"[web-apple-pay] Missing walletApiBaseUrl. Call configureApplePay({ merchantId, walletApiBaseUrl, ... }) before making wallet API requests."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return config.walletApiBaseUrl;
|
|
26
|
+
};
|
|
27
|
+
var apiFetch = async (path, options = {}) => {
|
|
28
|
+
const baseUrl = getWalletApiBaseUrl();
|
|
29
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
30
|
+
const hasBody = options.body !== void 0 && options.body !== null;
|
|
31
|
+
const headers = new Headers(options.headers);
|
|
32
|
+
if (!headers.has("Accept")) {
|
|
33
|
+
headers.set("Accept", "application/json");
|
|
34
|
+
}
|
|
35
|
+
if (!headers.has("Content-Type")) {
|
|
36
|
+
if (hasBody || method !== "GET" && method !== "HEAD") {
|
|
37
|
+
headers.set("Content-Type", "application/json");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
41
|
+
...options,
|
|
42
|
+
headers
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const contentType = response.headers.get("content-type") || "";
|
|
46
|
+
if (contentType.includes("application/json")) {
|
|
47
|
+
const errorBody = await response.json();
|
|
48
|
+
throw new ApiError(response.status, errorBody);
|
|
49
|
+
}
|
|
50
|
+
const errorText = await response.text();
|
|
51
|
+
throw new Error(
|
|
52
|
+
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return response.json();
|
|
56
|
+
};
|
|
57
|
+
export {
|
|
58
|
+
apiFetch
|
|
59
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/clients/payment-checkout/config.ts
|
|
2
|
+
var runtimeConfig = null;
|
|
3
|
+
var getApplePayConfig = () => runtimeConfig;
|
|
4
|
+
|
|
5
|
+
// src/clients/payment-checkout/types.ts
|
|
6
|
+
var ApiError = class extends Error {
|
|
7
|
+
status;
|
|
8
|
+
body;
|
|
9
|
+
constructor(status, body) {
|
|
10
|
+
super(`API request failed with status ${status}`);
|
|
11
|
+
this.name = "ApiError";
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.body = body;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/clients/payment-checkout/index.ts
|
|
18
|
+
var getWalletApiBaseUrl = () => {
|
|
19
|
+
const config = getApplePayConfig();
|
|
20
|
+
if (!config?.walletApiBaseUrl) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"[web-apple-pay] Missing walletApiBaseUrl. Call configureApplePay({ merchantId, walletApiBaseUrl, ... }) before making wallet API requests."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return config.walletApiBaseUrl;
|
|
26
|
+
};
|
|
27
|
+
var apiFetch = async (path, options = {}) => {
|
|
28
|
+
const baseUrl = getWalletApiBaseUrl();
|
|
29
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
30
|
+
const hasBody = options.body !== void 0 && options.body !== null;
|
|
31
|
+
const headers = new Headers(options.headers);
|
|
32
|
+
if (!headers.has("Accept")) {
|
|
33
|
+
headers.set("Accept", "application/json");
|
|
34
|
+
}
|
|
35
|
+
if (!headers.has("Content-Type")) {
|
|
36
|
+
if (hasBody || method !== "GET" && method !== "HEAD") {
|
|
37
|
+
headers.set("Content-Type", "application/json");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
41
|
+
...options,
|
|
42
|
+
headers
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const contentType = response.headers.get("content-type") || "";
|
|
46
|
+
if (contentType.includes("application/json")) {
|
|
47
|
+
const errorBody = await response.json();
|
|
48
|
+
throw new ApiError(response.status, errorBody);
|
|
49
|
+
}
|
|
50
|
+
const errorText = await response.text();
|
|
51
|
+
throw new Error(
|
|
52
|
+
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return response.json();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/clients/payment-checkout/session.ts
|
|
59
|
+
var getExpressCheckoutSession = () => {
|
|
60
|
+
return apiFetch("/v1/wallet/session", {
|
|
61
|
+
method: "GET",
|
|
62
|
+
credentials: "include"
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
export {
|
|
66
|
+
getExpressCheckoutSession
|
|
67
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SessionResponse = {
|
|
2
|
+
loggedIn: false;
|
|
3
|
+
userType: 'new-eligible-user';
|
|
4
|
+
} | {
|
|
5
|
+
loggedIn: true;
|
|
6
|
+
emailAddress: string;
|
|
7
|
+
contactId: string;
|
|
8
|
+
userType: 'active-subscription' | 'b2b-user' | 'new-eligible-user';
|
|
9
|
+
};
|
|
10
|
+
export declare class ApiError extends Error {
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly body: unknown;
|
|
13
|
+
constructor(status: number, body: unknown);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/clients/payment-checkout/types.ts
|
|
2
|
+
var ApiError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
constructor(status, body) {
|
|
6
|
+
super(`API request failed with status ${status}`);
|
|
7
|
+
this.name = "ApiError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
ApiError
|
|
14
|
+
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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,118 @@ 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
|
+
console.warn(
|
|
779
|
+
"[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
|
|
780
|
+
);
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
console.error("Error checking Apple Pay active card status:", error);
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
// src/clients/payment-checkout/types.ts
|
|
791
|
+
var ApiError = class extends Error {
|
|
792
|
+
status;
|
|
793
|
+
body;
|
|
794
|
+
constructor(status, body) {
|
|
795
|
+
super(`API request failed with status ${status}`);
|
|
796
|
+
this.name = "ApiError";
|
|
797
|
+
this.status = status;
|
|
798
|
+
this.body = body;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// src/clients/payment-checkout/index.ts
|
|
803
|
+
var getWalletApiBaseUrl = () => {
|
|
804
|
+
const config = getApplePayConfig();
|
|
805
|
+
if (!config?.walletApiBaseUrl) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
"[web-apple-pay] Missing walletApiBaseUrl. Call configureApplePay({ merchantId, walletApiBaseUrl, ... }) before making wallet API requests."
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
return config.walletApiBaseUrl;
|
|
811
|
+
};
|
|
812
|
+
var apiFetch = async (path, options = {}) => {
|
|
813
|
+
const baseUrl = getWalletApiBaseUrl();
|
|
814
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
815
|
+
const hasBody = options.body !== void 0 && options.body !== null;
|
|
816
|
+
const headers = new Headers(options.headers);
|
|
817
|
+
if (!headers.has("Accept")) {
|
|
818
|
+
headers.set("Accept", "application/json");
|
|
819
|
+
}
|
|
820
|
+
if (!headers.has("Content-Type")) {
|
|
821
|
+
if (hasBody || method !== "GET" && method !== "HEAD") {
|
|
822
|
+
headers.set("Content-Type", "application/json");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
826
|
+
...options,
|
|
827
|
+
headers
|
|
828
|
+
});
|
|
829
|
+
if (!response.ok) {
|
|
830
|
+
const contentType = response.headers.get("content-type") || "";
|
|
831
|
+
if (contentType.includes("application/json")) {
|
|
832
|
+
const errorBody = await response.json();
|
|
833
|
+
throw new ApiError(response.status, errorBody);
|
|
834
|
+
}
|
|
835
|
+
const errorText = await response.text();
|
|
836
|
+
throw new Error(
|
|
837
|
+
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
return response.json();
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// src/clients/payment-checkout/session.ts
|
|
844
|
+
var getExpressCheckoutSession = () => {
|
|
845
|
+
return apiFetch("/v1/wallet/session", {
|
|
846
|
+
method: "GET",
|
|
847
|
+
credentials: "include"
|
|
848
|
+
});
|
|
849
|
+
};
|
|
850
|
+
|
|
742
851
|
// src/apple-pay-button.ts
|
|
743
852
|
var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
|
|
853
|
+
var APPLE_PAY_EXCLUDED_VARIANTS = ["bundle", "insider_print"];
|
|
744
854
|
var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
745
855
|
static tag = "teg-apple-pay-button";
|
|
746
856
|
static define() {
|
|
@@ -753,6 +863,10 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
753
863
|
if (!offer) {
|
|
754
864
|
return null;
|
|
755
865
|
}
|
|
866
|
+
const isExcludedVariant = APPLE_PAY_EXCLUDED_VARIANTS.includes(offer.product?.variant);
|
|
867
|
+
if (isExcludedVariant) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
756
870
|
if (!new URLSearchParams(window.location.search).has(
|
|
757
871
|
FEATURE_APPLE_PAY_EXPRESS_CHECKOUT
|
|
758
872
|
)) {
|
|
@@ -766,12 +880,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
766
880
|
}
|
|
767
881
|
#offer = null;
|
|
768
882
|
#country = "";
|
|
883
|
+
#rendering = false;
|
|
769
884
|
get offer() {
|
|
770
885
|
return this.#offer;
|
|
771
886
|
}
|
|
772
887
|
set offer(value) {
|
|
773
888
|
this.#offer = value;
|
|
774
|
-
this
|
|
889
|
+
this.#updateDisabledState();
|
|
775
890
|
}
|
|
776
891
|
get country() {
|
|
777
892
|
return this.#country;
|
|
@@ -779,34 +894,52 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
779
894
|
set country(value) {
|
|
780
895
|
this.#country = value;
|
|
781
896
|
}
|
|
782
|
-
connectedCallback() {
|
|
783
|
-
if (this.querySelector(".apple-pay-container")) {
|
|
897
|
+
async connectedCallback() {
|
|
898
|
+
if (this.#rendering || this.querySelector(".apple-pay-container")) {
|
|
784
899
|
return;
|
|
785
900
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const button = this.querySelector(".apple-pay-btn");
|
|
792
|
-
button.addEventListener("click", () => {
|
|
793
|
-
if (!this.#offer) {
|
|
901
|
+
this.#rendering = true;
|
|
902
|
+
try {
|
|
903
|
+
const available = await isApplePayAvailable();
|
|
904
|
+
if (!available) {
|
|
905
|
+
this.style.display = "none";
|
|
794
906
|
return;
|
|
795
907
|
}
|
|
796
|
-
|
|
908
|
+
const sessionData = await this.#fetchSession();
|
|
909
|
+
if (sessionData?.loggedIn && (sessionData.userType === "active-subscription" || sessionData.userType === "b2b-user")) {
|
|
910
|
+
this.style.display = "none";
|
|
797
911
|
return;
|
|
798
912
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
913
|
+
this.style.removeProperty("display");
|
|
914
|
+
ensureApplePayLogoSymbol();
|
|
915
|
+
const template = document.createElement("template");
|
|
916
|
+
template.innerHTML = apple_pay_button_template_default;
|
|
917
|
+
this.appendChild(template.content.cloneNode(true));
|
|
918
|
+
this.#updateDisabledState();
|
|
919
|
+
const button = this.querySelector(".apple-pay-btn");
|
|
920
|
+
button.addEventListener("click", () => {
|
|
921
|
+
if (!this.#offer) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (document.querySelector(ApplePayModal.tag)) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
ApplePayModal.define();
|
|
928
|
+
const modal = document.createElement(ApplePayModal.tag);
|
|
929
|
+
modal.offer = this.#offer;
|
|
930
|
+
modal.country = this.#country;
|
|
931
|
+
if (this.dataset.loggedIn !== void 0) {
|
|
932
|
+
modal.dataset.loggedIn = this.dataset.loggedIn;
|
|
933
|
+
}
|
|
934
|
+
modal.open();
|
|
935
|
+
});
|
|
936
|
+
} catch {
|
|
937
|
+
this.style.display = "none";
|
|
938
|
+
} finally {
|
|
939
|
+
this.#rendering = false;
|
|
940
|
+
}
|
|
808
941
|
}
|
|
809
|
-
updateDisabledState() {
|
|
942
|
+
#updateDisabledState() {
|
|
810
943
|
const button = this.querySelector(".apple-pay-btn");
|
|
811
944
|
if (!button) {
|
|
812
945
|
return;
|
|
@@ -819,6 +952,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
819
952
|
button.setAttribute("disabled", "");
|
|
820
953
|
button.setAttribute("aria-disabled", "true");
|
|
821
954
|
}
|
|
955
|
+
async #fetchSession() {
|
|
956
|
+
try {
|
|
957
|
+
return await getExpressCheckoutSession();
|
|
958
|
+
} catch {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
822
962
|
};
|
|
823
963
|
|
|
824
964
|
// src/index.ts
|
|
@@ -829,6 +969,7 @@ function defineApplePayElements() {
|
|
|
829
969
|
export {
|
|
830
970
|
ApplePayButton,
|
|
831
971
|
ApplePayModal,
|
|
972
|
+
configureApplePay,
|
|
832
973
|
defineApplePayElements,
|
|
833
974
|
ensureApplePayLogoSymbol
|
|
834
975
|
};
|