You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
5.9 KiB

  1. // NOTE@eamodio This file is referenced in the webviews to we can't use anything vscode or other imports that aren't available in the webviews
  2. import { getDateDifference } from './system/date';
  3. export const enum SubscriptionPlanId {
  4. Free = 'free',
  5. FreePlus = 'free+',
  6. Pro = 'pro',
  7. Teams = 'teams',
  8. Enterprise = 'enterprise',
  9. }
  10. export type FreeSubscriptionPlans = Extract<SubscriptionPlanId, SubscriptionPlanId.Free | SubscriptionPlanId.FreePlus>;
  11. export type PaidSubscriptionPlans = Exclude<SubscriptionPlanId, SubscriptionPlanId.Free | SubscriptionPlanId.FreePlus>;
  12. export type RequiredSubscriptionPlans = Exclude<SubscriptionPlanId, SubscriptionPlanId.Free>;
  13. export interface Subscription {
  14. readonly plan: {
  15. readonly actual: SubscriptionPlan;
  16. readonly effective: SubscriptionPlan;
  17. };
  18. account: SubscriptionAccount | undefined;
  19. previewTrial?: SubscriptionPreviewTrial;
  20. state: SubscriptionState;
  21. }
  22. export interface SubscriptionPlan {
  23. readonly id: SubscriptionPlanId;
  24. readonly name: string;
  25. readonly bundle: boolean;
  26. readonly cancelled: boolean;
  27. readonly startedOn: string;
  28. readonly expiresOn?: string | undefined;
  29. readonly organizationId: string | undefined;
  30. }
  31. export interface SubscriptionAccount {
  32. readonly id: string;
  33. readonly name: string;
  34. readonly email: string | undefined;
  35. readonly verified: boolean;
  36. readonly createdOn: string;
  37. readonly organizationIds: string[];
  38. }
  39. export interface SubscriptionPreviewTrial {
  40. readonly startedOn: string;
  41. readonly expiresOn: string;
  42. }
  43. export const enum SubscriptionState {
  44. /** Indicates a user who hasn't verified their email address yet */
  45. VerificationRequired = -1,
  46. /** Indicates a Free user who hasn't yet started the preview trial */
  47. Free = 0,
  48. /** Indicates a Free user who is in preview trial */
  49. FreeInPreviewTrial,
  50. /** Indicates a Free user who's preview has expired trial */
  51. FreePreviewTrialExpired,
  52. /** Indicates a Free+ user with a completed trial */
  53. FreePlusInTrial,
  54. /** Indicates a Free+ user who's trial has expired */
  55. FreePlusTrialExpired,
  56. /** Indicates a Paid user */
  57. Paid,
  58. }
  59. export function computeSubscriptionState(subscription: Optional<Subscription, 'state'>): SubscriptionState {
  60. const {
  61. account,
  62. plan: { actual, effective },
  63. previewTrial: preview,
  64. } = subscription;
  65. if (account?.verified === false) return SubscriptionState.VerificationRequired;
  66. if (actual.id === effective.id) {
  67. switch (effective.id) {
  68. case SubscriptionPlanId.Free:
  69. return preview == null ? SubscriptionState.Free : SubscriptionState.FreePreviewTrialExpired;
  70. case SubscriptionPlanId.FreePlus:
  71. return SubscriptionState.FreePlusTrialExpired;
  72. case SubscriptionPlanId.Pro:
  73. case SubscriptionPlanId.Teams:
  74. case SubscriptionPlanId.Enterprise:
  75. return SubscriptionState.Paid;
  76. }
  77. }
  78. switch (effective.id) {
  79. case SubscriptionPlanId.Free:
  80. return preview == null ? SubscriptionState.Free : SubscriptionState.FreeInPreviewTrial;
  81. case SubscriptionPlanId.FreePlus:
  82. return SubscriptionState.FreePlusTrialExpired;
  83. case SubscriptionPlanId.Pro:
  84. return actual.id === SubscriptionPlanId.Free
  85. ? SubscriptionState.FreeInPreviewTrial
  86. : SubscriptionState.FreePlusInTrial;
  87. case SubscriptionPlanId.Teams:
  88. case SubscriptionPlanId.Enterprise:
  89. return SubscriptionState.Paid;
  90. }
  91. }
  92. export function getSubscriptionPlan(
  93. id: SubscriptionPlanId,
  94. bundle: boolean,
  95. organizationId: string | undefined,
  96. startedOn?: Date,
  97. expiresOn?: Date,
  98. cancelled: boolean = false,
  99. ): SubscriptionPlan {
  100. return {
  101. id: id,
  102. name: getSubscriptionPlanName(id),
  103. bundle: bundle,
  104. cancelled: cancelled,
  105. organizationId: organizationId,
  106. startedOn: (startedOn ?? new Date()).toISOString(),
  107. expiresOn: expiresOn != null ? expiresOn.toISOString() : undefined,
  108. };
  109. }
  110. export function getSubscriptionPlanName(id: SubscriptionPlanId) {
  111. switch (id) {
  112. case SubscriptionPlanId.FreePlus:
  113. return 'GitLens Free';
  114. case SubscriptionPlanId.Pro:
  115. return 'GitLens Pro';
  116. case SubscriptionPlanId.Teams:
  117. return 'GitLens Teams';
  118. case SubscriptionPlanId.Enterprise:
  119. return 'GitLens Enterprise';
  120. case SubscriptionPlanId.Free:
  121. default:
  122. return 'GitLens';
  123. }
  124. }
  125. const plansPriority = new Map<SubscriptionPlanId | undefined, number>([
  126. [undefined, -1],
  127. [SubscriptionPlanId.Free, 0],
  128. [SubscriptionPlanId.FreePlus, 1],
  129. [SubscriptionPlanId.Pro, 2],
  130. [SubscriptionPlanId.Teams, 3],
  131. [SubscriptionPlanId.Enterprise, 4],
  132. ]);
  133. export function getSubscriptionPlanPriority(id: SubscriptionPlanId | undefined): number {
  134. return plansPriority.get(id) ?? -1;
  135. }
  136. export function getSubscriptionTimeRemaining(
  137. subscription: Optional<Subscription, 'state'>,
  138. unit?: 'days' | 'hours' | 'minutes' | 'seconds',
  139. ): number | undefined {
  140. return getTimeRemaining(subscription.plan.effective.expiresOn, unit);
  141. }
  142. export function getTimeRemaining(
  143. expiresOn: string | undefined,
  144. unit?: 'days' | 'hours' | 'minutes' | 'seconds',
  145. ): number | undefined {
  146. return expiresOn != null ? getDateDifference(Date.now(), new Date(expiresOn), unit) : undefined;
  147. }
  148. export function isSubscriptionPaid(subscription: Optional<Subscription, 'state'>): boolean {
  149. return isSubscriptionPaidPlan(subscription.plan.effective.id);
  150. }
  151. export function isSubscriptionPaidPlan(id: SubscriptionPlanId): id is PaidSubscriptionPlans {
  152. return id !== SubscriptionPlanId.Free && id !== SubscriptionPlanId.FreePlus;
  153. }
  154. export function isSubscriptionExpired(subscription: Optional<Subscription, 'state'>): boolean {
  155. const remaining = getSubscriptionTimeRemaining(subscription);
  156. return remaining != null && remaining <= 0;
  157. }
  158. export function isSubscriptionTrial(subscription: Optional<Subscription, 'state'>): boolean {
  159. return subscription.plan.actual.id !== subscription.plan.effective.id;
  160. }
  161. export function isSubscriptionPreviewTrialExpired(subscription: Optional<Subscription, 'state'>): boolean | undefined {
  162. const remaining = getTimeRemaining(subscription.previewTrial?.expiresOn);
  163. return remaining != null ? remaining <= 0 : undefined;
  164. }