Files
hanzoai_agent/control-plane/internal/handlers/billing.go
zooqueenandHanzo Dev 1332f6e2bc feat(billing): add prepaid credit enforcement to control-plane
- BillingService client calls Commerce API for balance checks and debits
- Balance gate in prepareExecution blocks $0 balance with HTTP 402
- Post-execution billing in completeExecution/failExecution debits actual cost
- Balance proxy endpoint at GET /api/v1/billing/balance
- DB migration adds billing fields to executions table
- K8s deployment wired with BILLING_COMMERCE_URL and admin token secret

Co-authored-by: Hanzo Dev <[email protected]>
2026-02-18 13:20:36 -08:00

45 lines
1.2 KiB
Go

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/hanzoai/agents/control-plane/internal/services"
)
// BillingBalanceHandler proxies balance queries to Commerce, scoped to
// the authenticated user. The control-plane adds the admin token so
// the frontend never needs direct Commerce credentials.
//
// GET /api/v1/billing/balance?user=<iam-user-id>&currency=usd
func BillingBalanceHandler(billing *services.BillingService) gin.HandlerFunc {
return func(c *gin.Context) {
if billing == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "billing not configured"})
return
}
userID := c.Query("user")
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "user query parameter is required"})
return
}
available, err := billing.CheckBalance(c.Request.Context(), userID)
if err != nil {
if err == services.ErrCommerceUnavailable {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "billing service unavailable"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"available": available,
"currency": "usd",
})
}
}