Files
cozsweet-frontend-nextjs/src/stores/payment/payment-machine.helpers.ts
T

178 lines
4.3 KiB
TypeScript

import { ExceptionHandler } from "@/core/errors";
import { PaymentPlan, type PaymentPlansResponse } from "@/data/dto/payment";
import type { PaymentState } from "./payment-state";
export const MAX_ORDER_POLLING_MS = 5 * 60 * 1000;
export const PAYMENT_TIMEOUT_ERROR_MESSAGE =
"Payment timed out. Please try again.";
export function toPaymentErrorMessage(error: unknown): string {
return ExceptionHandler.message(error);
}
export function hasOrderPollingTimedOut(
startedAt: number | null,
now = Date.now(),
maxDurationMs = MAX_ORDER_POLLING_MS,
): boolean {
return startedAt !== null && now - startedAt >= maxDurationMs;
}
export function defaultAutoRenewForPlan(
planId: string,
plans: readonly PaymentPlan[] = [],
): boolean {
const plan = plans.find((item) => item.planId === planId);
if (plan?.dolAmount !== null && plan?.dolAmount !== undefined) return false;
return (
!planId.includes("lifetime") &&
!planId.startsWith("dol_") &&
!planId.startsWith("points_") &&
!planId.startsWith("credit_")
);
}
export function getDefaultPlanId(plans: readonly PaymentPlan[]): string {
return (
plans.find((plan) => plan.planId === "vip_monthly")?.planId ??
plans.find((plan) => plan.orderType.startsWith("vip_"))?.planId ??
plans[0]?.planId ??
""
);
}
export function hydratePlansState(
plans: PaymentPlan[],
selectedPlanId: string,
): Pick<
PaymentState,
"plans" | "selectedPlanId" | "autoRenew" | "errorMessage"
> {
const nextSelectedPlanId = selectedPlanId || getDefaultPlanId(plans);
return {
plans,
selectedPlanId: nextSelectedPlanId,
autoRenew: defaultAutoRenewForPlan(nextSelectedPlanId, plans),
errorMessage: null,
};
}
export function hydratePlansResponseState(
response: PaymentPlansResponse,
selectedPlanId: string,
): Pick<
PaymentState,
| "plans"
| "isFirstRecharge"
| "selectedPlanId"
| "autoRenew"
| "errorMessage"
> {
const plans = normalizeFirstRechargePlans(
response.plans,
response.isFirstRecharge,
);
return {
...hydratePlansState(plans, selectedPlanId),
isFirstRecharge: response.isFirstRecharge,
};
}
export function refreshPlansState(
plans: PaymentPlan[],
selectedPlanId: string,
): Pick<
PaymentState,
"plans" | "selectedPlanId" | "autoRenew" | "errorMessage"
> {
const nextSelectedPlanId = plans.some((plan) => plan.planId === selectedPlanId)
? selectedPlanId
: getDefaultPlanId(plans);
return {
plans,
selectedPlanId: nextSelectedPlanId,
autoRenew: defaultAutoRenewForPlan(nextSelectedPlanId, plans),
errorMessage: null,
};
}
export function refreshPlansResponseState(
response: PaymentPlansResponse,
selectedPlanId: string,
): Pick<
PaymentState,
| "plans"
| "isFirstRecharge"
| "selectedPlanId"
| "autoRenew"
| "errorMessage"
> {
const plans = normalizeFirstRechargePlans(
response.plans,
response.isFirstRecharge,
);
return {
...refreshPlansState(plans, selectedPlanId),
isFirstRecharge: response.isFirstRecharge,
};
}
export function normalizeFirstRechargePlans(
plans: readonly PaymentPlan[],
isFirstRecharge: boolean,
): PaymentPlan[] {
if (isFirstRecharge) return [...plans];
return plans.map(consumeFirstRechargePlan);
}
export function consumeFirstRechargePlan(plan: PaymentPlan): PaymentPlan {
const originalAmountCents = plan.originalAmountCents;
return PaymentPlan.from({
...plan.toJson(),
amountCents: originalAmountCents ?? plan.amountCents,
originalAmountCents: null,
isFirstRechargeOffer: false,
firstRechargeDiscountPercent: null,
promotionType: null,
});
}
export function consumeFirstRechargeState(
context: PaymentState,
): Pick<
PaymentState,
| "plans"
| "isFirstRecharge"
| "selectedPlanId"
| "autoRenew"
| "errorMessage"
> {
const plans = context.plans.map(consumeFirstRechargePlan);
return {
...refreshPlansState(plans, context.selectedPlanId),
isFirstRecharge: false,
};
}
export function selectPlanState(
planId: string,
plans: readonly PaymentPlan[],
): Partial<PaymentState> {
return {
...resetOrderState(),
selectedPlanId: planId,
autoRenew: defaultAutoRenewForPlan(planId, plans),
};
}
export function resetOrderState(): Partial<PaymentState> {
return {
currentOrderId: null,
payParams: null,
orderStatus: null,
orderPollingStartedAt: null,
errorMessage: null,
};
}