Fix Square card linkage: reference_id instead of customer_id (no Square customer provisioning)

The app does not provision Square customers, so sending the local user ID as
customer_id in Create Card was rejected with CUSTOMER_NOT_FOUND, and filtering
List Cards by it returned nothing. reference_id is Square's free-form client
reference — max 128 chars, no uniqueness constraint — and is echoed in both
Create and List responses.

- Create Card payload: reference_id = local user ID (customer_id absent)
- List Cards: native ?reference_id=<userID> filter (no limit/customer_id,
  no client-side filter, no cursor handling needed)
- Mock parity: CreateCardOnFile stores ReferenceID; GetCardsOnFile unchanged
- Regression guards: TestCreateCardOnFileHTTP_IdempotencyKey asserts
  reference_id=user_1 and customer_id ABSENT; new
  TestGetCardsOnFileHTTP_ReferenceIDFilter asserts the query shape
This commit is contained in:
2026-08-22 00:34:49 +01:00
parent 53ca89603d
commit 16240d67e3
5 changed files with 80 additions and 14 deletions
@@ -540,7 +540,7 @@ func TestCreateCardOnFileHTTP_IdempotencyKey(t *testing.T) {
t.Errorf("failed to decode request body: %v", err)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"card":{"id":"ccof_x","card_brand":"VISA","last_4":"4242","exp_month":12,"exp_year":2030,"fingerprint":"fp1","customer_id":"user_1","enabled":true,"version":1,"created_at":"2026-07-31T00:00:00Z"}}`))
_, _ = w.Write([]byte(`{"card":{"id":"ccof_x","card_brand":"VISA","last_4":"4242","exp_month":12,"exp_year":2030,"fingerprint":"fp1","customer_id":"","reference_id":"user_1","enabled":true,"version":1,"created_at":"2026-07-31T00:00:00Z"}}`))
}))
defer srv.Close()
@@ -562,8 +562,14 @@ func TestCreateCardOnFileHTTP_IdempotencyKey(t *testing.T) {
if !ok {
t.Fatalf("expected card object, got %v", captured["card"])
}
if card["customer_id"] != "user_1" {
t.Errorf("expected card.customer_id user_1, got %v", card["customer_id"])
// The local user ID goes in reference_id (free-form), NOT customer_id —
// the app has no Square customer provisioning, and customer_id would be
// rejected by the real Cards API (P1 regression guard).
if card["reference_id"] != "user_1" {
t.Errorf("expected card.reference_id user_1, got %v", card["reference_id"])
}
if _, present := card["customer_id"]; present {
t.Errorf("expected card.customer_id to be ABSENT (local IDs must not go in customer_id), got %v", card["customer_id"])
}
if gotAuth != "Bearer secret" {
t.Errorf("expected Authorization 'Bearer secret', got %q", gotAuth)
@@ -572,3 +578,47 @@ func TestCreateCardOnFileHTTP_IdempotencyKey(t *testing.T) {
t.Errorf("unexpected card result: %+v", res)
}
}
// TestGetCardsOnFileHTTP_ReferenceIDFilter verifies the List Cards request uses
// the native reference_id filter (the local user ID) — not the invalid
// customer_id — and that cards are returned unfiltered server-side.
func TestGetCardsOnFileHTTP_ReferenceIDFilter(t *testing.T) {
var gotRawQuery string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Errorf("expected GET, got %s", r.Method)
}
if r.URL.Path != "/v2/cards" {
t.Errorf("expected /v2/cards, got %s", r.URL.Path)
}
gotRawQuery = r.URL.RawQuery
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"cards":[{"id":"ccof_1","card_brand":"VISA","last_4":"4242","exp_month":12,"exp_year":2030,"fingerprint":"fp1","reference_id":"user_1","enabled":true,"version":1,"created_at":"2026-07-31T00:00:00Z"},{"id":"ccof_2","card_brand":"MASTERCARD","last_4":"1111","exp_month":6,"exp_year":2029,"fingerprint":"fp2","reference_id":"user_other","enabled":true,"version":1,"created_at":"2026-07-31T00:00:00Z"}]}`))
}))
defer srv.Close()
hc := &httpClient{baseURL: srv.URL, token: "t", http: srv.Client()}
cards, err := getCardsOnFileHTTPWithClient(context.Background(), "user_1", hc)
if err != nil {
t.Fatalf("getCardsOnFileHTTP failed: %v", err)
}
// Native reference_id filter — never the invalid customer_id, never a
// limit that Square's List Cards API doesn't support.
if !strings.Contains(gotRawQuery, "reference_id=user_1") {
t.Errorf("expected reference_id=user_1 in query, got %q", gotRawQuery)
}
if strings.Contains(gotRawQuery, "customer_id") {
t.Errorf("expected NO customer_id in query (local IDs are not Square customers), got %q", gotRawQuery)
}
if strings.Contains(gotRawQuery, "limit") {
t.Errorf("expected NO limit param (List Cards has no limit; server filters by reference_id), got %q", gotRawQuery)
}
if len(cards) != 2 {
t.Fatalf("expected 2 cards returned, got %d", len(cards))
}
if cards[0].ReferenceID != "user_1" || cards[1].ReferenceID != "user_other" {
t.Errorf("unexpected cards: %+v %+v", cards[0], cards[1])
}
}