Mirror Square's customer_id requirement in the dev mock card-creation gate

Square rejects POST /v2/cards without card.customer_id at runtime (confirmed by Square's SDK maintainer); the production client omits an empty id via omitempty and every caller provisions a customer first, so the mock now enforces the same structured 400 INVALID_REQUEST_ERROR to keep sandbox/dev parity with the gate the production code depends on.
This commit is contained in:
2026-08-22 00:34:49 +01:00
parent 352f9e50d4
commit 8a3a7ec062
2 changed files with 46 additions and 7 deletions
+31 -7
View File
@@ -186,7 +186,7 @@ func TestDevClient_CardOnFile_CreateAndGet(t *testing.T) {
ctx := context.Background()
userID := "user-test-123"
card, err := client.CreateCardOnFile(ctx, userID, "cnon:test-token", "")
card, err := client.CreateCardOnFile(ctx, userID, "cnon:test-token", "cus_test123")
require.NoError(t, err)
assert.NotEmpty(t, card.ID)
@@ -211,10 +211,10 @@ func TestDevClient_CardOnFile_MultipleCards(t *testing.T) {
ctx := context.Background()
userID := "user-test-multiple"
card1, err := client.CreateCardOnFile(ctx, userID, "cnon:token-1", "")
card1, err := client.CreateCardOnFile(ctx, userID, "cnon:token-1", "cus_test123")
require.NoError(t, err)
card2, err := client.CreateCardOnFile(ctx, userID, "cnon:token-2", "")
card2, err := client.CreateCardOnFile(ctx, userID, "cnon:token-2", "cus_test123")
require.NoError(t, err)
assert.True(t, card1.Enabled)
@@ -236,7 +236,7 @@ func TestDevClient_CardOnFile_Delete(t *testing.T) {
ctx := context.Background()
userID := "user-test-delete"
card, err := client.CreateCardOnFile(ctx, userID, "cnon:token-delete", "")
card, err := client.CreateCardOnFile(ctx, userID, "cnon:token-delete", "cus_test123")
require.NoError(t, err)
err = client.DeleteCardOnFile(ctx, card.ID)
@@ -286,7 +286,7 @@ func TestDevClient_CreateCardOnFile_RejectsRawPAN(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
card, err := client.CreateCardOnFile(ctx, "user-raw-"+tt.name, tt.cardNumber, "")
card, err := client.CreateCardOnFile(ctx, "user-raw-"+tt.name, tt.cardNumber, "cus_test123")
require.Error(t, err, "raw PAN must be rejected for production parity")
assert.Nil(t, card)
assert.Contains(t, err.Error(), "invalid source_id")
@@ -294,6 +294,30 @@ func TestDevClient_CreateCardOnFile_RejectsRawPAN(t *testing.T) {
}
}
// TestDevClient_CreateCardOnFile_RequiresCustomerID verifies the mock mirrors
// Square's real enforcement: Square's POST /v2/cards rejects a card without
// card.customer_id at runtime (confirmed by Square's own SDK maintainer). The
// production client omits an empty customer_id via omitempty and every
// production caller provisions a customer first, so the mock must reject it too
// — the same structured 400 INVALID_REQUEST_ERROR as the ccof: CreatePayment
// gate — so sandbox/dev tests exercise the same rejection as production.
func TestDevClient_CreateCardOnFile_RequiresCustomerID(t *testing.T) {
client := NewDevClient().(*MockClient)
ctx := context.Background()
_, err := client.CreateCardOnFile(ctx, "user-no-customer", "cnon:test-token", "")
require.Error(t, err, "card creation without customer_id must be rejected")
assert.Equal(t, "INVALID_REQUEST_ERROR", ErrorCode(err))
assert.Contains(t, ErrorDetail(err), "customer_id")
assert.Equal(t, http.StatusBadRequest, ErrorStatusCode(err))
card, err := client.CreateCardOnFile(ctx, "user-with-customer", "cnon:test-token", "cus_test123")
require.NoError(t, err)
assert.NotEmpty(t, card.ID)
assert.Equal(t, "VISA", card.Brand)
assert.Equal(t, "4242", card.Last4)
}
func TestCreatePayment_ShouldFail(t *testing.T) {
client := NewDevClient().(*MockClient)
client.ShouldFail = true
@@ -564,7 +588,7 @@ func TestDevClient_CreateCardOnFile_WithNewFields(t *testing.T) {
ctx := context.Background()
userID := "user-new-fields"
card, err := client.CreateCardOnFile(ctx, userID, "cnon:test-token", "")
card, err := client.CreateCardOnFile(ctx, userID, "cnon:test-token", "cus_test123")
require.NoError(t, err)
assert.True(t, card.Enabled)
@@ -583,7 +607,7 @@ func TestDevClient_DeleteCardOnFile_SoftDelete(t *testing.T) {
ctx := context.Background()
userID := "user-soft-delete"
card, err := client.CreateCardOnFile(ctx, userID, "cnon:token-soft", "")
card, err := client.CreateCardOnFile(ctx, userID, "cnon:token-soft", "cus_test123")
require.NoError(t, err)
err = client.DeleteCardOnFile(ctx, card.ID)