@economist/web-apple-pay 0.1.3-beta.45 → 0.1.3-beta.47
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 +50 -97
- package/dist/src/apple-pay-button.js +50 -97
- package/dist/src/apple-pay-button.stories.js +50 -97
- package/dist/src/apple-pay-modal.d.ts +7 -9
- package/dist/src/apple-pay-modal.js +18 -35
- package/dist/src/apple-pay-modal.stories.js +50 -97
- package/dist/src/clients/payment-checkout/apple-session.d.ts +1 -0
- package/dist/src/clients/payment-checkout/apple-session.js +9 -18
- package/dist/src/clients/payment-checkout/payment-handler.d.ts +1 -5
- package/dist/src/clients/payment-checkout/payment-handler.js +1 -21
- package/dist/src/clients/payment-checkout/types.d.ts +1 -5
- package/dist/src/index.js +50 -97
- package/dist/src/utils/recaptcha.d.ts +8 -6
- package/dist/src/utils/recaptcha.js +4 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -576,9 +576,7 @@ var loadRecaptchaScript = (siteKey) => {
|
|
|
576
576
|
if (existingScript) {
|
|
577
577
|
existingScript.addEventListener("load", () => resolve());
|
|
578
578
|
existingScript.addEventListener("error", () => {
|
|
579
|
-
|
|
580
|
-
console.error("[web-apple-pay] reCAPTCHA script load failed (existing script)", error);
|
|
581
|
-
reject(error);
|
|
579
|
+
reject(new Error("Failed to load existing reCAPTCHA script"));
|
|
582
580
|
});
|
|
583
581
|
return;
|
|
584
582
|
}
|
|
@@ -588,9 +586,7 @@ var loadRecaptchaScript = (siteKey) => {
|
|
|
588
586
|
script.defer = true;
|
|
589
587
|
script.onload = () => resolve();
|
|
590
588
|
script.onerror = () => {
|
|
591
|
-
|
|
592
|
-
console.error("[web-apple-pay] reCAPTCHA script load failed (new script)", error);
|
|
593
|
-
reject(error);
|
|
589
|
+
reject(new Error("Failed to load reCAPTCHA script"));
|
|
594
590
|
};
|
|
595
591
|
document.head.appendChild(script);
|
|
596
592
|
});
|
|
@@ -609,20 +605,8 @@ var getRecaptchaToken = async (action) => {
|
|
|
609
605
|
});
|
|
610
606
|
});
|
|
611
607
|
};
|
|
612
|
-
var getPurchaseRecaptchaTokens =
|
|
613
|
-
|
|
614
|
-
try {
|
|
615
|
-
const [checkEligibility, newBasket, registerUser] = await Promise.all([
|
|
616
|
-
getRecaptchaToken("checkEligibility"),
|
|
617
|
-
getRecaptchaToken("newBasket"),
|
|
618
|
-
getRecaptchaToken("registerUser")
|
|
619
|
-
]);
|
|
620
|
-
console.info("[web-apple-pay] All reCAPTCHA tokens acquired successfully");
|
|
621
|
-
return { checkEligibility, newBasket, registerUser };
|
|
622
|
-
} catch (error) {
|
|
623
|
-
console.error("[web-apple-pay] Failed to acquire one or more reCAPTCHA tokens", error);
|
|
624
|
-
throw error;
|
|
625
|
-
}
|
|
608
|
+
var getPurchaseRecaptchaTokens = () => {
|
|
609
|
+
return getRecaptchaToken("walletPurchase");
|
|
626
610
|
};
|
|
627
611
|
|
|
628
612
|
// src/apple-pay-modal.ts
|
|
@@ -849,10 +833,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
849
833
|
#keyHandler = null;
|
|
850
834
|
#isClosed = false;
|
|
851
835
|
/**
|
|
852
|
-
* Callback invoked from the CTA click handler
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
* new ApplePaySession()
|
|
836
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
837
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
838
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
839
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
840
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
841
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
856
842
|
*/
|
|
857
843
|
onConfirm = null;
|
|
858
844
|
set offer(value) {
|
|
@@ -957,20 +943,16 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
957
943
|
return;
|
|
958
944
|
}
|
|
959
945
|
errorElement?.classList.remove("apm__error--visible");
|
|
946
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
960
947
|
cta.disabled = true;
|
|
961
948
|
cta.setAttribute("aria-busy", "true");
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
if (this.#isClosed) {
|
|
966
|
-
console.warn("[web-apple-pay] Modal was closed before reCAPTCHA resolved \u2014 aborting Apple Pay initiation");
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
970
|
-
this.onConfirm?.(recaptchaTokens);
|
|
949
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
950
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
951
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
971
952
|
this.close();
|
|
972
|
-
}
|
|
973
|
-
|
|
953
|
+
}
|
|
954
|
+
recaptchaTokensPromise.catch(() => {
|
|
955
|
+
if (this.#isClosed) return;
|
|
974
956
|
cta.disabled = false;
|
|
975
957
|
cta.removeAttribute("aria-busy");
|
|
976
958
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -995,6 +977,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
995
977
|
);
|
|
996
978
|
}
|
|
997
979
|
open() {
|
|
980
|
+
this.#isClosed = false;
|
|
998
981
|
this.#triggerElement = document.activeElement;
|
|
999
982
|
document.body.appendChild(this);
|
|
1000
983
|
this.#keyHandler = (event) => {
|
|
@@ -1381,28 +1364,22 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1381
1364
|
try {
|
|
1382
1365
|
const validationHost = new URL(event.validationURL).host;
|
|
1383
1366
|
if (!validationHost.endsWith("apple.com")) {
|
|
1384
|
-
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
1385
1367
|
session.abort();
|
|
1386
1368
|
redirectToFallbackCheckout(config);
|
|
1387
1369
|
return;
|
|
1388
1370
|
}
|
|
1389
|
-
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
1390
1371
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
1391
|
-
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
1392
1372
|
const response = await validateAppleMerchant({
|
|
1393
1373
|
validationURL: event.validationURL,
|
|
1394
1374
|
recaptchaToken
|
|
1395
1375
|
});
|
|
1396
1376
|
if (!response?.merchantSession) {
|
|
1397
|
-
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
1398
1377
|
session.abort();
|
|
1399
1378
|
redirectToFallbackCheckout(config);
|
|
1400
1379
|
return;
|
|
1401
1380
|
}
|
|
1402
|
-
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
1403
1381
|
session.completeMerchantValidation(response.merchantSession);
|
|
1404
|
-
} catch
|
|
1405
|
-
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
1382
|
+
} catch {
|
|
1406
1383
|
session.abort();
|
|
1407
1384
|
redirectToFallbackCheckout(config);
|
|
1408
1385
|
}
|
|
@@ -1423,22 +1400,22 @@ function startApplePaySession(config) {
|
|
|
1423
1400
|
};
|
|
1424
1401
|
try {
|
|
1425
1402
|
const session = new ApplePaySession(3, paymentRequest);
|
|
1426
|
-
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
1427
1403
|
session.onvalidatemerchant = async (event) => {
|
|
1428
1404
|
await handleMerchantValidation(event, session, config);
|
|
1429
1405
|
};
|
|
1430
1406
|
session.onpaymentauthorized = (event) => {
|
|
1431
1407
|
trackWalletPaymentAuthorised(config.skuId);
|
|
1432
|
-
config.onPaymentAuthorized(event, session)
|
|
1408
|
+
config.onPaymentAuthorized(event, session).catch(() => {
|
|
1409
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
1410
|
+
redirectToFallbackCheckout(config);
|
|
1411
|
+
});
|
|
1433
1412
|
};
|
|
1434
1413
|
session.oncancel = () => {
|
|
1435
1414
|
config.onCancel?.();
|
|
1436
1415
|
};
|
|
1437
1416
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1438
1417
|
session.begin();
|
|
1439
|
-
|
|
1440
|
-
} catch (error) {
|
|
1441
|
-
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
1418
|
+
} catch {
|
|
1442
1419
|
redirectToFallbackCheckout(config);
|
|
1443
1420
|
}
|
|
1444
1421
|
}
|
|
@@ -1620,24 +1597,12 @@ function handleStructuredError(ctx) {
|
|
|
1620
1597
|
async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
1621
1598
|
const { skuId, returnUrl, country } = config;
|
|
1622
1599
|
let validatedEmail;
|
|
1623
|
-
console.info("[web-apple-pay] Payment authorised \u2014 starting purchase flow", {
|
|
1624
|
-
skuId,
|
|
1625
|
-
country,
|
|
1626
|
-
loggedIn: sessionData.loggedIn,
|
|
1627
|
-
userType: sessionData.userType,
|
|
1628
|
-
recaptchaTokensPresent: {
|
|
1629
|
-
checkEligibility: !!config.recaptchaTokens.checkEligibility,
|
|
1630
|
-
newBasket: !!config.recaptchaTokens.newBasket,
|
|
1631
|
-
registerUser: !!config.recaptchaTokens.registerUser
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
1600
|
try {
|
|
1635
1601
|
validatedEmail = validateContacts(
|
|
1636
1602
|
event,
|
|
1637
1603
|
sessionData,
|
|
1638
1604
|
config.supportedBillingCountries
|
|
1639
1605
|
);
|
|
1640
|
-
console.info("[web-apple-pay] Contact validation passed \u2014 calling purchase API");
|
|
1641
1606
|
const result = await performPurchase({
|
|
1642
1607
|
applePayToken: event.payment.token,
|
|
1643
1608
|
skuId: config.skuId,
|
|
@@ -1646,9 +1611,8 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1646
1611
|
...event.payment.shippingContact && {
|
|
1647
1612
|
shippingContact: event.payment.shippingContact
|
|
1648
1613
|
},
|
|
1649
|
-
|
|
1614
|
+
recaptchaToken: config.recaptchaToken
|
|
1650
1615
|
});
|
|
1651
|
-
console.info("[web-apple-pay] Purchase API succeeded", { skuId, basketId: result.basketId });
|
|
1652
1616
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1653
1617
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1654
1618
|
const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
|
|
@@ -1663,17 +1627,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1663
1627
|
});
|
|
1664
1628
|
} catch (err) {
|
|
1665
1629
|
if (err instanceof ContactValidationError) {
|
|
1666
|
-
console.warn("[web-apple-pay] Contact validation failed \u2014 keeping Apple Pay sheet open", err);
|
|
1667
1630
|
failApplePaySession(session, err.errors);
|
|
1668
1631
|
return;
|
|
1669
1632
|
}
|
|
1670
1633
|
const walletError = parseWalletApiError(err);
|
|
1671
|
-
console.error("[web-apple-pay] Purchase API failed", {
|
|
1672
|
-
skuId,
|
|
1673
|
-
errorType: walletError?.errorType ?? "UNKNOWN",
|
|
1674
|
-
message: walletError?.message,
|
|
1675
|
-
err
|
|
1676
|
-
});
|
|
1677
1634
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1678
1635
|
failApplePaySession(session);
|
|
1679
1636
|
if (!walletError) {
|
|
@@ -2204,11 +2161,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2204
2161
|
}
|
|
2205
2162
|
const skuId = this.#offer.sku_id;
|
|
2206
2163
|
const country = this.#country;
|
|
2207
|
-
modal.onConfirm = (
|
|
2208
|
-
|
|
2209
|
-
this.#initiateApplePay(skuId, country, recaptchaTokens);
|
|
2164
|
+
modal.onConfirm = (recaptchaTokensPromise) => {
|
|
2165
|
+
this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
|
|
2210
2166
|
};
|
|
2211
|
-
console.info("[web-apple-pay] Opening Apple Pay modal", { skuId, country });
|
|
2212
2167
|
modal.open();
|
|
2213
2168
|
}
|
|
2214
2169
|
async #fetchSession() {
|
|
@@ -2230,7 +2185,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2230
2185
|
return null;
|
|
2231
2186
|
}
|
|
2232
2187
|
}
|
|
2233
|
-
#buildSessionParams(skuId, country, sessionData, paymentOptions,
|
|
2188
|
+
#buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
|
|
2234
2189
|
const offer = this.#offer;
|
|
2235
2190
|
const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
|
|
2236
2191
|
return {
|
|
@@ -2242,43 +2197,41 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2242
2197
|
skuId,
|
|
2243
2198
|
returnUrl: this.#returnUrl,
|
|
2244
2199
|
userLoggedIn: sessionData.loggedIn,
|
|
2245
|
-
onPaymentAuthorized: (event, session) =>
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2200
|
+
onPaymentAuthorized: async (event, session) => {
|
|
2201
|
+
const recaptchaToken = await recaptchaTokensPromise;
|
|
2202
|
+
return handlePaymentAuthorized(event, session, sessionData, {
|
|
2203
|
+
skuId,
|
|
2204
|
+
country,
|
|
2205
|
+
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2206
|
+
returnUrl: this.#returnUrl,
|
|
2207
|
+
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2208
|
+
recaptchaToken
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2253
2211
|
};
|
|
2254
2212
|
}
|
|
2255
|
-
#initiateApplePay(skuId, country,
|
|
2213
|
+
#initiateApplePay(skuId, country, recaptchaTokensPromise) {
|
|
2256
2214
|
try {
|
|
2257
2215
|
const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
|
|
2258
2216
|
const paymentOptions = this.#paymentOptions;
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
loggedIn: sessionData.loggedIn,
|
|
2263
|
-
userType: sessionData.userType,
|
|
2264
|
-
hasPaymentOptions: !!paymentOptions,
|
|
2265
|
-
recaptchaTokensPresent: {
|
|
2266
|
-
checkEligibility: !!recaptchaTokens.checkEligibility,
|
|
2267
|
-
newBasket: !!recaptchaTokens.newBasket,
|
|
2268
|
-
registerUser: !!recaptchaTokens.registerUser
|
|
2269
|
-
}
|
|
2270
|
-
});
|
|
2217
|
+
if (!paymentOptions) {
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2271
2220
|
startApplePaySession(
|
|
2272
2221
|
this.#buildSessionParams(
|
|
2273
2222
|
skuId,
|
|
2274
2223
|
country,
|
|
2275
2224
|
sessionData,
|
|
2276
2225
|
paymentOptions,
|
|
2277
|
-
|
|
2226
|
+
recaptchaTokensPromise
|
|
2278
2227
|
)
|
|
2279
2228
|
);
|
|
2280
|
-
} catch
|
|
2281
|
-
|
|
2229
|
+
} catch {
|
|
2230
|
+
redirectToFallbackCheckout({
|
|
2231
|
+
returnUrl: this.#returnUrl,
|
|
2232
|
+
skuId,
|
|
2233
|
+
countryCode: country
|
|
2234
|
+
});
|
|
2282
2235
|
}
|
|
2283
2236
|
}
|
|
2284
2237
|
};
|
|
@@ -571,9 +571,7 @@ var loadRecaptchaScript = (siteKey) => {
|
|
|
571
571
|
if (existingScript) {
|
|
572
572
|
existingScript.addEventListener("load", () => resolve());
|
|
573
573
|
existingScript.addEventListener("error", () => {
|
|
574
|
-
|
|
575
|
-
console.error("[web-apple-pay] reCAPTCHA script load failed (existing script)", error);
|
|
576
|
-
reject(error);
|
|
574
|
+
reject(new Error("Failed to load existing reCAPTCHA script"));
|
|
577
575
|
});
|
|
578
576
|
return;
|
|
579
577
|
}
|
|
@@ -583,9 +581,7 @@ var loadRecaptchaScript = (siteKey) => {
|
|
|
583
581
|
script.defer = true;
|
|
584
582
|
script.onload = () => resolve();
|
|
585
583
|
script.onerror = () => {
|
|
586
|
-
|
|
587
|
-
console.error("[web-apple-pay] reCAPTCHA script load failed (new script)", error);
|
|
588
|
-
reject(error);
|
|
584
|
+
reject(new Error("Failed to load reCAPTCHA script"));
|
|
589
585
|
};
|
|
590
586
|
document.head.appendChild(script);
|
|
591
587
|
});
|
|
@@ -604,20 +600,8 @@ var getRecaptchaToken = async (action) => {
|
|
|
604
600
|
});
|
|
605
601
|
});
|
|
606
602
|
};
|
|
607
|
-
var getPurchaseRecaptchaTokens =
|
|
608
|
-
|
|
609
|
-
try {
|
|
610
|
-
const [checkEligibility, newBasket, registerUser] = await Promise.all([
|
|
611
|
-
getRecaptchaToken("checkEligibility"),
|
|
612
|
-
getRecaptchaToken("newBasket"),
|
|
613
|
-
getRecaptchaToken("registerUser")
|
|
614
|
-
]);
|
|
615
|
-
console.info("[web-apple-pay] All reCAPTCHA tokens acquired successfully");
|
|
616
|
-
return { checkEligibility, newBasket, registerUser };
|
|
617
|
-
} catch (error) {
|
|
618
|
-
console.error("[web-apple-pay] Failed to acquire one or more reCAPTCHA tokens", error);
|
|
619
|
-
throw error;
|
|
620
|
-
}
|
|
603
|
+
var getPurchaseRecaptchaTokens = () => {
|
|
604
|
+
return getRecaptchaToken("walletPurchase");
|
|
621
605
|
};
|
|
622
606
|
|
|
623
607
|
// src/apple-pay-modal.ts
|
|
@@ -844,10 +828,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
844
828
|
#keyHandler = null;
|
|
845
829
|
#isClosed = false;
|
|
846
830
|
/**
|
|
847
|
-
* Callback invoked from the CTA click handler
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* new ApplePaySession()
|
|
831
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
832
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
833
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
834
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
835
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
836
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
851
837
|
*/
|
|
852
838
|
onConfirm = null;
|
|
853
839
|
set offer(value) {
|
|
@@ -952,20 +938,16 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
952
938
|
return;
|
|
953
939
|
}
|
|
954
940
|
errorElement?.classList.remove("apm__error--visible");
|
|
941
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
955
942
|
cta.disabled = true;
|
|
956
943
|
cta.setAttribute("aria-busy", "true");
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
if (this.#isClosed) {
|
|
961
|
-
console.warn("[web-apple-pay] Modal was closed before reCAPTCHA resolved \u2014 aborting Apple Pay initiation");
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
965
|
-
this.onConfirm?.(recaptchaTokens);
|
|
944
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
945
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
946
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
966
947
|
this.close();
|
|
967
|
-
}
|
|
968
|
-
|
|
948
|
+
}
|
|
949
|
+
recaptchaTokensPromise.catch(() => {
|
|
950
|
+
if (this.#isClosed) return;
|
|
969
951
|
cta.disabled = false;
|
|
970
952
|
cta.removeAttribute("aria-busy");
|
|
971
953
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -990,6 +972,7 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
990
972
|
);
|
|
991
973
|
}
|
|
992
974
|
open() {
|
|
975
|
+
this.#isClosed = false;
|
|
993
976
|
this.#triggerElement = document.activeElement;
|
|
994
977
|
document.body.appendChild(this);
|
|
995
978
|
this.#keyHandler = (event) => {
|
|
@@ -1376,28 +1359,22 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1376
1359
|
try {
|
|
1377
1360
|
const validationHost = new URL(event.validationURL).host;
|
|
1378
1361
|
if (!validationHost.endsWith("apple.com")) {
|
|
1379
|
-
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
1380
1362
|
session.abort();
|
|
1381
1363
|
redirectToFallbackCheckout(config);
|
|
1382
1364
|
return;
|
|
1383
1365
|
}
|
|
1384
|
-
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
1385
1366
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
1386
|
-
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
1387
1367
|
const response = await validateAppleMerchant({
|
|
1388
1368
|
validationURL: event.validationURL,
|
|
1389
1369
|
recaptchaToken
|
|
1390
1370
|
});
|
|
1391
1371
|
if (!response?.merchantSession) {
|
|
1392
|
-
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
1393
1372
|
session.abort();
|
|
1394
1373
|
redirectToFallbackCheckout(config);
|
|
1395
1374
|
return;
|
|
1396
1375
|
}
|
|
1397
|
-
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
1398
1376
|
session.completeMerchantValidation(response.merchantSession);
|
|
1399
|
-
} catch
|
|
1400
|
-
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
1377
|
+
} catch {
|
|
1401
1378
|
session.abort();
|
|
1402
1379
|
redirectToFallbackCheckout(config);
|
|
1403
1380
|
}
|
|
@@ -1418,22 +1395,22 @@ function startApplePaySession(config) {
|
|
|
1418
1395
|
};
|
|
1419
1396
|
try {
|
|
1420
1397
|
const session = new ApplePaySession(3, paymentRequest);
|
|
1421
|
-
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
1422
1398
|
session.onvalidatemerchant = async (event) => {
|
|
1423
1399
|
await handleMerchantValidation(event, session, config);
|
|
1424
1400
|
};
|
|
1425
1401
|
session.onpaymentauthorized = (event) => {
|
|
1426
1402
|
trackWalletPaymentAuthorised(config.skuId);
|
|
1427
|
-
config.onPaymentAuthorized(event, session)
|
|
1403
|
+
config.onPaymentAuthorized(event, session).catch(() => {
|
|
1404
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
1405
|
+
redirectToFallbackCheckout(config);
|
|
1406
|
+
});
|
|
1428
1407
|
};
|
|
1429
1408
|
session.oncancel = () => {
|
|
1430
1409
|
config.onCancel?.();
|
|
1431
1410
|
};
|
|
1432
1411
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1433
1412
|
session.begin();
|
|
1434
|
-
|
|
1435
|
-
} catch (error) {
|
|
1436
|
-
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
1413
|
+
} catch {
|
|
1437
1414
|
redirectToFallbackCheckout(config);
|
|
1438
1415
|
}
|
|
1439
1416
|
}
|
|
@@ -1615,24 +1592,12 @@ function handleStructuredError(ctx) {
|
|
|
1615
1592
|
async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
1616
1593
|
const { skuId, returnUrl, country } = config;
|
|
1617
1594
|
let validatedEmail;
|
|
1618
|
-
console.info("[web-apple-pay] Payment authorised \u2014 starting purchase flow", {
|
|
1619
|
-
skuId,
|
|
1620
|
-
country,
|
|
1621
|
-
loggedIn: sessionData.loggedIn,
|
|
1622
|
-
userType: sessionData.userType,
|
|
1623
|
-
recaptchaTokensPresent: {
|
|
1624
|
-
checkEligibility: !!config.recaptchaTokens.checkEligibility,
|
|
1625
|
-
newBasket: !!config.recaptchaTokens.newBasket,
|
|
1626
|
-
registerUser: !!config.recaptchaTokens.registerUser
|
|
1627
|
-
}
|
|
1628
|
-
});
|
|
1629
1595
|
try {
|
|
1630
1596
|
validatedEmail = validateContacts(
|
|
1631
1597
|
event,
|
|
1632
1598
|
sessionData,
|
|
1633
1599
|
config.supportedBillingCountries
|
|
1634
1600
|
);
|
|
1635
|
-
console.info("[web-apple-pay] Contact validation passed \u2014 calling purchase API");
|
|
1636
1601
|
const result = await performPurchase({
|
|
1637
1602
|
applePayToken: event.payment.token,
|
|
1638
1603
|
skuId: config.skuId,
|
|
@@ -1641,9 +1606,8 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1641
1606
|
...event.payment.shippingContact && {
|
|
1642
1607
|
shippingContact: event.payment.shippingContact
|
|
1643
1608
|
},
|
|
1644
|
-
|
|
1609
|
+
recaptchaToken: config.recaptchaToken
|
|
1645
1610
|
});
|
|
1646
|
-
console.info("[web-apple-pay] Purchase API succeeded", { skuId, basketId: result.basketId });
|
|
1647
1611
|
trackWalletPaymentSuccess(skuId, result.basketId);
|
|
1648
1612
|
session.completePayment({ status: ApplePaySession.STATUS_SUCCESS });
|
|
1649
1613
|
const successEmail = sessionData.loggedIn ? sessionData.emailAddress : validatedEmail;
|
|
@@ -1658,17 +1622,10 @@ async function handlePaymentAuthorized(event, session, sessionData, config) {
|
|
|
1658
1622
|
});
|
|
1659
1623
|
} catch (err) {
|
|
1660
1624
|
if (err instanceof ContactValidationError) {
|
|
1661
|
-
console.warn("[web-apple-pay] Contact validation failed \u2014 keeping Apple Pay sheet open", err);
|
|
1662
1625
|
failApplePaySession(session, err.errors);
|
|
1663
1626
|
return;
|
|
1664
1627
|
}
|
|
1665
1628
|
const walletError = parseWalletApiError(err);
|
|
1666
|
-
console.error("[web-apple-pay] Purchase API failed", {
|
|
1667
|
-
skuId,
|
|
1668
|
-
errorType: walletError?.errorType ?? "UNKNOWN",
|
|
1669
|
-
message: walletError?.message,
|
|
1670
|
-
err
|
|
1671
|
-
});
|
|
1672
1629
|
trackWalletPaymentFailure(skuId, walletError?.errorType ?? "UNKNOWN");
|
|
1673
1630
|
failApplePaySession(session);
|
|
1674
1631
|
if (!walletError) {
|
|
@@ -2199,11 +2156,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2199
2156
|
}
|
|
2200
2157
|
const skuId = this.#offer.sku_id;
|
|
2201
2158
|
const country = this.#country;
|
|
2202
|
-
modal.onConfirm = (
|
|
2203
|
-
|
|
2204
|
-
this.#initiateApplePay(skuId, country, recaptchaTokens);
|
|
2159
|
+
modal.onConfirm = (recaptchaTokensPromise) => {
|
|
2160
|
+
this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
|
|
2205
2161
|
};
|
|
2206
|
-
console.info("[web-apple-pay] Opening Apple Pay modal", { skuId, country });
|
|
2207
2162
|
modal.open();
|
|
2208
2163
|
}
|
|
2209
2164
|
async #fetchSession() {
|
|
@@ -2225,7 +2180,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2225
2180
|
return null;
|
|
2226
2181
|
}
|
|
2227
2182
|
}
|
|
2228
|
-
#buildSessionParams(skuId, country, sessionData, paymentOptions,
|
|
2183
|
+
#buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
|
|
2229
2184
|
const offer = this.#offer;
|
|
2230
2185
|
const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
|
|
2231
2186
|
return {
|
|
@@ -2237,43 +2192,41 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2237
2192
|
skuId,
|
|
2238
2193
|
returnUrl: this.#returnUrl,
|
|
2239
2194
|
userLoggedIn: sessionData.loggedIn,
|
|
2240
|
-
onPaymentAuthorized: (event, session) =>
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2195
|
+
onPaymentAuthorized: async (event, session) => {
|
|
2196
|
+
const recaptchaToken = await recaptchaTokensPromise;
|
|
2197
|
+
return handlePaymentAuthorized(event, session, sessionData, {
|
|
2198
|
+
skuId,
|
|
2199
|
+
country,
|
|
2200
|
+
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2201
|
+
returnUrl: this.#returnUrl,
|
|
2202
|
+
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2203
|
+
recaptchaToken
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2248
2206
|
};
|
|
2249
2207
|
}
|
|
2250
|
-
#initiateApplePay(skuId, country,
|
|
2208
|
+
#initiateApplePay(skuId, country, recaptchaTokensPromise) {
|
|
2251
2209
|
try {
|
|
2252
2210
|
const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
|
|
2253
2211
|
const paymentOptions = this.#paymentOptions;
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
loggedIn: sessionData.loggedIn,
|
|
2258
|
-
userType: sessionData.userType,
|
|
2259
|
-
hasPaymentOptions: !!paymentOptions,
|
|
2260
|
-
recaptchaTokensPresent: {
|
|
2261
|
-
checkEligibility: !!recaptchaTokens.checkEligibility,
|
|
2262
|
-
newBasket: !!recaptchaTokens.newBasket,
|
|
2263
|
-
registerUser: !!recaptchaTokens.registerUser
|
|
2264
|
-
}
|
|
2265
|
-
});
|
|
2212
|
+
if (!paymentOptions) {
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2266
2215
|
startApplePaySession(
|
|
2267
2216
|
this.#buildSessionParams(
|
|
2268
2217
|
skuId,
|
|
2269
2218
|
country,
|
|
2270
2219
|
sessionData,
|
|
2271
2220
|
paymentOptions,
|
|
2272
|
-
|
|
2221
|
+
recaptchaTokensPromise
|
|
2273
2222
|
)
|
|
2274
2223
|
);
|
|
2275
|
-
} catch
|
|
2276
|
-
|
|
2224
|
+
} catch {
|
|
2225
|
+
redirectToFallbackCheckout({
|
|
2226
|
+
returnUrl: this.#returnUrl,
|
|
2227
|
+
skuId,
|
|
2228
|
+
countryCode: country
|
|
2229
|
+
});
|
|
2277
2230
|
}
|
|
2278
2231
|
}
|
|
2279
2232
|
};
|