@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 +140 -0
- package/dist/index.js +89 -24
- package/dist/src/apple-pay-button.d.ts +1 -2
- package/dist/src/apple-pay-button.js +83 -24
- package/dist/src/clients/payment-checkout/config.d.ts +7 -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 +44 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +89 -24
- package/dist/src/types/offer.types.d.ts +10 -0
- package/package.json +1 -1
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
|
|
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
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
};
|
|
@@ -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
|
|
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
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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,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
|
+
};
|
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,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
|
|
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
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
+
};
|