diff --git a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts index def72dd..89c8dc3 100644 --- a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts +++ b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts @@ -7,13 +7,15 @@ import { loadCurrentUserAccount } from "@/app/home/(user)/_lib/server/load-user- import { listProductTypes } from "@lib/data/products"; import { placeOrder, retrieveCart } from "@lib/data/cart"; import { createI18nServerInstance } from "~/lib/i18n/i18n.server"; -import { createAnalysisOrder } from '~/lib/services/order.service'; +import { createAnalysisOrder, getAnalysisOrder } from '~/lib/services/order.service'; import { getOrderedAnalysisIds, sendOrderToMedipost } from '~/lib/services/medipost.service'; import { createNotificationsApi } from '@kit/notifications/api'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; -import { AccountWithParams } from '@kit/accounts/api'; +import type { AccountWithParams } from '@kit/accounts/api'; +import type { StoreOrder } from '@medusajs/types'; const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages'; +const ANALYSIS_TYPE_HANDLE = 'synlab-analysis'; const MONTONIO_PAID_STATUS = 'PAID'; const env = () => z @@ -38,14 +40,12 @@ const sendEmail = async ({ account, email, analysisPackageName, - personName, partnerLocationName, language, }: { - account: AccountWithParams, + account: Pick, email: string, analysisPackageName: string, - personName: string, partnerLocationName: string, language: string, }) => { @@ -58,7 +58,7 @@ const sendEmail = async ({ const { html, subject } = await renderSynlabAnalysisPackageEmail({ analysisPackageName, - personName, + personName: account.name, partnerLocationName, language, }); @@ -83,9 +83,7 @@ const sendEmail = async ({ } } -export async function processMontonioCallback(orderToken: string) { - const { language } = await createI18nServerInstance(); - +async function decodeOrderToken(orderToken: string) { const secretKey = process.env.MONTONIO_SECRET_KEY as string; const decoded = jwt.verify(orderToken, secretKey, { @@ -96,50 +94,115 @@ export async function processMontonioCallback(orderToken: string) { throw new Error("Payment not successful"); } + return decoded; +} + +async function getCartByOrderToken(decoded: MontonioOrderToken) { + const [, , cartId] = decoded.merchantReferenceDisplay.split(':'); + if (!cartId) { + throw new Error("Cart ID not found"); + } + const cart = await retrieveCart(cartId); + if (!cart) { + throw new Error("Cart not found"); + } + return cart; +} + +async function getOrderResultParameters(medusaOrder: StoreOrder) { + const { productTypes } = await listProductTypes(); + const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE); + const analysisType = productTypes.find(({ metadata }) => metadata?.handle === ANALYSIS_TYPE_HANDLE); + + const analysisPackageOrderItem = medusaOrder.items?.find(({ product_type_id }) => product_type_id === analysisPackagesType?.id); + const analysisItems = medusaOrder.items?.filter(({ product_type_id }) => product_type_id === analysisType?.id); + + return { + medusaOrderId: medusaOrder.id, + email: medusaOrder.email, + analysisPackageOrder: analysisPackageOrderItem + ? { + partnerLocationName: analysisPackageOrderItem?.metadata?.partner_location_name as string ?? '', + analysisPackageName: analysisPackageOrderItem?.title ?? '', + } + : null, + analysisItemsOrder: Array.isArray(analysisItems) && analysisItems.length > 0 + ? analysisItems.map(({ product }) => ({ + analysisName: product?.title ?? '', + analysisId: product?.metadata?.analysisIdOriginal as string ?? '', + })) + : null, + }; +} + +async function sendAnalysisPackageOrderEmail({ + account, + email, + analysisPackageOrder, +}: { + account: AccountWithParams, + email: string, + analysisPackageOrder: { + partnerLocationName: string, + analysisPackageName: string, + }, +}) { + const { language } = await createI18nServerInstance(); + const { analysisPackageName, partnerLocationName } = analysisPackageOrder; + try { + await sendEmail({ + account: { id: account.id, name: account.name }, + email, + analysisPackageName, + partnerLocationName, + language, + }); + } catch (error) { + console.error("Failed to send email", error); + } +} + +export async function processMontonioCallback(orderToken: string) { const account = await loadCurrentUserAccount(); if (!account) { throw new Error("Account not found in context"); } try { - const [, , cartId] = decoded.merchantReferenceDisplay.split(':'); - if (!cartId) { - throw new Error("Cart ID not found"); - } + const decoded = await decodeOrderToken(orderToken); + const cart = await getCartByOrderToken(decoded); - const cart = await retrieveCart(cartId); - if (!cart) { - throw new Error("Cart not found"); - } - - const medusaOrder = await placeOrder(cartId, { revalidateCacheTags: false }); + const medusaOrder = await placeOrder(cart.id, { revalidateCacheTags: false }); const orderedAnalysisElements = await getOrderedAnalysisIds({ medusaOrder }); + + try { + const existingAnalysisOrder = await getAnalysisOrder({ medusaOrderId: medusaOrder.id }); + console.info(`Analysis order already exists for medusaOrderId=${medusaOrder.id}, orderId=${existingAnalysisOrder.id}`); + return { success: true, orderId: existingAnalysisOrder.id }; + } catch { + // ignored + } + const orderId = await createAnalysisOrder({ medusaOrder, orderedAnalysisElements }); + const orderResult = await getOrderResultParameters(medusaOrder); - const { productTypes } = await listProductTypes(); - const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE); - const analysisPackageOrderItem = medusaOrder.items?.find(({ product_type_id }) => product_type_id === analysisPackagesType?.id); + const { medusaOrderId, email, analysisPackageOrder, analysisItemsOrder } = orderResult; - const orderResult = { - medusaOrderId: medusaOrder.id, - email: medusaOrder.email, - partnerLocationName: analysisPackageOrderItem?.metadata?.partner_location_name as string ?? '', - analysisPackageName: analysisPackageOrderItem?.title ?? '', - orderedAnalysisElements, - }; + if (email) { + if (analysisPackageOrder) { + await sendAnalysisPackageOrderEmail({ account, email, analysisPackageOrder }); + } else { + console.info(`Order has no analysis package, skipping email.`); + } - const { medusaOrderId, email, partnerLocationName, analysisPackageName } = orderResult; - const personName = account.name; - - if (email && analysisPackageName) { - try { - await sendEmail({ account, email, analysisPackageName, personName, partnerLocationName, language }); - } catch (error) { - console.error("Failed to send email", error); + if (analysisItemsOrder) { + // @TODO send email for separate analyses + console.warn(`Order has analysis items, but no email template exists yet`); + } else { + console.info(`Order has no analysis items, skipping email.`); } } else { - // @TODO send email for separate analyses - console.error("Missing email or analysisPackageName", orderResult); + console.error("Missing email to send order result email", orderResult); } await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements });