import type { DoneActorEvent, ErrorActorEvent } from "xstate"; import type { GiftProductsResponse, PaymentPlansResponse, } from "@/data/schemas/payment"; import { consumeFirstRechargeState, hydrateGiftProductsState, hydratePlansResponseState, refreshPlansResponseState, selectPlanState, } from "../helper"; import { resetOrderState, toPaymentErrorMessage } from "../helper/order"; import type { PaymentEvent } from "../payment-events"; import type { PaymentState } from "../payment-state"; import { basePaymentMachineSetup, createPaymentActorActionSetup, } from "./setup"; function initializeCatalogState( context: PaymentState, event: Extract, ): Partial { const planCatalog = event.catalog ?? context.planCatalog; const giftCharacterId = planCatalog === "tip" ? (event.characterId ?? context.giftCharacterId) : null; const supportCharacterId = planCatalog === "default" ? (event.characterId ?? context.supportCharacterId) : null; const catalogChanged = planCatalog !== context.planCatalog; const characterChanged = giftCharacterId !== context.giftCharacterId || supportCharacterId !== context.supportCharacterId; const selectionChanged = planCatalog === "tip" && ((event.category ?? null) !== context.selectedGiftCategory || (event.planId ?? null) !== (context.selectedPlanId || null)); const commercialOfferId = planCatalog === "default" ? (event.commercialOfferId ?? null) : null; const offerChanged = commercialOfferId !== context.commercialOfferId; const chatActionId = event.chatActionId ?? null; const chatActionChanged = chatActionId !== context.chatActionId; return { planCatalog, ...(event.payChannel ? { payChannel: event.payChannel } : {}), giftCharacterId, supportCharacterId, commercialOfferId, chatActionId, requestedGiftCategory: planCatalog === "tip" ? (event.category ?? null) : null, requestedGiftPlanId: planCatalog === "tip" ? (event.planId ?? null) : null, ...(catalogChanged || characterChanged || selectionChanged || offerChanged || chatActionChanged ? { ...resetOrderState(), plans: [], giftCategories: [], giftProducts: [], selectedGiftCategory: null, selectedPlanId: planCatalog === "default" ? (event.planId ?? "") : "", isFirstRecharge: false, commercialOffer: null, autoRenew: planCatalog !== "tip", } : {}), }; } const initializeCatalogAction = basePaymentMachineSetup.assign( ({ context, event }) => event.type === "PaymentInit" ? initializeCatalogState(context, event) : {}, ); const refreshCatalogAction = basePaymentMachineSetup.assign( ({ context, event }) => event.type === "PaymentInit" ? initializeCatalogState(context, event) : {}, ); const selectPlanAction = basePaymentMachineSetup.assign( ({ context, event }) => event.type === "PaymentPlanSelected" ? selectPlanState(event.planId, context.plans) : {}, ); const changePayChannelAction = basePaymentMachineSetup.assign(({ event }) => event.type === "PaymentPayChannelChanged" ? { ...resetOrderState(), payChannel: event.payChannel, errorMessage: null, } : {}, ); const changeAutoRenewAction = basePaymentMachineSetup.assign(({ event }) => event.type === "PaymentAutoRenewChanged" ? { autoRenew: event.autoRenew, errorMessage: null } : {}, ); const changeAgreementAction = basePaymentMachineSetup.assign(({ event }) => event.type === "PaymentAgreementChanged" ? { agreed: event.agreed, errorMessage: null } : {}, ); const showCreateOrderValidationErrorAction = basePaymentMachineSetup.assign({ errorMessage: "Choose a plan and accept the agreement before continuing.", }); const clearPaymentErrorAction = basePaymentMachineSetup.assign({ errorMessage: null, }); const resetOrderAction = basePaymentMachineSetup.assign(() => resetOrderState(), ); const consumeFirstRechargeAction = basePaymentMachineSetup.assign( ({ context }) => consumeFirstRechargeState(context), ); export const catalogMachineSetup = basePaymentMachineSetup.extend({ actions: { initializeCatalog: initializeCatalogAction, refreshCatalog: refreshCatalogAction, selectPlan: selectPlanAction, changePayChannel: changePayChannelAction, changeAutoRenew: changeAutoRenewAction, changeAgreement: changeAgreementAction, showCreateOrderValidationError: showCreateOrderValidationErrorAction, clearPaymentError: clearPaymentErrorAction, resetOrder: resetOrderAction, consumeFirstRecharge: consumeFirstRechargeAction, }, guards: { isTipInit: ({ context, event }) => event.type === "PaymentInit" && (event.catalog ?? context.planCatalog) === "tip", isTipCatalog: ({ context }) => context.planCatalog === "tip", }, }); const cachedPlansDoneSetup = createPaymentActorActionSetup< DoneActorEvent >(); const plansDoneSetup = createPaymentActorActionSetup>(); const giftProductsDoneSetup = createPaymentActorActionSetup>(); const plansErrorSetup = createPaymentActorActionSetup(); const hydrateCachedPlansAction = cachedPlansDoneSetup.assign( ({ context, event }) => event.output ? hydratePlansResponseState(event.output, context.selectedPlanId) : {}, ); const hydratePlansAction = plansDoneSetup.assign(({ context, event }) => hydratePlansResponseState(event.output, context.selectedPlanId), ); const refreshPlansAction = plansDoneSetup.assign(({ context, event }) => refreshPlansResponseState(event.output, context.selectedPlanId), ); const hydrateGiftProductsAction = giftProductsDoneSetup.assign( ({ context, event }) => hydrateGiftProductsState( event.output, context.giftCharacterId ?? "", context.requestedGiftCategory ?? context.selectedGiftCategory, context.requestedGiftPlanId ?? context.selectedPlanId, ), ); const applyPlansErrorAction = plansErrorSetup.assign(({ event }) => ({ errorMessage: toPaymentErrorMessage(event.error), })); const applyGiftProductsErrorAction = plansErrorSetup.assign(({ event }) => ({ plans: [], giftCategories: [], giftProducts: [], selectedGiftCategory: null, selectedPlanId: "", errorMessage: toPaymentErrorMessage(event.error), })); export const idleState = catalogMachineSetup.createStateConfig({ on: { PaymentInit: [ { guard: "isTipInit", target: "loadingGiftProducts", actions: "initializeCatalog", }, { target: "loadingCachedPlans", actions: "initializeCatalog", }, ], }, }); export const loadingCachedPlansState = catalogMachineSetup.createStateConfig({ invoke: { src: "loadCachedPlans", input: ({ context }) => ({ catalog: context.planCatalog, commercialOfferId: context.commercialOfferId, supportCharacterId: context.supportCharacterId, }), onDone: [ { guard: ({ event }) => (event.output?.plans.length ?? 0) > 0, target: "refreshingPlans", actions: hydrateCachedPlansAction, }, { target: "loadingPlans" }, ], onError: { target: "loadingPlans" }, }, }); export const loadingPlansState = catalogMachineSetup.createStateConfig({ invoke: { src: "refreshPlans", input: ({ context }) => ({ catalog: context.planCatalog, commercialOfferId: context.commercialOfferId, supportCharacterId: context.supportCharacterId, }), onDone: { target: "ready", actions: hydratePlansAction, }, onError: { target: "ready", actions: applyPlansErrorAction, }, }, }); export const refreshingPlansState = catalogMachineSetup.createStateConfig({ invoke: { src: "refreshPlans", input: ({ context }) => ({ catalog: context.planCatalog, commercialOfferId: context.commercialOfferId, supportCharacterId: context.supportCharacterId, }), onDone: { target: "ready", actions: refreshPlansAction, }, onError: { target: "ready", actions: applyPlansErrorAction, }, }, }); export const loadingGiftProductsState = catalogMachineSetup.createStateConfig({ invoke: { src: "loadGiftProducts", input: ({ context }) => ({ characterId: context.giftCharacterId ?? "", }), onDone: { target: "ready", actions: hydrateGiftProductsAction, }, onError: { target: "ready", actions: applyGiftProductsErrorAction, }, }, }); export const readyState = catalogMachineSetup.createStateConfig({ on: { PaymentInit: [ { guard: "isTipInit", target: "loadingGiftProducts", actions: "refreshCatalog", }, { target: "refreshingPlans", actions: "refreshCatalog", }, ], PaymentCatalogRetryRequested: [ { guard: "isTipCatalog", target: "loadingGiftProducts", }, { target: "refreshingPlans" }, ], PaymentPlanSelected: { actions: "selectPlan" }, PaymentPayChannelChanged: { actions: "changePayChannel" }, PaymentAutoRenewChanged: { actions: "changeAutoRenew" }, PaymentAgreementChanged: { actions: "changeAgreement" }, PaymentCreateOrderSubmitted: [ { guard: "canCreateOrder", target: "creatingOrder", }, { actions: "showCreateOrderValidationError" }, ], PaymentErrorCleared: { actions: "clearPaymentError" }, }, });