@economist/web-apple-pay 0.1.3-beta.30 → 0.1.3-beta.31
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 +63 -38
- package/dist/src/apple-pay-button.js +63 -37
- package/dist/src/apple-pay-button.stories.js +63 -37
- package/dist/src/apple-pay-modal.js +1 -0
- package/dist/src/apple-pay-modal.stories.js +63 -37
- package/dist/src/clients/payment-checkout/apple-session.js +24 -3
- package/dist/src/clients/payment-checkout/detects.js +4 -0
- package/dist/src/clients/payment-checkout/messages.d.ts +13 -14
- package/dist/src/clients/payment-checkout/messages.js +0 -4
- package/dist/src/clients/payment-checkout/payment-handler.d.ts +0 -8
- package/dist/src/clients/payment-checkout/payment-handler.js +46 -27
- package/dist/src/clients/payment-checkout/types.d.ts +1 -1
- package/dist/src/constants.d.ts +1 -2
- package/dist/src/constants.js +0 -1
- package/dist/src/index.js +63 -38
- package/dist/src/utils/expressCheckoutRedirect.d.ts +29 -7
- package/dist/src/utils/expressCheckoutRedirect.js +23 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -956,6 +956,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
956
956
|
cta.removeAttribute("aria-busy");
|
|
957
957
|
}
|
|
958
958
|
}).catch(() => {
|
|
959
|
+
console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
|
|
959
960
|
if (cta) {
|
|
960
961
|
cta.removeAttribute("aria-busy");
|
|
961
962
|
}
|
|
@@ -1141,11 +1142,15 @@ var isApplePayAvailable = async () => {
|
|
|
1141
1142
|
}
|
|
1142
1143
|
const merchantId = getApplePayConfig()?.merchantId;
|
|
1143
1144
|
if (!merchantId) {
|
|
1145
|
+
console.warn(
|
|
1146
|
+
"[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
|
|
1147
|
+
);
|
|
1144
1148
|
return false;
|
|
1145
1149
|
}
|
|
1146
1150
|
const hasActiveCard = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
1147
1151
|
return hasActiveCard;
|
|
1148
1152
|
} catch (error) {
|
|
1153
|
+
console.error("Error checking Apple Pay active card status:", error);
|
|
1149
1154
|
return false;
|
|
1150
1155
|
}
|
|
1151
1156
|
};
|
|
@@ -1233,13 +1238,25 @@ var getExpressCheckoutSession = () => {
|
|
|
1233
1238
|
};
|
|
1234
1239
|
|
|
1235
1240
|
// src/utils/expressCheckoutRedirect.ts
|
|
1236
|
-
var
|
|
1237
|
-
var
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1241
|
+
var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
|
|
1242
|
+
var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
|
|
1243
|
+
function isAllowedRedirectUrl(url) {
|
|
1244
|
+
if (url.startsWith("/")) return !url.startsWith("//");
|
|
1245
|
+
try {
|
|
1246
|
+
const { hostname } = new URL(url);
|
|
1247
|
+
return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
|
|
1248
|
+
} catch {
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
var writeCheckoutRedirect = (originUrl) => {
|
|
1253
|
+
if (!isAllowedRedirectUrl(originUrl)) return;
|
|
1254
|
+
try {
|
|
1255
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1242
1258
|
};
|
|
1259
|
+
var captureOriginUrl = () => window.location.href;
|
|
1243
1260
|
var decodeSku = (sku) => {
|
|
1244
1261
|
try {
|
|
1245
1262
|
return atob(sku.substring(sku.indexOf("-") + 1));
|
|
@@ -1250,14 +1267,11 @@ var decodeSku = (sku) => {
|
|
|
1250
1267
|
var redirectToCheckoutComplete = (params) => {
|
|
1251
1268
|
const url = new URL("/checkout/complete", window.location.origin);
|
|
1252
1269
|
url.searchParams.set("basketId", params.basketId);
|
|
1253
|
-
if (params.queryString) {
|
|
1254
|
-
const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
|
|
1255
|
-
new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
|
|
1256
|
-
}
|
|
1257
1270
|
url.searchParams.set("lmo_offer", params.skuId);
|
|
1258
1271
|
url.searchParams.set("status", params.status);
|
|
1259
1272
|
url.searchParams.set("redirectToCheckout", "true");
|
|
1260
|
-
url.searchParams.set("
|
|
1273
|
+
if (params.email) url.searchParams.set("email", params.email);
|
|
1274
|
+
if (params.country) url.searchParams.set("country", params.country);
|
|
1261
1275
|
return url.pathname + url.search;
|
|
1262
1276
|
};
|
|
1263
1277
|
var redirectToCheckoutFallback = (params) => {
|
|
@@ -1268,7 +1282,7 @@ var redirectToCheckoutFallback = (params) => {
|
|
|
1268
1282
|
url.searchParams.set("inlineError", params.inlineError);
|
|
1269
1283
|
if (params.email) url.searchParams.set("email", params.email);
|
|
1270
1284
|
if (params.emailError) url.searchParams.set("emailError", params.emailError);
|
|
1271
|
-
url.searchParams.set("
|
|
1285
|
+
if (params.country) url.searchParams.set("country", params.country);
|
|
1272
1286
|
return url.pathname + url.search;
|
|
1273
1287
|
};
|
|
1274
1288
|
|
|
@@ -1352,17 +1366,16 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
|
|
|
1352
1366
|
var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
|
|
1353
1367
|
var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
|
|
1354
1368
|
var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
|
|
1355
|
-
var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
|
|
1356
|
-
var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
|
|
1357
1369
|
var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
|
|
1358
1370
|
|
|
1359
1371
|
// src/clients/payment-checkout/apple-session.ts
|
|
1360
1372
|
function redirectToFallbackCheckout(config) {
|
|
1373
|
+
writeCheckoutRedirect(config.returnUrl);
|
|
1361
1374
|
trackFallbackCheckout(config.skuId);
|
|
1362
1375
|
window.location.href = redirectToCheckoutFallback({
|
|
1363
1376
|
skuId: config.skuId,
|
|
1364
|
-
|
|
1365
|
-
|
|
1377
|
+
inlineError: MSG_PAYMENT_GENERIC_ERROR,
|
|
1378
|
+
country: config.countryCode
|
|
1366
1379
|
});
|
|
1367
1380
|
}
|
|
1368
1381
|
async function handleMerchantValidation(event, session, config) {
|
|
@@ -1385,6 +1398,7 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1385
1398
|
}
|
|
1386
1399
|
session.completeMerchantValidation(response.merchantSession);
|
|
1387
1400
|
} catch (err) {
|
|
1401
|
+
console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
|
|
1388
1402
|
session.abort();
|
|
1389
1403
|
redirectToFallbackCheckout(config);
|
|
1390
1404
|
}
|
|
@@ -1418,6 +1432,7 @@ function startApplePaySession(config) {
|
|
|
1418
1432
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1419
1433
|
session.begin();
|
|
1420
1434
|
} catch (err) {
|
|
1435
|
+
console.error("[web-apple-pay] startApplePaySession: failed to create session", err);
|
|
1421
1436
|
redirectToFallbackCheckout(config);
|
|
1422
1437
|
}
|
|
1423
1438
|
}
|
|
@@ -1539,8 +1554,6 @@ var shippingContactDetails = (shippingContact) => {
|
|
|
1539
1554
|
};
|
|
1540
1555
|
|
|
1541
1556
|
// src/clients/payment-checkout/payment-handler.ts
|
|
1542
|
-
var MSG_ELIGIBILITY = "Something went wrong, please try again";
|
|
1543
|
-
var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
|
|
1544
1557
|
var failApplePaySession = (session, errors = []) => {
|
|
1545
1558
|
session.completePayment({
|
|
1546
1559
|
status: ApplePaySession.STATUS_FAILURE,
|
|
@@ -1564,37 +1577,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
|
1564
1577
|
}
|
|
1565
1578
|
return void 0;
|
|
1566
1579
|
}
|
|
1567
|
-
function redirectFallback(skuId, returnUrl, options) {
|
|
1580
|
+
function redirectFallback(skuId, returnUrl, country, options) {
|
|
1581
|
+
writeCheckoutRedirect(returnUrl);
|
|
1568
1582
|
trackFallbackCheckout(skuId);
|
|
1569
1583
|
window.location.href = redirectToCheckoutFallback({
|
|
1570
1584
|
skuId,
|
|
1571
|
-
|
|
1585
|
+
country,
|
|
1572
1586
|
...options
|
|
1573
1587
|
});
|
|
1574
1588
|
}
|
|
1575
1589
|
var ERROR_HANDLERS = {
|
|
1576
|
-
|
|
1577
|
-
|
|
1590
|
+
// emailError flows as an inline field error on the email input.
|
|
1591
|
+
// email pre-populates the input so the user doesn't have to retype it.
|
|
1592
|
+
EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
|
|
1593
|
+
emailError: walletError.message,
|
|
1594
|
+
email: validatedEmail
|
|
1595
|
+
}),
|
|
1596
|
+
PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1597
|
+
ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1598
|
+
REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1599
|
+
ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
|
|
1600
|
+
emailError: walletError.message,
|
|
1578
1601
|
email: validatedEmail
|
|
1579
1602
|
}),
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
|
|
1583
|
-
),
|
|
1584
|
-
ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
|
|
1585
|
-
REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
|
|
1586
|
-
VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
|
|
1603
|
+
VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1604
|
+
RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
|
|
1587
1605
|
};
|
|
1588
1606
|
function handleStructuredError(ctx) {
|
|
1589
1607
|
const handler = ERROR_HANDLERS[ctx.walletError.errorType];
|
|
1590
1608
|
if (handler) {
|
|
1591
1609
|
handler(ctx);
|
|
1592
1610
|
} else {
|
|
1593
|
-
redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1611
|
+
redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1594
1612
|
}
|
|
1595
1613
|
}
|
|
1596
1614
|
async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
1597
|
-
const { skuId, returnUrl,
|
|
1615
|
+
const { skuId, returnUrl, country } = config;
|
|
1598
1616
|
let validatedEmail;
|
|
1599
1617
|
try {
|
|
1600
1618
|
validatedEmail = validateContacts(
|
|
@@ -1614,27 +1632,30 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1614
1632
|
});
|
|
1615
1633
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1616
1634
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1635
|
+
const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
|
|
1617
1636
|
const userStatus = sessionData.loggedIn ? "lex" : "nex";
|
|
1637
|
+
writeCheckoutRedirect(returnUrl);
|
|
1618
1638
|
window.location.href = redirectToCheckoutComplete({
|
|
1619
1639
|
basketId: result.basketId,
|
|
1620
1640
|
status: userStatus,
|
|
1621
1641
|
skuId: decodeSku(skuId),
|
|
1622
|
-
|
|
1623
|
-
|
|
1642
|
+
email: successEmail,
|
|
1643
|
+
country
|
|
1624
1644
|
});
|
|
1625
1645
|
} catch (err) {
|
|
1626
1646
|
if (err instanceof ContactValidationError) {
|
|
1627
1647
|
failApplePaySession(session, err.errors);
|
|
1628
1648
|
return;
|
|
1629
1649
|
}
|
|
1650
|
+
console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
|
|
1630
1651
|
const walletError = parseWalletApiError(err);
|
|
1631
1652
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1632
1653
|
failApplePaySession(session);
|
|
1633
1654
|
if (!walletError) {
|
|
1634
|
-
redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1655
|
+
redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1635
1656
|
return;
|
|
1636
1657
|
}
|
|
1637
|
-
handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
|
|
1658
|
+
handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
|
|
1638
1659
|
}
|
|
1639
1660
|
}
|
|
1640
1661
|
|
|
@@ -1904,6 +1925,7 @@ var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
|
|
|
1904
1925
|
var TOTAL_LABEL = "The Economist";
|
|
1905
1926
|
var DEFAULT_CURRENCY = "USD";
|
|
1906
1927
|
var DEFAULT_TOTAL = "0.00";
|
|
1928
|
+
var ERROR_INITIATE = "Failed to initiate Apple Pay session:";
|
|
1907
1929
|
var SEL_CONTAINER = ".apple-pay-container";
|
|
1908
1930
|
var SEL_BUTTON = ".apple-pay-btn";
|
|
1909
1931
|
var APPLE_PAY_EXCLUDED_VARIANTS = [
|
|
@@ -2027,15 +2049,16 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2027
2049
|
// ─── Render pipeline ────────────────────────────────────────────────────────
|
|
2028
2050
|
async #run() {
|
|
2029
2051
|
try {
|
|
2052
|
+
this.#renderTemplate();
|
|
2030
2053
|
if (await this.#checkAvailability()) return;
|
|
2031
2054
|
if (this.#checkOffer()) return;
|
|
2032
|
-
this.#renderTemplate();
|
|
2033
2055
|
if (await this.#checkSession()) return;
|
|
2034
2056
|
this.#captureReturnUrl();
|
|
2035
2057
|
this.#computeBillingCountries();
|
|
2036
2058
|
if (await this.#fetchAndCacheOptions()) return;
|
|
2037
2059
|
this.#finishLoading();
|
|
2038
2060
|
} catch (err) {
|
|
2061
|
+
console.error("[web-apple-pay] #run: unexpected error, hiding button", err);
|
|
2039
2062
|
this.style.display = "none";
|
|
2040
2063
|
this.querySelector(SEL_CONTAINER)?.remove();
|
|
2041
2064
|
} finally {
|
|
@@ -2047,6 +2070,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2047
2070
|
const available = await isApplePayAvailable();
|
|
2048
2071
|
if (!available) {
|
|
2049
2072
|
this.style.display = "none";
|
|
2073
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
2050
2074
|
return true;
|
|
2051
2075
|
}
|
|
2052
2076
|
return false;
|
|
@@ -2068,6 +2092,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2068
2092
|
#checkOffer() {
|
|
2069
2093
|
if (!this.#offer) {
|
|
2070
2094
|
this.style.display = "none";
|
|
2095
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
2071
2096
|
return true;
|
|
2072
2097
|
}
|
|
2073
2098
|
return false;
|
|
@@ -2175,6 +2200,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2175
2200
|
#scheduleRecaptchaTokens() {
|
|
2176
2201
|
const attempt = getPurchaseRecaptchaTokens();
|
|
2177
2202
|
attempt.catch((err) => {
|
|
2203
|
+
console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
|
|
2178
2204
|
if (this.#recaptchaTokensPromise === attempt) {
|
|
2179
2205
|
this.#recaptchaTokensPromise = null;
|
|
2180
2206
|
}
|
|
@@ -2217,7 +2243,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2217
2243
|
country,
|
|
2218
2244
|
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2219
2245
|
returnUrl: this.#returnUrl,
|
|
2220
|
-
queryString: buildQueryString(),
|
|
2221
2246
|
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2222
2247
|
recaptchaTokens
|
|
2223
2248
|
})
|
|
@@ -2237,6 +2262,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2237
2262
|
)
|
|
2238
2263
|
);
|
|
2239
2264
|
} catch (error) {
|
|
2265
|
+
console.error(ERROR_INITIATE, error);
|
|
2240
2266
|
}
|
|
2241
2267
|
}
|
|
2242
2268
|
};
|
|
@@ -2245,7 +2271,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2245
2271
|
var ApplePayEntryPoint = /* @__PURE__ */ ((ApplePayEntryPoint2) => {
|
|
2246
2272
|
ApplePayEntryPoint2["PAYWALL"] = "article_paywall";
|
|
2247
2273
|
ApplePayEntryPoint2["REGWALL"] = "article_regwall";
|
|
2248
|
-
ApplePayEntryPoint2["DUAL_OFFER_BANNER"] = "dual_offer_banner";
|
|
2249
2274
|
return ApplePayEntryPoint2;
|
|
2250
2275
|
})(ApplePayEntryPoint || {});
|
|
2251
2276
|
|
|
@@ -951,6 +951,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
951
951
|
cta.removeAttribute("aria-busy");
|
|
952
952
|
}
|
|
953
953
|
}).catch(() => {
|
|
954
|
+
console.error("[web-apple-pay] apple-pay-modal: failed to pre-fetch reCAPTCHA tokens; CTA remains disabled");
|
|
954
955
|
if (cta) {
|
|
955
956
|
cta.removeAttribute("aria-busy");
|
|
956
957
|
}
|
|
@@ -1136,11 +1137,15 @@ var isApplePayAvailable = async () => {
|
|
|
1136
1137
|
}
|
|
1137
1138
|
const merchantId = getApplePayConfig()?.merchantId;
|
|
1138
1139
|
if (!merchantId) {
|
|
1140
|
+
console.warn(
|
|
1141
|
+
"[web-apple-pay] Missing merchantId. Call configureApplePay({ merchantId, ... }) before rendering Apple Pay components."
|
|
1142
|
+
);
|
|
1139
1143
|
return false;
|
|
1140
1144
|
}
|
|
1141
1145
|
const hasActiveCard = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
1142
1146
|
return hasActiveCard;
|
|
1143
1147
|
} catch (error) {
|
|
1148
|
+
console.error("Error checking Apple Pay active card status:", error);
|
|
1144
1149
|
return false;
|
|
1145
1150
|
}
|
|
1146
1151
|
};
|
|
@@ -1228,13 +1233,25 @@ var getExpressCheckoutSession = () => {
|
|
|
1228
1233
|
};
|
|
1229
1234
|
|
|
1230
1235
|
// src/utils/expressCheckoutRedirect.ts
|
|
1231
|
-
var
|
|
1232
|
-
var
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1236
|
+
var CHECKOUT_REDIRECT_KEY = "checkout-redirect";
|
|
1237
|
+
var ALLOWED_REDIRECT_HOSTNAME = "economist.com";
|
|
1238
|
+
function isAllowedRedirectUrl(url) {
|
|
1239
|
+
if (url.startsWith("/")) return !url.startsWith("//");
|
|
1240
|
+
try {
|
|
1241
|
+
const { hostname } = new URL(url);
|
|
1242
|
+
return hostname === ALLOWED_REDIRECT_HOSTNAME || hostname.endsWith(`.${ALLOWED_REDIRECT_HOSTNAME}`);
|
|
1243
|
+
} catch {
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
var writeCheckoutRedirect = (originUrl) => {
|
|
1248
|
+
if (!isAllowedRedirectUrl(originUrl)) return;
|
|
1249
|
+
try {
|
|
1250
|
+
localStorage.setItem(CHECKOUT_REDIRECT_KEY, originUrl);
|
|
1251
|
+
} catch {
|
|
1252
|
+
}
|
|
1237
1253
|
};
|
|
1254
|
+
var captureOriginUrl = () => window.location.href;
|
|
1238
1255
|
var decodeSku = (sku) => {
|
|
1239
1256
|
try {
|
|
1240
1257
|
return atob(sku.substring(sku.indexOf("-") + 1));
|
|
@@ -1245,14 +1262,11 @@ var decodeSku = (sku) => {
|
|
|
1245
1262
|
var redirectToCheckoutComplete = (params) => {
|
|
1246
1263
|
const url = new URL("/checkout/complete", window.location.origin);
|
|
1247
1264
|
url.searchParams.set("basketId", params.basketId);
|
|
1248
|
-
if (params.queryString) {
|
|
1249
|
-
const qs = params.queryString.startsWith("?") ? params.queryString.slice(1) : params.queryString;
|
|
1250
|
-
new URLSearchParams(qs).forEach((v, k) => url.searchParams.set(k, v));
|
|
1251
|
-
}
|
|
1252
1265
|
url.searchParams.set("lmo_offer", params.skuId);
|
|
1253
1266
|
url.searchParams.set("status", params.status);
|
|
1254
1267
|
url.searchParams.set("redirectToCheckout", "true");
|
|
1255
|
-
url.searchParams.set("
|
|
1268
|
+
if (params.email) url.searchParams.set("email", params.email);
|
|
1269
|
+
if (params.country) url.searchParams.set("country", params.country);
|
|
1256
1270
|
return url.pathname + url.search;
|
|
1257
1271
|
};
|
|
1258
1272
|
var redirectToCheckoutFallback = (params) => {
|
|
@@ -1263,7 +1277,7 @@ var redirectToCheckoutFallback = (params) => {
|
|
|
1263
1277
|
url.searchParams.set("inlineError", params.inlineError);
|
|
1264
1278
|
if (params.email) url.searchParams.set("email", params.email);
|
|
1265
1279
|
if (params.emailError) url.searchParams.set("emailError", params.emailError);
|
|
1266
|
-
url.searchParams.set("
|
|
1280
|
+
if (params.country) url.searchParams.set("country", params.country);
|
|
1267
1281
|
return url.pathname + url.search;
|
|
1268
1282
|
};
|
|
1269
1283
|
|
|
@@ -1347,17 +1361,16 @@ var MSG_EMAIL_WRONG_FORMAT = "That email doesn\u2019t look right. Please enter a
|
|
|
1347
1361
|
var MSG_EMAIL_TOO_LONG = "This field must be 80 characters or less.";
|
|
1348
1362
|
var MSG_UNSUPPORTED_BILLING_COUNTRY = "Billing country does not match offer price zone";
|
|
1349
1363
|
var MSG_PAYMENT_GENERIC_ERROR = "Something went wrong. Please check your payment and billing details and try again or contact us.";
|
|
1350
|
-
var MSG_RECAPTCHA_VALIDATION_FAILED = "Security check failed. Please try again.";
|
|
1351
|
-
var MSG_EMAIL_COLLISION = "This email address is already linked to an account. Please log in, or try a different email address.";
|
|
1352
1364
|
var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
|
|
1353
1365
|
|
|
1354
1366
|
// src/clients/payment-checkout/apple-session.ts
|
|
1355
1367
|
function redirectToFallbackCheckout(config) {
|
|
1368
|
+
writeCheckoutRedirect(config.returnUrl);
|
|
1356
1369
|
trackFallbackCheckout(config.skuId);
|
|
1357
1370
|
window.location.href = redirectToCheckoutFallback({
|
|
1358
1371
|
skuId: config.skuId,
|
|
1359
|
-
|
|
1360
|
-
|
|
1372
|
+
inlineError: MSG_PAYMENT_GENERIC_ERROR,
|
|
1373
|
+
country: config.countryCode
|
|
1361
1374
|
});
|
|
1362
1375
|
}
|
|
1363
1376
|
async function handleMerchantValidation(event, session, config) {
|
|
@@ -1380,6 +1393,7 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1380
1393
|
}
|
|
1381
1394
|
session.completeMerchantValidation(response.merchantSession);
|
|
1382
1395
|
} catch (err) {
|
|
1396
|
+
console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
|
|
1383
1397
|
session.abort();
|
|
1384
1398
|
redirectToFallbackCheckout(config);
|
|
1385
1399
|
}
|
|
@@ -1413,6 +1427,7 @@ function startApplePaySession(config) {
|
|
|
1413
1427
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1414
1428
|
session.begin();
|
|
1415
1429
|
} catch (err) {
|
|
1430
|
+
console.error("[web-apple-pay] startApplePaySession: failed to create session", err);
|
|
1416
1431
|
redirectToFallbackCheckout(config);
|
|
1417
1432
|
}
|
|
1418
1433
|
}
|
|
@@ -1534,8 +1549,6 @@ var shippingContactDetails = (shippingContact) => {
|
|
|
1534
1549
|
};
|
|
1535
1550
|
|
|
1536
1551
|
// src/clients/payment-checkout/payment-handler.ts
|
|
1537
|
-
var MSG_ELIGIBILITY = "Something went wrong, please try again";
|
|
1538
|
-
var MSG_REGISTRATION = "We couldn't create your account. Please enter your email or try a different login method.";
|
|
1539
1552
|
var failApplePaySession = (session, errors = []) => {
|
|
1540
1553
|
session.completePayment({
|
|
1541
1554
|
status: ApplePaySession.STATUS_FAILURE,
|
|
@@ -1559,37 +1572,42 @@ function validateContacts(event, sessionData, supportedBillingCountries) {
|
|
|
1559
1572
|
}
|
|
1560
1573
|
return void 0;
|
|
1561
1574
|
}
|
|
1562
|
-
function redirectFallback(skuId, returnUrl, options) {
|
|
1575
|
+
function redirectFallback(skuId, returnUrl, country, options) {
|
|
1576
|
+
writeCheckoutRedirect(returnUrl);
|
|
1563
1577
|
trackFallbackCheckout(skuId);
|
|
1564
1578
|
window.location.href = redirectToCheckoutFallback({
|
|
1565
1579
|
skuId,
|
|
1566
|
-
|
|
1580
|
+
country,
|
|
1567
1581
|
...options
|
|
1568
1582
|
});
|
|
1569
1583
|
}
|
|
1570
1584
|
var ERROR_HANDLERS = {
|
|
1571
|
-
|
|
1572
|
-
|
|
1585
|
+
// emailError flows as an inline field error on the email input.
|
|
1586
|
+
// email pre-populates the input so the user doesn't have to retype it.
|
|
1587
|
+
EMAIL_COLLISION: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
|
|
1588
|
+
emailError: walletError.message,
|
|
1589
|
+
email: validatedEmail
|
|
1590
|
+
}),
|
|
1591
|
+
PAYMENT_DECLINED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1592
|
+
ELIGIBILITY_REJECTED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1593
|
+
REGISTRATION_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1594
|
+
ACCOUNT_CREATION_FAILED: ({ skuId, returnUrl, country, validatedEmail, walletError }) => redirectFallback(skuId, returnUrl, country, {
|
|
1595
|
+
emailError: walletError.message,
|
|
1573
1596
|
email: validatedEmail
|
|
1574
1597
|
}),
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR })
|
|
1578
|
-
),
|
|
1579
|
-
ELIGIBILITY_REJECTED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_ELIGIBILITY }),
|
|
1580
|
-
REGISTRATION_FAILED: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_REGISTRATION }),
|
|
1581
|
-
VALIDATION_ERROR: ({ skuId, returnUrl }) => redirectFallback(skuId, returnUrl, { inlineError: MSG_RECAPTCHA_VALIDATION_FAILED })
|
|
1598
|
+
VALIDATION_ERROR: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message }),
|
|
1599
|
+
RECAPTCHA_FAILED: ({ skuId, returnUrl, country, walletError }) => redirectFallback(skuId, returnUrl, country, { inlineError: walletError.message })
|
|
1582
1600
|
};
|
|
1583
1601
|
function handleStructuredError(ctx) {
|
|
1584
1602
|
const handler = ERROR_HANDLERS[ctx.walletError.errorType];
|
|
1585
1603
|
if (handler) {
|
|
1586
1604
|
handler(ctx);
|
|
1587
1605
|
} else {
|
|
1588
|
-
redirectFallback(ctx.skuId, ctx.returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1606
|
+
redirectFallback(ctx.skuId, ctx.returnUrl, ctx.country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1589
1607
|
}
|
|
1590
1608
|
}
|
|
1591
1609
|
async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
1592
|
-
const { skuId, returnUrl,
|
|
1610
|
+
const { skuId, returnUrl, country } = config;
|
|
1593
1611
|
let validatedEmail;
|
|
1594
1612
|
try {
|
|
1595
1613
|
validatedEmail = validateContacts(
|
|
@@ -1609,27 +1627,30 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1609
1627
|
});
|
|
1610
1628
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1611
1629
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1630
|
+
const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
|
|
1612
1631
|
const userStatus = sessionData.loggedIn ? "lex" : "nex";
|
|
1632
|
+
writeCheckoutRedirect(returnUrl);
|
|
1613
1633
|
window.location.href = redirectToCheckoutComplete({
|
|
1614
1634
|
basketId: result.basketId,
|
|
1615
1635
|
status: userStatus,
|
|
1616
1636
|
skuId: decodeSku(skuId),
|
|
1617
|
-
|
|
1618
|
-
|
|
1637
|
+
email: successEmail,
|
|
1638
|
+
country
|
|
1619
1639
|
});
|
|
1620
1640
|
} catch (err) {
|
|
1621
1641
|
if (err instanceof ContactValidationError) {
|
|
1622
1642
|
failApplePaySession(session, err.errors);
|
|
1623
1643
|
return;
|
|
1624
1644
|
}
|
|
1645
|
+
console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
|
|
1625
1646
|
const walletError = parseWalletApiError(err);
|
|
1626
1647
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1627
1648
|
failApplePaySession(session);
|
|
1628
1649
|
if (!walletError) {
|
|
1629
|
-
redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1650
|
+
redirectFallback(skuId, returnUrl, country, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1630
1651
|
return;
|
|
1631
1652
|
}
|
|
1632
|
-
handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
|
|
1653
|
+
handleStructuredError({ skuId, returnUrl, country, validatedEmail, walletError });
|
|
1633
1654
|
}
|
|
1634
1655
|
}
|
|
1635
1656
|
|
|
@@ -1899,6 +1920,7 @@ var FEATURE_APPLE_PAY_EXPRESS_CHECKOUT = "FEATURE_APPLE_PAY_EXPRESS_CHECKOUT";
|
|
|
1899
1920
|
var TOTAL_LABEL = "The Economist";
|
|
1900
1921
|
var DEFAULT_CURRENCY = "USD";
|
|
1901
1922
|
var DEFAULT_TOTAL = "0.00";
|
|
1923
|
+
var ERROR_INITIATE = "Failed to initiate Apple Pay session:";
|
|
1902
1924
|
var SEL_CONTAINER = ".apple-pay-container";
|
|
1903
1925
|
var SEL_BUTTON = ".apple-pay-btn";
|
|
1904
1926
|
var APPLE_PAY_EXCLUDED_VARIANTS = [
|
|
@@ -2022,15 +2044,16 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2022
2044
|
// ─── Render pipeline ────────────────────────────────────────────────────────
|
|
2023
2045
|
async #run() {
|
|
2024
2046
|
try {
|
|
2047
|
+
this.#renderTemplate();
|
|
2025
2048
|
if (await this.#checkAvailability()) return;
|
|
2026
2049
|
if (this.#checkOffer()) return;
|
|
2027
|
-
this.#renderTemplate();
|
|
2028
2050
|
if (await this.#checkSession()) return;
|
|
2029
2051
|
this.#captureReturnUrl();
|
|
2030
2052
|
this.#computeBillingCountries();
|
|
2031
2053
|
if (await this.#fetchAndCacheOptions()) return;
|
|
2032
2054
|
this.#finishLoading();
|
|
2033
2055
|
} catch (err) {
|
|
2056
|
+
console.error("[web-apple-pay] #run: unexpected error, hiding button", err);
|
|
2034
2057
|
this.style.display = "none";
|
|
2035
2058
|
this.querySelector(SEL_CONTAINER)?.remove();
|
|
2036
2059
|
} finally {
|
|
@@ -2042,6 +2065,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2042
2065
|
const available = await isApplePayAvailable();
|
|
2043
2066
|
if (!available) {
|
|
2044
2067
|
this.style.display = "none";
|
|
2068
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
2045
2069
|
return true;
|
|
2046
2070
|
}
|
|
2047
2071
|
return false;
|
|
@@ -2063,6 +2087,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2063
2087
|
#checkOffer() {
|
|
2064
2088
|
if (!this.#offer) {
|
|
2065
2089
|
this.style.display = "none";
|
|
2090
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
2066
2091
|
return true;
|
|
2067
2092
|
}
|
|
2068
2093
|
return false;
|
|
@@ -2170,6 +2195,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2170
2195
|
#scheduleRecaptchaTokens() {
|
|
2171
2196
|
const attempt = getPurchaseRecaptchaTokens();
|
|
2172
2197
|
attempt.catch((err) => {
|
|
2198
|
+
console.error("[web-apple-pay] reCAPTCHA token pre-fetch failed; will retry on modal open", err);
|
|
2173
2199
|
if (this.#recaptchaTokensPromise === attempt) {
|
|
2174
2200
|
this.#recaptchaTokensPromise = null;
|
|
2175
2201
|
}
|
|
@@ -2212,7 +2238,6 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2212
2238
|
country,
|
|
2213
2239
|
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2214
2240
|
returnUrl: this.#returnUrl,
|
|
2215
|
-
queryString: buildQueryString(),
|
|
2216
2241
|
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2217
2242
|
recaptchaTokens
|
|
2218
2243
|
})
|
|
@@ -2232,6 +2257,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2232
2257
|
)
|
|
2233
2258
|
);
|
|
2234
2259
|
} catch (error) {
|
|
2260
|
+
console.error(ERROR_INITIATE, error);
|
|
2235
2261
|
}
|
|
2236
2262
|
}
|
|
2237
2263
|
};
|