chore: cleanup logic
This commit is contained in:
@@ -28,15 +28,16 @@ export const linkScheduledCustomerProductsToSubscription = async ({
|
||||
env,
|
||||
});
|
||||
|
||||
let updatedCount = 0;
|
||||
const subscriptionStartMs = fromUnixTime(
|
||||
subscription.start_date ?? subscription.created,
|
||||
).getTime();
|
||||
let linkedCount = 0;
|
||||
|
||||
for (const cusProduct of cusProducts) {
|
||||
const subscriptionIds = cusProduct.subscription_ids ?? [];
|
||||
if (subscriptionIds.includes(subscription.id)) continue;
|
||||
|
||||
const nextSubscriptionIds = [...subscriptionIds, subscription.id];
|
||||
const subscriptionStartMs = fromUnixTime(
|
||||
subscription.start_date ?? subscription.created,
|
||||
).getTime();
|
||||
const hasStarted = hasCustomerProductStarted(cusProduct, {
|
||||
nowMs: subscriptionStartMs,
|
||||
});
|
||||
@@ -51,7 +52,7 @@ export const linkScheduledCustomerProductsToSubscription = async ({
|
||||
? cusProduct.scheduled_ids
|
||||
: [scheduleId],
|
||||
});
|
||||
updatedCount++;
|
||||
linkedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -69,12 +70,12 @@ export const linkScheduledCustomerProductsToSubscription = async ({
|
||||
subscription_ids: nextSubscriptionIds,
|
||||
},
|
||||
});
|
||||
updatedCount++;
|
||||
linkedCount++;
|
||||
}
|
||||
|
||||
if (updatedCount > 0) {
|
||||
if (linkedCount > 0) {
|
||||
logger.info(
|
||||
`[sub.created] linked ${updatedCount} scheduled customer product(s) to subscription ${subscription.id}`,
|
||||
`[sub.created] linked ${linkedCount} scheduled customer product(s) to subscription ${subscription.id}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,31 +26,6 @@ const getScheduledBillingCycleAnchorResetAt = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
const getStartsAt = ({
|
||||
startDate,
|
||||
isScheduled,
|
||||
endOfCycleMs,
|
||||
}: {
|
||||
startDate?: number;
|
||||
isScheduled: boolean;
|
||||
endOfCycleMs?: number;
|
||||
}) => {
|
||||
if (startDate) return startDate;
|
||||
if (isScheduled) return endOfCycleMs;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getResetCycleAnchor = ({
|
||||
startDate,
|
||||
resetCycleAnchorMs,
|
||||
}: {
|
||||
startDate?: number;
|
||||
resetCycleAnchorMs: number | "now";
|
||||
}) => {
|
||||
if (resetCycleAnchorMs !== "now") return resetCycleAnchorMs;
|
||||
return startDate ?? resetCycleAnchorMs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the new FullCusProduct to insert when attaching a product.
|
||||
*
|
||||
@@ -99,15 +74,11 @@ export const computeAttachNewCustomerProduct = ({
|
||||
);
|
||||
|
||||
const isScheduled = planTiming === "end_of_cycle";
|
||||
const startsAt = getStartsAt({
|
||||
startDate: params.start_date,
|
||||
isScheduled,
|
||||
endOfCycleMs,
|
||||
});
|
||||
const resetCycleAnchor = getResetCycleAnchor({
|
||||
startDate: params.start_date,
|
||||
resetCycleAnchorMs,
|
||||
});
|
||||
const startsAt = params.start_date ?? (isScheduled ? endOfCycleMs : undefined);
|
||||
const resetCycleAnchor =
|
||||
resetCycleAnchorMs === "now" && params.start_date !== undefined
|
||||
? params.start_date
|
||||
: resetCycleAnchorMs;
|
||||
|
||||
let existingUsagesConfig: ExistingUsagesConfig | undefined =
|
||||
!isScheduled && currentCustomerProduct
|
||||
|
||||
@@ -7,12 +7,13 @@ import {
|
||||
ErrCode,
|
||||
isFreeProduct,
|
||||
isOneOffProduct,
|
||||
ms,
|
||||
RecaseError,
|
||||
} from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
const START_DATE_TOLERANCE_MS = ms.minutes(1);
|
||||
import {
|
||||
isFutureStartDate,
|
||||
isPastStartDate,
|
||||
} from "@/internal/billing/v2/utils/startDateUtils";
|
||||
|
||||
const hasActivePaidRecurringSubscription = ({
|
||||
billingContext,
|
||||
@@ -40,7 +41,7 @@ export const handleStartDateErrors = ({
|
||||
}) => {
|
||||
if (params.start_date === undefined) return;
|
||||
|
||||
if (params.start_date < billingContext.currentEpochMs - START_DATE_TOLERANCE_MS) {
|
||||
if (isPastStartDate(params.start_date, billingContext.currentEpochMs)) {
|
||||
throw new RecaseError({
|
||||
message:
|
||||
"start_date cannot be set to a past timestamp. Use now or a future Unix timestamp in milliseconds.",
|
||||
@@ -67,9 +68,11 @@ export const handleStartDateErrors = ({
|
||||
});
|
||||
}
|
||||
|
||||
const isFutureStart =
|
||||
params.start_date > billingContext.currentEpochMs + START_DATE_TOLERANCE_MS;
|
||||
if (!isFutureStart) return;
|
||||
if (
|
||||
!isFutureStartDate(params.start_date, billingContext.currentEpochMs)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.invoice_mode?.enabled) {
|
||||
throw new RecaseError({
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
hasCustomItems,
|
||||
isFreeProduct,
|
||||
isOneOffProduct,
|
||||
ms,
|
||||
notNullish,
|
||||
orgDisableStripeWrites,
|
||||
orgToReturnUrl,
|
||||
@@ -23,6 +22,7 @@ import { setupFullCustomerContext } from "@/internal/billing/v2/setup/setupFullC
|
||||
import { setupInvoiceModeContext } from "@/internal/billing/v2/setup/setupInvoiceModeContext";
|
||||
import { setupResetCycleAnchor } from "@/internal/billing/v2/setup/setupResetCycleAnchor";
|
||||
import { setupTransitionConfigs } from "@/internal/billing/v2/setup/setupTransitionConfigs";
|
||||
import { isFutureStartDate } from "@/internal/billing/v2/utils/startDateUtils";
|
||||
import { setupAdjustableQuantities } from "../../../setup/setupAdjustableQuantities";
|
||||
import { setupAnchorResetRefund } from "../../../setup/setupAnchorResetRefund";
|
||||
import { setupAttachCheckoutMode } from "./setupAttachCheckoutMode";
|
||||
@@ -198,9 +198,7 @@ export const setupAttachBillingContext = async ({
|
||||
currentEpochMs,
|
||||
});
|
||||
|
||||
const hasFutureStartDate =
|
||||
params.start_date !== undefined &&
|
||||
params.start_date > currentEpochMs + ms.minutes(1);
|
||||
const hasFutureStartDate = isFutureStartDate(params.start_date, currentEpochMs);
|
||||
|
||||
const checkoutMode = setupAttachCheckoutMode({
|
||||
paymentMethod,
|
||||
|
||||
@@ -5,11 +5,11 @@ import type {
|
||||
StripeSubscriptionAction,
|
||||
StripeSubscriptionScheduleAction,
|
||||
} from "@autumn/shared";
|
||||
import { msToSeconds } from "@autumn/shared";
|
||||
import type { AutumnContext } from "@server/honoUtils/HonoEnv";
|
||||
import { buildStripeSubscriptionItemsUpdate } from "@server/internal/billing/v2/providers/stripe/utils/subscriptionItems/buildStripeSubscriptionItemsUpdate";
|
||||
import { buildStripeSubscriptionCreateAction } from "@server/internal/billing/v2/providers/stripe/utils/subscriptions/buildStripeSubscriptionCreateAction";
|
||||
import { buildStripeSubscriptionUpdateAction } from "@server/internal/billing/v2/providers/stripe/utils/subscriptions/buildStripeSubscriptionUpdateAction";
|
||||
import { stripePhaseStartsInFuture } from "@server/internal/billing/v2/utils/startDateUtils";
|
||||
import { billingPlanToOneOffStripeItemSpecs } from "@/internal/billing/v2/providers/stripe/utils/stripeItemSpec/billingPlanToOneOffStripeItemSpecs";
|
||||
|
||||
const scheduleStartsInFuture = ({
|
||||
@@ -21,10 +21,9 @@ const scheduleStartsInFuture = ({
|
||||
}) => {
|
||||
if (stripeSubscriptionScheduleAction?.type !== "create") return false;
|
||||
|
||||
const startDate = stripeSubscriptionScheduleAction.params.phases?.[0]?.start_date;
|
||||
return (
|
||||
typeof startDate === "number" &&
|
||||
startDate > msToSeconds(billingContext.currentEpochMs)
|
||||
return stripePhaseStartsInFuture(
|
||||
stripeSubscriptionScheduleAction.params.phases?.[0]?.start_date,
|
||||
billingContext.currentEpochMs,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
cp,
|
||||
isCustomerProductOnStripeSubscription,
|
||||
isCustomerProductOnStripeSubscriptionSchedule,
|
||||
msToSeconds,
|
||||
} from "@autumn/shared";
|
||||
import type { AutumnContext } from "@server/honoUtils/HonoEnv";
|
||||
import { buildStripePhasesUpdate } from "@server/internal/billing/v2/providers/stripe/utils/subscriptionSchedules/buildStripePhasesUpdate";
|
||||
import { stripePhaseStartsInFuture } from "@server/internal/billing/v2/utils/startDateUtils";
|
||||
import type Stripe from "stripe";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -173,10 +173,9 @@ const shouldCreateFutureSchedule = ({
|
||||
}) => {
|
||||
if (stripeSubscription) return false;
|
||||
|
||||
const startDate = scheduledPhases[0]?.start_date;
|
||||
return (
|
||||
typeof startDate === "number" &&
|
||||
startDate > msToSeconds(billingContext.currentEpochMs)
|
||||
return stripePhaseStartsInFuture(
|
||||
scheduledPhases[0]?.start_date,
|
||||
billingContext.currentEpochMs,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
19
server/src/internal/billing/v2/utils/startDateUtils.ts
Normal file
19
server/src/internal/billing/v2/utils/startDateUtils.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ms, secondsToMs } from "@autumn/shared";
|
||||
|
||||
const START_DATE_TOLERANCE_MS = ms.minutes(1);
|
||||
|
||||
export const isFutureStartDate = (
|
||||
startDate: number | undefined,
|
||||
currentEpochMs: number,
|
||||
toleranceMs = START_DATE_TOLERANCE_MS,
|
||||
) => startDate !== undefined && startDate > currentEpochMs + toleranceMs;
|
||||
|
||||
export const isPastStartDate = (startDate: number, currentEpochMs: number) =>
|
||||
startDate < currentEpochMs - START_DATE_TOLERANCE_MS;
|
||||
|
||||
export const stripePhaseStartsInFuture = (
|
||||
startDate: number | "now" | undefined,
|
||||
currentEpochMs: number,
|
||||
) =>
|
||||
typeof startDate === "number" &&
|
||||
isFutureStartDate(secondsToMs(startDate), currentEpochMs, 0);
|
||||
Reference in New Issue
Block a user