Skip to content

Commit a7b6fb7

Browse files
authored
Merge pull request #1445 from getfider/billing-improvements
2 parents 83d3a8c + 682f040 commit a7b6fb7

File tree

17 files changed

+132
-98
lines changed

17 files changed

+132
-98
lines changed

app/handlers/admin.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ func AdvancedSettingsPage() web.HandlerFunc {
3636
Page: "Administration/pages/AdvancedSettings.page",
3737
Title: "Advanced · Site Settings",
3838
Data: web.Map{
39-
"customCSS": c.Tenant().CustomCSS,
40-
"allowedSchemes": c.Tenant().AllowedSchemes,
41-
"licenseKey": billingState.Result.LicenseKey,
42-
"isCommercial": c.Tenant().IsCommercial,
39+
"customCSS": c.Tenant().CustomCSS,
40+
"allowedSchemes": c.Tenant().AllowedSchemes,
41+
"licenseKey": billingState.Result.LicenseKey,
42+
"hasCommercialFeatures": c.Tenant().HasCommercialFeatures,
4343
},
4444
})
4545
}

app/handlers/billing.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ func ManageBilling() web.HandlerFunc {
2727
Data: web.Map{
2828
"stripeCustomerID": billingState.Result.CustomerID,
2929
"stripeSubscriptionID": billingState.Result.SubscriptionID,
30-
"licenseKey": billingState.Result.LicenseKey,
31-
"paddleSubscriptionID": billingState.Result.PaddleSubscriptionID,
32-
"isCommercial": c.Tenant().IsCommercial,
30+
"licenseKey": billingState.Result.LicenseKey,
31+
"paddleSubscriptionID": billingState.Result.PaddleSubscriptionID,
32+
"hasCommercialFeatures": c.Tenant().HasCommercialFeatures,
3333
},
3434
})
3535
}

app/models/entity/tenant.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ type Tenant struct {
2222
IsEmailAuthAllowed bool `json:"isEmailAuthAllowed"`
2323
IsFeedEnabled bool `json:"isFeedEnabled"`
2424
PreventIndexing bool `json:"preventIndexing"`
25-
IsModerationEnabled bool `json:"isModerationEnabled"`
26-
IsCommercial bool `json:"isCommercial"`
25+
IsModerationEnabled bool `json:"isModerationEnabled"`
26+
HasCommercialFeatures bool `json:"hasCommercialFeatures"`
2727
}
2828

