UNPKG

@techsio/storefront-data

Version:

Medusa-first storefront data layer built on TanStack Query.

232 lines (231 loc) • 10.6 kB
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { getCachedCartById } from "../shared/cart-cache-sync.js"; import { resolveCheckoutCartInput, resolveEffectiveCheckoutCart, resolveExistingPaymentCollection, resolveSelectedPaymentProviderId } from "../shared/checkout-flow-utils.js"; import { createErrorWithStage } from "../shared/error-utils.js"; import { toPlainRecord } from "../shared/object-utils.js"; import { createMedusaCartFlow } from "./cart-flow.js"; const defaultNormalizeShippingData = (data)=>{ if (!data) return; const entries = Object.entries(data).filter(([, value])=>null != value && "" !== value); if (0 === entries.length) return; return Object.fromEntries(entries); }; const toComparableShippingData = (data)=>JSON.stringify(Object.entries(data ?? {}).filter(([, value])=>null != value && "" !== value).sort(([left], [right])=>left.localeCompare(right))); const isSameShippingSelection = ({ selectedOptionId, nextOptionId, nextData, currentData })=>{ if (selectedOptionId !== nextOptionId) return false; return toComparableShippingData(nextData) === toComparableShippingData(toPlainRecord(currentData)); }; const defaultResolvePaymentProviderId = ({ requestedPaymentProviderId, existingPaymentProviderId, paymentProviders })=>requestedPaymentProviderId ?? existingPaymentProviderId ?? paymentProviders[0]?.id ?? null; const resolvePaymentProviderSelection = (context, resolvePaymentProviderId)=>{ const resolvedPaymentProviderId = resolvePaymentProviderId?.(context); if (void 0 !== resolvedPaymentProviderId) return { paymentProviderId: resolvedPaymentProviderId, wasExplicit: true }; return { paymentProviderId: defaultResolvePaymentProviderId(context), wasExplicit: false }; }; const resolveCheckoutEffectiveCart = ({ cartId, cart, getCachedCart })=>resolveEffectiveCheckoutCart({ cartId, cart, getCachedCart }); const ensureCheckoutPaymentCollection = ({ effectiveCart, paymentProviderId, initiatePaymentAsync })=>{ const existingPaymentCollection = resolveExistingPaymentCollection(effectiveCart, paymentProviderId); if (existingPaymentCollection) return Promise.resolve(existingPaymentCollection); return initiatePaymentAsync(paymentProviderId); }; const completeCheckoutOrder = async ({ cartId, completeCartMutation })=>{ const result = await completeCartMutation({ cartId }); if ("order" !== result.type) throw result.error; return result.order; }; function createMedusaCheckoutFlow({ storefront, cartStorage, isActiveCartQueryKey }) { const checkoutHooks = storefront.hooks.checkout; const cartFlow = createMedusaCartFlow({ storefront, cartStorage, isActiveCartQueryKey }); function useCheckoutShipping(cartId, cart, options) { const { resolvedCartId, normalizedCart } = resolveCheckoutCartInput({ cartId, cart }); const normalizeShippingData = options?.normalizeShippingData ?? defaultNormalizeShippingData; const canLoadShipping = Boolean(resolvedCartId); const shipping = checkoutHooks.useCheckoutShipping({ cartId: resolvedCartId, cart: normalizedCart, enabled: options?.enabled ?? canLoadShipping, calculatePrices: options?.calculatePrices }, { onSuccess: (updatedCart)=>{ options?.onSuccess?.(updatedCart); }, onError: (error)=>{ options?.onError?.(error); } }); const setShipping = (optionId, data)=>{ const cleanedData = normalizeShippingData(data); const currentData = shipping.selectedShippingMethodId === optionId ? shipping.selectedShippingMethodData : void 0; if (isSameShippingSelection({ selectedOptionId: shipping.selectedShippingMethodId, nextOptionId: optionId, nextData: cleanedData, currentData })) return; shipping.setShippingMethod(optionId, cleanedData); }; const { shippingOptions, shippingPrices, isLoading, isFetching, isCalculating, isSettingShipping, selectedShippingMethodId, selectedOption } = shipping; return { shippingOptions, shippingPrices, isLoading, isFetching, isCalculating, setShipping, isSettingShipping, canLoadShipping, canSetShipping: shippingOptions.length > 0, selectedShippingMethodId, selectedOption }; } function useCheckoutPayment(cartId, regionId, cart, options) { const { resolvedCartId, normalizedCart } = resolveCheckoutCartInput({ cartId, cart }); const payment = checkoutHooks.useCheckoutPayment({ cartId: resolvedCartId, regionId, cart: normalizedCart, enabled: options?.enabled ?? Boolean(resolvedCartId) }, { onSuccess: (paymentCollection)=>{ options?.onSuccess?.(paymentCollection); }, onError: (error)=>{ options?.onError?.(error); } }); const { paymentProviders, initiatePayment, initiatePaymentAsync, isInitiatingPayment, isLoading, isFetching, canInitiatePayment, hasPaymentCollection, hasPaymentSessions } = payment; return { paymentProviders, initiatePayment, initiatePaymentAsync, isInitiatingPayment, isLoading, isFetching, canInitiatePayment, hasPaymentCollection, hasPaymentSessions }; } const resolveCheckoutPaymentProviderId = async ({ effectiveCart, effectiveRegionId, requestedPaymentProviderId, paymentProviders, resolvePaymentProviderId, queryClient })=>{ const existingPaymentProviderId = resolveSelectedPaymentProviderId(effectiveCart); const initialSelection = resolvePaymentProviderSelection({ cart: effectiveCart, existingPaymentProviderId, paymentProviders, requestedPaymentProviderId }, resolvePaymentProviderId); if (null != initialSelection.paymentProviderId) return initialSelection.paymentProviderId; if (initialSelection.wasExplicit || !effectiveRegionId) throw new Error("No payment provider available"); const refreshedPaymentProviders = await checkoutHooks.fetchPaymentProviders(queryClient, effectiveRegionId); const refreshedSelection = resolvePaymentProviderSelection({ cart: effectiveCart, existingPaymentProviderId, paymentProviders: refreshedPaymentProviders, requestedPaymentProviderId }, resolvePaymentProviderId); if (!refreshedSelection.paymentProviderId) throw new Error("No payment provider available"); return refreshedSelection.paymentProviderId; }; function useCompleteCheckout(input, options) { const queryClient = useQueryClient(); const { resolvedCartId, normalizedCart } = resolveCheckoutCartInput({ cartId: input.cartId, cart: input.cart }); const payment = useCheckoutPayment(resolvedCartId, input.regionId, normalizedCart, { enabled: input.enabled ?? Boolean(resolvedCartId) }); const completeCart = cartFlow.useCompleteCart(); const { paymentProviders, initiatePaymentAsync } = payment; const { mutateAsync: completeCartAsync } = completeCart; const resolvePaymentProviderId = options?.resolvePaymentProviderId; return useMutation({ mutationFn: async (request)=>{ const { resolvedCartId: mutationCartId, normalizedCart: mutationCart } = resolveCheckoutCartInput({ cartId: input.cartId, cart: input.cart }); if (!mutationCartId) throw createErrorWithStage("cart", "Cart id is required"); const effectiveCart = resolveCheckoutEffectiveCart({ cartId: mutationCartId, cart: mutationCart, getCachedCart: (effectiveCartId)=>getCachedCartById(queryClient, storefront.queryKeys.cart, effectiveCartId, { isActiveCartQueryKey }) ?? null }); const effectiveRegionId = input.regionId ?? effectiveCart?.region_id ?? void 0; let paymentProviderId; try { paymentProviderId = await resolveCheckoutPaymentProviderId({ effectiveCart, effectiveRegionId, requestedPaymentProviderId: request?.paymentProviderId, paymentProviders, resolvePaymentProviderId, queryClient }); } catch (error) { throw createErrorWithStage("payment_provider", "Failed to resolve payment provider", error); } let paymentCollection; try { paymentCollection = await ensureCheckoutPaymentCollection({ effectiveCart, paymentProviderId, initiatePaymentAsync }); } catch (error) { throw createErrorWithStage("payment", "Failed to initiate payment", error); } let order; try { order = await completeCheckoutOrder({ cartId: mutationCartId, completeCartMutation: completeCartAsync }); } catch (error) { throw createErrorWithStage("complete", "Failed to complete checkout", error); } return { order, paymentCollection, paymentProviderId }; }, onSuccess: (result)=>{ options?.onSuccess?.(result); }, onError: (error)=>{ options?.onError?.(error); } }); } return { useCheckoutShipping, useCheckoutPayment, useCompleteCheckout }; } export { createMedusaCheckoutFlow };