import { action, decorate, observable, get, set, has, keys } from 'mobx'; import { FormValidatorService, ProductOrderService, DeliveryValidator } from 'lib'; import { CHECKOUT_FIELDS } from 'templates/checkout/checkout.constants'; import { CheckoutStoreForm, CheckoutStoreCheckoutField } from 'types/stores'; import { FieldType, CheckoutFieldType, FieldId, CheckoutFieldGroup } from 'templates/checkout/checkout.types'; import store from 'stores'; import { navigate } from '@reach/router'; export default class CheckoutStore { grabAllFields = (): { [fieldID in FieldId]: CheckoutStoreCheckoutField } => { return CHECKOUT_FIELDS.reduce((acc: { [id in FieldId]: CheckoutFieldType }, currGroup: CheckoutFieldGroup) => { const fieldsForGroup = currGroup.fields.reduce((acc: { [id in FieldId]: CheckoutFieldType }, curr: CheckoutFieldType): any => { if (curr.fieldType !== FieldType.TextCaption) { return { ...acc, [curr.data.id]: { ...curr, value: '', error: null } } } return acc; }, {}); return { ...acc, ...fieldsForGroup } }, {}) } paymentIntent: any = null; merchantConfig: any = null; userOrderInformation: any = null; orderCreationError: any = null; checkoutError: string | null = null; form: CheckoutStoreForm = { fields: this.grabAllFields(), meta: { isValid: false, error: null } }; setInitialValues = () => { this.paymentIntent = null; this.merchantConfig = null; this.userOrderInformation = null; this.checkoutError = null; this.orderCreationError = null; this.form = { fields: this.grabAllFields(), meta: { isValid: false, error: null } } } resetPaymentIntent = () => { this.paymentIntent = null; } onChange = (value: string, id: FieldId): void => { this.form.fields[id].value = value; if (has(this.form.fields[id], 'error') && has(this.form.fields[id], 'rule')) { const validation = new FormValidatorService( {[id]: get(this.form.fields[id], 'value')}, {[id]: get(this.form.fields[id], 'rule')} ); if (!!validation && validation.passes()) { set(this.form.fields[id], 'error', null); this.form.meta.isValid = Object.keys(this.form.fields).every(fieldId => !this.form.fields[fieldId].error) } } }; validateAll = async (): void => { const values = {}; const rules = {}; const fieldsKeys = keys(this.form.fields); fieldsKeys.forEach(field => { if (has(this.form.fields[field], 'rule')) { values[field] = get(this.form.fields[field], 'value'); rules[field] = get(this.form.fields[field], 'rule'); } }); const validation = await new FormValidatorService(values, rules); const isValid = validation.passes(); this.form.meta.isValid = isValid; if (!isValid) { Object .entries(validation.errors.all()) .forEach(error => { set(this.form.fields[error[0]], 'error', error[1][0]); } ); } if (isValid && store.basketStore.selectedDeliveryConfig && this.canDelivertoPostalCode()) { this.checkoutError = null; this.buildOrderUserInformation(); navigate('/payment'); } else if (!store.basketStore.selectedDeliveryConfig) { store.basketStore.setDeliveryConfigError(); } } private hasValidCoverage = (config: any, outcode: string): boolean => { return config.deliveryCoverage.some(config => config.deliveryZones.some((zone: any) => zone.postalCode === outcode) ); } canDelivertoPostalCode = (): boolean => { const validatedPostalCode = ProductOrderService.validatePostalCode(this.form.fields.shippingAddressZipCode.value); const hasValidCoverage = this.hasValidCoverage(store.basketStore.selectedDeliveryConfig.config, validatedPostalCode.valueToSend); if (!validatedPostalCode || !hasValidCoverage) { this.checkoutError = 'Some products cannot be delivered to the delivery postcode provided'; return false; } return true; } isValid = (): boolean => { return get(this.form.meta, 'isValid'); } onSubmit = (merchantConfig: any): void => { this.merchantConfig = merchantConfig; this.validateAll(); } buildStripePaymentData = (nameOnCard: string) => { return { payment_method_data: { billing_details: { name: nameOnCard, email: this.form.fields.email.value } }, receipt_email: this.form.fields.email.value } } buildOrderUserInformation = () => { const { billingAddressFirstName, billingAddressLastName, email, billingAddressBusinessName, billingAddressPhone, billingAddressAddress1, billingAddressAddress2, billingAddressCity, billingAddressZipCode, shippingAddressFirstName, shippingAddressLastName, shippingAddressPhone, shippingAddressAddress1, shippingAddressAddress2, shippingAddressBusinessName, shippingAddressCity, shippingAddressZipCode, orderDeliveryInstructions, orderDeliveryNotHome } = this.form.fields; const getDeliveryPrice = () => { return store.basketStore.selectedDeliveryConfig.sameDay ? store.basketStore.selectedDeliveryConfig.config.deliveryPrice.sameDayPrice : store.basketStore.selectedDeliveryConfig.config.deliveryPrice.nextDayPrice } const buildGiftMesssages = (basketItem: any) => { return basketItem.orderItems.reduce((acc: any, curr: any) => { if (curr.message && curr.message.length) { return [ ...acc, curr.message ]; } return acc }, []) } const buildOrderItems = (): any => { return store.basketStore.basket.reduce((acc: any, currBasketItem: any) => { return [ ...acc, { product_var_id: currBasketItem.orderItems[0].variationId, quantity: currBasketItem.quantity, price: currBasketItem.total, giftMessages: buildGiftMesssages(currBasketItem) } ] }, []) } this.userOrderInformation = { currency: this.merchantConfig.currency, deliverAt: store.basketStore.selectedDeliveryConfig.date, deliveryInstructions: orderDeliveryInstructions.value, deliverySafePlace: orderDeliveryNotHome.value, merchant: { id: this.merchantConfig.fxId }, orderDeliveryConfig: { deliveryConfigId: store.basketStore.selectedDeliveryConfig.config.id, sameDay: store.basketStore.selectedDeliveryConfig.sameDay, price: getDeliveryPrice() }, customerUser: { email: email.value, givenName: billingAddressFirstName.value, familyName: billingAddressLastName.value }, billingAddress: { // TODO Handle Title, setting as string so not undefined in API / Stripe honorific: '', businessName: billingAddressBusinessName.value || '', recipientFullName: `${billingAddressFirstName.value} ${billingAddressLastName.value}`, address1: billingAddressAddress1.value, address2: billingAddressAddress2.value, city: billingAddressCity.value, postalCode: billingAddressZipCode.value, phone: billingAddressPhone.value, // TODO Handle Country country: '' }, shippingAddress: { honorific: '', businessName: shippingAddressBusinessName.value || '', recipientFullName: `${shippingAddressFirstName.value} ${shippingAddressLastName.value}`, address1: shippingAddressAddress1.value, address2: shippingAddressAddress2.value, city: shippingAddressCity.value, postalCode: shippingAddressZipCode.value, phone: shippingAddressPhone.value, country: '' }, orderItems: buildOrderItems() } } generatePaymentIntent = async () => { this.orderCreationError = null; const orderPaymentIntent = await ProductOrderService.generatePaymentIntent(this.userOrderInformation); try { this.paymentIntent = orderPaymentIntent; } catch (error) { this.orderCreationError = error; console.log(error); } } } decorate(CheckoutStore, { form: observable, merchantConfig: observable, paymentIntent: observable, userOrderInformation: observable, orderCreationError: observable, checkoutError: observable, grabAllFields: action, resetPaymentIntent: action, onChange: action, onSubmit: action, generatePaymentIntent: action });