Files
commerce/api/billing/plans_seller_test.go
zeekayandHanzo Dev 9b08b9a062 Each brand sells, takes cards and lists products as itself
Four places still answered for Hanzo on another brand's host.

- The org endpoint published the deployment's Square application for
  every brand. Those credentials are the default brand's account; any
  other brand now publishes Square, and the square provider, only with
  an account of its own (payment.SquareOwned).
- The purchase doors bought a plan wherever the catalog is not on sale:
  subscribe/card, POST /subscriptions and a plan change answer 404 on a
  host whose brand Sells nothing, as the list and GetPlan already do.
- The public catalog listed entries that name their brands under every
  brand whose category admits them. An entry now lists only under the
  brands it names, as the console scopes it, and with no ?brand the
  host's brand is the default rather than hanzo.
- Zoo's identity origin is zoolabs.id: it replaces zoo.id, which is not
  Zoo's, in the brand table and in Zoo's return hosts.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-24 01:47:20 -07:00

108 lines
3.9 KiB
Go

package billing
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/zap-proto/zip"
"github.com/hanzoai/commerce/util/test/ae"
)
// TestReadPlansSellsOnlyOnTheSellersHosts: GET /v1/billing/plans answers the
// catalog of the brand the host resolves to. Every brand's pay host reads it,
// and each listed Hanzo's ladder under its own name — pay.lux.cloud and
// pay.zoo.cloud offered Pro and Max as if Lux and Zoo sold them.
func TestReadPlansSellsOnlyOnTheSellersHosts(t *testing.T) {
c := ae.NewContext()
defer c.Close()
if _, _, err := SeedPlans(c); err != nil {
t.Fatalf("seed: %v", err)
}
// Hanzo's hosts, and a caller that names no host (the deployment default).
for _, host := range []string{"pay.hanzo.ai", "api.hanzo.ai", "PAY.HANZO.AI:443", ""} {
rows, err := ReadPlans(c, host, "", nil)
if err != nil {
t.Fatalf("%q: %v", host, err)
}
if len(rows) != len(catalog) {
t.Errorf("%q lists %d plans, the catalog has %d", host, len(rows), len(catalog))
}
}
// Every other brand's pay host sells none — an empty list, never Hanzo's, and
// never nil, which a JSON reader would take for a failure.
for _, host := range []string{"pay.lux.cloud", "pay.lux.tel", "pay.zoo.cloud", "Pay.Zoo.Cloud:443", "pay.pars.network"} {
rows, err := ReadPlans(c, host, "", nil)
if err != nil {
t.Fatalf("%q: %v", host, err)
}
if rows == nil || len(rows) != 0 {
t.Errorf("%q lists %v, want an empty list", host, rows)
}
if dns, _ := ReadPlans(c, host, "dns", nil); len(dns) != 0 {
t.Errorf("%q lists %d dns plans, want none", host, len(dns))
}
}
}
// TestSellsFollowsTheOrgEndpointsTable: the catalog's seller is decided by the
// same host→brand table the org endpoint wears, so a spoof suffix is not Hanzo.
func TestSellsFollowsTheOrgEndpointsTable(t *testing.T) {
for host, want := range map[string]bool{
"pay.hanzo.ai": true,
"hanzo.ai": true,
"pay.lux.cloud": false,
"lux.id": false,
"zoo.ngo": false,
"pay.hanzo.ai.lux.tel": false,
"pay.lux.cloud.hanzo.ai": true,
} {
if got := Sells(host); got != want {
t.Errorf("Sells(%q) = %v, want %v", host, got, want)
}
}
}
// The purchase doors ask the same question as the list. A plan bought on a
// brand's host that sells no catalog is a plan that is not there — refused with
// the same 404 as a slug that never existed, before anything is charged.
func TestPurchaseDoorsSellOnlyOnTheSellersHosts(t *testing.T) {
ctx := ae.NewContext()
defer ctx.Close()
if _, _, err := SeedPlans(ctx); err != nil {
t.Fatalf("seed: %v", err)
}
org := moneyOrg("acme")
at := func(host, method, path, pattern, body string, h zip.Handler) int {
req := httptest.NewRequest(method, path, bytes.NewBufferString(body))
req.Host = host
req.Header.Set("Content-Type", "application/json")
return driveSeeded(func(c *zip.Ctx) {
c.Locals("organization", org)
c.SetContext(ctx)
c1MintPrincipal(c)
}, pattern, req, h).StatusCode
}
for _, host := range []string{"pay.lux.cloud", "pay.lux.tel", "pay.zoo.cloud", "pay.pars.network"} {
if got := at(host, http.MethodPost, "/v1/billing/subscribe/card", "/v1/billing/subscribe/card",
`{"sourceId":"cnon:card-nonce-ok","planId":"pro"}`, SubscribeWithCard); got != http.StatusNotFound {
t.Errorf("subscribe/card on %s = %d, want 404", host, got)
}
if got := at(host, http.MethodPost, "/v1/billing/subscriptions", "/v1/billing/subscriptions",
`{"userId":"acme","planId":"free"}`, CreateBillingSubscription); got != http.StatusNotFound {
t.Errorf("create subscription on %s = %d, want 404", host, got)
}
}
// The seller's own host is not refused by this gate.
if got := at("pay.hanzo.ai", http.MethodPost, "/v1/billing/subscriptions", "/v1/billing/subscriptions",
`{"userId":"acme","planId":"free"}`, CreateBillingSubscription); got == http.StatusNotFound {
t.Errorf("create subscription on pay.hanzo.ai = 404, the seller's own host")
}
}