@economist/web-apple-pay 1.6.2-beta.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +110 -55
- package/dist/src/apple-pay-button.js +110 -49
- package/dist/src/apple-pay-button.stories.js +110 -56
- package/dist/src/apple-pay-modal.stories.js +110 -49
- package/dist/src/clients/payment-checkout/apple-session.js +1 -17
- package/dist/src/clients/payment-checkout/config.js +0 -7
- package/dist/src/clients/payment-checkout/detects.js +1 -10
- package/dist/src/clients/payment-checkout/payment-handler.js +0 -14
- package/dist/src/index.js +110 -55
- package/dist/src/stories/utils/story-env.js +0 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -439,17 +439,95 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
439
439
|
};
|
|
440
440
|
|
|
441
441
|
// src/templates/apple-pay-button.template.html
|
|
442
|
-
var apple_pay_button_template_default =
|
|
442
|
+
var apple_pay_button_template_default = `<style>
|
|
443
|
+
/* Use CSS variables for layout so parent walls can easily override placement and sizing */
|
|
444
|
+
teg-apple-pay-button {
|
|
445
|
+
display: block;
|
|
446
|
+
margin: var(--apple-pay-margin, 0);
|
|
447
|
+
width: 100%;
|
|
448
|
+
height: 100%;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
teg-apple-pay-button .apple-pay-container {
|
|
452
|
+
display: flex;
|
|
453
|
+
flex-direction: column;
|
|
454
|
+
align-items: stretch;
|
|
455
|
+
width: 100%;
|
|
456
|
+
height: 100%;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
teg-apple-pay-button .apple-pay-btn {
|
|
460
|
+
display: flex;
|
|
461
|
+
align-items: center;
|
|
462
|
+
justify-content: center;
|
|
463
|
+
gap: 0.375rem;
|
|
464
|
+
width: 100%;
|
|
465
|
+
min-width: 0;
|
|
466
|
+
height: 100%;
|
|
467
|
+
min-height: var(--apple-pay-height, 2.75rem);
|
|
468
|
+
background-color: #000;
|
|
469
|
+
color: #fff;
|
|
470
|
+
border: none;
|
|
471
|
+
border-radius: var(--apple-pay-border-radius, 1.375rem);
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
|
|
474
|
+
font-size: 1.0625rem;
|
|
475
|
+
font-weight: 500;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
teg-apple-pay-button .apple-pay-btn svg {
|
|
479
|
+
flex-shrink: 0;
|
|
480
|
+
display: block;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
teg-apple-pay-button .apple-pay-btn.wap-loading {
|
|
484
|
+
position: relative;
|
|
485
|
+
cursor: default;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
teg-apple-pay-button .apple-pay-btn.wap-loading svg {
|
|
489
|
+
visibility: hidden;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
teg-apple-pay-button .apple-pay-btn.wap-loading::after {
|
|
493
|
+
content: '';
|
|
494
|
+
position: absolute;
|
|
495
|
+
left: 50%;
|
|
496
|
+
top: 50%;
|
|
497
|
+
margin-left: -0.625rem;
|
|
498
|
+
margin-top: -0.625rem;
|
|
499
|
+
width: 1.25rem;
|
|
500
|
+
height: 1.25rem;
|
|
501
|
+
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
502
|
+
border-top-color: #fff;
|
|
503
|
+
border-radius: 50%;
|
|
504
|
+
animation: wap-button-spinner 0.8s linear infinite;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
@keyframes wap-button-spinner {
|
|
508
|
+
to {
|
|
509
|
+
transform: rotate(360deg);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
</style>
|
|
513
|
+
|
|
514
|
+
<div class="apple-pay-container" aria-label="Express checkout with Apple Pay">
|
|
515
|
+
<button
|
|
516
|
+
class="apple-pay-btn"
|
|
517
|
+
type="button"
|
|
518
|
+
aria-label="Pay with Apple Pay"
|
|
519
|
+
data-analytics="paywall:apple-pay-checkout"
|
|
520
|
+
>
|
|
521
|
+
<svg width="51" height="22" aria-label="Apple Pay" role="img">
|
|
522
|
+
<use href="#apple-pay-logo" />
|
|
523
|
+
</svg>
|
|
524
|
+
</button>
|
|
525
|
+
</div>
|
|
526
|
+
`;
|
|
443
527
|
|
|
444
528
|
// src/clients/payment-checkout/config.ts
|
|
445
529
|
var runtimeConfig = null;
|
|
446
530
|
var configureApplePay = (config) => {
|
|
447
|
-
console.log("[web-apple-pay] configureApplePay called", {
|
|
448
|
-
merchantId: config.merchantId,
|
|
449
|
-
walletApiBaseUrl: config.walletApiBaseUrl,
|
|
450
|
-
debugBypassEnabled: config.debugBypassEnabled,
|
|
451
|
-
recaptchaSiteKey: config.recaptchaSiteKey ? "***" : void 0
|
|
452
|
-
});
|
|
453
531
|
runtimeConfig = {
|
|
454
532
|
...config
|
|
455
533
|
};
|
|
@@ -464,27 +542,21 @@ var isAppleDeviceOrSafari = () => {
|
|
|
464
542
|
};
|
|
465
543
|
var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
|
|
466
544
|
var isApplePayAvailable = async () => {
|
|
467
|
-
console.log("[web-apple-pay] isApplePayAvailable: checking availability");
|
|
468
545
|
if (bypassApplePayChecks()) {
|
|
469
|
-
console.log("[web-apple-pay] isApplePayAvailable: bypass active \u2192 true");
|
|
470
546
|
return true;
|
|
471
547
|
}
|
|
472
548
|
if (!isAppleDeviceOrSafari()) {
|
|
473
|
-
console.log("[web-apple-pay] isApplePayAvailable: not Apple device or Safari \u2192 false");
|
|
474
549
|
return false;
|
|
475
550
|
}
|
|
476
551
|
const applePaySession = window.ApplePaySession;
|
|
477
552
|
if (!applePaySession) {
|
|
478
|
-
console.log("[web-apple-pay] isApplePayAvailable: ApplePaySession not found on window \u2192 false");
|
|
479
553
|
return false;
|
|
480
554
|
}
|
|
481
555
|
try {
|
|
482
556
|
if (typeof applePaySession.canMakePayments !== "function") {
|
|
483
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePayments is not a function \u2192 false");
|
|
484
557
|
return false;
|
|
485
558
|
}
|
|
486
559
|
if (!applePaySession.canMakePayments()) {
|
|
487
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePayments() returned false \u2192 false");
|
|
488
560
|
return false;
|
|
489
561
|
}
|
|
490
562
|
const merchantId = getApplePayConfig()?.merchantId;
|
|
@@ -494,10 +566,7 @@ var isApplePayAvailable = async () => {
|
|
|
494
566
|
);
|
|
495
567
|
return false;
|
|
496
568
|
}
|
|
497
|
-
|
|
498
|
-
const result = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
499
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePaymentsWithActiveCard \u2192", result);
|
|
500
|
-
return result;
|
|
569
|
+
return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
501
570
|
} catch (error) {
|
|
502
571
|
console.error("Error checking Apple Pay active card status:", error);
|
|
503
572
|
return false;
|
|
@@ -759,13 +828,6 @@ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
|
|
|
759
828
|
|
|
760
829
|
// src/clients/payment-checkout/apple-session.ts
|
|
761
830
|
function startApplePaySession(config) {
|
|
762
|
-
console.log("[web-apple-pay] startApplePaySession", {
|
|
763
|
-
skuId: config.skuId,
|
|
764
|
-
countryCode: config.countryCode,
|
|
765
|
-
currencyCode: config.currencyCode,
|
|
766
|
-
totalAmount: config.totalAmount,
|
|
767
|
-
userLoggedIn: config.userLoggedIn
|
|
768
|
-
});
|
|
769
831
|
const paymentRequest = {
|
|
770
832
|
countryCode: config.countryCode,
|
|
771
833
|
currencyCode: config.currencyCode,
|
|
@@ -781,11 +843,9 @@ function startApplePaySession(config) {
|
|
|
781
843
|
};
|
|
782
844
|
const session = new ApplePaySession(3, paymentRequest);
|
|
783
845
|
session.onvalidatemerchant = async (event) => {
|
|
784
|
-
console.log("[web-apple-pay] onvalidatemerchant: received", { validationURL: event.validationURL });
|
|
785
846
|
try {
|
|
786
847
|
const validationHost = new URL(event.validationURL).host;
|
|
787
848
|
if (!validationHost.endsWith("apple.com")) {
|
|
788
|
-
console.warn("[web-apple-pay] onvalidatemerchant: invalid host, aborting", { validationHost });
|
|
789
849
|
trackFallbackCheckout(config.skuId);
|
|
790
850
|
session.abort();
|
|
791
851
|
window.location.href = redirectToCheckoutFallback({
|
|
@@ -796,13 +856,11 @@ function startApplePaySession(config) {
|
|
|
796
856
|
return;
|
|
797
857
|
}
|
|
798
858
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
799
|
-
console.log("[web-apple-pay] onvalidatemerchant: calling validateAppleMerchant");
|
|
800
859
|
const response = await validateAppleMerchant({
|
|
801
860
|
validationURL: event.validationURL,
|
|
802
861
|
recaptchaToken
|
|
803
862
|
});
|
|
804
863
|
if (!response?.merchantSession) {
|
|
805
|
-
console.warn("[web-apple-pay] onvalidatemerchant: no merchantSession in response, aborting");
|
|
806
864
|
trackFallbackCheckout(config.skuId);
|
|
807
865
|
session.abort();
|
|
808
866
|
window.location.href = redirectToCheckoutFallback({
|
|
@@ -812,10 +870,8 @@ function startApplePaySession(config) {
|
|
|
812
870
|
});
|
|
813
871
|
return;
|
|
814
872
|
}
|
|
815
|
-
console.log("[web-apple-pay] onvalidatemerchant: merchant validated successfully");
|
|
816
873
|
session.completeMerchantValidation(response.merchantSession);
|
|
817
|
-
} catch
|
|
818
|
-
console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
|
|
874
|
+
} catch {
|
|
819
875
|
trackFallbackCheckout(config.skuId);
|
|
820
876
|
session.abort();
|
|
821
877
|
window.location.href = redirectToCheckoutFallback({
|
|
@@ -826,15 +882,12 @@ function startApplePaySession(config) {
|
|
|
826
882
|
}
|
|
827
883
|
};
|
|
828
884
|
session.onpaymentauthorized = (event) => {
|
|
829
|
-
console.log("[web-apple-pay] onpaymentauthorized: payment authorized by user", { skuId: config.skuId });
|
|
830
885
|
trackWalletPaymentAuthorised(config.skuId);
|
|
831
886
|
config.onPaymentAuthorized(event, session);
|
|
832
887
|
};
|
|
833
888
|
session.oncancel = () => {
|
|
834
|
-
console.log("[web-apple-pay] oncancel: payment sheet cancelled by user", { skuId: config.skuId });
|
|
835
889
|
config.onCancel?.();
|
|
836
890
|
};
|
|
837
|
-
console.log("[web-apple-pay] startApplePaySession: beginning session");
|
|
838
891
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
839
892
|
session.begin();
|
|
840
893
|
}
|
|
@@ -1013,16 +1066,12 @@ function handleStructuredError(ctx) {
|
|
|
1013
1066
|
async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
1014
1067
|
const { skuId, returnUrl, queryString } = config;
|
|
1015
1068
|
let validatedEmail;
|
|
1016
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: starting", { skuId, userLoggedIn: sessionData.loggedIn });
|
|
1017
1069
|
try {
|
|
1018
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 1 \u2014 validating contacts");
|
|
1019
1070
|
validatedEmail = validateContacts(
|
|
1020
1071
|
event,
|
|
1021
1072
|
sessionData,
|
|
1022
1073
|
config.supportedBillingCountries
|
|
1023
1074
|
);
|
|
1024
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: contacts valid", { validatedEmail });
|
|
1025
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 2 \u2014 calling performPurchase", { skuId });
|
|
1026
1075
|
const result = await performPurchase({
|
|
1027
1076
|
applePayToken: event.payment.token,
|
|
1028
1077
|
skuId: config.skuId,
|
|
@@ -1032,8 +1081,6 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1032
1081
|
},
|
|
1033
1082
|
recaptchaTokens: config.recaptchaTokens
|
|
1034
1083
|
});
|
|
1035
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: performPurchase succeeded", { basketId: result.basketId });
|
|
1036
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 3 \u2014 completing payment and redirecting");
|
|
1037
1084
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1038
1085
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1039
1086
|
const userStatus = sessionData.loggedIn ? "lex" : "nex";
|
|
@@ -1046,25 +1093,17 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1046
1093
|
});
|
|
1047
1094
|
} catch (err) {
|
|
1048
1095
|
if (err instanceof ContactValidationError) {
|
|
1049
|
-
console.warn("[web-apple-pay] handlePaymentAuthorized: contact validation failed", err.errors);
|
|
1050
1096
|
failApplePaySession(session, err.errors);
|
|
1051
1097
|
return;
|
|
1052
1098
|
}
|
|
1053
1099
|
console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
|
|
1054
1100
|
const walletError = parseWalletApiError(err);
|
|
1055
|
-
console.error("[web-apple-pay] handlePaymentAuthorized: BFF/network error", {
|
|
1056
|
-
skuId,
|
|
1057
|
-
errorType: walletError?.errorType ?? "UNKNOWN",
|
|
1058
|
-
message: walletError?.message
|
|
1059
|
-
});
|
|
1060
1101
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1061
1102
|
failApplePaySession(session);
|
|
1062
1103
|
if (!walletError) {
|
|
1063
|
-
console.warn("[web-apple-pay] handlePaymentAuthorized: unstructured error, using generic fallback");
|
|
1064
1104
|
redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1065
1105
|
return;
|
|
1066
1106
|
}
|
|
1067
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: dispatching structured error handler", { errorType: walletError.errorType });
|
|
1068
1107
|
handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
|
|
1069
1108
|
}
|
|
1070
1109
|
}
|
|
@@ -1452,14 +1491,16 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1452
1491
|
async #run() {
|
|
1453
1492
|
try {
|
|
1454
1493
|
if (await this.#checkAvailability()) return;
|
|
1455
|
-
if (await this.#checkSession()) return;
|
|
1456
1494
|
if (this.#checkOffer()) return;
|
|
1495
|
+
this.#renderTemplate();
|
|
1496
|
+
if (await this.#checkSession()) return;
|
|
1457
1497
|
this.#captureReturnUrl();
|
|
1458
1498
|
this.#computeBillingCountries();
|
|
1459
1499
|
if (await this.#fetchAndCacheOptions()) return;
|
|
1460
|
-
this.#
|
|
1500
|
+
this.#finishLoading();
|
|
1461
1501
|
} catch {
|
|
1462
1502
|
this.style.display = "none";
|
|
1503
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
1463
1504
|
} finally {
|
|
1464
1505
|
this.#rendering = false;
|
|
1465
1506
|
}
|
|
@@ -1481,6 +1522,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1481
1522
|
this.#sessionData = await this.#fetchSession();
|
|
1482
1523
|
if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
|
|
1483
1524
|
this.style.display = "none";
|
|
1525
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
1484
1526
|
return true;
|
|
1485
1527
|
}
|
|
1486
1528
|
return false;
|
|
@@ -1516,6 +1558,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1516
1558
|
this.#paymentOptions = await this.#fetchPaymentOptions();
|
|
1517
1559
|
if (!this.#paymentOptions?.apple_pay) {
|
|
1518
1560
|
this.style.display = "none";
|
|
1561
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
1519
1562
|
return true;
|
|
1520
1563
|
}
|
|
1521
1564
|
return false;
|
|
@@ -1527,13 +1570,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1527
1570
|
const template = document.createElement("template");
|
|
1528
1571
|
template.innerHTML = apple_pay_button_template_default;
|
|
1529
1572
|
this.appendChild(template.content.cloneNode(true));
|
|
1530
|
-
this.#updateDisabledState();
|
|
1531
|
-
trackExpressWalletImpression(this.#offer.sku_id, {
|
|
1532
|
-
entryPoint: this.#entryPoint,
|
|
1533
|
-
country: this.#country
|
|
1534
|
-
});
|
|
1535
1573
|
const button = this.querySelector(SEL_BUTTON);
|
|
1574
|
+
button.classList.add("wap-loading");
|
|
1575
|
+
button.setAttribute("disabled", "");
|
|
1576
|
+
button.setAttribute("aria-disabled", "true");
|
|
1577
|
+
button.setAttribute("aria-busy", "true");
|
|
1536
1578
|
button.addEventListener("click", () => {
|
|
1579
|
+
if (button.classList.contains("wap-loading")) return;
|
|
1537
1580
|
this.#openModal();
|
|
1538
1581
|
});
|
|
1539
1582
|
this.addEventListener("apple-pay-confirmed", (event) => {
|
|
@@ -1541,6 +1584,17 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1541
1584
|
this.#initiateApplePay(skuId, country);
|
|
1542
1585
|
});
|
|
1543
1586
|
}
|
|
1587
|
+
#finishLoading() {
|
|
1588
|
+
const button = this.querySelector(SEL_BUTTON);
|
|
1589
|
+
if (button) {
|
|
1590
|
+
button.classList.remove("wap-loading");
|
|
1591
|
+
this.#updateDisabledState();
|
|
1592
|
+
trackExpressWalletImpression(this.#offer.sku_id, {
|
|
1593
|
+
entryPoint: this.#entryPoint,
|
|
1594
|
+
country: this.#country
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1544
1598
|
// ─── Private helpers ────────────────────────────────────────────────────────
|
|
1545
1599
|
#updateDisabledState() {
|
|
1546
1600
|
const button = this.querySelector(SEL_BUTTON);
|
|
@@ -1548,6 +1602,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1548
1602
|
if (this.#offer) {
|
|
1549
1603
|
button.removeAttribute("disabled");
|
|
1550
1604
|
button.removeAttribute("aria-disabled");
|
|
1605
|
+
button.removeAttribute("aria-busy");
|
|
1551
1606
|
return;
|
|
1552
1607
|
}
|
|
1553
1608
|
button.setAttribute("disabled", "");
|
|
@@ -439,7 +439,91 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
439
439
|
};
|
|
440
440
|
|
|
441
441
|
// src/templates/apple-pay-button.template.html
|
|
442
|
-
var apple_pay_button_template_default =
|
|
442
|
+
var apple_pay_button_template_default = `<style>
|
|
443
|
+
/* Use CSS variables for layout so parent walls can easily override placement and sizing */
|
|
444
|
+
teg-apple-pay-button {
|
|
445
|
+
display: block;
|
|
446
|
+
margin: var(--apple-pay-margin, 0);
|
|
447
|
+
width: 100%;
|
|
448
|
+
height: 100%;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
teg-apple-pay-button .apple-pay-container {
|
|
452
|
+
display: flex;
|
|
453
|
+
flex-direction: column;
|
|
454
|
+
align-items: stretch;
|
|
455
|
+
width: 100%;
|
|
456
|
+
height: 100%;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
teg-apple-pay-button .apple-pay-btn {
|
|
460
|
+
display: flex;
|
|
461
|
+
align-items: center;
|
|
462
|
+
justify-content: center;
|
|
463
|
+
gap: 0.375rem;
|
|
464
|
+
width: 100%;
|
|
465
|
+
min-width: 0;
|
|
466
|
+
height: 100%;
|
|
467
|
+
min-height: var(--apple-pay-height, 2.75rem);
|
|
468
|
+
background-color: #000;
|
|
469
|
+
color: #fff;
|
|
470
|
+
border: none;
|
|
471
|
+
border-radius: var(--apple-pay-border-radius, 1.375rem);
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
font-family: var(--mb-typeface-sans, var(--wall-type-system-sans));
|
|
474
|
+
font-size: 1.0625rem;
|
|
475
|
+
font-weight: 500;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
teg-apple-pay-button .apple-pay-btn svg {
|
|
479
|
+
flex-shrink: 0;
|
|
480
|
+
display: block;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
teg-apple-pay-button .apple-pay-btn.wap-loading {
|
|
484
|
+
position: relative;
|
|
485
|
+
cursor: default;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
teg-apple-pay-button .apple-pay-btn.wap-loading svg {
|
|
489
|
+
visibility: hidden;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
teg-apple-pay-button .apple-pay-btn.wap-loading::after {
|
|
493
|
+
content: '';
|
|
494
|
+
position: absolute;
|
|
495
|
+
left: 50%;
|
|
496
|
+
top: 50%;
|
|
497
|
+
margin-left: -0.625rem;
|
|
498
|
+
margin-top: -0.625rem;
|
|
499
|
+
width: 1.25rem;
|
|
500
|
+
height: 1.25rem;
|
|
501
|
+
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
502
|
+
border-top-color: #fff;
|
|
503
|
+
border-radius: 50%;
|
|
504
|
+
animation: wap-button-spinner 0.8s linear infinite;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
@keyframes wap-button-spinner {
|
|
508
|
+
to {
|
|
509
|
+
transform: rotate(360deg);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
</style>
|
|
513
|
+
|
|
514
|
+
<div class="apple-pay-container" aria-label="Express checkout with Apple Pay">
|
|
515
|
+
<button
|
|
516
|
+
class="apple-pay-btn"
|
|
517
|
+
type="button"
|
|
518
|
+
aria-label="Pay with Apple Pay"
|
|
519
|
+
data-analytics="paywall:apple-pay-checkout"
|
|
520
|
+
>
|
|
521
|
+
<svg width="51" height="22" aria-label="Apple Pay" role="img">
|
|
522
|
+
<use href="#apple-pay-logo" />
|
|
523
|
+
</svg>
|
|
524
|
+
</button>
|
|
525
|
+
</div>
|
|
526
|
+
`;
|
|
443
527
|
|
|
444
528
|
// src/clients/payment-checkout/config.ts
|
|
445
529
|
var runtimeConfig = null;
|
|
@@ -453,27 +537,21 @@ var isAppleDeviceOrSafari = () => {
|
|
|
453
537
|
};
|
|
454
538
|
var bypassApplePayChecks = () => new URLSearchParams(window.location.search).get("BYPASS_APPLE_PAY_CHECKS") !== null && getApplePayConfig()?.debugBypassEnabled === true;
|
|
455
539
|
var isApplePayAvailable = async () => {
|
|
456
|
-
console.log("[web-apple-pay] isApplePayAvailable: checking availability");
|
|
457
540
|
if (bypassApplePayChecks()) {
|
|
458
|
-
console.log("[web-apple-pay] isApplePayAvailable: bypass active \u2192 true");
|
|
459
541
|
return true;
|
|
460
542
|
}
|
|
461
543
|
if (!isAppleDeviceOrSafari()) {
|
|
462
|
-
console.log("[web-apple-pay] isApplePayAvailable: not Apple device or Safari \u2192 false");
|
|
463
544
|
return false;
|
|
464
545
|
}
|
|
465
546
|
const applePaySession = window.ApplePaySession;
|
|
466
547
|
if (!applePaySession) {
|
|
467
|
-
console.log("[web-apple-pay] isApplePayAvailable: ApplePaySession not found on window \u2192 false");
|
|
468
548
|
return false;
|
|
469
549
|
}
|
|
470
550
|
try {
|
|
471
551
|
if (typeof applePaySession.canMakePayments !== "function") {
|
|
472
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePayments is not a function \u2192 false");
|
|
473
552
|
return false;
|
|
474
553
|
}
|
|
475
554
|
if (!applePaySession.canMakePayments()) {
|
|
476
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePayments() returned false \u2192 false");
|
|
477
555
|
return false;
|
|
478
556
|
}
|
|
479
557
|
const merchantId = getApplePayConfig()?.merchantId;
|
|
@@ -483,10 +561,7 @@ var isApplePayAvailable = async () => {
|
|
|
483
561
|
);
|
|
484
562
|
return false;
|
|
485
563
|
}
|
|
486
|
-
|
|
487
|
-
const result = await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
488
|
-
console.log("[web-apple-pay] isApplePayAvailable: canMakePaymentsWithActiveCard \u2192", result);
|
|
489
|
-
return result;
|
|
564
|
+
return await applePaySession.canMakePaymentsWithActiveCard(merchantId);
|
|
490
565
|
} catch (error) {
|
|
491
566
|
console.error("Error checking Apple Pay active card status:", error);
|
|
492
567
|
return false;
|
|
@@ -748,13 +823,6 @@ var MSG_CONTACT_VALIDATION_INTERNAL = "Could not validate Apple Pay contact";
|
|
|
748
823
|
|
|
749
824
|
// src/clients/payment-checkout/apple-session.ts
|
|
750
825
|
function startApplePaySession(config) {
|
|
751
|
-
console.log("[web-apple-pay] startApplePaySession", {
|
|
752
|
-
skuId: config.skuId,
|
|
753
|
-
countryCode: config.countryCode,
|
|
754
|
-
currencyCode: config.currencyCode,
|
|
755
|
-
totalAmount: config.totalAmount,
|
|
756
|
-
userLoggedIn: config.userLoggedIn
|
|
757
|
-
});
|
|
758
826
|
const paymentRequest = {
|
|
759
827
|
countryCode: config.countryCode,
|
|
760
828
|
currencyCode: config.currencyCode,
|
|
@@ -770,11 +838,9 @@ function startApplePaySession(config) {
|
|
|
770
838
|
};
|
|
771
839
|
const session = new ApplePaySession(3, paymentRequest);
|
|
772
840
|
session.onvalidatemerchant = async (event) => {
|
|
773
|
-
console.log("[web-apple-pay] onvalidatemerchant: received", { validationURL: event.validationURL });
|
|
774
841
|
try {
|
|
775
842
|
const validationHost = new URL(event.validationURL).host;
|
|
776
843
|
if (!validationHost.endsWith("apple.com")) {
|
|
777
|
-
console.warn("[web-apple-pay] onvalidatemerchant: invalid host, aborting", { validationHost });
|
|
778
844
|
trackFallbackCheckout(config.skuId);
|
|
779
845
|
session.abort();
|
|
780
846
|
window.location.href = redirectToCheckoutFallback({
|
|
@@ -785,13 +851,11 @@ function startApplePaySession(config) {
|
|
|
785
851
|
return;
|
|
786
852
|
}
|
|
787
853
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
788
|
-
console.log("[web-apple-pay] onvalidatemerchant: calling validateAppleMerchant");
|
|
789
854
|
const response = await validateAppleMerchant({
|
|
790
855
|
validationURL: event.validationURL,
|
|
791
856
|
recaptchaToken
|
|
792
857
|
});
|
|
793
858
|
if (!response?.merchantSession) {
|
|
794
|
-
console.warn("[web-apple-pay] onvalidatemerchant: no merchantSession in response, aborting");
|
|
795
859
|
trackFallbackCheckout(config.skuId);
|
|
796
860
|
session.abort();
|
|
797
861
|
window.location.href = redirectToCheckoutFallback({
|
|
@@ -801,10 +865,8 @@ function startApplePaySession(config) {
|
|
|
801
865
|
});
|
|
802
866
|
return;
|
|
803
867
|
}
|
|
804
|
-
console.log("[web-apple-pay] onvalidatemerchant: merchant validated successfully");
|
|
805
868
|
session.completeMerchantValidation(response.merchantSession);
|
|
806
|
-
} catch
|
|
807
|
-
console.error("[web-apple-pay] onvalidatemerchant: error during validation", err);
|
|
869
|
+
} catch {
|
|
808
870
|
trackFallbackCheckout(config.skuId);
|
|
809
871
|
session.abort();
|
|
810
872
|
window.location.href = redirectToCheckoutFallback({
|
|
@@ -815,15 +877,12 @@ function startApplePaySession(config) {
|
|
|
815
877
|
}
|
|
816
878
|
};
|
|
817
879
|
session.onpaymentauthorized = (event) => {
|
|
818
|
-
console.log("[web-apple-pay] onpaymentauthorized: payment authorized by user", { skuId: config.skuId });
|
|
819
880
|
trackWalletPaymentAuthorised(config.skuId);
|
|
820
881
|
config.onPaymentAuthorized(event, session);
|
|
821
882
|
};
|
|
822
883
|
session.oncancel = () => {
|
|
823
|
-
console.log("[web-apple-pay] oncancel: payment sheet cancelled by user", { skuId: config.skuId });
|
|
824
884
|
config.onCancel?.();
|
|
825
885
|
};
|
|
826
|
-
console.log("[web-apple-pay] startApplePaySession: beginning session");
|
|
827
886
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
828
887
|
session.begin();
|
|
829
888
|
}
|
|
@@ -1002,16 +1061,12 @@ function handleStructuredError(ctx) {
|
|
|
1002
1061
|
async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
1003
1062
|
const { skuId, returnUrl, queryString } = config;
|
|
1004
1063
|
let validatedEmail;
|
|
1005
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: starting", { skuId, userLoggedIn: sessionData.loggedIn });
|
|
1006
1064
|
try {
|
|
1007
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 1 \u2014 validating contacts");
|
|
1008
1065
|
validatedEmail = validateContacts(
|
|
1009
1066
|
event,
|
|
1010
1067
|
sessionData,
|
|
1011
1068
|
config.supportedBillingCountries
|
|
1012
1069
|
);
|
|
1013
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: contacts valid", { validatedEmail });
|
|
1014
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 2 \u2014 calling performPurchase", { skuId });
|
|
1015
1070
|
const result = await performPurchase({
|
|
1016
1071
|
applePayToken: event.payment.token,
|
|
1017
1072
|
skuId: config.skuId,
|
|
@@ -1021,8 +1076,6 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1021
1076
|
},
|
|
1022
1077
|
recaptchaTokens: config.recaptchaTokens
|
|
1023
1078
|
});
|
|
1024
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: performPurchase succeeded", { basketId: result.basketId });
|
|
1025
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: step 3 \u2014 completing payment and redirecting");
|
|
1026
1079
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1027
1080
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1028
1081
|
const userStatus = sessionData.loggedIn ? "lex" : "nex";
|
|
@@ -1035,25 +1088,17 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1035
1088
|
});
|
|
1036
1089
|
} catch (err) {
|
|
1037
1090
|
if (err instanceof ContactValidationError) {
|
|
1038
|
-
console.warn("[web-apple-pay] handlePaymentAuthorized: contact validation failed", err.errors);
|
|
1039
1091
|
failApplePaySession(session, err.errors);
|
|
1040
1092
|
return;
|
|
1041
1093
|
}
|
|
1042
1094
|
console.error("[web-apple-pay] onpaymentauthorized error", { skuId, err });
|
|
1043
1095
|
const walletError = parseWalletApiError(err);
|
|
1044
|
-
console.error("[web-apple-pay] handlePaymentAuthorized: BFF/network error", {
|
|
1045
|
-
skuId,
|
|
1046
|
-
errorType: walletError?.errorType ?? "UNKNOWN",
|
|
1047
|
-
message: walletError?.message
|
|
1048
|
-
});
|
|
1049
1096
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1050
1097
|
failApplePaySession(session);
|
|
1051
1098
|
if (!walletError) {
|
|
1052
|
-
console.warn("[web-apple-pay] handlePaymentAuthorized: unstructured error, using generic fallback");
|
|
1053
1099
|
redirectFallback(skuId, returnUrl, { inlineError: MSG_PAYMENT_GENERIC_ERROR });
|
|
1054
1100
|
return;
|
|
1055
1101
|
}
|
|
1056
|
-
console.log("[web-apple-pay] handlePaymentAuthorized: dispatching structured error handler", { errorType: walletError.errorType });
|
|
1057
1102
|
handleStructuredError({ skuId, returnUrl, validatedEmail, walletError });
|
|
1058
1103
|
}
|
|
1059
1104
|
}
|
|
@@ -1441,14 +1486,16 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1441
1486
|
async #run() {
|
|
1442
1487
|
try {
|
|
1443
1488
|
if (await this.#checkAvailability()) return;
|
|
1444
|
-
if (await this.#checkSession()) return;
|
|
1445
1489
|
if (this.#checkOffer()) return;
|
|
1490
|
+
this.#renderTemplate();
|
|
1491
|
+
if (await this.#checkSession()) return;
|
|
1446
1492
|
this.#captureReturnUrl();
|
|
1447
1493
|
this.#computeBillingCountries();
|
|
1448
1494
|
if (await this.#fetchAndCacheOptions()) return;
|
|
1449
|
-
this.#
|
|
1495
|
+
this.#finishLoading();
|
|
1450
1496
|
} catch {
|
|
1451
1497
|
this.style.display = "none";
|
|
1498
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
1452
1499
|
} finally {
|
|
1453
1500
|
this.#rendering = false;
|
|
1454
1501
|
}
|
|
@@ -1470,6 +1517,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1470
1517
|
this.#sessionData = await this.#fetchSession();
|
|
1471
1518
|
if (this.#sessionData?.loggedIn && (this.#sessionData.userType === "active-subscription" || this.#sessionData.userType === "b2b-user")) {
|
|
1472
1519
|
this.style.display = "none";
|
|
1520
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
1473
1521
|
return true;
|
|
1474
1522
|
}
|
|
1475
1523
|
return false;
|
|
@@ -1505,6 +1553,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1505
1553
|
this.#paymentOptions = await this.#fetchPaymentOptions();
|
|
1506
1554
|
if (!this.#paymentOptions?.apple_pay) {
|
|
1507
1555
|
this.style.display = "none";
|
|
1556
|
+
this.querySelector(SEL_CONTAINER)?.remove();
|
|
1508
1557
|
return true;
|
|
1509
1558
|
}
|
|
1510
1559
|
return false;
|
|
@@ -1516,13 +1565,13 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1516
1565
|
const template = document.createElement("template");
|
|
1517
1566
|
template.innerHTML = apple_pay_button_template_default;
|
|
1518
1567
|
this.appendChild(template.content.cloneNode(true));
|
|
1519
|
-
this.#updateDisabledState();
|
|
1520
|
-
trackExpressWalletImpression(this.#offer.sku_id, {
|
|
1521
|
-
entryPoint: this.#entryPoint,
|
|
1522
|
-
country: this.#country
|
|
1523
|
-
});
|
|
1524
1568
|
const button = this.querySelector(SEL_BUTTON);
|
|
1569
|
+
button.classList.add("wap-loading");
|
|
1570
|
+
button.setAttribute("disabled", "");
|
|
1571
|
+
button.setAttribute("aria-disabled", "true");
|
|
1572
|
+
button.setAttribute("aria-busy", "true");
|
|
1525
1573
|
button.addEventListener("click", () => {
|
|
1574
|
+
if (button.classList.contains("wap-loading")) return;
|
|
1526
1575
|
this.#openModal();
|
|
1527
1576
|
});
|
|
1528
1577
|
this.addEventListener("apple-pay-confirmed", (event) => {
|
|
@@ -1530,6 +1579,17 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1530
1579
|
this.#initiateApplePay(skuId, country);
|
|
1531
1580
|
});
|
|
1532
1581
|
}
|
|
1582
|
+
#finishLoading() {
|
|
1583
|
+
const button = this.querySelector(SEL_BUTTON);
|
|
1584
|
+
if (button) {
|
|
1585
|
+
button.classList.remove("wap-loading");
|
|
1586
|
+
this.#updateDisabledState();
|
|
1587
|
+
trackExpressWalletImpression(this.#offer.sku_id, {
|
|
1588
|
+
entryPoint: this.#entryPoint,
|
|
1589
|
+
country: this.#country
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1533
1593
|
// ─── Private helpers ────────────────────────────────────────────────────────
|
|
1534
1594
|
#updateDisabledState() {
|
|
1535
1595
|
const button = this.querySelector(SEL_BUTTON);
|
|
@@ -1537,6 +1597,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
1537
1597
|
if (this.#offer) {
|
|
1538
1598
|
button.removeAttribute("disabled");
|
|
1539
1599
|
button.removeAttribute("aria-disabled");
|
|
1600
|
+
button.removeAttribute("aria-busy");
|
|
1540
1601
|
return;
|
|
1541
1602
|
}
|
|
1542
1603
|
button.setAttribute("disabled", "");
|