JezK
Edit File: sc-stripe-payment-element.cjs.entry.js.map
{"file":"sc-stripe-payment-element.entry.cjs.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAM,yBAAyB,GAAG,mQAAmQ,CAAC;AACtS,qCAAe,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8ECgHP,UAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","names":[],"sources":["src/components/ui/stripe-payment-element/sc-stripe-payment-element.scss?tag=sc-stripe-payment-element","src/components/ui/stripe-payment-element/sc-stripe-payment-element.tsx"],"sourcesContent":["sc-stripe-payment-element {\n display: block;\n\n [hidden] {\n display: none;\n }\n}\n\n.loader {\n display: grid;\n height: 128px;\n gap: 2em;\n\n &__row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1em;\n }\n\n &__details {\n display: grid;\n gap: 0.5em;\n }\n}\n","import { Component, Element, Event, EventEmitter, h, Method, State, Watch } from '@stencil/core';\nimport { loadStripe } from '@stripe/stripe-js/pure';\nimport { __ } from '@wordpress/i18n';\nimport { addQueryArgs } from '@wordpress/url';\nimport { state as selectedProcessor } from '@store/selected-processor';\n\nimport { CustomStripeElementChangeEvent, FormStateSetter, PaymentInfoAddedParams } from '../../../types';\nimport { state as checkoutState, onChange } from '@store/checkout';\nimport { onChange as onChangeFormState } from '@store/form';\nimport { state as processorsState } from '@store/processors';\nimport { currentFormState } from '@store/form/getters';\nimport { createErrorNotice } from '@store/notices/mutations';\nimport { updateFormState } from '@store/form/mutations';\nimport { getResolvedBillingAddress, getResolvedBillingEmail, toStripeAddress } from '@store/checkout/getters';\nimport { getProcessorByType } from '@store/processors/getters';\n\n@Component({\n tag: 'sc-stripe-payment-element',\n styleUrl: 'sc-stripe-payment-element.scss',\n shadow: false,\n})\nexport class ScStripePaymentElement {\n /** This element */\n @Element() el: HTMLScStripePaymentElementElement;\n\n /** Holds the element container. */\n private container: HTMLDivElement;\n\n /** holds the stripe element. */\n private element: any;\n\n private unlistenToFormState: () => void;\n private unlistenToCheckout: () => void;\n\n /** The error. */\n @State() error: string;\n\n /** Are we confirming the order? */\n @State() confirming: boolean = false;\n\n /** Are we initializing stripe? */\n @State() isInitializingStripe: boolean = false;\n\n /** Are we creating our updating stripe elements? */\n @State() isCreatingUpdatingStripeElement: boolean = false;\n\n /** Are we loaded? */\n @State() loaded: boolean = false;\n\n /** The order/invoice was paid for. */\n @Event() scPaid: EventEmitter<void>;\n\n /** Set the state */\n @Event() scSetState: EventEmitter<FormStateSetter>;\n\n /** Payment information was added */\n @Event() scPaymentInfoAdded: EventEmitter<PaymentInfoAddedParams>;\n\n @State() styles: CSSStyleDeclaration;\n\n async componentWillLoad() {\n this.fetchStyles();\n this.syncCheckoutMode();\n }\n\n @Watch('styles')\n async handleStylesChange() {\n this.createOrUpdateElements();\n }\n\n async fetchStyles() {\n this.styles = (await this.getComputedStyles()) as CSSStyleDeclaration;\n }\n\n /**\n * We wait for our property value to resolve (styles have been loaded)\n * This prevents the element appearance api being set before the styles are loaded.\n */\n getComputedStyles() {\n return new Promise(resolve => {\n let checkInterval = setInterval(() => {\n const styles = window.getComputedStyle(document.body);\n const color = styles.getPropertyValue('--sc-color-primary-500');\n if (color) {\n clearInterval(checkInterval);\n resolve(styles);\n }\n }, 100);\n });\n }\n\n /** Sync the checkout mode */\n async syncCheckoutMode() {\n onChange('checkout', () => {\n this.initializeStripe();\n });\n }\n\n async componentDidLoad() {\n this.initializeStripe();\n }\n\n async initializeStripe() {\n if (typeof checkoutState?.checkout?.live_mode === 'undefined' || processorsState?.instances?.stripe || this.isInitializingStripe) {\n return;\n }\n this.isInitializingStripe = true;\n const { processor_data } = getProcessorByType('stripe') || {};\n\n try {\n processorsState.instances.stripe = await loadStripe(processor_data?.publishable_key, { stripeAccount: processor_data?.account_id });\n this.error = '';\n } catch (e) {\n this.error = e?.message || __('Stripe could not be loaded', 'surecart');\n this.isInitializingStripe = false;\n // don't continue.\n return;\n }\n\n // create or update elements.\n this.createOrUpdateElements();\n this.handleUpdateElement();\n this.unlistenToCheckout = onChange('checkout', () => {\n this.fetchStyles();\n this.createOrUpdateElements();\n this.handleUpdateElement();\n });\n\n // we need to listen to the form state and pay when the form state enters the paying state.\n this.unlistenToFormState = onChangeFormState('formState', () => {\n if (!checkoutState?.checkout?.payment_method_required) return;\n if ('paying' === currentFormState()) {\n this.maybeConfirmOrder();\n }\n });\n this.isInitializingStripe = false;\n }\n\n clearStripeInstances() {\n this.isInitializingStripe = false;\n this.isCreatingUpdatingStripeElement = false;\n if (this?.element) {\n try {\n this.element?.unmount?.(); // If Stripe provides this method\n } catch (e) {\n console.warn('Could not unmount Stripe element:', e);\n }\n this.element = null;\n }\n if (processorsState?.instances?.stripeElements) {\n processorsState.instances.stripeElements = null;\n }\n if (processorsState?.instances?.stripe) {\n processorsState.instances.stripe = null;\n }\n }\n\n disconnectedCallback() {\n this.unlistenToFormState();\n this.unlistenToCheckout();\n this.clearStripeInstances();\n }\n\n getElementsConfig() {\n const styles = getComputedStyle(this.el);\n return {\n mode: checkoutState.checkout?.remaining_amount_due > 0 ? 'payment' : 'setup',\n amount: checkoutState.checkout?.remaining_amount_due,\n currency: checkoutState.checkout?.currency,\n setupFutureUsage: checkoutState.checkout?.reusable_payment_method_required ? 'off_session' : null,\n appearance: {\n variables: {\n colorPrimary: styles.getPropertyValue('--sc-color-primary-500') || 'black',\n colorText: styles.getPropertyValue('--sc-input-label-color') || 'black',\n borderRadius: styles.getPropertyValue('--sc-input-border-radius-medium') || '4px',\n colorBackground: styles.getPropertyValue('--sc-input-background-color') || 'white',\n fontSizeBase: styles.getPropertyValue('--sc-input-font-size-medium') || '16px',\n colorLogo: styles.getPropertyValue('--sc-stripe-color-logo') || 'light',\n colorLogoTab: styles.getPropertyValue('--sc-stripe-color-logo-tab') || 'light',\n colorLogoTabSelected: styles.getPropertyValue('--sc-stripe-color-logo-tab-selected') || 'light',\n colorTextPlaceholder: styles.getPropertyValue('--sc-input-placeholder-color') || 'black',\n },\n rules: {\n '.Input': {\n border: styles.getPropertyValue('--sc-input-border'),\n },\n },\n },\n };\n }\n\n maybeApplyFilters(options: any): any {\n if (!window?.wp?.hooks?.applyFilters) return options;\n\n return {\n ...options,\n paymentMethodOrder: window.wp.hooks.applyFilters('surecart_stripe_payment_element_payment_method_order', [], checkoutState.checkout),\n wallets: window.wp.hooks.applyFilters('surecart_stripe_payment_element_wallets', {}, checkoutState.checkout),\n terms: window.wp.hooks.applyFilters('surecart_stripe_payment_element_terms', {}, checkoutState.checkout),\n fields: window.wp.hooks.applyFilters('surecart_stripe_payment_element_fields', options.fields ?? {}),\n };\n }\n\n /** Update the payment element mode, amount and currency when it changes. */\n createOrUpdateElements() {\n // need an order amount, etc.\n if (!checkoutState?.checkout?.payment_method_required) return;\n if (!processorsState.instances.stripe || this.isCreatingUpdatingStripeElement) return;\n if (checkoutState.checkout?.status && ['paid', 'processing'].includes(checkoutState.checkout?.status)) return;\n\n this.isCreatingUpdatingStripeElement = true;\n\n // create the elements if they have not yet been created.\n if (!processorsState.instances.stripeElements) {\n // we have what we need, load elements.\n processorsState.instances.stripeElements = processorsState.instances.stripe.elements(this.getElementsConfig() as any);\n const address = toStripeAddress(getResolvedBillingAddress());\n\n const options = this.maybeApplyFilters({\n defaultValues: {\n billingDetails: {\n ...(checkoutState.checkout?.name ? { name: checkoutState.checkout.name } : {}),\n ...(getResolvedBillingEmail() ? { email: getResolvedBillingEmail() } : {}),\n ...(checkoutState.checkout?.phone ? { phone: checkoutState.checkout.phone } : {}),\n ...(address ? { address } : {}),\n },\n },\n fields: {\n billingDetails: {\n email: 'never',\n },\n },\n } as any);\n\n // create the payment element.\n (processorsState.instances.stripeElements as any).create('payment', options).mount(this.container);\n\n this.element = processorsState.instances.stripeElements.getElement('payment');\n this.element.on('ready', () => (this.loaded = true));\n this.element.on('change', (event: CustomStripeElementChangeEvent) => {\n const requiredShippingPaymentTypes = ['cashapp', 'klarna', 'clearpay'];\n checkoutState.paymentMethodRequiresShipping = requiredShippingPaymentTypes.includes(event?.value?.type);\n\n if (event.complete) {\n this.scPaymentInfoAdded.emit({\n checkout_id: checkoutState.checkout?.id,\n currency: checkoutState.checkout?.currency,\n processor_type: 'stripe',\n total_amount: checkoutState.checkout?.total_amount,\n line_items: checkoutState.checkout?.line_items,\n payment_method: {\n billing_details: {\n email: checkoutState.checkout?.email,\n name: checkoutState.checkout?.name,\n },\n },\n });\n }\n });\n this.isCreatingUpdatingStripeElement = false;\n return;\n }\n processorsState.instances.stripeElements.update(this.getElementsConfig());\n this.isCreatingUpdatingStripeElement = false;\n }\n\n /** Update the default attributes of the element when they cahnge. */\n handleUpdateElement() {\n if (!this.element) return;\n if (checkoutState.checkout?.status !== 'draft') return;\n\n const address = toStripeAddress(getResolvedBillingAddress());\n\n const options = this.maybeApplyFilters({\n defaultValues: {\n billingDetails: {\n ...(checkoutState.checkout?.name ? { name: checkoutState.checkout.name } : {}),\n ...(getResolvedBillingEmail() ? { email: getResolvedBillingEmail() } : {}),\n ...(checkoutState.checkout?.phone ? { phone: checkoutState.checkout.phone } : {}),\n ...(address ? { address } : {}),\n },\n },\n fields: {\n billingDetails: {\n email: 'never',\n },\n },\n } as any);\n\n this.element.update(options);\n }\n\n async submit() {\n // this processor is not selected.\n if (selectedProcessor?.id !== 'stripe') return;\n // submit the elements.\n const { error } = await (processorsState.instances.stripeElements as any).submit();\n if (error) {\n console.error({ error });\n updateFormState('REJECT');\n createErrorNotice(error);\n this.error = error.message;\n return;\n }\n }\n\n /**\n * Watch order status and maybe confirm the order.\n */\n async maybeConfirmOrder() {\n // this processor is not selected.\n if (selectedProcessor?.id !== 'stripe') return;\n // must be a stripe session\n if (checkoutState.checkout?.payment_intent?.processor_type !== 'stripe') return;\n // need an external_type\n if (!checkoutState.checkout?.payment_intent?.processor_data?.stripe?.type) return;\n // we need a client secret.\n if (!checkoutState.checkout?.payment_intent?.processor_data?.stripe?.client_secret) return;\n // confirm the intent.\n return await this.confirm(checkoutState.checkout?.payment_intent?.processor_data?.stripe?.type);\n }\n\n @Method()\n async confirm(type: 'setup' | 'payment', args = {}) {\n const address = toStripeAddress(getResolvedBillingAddress());\n const confirmArgs = {\n elements: processorsState.instances.stripeElements,\n clientSecret: checkoutState.checkout?.payment_intent?.processor_data?.stripe?.client_secret,\n confirmParams: {\n return_url: addQueryArgs(window.location.href, {\n ...(checkoutState.checkout.id ? { checkout_id: checkoutState.checkout.id } : {}),\n }),\n payment_method_data: {\n billing_details: {\n ...(getResolvedBillingEmail() ? { email: getResolvedBillingEmail() } : {}),\n ...(checkoutState.checkout?.name ? { name: checkoutState.checkout.name } : {}),\n ...(checkoutState.checkout?.phone ? { phone: checkoutState.checkout.phone } : {}),\n ...(address ? { address } : {}),\n },\n },\n },\n redirect: 'if_required',\n ...args,\n };\n\n // prevent possible double-charges\n if (this.confirming) return;\n\n // stripe must be loaded.\n if (!processorsState.instances.stripe) return;\n\n try {\n this.scSetState.emit('PAYING');\n const response =\n type === 'setup' ? await processorsState.instances.stripe.confirmSetup(confirmArgs as any) : await processorsState.instances.stripe.confirmPayment(confirmArgs as any);\n if (response?.error) {\n this.error = response.error.message;\n throw response.error;\n } else {\n this.scSetState.emit('PAID');\n // paid\n this.scPaid.emit();\n }\n } catch (e) {\n console.error(e);\n updateFormState('REJECT');\n createErrorNotice(e);\n if (e.message) {\n this.error = e.message;\n }\n } finally {\n this.confirming = false;\n }\n }\n\n render() {\n return (\n <div class=\"sc-stripe-payment-element\" data-testid=\"stripe-payment-element\">\n {!!this.error && (\n <sc-text\n style={{\n 'color': 'var(--sc-color-danger-500)',\n '--font-size': 'var(--sc-font-size-small)',\n 'marginBottom': '0.5em',\n }}\n >\n {this.error}\n </sc-text>\n )}\n\n <div class=\"loader\" hidden={this.loaded}>\n <div class=\"loader__row\">\n <div style={{ width: '50%' }}>\n <sc-skeleton style={{ width: '50%', marginBottom: '0.5em' }}></sc-skeleton>\n <sc-skeleton></sc-skeleton>\n </div>\n <div style={{ flex: '1' }}>\n <sc-skeleton style={{ width: '50%', marginBottom: '0.5em' }}></sc-skeleton>\n <sc-skeleton></sc-skeleton>\n </div>\n <div style={{ flex: '1' }}>\n <sc-skeleton style={{ width: '50%', marginBottom: '0.5em' }}></sc-skeleton>\n <sc-skeleton></sc-skeleton>\n </div>\n </div>\n <div class=\"loader__details\">\n <sc-skeleton style={{ height: '1rem' }}></sc-skeleton>\n <sc-skeleton style={{ height: '1rem', width: '30%' }}></sc-skeleton>\n </div>\n </div>\n\n <div hidden={!this.loaded} class=\"sc-payment-element-container\" ref={el => (this.container = el as HTMLDivElement)}></div>\n </div>\n );\n }\n}\n"],"version":3}