Skip to content

Commit df5181b

Browse files
fix(billing): translate stripe errors in billing services (#1850)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 4b1b56a commit df5181b

5 files changed

Lines changed: 53 additions & 54 deletions

File tree

billing/checkout/service.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/stripe/stripe-go/v79"
1414

1515
"github.com/raystack/frontier/billing"
16+
billingerrors "github.com/raystack/frontier/billing/errors"
1617
"github.com/raystack/frontier/internal/metrics"
1718

1819
"github.com/raystack/frontier/pkg/metadata"
@@ -350,7 +351,7 @@ func (s *Service) Create(ctx context.Context, ch Checkout) (Checkout, error) {
350351
PaymentMethodCollection: stripe.String(string(stripe.PaymentLinkPaymentMethodCollectionIfRequired)),
351352
})
352353
if err != nil {
353-
return Checkout{}, fmt.Errorf("failed to create subscription at billing provider: %w", err)
354+
return Checkout{}, fmt.Errorf("failed to create subscription at billing provider: %w", billingerrors.TranslateStripeError(err))
354355
}
355356

356357
return s.repository.Create(ctx, Checkout{
@@ -481,7 +482,7 @@ func (s *Service) Create(ctx context.Context, ch Checkout) (Checkout, error) {
481482
},
482483
})
483484
if err != nil {
484-
return Checkout{}, fmt.Errorf("failed to buy product at billing provider: %w", err)
485+
return Checkout{}, fmt.Errorf("failed to buy product at billing provider: %w", billingerrors.TranslateStripeError(err))
485486
}
486487

487488
return s.repository.Create(ctx, Checkout{
@@ -559,7 +560,7 @@ func (s *Service) SyncWithProvider(ctx context.Context, customerID string) error
559560
},
560561
})
561562
if err != nil {
562-
errs = append(errs, fmt.Errorf("failed to get checkout session from billing provider: %w", err))
563+
errs = append(errs, fmt.Errorf("failed to get checkout session from billing provider: %w", billingerrors.TranslateStripeError(err)))
563564
continue
564565
}
565566
if ch.PaymentStatus != string(checkoutSession.PaymentStatus) {
@@ -735,7 +736,7 @@ func (s *Service) ensureSubscription(ctx context.Context, ch Checkout) (string,
735736
},
736737
})
737738
if err != nil {
738-
return "", fmt.Errorf("failed to get subscription from billing provider: %w", err)
739+
return "", fmt.Errorf("failed to get subscription from billing provider: %w", billingerrors.TranslateStripeError(err))
739740
}
740741

741742
// create subscription
@@ -802,7 +803,7 @@ func (s *Service) CreateSessionForPaymentMethod(ctx context.Context, ch Checkout
802803
},
803804
})
804805
if err != nil {
805-
return Checkout{}, fmt.Errorf("failed to create checkout at billing provider: %w", err)
806+
return Checkout{}, fmt.Errorf("failed to create checkout at billing provider: %w", billingerrors.TranslateStripeError(err))
806807
}
807808

