import { html, css, CSSResult } from 'lit'; import { property } from 'lit/decorators.js'; import { loadStripe } from '@stripe/stripe-js'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; // import { get } from 'lit-translate'; import { SkhemataBase } from '@skhemata/skhemata-base'; import { SkhemataForm, SkhemataFormTextbox, SkhemataFormTextarea, SkhemataFormDropdown, SkhemataFormCheckbox, SkhemataFormButton, SkhemataFormAutocomplete, } from '@skhemata/skhemata-form'; import { SkhemataSubscriptionPlan } from './SkhemataSubscriptionPlan'; import { SkhemataFormStripe } from './SkhemataFormStripe'; import { genId } from './directives'; export class SkhemataSubscription extends SkhemataBase { static get styles() { return [ ...super.styles, css` :host { display: block; padding: 25px; color: var(--skhemata-subscription-text-color, #000); --stripe-elements-width: 0px; } .panel .panel-block { flex-direction: column; align-items: normal; padding: 1rem; } .panel .column { padding: 0rem 0.75rem; } .panel .columns { padding: 0rem; margin-top: 0rem; } .left-column { background: #f3f4f6; border: 1px solid #e6e6e6; } .column.right { text-align: right; } #terms-checkbox { margin-top: 1rem; } .review-container { box-shadow: none; } .total-payment { color: var(--skhemata-subscription-summary-color, #00c4a7); } hr { background-color: #e2e2e2; margin-top: 0; margin-bottom: 2rem; } .dropdown-trigger button { color: #d0d0d0; } #plans option { padding-right: 1rem; } .modal-button { margin: auto; width: 30%; height: 1.5rem; } .review-message { margin: 2rem 0; font-size: 0.8em; } .back-button { margin-top: 0.5rem; } .button.is-primary { background-color: var( --skhemata-subscription-button-background-color, #00c4a7 ); color: var(--skhemata-subscription-button-text-color, #ffffff); } .button.is-primary:hover { background-color: var( --skhemata-subscription-button-background-color, #00c4a7 ); color: var(--skhemata-subscription-button-text-color, #ffffff); opacity: 75%; } a.text-link { color: var(--skhemata-subscription-link-color, rgb(50, 115, 220)); } a.text-link:hover { opacity: 75%; } `, ]; } static get scopedElements() { return { 'skhemata-form': SkhemataForm, 'skhemata-form-textbox': SkhemataFormTextbox, 'skhemata-form-textarea': SkhemataFormTextarea, 'skhemata-form-dropdown': SkhemataFormDropdown, 'skhemata-form-checkbox': SkhemataFormCheckbox, 'skhemata-form-button': SkhemataFormButton, 'skhemata-form-stripe': SkhemataFormStripe, 'skhemata-form-autocomplete': SkhemataFormAutocomplete, 'skhemata-subscription-plan': SkhemataSubscriptionPlan, }; } @property({}) elements: any; @property({}) cardNumberElement: any; @property({}) cardExpiryElement: any; @property({}) cardCvcElement: any; @property({ type: Object }) formData = { first_name: '', last_name: '', email: '', mail_code: '', city_id: 1, address_city: '', address_country: '', address_state: '', street1: '', portal_name: '', subscription_plan_id: -1, source: '', currency: 'usd', terms: false, token: '', use_sca: 1, create_sample_data: 0, }; @property({ type: String }) thankYouTitle = 'Thank you for the subscription.'; @property({ type: String }) thankYouMessage = 'We will be in touch to get you started!'; @property({ type: Number }) total = 0.0; @property({ type: Number }) tax: any; @property({ type: Object }) stripe: any; @property({ type: String }) stripeKeySelected = ''; @property({ type: Object, attribute: 'stripe-public-keys' }) stripePublicKeys: any; @property({ type: Number }) defaultPlan?: number; @property({ type: Number }) selectedPlanIndex = -1; @property({ type: Boolean, attribute: false }) isLoading = false; @property({ type: Object }) currency = { locale: 'en-US', code: 'USD', }; async handleSubmit() { this.isLoading = true; const skhemataFormStripe: any = this.shadowRoot?.getElementById( 'skhemata-form-stripe' ); const { stripeElements } = skhemataFormStripe; stripeElements ?.createToken() .then((response: any) => response) .then((response: any) => { const cardToken = response.token.id; const subscription = this.skhemata?.createSubscription({ ...this.formData, portal_name: genId(12), }); subscription ?.payWithStripe(this.stripe, cardToken) .then(data => { dispatchEvent( new CustomEvent('payment', { detail: { status: 'success', data, }, }) ); this.isLoading = false; this.openThankYouModal(); this.selectedPlanIndex = -1; this.formData = { first_name: '', last_name: '', email: '', mail_code: '', city_id: 1, address_city: '', address_country: '', address_state: '', street1: '', portal_name: '', subscription_plan_id: -1, source: '', currency: 'usd', terms: false, token: '', use_sca: 1, create_sample_data: 0, }; }) .catch(error => { this.isLoading = false; this.dispatchEvent( new CustomEvent('payment', { detail: { status: 'error', data: error, }, }) ); }); }); } handleChange(event: any) { if (!event || !event.detail || !event.detail.data) return; const inputData = event.detail.data; if (inputData['first-name']) this.formData.first_name = inputData['first-name']; if (inputData['last-name']) this.formData.last_name = inputData['last-name']; if (inputData.email) this.formData.email = inputData.email; if (inputData['street-address']) this.formData.street1 = inputData['street-address']; if (inputData['mail-code']) this.formData.mail_code = inputData['mail-code']; if (inputData.city) this.formData.city_id = inputData.city; if (inputData['portal-name']) this.formData.portal_name = inputData['portal-name']; if (inputData.plan || inputData.plan === '') this.selectedPlanIndex = inputData.plan ? parseInt(inputData.plan, 10) : -1; this.formData.subscription_plan_id = this.configData[this.selectedPlanIndex]?.id; if (inputData.source) this.formData.source = inputData.source; if (inputData.terms) this.formData.terms = inputData.terms; } async firstUpdated() { await super.firstUpdated(); this.stripe = await loadStripe(this.stripePublicKeys.CA); if (this.defaultPlan) { this.selectPlan( new CustomEvent('select-plan', { detail: { data: this.defaultPlan, }, }) ); } } submitForm() { const paymentForm: any = this.shadowRoot?.getElementById('payment-form'); paymentForm.handleSubmit(); } selectPlan(e: any) { this.selectedPlanIndex = e.detail.data; this.requestUpdate(); window.scrollTo({ top: 0 }); } displayPlans() { this.selectedPlanIndex = -1; this.requestUpdate(); window.scrollTo({ top: 0 }); } // TODO: move tax logic to backend and SDK async getTaxRate(e?: any) { const cityElement: SkhemataFormAutocomplete | null | undefined = this.shadowRoot?.querySelector('[name="city"]'); const selected: string | undefined = cityElement?.selected .trim() .replace(/,/g, ''); if (selected) { const locales = await fetch( `${this.api.url}/locale/city/${selected}` ).then(res => res.json()); const locale = locales[0]; if (locale.country_id === 33) { this.stripe = await loadStripe(this.stripePublicKeys.CA); this.stripeKeySelected = this.stripePublicKeys.CA; const subcountryTax = await fetch( `${this.api.url}/locale/subcountry-tax/33` ).then(res => res.json()); this.tax = JSON.parse( subcountryTax.filter( (tax: any) => tax.subcountry_id === locale.subcountry_id )[0].rate ); this.tax.rateTotal = this.tax.gst + this.tax.pst + this.tax.hst; this.tax.feeTotal = 0.01 * this.tax.rateTotal * this.configData[this.selectedPlanIndex]?.price; this.total = this.configData[this.selectedPlanIndex]?.price + this.tax.feeTotal; return; } } this.tax = undefined; this.stripe = await loadStripe(this.stripePublicKeys.US); this.stripeKeySelected = this.stripePublicKeys.US; if (e) this.total = this.configData && this.shadowRoot?.getElementById('plans')?.getAttribute('value') ? this.configData[this.selectedPlanIndex]?.price : 0; // use trial price if trial is defined if ( typeof this.configData[this.selectedPlanIndex]?.trial?.price === 'number' ) { this.total = this.configData[this.selectedPlanIndex].trial.price; } } openThankYouModal() { this.shadowRoot ?.getElementById('thank-you-modal') ?.classList.add('is-active'); } closeThankYouModal() { this.shadowRoot ?.getElementById('thank-you-modal') ?.classList.remove('is-active'); } render() { const selectedPlan = this.configData ? this.configData[this.selectedPlanIndex] : undefined; const billingDate = new Date(); if (selectedPlan?.trial) { let days = selectedPlan.trial.periodLength; if (selectedPlan.periodType === 'month') { days *= 30; } else if (selectedPlan.periodType === 'year') { days *= 365; } billingDate.setDate(billingDate.getDate() + days); } const plans = html`
`; const accountInfo = html`

