satellite/payments/stripecoinpayments: credits added to invoice calculations
Change-Id: I6d3f5244a46f8945d2703af39ced333940db34e9
This commit is contained in:
parent
985c3ef897
commit
dca6fcbe28
@ -33,7 +33,7 @@ type CreditsDB interface {
|
||||
// ListCreditsSpendingsPaged returns all spending of specific user.
|
||||
ListCreditsSpendingsPaged(ctx context.Context, status int, offset int64, limit int, before time.Time) (CreditsSpendingsPage, error)
|
||||
// ApplyCreditsSpending updated spending's status.
|
||||
ApplyCreditsSpending(ctx context.Context, spendingID uuid.UUID, status int) (err error)
|
||||
ApplyCreditsSpending(ctx context.Context, spendingID uuid.UUID) (err error)
|
||||
|
||||
// Balance returns difference between all credits and creditsSpendings of specific user.
|
||||
Balance(ctx context.Context, userID uuid.UUID) (int64, error)
|
||||
@ -52,27 +52,27 @@ type credits struct {
|
||||
// CreditsSpending is an entity that holds funds been used from Accounts bonus credit balance.
|
||||
// Status shows if spending have been used to pay for invoice already or not.
|
||||
type CreditsSpending struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ProjectID uuid.UUID `json:"projectId"`
|
||||
UserID uuid.UUID `json:"userId"`
|
||||
Amount int64 `json:"amount"`
|
||||
Status int `json:"status"`
|
||||
Created time.Time `json:"created"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
ProjectID uuid.UUID `json:"projectId"`
|
||||
UserID uuid.UUID `json:"userId"`
|
||||
Amount int64 `json:"amount"`
|
||||
Status CreditsSpendingStatus `json:"status"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
// CreditsSpendingsPage holds set of spendings and indicates if
|
||||
// there are more spendings to fetch.
|
||||
// CreditsSpendingsPage holds set of creditsSpendings and indicates if
|
||||
// there are more creditsSpendings to fetch.
|
||||
type CreditsSpendingsPage struct {
|
||||
Spendings []CreditsSpending
|
||||
Next bool
|
||||
NextOffset int64
|
||||
}
|
||||
|
||||
// CreditsSpendingStatus indicates the state of the spending.
|
||||
// CreditsSpendingStatus indicates the state of the creditsSpending.
|
||||
type CreditsSpendingStatus int
|
||||
|
||||
const (
|
||||
// CreditsSpendingStatusUnapplied is a default spending state.
|
||||
// CreditsSpendingStatusUnapplied is a default creditsSpending state.
|
||||
CreditsSpendingStatusUnapplied CreditsSpendingStatus = 0
|
||||
// CreditsSpendingStatusApplied status indicates that spending was applied.
|
||||
CreditsSpendingStatusApplied CreditsSpendingStatus = 1
|
||||
|
@ -35,7 +35,7 @@ func TestCreditsRepository(t *testing.T) {
|
||||
ProjectID: testrand.UUID(),
|
||||
UserID: userID,
|
||||
Amount: 5,
|
||||
Status: int(stripecoinpayments.CreditsSpendingStatusUnapplied),
|
||||
Status: stripecoinpayments.CreditsSpendingStatusUnapplied,
|
||||
}
|
||||
|
||||
t.Run("insert", func(t *testing.T) {
|
||||
@ -53,6 +53,7 @@ func TestCreditsRepository(t *testing.T) {
|
||||
spendings, err := creditsRepo.ListCreditsSpendings(ctx, userID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(spendings))
|
||||
spending.ID = spendings[0].ID
|
||||
})
|
||||
|
||||
t.Run("get credit by transactionID", func(t *testing.T) {
|
||||
@ -62,12 +63,12 @@ func TestCreditsRepository(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("update spending", func(t *testing.T) {
|
||||
err := creditsRepo.ApplyCreditsSpending(ctx, spending.ID, int(stripecoinpayments.CreditsSpendingStatusApplied))
|
||||
err := creditsRepo.ApplyCreditsSpending(ctx, spending.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
spendings, err := creditsRepo.ListCreditsSpendings(ctx, userID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int(stripecoinpayments.CreditsSpendingStatusApplied), spendings[0].Status)
|
||||
require.Equal(t, stripecoinpayments.CreditsSpendingStatusApplied, spendings[0].Status)
|
||||
spending = spendings[0]
|
||||
})
|
||||
|
||||
|
@ -17,8 +17,8 @@ var ErrProjectRecordExists = Error.New("invoice project record already exists")
|
||||
//
|
||||
// architecture: Database
|
||||
type ProjectRecordsDB interface {
|
||||
// Create creates new invoice project record with coupon usages in the DB.
|
||||
Create(ctx context.Context, records []CreateProjectRecord, couponUsages []CouponUsage, start, end time.Time) error
|
||||
// Create creates new invoice project record with coupon usages and credits spendings in the DB.
|
||||
Create(ctx context.Context, records []CreateProjectRecord, couponUsages []CouponUsage, creditsSpendings []CreditsSpending, start, end time.Time) error
|
||||
// Check checks if invoice project record for specified project and billing period exists.
|
||||
Check(ctx context.Context, projectID uuid.UUID, start, end time.Time) error
|
||||
// Get returns record for specified project and billing period.
|
||||
|
@ -40,6 +40,7 @@ func TestProjectRecords(t *testing.T) {
|
||||
},
|
||||
},
|
||||
[]stripecoinpayments.CouponUsage{},
|
||||
[]stripecoinpayments.CreditsSpending{},
|
||||
start, end,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
@ -93,7 +94,7 @@ func TestProjectRecordsList(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
err := projectRecordsDB.Create(ctx, createProjectRecords, []stripecoinpayments.CouponUsage{}, start, end)
|
||||
err := projectRecordsDB.Create(ctx, createProjectRecords, []stripecoinpayments.CouponUsage{}, []stripecoinpayments.CreditsSpending{}, start, end)
|
||||
require.NoError(t, err)
|
||||
|
||||
page, err := projectRecordsDB.ListUnapplied(ctx, 0, limit, time.Now())
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/skyrings/skyring-common/tools/uuid"
|
||||
"github.com/spacemonkeygo/monkit/v3"
|
||||
"github.com/stripe/stripe-go"
|
||||
"github.com/stripe/stripe-go/client"
|
||||
@ -403,6 +404,7 @@ func (service *Service) createProjectRecords(ctx context.Context, projects []con
|
||||
|
||||
var records []CreateProjectRecord
|
||||
var usages []CouponUsage
|
||||
var creditsSpendings []CreditsSpending
|
||||
for _, project := range projects {
|
||||
if err = ctx.Err(); err != nil {
|
||||
return err
|
||||
@ -438,15 +440,18 @@ func (service *Service) createProjectRecords(ctx context.Context, projects []con
|
||||
|
||||
currentUsagePrice := service.calculateProjectUsagePrice(usage.Egress, usage.Storage, usage.ObjectCount).TotalInt64()
|
||||
|
||||
amountToChargeFromCoupon := currentUsagePrice
|
||||
amountToChargeFromCoupon := int64(0)
|
||||
|
||||
// TODO: only for 1 coupon per project
|
||||
for _, coupon := range coupons {
|
||||
amountToChargeFromCoupon = currentUsagePrice
|
||||
|
||||
if coupon.IsExpired() {
|
||||
if err = service.db.Coupons().Update(ctx, coupon.ID, payments.CouponExpired); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amountToChargeFromCoupon = 0
|
||||
continue
|
||||
}
|
||||
|
||||
@ -467,9 +472,39 @@ func (service *Service) createProjectRecords(ctx context.Context, projects []con
|
||||
CouponID: coupon.ID,
|
||||
})
|
||||
}
|
||||
|
||||
leftAfterCoupons := currentUsagePrice - amountToChargeFromCoupon
|
||||
if leftAfterCoupons == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
userBonuses, err := service.db.Credits().Balance(ctx, project.OwnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if userBonuses > 0 {
|
||||
if leftAfterCoupons >= userBonuses {
|
||||
leftAfterCoupons = userBonuses
|
||||
}
|
||||
|
||||
amountChargedFromBonuses := leftAfterCoupons
|
||||
creditSpendingID, err := uuid.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
creditsSpendings = append(creditsSpendings, CreditsSpending{
|
||||
ID: *creditSpendingID,
|
||||
Amount: amountChargedFromBonuses,
|
||||
UserID: project.OwnerID,
|
||||
ProjectID: project.ID,
|
||||
Status: CreditsSpendingStatusUnapplied,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return service.db.ProjectRecords().Create(ctx, records, usages, start, end)
|
||||
return service.db.ProjectRecords().Create(ctx, records, usages, creditsSpendings, start, end)
|
||||
}
|
||||
|
||||
// InvoiceApplyProjectRecords iterates through unapplied invoice project records and creates invoice line items
|
||||
@ -631,7 +666,7 @@ func (service *Service) applyCoupons(ctx context.Context, usages []CouponUsage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// createInvoiceItems consumes invoice project record and creates invoice line items for stripe customer.
|
||||
// createInvoiceCouponItems consumes invoice project record and creates invoice line items for stripe customer.
|
||||
func (service *Service) createInvoiceCouponItems(ctx context.Context, coupon payments.Coupon, usage CouponUsage, customerID string) (err error) {
|
||||
defer mon.Task()(&ctx, customerID, coupon)(&err)
|
||||
|
||||
@ -722,11 +757,11 @@ func (service *Service) applySpendings(ctx context.Context, spendings []CreditsS
|
||||
return nil
|
||||
}
|
||||
|
||||
// createInvoiceItems consumes invoice project record and creates invoice line items for stripe customer.
|
||||
// createInvoiceCreditItem consumes invoice project record and creates invoice line items for stripe customer.
|
||||
func (service *Service) createInvoiceCreditItem(ctx context.Context, spending CreditsSpending) (err error) {
|
||||
defer mon.Task()(&ctx, spending)(&err)
|
||||
|
||||
err = service.db.Credits().ApplyCreditsSpending(ctx, spending.ID, int(CreditsSpendingStatusApplied))
|
||||
err = service.db.Credits().ApplyCreditsSpending(ctx, spending.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/coinpayments"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
dbx "storj.io/storj/satellite/satellitedb/dbx"
|
||||
"storj.io/storj/satellite/satellitedb/dbx"
|
||||
)
|
||||
|
||||
// ensures that credit implements payments.CreditsDB.
|
||||
@ -104,13 +104,18 @@ func (credits *credit) ListCreditsPaged(ctx context.Context, offset int64, limit
|
||||
func (credits *credit) InsertCreditsSpending(ctx context.Context, spending stripecoinpayments.CreditsSpending) (err error) {
|
||||
defer mon.Task()(&ctx, spending)(&err)
|
||||
|
||||
id, err := uuid.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = credits.db.Create_CreditsSpending(
|
||||
ctx,
|
||||
dbx.CreditsSpending_Id(spending.ID[:]),
|
||||
dbx.CreditsSpending_Id(id[:]),
|
||||
dbx.CreditsSpending_UserId(spending.UserID[:]),
|
||||
dbx.CreditsSpending_ProjectId(spending.ProjectID[:]),
|
||||
dbx.CreditsSpending_Amount(spending.Amount),
|
||||
dbx.CreditsSpending_Status(spending.Status),
|
||||
dbx.CreditsSpending_Status(int(spending.Status)),
|
||||
)
|
||||
|
||||
return err
|
||||
@ -132,13 +137,13 @@ func (credits *credit) ListCreditsSpendings(ctx context.Context, userID uuid.UUI
|
||||
}
|
||||
|
||||
// ApplyCreditsSpending applies spending and updates its status.
|
||||
func (credits *credit) ApplyCreditsSpending(ctx context.Context, spendingID uuid.UUID, status int) (err error) {
|
||||
func (credits *credit) ApplyCreditsSpending(ctx context.Context, spendingID uuid.UUID) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = credits.db.Update_CreditsSpending_By_Id(
|
||||
ctx,
|
||||
dbx.CreditsSpending_Id(spendingID[:]),
|
||||
dbx.CreditsSpending_Update_Fields{Status: dbx.CreditsSpending_Status(status)},
|
||||
dbx.CreditsSpending_Update_Fields{Status: dbx.CreditsSpending_Status(int(stripecoinpayments.CreditsSpendingStatusApplied))},
|
||||
)
|
||||
|
||||
return err
|
||||
@ -248,9 +253,15 @@ func fromDBXSpending(dbxSpending *dbx.CreditsSpending) (spending stripecoinpayme
|
||||
return stripecoinpayments.CreditsSpending{}, err
|
||||
}
|
||||
|
||||
spending.Status = dbxSpending.Status
|
||||
spending.Status = stripecoinpayments.CreditsSpendingStatus(dbxSpending.Status)
|
||||
spending.Created = dbxSpending.CreatedAt
|
||||
spending.Amount = dbxSpending.Amount
|
||||
spendingID, err := dbutil.BytesToUUID(dbxSpending.Id)
|
||||
if err != nil {
|
||||
return stripecoinpayments.CreditsSpending{}, err
|
||||
}
|
||||
|
||||
spending.ID = spendingID
|
||||
|
||||
return spending, nil
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ type invoiceProjectRecords struct {
|
||||
}
|
||||
|
||||
// Create creates new invoice project record in the DB.
|
||||
func (db *invoiceProjectRecords) Create(ctx context.Context, records []stripecoinpayments.CreateProjectRecord, couponUsages []stripecoinpayments.CouponUsage, start, end time.Time) (err error) {
|
||||
func (db *invoiceProjectRecords) Create(ctx context.Context, records []stripecoinpayments.CreateProjectRecord, couponUsages []stripecoinpayments.CouponUsage, creditsSpendings []stripecoinpayments.CreditsSpending, start, end time.Time) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
return db.db.WithTx(ctx, func(ctx context.Context, tx *dbx.Tx) error {
|
||||
@ -79,6 +79,21 @@ func (db *invoiceProjectRecords) Create(ctx context.Context, records []stripecoi
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, creditsSpending := range creditsSpendings {
|
||||
_, err = db.db.Create_CreditsSpending(
|
||||
ctx,
|
||||
dbx.CreditsSpending_Id(creditsSpending.ID[:]),
|
||||
dbx.CreditsSpending_UserId(creditsSpending.UserID[:]),
|
||||
dbx.CreditsSpending_ProjectId(creditsSpending.ProjectID[:]),
|
||||
dbx.CreditsSpending_Amount(creditsSpending.Amount),
|
||||
dbx.CreditsSpending_Status(int(creditsSpending.Status)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user