package main import ( "context" "log" "net/http" "github.com/flatrender/payment-svc/internal/config" "github.com/flatrender/payment-svc/internal/db" "github.com/flatrender/payment-svc/internal/handlers" "github.com/flatrender/payment-svc/internal/middleware" "github.com/flatrender/payment-svc/internal/web" "github.com/flatrender/payment-svc/internal/zarinpal" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgxpool" ) func main() { cfg := config.Load() pool, err := pgxpool.New(context.Background(), cfg.DatabaseURL) if err != nil { log.Fatalf("connect db: %v", err) } defer pool.Close() if err := pool.Ping(context.Background()); err != nil { log.Fatalf("ping db: %v", err) } store := db.NewStore(pool) zp := zarinpal.New() disp := handlers.NewDispatcher(store) // Background webhook delivery loop. go disp.Run(context.Background()) payH := handlers.NewPayHandler(store, zp, disp, cfg) adminH := handlers.NewAdminHandler(store) r := gin.Default() r.SetTrustedProxies(nil) //nolint — behind mirror-nginx; we read X-Forwarded-* only informally health := func(c *gin.Context) { if err := store.Ping(c.Request.Context()); err != nil { c.JSON(http.StatusServiceUnavailable, gin.H{"status": "down", "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "ok", "service": "payment"}) } r.GET("/health", health) r.GET("/healthz", health) // Public pages + ZarinPal callback (the single verified domain). r.GET("/", web.Landing) r.GET("/result", web.Result) r.GET("/callback/zarinpal", payH.Callback) v1 := r.Group("/v1") // ── Client API (API key + HMAC signature) ──────────────────────────────── pay := v1.Group("/pay", middleware.ClientAuth(store)) { pay.POST("/request", payH.Request) pay.POST("/inquiry", payH.Inquiry) } // ── Admin API (FlatRender admin JWT) ───────────────────────────────────── admin := v1.Group("/admin", middleware.JWTAuth(cfg.JWTSecret), middleware.RequireAdmin()) { admin.GET("/clients", adminH.List) admin.POST("/clients", adminH.Create) admin.GET("/clients/:id", adminH.Get) admin.PUT("/clients/:id", adminH.Update) admin.DELETE("/clients/:id", adminH.Delete) admin.POST("/clients/:id/rotate-secret", adminH.RotateSecret) admin.GET("/transactions", adminH.ListTransactions) } log.Printf("payment-svc listening on :%s (sandbox=%v, unit=%s, base=%s)", cfg.Port, cfg.ZarinPalSandbox, cfg.ZarinPalAmountUnit, cfg.PublicBaseURL) if err := r.Run(":" + cfg.Port); err != nil { log.Fatalf("server: %v", err) } }