Account Information

`; const otherInfo = html`

Other Information

`; const paymentInfo = html`

Payment Information

this.getTaxRate()} >
`; const taxes = () => { if (this.tax?.hst) { return html`
HST
${this.tax.hst}%
Sales Tax
$${this.tax?.feeTotal?.toFixed(2)} (${this.tax.rateTotal}%)
`; } if (this.tax?.gst || this.tax?.pst) { return html` ${this.tax?.gst ? html`
GST
${this.tax.gst}%
` : null} ${this.tax?.pst ? html`
PST
${this.tax.pst}%
` : null}
Sales Tax
$${this.tax?.feeTotal?.toFixed(2)} (${this.tax.rateTotal}%)
`; } return html``; }; const reviewPayment = html`

Review Payment

Select Plan
Price
${this.configData?.length ? html` ${this.configData.map((plan: any, index: number) => unsafeHTML(` `) )} ` : html``}
$${this.configData && this.shadowRoot?.getElementById('plans')?.getAttribute('value') ? selectedPlan?.price : '0.00'}
${taxes()}
Total Payment ${this.configData && selectedPlan?.trial ? `(${selectedPlan.trial.price === 0 ? 'Free' : ''} ${ selectedPlan.trial.periodLength } ${selectedPlan.trial.periodType} trial)` : ''}
$${this.total?.toFixed(2)}
${this.configData && selectedPlan?.trial ? `Your next billing will start on ${billingDate.toLocaleDateString()}` : ''}
Thrinacia plans are available for purchase worldwide. Once you subscribe to the selected plan we will start deployment of your new website.
`; const modal = html` `; const form = html`
${accountInfo} ${paymentInfo} ${otherInfo}
${reviewPayment}
`; return html`
${this.configData && selectedPlan ? form : plans}
${modal}`; } }