Subscription Package
pkg/subscription provides feature flags, entity limits, seat limits, grace periods, and Stripe entitlement sync for multi-tenant ERP applications.
Initialization
import (
"github.com/iota-uz/iota-sdk/pkg/subscription"
)
cfg := subscription.Config{
DefaultPlan: "FREE",
Plans: []subscription.PlanDefinition{
{
PlanID: "FREE",
DisplayName: "Free",
Features: []string{"core_access"},
EntityLimits: map[string]int{"drivers": 50},
},
{
PlanID: "PRO",
DisplayName: "Pro",
ParentPlanID: "FREE",
Features: []string{"shyona_access"},
EntityLimits: map[string]int{"drivers": -1},
},
},
}
svc, err := subscription.NewService(cfg)
if err != nil {
panic(err)
}
app.RegisterServices(svc)Middleware
import "github.com/iota-uz/iota-sdk/pkg/subscription"
router.Use(subscription.RequireFeature(svc, subscription.FeatureKey("core_access")))
router.Use(subscription.RequirePlan(svc, "FREE", "PRO"))
router.Use(subscription.EnforceLimit(svc, "drivers"))Repository-Backed Middleware Bridge
For Stripe-driven entitlement changes that are written to PostgreSQL, use the bridge evaluator so middleware reads current repository state instead of only in-memory grants:
import (
"github.com/iota-uz/iota-sdk/pkg/subscription"
subbridge "github.com/iota-uz/iota-sdk/pkg/subscription/bridge"
)
repoEvaluator, err := subbridge.NewEvaluator(cfg, subscriptionRepo)
if err != nil {
panic(err)
}
router.Use(subscription.RequireFeature(repoEvaluator, "core_access"))
router.Use(subscription.RequirePlan(repoEvaluator, "FREE", "PRO"))
router.Use(subscription.EnforceLimit(repoEvaluator, "drivers"))Service-Layer Checks
subject := subscription.Subject{
Scope: subscription.ScopeTenant,
ID: tenantID,
}
quota, err := subscription.NewQuotaKey("drivers", "", subscription.WindowNone)
if err != nil {
return err
}
result, err := svc.EvaluateLimit(
ctx,
subject,
quota,
)
if err != nil {
return err
}
if !result.Allowed {
return subscription.LimitExceededError{
Quota: quota,
Current: result.Current,
Limit: result.Limit,
}
}Stripe Webhook Sync
Use pkg/subscription/stripe to process Stripe entitlement and billing lifecycle events:
import substripe "github.com/iota-uz/iota-sdk/pkg/subscription/stripe"
stripeSync, err := substripe.NewService(
substripe.Config{
SecretKey: stripeSecret,
GracePeriodDays: 7,
DefaultPlan: "FREE",
},
subscriptionRepo,
nil, // cache invalidator (optional; implement stripe.CacheInvalidator if needed)
nil, // use default stripe client
)
if err != nil {
panic(err)
}stripeSync.HandleStripeEvent(ctx, event) supports:
entitlements.active_entitlement_summary.updatedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
UI Components
Reusable Templ components live in pkg/subscription/ui/components:
UsageBarPlanBadgeUpgradePromptLimitWarningPlanComparison
Testing
Use pkg/subscription/testing.NewMockEngine() in module tests.
Migration Notes
Migration migrations/changes-1772374064.sql creates:
subscription_entitlementssubscription_entity_countssubscription_planssubscription_webhook_events
The application seed flow backfills tenants with a default FREE entitlement row and initializes current_seats from the current tenant user count.