@economist/web-apple-pay 0.1.3-beta.2 → 0.1.3-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +633 -191
- package/dist/src/apple-pay-button.js +628 -180
- package/dist/src/apple-pay-button.stories.js +654 -195
- package/dist/src/apple-pay-modal.d.ts +16 -1
- package/dist/src/apple-pay-modal.js +556 -10
- package/dist/src/apple-pay-modal.stories.js +628 -180
- package/dist/src/clients/payment-checkout/apple-session.js +34 -59
- package/dist/src/clients/payment-checkout/config.js +0 -7
- package/dist/src/clients/payment-checkout/detects.js +2 -10
- package/dist/src/clients/payment-checkout/index.js +7 -0
- package/dist/src/clients/payment-checkout/payment-handler.d.ts +1 -0
- package/dist/src/clients/payment-checkout/payment-handler.js +36 -39
- package/dist/src/clients/payment-checkout/session.js +7 -0
- package/dist/src/clients/payment-checkout/shipping-contact-details.js +0 -21
- package/dist/src/clients/payment-checkout/types.d.ts +1 -0
- package/dist/src/index.js +633 -191
- package/dist/src/stories/utils/story-env.js +18 -7
- package/dist/src/utils/expressCheckoutRedirect.d.ts +27 -6
- package/dist/src/utils/expressCheckoutRedirect.js +19 -4
- package/package.json +1 -1
|
@@ -43,11 +43,18 @@ var apiFetch = async (path, options = {}) => {
|
|
|
43
43
|
});
|
|
44
44
|
if (!response.ok) {
|
|
45
45
|
const contentType = response.headers.get("content-type") || "";
|
|
46
|
+
console.warn("[web-apple-pay] apiFetch: non-2xx response", {
|
|
47
|
+
path,
|
|
48
|
+
status: response.status,
|
|
49
|
+
contentType
|
|
50
|
+
});
|
|
46
51
|
if (contentType.includes("application/json")) {
|
|
47
52
|
const errorBody = await response.json();
|
|
53
|
+
console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
|
|
48
54
|
throw new ApiError(response.status, errorBody);
|
|
49
55
|
}
|
|
50
56
|
const errorText = await response.text();
|
|
57
|
+
console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
|
|
51
58
|
throw new Error(
|
|
52
59
|
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
53
60
|
);
|
|
@@ -62,15 +69,37 @@ var validateAppleMerchant = (data) => {
|
|
|
62
69
|
};
|
|
63
70
|
|
|
64
71
|
// src/utils/expressCheckoutRedirect.ts
|
|
72
|
+
var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
|
|
73
|
+
var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
|
|
74
|
+
var writeCheckoutRedirect = (originUrl) => {
|
|
75
|
+
if (originUrl.startsWith("/")) {
|
|
76
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const { hostname } = new URL(originUrl);
|
|
81
|
+
const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
|
|
82
|
+
if (isAllowed) {
|
|
83
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var decodeSku = (sku) => {
|
|
89
|
+
try {
|
|
90
|
+
return atob(sku.substring(sku.indexOf("-") + 1));
|
|
91
|
+
} catch {
|
|
92
|
+
return sku;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
65
95
|
var redirectToCheckoutFallback = (params) => {
|
|
66
96
|
const url = new URL(window.location.href);
|
|
67
97
|
url.pathname = "/checkout";
|
|
68
|
-
url.searchParams.set("lmo_offer", params.skuId);
|
|
98
|
+
url.searchParams.set("lmo_offer", decodeSku(params.skuId));
|
|
69
99
|
if (params.inlineError)
|
|
70
100
|
url.searchParams.set("inlineError", params.inlineError);
|
|
71
101
|
if (params.email) url.searchParams.set("email", params.email);
|
|
72
102
|
if (params.emailError) url.searchParams.set("emailError", params.emailError);
|
|
73
|
-
url.searchParams.set("returnUrl", params.returnUrl);
|
|
74
103
|
return url.pathname + url.search;
|
|
75
104
|
};
|
|
76
105
|
|
|
@@ -159,72 +188,39 @@ var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment
|
|
|
159
188
|
|
|
160
189
|
// src/clients/payment-checkout/apple-session.ts
|
|
161
190
|
function redirectToFallbackCheckout(config) {
|
|
162
|
-
|
|
163
|
-
skuId: config.skuId,
|
|
164
|
-
returnUrl: config.returnUrl,
|
|
165
|
-
inlineError: MSG_PAYMENT_GENERIC_ERROR
|
|
166
|
-
});
|
|
191
|
+
writeCheckoutRedirect(config.returnUrl);
|
|
167
192
|
trackFallbackCheckout(config.skuId);
|
|
168
193
|
window.location.href = redirectToCheckoutFallback({
|
|
169
194
|
skuId: config.skuId,
|
|
170
|
-
returnUrl: config.returnUrl,
|
|
171
195
|
inlineError: MSG_PAYMENT_GENERIC_ERROR
|
|
172
196
|
});
|
|
173
197
|
}
|
|
174
198
|
async function handleMerchantValidation(event, session, config) {
|
|
175
|
-
console.log("[web-apple-pay] handleMerchantValidation: start", {
|
|
176
|
-
skuId: config.skuId,
|
|
177
|
-
validationURL: event.validationURL
|
|
178
|
-
});
|
|
179
199
|
try {
|
|
180
200
|
const validationHost = new URL(event.validationURL).host;
|
|
181
|
-
console.log("[web-apple-pay] handleMerchantValidation: host parsed", {
|
|
182
|
-
validationHost
|
|
183
|
-
});
|
|
184
201
|
if (!validationHost.endsWith("apple.com")) {
|
|
185
|
-
console.warn(
|
|
186
|
-
"[web-apple-pay] handleMerchantValidation: invalid validation host",
|
|
187
|
-
{ validationHost }
|
|
188
|
-
);
|
|
189
202
|
session.abort();
|
|
190
203
|
redirectToFallbackCheckout(config);
|
|
191
204
|
return;
|
|
192
205
|
}
|
|
193
|
-
console.log("[web-apple-pay] handleMerchantValidation: requesting recaptcha token");
|
|
194
206
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
195
|
-
console.log("[web-apple-pay] handleMerchantValidation: recaptcha token received");
|
|
196
207
|
const response = await validateAppleMerchant({
|
|
197
208
|
validationURL: event.validationURL,
|
|
198
209
|
recaptchaToken
|
|
199
210
|
});
|
|
200
211
|
if (!response?.merchantSession) {
|
|
201
|
-
console.warn(
|
|
202
|
-
"[web-apple-pay] handleMerchantValidation: missing merchantSession in response"
|
|
203
|
-
);
|
|
204
212
|
session.abort();
|
|
205
213
|
redirectToFallbackCheckout(config);
|
|
206
214
|
return;
|
|
207
215
|
}
|
|
208
|
-
console.log("[web-apple-pay] handleMerchantValidation: completing merchant validation");
|
|
209
216
|
session.completeMerchantValidation(response.merchantSession);
|
|
210
|
-
console.log("[web-apple-pay] handleMerchantValidation: success");
|
|
211
217
|
} catch (err) {
|
|
212
|
-
console.error("[web-apple-pay]
|
|
213
|
-
skuId: config.skuId,
|
|
214
|
-
err
|
|
215
|
-
});
|
|
218
|
+
console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
|
|
216
219
|
session.abort();
|
|
217
220
|
redirectToFallbackCheckout(config);
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
223
|
function startApplePaySession(config) {
|
|
221
|
-
console.log("[web-apple-pay] startApplePaySession", {
|
|
222
|
-
skuId: config.skuId,
|
|
223
|
-
countryCode: config.countryCode,
|
|
224
|
-
currencyCode: config.currencyCode,
|
|
225
|
-
totalAmount: config.totalAmount,
|
|
226
|
-
userLoggedIn: config.userLoggedIn
|
|
227
|
-
});
|
|
228
224
|
const paymentRequest = {
|
|
229
225
|
countryCode: config.countryCode,
|
|
230
226
|
currencyCode: config.currencyCode,
|
|
@@ -238,43 +234,22 @@ function startApplePaySession(config) {
|
|
|
238
234
|
// Only request email from Apple Pay sheet if user is NOT logged in
|
|
239
235
|
...!config.userLoggedIn && { requiredShippingContactFields: ["email"] }
|
|
240
236
|
};
|
|
241
|
-
console.log("[web-apple-pay] startApplePaySession: payment request created", {
|
|
242
|
-
skuId: config.skuId,
|
|
243
|
-
requiredShippingContactFields: paymentRequest.requiredShippingContactFields
|
|
244
|
-
});
|
|
245
237
|
try {
|
|
246
238
|
const session = new ApplePaySession(3, paymentRequest);
|
|
247
|
-
console.log("[web-apple-pay] startApplePaySession: ApplePaySession created");
|
|
248
239
|
session.onvalidatemerchant = async (event) => {
|
|
249
|
-
console.log("[web-apple-pay] onvalidatemerchant: invoked", {
|
|
250
|
-
skuId: config.skuId
|
|
251
|
-
});
|
|
252
240
|
await handleMerchantValidation(event, session, config);
|
|
253
241
|
};
|
|
254
242
|
session.onpaymentauthorized = (event) => {
|
|
255
|
-
console.log("[web-apple-pay] onpaymentauthorized: invoked", {
|
|
256
|
-
skuId: config.skuId
|
|
257
|
-
});
|
|
258
243
|
trackWalletPaymentAuthorised(config.skuId);
|
|
259
244
|
config.onPaymentAuthorized(event, session);
|
|
260
245
|
};
|
|
261
246
|
session.oncancel = () => {
|
|
262
|
-
console.log("[web-apple-pay] oncancel: invoked", {
|
|
263
|
-
skuId: config.skuId
|
|
264
|
-
});
|
|
265
247
|
config.onCancel?.();
|
|
266
248
|
};
|
|
267
|
-
console.log("[web-apple-pay] startApplePaySession: opening payment sheet", {
|
|
268
|
-
skuId: config.skuId
|
|
269
|
-
});
|
|
270
249
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
271
250
|
session.begin();
|
|
272
|
-
console.log("[web-apple-pay] startApplePaySession: session begin called");
|
|
273
251
|
} catch (err) {
|
|
274
|
-
console.error("[web-apple-pay] startApplePaySession: failed to
|
|
275
|
-
skuId: config.skuId,
|
|
276
|
-
err
|
|
277
|
-
});
|
|
252
|
+
console.error("[web-apple-pay] startApplePaySession: failed to create session", err);
|
|
278
253
|
redirectToFallbackCheckout(config);
|
|
279
254
|
}
|
|
280
255
|
}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
// src/clients/payment-checkout/config.ts
|
|
2
2
|
var runtimeConfig = null;
|
|
3
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
4
|
runtimeConfig = {
|
|
11
5
|
...config
|
|
12
6
|
};
|
|
13
7
|
};
|
|
14
8
|
var getApplePayConfig = () => runtimeConfig;
|
|
15
9
|
var resetApplePayConfig = () => {
|
|
16
|
-
console.log("[web-apple-pay] resetApplePayConfig called");
|
|
17
10
|
runtimeConfig = null;
|
|
18
11
|
};
|
|
19
12
|
export {
|
|
@@ -10,27 +10,21 @@ var isAppleDeviceOrSafari = () => {
|
|
|
10
10
|
};
|
|
11
11
|
var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
|
|
12
12
|
var isApplePayAvailable = async () => {
|
|
13
|
-
console.log("[web-apple-pay] isApplePayAvailable: checking availability");
|
|
14
13
|
if (bypassApplePayChecks()) {
|
|
15
|
-
console.log("[web-apple-pay] isApplePayAvailable: bypass active \u2192 true");
|
|
16
14
|
return true;
|
|
17
15
|
}
|
|
18
16
|
if (!isAppleDeviceOrSafari()) {
|
|
19
|
-
console.log("[web-apple-pay] isApplePayAvailable: not Apple device or Safari \u2192 false");
|
|
20
17
|
return false;
|
|
21
18
|
}
|
|
22
19
|
const applePaySession = window.ApplePaySession;
|
|
23
20
|
if (!applePaySession) {
|
|
24
|
-
console.log("[web-apple-pay] isApplePayAvailable: ApplePaySession not found on window \u2192 false");
|
|
25
21
|
return false;
|
|
26
22
|
}
|
|
27
23
|
try {
|
|
28
24
|
if (typeof applePaySession.canMakePayments !== "function") {
|
|
29
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePayments is not a function \u2192 false");
|
|
30
25
|
return false;
|
|
31
26
|
}
|
|
32
27
|
if (!applePaySession.canMakePayments()) {
|
|
33
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePayments() returned false \u2192 false");
|
|
34
28
|
return false;
|
|
35
29
|
}
|
|
36
30
|
const merchantId = getApplePayConfig()?.merchantId;
|
|
@@ -40,10 +34,8 @@ var isApplePayAvailable = async () => {
|
|
|
40
34
|
);
|
|
41
35
|
return false;
|
|
42
36
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePaymentsWithActiveCard \u2192", result);
|
|
46
|
-
return result;
|
|
37
|
+
const hasActiveCard = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
38
|
+
return hasActiveCard;
|
|
47
39
|
} catch (error) {
|
|
48
40
|
console.error("Error checking Apple Pay active card status:", error);
|
|
49
41
|
return false;
|
|
@@ -43,11 +43,18 @@ var apiFetch = async (path, options = {}) => {
|
|
|
43
43
|
});
|
|
44
44
|
if (!response.ok) {
|
|
45
45
|
const contentType = response.headers.get("content-type") || "";
|
|
46
|
+
console.warn("[web-apple-pay] apiFetch: non-2xx response", {
|
|
47
|
+
path,
|
|
48
|
+
status: response.status,
|
|
49
|
+
contentType
|
|
50
|
+
});
|
|
46
51
|
if (contentType.includes("application/json")) {
|
|
47
52
|
const errorBody = await response.json();
|
|
53
|
+
console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
|
|
48
54
|
throw new ApiError(response.status, errorBody);
|
|
49
55
|
}
|
|
50
56
|
const errorText = await response.text();
|
|
57
|
+
console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
|
|
51
58
|
throw new Error(
|
|
52
59
|
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
53
60
|
);
|
|
@@ -43,11 +43,18 @@ var apiFetch = async (path, options = {}) => {
|
|
|
43
43
|
});
|
|
44
44
|
if (!response.ok) {
|
|
45
45
|
const contentType = response.headers.get("content-type") || "";
|
|
46
|
+
console.warn("[web-apple-pay] apiFetch: non-2xx response", {
|
|
47
|
+
path,
|
|
48
|
+
status: response.status,
|
|
49
|
+
contentType
|
|
50
|
+
});
|
|
46
51
|
if (contentType.includes("application/json")) {
|
|
47
52
|
const errorBody = await response.json();
|
|
53
|
+
console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
|
|
48
54
|
throw new ApiError(response.status, errorBody);
|
|
49
55
|
}
|
|
50
56
|
const errorText = await response.text();
|
|
57
|
+
console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
|
|
51
58
|
throw new Error(
|
|
52
59
|
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
53
60
|
);
|
|
@@ -208,25 +215,13 @@ var billingContactDetails = (contact, supportedBillingCountries) => {
|
|
|
208
215
|
// src/clients/payment-checkout/shipping-contact-details.ts
|
|
209
216
|
var EMAIL_FORMAT_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
210
217
|
var shippingContactDetails = (shippingContact) => {
|
|
211
|
-
console.log("[web-apple-pay] shippingContactDetails: start", {
|
|
212
|
-
hasShippingContact: Boolean(shippingContact),
|
|
213
|
-
hasEmailAddress: shippingContact?.emailAddress !== void 0
|
|
214
|
-
});
|
|
215
218
|
const errors = [];
|
|
216
219
|
let emailAddress = "";
|
|
217
220
|
if (shippingContact?.emailAddress === void 0 || shippingContact?.emailAddress === "") {
|
|
218
|
-
console.log(
|
|
219
|
-
"[web-apple-pay] shippingContactDetails: email missing or empty"
|
|
220
|
-
);
|
|
221
221
|
pushRequiredFieldError(errors, "shipping", "emailAddress");
|
|
222
222
|
} else {
|
|
223
|
-
console.log("[web-apple-pay] shippingContactDetails: email provided");
|
|
224
223
|
emailAddress = shippingContact.emailAddress.trim();
|
|
225
|
-
console.log("[web-apple-pay] shippingContactDetails: email trimmed", {
|
|
226
|
-
emailLength: emailAddress.length
|
|
227
|
-
});
|
|
228
224
|
if (emailAddress.length > 80) {
|
|
229
|
-
console.log("[web-apple-pay] shippingContactDetails: email too long");
|
|
230
225
|
pushFieldError(
|
|
231
226
|
errors,
|
|
232
227
|
"shipping",
|
|
@@ -235,9 +230,6 @@ var shippingContactDetails = (shippingContact) => {
|
|
|
235
230
|
MSG_EMAIL_TOO_LONG
|
|
236
231
|
);
|
|
237
232
|
} else if (!EMAIL_FORMAT_REGEX.test(emailAddress)) {
|
|
238
|
-
console.log(
|
|
239
|
-
"[web-apple-pay] shippingContactDetails: email format invalid"
|
|
240
|
-
);
|
|
241
233
|
pushFieldError(
|
|
242
234
|
errors,
|
|
243
235
|
"shipping",
|
|
@@ -245,21 +237,31 @@ var shippingContactDetails = (shippingContact) => {
|
|
|
245
237
|
"format",
|
|
246
238
|
MSG_EMAIL_WRONG_FORMAT
|
|
247
239
|
);
|
|
248
|
-
} else {
|
|
249
|
-
console.log("[web-apple-pay] shippingContactDetails: email valid");
|
|
250
240
|
}
|
|
251
241
|
}
|
|
252
242
|
if (errors.length > 0) {
|
|
253
|
-
console.log("[web-apple-pay] shippingContactDetails: throwing validation error", {
|
|
254
|
-
errorCount: errors.length
|
|
255
|
-
});
|
|
256
243
|
throw new ContactValidationError(errors);
|
|
257
244
|
}
|
|
258
|
-
console.log("[web-apple-pay] shippingContactDetails: success");
|
|
259
245
|
return { emailAddress };
|
|
260
246
|
};
|
|
261
247
|
|
|
262
248
|
// src/utils/expressCheckoutRedirect.ts
|
|
249
|
+
var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
|
|
250
|
+
var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
|
|
251
|
+
var writeCheckoutRedirect = (originUrl) => {
|
|
252
|
+
if (originUrl.startsWith("/")) {
|
|
253
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const { hostname } = new URL(originUrl);
|
|
258
|
+
const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
|
|
259
|
+
if (isAllowed) {
|
|
260
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
};
|
|
263
265
|
var decodeSku = (sku) => {
|
|
264
266
|
try {
|
|
265
267
|
return atob(sku.substring(sku.indexOf("-") + 1));
|
|
@@ -277,18 +279,16 @@ var redirectToCheckoutComplete = (params) => {
|
|
|
277
279
|
url.searchParams.set("lmo_offer", params.skuId);
|
|
278
280
|
url.searchParams.set("status", params.status);
|
|
279
281
|
url.searchParams.set("redirectToCheckout", "true");
|
|
280
|
-
url.searchParams.set("returnUrl", params.returnUrl);
|
|
281
282
|
return url.pathname + url.search;
|
|
282
283
|
};
|
|
283
284
|
var redirectToCheckoutFallback = (params) => {
|
|
284
285
|
const url = new URL(window.location.href);
|
|
285
286
|
url.pathname = "/checkout";
|
|
286
|
-
url.searchParams.set("lmo_offer", params.skuId);
|
|
287
|
+
url.searchParams.set("lmo_offer", decodeSku(params.skuId));
|
|
287
288
|
if (params.inlineError)
|
|
288
289
|
url.searchParams.set("inlineError", params.inlineError);
|
|
289
290
|
if (params.email) url.searchParams.set("email", params.email);
|
|
290
291
|
if (params.emailError) url.searchParams.set("emailError", params.emailError);
|
|
291
|
-
url.searchParams.set("returnUrl", params.returnUrl);
|
|
292
292
|
return url.pathname + url.search;
|
|
293
293
|
};
|
|
294
294
|
|
|
@@ -303,9 +303,15 @@ var failApplePaySession = (session, errors = []) => {
|
|
|
303
303
|
};
|
|
304
304
|
function parseWalletApiError(err) {
|
|
305
305
|
const body = err instanceof ApiError ? err.body : err;
|
|
306
|
+
console.warn("[web-apple-pay] parseWalletApiError: raw error", {
|
|
307
|
+
isApiError: err instanceof ApiError,
|
|
308
|
+
body
|
|
309
|
+
});
|
|
306
310
|
if (body && typeof body === "object" && "errorType" in body && "message" in body) {
|
|
311
|
+
console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
|
|
307
312
|
return body;
|
|
308
313
|
}
|
|
314
|
+
console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
|
|
309
315
|
return null;
|
|
310
316
|
}
|
|
311
317
|
function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
@@ -319,10 +325,10 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
|
319
325
|
return void 0;
|
|
320
326
|
}
|
|
321
327
|
function redirectFallback(skuId, returnUrl, options) {
|
|
328
|
+
writeCheckoutRedirect(returnUrl);
|
|
322
329
|
trackFallbackCheckout(skuId);
|
|
323
330
|
window.location.href = redirectToCheckoutFallback({
|
|
324
331
|
skuId,
|
|
325
|
-
returnUrl,
|
|
326
332
|
...options
|
|
327
333
|
});
|
|
328
334
|
}
|
|
@@ -350,58 +356,49 @@ function handleStructuredError(ctx) {
|
|
|
350
356
|
async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
351
357
|
const { skuId, returnUrl, queryString } = config;
|
|
352
358
|
let validatedEmail;
|
|
353
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: starting", { skuId, userLoggedIn: sessionData.loggedIn });
|
|
354
359
|
try {
|
|
355
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 1 \u2014 validating contacts");
|
|
356
360
|
validatedEmail = validateContacts(
|
|
357
361
|
event,
|
|
358
362
|
sessionData,
|
|
359
363
|
config.supportedBillingCountries
|
|
360
364
|
);
|
|
361
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: contacts valid", { validatedEmail });
|
|
362
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 2 \u2014 calling performPurchase", { skuId });
|
|
363
365
|
const result = await performPurchase({
|
|
364
366
|
applePayToken: event.payment.token,
|
|
365
367
|
skuId: config.skuId,
|
|
368
|
+
currency: config.currency,
|
|
366
369
|
billingContact: event.payment.billingContact,
|
|
367
370
|
...event.payment.shippingContact && {
|
|
368
371
|
shippingContact: event.payment.shippingContact
|
|
369
372
|
},
|
|
370
373
|
recaptchaTokens: config.recaptchaTokens
|
|
371
374
|
});
|
|
372
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: performPurchase succeeded", { basketId: result.basketId });
|
|
373
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 3 \u2014 completing payment and redirecting");
|
|
374
375
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
375
376
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
376
377
|
const userStatus = sessionData.loggedIn ? "lex" : "nex";
|
|
378
|
+
writeCheckoutRedirect(returnUrl);
|
|
377
379
|
window.location.href = redirectToCheckoutComplete({
|
|
378
380
|
basketId: result.basketId,
|
|
379
381
|
status: userStatus,
|
|
380
382
|
skuId: decodeSku(skuId),
|
|
381
|
-
returnUrl,
|
|
382
383
|
queryString
|
|
383
384
|
});
|
|
384
385
|
} catch (err) {
|
|
385
386
|
if (err instanceof ContactValidationError) {
|
|
386
|
-
console.warn("[web-apple-pay] handlePaymentAuthorized: contact validation failed", err.errors);
|
|
387
387
|
failApplePaySession(session, err.errors);
|
|
388
388
|
return;
|
|
389
389
|
}
|
|
390
390
|
console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
|
|
391
391
|
const walletError = parseWalletApiError(err);
|
|
392
|
-
console.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
message: walletError?.message
|
|
392
|
+
console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
|
|
393
|
+
walletError,
|
|
394
|
+
validatedEmail
|
|
396
395
|
});
|
|
397
396
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
398
397
|
failApplePaySession(session);
|
|
399
398
|
if (!walletError) {
|
|
400
|
-
console.warn("[web-apple-pay] handlePaymentAuthorized: unstructured error, using generic fallback");
|
|
401
399
|
redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
402
400
|
return;
|
|
403
401
|
}
|
|
404
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: dispatching structured error handler", { errorType: walletError.errorType });
|
|
405
402
|
handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
|
|
406
403
|
}
|
|
407
404
|
}
|
|
@@ -43,11 +43,18 @@ var apiFetch = async (path, options = {}) => {
|
|
|
43
43
|
});
|
|
44
44
|
if (!response.ok) {
|
|
45
45
|
const contentType = response.headers.get("content-type") || "";
|
|
46
|
+
console.warn("[web-apple-pay] apiFetch: non-2xx response", {
|
|
47
|
+
path,
|
|
48
|
+
status: response.status,
|
|
49
|
+
contentType
|
|
50
|
+
});
|
|
46
51
|
if (contentType.includes("application/json")) {
|
|
47
52
|
const errorBody = await response.json();
|
|
53
|
+
console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
|
|
48
54
|
throw new ApiError(response.status, errorBody);
|
|
49
55
|
}
|
|
50
56
|
const errorText = await response.text();
|
|
57
|
+
console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
|
|
51
58
|
throw new Error(
|
|
52
59
|
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
53
60
|
);
|
|
@@ -49,25 +49,13 @@ var ContactValidationError = class extends Error {
|
|
|
49
49
|
// src/clients/payment-checkout/shipping-contact-details.ts
|
|
50
50
|
var EMAIL_FORMAT_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
51
51
|
var shippingContactDetails = (shippingContact) => {
|
|
52
|
-
console.log("[web-apple-pay] shippingContactDetails: start", {
|
|
53
|
-
hasShippingContact: Boolean(shippingContact),
|
|
54
|
-
hasEmailAddress: shippingContact?.emailAddress !== void 0
|
|
55
|
-
});
|
|
56
52
|
const errors = [];
|
|
57
53
|
let emailAddress = "";
|
|
58
54
|
if (shippingContact?.emailAddress === void 0 || shippingContact?.emailAddress === "") {
|
|
59
|
-
console.log(
|
|
60
|
-
"[web-apple-pay] shippingContactDetails: email missing or empty"
|
|
61
|
-
);
|
|
62
55
|
pushRequiredFieldError(errors, "shipping", "emailAddress");
|
|
63
56
|
} else {
|
|
64
|
-
console.log("[web-apple-pay] shippingContactDetails: email provided");
|
|
65
57
|
emailAddress = shippingContact.emailAddress.trim();
|
|
66
|
-
console.log("[web-apple-pay] shippingContactDetails: email trimmed", {
|
|
67
|
-
emailLength: emailAddress.length
|
|
68
|
-
});
|
|
69
58
|
if (emailAddress.length > 80) {
|
|
70
|
-
console.log("[web-apple-pay] shippingContactDetails: email too long");
|
|
71
59
|
pushFieldError(
|
|
72
60
|
errors,
|
|
73
61
|
"shipping",
|
|
@@ -76,9 +64,6 @@ var shippingContactDetails = (shippingContact) => {
|
|
|
76
64
|
MSG_EMAIL_TOO_LONG
|
|
77
65
|
);
|
|
78
66
|
} else if (!EMAIL_FORMAT_REGEX.test(emailAddress)) {
|
|
79
|
-
console.log(
|
|
80
|
-
"[web-apple-pay] shippingContactDetails: email format invalid"
|
|
81
|
-
);
|
|
82
67
|
pushFieldError(
|
|
83
68
|
errors,
|
|
84
69
|
"shipping",
|
|
@@ -86,17 +71,11 @@ var shippingContactDetails = (shippingContact) => {
|
|
|
86
71
|
"format",
|
|
87
72
|
MSG_EMAIL_WRONG_FORMAT
|
|
88
73
|
);
|
|
89
|
-
} else {
|
|
90
|
-
console.log("[web-apple-pay] shippingContactDetails: email valid");
|
|
91
74
|
}
|
|
92
75
|
}
|
|
93
76
|
if (errors.length > 0) {
|
|
94
|
-
console.log("[web-apple-pay] shippingContactDetails: throwing validation error", {
|
|
95
|
-
errorCount: errors.length
|
|
96
|
-
});
|
|
97
77
|
throw new ContactValidationError(errors);
|
|
98
78
|
}
|
|
99
|
-
console.log("[web-apple-pay] shippingContactDetails: success");
|
|
100
79
|
return { emailAddress };
|
|
101
80
|
};
|
|
102
81
|
export {
|
|
@@ -87,6 +87,7 @@ export type PurchaseRequest = {
|
|
|
87
87
|
* only provide a `skuId`. Frontend uses `skuId` directly — BFF must be aligned.
|
|
88
88
|
*/
|
|
89
89
|
skuId: string;
|
|
90
|
+
currency: string;
|
|
90
91
|
billingContact: ApplePayPaymentContact;
|
|
91
92
|
/** Required if loggedIn: false — contains emailAddress from Apple Pay sheet. */
|
|
92
93
|
shippingContact?: ApplePayPaymentContact;
|