Skip to content

Commit 180e18f

Browse files
committed
pricing: incorporate andrey's changes
Co-authored-by: [email protected]
1 parent f5d4324 commit 180e18f

File tree

8 files changed

+99
-16
lines changed

8 files changed

+99
-16
lines changed

src/packages/next/components/store/quota-config-presets.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6+
import { ReactNode } from "react";
7+
68
import { IconName } from "@cocalc/frontend/components/icon";
79
import { Uptime } from "@cocalc/util/consts/site-license";
810
import { Paragraph } from "components/misc";
911
import A from "components/misc/A";
10-
import { ReactNode } from "react";
12+
import { STANDARD_DISK } from "@cocalc/util/consts/billing";
1113

1214
export type Preset = "standard" | "instructor" | "research";
1315

@@ -19,7 +21,7 @@ export const PRESET_MATCH_FIELDS: Record<string, string> = {
1921
ram: "memory",
2022
uptime: "idle timeout",
2123
member: "member hosting",
22-
};
24+
} as const;
2325

2426
export interface PresetConfig {
2527
icon: IconName;
@@ -42,7 +44,6 @@ type PresetEntries = {
4244
// some constants to keep text and preset in sync
4345
const STANDARD_CPU = 1;
4446
const STANDARD_RAM = 4;
45-
const STANDARD_DISK = 3;
4647

4748
const PRESET_STANDARD_NAME = "Standard";
4849

@@ -149,7 +150,7 @@ export const SITE_LICENSE: PresetEntries = {
149150
),
150151
cpu: 1,
151152
ram: 2 * STANDARD_RAM,
152-
disk: 15,
153+
disk: Math.max(15, 4 * STANDARD_DISK),
153154
uptime: "medium",
154155
member: true,
155156
},
@@ -191,8 +192,8 @@ export const SITE_LICENSE: PresetEntries = {
191192
</>
192193
),
193194
cpu: 2,
194-
ram: 10,
195-
disk: 10,
195+
ram: 2 * STANDARD_RAM,
196+
disk: Math.max(15, 4 * STANDARD_DISK),
196197
uptime: "day",
197198
member: true,
198199
},

src/packages/util/consts/billing.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ export const AVG_YEAR_DAYS = 12 * AVG_MONTH_DAYS;
1010
export const ONE_MONTH_MS = AVG_MONTH_DAYS * ONE_DAY_MS;
1111

1212
// throughout the UI, we show this price as the minimum (per month)
13-
export const LICENSE_MIN_PRICE = "about $6/month";
13+
// It is nice if it is vague enough to match price changes.
14+
export const LICENSE_MIN_PRICE = "a few $ per month";
1415

1516
// Trial Banner in the UI
1617
export const EVALUATION_PERIOD_DAYS = 3;
1718
export const BANNER_NON_DISMISSIBLE_DAYS = 7;
19+
20+
// The "standard license" disk size.
21+
// used in next/store and student-pay
22+
// TODO: once the new file storage is in place, incease it to 10
23+
export const STANDARD_DISK = 3;

src/packages/util/licenses/purchase/compute-cost.test.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { compute_cost } from "./compute-cost";
22
import { decimalMultiply } from "@cocalc/util/stripe/calc";
33

