fix: proration calculation
This commit is contained in:
@@ -17,7 +17,11 @@ import { getCheckPreview } from "./getCheckPreview.js";
|
||||
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { getProration } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js";
|
||||
import { formatUnixToDate, notNullish } from "@/utils/genUtils.js";
|
||||
import {
|
||||
formatUnixToDate,
|
||||
formatUnixToDateTime,
|
||||
notNullish,
|
||||
} from "@/utils/genUtils.js";
|
||||
import { featureToCusPrice } from "@/internal/customers/cusProducts/cusPrices/convertCusPriceUtils.js";
|
||||
import { priceToInvoiceAmount } from "@/internal/products/prices/priceUtils/priceToInvoiceAmount.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
@@ -126,10 +130,8 @@ export const getOptions = ({
|
||||
now,
|
||||
});
|
||||
|
||||
// if (finalProration) {
|
||||
// console.log("Start:", formatUnixToDate(finalProration.start));
|
||||
// console.log("End:", formatUnixToDate(finalProration.end));
|
||||
// }
|
||||
if (finalProration) {
|
||||
}
|
||||
|
||||
let priceData = itemToPriceOrTiers({
|
||||
item: i,
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
import { logger } from "better-auth";
|
||||
import Stripe from "stripe";
|
||||
import { isTrialing } from "../../cusProducts/cusProductUtils.js";
|
||||
import { formatUnixToDate, notNullish } from "@/utils/genUtils.js";
|
||||
import {
|
||||
formatUnixToDate,
|
||||
formatUnixToDateTime,
|
||||
notNullish,
|
||||
} from "@/utils/genUtils.js";
|
||||
import { priceToUsageModel } from "@/internal/products/prices/priceUtils/convertPrice.js";
|
||||
import {
|
||||
formatPrice,
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
getAlignedIntervalUnix,
|
||||
subtractBillingIntervalUnix,
|
||||
subtractFromUnixTillAligned,
|
||||
subtractIntervalForProration,
|
||||
} from "../../products/prices/billingIntervalUtils.js";
|
||||
import {
|
||||
priceToFeature,
|
||||
@@ -131,7 +132,7 @@ export const getProration = ({
|
||||
// alwaysReturn: true,
|
||||
// });
|
||||
|
||||
let start = subtractBillingIntervalUnix({
|
||||
let start = subtractIntervalForProration({
|
||||
unixTimestamp: end!,
|
||||
interval,
|
||||
intervalCount,
|
||||
|
||||
@@ -249,3 +249,84 @@ export const subtractFromUnixTillAligned = ({
|
||||
|
||||
return getTime(alignedDate);
|
||||
};
|
||||
|
||||
// Subtracts an interval from a period end, preserving end-of-month anchoring
|
||||
// e.g. 30 Sep -> 31 Aug (not 30 Aug)
|
||||
export const subtractIntervalForProration = ({
|
||||
unixTimestamp,
|
||||
interval,
|
||||
intervalCount = 1,
|
||||
}: {
|
||||
unixTimestamp: number;
|
||||
interval: BillingInterval;
|
||||
intervalCount?: number;
|
||||
}) => {
|
||||
const endDate = new UTCDate(unixTimestamp);
|
||||
|
||||
const isEndOfMonth = () => {
|
||||
const lastDay = new UTCDate(
|
||||
endDate.getFullYear(),
|
||||
endDate.getMonth() + 1,
|
||||
0
|
||||
).getDate();
|
||||
return getDate(endDate) === lastDay;
|
||||
};
|
||||
|
||||
const preserveTime = (d: UTCDate) => {
|
||||
let preserved = new UTCDate(d.getTime());
|
||||
preserved = new UTCDate(setHours(preserved, getHours(endDate)).getTime());
|
||||
preserved = new UTCDate(
|
||||
setMinutes(preserved, getMinutes(endDate)).getTime()
|
||||
);
|
||||
preserved = new UTCDate(
|
||||
setSeconds(preserved, getSeconds(endDate)).getTime()
|
||||
);
|
||||
return preserved;
|
||||
};
|
||||
|
||||
const setToLastDayOfMonth = (d: UTCDate) => {
|
||||
const last = new UTCDate(d.getFullYear(), d.getMonth() + 1, 0).getDate();
|
||||
return new UTCDate(setDate(d, last).getTime());
|
||||
};
|
||||
|
||||
switch (interval) {
|
||||
case BillingInterval.Week: {
|
||||
const sub = new UTCDate(subWeeks(endDate, 1 * intervalCount).getTime());
|
||||
return getTime(sub);
|
||||
}
|
||||
case BillingInterval.Month: {
|
||||
let sub = new UTCDate(subMonths(endDate, 1 * intervalCount).getTime());
|
||||
if (isEndOfMonth()) {
|
||||
sub = setToLastDayOfMonth(sub);
|
||||
}
|
||||
sub = preserveTime(sub);
|
||||
return getTime(sub);
|
||||
}
|
||||
case BillingInterval.Quarter: {
|
||||
let sub = new UTCDate(subMonths(endDate, 3 * intervalCount).getTime());
|
||||
if (isEndOfMonth()) {
|
||||
sub = setToLastDayOfMonth(sub);
|
||||
}
|
||||
sub = preserveTime(sub);
|
||||
return getTime(sub);
|
||||
}
|
||||
case BillingInterval.SemiAnnual: {
|
||||
let sub = new UTCDate(subMonths(endDate, 6 * intervalCount).getTime());
|
||||
if (isEndOfMonth()) {
|
||||
sub = setToLastDayOfMonth(sub);
|
||||
}
|
||||
sub = preserveTime(sub);
|
||||
return getTime(sub);
|
||||
}
|
||||
case BillingInterval.Year: {
|
||||
let sub = new UTCDate(subYears(endDate, 1 * intervalCount).getTime());
|
||||
if (isEndOfMonth()) {
|
||||
sub = setToLastDayOfMonth(sub);
|
||||
}
|
||||
sub = preserveTime(sub);
|
||||
return getTime(sub);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid billing interval: ${interval}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { format } from "date-fns";
|
||||
import KSUID from "ksuid";
|
||||
import RecaseError from "./errorUtils.js";
|
||||
import { ErrCode } from "@/errors/errCodes.js";
|
||||
import { UTCDate } from "@date-fns/utc";
|
||||
|
||||
export const generateId = (prefix: string) => {
|
||||
if (!prefix) {
|
||||
@@ -53,11 +54,17 @@ export const notNullish = (value: any) => {
|
||||
return !nullish(value);
|
||||
};
|
||||
|
||||
export const formatUnixToDateTime = (unixDate?: number | null) => {
|
||||
export const formatUnixToDateTime = (
|
||||
unixDate?: number | null,
|
||||
withTimezone?: boolean
|
||||
) => {
|
||||
if (!unixDate) {
|
||||
return "undefined unix date";
|
||||
}
|
||||
return format(new Date(unixDate), "dd MMM yyyy HH:mm:ss");
|
||||
return format(
|
||||
new UTCDate(unixDate),
|
||||
withTimezone ? "dd MMM yyyy HH:mm:ss z" : "dd MMM yyyy HH:mm:ss"
|
||||
);
|
||||
};
|
||||
|
||||
export const formatUnixToDate = (unixDate?: number) => {
|
||||
|
||||
@@ -117,6 +117,8 @@ describe(`${chalk.yellowBright(`${testCase}: Testing upgrades with prepaid singl
|
||||
waitForSeconds: 20,
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
await attachAndExpectCorrect({
|
||||
autumn,
|
||||
customerId,
|
||||
|
||||
Reference in New Issue
Block a user