Files
zooqueenandHanzo Dev a265087b13 No route makes or records a refund; a refund or dispute is recorded only from the processor's signed webhook, once, and an inquiry that closes without a chargeback restores its period
Commerce never initiates a refund. POST /v1/billing/refunds, POST /v1/billing/refund,
POST /v1/order/:orderid/refund and POST /v1/payment/:paymentid/refund are gone with
their handlers (engine.CreateRefund, checkout Refund, square.Refund) and the helpers
only they used; GET /v1/billing/refunds stays read-only. A claim is settled by a
replacement order, never a refund.

A refund or dispute of a plan's payment reaches its invoice only from the
processor's webhook, under the processor's own refund id, through one locked
writer that reads the invoice afresh (engine.RecordRefund, engine.RecordDispute).
Every state a dispute reports reaches the invoice; INQUIRY_CLOSED holds the
payment no longer, WON stays final and LOST stays disputed.

No backfill of the payment-to-invoice index is run: the only plan payments made
before v1.50.139 are the team's own card purchases (z24, zk) and one Square
record.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-01 17:04:54 -07:00

82 lines
2.4 KiB
Go

package test
import (
"net/http"
"strconv"
"testing"
billingApi "github.com/hanzoai/commerce/api/billing"
"github.com/hanzoai/commerce/datastore"
"github.com/hanzoai/commerce/middleware/iammiddleware"
"github.com/hanzoai/commerce/models/fixtures"
"github.com/hanzoai/commerce/models/organization"
"github.com/hanzoai/commerce/util/permission"
"github.com/hanzoai/commerce/util/test/ae"
"github.com/hanzoai/commerce/util/test/zipclient"
"github.com/hanzoai/commerce/util/zipctx"
. "github.com/hanzoai/commerce/util/test/ginkgo"
)
// The suite drives the FULL billing surface, which includes money-MINT routes
// (credit-grants, deposit) reachable only by a SuperAdmin: an IAM principal whose
// home org is the reserved "admin" org. It presents the identity the boundary
// mints for the platform's own application (X-User-Owner: admin), the same
// principal api/billing's platformApp stamps, and pins X-Org-Id to the fixture
// org ("suchtees") so every spec stays in one namespace.
func Test(t *testing.T) {
Setup("api/billing", t)
}
var (
ctx ae.Context
db *datastore.Datastore
org *organization.Organization
cl *zipclient.Client
accessToken string
)
// Setup test context
var _ = BeforeSuite(func() {
// Create new test context
ctx = ae.NewContext()
// Synthetic request context for fixtures
c := zipctx.New(ctx)
// Run default fixtures to setup organization
org = fixtures.Organization(c).(*organization.Organization)
tok, _ := org.GetTokenByName("test-secret-key")
org.MustUpdate()
accessToken = tok.String
// Save namespaced db
db = datastore.New(org.Namespaced(ctx))
// Create client
cl = zipclient.New(ctx)
// A per-org access token carries only org-level Admin and is (correctly) 403
// on the mint routes: an org owner must never self-mint spendable balance.
perms := strconv.FormatInt(int64(permission.Admin|permission.Live), 10)
cl.Defaults(func(r *http.Request) {
r.Header.Set("X-User-Id", "app_platform")
r.Header.Set("X-User-Owner", "admin")
r.Header.Set("X-Org-Id", org.Name)
r.Header.Set("X-User-Permissions", perms)
})
cl.IgnoreErrors(true)
// Validate the identity headers the way the boundary does in production,
// then add the billing API routes behind it.
cl.Router.Use(iammiddleware.IAMTokenRequired())
billingApi.Route(cl.Router)
})
// Tear-down test context
var _ = AfterSuite(func() {
ctx.Close()
})