808809
return s.repository.Create(ctx, Checkout{
@@ -844,7 +845,7 @@ func (s *Service) CreateSessionForCustomerPortal(ctx context.Context, ch Checkou
844845
session, err := s.stripeClient.BillingPortalSessions.New(sessionParams)
845846

846847
if err != nil {
847-
return Checkout{}, fmt.Errorf("failed to create session for customer portal: %w", err)
848+
return Checkout{}, fmt.Errorf("failed to create session for customer portal: %w", billingerrors.TranslateStripeError(err))
848849
}
849850

850851
return Checkout{
@@ -989,7 +990,7 @@ func (s *Service) Apply(ctx context.Context, ch Checkout) (*subscription.Subscri
989990
Coupon: couponID,
990991
})
991992
if err != nil {
992-
return nil, nil, fmt.Errorf("failed to create subscription at billing provider: %w", err)
993+
return nil, nil, fmt.Errorf("failed to create subscription at billing provider: %w", billingerrors.TranslateStripeError(err))
993994
}
994995

995996
// register subscription in frontier

billing/customer/service.go

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package customer
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"log/slog"
78
"math/rand"
@@ -14,6 +15,7 @@ import (
1415
"github.com/stripe/stripe-go/v79"
1516

1617
"github.com/raystack/frontier/billing"
18+
billingerrors "github.com/raystack/frontier/billing/errors"
1719
"github.com/raystack/frontier/internal/metrics"
1820

1921
"slices"
@@ -134,14 +136,11 @@ func (s *Service) RegisterToProvider(ctx context.Context, customer Customer) (*s
134136
TestClock: customer.StripeTestClockID,
135137
})
136138
if err != nil {
137-
if stripeErr, ok := err.(*stripe.Error); ok {
138-
switch stripeErr.Code {
139-
case stripe.ErrorCodeParameterMissing:
140-
// stripe error
141-
return nil, fmt.Errorf("missing parameter while registering to biller: %s", stripeErr.Error())
142-
}
139+
var stripeErr *stripe.Error
140+
if errors.As(err, &stripeErr) && stripeErr.Code == stripe.ErrorCodeParameterMissing {
141+
return nil, fmt.Errorf("missing parameter while registering to biller: %s: %w", stripeErr.Msg, err)
143142
}
144-
return nil, fmt.Errorf("failed to register in billing provider: %w", err)
143+
return nil, fmt.Errorf("failed to register in billing provider: %w", billingerrors.TranslateStripeError(err))
145144
}
146145

147146
return stripeCustomer, nil
@@ -194,14 +193,11 @@ func (s *Service) Update(ctx context.Context, customer Customer) (Customer, erro
194193
},
195194
})
196195
if err != nil {
197-
if stripeErr, ok := err.(*stripe.Error); ok {
198-
switch stripeErr.Code {
199-
case stripe.ErrorCodeParameterMissing:
200-
// stripe error
201-
return Customer{}, fmt.Errorf("missing parameter while registering to biller: %s", stripeErr.Error())
202-
}
196+
var stripeErr *stripe.Error
197+
if errors.As(err, &stripeErr) && stripeErr.Code == stripe.ErrorCodeParameterMissing {
198+
return Customer{}, fmt.Errorf("missing parameter while registering to biller: %s: %w", stripeErr.Msg, err)
203199
}
204-
return Customer{}, fmt.Errorf("failed to register in billing provider: %w", err)
200+
return Customer{}, fmt.Errorf("failed to register in billing provider: %w", billingerrors.TranslateStripeError(err))
205201
}
206202
customer.ProviderID = stripeCustomer.ID
207203
return s.repository.UpdateByID(ctx, customer)
@@ -286,17 +282,9 @@ func (s *Service) Delete(ctx context.Context, id string) error {
286282
Context: ctx,
287283
},
288284
}); err != nil {
289-
var throw = true
290-
// Try to safely cast a generic error to a stripe.Error so that we can get at
291-
// some additional Stripe-specific information about what went wrong.
292-
if stripeErr, ok := err.(*stripe.Error); ok {
293-
// The Code field will contain a basic identifier for the failure.
294-
if stripeErr.Code == stripe.ErrorCodeResourceMissing {
295-
// it's ok if the customer is already deleted
296-
throw = false
297-
}
298-
}
299-
if throw {
285+
err = billingerrors.TranslateStripeError(err)
286+
// it's ok if the customer is already deleted
287+
if !errors.Is(err, billingerrors.ErrProviderResourceMissing) {
300288
return fmt.Errorf("failed to delete customer from billing provider: %w", err)
301289
}
302290
}
@@ -352,6 +340,9 @@ func (s *Service) ListPaymentMethods(ctx context.Context, id string) ([]PaymentM
352340

353341
paymentMethods = append(paymentMethods, pm)
354342
}
343+
if err := stripePaymentMethodItr.Err(); err != nil {
344+
return nil, fmt.Errorf("failed to list payment methods from billing provider: %w", billingerrors.TranslateStripeError(err))
345+
}
355346
return paymentMethods, nil
356347
}
357348

@@ -432,7 +423,7 @@ func (s *Service) SyncWithProvider(ctx context.Context, customr Customer) error
432423
},
433424
})
434425
if err != nil {
435-
return fmt.Errorf("failed to get customer from billing provider: %w", err)
426+
return fmt.Errorf("failed to get customer from billing provider: %w", billingerrors.TranslateStripeError(err))
436427
}
437428

