package main import ( "log" "github.com/breakpilot/billing-service/internal/config" "github.com/breakpilot/billing-service/internal/database" "github.com/breakpilot/billing-service/internal/handlers" "github.com/breakpilot/billing-service/internal/middleware" "github.com/breakpilot/billing-service/internal/services" "github.com/gin-gonic/gin" ) func main() { // Load configuration cfg, err := config.Load() if err != nil { log.Fatalf("Failed to load config: %v", err) } // Initialize database db, err := database.Connect(cfg.DatabaseURL) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer db.Close() // Run migrations if err := database.Migrate(db); err != nil { log.Fatalf("Failed to run migrations: %v", err) } // Setup Gin router if cfg.Environment == "production" { gin.SetMode(gin.ReleaseMode) } router := gin.Default() // Global middleware router.Use(middleware.CORS()) router.Use(middleware.RequestLogger()) router.Use(middleware.RateLimiter()) // Health check (no auth required) router.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{ "status": "healthy", "service": "billing-service", "version": "1.0.0", }) }) // Initialize services subscriptionService := services.NewSubscriptionService(db) // Create Stripe service (mock or real depending on config) var stripeService *services.StripeService if cfg.IsMockMode() { log.Println("Starting in MOCK MODE - Stripe API calls will be simulated") stripeService = services.NewMockStripeService( cfg.BillingSuccessURL, cfg.BillingCancelURL, cfg.TrialPeriodDays, subscriptionService, ) } else { stripeService = services.NewStripeService( cfg.StripeSecretKey, cfg.StripeWebhookSecret, cfg.BillingSuccessURL, cfg.BillingCancelURL, cfg.TrialPeriodDays, subscriptionService, ) } entitlementService := services.NewEntitlementService(db, subscriptionService) usageService := services.NewUsageService(db, entitlementService) // Initialize handlers billingHandler := handlers.NewBillingHandler( db, subscriptionService, stripeService, entitlementService, usageService, ) webhookHandler := handlers.NewWebhookHandler( db, cfg.StripeWebhookSecret, subscriptionService, entitlementService, ) // API v1 routes v1 := router.Group("/api/v1/billing") { // Stripe webhook (no auth - uses Stripe signature) v1.POST("/webhook", webhookHandler.HandleStripeWebhook) // ============================================= // User Endpoints (require JWT auth) // ============================================= user := v1.Group("") user.Use(middleware.AuthMiddleware(cfg.JWTSecret)) { // Subscription status and management user.GET("/status", billingHandler.GetBillingStatus) user.GET("/plans", billingHandler.GetPlans) user.POST("/trial/start", billingHandler.StartTrial) user.POST("/change-plan", billingHandler.ChangePlan) user.POST("/cancel", billingHandler.CancelSubscription) user.GET("/portal", billingHandler.GetCustomerPortal) } // ============================================= // Internal Endpoints (service-to-service) // ============================================= internal := v1.Group("") internal.Use(middleware.InternalAPIKeyMiddleware(cfg.InternalAPIKey)) { // Entitlements internal.GET("/entitlements/:userId", billingHandler.GetEntitlements) internal.GET("/entitlements/check/:userId/:feature", billingHandler.CheckEntitlement) // Usage tracking internal.POST("/usage/track", billingHandler.TrackUsage) internal.GET("/usage/check/:userId/:type", billingHandler.CheckUsage) } } // Start server port := cfg.Port if port == "" { port = "8083" } log.Printf("Starting Billing Service on port %s", port) if err := router.Run(":" + port); err != nil { log.Fatalf("Failed to start server: %v", err) } }