storj/satellite/satellitedb/invoiceprojectrecords.go
Kaloyan Raev 49571f1a23 satellite/payments: all invoice commands require period
To avoid including multiple months in a single invoice, we need all
inspector's invoice commands to run in for specific period.

See https://storjlabs.atlassian.net/browse/USR-725

Change-Id: I3637dc189234f02350daca8d897c21765762ea55
2020-05-14 11:50:19 +00:00

210 lines
6.7 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package satellitedb
import (
"context"
"database/sql"
"time"
"github.com/zeebo/errs"
"storj.io/common/uuid"
"storj.io/storj/satellite/payments/stripecoinpayments"
"storj.io/storj/satellite/satellitedb/dbx"
)
// ensure that invoiceProjectRecords implements stripecoinpayments.ProjectRecordsDB.
var _ stripecoinpayments.ProjectRecordsDB = (*invoiceProjectRecords)(nil)
// invoiceProjectRecordState defines states of the invoice project record.
type invoiceProjectRecordState int
const (
// invoice project record is not yet applied to customer invoice.
invoiceProjectRecordStateUnapplied invoiceProjectRecordState = 0
// invoice project record has been used during creating customer invoice.
invoiceProjectRecordStateConsumed invoiceProjectRecordState = 1
)
// Int returns intent state as int.
func (intent invoiceProjectRecordState) Int() int {
return int(intent)
}
// invoiceProjectRecords is stripecoinpayments project records DB.
//
// architecture: Database
type invoiceProjectRecords struct {
db *satelliteDB
}
// Create creates new invoice project record in the DB.
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 {
for _, record := range records {
id, err := uuid.New()
if err != nil {
return Error.Wrap(err)
}
_, err = db.db.Create_StripecoinpaymentsInvoiceProjectRecord(ctx,
dbx.StripecoinpaymentsInvoiceProjectRecord_Id(id[:]),
dbx.StripecoinpaymentsInvoiceProjectRecord_ProjectId(record.ProjectID[:]),
dbx.StripecoinpaymentsInvoiceProjectRecord_Storage(record.Storage),
dbx.StripecoinpaymentsInvoiceProjectRecord_Egress(record.Egress),
dbx.StripecoinpaymentsInvoiceProjectRecord_Objects(int64(record.Objects)),
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodStart(start),
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodEnd(end),
dbx.StripecoinpaymentsInvoiceProjectRecord_State(invoiceProjectRecordStateUnapplied.Int()),
)
if err != nil {
return err
}
}
for _, couponUsage := range couponUsages {
_, err = db.db.Create_CouponUsage(
ctx,
dbx.CouponUsage_CouponId(couponUsage.CouponID[:]),
dbx.CouponUsage_Amount(couponUsage.Amount),
dbx.CouponUsage_Status(int(couponUsage.Status)),
dbx.CouponUsage_Period(couponUsage.Period),
)
if err != nil {
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)),
dbx.CreditsSpending_Period(creditsSpending.Period),
)
if err != nil {
return err
}
}
return nil
})
}
// Check checks if invoice project record for specified project and billing period exists.
func (db *invoiceProjectRecords) Check(ctx context.Context, projectID uuid.UUID, start, end time.Time) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = db.db.Get_StripecoinpaymentsInvoiceProjectRecord_By_ProjectId_And_PeriodStart_And_PeriodEnd(ctx,
dbx.StripecoinpaymentsInvoiceProjectRecord_ProjectId(projectID[:]),
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodStart(start),
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodEnd(end),
)
if err != nil {
if err == sql.ErrNoRows {
return nil
}
return err
}
return stripecoinpayments.ErrProjectRecordExists
}
// Get returns record for specified project and billing period.
func (db *invoiceProjectRecords) Get(ctx context.Context, projectID uuid.UUID, start, end time.Time) (record *stripecoinpayments.ProjectRecord, err error) {
defer mon.Task()(&ctx)(&err)
dbxRecord, err := db.db.Get_StripecoinpaymentsInvoiceProjectRecord_By_ProjectId_And_PeriodStart_And_PeriodEnd(ctx,
dbx.StripecoinpaymentsInvoiceProjectRecord_ProjectId(projectID[:]),
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodStart(start),
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodEnd(end),
)
if err != nil {
return nil, err
}
return fromDBXInvoiceProjectRecord(dbxRecord)
}
// Consume consumes invoice project record.
func (db *invoiceProjectRecords) Consume(ctx context.Context, id uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = db.db.Update_StripecoinpaymentsInvoiceProjectRecord_By_Id(ctx,
dbx.StripecoinpaymentsInvoiceProjectRecord_Id(id[:]),
dbx.StripecoinpaymentsInvoiceProjectRecord_Update_Fields{
State: dbx.StripecoinpaymentsInvoiceProjectRecord_State(invoiceProjectRecordStateConsumed.Int()),
},
)
return err
}
// ListUnapplied returns project records page with unapplied project records.
func (db *invoiceProjectRecords) ListUnapplied(ctx context.Context, offset int64, limit int, start, end time.Time) (_ stripecoinpayments.ProjectRecordsPage, err error) {
defer mon.Task()(&ctx)(&err)
var page stripecoinpayments.ProjectRecordsPage
dbxRecords, err := db.db.Limited_StripecoinpaymentsInvoiceProjectRecord_By_PeriodStart_And_PeriodEnd_And_State(ctx,
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodStart(start),
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodEnd(end),
dbx.StripecoinpaymentsInvoiceProjectRecord_State(invoiceProjectRecordStateUnapplied.Int()),
limit+1,
offset,
)
if err != nil {
return stripecoinpayments.ProjectRecordsPage{}, err
}
if len(dbxRecords) == limit+1 {
page.Next = true
page.NextOffset = offset + int64(limit)
dbxRecords = dbxRecords[:len(dbxRecords)-1]
}
for _, dbxRecord := range dbxRecords {
record, err := fromDBXInvoiceProjectRecord(dbxRecord)
if err != nil {
return stripecoinpayments.ProjectRecordsPage{}, err
}
page.Records = append(page.Records, *record)
}
return page, nil
}
// fromDBXInvoiceProjectRecord converts *dbx.StripecoinpaymentsInvoiceProjectRecord to *stripecoinpayments.ProjectRecord
func fromDBXInvoiceProjectRecord(dbxRecord *dbx.StripecoinpaymentsInvoiceProjectRecord) (*stripecoinpayments.ProjectRecord, error) {
id, err := uuid.FromBytes(dbxRecord.Id)
if err != nil {
return nil, errs.Wrap(err)
}
projectID, err := uuid.FromBytes(dbxRecord.ProjectId)
if err != nil {
return nil, errs.Wrap(err)
}
return &stripecoinpayments.ProjectRecord{
ID: id,
ProjectID: projectID,
Storage: dbxRecord.Storage,
Egress: dbxRecord.Egress,
Objects: float64(dbxRecord.Objects),
PeriodStart: dbxRecord.PeriodStart,
PeriodEnd: dbxRecord.PeriodEnd,
}, nil
}