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 calculateStripeLineItems, 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-5JJNRIKL.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-Y32E43UA.js"; 65import { 66 init_input_v1 67} from "./chunk-7HBS56XK.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.name) globalThis.cfGarlicUtils.store("name", data.name); 686 const address = convertAddressFromStripe(data.address); 687 Object.keys(address).forEach((k) => { 688 if (address[k]) globalThis.cfGarlicUtils.store(k, address[k]); 689 }); 690 } 691 syncInputFields() { 692 Object.keys(this.guestInputFields).forEach((k) => { 693 const value = this.checkoutData.contact[k]; 694 if (k == "phone_number") { 695 ; 696 this.guestInputFields[k].iti.setNumber(value ?? ""); 697 } else { 698 this.guestInputFields[k].value = value ?? ""; 699 } 700 this.updateInputState(k, value); 701 }); 702 } 703 errorInputForInput(input) { 704 const id = input.closest("[data-page-element]").getAttribute("id"); 705 return this.element.querySelector(`[data-input-id="${id}"]`); 706 } 707 updateWithShippingAddress(cart) { 708 this.checkoutData.address = {}; 709 if (hasPhysicalProducts(cart)) { 710 const sessionShippingAddress = globalThis.sessionData.contact.shipping_addresses?.[0] ?? globalThis.sessionData.contact.billing_addresses?.[0]; 711 if (sessionShippingAddress) { 712 this.checkoutData.address = convertAddressFromSession(sessionShippingAddress); 713 } 714 } 715 if (this.getState() == "oto" && !hasPhysicalProducts(cart)) { 716 const sessionBillingAddress = globalThis.sessionData.contact.billing_addresses?.[0]; 717 if (sessionBillingAddress) { 718 this.checkoutData.address = convertAddressFromSession(sessionBillingAddress); 719 } 720 } 721 if (globalThis.sessionData.contact) { 722 this.checkoutData.contact.first_name = globalThis.sessionData.contact.first_name; 723 this.checkoutData.contact.last_name = globalThis.sessionData.contact.last_name; 724 this.checkoutData.contact.phone_number = globalThis.sessionData.contact.phone_number; 725 this.syncInputFields(); 726 } 727 } 728 initializeElements(session, amount) { 729 const cart = this.store.checkoutCart.get(); 730 this.stripeElementOptions = { 731 amount, 732 mode: getStripeMode(cart, amount), 733 currency: stripeCurrency.toLowerCase() ?? "usd", 734 appearance: this.parsedAppearence, 735 loader: "never", 736 fonts: this.stripeFonts, 737 locale: this.component.locale,
738 ...this.component.paymentGateway.disable_stripe_payment_method_future_usage ? {} : { setup_future_usage: "off_session" }, 739 ...session?.can_use_dpm ? {} : { payment_method_types: ["card"] }, 740 ...session?.stripe_options ?? {} 741 }; 742 this.stripeElements = this.stripe.elements(this.stripeElementOptions); 743 globalThis.stripeElements = this.stripeElements; 744 this.stripeElements.on("update-end", () => { 745 this.updatingExpressEnded(); 746 }); 747 } 748 mount() { 749 if (!this.stripe) { 750 this.pushSetupError(1 /* STRIPE_LIB_INITIALIZATION_ERROR */); 751 return; 752 } 753 this.submitNotifications = this.component.getComponentGroup(CheckoutSubmitNotificationV1); 754 this.detectIfReturningFailedPayment(); 755 this.backfillContactData(); 756 Promise.all([ 757 this.ensureStripeSession(), 758 CheckoutExistingOrders.fetch({ 759 integrations_stripe_installation_id: stripeInstallationId 760 }).catch((error) => { 761 const cause = error instanceof CheckoutCriticalError ? error.cause : null; 762 if (cause !== MANUALLY_ABORTED) throw error; 763 }).then(() => { 764 this.updateSubmitButton(); 765 }) 766 ]).then(([sessionData]) => { 767 if (sessionData) { 768 const cart = this.store.checkoutCart.get(); 769 const intialAmount = getCartEstimatedTotal(cart); 770 this.initializeElements(sessionData, intialAmount); 771 this.sendOrderPreview(); 772 setTimeout(() => { 773 this.oneTimeListeners(); 774 }, 1e3); 775 } 776 }).catch((error) => { 777 this.pushSetupError(64 /* MOUNT_REJECTED_ERROR */, void 0, { 778 failureKind: "mount_rejected", 779 mountError: String(error?.message ?? error), 780 // Keep the stack and any wrapped cause: the notify message is held constant per 781 // code for grouping, so context is the only place the specific failure survives. 782 mountErrorStack: error?.stack, 783 mountErrorCause: error?.cause ? String(error.cause?.message ?? error.cause) : void 0 784 }); 785 }); 786 setupOrderSummaries(this.component); 787 this.component.setHandlers(CheckoutMultiStepV2, { 788 onValidateStep: (step) => this.validateStep(step), 789 onStepChange: (oldStep, step) => { 790 this.stripeElements.update({ appearance: this.parsedAppearence }); 791 const formStructure = FORM_STRUCTURE_BY_TOTAL_STEPS[this.getTotalSteps()]; 792 if (formStructure[oldStep].includes("contact") && oldStep < step) { 793 collectLeads(this.checkoutData.contact); 794 } 795 } 796 }); 797 this.component.setHandlers(CheckoutTosV1, { 798 onAcceptedChange: (accepted) => { 799 if (this.submitedOnce && accepted) { 800 this.clearErrors("tos"); 801 this.showErrors(); 802 } 803 } 804 }); 805 this.shippingComponents = this.component.getComponentGroup(CheckoutShippingProfileSelectV1, { 806 onSelectOption: (option) => this.selectShippingOption(option) 807 }); 808 window.addEventListener("ON_APPLY_COUPON", (event) => { 809 this.sendOrderPreview(event.detail); 810 }); 811 window.addEventListener("ON_CLEAR_COUPON", (event) => { 812 this.sendOrderPreview(event.detail); 813 }); 814 this.loadingComponents = this.component.getComponentGroup(CheckoutLoadingV1); 815 this.submitButtons = this.component.getComponentGroup(CheckoutSubmitButtonV1, { 816 // NOTE: returned so the button can tell a finished attempt from one that is 817 // still running, and unlock itself when we never got as far as the POST. 818 onSubmit: () => this.handleSubmit(this.selectedShippingOption) 819 }); 820 } 821 detectIfReturningFailedPayment() { 822 const urlParams = new URLSearchParams(window.location.search); 823 const failedPaymentMessage = urlParams.get("failed_payment_message"); 824 if (failedPaymentMessage) { 825 const decodedFailedMessage = decodeURIComponent(failedPaymentMessage.replace(/\+/g, " ")); 826 this.handleError({ message: decodedFailedMessage }, true); 827 const visibleSubmitButton = this.getStateElement().querySelector("[data-page-element='CheckoutSubmitButton/V1']"); 828 $("html, body").stop(true, true).animate( 829 { 830 scrollTop: $(visibleSubmitButton).offset().top - 100 831 }, 832 500 833 ); 834 this.submitedOnce = true; 835 } 836 } 837 submitCheckout(redirectOverride) { 838 return this.handleSubmit(this.selectedShippingOption, false, redirectOverride); 839 } 840 clearErrors(field) { 841 this.errors.globalErrors = []; 842 if (field) { 843 delete this.errors.fields[field]; 844 } 845 } 846 clearAllErrors() { 847 this.errors.globalErrors = []; 848 this.errors.fields = {}; 849 } 850 pushGlobalError(message) { 851 this.errors.globalErrors.push({ message }); 852 } 853 pushFieldError(field, message) { 854 this.errors.fields[field] = { message }; 855 } 856 showErrors({ shouldScrollToError } = { shouldScrollToError: false }) { 857 const stepStore = this.store.checkout.step; 858 const currentStep = stepStore.get(); 859 const totalSteps = this.getTotalSteps();
860 let wrapperStateElement = this.getStateElement(); 861 let hasFieldErrors = false; 862 let firstFieldWithError = null; 863 const formStructure = FORM_STRUCTURE_BY_TOTAL_STEPS[totalSteps]; 864 for (let stepIndex = 0; stepIndex < formStructure.length; stepIndex++) { 865 const stepFields = formStructure[stepIndex]; 866 for (const field of stepFields) { 867 if (this.errors.fields[field]) { 868 hasFieldErrors = true; 869 if (stepIndex !== currentStep) stepStore.set(stepIndex); 870 if (!firstFieldWithError) firstFieldWithError = field; 871 break; 872 } 873 } 874 } 875 if (totalSteps > 1) { 876 wrapperStateElement = wrapperStateElement.querySelector( 877 `.elMultiStepBody[data-step-number="${stepStore.get() + 1}"]` 878 ); 879 } 880 Object.keys(this.guestInputFields).forEach((field) => { 881 if (this.errors.fields[field]) { 882 const errorfield = this.errorInputForInput(this.guestInputFields[field]); 883 errorfield.style.height = "20px"; 884 errorfield.previousElementSibling.classList.add("elInputError"); 885 errorfield.textContent = this.errors.fields[field].message; 886 } 887 }); 888 const generalErrorsWrapper = wrapperStateElement.querySelector(".checkout-general-errors-wrapper"); 889 this.component.element.querySelectorAll(".checkout-general-errors-wrapper").forEach((w) => { 890 w.classList.add("forceHide"); 891 const span = w.querySelector("span"); 892 if (span) span.innerHTML = ""; 893 }); 894 const hasGlobalErrors = this.errors.globalErrors.length && this.errors?.globalErrors?.[0]?.message; 895 if (hasGlobalErrors) { 896 const errorMessage = this.errors.globalErrors[0].message; 897 generalErrorsWrapper.querySelector("span").innerHTML = errorMessage; 898 generalErrorsWrapper.classList.remove("forceHide"); 899 } 900 if (shouldScrollToError && (hasGlobalErrors || hasFieldErrors)) { 901 if (stripeElementsForms.includes(firstFieldWithError)) { 902 const scrollToElement = wrapperStateElement.querySelector(`.stripe-${firstFieldWithError}`); 903 scrollToFirstVisibleError(scrollToElement); 904 } else { 905 scrollToFirstVisibleError(generalErrorsWrapper); 906 } 907 } 908 } 909 removeSubmitNotification() { 910 this.submitNotifications.setStatus("", "", ""); 911 } 912 setSubmitNotification(text, subtext, extraText) { 913 this.submitNotifications.setStatus(text, subtext, extraText); 914 } 915 get montedElements() {
916 return this._stripeData[this.getState()].mountedElements; 917 } 918 get stripeElements() { 919 return this._stripeData[this.getState()].stripeElements; 920 } 921 set stripeElements(stripeElement) { 922 this._stripeData[this.getState()].stripeElements = stripeElement; 923 } 924 getState() { 925 return this.component.element.dataset.currentState; 926 } 927 setState(state) { 928 this.component.element.dataset.currentState = state; 929 } 930 updatingExpressStarted() { 931 this.getStateElement().querySelector(".stripe-express-loader-wrapper")?.classList.remove("forceHide"); 932 this.getStateElement().querySelector(".stripe-expressCheckout")?.classList.add("elHidden"); 933 } 934 updatingExpressEnded() { 935 this.getStateElement().querySelector(".stripe-express-loader-wrapper")?.classList.add("forceHide"); 936 this.getStateElement().querySelector(".stripe-expressCheckout")?.classList.remove("elHidden"); 937 } 938 getStateElement() { 939 return this.component.element.querySelector(`[data-state="${this.component.element.dataset.currentState}"]`); 940 } 941 unmountElement(name) { 942 if (!this.montedElements[name].element) return; 943 this.montedElements[name].element.unmount(); 944 const stripeElement = this.getStateElement().querySelector(`.stripe-${name}`); 945 delete stripeElement?.style.opacity; 946 this.montedElements[name].lastChangeEvent = null; 947 this.montedElements[name].mounted = false; 948 } 949 mountElement(name, options, overrideSelector, overrideMountName) { 950 console.log("[Stripe] mounteElement =>", name, options); 951 const state = this.getState(); 952 const selector = overrideSelector ?? `.stripe-${name}`; 953 const hasElement = this.getStateElement().querySelector(selector); 954 if (!hasElement) return; 955 const mountName = overrideMountName ?? name; 956 if (!this.montedElements[mountName].element) { 957 options = options ?? {}; 958 this.montedElements[mountName].element = this.stripeElements.create(name, options); 959 } 960 this.montedElements[mountName].element.mount(`[data-state=${state}] ${selector}`); 961 this.montedElements[mountName].mounted = true; 962 } 963 mountLink() { 964 if (this.montedElements.linkAuthentication.mounted) return; 965 const options = {}; 966 if (this.checkoutData.contact.email) { 967 options.defaultValues = { email: this.checkoutData.contact.email }; 968 } 969 this.mountElement("linkAuthentication", options); 970 this.montedElements.linkAuthentication.element?.on("change", (event) => { 971 this.removeSubmitNotificationStripeElementChange(); 972 console.log("[Stripe] linkAuthentication [incoming-event]: ", event); 973 delete this.errors.fields["linkAuthentication"]; 974 this.montedElements.linkAuthentication.lastChangeEvent = event; 975 if (!event.complete) return; 976 this.checkoutData.contact.email = event.value.email; 977 this.triggerTrackingForStripeElement({ email: event.value.email }); 978 this.updatePaymentElementDefaultValues(); 979 }); 980 this.montedElements.linkAuthentication.element?.on("ready", () => { 981 this.elementReady("linkAuthentication"); 982 this.elementReady("contact-form"); 983 }); 984 this.montedElements.linkAuthentication.element?.on( 985 "loaderror", 986 (error) => this.handleStripeElementLoadingError(error) 987 ); 988 } 989 pushSetupError(code, customMessage, context) { 990 this.setupErrors.push(code); 991 const setupErrorComponent = this.component.getComponent(CheckoutConfigurationErrorV1); 992 const errorMessage = customMessage || getStripeCheckoutErrorMessage(this.setupErrors); 993 setupErrorComponent.setErrors(errorMessage); 994 scrollToFirstVisibleError(setupErrorComponent.element); 995 globalThis.Honeybadger?.notify(`StripeCheckout setup error: ${StripeCheckoutErrorCode[code]}`, { 996 context: { 997 errorCode: `0x${code.toString(16)}`, 998 // The banner shows the OR of all codes pushed so far. Report it too so 999 // a banner screenshot (e.g. "Code: 0x24") can be matched to notices. 1000 errorMask: `0x${this.setupErrors.reduce((acc, val) => acc | val, 0).toString(16)}`, 1001 checkoutState: this.getState(), 1002 hasContactEmail: Boolean(globalThis.sessionData?.contact?.email), 1003 ...context ?? {} 1004 } 1005 }); 1006 } 1007 handleStripeElementLoadingError({ elementType, error }) { 1008 const errorEnumName = `STRIPE_${elementType.split(/(?=[A-Z])/).join("_").toUpperCase()}_ELEMENT_INITIALIZATION_ERROR`; 1009 const errorCode = StripeCheckoutErrorCode[errorEnumName]; 1010 const errorMessage = error?.message ? `Error loading ${elementType} element: ${error.message}` : getStripeCheckoutErrorMessage([errorCode]); 1011 this.pushSetupError(errorCode, errorMessage, { elementType, stripeError: error }); 1012 console.error(`Stripe ${elementType} element initialization error: `, error); 1013 } 1014 getTotalSteps() {
1015 return this.getState() !== "guest" ? 1 : this.component.totalSteps; 1016 } 1017 mountAddress() { 1018 const options = this.addressOptions; 1019 const mode = options.mode; 1020 const state = this.getState(); 1021 if (this.montedElements[mode].mounted) return; 1022 if (mode === "billing" && state === "oto" /* OTO */ && !this.paymentDeclined) return; 1023 const stateElement = this.getStateElement(); 1024 const otherMode = options.mode == "shipping" ? "billing" : "shipping"; 1025 stateElement.querySelector(`.stripe-address-${otherMode}-container`)?.classList.add("forceHide"); 1026 this.unmountElement(otherMode); 1027 if (mode === "billing" && !this.component.billingEnabled) { 1028 const contactFormElement = stateElement.querySelector(".stripe-contact-form"); 1029 contactFormElement?.classList.remove("forceHide"); 1030 return; 1031 } 1032 if (mode === "shipping" && !this.component.billingEnabled && this.component.totalSteps === 1) { 1033 const contactFormElement = stateElement.querySelector(".stripe-contact-form"); 1034 contactFormElement?.classList.add("forceHide"); 1035 } 1036 const stripeElement = stateElement.querySelector(`.stripe-${mode}`); 1037 stateElement.querySelector('[data-stripe-loading-element="address"]').classList.remove("forceHide"); 1038 stripeElement.classList.add("elAbsolute"); 1039 delete stripeElement.style.opacity; 1040 stateElement.querySelector(`.stripe-address-${mode}-container`)?.classList.remove("forceHide"); 1041 this.mountElement("address", options, `.stripe-${mode}`, mode); 1042 this.montedElements[mode].element?.on("change", (event) => { 1043 this.removeSubmitNotificationStripeElementChange(); 1044 this.montedElements[mode].lastChangeEvent = event; 1045 delete this.errors.fields[mode]; 1046 const parsedData = this.parsedAddressEventData(event); 1047 console.log("[Stripe] address [incoming-event]: ", event); 1048 this.checkoutData.contact.first_name = parsedData.first_name; 1049 this.checkoutData.contact.last_name = parsedData.last_name; 1050 this.checkoutData.contact.phone_number = parsedData.phone_number; 1051 this.triggerTrackingForStripeElement({ 1052 firstName: parsedData.first_name, 1053 lastName: parsedData.last_name, 1054 phone: parsedData.phone_number 1055 }); 1056 this.syncInputFields(); 1057 this.updatePaymentElementDefaultValues(); 1058 this.checkoutData.address = parsedData.address; 1059 if (hasPhysicalProducts(globalThis.Checkout.computed.checkoutCart.get())) { 1060 if (this.hasValidAddress(this.checkoutData.address)) {
1061 this.shippingComponents.setLoading(); 1062 } else { 1063 this.shippingComponents.setInvalidAddress(); 1064 } 1065 } 1066 this.sendOrderPreview({ throttle: true }); 1067 }); 1068 this.montedElements[mode].element?.on("ready", () => { 1069 stateElement.querySelector(`.stripe-address-${mode}-container`).classList.remove("forceHide"); 1070 this.elementReady(mode); 1071 }); 1072 this.montedElements[mode].element?.on("loaderror", (error) => this.handleStripeElementLoadingError(error)); 1073 } 1074 parsedAddressEventData(event) { 1075 const data = {}; 1076 if (event.value.firstName || event.value.lastName) { 1077 data.first_name = event.value.firstName; 1078 data.last_name = event.value.lastName; 1079 } else { 1080 const [first_name, ...last_name] = (event.value?.name ?? "").split(" "); 1081 data.first_name = first_name; 1082 data.last_name = last_name.join(" "); 1083 } 1084 data.phone_number = event.value.phone; 1085 data.address = convertAddressFromStripe(event.value.address); 1086 return data; 1087 } 1088 get paymentOptions() { 1089 let address = "auto"; 1090 if (!this.component.billingEnabled && this.component.billingFields === "if_required") { 1091 address = "if_required"; 1092 } 1093 return { 1094 defaultValues: this.generatePaymentDefaultValues(), 1095 fields: { 1096 billingDetails: { 1097 name: "never", 1098 phone: "auto", 1099 address 1100 } 1101 } 1102 }; 1103 } 1104 mountPayment() { 1105 if (this.montedElements.payment.mounted) return; 1106 const options = { 1107 terms: { card: "never" }, 1108 ...this.paymentOptions 1109 }; 1110 this.mountElement("payment", options); 1111 this.montedElements.payment.element?.on("change", (event) => { 1112 this.removeSubmitNotificationStripeElementChange(); 1113 delete this.errors.fields["payment"]; 1114 console.log("[Stripe] paymentMethods [incoming-event]: ", event); 1115 this.montedElements.payment.lastChangeEvent = event; 1116 this.sendOrderPreview({ throttle: true }); 1117 }); 1118 this.montedElements.payment.element?.on("ready", () => { 1119 this.elementReady("payment"); 1120 }); 1121 this.montedElements.payment.element?.on("loaderror", (error) => this.handleStripeElementLoadingError(error)); 1122 } 1123 generatePaymentDefaultValues() { 1124 const defaultValues = {}; 1125 if (this.checkoutData.contact.first_name || this.checkoutData.contact.last_name) { 1126 defaultValues.billingDetails = defaultValues?.billingDetails ?? {}; 1127 defaultValues.billingDetails.name = [ 1128 this.checkoutData.contact.first_name ?? "", 1129 this.checkoutData.contact.last_name ?? "" 1130 ].filter(Boolean).join(" "); 1131 } 1132 if (this.checkoutData.contact.email) { 1133 defaultValues.billingDetails = defaultValues?.billingDetails ?? {}; 1134 defaultValues.billingDetails.email = this.checkoutData.contact.email; 1135 } 1136 if (this.checkoutData.contact.phone_number && this.hasPhoneNumber()) { 1137 defaultValues.billingDetails = defaultValues?.billingDetails ?? {}; 1138 defaultValues.billingDetails.phone = this.checkoutData.contact.phone_number; 1139 } 1140 return defaultValues; 1141 } 1142 updatePaymentElementDefaultValues() { 1143 if (this.getState() !== "guest") return; 1144 this.montedElements.payment.element?.update({ 1145 ...this.paymentOptions 1146 }); 1147 } 1148 handleOrderPreviewForExpressCheckout(response, resolve) { 1149 const cart = this.store.checkoutCart.get(); 1150 this.stripeElements.update({ 1151 amount: response.total_amount_cents, 1152 mode: getStripeMode(cart, response.total_amount_cents) 1153 }); 1154 const changeResolveDetails = calculateStripeChangeResolveDetails( 1155 response.line_items, 1156 response.shipping_quotes_response?.options, 1157 response.selected_shipping_option, 1158 response.tax_amount_cents, 1159 response.discount_amount_cents 1160 ); 1161 resolve(changeResolveDetails); 1162 } 1163 mountExpress() { 1164 if (this.montedElements.expressCheckout.mounted) return; 1165 this.mountElement("expressCheckout", { 1166 paymentMethods: { link: this.component.payWithLinkEnabled ? "auto" : "never" } 1167 }); 1168 this.montedElements.expressCheckout.element?.on("confirm", (evt) => { 1169 const shippingOption = this.formatExpressShippingOption(evt.shippingRate?.
1169id); 1170 this.storeShippingDataFromExpress(evt.shippingAddress); 1171 this.handleSubmit(shippingOption, true); 1172 }); 1173 this.montedElements.expressCheckout.element?.on("click", ({ resolve }) => { 1174 resolve({ 1175 shippingAddressRequired: true, 1176 emailRequired: true, 1177 phoneNumberRequired: this.hasPhoneNumber(), 1178 shippingRates: [{ id: "calculating", amount: 0, displayName: "Calculating..." }], 1179 lineItems: calculateStripeLineItems(this.summaryLineItems) 1180 }); 1181 }); 1182 const sendExpressCheckoutOrderPreview = async (resolve, reject) => { 1183 const cart = this.store.checkoutCart.get(); 1184 const dataToPreview = { 1185 cart, 1186 address: this.digitalPaymentData.address, 1187 integrations_stripe_installation_id: stripeInstallationId 1188 }; 1189 const couponState = this.store.coupons.state.get(); 1190 let couponCode; 1191 if (couponState == "applying" /* APPLYING */) { 1192 couponCode = this.store.coupons.currentCode.get(); 1193 } else { 1194 couponCode = this.store.coupons.appliedCode.get(); 1195 } 1196 dataToPreview.coupon_code = couponCode; 1197 dataToPreview.shippingOption = this.formatExpressShippingOption(this.digitalPaymentData.shippingRateId); 1198 await fetchOrderPreview(dataToPreview).then((response) => { 1199 if (isResponseError(response)) { 1200 reject(); 1201 } else { 1202 this.handleOrderPreviewForExpressCheckout(response, resolve); 1203 } 1204 }); 1205 }; 1206 this.montedElements.expressCheckout.element?.on("shippingaddresschange", async ({ address, resolve, reject }) => { 1207 this.digitalPaymentData.address = convertAddressFromStripe(address); 1208 sendExpressCheckoutOrderPreview(resolve, reject); 1209 }); 1210 this.montedElements.expressCheckout.element?.on("cancel", () => { 1211 const cart = this.store.checkoutCart.get(); 1212 this.stripeElements.update({ 1213 amount: this.summaryLineItemSubtotalCents, 1214 mode: getStripeMode(cart, this.summaryLineItemSubtotalCents) 1215 }); 1216 }); 1217 this.montedElements.expressCheckout.element?.on("shippingratechange", async ({ shippingRate, resolve, reject }) => { 1218 this.digitalPaymentData.shippingRateId = shippingRate.id; 1219 sendExpressCheckoutOrderPreview(resolve, reject); 1220 }); 1221 this.montedElements.payment.element?.on("loaderror", (error) => this.handleStripeElementLoadingError(error)); 1222 } 1223 formatExpressShippingOption(id) { 1224 if (id && id != STRIPE_SKIP_SHIPPING_ID) { 1225 return JSON.parse(id); 1226 } 1227 } 1228 elementReady(name) { 1229 const stateElement = this.getStateElement(); 1230 const loaderElementName = ["shipping", "billing"].includes(name) ? "address" : name; 1231 const loaderElement = stateElement.querySelector(`[data-stripe-loading-element="${loaderElementName}"]`); 1232 const stripeElement = stateElement.querySelector(`.stripe-${name}`); 1233 this.stripeElements.update({ appearance: this.parsedAppearence }); 1234 let obeserverTimeout; 1235 const resizeObserver = new ResizeObserver(() => { 1236 clearTimeout(obeserverTimeout); 1237 obeserverTimeout = setTimeout(() => { 1238 resizeObserver.disconnect(); 1239 this.stripeElements.update({ appearance: this.parsedAppearence }); 1240 loaderElement.classList.add("forceHide"); 1241 stripeElement.classList.remove("elAbsolute"); 1242 stripeElement.style.opacity = "1"; 1243 }, 500); 1244 }); 1245 resizeObserver.observe(stripeElement); 1246 } 1247 oneTimeListeners() { 1248 const signOutButtonthis = this.component.element.querySelector(".sign-out-button"); 1249 signOutButtonthis.addEventListener("click", () => { 1250 fetch("/contacts/sign_out", { 1251 method: "DELETE", 1252 headers: { "Content-Type": "application/json" }, 1253 credentials: "same-origin" 1254 }).then(() => this.ensureStripeSession()).then((session) => { 1255 if (session) { 1256 this.store.checkout.step.set(0); 1257 this.setState("guest"); 1258 this.initializeElements(session, this.summaryLineItemSubtotalCents); 1259 setTimeout(() => { 1260 this.updateElementsDisplay(); 1261 const cart = this.store.checkoutCart.get(); 1262 CheckoutExistingOrders.clearUpdatableOrders(cart.items[0]?.product_id); 1263 }, 1e3); 1264 } 1265 }); 1266 }); 1267 this.component.getComponents("Input/V1").forEach((inputComponent) => { 1268 const input = inputComponent.element.querySelector("input"); 1269 const inputName = input.getAttribute("name"); 1270 if (!["first_name", "last_name", "phone_number"].includes(inputName)) return; 1271 if (inputName == "phone_number") { 1272 IntlTel_initPhoneInput(input, { initialCountry: globalThis.cfVisitorData?.country ?? "us" }); 1273 } 1274 this.guestInputFields[inputName] = input;
1275 ["input", "focus"].forEach((eventName) => { 1276 input.addEventListener(eventName, () => { 1277 const errorfield = this.errorInputForInput(input); 1278 errorfield.style.height = "0px"; 1279 errorfield.previousElementSibling.classList.remove("elInputError"); 1280 errorfield.textContent = ""; 1281 }); 1282 }); 1283 input.addEventListener("blur", (event) => { 1284 const input2 = event.target; 1285 let value = input2.value; 1286 if (inputName == "phone_number") { 1287 value = input2.iti.getNumber(); 1288 } 1289 this.checkoutData.contact[inputName] = value; 1290 this.updateInputState(inputName, value); 1291 this.updatePaymentElementDefaultValues(); 1292 }); 1293 }); 1294 this.store.checkoutCart.subscribe(() => { 1295 this.updateElementsDisplay(); 1296 this.sendOrderPreview(); 1297 if (this.submitedOnce) { 1298 this.clearErrors("products"); 1299 this.showErrors(); 1300 } 1301 }); 1302 this.listenAndSurfaceSummaryErrors(); 1303 } 1304 // Mirrors Rebilly's `listenAndUpdateCheckoutErrors` (rebilly-checkout.ygg): when an 1305 // order-preview error lands on `summary.error.message`, surface it via the same 1306 // `.checkout-general-errors-wrapper` that submit-time errors use. 1307 listenAndSurfaceSummaryErrors() { 1308 this.store.summary.subscribe((summary) => { 1309 const message = summary?.state === "error" /* ERROR */ && summary?.error?.message ? summary.error.message : null; 1310 if (this.summaryErrorEntry) { 1311 const idx = this.errors.globalErrors.indexOf(this.summaryErrorEntry); 1312 if (idx >= 0) this.errors.globalErrors.splice(idx, 1); 1313 this.summaryErrorEntry = null; 1314 } 1315 if (message) { 1316 this.summaryErrorEntry = { message }; 1317 this.errors.globalErrors.push(this.summaryErrorEntry); 1318 } 1319 this.showErrors(); 1320 }); 1321 } 1322 updateElementsDisplay() { 1323 this.cartAddressUpdates(); 1324 this.updatePaymentElement(); 1325 this.updateShippingOptionsDisplay(); 1326 this.updateSubmitButton(); 1327 } 1328 updatePaymentElement() { 1329 if (this.hasPaymentElement()) { 1330 this.mountPayment(); 1331 this.mountExpress(); 1332 this.mountLink();
1333 this.getStateElement().querySelector(".stripe-payment-container")?.classList.remove("forceHide"); 1334 this.getStateElement().querySelector(".stripe-expressCheckout-container")?.classList.remove("forceHide"); 1335 } else { 1336 this.unmountElement("linkAuthentication"); 1337 this.unmountElement("payment"); 1338 this.unmountElement("expressCheckout"); 1339 this.getStateElement().querySelector(".stripe-payment-container")?.classList.add("forceHide"); 1340 this.getStateElement().querySelector(".stripe-expressCheckout-container")?.classList.add("forceHide"); 1341 } 1342 } 1343 cartAddressUpdates() { 1344 const cart = this.store.checkoutCart.get(); 1345 const hasPhysical = hasPhysicalProducts(cart); 1346 if (this.oldHasPhysical != hasPhysical) { 1347 this.updateWithShippingAddress(cart); 1348 } 1349 if (this.getState() == "oto") { 1350 if (this.otoRequiresShippingForm() || this.paymentDeclined) { 1351 this.mountAddress(); 1352 this.getStateElement().querySelector(".stripe-address")?.classList.remove("forceHide"); 1353 } else {
1354 this.getStateElement().querySelector(".stripe-address")?.classList.add("forceHide"); 1355 } 1356 if (hasUpgradeDowngradeItems(cart)) { 1357 this.getStateElement().querySelector('[data-page-element="CheckoutProductSelectBump/V2"]')?.classList.add("forceHide"); 1358 } else { 1359 this.getStateElement().querySelector('[data-page-element="CheckoutProductSelectBump/V2"]')?.classList.remove("forceHide"); 1360 } 1361 } else if (hasUpgradeDowngradeItems(cart)) { 1362 this.getStateElement().querySelector(".stripe-address")?.classList.add("forceHide"); 1363 } else { 1364 this.mountAddress(); 1365 this.getStateElement().querySelector(".stripe-address")?.classList.remove("forceHide"); 1366 } 1367 this.oldHasPhysical = hasPhysical; 1368 } 1369 updateSubmitButton() { 1370 if (this.submitInFlight) return; 1371 const cart = this.store.checkoutCart.get(); 1372 if (hasUpgradeDowngradeItems(cart)) { 1373 this.submitButtons.update(this.component.buttonUpgradeDowngrade); 1374 } else { 1375 this.submitButtons.setButtonInitialHTML(); 1376 } 1377 } 1378 get addressOptions() { 1379 const hasPhysical = hasPhysicalProducts(this.store.checkoutCart.get()); 1380 const options = { 1381 mode: hasPhysical ? "shipping" : "billing", 1382 fields: { phone: this.hasPhoneNumber() ? "always" : "never" }, 1383 validation: this.hasPhoneNumber() ? { phone: { required: "always" } } : {}, 1384 display: { 1385 name: "split" 1386 } 1387 }; 1388 options.autocomplete = { mode: "automatic" }; 1389 options.defaultValues = {}; 1390 if (Object.keys(this.checkoutData.address).length || this.get
1390State() == "oto") { 1391 options.defaultValues.address = convertToStripeAddress(this.checkoutData.address); 1392 } 1393 const { first_name, last_name, phone_number } = this.checkoutData.contact ?? {}; 1394 if (options.display.name == "split") { 1395 if (first_name) options.defaultValues.firstName = first_name; 1396 if (last_name) options.defaultValues.lastName = last_name; 1397 } else { 1398 if (first_name) { 1399 options.defaultValues.name = first_name; 1400 if (last_name) { 1401 options.defaultValues.name += ` ${last_name}`; 1402 } 1403 } 1404 } 1405 if (this.checkoutData.contact.phone_number && this.hasPhoneNumber()) { 1406 options.defaultValues.phone = this.checkoutData.contact.phone_number; 1407 } 1408 if (options.defaultValues.address && first_name && last_name) { 1409 const phoneNumberObj = this.hasPhoneNumber() ? { phone: phone_number } : {}; 1410 options.contacts = [ 1411 { name: `${first_name} ${last_name}`, address: options.defaultValues.address, ...phoneNumberObj } 1412 ]; 1413 } 1414 return options; 1415 } 1416 hasPhoneNumber() { 1417 const showPhoneNumber = this.component.contactFields.includes("phone_number"); 1418 return showPhoneNumber; 1419 } 1420 stripeElementHasError(type) { 1421 return !!this.montedElements[type].lastChangeEvent && !this.montedElements[type].lastChangeEvent?.complete; 1422 } 1423 shouldClearFormErrors(type) { 1424 return !this.submitedOnce && this.stripeElementHasError(type); 1425 } 1426 clearStripeElement(type) { 1427 console.log("[Stripe][clearStripeElement] =>", type); 1428 this.montedElements[type].element?.clear(); 1429 } 1430 processCheckoutError(errorsByForm) { 1431 for (const [, errors] of Object.entries(errorsByForm)) { 1432 for (const [field, error] of Object.entries(errors?.fields ?? {})) { 1433 if (!error) continue; 1434 this.pushFieldError(field, error.message); 1435 } 1436 if (errors?.globalErrors?.length) { 1437 this.pushGlobalError(errors.globalErrors[0].message); 1438 } 1439 } 1440 return !hasErrors(this.errors); 1441 } 1442 shippingOptionErrors() { 1443 const state = this.component.getComponent(CheckoutShippingProfileSelectV1); 1444 const cart = this.store.checkoutCart.get(); 1445 if (hasUpgradeDowngradeItems(cart)) { 1446 return null; 1447 } else if (state) { 1448 const { shippingOptions, shippingOption, loadingShipping } = state; 1449 return shippingOptionErrors(loadingShipping, cart, shippingOptions, shippingOption, true, null); 1450 } 1451 } 1452 tosErrors() { 1453 const state = this.component.getComponent(CheckoutTosV1); 1454 if (state) { 1455 return tosErrors(state.accepted, true, true); 1456 } 1457 } 1458 contactErrors() { 1459 return contactErrors(this.checkoutData.contact, Object.keys(this.guestInputFields)); 1460 } 1461 stripeFormErrors(type) { 1462 if (this.shouldValidateStripeElements() && this.stripeElementHasError(type)) { 1463 return { fields: { [type]: { message: "has error" } } }; 1464 } 1465 } 1466 formErrorsByFormName(formName) { 1467 switch (formName) { 1468 case "products": 1469 return productErrors(this.store.checkoutCart.get(), true); 1470 case "shippingOption": 1471 return this.shippingOptionErrors(); 1472 case "tos": 1473 return this.tosErrors(); 1474 case "contact": 1475 return this.contactErrors(); 1476 } 1477 } 1478 async validateStep(step) { 1479 this.clearAllErrors(); 1480 const formErrors = {}; 1481 const curretStepForms = FORM_STRUCTURE_BY_TOTAL_STEPS[this.getTotalSteps()][step]; 1482 let hasStripeElementInStep = false; 1483 for (const field of curretStepForms) { 1484 if (stripeElementsForms.includes(field)) { 1485 hasStripeElementInStep = true; 1486 formErrors[field] = this.stripeFormErrors(field); 1487 } else { 1488 formErrors[field] = this.formErrorsByFormName(field); 1489 } 1490 } 1491 const formValid = this.processCheckoutError(formErrors); 1492 if (!formValid && hasStripeElementInStep) { 1493 await this.stripeElements.submit(); 1494 if (this.shouldClearFormErrors("billing")) this.clearStripeElement("billing"); 1495 if (this.shouldClearFormErrors("shipping")) this.clearStripeElement("shipping"); 1496 if (this.shouldClearFormErrors("payment")) this.clearStripeElement("payment"); 1497 } 1498 const customFieldsValid = checkValidCustomFields(this.component.element); 1499 const valid = formValid && customFieldsValid; 1500 if (!valid) { 1501 this.showErrors({ shouldScrollToError: true }); 1502 } 1503 return valid; 1504 } 1505 updateShippingOptionsDisplay() {
1506 const stateElement = this.getStateElement(); 1507 const cart = this.store.checkoutCart.get(); 1508 if (hasPhysicalProducts(cart) && !hasUpgradeDowngradeItems(cart)) { 1509 stateElement.querySelector(".shipping-options-container")?.classList.remove("forceHide"); 1510 this.shippingComponents.show(); 1511 } else { 1512 this.shippingComponents.hide(); 1513 stateElement.querySelector(".shipping-options-container")?.classList.add("forceHide"); 1514 } 1515 } 1516 selectShippingOption(option) { 1517 this.selectedShippingOption = option; 1518 this.shippingComponents.setOption(option); 1519 this.sendOrderPreview(); 1520 if (this.submitedOnce) { 1521 this.clearErrors("shippingOption"); 1522 this.showErrors(); 1523 } 1524 } 1525 updateShippingOptions(update) { 1526 this.shippingComponents.update(update); 1527 } 1528 async sendOrderPreview(options) { 1529 const { skipUpdatingStripe, throttle, isCouponValidation, skipThrottle, forceOrderPreview } = options ?? {}; 1530 if (this.preventOrderPreview && !forceOrderPreview) return; 1531 const { summary } = this.store; 1532 const cart = this.store.checkoutCart.get(); 1533 if (!cart.items?.length) { 1534 if (this.store.coupons.appliedCode.get() || this.store.coupons.currentCode.get()) { 1535 this.store.coupons.appliedCode.set(""); 1536 this.store.coupons.currentCode.set(""); 1537 this.store.coupons.state.set("ready" /* READY */); 1538 } 1539 summary.set({ state: "waiting" /* WAITING */ }); 1540 return; 1541 } 1542 const couponStateBeforeApplying = this.store.coupons.state.get(); 1543 if (isCouponValidation && couponStateBeforeApplying != "applying" /* APPLYING */) { 1544 this.couponStateBeforeApplying = couponStateBeforeApplying; 1545 this.store.coupons.state.set("applying" /* APPLYING */); 1546 } 1547 const payload = this.hasValidAddress(this.checkoutData.address) ? { 1548 address: this.checkoutData.address, 1549 shippingOption: this.selectedShippingOption, 1550 cart 1551 } : { cart }; 1552 payload.integrations_stripe_installation_id = stripeInstallationId; 1553 const couponState = this.store.coupons.state.get(); 1554 let couponCode; 1555 if (couponState == "applying" /* APPLYING */) { 1556 couponCode = this.store.coupons.currentCode.get(); 1557 } else { 1558 couponCode = this.store.coupons.appliedCode.get(); 1559 } 1560 payload.coupon_code = couponCode; 1561 const canUpdateSummary = this.hasValidDataForSummaryUpdate(payload); 1562 const canFetchLineItemSubtotal = hasValidDataForLineItemSubtotal(payload); 1563 if (!canUpdateSummary) { 1564 summary.set({ 1565 state: "waiting" /* WAITING */ 1566 }); 1567 } 1568 if (canUpdateSummary && !canFetchLineItemSubtotal) { 1569 return; 1570 } 1571 this.updatingExpressStarted(); 1572 clearTimeout(this.sendOrderPreviewTimer); 1573 const sendOrderPreviewFn = async () => { 1574 const response = await fetchOrderPreview(payload, () => { 1575 if (canUpdateSummary) { 1576 if (hasPhysicalProducts(cart)) {
1577 this.shippingComponents.setLoading(); 1578 } 1579 summary.set({ 1580 state: "calculating" /* CALCULATING */ 1581 }); 1582 } 1583 }); 1584 if (canFetchLineItemSubtotal && !isResponseError(response)) { 1585 this.summaryLineItems = response.line_items; 1586 this.summaryLineItemSubtotalCents = response.line_item_subtotal_cents; 1587 const currentCode = this.store.coupons.currentCode.get(); 1588 const couponCodeApplied = response.discounts?.find( 1589 (discount) => discount.code?.toUpperCase() == currentCode?.toUpperCase() 1590 ); 1591 if (couponCodeApplied) { 1592 this.store.coupons.appliedCode.set(currentCode); 1593 this.store.coupons.state.set("applied" /* APPLIED */); 1594 } else { 1595 this.store.coupons.state.set("ready" /* READY */); 1596 } 1597 if (!skipUpdatingStripe) { 1598 this.stripeElements.update({ 1599 amount: response.line_item_subtotal_cents, 1600 mode: getStripeMode(cart, response.line_item_subtotal_cents) 1601 }); 1602 } 1603 } else if (isBackendError(response)) { 1604 this.updatingExpressEnded(); 1605 if (isResponseError(response)) { 1606 this.handleOrderPreviewErrorForExpress(response.error); 1607 } 1608 } 1609 if (canUpdateSummary || couponCode) { 1610 if (isResponseError(response)) { 1611 if (isBackendError(response)) { 1612 this.handleOrderPreviewErrorForSummary(response.error); 1613 } 1614 } else { 1615 const summaryResponse = processOrderPreviewResponseForSummary(response); 1616 const { shipping_quotes_response, selected_shipping_option } = response; 1617 this.selectedShippingOption = selected_shipping_option; 1618 summary.set({ 1619 state: "ok" /* OK */, 1620 data: summaryResponse 1621 }); 1622 if (hasPhysicalProducts(cart)) { 1623 this.shippingComponents.setShippingOptions( 1624 shipping_quotes_response?.options ?? [], 1625 selected_shipping_option 1626 ); 1627 } 1628 } 1629 } 1630 }; 1631 if (skipThrottle) { 1632 return await sendOrderPreviewFn(); 1633 } else { 1634 this.sendOrderPreviewTimer = setTimeout( 1635 async () => { 1636 await sendOrderPreviewFn(); 1637 }, 1638 throttle ? 500 : 0 1639 ); 1640 } 1641 } 1642 startLoadingState() { 1643 this.submitInFlight = true; 1644 this.loadingComponents.show(); 1645 this.submitButtons.setSubmittingText(); 1646 } 1647 removeLoadingState() { 1648 this.submitInFlight = false; 1649 this.loadingComponents.hide(); 1650 this.submitButtons.setButtonInitialHTML(); 1651 } 1652 handleStripeError(error) { 1653 if (!this.hasPaymentElement() && STRIPE_INSTRUMENT_REJECTION_CODES.has(error.code)) { 1654 this.paymentDeclined = true; 1655 this.updateElementsDisplay(); 1656 } 1657 return error.message; 1658 } 1659 handleOrderPreviewErrorForExpress(response) { 1660 const errorDetail = response.details?.[0]; 1661 if (errorDetail?.type == "InvalidCouponError") { 1662 this.store.coupons.appliedCode.set(""); 1663 this.store.coupons.currentCode.set(""); 1664 this.store.coupons.errorMessage.set(errorDetail.message); 1665 this.store.coupons.state.set("error" /* ERROR */); 1666 } else { 1667 globalThis.Checkout.store.coupons.state.set(this.couponStateBeforeApplying); 1668 } 1669 } 1670 handleOrderPreviewErrorForSummary(response) { 1671 const errorDetail = response.details?.[0]; 1672 if (errorDetail?.type == "InvalidCouponError") { 1673 const summaryResponse = processOrderPreviewResponseForSummary(errorDetail.preview); 1674 this.store.summary.set({ 1675 state: "ok" /* OK */, 1676 data: summaryResponse 1677 }); 1678 } else if (errorDetail?.message) { 1679 this.store.summary.set({ 1680 state: "error" /* ERROR */, 1681 error: { message: errorDetail.message } 1682 }); 1683 } else { 1684 this.store.summary.set({ 1685 state: "error" /* ERROR */, 1686 error: { message: "There was an error calculating your order total" } 1687 }); 1688 } 1689 } 1690 handleError(error, skipRemoveLoadingState = false) { 1691 const GENERIC_PAYMENT_ERROR = "There was a problem processing your payment. Please try again or use a different payment method."; 1692 console.error("Error Submiting Payment:", error); 1693 if (!skipRemoveLoadingState) { 1694 this.removeLoadingState(); 1695 } 1696 let displayMessage = ""; 1697 let actionable = true; 1698 if (isStripeError(error)) { 1699 displayMessage = this.handleStripeError(error); 1700 } else { 1701 displayMessage = serverCheckoutErrorMessage(error) ?? GENER
1701IC_PAYMENT_ERROR; 1702 actionable = isActionableCheckoutError(error); 1703 } 1704 this.lastSubmitFailure = { message: displayMessage, actionable }; 1705 this.setSubmitNotification("Failed Submit", displayMessage); 1706 } 1707 async processStripeResponse(next_url, { error, paymentIntent }) { 1708 if (error) { 1709 this.handleError(error); 1710 } else if (paymentIntent.status === "succeeded") { 1711 fetch(next_url, { 1712 method: "GET", 1713 redirect: "manual" 1714 }).then((response) => { 1715 if (response.status >= 300 && response.status < 400) { 1716 const location = response.headers.get("Location"); 1717 window.location.href = location; 1718 } else { 1719 response.text().then((data) => { 1720 const jsonResponse = JSON.parse(data); 1721 this.handleError({ 1722 message: jsonResponse.error 1723 }); 1724 }); 1725 } 1726 }); 1727 } else if (paymentIntent.status === "processing") { 1728 setTimeout(async () => { 1729 const result = await this.stripe.retrievePaymentIntent(paymentIntent.client_secret); 1730 this.processStripeResponse(next_url, result); 1731 }, 1e3); 1732 } else { 1733 const voucherNextActions = ["konbini_display_details", "boleto_display_details", "oxxo_display_details", "multibanco_display_details"]; 1734 const nextActionType = paymentIntent.next_action?.type; 1735 if (paymentIntent.status === "requires_action" && nextActionType && voucherNextActions.includes(nextActionType)) { 1736 window.location.href = next_url; 1737 } else { 1738 this.removeLoadingState(); 1739 } 1740 } 1741 } 1742 async handleServerResponse(response) { 1743 if (isResponseError(response)) { 1744 this.handleError(response.error); 1745 } else { 1746 if (response.payment_intent?.status === "requires_action") { 1747 const result = await this.stripe.handleNextAction({ 1748 clientSecret: response.payment_intent.client_secret 1749 }); 1750 if (result.error) { 1751 this.handleError(result.error); 1752 } else if ("setupIntent" in result && result.setupIntent) { 1753 if (result.setupIntent.status === "succeeded") { 1754 window.location.href = response.next_url; 1755 } else { 1756 this.removeLoadingState(); 1757 } 1758 } else { 1759 await this.processStripeResponse(response.next_url, result); 1760 } 1761 } else if (response.next_url) { 1762 window.location.href = response.next_url; 1763 } else { 1764 this.handleError(response); 1765 } 1766 } 1767 } 1768 shouldIncludeRegistration() { 1769 return document.getElementsByName("registration[calendar_event_id]")?.length > 0; 1770 } 1771 buildRegistrationDetails() { 1772 return { 1773 calendar_event_id: document.getElementsByName("registration[calendar_event_id]")[0].value, 1774 occurs_at: document.getElementsByName("registration[occurs_at]")[0].value 1775 }; 1776 } 1777 buildSubmitPayload(shippingOption, idempotencyKey, confirmationToken, redirectOverride) { 1778 const customFields = buildCustomFields(); 1779 const cart = this.store.checkoutCart.get(); 1780 const coupon = this.store.coupons.appliedCode.get(); 1781 const defaultRedirect = this.element.querySelector('[href="#submit-checkout-form"]')?.getAttribute("data-on-submit-go-to"); 1782 const redirect_to = redirectOverride || defaultRedirect; 1783 const payload = { 1784 ...customFields ? customFields : {}, 1785 payment_processor: "stripe", 1786 integrations_stripe_installation_id: stripeInstallationId, 1787 request_idempotency: idempotencyKey, 1788 skip_optin_track: document.getElementById("cfAR")?.getAttribute("data-leads-collected") === "true", 1789 purchase: { 1790 coupon_codes: [coupon].filter(Boolean), 1791 process_new_order: true, 1792 selected_shipping_option: shippingOption, 1793 order_id: cart.order_id, 1794 product_variants: cart.items.map(({ variant_id, price_id, line_item_id, quantity }) => { 1795 return { 1796 id: variant_id, 1797 quantity, 1798 price_id, 1799 ...line_item_id ? { line_item_id } : {} 1800 }; 1801 }) 1802 } 1803 }; 1804 if (redirect_to) { 1805 payload.redirect_to = redirect_to; 1806 } 1807 if (confirmationToken) { 1808 payload.token = confirmationToken.id; 1809 } else if (hasUpgradeDowngradeItems(cart)) { 1810 payload.contact = { 1811 email: globalThis.sessionData.contact.email, 1812 first_name: globalThis.sessionData.contact.first_name, 1813 last_name: globalThis.sessionData.contact.last_name, 1814 phone_number: globalThis.sessionData.contact.phone_number 1815 }; 1816 } else { 1817 payload.contact = { 1818 email: globalThis.sessionData.contact.email, 1819 first_name: this.checkoutData.contact.first_name, 1820 last_name: this.checkoutData.contact.last_name, 1821 phone_number: this.checkoutData.contact.phone_number 1822 }; 1823 if (Object.keys(this.checkoutData.address).length) { 1824 payload.contact.shipping_addresses_attributes = [ 1825 { 1826 physical_address_attributes: { 1827 address_one: this.checkoutData.address.address_one, 1828 address_two: this.checkoutData.address.address_two, 1829 city: this.checkoutData.address.city, 1830 region_name: this.checkoutData.address.state, 1831 country_id: this.checkoutData.address.country, 1832 postal_code: this.checkoutData.address.postal_code 1833 } 1834 } 1835 ]; 1836 } 1837 } 1838 if (this.shouldIncludeRegistration()) payload["registration"] = this.buildRegistrationDetail
1838s(); 1839 return payload; 1840 } 1841 otoRequiresShippingForm() { 1842 const hasPhysical = hasPhysicalProducts(this.store.checkoutCart.get()); 1843 return hasPhysical && !globalThis.sessionData.contact.shipping_addresses?.[0]; 1844 } 1845 hasPaymentElement() { 1846 return !hasUpgradeDowngradeItems(this.store.checkoutCart.get()) && ["guest", "saved"].includes(this.getState()) || this.paymentDeclined; 1847 } 1848 shouldValidateStripeElements() { 1849 return this.hasPaymentElement() || this.otoRequiresShippingForm(); 1850 } 1851 hasStripeToken() { 1852 return this.hasPaymentElement(); 1853 } 1854 async handleSubmit(shippingOption, isExpress, redirectOverride) { 1855 this.removeSubmitNotification(); 1856 this.lastSubmitFailure = null; 1857 this.submitedOnce = true; 1858 if (!isExpress && !await this.validateStep(this.store.checkout.step.get())) { 1859 return false; 1860 } 1861 if (!isExpress && !checkValidCustomFields(this.component.element)) { 1862 return false; 1863 } 1864 if (!isExpress) { 1865 if (this.shouldValidateStripeElements()) { 1866 const submitError = await this.stripeElements.submit(); 1867 if (submitError.error) { 1868 console.log("[Stripe] submitError =>", submitError); 1869 return false; 1870 } 1871 } 1872 if (this.store.summary.get().state != "ok" /* OK */) return false; 1873 } 1874 this.startLoadingState(); 1875 let confirmationToken; 1876 if (isExpress || this.hasStripeToken()) { 1877 const confirmationTokenResult = await this.stripe.createConfirmationToken({ 1878 elements: this.stripeElements, 1879 params: { 1880 payment_method_data: { 1881 billing_details: { 1882 name: this.checkoutData.contact.first_name + " " + this.checkoutData.contact.last_name, 1883 phone: this.checkoutData.contact.phone_number, 1884 email: this.checkoutData.contact.email 1885 } 1886 } 1887 } 1888 }); 1889 if (confirmationTokenResult.error) { 1890 this.handleError(confirmationTokenResult.error); 1891 return false; 1892 } 1893 confirmationToken = confirmationTokenResult.confirmationToken; 1894 } 1895 const payload = this.buildSubmitPayload(shippingOption, uuidv4(), confirmationToken, redirectOverride); 1896 const response = await postSubmit(payload); 1897 await this.handleServerResponse(response); 1898 return !this.lastSubmitFailure; 1899 } 1900 hasValidAddress(address) { 1901 let addressErrorsVal; 1902 const cart = this.store.checkoutCart.get(); 1903 if (!hasPhysicalProducts(cart) && !this.component.billingEnabled || hasUpgradeDowngradeItems(cart) && !this.hasPaymentElement()) { 1904 addressErrorsVal = null; 1905 } else if (this.getState() == "oto" && !hasPhysicalProducts(cart) && !this.hasPaymentElement()) { 1906 addressErrorsVal = addressErrors(address ?? {}, true, []); 1907 } else { 1908 let requiredFields = ["address_one", "city", "state", "country", "postal_code"]; 1909 const stripeAddressFieldExceptions = globalThis.globalResourceData.stripeAddressFieldExceptions; 1910 if (address?.country && stripeAddressFieldExceptions.postalCodeless.includes(address?.country)) { 1911 requiredFields = requiredFields.filter((field) => field !== "postal_code"); 1912 } 1913 if (address?.country && stripeAddressFieldExceptions.cityless.includes(address?.country)) { 1914 requiredFields = requiredFields.filter((field) => field !== "city"); 1915 } 1916 if (address?.country && stripeAddressFieldExceptions.regionless.includes(address?.country)) { 1917 requiredFields = requiredFields.filter((field) => field !== "state"); 1918 } 1919 addressErrorsVal = addressErrors(address ?? {}, true, requiredFields); 1920 } 1921 return !hasErrors(addressErrorsVal); 1922 } 1923 hasValidDataForSummaryUpdate(payload) { 1924 const { cart, address } = payload; 1925 const productSelectErrors = productErrors(cart, true); 1926 const hasValidAddress = this.hasValidAddress(address); 1927 return !hasErrors(productSelectErrors) && hasValidAddress; 1928 } 1929 // NOTE: for OTO this gets triggered after mounted. 1930 // We can enhance it later to only skip after this mounting change event. 1931 // Firstly we need to understand how to verify that 1932 removeSubmitNotificationStripeElementChange() { 1933 if (this.getState() != "oto" /* OTO */) this.removeSubmitNotification(); 1934 } 1935}; 1936
1937// projects/lib/packages/yggdrasil-blueprints/__generated__/blueprints/stripe-checkout.ts 1938init_runtime(); 1939var StripeCheckout = class extends CF2Component { 1940 constructor(el, runtimeSel) { 1941 super(el, runtimeSel); 1942 __publicField(this, "checkout"); 1943 __publicField(this, "_lazyInitPending"); 1944 } 1945 _doInitialize() { 1946 this.checkout = createInstance(this); 1947 } 1948 _doMount() { 1949 this.checkout.mount(); 1950 } 1951 initialize() { 1952 if (this.lazyLoadCheckout) { 1953 this._lazyInitPending = true; 1954 ensureCheckoutStoreInitialized(); 1955 return; 1956 } 1957 this._doInitialize(); 1958 } 1959 mount() { 1960 if (this._lazyInitPending) { 1961 return; 1962 } 1963 this._doMount(); 1964 } 1965 async _performLazyInit() { 1966 this._lazyInitPending = false; 1967 await ensureStripeScriptLoaded(); 1968 this._doInitialize(); 1969 this._doMount(); 1970 } 1971 getCart() { 1972 return this.checkout.store.checkoutCart.get(); 1973 } 1974 async submitOTO(redirectOverride) { 1975 const ok = await this.checkout.submitCheckout(redirectOverride); 1976 const failure = this.checkout.lastSubmitFailure; 1977 return { ok, actionable: failure ? failure.actionable : true, message: failure && failure.message }; 1978 } 1979}; 1980registerComponent("StripeCheckout", StripeCheckout); 1981window["StripeCheckout"] = StripeCheckout; 1982//# sourceMappingURL=stripe-checkout-4J37BNBN.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.