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

Team trial mode #1611

Merged
merged 41 commits into from
Feb 6, 2023
Merged

Team trial mode #1611

merged 41 commits into from
Feb 6, 2023

Conversation

knolleary
Copy link
Member

@knolleary knolleary commented Jan 25, 2023

Description

Work-in-progress : will update this description with progress

This introduces the new Team Trial mode as described in #1578.

Database changes

  • Subscription model is used to track the state of a trial.
    • Subscription.status enum extended to include trial
    • Subscription.trialStatus column added - an enum that tracks the state of the trial (such as what emails have been sent)
    • Subscription.trialEndsAt - date when the trial ends
  • When we create a 'personal team', we now create a Subscription without customer/subscription details as there is no relation to stripe at this point.

API Changes

  • The /api/v1/team/:teamid endpoint has been updated to include billing information:

    • a billing object is added to the response if billing is enabled on the platform
      • billing.active - boolean, whether there is an active Stripe subscription for this team
      • billing.canceled - boolean, whether the Stripe subscription is canceled
      • billing.trial - boolean, whether this is a trial Subscription
      • billing.trialEnded - boolean, whether the trial has ended
      • billing.trialEndsAt - date, when the trial ends
      • billing.trialProjectAllowed - boolean, whether the team is allowed to create a trial project (ie, they don't already have a trial project created)
  • Project Create api returns 403/Payment Required if called but no billing setup and not a permitted trial project

Housekeeper

A house keep task is added that runs every 30 minutes. It looks for teams that have ended their trial and does the work to either suspend projects (because billing not configured) or to add project to billing. It then sends the team owners an email to notify them.

It also looks for trials that end in 8 days or 2 days and sends a reminder email about the pending end of the trial

UI

The team gets a new banner when in trial mode that clearly communicates how much time is left in the trial and whether they need to setup billing or not.

Create Project

When in trial mode, without billing configured, the create project page disables all of the non-trial type projects and labels the Trial Type project as 'Free', but with the regular cost also listed. The Charges summary table reflects this as well.

Once they setup billing, the create project page allows them to create anything as normal.


I'm trying to structure the commits to make review easier, however the work has evolved to a point the earlier commits are reworked by previous ones.

Tasks

  • Allow admin to identify what ProjectType is available in trial mode
  • Restrict API actions when in trial mode without billing setup
    • Only allow creation of one project of the nominated type
    • Do not allow device registration (TODO: confirm this is a desired restriction)
    • Do not allow team invites (TODO: confirm this is a desired restriction)
  • Restrict API actions when trial ended without billing setup
    • Do not allow project creation
    • Do not allow device registration
    • Do not allow team invite
    • Do not allow project-actions on suspended projects other than delete
  • Send welcome email when trial team created
  • Create housekeeper task:
    • send trial-ending notifications on Days T-7, T-3 and T-1 (or some similar scheme - need to verify what frequency is needed for this type of notification to adhere to the stripe documented requirements.
    • If no billing setup, suspend project
    • If billing setup, add project to subscription (using billing_state added by Store project billing state when adding/removing subscription #1619)

Related Issue(s)

#1578

Checklist

  • I have read the contribution guidelines
  • Suitable unit/system level tests have been added and they pass
  • Documentation has been updated
    • Upgrade instructions
    • Configuration details
    • Concepts
  • Changes flowforge.yml?
    • Issue/PR raised on flowforge/helm to update ConfigMap Template
    • Issue/PR raised on flowforge/CloudProject to update values for Staging/Production

Labels

  • Backport needed? -> add the backport label
  • Includes a DB migration? -> add the area:migration label

@knolleary knolleary marked this pull request as draft January 25, 2023 13:56
@knolleary knolleary linked an issue Jan 26, 2023 that may be closed by this pull request
@knolleary knolleary added the area:migration Involves a database migration label Jan 27, 2023
@knolleary knolleary marked this pull request as ready for review February 1, 2023 17:04
@knolleary knolleary requested a review from Pezmc February 1, 2023 21:03
Copy link
Contributor

@Pezmc Pezmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done a first pass, will go through again tomorrow.

There's some work to combine this with the changes in #1642

if (!subscription) {
// No billing subscription - not allowed to create anything
return false
} else if (subscription.isActive()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General feedback for this file: The nested if statements aren't needed here as each path is returning. Following the return early pattern might make this slightly easier to follow.

forge/containers/wrapper.js Outdated Show resolved Hide resolved
forge/containers/wrapper.js Outdated Show resolved Hide resolved
this._app.log.error(`Problem adding project to subscription: ${err}`)
throw new Error('Problem adding project to subscription')
const billingState = await project.getSetting(this.KEY_BILLING_STATE)
if (billingState !== this.BILLING_STATES.TRIAL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, #1642 added two helper methods, SubscriptionHandler.addProject and SubscriptionHander.removeProject that this logic should be moved into to keep subscription related logic in once place.

forge/containers/wrapper.js Outdated Show resolved Hide resolved
forge/ee/db/models/Subscription.js Show resolved Hide resolved
@@ -42,14 +65,22 @@ module.exports = {
// Should this subscription be treated as active/usable
// Stripe states such as past_due and trialing are still active
isActive () {
return !this.isCanceled()
return this.status === STATUS.ACTIVE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above is now out of date. I also think we should have a clearer naming convention to clarify that we're talking about the Stripe subscription, rather than the local subscription. Arguably a person in a trial has an active local subscription, they just don't have a stripe subscription yet. I'd suggest isActiveInStripe() and isActive(), which would make the intension much clearer.

Do I want to guard against them not being set up in stripe? => isActiveInStripe()
Do I want to guard against the team having any form of active subscription? => isActive()

Edit: perhaps isBillingActive()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be addressed as part of any wider billing changes in a follow up.

@@ -240,7 +252,7 @@ module.exports.init = async function (app) {
const billingIds = getBillingIdsForTeam(team)

const subscription = await app.db.models.Subscription.byTeamId(team.id)
if (subscription) {
if (subscription && subscription.isActive()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example where I feel subscription?.isActiveInStripe() communicates the intension much clearer.
Not all subscriptions that are 'active' need to go through this step.

forge/ee/lib/billing/index.js Outdated Show resolved Hide resolved
forge/ee/lib/billing/index.js Outdated Show resolved Hide resolved
await app.auditLog.User.account.verify.autoCreateTeam(request.session?.User || verifiedUser, null, team)

if (app.license.active() && app.billing && app.settings.get('user:team:trial-mode')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nb] General feedback for our codebase, I think app.settings should offer helpers like app.settings.trialModeEnabled() non blocking

Copy link
Contributor

@Pezmc Pezmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nick and I re-ran though this on a call together, and I'm happy for this to be deployed to staging and tested as is to give us time to test the functionality; and we can follow up with any UX polish and UI changes in follow ups.

@Pezmc
Copy link
Contributor

Pezmc commented Feb 6, 2023

@knolleary Tests pass with the change I made above 442d1fd, I'm 👍, I'll leave it to you to merge and trigger a staging deploy.

@knolleary
Copy link
Member Author

We had an unrelated test failure -waiting the re-run to pass then will merge etc.

@knolleary knolleary merged commit 951ab15 into main Feb 6, 2023
@knolleary knolleary deleted the team-trial-mode branch February 6, 2023 20:30
@joepavitt joepavitt mentioned this pull request Feb 7, 2023
@knolleary knolleary mentioned this pull request Feb 7, 2023
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:migration Involves a database migration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Team Trial Mode
2 participants