storj/satellite/payments/stripecoinpayments/creditcards.go
Michal Niewrzal 2eb2c25e51 satellite/payments/stripecoinpayments: add StripeClient interface to be
able to cover more testing scenarios

Currently, its hard to implement test suite for payments because
mockpayments is on to high level and we cannot emulate many things e.g.
adding credit card. This change is first to be able to add mock for
Stripe client and do more granular tests.

Change-Id: Ied85d4bd0642debdffe1161657c1e475202e9d23
2020-05-15 10:52:44 +02:00

152 lines
4.4 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package stripecoinpayments
import (
"context"
"github.com/stripe/stripe-go"
"github.com/zeebo/errs"
"storj.io/common/uuid"
"storj.io/storj/satellite/payments"
)
// creditCards is an implementation of payments.CreditCards.
//
// architecture: Service
type creditCards struct {
service *Service
}
// List returns a list of credit cards for a given payment account.
func (creditCards *creditCards) List(ctx context.Context, userID uuid.UUID) (cards []payments.CreditCard, err error) {
defer mon.Task()(&ctx, userID)(&err)
customerID, err := creditCards.service.db.Customers().GetCustomerID(ctx, userID)
if err != nil {
return nil, Error.Wrap(err)
}
customer, err := creditCards.service.stripeClient.Customers().Get(customerID, nil)
if err != nil {
return nil, Error.Wrap(err)
}
params := &stripe.PaymentMethodListParams{
Customer: &customerID,
Type: stripe.String(string(stripe.PaymentMethodTypeCard)),
}
paymentMethodsIterator := creditCards.service.stripeClient.PaymentMethods().List(params)
for paymentMethodsIterator.Next() {
stripeCard := paymentMethodsIterator.PaymentMethod()
isDefault := false
if customer.InvoiceSettings.DefaultPaymentMethod != nil {
isDefault = customer.InvoiceSettings.DefaultPaymentMethod.ID == stripeCard.ID
}
cards = append(cards, payments.CreditCard{
ID: stripeCard.ID,
ExpMonth: int(stripeCard.Card.ExpMonth),
ExpYear: int(stripeCard.Card.ExpYear),
Brand: string(stripeCard.Card.Brand),
Last4: stripeCard.Card.Last4,
IsDefault: isDefault,
})
}
if err = paymentMethodsIterator.Err(); err != nil {
return nil, Error.Wrap(err)
}
return cards, nil
}
// Add is used to save new credit card, attach it to payment account and make it default.
func (creditCards *creditCards) Add(ctx context.Context, userID uuid.UUID, cardToken string) (err error) {
defer mon.Task()(&ctx, userID, cardToken)(&err)
customerID, err := creditCards.service.db.Customers().GetCustomerID(ctx, userID)
if err != nil {
return payments.ErrAccountNotSetup.Wrap(err)
}
cardParams := &stripe.PaymentMethodParams{
Type: stripe.String(string(stripe.PaymentMethodTypeCard)),
Card: &stripe.PaymentMethodCardParams{Token: &cardToken},
}
card, err := creditCards.service.stripeClient.PaymentMethods().New(cardParams)
if err != nil {
return Error.Wrap(err)
}
attachParams := &stripe.PaymentMethodAttachParams{
Customer: &customerID,
}
card, err = creditCards.service.stripeClient.PaymentMethods().Attach(card.ID, attachParams)
if err != nil {
return Error.Wrap(err)
}
params := &stripe.CustomerParams{
InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{
DefaultPaymentMethod: stripe.String(card.ID),
},
}
_, err = creditCards.service.stripeClient.Customers().Update(customerID, params)
// TODO: handle created but not attached card manually?
return Error.Wrap(err)
}
// MakeDefault makes a credit card default payment method.
// this credit card should be attached to account before make it default.
func (creditCards *creditCards) MakeDefault(ctx context.Context, userID uuid.UUID, cardID string) (err error) {
defer mon.Task()(&ctx, userID, cardID)(&err)
customerID, err := creditCards.service.db.Customers().GetCustomerID(ctx, userID)
if err != nil {
return payments.ErrAccountNotSetup.Wrap(err)
}
params := &stripe.CustomerParams{
InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{
DefaultPaymentMethod: stripe.String(cardID),
},
}
_, err = creditCards.service.stripeClient.Customers().Update(customerID, params)
return Error.Wrap(err)
}
// Remove is used to remove credit card from payment account.
func (creditCards *creditCards) Remove(ctx context.Context, userID uuid.UUID, cardID string) (err error) {
defer mon.Task()(&ctx, cardID)(&err)
customerID, err := creditCards.service.db.Customers().GetCustomerID(ctx, userID)
if err != nil {
return payments.ErrAccountNotSetup.Wrap(err)
}
customer, err := creditCards.service.stripeClient.Customers().Get(customerID, nil)
if err != nil {
return Error.Wrap(err)
}
if customer.InvoiceSettings != nil &&
customer.InvoiceSettings.DefaultPaymentMethod != nil &&
customer.InvoiceSettings.DefaultPaymentMethod.ID == cardID {
return Error.Wrap(errs.New("can not detach default payment method."))
}
_, err = creditCards.service.stripeClient.PaymentMethods().Detach(cardID, nil)
return Error.Wrap(err)
}