@economist/web-apple-pay 0.1.1-beta.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.
Files changed (55) hide show
  1. package/README.md +186 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1642 -0
  4. package/dist/src/apple-pay-button.d.ts +45 -0
  5. package/dist/src/apple-pay-button.js +1621 -0
  6. package/dist/src/apple-pay-button.stories.js +1934 -0
  7. package/dist/src/apple-pay-logo.d.ts +1 -0
  8. package/dist/src/apple-pay-logo.js +14 -0
  9. package/dist/src/apple-pay-modal.d.ts +12 -0
  10. package/dist/src/apple-pay-modal.js +442 -0
  11. package/dist/src/apple-pay-modal.stories.js +1778 -0
  12. package/dist/src/clients/payment-checkout/apple-session.d.ts +18 -0
  13. package/dist/src/clients/payment-checkout/apple-session.js +243 -0
  14. package/dist/src/clients/payment-checkout/billing-contact-details.d.ts +21 -0
  15. package/dist/src/clients/payment-checkout/billing-contact-details.js +109 -0
  16. package/dist/src/clients/payment-checkout/config.d.ts +9 -0
  17. package/dist/src/clients/payment-checkout/config.js +23 -0
  18. package/dist/src/clients/payment-checkout/contact-validation.d.ts +21 -0
  19. package/dist/src/clients/payment-checkout/contact-validation.js +50 -0
  20. package/dist/src/clients/payment-checkout/detects.d.ts +3 -0
  21. package/dist/src/clients/payment-checkout/detects.js +56 -0
  22. package/dist/src/clients/payment-checkout/index.d.ts +5 -0
  23. package/dist/src/clients/payment-checkout/index.js +83 -0
  24. package/dist/src/clients/payment-checkout/messages.d.ts +33 -0
  25. package/dist/src/clients/payment-checkout/messages.js +19 -0
  26. package/dist/src/clients/payment-checkout/payment-handler.d.ts +36 -0
  27. package/dist/src/clients/payment-checkout/payment-handler.js +389 -0
  28. package/dist/src/clients/payment-checkout/session.d.ts +2 -0
  29. package/dist/src/clients/payment-checkout/session.js +67 -0
  30. package/dist/src/clients/payment-checkout/shipping-contact-details.d.ts +10 -0
  31. package/dist/src/clients/payment-checkout/shipping-contact-details.js +83 -0
  32. package/dist/src/clients/payment-checkout/types.d.ts +118 -0
  33. package/dist/src/clients/payment-checkout/types.js +14 -0
  34. package/dist/src/index.d.ts +7 -0
  35. package/dist/src/index.js +1642 -0
  36. package/dist/src/stories/fixtures/offer.d.ts +2 -0
  37. package/dist/src/stories/fixtures/offer.js +28 -0
  38. package/dist/src/stories/utils/story-env.d.ts +11 -0
  39. package/dist/src/stories/utils/story-env.js +148 -0
  40. package/dist/src/types/html-template.d.js +0 -0
  41. package/dist/src/types/offer.types.d.ts +73 -0
  42. package/dist/src/types/offer.types.js +0 -0
  43. package/dist/src/utils/billing-countries.d.ts +19 -0
  44. package/dist/src/utils/billing-countries.js +263 -0
  45. package/dist/src/utils/define.d.ts +1 -0
  46. package/dist/src/utils/define.js +9 -0
  47. package/dist/src/utils/expressCheckoutRedirect.d.ts +55 -0
  48. package/dist/src/utils/expressCheckoutRedirect.js +46 -0
  49. package/dist/src/utils/expressCheckoutTracking.d.ts +64 -0
  50. package/dist/src/utils/expressCheckoutTracking.js +84 -0
  51. package/dist/src/utils/pricing.d.ts +1 -0
  52. package/dist/src/utils/pricing.js +60 -0
  53. package/dist/src/utils/recaptcha.d.ts +22 -0
  54. package/dist/src/utils/recaptcha.js +54 -0
  55. package/package.json +64 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ApplePaySession lifecycle manager.
