178 lines
4.3 KiB
TypeScript
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,
|
|
};
|
|
}
|