{"version":3,"sources":["libs/galaxy/frequency/src/interface.ts","libs/galaxy/frequency/src/frequency.pipe.ts","libs/galaxy/frequency/src/frequency.component.ts","libs/galaxy/frequency/src/frequency.component.html","libs/galaxy/frequency/src/frequency.module.ts","libs/country-state/src/lib/interfaces.ts","libs/country-state/src/lib/country-state.service.ts","libs/country-state/src/lib/country-state.module.ts","node_modules/ng-recaptcha/fesm2020/ng-recaptcha.mjs","libs/billing-ui/src/lib/billing-erp-url.service.ts","libs/billing-ui/src/lib/charge-invoice-dialog/charge-invoice-dialog.component.ts","libs/billing-ui/src/lib/charge-invoice-dialog/charge-invoice-dialog.component.html","libs/billing-ui/src/lib/contact-information/subsidiary-warning-dialog/subsidiary-warning-dialog.component.ts","libs/billing-ui/src/lib/contact-information/subsidiary-warning-dialog/subsidiary-warning-dialog.html","libs/billing-ui/src/lib/contact-information/edit-contact-info-dialog.component.ts","libs/billing-ui/src/lib/contact-information/edit-contact-info-dialog.component.html","libs/billing-ui/src/lib/discount/add-discount-modal/retail-discount-dialog.component.ts","libs/billing-ui/src/lib/discount/add-discount-modal/retail-discount-dialog.component.html","libs/billing-ui/src/lib/i18n/assets/en_devel.json","libs/billing-ui/src/lib/i18n/billing-ui-i18n.module.ts","libs/billing-ui/src/lib/stripe-url.service.ts","libs/billing-ui/src/lib/shared/datasource-helpers.ts","libs/billing-ui/src/lib/shared/price-column-style.directive.ts","libs/billing-ui/src/lib/stripe.service.ts","libs/billing-ui/src/lib/payment-method/edit-payment-method-dialog.component.ts","libs/billing-ui/src/lib/payment-method/edit-payment-method-dialog.component.html","libs/billing-ui/src/lib/utils/frequency.ts","libs/billing-ui/src/lib/utils/price-display.ts","libs/billing-ui/src/lib/simple-price-display/simple-price-display.component.ts","libs/billing-ui/src/lib/simple-price-display/simple-price-display.component.html","libs/billing-ui/src/lib/price-display/price-display.component.ts","libs/billing-ui/src/lib/price-display/price-display.component.html","libs/billing-ui/src/lib/purchase/dunning-dialog/dunning-dialog.component.ts","libs/billing-ui/src/lib/purchase/dunning-dialog/dunning-dialog.component.html","libs/billing-ui/src/lib/purchase/purchase-item-list/purchase-item-list.component.ts","libs/billing-ui/src/lib/purchase/purchase-item-list/purchase-item-list.component.html","libs/billing-ui/src/lib/shared/pipes.ts","libs/billing-ui/src/lib/purchase/purchase-list/can-void-purchase.pipe.ts","libs/billing-ui/src/lib/shared/animations.ts","libs/billing-ui/src/lib/shared/to-string-converstions.ts","libs/billing-ui/src/lib/purchase/purchase-item-row.service.ts","libs/billing-ui/src/lib/purchase/purchase-list.datasource.ts","libs/billing-ui/src/lib/purchase/retry-purchase-dialog/retry-purchase-dialog.component.ts","libs/billing-ui/src/lib/purchase/retry-purchase-dialog/retry-purchase-dialog.component.html","libs/billing-ui/src/lib/purchase/void-purchase-dialog/void-purchase-dialog.component.ts","libs/billing-ui/src/lib/purchase/void-purchase-dialog/void-purchase-dialog.component.html","libs/billing-ui/src/lib/shared/cdk-detail-row.directive.ts","libs/billing-ui/src/lib/purchase/purchase-list/purchase-list.component.ts","libs/billing-ui/src/lib/purchase/purchase-list/purchase-list.component.html","libs/billing-ui/src/lib/shared/file.ts","libs/billing-ui/src/lib/sales-credit-note-list/sales-credit-note-list-datasource.ts","libs/billing-ui/src/lib/sales-credit-note-list/sales-credit-note-list.component.ts","libs/billing-ui/src/lib/sales-credit-note-list/sales-credit-note-list.component.html","libs/billing-ui/src/lib/sales-invoice-list/sales-invoice-list-datasource.ts","libs/billing-ui/src/lib/sales-invoice-list/sales-invoice-list.component.ts","libs/billing-ui/src/lib/sales-invoice-list/sales-invoice-list.component.html","libs/billing-ui/src/lib/simple-price-display/discount-display/format-date.pipe.ts","libs/billing-ui/src/lib/simple-price-display/discount-display/discount-display.component.ts","libs/billing-ui/src/lib/simple-price-display/discount-display/discount-display.component.html","libs/billing-ui/src/lib/utils/pipes/currency-without-usd.pipe.ts","libs/billing-ui/src/lib/simple-price-display/stairstep-price-display/stairstep-dialog/stairstep-dialog.component.ts","libs/billing-ui/src/lib/simple-price-display/stairstep-price-display/stairstep-dialog/stairstep-dialog.component.html","libs/billing-ui/src/lib/utils/pipes/price-in-dollars.pipe.ts","libs/billing-ui/src/lib/utils/pipes/resolve-currency-code.pipe.ts","libs/billing-ui/src/lib/simple-price-display/stairstep-price-display/stairstep-price-display.component.ts","libs/billing-ui/src/lib/simple-price-display/stairstep-price-display/stairstep-price-display.component.html","libs/billing-ui/src/lib/subscription-list/change-frequency-dialog/change-frequency.component.ts","libs/billing-ui/src/lib/subscription-list/change-frequency-dialog/change-frequency.component.html","libs/billing-ui/src/lib/subscription-list/change-price-dialog/change-price-dialog.component.ts","libs/billing-ui/src/lib/subscription-list/change-price-dialog/change-price-dialog.component.html","libs/billing-ui/src/lib/subscription-list/change-renewal-dialog copy/change-renewal.component.ts","libs/billing-ui/src/lib/subscription-list/change-renewal-dialog copy/change-renewal.component.html","libs/billing-ui/src/lib/subscription-list/subscription-list.data-source.ts","libs/billing-ui/src/lib/utils/pipes/frequency.pipe.ts","libs/billing-ui/src/lib/subscription-list/subscription-list.component.ts","libs/billing-ui/src/lib/subscription-list/subscription-list.component.html","libs/billing-ui/src/lib/utils/strategy.ts","libs/billing-ui/src/lib/utils/pipes/billing-strategy.pipe.ts","libs/billing-ui/src/lib/utils/pipes/tax-description.pipe.ts","libs/billing-ui/src/lib/variable-price-input/variable-price-input.component.ts","libs/billing-ui/src/lib/variable-price-input/variable-price-input.component.html","libs/billing-ui/src/lib/billing-ui.module.ts"],"sourcesContent":["export enum Frequency {\n ONE_TIME = 'One Time',\n YEARLY = 'Yearly',\n MONTHLY = 'Monthly',\n WEEKLY = 'Weekly',\n DAILY = 'Daily',\n QUARTERLY = 'Quarterly',\n BI_WEEKLY = 'Bi-Weekly',\n}\n\nexport enum Form {\n SHORT = 'short',\n LONG = 'long',\n VERBOSE = 'verbose',\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { Frequency } from './interface';\n\nconst BaseTranslationKey = 'GALAXY.FREQUENCY.';\n\nconst ShortFrequencyTranslationKey = BaseTranslationKey + 'SHORT.';\nconst LongFrequencyTranslationKey = BaseTranslationKey + 'LONG.';\nconst VerboseFrequencyTranslationKey = BaseTranslationKey + 'VERBOSE.';\n\nconst ShortFreqString: Record = {\n [Frequency.MONTHLY]: ShortFrequencyTranslationKey + 'MONTHLY',\n [Frequency.YEARLY]: ShortFrequencyTranslationKey + 'YEARLY',\n [Frequency.WEEKLY]: ShortFrequencyTranslationKey + 'WEEKLY',\n [Frequency.DAILY]: ShortFrequencyTranslationKey + 'DAILY',\n [Frequency.QUARTERLY]: ShortFrequencyTranslationKey + 'QUARTERLY',\n [Frequency.BI_WEEKLY]: ShortFrequencyTranslationKey + 'BI_WEEKLY',\n};\n\nconst LongFreqString: Record = {\n [Frequency.MONTHLY]: LongFrequencyTranslationKey + 'MONTHLY',\n [Frequency.YEARLY]: LongFrequencyTranslationKey + 'YEARLY',\n [Frequency.WEEKLY]: LongFrequencyTranslationKey + 'WEEKLY',\n [Frequency.DAILY]: LongFrequencyTranslationKey + 'DAILY',\n [Frequency.QUARTERLY]: LongFrequencyTranslationKey + 'QUARTERLY',\n [Frequency.BI_WEEKLY]: LongFrequencyTranslationKey + 'BI_WEEKLY',\n};\n\nconst VerboseFreqString: Record = {\n [Frequency.MONTHLY]: VerboseFrequencyTranslationKey + 'MONTHLY',\n [Frequency.YEARLY]: VerboseFrequencyTranslationKey + 'YEARLY',\n [Frequency.WEEKLY]: VerboseFrequencyTranslationKey + 'WEEKLY',\n [Frequency.DAILY]: VerboseFrequencyTranslationKey + 'DAILY',\n [Frequency.QUARTERLY]: VerboseFrequencyTranslationKey + 'QUARTERLY',\n [Frequency.BI_WEEKLY]: VerboseFrequencyTranslationKey + 'BI_WEEKLY',\n};\n\n@Pipe({ name: 'glxyFrequency' })\nexport class GalaxyFrequencyPipe implements PipeTransform {\n transform(frequency: Frequency = Frequency.ONE_TIME, form: 'short' | 'long' | 'verbose' = 'short'): string {\n let freq = '';\n if (form.toLowerCase() === 'long') {\n freq = this.getLongFrequency(frequency);\n } else if (form.toLowerCase() === 'verbose') {\n freq = this.getVerboseFrequency(frequency);\n } else {\n freq = this.getShortFrequency(frequency);\n }\n\n if (freq !== '') {\n return `${freq}`;\n }\n return '';\n }\n\n getShortFrequency(frequency: Frequency): string {\n return ShortFreqString[frequency] || '';\n }\n\n getLongFrequency(frequency: Frequency): string {\n return LongFreqString[frequency] || '';\n }\n getVerboseFrequency(frequency: Frequency): string {\n return VerboseFreqString[frequency] || '';\n }\n}\n","import { Component, HostBinding, Input } from '@angular/core';\nimport { Form, Frequency } from './interface';\n\n@Component({\n selector: 'glxy-frequency',\n templateUrl: './frequency.component.html',\n styleUrls: ['./frequency.component.scss'],\n})\nexport class GalaxyFrequencyComponent {\n @HostBinding('class') class = 'glxy-frequency';\n @Input({ required: true }) value = '';\n /**\n * If you want the currency code to match the text sizing of the frequency, provide it separately from the value.\n */\n @Input() currencyCode?: string;\n @Input() frequency: Frequency = Frequency.ONE_TIME;\n @Input() form: Form = Form.SHORT;\n @Input() size: 'regular' | 'large' = 'regular';\n @Input() align: 'end' | 'start' = 'end';\n @Input() allowCustomStyling = false;\n Frequency = Frequency;\n Form = Form;\n}\n","
\n \n {{ value }}\n
\n
\n  {{ currencyCode }}\n  \n {{ frequency | glxyFrequency : form | translate }}\n
\n\n","import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { GalaxyI18NModule } from '@vendasta/galaxy/i18n';\n\nimport { GalaxyFrequencyComponent } from './frequency.component';\nexport { GalaxyFrequencyComponent } from './frequency.component';\nimport { GalaxyFrequencyPipe } from './frequency.pipe';\nexport { GalaxyFrequencyPipe } from './frequency.pipe';\n\nexport const MODULE_IMPORTS = [CommonModule, GalaxyI18NModule, TranslateModule];\n\nexport const MODULE_DECLARATIONS = [GalaxyFrequencyComponent, GalaxyFrequencyPipe];\n\n@NgModule({\n declarations: MODULE_DECLARATIONS,\n exports: MODULE_DECLARATIONS,\n imports: MODULE_IMPORTS,\n})\nexport class GalaxyFrequencyModule {}\n","import { InjectionToken } from '@angular/core';\nimport { Observable } from 'rxjs';\n\nexport const CountryStateServiceInterfaceToken: InjectionToken =\n new InjectionToken('CountryStateServiceInterface');\n\nexport interface Country {\n code: string; // ISO 3166 alpha-2 code\n name: string;\n}\n\nexport interface State {\n code: string;\n name: string;\n}\n\nexport interface CountryStateServiceInterface {\n getCountriesOptions(): Observable;\n getStatesOptions(countryCode: string): Observable;\n}\n","import { Inject, Injectable } from '@angular/core';\nimport { CountryStateServiceInterface, CountryStateServiceInterfaceToken, Country, State } from './interfaces';\nimport { Observable } from 'rxjs';\n\nexport const COUNTRIES_WITH_OPTIONAL_STATES: string[] = ['GB', 'FR', 'DE', 'MT', 'ZA', 'BS', 'BM', 'HK', 'PR'];\n\n@Injectable()\nexport class CountryStateService implements CountryStateServiceInterface {\n constructor(@Inject(CountryStateServiceInterfaceToken) private innerService: CountryStateServiceInterface) {}\n getCountriesOptions(): Observable {\n return this.innerService.getCountriesOptions();\n }\n\n getStatesOptions(countryCode: string): Observable {\n return this.innerService.getStatesOptions(countryCode);\n }\n}\n","import { NgModule } from '@angular/core';\n\nimport { CountryStateService } from './country-state.service';\n\n@NgModule({\n imports: [],\n providers: [CountryStateService],\n declarations: [],\n exports: [],\n})\nexport class CountryStateServiceModule {}\n","import * as i0 from '@angular/core';\nimport { InjectionToken, PLATFORM_ID, Injectable, Inject, Optional, EventEmitter, Component, Input, HostBinding, Output, NgModule, forwardRef, Directive, HostListener } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { of, BehaviorSubject, Subject } from 'rxjs';\nimport { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';\nconst RECAPTCHA_LANGUAGE = new InjectionToken(\"recaptcha-language\");\nconst RECAPTCHA_BASE_URL = new InjectionToken(\"recaptcha-base-url\");\nconst RECAPTCHA_NONCE = new InjectionToken(\"recaptcha-nonce-tag\");\nconst RECAPTCHA_SETTINGS = new InjectionToken(\"recaptcha-settings\");\nconst RECAPTCHA_V3_SITE_KEY = new InjectionToken(\"recaptcha-v3-site-key\");\nfunction loadScript(renderMode, onLoaded, urlParams, url, nonce) {\n window.ng2recaptchaloaded = () => {\n onLoaded(grecaptcha);\n };\n const script = document.createElement(\"script\");\n script.innerHTML = \"\";\n const baseUrl = url || \"https://www.google.com/recaptcha/api.js\";\n script.src = `${baseUrl}?render=${renderMode}&onload=ng2recaptchaloaded${urlParams}`;\n if (nonce) {\n script.nonce = nonce;\n }\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n}\nconst loader = {\n loadScript\n};\nlet RecaptchaLoaderService = /*#__PURE__*/(() => {\n class RecaptchaLoaderService {\n constructor(\n // eslint-disable-next-line @typescript-eslint/ban-types\n platformId, language, baseUrl, nonce, v3SiteKey) {\n this.platformId = platformId;\n this.language = language;\n this.baseUrl = baseUrl;\n this.nonce = nonce;\n this.v3SiteKey = v3SiteKey;\n this.init();\n this.ready = isPlatformBrowser(this.platformId) ? RecaptchaLoaderService.ready.asObservable() : of();\n }\n /** @internal */\n init() {\n if (RecaptchaLoaderService.ready) {\n return;\n }\n if (isPlatformBrowser(this.platformId)) {\n const subject = new BehaviorSubject(null);\n RecaptchaLoaderService.ready = subject;\n const langParam = this.language ? \"&hl=\" + this.language : \"\";\n const renderMode = this.v3SiteKey || \"explicit\";\n loader.loadScript(renderMode, grecaptcha => subject.next(grecaptcha), langParam, this.baseUrl, this.nonce);\n }\n }\n }\n /**\n * @internal\n * @nocollapse\n */\n RecaptchaLoaderService.ready = null;\n RecaptchaLoaderService.ɵfac = function RecaptchaLoaderService_Factory(t) {\n return new (t || RecaptchaLoaderService)(i0.ɵɵinject(PLATFORM_ID), i0.ɵɵinject(RECAPTCHA_LANGUAGE, 8), i0.ɵɵinject(RECAPTCHA_BASE_URL, 8), i0.ɵɵinject(RECAPTCHA_NONCE, 8), i0.ɵɵinject(RECAPTCHA_V3_SITE_KEY, 8));\n };\n RecaptchaLoaderService.ɵprov = /* @__PURE__ */i0.ɵɵdefineInjectable({\n token: RecaptchaLoaderService,\n factory: RecaptchaLoaderService.ɵfac\n });\n return RecaptchaLoaderService;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet nextId = 0;\nlet RecaptchaComponent = /*#__PURE__*/(() => {\n class RecaptchaComponent {\n constructor(elementRef, loader, zone, settings) {\n this.elementRef = elementRef;\n this.loader = loader;\n this.zone = zone;\n this.id = `ngrecaptcha-${nextId++}`;\n this.errorMode = \"default\";\n this.resolved = new EventEmitter();\n // The rename will happen a bit later\n // eslint-disable-next-line @angular-eslint/no-output-native\n this.error = new EventEmitter();\n if (settings) {\n this.siteKey = settings.siteKey;\n this.theme = settings.theme;\n this.type = settings.type;\n this.size = settings.size;\n this.badge = settings.badge;\n }\n }\n ngAfterViewInit() {\n this.subscription = this.loader.ready.subscribe(grecaptcha => {\n if (grecaptcha != null && grecaptcha.render instanceof Function) {\n this.grecaptcha = grecaptcha;\n this.renderRecaptcha();\n }\n });\n }\n ngOnDestroy() {\n // reset the captcha to ensure it does not leave anything behind\n // after the component is no longer needed\n this.grecaptchaReset();\n if (this.subscription) {\n this.subscription.unsubscribe();\n }\n }\n /**\n * Executes the invisible recaptcha.\n * Does nothing if component's size is not set to \"invisible\".\n */\n execute() {\n if (this.size !== \"invisible\") {\n return;\n }\n if (this.widget != null) {\n this.grecaptcha.execute(this.widget);\n } else {\n // delay execution of recaptcha until it actually renders\n this.executeRequested = true;\n }\n }\n reset() {\n if (this.widget != null) {\n if (this.grecaptcha.getResponse(this.widget)) {\n // Only emit an event in case if something would actually change.\n // That way we do not trigger \"touching\" of the control if someone does a \"reset\"\n // on a non-resolved captcha.\n this.resolved.emit(null);\n }\n this.grecaptchaReset();\n }\n }\n /**\n * ⚠️ Warning! Use this property at your own risk!\n *\n * While this member is `public`, it is not a part of the component's public API.\n * The semantic versioning guarantees _will not be honored_! Thus, you might find that this property behavior changes in incompatible ways in minor or even patch releases.\n * You are **strongly advised** against using this property.\n * Instead, use more idiomatic ways to get reCAPTCHA value, such as `resolved` EventEmitter, or form-bound methods (ngModel, formControl, and the likes).å\n */\n get __unsafe_widgetValue() {\n return this.widget != null ? this.grecaptcha.getResponse(this.widget) : null;\n }\n /** @internal */\n expired() {\n this.resolved.emit(null);\n }\n /** @internal */\n errored(args) {\n this.error.emit(args);\n }\n /** @internal */\n captchaResponseCallback(response) {\n this.resolved.emit(response);\n }\n /** @internal */\n grecaptchaReset() {\n if (this.widget != null) {\n this.zone.runOutsideAngular(() => this.grecaptcha.reset(this.widget));\n }\n }\n /** @internal */\n renderRecaptcha() {\n // This `any` can be removed after @types/grecaptcha get updated\n const renderOptions = {\n badge: this.badge,\n callback: response => {\n this.zone.run(() => this.captchaResponseCallback(response));\n },\n \"expired-callback\": () => {\n this.zone.run(() => this.expired());\n },\n sitekey: this.siteKey,\n size: this.size,\n tabindex: this.tabIndex,\n theme: this.theme,\n type: this.type\n };\n if (this.errorMode === \"handled\") {\n renderOptions[\"error-callback\"] = (...args) => {\n this.zone.run(() => this.errored(args));\n };\n }\n this.widget = this.grecaptcha.render(this.elementRef.nativeElement, renderOptions);\n if (this.executeRequested === true) {\n this.executeRequested = false;\n this.execute();\n }\n }\n }\n RecaptchaComponent.ɵfac = function RecaptchaComponent_Factory(t) {\n return new (t || RecaptchaComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(RecaptchaLoaderService), i0.ɵɵdirectiveInject(i0.NgZone), i0.ɵɵdirectiveInject(RECAPTCHA_SETTINGS, 8));\n };\n RecaptchaComponent.ɵcmp = /* @__PURE__ */i0.ɵɵdefineComponent({\n type: RecaptchaComponent,\n selectors: [[\"re-captcha\"]],\n hostVars: 1,\n hostBindings: function RecaptchaComponent_HostBindings(rf, ctx) {\n if (rf & 2) {\n i0.ɵɵattribute(\"id\", ctx.id);\n }\n },\n inputs: {\n id: \"id\",\n siteKey: \"siteKey\",\n theme: \"theme\",\n type: \"type\",\n size: \"size\",\n tabIndex: \"tabIndex\",\n badge: \"badge\",\n errorMode: \"errorMode\"\n },\n outputs: {\n resolved: \"resolved\",\n error: \"error\"\n },\n exportAs: [\"reCaptcha\"],\n decls: 0,\n vars: 0,\n template: function RecaptchaComponent_Template(rf, ctx) {},\n encapsulation: 2\n });\n return RecaptchaComponent;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet RecaptchaCommonModule = /*#__PURE__*/(() => {\n class RecaptchaCommonModule {}\n RecaptchaCommonModule.ɵfac = function RecaptchaCommonModule_Factory(t) {\n return new (t || RecaptchaCommonModule)();\n };\n RecaptchaCommonModule.ɵmod = /* @__PURE__ */i0.ɵɵdefineNgModule({\n type: RecaptchaCommonModule\n });\n RecaptchaCommonModule.ɵinj = /* @__PURE__ */i0.ɵɵdefineInjector({});\n return RecaptchaCommonModule;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet RecaptchaModule = /*#__PURE__*/(() => {\n class RecaptchaModule {}\n RecaptchaModule.ɵfac = function RecaptchaModule_Factory(t) {\n return new (t || RecaptchaModule)();\n };\n RecaptchaModule.ɵmod = /* @__PURE__ */i0.ɵɵdefineNgModule({\n type: RecaptchaModule\n });\n RecaptchaModule.ɵinj = /* @__PURE__ */i0.ɵɵdefineInjector({\n providers: [RecaptchaLoaderService],\n imports: [RecaptchaCommonModule]\n });\n return RecaptchaModule;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/**\n * The main service for working with reCAPTCHA v3 APIs.\n *\n * Use the `execute` method for executing a single action, and\n * `onExecute` observable for listening to all actions at once.\n */\nlet ReCaptchaV3Service = /*#__PURE__*/(() => {\n class ReCaptchaV3Service {\n constructor(zone, siteKey,\n // eslint-disable-next-line @typescript-eslint/ban-types\n platformId, baseUrl, nonce, language) {\n /** @internal */\n this.onLoadComplete = grecaptcha => {\n this.grecaptcha = grecaptcha;\n if (this.actionBacklog && this.actionBacklog.length > 0) {\n this.actionBacklog.forEach(([action, subject]) => this.executeActionWithSubject(action, subject));\n this.actionBacklog = undefined;\n }\n };\n this.zone = zone;\n this.isBrowser = isPlatformBrowser(platformId);\n this.siteKey = siteKey;\n this.nonce = nonce;\n this.language = language;\n this.baseUrl = baseUrl;\n this.init();\n }\n get onExecute() {\n if (!this.onExecuteSubject) {\n this.onExecuteSubject = new Subject();\n this.onExecuteObservable = this.onExecuteSubject.asObservable();\n }\n return this.onExecuteObservable;\n }\n get onExecuteError() {\n if (!this.onExecuteErrorSubject) {\n this.onExecuteErrorSubject = new Subject();\n this.onExecuteErrorObservable = this.onExecuteErrorSubject.asObservable();\n }\n return this.onExecuteErrorObservable;\n }\n /**\n * Executes the provided `action` with reCAPTCHA v3 API.\n * Use the emitted token value for verification purposes on the backend.\n *\n * For more information about reCAPTCHA v3 actions and tokens refer to the official documentation at\n * https://developers.google.com/recaptcha/docs/v3.\n *\n * @param {string} action the action to execute\n * @returns {Observable} an `Observable` that will emit the reCAPTCHA v3 string `token` value whenever ready.\n * The returned `Observable` completes immediately after emitting a value.\n */\n execute(action) {\n const subject = new Subject();\n if (this.isBrowser) {\n if (!this.grecaptcha) {\n if (!this.actionBacklog) {\n this.actionBacklog = [];\n }\n this.actionBacklog.push([action, subject]);\n } else {\n this.executeActionWithSubject(action, subject);\n }\n }\n return subject.asObservable();\n }\n /** @internal */\n executeActionWithSubject(action, subject) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const onError = error => {\n this.zone.run(() => {\n subject.error(error);\n if (this.onExecuteErrorSubject) {\n // We don't know any better at this point, unfortunately, so have to resort to `any`\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n this.onExecuteErrorSubject.next({\n action,\n error\n });\n }\n });\n };\n this.zone.runOutsideAngular(() => {\n try {\n this.grecaptcha.execute(this.siteKey, {\n action\n }).then(token => {\n this.zone.run(() => {\n subject.next(token);\n subject.complete();\n if (this.onExecuteSubject) {\n this.onExecuteSubject.next({\n action,\n token\n });\n }\n });\n }, onError);\n } catch (e) {\n onError(e);\n }\n });\n }\n /** @internal */\n init() {\n if (this.isBrowser) {\n if (\"grecaptcha\" in window) {\n this.grecaptcha = grecaptcha;\n } else {\n const langParam = this.language ? \"&hl=\" + this.language : \"\";\n loader.loadScript(this.siteKey, this.onLoadComplete, langParam, this.baseUrl, this.nonce);\n }\n }\n }\n }\n ReCaptchaV3Service.ɵfac = function ReCaptchaV3Service_Factory(t) {\n return new (t || ReCaptchaV3Service)(i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(RECAPTCHA_V3_SITE_KEY), i0.ɵɵinject(PLATFORM_ID), i0.ɵɵinject(RECAPTCHA_BASE_URL, 8), i0.ɵɵinject(RECAPTCHA_NONCE, 8), i0.ɵɵinject(RECAPTCHA_LANGUAGE, 8));\n };\n ReCaptchaV3Service.ɵprov = /* @__PURE__ */i0.ɵɵdefineInjectable({\n token: ReCaptchaV3Service,\n factory: ReCaptchaV3Service.ɵfac\n });\n return ReCaptchaV3Service;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet RecaptchaV3Module = /*#__PURE__*/(() => {\n class RecaptchaV3Module {}\n RecaptchaV3Module.ɵfac = function RecaptchaV3Module_Factory(t) {\n return new (t || RecaptchaV3Module)();\n };\n RecaptchaV3Module.ɵmod = /* @__PURE__ */i0.ɵɵdefineNgModule({\n type: RecaptchaV3Module\n });\n RecaptchaV3Module.ɵinj = /* @__PURE__ */i0.ɵɵdefineInjector({\n providers: [ReCaptchaV3Service]\n });\n return RecaptchaV3Module;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet RecaptchaValueAccessorDirective = /*#__PURE__*/(() => {\n class RecaptchaValueAccessorDirective {\n constructor(host) {\n this.host = host;\n this.requiresControllerReset = false;\n }\n writeValue(value) {\n if (!value) {\n this.host.reset();\n } else {\n // In this case, it is most likely that a form controller has requested to write a specific value into the component.\n // This isn't really a supported case - reCAPTCHA values are single-use, and, in a sense, readonly.\n // What this means is that the form controller has recaptcha control state of X, while reCAPTCHA itself can't \"restore\"\n // to that state. In order to make form controller aware of this discrepancy, and to fix the said misalignment,\n // we'll be telling the controller to \"reset\" the value back to null.\n if (this.host.__unsafe_widgetValue !== value && Boolean(this.host.__unsafe_widgetValue) === false) {\n this.requiresControllerReset = true;\n }\n }\n }\n registerOnChange(fn) {\n this.onChange = fn;\n if (this.requiresControllerReset) {\n this.requiresControllerReset = false;\n this.onChange(null);\n }\n }\n registerOnTouched(fn) {\n this.onTouched = fn;\n }\n onResolve($event) {\n if (this.onChange) {\n this.onChange($event);\n }\n if (this.onTouched) {\n this.onTouched();\n }\n }\n }\n RecaptchaValueAccessorDirective.ɵfac = function RecaptchaValueAccessorDirective_Factory(t) {\n return new (t || RecaptchaValueAccessorDirective)(i0.ɵɵdirectiveInject(RecaptchaComponent));\n };\n RecaptchaValueAccessorDirective.ɵdir = /* @__PURE__ */i0.ɵɵdefineDirective({\n type: RecaptchaValueAccessorDirective,\n selectors: [[\"re-captcha\", \"formControlName\", \"\"], [\"re-captcha\", \"formControl\", \"\"], [\"re-captcha\", \"ngModel\", \"\"]],\n hostBindings: function RecaptchaValueAccessorDirective_HostBindings(rf, ctx) {\n if (rf & 1) {\n i0.ɵɵlistener(\"resolved\", function RecaptchaValueAccessorDirective_resolved_HostBindingHandler($event) {\n return ctx.onResolve($event);\n });\n }\n },\n features: [i0.ɵɵProvidersFeature([{\n multi: true,\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RecaptchaValueAccessorDirective)\n }])]\n });\n return RecaptchaValueAccessorDirective;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\nlet RecaptchaFormsModule = /*#__PURE__*/(() => {\n class RecaptchaFormsModule {}\n RecaptchaFormsModule.ɵfac = function RecaptchaFormsModule_Factory(t) {\n return new (t || RecaptchaFormsModule)();\n };\n RecaptchaFormsModule.ɵmod = /* @__PURE__ */i0.ɵɵdefineNgModule({\n type: RecaptchaFormsModule\n });\n RecaptchaFormsModule.ɵinj = /* @__PURE__ */i0.ɵɵdefineInjector({\n imports: [FormsModule, RecaptchaCommonModule]\n });\n return RecaptchaFormsModule;\n})();\n(() => {\n (typeof ngDevMode === \"undefined\" || ngDevMode) && void 0;\n})();\n\n/**\n * Generated bundle index. Do not edit.\n */\n\nexport { RECAPTCHA_BASE_URL, RECAPTCHA_LANGUAGE, RECAPTCHA_NONCE, RECAPTCHA_SETTINGS, RECAPTCHA_V3_SITE_KEY, ReCaptchaV3Service, RecaptchaComponent, RecaptchaFormsModule, RecaptchaLoaderService, RecaptchaModule, RecaptchaV3Module, RecaptchaValueAccessorDirective };\n","import { EnvironmentService, Environment } from '@galaxy/core';\nimport { Injectable } from '@angular/core';\n\n@Injectable()\nexport class BillingErpUrlService {\n private _host: string;\n\n constructor(private environmentService: EnvironmentService) {}\n\n private getFinancialForceHost(): void {\n if (this._host) {\n return;\n }\n\n switch (this.environmentService.getEnvironment()) {\n case Environment.PROD: {\n this._host = '//na102.lightning.force.com';\n break;\n }\n\n case Environment.DEMO: {\n this._host = '//cs3.lightning.force.com';\n break;\n }\n\n default: {\n this._host = '//cs3.lightning.force.com';\n break;\n }\n }\n }\n\n private getNetSuiteHost(): void {\n if (this._host) {\n return;\n }\n\n switch (this.environmentService.getEnvironment()) {\n case Environment.PROD: {\n this._host = '//4336003.app.netsuite.com';\n break;\n }\n\n case Environment.DEMO: {\n this._host = '//4336003-sb2.app.netsuite.com';\n break;\n }\n\n default: {\n this._host = '//4336003-sb2.app.netsuite.com';\n break;\n }\n }\n }\n\n public getSalesInvoiceDetails(invoiceId: string): string {\n this.getNetSuiteHost();\n return `${this._host}/app/accounting/transactions/custinvc.nl?id=${invoiceId}`;\n }\n\n public getSalesCreditNoteDetails(creditNoteId: string): string {\n this.getNetSuiteHost();\n return `${this._host}/app/accounting/transactions/custcred.nl?id=${creditNoteId}`;\n }\n\n public getBankReconciliationStatementList(): string {\n this.getFinancialForceHost();\n return `${this._host}/lightning/o/c2g__codaBankReconciliation__c/list`;\n }\n}\n","import { CurrencyPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, Inject } from '@angular/core';\nimport { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';\nimport { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';\nimport { MatSnackBarConfig } from '@angular/material/snack-bar';\nimport { Currency, MerchantService } from '@galaxy/billing';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { BehaviorSubject } from 'rxjs';\nimport { catchError, finalize, tap } from 'rxjs/operators';\n\ninterface DialogData {\n merchantId: string;\n invoiceId: string;\n outstandingBalance: number;\n totalAmount: number;\n invoiceNumber: number;\n invoiceCurrency: Currency;\n}\n\n@Component({\n selector: 'billing-ui-charge-invoice-dialog',\n templateUrl: './charge-invoice-dialog.component.html',\n styleUrls: ['./charge-invoice-dialog.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ChargeInvoiceDialogComponent {\n loading$$: BehaviorSubject = new BehaviorSubject(false);\n merchantId: string;\n invoiceId: string;\n outstandingBalance: number;\n totalAmount: number;\n invoiceNumber: number;\n invoiceCurrency: Currency;\n formGroup: UntypedFormGroup;\n\n constructor(\n public dialogRef: MatDialogRef,\n private merchantService: MerchantService,\n private alertService: SnackbarService,\n private formBuilder: UntypedFormBuilder,\n @Inject(MAT_DIALOG_DATA) public data: DialogData,\n ) {\n this.merchantId = data.merchantId;\n this.invoiceId = data.invoiceId;\n this.outstandingBalance = data.outstandingBalance;\n this.totalAmount = data.totalAmount;\n this.invoiceNumber = data.invoiceNumber;\n this.invoiceCurrency = data.invoiceCurrency;\n this.formGroup = this.formBuilder.group({\n amount: new UntypedFormControl(null, [Validators.required, Validators.min(0.5)]),\n });\n }\n\n public onSave(): void {\n this.loading$$.next(true);\n const { amount } = this.formGroup.getRawValue();\n const cleanedAmount = amount.replace(/,/g, '');\n const SNACKBAR_CONFIG = { duration: 45 * 1000, panelClass: [] } as MatSnackBarConfig;\n this.merchantService\n .chargeSalesInvoice(this.merchantId, this.invoiceId, Number((parseFloat(cleanedAmount) * 100).toFixed(0)))\n .pipe(\n tap(() => {\n const displayAmount = new CurrencyPipe('en-US').transform(\n cleanedAmount,\n this.invoiceCurrency,\n 'symbol',\n '1.2-2',\n );\n this.alertService.openWithOptions(`Successfully charged invoice ${displayAmount}`, {\n duration: SNACKBAR_CONFIG.duration,\n action: 'OK',\n });\n this.dialogRef.close();\n }),\n catchError((err) => {\n const errorMessage = err.error && err.error.message ? err.error.message : `Failed to charge invoice.`;\n this.alertService.openWithOptions(errorMessage, { duration: SNACKBAR_CONFIG.duration, action: 'Dismiss' });\n this.dialogRef.close();\n throw err;\n }),\n finalize(() => this.loading$$.next(false)),\n )\n .subscribe();\n }\n}\n","

{{ 'CHARGE_INVOICE_DIALOG.TITLE' | translate : { number: invoiceNumber } }}

\n
\n \n
\n

\n {{ 'CHARGE_INVOICE_DIALOG.TOTAL' | translate }}\n \n {{ totalAmount / 100 | currency : invoiceCurrency : 'symbol' : '1.2-2' }}\n \n

\n

\n {{ 'CHARGE_INVOICE_DIALOG.OUTSTANDING' | translate }}\n \n {{ outstandingBalance / 100 | currency : invoiceCurrency : 'symbol' : '1.2-2' }}\n \n

\n
\n \n \n {{ 'CHARGE_INVOICE_DIALOG.AMOUNT' | translate }}\n \n \n {{ 'CHARGE_INVOICE_DIALOG.ERROR_AMOUNT_IS_REQUIRED' | translate }}\n \n \n {{ 'CHARGE_INVOICE_DIALOG.ERROR_MINIMUM_AMOUNT' | translate }}¢\n \n \n
\n\n \n \n \n \n
\n","import { Component } from '@angular/core';\n\n@Component({\n templateUrl: './subsidiary-warning-dialog.html',\n})\nexport class SubsidiaryWarningDialogComponent {}\n","

{{ 'SUBSIDIARY_CHANGE_WARNING.TITLE' | translate }}

\n\n

{{ 'SUBSIDIARY_CHANGE_WARNING.WILL_REQUIRE_PAYMENT_METHOD' | translate }}

\n

{{ 'SUBSIDIARY_CHANGE_WARNING.AVOID_TRANSACTION_DELAYS' | translate }}

\n
\n\n \n\n","import { Component, OnInit, OnDestroy, Inject } from '@angular/core';\nimport { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';\nimport { UntypedFormBuilder, UntypedFormGroup, Validators, UntypedFormControl } from '@angular/forms';\n\nimport { BehaviorSubject, Observable, Subscription, of } from 'rxjs';\nimport { distinctUntilChanged, tap, switchMap, finalize, catchError, take, filter, map } from 'rxjs/operators';\n\nimport { Validator } from '@vendasta/shared';\nimport { Country, State } from '@vendasta/country-state';\nimport { Merchant, MerchantService } from '@galaxy/billing';\nimport { SubsidiaryWarningDialogComponent } from './subsidiary-warning-dialog/subsidiary-warning-dialog.component';\nimport { AddressAPIService } from '@vendasta/address';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { TranslateService } from '@ngx-translate/core';\n\ninterface ContactInfoData {\n merchantId: string;\n merchant?: Merchant;\n}\n\nconst LOWER_US_COUNTRY_CODE = 'us';\n\n@Component({\n selector: 'billing-ui-edit-contact-info-dialog',\n templateUrl: 'edit-contact-info-dialog.component.html',\n styleUrls: ['edit-contact-info-dialog.component.scss'],\n})\nexport class EditContactInfoDialogComponent implements OnInit, OnDestroy {\n public merchantId: string;\n public merchant: Merchant | null;\n public formGroup: UntypedFormGroup;\n public countries$: Observable;\n public states$$: BehaviorSubject = new BehaviorSubject([]);\n public isSaving$$: BehaviorSubject = new BehaviorSubject(false);\n public showSubsidiaryChangeWarning$$: BehaviorSubject = new BehaviorSubject(false);\n stateRequired = true;\n\n private subscriptions: Subscription[] = [];\n\n constructor(\n public dialog: MatDialog,\n public dialogRef: MatDialogRef,\n private formBuilder: UntypedFormBuilder,\n private addressService: AddressAPIService,\n private merchantService: MerchantService,\n private snackbarService: SnackbarService,\n private translateService: TranslateService,\n @Inject(MAT_DIALOG_DATA) private data: ContactInfoData,\n ) {\n this.merchantId = this.data.merchantId;\n this.merchant = this.data.merchant || null;\n this.countries$ = this.addressService.listAllCountryOptions().pipe(\n map((countries) => countries.slice().sort((a, b) => a.name.localeCompare(b.name))), // sort a copy alphabetically\n );\n\n this.formGroup = this.formBuilder.group({\n contactName: ['', Validators.required],\n companyName: ['', Validators.required],\n emailAddresses: ['', [Validators.required, Validator.isEmailList]],\n phoneNumber: ['', [Validators.required, Validator.isPhoneNumber]],\n address: ['', Validators.required],\n city: ['', Validators.required],\n state: ['', Validators.required],\n country: ['', Validators.required],\n postalCode: ['', Validators.required],\n });\n }\n\n get country(): UntypedFormControl {\n return this.formGroup.get('country') as UntypedFormControl;\n }\n\n get state(): UntypedFormControl {\n return this.formGroup.get('state') as UntypedFormControl;\n }\n\n get emailAddresses(): UntypedFormControl {\n return this.formGroup.get('emailAddresses') as UntypedFormControl;\n }\n\n get postalCode(): UntypedFormControl {\n return this.formGroup.get('postalCode') as UntypedFormControl;\n }\n\n ngOnInit(): void {\n this.subscriptions.push(\n this.country.valueChanges\n .pipe(\n distinctUntilChanged(),\n tap(() => this.state.patchValue('')), // reset state selection if country changes\n tap((countryCode) =>\n this.showSubsidiaryChangeWarning$$.next(\n this.wouldChangeSubsidiary(countryCode, this.merchant?.country || ''),\n ),\n ),\n filter((countryCode) => !!countryCode),\n switchMap((countryCode) => {\n const locale = this.translateService.currentLang || this.translateService.defaultLang;\n return this.addressService.getCountryConfiguration(countryCode, locale);\n }),\n )\n .subscribe((config) => {\n const states = config?.zones || [];\n this.states$$.next(states);\n if (states.length > 0) {\n this.stateRequired = true;\n this.state.setValidators(Validators.required);\n } else {\n this.stateRequired = false;\n this.state.clearValidators();\n }\n if (this.merchant && states.find((s) => s.code === this.merchant.state)) {\n this.state.patchValue(this.merchant.state);\n }\n\n this.postalCode.setValidators([Validators.required, Validator.isValidPostalCode(config?.code)]);\n\n this.postalCode.updateValueAndValidity();\n this.state.updateValueAndValidity();\n this.country.updateValueAndValidity();\n }),\n );\n\n this.setContactInfo();\n }\n\n /**\n * Determines whether or not the merchant would change billing subsidiaries based on their country seleection\n *\n * @param newCountryCode - The two letter country code of a selected country\n * @param currentCountryCode - The current country code of the merchant\n * @returns {boolean} - True if the merchant would change subsidiaries as a result of the selected country; else false\n */\n private wouldChangeSubsidiary(newCountryCode: string, currentCountryCode: string): boolean {\n switch (currentCountryCode.toLowerCase()) {\n case LOWER_US_COUNTRY_CODE:\n return newCountryCode.toLowerCase() !== LOWER_US_COUNTRY_CODE;\n default:\n return newCountryCode.toLowerCase() === LOWER_US_COUNTRY_CODE;\n }\n }\n\n private setContactInfo(): void {\n if (!this.merchant) {\n return;\n }\n\n let concatenatedEmails: string = this.merchant.emailAddress || '';\n const emailAddresses: string[] = this.merchant.additionalEmailAddresses\n ? new Array(...this.merchant.additionalEmailAddresses)\n : [];\n if (emailAddresses && emailAddresses.length > 0) {\n if (this.merchant.emailAddress) {\n emailAddresses.unshift(this.merchant.emailAddress);\n }\n concatenatedEmails = emailAddresses.join(', ');\n }\n\n this.formGroup.setValue({\n contactName: this.merchant.contactName || '',\n companyName: this.merchant.companyName || '',\n emailAddresses: concatenatedEmails,\n phoneNumber: this.merchant.phoneNumber || '',\n address: this.merchant.address || '',\n city: this.merchant.city || '',\n state: this.merchant.state || '',\n country: this.merchant.country || '',\n postalCode: this.merchant.zipCode || '',\n });\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n }\n\n onSave(): void {\n if (this.formGroup.invalid) {\n this.formGroup.markAllAsTouched();\n return;\n }\n\n this.isSaving$$.next(true);\n const { contactName, companyName, emailAddresses, phoneNumber, address, city, state, country, postalCode } =\n this.formGroup.getRawValue();\n\n const emailList: string[] = emailAddresses.split(/[,;]+/).map((email) => email.trim());\n const primaryEmail: string = emailList.shift(); // remove first email address from list and use it as as the primary email\n\n let resp$: Observable;\n if (this.merchant) {\n resp$ = this.merchantService.update(\n this.merchantId,\n address,\n city,\n state,\n country,\n postalCode,\n primaryEmail,\n phoneNumber,\n contactName,\n companyName,\n emailList,\n );\n } else {\n resp$ = this.merchantService.create(\n this.merchantId,\n address,\n city,\n state,\n country,\n postalCode,\n primaryEmail,\n phoneNumber,\n contactName,\n companyName,\n emailList,\n );\n }\n\n resp$\n .pipe(\n take(1),\n tap(() => {\n this.snackbarService.openSuccessSnack('BILLING_CONTACT.SAVE_SUCCESS');\n this.onClose(true);\n }),\n catchError(() => {\n this.snackbarService.openErrorSnack('BILLING_CONTACT.SAVE_ERROR');\n return of(null);\n }),\n finalize(() => this.isSaving$$.next(false)),\n )\n .subscribe();\n }\n\n public onClose(wasUpdated: boolean): void {\n this.dialogRef.close(wasUpdated);\n this.showSubsidiaryChangeWarning$$.pipe(take(1)).subscribe((showWarning) => {\n if (showWarning) {\n this.dialog.open(SubsidiaryWarningDialogComponent);\n }\n });\n }\n}\n","
\n \n
\n

{{ 'BILLING_CONTACT.TITLE' | translate }}

\n\n
\n \n {{ 'BILLING_CONTACT.COMPANY_NAME' | translate }}\n \n \n \n {{ 'BILLING_CONTACT.NAME' | translate }}\n \n \n \n {{ 'BILLING_CONTACT.EMAIL_ADDRESS' | translate }}\n \n {{ 'BILLING_CONTACT.EMAIL_DELIMITER' | translate }}\n \n At least one email address is required\n \n \n Email addresses entered must be valid: example@example.com\n \n \n \n {{ 'BILLING_CONTACT.PHONE_NUMBER' | translate }}\n \n \n \n \n {{ 'SUBSIDIARY_CHANGE_WARNING.WARNING' | translate }}\n — {{ 'SUBSIDIARY_CHANGE_WARNING.WILL_REQUIRE_PAYMENT_METHOD' | translate }}\n \n \n \n {{ 'BILLING_CONTACT.ADDRESS' | translate }}\n \n \n \n {{ 'BILLING_CONTACT.COUNTRY' | translate }}\n \n \n {{ country.name }}\n \n \n \n \n {{ 'BILLING_CONTACT.CITY' | translate }}\n \n \n \n {{ 'BILLING_CONTACT.PROVINCE' | translate }}\n \n \n {{ state.name }}\n \n \n \n \n {{ 'BILLING_CONTACT.POSTAL_CODE' | translate }}\n \n \n
\n
\n\n \n \n\n","import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n inject,\n Inject,\n OnInit,\n signal,\n WritableSignal,\n} from '@angular/core';\nimport { CommonModule, getCurrencySymbol, NgFor, NgIf } from '@angular/common';\nimport { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';\nimport { MatButtonModule } from '@angular/material/button';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\nimport {\n FormBuilder,\n FormControl,\n ReactiveFormsModule,\n UntypedFormControl,\n UntypedFormGroup,\n Validators,\n} from '@angular/forms';\nimport { TranslationModule } from '@galaxy/form-builder';\nimport { Discount, DiscountType } from '@galaxy/billing';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport {\n getTimezoneOffsetInMilliseconds,\n getUTCEndOfDay,\n getUTCStartOfDay,\n getUTCTime,\n} from '@vendasta/galaxy/utility/date-utils';\nimport { GalaxyButtonLoadingIndicatorModule } from '@vendasta/galaxy/button-loading-indicator';\nimport { TranslateService } from '@ngx-translate/core';\nimport { Moment } from 'moment/moment';\n\nexport type DiscountForRetailDialog = Partial & Pick;\nexport type DiscountFromRetailDialog = DiscountForRetailDialog & Required>;\nexport type OnSaveResponse = Promise<{ success: boolean; discount: DiscountFromRetailDialog }>;\n\nexport interface DiscountDialogInfo {\n discount: DiscountForRetailDialog;\n currencyCode: string;\n mode: 'edit' | 'create';\n onSave: (discount: DiscountFromRetailDialog, mode: 'edit' | 'create') => OnSaveResponse;\n hideDates?: boolean;\n}\n\nconst AMOUNT_VALIDATORS = [Validators.required, Validators.min(0)];\nconst PERCENT_VALIDATORS = [Validators.required, Validators.max(100), Validators.pattern('^[0-9]*$')]; // only whole numbers\nconst RETAIL_DISCOUNT_TYPE_OPTIONS = [DiscountType.FIXED_AMOUNT_PER_UNIT, DiscountType.PERCENT_AMOUNT];\nconst DISCOUNT_DURATION_OPTIONS = {\n FOREVER: 'forever',\n DATE: 'date',\n};\n\n@Component({\n selector: 'billing-ui-add-discount-modal',\n standalone: true,\n imports: [\n CommonModule,\n MatDialogModule,\n MatButtonModule,\n GalaxyFormFieldModule,\n ReactiveFormsModule,\n TranslationModule,\n MatInputModule,\n NgIf,\n NgFor,\n MatRadioModule,\n MatSelectModule,\n MatDatepickerModule,\n GalaxyButtonLoadingIndicatorModule,\n ],\n providers: [],\n templateUrl: './retail-discount-dialog.component.html',\n styleUrls: ['./retail-discount-dialog.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class RetailDiscountDialogComponent implements OnInit {\n formGroup: UntypedFormGroup;\n\n fixedAmountPerUnitDiscountType = DiscountType.FIXED_AMOUNT_PER_UNIT;\n minimumEndDate: WritableSignal = signal(new Date());\n\n currencySymbol = getCurrencySymbol(this.data.currencyCode, 'wide');\n\n isLoading = signal(false);\n private _destroyRef: DestroyRef = inject(DestroyRef);\n\n now = new Date();\n constructor(\n public dialogRef: MatDialogRef,\n private translateService: TranslateService,\n private snackbarService: SnackbarService,\n private formBuilder: FormBuilder,\n @Inject(MAT_DIALOG_DATA) public data: DiscountDialogInfo,\n ) {}\n\n ngOnInit(): void {\n this.formGroup = this.formBuilder.group({\n description: new FormControl(this.data.discount?.description || '', [Validators.required]),\n discountType: new UntypedFormControl(this.data.discount?.discountType || this.fixedAmountPerUnitDiscountType, [\n Validators.required,\n isValidDiscountTypeForRetail,\n ]),\n amount: new FormControl(0, AMOUNT_VALIDATORS),\n percentAmount: new FormControl(0, PERCENT_VALIDATORS),\n startDate: new FormControl(this.now, [Validators.required]),\n endDate: new FormControl(null, []),\n duration: new UntypedFormControl(DISCOUNT_DURATION_OPTIONS.FOREVER, [Validators.required]),\n });\n\n if (this.data.discount.discountType === DiscountType.PERCENT_AMOUNT) {\n this.percentAmount.patchValue(this.data.discount.amount ?? 0, {\n emitEvent: false,\n });\n } else {\n this.amount.patchValue(this.data.discount.amount ?? 0, {\n emitEvent: false,\n });\n }\n\n this.duration.valueChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe((value) => {\n // If we are setting the date to forever - then that assumes it starts now\n if (value === DISCOUNT_DURATION_OPTIONS.FOREVER) {\n this.startDate.patchValue(smallestDate(this.startDate.value, this.now), { emitEvent: false });\n }\n });\n\n this.startDate.valueChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe((val) => {\n this.minimumEndDate.set(val);\n });\n\n if (this.data.mode === 'edit') {\n this.prefillValues(this.data.discount);\n }\n\n this.discountType.valueChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe((val) => {\n if (val === DiscountType.FIXED_AMOUNT_PER_UNIT) {\n this.amount.patchValue(0);\n }\n if (val === DiscountType.PERCENT_AMOUNT) {\n this.percentAmount.patchValue(0);\n }\n });\n }\n\n get description(): FormControl {\n return this.formGroup.get('description') as UntypedFormControl;\n }\n\n get amount(): FormControl {\n return this.formGroup.get('amount') as UntypedFormControl;\n }\n\n get percentAmount(): FormControl {\n return this.formGroup.get('percentAmount') as UntypedFormControl;\n }\n\n get duration(): UntypedFormControl {\n return this.formGroup.get('duration') as UntypedFormControl;\n }\n\n get discountType(): UntypedFormControl {\n return this.formGroup.get('discountType') as UntypedFormControl;\n }\n\n get startDate(): UntypedFormControl {\n return this.formGroup.get('startDate') as UntypedFormControl;\n }\n\n get endDate(): UntypedFormControl {\n return this.formGroup.get('endDate') as UntypedFormControl;\n }\n\n private prefillValues(discount: Partial): void {\n this.description.patchValue(discount.description ?? '', {\n emitEvent: false,\n });\n this.discountType.patchValue(discount.discountType || this.fixedAmountPerUnitDiscountType, {\n emitEvent: false,\n });\n if (discount.discountType === DiscountType.FIXED_AMOUNT_PER_UNIT) {\n this.amount.patchValue((discount.amount ?? 0) / 100, {\n emitEvent: false,\n }); // convert to cents\n } else {\n this.percentAmount.patchValue(discount.amount ?? 0, {\n emitEvent: false,\n });\n }\n\n let hasDate = false;\n if (discount.start) {\n if (discount.start.getTime() > this.now.getTime()) {\n hasDate = true;\n }\n // need to convert discount start & end timestamps to UTC since at this point the value is the user's local time\n // (eg. converting to UTCStartOfDay could mean the wrong day)\n this.startDate.patchValue(adjustDateToForm(discount.start), {\n emitEvent: false,\n });\n }\n\n const zeroDate = new Date('0001-01-01T00:00:00Z').valueOf();\n if (discount.end && discount.end.valueOf() !== zeroDate) {\n hasDate = true;\n this.endDate.patchValue(adjustDateToForm(discount.end), {\n emitEvent: false,\n });\n }\n\n if (hasDate) {\n this.duration.patchValue(DISCOUNT_DURATION_OPTIONS.DATE, {\n emitEvent: false,\n });\n }\n }\n\n async onSave(): Promise {\n if (!this.formGroup.valid) {\n this.snackbarService.openErrorSnack(\n this.translateService.instant('BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.ERROR_MISSING_INFORMATION'),\n );\n return;\n }\n this.isLoading.set(true);\n // calculate discount amount according to the discount type\n let amount = this.amount.value;\n if (this.discountType.value === DiscountType.FIXED_AMOUNT_PER_UNIT) {\n amount = amount * 100; // stored in cents\n } else {\n amount = this.percentAmount.value;\n }\n let end: Date | undefined = undefined;\n if (this.endDate?.value && this.duration.value === DISCOUNT_DURATION_OPTIONS.DATE) {\n end = getUTCEndOfDay(adjustDateFromForm(this.endDate.value));\n }\n const start = getUTCStartOfDay(adjustDateFromForm(this.startDate.value));\n\n const discount: DiscountFromRetailDialog = {\n ...this.data.discount,\n discountType: this.discountType.value,\n amount,\n start,\n end: end,\n description: this.description.value,\n };\n if (this.data.onSave) {\n const resp = await this.data.onSave(discount, this.data.mode);\n if (resp.success) {\n this.dialogRef.close(resp.discount);\n }\n } else {\n this.dialogRef.close(false);\n }\n this.isLoading.set(false);\n }\n}\n\nfunction isValidDiscountTypeForRetail() {\n return (control: UntypedFormControl) => {\n if (RETAIL_DISCOUNT_TYPE_OPTIONS.findIndex(control.value) === -1) {\n return { invalidDiscountType: true };\n }\n return null;\n };\n}\n\nfunction adjustDateToForm(d: Date): Date {\n return getUTCTime(d);\n}\n\nfunction adjustDateFromForm(d: Date | Moment): Date {\n return new Date(dateFromMoment(d).getTime() - getTimezoneOffsetInMilliseconds());\n}\n\nfunction smallestDate(d1: Date | Moment, d2: Date | Moment): Date | Moment {\n if (dateFromMoment(d1).getTime() < dateFromMoment(d2).getTime()) {\n return d1;\n }\n return d2;\n}\n\nfunction dateFromMoment(d: Date | Moment): Date {\n if ('toDate' in d) {\n return d.toDate();\n }\n return d;\n}\n","
\n

\n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.TITLE' | translate }}\n \n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.EDIT_TITLE' | translate }}\n \n

\n\n \n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.NAME_LABEL' | translate }}\n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.NAME_HINT' | translate }}\n \n \n\n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.TYPE_LABEL' | translate }}\n \n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.TYPE_FIXED_AMOUNT_PER_UNIT' | translate }}\n \n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.PERCENTAGE' | translate }}\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.DURATION' | translate }}\n \n {{\n 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.DURATION_FOREVER' | translate\n }}\n {{\n 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.DURATION_CUSTOM' | translate\n }}\n \n \n\n \n
\n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.START_DATE' | translate }}\n {{\n 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.START_END_UTC' | translate\n }}\n \n \n \n {{\n 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.START_DATE_HINT' | translate\n }}\n \n\n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.END_DATE' | translate }}\n \n \n \n \n {{ 'BILLING_SUBSCRIPTIONS.CREATE_SUBSCRIPTIONS.DISCOUNTS.END_DATE_AFTER_START' | translate }}\n \n \n
\n
\n
\n
\n\n \n \n \n \n
\n","{\n \"PRICING_COMMONS\": {\n \"STARTING_AT_LABEL\": \"Starting at\",\n \"MINIMUM_PRICE_LABEL\": \"Minimum\",\n \"FREE_LABEL\": \"Free\",\n \"CONTACT_SALES_LABEL\": \"Contact Sales\",\n \"SETUP_FEE_LABEL\": \"Setup fee\",\n \"MANAGEMENT_FEE_LABEL\": \"Management fee\",\n \"PRICE_UNAVAILABLE_LABEL\": \"Price unavailable\",\n \"VARIABLE_PRICE_INPUT\": {\n \"LABEL\": \"Enter amount\",\n \"ADD_NOTE\": \"Add note\",\n \"NOTE_LABEL\": \"Note\"\n }\n },\n \"SUBSIDIARY_CHANGE_WARNING\": {\n \"TITLE\": \"Re-enter your payment method\",\n \"WILL_REQUIRE_PAYMENT_METHOD\": \"Changing your billing contact's country may require you to re-enter a payment method.\",\n \"AVOID_TRANSACTION_DELAYS\": \"Ensure that you re-enter a payment method to avoid delaying future transactions.\",\n \"CONFIRM\": \"Confirm\",\n \"WARNING\": \"Warning\"\n },\n \"REFUND_DIALOG\": {\n \"TITLE\": \"Issue refund\",\n \"ORIGINAL_CHARGE\": \"The original charge was\",\n \"AMOUNT\": \"Refund amount\",\n \"REFUND_MUST_BE_GREATER_THAN_ZERO\": \"Refund must be greater than\",\n \"REFUND_CANNOT_EXCEED_AMOUNT\": \"Refund cannot exceed amount\",\n \"REASON_LABEL\": \"Reason\",\n \"REASON_LABEL_HINT\": \"Reason will not be visible to customer\",\n \"SELECT_REASON\": \"Select a reason\",\n \"REASON_REQUIRED\": \"Reason for refund required\",\n \"DETAILED_NOTES\": \"Detailed notes\",\n \"NOTES_REQUIRED\": \"Notes required\",\n \"NOTES_LABEL\": \"Notes\",\n \"NOTES_LABEL_HINT\": \"Notes will be visible to customer\",\n \"CANCEL\": \"Cancel\",\n \"CONFIRM\": \"Confirm\"\n },\n \"SUBSCRIPTIONS\": {\n \"FILTERS\": {\n \"SEARCH\": \"Search subscriptions\",\n \"FREQUENCY\": {\n \"ONE_TIME\": \"One Time\",\n \"MONTHLY\": \"Monthly\",\n \"YEARLY\": \"Yearly\"\n },\n \"DATE\": {\n \"ENDS_BEFORE\": \"Ends Before\",\n \"ENDS_AFTER\": \"Ends After\"\n }\n },\n \"EXPORT\": {\n \"SUCCESS\": \"Export started, you will receive a download notification when your file is ready\",\n \"ERROR\": \"Failed to start subscriptions export\",\n \"LABEL\": \"Export as CSV\"\n }\n },\n \"BILLING_CONTACT\": {\n \"TITLE\": \"Billing contact\",\n \"COMPANY_NAME\": \"Company name\",\n \"NAME\": \"Name\",\n \"EMAIL_ADDRESS\": \"Email address(es)\",\n \"EMAIL_DELIMITER\": \"Comma or semicolon separated\",\n \"EMAIL_REQUIRED_ERROR\": \"At least one email address is required\",\n \"EMAIL_INVALID_ERROR\": \"Email addresses entered must be valid: example@example.com\",\n \"PHONE_NUMBER\": \"Phone number\",\n \"ADDRESS\": \"Address\",\n \"COUNTRY\": \"Country\",\n \"CITY\": \"City\",\n \"PROVINCE\": \"State/Province\",\n \"POSTAL_CODE\": \"Zip/Postal code\",\n \"CANCEL\": \"Cancel\",\n \"SAVE\": \"Save\",\n \"SAVE_SUCCESS\": \"Successfully updated contact information.\",\n \"SAVE_ERROR\": \"Error updating contact information.\"\n },\n \"CHARGE_INVOICE_DIALOG\": {\n \"TITLE\": \"Charge sales invoice ({{ number }})\",\n \"TOTAL\": \"Total amount:\",\n \"OUTSTANDING\": \"Outstanding balance:\",\n \"AMOUNT\": \"Amount\",\n \"CANCEL\": \"Cancel\",\n \"CHARGE\": \"Charge invoice\",\n \"ERROR_AMOUNT_IS_REQUIRED\": \"Amount is required\",\n \"ERROR_MINIMUM_AMOUNT\": \"Amount must be at least 50\"\n }\n}\n","import { NgModule } from '@angular/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { LexiconModule } from '@galaxy/lexicon';\nimport En from './assets/en_devel.json';\n\n@NgModule({\n imports: [\n TranslateModule,\n LexiconModule.forChild({\n componentName: 'common/ui-billing',\n baseTranslation: En,\n }),\n ],\n})\nexport class BillingUiI18nModule {}\n","import { EnvironmentService, Environment } from '@galaxy/core';\nimport { Injectable } from '@angular/core';\n\n@Injectable()\nexport class StripeUrlService {\n private _host: string;\n\n constructor(private environmentService: EnvironmentService) {}\n\n private getHost(): void {\n if (this._host) {\n return;\n }\n switch (this.environmentService.getEnvironment()) {\n case Environment.PROD: {\n this._host = '//dashboard.stripe.com';\n break;\n }\n\n case Environment.DEMO: {\n this._host = '//dashboard.stripe.com/test';\n break;\n }\n\n default: {\n this._host = '//dashboard.stripe.com/test';\n break;\n }\n }\n }\n\n public getChargeDetailsUrl(chargeId: string): string {\n this.getHost();\n return `${this._host}/payments/${chargeId}`;\n }\n}\n","/**\n * getRequestPageSize determines how many items will need to be retrieved to complete the current page of data\n * @param dataLength - the length of the currently loaded list of data\n * @param pageIndex\n * @param pageSize\n */\nexport const getRequestPageSize = (dataLength: number, pageIndex: number, pageSize: number) => {\n const startIndex = pageIndex * pageSize;\n const endIndex = startIndex + pageSize;\n return endIndex - dataLength;\n};\n","import { Directive, ElementRef, Renderer2 } from '@angular/core';\n\n@Directive({\n selector: '[billingUiPriceColumnStyle]',\n standalone: true,\n})\nexport class PriceColumnStyleDirective {\n constructor(private el: ElementRef, private renderer: Renderer2) {\n renderer.setStyle(el.nativeElement, 'text-align', 'right');\n }\n}\n","import { Injectable } from '@angular/core';\nimport { Observable, Observer } from 'rxjs';\n\nexport interface Script {\n name: string;\n src: string;\n}\n\n@Injectable()\nexport class StripeService {\n private loaded = false;\n public loadScript(): Observable