Expand payment remediation test coverage

Adds tests for chargeFailureStatus retryable-vs-definitive classification (429/408/425 -> 503, 4xx declines -> 402), legacy NULL-key refund resume, tip/discount split-record math, and the shared charge helpers adopted by till and gift-card paths.
This commit is contained in:
2026-08-22 00:34:49 +01:00
parent 57bdeb9232
commit 2a78383a2d
5 changed files with 563 additions and 9 deletions
@@ -3,10 +3,13 @@
package payments
import (
"bytes"
"context"
"fmt"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
@@ -345,3 +348,66 @@ type failingDeleteClient struct {
func (c *failingDeleteClient) DeleteCardOnFile(ctx context.Context, cardID string) error {
return fmt.Errorf("square: network error disabling card %s", cardID)
}
// syncBuffer is a mutex-guarded slog writer so log records can be read safely
// under -race.
type syncBuffer struct {
mu sync.Mutex
buf bytes.Buffer
}
func (b *syncBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.Write(p)
}
func (b *syncBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.String()
}
// tokenSafeFailingDeleteClient fails with a token-free error so the
// log-redaction test isolates redaction of the square_card_id attribute rather
// than the error string.
type tokenSafeFailingDeleteClient struct {
square.SquareClient
}
func (c *tokenSafeFailingDeleteClient) DeleteCardOnFile(ctx context.Context, cardID string) error {
return fmt.Errorf("square: network error disabling card at Square")
}
// TestDeletePaymentMethod_LogsRedactCardToken verifies the local card-delete
// warning logs the redacted tokenPrefix form of the square_card_id (a ccof:
// token), never the full value (SECURITY: full ccof tokens must not reach
// server logs).
func TestDeletePaymentMethod_LogsRedactCardToken(t *testing.T) {
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(tx)
require.NoError(t, err)
fullToken := "ccof:secret_token_123456"
cardID, err := fixtures.CreateTestPaymentMethod(tx, userID, fullToken, "VISA", "9999")
require.NoError(t, err)
origClient := SquareClient
SquareClient = &tokenSafeFailingDeleteClient{SquareClient: square.NewDevClient()}
defer func() { SquareClient = origClient }()
var sb syncBuffer
origLogger := slog.Default()
slog.SetDefault(slog.New(slog.NewTextHandler(&sb, nil)))
defer slog.SetDefault(origLogger)
svc := NewPaymentService()
require.NoError(t, svc.DeletePaymentMethod(ctx, cardID, userID))
logs := sb.String()
if strings.Contains(logs, fullToken) {
t.Errorf("full ccof token %q leaked into logs: %q", fullToken, logs)
}
if !strings.Contains(logs, "ccof:sec...") {
t.Errorf("expected redacted token prefix in logs, got %q", logs)
}
}