438429
var shouldUpdate bool

billing/invoice/service.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/stripe/stripe-go/v79"
2121

2222
"github.com/raystack/frontier/billing"
23+
billingerrors "github.com/raystack/frontier/billing/errors"
2324
"github.com/raystack/frontier/internal/metrics"
2425

2526
"github.com/raystack/frontier/billing/customer"
@@ -287,7 +288,7 @@ func (s *Service) SyncWithProvider(ctx context.Context, customr customer.Custome
287288
return errors.Join(errs...)
288289
}
289290
if err := stripeInvoices.Err(); err != nil {
290-
return fmt.Errorf("failed to list invoices: %w", err)
291+
return fmt.Errorf("failed to list invoices: %w", billingerrors.TranslateStripeError(err))
291292
}
292293
return nil
293294
}
@@ -361,7 +362,7 @@ func (s *Service) GetUpcoming(ctx context.Context, customerID string) (Invoice,
361362
s.log.DebugContext(ctx, "no upcoming invoice", "error", stripeErr)
362363
return Invoice{}, nil
363364
}
364-
return Invoice{}, fmt.Errorf("failed to get upcoming invoice: %w", err)
365+
return Invoice{}, fmt.Errorf("failed to get upcoming invoice: %w", billingerrors.TranslateStripeError(err))
365366
}
366367

367368
return stripeInvoiceToInvoice(customerID, stripeInvoice), nil
@@ -693,7 +694,7 @@ func (s *Service) CreateInProvider(ctx context.Context, custmr customer.Customer
693694
},
694695
})
695696
if err != nil {
696-
return nil, fmt.Errorf("failed to create invoice: %w", err)
697+
return nil, fmt.Errorf("failed to create invoice: %w", billingerrors.TranslateStripeError(err))
697698
}
698699

699700
// create line item for the invoice
@@ -726,19 +727,23 @@ func (s *Service) CreateInProvider(ctx context.Context, custmr customer.Customer
726727
Period: itemPeriod,
727728
})
728729
if err != nil {
729-
return nil, fmt.Errorf("failed to create invoice item: %w", err)
730+
return nil, fmt.Errorf("failed to create invoice item: %w", billingerrors.TranslateStripeError(err))
730731
}
731732
}
732733

733734
// fetch updated stripe invoice
734-
return s.stripeClient.Invoices.Get(stripeInvoice.ID, &stripe.InvoiceParams{
735+
updatedInvoice, err := s.stripeClient.Invoices.Get(stripeInvoice.ID, &stripe.InvoiceParams{
735736
Params: stripe.Params{
736737
Context: ctx,
737738
},
738739
Expand: []*string{
739740
new("lines"),
740741
},
741742
})
743+
if err != nil {
744+
return nil, fmt.Errorf("failed to get invoice from billing provider: %w", billingerrors.TranslateStripeError(err))
745+
}
746+
return updatedInvoice, nil
742747
}
743748

744749
// Reconcile checks all paid invoices and reconciles them with the system.
@@ -835,7 +840,7 @@ func (s *Service) reconcileCreditInvoice(ctx context.Context, inv Invoice) error
835840
func (s *Service) TriggerSyncByProviderID(ctx context.Context, id string) error {
836841
stripeInvoice, err := s.stripeClient.Invoices.Get(id, &stripe.InvoiceParams{})
837842
if err != nil {
838-
return err
843+
return fmt.Errorf("failed to get invoice from billing provider: %w", billingerrors.TranslateStripeError(err))
839844
}
840845

841846
customrs, err := s.customerService.List(ctx, customer.Filter{

billing/product/service.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"slices"
1313

1414
"github.com/google/uuid"
15+
billingerrors "github.com/raystack/frontier/billing/errors"
1516
"github.com/raystack/frontier/pkg/utils"
1617
"github.com/stripe/stripe-go/v79/client"
1718
)
@@ -85,7 +86,7 @@ func (s *Service) Create(ctx context.Context, product Product) (Product, error)
8586
},
8687
})
8788
if err != nil {
88-
return Product{}, err
89+
return Product{}, fmt.Errorf("failed to create product at billing provider: %w", billingerrors.TranslateStripeError(err))
8990
}
9091