3
+ * Creates the native Apple Pay payment sheet and wires merchant validation,
4
+ * payment authorization, and cancellation handlers.
5
+ */
6
+ export type ApplePaySessionConfig = {
7
+ countryCode: string;
8
+ currencyCode: string;
9
+ supportedNetworks: string[];
10
+ totalAmount: string;
11
+ totalLabel: string;
12
+ skuId: string;
13
+ returnUrl: string;
14
+ userLoggedIn: boolean;
15
+ onPaymentAuthorized: (event: ApplePayJS.ApplePayPaymentAuthorizedEvent, session: ApplePaySession) => Promise<void>;
16
+ onCancel?: () => void;
17
+ };
18
+ export declare function startApplePaySession(config: ApplePaySessionConfig): void;
@@ -0,0 +1,243 @@
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
+ var validateAppleMerchant = (data) => {
58
+ return apiFetch("/v1/wallet/validate-merchant", {
59
+ method: "POST",
60
+ body: JSON.stringify(data)
61
+ });
62
+ };
63
+
64
+ // src/utils/expressCheckoutRedirect.ts
65
+ var redirectToCheckoutFallback = (params) => {
66
+ const url = new URL(window.location.href);
67
+ url.pathname = "/checkout";
68
+ url.searchParams.set("lmo_offer", params.skuId);
69
+ if (params.inlineError)
70
+ url.searchParams.set("inlineError", params.inlineError);
71
+ if (params.email) url.searchParams.set("email", params.email);
72
+ if (params.emailError) url.searchParams.set("emailError", params.emailError);
73
+ url.searchParams.set("returnUrl", params.returnUrl);
74
+ return url.pathname + url.search;
75
+ };
76
+
77
+ // src/utils/recaptcha.ts
78
+ var loadRecaptchaScript = (siteKey) => {
79
+ return new Promise((resolve, reject) => {
80
+ if (typeof grecaptcha !== "undefined") return resolve();
81
+ const existingScript = document.querySelector(
82
+ 'script[src*="recaptcha/api.js"]'
83
+ );
84
+ if (existingScript) {
85
+ existingScript.addEventListener("load", () => resolve());
86
+ existingScript.addEventListener(
87
+ "error",
88
+ () => reject(new Error("Failed to load existing reCAPTCHA script"))
89
+ );
90
+ return;
91
+ }
92
+ const script = document.createElement("script");
93
+ script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
94
+ script.async = true;
95
+ script.defer = true;
96
+ script.onload = () => resolve();
97
+ script.onerror = () => reject(new Error("Failed to load reCAPTCHA script"));
98
+ document.head.appendChild(script);
99
+ });
100
+ };
101
+ var getRecaptchaToken = async (action) => {
102
+ const siteKey = getApplePayConfig()?.recaptchaSiteKey;
103
+ if (!siteKey) {
104
+ throw new Error(
105
+ "[web-apple-pay] Missing recaptchaSiteKey. Call configureApplePay({ recaptchaSiteKey, ... }) before initiating Apple Pay."
106
+ );
107
+ }
108
+ await loadRecaptchaScript(siteKey);
109
+ return new Promise((resolve, reject) => {
110
+ grecaptcha.ready(() => {
111
+ grecaptcha.execute(siteKey, { action }).then(resolve).catch(reject);
112
+ });
113
+ });
114
+ };
115
+
116
+ // src/utils/expressCheckoutTracking.ts
117
+ var EXPRESS_CHECKOUT_EVENTS = {
118
+ /** Wallet button displayed */
119
+ IMPRESSION: "express_wallet_impression",
120
+ /** Wallet button clicked */
121
+ BUTTON_CLICK: "express_wallet_click",
122
+ /** Apple Pay sheet opened */
123
+ SHEET_OPEN: "wallet_payment_sheet_open",
124
+ /** Payment authorised by the user in the Apple Pay sheet */
125
+ AUTHORISED: "wallet_payment_authorised",
126
+ /** Purchase completed — BFF returned success */
127
+ SUCCESS: "wallet_payment_success",
128
+ /** Payment failed — BFF returned error */
129
+ FAILURE: "wallet_payment_failure",
130
+ /** Redirect to standard checkout (any fallback path) */
131
+ FALLBACK_CHECKOUT: "fallback_checkout"
132
+ };
133
+ var emitTedlEvent = (event) => {
134
+ window.tedl = window.tedl ?? {};
135
+ window.tedl.events = window.tedl.events ?? [];
136
+ window.tedl.events.push(event);
137
+ };
138
+ var track = (action, extra) => {
139
+ emitTedlEvent({
140
+ name: "user.journey",
141
+ type: "checkout",
142
+ action,
143
+ info: "applepay",
144
+ ...extra
145
+ });
146
+ };
147
+ var trackWalletPaymentSheetOpen = (skuId) => {
148
+ track(EXPRESS_CHECKOUT_EVENTS.SHEET_OPEN, { sku_id: skuId });
149
+ };
150
+ var trackWalletPaymentAuthorised = (skuId) => {
151
+ track(EXPRESS_CHECKOUT_EVENTS.AUTHORISED, { sku_id: skuId });
152
+ };
153
+ var trackFallbackCheckout = (skuId) => {
154
+ track(EXPRESS_CHECKOUT_EVENTS.FALLBACK_CHECKOUT, { sku_id: skuId });
155
+ };
156
+
157
+ // src/clients/payment-checkout/messages.ts
158
+ var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
159
+
160
+ // src/clients/payment-checkout/apple-session.ts
161
+ function startApplePaySession(config) {
162
+ console.log("[web-apple-pay] startApplePaySession", {
163
+ skuId: config.skuId,
164
+ countryCode: config.countryCode,
165
+ currencyCode: config.currencyCode,
166
+ totalAmount: config.totalAmount,
167
+ userLoggedIn: config.userLoggedIn
168
+ });
169
+ const paymentRequest = {
170
+ countryCode: config.countryCode,
171
+ currencyCode: config.currencyCode,
172
+ merchantCapabilities: ["supports3DS"],
173
+ supportedNetworks: config.supportedNetworks,
174
+ total: {
175
+ label: config.totalLabel,
176
+ amount: config.totalAmount
177
+ },
178
+ requiredBillingContactFields: ["postalAddress", "name"],
179
+ // Only request email from Apple Pay sheet if user is NOT logged in
180
+ ...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
181
+ };
182
+ const session = new ApplePaySession(3, paymentRequest);
183
+ session.onvalidatemerchant = async (event) => {
184
+ console.log("[web-apple-pay] onvalidatemerchant: received", { validationURL: event.validationURL });
185
+ try {
186
+ const validationHost = new URL(event.validationURL).host;
187
+ if (!validationHost.endsWith("apple.com")) {
188
+ console.warn("[web-apple-pay] onvalidatemerchant: invalid host, aborting", { validationHost });
189
+ trackFallbackCheckout(config.skuId);
190
+ session.abort();
191
+ window.location.href = redirectToCheckoutFallback({
192
+ skuId: config.skuId,
193
+ returnUrl: config.returnUrl,
194
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
195
+ });
196
+ return;
197
+ }
198
+ const recaptchaToken = await getRecaptchaToken("verifyMerchant");
199
+ console.log("[web-apple-pay] onvalidatemerchant: calling validateAppleMerchant");
200
+ const response = await validateAppleMerchant({
201
+ validationURL: event.validationURL,
202
+ recaptchaToken
203
+ });
204
+ if (!response?.merchantSession) {
205
+ console.warn("[web-apple-pay] onvalidatemerchant: no merchantSession in response, aborting");
206
+ trackFallbackCheckout(config.skuId);
207
+ session.abort();
208
+ window.location.href = redirectToCheckoutFallback({
209
+ skuId: config.skuId,
210
+ returnUrl: config.returnUrl,
211
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
212
+ });
213
+ return;
214
+ }
215
+ console.log("[web-apple-pay] onvalidatemerchant: merchant validated successfully");
216
+ session.completeMerchantValidation(response.merchantSession);
217
+ } catch (err) {
218
+ console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
219
+ trackFallbackCheckout(config.skuId);
220
+ session.abort();
221
+ window.location.href = redirectToCheckoutFallback({
222
+ skuId: config.skuId,
223
+ returnUrl: config.returnUrl,
224
+ inlineError: MSG_PAYMENT_GENERIC_ERROR
225
+ });
226
+ }
227
+ };
228
+ session.onpaymentauthorized = (event) => {
229
+ console.log("[web-apple-pay] onpaymentauthorized: payment authorized by user", { skuId: config.skuId });
230
+ trackWalletPaymentAuthorised(config.skuId);
231
+ config.onPaymentAuthorized(event, session);
232
+ };
233
+ session.oncancel = () => {
234
+ console.log("[web-apple-pay] oncancel: payment sheet cancelled by user", { skuId: config.skuId });
235
+ config.onCancel?.();
236
+ };
237
+ console.log("[web-apple-pay] startApplePaySession: beginning session");
238
+ trackWalletPaymentSheetOpen(config.skuId);
239
+ session.begin();
240
+ }
241
+ export {
242
+ startApplePaySession
243
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Apple Pay billing contact local validator.
3
+ *
4
+ * Validates required billing fields BEFORE sending to the BFF /v1/wallet/purchase.
5
+ * On validation failure, throws ContactValidationError with ApplePayError[] objects
6
+ * that Apple uses to highlight problematic billing fields inline — keeping the sheet open.
7
+ */
8
+ export type BillingValidationResult = {
9
+ firstName: string;
10
+ lastName: string;
11
+ };
12
+ /**
13
+ * Validates the billing contact from the Apple Pay wallet.
14
+ *
15
+ * @param contact - Billing address from the Apple Pay wallet
16
+ * @param supportedBillingCountries - Country codes the offer supports billing to
17
+ * @throws {ContactValidationError} when required fields are missing — Apple Pay
18
+ * sheet stays open and the first error text is shown to the user as a summary
19
+ * @returns firstName and lastName extracted from the contact
20
+ */
21
+ export declare const billingContactDetails: (contact: ApplePayJS.ApplePayPaymentContact, supportedBillingCountries: string[]) => BillingValidationResult;
@@ -0,0 +1,109 @@
1
+ // src/utils/expressCheckoutTracking.ts
2
+ var emitTedlEvent = (event) => {
3
+ window.tedl = window.tedl ?? {};
4
+ window.tedl.events = window.tedl.events ?? [];
5
+ window.tedl.events.push(event);
6
+ };
7
+ var track = (action, extra) => {
8
+ emitTedlEvent({
9
+ name: "user.journey",
10
+ type: "checkout",
11
+ action,
12
+ info: "applepay",
13
+ ...extra
14
+ });
15
+ };
16
+ var trackValidationFieldError = (field, errorType) => {
17
+ track(`validation_${field}_${errorType}`, {});
18
+ };
19
+
20
+ // src/clients/payment-checkout/messages.ts
21
+ var MSG_MISSING_REQUIRED_FIELD = "Missing required field";
22
+ var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
23
+ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
24
+
25
+ // src/clients/payment-checkout/contact-validation.ts
26
+ var pushFieldError = (errors, billingOrShippingContact = "billing", field, errorType, errorText) => {
27
+ const errorCode = billingOrShippingContact === "billing" ? "billingContactInvalid" : "shippingContactInvalid";
28
+ errors.push(new ApplePayError(errorCode, field, errorText));
29
+ trackValidationFieldError(field, errorType);
30
+ return "";
31
+ };
32
+ var pushRequiredFieldError = (errors, billingOrShippingContact = "billing", field) => {
33
+ return pushFieldError(
34
+ errors,
35
+ billingOrShippingContact,
36
+ field,
37
+ "required",
38
+ MSG_MISSING_REQUIRED_FIELD
39
+ );
40
+ };
41
+ var ContactValidationError = class extends Error {
42
+ constructor(errors) {
43
+ super(MSG_CONTACT_VALIDATION_INTERNAL);
44
+ this.errors = errors;
45
+ }
46
+ };
47
+
48
+ // src/clients/payment-checkout/billing-contact-details.ts
49
+ var countriesWithoutCityField = ["KY", "CK", "NR", "TC"];
50
+ var countriesRequiringAllFields = ["US", "CA", "GB"];
51
+ var resolveCountryConfig = (country) => {
52
+ const hasCityField = !countriesWithoutCityField.includes(country);
53
+ const requiresStateAndPostcode = countriesRequiringAllFields.includes(country);
54
+ return {
55
+ hasCityField,
56
+ requiresStateAndPostcode,
57
+ // if there is no city field, we require state and use it in place of city
58
+ requiresStateField: !hasCityField || requiresStateAndPostcode
59
+ };
60
+ };
61
+ var validateAddressFields = (errors, contact, { hasCityField, requiresStateAndPostcode, requiresStateField }) => {
62
+ const locality = !hasCityField ? contact.administrativeArea : contact.locality;
63
+ if (!contact.addressLines?.at(0)?.trim()) {
64
+ pushRequiredFieldError(errors, "billing", "addressLines");
65
+ }
66
+ if (!locality?.trim() && hasCityField) {
67
+ pushRequiredFieldError(errors, "billing", "locality");
68
+ }
69
+ if (!contact.administrativeArea?.trim() && requiresStateField) {
70
+ pushRequiredFieldError(errors, "billing", "administrativeArea");
71
+ }
72
+ if (!contact.postalCode?.trim() && requiresStateAndPostcode) {
73
+ pushRequiredFieldError(errors, "billing", "postalCode");
74
+ }
75
+ };
76
+ var validateNameFields = (errors, contact) => {
77
+ const firstName = contact.givenName?.trim() || "";
78
+ const lastName = contact.familyName?.trim() || "";
79
+ if (!firstName || !lastName) {
80
+ pushRequiredFieldError(errors, "billing", "name");
81
+ }
82
+ return { firstName, lastName };
83
+ };
84
+ var validateCountrySupport = (errors, country, supportedBillingCountries) => {
85
+ if (!supportedBillingCountries.includes(country)) {
86
+ pushFieldError(
87
+ errors,
88
+ "billing",
89
+ "countryCode",
90
+ "not-supported",
91
+ MSG_UNSUPPORTED_BILLING_COUNTRY
92
+ );
93
+ }
94
+ };
95
+ var billingContactDetails = (contact, supportedBillingCountries) => {
96
+ const errors = [];
97
+ const country = contact.countryCode?.toUpperCase() || pushRequiredFieldError(errors, "billing", "country");
98
+ const config = resolveCountryConfig(country);
99
+ validateAddressFields(errors, contact, config);
100
+ const { firstName, lastName } = validateNameFields(errors, contact);
101
+ validateCountrySupport(errors, country, supportedBillingCountries);
102
+ if (errors.length > 0) {
103
+ throw new ContactValidationError(errors);
104
+ }
105
+ return { firstName, lastName };
106
+ };
107
+ export {
108
+ billingContactDetails
109
+ };
@@ -0,0 +1,9 @@
1
+ export interface ApplePayRuntimeConfig {
2
+ merchantId: string;
3
+ recaptchaSiteKey?: string;
4
+ debugBypassEnabled?: boolean;
5
+ walletApiBaseUrl: string;
6
+ }
7
+ export declare const configureApplePay: (config: ApplePayRuntimeConfig) => void;
8
+ export declare const getApplePayConfig: () => ApplePayRuntimeConfig | null;
9
+ export declare const resetApplePayConfig: () => void;
@@ -0,0 +1,23 @@
1
+ // src/clients/payment-checkout/config.ts
2
+ var runtimeConfig = null;
3
+ var configureApplePay = (config) => {
4
+ console.log("[web-apple-pay] configureApplePay called", {
5
+ merchantId: config.merchantId,
6
+ walletApiBaseUrl: config.walletApiBaseUrl,
7
+ debugBypassEnabled: config.debugBypassEnabled,
8
+ recaptchaSiteKey: config.recaptchaSiteKey ? "***" : void 0
9
+ });
10
+ runtimeConfig = {
11
+ ...config
12
+ };
13
+ };
14
+ var getApplePayConfig = () => runtimeConfig;
15
+ var resetApplePayConfig = () => {
16
+ console.log("[web-apple-pay] resetApplePayConfig called");
17
+ runtimeConfig = null;
18
+ };
19
+ export {
20
+ configureApplePay,
21
+ getApplePayConfig,
22
+ resetApplePayConfig
23
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Apple Pay contact validation helpers.
3
+ * Ported from sma-checkout — helpers/contact-validation.ts
4
+ *
5
+ * pushFieldError / pushRequiredFieldError also fire TEDL analytics per field.
6
+ */
7
+ import ApplePayErrorContactField = ApplePayJS.ApplePayErrorContactField;
8
+ /**
9
+ * Appends an ApplePayError to the error accumulator and fires a TEDL analytics
10
+ * event for the failing field. Returns '' so it can be used inline with ||.
11
+ */
12
+ export declare const pushFieldError: (errors: ApplePayError[], billingOrShippingContact: "billing" | "shipping" | undefined, field: ApplePayErrorContactField, errorType: "required" | "format" | "not-supported", errorText: string) => string;
13
+ /**
14
+ * Convenience wrapper for required-field errors.
15
+ * Uses the standard 'Missing required field' message — matches sma-checkout exactly.
16
+ */
17
+ export declare const pushRequiredFieldError: (errors: ApplePayError[], billingOrShippingContact: "billing" | "shipping" | undefined, field: ApplePayErrorContactField) => string;
18
+ export declare class ContactValidationError extends Error {
19
+ errors: ApplePayError[];
20
+ constructor(errors: ApplePayError[]);
21
+ }
@@ -0,0 +1,50 @@
1
+ // src/utils/expressCheckoutTracking.ts
2
+ var emitTedlEvent = (event) => {
3
+ window.tedl = window.tedl ?? {};
4
+ window.tedl.events = window.tedl.events ?? [];
5
+ window.tedl.events.push(event);
6
+ };
7
+ var track = (action, extra) => {
8
+ emitTedlEvent({
9
+ name: "user.journey",
10
+ type: "checkout",
11
+ action,
12
+ info: "applepay",
13
+ ...extra
14
+ });
15
+ };
16
+ var trackValidationFieldError = (field, errorType) => {
17
+ track(`validation_${field}_${errorType}`, {});
18
+ };
19
+
20
+ // src/clients/payment-checkout/messages.ts
21
+ var MSG_MISSING_REQUIRED_FIELD = "Missing required field";
22
+ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
23
+
24
+ // src/clients/payment-checkout/contact-validation.ts
25
+ var pushFieldError = (errors, billingOrShippingContact = "billing", field, errorType, errorText) => {
26
+ const errorCode = billingOrShippingContact === "billing" ? "billingContactInvalid" : "shippingContactInvalid";
27
+ errors.push(new ApplePayError(errorCode, field, errorText));
28
+ trackValidationFieldError(field, errorType);
29
+ return "";
30
+ };
31
+ var pushRequiredFieldError = (errors, billingOrShippingContact = "billing", field) => {
32
+ return pushFieldError(
33
+ errors,
34
+ billingOrShippingContact,
35
+ field,
36
+ "required",
37
+ MSG_MISSING_REQUIRED_FIELD
38
+ );
39
+ };
40
+ var ContactValidationError = class extends Error {
41
+ constructor(errors) {
42
+ super(MSG_CONTACT_VALIDATION_INTERNAL);
43
+ this.errors = errors;
44
+ }
45
+ };
46
+ export {
47
+ ContactValidationError,
48
+ pushFieldError,
49
+ pushRequiredFieldError
50
+ };
@@ -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,56 @@
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
+ console.log("[web-apple-pay] isApplePayAvailable: checking availability");
14
+ if (bypassApplePayChecks()) {
15
+ console.log("[web-apple-pay] isApplePayAvailable: bypass active \u2192 true");
16
+ return true;
17
+ }
18
+ if (!isAppleDeviceOrSafari()) {
19
+ console.log("[web-apple-pay] isApplePayAvailable: not Apple device or Safari \u2192 false");
20
+ return false;
21
+ }
22
+ const applePaySession = window.ApplePaySession;
23
+ if (!applePaySession) {
24
+ console.log("[web-apple-pay] isApplePayAvailable: ApplePaySession not found on window \u2192 false");
25
+ return false;
26
+ }
27
+ try {
28
+ if (typeof applePaySession.canMakePayments !== "function") {
29
+ console.log("[web-apple-pay] isApplePayAvailable: canMakePayments is not a function \u2192 false");
30
+ return false;
31
+ }
32
+ if (!applePaySession.canMakePayments()) {
33
+ console.log("[web-apple-pay] isApplePayAvailable: canMakePayments() returned false \u2192 false");
34
+ return false;
35
+ }
36
+ const merchantId = getApplePayConfig()?.merchantId;
37
+ if (!merchantId) {
38
+ console.warn(
39
+ "[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
40
+ );
41
+ return false;
42
+ }
43
+ console.log("[web-apple-pay] isApplePayAvailable : calling canMakePaymentsWithActiveCard", { merchantId });
44
+ const result = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
45
+ console.log("[web-apple-pay] isApplePayAvailable: canMakePaymentsWithActiveCard \u2192", result);
46
+ return result;
47
+ } catch (error) {
48
+ console.error("Error checking Apple Pay active card status:", error);
49
+ return false;
50
+ }
51
+ };
52
+ export {
53
+ bypassApplePayChecks,
54
+ isAppleDeviceOrSafari,
55
+ isApplePayAvailable
56
+ };
@@ -0,0 +1,5 @@
1
+ import type { PaymentOptionsRequest, PaymentOptionsResponse, PurchaseRequest, PurchaseResponse, ValidateMerchantRequest, ValidateMerchantResponse } from './types';
2
+ export declare const apiFetch: <T>(path: string, options?: RequestInit) => Promise<T>;
3
+ export declare const getPaymentOptions: (data: PaymentOptionsRequest) => Promise<PaymentOptionsResponse>;
4
+ export declare const validateAppleMerchant: (data: ValidateMerchantRequest) => Promise<ValidateMerchantResponse>;
5
+ export declare const performPurchase: (data: PurchaseRequest) => Promise<PurchaseResponse>;
@@ -0,0 +1,83 @@
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
+ var getPaymentOptions = (data) => {
58
+ const { sku, countryCode, currencyCode, checkoutUrl } = data;
59
+ const params = new URLSearchParams({ checkoutUrl });
60
+ return apiFetch(
61
+ `/v1/wallet/payment-options/sku/${sku}/${countryCode}/${currencyCode}?${params}`,
62
+ { method: "GET" }
63
+ );
64
+ };
65
+ var validateAppleMerchant = (data) => {
66
+ return apiFetch("/v1/wallet/validate-merchant", {
67
+ method: "POST",
68
+ body: JSON.stringify(data)
69
+ });
70
+ };
71
+ var performPurchase = (data) => {
72
+ return apiFetch("/v1/wallet/purchase", {
73
+ method: "POST",
74
+ body: JSON.stringify(data),
75
+ credentials: "include"
76
+ });
77
+ };
78
+ export {
79
+ apiFetch,
80
+ getPaymentOptions,
81
+ performPurchase,
82
+ validateAppleMerchant
83
+ };