Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ ASANA_OAUTH_CLIENT_SECRET=dummy_value
# Open AI only requires secret
OPEN_AI_CLIENT_SECRET=dummy_value
# Mandrill (Mailchimp) only requires secret
MANDRILL_CLIENT_SECRET=dummy_value
MANDRILL_CLIENT_SECRET=dummy_value
# Stripe subscription config
STRIPE_SECRET_KEY=dummy_value
STRIPE_WEBHOOK_SECRET=dummy_value
STRIPE_PRICE_ID=dummy_value
4 changes: 2 additions & 2 deletions backend/api/dashboard_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ func TestDashboardData(t *testing.T) {
})
assert.NoError(t, err)
UnauthorizedTest(t, "GET", "/dashboard/data/", nil)
NoBusinessAccessTest(t, "GET", "/dashboard/data/", api, authToken)
EnableBusinessAccess(t, api, userID)
NoSubscriptionAccessTest(t, "GET", "/dashboard/data/", api, authToken)
EnableSubscriptionAccess(t, api, userID)
t.Run("Success", func(t *testing.T) {
request, _ := http.NewRequest("GET", "/dashboard/data/", nil)
request.Header.Add("Authorization", "Bearer "+authToken)
Expand Down
4 changes: 2 additions & 2 deletions backend/api/dashboard_fetch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ func TestDashboardFetch(t *testing.T) {
})
assert.NoError(t, err)

NoBusinessAccessTest(t, "GET", "/dashboard/data/fetch/", api, authToken)
EnableBusinessAccess(t, api, userID)
NoSubscriptionAccessTest(t, "GET", "/dashboard/data/fetch/", api, authToken)
EnableSubscriptionAccess(t, api, userID)

t.Run("Success", func(t *testing.T) {
request, _ := http.NewRequest("GET", "/dashboard/data/fetch/", nil)
Expand Down
12 changes: 6 additions & 6 deletions backend/api/dashboard_team_members_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ func TestDashboardTeamMemberCreate(t *testing.T) {
userID := getUserIDFromAuthToken(t, api.DB, authToken)

UnauthorizedTest(t, "POST", "/dashboard/team_members/", nil)
NoBusinessAccessTest(t, "POST", "/dashboard/team_members/", api, authToken)
EnableBusinessAccess(t, api, userID)
NoSubscriptionAccessTest(t, "POST", "/dashboard/team_members/", api, authToken)
EnableSubscriptionAccess(t, api, userID)
t.Run("MissingName", func(t *testing.T) {
database.GetDashboardTeamMemberCollection(api.DB).DeleteMany(context.Background(), bson.M{})
bodyParams, err := json.Marshal(DashboardTeamMemberCreateParams{
Expand Down Expand Up @@ -130,8 +130,8 @@ func TestDashboardTeamMemberDelete(t *testing.T) {
teamMemberCollection := database.GetDashboardTeamMemberCollection(api.DB)

UnauthorizedTest(t, "DELETE", "/dashboard/team_members/id/", nil)
NoBusinessAccessTest(t, "DELETE", "/dashboard/team_members/id/", api, authToken)
EnableBusinessAccess(t, api, userID)
NoSubscriptionAccessTest(t, "DELETE", "/dashboard/team_members/id/", api, authToken)
EnableSubscriptionAccess(t, api, userID)
t.Run("InvalidID", func(t *testing.T) {
ServeRequest(t, authToken, "DELETE", "/dashboard/team_members/123/", nil, http.StatusNotFound, api)
})
Expand Down Expand Up @@ -165,8 +165,8 @@ func TestDashboardTeamMemberList(t *testing.T) {
teamMemberCollection := database.GetDashboardTeamMemberCollection(api.DB)

UnauthorizedTest(t, "GET", "/dashboard/team_members/", nil)
NoBusinessAccessTest(t, "GET", "/dashboard/team_members/", api, authToken)
EnableBusinessAccess(t, api, userID)
NoSubscriptionAccessTest(t, "GET", "/dashboard/team_members/", api, authToken)
EnableSubscriptionAccess(t, api, userID)
t.Run("SuccessNoResults", func(t *testing.T) {
teamMemberCollection.DeleteMany(context.Background(), bson.M{})
response := ServeRequest(t, authToken, "GET", "/dashboard/team_members/", nil, http.StatusOK, api)
Expand Down
57 changes: 56 additions & 1 deletion backend/api/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"github.com/GeneralTask/task-manager/backend/external"
"github.com/gin-gonic/gin"
guuid "github.com/google/uuid"
"github.com/stripe/stripe-go/v76"
checkoutsession "github.com/stripe/stripe-go/v76/checkout/session"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
Expand Down Expand Up @@ -158,11 +161,63 @@ func (api *API) LoginCallback(c *gin.Context) {
if userIsNew != nil && *userIsNew {
c.Redirect(302, config.GetConfigValue("HOME_URL")+"tos-summary")
} else {
c.Redirect(302, config.GetConfigValue("HOME_URL"))
// Check subscription status and redirect to Stripe Checkout if not subscribed
redirectURL := getPostLoginRedirectURL(api, userID)
c.Redirect(302, redirectURL)
}
}
}

func getPostLoginRedirectURL(api *API, userID primitive.ObjectID) string {
userCollection := database.GetUserCollection(api.DB)
var userObject database.User
err := userCollection.FindOne(context.Background(), bson.M{"_id": userID}).Decode(&userObject)
if err != nil {
api.Logger.Error().Err(err).Msg("failed to find user for subscription check on login")
return config.GetConfigValue("HOME_URL")
}

if isUserSubscribed(&userObject) {
return config.GetConfigValue("HOME_URL")
}

// User is not subscribed — create a Stripe Checkout session and redirect
stripeCustomerID, err := getOrCreateStripeCustomer(api, &userObject)
if err != nil {
api.Logger.Error().Err(err).Msg("failed to create stripe customer on login")
return config.GetConfigValue("HOME_URL")
}

stripe.Key = config.GetConfigValue("STRIPE_SECRET_KEY")
priceID := config.GetConfigValue("STRIPE_PRICE_ID")
homeURL := config.GetConfigValue("HOME_URL")

params := &stripe.CheckoutSessionParams{
Customer: stripe.String(stripeCustomerID),
Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
LineItems: []*stripe.CheckoutSessionLineItemParams{
{
Price: stripe.String(priceID),
Quantity: stripe.Int64(1),
},
},
SuccessURL: stripe.String(homeURL + "?subscription=success"),
CancelURL: stripe.String(homeURL + "?subscription=canceled"),
AllowPromotionCodes: stripe.Bool(true),
SubscriptionData: &stripe.CheckoutSessionSubscriptionDataParams{
TrialPeriodDays: stripe.Int64(14),
},
}

session, err := checkoutsession.New(params)
if err != nil {
api.Logger.Error().Err(err).Msg("failed to create checkout session on login redirect")
return config.GetConfigValue("HOME_URL")
}

return session.URL
}

func createNewUserTasks(userID primitive.ObjectID, db *mongo.Database) error {
taskCollection := database.GetTaskCollection(db)
for index, title := range constants.StarterTasks {
Expand Down
14 changes: 11 additions & 3 deletions backend/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ func GetRouter(handlers *API) *gin.Engine {

router.POST("/linear/webhook/", handlers.LinearWebhook)

// Stripe webhook (unauthenticated, signature-verified)
router.POST("/subscriptions/webhook/", handlers.StripeWebhook)

// Slack App (Workspace level) endpoint for oauth verification
// We need this as we don't actually use the token provided, but still need to access it to
// successfully install our app in a new Workspace
Expand Down Expand Up @@ -137,14 +140,19 @@ func GetRouter(handlers *API) *gin.Engine {

router.GET("/daily_task_completion/", handlers.DailyTaskCompletionList)

// Add business middleware. Endpoints below this require business mode to be enabled
router.Use(BusinessMiddleware(handlers.DB))
// Subscription management endpoints
router.GET("/subscriptions/status/", handlers.SubscriptionStatus)
router.POST("/subscriptions/create-checkout-session/", handlers.CreateCheckoutSession)
router.POST("/subscriptions/create-portal-session/", handlers.CreatePortalSession)

// Add subscription middleware. Endpoints below this require an active subscription
router.Use(SubscriptionMiddleware(handlers.DB))
router.GET("/dashboard/data/", handlers.DashboardData)
router.GET("/dashboard/team_members/", handlers.DashboardTeamMembersList)
router.POST("/dashboard/team_members/", handlers.DashboardTeamMemberCreate)
router.DELETE("/dashboard/team_members/:team_member_id/", handlers.DashboardTeamMemberDelete)
router.GET("/dashboard/data/fetch/", handlers.DashboardFetch)
router.GET("/ping_business/", handlers.Ping)
router.GET("/ping_subscribed/", handlers.Ping)

return router
}
Loading