9192
productOb, err := s.productRepository.Create(ctx, product)
@@ -213,7 +214,7 @@ func (s *Service) Update(ctx context.Context, product Product) (Product, error)
213214
},
214215
})
215216
if err != nil {
216-
return Product{}, err
217+
return Product{}, fmt.Errorf("failed to update product at billing provider: %w", billingerrors.TranslateStripeError(err))
217218
}
218219

219220
// check feature updates in product
@@ -405,7 +406,7 @@ func (s *Service) setPriceActive(ctx context.Context, price Price, active bool)
405406
Params: stripe.Params{Context: ctx},
406407
Active: new(active),
407408
}); err != nil {
408-
return err
409+
return fmt.Errorf("failed to update price at billing provider: %w", billingerrors.TranslateStripeError(err))
409410
}
410411
}
411412
if active {
@@ -481,7 +482,7 @@ func (s *Service) CreatePrice(ctx context.Context, price Price) (Price, error) {
481482
}
482483
stripePrice, err := s.stripeClient.Prices.New(providerParams)
483484
if err != nil {
484-
return Price{}, err
485+
return Price{}, fmt.Errorf("failed to create price at billing provider: %w", billingerrors.TranslateStripeError(err))
485486
}
486487

487488
price.ProviderID = stripePrice.ID
@@ -533,7 +534,7 @@ func (s *Service) UpdatePrice(ctx context.Context, price Price) (Price, error) {
533534
},
534535
})
535536
if err != nil {
536-
return Price{}, err
537+
return Price{}, fmt.Errorf("failed to update price at billing provider: %w", billingerrors.TranslateStripeError(err))
537538
}
538539

539540
return s.priceRepository.UpdateByID(ctx, existingPrice)

billing/subscription/service.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/raystack/frontier/billing/credit"
2121

2222
"github.com/raystack/frontier/billing"
23+
billingerrors "github.com/raystack/frontier/billing/errors"
2324

