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
80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
package wire
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/zap-proto/zip"
|
|
|
|
"github.com/hanzoai/commerce/datastore"
|
|
"github.com/hanzoai/commerce/log"
|
|
"github.com/hanzoai/commerce/middleware"
|
|
"github.com/hanzoai/commerce/models/order"
|
|
"github.com/hanzoai/commerce/util/json/http"
|
|
)
|
|
|
|
type wireCreditRequest struct {
|
|
OrderID string `json:"orderId"`
|
|
Amount float64 `json:"amount"`
|
|
Currency string `json:"currency"`
|
|
Reference string `json:"reference"`
|
|
Notes string `json:"notes"`
|
|
}
|
|
|
|
type wireCreditResponse struct {
|
|
OrderID string `json:"orderId"`
|
|
Status string `json:"status"`
|
|
Reference string `json:"reference"`
|
|
}
|
|
|
|
// Credit manually credits an account when a wire transfer is received.
|
|
// POST /v1/checkout/wire/credit
|
|
// Admin-only endpoint. Marks the pending wire payment as completed.
|
|
func Credit(c *zip.Ctx) error {
|
|
// Money move (manually marks an order paid): admin-only, enforced inside the
|
|
// handler because the route middleware no-ops on the IAM path (Red HIGH-4).
|
|
if !middleware.RequireAdmin(c) {
|
|
return nil // RequireAdmin already wrote the 403 response
|
|
}
|
|
|
|
var req wireCreditRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return http.Fail(c, 400, "Invalid request", err)
|
|
}
|
|
|
|
if req.OrderID == "" {
|
|
return http.Fail(c, 400, "orderId is required", errors.New("orderId is required"))
|
|
}
|
|
if req.Amount <= 0 {
|
|
return http.Fail(c, 400, "amount must be positive", errors.New("amount must be positive"))
|
|
}
|
|
|
|
org := middleware.GetOrganization(c)
|
|
|
|
// Set up the db with the namespaced context. NewNamespaced isolates the
|
|
// order in the caller org's own store (Red CRIT-2).
|
|
ctx := org.Namespaced(c.Context())
|
|
db := datastore.NewNamespaced(ctx)
|
|
|
|
// Get the order
|
|
ord := order.New(db)
|
|
if err := ord.GetById(req.OrderID); err != nil {
|
|
return http.Fail(c, 404, "Order not found", err)
|
|
}
|
|
|
|
// Mark order as paid via wire credit
|
|
ord.Status = order.Open
|
|
if err := ord.Put(); err != nil {
|
|
log.Error("Failed to save order after wire credit: %v", err, c)
|
|
return http.Fail(c, 500, "Failed to update order", err)
|
|
}
|
|
|
|
log.Info("Wire credit applied: order=%s amount=%.2f currency=%s ref=%s",
|
|
req.OrderID, req.Amount, req.Currency, req.Reference, c)
|
|
|
|
return http.Render(c, 200, wireCreditResponse{
|
|
OrderID: req.OrderID,
|
|
Status: "credited",
|
|
Reference: req.Reference,
|
|
})
|
|
}
|