UNPKG

@cocalc/server

Version:

CoCalc server functionality: functions used by either the hub and the next.js server

116 lines (103 loc) 4.13 kB
/* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ import { LicenseIdleTimeoutsKeysOrdered } from "@cocalc/util/consts/site-license"; import { PurchaseInfo } from "@cocalc/util/licenses/purchase/types"; import { getDays } from "@cocalc/util/stripe/timecalcs"; import { getDedicatedDiskKey, PRICES } from "@cocalc/util/upgrades/dedicated"; // When we change pricing, the products in stripe will already // exist with old prices (often grandfathered) so we may want to // instead change the version so new products get created // automatically. // 20220406: version "1" after discovering an unintentional volume discount, // skewing the unit price per "product" in stripe. // 20220425: keeping version "1" when introducing "boost" (appending an uppercase "B") // and dedicated resources (they are explicitly listed and define their own "stripeID") // i.e. starting with "dVW" or "dD", which is distinct from starting with "a[idle]" // 20220601: price increases, due to inflation, etc. const VERSION = 2; export function getProductId(info: PurchaseInfo): string { /* We generate a unique identifier that represents the parameters of the purchase. The following parameters determine what "product" they are purchasing: - custom_uptime (until 2022-02: custom_always_running) - custom_cpu - custom_dedicated_cpu - custom_disk - custom_member - custom_ram - custom_dedicated_ram - period: subscription or set number of days We encode these in a string which serves to identify the product. */ function period(): string { if (info.type === "disk") throw new Error("disk do not have a period"); if (info.subscription == "no") { return getDays(info).toString(); } else { return "0"; // 0 means "subscription" -- same product for all types of subscription billing; } } // this is backwards compatible: short: 0, always_running: 1, ... function idleTimeout(): number { if (info.type !== "quota") throw new Error("idle_timeout only for quota"); switch (info.custom_uptime) { case "short": return 0; case "always_running": return 1; default: return 1 + LicenseIdleTimeoutsKeysOrdered.indexOf(info.custom_uptime); } } const type = info.type; const pid = [`license_`]; switch (type) { case "quota": pid.push( ...[ `a${idleTimeout()}`, `b${info.user == "business" ? 1 : 0}`, `c${info.custom_cpu}`, `d${info.custom_disk}`, `m${info.custom_member ? 1 : 0}`, `p${period()}`, `r${info.custom_ram}`, ] ); if (info.custom_dedicated_ram) { pid.push(`y${info.custom_dedicated_ram}`); } if (info.custom_dedicated_cpu) { pid.push(`z${Math.round(10 * info.custom_dedicated_cpu)}`); } // boost licenses have the same price as corresponding regular licenses, but their user visible title/description is different! if (info.boost === true) { pid.push("B"); } break; // this makes also sure to only purchase a known disk (nothing made up) case "disk": if (typeof info.dedicated_disk === "boolean") { throw new Error(`didicated_disk configuration must be an object!`); } const disk = PRICES.disks[getDedicatedDiskKey(info.dedicated_disk)]; if (disk == null) { throw new Error("no disk found – should never happen!"); } pid.push(disk.stripeID); break; // we make sure only known VMs can be bought (and later we check if the price matches as well) case "vm": const vm = PRICES.vms[info.dedicated_vm.machine]; if (vm == null) { throw new Error(`VM of type ${info.dedicated_vm.machine} not found`); } pid.push(vm.stripeID); break; default: throw new Error(`Product ID: unknown type ${type}`); } pid.push(`_v${VERSION}`); return pid.join(""); }