satellite/console: add triggerAttemptPaymentIfFrozen
add triggerAttemptPaymentIfFrozen to check if the account is frozen and if frozen, will trigger an attempt to pay outstanding invoices Issue: https://github.com/storj/storj/issues/5398 Change-Id: I0da6a982e2da4204dee219d98ce2d503cbbb6f8e
This commit is contained in:
parent
4241e6bf5f
commit
db489125b8
@ -4,6 +4,7 @@
|
||||
package consoleapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -30,13 +31,15 @@ var (
|
||||
type Payments struct {
|
||||
log *zap.Logger
|
||||
service *console.Service
|
||||
accountFreezeService *console.AccountFreezeService
|
||||
}
|
||||
|
||||
// NewPayments is a constructor for api payments controller.
|
||||
func NewPayments(log *zap.Logger, service *console.Service) *Payments {
|
||||
func NewPayments(log *zap.Logger, service *console.Service, accountFreezeService *console.AccountFreezeService) *Payments {
|
||||
return &Payments{
|
||||
log: log,
|
||||
service: service,
|
||||
accountFreezeService: accountFreezeService,
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,6 +131,34 @@ func (p *Payments) ProjectsCharges(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// triggerAttemptPaymentIfFrozen checks if the account is frozen and if frozen, will trigger attempt to pay outstanding invoices.
|
||||
func (p *Payments) triggerAttemptPaymentIfFrozen(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
userID, err := p.service.GetUserID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isFrozen, err := p.accountFreezeService.IsUserFrozen(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isFrozen {
|
||||
err = p.service.Payments().AttemptPayOverdueInvoices(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.accountFreezeService.UnfreezeUser(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCreditCard is used to save new credit card and attach it to payment account.
|
||||
func (p *Payments) AddCreditCard(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
@ -152,6 +183,12 @@ func (p *Payments) AddCreditCard(w http.ResponseWriter, r *http.Request) {
|
||||
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = p.triggerAttemptPaymentIfFrozen(ctx)
|
||||
if err != nil {
|
||||
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ListCreditCards returns a list of credit cards for a given payment account.
|
||||
@ -206,6 +243,12 @@ func (p *Payments) MakeCreditCardDefault(w http.ResponseWriter, r *http.Request)
|
||||
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = p.triggerAttemptPaymentIfFrozen(ctx)
|
||||
if err != nil {
|
||||
p.serveJSONError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveCreditCard is used to detach a credit card from payment account.
|
||||
|
@ -305,7 +305,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
||||
abRouter.Handle("/hit/{action}", server.withAuth(http.HandlerFunc(abController.SendHit))).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
paymentController := consoleapi.NewPayments(logger, service)
|
||||
paymentController := consoleapi.NewPayments(logger, service, accountFreezeService)
|
||||
paymentsRouter := router.PathPrefix("/api/v0/payments").Subrouter()
|
||||
paymentsRouter.Use(server.withAuth)
|
||||
paymentsRouter.HandleFunc("/cards", paymentController.AddCreditCard).Methods(http.MethodPost)
|
||||
|
@ -625,6 +625,23 @@ func (payment Payments) GetCoupon(ctx context.Context) (coupon *payments.Coupon,
|
||||
return coupon, nil
|
||||
}
|
||||
|
||||
// AttemptPayOverdueInvoices attempts to pay a user's open, overdue invoices.
|
||||
func (payment Payments) AttemptPayOverdueInvoices(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
user, err := payment.service.getUserAndAuditLog(ctx, "attempt to pay overdue invoices")
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
err = payment.service.accounts.Invoices().AttemptPayOverdueInvoices(ctx, user.ID)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRegistrationSecret returns a RegistrationToken if applicable (nil if not), and an error
|
||||
// if and only if the registration shouldn't proceed.
|
||||
func (s *Service) checkRegistrationSecret(ctx context.Context, tokenSecret RegistrationSecret) (*RegistrationToken, error) {
|
||||
|
@ -20,6 +20,8 @@ type Invoices interface {
|
||||
ListWithDiscounts(ctx context.Context, userID uuid.UUID) ([]Invoice, []CouponUsage, error)
|
||||
// CheckPendingItems returns if pending invoice items for a given payment account exist.
|
||||
CheckPendingItems(ctx context.Context, userID uuid.UUID) (existingItems bool, err error)
|
||||
// AttemptPayOverdueInvoices attempts to pay a user's open, overdue invoices.
|
||||
AttemptPayOverdueInvoices(ctx context.Context, userID uuid.UUID) (err error)
|
||||
}
|
||||
|
||||
// Invoice holds all public information about invoice.
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stripe/stripe-go/v72"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/satellite/payments"
|
||||
@ -20,6 +21,45 @@ type invoices struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
// AttemptPayOverdueInvoices attempts to pay a user's open, overdue invoices.
|
||||
func (invoices *invoices) AttemptPayOverdueInvoices(ctx context.Context, userID uuid.UUID) (err error) {
|
||||
customerID, err := invoices.service.db.Customers().GetCustomerID(ctx, userID)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
params := &stripe.InvoiceListParams{
|
||||
Customer: &customerID,
|
||||
Status: stripe.String(string(stripe.InvoiceStatusOpen)),
|
||||
DueDateRange: &stripe.RangeQueryParams{LesserThan: time.Now().Unix()},
|
||||
}
|
||||
|
||||
var errGrp errs.Group
|
||||
|
||||
invoicesIterator := invoices.service.stripeClient.Invoices().List(params)
|
||||
for invoicesIterator.Next() {
|
||||
stripeInvoice := invoicesIterator.Invoice()
|
||||
|
||||
params := &stripe.InvoicePayParams{}
|
||||
invResponse, err := invoices.service.stripeClient.Invoices().Pay(stripeInvoice.ID, params)
|
||||
if err != nil {
|
||||
errGrp.Add(Error.New("unable to pay invoice %s: %w", stripeInvoice.ID, err))
|
||||
continue
|
||||
}
|
||||
|
||||
if invResponse != nil && invResponse.Status != stripe.InvoiceStatusPaid {
|
||||
errGrp.Add(Error.New("invoice not paid after payment triggered %s", stripeInvoice.ID))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err = invoicesIterator.Err(); err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
return errGrp.Err()
|
||||
}
|
||||
|
||||
// List returns a list of invoices for a given payment account.
|
||||
func (invoices *invoices) List(ctx context.Context, userID uuid.UUID) (invoicesList []payments.Invoice, err error) {
|
||||
defer mon.Task()(&ctx, userID)(&err)
|
||||
|
Loading…
Reference in New Issue
Block a user