2929
func (t *Tenant) IsDisabled() bool {

app/pkg/web/testdata/home_ssr.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ <h2 class="text-display2">Please enable JavaScript</h2>
4545

4646
<script id="server-data" type="application/json">
4747

48-
{"contextID":"CONTEXT_ID","description":"My Page Description","page":"Test.page","props":{"countPerStatus":{},"posts":[],"tags":[]},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"en","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"isCommercial":false},"title":"My Page Title · "}
48+
{"contextID":"CONTEXT_ID","description":"My Page Description","page":"Test.page","props":{"countPerStatus":{},"posts":[],"tags":[]},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"en","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"hasCommercialFeatures":false},"title":"My Page Title · "}
4949

5050
</script>
5151

app/pkg/web/testdata/tenant.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ <h2 class="text-display2">Please enable JavaScript</h2>
4545

4646
<script id="server-data" type="application/json">
4747

48-
{"contextID":"CONTEXT_ID","page":"","props":{},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"Game of Thrones","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"isCommercial":false},"title":"Game of Thrones"}
48+
{"contextID":"CONTEXT_ID","page":"","props":{},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"Game of Thrones","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"hasCommercialFeatures":false},"title":"Game of Thrones"}
4949

5050
</script>
5151

app/services/sqlstore/dbEntities/tenant.go

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,60 +8,61 @@ import (
88
)
99

1010
type Tenant struct {
11-
ID int `db:"id"`
12-
Name string `db:"name"`
13-
Subdomain string `db:"subdomain"`
14-
CNAME string `db:"cname"`
15-
Invitation string `db:"invitation"`
16-
WelcomeMessage string `db:"welcome_message"`
17-
WelcomeHeader string `db:"welcome_header"`
18-
Status int `db:"status"`
19-
Locale string `db:"locale"`
20-
IsPrivate bool `db:"is_private"`
21-
LogoBlobKey string `db:"logo_bkey"`
22-
CustomCSS string `db:"custom_css"`
23-
AllowedSchemes string `db:"allowed_schemes"`
24-
IsEmailAuthAllowed bool `db:"is_email_auth_allowed"`
25-
IsFeedEnabled bool `db:"is_feed_enabled"`
26-
PreventIndexing bool `db:"prevent_indexing"`
27-
IsModerationEnabled bool `db:"is_moderation_enabled"`
28-
IsPro bool `db:"is_pro"`
11+
ID int `db:"id"`
12+
Name string `db:"name"`
13+
Subdomain string `db:"subdomain"`
14+
CNAME string `db:"cname"`
15+
Invitation string `db:"invitation"`
16+
WelcomeMessage string `db:"welcome_message"`
17+
WelcomeHeader string `db:"welcome_header"`
18+
Status int `db:"status"`
19+
Locale string `db:"locale"`
20+
IsPrivate bool `db:"is_private"`
21+
LogoBlobKey string `db:"logo_bkey"`
22+
CustomCSS string `db:"custom_css"`
23+
AllowedSchemes string `db:"allowed_schemes"`
24+
IsEmailAuthAllowed bool `db:"is_email_auth_allowed"`
25+
IsFeedEnabled bool `db:"is_feed_enabled"`
26+
PreventIndexing bool `db:"prevent_indexing"`
27+
IsModerationEnabled bool `db:"is_moderation_enabled"`
28+
IsPro bool `db:"is_pro"`
29+
HasPaddleSubscription bool `db:"has_paddle_subscription"`
2930
}
3031

3132
func (t *Tenant) ToModel() *entity.Tenant {
3233
if t == nil {
3334
return nil
3435
}
3536

36-
// Compute isCommercial based on hosting mode
37-
var isCommercial bool
37+
// Compute hasCommercialFeatures based on hosting mode
38+
var hasCommercialFeatures bool
3839
if env.IsSingleHostMode() {
3940
// Self-hosted: check if license service validated successfully
40-
isCommercial = services.IsCommercialFeatureEnabled(services.FeatureContentModeration)
41+
hasCommercialFeatures = services.IsCommercialFeatureEnabled(services.FeatureContentModeration)
4142
} else {
42-
// Hosted multi-tenant: check if this tenant has Pro subscription
43-
isCommercial = t.IsPro
43+
// Hosted multi-tenant: check if this tenant has Pro subscription or legacy Paddle subscription
44+
hasCommercialFeatures = t.IsPro || t.HasPaddleSubscription
4445
}
4546

4647
tenant := &entity.Tenant{
47-
ID: t.ID,
48-
Name: t.Name,
49-
Subdomain: t.Subdomain,
50-
CNAME: t.CNAME,
51-
Invitation: t.Invitation,
52-
WelcomeMessage: t.WelcomeMessage,
53-
WelcomeHeader: t.WelcomeHeader,
54-
Status: enum.TenantStatus(t.Status),
55-
Locale: t.Locale,
56-
IsPrivate: t.IsPrivate,
57-
LogoBlobKey: t.LogoBlobKey,
58-
CustomCSS: t.CustomCSS,
59-
AllowedSchemes: t.AllowedSchemes,
60-
IsEmailAuthAllowed: t.IsEmailAuthAllowed,
61-
IsFeedEnabled: t.IsFeedEnabled,
62-
PreventIndexing: t.PreventIndexing,
63-
IsModerationEnabled: t.IsModerationEnabled,
64-
IsCommercial: isCommercial,
48+
ID: t.ID,
49+
Name: t.Name,
50+
Subdomain: t.Subdomain,
51+
CNAME: t.CNAME,
52+
Invitation: t.Invitation,
53+
WelcomeMessage: t.WelcomeMessage,
54+
WelcomeHeader: t.WelcomeHeader,
55+
Status: enum.TenantStatus(t.Status),
56+
Locale: t.Locale,
57+
IsPrivate: t.IsPrivate,
58+
LogoBlobKey: t.LogoBlobKey,
59+
CustomCSS: t.CustomCSS,
60+
AllowedSchemes: t.AllowedSchemes,
61+
IsEmailAuthAllowed: t.IsEmailAuthAllowed,
62+
IsFeedEnabled: t.IsFeedEnabled,
63+
PreventIndexing: t.PreventIndexing,
64+
IsModerationEnabled: t.IsModerationEnabled,
65+
HasCommercialFeatures: hasCommercialFeatures,
6566
}
6667

6768
return tenant

app/services/sqlstore/postgres/billing.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func activateStripeSubscription(ctx context.Context, c *cmd.ActivateStripeSubscr
7676
INSERT INTO tenants_billing (tenant_id, stripe_customer_id, stripe_subscription_id, license_key)
7777
VALUES ($1, $2, $3, $4)
7878
ON CONFLICT (tenant_id) DO UPDATE
79-
SET stripe_customer_id = $2, stripe_subscription_id = $3, license_key = $4
79+
SET stripe_customer_id = $2, stripe_subscription_id = $3, license_key = $4, paddle_subscription_id = NULL
8080
`, c.TenantID, c.CustomerID, c.SubscriptionID, c.LicenseKey)
8181
if err != nil {
8282
return errors.Wrap(err, "failed to activate stripe subscription")

app/services/sqlstore/postgres/tenant.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,11 @@ func getFirstTenant(ctx context.Context, q *query.GetFirstTenant) error {
208208
tenant := dbEntities.Tenant{}
209209

210210
err := trx.Get(&tenant, `
211-
SELECT id, name, subdomain, cname, invitation, locale, welcome_message, welcome_header, status, is_private, logo_bkey, custom_css, allowed_schemes, is_email_auth_allowed, is_feed_enabled, is_moderation_enabled, prevent_indexing, is_pro
212-
FROM tenants
213-
ORDER BY id LIMIT 1
211+
SELECT t.id, t.name, t.subdomain, t.cname, t.invitation, t.locale, t.welcome_message, t.welcome_header, t.status, t.is_private, t.logo_bkey, t.custom_css, t.allowed_schemes, t.is_email_auth_allowed, t.is_feed_enabled, t.is_moderation_enabled, t.prevent_indexing, t.is_pro,
212+
(b.paddle_subscription_id IS NOT NULL AND b.stripe_subscription_id IS NULL) AS has_paddle_subscription
213+
FROM tenants t
214+
LEFT JOIN tenants_billing b ON b.tenant_id = t.id
215+
ORDER BY t.id LIMIT 1
214216
`)
215217
if err != nil {
216218
return errors.Wrap(err, "failed to get first tenant")
@@ -226,10 +228,12 @@ func getTenantByDomain(ctx context.Context, q *query.GetTenantByDomain) error {
226228
tenant := dbEntities.Tenant{}
227229

228230
err := trx.Get(&tenant, `
229-
SELECT id, name, subdomain, cname, invitation, locale, welcome_message, welcome_header, status, is_private, logo_bkey, custom_css, allowed_schemes, is_email_auth_allowed, is_feed_enabled, is_moderation_enabled, prevent_indexing, is_pro
231+
SELECT t.id, t.name, t.subdomain, t.cname, t.invitation, t.locale, t.welcome_message, t.welcome_header, t.status, t.is_private, t.logo_bkey, t.custom_css, t.allowed_schemes, t.is_email_auth_allowed, t.is_feed_enabled, t.is_moderation_enabled, t.prevent_indexing, t.is_pro,
232+
(b.paddle_subscription_id IS NOT NULL AND b.stripe_subscription_id IS NULL) AS has_paddle_subscription
230233
FROM tenants t
231-
WHERE subdomain = $1 OR subdomain = $2 OR cname = $3
232-
ORDER BY cname DESC
234+
LEFT JOIN tenants_billing b ON b.tenant_id = t.id
235+
WHERE t.subdomain = $1 OR t.subdomain = $2 OR t.cname = $3
236+
ORDER BY t.cname DESC
233237
`, env.Subdomain(q.Domain), q.Domain, q.Domain)
234238
if err != nil {
235239
return errors.Wrap(err, "failed to get tenant with domain '%s'", q.Domain)

app/tasks/userlist.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func UserListCreateCompany(tenant entity.Tenant, user entity.User) worker.Task {
2020
})
2121

2222
plan := enum.PlanFree
23-
if tenant.IsCommercial {
23+
if tenant.HasCommercialFeatures {
2424
plan = enum.PlanPro
2525
}
2626

commercial/components/ModerationIndicator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const ModerationIndicator = () => {
1111
const [loading, setLoading] = useState(true)
1212

1313
// Check if commercial license is available
14-
const hasCommercialLicense = fider.session.tenant.isCommercial
14+
const hasCommercialLicense = fider.session.tenant.hasCommercialFeatures
1515

1616
useEffect(() => {
1717
const fetchCount = async () => {

0 commit comments

Comments
 (0)