Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GLVSC-593: Check expiration of paid accounts offline #3522

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17548,7 +17548,7 @@
{
"view": "gitlens.views.worktrees",
"contents": "[Upgrade to Pro](command:gitlens.plus.upgrade?%7B%22source%22%3A%22worktrees%22%7D)",
"when": "gitlens:plus:required && gitlens:plus:state == 4"
"when": "gitlens:plus:required && (gitlens:plus:state == 4 || gitlens:plus:state == 7)"
},
{
"view": "gitlens.views.worktrees",
Expand All @@ -17558,12 +17558,12 @@
{
"view": "gitlens.views.worktrees",
"contents": "Launchpad sale: Save 75% or more on GitLens Pro",
"when": "gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == launchpad-extended"
"when": "gitlens:plus:required && (gitlens:plus:state == 4 || gitlens:plus:state == 7) && gitlens:promo == launchpad-extended"
},
{
"view": "gitlens.views.worktrees",
"contents": "Limited-time sale: Save up to 80% on GitLen Pro",
"when": "gitlens:plus:required && gitlens:plus:state == 4 && gitlens:promo == devexdays"
"when": "gitlens:plus:required && (gitlens:plus:state == 4 ||  gitlens:plus:state == 7) && gitlens:promo == devexdays"
},
{
"view": "gitlens.views.worktrees",
Expand Down Expand Up @@ -17845,6 +17845,15 @@
},
"when": "gitlens:plus:state == 6"
},
{
"id": "pro-expired-reactivate",
"title": "Reactivate Pro Power-up",
"description": "Reactivate your Pro account and experience all the new [Pro features](https://gitkraken.com/gitlens/pro-features?utm_source=gitlens-extension&utm_medium=in-app-links) and the full [GitKraken DevEx platform](https://gitkraken.com/devex?utm_source=gitlens-extension&utm_medium=in-app-links).\n\n[Reactivate Pro](command:gitlens.plus.upgrade?%7B%22source%22%3A%22walkthrough%22%7D)\n\n**Pro Features**\n$(gitlens-graph)  [Commit Graph](command:gitlens.openWalkthrough?%7B%22step%22%3A%22visualize%22,%22source%22%3A%22walkthrough%22%7D) — visualize your repository and keep track of all work in progress\n$(rocket)  [Launchpad](command:gitlens.openWalkthrough?%7B%22step%22%3A%22launchpad%22,%22source%22%3A%22walkthrough%22%7D) — stay focused and keep your team unblocked\n$(gitlens-code-suggestion)  [Code Suggest](command:gitlens.openWalkthrough?%7B%22step%22%3A%22code-collab%22,%22source%22%3A%22walkthrough%22%7D) — free your code reviews from unnecessary restrictions\n$(gitlens-cloud-patch)  [Cloud Patches](command:gitlens.openWalkthrough?%7B%22step%22%3A%22code-collab%22,%22source%22%3A%22walkthrough%22%7D) — easily and securely share code with your teammates\n$(gitlens-worktrees-view)  **Worktrees** — work on multiple branches simultaneously\n$(gitlens-workspaces-view)  **Workspaces** — group and manage multiple repositories together\n$(graph-scatter)  [Visual File History](command:gitlens.openWalkthrough?%7B%22step%22%3A%22visualize%22,%22source%22%3A%22walkthrough%22%7D) — visualize the evolution of a file and quickly identify when the most impactful changes were made and by whom",
"media": {
"markdown": "walkthroughs/welcome/pro-reactivate.md"
},
"when": "gitlens:plus:state == 7"
},
{
"id": "visualize",
"title": "Visualize with Commit Graph & Visual File History",
Expand Down
2 changes: 2 additions & 0 deletions src/constants.subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ export const enum SubscriptionState {
ProTrialReactivationEligible = 5,
/** Indicates a Pro/Teams/Enterprise paid user */
Paid = 6,
/** Indicates a Paid user who's license has expired */
PaidExpired = 7,
}
1 change: 1 addition & 0 deletions src/constants.telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ export type TelemetryEvents = {
| 'pro-trial'
| 'pro-upgrade'
| 'pro-reactivate'
| 'pro-expired-reactivate'
| 'pro-paid'
| 'visualize'
| 'launchpad'
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export type WalkthroughSteps =
| 'pro-trial'
| 'pro-upgrade'
| 'pro-reactivate'
| 'pro-expired-reactivate'
| 'pro-paid'
| 'visualize'
| 'launchpad'
Expand Down
7 changes: 5 additions & 2 deletions src/git/gitProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type { Container } from '../container';
import { AccessDeniedError, CancellationError, ProviderNotFoundError, ProviderNotSupportedError } from '../errors';
import type { FeatureAccess, Features, PlusFeatures, RepoFeatureAccess } from '../features';
import type { Subscription } from '../plus/gk/account/subscription';
import { isSubscriptionPaidPlan } from '../plus/gk/account/subscription';
import { isSubscriptionExpired, isSubscriptionPaidPlan } from '../plus/gk/account/subscription';
import type { SubscriptionChangeEvent } from '../plus/gk/account/subscriptionService';
import type { HostingIntegration } from '../plus/integrations/integration';
import type { RepoComparisonKey } from '../repositories';
Expand Down Expand Up @@ -773,7 +773,10 @@ export class GitProviderService implements Disposable {

const plan = subscription.plan.effective.id;
if (isSubscriptionPaidPlan(plan)) {
return { allowed: subscription.account?.verified !== false, subscription: { current: subscription } };
return {
allowed: subscription.account?.verified !== false && !isSubscriptionExpired(subscription),
subscription: { current: subscription },
};
}

if (feature === 'launchpad') {
Expand Down
3 changes: 3 additions & 0 deletions src/plus/gk/account/promos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const promos: Promo[] = [
SubscriptionState.ProTrial,
SubscriptionState.ProTrialExpired,
SubscriptionState.ProTrialReactivationEligible,
SubscriptionState.PaidExpired,
axosoft-ramint marked this conversation as resolved.
Show resolved Hide resolved
],
startsOn: new Date('2024-09-27T06:59:00.000Z').getTime(),
expiresOn: new Date('2024-10-14T06:59:00.000Z').getTime(),
Expand All @@ -48,6 +49,7 @@ const promos: Promo[] = [
SubscriptionState.ProTrial,
SubscriptionState.ProTrialExpired,
SubscriptionState.ProTrialReactivationEligible,
SubscriptionState.PaidExpired,
],
startsOn: new Date('2024-10-13T06:59:00.000Z').getTime(),
expiresOn: new Date('2024-11-05T06:59:00.000Z').getTime(),
Expand All @@ -66,6 +68,7 @@ const promos: Promo[] = [
SubscriptionState.ProTrial,
SubscriptionState.ProTrialExpired,
SubscriptionState.ProTrialReactivationEligible,
SubscriptionState.PaidExpired,
],
command: { tooltip: 'Limited-Time sale: Save 33% or more on your 1st seat of Pro. See your special price' },
quickpick: {
Expand Down
15 changes: 14 additions & 1 deletion src/plus/gk/account/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface SubscriptionPlan {
readonly startedOn: string;
readonly expiresOn?: string | undefined;
readonly organizationId: string | undefined;
readonly isTrial: boolean;
}

export interface SubscriptionAccount {
Expand Down Expand Up @@ -93,6 +94,8 @@ export function getSubscriptionStateString(state: SubscriptionState | undefined)
return 'trial-reactivation-eligible';
case SubscriptionState.Paid:
return 'paid';
case SubscriptionState.PaidExpired:
return 'paid-expired';
default:
return 'unknown';
}
Expand Down Expand Up @@ -123,6 +126,10 @@ export function computeSubscriptionState(subscription: Optional<Subscription, 's
case SubscriptionPlanId.Pro:
case SubscriptionPlanId.Teams:
case SubscriptionPlanId.Enterprise:
if (effective.expiresOn != null && new Date(effective.expiresOn) < new Date()) {
return SubscriptionState.PaidExpired;
}

return SubscriptionState.Paid;
}
}
Expand All @@ -146,6 +153,10 @@ export function computeSubscriptionState(subscription: Optional<Subscription, 's

case SubscriptionPlanId.Teams:
case SubscriptionPlanId.Enterprise:
if (effective.expiresOn != null && new Date(effective.expiresOn) < new Date()) {
return SubscriptionState.PaidExpired;
}

return SubscriptionState.Paid;
}
}
Expand All @@ -159,6 +170,7 @@ export function getSubscriptionPlan(
expiresOn?: Date,
cancelled: boolean = false,
nextTrialOptInDate?: string,
isTrial: boolean = false,
): SubscriptionPlan {
return {
id: id,
Expand All @@ -170,6 +182,7 @@ export function getSubscriptionPlan(
nextTrialOptInDate: nextTrialOptInDate,
startedOn: (startedOn ?? new Date()).toISOString(),
expiresOn: expiresOn != null ? expiresOn.toISOString() : undefined,
isTrial: isTrial,
};
}

Expand Down Expand Up @@ -230,7 +243,7 @@ export function isSubscriptionExpired(subscription: Optional<Subscription, 'stat
}

export function isSubscriptionTrial(subscription: Optional<Subscription, 'state'>): boolean {
return subscription.plan.actual.id !== subscription.plan.effective.id;
return subscription.plan.effective.isTrial;
}

export function isSubscriptionInProTrial(subscription: Optional<Subscription, 'state'>): boolean {
Expand Down
8 changes: 7 additions & 1 deletion src/plus/gk/account/subscriptionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ export class SubscriptionService implements Disposable {
step: 'pro-paid',
});
break;
case SubscriptionState.PaidExpired:
void executeCommand<OpenWalkthroughCommandArgs>(Commands.OpenWalkthrough, {
...source,
step: 'pro-expired-reactivate',
});
break;
}
}

Expand Down Expand Up @@ -716,7 +722,6 @@ export class SubscriptionService implements Disposable {
const days = proPreviewLengthInDays;
const subscription = getPreviewSubscription(days, this._subscription);
this.changeSubscription(subscription);

setTimeout(async () => {
const confirm: MessageItem = { title: 'Continue' };
const learn: MessageItem = { title: 'See Pro Features' };
Expand Down Expand Up @@ -1229,6 +1234,7 @@ export class SubscriptionService implements Disposable {
undefined,
new Date(subscription.previewTrial.startedOn),
new Date(subscription.previewTrial.expiresOn),
true,
),
},
};
Expand Down
33 changes: 26 additions & 7 deletions src/plus/gk/checkin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SubscriptionPlanId } from '../../constants.subscription';
import type { Organization } from './account/organization';
import type { Subscription } from './account/subscription';
import { getSubscriptionPlan, getSubscriptionPlanPriority } from './account/subscription';
import { getSubscriptionPlan, getSubscriptionPlanPriority, getTimeRemaining } from './account/subscription';

export type GKLicenses = Partial<Record<GKLicenseType, GKLicense>>;

Expand Down Expand Up @@ -69,9 +69,6 @@ export function getSubscriptionFromCheckIn(

let effectiveLicenses = Object.entries(data.licenses.effectiveLicenses) as [GKLicenseType, GKLicense][];
let paidLicenses = Object.entries(data.licenses.paidLicenses) as [GKLicenseType, GKLicense][];
paidLicenses = paidLicenses.filter(
license => license[1].latestStatus !== 'expired' && license[1].latestStatus !== 'cancelled',
);
if (paidLicenses.length > 1) {
paidLicenses.sort(
(a, b) =>
Expand All @@ -81,6 +78,7 @@ export function getSubscriptionFromCheckIn(
licenseStatusPriority(a[1].latestStatus)),
);
}

if (effectiveLicenses.length > 1) {
effectiveLicenses.sort(
(a, b) =>
Expand Down Expand Up @@ -133,13 +131,21 @@ export function getSubscriptionFromCheckIn(
organizationId != null ? paidLicensesByOrganizationId.get(organizationId) ?? bestPaidLicense : bestPaidLicense;
if (chosenPaidLicense != null) {
const [licenseType, license] = chosenPaidLicense;
const latestStartDate = new Date(license.latestStartDate);
const latestEndDate = new Date(license.latestEndDate);
const today = new Date();
actual = getSubscriptionPlan(
convertLicenseTypeToPlanId(licenseType),
isBundleLicenseType(licenseType),
license.reactivationCount ?? 0,
license.organizationId,
new Date(license.latestStartDate),
new Date(license.latestEndDate),
latestStartDate,
latestEndDate,
undefined,
undefined,
(license.latestStatus === 'in_trial' || license.latestStatus === 'trial') &&
latestEndDate > today &&
today > latestStartDate,
);
}

Expand All @@ -157,6 +163,7 @@ export function getSubscriptionFromCheckIn(
undefined,
undefined,
data.nextOptInDate,
false,
);
}

Expand All @@ -167,6 +174,9 @@ export function getSubscriptionFromCheckIn(
: bestEffectiveLicense;
if (chosenEffectiveLicense != null) {
const [licenseType, license] = chosenEffectiveLicense;
const latestStartDate = new Date(license.latestStartDate);
const latestEndDate = new Date(license.latestEndDate);
const today = new Date();
effective = getSubscriptionPlan(
convertLicenseTypeToPlanId(licenseType),
isBundleLicenseType(licenseType),
Expand All @@ -176,10 +186,19 @@ export function getSubscriptionFromCheckIn(
new Date(license.latestEndDate),
license.latestStatus === 'cancelled',
license.nextOptInDate ?? data.nextOptInDate,
(license.latestStatus === 'in_trial' || license.latestStatus === 'trial') &&
latestEndDate > today &&
today > latestStartDate,
);
}

if (effective == null || getSubscriptionPlanPriority(actual.id) >= getSubscriptionPlanPriority(effective.id)) {
const remainingTime = getTimeRemaining(actual.expiresOn);
if (
effective == null ||
(getSubscriptionPlanPriority(actual.id) >= getSubscriptionPlanPriority(effective.id) &&
remainingTime != null &&
remainingTime > 0)
) {
effective = { ...actual };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,19 @@ export class GlFeatureGatePlusState extends LitElement {
features.
</p>`;

case SubscriptionState.PaidExpired:
return html` <gl-button
appearance="${appearance}"
href="${generateCommandLink(Commands.PlusUpgrade, this.source)}"
>Upgrade to Pro</gl-button
>
${this.renderPromo(promo)}
<p>
Your Pro license has ended. Please upgrade for full access to
${this.featureWithArticleIfNeeded ? `${this.featureWithArticleIfNeeded} and other ` : ''}Pro
features.
</p>`;

case SubscriptionState.ProTrialReactivationEligible:
return html`
<gl-button
Expand Down
7 changes: 7 additions & 0 deletions src/webviews/apps/shared/components/feature-badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,13 @@ export class GlFeatureBadge extends LitElement {
${this.renderUpgradeActions(html`<p>Please upgrade for full access to Pro features:</p>`)}`;
break;

case SubscriptionState.PaidExpired:
content = html`<p>
Your Pro license has expired. You can now only use Pro features on publicly-hosted repos.
</p>
${this.renderUpgradeActions(html`<p>Please upgrade for full access to Pro features:</p>`)}`;
break;

case SubscriptionState.ProTrialReactivationEligible:
content = html`<p>
Reactivate your Pro trial and experience all the new Pro features — free for another
Expand Down
Loading