S SmartDocs
Series: Go go 100 lines · Updated 2026-04-18

main.go

Go/payment-gateway-challenge/cmd/server/main.go

// Command server is the payment gateway process entrypoint.
//
// It loads configuration, connects to PostgreSQL, wires the bank HTTP client,
// and serves the Echo API until SIGINT/SIGTERM.
//
// @title						Payment Gateway Challenge (Go)
// @version					1.0
// @description				Checkout.com offline coding interview — Payment Gateway API (Echo + PostgreSQL). Use **Swagger UI** at `/swagger/index.html`. Behaviour and assumptions: see `DESIGN.md`.
// @termsOfService				http://swagger.io/terms/
//
// @contact.name				Checkout.com Recruitment
// @contact.url				https://www.checkout.com/careers
// @contact.email				careers@checkout.com
//
// @license.name				MIT
// @license.url				https://opensource.org/licenses/MIT
//
// @host						localhost:8090
// @BasePath					/
package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"payment-gateway-challenge/docs"
	"payment-gateway-challenge/internal/bank"
	"payment-gateway-challenge/internal/config"
	"payment-gateway-challenge/internal/database"
	"payment-gateway-challenge/internal/handlers"
	"payment-gateway-challenge/internal/repository"
	"payment-gateway-challenge/internal/router"

	"github.com/joho/godotenv"
)

func main() {
	if err := godotenv.Load(); err != nil {
		log.Printf("no .env file loaded: %v", err)
	}

	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("config: %v", err)
	}

	// Align Swagger "Try it out" host with the actual HTTP listen address
	// (e.g. :8090 → localhost:8090, or 0.0.0.0:8090 unchanged).
	docs.SwaggerInfo.Host = swaggerAPIHost(cfg.ServerAddr)
	docs.SwaggerInfo.Version = "1.0"

	db, err := database.Connect(cfg.DatabaseURL)
	if err != nil {
		log.Fatalf("database: %v", err)
	}

	// Dedicated HTTP client for outbound bank calls (separate timeout from Echo server).
	bankHTTP := &http.Client{Timeout: cfg.HTTPClientTimeout}
	bankClient := bank.NewClient(cfg.BankSimulatorURL, bankHTTP)

	repo := repository.NewGormPaymentRepo(db)
	api := handlers.NewPaymentAPI(cfg, repo, bankClient)
	e := router.NewEcho(api)

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	go func() {
		<-ctx.Done()
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		if err := e.Shutdown(shutdownCtx); err != nil {
			log.Printf("graceful shutdown: %v", err)
		}
	}()

	log.Printf("payment gateway listening on %s (bank: %s)", cfg.ServerAddr, cfg.BankSimulatorURL)
	log.Printf("Swagger UI: http://%s/swagger/index.html", docs.SwaggerInfo.Host)
	if err := e.Start(cfg.ServerAddr); err != nil && err != http.ErrServerClosed {
		log.Fatalf("server: %v", err)
	}
}

// swaggerAPIHost converts a listen address like ":8090" into a host Swagger UI can call.
func swaggerAPIHost(listenAddr string) string {
	listenAddr = strings.TrimSpace(listenAddr)
	if listenAddr == "" {
		return "localhost:8090"
	}
	if strings.HasPrefix(listenAddr, ":") {
		return "localhost" + listenAddr
	}
	return listenAddr
}

Related articles