mirror of
https://github.com/hanzoai/agency.git
synced 2026-11-02 19:13:28 +00:00
The onboarding checkout POSTed the mint straight from the browser to commerce /v1/checkout/sessions. commerce v1.46.4 (P0) closed that anonymous path, so the browser POST now 401s and checkout is broken. Fix: a small stdlib-only Go server (server/) serves the embedded Vite SPA AND a single server-side checkout entry POST /v1/checkout. The browser holds no token and can choose no org/price/redirect — it only names a plan. The BFF calls commerce with a per-org Published storefront token from KMS; org is derived from the token, prices from the server-side plans map, success/cancel URLs from PUBLIC_BASE_URL (https://hanzo.agency). Fails closed (503) without the token — never an anon mint. - server/main.go, checkout.go, spa.go, embed.go — BFF + SPA server - server/checkout_test.go — authed mint, no client override, fail-closed, wire - src/lib/commerce.ts — browser now calls same-origin /v1/checkout - Dockerfile — node build (dist) -> go build (embed) -> alpine - pairs with commerce v1.46.7 (adds hanzo.agency to the redirect allowlist)
41 lines
1.1 KiB
Go
41 lines
1.1 KiB
Go
package main
|
|
|
|
import (
|
|
"io/fs"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// spaHandler serves the embedded Vite SPA. Hashed asset requests (a file that
|
|
// exists in the bundle) are served with an immutable long cache; every other
|
|
// path falls through to index.html so the client-side router (react-router)
|
|
// renders it, with no-cache so a deploy rolls out without a stale shell.
|
|
func spaHandler() http.Handler {
|
|
root := spaFS()
|
|
files := http.FileServer(http.FS(root))
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
p := strings.TrimPrefix(r.URL.Path, "/")
|
|
if p == "" {
|
|
p = "index.html"
|
|
}
|
|
|
|
if i := strings.LastIndexByte(p, '.'); i >= 0 && !strings.Contains(p[i:], "/") {
|
|
if st, err := fs.Stat(root, p); err == nil && !st.IsDir() {
|
|
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
|
files.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
idx, err := fs.ReadFile(root, "index.html")
|
|
if err != nil {
|
|
http.Error(w, "agency SPA not built", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
_, _ = w.Write(idx)
|
|
})
|
|
}
|