320 lines
9.1 KiB
TypeScript
320 lines
9.1 KiB
TypeScript
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<PaymentEvent, { type: "PaymentInit" }>,
|
|
): Partial<PaymentState> {
|
|
const planCatalog = event.catalog ?? context.planCatalog;
|
|
const giftCharacterId =
|
|
planCatalog === "tip"
|
|
? (event.characterId ?? context.giftCharacterId)
|
|
: null;
|
|
const catalogChanged = planCatalog !== context.planCatalog;
|
|
const characterChanged = giftCharacterId !== context.giftCharacterId;
|
|
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;
|
|
|
|
return {
|
|
planCatalog,
|
|
...(event.payChannel ? { payChannel: event.payChannel } : {}),
|
|
giftCharacterId,
|
|
commercialOfferId,
|
|
requestedGiftCategory:
|
|
planCatalog === "tip" ? (event.category ?? null) : null,
|
|
requestedGiftPlanId:
|
|
planCatalog === "tip" ? (event.planId ?? null) : null,
|
|
...(catalogChanged || characterChanged || selectionChanged || offerChanged
|
|
? {
|
|
...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<PaymentPlansResponse | null>
|
|
>();
|
|
const plansDoneSetup =
|
|
createPaymentActorActionSetup<DoneActorEvent<PaymentPlansResponse>>();
|
|
const giftProductsDoneSetup =
|
|
createPaymentActorActionSetup<DoneActorEvent<GiftProductsResponse>>();
|
|
const plansErrorSetup = createPaymentActorActionSetup<ErrorActorEvent>();
|
|
|
|
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,
|
|
}),
|
|
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,
|
|
}),
|
|
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,
|
|
}),
|
|
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" },
|
|
},
|
|
});
|