Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions stripesync/coupons.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package stripesync
import (
"context"
"database/sql"
"errors"
"fmt"

"github.com/friendlycaptcha/friendly-stripe-sync/internal/db/postgres"
Expand Down Expand Up @@ -63,9 +62,8 @@ func (o *StripeSync) ensureCouponLoaded(c context.Context, couponID string) (boo
// HandleCouponUpdated will fetch the coupon from Stripe because AppliesTo isn't set
err = o.handleCouponUpdated(c, &stripe.Coupon{ID: couponID})
if err != nil {
var stripeErr *stripe.Error
if errors.As(err, &stripeErr) && stripeErr.Code == stripe.ErrorCodeResourceMissing {
log.Info().Str("coupon_id", couponID).Msg("Skipping coupon that no longer exists in Stripe")
if IsMissingResourceError(err) {
log.Info().Err(err).Str("coupon_id", couponID).Msg("Skipping coupon that no longer exists in Stripe")
return false, nil
}
return false, fmt.Errorf("failed to upsert coupon: %w", err)
Expand Down
56 changes: 56 additions & 0 deletions stripesync/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package stripesync

import (
"errors"

"github.com/rs/zerolog/log"
"github.com/stripe/stripe-go/v74"
)

// IsMissingResourceError reports whether err is a Stripe error for a resource that does not exist.
func IsMissingResourceError(err error) bool {
var stripeErr *stripe.Error
return errors.As(err, &stripeErr) && stripeErr.Code == stripe.ErrorCodeResourceMissing
}

type stripeLogger struct {
downgrade func(error) bool
}

func (stripeLogger) Debugf(format string, v ...interface{}) {
stripe.DefaultLeveledLogger.Debugf(format, v...)
}

func (stripeLogger) Infof(format string, v ...interface{}) {
stripe.DefaultLeveledLogger.Infof(format, v...)
}

func (stripeLogger) Warnf(format string, v ...interface{}) {
stripe.DefaultLeveledLogger.Warnf(format, v...)
}

func (l stripeLogger) Errorf(format string, v ...interface{}) {
if l.downgrade != nil {
for _, a := range v {
if err, ok := a.(error); ok && l.downgrade(err) {
log.Warn().Msgf(format, v...)
return
}
}
}

log.Error().Msgf(format, v...)
}

func stripeBackends(downgrade func(error) bool) *stripe.Backends {
logger := stripeLogger{downgrade: downgrade}
newBackend := func(t stripe.SupportedBackend) stripe.Backend {
return stripe.GetBackendWithConfig(t, &stripe.BackendConfig{LeveledLogger: logger})
}

return &stripe.Backends{
API: newBackend(stripe.APIBackend),
Connect: newBackend(stripe.ConnectBackend),
Uploads: newBackend(stripe.UploadsBackend),
}
}
50 changes: 50 additions & 0 deletions stripesync/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package stripesync

import (
"bytes"
"net/http"
"net/http/httptest"
"testing"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stripe/stripe-go/v74"
"github.com/stripe/stripe-go/v74/coupon"
)

func TestStripeLoggerErrorLevel(t *testing.T) {
tests := []struct {
name string
body string
downgrade func(error) bool
want string
}{
{"missing resource without downgrade", `{"error":{"code":"resource_missing","type":"invalid_request_error"}}`, nil, `"level":"error"`},
{"missing resource with downgrade", `{"error":{"code":"resource_missing","type":"invalid_request_error"}}`, IsMissingResourceError, `"level":"warn"`},
{"other error with downgrade", `{"error":{"code":"api_key_expired","type":"invalid_request_error"}}`, IsMissingResourceError, `"level":"error"`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(tt.body))
}))
defer srv.Close()

var buf bytes.Buffer
prev := log.Logger
log.Logger = zerolog.New(&buf)
defer func() { log.Logger = prev }()

b := stripe.GetBackendWithConfig(stripe.APIBackend, &stripe.BackendConfig{
LeveledLogger: stripeLogger{downgrade: tt.downgrade},
URL: stripe.String(srv.URL),
})
client := coupon.Client{B: b, Key: "sk_test_dummy"}
_, _ = client.Get("co_missing", nil)

assert.Contains(t, buf.String(), tt.want)
})
}
}
5 changes: 4 additions & 1 deletion stripesync/stripesync.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ type Config struct {
// ExcludedFields is a list of fields that should be excluded from the sync.
ExcludedFields []string

// DowngradeStripeError decides which Stripe SDK errors to log at warn instead of error.
DowngradeStripeError func(error) bool

Postgres PostgresConfig
}

Expand Down Expand Up @@ -50,7 +53,7 @@ func (pc PostgresConfig) StoreConfig() postgres.Config {
// New creates a new StripeSync handle.
func New(cfg Config) (*StripeSync, error) {
stripeClient := &client.API{}
stripeClient.Init(cfg.StripeAPIKey, nil)
stripeClient.Init(cfg.StripeAPIKey, stripeBackends(cfg.DowngradeStripeError))

db, err := postgres.NewPostgresStore(cfg.Postgres.StoreConfig())
if err != nil {
Expand Down
Loading