4-
describe("use the compute-cost function with v1 pricing, no version, and a test version to compute the price of a license", () => {
4+
const MONTHLY_V1 = 27.15;
5+
6+
describe("compute-cost v1 pricing", () => {
57
// This is a monthly business subscription for 3 projects with 1 cpu, 2 GB ram and 3 GB disk,
68
// using v1 pricing. On the website right now it says this should cost:
79
// "Cost: USD $27.15 monthly USD $9.05 per project"
8-
const monthly1 = 27.15;
10+
const monthly1 = MONTHLY_V1;
911
const info1 = {
1012
version: "1",
1113
end: new Date("2024-01-06T22:00:02.582Z"),
@@ -103,3 +105,53 @@ describe("a couple more consistency checks with prod", () => {
103105
expect(cost.cost).toBe(amount);
104106
});
105107
});
108+
109+
describe("compute-cost v3 pricing", () => {
110+
// This is a monthly business subscription for 3 projects with 1 cpu, 2 GB ram and 3 GB disk,
111+
// using v3 pricing.
112+
const monthly3 = 31.5;
113+
const info1 = {
114+
version: "3",
115+
end: new Date("2024-01-06T22:00:02.582Z"),
116+
type: "quota",
117+
user: "business",
118+
boost: false,
119+
start: new Date("2023-12-05T17:15:55.781Z"),
120+
upgrade: "custom",
121+
quantity: 3,
122+
account_id: "6aae57c6-08f1-4bb5-848b-3ceb53e61ede",
123+
custom_cpu: 1,
124+
custom_ram: 2,
125+
custom_disk: 3,
126+
subscription: "monthly",
127+
custom_member: true,
128+
custom_uptime: "short",
129+
custom_dedicated_cpu: 0,
130+
custom_dedicated_ram: 0,
131+
} as const;
132+
133+
it("computes the cost", () => {
134+
const cost1 = compute_cost(info1);
135+
expect(decimalMultiply(cost1.cost_sub_month, cost1.quantity)).toBe(
136+
monthly3,
137+
);
138+
});
139+
140+
it("version 1 is the default", () => {
141+
const info = { ...info1 };
142+
// @ts-ignore
143+
delete info["version"];
144+
const cost = compute_cost(info);
145+
expect(decimalMultiply(cost.cost_sub_month, cost.quantity)).toBe(
146+
MONTHLY_V1,
147+
);
148+
});
149+
150+
it("computes correct cost with a different version of pricing params", () => {
151+
const info = { ...info1 };
152+
// @ts-ignore
153+
info.version = "test_1";
154+
const cost = compute_cost(info);
155+
expect(decimalMultiply(cost.cost_sub_month, cost.quantity)).toBe(54.3);
156+
});
157+
});

src/packages/util/licenses/purchase/compute-cost.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ export function compute_cost(info: PurchaseInfo): Cost {
134134
cost_per_project_per_month *=
135135
COSTS.user_discount[user] * COSTS.sub_discount[subscription];
136136

137-
cost_per_project_per_month = round2up(cost_per_project_per_month);
137+
// If the numbers were picked to give clean prices, it is possible to get
138+
// things like 12.50000001 and we do NOT want to round it up to 12.51.
139+
cost_per_project_per_month = round2up(cost_per_project_per_month - 0.00001);
138140

139141
// It's convenient in all cases to have the actual amount we will be charging
140142
// for both monthly and yearly available.

src/packages/util/licenses/purchase/consts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import costVersions from "./cost-versions";
1111
// is used when the version is not defined.
1212
const FALLBACK_VERSION = "1";
1313

14-
export const CURRENT_VERSION = "1";
14+
export const CURRENT_VERSION = "3";
1515

1616
// Another gamble implicit in this is that pre's are available. When they
1717
// aren't, cocalc.com switches to uses MUCH more expensive non-preemptibles.

src/packages/util/licenses/purchase/cost-versions.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,25 @@ const COST = {
6767
ALWAYS_RUNNING_FACTOR: 2,
6868
},
6969

70+
3: {
71+
SUB_DISCOUNT: { no: 1, monthly: 0.9, yearly: 0.75 },
72+
GCE_COSTS: {
73+
ram: 0.625, // for pre-emptibles
74+
cpu: 5, // for pre-emptibles
75+
disk: 0.04, // per GB/month
76+
non_pre_factor: 3.5, // Roughly Google's factor for non-preemptible's
77+
},
78+
// 2025-08: Andrey increases it to 1
79+
COST_MULTIPLIER: 1,
80+
NONMEMBER_DENSITY: 2,
81+
ACADEMIC_DISCOUNT: 0.6,
82+
// 2025-08: in anticipation of new file storage
83+
DISK_FACTOR: 6.25,
84+
RAM_OVERCOMMIT: 5,
85+
CPU_OVERCOMMIT: 10,
86+
ALWAYS_RUNNING_FACTOR: 2,
87+
},
88+
7089
// this version is PURELY for testing purposes
7190
test_1: {
7291
SUB_DISCOUNT: { no: 1, monthly: 0.9, yearly: 0.85 },

src/packages/util/licenses/purchase/purchase-info.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import dayjs from "dayjs";
2+
3+
import type { Date0 } from "@cocalc/util/types/store";
14
import type {
25
Period,
36
SiteLicenseDescriptionDB,
47
} from "@cocalc/util/upgrades/shopping";
5-
import type { PurchaseInfo, StartEndDates, Subscription } from "./types";
68
import { CURRENT_VERSION } from "./consts";
7-
import type { Date0 } from "@cocalc/util/types/store";
8-
import dayjs from "dayjs";
9+
import type { PurchaseInfo, StartEndDates, Subscription } from "./types";
910

1011
// this ALWAYS returns purchaseInfo that is the *current* version.
1112
export default function getPurchaseInfo(

src/packages/util/licenses/purchase/student-pay.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import type { PurchaseInfo } from "./types";
21
import dayjs from "dayjs";
32

3+
import { STANDARD_DISK } from "@cocalc/util/consts/billing";
4+
import type { PurchaseInfo } from "./types";
5+
46
export const DEFAULT_PURCHASE_INFO = {
57
type: "quota",
68
user: "academic",
@@ -11,7 +13,7 @@ export const DEFAULT_PURCHASE_INFO = {
1113
custom_dedicated_cpu: 0,
1214
custom_ram: 4,
1315
custom_dedicated_ram: 0,
14-
custom_disk: 3,
16+
custom_disk: STANDARD_DISK,
1517
custom_member: true,
1618
custom_uptime: "short",
1719
start: new Date(),

0 commit comments

Comments
 (0)