1import { 2 CheckoutConfigurationErrorV1, 3 CheckoutCriticalError, 4 CheckoutExistingOrders, 5 CheckoutLoadingV1, 6 CheckoutMultiStepV2, 7 CheckoutShippingProfileSelectV1, 8 CheckoutSubmitButtonV1, 9 CheckoutSubmitNotificationV1, 10 CheckoutTosV1, 11 ERROR_CODES, 12 STRIPE_SKIP_SHIPPING_ID, 13 addressErrors, 14 buildCustomFields, 15 buildProductSelectBatched, 16 calculateStripeChangeResolveDetails, 17 calculateStripeClickResolveDetails, 18 checkValidCustomFields, 19 collectLeads, 20 contactErrors, 21 convertAddressFromSession, 22 convertAddressFromStripe, 23 convertToStripeAddress, 24 getCartEstimatedTotal, 25 getStripeMode, 26 hasErrors, 27 hasPhysicalProducts, 28 hasUpgradeDowngradeItems, 29 isActionableCheckoutError, 30 makeCurrentPageFetcher, 31 normalizeServerCheckoutError, 32 parseCssVars, 33 productErrors, 34 renderedHref, 35 scrollToFirstVisibleError, 36 serverCheckoutErrorMessage, 37 setupOrderSummaries, 38 shippingOptionErrors, 39 tosErrors 40} from "./chunk-SYANC6RH.js"; 41import "./chunk-W3RKY5WP.js"; 42import { 43 CFFetcherError, 44 Fetcher, 45 MANUALLY_ABORTED, 46 checkoutNonceParams, 47 fetchCheckoutNonce, 48 init_fetcher, 49 init_nonce, 50 isBackendError, 51 isFetcherError, 52 isResponseError 53} from "./chunk-YSVSPNWW.js"; 54import "./chunk-372FPXG6.js"; 55import { 56 atom, 57 computed, 58 deepEqual, 59 formatNumberToCurrency, 60 init_general, 61 init_nanostores 62} from "./chunk-BY72GGKB.js"; 63import "./chunk-4AIF3XZW.js"; 64import "./chunk-TLMSI2UF.js"; 65import { 66 init_input_v1 67} from "./chunk-FQR6BHZQ.js"; 68import { 69 IntlTel_initPhoneInput, 70 init_cf_utils, 71 uuidv4 72} from "./chunk-KIZY5IRN.js"; 73import "./chunk-5NUE6T5P.js"; 74import { 75 CF2Component, 76 init_runtime, 77 registerComponent 78} from "./chunk-NQUHJ6NR.js"; 79import { 80 __publicField, 81 init_define_process 82} from "./chunk-RFSPGZ3L.js"; 83
84// projects/lib/packages/yggdrasil-blueprints/__generated__/packs/stripe-checkout.ts 85init_define_process(); 86
87// projects/lib/packages/yggdrasil-blueprints/__generated__/blueprints/stripe-checkout.ts 88init_define_process(); 89init_input_v1(); 90
91// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/stripeCheckout.ts 92init_define_process(); 93
94// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/summary.ts 95init_define_process(); 96init_general(); 97init_fetcher(); 98var orderPreviewFetcher = makeCurrentPageFetcher({ 99 customEvent: "CF2:OrderPreview" 100}); 101var lastOrderPreviewData; 102var lastOrderPreviewResponse; 103function hasValidDataForLineItemSubtotal(payload) { 104 const { cart } = payload; 105 const productSelectErrors = productErrors(cart, true); 106 return !hasErrors(productSelectErrors); 107} 108async function fetchOrderPreview(payload, onStart) { 109 const dataToPreview = buildOrderPreviewPayload(payload); 110 if (lastOrderPreviewResponse && deepEqual(dataToPreview, lastOrderPreviewData)) { 111 return lastOrderPreviewResponse; 112 } 113 lastOrderPreviewData = dataToPreview; 114 if (onStart) onStart(); 115 const response = await orderPreviewFetcher.post("/order_preview", dataToPreview); 116 if (!isResponseError(response)) { 117 lastOrderPreviewResponse = response; 118 return lastOrderPreviewResponse; 119 } else { 120 return response; 121 } 122} 123function processOrderPreviewResponseForSummary(summaryData) { 124 let nextInvoiceText; 125 if (summaryData.next_invoice_date) { 126 const nextInvoiceDate = new Date(summaryData.next_invoice_date); 127 const currentDate = /* @__PURE__ */ new Date(); 128 const diff = nextInvoiceDate.getTime() - currentDate.getTime(); 129 if (diff > 5 * 60 * 1e3) { 130 if (diff < 24 * 60 * 60 * 1e3) { 131 nextInvoiceText = nextInvoiceDate.toLocaleString(); 132 } else { 133 nextInvoiceText = nextInvoiceDate.toLocaleDateString(); 134 } 135 } 136 } 137 return { 138 currency: summaryData.currency, 139 line_items: summaryData.line_items, 140 total: { 141 amount: parseFloat(summaryData.total_amount?.amount), 142 formatted: summaryData.total_amount_formatted, 143 amount_cents: summaryData.total_amount_cents 144 }, 145 discount: { 146 amount: parseFloat(summaryData.discount_amount?.amount), 147 formatted: summaryData.discount_amount_formatted, 148 amount_cents: summaryData.discount_amount_cents 149 }, 150 lineItemSubtotal: { 151 amount: parseFloat(summaryData.line_item_subtotal?.amount), 152 formatted: summaryData.line_item_subtotal_formatted, 153 amount_cents: summaryData.line_item_subtotal_cents 154 }, 155 discounts: (summaryData.discounts ?? []).map((discount) => { 156 return { 157 name: discount.name, 158 code: discount.code, 159 amount_formatted: "-" + formatNumberToCurrency(summaryData.currency_symbol, discount.amount) 160 }; 161 }), 162 ...nextInvoiceText ? { next_invoice_date: nextInvoiceText } : {}, 163 subtotal: { 164 amount: parseFloat(summaryData.subtotal_amount.amount), 165 formatted: summaryData.subtotal_amount_formatted, 166 amount_cents: summaryData.subtotal_amount_cents 167 }, 168 shipping: { 169 amount: parseFloat(summaryData.shipping_amount.amount), 170 formatted: summaryData.shipping_amount_formatted, 171 amount_cents: summaryData.shipping_amount_cents 172 }, 173 tax: { 174 amount: parseFloat(summaryData.tax_amount.amount), 175 formatted: summaryData.tax_amount_formatted, 176 amount_cents: summaryData.tax_amount_cents 177 }, 178 ...summaryData.upcoming_invoice?.interim_amount ? { 179 interim: { 180 amount: parseFloat(summaryData.upcoming_invoice.interim_amount.amount), 181 formatted: summaryData.upcoming_invoice?.interim_amount_formatted, 182 amount_cents: summaryData.upcoming_invoice?.interim_amount_cents 183 } 184 } : {}, 185 ...summaryData.upcoming_invoice ? { 186 upcoming_invoice: { 187 line_items: summaryData.upcoming_invoice.line_items.filter((i) => i.interim).map((item) => { 188 return { 189 variant_name: item.variant_name, 190 quantity: item.quantity, 191 amount: parseFloat(item.amount.amount), 192 formatted: item.amount_formatted 193 }; 194 }) 195 } 196 } : {} 197 }; 198} 199function buildCartDetails(cart) { 200 const orderId = cart.order_id; 201 const lineItems = cart.items.map(({ price_id, line_item_id, variant_id, quantity }) => { 202 const data = { 203 variant_id, 204 price_id, 205 ...line_item_id ? { line_item_id } : {}, 206 quantity 207 }; 208 return data; 209 }); 210 return { order_id: orderId, line_items: lineItems }; 211} 212function buildOrderPreviewPayload(payload) { 213 const { address, shippingOption, cart, integrations_stripe_installation_id, coupon_code } = payload; 214 const isPhysical = hasPhysicalProducts(cart); 215 return { 216 integrations_stripe_installation_id, 217 order: { 218 full_preview: true, 219 coupon_codes: coupon_code, 220 ...address ? buildAddressParams(address, isPhysical) ?? {} : {}, 221 ...buildCartDetails(cart), 222 ...shippingOption?.amount ? { selected_shipping_option: shippingOption } : {} 223 } 224 }; 225} 226function buildAddressParams(address, isPhysical) { 227 const delivery_address = { ...address }; 228 const billing_address = address.id ? { id: address.id } : { ...address }; 229 if (isPhysical) { 230 return { 231 delivery_address, 232 billing_address 233 }; 234 } else { 235 return { billing_address }; 236 } 237} 238
239// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/consts.ts 240init_define_process(); 241var stripeElementsForms = ["linkAuthentication", "shipping", "billing", "payment"]; 242var FORM_STRUCTURE_BY_TOTAL_STEPS = { 243 1: [["linkAuthentication", "shipping", "billing", "shippingOption", "products", "payment", "tos"]], 244 2: [ 245 ["linkAuthentication", "contact"], 246 ["shipping", "billing", "shippingOption", "products", "payment", "tos"] 247 ], 248 3: [["products"], ["linkAuthentication", "contact"], ["shipping", "billing", "shippingOption", "payment", "tos"]] 249}; 250
251// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/errors.ts 252init_define_process(); 253var StripeCheckoutErrorCode = /* @__PURE__ */ ((StripeCheckoutErrorCode2) => { 254 StripeCheckoutErrorCode2[StripeCheckoutErrorCode2["STRIPE_LIB_INITIALIZATION_ERROR"] = 1] = "STRIPE_LIB_INITIALIZATION_ERROR"; 255 StripeCheckoutErrorCode2[StripeCheckoutErrorCode2["STRIPE_ADDRESS_ELEMENT_INITIALIZATION_ERROR"] = 2] = "STRIPE_ADDRESS_ELEMENT_INITIALIZATION_ERROR"; 256 StripeCheckoutErrorCode2[StripeCheckoutErrorCode2["STRIPE_PAYMENT_ELEMENT_INITIALIZATION_ERROR"] = 4] = "STRIPE_PAYMENT_ELEMENT_INITIALIZATION_ERROR"; 257 StripeCheckoutErrorCode2[StripeCheckoutErrorCode2["STRIPE_LINK_AUTHENTICATION_ELEMENT_INITIALIZATION_ERROR"] = 8] = "STRIPE_LINK_AUTHENTICATION_ELEMENT_INITIALIZATION_ERROR"; 258 StripeCheckoutErrorCode2[StripeCheckoutErrorCode2["STRIPE_EXPRESS_CHECKOUT_ELEMENT_INITIALIZATION_ERROR"] = 16] = "STRIPE_EXPRESS_CHECKOUT_ELEMENT_INITIALIZATION_ERROR"; 259 StripeCheckoutErrorCode2[StripeCheckoutErrorCode2["API_SESSION_INITIALIZATION_ERROR"] = 32] = "API_SESSION_INITIALIZATION_ERROR"; 260 StripeCheckoutErrorCode2[StripeCheckoutErrorCode2["MOUNT_REJECTED_ERROR"] = 64] = "MOUNT_REJECTED_ERROR"; 261 return StripeCheckoutErrorCode2; 262})(StripeCheckoutErrorCode || {}); 263function getStripeCheckoutErrorMessage(stripeErrors) { 264 if (stripeErrors.length > 0) { 265 const errorCodeMask = `0x${Number(stripeErrors.reduce((acc, val) => acc | val, 0)).toString(16)}`; 266 return "Error initializing Checkout Element. Code: " + errorCodeMask; 267 } 268} 269function getInitialStripeCheckoutErrors() { 270 const initialCriticalErrors = []; 271 if (!globalThis.globalResourceData.productVariantWithNoBump?.id) { 272 initialCriticalErrors.push({ code: ERROR_CODES.EMPTY_PRODUCTS_ERROR }); 273 } 274 return initialCriticalErrors; 275} 276
277// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/stripeCheckout.ts 278init_fetcher(); 279init_cf_utils(); 280
281// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/api.ts 282init_define_process(); 283init_fetcher(); 284init_nonce(); 285function makeApiFetcher(options) { 286 const { headers, ...requestOptions } = options; 287 const fetcher = new Fetcher({ 288 debug: true, 289 pathPrefix: "/user_pages/api/v1/", 290 headers: { 291 Accept: "application/json", 292 "Content-Type": "application/json", 293 ...headers ?? {} 294 }, 295 requestOptions: { 296 retries: 3, 297 timeoutMS: 3e4, 298 timeoutAfterRetrial: 1e3, 299 shouldCaptureServerError: true, 300 convertThrowToErrorResponse: true, 301 ...requestOptions 302 } 303 }); 304 return fetcher; 305} 306var stripeSessionApiFetcher = makeApiFetcher({ 307 onlyOneInflightRequest: true, 308 simulateNetworkError: false, 309 // `stripe_get_session` only syncs the contact and mints a customer session, so a 310 // replay is harmless. It is also the call that fails when Stripe's per-object lock 311 // is held (ENG-7225) â the one case where retrying a 5xx is exactly the fix. 312 retryServerErrors: true, 313 // Same reasoning for a timed-out attempt. This is deliberately not switched on for 314 // `stripeSubmitFetcher` below: replaying a submit is a much heavier claim to make. 315 retryTimeouts: true, 316 // ...but bound the sequence in wall clock. Making `retries` finally apply to timeouts is 317 // a behaviour change, and on the inherited `timeoutMS: 30000` / `retries: 3` it would 318 // cost the buyer 3 x 30s + 2 x 1s = 92s of blank checkout before the banner, and issue 3 319 // backend requests per browser. Aborting a fetch does not stop the Rails request â Puma 320 // holds the thread until it responds â so those retries would amplify load onto a pool 321 // whose threads are already parked on the object-lock sleep, which is precisely when a 322 // timeout is most likely. 15s clears the server's 8s sleep budget plus a normal Stripe 323 // round trip, and 2 attempts keep the retry that actually fixes a transient lock while 324 // holding the worst case at ~31s â the same ceiling buyers saw before. 325 timeoutMS: 15e3, 326 retries: 2 327}); 328var stripeSubmitFetcher = new Fetcher({ 329 debug: true, 330 method: "POST", 331 headers: { 332 Accept: "application/json", 333 "Content-Type": "application/json", 334 "X-CF2-POST-TYPE": "submit" 335 }, 336 requestOptions: { 337 retries: 3, 338 timeoutMS: 6e4, 339 timeoutAfterRetrial: 1e3, 340 shouldCaptureServerError: true, 341 convertThrowToErrorResponse: true, 342 onlyOneInflightRequest: true, 343 canAbort: false 344 } 345}); 346function isStripeError(error) { 347 if (typeof error === "object") { 348 const stripeError = error; 349 if (stripeError?.code && stripeError?.message) { 350 return true; 351 } 352 } 353 return false; 354} 355async function postSubmit(payload) { 356 const nonce = await fetchCheckoutNonce(); 357 const response = await stripeSubmitFetcher.fetch( 358 renderedHref(), 359 { 360 credentials: "same-origin", 361 body: JSON.stringify({ ...payload, ...checkoutNonceParams(nonce) }) 362 }, 363 {} 364 ); 365 if (isResponseError(response)) { 366 return response; 367 } else { 368 let body; 369 if (response.headers.get("Content-Type").includes("text/html")) { 370 body = { 371 next_url: response.headers.get("Location") 372 }; 373 } else { 374 body = await response.json(); 375 } 376 if (response.ok) { 377 return { 378 ...body, 379 headers: { 380 get: (name) => response.headers?.get(name) 381 } 382 }; 383 } else if (response.status === 422) { 384 return { 385 fetcherErrorType: 1 /* Backend */, 386 error: normalizeServerCheckoutError(body) 387 }; 388 } else { 389 return { 390 fetcherErrorType: 0 /* Internal */, 391 error: `Unexpected response status ${response.status}` 392 }; 393 } 394 } 395} 396
397// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/store.ts 398init_define_process(); 399init_nanostores(); 400function ensureCheckoutStoreInitialized() { 401 globalThis.Checkout.store = globalThis.Checkout.store ?? { 402 state: atom("start" /* START */), 403 coupons: { 404 appliedCode: atom(""), 405 currentCode: atom(""), 406 errorMessage: atom(""), 407 state: atom("ready" /* READY */) 408 }, 409 summary: atom({ 410 state: "waiting" /* WAITING */, 411 data: {} 412 }), 413 checkout: { 414 step: atom(0) 415 }, 416 submitting: atom({ 417 state: "idle" /* IDLE */ 418 }), 419 criticalErrors: atom(getInitialStripeCheckoutErrors()), 420 featureFlags: {}, 421 selectedOrderDetailId: atom(null) 422 }; 423 globalThis.Checkout.computed = globalThis.Checkout.computed ?? { 424 checkoutCart: buildProductSelectBatched() 425 }; 426 globalThis.Checkout.computed.isCouponReady = computed( 427 [globalThis.Checkout.store.state, globalThis.Checkout.computed.checkoutCart], 428 (state, cart) => { 429 const showAllProductErrors = true; 430 const pErrors = globalThis.Checkout.utils.productErrors(cart, showAllProductErrors); 431 return state == globalThis.Checkout.StoreStates.START && !globalThis.Checkout.utils.hasErrors(pErrors); 432 } 433 ); 434 return { 435 ...globalThis.Checkout.store, 436 ...globalThis.Checkout.computed 437 }; 438} 439
440// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/stripe.ts 441init_define_process(); 442var Stripe; 443var stripeCurrency = "usd"; 444var stripeInstallationId = ""; 445function ensureStripeInitialized() { 446 if (!Stripe) { 447 const { 448 stripePublishableKey, 449 stripeAccountId, 450 stripeInstallationId: instalationId, 451 stripeCurrency: currency 452 } = document.querySelector("#payment-gateway-keys").dataset; 453 Stripe = globalThis.Stripe?.(stripePublishableKey, { 454 stripeAccount: stripeAccountId, 455 betas: ["disable_deferred_intent_client_validation_beta_1"] 456 }); 457 stripeInstallationId = instalationId; 458 stripeCurrency = currency; 459 } 460 return Stripe; 461} 462function ensureStripeScriptLoaded() { 463 if (globalThis.Stripe) return Promise.resolve(); 464 const lazyScript = document.querySelector('script[data-lazy-src*="stripe"]'); 465 if (!lazyScript) return Promise.resolve(); 466 return new Promise((resolve, reject) => { 467 const script = document.createElement("script"); 468 script.src = lazyScript.getAttribute("data-lazy-src"); 469 script.onload = () => resolve(); 470 script.onerror = () => reject(new Error("Failed to load Stripe.js")); 471 lazyScript.parentNode.insertBefore(script, lazyScript); 472 lazyScript.remove(); 473 }); 474} 475function buildStripeFonts() { 476 const fonts = []; 477 const googleFontsSrc = document.getElementById("cf-lander-font-families")?.href; 478 if (googleFontsSrc) { 479 fonts.push({ cssSrc: googleFontsSrc }); 480 } 481 try { 482 const serializedCustomFonts = JSON.parse( 483 document.getElementById("cf-lander-serialized-custom-fonts")?.textContent ?? "[]" 484 ); 485 fonts.push( 486 ...serializedCustomFonts.map( 487 (customFont) => ({ 488 family: customFont.family, 489 src: customFont.files.map((src) => `url(${new URL(src, document.location.href).toString()})`).join(",") 490 }) 491 ) 492 ); 493 } catch (e) { 494 console.error("Error preparing custom fonts to stripe elements", e); 495 } 496 return fonts; 497} 498
499// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/enums.ts 500init_define_process(); 501var CheckoutStates = /* @__PURE__ */ ((CheckoutStates2) => { 502 CheckoutStates2["GUEST"] = "guest"; 503 CheckoutStates2["OTO"] = "oto"; 504 CheckoutStates2["SAVED"] = "saved"; 505 return CheckoutStates2; 506})(CheckoutStates || {}); 507
508// projects/lib/packages/yggdrasil-blueprints/src/Elements/Checkout/V2/StripeCheckout/stripeCheckout.ts 509init_cf_utils(); 510function createInstance(component) { 511 const stripe = ensureStripeInitialized(); 512 const store = ensureCheckoutStoreInitialized(); 513 return new StripeCheckoutLogic(component, stripe, store); 514} 515var CHECKOUT_STATES = Object.values(CheckoutStates); 516var STRIPE_INSTRUMENT_REJECTION_CODES = /* @__PURE__ */ new Set([ 517 "card_declined", 518 "card_not_supported", 519 "currency_not_supported", 520 "expired_card", 521 "incorrect_cvc", 522 "incorrect_number", 523 "incorrect_zip", 524 "processing_error" 525]); 526var StripeCheckoutLogic = class { 527 constructor(component, stripe, store) { 528 this.component = component; 529 this.stripe = stripe; 530 this.store = store; 531 __publicField(this, "_stripeData"); 532 __publicField(this, "guestInputFields", {}); 533 __publicField(this, "paymentDeclined", false); 534 __publicField(this, "submitedOnce", false); 535 __publicField(this, "checkoutData", { contact: {}, address: {} }); 536 __publicField(this, "digitalPaymentData", { contact: {}, address: {}, shippingRateId: "" }); 537 __publicField(this, "parsedAppearence"); 538 __publicField(this, "summaryLineItemSubtotalCents"); 539 __publicField(this, "summaryLineItems"); 540 __publicField(this, "stripeElementOptions"); 541 __publicField(this, "stripeFonts"); 542 __publicField(this, "shippingComponents"); 543 __publicField(this, "selectedShippingOption"); 544 __publicField(this, "errors"); 545 __publicField(this, "element"); 546 __publicField(this, "loadingComponents"); 547 __publicField(this, "submitButtons"); 548 __publicField(this, "submitNotifications"); 549 __publicField(this, "sendOrderPreviewTimer"); 550 __publicField(this, "lastSubmitFailure", null); 551 __publicField(this, "setupErrors"); 552 __publicField(this, "oldHasPhysical"); 553 __publicField(this, "preventOrderPreview", false); 554 __publicField(this, "submitInFlight", false); 555 // Identity-tracked entry the summary subscriber pushes into `errors.globalErrors` so we 556 // can remove exactly it without clobbering globals set by submit-time flows. 557 __publicField(this, "summaryErrorEntry", null); 558 __publicField(this, "couponStateBeforeApplying"); 559 __publicField(this, "sentToTracking", { email: null, firstName: null, lastName: null, phone: null }); 560 __publicField(this, "trackingTimer"); 561 this._stripeData = {}; 562 CHECKOUT_STATES.forEach((state) => { 563 this._stripeData[state] = { 564 stripeElements: null, 565 mountedElements: { 566 shipping: {}, 567 billing: {}, 568 expressCheckout: {}, 569 linkAuthentication: {}, 570 payment: {} 571 } 572 }; 573 }); 574 this.element = component.element; 575 this.errors = { globalErrors: [], fields: {} }; 576 this.setupErrors = []; 577 this.parsedAppearence = parseCssVars(component.element, this.component.appearance); 578 this.stripeFonts = buildStripeFonts(); 579 const cart = this.store.checkoutCart.get(); 580 if (globalThis.sessionData.contact.email) { 581 this.checkoutData.contact.email = globalThis.sessionData.contact.email; 582 } 583 this.updateWithShippingAddress(cart); 584 globalThis.montedElements = this.montedElements; 585 console.log("[Stripe] appearance => ", this.parsedAppearence); 586 } 587 triggerTrackingForStripeElement(data) { 588 clearTimeout(this.trackingTimer); 589 this.trackingTimer = setTimeout(() => { 590 try { 591 if (data.email && this.sentToTracking.email !== data.email) { 592 this.sentToTracking.email = data.email; 593 this.createTrackingInput("email", data.email); 594 } 595 if (data.firstName && this.sentToTracking.firstName !== data.firstName && !this.guestInputFields.first_name) { 596 this.sentToTracking.firstName = data.firstName; 597 this.createTrackingInput("first_name", data.firstName); 598 } 599 if (data.lastName && this.sentToTracking.lastName !== data.lastName && !this.guestInputFields.last_name) { 600 this.sentToTracking.lastName = data.lastName; 601 this.createTrackingInput("last_name", data.lastName); 602 } 603 if (data.phone && this.sentToTracking.phone !== data.phone && !this.guestInputFields.phone_number) { 604 this.sentToTracking.phone = data.phone; 605 this.createTrackingInput("phone_number", data.phone); 606 } 607 } catch (error) { 608 console.error("[Stripe] Tracking helper error:", error); 609 } 610 }, 200); 611 } 612 createTrackingInput(name, value) { 613 const input = document.createElement("input"); 614 input.type = name.includes("email") ? "email" : "text"; 615 input.name = name; 616 input.value = value; 617 input.style.cssText = "position:absolute;
617left:-9999px;opacity:0;pointer-events:none"; 618 document.body.appendChild(input); 619 input.dispatchEvent(new Event("focusout", { bubbles: true })); 620 input.dispatchEvent(new Event("change", { bubbles: true })); 621 input.dispatchEvent(new Event("blur", { bubbles: true })); 622 document.body.removeChild(input); 623 } 624 async ensureStripeSession() { 625 const startedAt = Date.now(); 626 const sessionResponse = await stripeSessionApiFetcher.post("checkouts/stripe_get_session", { 627 referrer_url: window.location.href, 628 integrations_stripe_installation_id: stripeInstallationId 629 }); 630 if (isResponseError(sessionResponse)) { 631 const failureKind = this.sessionFailureKind(sessionResponse); 632 const durationMs = Date.now() - startedAt; 633 if (failureKind === "superseded" && !this.component.element.isConnected) { 634 globalThis.Honeybadger?.notify("StripeCheckout session superseded", { 635 context: { durationMs, checkoutState: this.getState() } 636 }); 637 return null; 638 } 639 this.pushSetupError(32 /* API_SESSION_INITIALIZATION_ERROR */, void 0, { 640 failureKind, 641 durationMs 642 }); 643 } else { 644 return sessionResponse; 645 } 646 } 647 // Maps the fetcher error to a stable label: 648 // 'timeout_error' â every attempt hit the fetcher's own 30s timer 649 // 'server_error' â the endpoint 5xx'd on every attempt 650 // 'network_error' â the request itself failed 651 // 'superseded' â a later request on the same fetcher replaced this one 652 sessionFailureKind(response) { 653 if (!isFetcherError(response)) return "backend_error"; 654 const error = response.error; 655 if (error === MANUALLY_ABORTED) return "superseded"; 656 if (error instanceof CFFetcherError) return error.type.toLowerCase(); 657 return String(error); 658 } 659 backfillContactData() { 660 const shouldUseGarlic = this.getState() === "guest"; 661 ["first_name", "last_name", "email", "phone_number", "name"].forEach((k) => { 662 if (k === "phone_number" && !this.hasPhoneNumber()) return; 663 const garlicValue = shouldUseGarlic ? globalThis.cfGarlicUtils.retrieve(k) : ""; 664 const sessionValue = globalThis.sessionData.contact[k]; 665 if (!garlicValue && !sessionValue) return; 666 if (k === "name" && !this.checkoutData.contact.first_name) { 667 const [first_name, ...last_names] = garlicValue.split(" "); 668 this.checkoutData.contact.first_name = first_name; 669 this.checkoutData.contact.last_name = last_names.join(" "); 670 } else { 671 this.checkoutData.contact[k] = sessionValue ?? garlicValue; 672 } 673 }); 674 this.syncInputFields(); 675 } 676 updateInputState(name, value) { 677 globalThis.cfGarlicUtils.store(name, value); 678 if (value) { 679 this.guestInputFields[name].closest('[data-page-element="Input/V1"]').classList.add("hasValue"); 680 } else { 681 this.guestInputFields[name].closest('[data-page-element="Input/V1"]').classList.remove("hasValue"); 682 } 683 } 684 storeShippingDataFromExpress(data) { 685 if (!data) return; 686 if (data.name) globalThis.cfGarlicUtils.store("name", data.name); 687 if (!data.address) return; 688 const address = convertAddressFromStripe(data.address); 689 Object.keys(address).forEach((k) => { 690 if (address[k]) globalThis.cfGarlicUtils.store(k, address[k]); 691 }); 692 } 693 syncInputFields() { 694 Object.keys(this.guestInputFields).forEach((k) => { 695 const value = this.checkoutData.contact[k]; 696 if (k == "phone_number") { 697 ; 698 this.guestInputFields[k].iti.setNumber(value ?? ""); 699 } else { 700 this.guestInputFields[k].value = value ?? ""; 701 } 702 this.updateInputState(k, value); 703 }); 704 } 705 errorInputForInput(input) { 706 const id = input.closest("[data-page-element]").getAttribute("id"); 707 return this.element.querySelector(`[data-input-id="${id}"]`); 708 } 709 updateWithShippingAddress(cart) { 710 this.checkoutData.address = {}; 711 if (hasPhysicalProducts(cart)) { 712 const sessionShippingAddress = globalThis.sessionData.contact.shipping_addresses?.[0] ?? globalThis.sessionData.contact.billing_addresses?.[0]; 713 if (sessionShippingAddress) { 714 this.checkoutData.address = convertAddressFromSession(sessionShippingAddress); 715 } 716 } 717 if (this.getState() == "oto" && !hasPhysicalProducts(cart)) { 718 const sessionBillingAddress = globalThis.sessionData.contact.billing_addresses?.[0]; 719 if (sessionBillingAddress) { 720 this.checkoutData.address = convertAddressFromSession(sessionBillingAddress); 721 } 722 } 723 if (globalThis.sessionData.contact) { 724 this.checkoutData.contact.first_name = globalThis.sessionData.contact.first_name; 725 this.checkoutData.contact.last_name = globalThis.sessionData.contact.last_name; 726 this.checkoutData.contact.phone_number = globalThis.sessionData.contact.phone_number; 727 this.syncInputFields(); 728 } 729 } 730 initializeElements(session, amount) { 731 const cart = this.store.checkoutCart.get(); 732 this.stripeElementOptions = { 733 amount, 734 mode: getStripeMode(cart, amount), 735 currency: stripeCurrency.toLowerCase() ?? "usd", 736 appearance: this.parsedAppearence, 737 loader: "never", 738 fonts: this.stripeFonts, 739 locale: this.component.locale,
740 ...this.component.paymentGateway.disable_stripe_payment_method_future_usage ? {} : { setup_future_usage: "off_session" }, 741 ...session?.can_use_dpm ? {} : { payment_method_types: ["card"] }, 742 ...session?.stripe_options ?? {} 743 }; 744 this.stripeElements = this.stripe.elements(this.stripeElementOptions); 745 globalThis.stripeElements = this.stripeElements; 746 this.stripeElements.on("update-end", () => { 747 this.updatingExpressEnded(); 748 }); 749 } 750 mount() { 751 if (!this.stripe) { 752 this.pushSetupError(1 /* STRIPE_LIB_INITIALIZATION_ERROR */); 753 return; 754 } 755 this.submitNotifications = this.component.getComponentGroup(CheckoutSubmitNotificationV1); 756 this.detectIfReturningFailedPayment(); 757 this.backfillContactData(); 758 Promise.all([ 759 this.ensureStripeSession(), 760 CheckoutExistingOrders.fetch({ 761 integrations_stripe_installation_id: stripeInstallationId 762 }).catch((error) => { 763 const cause = error instanceof CheckoutCriticalError ? error.cause : null; 764 if (cause !== MANUALLY_ABORTED) throw error; 765 }).then(() => { 766 this.updateSubmitButton(); 767 }) 768 ]).then(([sessionData]) => { 769 if (sessionData) { 770 const cart = this.store.checkoutCart.get(); 771 const intialAmount = getCartEstimatedTotal(cart); 772 this.initializeElements(sessionData, intialAmount); 773 this.sendOrderPreview(); 774 setTimeout(() => { 775 this.oneTimeListeners(); 776 }, 1e3); 777 } 778 }).catch((error) => { 779 this.pushSetupError(64 /* MOUNT_REJECTED_ERROR */, void 0, { 780 failureKind: "mount_rejected", 781 mountError: String(error?.message ?? error), 782 // Keep the stack and any wrapped cause: the notify message is held constant per 783 // code for grouping, so context is the only place the specific failure survives. 784 mountErrorStack: error?.stack, 785 mountErrorCause: error?.cause ? String(error.cause?.message ?? error.cause) : void 0 786 }); 787 }); 788 setupOrderSummaries(this.component); 789 this.component.setHandlers(CheckoutMultiStepV2, { 790 onValidateStep: (step) => this.validateStep(step), 791 onStepChange: (oldStep, step) => { 792 this.stripeElements.update({ appearance: this.parsedAppearence }); 793 const formStructure = FORM_STRUCTURE_BY_TOTAL_STEPS[this.getTotalSteps()]; 794 if (formStructure[oldStep].includes("contact") && oldStep < step) { 795 collectLeads(this.checkoutData.contact); 796 } 797 } 798 }); 799 this.component.setHandlers(CheckoutTosV1, { 800 onAcceptedChange: (accepted) => { 801 if (this.submitedOnce && accepted) { 802 this.clearErrors("tos"); 803 this.showErrors(); 804 } 805 } 806 }); 807 this.shippingComponents = this.component.getComponentGroup(CheckoutShippingProfileSelectV1, { 808 onSelectOption: (option) => this.selectShippingOption(option) 809 }); 810 window.addEventListener("ON_APPLY_COUPON", (event) => { 811 this.sendOrderPreview(event.detail); 812 }); 813 window.addEventListener("ON_CLEAR_COUPON", (event) => { 814 this.sendOrderPreview(event.detail); 815 }); 816 this.loadingComponents = this.component.getComponentGroup(CheckoutLoadingV1); 817 this.submitButtons = this.component.getComponentGroup(CheckoutSubmitButtonV1, { 818 // NOTE: returned so the button can tell a finished attempt from one that is 819 // still running, and unlock itself when we never got as far as the POST. 820 onSubmit: () => this.handleSubmit(this.selectedShippingOption) 821 }); 822 } 823 detectIfReturningFailedPayment() { 824 const urlParams = new URLSearchParams(window.location.search); 825 const failedPaymentMessage = urlParams.get("failed_payment_message"); 826 if (failedPaymentMessage) { 827 const decodedFailedMessage = decodeURIComponent(failedPaymentMessage.replace(/\+/g, " ")); 828 this.handleError({ message: decodedFailedMessage }, true); 829 const visibleSubmitButton = this.getStateElement().querySelector("[data-page-element='CheckoutSubmitButton/V1']"); 830 $("html, body").stop(true, true).animate( 831 { 832 scrollTop: $(visibleSubmitButton).offset().top - 100 833 }, 834 500 835 ); 836 this.submitedOnce = true; 837 } 838 } 839 submitCheckout(redirectOverride) { 840 return this.handleSubmit(this.selectedShippingOption, false, redirectOverride); 841 } 842 clearErrors(field) { 843 this.errors.globalErrors = []; 844 if (field) { 845 delete this.errors.fields[field]; 846 } 847 } 848 clearAllErrors() { 849 this.errors.globalErrors = []; 850 this.errors.fields = {}; 851 } 852 pushGlobalError(message) { 853 this.errors.globalErrors.push({ message }); 854 } 855 pushFieldError(field, message) { 856 this.errors.fields[field] = { message }; 857 } 858 showErrors({ shouldScrollToError } = { shouldScrollToError: false }) { 859 const stepStore = this.store.checkout.step; 860 const currentStep = stepStore.get(); 861 const totalSteps = this.getTotalSteps();
862 let wrapperStateElement = this.getStateElement(); 863 let hasFieldErrors = false; 864 let firstFieldWithError = null; 865 const formStructure = FORM_STRUCTURE_BY_TOTAL_STEPS[totalSteps]; 866 for (let stepIndex = 0; stepIndex < formStructure.length; stepIndex++) { 867 const stepFields = formStructure[stepIndex]; 868 for (const field of stepFields) { 869 if (this.errors.fields[field]) { 870 hasFieldErrors = true; 871 if (stepIndex !== currentStep) stepStore.set(stepIndex); 872 if (!firstFieldWithError) firstFieldWithError = field; 873 break; 874 } 875 } 876 } 877 if (totalSteps > 1) { 878 wrapperStateElement = wrapperStateElement.querySelector( 879 `.elMultiStepBody[data-step-number="${stepStore.get() + 1}"]` 880 ); 881 } 882 Object.keys(this.guestInputFields).forEach((field) => { 883 if (this.errors.fields[field]) { 884 const errorfield = this.errorInputForInput(this.guestInputFields[field]); 885 errorfield.style.height = "20px"; 886 errorfield.previousElementSibling.classList.add("elInputError"); 887 errorfield.textContent = this.errors.fields[field].message; 888 } 889 }); 890 const generalErrorsWrapper = wrapperStateElement.querySelector(".checkout-general-errors-wrapper"); 891 this.component.element.querySelectorAll(".checkout-general-errors-wrapper").forEach((w) => { 892 w.classList.add("forceHide"); 893 const span = w.querySelector("span"); 894 if (span) span.innerHTML = ""; 895 }); 896 const hasGlobalErrors = this.errors.globalErrors.length && this.errors?.globalErrors?.[0]?.message; 897 if (hasGlobalErrors) { 898 const errorMessage = this.errors.globalErrors[0].message; 899 generalErrorsWrapper.querySelector("span").innerHTML = errorMessage; 900 generalErrorsWrapper.classList.remove("forceHide"); 901 } 902 if (shouldScrollToError && (hasGlobalErrors || hasFieldErrors)) { 903 if (stripeElementsForms.includes(firstFieldWithError)) { 904 const scrollToElement = wrapperStateElement.querySelector(`.stripe-${firstFieldWithError}`); 905 scrollToFirstVisibleError(scrollToElement); 906 } else { 907 scrollToFirstVisibleError(generalErrorsWrapper); 908 } 909 } 910 } 911 removeSubmitNotification() { 912 this.submitNotifications.setStatus("", "", ""); 913 } 914 setSubmitNotification(text, subtext, extraText) { 915 this.submitNotifications.setStatus(text, subtext, extraText); 916 } 917 get montedElements() {
918 return this._stripeData[this.getState()].mountedElements; 919 } 920 get stripeElements() { 921 return this._stripeData[this.getState()].stripeElements; 922 } 923 set stripeElements(stripeElement) { 924 this._stripeData[this.getState()].stripeElements = stripeElement; 925 } 926 getState() { 927 return this.component.element.dataset.currentState; 928 } 929 setState(state) { 930 this.component.element.dataset.currentState = state; 931 } 932 updatingExpressStarted() { 933 this.getStateElement().querySelector(".stripe-express-loader-wrapper")?.classList.remove("forceHide"); 934 this.getStateElement().querySelector(".stripe-expressCheckout")?.classList.add("elHidden"); 935 } 936 updatingExpressEnded() { 937 this.getStateElement().querySelector(".stripe-express-loader-wrapper")?.classList.add("forceHide"); 938 this.getStateElement().querySelector(".stripe-expressCheckout")?.classList.remove("elHidden"); 939 } 940 getStateElement() { 941 return this.component.element.querySelector(`[data-state="${this.component.element.dataset.currentState}"]`); 942 } 943 unmountElement(name) { 944 if (!this.montedElements[name].element) return; 945 this.montedElements[name].element.unmount(); 946 const stripeElement = this.getStateElement().querySelector(`.stripe-${name}`); 947 delete stripeElement?.style.opacity; 948 this.montedElements[name].lastChangeEvent = null; 949 this.montedElements[name].mounted = false; 950 } 951 mountElement(name, options, overrideSelector, overrideMountName) { 952 console.log("[Stripe] mounteElement =>", name, options); 953 const state = this.getState(); 954 const selector = overrideSelector ?? `.stripe-${name}`; 955 const hasElement = this.getStateElement().querySelector(selector); 956 if (!hasElement) return; 957 const mountName = overrideMountName ?? name; 958 if (!this.montedElements[mountName].element) { 959 options = options ?? {}; 960 this.montedElements[mountName].element = this.stripeElements.create(name, options); 961 } 962 this.montedElements[mountName].element.mount(`[data-state=${state}] ${selector}`); 963 this.montedElements[mountName].mounted = true; 964 } 965 mountLink() { 966 if (this.montedElements.linkAuthentication.mounted) return; 967 const options = {}; 968 if (this.checkoutData.contact.email) { 969 options.defaultValues = { email: this.checkoutData.contact.email }; 970 } 971 this.mountElement("linkAuthentication", options); 972 this.montedElements.linkAuthentication.element?.on("change", (event) => { 973 this.removeSubmitNotificationStripeElementChange(); 974 console.log("[Stripe] linkAuthentication [incoming-event]: ", event); 975 delete this.errors.fields["linkAuthentication"]; 976 this.montedElements.linkAuthentication.lastChangeEvent = event; 977 if (!event.complete) return; 978 this.checkoutData.contact.email = event.value.email; 979 this.triggerTrackingForStripeElement({ email: event.value.email }); 980 this.updatePaymentElementDefaultValues(); 981 }); 982 this.montedElements.linkAuthentication.element?.on("ready", () => { 983 this.elementReady("linkAuthentication"); 984 this.elementReady("contact-form"); 985 }); 986 this.montedElements.linkAuthentication.element?.on( 987 "loaderror", 988 (error) => this.handleStripeElementLoadingError(error) 989 ); 990 } 991 pushSetupError(code, customMessage, context) { 992 this.setupErrors.push(code); 993 const setupErrorComponent = this.component.getComponent(CheckoutConfigurationErrorV1); 994 const errorMessage = customMessage || getStripeCheckoutErrorMessage(this.setupErrors); 995 setupErrorComponent.setErrors(errorMessage); 996 scrollToFirstVisibleError(setupErrorComponent.element); 997 globalThis.Honeybadger?.notify(`StripeCheckout setup error: ${StripeCheckoutErrorCode[code]}`, { 998 context: { 999 errorCode: `0x${code.toString(16)}`, 1000 // The banner shows the OR of all codes pushed so far. Report it too so 1001 // a banner screenshot (e.g. "Code: 0x24") can be matched to notices. 1002 errorMask: `0x${this.setupErrors.reduce((acc, val) => acc | val, 0).toString(16)}`, 1003 checkoutState: this.getState(), 1004 hasContactEmail: Boolean(globalThis.sessionData?.contact?.email), 1005 ...context ?? {} 1006 } 1007 }); 1008 } 1009 handleStripeElementLoadingError({ elementType, error }) { 1010 const errorEnumName = `STRIPE_${elementType.split(/(?=[A-Z])/).join("_").toUpperCase()}_ELEMENT_INITIALIZATION_ERROR`; 1011 const errorCode = StripeCheckoutErrorCode[errorEnumName]; 1012 const errorMessage = error?.message ? `Error loading ${elementType} element: ${error.message}` : getStripeCheckoutErrorMessage([errorCode]); 1013 this.pushSetupError(errorCode, errorMessage, { elementType, stripeError: error }); 1014 console.error(`Stripe ${elementType} element initialization error: `, error); 1015 } 1016 getTotalSteps() {
1017 return this.getState() !== "guest" ? 1 : this.component.totalSteps; 1018 } 1019 mountAddress() { 1020 const options = this.addressOptions; 1021 const mode = options.mode; 1022 const state = this.getState(); 1023 if (this.montedElements[mode].mounted) return; 1024 if (mode === "billing" && state === "oto" /* OTO */ && !this.paymentDeclined) return; 1025 const stateElement = this.getStateElement(); 1026 const otherMode = options.mode == "shipping" ? "billing" : "shipping"; 1027 stateElement.querySelector(`.stripe-address-${otherMode}-container`)?.classList.add("forceHide"); 1028 this.unmountElement(otherMode); 1029 if (mode === "billing" && !this.component.billingEnabled) { 1030 const contactFormElement = stateElement.querySelector(".stripe-contact-form"); 1031 contactFormElement?.classList.remove("forceHide"); 1032 return; 1033 } 1034 if (mode === "shipping" && !this.component.billingEnabled && this.component.totalSteps === 1) { 1035 const contactFormElement = stateElement.querySelector(".stripe-contact-form"); 1036 contactFormElement?.classList.add("forceHide"); 1037 } 1038 const stripeElement = stateElement.querySelector(`.stripe-${mode}`); 1039 stateElement.querySelector('[data-stripe-loading-element="address"]').classList.remove("forceHide"); 1040 stripeElement.classList.add("elAbsolute"); 1041 delete stripeElement.style.opacity; 1042 stateElement.querySelector(`.stripe-address-${mode}-container`)?.classList.remove("forceHide"); 1043 this.mountElement("address", options, `.stripe-${mode}`, mode); 1044 this.montedElements[mode].element?.on("change", (event) => { 1045 this.removeSubmitNotificationStripeElementChange(); 1046 this.montedElements[mode].lastChangeEvent = event; 1047 delete this.errors.fields[mode]; 1048 const parsedData = this.parsedAddressEventData(event); 1049 console.log("[Stripe] address [incoming-event]: ", event); 1050 this.checkoutData.contact.first_name = parsedData.first_name; 1051 this.checkoutData.contact.last_name = parsedData.last_name; 1052 this.checkoutData.contact.phone_number = parsedData.phone_number; 1053 this.triggerTrackingForStripeElement({ 1054 firstName: parsedData.first_name, 1055 lastName: parsedData.last_name, 1056 phone: parsedData.phone_number 1057 }); 1058 this.syncInputFields(); 1059 this.updatePaymentElementDefaultValues(); 1060 this.checkoutData.address = parsedData.address; 1061 if (hasPhysicalProducts(globalThis.Checkout.computed.checkoutCart.get())) { 1062 if (this.hasValidAddress(this.checkoutData.address)) {
1063 this.shippingComponents.setLoading(); 1064 } else { 1065 this.shippingComponents.setInvalidAddress(); 1066 } 1067 } 1068 this.sendOrderPreview({ throttle: true }); 1069 }); 1070 this.montedElements[mode].element?.on("ready", () => { 1071 stateElement.querySelector(`.stripe-address-${mode}-container`).classList.remove("forceHide"); 1072 this.elementReady(mode); 1073 }); 1074 this.montedElements[mode].element?.on("loaderror", (error) => this.handleStripeElementLoadingError(error)); 1075 } 1076 parsedAddressEventData(event) { 1077 const data = {}; 1078 if (event.value.firstName || event.value.lastName) { 1079 data.first_name = event.value.firstName; 1080 data.last_name = event.value.lastName; 1081 } else { 1082 const [first_name, ...last_name] = (event.value?.name ?? "").split(" "); 1083 data.first_name = first_name; 1084 data.last_name = last_name.join(" "); 1085 } 1086 data.phone_number = event.value.phone; 1087 data.address = convertAddressFromStripe(event.value.address); 1088 return data; 1089 } 1090 get paymentOptions() { 1091 let address = "auto"; 1092 if (!this.component.billingEnabled && this.component.billingFields === "if_required") { 1093 address = "if_required"; 1094 } 1095 return { 1096 defaultValues: this.generatePaymentDefaultValues(), 1097 fields: { 1098 billingDetails: { 1099 name: "never", 1100 phone: "auto", 1101 address 1102 } 1103 } 1104 }; 1105 } 1106 mountPayment() { 1107 if (this.montedElements.payment.mounted) return; 1108 const options = { 1109 terms: { card: "never" }, 1110 ...this.paymentOptions 1111 }; 1112 this.mountElement("payment", options); 1113 this.montedElements.payment.element?.on("change", (event) => { 1114 this.removeSubmitNotificationStripeElementChange(); 1115 delete this.errors.fields["payment"]; 1116 console.log("[Stripe] paymentMethods [incoming-event]: ", event); 1117 this.montedElements.payment.lastChangeEvent = event; 1118 this.sendOrderPreview({ throttle: true }); 1119 }); 1120 this.montedElements.payment.element?.on("ready", () => { 1121 this.elementReady("payment"); 1122 }); 1123 this.montedElements.payment.element?.on("loaderror", (error) => this.handleStripeElementLoadingError(error)); 1124 } 1125 generatePaymentDefaultValues() { 1126 const defaultValues = {}; 1127 if (this.checkoutData.contact.first_name || this.checkoutData.contact.last_name) { 1128 defaultValues.billingDetails = defaultValues?.billingDetails ?? {}; 1129 defaultValues.billingDetails.name = [ 1130 this.checkoutData.contact.first_name ?? "", 1131 this.checkoutData.contact.last_name ?? "" 1132 ].filter(Boolean).join(" "); 1133 } 1134 if (this.checkoutData.contact.email) { 1135 defaultValues.billingDetails = defaultValues?.billingDetails ?? {}; 1136 defaultValues.billingDetails.email = this.checkoutData.contact.email; 1137 } 1138 if (this.checkoutData.contact.phone_number && this.hasPhoneNumber()) { 1139 defaultValues.billingDetails = defaultValues?.billingDetails ?? {}; 1140 defaultValues.billingDetails.phone = this.checkoutData.contact.phone_number; 1141 } 1142 return defaultValues; 1143 } 1144 updatePaymentElementDefaultValues() { 1145 if (this.getState() !== "guest") return; 1146 this.montedElements.payment.element?.update({ 1147 ...this.paymentOptions 1148 }); 1149 } 1150 handleOrderPreviewForExpressCheckout(response, resolve) { 1151 const cart = this.store.checkoutCart.get(); 1152 this.stripeElements.update({ 1153 amount: response.total_amount_cents, 1154 mode: getStripeMode(cart, response.total_amount_cents) 1155 }); 1156 const changeResolveDetails = calculateStripeChangeResolveDetails( 1157 response.line_items, 1158 response.shipping_quotes_response?.options, 1159 response.selected_shipping_option, 1160 response.total_amount_cents, 1161 response.discount_amount_cents 1162 ); 1163 resolve(changeResolveDetails); 1164 } 1165 // What the wallet sheet asks for. Express Checkout has no billing-address-change event, so the 1166 // shipping address is the only address the sheet can collect before `confirm` â and the preview 1167 // round trip it triggers is also the only thing that moves the sheet off the pre-discount, 1168 // pre-tax subtotal the Elements were mounted with. Ask for it unless neither is needed. 1169 expressClickResolveDetails() { 1170 const cart = this.store.checkoutCart.get(); 1171 const summary = this.store.summary.get(); 1172 const totalAmountCents = summary.data?.total?.amount_cents; 1173 const finalTotalKnown = summary.state == "ok" /* OK */ && totalAmountCents != null; 1174 const needsAddress = hasPhysicalProducts(cart) || !!this.component.paymentGateway.stripe_tax_enabled || !finalTotalKnown; 1175 if (needsAddress) { 1176 return calculateStripeClickResolveDetails({ 1177 needsAddress, 1178 phoneNumberRequired: this.hasPhoneNumber(), 1179 lineItems: this.summaryLineItems, 1180 // `line_item_subtotal_cents` sums every invoice line including shipping, while 1181 // `line_items` excludes it â so the selected rate has to be named here, or the buyer is 1182 // shown their delivery charge as "Taxes". 1183 totalAmountCents: this.summaryLineItemSubtotalCents, 1184 shippingOption: this.selectedShippingOption 1185 }); 1186 } 1187 this.stripeElements.update({ 1188 amount: totalAmountCents, 1189 mode: getStripeMode(cart, totalAmountCents) 1190 }); 1191 return calculateStripeClickResolveDetails({ 1192 needsAddress, 1193 phoneNumberRequired: this.hasPhoneNumber(), 1194 lineItems: this.summaryLineItems, 1195 totalAmountCents, 1196 discountAmountCents: summary.data?.discount?.amount_cents 1197 }); 1198 } 1199 // Settles the wallet's pending update exactly once, and never rejects: a handler that leaves 1200 // the sheet unsettled is not an error the wallet can show, it is a ~30s "Payment Not Completed" 1201 // timeout. 1202 async sendExpressCheckoutOrderPreview(resolve, reject) { 1203 let settled = false;
1204 const settle = (settleWith) => { 1205 if (settled) return; 1206 settleWith(); 1207 settled = true; 1208 }; 1209 try { 1210 const cart = this.store.checkoutCart.get(); 1211 const dataToPreview = { 1212 cart, 1213 address: this.digitalPaymentData.address, 1214 integrations_stripe_installation_id: stripeInstallationId 1215 }; 1216 const couponState = this.store.coupons.state.get(); 1217 let couponCode; 1218 if (couponState == "applying" /* APPLYING */) { 1219 couponCode = this.store.coupons.currentCode.get(); 1220 } else { 1221 couponCode = this.store.coupons.appliedCode.get(); 1222 } 1223 dataToPreview.coupon_code = couponCode; 1224 dataToPreview.shippingOption = this.formatExpressShippingOption(this.digitalPaymentData.shippingRateId); 1225 const response = await fetchOrderPreview(dataToPreview); 1226 if (isResponseError(response)) { 1227 settle(reject); 1228 } else { 1229 settle(() => this.handleOrderPreviewForExpressCheckout(response, resolve)); 1230 } 1231 } catch (error) { 1232 settle(reject); 1233 globalThis.Honeybadger?.notify("StripeCheckout express preview failed", { 1234 context: { 1235 error: error?.message ?? String(error), 1236 errorName: error?.name, 1237 checkoutState: this.getState(), 1238 summaryState: this.store.summary.get().state 1239 } 1240 }); 1241 } 1242 } 1243 mountExpress() { 1244 if (this.montedElements.expressCheckout.mounted) return; 1245 this.mountElement("expressCheckout", { 1246 paymentMethods: { link: this.component.payWithLinkEnabled ? "auto" : "never" } 1247 }); 1248 this.montedElements.expressCheckout.element?.on("confirm", (evt) => { 1249 const shippingOption = this.formatExpressShippingOption(evt.shippingRate?.id); 1250 this.storeShippingDataFromExpress(evt.shippingAddress ?? evt.billingDetails); 1251 this.handleSubmit(shippingOption, true); 1252 }); 1253 this.montedElements.expressCheckout.element?.on("click", ({ resolve }) => { 1254 let resolveDetails; 1255 try { 1256 resolveDetails = this.expressClickResolveDetails(); 1257 } catch (error) { 1258 resolveDetails = calculateStripeClickResolveDetails({ 1259 needsAddress: true, 1260 phoneNumberRequired: this.hasPhoneNumber(), 1261 lineItems: this.summaryLineItems, 1262 totalAmountCents: this.summaryLineItemSubtotalCents, 1263 shippingOption: this.selectedShippingOption 1264 }); 1265 globalThis.Honeybadger?.notify("StripeCheckout express click failed", { 1266 context: { 1267 error: error?.message ?? String(error), 1268 errorName: error?.name, 1269 checkoutState: this.getState(), 1270 summaryState: this.store.summary.get().state 1271 } 1272 }); 1273 } 1274 resolve(resolveDetails); 1275 }); 1276 this.montedElements.expressCheckout.element?.on("shippingaddresschange", async ({ address, resolve, reject }) => { 1277 this.digitalPaymentData.address = convertAddressFromStripe(address); 1278 await this.sendExpressCheckoutOrderPreview(resolve, reject); 1279 }); 1280 this.montedElements.expressCheckout.element?.on("cancel", () => { 1281 const cart = this.store.checkoutCart.get(); 1282 this.stripeElements.update({ 1283 amount: this.summaryLineItemSubtotalCents, 1284 mode: getStripeMode(cart, this.summaryLineItemSubtotalCents) 1285 }); 1286 }); 1287 this.montedElements.expressCheckout.element?.on("shippingratechange", async ({ shippingRate, resolve, reject }) => { 1288 this.digitalPaymentData.shippingRateId = shippingRate.id; 1289 await this.sendExpressCheckoutOrderPreview(resolve, reject); 1290 }); 1291 this.montedElements.payment.element?.on("loaderror", (error) => this.handleStripeElementLoadingError(error)); 1292 } 1293 formatExpressShippingOption(id) { 1294 if (id && id != STRIPE_SKIP_SHIPPING_ID) { 1295 return JSON.parse(id); 1296 } 1297 } 1298 elementReady(name) { 1299 const stateElement = this.getStateElement(); 1300 const loaderElementName = ["shipping", "billing"].includes(name) ? "address" : name; 1301 const loaderElement = stateElement.querySelector(`[data-stripe-loading-element="${loaderElementName}"]`); 1302 const stripeElement = stateElement.querySelector(`.stripe-${name}`); 1303 this.stripeElements.update({ appearance: this.parsedAppearence }); 1304 let obeserverTimeout; 1305 const resizeObserver = new ResizeObserver(() => { 1306 clearTimeout(obeserverTimeout); 1307 obeserverTimeout = setTimeout(() => { 1308 resizeObserver.disconnect(); 1309 this.stripeElements.update({ appearance: this.parsedAppearence }); 1310 loaderElement.classList.add("forceHide");
1311 stripeElement.classList.remove("elAbsolute"); 1312 stripeElement.style.opacity = "1"; 1313 }, 500); 1314 }); 1315 resizeObserver.observe(stripeElement); 1316 } 1317 oneTimeListeners() { 1318 const signOutButtonthis = this.component.element.querySelector(".sign-out-button"); 1319 signOutButtonthis.addEventListener("click", () => { 1320 fetch("/contacts/sign_out", { 1321 method: "DELETE", 1322 headers: { "Content-Type": "application/json" }, 1323 credentials: "same-origin" 1324 }).then(() => this.ensureStripeSession()).then((session) => { 1325 if (session) { 1326 this.store.checkout.step.set(0); 1327 this.setState("guest"); 1328 this.initializeElements(session, this.summaryLineItemSubtotalCents); 1329 setTimeout(() => { 1330 this.updateElementsDisplay(); 1331 const cart = this.store.checkoutCart.get(); 1332 CheckoutExistingOrders.clearUpdatableOrders(cart.items[0]?.product_id); 1333 }, 1e3); 1334 } 1335 }); 1336 }); 1337 this.component.getComponents("Input/V1").forEach((inputComponent) => { 1338 const input = inputComponent.element.querySelector("input"); 1339 const inputName = input.getAttribute("name"); 1340 if (!["first_name", "last_name", "phone_number"].includes(inputName)) return; 1341 if (inputName == "phone_number") { 1342 IntlTel_initPhoneInput(input, { initialCountry: globalThis.cfVisitorData?.country ?? "us" }); 1343 } 1344 this.guestInputFields[inputName] = input;
1345 ["input", "focus"].forEach((eventName) => { 1346 input.addEventListener(eventName, () => { 1347 const errorfield = this.errorInputForInput(input); 1348 errorfield.style.height = "0px"; 1349 errorfield.previousElementSibling.classList.remove("elInputError"); 1350 errorfield.textContent = ""; 1351 }); 1352 }); 1353 input.addEventListener("blur", (event) => { 1354 const input2 = event.target; 1355 let value = input2.value; 1356 if (inputName == "phone_number") { 1357 value = input2.iti.getNumber(); 1358 } 1359 this.checkoutData.contact[inputName] = value; 1360 this.updateInputState(inputName, value); 1361 this.updatePaymentElementDefaultValues(); 1362 }); 1363 }); 1364 this.store.checkoutCart.subscribe(() => { 1365 this.updateElementsDisplay(); 1366 this.sendOrderPreview(); 1367 if (this.submitedOnce) { 1368 this.clearErrors("products"); 1369 this.showErrors(); 1370 } 1371 }); 1372 this.listenAndSurfaceSummaryErrors(); 1373 } 1374 // Mirrors Rebilly's `listenAndUpdateCheckoutErrors` (rebilly-checkout.ygg): when an 1375 // order-preview error lands on `summary.error.message`, surface it via the same 1376 // `.checkout-general-errors-wrapper` that submit-time errors use. 1377 listenAndSurfaceSummaryErrors() { 1378 this.store.summary.subscribe((summary) => { 1379 const message = summary?.state === "error" /* ERROR */ && summary?.error?.message ? summary.error.message : null; 1380 if (this.summaryErrorEntry) { 1381 const idx = this.errors.globalErrors.indexOf(this.summaryErrorEntry); 1382 if (idx >= 0) this.errors.globalErrors.splice(idx, 1); 1383 this.summaryErrorEntry = null; 1384 } 1385 if (message) { 1386 this.summaryErrorEntry = { message }; 1387 this.errors.globalErrors.push(this.summaryErrorEntry); 1388 } 1389 this.showErrors(); 1390 }); 1391 } 1392 updateElementsDisplay() { 1393 this.cartAddressUpdates(); 1394 this.updatePaymentElement(); 1395 this.updateShippingOptionsDisplay(); 1396 this.updateSubmitButton(); 1397 } 1398 updatePaymentElement() { 1399 if (this.hasPaymentElement()) { 1400 this.mountPayment(); 1401 this.mountExpress(); 1402 this.mountLink();
1403 this.getStateElement().querySelector(".stripe-payment-container")?.classList.remove("forceHide"); 1404 this.getStateElement().querySelector(".stripe-expressCheckout-container")?.classList.remove("forceHide"); 1405 } else { 1406 this.unmountElement("linkAuthentication"); 1407 this.unmountElement("payment"); 1408 this.unmountElement("expressCheckout"); 1409 this.getStateElement().querySelector(".stripe-payment-container")?.classList.add("forceHide"); 1410 this.getStateElement().querySelector(".stripe-expressCheckout-container")?.classList.add("forceHide"); 1411 } 1412 } 1413 cartAddressUpdates() { 1414 const cart = this.store.checkoutCart.get(); 1415 const hasPhysical = hasPhysicalProducts(cart); 1416 if (this.oldHasPhysical != hasPhysical) { 1417 this.updateWithShippingAddress(cart); 1418 } 1419 if (this.getState() == "oto") { 1420 if (this.otoRequiresShippingForm() || this.paymentDeclined) { 1421 this.mountAddress(); 1422 this.getStateElement().querySelector(".stripe-address")?.classList.remove("forceHide"); 1423 } else {
1424 this.getStateElement().querySelector(".stripe-address")?.classList.add("forceHide"); 1425 } 1426 if (hasUpgradeDowngradeItems(cart)) { 1427 this.getStateElement().querySelector('[data-page-element="CheckoutProductSelectBump/V2"]')?.classList.add("forceHide"); 1428 } else { 1429 this.getStateElement().querySelector('[data-page-element="CheckoutProductSelectBump/V2"]')?.classList.remove("forceHide"); 1430 } 1431 } else if (hasUpgradeDowngradeItems(cart)) { 1432 this.getStateElement().querySelector(".stripe-address")?.classList.add("forceHide"); 1433 } else { 1434 this.mountAddress(); 1435 this.getStateElement().querySelector(".stripe-address")?.classList.remove("forceHide"); 1436 } 1437 this.oldHasPhysical = hasPhysical; 1438 } 1439 updateSubmitButton() { 1440 if (this.submitInFlight) return; 1441 const cart = this.store.checkoutCart.get(); 1442 if (hasUpgradeDowngradeItems(cart)) { 1443 this.submitButtons.update(this.component.buttonUpgradeDowngrade); 1444 } else { 1445 this.submitButtons.setButtonInitialHTML(); 1446 } 1447 } 1448 get addressOptions() { 1449 const hasPhysical = hasPhysicalProducts(this.store.checkoutCart.get()); 1450 const options = { 1451 mode: hasPhysical ? "shipping" : "billing", 1452 fields: { phone: this.hasPhoneNumber() ? "always" : "never" }, 1453 validation: this.hasPhoneNumber() ? { phone: { required: "always" } } : {}, 1454 display: { 1455 name: "split" 1456 } 1457 }; 1458 options.autocomplete = { mode: "automatic" }; 1459 options.defaultValues = {}; 1460 if (Object.keys(this.checkoutData.address).length || this.get
1460State() == "oto") { 1461 options.defaultValues.address = convertToStripeAddress(this.checkoutData.address); 1462 } 1463 const { first_name, last_name, phone_number } = this.checkoutData.contact ?? {}; 1464 if (options.display.name == "split") { 1465 if (first_name) options.defaultValues.firstName = first_name; 1466 if (last_name) options.defaultValues.lastName = last_name; 1467 } else { 1468 if (first_name) { 1469 options.defaultValues.name = first_name; 1470 if (last_name) { 1471 options.defaultValues.name += ` ${last_name}`; 1472 } 1473 } 1474 } 1475 if (this.checkoutData.contact.phone_number && this.hasPhoneNumber()) { 1476 options.defaultValues.phone = this.checkoutData.contact.phone_number; 1477 } 1478 if (options.defaultValues.address && first_name && last_name) { 1479 const phoneNumberObj = this.hasPhoneNumber() ? { phone: phone_number } : {}; 1480 options.contacts = [ 1481 { name: `${first_name} ${last_name}`, address: options.defaultValues.address, ...phoneNumberObj } 1482 ]; 1483 } 1484 return options; 1485 } 1486 hasPhoneNumber() { 1487 const showPhoneNumber = this.component.contactFields.includes("phone_number"); 1488 return showPhoneNumber; 1489 } 1490 stripeElementHasError(type) { 1491 return !!this.montedElements[type].lastChangeEvent && !this.montedElements[type].lastChangeEvent?.complete; 1492 } 1493 shouldClearFormErrors(type) { 1494 return !this.submitedOnce && this.stripeElementHasError(type); 1495 } 1496 clearStripeElement(type) { 1497 console.log("[Stripe][clearStripeElement] =>", type); 1498 this.montedElements[type].element?.clear(); 1499 } 1500 processCheckoutError(errorsByForm) { 1501 for (const [, errors] of Object.entries(errorsByForm)) { 1502 for (const [field, error] of Object.entries(errors?.fields ?? {})) { 1503 if (!error) continue; 1504 this.pushFieldError(field, error.message); 1505 } 1506 if (errors?.globalErrors?.length) { 1507 this.pushGlobalError(errors.globalErrors[0].message); 1508 } 1509 } 1510 return !hasErrors(this.errors); 1511 } 1512 shippingOptionErrors() { 1513 const state = this.component.getComponent(CheckoutShippingProfileSelectV1); 1514 const cart = this.store.checkoutCart.get(); 1515 if (hasUpgradeDowngradeItems(cart)) { 1516 return null; 1517 } else if (state) { 1518 const { shippingOptions, shippingOption, loadingShipping } = state; 1519 return shippingOptionErrors(loadingShipping, cart, shippingOptions, shippingOption, true, null); 1520 } 1521 } 1522 tosErrors() { 1523 const state = this.component.getComponent(CheckoutTosV1); 1524 if (state) { 1525 return tosErrors(state.accepted, true, true); 1526 } 1527 } 1528 contactErrors() { 1529 return contactErrors(this.checkoutData.contact, Object.keys(this.guestInputFields)); 1530 } 1531 stripeFormErrors(type) { 1532 if (this.shouldValidateStripeElements() && this.stripeElementHasError(type)) { 1533 return { fields: { [type]: { message: "has error" } } }; 1534 } 1535 } 1536 formErrorsByFormName(formName) { 1537 switch (formName) { 1538 case "products": 1539 return productErrors(this.store.checkoutCart.get(), true); 1540 case "shippingOption": 1541 return this.shippingOptionErrors(); 1542 case "tos": 1543 return this.tosErrors(); 1544 case "contact": 1545 return this.contactErrors(); 1546 } 1547 } 1548 async validateStep(step) { 1549 this.clearAllErrors(); 1550 const formErrors = {}; 1551 const curretStepForms = FORM_STRUCTURE_BY_TOTAL_STEPS[this.getTotalSteps()][step]; 1552 let hasStripeElementInStep = false; 1553 for (const field of curretStepForms) { 1554 if (stripeElementsForms.includes(field)) { 1555 hasStripeElementInStep = true; 1556 formErrors[field] = this.stripeFormErrors(field); 1557 } else { 1558 formErrors[field] = this.formErrorsByFormName(field); 1559 } 1560 } 1561 const formValid = this.processCheckoutError(formErrors); 1562 if (!formValid && hasStripeElementInStep) { 1563 await this.stripeElements.submit(); 1564 if (this.shouldClearFormErrors("billing")) this.clearStripeElement("billing"); 1565 if (this.shouldClearFormErrors("shipping")) this.clearStripeElement("shipping"); 1566 if (this.shouldClearFormErrors("payment")) this.clearStripeElement("payment"); 1567 } 1568 const customFieldsValid = checkValidCustomFields(this.component.element); 1569 const valid = formValid && customFieldsValid; 1570 if (!valid) { 1571 this.showErrors({ shouldScrollToError: true }); 1572 } 1573 return valid; 1574 } 1575 updateShippingOptionsDisplay() {
1576 const stateElement = this.getStateElement(); 1577 const cart = this.store.checkoutCart.get(); 1578 if (hasPhysicalProducts(cart) && !hasUpgradeDowngradeItems(cart)) { 1579 stateElement.querySelector(".shipping-options-container")?.classList.remove("forceHide"); 1580 this.shippingComponents.show(); 1581 } else { 1582 this.shippingComponents.hide(); 1583 stateElement.querySelector(".shipping-options-container")?.classList.add("forceHide"); 1584 } 1585 } 1586 selectShippingOption(option) { 1587 this.selectedShippingOption = option; 1588 this.shippingComponents.setOption(option); 1589 this.sendOrderPreview(); 1590 if (this.submitedOnce) { 1591 this.clearErrors("shippingOption"); 1592 this.showErrors(); 1593 } 1594 } 1595 updateShippingOptions(update) { 1596 this.shippingComponents.update(update); 1597 } 1598 async sendOrderPreview(options) { 1599 const { skipUpdatingStripe, throttle, isCouponValidation, skipThrottle, forceOrderPreview } = options ?? {}; 1600 if (this.preventOrderPreview && !forceOrderPreview) return; 1601 const { summary } = this.store; 1602 const cart = this.store.checkoutCart.get(); 1603 if (!cart.items?.length) { 1604 if (this.store.coupons.appliedCode.get() || this.store.coupons.currentCode.get()) { 1605 this.store.coupons.appliedCode.set(""); 1606 this.store.coupons.currentCode.set(""); 1607 this.store.coupons.state.set("ready" /* READY */); 1608 } 1609 summary.set({ state: "waiting" /* WAITING */ }); 1610 return; 1611 } 1612 const couponStateBeforeApplying = this.store.coupons.state.get(); 1613 if (isCouponValidation && couponStateBeforeApplying != "applying" /* APPLYING */) { 1614 this.couponStateBeforeApplying = couponStateBeforeApplying; 1615 this.store.coupons.state.set("applying" /* APPLYING */); 1616 } 1617 const payload = this.hasValidAddress(this.checkoutData.address) ? { 1618 address: this.checkoutData.address, 1619 shippingOption: this.selectedShippingOption, 1620 cart 1621 } : { cart }; 1622 payload.integrations_stripe_installation_id = stripeInstallationId; 1623 const couponState = this.store.coupons.state.get(); 1624 let couponCode; 1625 if (couponState == "applying" /* APPLYING */) { 1626 couponCode = this.store.coupons.currentCode.get(); 1627 } else { 1628 couponCode = this.store.coupons.appliedCode.get(); 1629 } 1630 payload.coupon_code = couponCode; 1631 const canUpdateSummary = this.hasValidDataForSummaryUpdate(payload); 1632 const canFetchLineItemSubtotal = hasValidDataForLineItemSubtotal(payload); 1633 if (!canUpdateSummary) { 1634 summary.set({ 1635 state: "waiting" /* WAITING */ 1636 }); 1637 } 1638 if (canUpdateSummary && !canFetchLineItemSubtotal) { 1639 return; 1640 } 1641 this.updatingExpressStarted(); 1642 clearTimeout(this.sendOrderPreviewTimer); 1643 const sendOrderPreviewFn = async () => { 1644 const response = await fetchOrderPreview(payload, () => { 1645 if (canUpdateSummary) { 1646 if (hasPhysicalProducts(cart)) {
1647 this.shippingComponents.setLoading(); 1648 } 1649 summary.set({ 1650 state: "calculating" /* CALCULATING */ 1651 }); 1652 } 1653 }); 1654 if (canFetchLineItemSubtotal && !isResponseError(response)) { 1655 this.summaryLineItems = response.line_items; 1656 this.summaryLineItemSubtotalCents = response.line_item_subtotal_cents; 1657 const currentCode = this.store.coupons.currentCode.get(); 1658 const couponCodeApplied = response.discounts?.find( 1659 (discount) => discount.code?.toUpperCase() == currentCode?.toUpperCase() 1660 ); 1661 if (couponCodeApplied) { 1662 this.store.coupons.appliedCode.set(currentCode); 1663 this.store.coupons.state.set("applied" /* APPLIED */); 1664 } else { 1665 this.store.coupons.state.set("ready" /* READY */); 1666 } 1667 if (!skipUpdatingStripe) { 1668 this.stripeElements.update({ 1669 amount: response.line_item_subtotal_cents, 1670 mode: getStripeMode(cart, response.line_item_subtotal_cents) 1671 }); 1672 } 1673 } else if (isBackendError(response)) { 1674 this.updatingExpressEnded(); 1675 if (isResponseError(response)) { 1676 this.handleOrderPreviewErrorForExpress(response.error); 1677 } 1678 } 1679 if (canUpdateSummary || couponCode) { 1680 if (isResponseError(response)) { 1681 if (isBackendError(response)) { 1682 this.handleOrderPreviewErrorForSummary(response.error); 1683 } 1684 } else { 1685 const summaryResponse = processOrderPreviewResponseForSummary(response); 1686 const { shipping_quotes_response, selected_shipping_option } = response; 1687 this.selectedShippingOption = selected_shipping_option; 1688 summary.set({ 1689 state: "ok" /* OK */, 1690 data: summaryResponse 1691 }); 1692 if (hasPhysicalProducts(cart)) { 1693 this.shippingComponents.setShippingOptions( 1694 shipping_quotes_response?.options ?? [], 1695 selected_shipping_option 1696 ); 1697 } 1698 } 1699 } 1700 }; 1701 if (skipThrottle) { 1702 return await sendOrderPreviewFn(); 1703 } else { 1704 this.sendOrderPreviewTimer = setTimeout( 1705 async () => { 1706 await sendOrderPreviewFn(); 1707 }, 1708 throttle ? 500 : 0 1709 ); 1710 } 1711 } 1712 startLoadingState() { 1713 this.submitInFlight = true; 1714 this.loadingComponents.show(); 1715 this.submitButtons.setSubmittingText(); 1716 } 1717 removeLoadingState() { 1718 this.submitInFlight = false; 1719 this.loadingComponents.hide(); 1720 this.submitButtons.setButtonInitialHTML(); 1721 } 1722 handleStripeError(error) { 1723 if (!this.hasPaymentElement() && STRIPE_INSTRUMENT_REJECTION_CODES.has(error.code)) { 1724 this.paymentDeclined = true; 1725 this.updateElementsDisplay(); 1726 } 1727 return error.message; 1728 } 1729 handleOrderPreviewErrorForExpress(response) { 1730 const errorDetail = response.details?.[0]; 1731 if (errorDetail?.type == "InvalidCouponError") { 1732 this.store.coupons.appliedCode.set(""); 1733 this.store.coupons.currentCode.set(""); 1734 this.store.coupons.errorMessage.set(errorDetail.message); 1735 this.store.coupons.state.set("error" /* ERROR */); 1736 } else { 1737 globalThis.Checkout.store.coupons.state.set(this.couponStateBeforeApplying); 1738 } 1739 } 1740 handleOrderPreviewErrorForSummary(response) { 1741 const errorDetail = response.details?.[0]; 1742 if (errorDetail?.type == "InvalidCouponError") { 1743 const summaryResponse = processOrderPreviewResponseForSummary(errorDetail.preview); 1744 this.store.summary.set({ 1745 state: "ok" /* OK */, 1746 data: summaryResponse 1747 }); 1748 } else if (errorDetail?.message) { 1749 this.store.summary.set({ 1750 state: "error" /* ERROR */, 1751 error: { message: errorDetail.message } 1752 }); 1753 } else { 1754 this.store.summary.set({ 1755 state: "error" /* ERROR */, 1756 error: { message: "There was an error calculating your order total" } 1757 }); 1758 } 1759 } 1760 handleError(error, skipRemoveLoadingState = false) { 1761 const GENERIC_PAYMENT_ERROR = "There was a problem processing your payment. Please try again or use a different payment method."; 1762 console.error("Error Submiting Payment:", error); 1763 if (!skipRemoveLoadingState) { 1764 this.removeLoadingState(); 1765 } 1766 let displayMessage = ""; 1767 let actionable = true; 1768 if (isStripeError(error)) { 1769 displayMessage = this.handleStripeError(error); 1770 } else { 1771 displayMessage = serverCheckoutErrorMessage(error) ?? GENER
1771IC_PAYMENT_ERROR; 1772 actionable = isActionableCheckoutError(error); 1773 } 1774 this.lastSubmitFailure = { message: displayMessage, actionable }; 1775 this.setSubmitNotification("Failed Submit", displayMessage); 1776 } 1777 async processStripeResponse(next_url, { error, paymentIntent }) { 1778 if (error) { 1779 this.handleError(error); 1780 } else if (paymentIntent.status === "succeeded") { 1781 fetch(next_url, { 1782 method: "GET", 1783 redirect: "manual" 1784 }).then((response) => { 1785 if (response.status >= 300 && response.status < 400) { 1786 const location = response.headers.get("Location"); 1787 window.location.href = location; 1788 } else { 1789 response.text().then((data) => { 1790 const jsonResponse = JSON.parse(data); 1791 this.handleError({ 1792 message: jsonResponse.error 1793 }); 1794 }); 1795 } 1796 }); 1797 } else if (paymentIntent.status === "processing") { 1798 setTimeout(async () => { 1799 const result = await this.stripe.retrievePaymentIntent(paymentIntent.client_secret); 1800 this.processStripeResponse(next_url, result); 1801 }, 1e3); 1802 } else { 1803 const voucherNextActions = ["konbini_display_details", "boleto_display_details", "oxxo_display_details", "multibanco_display_details"]; 1804 const nextActionType = paymentIntent.next_action?.type; 1805 if (paymentIntent.status === "requires_action" && nextActionType && voucherNextActions.includes(nextActionType)) { 1806 window.location.href = next_url; 1807 } else { 1808 this.removeLoadingState(); 1809 } 1810 } 1811 } 1812 async handleServerResponse(response) { 1813 if (isResponseError(response)) { 1814 this.handleError(response.error); 1815 } else { 1816 if (response.payment_intent?.status === "requires_action") { 1817 const result = await this.stripe.handleNextAction({ 1818 clientSecret: response.payment_intent.client_secret 1819 }); 1820 if (result.error) { 1821 this.handleError(result.error); 1822 } else if ("setupIntent" in result && result.setupIntent) { 1823 if (result.setupIntent.status === "succeeded") { 1824 window.location.href = response.next_url; 1825 } else { 1826 this.removeLoadingState(); 1827 } 1828 } else { 1829 await this.processStripeResponse(response.next_url, result); 1830 } 1831 } else if (response.next_url) { 1832 window.location.href = response.next_url; 1833 } else { 1834 this.handleError(response); 1835 } 1836 } 1837 } 1838 shouldIncludeRegistration() { 1839 return document.getElementsByName("registration[calendar_event_id]")?.length > 0; 1840 } 1841 buildRegistrationDetails() { 1842 return { 1843 calendar_event_id: document.getElementsByName("registration[calendar_event_id]")[0].value, 1844 occurs_at: document.getElementsByName("registration[occurs_at]")[0].value 1845 }; 1846 } 1847 buildSubmitPayload(shippingOption, idempotencyKey, confirmationToken, redirectOverride) { 1848 const customFields = buildCustomFields(); 1849 const cart = this.store.checkoutCart.get(); 1850 const coupon = this.store.coupons.appliedCode.get(); 1851 const defaultRedirect = this.element.querySelector('[href="#submit-checkout-form"]')?.getAttribute("data-on-submit-go-to"); 1852 const redirect_to = redirectOverride || defaultRedirect; 1853 const payload = { 1854 ...customFields ? customFields : {}, 1855 payment_processor: "stripe", 1856 integrations_stripe_installation_id: stripeInstallationId, 1857 request_idempotency: idempotencyKey, 1858 skip_optin_track: document.getElementById("cfAR")?.getAttribute("data-leads-collected") === "true", 1859 purchase: { 1860 coupon_codes: [coupon].filter(Boolean), 1861 process_new_order: true, 1862 selected_shipping_option: shippingOption, 1863 order_id: cart.order_id, 1864 product_variants: cart.items.map(({ variant_id, price_id, line_item_id, quantity }) => { 1865 return { 1866 id: variant_id, 1867 quantity, 1868 price_id, 1869 ...line_item_id ? { line_item_id } : {} 1870 }; 1871 }) 1872 } 1873 }; 1874 if (redirect_to) { 1875 payload.redirect_to = redirect_to; 1876 } 1877 if (confirmationToken) { 1878 payload.token = confirmationToken.id; 1879 } else if (hasUpgradeDowngradeItems(cart)) { 1880 payload.contact = { 1881 email: globalThis.sessionData.contact.email, 1882 first_name: globalThis.sessionData.contact.first_name, 1883 last_name: globalThis.sessionData.contact.last_name, 1884 phone_number: globalThis.sessionData.contact.phone_number 1885 }; 1886 } else { 1887 payload.contact = { 1888 email: globalThis.sessionData.contact.email, 1889 first_name: this.checkoutData.contact.first_name, 1890 last_name: this.checkoutData.contact.last_name, 1891 phone_number: this.checkoutData.contact.phone_number 1892 }; 1893 if (Object.keys(this.checkoutData.address).length) { 1894 payload.contact.shipping_addresses_attributes = [ 1895 { 1896 physical_address_attributes: { 1897 address_one: this.checkoutData.address.address_one, 1898 address_two: this.checkoutData.address.address_two, 1899 city: this.checkoutData.address.city, 1900 region_name: this.checkoutData.address.state, 1901 country_id: this.checkoutData.address.country, 1902 postal_code: this.checkoutData.address.postal_code 1903 } 1904 } 1905 ]; 1906 } 1907 } 1908 if (this.shouldIncludeRegistration()) payload["registration"] = this.buildRegistrationDetail
1908s(); 1909 return payload; 1910 } 1911 otoRequiresShippingForm() { 1912 const hasPhysical = hasPhysicalProducts(this.store.checkoutCart.get()); 1913 return hasPhysical && !globalThis.sessionData.contact.shipping_addresses?.[0]; 1914 } 1915 hasPaymentElement() { 1916 return !hasUpgradeDowngradeItems(this.store.checkoutCart.get()) && ["guest", "saved"].includes(this.getState()) || this.paymentDeclined; 1917 } 1918 shouldValidateStripeElements() { 1919 return this.hasPaymentElement() || this.otoRequiresShippingForm(); 1920 } 1921 hasStripeToken() { 1922 return this.hasPaymentElement(); 1923 } 1924 async handleSubmit(shippingOption, isExpress, redirectOverride) { 1925 this.removeSubmitNotification(); 1926 this.lastSubmitFailure = null; 1927 this.submitedOnce = true; 1928 if (!isExpress && !await this.validateStep(this.store.checkout.step.get())) { 1929 return false; 1930 } 1931 if (!isExpress && !checkValidCustomFields(this.component.element)) { 1932 return false; 1933 } 1934 if (!isExpress) { 1935 if (this.shouldValidateStripeElements()) { 1936 const submitError = await this.stripeElements.submit(); 1937 if (submitError.error) { 1938 console.log("[Stripe] submitError =>", submitError); 1939 return false; 1940 } 1941 } 1942 if (this.store.summary.get().state != "ok" /* OK */) return false; 1943 } 1944 this.startLoadingState(); 1945 let confirmationToken; 1946 if (isExpress || this.hasStripeToken()) { 1947 const confirmationTokenResult = await this.stripe.createConfirmationToken({ 1948 elements: this.stripeElements, 1949 params: { 1950 payment_method_data: { 1951 billing_details: { 1952 name: this.checkoutData.contact.first_name + " " + this.checkoutData.contact.last_name, 1953 phone: this.checkoutData.contact.phone_number, 1954 email: this.checkoutData.contact.email 1955 } 1956 } 1957 } 1958 }); 1959 if (confirmationTokenResult.error) { 1960 this.handleError(confirmationTokenResult.error); 1961 return false; 1962 } 1963 confirmationToken = confirmationTokenResult.confirmationToken; 1964 } 1965 const payload = this.buildSubmitPayload(shippingOption, uuidv4(), confirmationToken, redirectOverride); 1966 const response = await postSubmit(payload); 1967 await this.handleServerResponse(response); 1968 return !this.lastSubmitFailure; 1969 } 1970 hasValidAddress(address) { 1971 let addressErrorsVal; 1972 const cart = this.store.checkoutCart.get(); 1973 if (!hasPhysicalProducts(cart) && !this.component.billingEnabled || hasUpgradeDowngradeItems(cart) && !this.hasPaymentElement()) { 1974 addressErrorsVal = null; 1975 } else if (this.getState() == "oto" && !hasPhysicalProducts(cart) && !this.hasPaymentElement()) { 1976 addressErrorsVal = addressErrors(address ?? {}, true, []); 1977 } else { 1978 let requiredFields = ["address_one", "city", "state", "country", "postal_code"]; 1979 const stripeAddressFieldExceptions = globalThis.globalResourceData.stripeAddressFieldExceptions; 1980 if (address?.country && stripeAddressFieldExceptions.postalCodeless.includes(address?.country)) { 1981 requiredFields = requiredFields.filter((field) => field !== "postal_code"); 1982 } 1983 if (address?.country && stripeAddressFieldExceptions.cityless.includes(address?.country)) { 1984 requiredFields = requiredFields.filter((field) => field !== "city"); 1985 } 1986 if (address?.country && stripeAddressFieldExceptions.regionless.includes(address?.country)) { 1987 requiredFields = requiredFields.filter((field) => field !== "state"); 1988 } 1989 addressErrorsVal = addressErrors(address ?? {}, true, requiredFields); 1990 } 1991 return !hasErrors(addressErrorsVal); 1992 } 1993 hasValidDataForSummaryUpdate(payload) { 1994 const { cart, address } = payload; 1995 const productSelectErrors = productErrors(cart, true); 1996 const hasValidAddress = this.hasValidAddress(address); 1997 return !hasErrors(productSelectErrors) && hasValidAddress; 1998 } 1999 // NOTE: for OTO this gets triggered after mounted. 2000 // We can enhance it later to only skip after this mounting change event. 2001 // Firstly we need to understand how to verify that 2002 removeSubmitNotificationStripeElementChange() { 2003 if (this.getState() != "oto" /* OTO */) this.removeSubmitNotification(); 2004 } 2005}; 2006
2007// projects/lib/packages/yggdrasil-blueprints/__generated__/blueprints/stripe-checkout.ts 2008init_runtime(); 2009var StripeCheckout = class extends CF2Component { 2010 constructor(el, runtimeSel) { 2011 super(el, runtimeSel); 2012 __publicField(this, "checkout"); 2013 __publicField(this, "_lazyInitPending"); 2014 } 2015 _doInitialize() { 2016 this.checkout = createInstance(this); 2017 } 2018 _doMount() { 2019 this.checkout.mount(); 2020 } 2021 initialize() { 2022 if (this.lazyLoadCheckout) { 2023 this._lazyInitPending = true; 2024 ensureCheckoutStoreInitialized(); 2025 return; 2026 } 2027 this._doInitialize(); 2028 } 2029 mount() { 2030 if (this._lazyInitPending) { 2031 return; 2032 } 2033 this._doMount(); 2034 } 2035 async _performLazyInit() { 2036 this._lazyInitPending = false; 2037 await ensureStripeScriptLoaded(); 2038 this._doInitialize(); 2039 this._doMount(); 2040 } 2041 getCart() { 2042 return this.checkout.store.checkoutCart.get(); 2043 } 2044 async submitOTO(redirectOverride) { 2045 const ok = await this.checkout.submitCheckout(redirectOverride); 2046 const failure = this.checkout.lastSubmitFailure; 2047 return { ok, actionable: failure ? failure.actionable : true, message: failure && failure.message }; 2048 } 2049}; 2050registerComponent("StripeCheckout", StripeCheckout); 2051window["StripeCheckout"] = StripeCheckout; 2052//# sourceMappingURL=stripe-checkout-2DRIRV4Y.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.