mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
Multi-tenant commerce and billing: orders, subscriptions, invoices, plans, usage metering and a credit ledger. One Go binary. MIT OR Apache-2.0. Claude-Session: https://claude.ai/code/session_01SAtaDFm8VDiWrhYHNNtxMw
66 lines
2.2 KiB
Go
66 lines
2.2 KiB
Go
// Package exchange wires the order-exchange HTTP surface: CRUD on exchanges
|
|
// plus confirm/cancel transitions. Every handler is tenant-scoped via the
|
|
// caller's organization namespace; custom sub-routes pass the Namespace
|
|
// middleware explicitly since the REST layer only auto-namespaces default CRUD.
|
|
package exchange
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/zap-proto/zip"
|
|
|
|
"github.com/hanzoai/commerce/datastore"
|
|
"github.com/hanzoai/commerce/middleware"
|
|
exchangeModel "github.com/hanzoai/commerce/models/exchange"
|
|
"github.com/hanzoai/commerce/util/json/http"
|
|
"github.com/hanzoai/commerce/util/rest"
|
|
)
|
|
|
|
func Route(router *zip.Group, args ...zip.Handler) {
|
|
namespaced := middleware.Namespace()
|
|
|
|
api := rest.New(exchangeModel.Exchange{})
|
|
api.POST("/:exchangeid/confirm", namespaced, Confirm)
|
|
api.POST("/:exchangeid/cancel", namespaced, Cancel)
|
|
api.Route(router, args...)
|
|
}
|
|
|
|
// Confirm transitions an open exchange to confirmed.
|
|
func Confirm(c *zip.Ctx) error {
|
|
return transition(c, exchangeModel.StatusConfirmed, "confirm")
|
|
}
|
|
|
|
// Cancel transitions an open exchange to canceled.
|
|
func Cancel(c *zip.Ctx) error {
|
|
return transition(c, exchangeModel.StatusCanceled, "cancel")
|
|
}
|
|
|
|
// transition sets an open (requested) exchange's status to target. Only open
|
|
// exchanges may be confirmed or canceled; a resolved exchange is rejected.
|
|
func transition(c *zip.Ctx, target, verb string) error {
|
|
org := middleware.GetOrganization(c)
|
|
// Per-org store: exchanges are CREATED via NewNamespaced, so the confirm/
|
|
// cancel transition MUST read from the same per-org store. datastore.New binds
|
|
// the shared systemDB regardless of the namespace in the context, so with the
|
|
// resolver installed in prod it would miss the exchange (Red MED-1).
|
|
db := datastore.NewNamespaced(org.Namespaced(c.Context()))
|
|
|
|
id := c.Param("exchangeid")
|
|
|
|
e := exchangeModel.New(db)
|
|
if err := e.GetById(id); err != nil {
|
|
return http.Fail(c, 404, "No exchange found with id: "+id, err)
|
|
}
|
|
|
|
if !e.IsOpen() {
|
|
return http.Fail(c, 409, "Cannot "+verb+" exchange in status: "+e.Status, errors.New("exchange not open"))
|
|
}
|
|
|
|
e.Status = target
|
|
if err := e.Update(); err != nil {
|
|
return http.Fail(c, 500, "Failed to "+verb+" exchange", err)
|
|
}
|
|
|
|
return http.Render(c, 200, e)
|
|
}
|