@economist/web-apple-pay 0.1.3-beta.44 → 0.1.3-beta.46
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 +51 -35
- package/dist/src/apple-pay-button.js +51 -35
- package/dist/src/apple-pay-button.stories.js +51 -35
- package/dist/src/apple-pay-modal.d.ts +7 -5
- package/dist/src/apple-pay-modal.js +14 -13
- package/dist/src/apple-pay-modal.stories.js +51 -35
- package/dist/src/clients/payment-checkout/apple-session.js +16 -3
- package/dist/src/index.js +51 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -849,10 +849,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
849
849
|
#keyHandler = null;
|
|
850
850
|
#isClosed = false;
|
|
851
851
|
/**
|
|
852
|
-
* Callback invoked from the CTA click handler
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
* new ApplePaySession()
|
|
852
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
853
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
854
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
855
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
856
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
857
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
856
858
|
*/
|
|
857
859
|
onConfirm = null;
|
|
858
860
|
set offer(value) {
|
|
@@ -957,20 +959,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
957
959
|
return;
|
|
958
960
|
}
|
|
959
961
|
errorElement?.classList.remove("apm__error--visible");
|
|
962
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
960
963
|
cta.disabled = true;
|
|
961
964
|
cta.setAttribute("aria-busy", "true");
|
|
962
965
|
console.info("[web-apple-pay] CTA clicked \u2014 fetching reCAPTCHA tokens");
|
|
963
|
-
getPurchaseRecaptchaTokens()
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
970
|
-
this.onConfirm?.(recaptchaTokens);
|
|
966
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
967
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
968
|
+
console.info("[web-apple-pay] Calling onConfirm synchronously to initiate Apple Pay session");
|
|
969
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
971
970
|
this.close();
|
|
972
|
-
}
|
|
971
|
+
}
|
|
972
|
+
recaptchaTokensPromise.catch((error) => {
|
|
973
973
|
console.error("[web-apple-pay] reCAPTCHA token fetch failed", error);
|
|
974
|
+
if (this.#isClosed) return;
|
|
974
975
|
cta.disabled = false;
|
|
975
976
|
cta.removeAttribute("aria-busy");
|
|
976
977
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -1381,22 +1382,28 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1381
1382
|
try {
|
|
1382
1383
|
const validationHost = new URL(event.validationURL).host;
|
|
1383
1384
|
if (!validationHost.endsWith("apple.com")) {
|
|
1385
|
+
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
1384
1386
|
session.abort();
|
|
1385
1387
|
redirectToFallbackCheckout(config);
|
|
1386
1388
|
return;
|
|
1387
1389
|
}
|
|
1390
|
+
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
1388
1391
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
1392
|
+
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
1389
1393
|
const response = await validateAppleMerchant({
|
|
1390
1394
|
validationURL: event.validationURL,
|
|
1391
1395
|
recaptchaToken
|
|
1392
1396
|
});
|
|
1393
1397
|
if (!response?.merchantSession) {
|
|
1398
|
+
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
1394
1399
|
session.abort();
|
|
1395
1400
|
redirectToFallbackCheckout(config);
|
|
1396
1401
|
return;
|
|
1397
1402
|
}
|
|
1403
|
+
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
1398
1404
|
session.completeMerchantValidation(response.merchantSession);
|
|
1399
|
-
} catch {
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
1400
1407
|
session.abort();
|
|
1401
1408
|
redirectToFallbackCheckout(config);
|
|
1402
1409
|
}
|
|
@@ -1417,19 +1424,26 @@ function startApplePaySession(config) {
|
|
|
1417
1424
|
};
|
|
1418
1425
|
try {
|
|
1419
1426
|
const session = new ApplePaySession(3, paymentRequest);
|
|
1427
|
+
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
1420
1428
|
session.onvalidatemerchant = async (event) => {
|
|
1421
1429
|
await handleMerchantValidation(event, session, config);
|
|
1422
1430
|
};
|
|
1423
1431
|
session.onpaymentauthorized = (event) => {
|
|
1424
1432
|
trackWalletPaymentAuthorised(config.skuId);
|
|
1425
|
-
config.onPaymentAuthorized(event, session)
|
|
1433
|
+
config.onPaymentAuthorized(event, session).catch((err) => {
|
|
1434
|
+
console.error("[web-apple-pay] onPaymentAuthorized threw an unhandled error \u2014 aborting session", err);
|
|
1435
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
1436
|
+
redirectToFallbackCheckout(config);
|
|
1437
|
+
});
|
|
1426
1438
|
};
|
|
1427
1439
|
session.oncancel = () => {
|
|
1428
1440
|
config.onCancel?.();
|
|
1429
1441
|
};
|
|
1430
1442
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1431
1443
|
session.begin();
|
|
1432
|
-
|
|
1444
|
+
console.info("[web-apple-pay] ApplePaySession.begin() called \u2014 payment sheet should appear");
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
1433
1447
|
redirectToFallbackCheckout(config);
|
|
1434
1448
|
}
|
|
1435
1449
|
}
|
|
@@ -2195,9 +2209,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2195
2209
|
}
|
|
2196
2210
|
const skuId = this.#offer.sku_id;
|
|
2197
2211
|
const country = this.#country;
|
|
2198
|
-
modal.onConfirm = (
|
|
2212
|
+
modal.onConfirm = (recaptchaTokensPromise) => {
|
|
2199
2213
|
console.info("[web-apple-pay] onConfirm received from modal \u2014 initiating Apple Pay session", { skuId, country });
|
|
2200
|
-
this.#initiateApplePay(skuId, country,
|
|
2214
|
+
this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
|
|
2201
2215
|
};
|
|
2202
2216
|
console.info("[web-apple-pay] Opening Apple Pay modal", { skuId, country });
|
|
2203
2217
|
modal.open();
|
|
@@ -2221,7 +2235,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2221
2235
|
return null;
|
|
2222
2236
|
}
|
|
2223
2237
|
}
|
|
2224
|
-
#buildSessionParams(skuId, country, sessionData, paymentOptions,
|
|
2238
|
+
#buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
|
|
2225
2239
|
const offer = this.#offer;
|
|
2226
2240
|
const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
|
|
2227
2241
|
return {
|
|
@@ -2233,31 +2247,33 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2233
2247
|
skuId,
|
|
2234
2248
|
returnUrl: this.#returnUrl,
|
|
2235
2249
|
userLoggedIn: sessionData.loggedIn,
|
|
2236
|
-
onPaymentAuthorized: (event, session) =>
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2250
|
+
onPaymentAuthorized: async (event, session) => {
|
|
2251
|
+
const recaptchaTokens = await recaptchaTokensPromise;
|
|
2252
|
+
return handlePaymentAuthorized(event, session, sessionData, {
|
|
2253
|
+
skuId,
|
|
2254
|
+
country,
|
|
2255
|
+
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2256
|
+
returnUrl: this.#returnUrl,
|
|
2257
|
+
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2258
|
+
recaptchaTokens
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2244
2261
|
};
|
|
2245
2262
|
}
|
|
2246
|
-
#initiateApplePay(skuId, country,
|
|
2263
|
+
#initiateApplePay(skuId, country, recaptchaTokensPromise) {
|
|
2247
2264
|
try {
|
|
2248
2265
|
const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
|
|
2249
2266
|
const paymentOptions = this.#paymentOptions;
|
|
2267
|
+
if (!paymentOptions) {
|
|
2268
|
+
console.error("[web-apple-pay] #initiateApplePay called before payment options were fetched");
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2250
2271
|
console.info("[web-apple-pay] Starting Apple Pay session", {
|
|
2251
2272
|
skuId,
|
|
2252
2273
|
country,
|
|
2253
2274
|
loggedIn: sessionData.loggedIn,
|
|
2254
2275
|
userType: sessionData.userType,
|
|
2255
|
-
hasPaymentOptions:
|
|
2256
|
-
recaptchaTokensPresent: {
|
|
2257
|
-
checkEligibility: !!recaptchaTokens.checkEligibility,
|
|
2258
|
-
newBasket: !!recaptchaTokens.newBasket,
|
|
2259
|
-
registerUser: !!recaptchaTokens.registerUser
|
|
2260
|
-
}
|
|
2276
|
+
hasPaymentOptions: true
|
|
2261
2277
|
});
|
|
2262
2278
|
startApplePaySession(
|
|
2263
2279
|
this.#buildSessionParams(
|
|
@@ -2265,7 +2281,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2265
2281
|
country,
|
|
2266
2282
|
sessionData,
|
|
2267
2283
|
paymentOptions,
|
|
2268
|
-
|
|
2284
|
+
recaptchaTokensPromise
|
|
2269
2285
|
)
|
|
2270
2286
|
);
|
|
2271
2287
|
} catch (error) {
|
|
@@ -844,10 +844,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
844
844
|
#keyHandler = null;
|
|
845
845
|
#isClosed = false;
|
|
846
846
|
/**
|
|
847
|
-
* Callback invoked from the CTA click handler
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* new ApplePaySession()
|
|
847
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
848
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
849
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
850
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
851
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
852
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
851
853
|
*/
|
|
852
854
|
onConfirm = null;
|
|
853
855
|
set offer(value) {
|
|
@@ -952,20 +954,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
952
954
|
return;
|
|
953
955
|
}
|
|
954
956
|
errorElement?.classList.remove("apm__error--visible");
|
|
957
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
955
958
|
cta.disabled = true;
|
|
956
959
|
cta.setAttribute("aria-busy", "true");
|
|
957
960
|
console.info("[web-apple-pay] CTA clicked \u2014 fetching reCAPTCHA tokens");
|
|
958
|
-
getPurchaseRecaptchaTokens()
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
965
|
-
this.onConfirm?.(recaptchaTokens);
|
|
961
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
962
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
963
|
+
console.info("[web-apple-pay] Calling onConfirm synchronously to initiate Apple Pay session");
|
|
964
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
966
965
|
this.close();
|
|
967
|
-
}
|
|
966
|
+
}
|
|
967
|
+
recaptchaTokensPromise.catch((error) => {
|
|
968
968
|
console.error("[web-apple-pay] reCAPTCHA token fetch failed", error);
|
|
969
|
+
if (this.#isClosed) return;
|
|
969
970
|
cta.disabled = false;
|
|
970
971
|
cta.removeAttribute("aria-busy");
|
|
971
972
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -1376,22 +1377,28 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1376
1377
|
try {
|
|
1377
1378
|
const validationHost = new URL(event.validationURL).host;
|
|
1378
1379
|
if (!validationHost.endsWith("apple.com")) {
|
|
1380
|
+
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
1379
1381
|
session.abort();
|
|
1380
1382
|
redirectToFallbackCheckout(config);
|
|
1381
1383
|
return;
|
|
1382
1384
|
}
|
|
1385
|
+
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
1383
1386
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
1387
|
+
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
1384
1388
|
const response = await validateAppleMerchant({
|
|
1385
1389
|
validationURL: event.validationURL,
|
|
1386
1390
|
recaptchaToken
|
|
1387
1391
|
});
|
|
1388
1392
|
if (!response?.merchantSession) {
|
|
1393
|
+
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
1389
1394
|
session.abort();
|
|
1390
1395
|
redirectToFallbackCheckout(config);
|
|
1391
1396
|
return;
|
|
1392
1397
|
}
|
|
1398
|
+
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
1393
1399
|
session.completeMerchantValidation(response.merchantSession);
|
|
1394
|
-
} catch {
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
1395
1402
|
session.abort();
|
|
1396
1403
|
redirectToFallbackCheckout(config);
|
|
1397
1404
|
}
|
|
@@ -1412,19 +1419,26 @@ function startApplePaySession(config) {
|
|
|
1412
1419
|
};
|
|
1413
1420
|
try {
|
|
1414
1421
|
const session = new ApplePaySession(3, paymentRequest);
|
|
1422
|
+
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
1415
1423
|
session.onvalidatemerchant = async (event) => {
|
|
1416
1424
|
await handleMerchantValidation(event, session, config);
|
|
1417
1425
|
};
|
|
1418
1426
|
session.onpaymentauthorized = (event) => {
|
|
1419
1427
|
trackWalletPaymentAuthorised(config.skuId);
|
|
1420
|
-
config.onPaymentAuthorized(event, session)
|
|
1428
|
+
config.onPaymentAuthorized(event, session).catch((err) => {
|
|
1429
|
+
console.error("[web-apple-pay] onPaymentAuthorized threw an unhandled error \u2014 aborting session", err);
|
|
1430
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
1431
|
+
redirectToFallbackCheckout(config);
|
|
1432
|
+
});
|
|
1421
1433
|
};
|
|
1422
1434
|
session.oncancel = () => {
|
|
1423
1435
|
config.onCancel?.();
|
|
1424
1436
|
};
|
|
1425
1437
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1426
1438
|
session.begin();
|
|
1427
|
-
|
|
1439
|
+
console.info("[web-apple-pay] ApplePaySession.begin() called \u2014 payment sheet should appear");
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
1428
1442
|
redirectToFallbackCheckout(config);
|
|
1429
1443
|
}
|
|
1430
1444
|
}
|
|
@@ -2190,9 +2204,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2190
2204
|
}
|
|
2191
2205
|
const skuId = this.#offer.sku_id;
|
|
2192
2206
|
const country = this.#country;
|
|
2193
|
-
modal.onConfirm = (
|
|
2207
|
+
modal.onConfirm = (recaptchaTokensPromise) => {
|
|
2194
2208
|
console.info("[web-apple-pay] onConfirm received from modal \u2014 initiating Apple Pay session", { skuId, country });
|
|
2195
|
-
this.#initiateApplePay(skuId, country,
|
|
2209
|
+
this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
|
|
2196
2210
|
};
|
|
2197
2211
|
console.info("[web-apple-pay] Opening Apple Pay modal", { skuId, country });
|
|
2198
2212
|
modal.open();
|
|
@@ -2216,7 +2230,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2216
2230
|
return null;
|
|
2217
2231
|
}
|
|
2218
2232
|
}
|
|
2219
|
-
#buildSessionParams(skuId, country, sessionData, paymentOptions,
|
|
2233
|
+
#buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
|
|
2220
2234
|
const offer = this.#offer;
|
|
2221
2235
|
const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
|
|
2222
2236
|
return {
|
|
@@ -2228,31 +2242,33 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2228
2242
|
skuId,
|
|
2229
2243
|
returnUrl: this.#returnUrl,
|
|
2230
2244
|
userLoggedIn: sessionData.loggedIn,
|
|
2231
|
-
onPaymentAuthorized: (event, session) =>
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2245
|
+
onPaymentAuthorized: async (event, session) => {
|
|
2246
|
+
const recaptchaTokens = await recaptchaTokensPromise;
|
|
2247
|
+
return handlePaymentAuthorized(event, session, sessionData, {
|
|
2248
|
+
skuId,
|
|
2249
|
+
country,
|
|
2250
|
+
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2251
|
+
returnUrl: this.#returnUrl,
|
|
2252
|
+
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2253
|
+
recaptchaTokens
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2239
2256
|
};
|
|
2240
2257
|
}
|
|
2241
|
-
#initiateApplePay(skuId, country,
|
|
2258
|
+
#initiateApplePay(skuId, country, recaptchaTokensPromise) {
|
|
2242
2259
|
try {
|
|
2243
2260
|
const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
|
|
2244
2261
|
const paymentOptions = this.#paymentOptions;
|
|
2262
|
+
if (!paymentOptions) {
|
|
2263
|
+
console.error("[web-apple-pay] #initiateApplePay called before payment options were fetched");
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2245
2266
|
console.info("[web-apple-pay] Starting Apple Pay session", {
|
|
2246
2267
|
skuId,
|
|
2247
2268
|
country,
|
|
2248
2269
|
loggedIn: sessionData.loggedIn,
|
|
2249
2270
|
userType: sessionData.userType,
|
|
2250
|
-
hasPaymentOptions:
|
|
2251
|
-
recaptchaTokensPresent: {
|
|
2252
|
-
checkEligibility: !!recaptchaTokens.checkEligibility,
|
|
2253
|
-
newBasket: !!recaptchaTokens.newBasket,
|
|
2254
|
-
registerUser: !!recaptchaTokens.registerUser
|
|
2255
|
-
}
|
|
2271
|
+
hasPaymentOptions: true
|
|
2256
2272
|
});
|
|
2257
2273
|
startApplePaySession(
|
|
2258
2274
|
this.#buildSessionParams(
|
|
@@ -2260,7 +2276,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2260
2276
|
country,
|
|
2261
2277
|
sessionData,
|
|
2262
2278
|
paymentOptions,
|
|
2263
|
-
|
|
2279
|
+
recaptchaTokensPromise
|
|
2264
2280
|
)
|
|
2265
2281
|
);
|
|
2266
2282
|
} catch (error) {
|
|
@@ -852,10 +852,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
852
852
|
#keyHandler = null;
|
|
853
853
|
#isClosed = false;
|
|
854
854
|
/**
|
|
855
|
-
* Callback invoked from the CTA click handler
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
* new ApplePaySession()
|
|
855
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
856
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
857
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
858
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
859
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
860
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
859
861
|
*/
|
|
860
862
|
onConfirm = null;
|
|
861
863
|
set offer(value) {
|
|
@@ -960,20 +962,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
960
962
|
return;
|
|
961
963
|
}
|
|
962
964
|
errorElement?.classList.remove("apm__error--visible");
|
|
965
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
963
966
|
cta.disabled = true;
|
|
964
967
|
cta.setAttribute("aria-busy", "true");
|
|
965
968
|
console.info("[web-apple-pay] CTA clicked \u2014 fetching reCAPTCHA tokens");
|
|
966
|
-
getPurchaseRecaptchaTokens()
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
973
|
-
this.onConfirm?.(recaptchaTokens);
|
|
969
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
970
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
971
|
+
console.info("[web-apple-pay] Calling onConfirm synchronously to initiate Apple Pay session");
|
|
972
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
974
973
|
this.close();
|
|
975
|
-
}
|
|
974
|
+
}
|
|
975
|
+
recaptchaTokensPromise.catch((error) => {
|
|
976
976
|
console.error("[web-apple-pay] reCAPTCHA token fetch failed", error);
|
|
977
|
+
if (this.#isClosed) return;
|
|
977
978
|
cta.disabled = false;
|
|
978
979
|
cta.removeAttribute("aria-busy");
|
|
979
980
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -1384,22 +1385,28 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1384
1385
|
try {
|
|
1385
1386
|
const validationHost = new URL(event.validationURL).host;
|
|
1386
1387
|
if (!validationHost.endsWith("apple.com")) {
|
|
1388
|
+
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
1387
1389
|
session.abort();
|
|
1388
1390
|
redirectToFallbackCheckout(config);
|
|
1389
1391
|
return;
|
|
1390
1392
|
}
|
|
1393
|
+
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
1391
1394
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
1395
|
+
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
1392
1396
|
const response = await validateAppleMerchant({
|
|
1393
1397
|
validationURL: event.validationURL,
|
|
1394
1398
|
recaptchaToken
|
|
1395
1399
|
});
|
|
1396
1400
|
if (!response?.merchantSession) {
|
|
1401
|
+
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
1397
1402
|
session.abort();
|
|
1398
1403
|
redirectToFallbackCheckout(config);
|
|
1399
1404
|
return;
|
|
1400
1405
|
}
|
|
1406
|
+
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
1401
1407
|
session.completeMerchantValidation(response.merchantSession);
|
|
1402
|
-
} catch {
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
1403
1410
|
session.abort();
|
|
1404
1411
|
redirectToFallbackCheckout(config);
|
|
1405
1412
|
}
|
|
@@ -1420,19 +1427,26 @@ function startApplePaySession(config) {
|
|
|
1420
1427
|
};
|
|
1421
1428
|
try {
|
|
1422
1429
|
const session = new ApplePaySession(3, paymentRequest);
|
|
1430
|
+
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
1423
1431
|
session.onvalidatemerchant = async (event) => {
|
|
1424
1432
|
await handleMerchantValidation(event, session, config);
|
|
1425
1433
|
};
|
|
1426
1434
|
session.onpaymentauthorized = (event) => {
|
|
1427
1435
|
trackWalletPaymentAuthorised(config.skuId);
|
|
1428
|
-
config.onPaymentAuthorized(event, session)
|
|
1436
|
+
config.onPaymentAuthorized(event, session).catch((err) => {
|
|
1437
|
+
console.error("[web-apple-pay] onPaymentAuthorized threw an unhandled error \u2014 aborting session", err);
|
|
1438
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
1439
|
+
redirectToFallbackCheckout(config);
|
|
1440
|
+
});
|
|
1429
1441
|
};
|
|
1430
1442
|
session.oncancel = () => {
|
|
1431
1443
|
config.onCancel?.();
|
|
1432
1444
|
};
|
|
1433
1445
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1434
1446
|
session.begin();
|
|
1435
|
-
|
|
1447
|
+
console.info("[web-apple-pay] ApplePaySession.begin() called \u2014 payment sheet should appear");
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
1436
1450
|
redirectToFallbackCheckout(config);
|
|
1437
1451
|
}
|
|
1438
1452
|
}
|
|
@@ -2198,9 +2212,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2198
2212
|
}
|
|
2199
2213
|
const skuId = this.#offer.sku_id;
|
|
2200
2214
|
const country = this.#country;
|
|
2201
|
-
modal.onConfirm = (
|
|
2215
|
+
modal.onConfirm = (recaptchaTokensPromise) => {
|
|
2202
2216
|
console.info("[web-apple-pay] onConfirm received from modal \u2014 initiating Apple Pay session", { skuId, country });
|
|
2203
|
-
this.#initiateApplePay(skuId, country,
|
|
2217
|
+
this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
|
|
2204
2218
|
};
|
|
2205
2219
|
console.info("[web-apple-pay] Opening Apple Pay modal", { skuId, country });
|
|
2206
2220
|
modal.open();
|
|
@@ -2224,7 +2238,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2224
2238
|
return null;
|
|
2225
2239
|
}
|
|
2226
2240
|
}
|
|
2227
|
-
#buildSessionParams(skuId, country, sessionData, paymentOptions,
|
|
2241
|
+
#buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
|
|
2228
2242
|
const offer = this.#offer;
|
|
2229
2243
|
const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
|
|
2230
2244
|
return {
|
|
@@ -2236,31 +2250,33 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2236
2250
|
skuId,
|
|
2237
2251
|
returnUrl: this.#returnUrl,
|
|
2238
2252
|
userLoggedIn: sessionData.loggedIn,
|
|
2239
|
-
onPaymentAuthorized: (event, session) =>
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2253
|
+
onPaymentAuthorized: async (event, session) => {
|
|
2254
|
+
const recaptchaTokens = await recaptchaTokensPromise;
|
|
2255
|
+
return handlePaymentAuthorized(event, session, sessionData, {
|
|
2256
|
+
skuId,
|
|
2257
|
+
country,
|
|
2258
|
+
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2259
|
+
returnUrl: this.#returnUrl,
|
|
2260
|
+
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2261
|
+
recaptchaTokens
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2247
2264
|
};
|
|
2248
2265
|
}
|
|
2249
|
-
#initiateApplePay(skuId, country,
|
|
2266
|
+
#initiateApplePay(skuId, country, recaptchaTokensPromise) {
|
|
2250
2267
|
try {
|
|
2251
2268
|
const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
|
|
2252
2269
|
const paymentOptions = this.#paymentOptions;
|
|
2270
|
+
if (!paymentOptions) {
|
|
2271
|
+
console.error("[web-apple-pay] #initiateApplePay called before payment options were fetched");
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2253
2274
|
console.info("[web-apple-pay] Starting Apple Pay session", {
|
|
2254
2275
|
skuId,
|
|
2255
2276
|
country,
|
|
2256
2277
|
loggedIn: sessionData.loggedIn,
|
|
2257
2278
|
userType: sessionData.userType,
|
|
2258
|
-
hasPaymentOptions:
|
|
2259
|
-
recaptchaTokensPresent: {
|
|
2260
|
-
checkEligibility: !!recaptchaTokens.checkEligibility,
|
|
2261
|
-
newBasket: !!recaptchaTokens.newBasket,
|
|
2262
|
-
registerUser: !!recaptchaTokens.registerUser
|
|
2263
|
-
}
|
|
2279
|
+
hasPaymentOptions: true
|
|
2264
2280
|
});
|
|
2265
2281
|
startApplePaySession(
|
|
2266
2282
|
this.#buildSessionParams(
|
|
@@ -2268,7 +2284,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2268
2284
|
country,
|
|
2269
2285
|
sessionData,
|
|
2270
2286
|
paymentOptions,
|
|
2271
|
-
|
|
2287
|
+
recaptchaTokensPromise
|
|
2272
2288
|
)
|
|
2273
2289
|
);
|
|
2274
2290
|
} catch (error) {
|
|
@@ -7,12 +7,14 @@ export declare class ApplePayModal extends HTMLElement {
|
|
|
7
7
|
static readonly tag = "teg-apple-pay-modal";
|
|
8
8
|
static define(): void;
|
|
9
9
|
/**
|
|
10
|
-
* Callback invoked from the CTA click handler
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* new ApplePaySession()
|
|
10
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
11
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
12
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
13
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
14
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
15
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
14
16
|
*/
|
|
15
|
-
onConfirm: ((
|
|
17
|
+
onConfirm: ((recaptchaTokensPromise: Promise<RecaptchaTokens>) => void) | null;
|
|
16
18
|
set offer(value: Offer | null);
|
|
17
19
|
get country(): string;
|
|
18
20
|
set country(value: string);
|
|
@@ -844,10 +844,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
844
844
|
#keyHandler = null;
|
|
845
845
|
#isClosed = false;
|
|
846
846
|
/**
|
|
847
|
-
* Callback invoked from the CTA click handler
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* new ApplePaySession()
|
|
847
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
848
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
849
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
850
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
851
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
852
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
851
853
|
*/
|
|
852
854
|
onConfirm = null;
|
|
853
855
|
set offer(value) {
|
|
@@ -952,20 +954,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
952
954
|
return;
|
|
953
955
|
}
|
|
954
956
|
errorElement?.classList.remove("apm__error--visible");
|
|
957
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
955
958
|
cta.disabled = true;
|
|
956
959
|
cta.setAttribute("aria-busy", "true");
|
|
957
960
|
console.info("[web-apple-pay] CTA clicked \u2014 fetching reCAPTCHA tokens");
|
|
958
|
-
getPurchaseRecaptchaTokens()
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
965
|
-
this.onConfirm?.(recaptchaTokens);
|
|
961
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
962
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
963
|
+
console.info("[web-apple-pay] Calling onConfirm synchronously to initiate Apple Pay session");
|
|
964
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
966
965
|
this.close();
|
|
967
|
-
}
|
|
966
|
+
}
|
|
967
|
+
recaptchaTokensPromise.catch((error) => {
|
|
968
968
|
console.error("[web-apple-pay] reCAPTCHA token fetch failed", error);
|
|
969
|
+
if (this.#isClosed) return;
|
|
969
970
|
cta.disabled = false;
|
|
970
971
|
cta.removeAttribute("aria-busy");
|
|
971
972
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -844,10 +844,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
844
844
|
#keyHandler = null;
|
|
845
845
|
#isClosed = false;
|
|
846
846
|
/**
|
|
847
|
-
* Callback invoked from the CTA click handler
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* new ApplePaySession()
|
|
847
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
848
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
849
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
850
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
851
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
852
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
851
853
|
*/
|
|
852
854
|
onConfirm = null;
|
|
853
855
|
set offer(value) {
|
|
@@ -952,20 +954,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
952
954
|
return;
|
|
953
955
|
}
|
|
954
956
|
errorElement?.classList.remove("apm__error--visible");
|
|
957
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
955
958
|
cta.disabled = true;
|
|
956
959
|
cta.setAttribute("aria-busy", "true");
|
|
957
960
|
console.info("[web-apple-pay] CTA clicked \u2014 fetching reCAPTCHA tokens");
|
|
958
|
-
getPurchaseRecaptchaTokens()
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
965
|
-
this.onConfirm?.(recaptchaTokens);
|
|
961
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
962
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
963
|
+
console.info("[web-apple-pay] Calling onConfirm synchronously to initiate Apple Pay session");
|
|
964
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
966
965
|
this.close();
|
|
967
|
-
}
|
|
966
|
+
}
|
|
967
|
+
recaptchaTokensPromise.catch((error) => {
|
|
968
968
|
console.error("[web-apple-pay] reCAPTCHA token fetch failed", error);
|
|
969
|
+
if (this.#isClosed) return;
|
|
969
970
|
cta.disabled = false;
|
|
970
971
|
cta.removeAttribute("aria-busy");
|
|
971
972
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -1376,22 +1377,28 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1376
1377
|
try {
|
|
1377
1378
|
const validationHost = new URL(event.validationURL).host;
|
|
1378
1379
|
if (!validationHost.endsWith("apple.com")) {
|
|
1380
|
+
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
1379
1381
|
session.abort();
|
|
1380
1382
|
redirectToFallbackCheckout(config);
|
|
1381
1383
|
return;
|
|
1382
1384
|
}
|
|
1385
|
+
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
1383
1386
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
1387
|
+
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
1384
1388
|
const response = await validateAppleMerchant({
|
|
1385
1389
|
validationURL: event.validationURL,
|
|
1386
1390
|
recaptchaToken
|
|
1387
1391
|
});
|
|
1388
1392
|
if (!response?.merchantSession) {
|
|
1393
|
+
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
1389
1394
|
session.abort();
|
|
1390
1395
|
redirectToFallbackCheckout(config);
|
|
1391
1396
|
return;
|
|
1392
1397
|
}
|
|
1398
|
+
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
1393
1399
|
session.completeMerchantValidation(response.merchantSession);
|
|
1394
|
-
} catch {
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
1395
1402
|
session.abort();
|
|
1396
1403
|
redirectToFallbackCheckout(config);
|
|
1397
1404
|
}
|
|
@@ -1412,19 +1419,26 @@ function startApplePaySession(config) {
|
|
|
1412
1419
|
};
|
|
1413
1420
|
try {
|
|
1414
1421
|
const session = new ApplePaySession(3, paymentRequest);
|
|
1422
|
+
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
1415
1423
|
session.onvalidatemerchant = async (event) => {
|
|
1416
1424
|
await handleMerchantValidation(event, session, config);
|
|
1417
1425
|
};
|
|
1418
1426
|
session.onpaymentauthorized = (event) => {
|
|
1419
1427
|
trackWalletPaymentAuthorised(config.skuId);
|
|
1420
|
-
config.onPaymentAuthorized(event, session)
|
|
1428
|
+
config.onPaymentAuthorized(event, session).catch((err) => {
|
|
1429
|
+
console.error("[web-apple-pay] onPaymentAuthorized threw an unhandled error \u2014 aborting session", err);
|
|
1430
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
1431
|
+
redirectToFallbackCheckout(config);
|
|
1432
|
+
});
|
|
1421
1433
|
};
|
|
1422
1434
|
session.oncancel = () => {
|
|
1423
1435
|
config.onCancel?.();
|
|
1424
1436
|
};
|
|
1425
1437
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1426
1438
|
session.begin();
|
|
1427
|
-
|
|
1439
|
+
console.info("[web-apple-pay] ApplePaySession.begin() called \u2014 payment sheet should appear");
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
1428
1442
|
redirectToFallbackCheckout(config);
|
|
1429
1443
|
}
|
|
1430
1444
|
}
|
|
@@ -2190,9 +2204,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2190
2204
|
}
|
|
2191
2205
|
const skuId = this.#offer.sku_id;
|
|
2192
2206
|
const country = this.#country;
|
|
2193
|
-
modal.onConfirm = (
|
|
2207
|
+
modal.onConfirm = (recaptchaTokensPromise) => {
|
|
2194
2208
|
console.info("[web-apple-pay] onConfirm received from modal \u2014 initiating Apple Pay session", { skuId, country });
|
|
2195
|
-
this.#initiateApplePay(skuId, country,
|
|
2209
|
+
this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
|
|
2196
2210
|
};
|
|
2197
2211
|
console.info("[web-apple-pay] Opening Apple Pay modal", { skuId, country });
|
|
2198
2212
|
modal.open();
|
|
@@ -2216,7 +2230,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2216
2230
|
return null;
|
|
2217
2231
|
}
|
|
2218
2232
|
}
|
|
2219
|
-
#buildSessionParams(skuId, country, sessionData, paymentOptions,
|
|
2233
|
+
#buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
|
|
2220
2234
|
const offer = this.#offer;
|
|
2221
2235
|
const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
|
|
2222
2236
|
return {
|
|
@@ -2228,31 +2242,33 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2228
2242
|
skuId,
|
|
2229
2243
|
returnUrl: this.#returnUrl,
|
|
2230
2244
|
userLoggedIn: sessionData.loggedIn,
|
|
2231
|
-
onPaymentAuthorized: (event, session) =>
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2245
|
+
onPaymentAuthorized: async (event, session) => {
|
|
2246
|
+
const recaptchaTokens = await recaptchaTokensPromise;
|
|
2247
|
+
return handlePaymentAuthorized(event, session, sessionData, {
|
|
2248
|
+
skuId,
|
|
2249
|
+
country,
|
|
2250
|
+
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2251
|
+
returnUrl: this.#returnUrl,
|
|
2252
|
+
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2253
|
+
recaptchaTokens
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2239
2256
|
};
|
|
2240
2257
|
}
|
|
2241
|
-
#initiateApplePay(skuId, country,
|
|
2258
|
+
#initiateApplePay(skuId, country, recaptchaTokensPromise) {
|
|
2242
2259
|
try {
|
|
2243
2260
|
const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
|
|
2244
2261
|
const paymentOptions = this.#paymentOptions;
|
|
2262
|
+
if (!paymentOptions) {
|
|
2263
|
+
console.error("[web-apple-pay] #initiateApplePay called before payment options were fetched");
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2245
2266
|
console.info("[web-apple-pay] Starting Apple Pay session", {
|
|
2246
2267
|
skuId,
|
|
2247
2268
|
country,
|
|
2248
2269
|
loggedIn: sessionData.loggedIn,
|
|
2249
2270
|
userType: sessionData.userType,
|
|
2250
|
-
hasPaymentOptions:
|
|
2251
|
-
recaptchaTokensPresent: {
|
|
2252
|
-
checkEligibility: !!recaptchaTokens.checkEligibility,
|
|
2253
|
-
newBasket: !!recaptchaTokens.newBasket,
|
|
2254
|
-
registerUser: !!recaptchaTokens.registerUser
|
|
2255
|
-
}
|
|
2271
|
+
hasPaymentOptions: true
|
|
2256
2272
|
});
|
|
2257
2273
|
startApplePaySession(
|
|
2258
2274
|
this.#buildSessionParams(
|
|
@@ -2260,7 +2276,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2260
2276
|
country,
|
|
2261
2277
|
sessionData,
|
|
2262
2278
|
paymentOptions,
|
|
2263
|
-
|
|
2279
|
+
recaptchaTokensPromise
|
|
2264
2280
|
)
|
|
2265
2281
|
);
|
|
2266
2282
|
} catch (error) {
|
|
@@ -201,22 +201,28 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
201
201
|
try {
|
|
202
202
|
const validationHost = new URL(event.validationURL).host;
|
|
203
203
|
if (!validationHost.endsWith("apple.com")) {
|
|
204
|
+
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
204
205
|
session.abort();
|
|
205
206
|
redirectToFallbackCheckout(config);
|
|
206
207
|
return;
|
|
207
208
|
}
|
|
209
|
+
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
208
210
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
211
|
+
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
209
212
|
const response = await validateAppleMerchant({
|
|
210
213
|
validationURL: event.validationURL,
|
|
211
214
|
recaptchaToken
|
|
212
215
|
});
|
|
213
216
|
if (!response?.merchantSession) {
|
|
217
|
+
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
214
218
|
session.abort();
|
|
215
219
|
redirectToFallbackCheckout(config);
|
|
216
220
|
return;
|
|
217
221
|
}
|
|
222
|
+
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
218
223
|
session.completeMerchantValidation(response.merchantSession);
|
|
219
|
-
} catch {
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
220
226
|
session.abort();
|
|
221
227
|
redirectToFallbackCheckout(config);
|
|
222
228
|
}
|
|
@@ -237,19 +243,26 @@ function startApplePaySession(config) {
|
|
|
237
243
|
};
|
|
238
244
|
try {
|
|
239
245
|
const session = new ApplePaySession(3, paymentRequest);
|
|
246
|
+
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
240
247
|
session.onvalidatemerchant = async (event) => {
|
|
241
248
|
await handleMerchantValidation(event, session, config);
|
|
242
249
|
};
|
|
243
250
|
session.onpaymentauthorized = (event) => {
|
|
244
251
|
trackWalletPaymentAuthorised(config.skuId);
|
|
245
|
-
config.onPaymentAuthorized(event, session)
|
|
252
|
+
config.onPaymentAuthorized(event, session).catch((err) => {
|
|
253
|
+
console.error("[web-apple-pay] onPaymentAuthorized threw an unhandled error \u2014 aborting session", err);
|
|
254
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
255
|
+
redirectToFallbackCheckout(config);
|
|
256
|
+
});
|
|
246
257
|
};
|
|
247
258
|
session.oncancel = () => {
|
|
248
259
|
config.onCancel?.();
|
|
249
260
|
};
|
|
250
261
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
251
262
|
session.begin();
|
|
252
|
-
|
|
263
|
+
console.info("[web-apple-pay] ApplePaySession.begin() called \u2014 payment sheet should appear");
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
253
266
|
redirectToFallbackCheckout(config);
|
|
254
267
|
}
|
|
255
268
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -849,10 +849,12 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
849
849
|
#keyHandler = null;
|
|
850
850
|
#isClosed = false;
|
|
851
851
|
/**
|
|
852
|
-
* Callback invoked from the CTA click handler
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
* new ApplePaySession()
|
|
852
|
+
* Callback invoked synchronously from the CTA click handler.
|
|
853
|
+
* Receives a Promise of reCAPTCHA tokens — the promise is already in-flight
|
|
854
|
+
* by the time this is called. The caller (ApplePayButton) must call
|
|
855
|
+
* new ApplePaySession() synchronously inside this callback to stay within
|
|
856
|
+
* Safari's user-gesture window. The token promise resolves before
|
|
857
|
+
* onPaymentAuthorized fires (user still needs to authenticate on the sheet).
|
|
856
858
|
*/
|
|
857
859
|
onConfirm = null;
|
|
858
860
|
set offer(value) {
|
|
@@ -957,20 +959,19 @@ var ApplePayModal = class _ApplePayModal extends HTMLElement {
|
|
|
957
959
|
return;
|
|
958
960
|
}
|
|
959
961
|
errorElement?.classList.remove("apm__error--visible");
|
|
962
|
+
this.querySelector('[role="alert"]:not(#apple-pay-modal-error)')?.remove();
|
|
960
963
|
cta.disabled = true;
|
|
961
964
|
cta.setAttribute("aria-busy", "true");
|
|
962
965
|
console.info("[web-apple-pay] CTA clicked \u2014 fetching reCAPTCHA tokens");
|
|
963
|
-
getPurchaseRecaptchaTokens()
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
console.info("[web-apple-pay] Calling onConfirm to initiate Apple Pay session");
|
|
970
|
-
this.onConfirm?.(recaptchaTokens);
|
|
966
|
+
const recaptchaTokensPromise = getPurchaseRecaptchaTokens();
|
|
967
|
+
if (!this.#isClosed && this.onConfirm) {
|
|
968
|
+
console.info("[web-apple-pay] Calling onConfirm synchronously to initiate Apple Pay session");
|
|
969
|
+
this.onConfirm(recaptchaTokensPromise);
|
|
971
970
|
this.close();
|
|
972
|
-
}
|
|
971
|
+
}
|
|
972
|
+
recaptchaTokensPromise.catch((error) => {
|
|
973
973
|
console.error("[web-apple-pay] reCAPTCHA token fetch failed", error);
|
|
974
|
+
if (this.#isClosed) return;
|
|
974
975
|
cta.disabled = false;
|
|
975
976
|
cta.removeAttribute("aria-busy");
|
|
976
977
|
const existingError = this.querySelector("#apple-pay-modal-error");
|
|
@@ -1381,22 +1382,28 @@ async function handleMerchantValidation(event, session, config) {
|
|
|
1381
1382
|
try {
|
|
1382
1383
|
const validationHost = new URL(event.validationURL).host;
|
|
1383
1384
|
if (!validationHost.endsWith("apple.com")) {
|
|
1385
|
+
console.error("[web-apple-pay] Merchant validation URL is not from apple.com \u2014 aborting session", { validationURL: event.validationURL });
|
|
1384
1386
|
session.abort();
|
|
1385
1387
|
redirectToFallbackCheckout(config);
|
|
1386
1388
|
return;
|
|
1387
1389
|
}
|
|
1390
|
+
console.info("[web-apple-pay] Merchant validation started", { validationHost });
|
|
1388
1391
|
const recaptchaToken = await getRecaptchaToken("verifyMerchant");
|
|
1392
|
+
console.info("[web-apple-pay] verifyMerchant reCAPTCHA token acquired \u2014 calling validateAppleMerchant");
|
|
1389
1393
|
const response = await validateAppleMerchant({
|
|
1390
1394
|
validationURL: event.validationURL,
|
|
1391
1395
|
recaptchaToken
|
|
1392
1396
|
});
|
|
1393
1397
|
if (!response?.merchantSession) {
|
|
1398
|
+
console.error("[web-apple-pay] Merchant validation response missing merchantSession \u2014 aborting session", { response });
|
|
1394
1399
|
session.abort();
|
|
1395
1400
|
redirectToFallbackCheckout(config);
|
|
1396
1401
|
return;
|
|
1397
1402
|
}
|
|
1403
|
+
console.info("[web-apple-pay] Merchant validation succeeded \u2014 completing merchant validation");
|
|
1398
1404
|
session.completeMerchantValidation(response.merchantSession);
|
|
1399
|
-
} catch {
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
console.error("[web-apple-pay] Merchant validation threw an error \u2014 aborting session", error);
|
|
1400
1407
|
session.abort();
|
|
1401
1408
|
redirectToFallbackCheckout(config);
|
|
1402
1409
|
}
|
|
@@ -1417,19 +1424,26 @@ function startApplePaySession(config) {
|
|
|
1417
1424
|
};
|
|
1418
1425
|
try {
|
|
1419
1426
|
const session = new ApplePaySession(3, paymentRequest);
|
|
1427
|
+
console.info("[web-apple-pay] ApplePaySession created successfully");
|
|
1420
1428
|
session.onvalidatemerchant = async (event) => {
|
|
1421
1429
|
await handleMerchantValidation(event, session, config);
|
|
1422
1430
|
};
|
|
1423
1431
|
session.onpaymentauthorized = (event) => {
|
|
1424
1432
|
trackWalletPaymentAuthorised(config.skuId);
|
|
1425
|
-
config.onPaymentAuthorized(event, session)
|
|
1433
|
+
config.onPaymentAuthorized(event, session).catch((err) => {
|
|
1434
|
+
console.error("[web-apple-pay] onPaymentAuthorized threw an unhandled error \u2014 aborting session", err);
|
|
1435
|
+
session.completePayment({ status: ApplePaySession.STATUS_FAILURE });
|
|
1436
|
+
redirectToFallbackCheckout(config);
|
|
1437
|
+
});
|
|
1426
1438
|
};
|
|
1427
1439
|
session.oncancel = () => {
|
|
1428
1440
|
config.onCancel?.();
|
|
1429
1441
|
};
|
|
1430
1442
|
trackWalletPaymentSheetOpen(config.skuId);
|
|
1431
1443
|
session.begin();
|
|
1432
|
-
|
|
1444
|
+
console.info("[web-apple-pay] ApplePaySession.begin() called \u2014 payment sheet should appear");
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
console.error("[web-apple-pay] ApplePaySession creation or begin() threw \u2014 redirecting to fallback checkout", error);
|
|
1433
1447
|
redirectToFallbackCheckout(config);
|
|
1434
1448
|
}
|
|
1435
1449
|
}
|
|
@@ -2195,9 +2209,9 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2195
2209
|
}
|
|
2196
2210
|
const skuId = this.#offer.sku_id;
|
|
2197
2211
|
const country = this.#country;
|
|
2198
|
-
modal.onConfirm = (
|
|
2212
|
+
modal.onConfirm = (recaptchaTokensPromise) => {
|
|
2199
2213
|
console.info("[web-apple-pay] onConfirm received from modal \u2014 initiating Apple Pay session", { skuId, country });
|
|
2200
|
-
this.#initiateApplePay(skuId, country,
|
|
2214
|
+
this.#initiateApplePay(skuId, country, recaptchaTokensPromise);
|
|
2201
2215
|
};
|
|
2202
2216
|
console.info("[web-apple-pay] Opening Apple Pay modal", { skuId, country });
|
|
2203
2217
|
modal.open();
|
|
@@ -2221,7 +2235,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2221
2235
|
return null;
|
|
2222
2236
|
}
|
|
2223
2237
|
}
|
|
2224
|
-
#buildSessionParams(skuId, country, sessionData, paymentOptions,
|
|
2238
|
+
#buildSessionParams(skuId, country, sessionData, paymentOptions, recaptchaTokensPromise) {
|
|
2225
2239
|
const offer = this.#offer;
|
|
2226
2240
|
const totalAmount = offer.price?.purchase ? `${offer.price.purchase}` : DEFAULT_TOTAL;
|
|
2227
2241
|
return {
|
|
@@ -2233,31 +2247,33 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2233
2247
|
skuId,
|
|
2234
2248
|
returnUrl: this.#returnUrl,
|
|
2235
2249
|
userLoggedIn: sessionData.loggedIn,
|
|
2236
|
-
onPaymentAuthorized: (event, session) =>
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2250
|
+
onPaymentAuthorized: async (event, session) => {
|
|
2251
|
+
const recaptchaTokens = await recaptchaTokensPromise;
|
|
2252
|
+
return handlePaymentAuthorized(event, session, sessionData, {
|
|
2253
|
+
skuId,
|
|
2254
|
+
country,
|
|
2255
|
+
currency: offer.price?.currency_code ?? DEFAULT_CURRENCY,
|
|
2256
|
+
returnUrl: this.#returnUrl,
|
|
2257
|
+
supportedBillingCountries: this.#supportedBillingCountries,
|
|
2258
|
+
recaptchaTokens
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2244
2261
|
};
|
|
2245
2262
|
}
|
|
2246
|
-
#initiateApplePay(skuId, country,
|
|
2263
|
+
#initiateApplePay(skuId, country, recaptchaTokensPromise) {
|
|
2247
2264
|
try {
|
|
2248
2265
|
const sessionData = this.#sessionData ?? _ApplePayButton.#ANONYMOUS_SESSION;
|
|
2249
2266
|
const paymentOptions = this.#paymentOptions;
|
|
2267
|
+
if (!paymentOptions) {
|
|
2268
|
+
console.error("[web-apple-pay] #initiateApplePay called before payment options were fetched");
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2250
2271
|
console.info("[web-apple-pay] Starting Apple Pay session", {
|
|
2251
2272
|
skuId,
|
|
2252
2273
|
country,
|
|
2253
2274
|
loggedIn: sessionData.loggedIn,
|
|
2254
2275
|
userType: sessionData.userType,
|
|
2255
|
-
hasPaymentOptions:
|
|
2256
|
-
recaptchaTokensPresent: {
|
|
2257
|
-
checkEligibility: !!recaptchaTokens.checkEligibility,
|
|
2258
|
-
newBasket: !!recaptchaTokens.newBasket,
|
|
2259
|
-
registerUser: !!recaptchaTokens.registerUser
|
|
2260
|
-
}
|
|
2276
|
+
hasPaymentOptions: true
|
|
2261
2277
|
});
|
|
2262
2278
|
startApplePaySession(
|
|
2263
2279
|
this.#buildSessionParams(
|
|
@@ -2265,7 +2281,7 @@ var ApplePayButton = class _ApplePayButton extends HTMLElement {
|
|
|
2265
2281
|
country,
|
|
2266
2282
|
sessionData,
|
|
2267
2283
|
paymentOptions,
|
|
2268
|
-
|
|
2284
|
+
recaptchaTokensPromise
|
|
2269
2285
|
)
|
|
2270
2286
|
);
|
|
2271
2287
|
} catch (error) {
|
package/package.json
CHANGED