2425
"github.com/raystack/frontier/billing/product"
2526
"github.com/raystack/frontier/pkg/utils"
@@ -330,7 +331,7 @@ func (s *Service) Cancel(ctx context.Context, id string, immediate bool) (Subscr
330331
Prorate: new(true),
331332
})
332333
if err != nil {
333-
return Subscription{}, fmt.Errorf("failed to cancel subscription at billing provider: %w", err)
334+
return Subscription{}, fmt.Errorf("failed to cancel subscription at billing provider: %w", billingerrors.TranslateStripeError(err))
334335
}
335336
sub.State = string(stripeSubscription.Status)
336337
if stripeSubscription.CanceledAt > 0 {
@@ -359,7 +360,7 @@ func (s *Service) Cancel(ctx context.Context, id string, immediate bool) (Subscr
359360
EndBehavior: stripe.String(string(stripe.SubscriptionScheduleEndBehaviorCancel)),
360361
})
361362
if err != nil {
362-
return sub, fmt.Errorf("failed to cancel subscription schedule at billing provider: %w", err)
363+
return sub, fmt.Errorf("failed to cancel subscription schedule at billing provider: %w", billingerrors.TranslateStripeError(err))
363364
}
364365
sub.Phase.PlanID = ""
365366
sub.Phase.Reason = SubscriptionCancel.String()
@@ -381,8 +382,8 @@ func (s *Service) createOrGetSchedule(ctx context.Context, sub Subscription) (*s
381382
},
382383
})
383384
if err != nil {
384-
// check if it's a subscription not found err
385-
if stripeErr, ok := err.(*stripe.Error); ok && stripeErr.Code == stripe.ErrorCodeResourceMissing {
385+
err = billingerrors.TranslateStripeError(err)
386+
if errors.Is(err, billingerrors.ErrProviderResourceMissing) {
386387
return nil, nil, ErrSubscriptionOnProviderNotFound
387388
}
388389
return nil, nil, fmt.Errorf("failed to get subscription from billing provider: %w", err)
@@ -398,7 +399,7 @@ func (s *Service) createOrGetSchedule(ctx context.Context, sub Subscription) (*s
398399
},
399400
})
400401
if err != nil {
401-
return nil, nil, fmt.Errorf("failed to get subscription schedule from billing provider: %w", err)
402+
return nil, nil, fmt.Errorf("failed to get subscription schedule from billing provider: %w", billingerrors.TranslateStripeError(err))
402403
}
403404
stripeSubscription.Schedule = schedule
404405
}
@@ -421,7 +422,7 @@ func (s *Service) createOrGetSchedule(ctx context.Context, sub Subscription) (*s
421422
},
422423
})
423424
if err != nil {
424-
return nil, nil, fmt.Errorf("failed to create subscription schedule at billing provider: %w", err)
425+
return nil, nil, fmt.Errorf("failed to create subscription schedule at billing provider: %w", billingerrors.TranslateStripeError(err))
425426
}
426427
}
427428
return stripeSubscription, stripeSubscription.Schedule, nil
@@ -483,7 +484,7 @@ func (s *Service) UpdateProductQuantity(ctx context.Context, orgID string, curre
483484
PendingInvoiceItemInterval: getPendingInvoiceItemInterval(currentPlan),
484485
})
485486
if err != nil {
486-
return fmt.Errorf("failed to update subscription quantity at billing provider: %w", err)
487+
return fmt.Errorf("failed to update subscription quantity at billing provider: %w", billingerrors.TranslateStripeError(err))
487488
}
488489
}
489490
}
@@ -561,7 +562,7 @@ func (s *Service) UpdateProductQuantity(ctx context.Context, orgID string, curre
561562
Phases: updatedPhases,
562563
})
563564
if err != nil {
564-
return fmt.Errorf("failed to update subscription schedule at billing provider: %w", err)
565+
return fmt.Errorf("failed to update subscription schedule at billing provider: %w", billingerrors.TranslateStripeError(err))
565566
}
566567
}
567568

@@ -773,7 +774,7 @@ func (s *Service) ChangePlan(ctx context.Context, id string, changeRequest Chang
773774
},
774775
})
775776
if err != nil {
776-
return change, fmt.Errorf("failed to update subscription schedule at billing provider: %w", err)
777+
return change, fmt.Errorf("failed to update subscription schedule at billing provider: %w", billingerrors.TranslateStripeError(err))
777778
}
778779

779780
// update subscription with new phase
@@ -968,7 +969,7 @@ func (s *Service) CancelUpcomingPhase(ctx context.Context, sub Subscription) err
968969
},
969970
})
970971
if err != nil {
971-
return fmt.Errorf("failed to update subscription schedule at billing provider: %w", err)
972+
return fmt.Errorf("failed to update subscription schedule at billing provider: %w", billingerrors.TranslateStripeError(err))
972973
}
973974

974975
sub.Phase.Reason = ""

0 commit comments

Comments
 (0)