@economist/web-apple-pay 0.1.3-beta.20 → 0.1.3-beta.22
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 +89 -27
- package/dist/src/apple-pay-button.js +89 -27
- package/dist/src/apple-pay-button.stories.js +107 -27
- package/dist/src/apple-pay-modal.js +25 -6
- package/dist/src/apple-pay-modal.stories.js +89 -27
- package/dist/src/clients/payment-checkout/apple-session.js +27 -14
- package/dist/src/clients/payment-checkout/index.js +7 -0
- package/dist/src/clients/payment-checkout/payment-handler.js +44 -18
- package/dist/src/clients/payment-checkout/session.js +7 -0
- package/dist/src/clients/payment-checkout/types.d.ts +1 -1
- package/dist/src/index.js +89 -27
- package/dist/src/stories/utils/story-env.js +18 -0
- package/dist/src/utils/expressCheckoutRedirect.d.ts +27 -6
- package/dist/src/utils/expressCheckoutRedirect.js +21 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -516,11 +516,13 @@ var apple_pay_modal_template_default = `<style>
|
|
|
516
516
|
</svg>
|
|
517
517
|
Please accept the terms to continue
|
|
518
518
|
</div>
|
|
519
|
-
<button class="apm__cta" id="apple-pay-modal-cta">
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
<
|
|
523
|
-
|
|
519
|
+
<button class="apm__cta" id="apple-pay-modal-cta" aria-label="Continue with Apple Pay">
|
|
520
|
+
<span class="apm__cta-content">
|
|
521
|
+
Continue with
|
|
522
|
+
<svg width="51" height="22" aria-hidden="true" role="img">
|
|
523
|
+
<use href="#apple-pay-logo" />
|
|
524
|
+
</svg>
|
|
525
|
+
</span>
|
|
524
526
|
</button>
|
|
525
527
|
</div>
|
|
526
528
|
</div>
|
|
@@ -633,6 +635,7 @@ function getOfferTypeText(offerType) {
|
|
|
633
635
|
}
|
|
634
636
|
var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
|
|
635
637
|
var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
|
|
638
|
+
var MSG_RECAPTCHA_LOAD_ERROR = "Something went wrong. Please close and try again.";
|
|
636
639
|
function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
|
|
637
640
|
const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
|
|
638
641
|
if (isNYT) {
|
|
@@ -924,6 +927,22 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
924
927
|
}
|
|
925
928
|
}).catch(() => {
|
|
926
929
|
console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
|
|
930
|
+
if (cta) {
|
|
931
|
+
cta.removeAttribute("aria-busy");
|
|
932
|
+
}
|
|
933
|
+
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
934
|
+
if (existingError) {
|
|
935
|
+
const icon = existingError.querySelector("svg");
|
|
936
|
+
existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
|
|
937
|
+
if (icon) existingError.prepend(icon);
|
|
938
|
+
existingError.classList.add("apm__error--visible");
|
|
939
|
+
} else {
|
|
940
|
+
const errorNotice = document.createElement("div");
|
|
941
|
+
errorNotice.setAttribute("role", "alert");
|
|
942
|
+
errorNotice.className = "apm__error apm__error--visible";
|
|
943
|
+
errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
|
|
944
|
+
cta?.insertAdjacentElement("beforebegin", errorNotice);
|
|
945
|
+
}
|
|
927
946
|
});
|
|
928
947
|
}
|
|
929
948
|
cta?.addEventListener("click", () => {
|
|
@@ -934,8 +953,8 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
934
953
|
}
|
|
935
954
|
if (this.#recaptchaTokens) {
|
|
936
955
|
this.onConfirm?.(this.#recaptchaTokens);
|
|
956
|
+
this.close();
|
|
937
957
|
}
|
|
938
|
-
this.close();
|
|
939
958
|
});
|
|
940
959
|
this.querySelector("#apple-pay-modal-close")?.addEventListener(
|
|
941
960
|
"click",
|
|
@@ -1137,11 +1156,18 @@ var apiFetch = async (path, options = {}) => {
|
|
|
1137
1156
|
});
|
|
1138
1157
|
if (!response.ok) {
|
|
1139
1158
|
const contentType = response.headers.get("content-type") || "";
|
|
1159
|
+
console.warn("[web-apple-pay] apiFetch: non-2xx response", {
|
|
1160
|
+
path,
|
|
1161
|
+
status: response.status,
|
|
1162
|
+
contentType
|
|
1163
|
+
});
|
|
1140
1164
|
if (contentType.includes("application/json")) {
|
|
1141
1165
|
const errorBody = await response.json();
|
|
1166
|
+
console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
|
|
1142
1167
|
throw new ApiError(response.status, errorBody);
|
|
1143
1168
|
}
|
|
1144
1169
|
const errorText = await response.text();
|
|
1170
|
+
console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
|
|
1145
1171
|
throw new Error(
|
|
1146
1172
|
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
1147
1173
|
);
|
|
@@ -1179,6 +1205,22 @@ var getExpressCheckoutSession = () => {
|
|
|
1179
1205
|
};
|
|
1180
1206
|
|
|
1181
1207
|
// src/utils/expressCheckoutRedirect.ts
|
|
1208
|
+
var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
|
|
1209
|
+
var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
|
|
1210
|
+
var writeCheckoutRedirect = (originUrl) => {
|
|
1211
|
+
if (originUrl.startsWith("/")) {
|
|
1212
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
try {
|
|
1216
|
+
const { hostname } = new URL(originUrl);
|
|
1217
|
+
const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
|
|
1218
|
+
if (isAllowed) {
|
|
1219
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
1220
|
+
}
|
|
1221
|
+
} catch {
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1182
1224
|
var captureOriginUrl = () => window.location.href;
|
|
1183
1225
|
var buildQueryString = () => {
|
|
1184
1226
|
const countryCode = new URLSearchParams(window.location.search).get(
|
|
@@ -1188,11 +1230,8 @@ var buildQueryString = () => {
|
|
|
1188
1230
|
};
|
|
1189
1231
|
var decodeSku = (sku) => {
|
|
1190
1232
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
return decoded;
|
|
1194
|
-
} catch (err) {
|
|
1195
|
-
console.warn("[web-apple-pay] decodeSku: failed to decode, returning raw", { input: sku, err });
|
|
1233
|
+
return atob(sku.substring(sku.indexOf("-") + 1));
|
|
1234
|
+
} catch {
|
|
1196
1235
|
return sku;
|
|
1197
1236
|
}
|
|
1198
1237
|
};
|
|
@@ -1206,24 +1245,16 @@ var redirectToCheckoutComplete = (params) => {
|
|
|
1206
1245
|
url.searchParams.set("lmo_offer", params.skuId);
|
|
1207
1246
|
url.searchParams.set("status", params.status);
|
|
1208
1247
|
url.searchParams.set("redirectToCheckout", "true");
|
|
1209
|
-
url.searchParams.set("returnUrl", params.returnUrl);
|
|
1210
1248
|
return url.pathname + url.search;
|
|
1211
1249
|
};
|
|
1212
1250
|
var redirectToCheckoutFallback = (params) => {
|
|
1213
|
-
const decodedOffer = decodeSku(params.skuId);
|
|
1214
|
-
console.log("[web-apple-pay] redirectToCheckoutFallback", {
|
|
1215
|
-
rawSkuId: params.skuId,
|
|
1216
|
-
decodedOffer
|
|
1217
|
-
});
|
|
1218
1251
|
const url = new URL(window.location.href);
|
|
1219
1252
|
url.pathname = "/checkout";
|
|
1220
|
-
url.searchParams.set("lmo_offer",
|
|
1253
|
+
url.searchParams.set("lmo_offer", decodeSku(params.skuId));
|
|
1221
1254
|
if (params.inlineError)
|
|
1222
1255
|
url.searchParams.set("inlineError", params.inlineError);
|
|
1223
1256
|
if (params.email) url.searchParams.set("email", params.email);
|
|
1224
1257
|
if (params.emailError) url.searchParams.set("emailError", params.emailError);
|
|
1225
|
-
url.searchParams.set("returnUrl", params.returnUrl);
|
|
1226
|
-
console.log("[web-apple-pay] fallback redirect URL", url.pathname + url.search);
|
|
1227
1258
|
return url.pathname + url.search;
|
|
1228
1259
|
};
|
|
1229
1260
|
|
|
@@ -1313,10 +1344,10 @@ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
|
|
|
1313
1344
|
|
|
1314
1345
|
// src/clients/payment-checkout/apple-session.ts
|
|
1315
1346
|
function redirectToFallbackCheckout(config) {
|
|
1347
|
+
writeCheckoutRedirect(config.returnUrl);
|
|
1316
1348
|
trackFallbackCheckout(config.skuId);
|
|
1317
1349
|
window.location.href = redirectToCheckoutFallback({
|
|
1318
1350
|
skuId: config.skuId,
|
|
1319
|
-
returnUrl: config.returnUrl,
|
|
1320
1351
|
inlineError: MSG_PAYMENT_GENERIC_ERROR
|
|
1321
1352
|
});
|
|
1322
1353
|
}
|
|
@@ -1506,9 +1537,15 @@ var failApplePaySession = (session, errors = []) => {
|
|
|
1506
1537
|
};
|
|
1507
1538
|
function parseWalletApiError(err) {
|
|
1508
1539
|
const body = err instanceof ApiError ? err.body : err;
|
|
1540
|
+
console.warn("[web-apple-pay] parseWalletApiError: raw error", {
|
|
1541
|
+
isApiError: err instanceof ApiError,
|
|
1542
|
+
body
|
|
1543
|
+
});
|
|
1509
1544
|
if (body && typeof body === "object" && "errorType" in body && "message" in body) {
|
|
1545
|
+
console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
|
|
1510
1546
|
return body;
|
|
1511
1547
|
}
|
|
1548
|
+
console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
|
|
1512
1549
|
return null;
|
|
1513
1550
|
}
|
|
1514
1551
|
function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
@@ -1522,11 +1559,10 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
|
1522
1559
|
return void 0;
|
|
1523
1560
|
}
|
|
1524
1561
|
function redirectFallback(skuId, returnUrl, options) {
|
|
1525
|
-
|
|
1562
|
+
writeCheckoutRedirect(returnUrl);
|
|
1526
1563
|
trackFallbackCheckout(skuId);
|
|
1527
1564
|
window.location.href = redirectToCheckoutFallback({
|
|
1528
1565
|
skuId,
|
|
1529
|
-
returnUrl,
|
|
1530
1566
|
...options
|
|
1531
1567
|
});
|
|
1532
1568
|
}
|
|
@@ -1541,7 +1577,12 @@ var ERROR_HANDLERS = {
|
|
|
1541
1577
|
),
|
|
1542
1578
|
ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
|
|
1543
1579
|
REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
|
|
1544
|
-
|
|
1580
|
+
ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
|
|
1581
|
+
emailError: MSG_REGISTRATION,
|
|
1582
|
+
email: validatedEmail
|
|
1583
|
+
}),
|
|
1584
|
+
VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED }),
|
|
1585
|
+
RECAPTCHA_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
|
|
1545
1586
|
};
|
|
1546
1587
|
function handleStructuredError(ctx) {
|
|
1547
1588
|
const handler = ERROR_HANDLERS[ctx.walletError.errorType];
|
|
@@ -1573,11 +1614,11 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1573
1614
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1574
1615
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1575
1616
|
const userStatus = sessionData.loggedIn ? "lex" : "nex";
|
|
1617
|
+
writeCheckoutRedirect(returnUrl);
|
|
1576
1618
|
window.location.href = redirectToCheckoutComplete({
|
|
1577
1619
|
basketId: result.basketId,
|
|
1578
1620
|
status: userStatus,
|
|
1579
1621
|
skuId: decodeSku(skuId),
|
|
1580
|
-
returnUrl,
|
|
1581
1622
|
queryString
|
|
1582
1623
|
});
|
|
1583
1624
|
} catch (err) {
|
|
@@ -1587,6 +1628,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1587
1628
|
}
|
|
1588
1629
|
console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
|
|
1589
1630
|
const walletError = parseWalletApiError(err);
|
|
1631
|
+
console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
|
|
1632
|
+
walletError,
|
|
1633
|
+
validatedEmail
|
|
1634
|
+
});
|
|
1590
1635
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1591
1636
|
failApplePaySession(session);
|
|
1592
1637
|
if (!walletError) {
|
|
@@ -2081,7 +2126,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2081
2126
|
if (button) {
|
|
2082
2127
|
button.classList.remove("wap-loading");
|
|
2083
2128
|
this.#updateDisabledState();
|
|
2084
|
-
this.#
|
|
2129
|
+
this.#scheduleRecaptchaTokens();
|
|
2085
2130
|
trackExpressWalletImpression(this.#offer.sku_id, {
|
|
2086
2131
|
entryPoint: this.#entryPoint,
|
|
2087
2132
|
country: this.#country
|
|
@@ -2120,10 +2165,27 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2120
2165
|
const country = this.#country;
|
|
2121
2166
|
modal.onConfirm = (recaptchaTokens) => {
|
|
2122
2167
|
this.#initiateApplePay(skuId, country, recaptchaTokens);
|
|
2123
|
-
this.#
|
|
2168
|
+
this.#scheduleRecaptchaTokens();
|
|
2124
2169
|
};
|
|
2125
2170
|
modal.open(this.#recaptchaTokensPromise);
|
|
2126
2171
|
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
|
|
2174
|
+
* If the fetch rejects, the field is cleared so the next `modal.open()` call
|
|
2175
|
+
* falls back to a new attempt via the modal's own null-fallback path — preventing
|
|
2176
|
+
* a single transient failure from permanently disabling the CTA on all retries.
|
|
2177
|
+
* The identity guard prevents a late rejection from clobbering a newer attempt.
|
|
2178
|
+
*/
|
|
2179
|
+
#scheduleRecaptchaTokens() {
|
|
2180
|
+
const attempt = getPurchaseRecaptchaTokens();
|
|
2181
|
+
attempt.catch((err) => {
|
|
2182
|
+
console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
|
|
2183
|
+
if (this.#recaptchaTokensPromise === attempt) {
|
|
2184
|
+
this.#recaptchaTokensPromise = null;
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
this.#recaptchaTokensPromise = attempt;
|
|
2188
|
+
}
|
|
2127
2189
|
async #fetchSession() {
|
|
2128
2190
|
try {
|
|
2129
2191
|
return await getExpressCheckoutSession();
|
|
@@ -516,11 +516,13 @@ var apple_pay_modal_template_default = `<style>
|
|
|
516
516
|
</svg>
|
|
517
517
|
Please accept the terms to continue
|
|
518
518
|
</div>
|
|
519
|
-
<button class="apm__cta" id="apple-pay-modal-cta">
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
<
|
|
523
|
-
|
|
519
|
+
<button class="apm__cta" id="apple-pay-modal-cta" aria-label="Continue with Apple Pay">
|
|
520
|
+
<span class="apm__cta-content">
|
|
521
|
+
Continue with
|
|
522
|
+
<svg width="51" height="22" aria-hidden="true" role="img">
|
|
523
|
+
<use href="#apple-pay-logo" />
|
|
524
|
+
</svg>
|
|
525
|
+
</span>
|
|
524
526
|
</button>
|
|
525
527
|
</div>
|
|
526
528
|
</div>
|
|
@@ -628,6 +630,7 @@ function getOfferTypeText(offerType) {
|
|
|
628
630
|
}
|
|
629
631
|
var TERMS_LINK = '<a href="https://www.economistgroup.com/terms-of-use" target="_blank" rel="noopener noreferrer">Terms and Conditions</a>';
|
|
630
632
|
var PRIVACY_LINK = '<a href="https://www.economistgroup.com/privacy-policy" target="_blank" rel="noopener noreferrer">Privacy Policy</a>';
|
|
633
|
+
var MSG_RECAPTCHA_LOAD_ERROR = "Something went wrong. Please close and try again.";
|
|
631
634
|
function getCheckboxTermsHtml(isNYT, isLoggedIn, autoRenewal) {
|
|
632
635
|
const autoRenewalClause = autoRenewal ? " and auto-renewal terms above," : "";
|
|
633
636
|
if (isNYT) {
|
|
@@ -919,6 +922,22 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
919
922
|
}
|
|
920
923
|
}).catch(() => {
|
|
921
924
|
console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
|
|
925
|
+
if (cta) {
|
|
926
|
+
cta.removeAttribute("aria-busy");
|
|
927
|
+
}
|
|
928
|
+
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
929
|
+
if (existingError) {
|
|
930
|
+
const icon = existingError.querySelector("svg");
|
|
931
|
+
existingError.textContent = MSG_RECAPTCHA_LOAD_ERROR;
|
|
932
|
+
if (icon) existingError.prepend(icon);
|
|
933
|
+
existingError.classList.add("apm__error--visible");
|
|
934
|
+
} else {
|
|
935
|
+
const errorNotice = document.createElement("div");
|
|
936
|
+
errorNotice.setAttribute("role", "alert");
|
|
937
|
+
errorNotice.className = "apm__error apm__error--visible";
|
|
938
|
+
errorNotice.textContent = MSG_RECAPTCHA_LOAD_ERROR;
|
|
939
|
+
cta?.insertAdjacentElement("beforebegin", errorNotice);
|
|
940
|
+
}
|
|
922
941
|
});
|
|
923
942
|
}
|
|
924
943
|
cta?.addEventListener("click", () => {
|
|
@@ -929,8 +948,8 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
929
948
|
}
|
|
930
949
|
if (this.#recaptchaTokens) {
|
|
931
950
|
this.onConfirm?.(this.#recaptchaTokens);
|
|
951
|
+
this.close();
|
|
932
952
|
}
|
|
933
|
-
this.close();
|
|
934
953
|
});
|
|
935
954
|
this.querySelector("#apple-pay-modal-close")?.addEventListener(
|
|
936
955
|
"click",
|
|
@@ -1132,11 +1151,18 @@ var apiFetch = async (path, options = {}) => {
|
|
|
1132
1151
|
});
|
|
1133
1152
|
if (!response.ok) {
|
|
1134
1153
|
const contentType = response.headers.get("content-type") || "";
|
|
1154
|
+
console.warn("[web-apple-pay] apiFetch: non-2xx response", {
|
|
1155
|
+
path,
|
|
1156
|
+
status: response.status,
|
|
1157
|
+
contentType
|
|
1158
|
+
});
|
|
1135
1159
|
if (contentType.includes("application/json")) {
|
|
1136
1160
|
const errorBody = await response.json();
|
|
1161
|
+
console.warn("[web-apple-pay] apiFetch: parsed JSON error body", { errorBody });
|
|
1137
1162
|
throw new ApiError(response.status, errorBody);
|
|
1138
1163
|
}
|
|
1139
1164
|
const errorText = await response.text();
|
|
1165
|
+
console.warn("[web-apple-pay] apiFetch: non-JSON error body", { errorText });
|
|
1140
1166
|
throw new Error(
|
|
1141
1167
|
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
|
|
1142
1168
|
);
|
|
@@ -1174,6 +1200,22 @@ var getExpressCheckoutSession = () => {
|
|
|
1174
1200
|
};
|
|
1175
1201
|
|
|
1176
1202
|
// src/utils/expressCheckoutRedirect.ts
|
|
1203
|
+
var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
|
|
1204
|
+
var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
|
|
1205
|
+
var writeCheckoutRedirect = (originUrl) => {
|
|
1206
|
+
if (originUrl.startsWith("/")) {
|
|
1207
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
try {
|
|
1211
|
+
const { hostname } = new URL(originUrl);
|
|
1212
|
+
const isAllowed = hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
|
|
1213
|
+
if (isAllowed) {
|
|
1214
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
1215
|
+
}
|
|
1216
|
+
} catch {
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1177
1219
|
var captureOriginUrl = () => window.location.href;
|
|
1178
1220
|
var buildQueryString = () => {
|
|
1179
1221
|
const countryCode = new URLSearchParams(window.location.search).get(
|
|
@@ -1183,11 +1225,8 @@ var buildQueryString = () => {
|
|
|
1183
1225
|
};
|
|
1184
1226
|
var decodeSku = (sku) => {
|
|
1185
1227
|
try {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
return decoded;
|
|
1189
|
-
} catch (err) {
|
|
1190
|
-
console.warn("[web-apple-pay] decodeSku: failed to decode, returning raw", { input: sku, err });
|
|
1228
|
+
return atob(sku.substring(sku.indexOf("-") + 1));
|
|
1229
|
+
} catch {
|
|
1191
1230
|
return sku;
|
|
1192
1231
|
}
|
|
1193
1232
|
};
|
|
@@ -1201,24 +1240,16 @@ var redirectToCheckoutComplete = (params) => {
|
|
|
1201
1240
|
url.searchParams.set("lmo_offer", params.skuId);
|
|
1202
1241
|
url.searchParams.set("status", params.status);
|
|
1203
1242
|
url.searchParams.set("redirectToCheckout", "true");
|
|
1204
|
-
url.searchParams.set("returnUrl", params.returnUrl);
|
|
1205
1243
|
return url.pathname + url.search;
|
|
1206
1244
|
};
|
|
1207
1245
|
var redirectToCheckoutFallback = (params) => {
|
|
1208
|
-
const decodedOffer = decodeSku(params.skuId);
|
|
1209
|
-
console.log("[web-apple-pay] redirectToCheckoutFallback", {
|
|
1210
|
-
rawSkuId: params.skuId,
|
|
1211
|
-
decodedOffer
|
|
1212
|
-
});
|
|
1213
1246
|
const url = new URL(window.location.href);
|
|
1214
1247
|
url.pathname = "/checkout";
|
|
1215
|
-
url.searchParams.set("lmo_offer",
|
|
1248
|
+
url.searchParams.set("lmo_offer", decodeSku(params.skuId));
|
|
1216
1249
|
if (params.inlineError)
|
|
1217
1250
|
url.searchParams.set("inlineError", params.inlineError);
|
|
1218
1251
|
if (params.email) url.searchParams.set("email", params.email);
|
|
1219
1252
|
if (params.emailError) url.searchParams.set("emailError", params.emailError);
|
|
1220
|
-
url.searchParams.set("returnUrl", params.returnUrl);
|
|
1221
|
-
console.log("[web-apple-pay] fallback redirect URL", url.pathname + url.search);
|
|
1222
1253
|
return url.pathname + url.search;
|
|
1223
1254
|
};
|
|
1224
1255
|
|
|
@@ -1308,10 +1339,10 @@ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
|
|
|
1308
1339
|
|
|
1309
1340
|
// src/clients/payment-checkout/apple-session.ts
|
|
1310
1341
|
function redirectToFallbackCheckout(config) {
|
|
1342
|
+
writeCheckoutRedirect(config.returnUrl);
|
|
1311
1343
|
trackFallbackCheckout(config.skuId);
|
|
1312
1344
|
window.location.href = redirectToCheckoutFallback({
|
|
1313
1345
|
skuId: config.skuId,
|
|
1314
|
-
returnUrl: config.returnUrl,
|
|
1315
1346
|
inlineError: MSG_PAYMENT_GENERIC_ERROR
|
|
1316
1347
|
});
|
|
1317
1348
|
}
|
|
@@ -1501,9 +1532,15 @@ var failApplePaySession = (session, errors = []) => {
|
|
|
1501
1532
|
};
|
|
1502
1533
|
function parseWalletApiError(err) {
|
|
1503
1534
|
const body = err instanceof ApiError ? err.body : err;
|
|
1535
|
+
console.warn("[web-apple-pay] parseWalletApiError: raw error", {
|
|
1536
|
+
isApiError: err instanceof ApiError,
|
|
1537
|
+
body
|
|
1538
|
+
});
|
|
1504
1539
|
if (body && typeof body === "object" && "errorType" in body && "message" in body) {
|
|
1540
|
+
console.warn("[web-apple-pay] parseWalletApiError: structured BFF error", body);
|
|
1505
1541
|
return body;
|
|
1506
1542
|
}
|
|
1543
|
+
console.warn("[web-apple-pay] parseWalletApiError: unstructured error \u2014 no errorType found");
|
|
1507
1544
|
return null;
|
|
1508
1545
|
}
|
|
1509
1546
|
function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
@@ -1517,11 +1554,10 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
|
1517
1554
|
return void 0;
|
|
1518
1555
|
}
|
|
1519
1556
|
function redirectFallback(skuId, returnUrl, options) {
|
|
1520
|
-
|
|
1557
|
+
writeCheckoutRedirect(returnUrl);
|
|
1521
1558
|
trackFallbackCheckout(skuId);
|
|
1522
1559
|
window.location.href = redirectToCheckoutFallback({
|
|
1523
1560
|
skuId,
|
|
1524
|
-
returnUrl,
|
|
1525
1561
|
...options
|
|
1526
1562
|
});
|
|
1527
1563
|
}
|
|
@@ -1536,7 +1572,12 @@ var ERROR_HANDLERS = {
|
|
|
1536
1572
|
),
|
|
1537
1573
|
ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
|
|
1538
1574
|
REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
|
|
1539
|
-
|
|
1575
|
+
ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, validatedEmail }) => redirectFallback(skuId, returnUrl, {
|
|
1576
|
+
emailError: MSG_REGISTRATION,
|
|
1577
|
+
email: validatedEmail
|
|
1578
|
+
}),
|
|
1579
|
+
VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED }),
|
|
1580
|
+
RECAPTCHA_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
|
|
1540
1581
|
};
|
|
1541
1582
|
function handleStructuredError(ctx) {
|
|
1542
1583
|
const handler = ERROR_HANDLERS[ctx.walletError.errorType];
|
|
@@ -1568,11 +1609,11 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1568
1609
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1569
1610
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1570
1611
|
const userStatus = sessionData.loggedIn ? "lex" : "nex";
|
|
1612
|
+
writeCheckoutRedirect(returnUrl);
|
|
1571
1613
|
window.location.href = redirectToCheckoutComplete({
|
|
1572
1614
|
basketId: result.basketId,
|
|
1573
1615
|
status: userStatus,
|
|
1574
1616
|
skuId: decodeSku(skuId),
|
|
1575
|
-
returnUrl,
|
|
1576
1617
|
queryString
|
|
1577
1618
|
});
|
|
1578
1619
|
} catch (err) {
|
|
@@ -1582,6 +1623,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1582
1623
|
}
|
|
1583
1624
|
console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
|
|
1584
1625
|
const walletError = parseWalletApiError(err);
|
|
1626
|
+
console.warn("[web-apple-pay] handlePaymentAuthorized: walletError result", {
|
|
1627
|
+
walletError,
|
|
1628
|
+
validatedEmail
|
|
1629
|
+
});
|
|
1585
1630
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1586
1631
|
failApplePaySession(session);
|
|
1587
1632
|
if (!walletError) {
|
|
@@ -2076,7 +2121,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2076
2121
|
if (button) {
|
|
2077
2122
|
button.classList.remove("wap-loading");
|
|
2078
2123
|
this.#updateDisabledState();
|
|
2079
|
-
this.#
|
|
2124
|
+
this.#scheduleRecaptchaTokens();
|
|
2080
2125
|
trackExpressWalletImpression(this.#offer.sku_id, {
|
|
2081
2126
|
entryPoint: this.#entryPoint,
|
|
2082
2127
|
country: this.#country
|
|
@@ -2115,10 +2160,27 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2115
2160
|
const country = this.#country;
|
|
2116
2161
|
modal.onConfirm = (recaptchaTokens) => {
|
|
2117
2162
|
this.#initiateApplePay(skuId, country, recaptchaTokens);
|
|
2118
|
-
this.#
|
|
2163
|
+
this.#scheduleRecaptchaTokens();
|
|
2119
2164
|
};
|
|
2120
2165
|
modal.open(this.#recaptchaTokensPromise);
|
|
2121
2166
|
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Starts a fresh reCAPTCHA token pre-fetch and stores its promise.
|
|
2169
|
+
* If the fetch rejects, the field is cleared so the next `modal.open()` call
|
|
2170
|
+
* falls back to a new attempt via the modal's own null-fallback path — preventing
|
|
2171
|
+
* a single transient failure from permanently disabling the CTA on all retries.
|
|
2172
|
+
* The identity guard prevents a late rejection from clobbering a newer attempt.
|
|
2173
|
+
*/
|
|
2174
|
+
#scheduleRecaptchaTokens() {
|
|
2175
|
+
const attempt = getPurchaseRecaptchaTokens();
|
|
2176
|
+
attempt.catch((err) => {
|
|
2177
|
+
console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
|
|
2178
|
+
if (this.#recaptchaTokensPromise === attempt) {
|
|
2179
|
+
this.#recaptchaTokensPromise = null;
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
this.#recaptchaTokensPromise = attempt;
|
|
2183
|
+
}
|
|
2122
2184
|
async #fetchSession() {
|
|
2123
2185
|
try {
|
|
2124
2186
|
return await getExpressCheckoutSession();
|