import { useAppStore } from "@/widget/app/providers/stores/appStore.ts"; import { useDiscountsStore } from "@/widget/app/providers/stores/discountsStore.ts"; import { IFileData, IOrderData, IOrderDetails, IOrderDetailsHelperResult, IOrderDetailsType, ISubmitsRequestParams, PaymentMethods, } from "@/widget/shared/types/orders"; import { handleSubmissionRequest } from "@/widget/shared/api/handleOrderSubmission.ts"; import { Field, IFileUploadField, IOptions, IRepeaterField, IGroupField, } from "@/widget/shared/types/fields"; import { usePaymentStore } from "@/widget/app/providers/stores/paymentsStore.ts"; import { usePaymentAfterSubmitStore } from "@/widget/app/providers/stores/paymentAfterSubmit.ts"; import { useSubmissionStore } from "@/widget/app/providers/stores/submissionStore.ts"; import { useSettingsStore } from "@/widget/app/providers/stores/settingsStore.ts"; import { ContactFormFields } from "@/widget/shared/types/orders/contact-form-fields.type"; import { useOrderFormStore } from "@/widget/app/providers/stores/orderFormStore.ts"; import { useFieldsStore } from "@/widget/app/providers/stores/fieldsStore.ts"; import { getNonce } from "@/widget/shared/utils/nonce.utils"; type OrdersResponseData = { success: boolean; message: string; clientSecret?: string; requiresAction?: boolean; paymentIntentId?: string; orderId?: number; page?: string; url?: string; data?: { orderId: number; message: string; clientSecret: string; }; }; export type OrdersResponse = OrdersResponseData; interface IOrderResult { makePayment: ( type: PaymentMethods, orderId: number, ) => Promise; createOrder: ( type: PaymentMethods, orderInputs?: ContactFormFields[], ) => Promise; razorpayPaymentReceived: ( orderId: number | undefined, currency: string, ) => Promise; } const prepareOrderData = ( type: PaymentMethods, orderInputs?: ContactFormFields[], ): IOrderData => { const appStore = useAppStore(); const paymentStore = usePaymentStore(); const discountsStore = useDiscountsStore(); const orderFormStore = useOrderFormStore(); const legacyFormFields = []; if (orderInputs?.length) { for (const orderInput of orderInputs) { legacyFormFields.push({ ...orderInput, }); } } else { for (const formField of orderFormStore.getFormFields) { if (formField.type !== "space") { legacyFormFields.push({ ...formField, attributes: { ...formField.attributes, label: formField.label, }, }); } } } const data: IOrderDetails = getOrderDetails(); const orderDetails: IOrderDetailsType = data.fields; const hiddenOrderDetails: IOrderDetailsType = data.hiddenFields; const paymentMethodId: string = (paymentStore.paymentMethodId as unknown as string) || ""; const paymentIntentId: string = (paymentStore.paymentIntentId as unknown as string) || ""; let descriptions: Field[] = []; for (const field of getClearFields()) { if (field.fieldName === "repeater" && "groupElements" in field) { const groupElements: Field[] = []; field.groupElements.forEach((element) => { if ("entries" in element && typeof element.entries === "function") { for (const [_, existingField] of element.entries()) { groupElements.push(existingField as Field); } } }); descriptions.push({ ...field, // @ts-ignore groupElements, }); } else if (field.fieldName === "group" && "groupElements" in field) { const groupElements: Field[] = []; field.groupElements.forEach((element) => { if ("entries" in element && typeof element.entries === "function") { for (const [_, existingField] of element.entries()) { groupElements.push(existingField as Field); } } }); descriptions = [...descriptions, ...groupElements]; } else { descriptions.push(field); } } return { orderDetails, status: "pending", hiddenOrderDetails, paymentMethod: type, descriptions: descriptions, calcName: appStore.getCalcTitle, id: Number(appStore.getCalcId || 0), promocodes: discountsStore.getUsedPromocodes, formDetails: { fields: legacyFormFields, }, ...(paymentMethodId ? { paymentMethodId: paymentMethodId || "" } : {}), ...(paymentIntentId ? { paymentIntentId: paymentIntentId || "" } : {}), }; }; const getUsedFiles = (): IFileData[] => { const fieldStore = useFieldsStore(); const fields = fieldStore.getFields.flatMap((section) => { if ("fields" in section) { return Array.from(section.fields.values()); } return []; }); let pureFields: IFileUploadField[] = fields.filter( (f) => f.fieldName === "file_upload", ) as IFileUploadField[]; let repeaterFields: IRepeaterField[] = fields.filter( (f) => f.fieldName === "repeater", ) as IRepeaterField[]; repeaterFields.forEach((repeater: IRepeaterField) => { let aliasCounter: Record = {}; const elements: IFileUploadField[] = []; repeater.groupElements.forEach((element) => { for (const [_, existingField] of element.entries()) { if (existingField.fieldName === "file_upload") { elements.push(existingField as IFileUploadField); } } }); const fileElements = elements.map((f: IFileUploadField) => { const currentIndex = aliasCounter[f.alias] ?? 0; const uniqueAlias = `${f.alias}_${currentIndex}`; aliasCounter[f.alias] = currentIndex + 1; if (!f.options) f.options = { value: undefined }; if (window.ccb_rep_files) { f.options.value = window.ccb_rep_files[uniqueAlias]; } f.customAlias = uniqueAlias; f.inRepeater = true; return f; }); pureFields.push(...fileElements); }); const groupFields: IGroupField[] = fieldStore.getFields.filter( (f) => f.fieldName === "group", ) as IGroupField[]; groupFields.forEach((group: IGroupField) => { group.groupElements.forEach((element) => { for (const [_, existingField] of element.entries()) { if (existingField.fieldName === "file_upload") { pureFields.push(existingField as IFileUploadField); } } }); }); const result: IFileData[] = []; for (const field of pureFields) { if (field.alias.includes("file_upload")) { result.push({ alias: field.customAlias || field.alias, files: (field.inRepeater ? field.options?.value : field.files) || [], inRepeater: field.inRepeater || false, }); } } return result; }; const getClearFields = (): Field[] => { const fieldStore = useFieldsStore(); const fields = fieldStore.getFields; const result: Field[] = []; for (const section of fields) { if (!("fields" in section && section.fields)) continue; for (const field of Array.from(section.fields.values()) as Field[]) { if ( field.fieldName === "group" && "groupElements" in field && !field.hidden ) { const groupField = field as IGroupField; groupField.groupElements.forEach( (groupFieldMap: Map) => { Array.from(groupFieldMap.values()).forEach( (groupInnerField: Field) => { if ( groupInnerField.alias && !["total", "html", "line", "page_break"].includes( groupInnerField.fieldName, ) && (groupInnerField.fieldName === "repeater" || groupInnerField.addToSummary === true || groupInnerField.addToSummary === false || groupInnerField.alias.indexOf("file") !== -1) && (!groupInnerField.hidden || groupInnerField.calculateHidden) && hideEmptyFields(groupInnerField) ) { result.push(groupInnerField); } }, ); }, ); continue; } if ( field.fieldName === "repeater" && "groupElements" in field && (!field.hidden || ("calculateHidden" in field && field.calculateHidden)) ) { const repeaterField = field as IRepeaterField; result.push(field); repeaterField.groupElements.forEach((repMap) => { for (const [_, innerField] of repMap.entries()) { if ( innerField.alias && !["total", "html", "line", "page_break"].includes( innerField.fieldName, ) && (innerField.addToSummary === true || innerField.addToSummary === false || innerField.alias.indexOf("file") !== -1) && (!innerField.hidden || ("calculateHidden" in innerField && innerField.calculateHidden)) && hideEmptyFields(innerField) ) { result.push(innerField); } } }); continue; } if ( field.alias && !["total", "html", "line", "page_break"].includes(field.fieldName) && (field.fieldName === "repeater" || field.addToSummary === true || field.addToSummary === false || field.alias.indexOf("file") !== -1) && (!field.hidden || field.calculateHidden) && hideEmptyFields(field) ) { result.push(field); } } } return result; }; const getOrderDetails = (): IOrderDetails => { const filteredFields: Field[] = getClearFields(); const includeFieldsAliases: string[] = filteredFields.map( (f: Field) => f.alias, ); const excludedFields = filteredFields.filter( (f: Field) => f.alias && !includeFieldsAliases.includes(f.alias), ); return { fields: orderDetailsHelper(filteredFields), hiddenFields: orderDetailsHelper(excludedFields), }; }; const orderDetailsHelper = (fields: Field[]): IOrderDetailsType => { const data: IOrderDetailsType = []; const settingsStore = useSettingsStore(); const generalSettings = settingsStore.getGeneralSettings; for (const field of fields) { if (field.fieldName === "repeater") { const elements: Field[] = []; (field as IRepeaterField).groupElements.forEach((element) => { for (const [_, existingField] of element.entries()) { elements.push({ ...existingField } as Field); } }); data.push({ alias: field.alias, groupTitle: field.label, groupElements: orderDetailsHelper(elements), length: (field as IRepeaterField).groupElements?.length, value: field.displayValue, }); } else if (field.fieldName === "group") { (field as IGroupField).groupElements.forEach((element) => { for (const [_, existingField] of element.entries()) { const item: IOrderDetailsHelperResult = { ...existingField, title: existingField.label || "", originalValue: existingField.value || 0, value: existingField.displayValue?.toString() || "", options: "options" in existingField ? (existingField.options as IOptions[]).map( (option: IOptions) => ({ label: option.optionText || "", value: option.optionConverted || "", converted: option.optionConverted || "", }), ) : [], }; if ("options" in item && Array.isArray(item.options)) { if ( "selectedOption" in item && Array.isArray(item.selectedOption) ) { item.options = item.selectedOption.map((option: IOptions) => ({ label: option.optionText, value: option.optionConverted, converted: option.optionConverted, optionValue: option.optionValue, })); } } data.push(item); } }); } else { const item: IOrderDetailsHelperResult = { alias: field.alias, title: field.label, value: field.displayValue, addToSummary: field.addToSummary, originalValue: field.value || 0, ...(field.fieldName === "file_upload" && (field.value || 0) <= 0 && !generalSettings?.hideEmptyForOrdersPdfEmails ? { hideOrderPdf: true, } : {}), ...(typeof field.repeaterIdx !== "undefined" ? { idx: field.repeaterIdx, } : {}), ...(typeof field.repeaterAlias !== "undefined" ? { repeaterAlias: field.repeaterAlias, } : {}), ...(field.fieldName === "geolocation" && "geoType" in field ? { geoType: field.geoType, } : {}), }; if (field.fieldName === "geolocation" && "geoType" in field) { const hasUserSelectedOptions = "userSelectedOptions" in field && field.userSelectedOptions && typeof field.userSelectedOptions === "object"; if ( hasUserSelectedOptions && (field.geoType === "twoPointLocation" || field.geoType === "userLocation") ) { type TwoPointLocation = { twoPoints?: { from?: { addressName?: string; addressLink?: string }; to?: { addressName?: string; addressLink?: string }; }; distance_view?: string; }; type SingleLocation = { addressLink?: string; }; const selectedOptions = field.userSelectedOptions as Partial< TwoPointLocation & SingleLocation >; if ( field.geoType === "twoPointLocation" && selectedOptions.twoPoints ) { const from = selectedOptions.twoPoints.from || {}; const to = selectedOptions.twoPoints.to || {}; const distanceView = selectedOptions.distance_view || ""; item.options = [ { label: from.addressName || "", value: from.addressLink || "", addressLink: from.addressLink || "", converted: "", }, { label: to.addressName || "", value: "", addressLink: to.addressLink || "", converted: "", }, { label: "", distanceView: distanceView, value: "", converted: "", }, ]; } else { const label0 = Array.isArray(field.extraDisplayView) ? field.extraDisplayView?.[0] || "" : ""; const converted = Array.isArray(field.extraDisplayView) ? field.extraDisplayView?.[1] || "" : ""; const addressLink = selectedOptions.addressLink || ""; item.options = [ { label: label0, distanceView: converted, converted: "", value: "", addressLink: addressLink, }, ]; } } } if ("selectedOption" in field && Array.isArray(field.selectedOption)) { item.options = field.selectedOption.map((o: IOptions) => ({ label: o.optionText, value: o.optionConverted, converted: o.optionConverted, optionValue: o.optionValue, })); } if ( "selectedOption" in field && field.selectedOption && !Array.isArray(field.selectedOption) ) { item.options = [ { label: field.selectedOption.optionText, value: field.selectedOption.optionConverted, converted: field.selectedOption.optionConverted, optionValue: field.selectedOption.optionValue, }, ]; } if ( field.alias.includes("file_upload") && !field.useCurrency && "extraDisplayView" in field ) { item.value = field.displayValue || ""; } else if ("options" in field && Array.isArray(field.options)) { if ("selectedOption" in field && Array.isArray(field.selectedOption)) { item.options = field.selectedOption.map((option: IOptions) => ({ label: option.optionText, value: option.optionConverted, converted: option.optionConverted, optionValue: option.optionValue, })); } } if ("summaryView" in field && field.summaryView) { item.summary_value = field.displayValue || ""; item.summary_view = field.summaryView; if ("extraDisplayView" in field) { item.extraView = field.extraDisplayView; } } if ("extraDisplayView" in field && field.extraDisplayView) { item.option_unit = field.extraDisplayView.toString(); } if (field.fieldName !== "line") { data.push(item); } } } return data; }; const makePayment = async ( type: PaymentMethods, orderId: number | undefined, ): Promise => { const submissionStore = useSubmissionStore(); const paymentStore = usePaymentStore(); const data: IOrderData = prepareOrderData(type); data.orderId = orderId; const createOrderParams: ISubmitsRequestParams = { data, nonce: getNonce("ccb_payment"), action: "ccb_payment", files: getUsedFiles(), }; const response: OrdersResponse = await handleSubmissionRequest(createOrderParams); if (response.success) { if (type === "paypal") { window.location.href = response.url || ""; } } paymentStore.updatePaymentType(""); submissionStore.setOrderId(response.orderId); return response; }; const makeWooCommercePayment = async ( type: PaymentMethods, orderId: number | undefined, ): Promise => { const submissionStore = useSubmissionStore(); const paymentStore = usePaymentStore(); const data: IOrderData = prepareOrderData(type); data.orderId = orderId; const createOrderParams: ISubmitsRequestParams = { data, nonce: getNonce("ccb_woocommerce_payment"), action: "ccb_woocommerce_payment", files: getUsedFiles(), }; const response: OrdersResponse = await handleSubmissionRequest(createOrderParams); paymentStore.updatePaymentType(""); submissionStore.setOrderId(response.orderId); const settingsStore = useSettingsStore(); if (settingsStore.getWooCheckoutSettings?.redirectTo !== "stay") { window.location.href = response.page || ""; return { success: true, message: "Redirecting...", }; } else if (settingsStore.getWooCheckoutSettings?.redirectTo === "stay") { return { success: true, message: "Stay on page", }; } return response; }; const createOrder = async ( type: PaymentMethods, orderInputs?: ContactFormFields[], ): Promise => { const settingsStore = useSettingsStore(); const submissionStore = useSubmissionStore(); const paymentAfterSubmitStore = usePaymentAfterSubmitStore(); const data: IOrderData = prepareOrderData(type, orderInputs); const orderFormStore = useOrderFormStore(); if (type === "no_payments" && settingsStore.getRecaptchaSettings?.enable) { const token = orderFormStore.getCaptchaToken; if (!token) { return { success: false, message: "Captcha token is required", }; } data.captcha = { token, captchaSend: true, }; orderFormStore.setCaptchaToken(""); } if (type === "woocommerce") { if (!settingsStore.getWooCheckoutSettings?.productId) { return { success: false, message: "Invalid Product Id", }; } const productId = settingsStore.getWooCheckoutSettings?.productId; const currentProductId = settingsStore.getWooProductsSettings?.currentProductId; data.product_id = productId === "current_product" ? currentProductId : +productId; if (settingsStore.getWooProductsSettings?.isVariable) { const attributes = settingsStore.getWooProductsSettings?.attributeKeys; if (attributes?.length && attributes.length > 0) { if (!data.product_attributes) { data.product_attributes = {}; } for (const attribute of attributes) { const attr: HTMLInputElement | null = document.querySelector( `[name^="${attribute}"]`, ); if (attr) { data.product_attributes[attribute] = attr.value; } } } } } if ( paymentAfterSubmitStore.isPaymentAfterSubmit && paymentAfterSubmitStore.getLastOrderId && type && type !== "no_payments" ) { submissionStore.setSendPaymentType(type); if (type === "woocommerce") { return await makeWooCommercePayment( type, paymentAfterSubmitStore.getLastOrderId, ); } return await makePayment(type, paymentAfterSubmitStore.getLastOrderId); } else { data.isPaymentAfterSubmit = paymentAfterSubmitStore.isPaymentAfterSubmit; data.orderId = paymentAfterSubmitStore.getLastOrderId; const createOrderParams: ISubmitsRequestParams = { data, nonce: getNonce("ccb_add_order"), action: "create_cc_order", files: getUsedFiles(), }; const response: OrdersResponse = await handleSubmissionRequest(createOrderParams); if (!response.success) { return response; } if (response.success) { if (type === "paypal") { window.location.href = response.url || ""; } } if ( paymentAfterSubmitStore.isPaymentAfterSubmit && type === "no_payments" && response.data ) { paymentAfterSubmitStore.setLastOrderId(response.data.orderId); paymentAfterSubmitStore.setSubmit(true); } else if ( type === "woocommerce" && settingsStore.getWooCheckoutSettings?.redirectTo !== "stay" ) { window.location.href = response.page || ""; return { success: true, message: "Redirecting...", }; } else if ( type === "woocommerce" && settingsStore.getWooCheckoutSettings?.redirectTo === "stay" ) { return { success: true, message: "Stay on page", }; } else if ( paymentAfterSubmitStore.isPaymentAfterSubmit && type && type !== "no_payments" ) { const paymentStore = usePaymentStore(); paymentAfterSubmitStore.setSubmit(false); paymentStore.updatePaymentType(""); } submissionStore.setSendPaymentType(type); submissionStore.setOrderId( response.orderId ? response.orderId : response.data?.orderId, ); return response; } }; const razorpayPaymentReceived = async ( orderId: number | undefined, currency: string, ): Promise => { const data: IOrderData = prepareOrderData("razorpay"); data.orderId = orderId; data.paymentCurrency = currency; const createOrderParams: ISubmitsRequestParams = { data, nonce: getNonce("ccb_razorpay_receive"), action: "ccb_razorpay_payment_received", }; await handleSubmissionRequest(createOrderParams); }; const hideEmptyFields = (existingField: Field): string | boolean => { const settings = useSettingsStore(); if (!settings.general?.hideEmptyForOrdersPdfEmails) { if ( "selectedOption" in existingField && Array.isArray(existingField.selectedOption) ) { if (existingField.summaryView === "show_value") { let sum = 0; for (const option of existingField.selectedOption) { let parts: string[] = []; if (option.optionValue?.includes("|")) { parts = option.optionValue.split("|"); } if (!parts.length && option.optionValue?.includes("_")) { parts = option.optionValue.split("_"); } sum += +parts?.[0] || 0; } return sum > 0; } return (existingField.selectedOption.length > 0) as boolean; } else if ( "selectedOption" in existingField && !Array.isArray(existingField.selectedOption) && existingField.selectedOption ) { if (existingField.summaryView === "show_value") { let parts: string[] = []; if (existingField.selectedOption.optionValue?.includes("|")) { parts = existingField.selectedOption.optionValue.split("|"); } if (existingField.selectedOption.optionValue?.includes("_")) { parts = existingField.selectedOption.optionValue.split("_"); } return (+parts?.[0] || 0) as unknown as boolean; } return true; } else if (["validated_form", "text"].includes(existingField.fieldName)) { return (existingField.displayValue || "") as string; } else if ( existingField.fieldName === "geolocation" && "geoType" in existingField && existingField.geoType === "multiplyLocation" ) { return (existingField.displayValue || "") as string; } else if ( (existingField.fieldName === "datePicker" && existingField.displayValue.length > 0) || (existingField.fieldName === "timePicker" && existingField.displayValue.length > 0) ) { return (existingField.displayValue || "") as string; } else if ( existingField.fieldName === "file_upload" && "allowPrice" in existingField && "files" in existingField && Array.isArray(existingField.files) && existingField.files.length > 0 ) { return (existingField.files.length > 0) as boolean; } else { return (existingField.value || "") as string; } } return true; }; export function useOrder(): IOrderResult { return { createOrder, makePayment, razorpayPaymentReceived, }; }