Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/licenses/purchase/purchase-info.ts
5537 views
1
import dayjs from "dayjs";
2
3
import type { Date0 } from "@cocalc/util/types/store";
4
import type {
5
Period,
6
SiteLicenseDescriptionDB,
7
} from "@cocalc/util/upgrades/shopping";
8
import { CURRENT_VERSION } from "./consts";
9
import type { PurchaseInfo, StartEndDates, Subscription } from "./types";
10
11
// this ALWAYS returns purchaseInfo that is the *current* version.
12
export default function getPurchaseInfo(
13
conf: SiteLicenseDescriptionDB,
14
): PurchaseInfo {
15
conf.type = conf.type ?? "quota"; // backwards compatibility
16
17
const { title, description } = conf;
18
19
switch (conf.type) {
20
case "quota":
21
const {
22
type,
23
user,
24
run_limit,
25
period,
26
ram,
27
cpu,
28
disk,
29
member,
30
uptime,
31
source,
32
boost = false,
33
} = conf;
34
return {
35
version: CURRENT_VERSION,
36
type, // "quota"
37
user,
38
upgrade: "custom" as "custom",
39
quantity: run_limit,
40
subscription: (period == "range" ? "no" : period) as Subscription,
41
...fixRange(conf.range, conf.period),
42
custom_ram: ram,
43
custom_dedicated_ram: 0,
44
custom_cpu: cpu,
45
custom_dedicated_cpu: 0,
46
custom_disk: disk,
47
custom_member: member,
48
custom_uptime: uptime,
49
boost,
50
title,
51
description,
52
source,
53
};
54
55
case "vm":
56
return {
57
version: CURRENT_VERSION,
58
type: "vm",
59
quantity: 1,
60
dedicated_vm: conf.dedicated_vm,
61
subscription: "no",
62
...fixRange(conf.range, conf.period),
63
title,
64
description,
65
};
66
67
case "disk":
68
return {
69
version: CURRENT_VERSION,
70
type: "disk",
71
quantity: 1,
72
dedicated_disk: conf.dedicated_disk,
73
subscription: conf.period,
74
title,
75
description,
76
...fixRange(null, conf.period),
77
};
78
}
79
}
80
81
// Make sure both start and end dates defined as Date. For all licenses we
82
// always require that both are defined. For a subscription, the end date must
83
// be defined, but it will get periodically moved forward as the subscription
84
// is updated.
85
// Later, when actually saving the range to the database, we will maybe append
86
// a portion of the start which is in the past.
87
export function fixRange(
88
rangeOrig: readonly [Date0 | string, Date0 | string] | undefined | null,
89
period: Period,
90
noRangeShift?: boolean,
91
): StartEndDates {
92
if (period != "range" && !noRangeShift) {
93
// ATTN! -- we messed up and didn't deal with this case before, and a user
94
// could in theory:
95
// 1. set the period to 'range', and put in a week period via start and end
96
// 2. set the period to 'yearly'.
97
// Then rangeOrig is still a week, so they pay for one week instead of one year!
98
// Instead, in whenever the period is 'monthly' or 'yearly' (anything but 'range',
99
// we unset rangeOrig here so we use start=now and end=now + a year (say) for
100
// the price computation.
101
rangeOrig = null;
102
}
103
const now = new Date();
104
if (rangeOrig == null) {
105
if (period == "range") {
106
throw Error(
107
"if period is 'range', then start and end dates must be explicitly given",
108
);
109
}
110
// we expand the dates to be as inclusive as possible for subscriptions, since
111
// that doesn't result in any more charge to the user.
112
return {
113
start: dayjs(now).startOf("day").toDate(),
114
end: dayjs(addPeriod(now, period)).endOf("day").toDate(),
115
};
116
}
117
118
return {
119
start: rangeOrig?.[0] ? new Date(rangeOrig?.[0]) : now,
120
end: rangeOrig?.[1] ? new Date(rangeOrig?.[1]) : addPeriod(now, period),
121
};
122
}
123
124
function addPeriod(date: Date, period: Period): Date {
125
if (period == "range") {
126
throw Error("period must not be range");
127
} else if (period == "monthly") {
128
return dayjs(date).add(1, "month").toDate();
129
} else if (period == "yearly") {
130
return dayjs(date).add(1, "year").toDate();
131
} else {
132
throw Error(`unsupported period ${period}`);
133
}
134
}
135
136