mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
package costs
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/zap-proto/zip"
|
|
|
|
"github.com/hanzoai/commerce/middleware"
|
|
"github.com/hanzoai/commerce/util/permission"
|
|
)
|
|
|
|
// Route registers the vendor-cost (COGS) endpoints. These are a PLATFORM
|
|
// god-view (what WE pay our vendors across the whole business), so on top of the
|
|
// route-level token gate every handler ALSO enforces requireCostsAdmin — see its
|
|
// doc for why the middleware alone is not enough.
|
|
func Route(r *zip.Group, args ...zip.Handler) {
|
|
adminRequired := middleware.TokenRequired(permission.Admin)
|
|
|
|
api := r.Group("costs")
|
|
api.Use(adminRequired)
|
|
|
|
api.Raw(http.MethodGet, "", GetCosts)
|
|
api.Raw(http.MethodGet, "/margin", GetMargin)
|
|
}
|
|
|
|
// requireCostsAdmin is the in-handler gate for the vendor-cost god-view. It fails
|
|
// closed (403) unless the caller is a PLATFORM admin, and is enforced INSIDE each
|
|
// handler: the route-level middleware.TokenRequired(permission.Admin) admits the
|
|
// org-level Admin bit, and these figures need the stricter platform predicate.
|
|
//
|
|
// These figures are cross-tenant PLATFORM spend/margin, so this gates on the
|
|
// STRICTER SuperAdmin predicate, NEVER the org-level Admin bit an org owner
|
|
// carries. That gate is shared with the SaaS
|
|
// metrics god-view (api/metrics) as the single middleware.RequirePlatformAdmin
|
|
// predicate — one and only one definition of "may read cross-org platform data",
|
|
// so costs and metrics can never drift apart. See middleware.MayReadPlatform for
|
|
// the exact principals admitted and why org-level admin is refused.
|
|
func requireCostsAdmin(c *zip.Ctx) bool {
|
|
return middleware.RequirePlatformAdmin(c)
|
|
}
|
|
|
|
// GetCosts returns the per-vendor COGS breakdown for a period.
|
|
//
|
|
// GET /v1/costs?period=YYYY-MM
|
|
//
|
|
// Response: {period, vendors:[{vendor,service,amountCents,period,source,note}], totalCents, currency}.
|
|
func GetCosts(c *zip.Ctx) error {
|
|
if !requireCostsAdmin(c) {
|
|
return nil
|
|
}
|
|
org := middleware.GetOrganization(c)
|
|
ctx := org.Namespaced(c.Context())
|
|
p := period(c.Query("period"))
|
|
|
|
report, _ := buildReport(ctx, org.TestMode(), p)
|
|
return c.JSON(200, report)
|
|
}
|
|
|
|
// GetMargin returns revenue (the usage ledger) minus COGS (this package) plus the
|
|
// gross-margin percentage — the MARGIN view for admin.hanzo.ai.
|
|
//
|
|
// GET /v1/costs/margin?period=YYYY-MM
|
|
//
|
|
// Revenue is the sum of api-usage charges in the period (what customers paid);
|
|
// COGS is the vendor total. Kept DRY with GetCosts: buildReport returns both the
|
|
// vendor lines and the ledger aggregate, so revenue and cost come from one walk.
|
|
func GetMargin(c *zip.Ctx) error {
|
|
if !requireCostsAdmin(c) {
|
|
return nil
|
|
}
|
|
org := middleware.GetOrganization(c)
|
|
ctx := org.Namespaced(c.Context())
|
|
p := period(c.Query("period"))
|
|
|
|
report, ledger := buildReport(ctx, org.TestMode(), p)
|
|
|
|
revenue := ledger.RevenueCents
|
|
cogs := report.TotalCents
|
|
margin := revenue - cogs
|
|
|
|
return c.JSON(200, MarginReport{
|
|
Period: p,
|
|
RevenueCents: revenue,
|
|
COGSCents: cogs,
|
|
MarginCents: margin,
|
|
GrossMarginPct: grossMarginPct(revenue, margin),
|
|
Vendors: report.Vendors,
|
|
Currency: "usd",
|
|
})
|
|
}
|