Project Payment methods (#2037)

This commit is contained in:
Bogdan Artemenko 2019-07-10 23:29:26 +03:00 committed by GitHub
parent 02565db73a
commit 32e0227c45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2464 additions and 436 deletions

View File

@ -42,6 +42,13 @@ const (
// DeleteAPIKeysMutation is a mutation name for api key deleting
DeleteAPIKeysMutation = "deleteAPIKeys"
// AddPaymentMethodMutation is mutation name for adding new payment method
AddPaymentMethodMutation = "addPaymentMethod"
// DeletePaymentMethodMutation is mutation name for deleting payment method
DeletePaymentMethodMutation = "deletePaymentMethod"
// SetDefaultPaymentMethodMutation is mutation name setting payment method as default payment method
SetDefaultPaymentMethodMutation = "setDefaultPaymentMethod"
// InputArg is argument name for all input types
InputArg = "input"
// FieldProjectID is field name for projectID
@ -420,6 +427,92 @@ func rootMutation(log *zap.Logger, service *console.Service, mailService *mailse
return keys, nil
},
},
AddPaymentMethodMutation: &graphql.Field{
Type: graphql.Boolean,
Args: graphql.FieldConfigArgument{
FieldProjectID: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
FieldCardToken: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
FieldIsDefault: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.Boolean),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
projectID, _ := p.Args[FieldProjectID].(string)
cardToken, _ := p.Args[FieldCardToken].(string)
isDefault, _ := p.Args[FieldIsDefault].(bool)
projID, err := uuid.Parse(projectID)
if err != nil {
return false, err
}
_, err = service.AddNewPaymentMethod(p.Context, cardToken, isDefault, *projID)
if err != nil {
return false, err
}
return true, nil
},
},
DeletePaymentMethodMutation: &graphql.Field{
Type: graphql.Boolean,
Args: graphql.FieldConfigArgument{
FieldID: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
fieldProjectPaymentID, _ := p.Args[FieldID].(string)
paymentID, err := uuid.Parse(fieldProjectPaymentID)
if err != nil {
return false, err
}
err = service.DeleteProjectPaymentMethod(p.Context, *paymentID)
if err != nil {
return false, err
}
return true, nil
},
},
SetDefaultPaymentMethodMutation: &graphql.Field{
Type: graphql.Boolean,
Args: graphql.FieldConfigArgument{
FieldProjectID: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
FieldID: &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
fieldProjectID, _ := p.Args[FieldProjectID].(string)
fieldProjectPaymentID, _ := p.Args[FieldID].(string)
paymentID, err := uuid.Parse(fieldProjectPaymentID)
if err != nil {
return false, err
}
projectID, err := uuid.Parse(fieldProjectID)
if err != nil {
return false, err
}
err = service.SetDefaultPaymentMethod(p.Context, *paymentID, *projectID)
if err != nil {
return false, err
}
return true, nil
},
},
},
})
}

View File

@ -25,6 +25,8 @@ const (
BucketUsageType = "bucketUsage"
// BucketUsagePageType is a field name for bucket usage page
BucketUsagePageType = "bucketUsagePage"
// PaymentMethodType is a field name for payment method
PaymentMethodType = "paymentMethod"
// FieldName is a field name for "name"
FieldName = "name"
// FieldBucketName is a field name for "bucket name"
@ -39,6 +41,8 @@ const (
FieldUsage = "usage"
// FieldBucketUsages is a field name for bucket usages
FieldBucketUsages = "bucketUsages"
// FieldPaymentMethods is a field name for payments methods
FieldPaymentMethods = "paymentMethods"
// FieldStorage is a field name for storage total
FieldStorage = "storage"
// FieldEgress is a field name for egress total
@ -51,6 +55,14 @@ const (
FieldCurrentPage = "currentPage"
// FieldTotalCount is a field name for bucket usage count total
FieldTotalCount = "totalCount"
// FieldCardBrand is a field name for credit card brand
FieldCardBrand = "brand"
// FieldCardLastFour is a field name for credit card last four digits
FieldCardLastFour = "lastFour"
// FieldCardToken is a field name for credit card token
FieldCardToken = "cardToken"
// FieldIsDefault is a field name for default payment method
FieldIsDefault = "isDefault"
// CursorArg is an argument name for cursor
CursorArg = "cursor"
// PageArg ia an argument name for page number
@ -189,6 +201,35 @@ func graphqlProject(service *console.Service, types *TypeCreator) *graphql.Objec
return service.GetBucketTotals(p.Context, project.ID, cursor, before)
},
},
FieldPaymentMethods: &graphql.Field{
Type: graphql.NewList(types.paymentMethod),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
project, _ := p.Source.(*console.Project)
paymentMethods, err := service.GetProjectPaymentMethods(p.Context, project.ID)
if err != nil {
return nil, err
}
var projectPaymentMethods []projectPayment
for _, paymentMethod := range paymentMethods {
projectPaymentMethod := projectPayment{
ID: paymentMethod.ID.String(),
LastFour: paymentMethod.Card.LastFour,
AddedAt: paymentMethod.CreatedAt,
CardBrand: paymentMethod.Card.Brand,
ExpMonth: paymentMethod.Card.ExpirationMonth,
ExpYear: paymentMethod.Card.ExpirationYear,
HolderName: paymentMethod.Card.Name,
IsDefault: paymentMethod.IsDefault,
}
projectPaymentMethods = append(projectPaymentMethods, projectPaymentMethod)
}
return projectPaymentMethods, nil
},
},
},
})
}
@ -307,6 +348,61 @@ func graphqlProjectUsage() *graphql.Object {
})
}
const (
// FieldExpirationYear is field name for expiration year
FieldExpirationYear = "expYear"
// FieldExpirationMonth is field name for expiration month
FieldExpirationMonth = "expMonth"
// FieldHolderName is field name for holder name
FieldHolderName = "holderName"
// FieldAddedAt is field name for added at date
FieldAddedAt = "addedAt"
)
// graphqlPaymentMethod creates invoice payment method graphql type
func graphqlPaymentMethod() *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{
Name: PaymentMethodType,
Fields: graphql.Fields{
FieldID: &graphql.Field{
Type: graphql.String,
},
FieldExpirationYear: &graphql.Field{
Type: graphql.Int,
},
FieldExpirationMonth: &graphql.Field{
Type: graphql.Int,
},
FieldCardBrand: &graphql.Field{
Type: graphql.String,
},
FieldCardLastFour: &graphql.Field{
Type: graphql.String,
},
FieldHolderName: &graphql.Field{
Type: graphql.String,
},
FieldAddedAt: &graphql.Field{
Type: graphql.DateTime,
},
FieldIsDefault: &graphql.Field{
Type: graphql.Boolean,
},
},
})
}
type projectPayment struct {
ID string
ExpYear int64
ExpMonth int64
CardBrand string
LastFour string
HolderName string
AddedAt time.Time
IsDefault bool
}
// fromMapProjectInfo creates console.ProjectInfo from input args
func fromMapProjectInfo(args map[string]interface{}) (project console.ProjectInfo) {
project.Name, _ = args[FieldName].(string)

View File

@ -25,6 +25,7 @@ type TypeCreator struct {
projectUsage *graphql.Object
bucketUsage *graphql.Object
bucketUsagePage *graphql.Object
paymentMethod *graphql.Object
projectMember *graphql.Object
apiKeyInfo *graphql.Object
createAPIKey *graphql.Object
@ -78,6 +79,11 @@ func (c *TypeCreator) Create(log *zap.Logger, service *console.Service, mailServ
return err
}
c.paymentMethod = graphqlPaymentMethod()
if err := c.paymentMethod.Error(); err != nil {
return err
}
c.bucketUsagePage = graphqlBucketUsagePage(c)
if err := c.bucketUsagePage.Error(); err != nil {
return err

View File

@ -13,16 +13,34 @@ import (
// ProjectPayments is project payment infos store interface
type ProjectPayments interface {
Create(ctx context.Context, info ProjectPayment) (*ProjectPayment, error)
GetByProjectID(ctx context.Context, projectID uuid.UUID) (*ProjectPayment, error)
GetByPayerID(ctx context.Context, payerID uuid.UUID) (*ProjectPayment, error)
Update(ctx context.Context, info ProjectPayment) error
Delete(ctx context.Context, projectPaymentID uuid.UUID) error
GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]*ProjectPayment, error)
GetByID(ctx context.Context, projectPaymentID uuid.UUID) (*ProjectPayment, error)
GetDefaultByProjectID(ctx context.Context, projectID uuid.UUID) (*ProjectPayment, error)
GetByPayerID(ctx context.Context, payerID uuid.UUID) ([]*ProjectPayment, error)
}
// ProjectPayment contains project payment info
type ProjectPayment struct {
ID uuid.UUID
ProjectID uuid.UUID
PayerID uuid.UUID
PaymentMethodID []byte
Card Card
IsDefault bool
CreatedAt time.Time
}
// Card contains customer card info
type Card struct {
Country string
Brand string
Name string
ExpirationMonth int64
ExpirationYear int64
LastFour string
}

View File

@ -64,18 +64,18 @@ func TestProjectPaymentInfos(t *testing.T) {
info, err := consoleDB.ProjectPayments().GetByProjectID(ctx, proj.ID)
assert.NoError(t, err)
assert.Equal(t, proj.ID, info.ProjectID)
assert.Equal(t, userPmInfo.UserID, info.PayerID)
assert.Equal(t, paymentMethodID, info.PaymentMethodID)
assert.Equal(t, proj.ID, info[0].ProjectID)
assert.Equal(t, userPmInfo.UserID, info[0].PayerID)
assert.Equal(t, paymentMethodID, info[0].PaymentMethodID)
})
t.Run("get by payer id", func(t *testing.T) {
info, err := consoleDB.ProjectPayments().GetByPayerID(ctx, userPmInfo.UserID)
assert.NoError(t, err)
assert.Equal(t, proj.ID, info.ProjectID)
assert.Equal(t, userPmInfo.UserID, info.PayerID)
assert.Equal(t, paymentMethodID, info.PaymentMethodID)
assert.Equal(t, proj.ID, info[0].ProjectID)
assert.Equal(t, userPmInfo.UserID, info[0].PayerID)
assert.Equal(t, paymentMethodID, info[0].PaymentMethodID)
})
})
}

View File

@ -6,6 +6,7 @@ package console
import (
"context"
"crypto/subtle"
"database/sql"
"fmt"
"time"
@ -168,6 +169,164 @@ func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret R
return u, nil
}
// AddNewPaymentMethod adds new payment method for project
func (s *Service) AddNewPaymentMethod(ctx context.Context, paymentMethodToken string, isDefault bool, projectID uuid.UUID) (payment *ProjectPayment, err error) {
defer mon.Task()(&ctx)(&err)
authorization, err := GetAuth(ctx)
if err != nil {
return nil, err
}
userPayments, err := s.store.UserPayments().Get(ctx, authorization.User.ID)
if err != nil {
return nil, err
}
params := payments.AddPaymentMethodParams{
Token: paymentMethodToken,
CustomerID: string(userPayments.CustomerID),
}
method, err := s.pm.AddPaymentMethod(ctx, params)
if err != nil {
return nil, err
}
tx, err := s.store.BeginTx(ctx)
if err != nil {
return nil, err
}
var pp *ProjectPayment
err = withTx(tx, func(tx DBTx) error {
if isDefault {
projectPayment, err := tx.ProjectPayments().GetDefaultByProjectID(ctx, projectID)
if err != nil {
if err != sql.ErrNoRows {
return err
}
}
if projectPayment != nil {
projectPayment.IsDefault = false
err = tx.ProjectPayments().Update(ctx, *projectPayment)
if err != nil {
return err
}
}
}
projectPaymentInfo := ProjectPayment{
ProjectID: projectID,
PayerID: authorization.User.ID,
PaymentMethodID: method.ID,
CreatedAt: time.Now(),
IsDefault: isDefault,
}
pp, err = tx.ProjectPayments().Create(ctx, projectPaymentInfo)
return err
})
return pp, nil
}
// SetDefaultPaymentMethod set default payment method for given project
func (s *Service) SetDefaultPaymentMethod(ctx context.Context, projectPaymentID uuid.UUID, projectID uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = GetAuth(ctx)
if err != nil {
return err
}
tx, err := s.store.BeginTx(ctx)
if err != nil {
return err
}
err = withTx(tx, func(tx DBTx) error {
projectPayment, err := tx.ProjectPayments().GetDefaultByProjectID(ctx, projectID)
if err != nil {
return err
}
projectPayment.IsDefault = false
err = tx.ProjectPayments().Update(ctx, *projectPayment)
if err != nil {
return err
}
projectPayment, err = tx.ProjectPayments().GetByID(ctx, projectPaymentID)
if err != nil {
return err
}
projectPayment.IsDefault = true
return tx.ProjectPayments().Update(ctx, *projectPayment)
})
return err
}
// DeleteProjectPaymentMethod deletes selected payment method
func (s *Service) DeleteProjectPaymentMethod(ctx context.Context, projectPayment uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = GetAuth(ctx)
if err != nil {
return err
}
return s.store.ProjectPayments().Delete(ctx, projectPayment)
}
// GetProjectPaymentMethods retrieves project payment methods
func (s *Service) GetProjectPaymentMethods(ctx context.Context, projectID uuid.UUID) ([]ProjectPayment, error) {
var err error
defer mon.Task()(&ctx)(&err)
_, err = GetAuth(ctx)
if err != nil {
return nil, err
}
projectPaymentInfos, err := s.store.ProjectPayments().GetByProjectID(ctx, projectID)
if err != nil {
return nil, err
}
var projectPayments []ProjectPayment
for _, payment := range projectPaymentInfos {
pm, err := s.pm.GetPaymentMethod(ctx, payment.PaymentMethodID)
if err != nil {
return nil, err
}
projectPayment := ProjectPayment{
ID: payment.ID,
CreatedAt: pm.CreatedAt,
PaymentMethodID: pm.ID,
IsDefault: payment.IsDefault,
PayerID: payment.PayerID,
ProjectID: projectID,
Card: Card{
LastFour: pm.Card.LastFour,
Name: pm.Card.Name,
Brand: pm.Card.Brand,
Country: pm.Card.Country,
ExpirationMonth: pm.Card.ExpMonth,
ExpirationYear: pm.Card.ExpYear,
},
}
projectPayments = append(projectPayments, projectPayment)
}
return projectPayments, nil
}
// GenerateActivationToken - is a method for generating activation token
func (s *Service) GenerateActivationToken(ctx context.Context, id uuid.UUID, email string) (token string, err error) {
defer mon.Task()(&ctx)(&err)
@ -931,7 +1090,7 @@ func (s *Service) CreateMonthlyProjectInvoices(ctx context.Context, date time.Ti
continue
}
paymentInfo, err := s.store.ProjectPayments().GetByProjectID(ctx, proj.ID)
paymentInfo, err := s.store.ProjectPayments().GetDefaultByProjectID(ctx, proj.ID)
if err != nil {
invoiceError.Add(err)
continue

View File

@ -6,10 +6,11 @@ package localpayments
import (
"context"
"crypto/rand"
mathRand "math/rand"
"time"
"github.com/zeebo/errs"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"gopkg.in/spacemonkeygo/monkit.v2"
"storj.io/storj/satellite/payments"
)
@ -30,22 +31,6 @@ var storjCustomer = payments.Customer{
CreatedAt: creationDate,
}
// defaultPaymentMethod represents one and only payment method for local payments,
// which attached to all customers by default
var defaultPaymentMethod = payments.PaymentMethod{
ID: []byte("0"),
CustomerID: []byte("0"),
Card: payments.Card{
Country: "us",
Brand: "visa",
Name: "Storj Labs",
ExpMonth: 12,
ExpYear: 2024,
LastFour: "3567",
},
CreatedAt: creationDate,
}
// internalPaymentsErr is a wrapper for local payments service errors
var internalPaymentsErr = errs.Class("internal payments error")
@ -59,6 +44,10 @@ type service struct {
db DB
}
func (*service) AddPaymentMethod(ctx context.Context, params payments.AddPaymentMethodParams) (*payments.PaymentMethod, error) {
return paymentMethod("", []byte(params.CustomerID)), nil
}
// NewService create new instance of local payments service
func NewService(db DB) payments.Service {
return &service{db: db}
@ -89,19 +78,19 @@ func (*service) GetCustomer(ctx context.Context, id []byte) (_ *payments.Custome
// GetCustomerDefaultPaymentMethod always returns defaultPaymentMethod
func (*service) GetCustomerDefaultPaymentMethod(ctx context.Context, customerID []byte) (_ *payments.PaymentMethod, err error) {
defer mon.Task()(&ctx)(&err)
return &defaultPaymentMethod, nil
return paymentMethod("", customerID), nil
}
// GetCustomerPaymentsMethods always returns payments.Customer list with defaultPaymentMethod
func (*service) GetCustomerPaymentsMethods(ctx context.Context, customerID []byte) (_ []payments.PaymentMethod, err error) {
defer mon.Task()(&ctx)(&err)
return []payments.PaymentMethod{defaultPaymentMethod}, nil
return []payments.PaymentMethod{*paymentMethod("", customerID)}, nil
}
// GetPaymentMethod always returns defaultPaymentMethod or error
func (*service) GetPaymentMethod(ctx context.Context, id []byte) (_ *payments.PaymentMethod, err error) {
defer mon.Task()(&ctx)(&err)
return &defaultPaymentMethod, nil
return paymentMethod(string(id), []byte("")), nil
}
// CreateProjectInvoice creates invoice from provided params
@ -118,3 +107,39 @@ func (*service) GetInvoice(ctx context.Context, id []byte) (_ *payments.Invoice,
// TODO: get project invoice stamp by invoice id from the db and fill data
return &payments.Invoice{}, nil
}
// paymentMethod returns paymentMethod object which mocks stripe response
func paymentMethod(methodID string, customerID []byte) *payments.PaymentMethod {
id := methodID
if methodID == "" {
id = "pm_" + randomString(24)
}
cusID := customerID
if len(customerID) <= 1 {
cusID = []byte("cus_" + randomString(14))
}
return &payments.PaymentMethod{
ID: []byte(id),
CustomerID: cusID,
Card: payments.Card{
Country: "us",
Brand: "visa",
Name: "Storj Labs",
ExpMonth: 12,
ExpYear: 2024,
LastFour: "3567",
},
CreatedAt: creationDate,
IsDefault: true,
}
}
func randomString(len int) string {
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(65 + mathRand.Intn(25))
}
return string(bytes)
}

View File

@ -11,6 +11,7 @@ import (
// Service is interfaces that defines behavior for working with payments
type Service interface {
CreateCustomer(ctx context.Context, params CreateCustomerParams) (*Customer, error)
AddPaymentMethod(ctx context.Context, params AddPaymentMethodParams) (*PaymentMethod, error)
GetCustomer(ctx context.Context, id []byte) (*Customer, error)
GetCustomerDefaultPaymentMethod(ctx context.Context, customerID []byte) (*PaymentMethod, error)
GetCustomerPaymentsMethods(ctx context.Context, customerID []byte) ([]PaymentMethod, error)
@ -25,6 +26,12 @@ type CreateCustomerParams struct {
Name string
}
// AddPaymentMethodParams contains info needed to create new payment method
type AddPaymentMethodParams struct {
Token string
CustomerID string
}
// Customer contains customer info
type Customer struct {
ID []byte
@ -50,7 +57,8 @@ type PaymentMethod struct {
ID []byte
CustomerID []byte
Card Card
Card Card
IsDefault bool
CreatedAt time.Time
}

View File

@ -8,11 +8,11 @@ import (
"fmt"
"time"
stripe "github.com/stripe/stripe-go"
"github.com/stripe/stripe-go"
"github.com/stripe/stripe-go/client"
"github.com/zeebo/errs"
"go.uber.org/zap"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"gopkg.in/spacemonkeygo/monkit.v2"
"storj.io/storj/satellite/payments"
)
@ -90,6 +90,40 @@ func (s *service) GetCustomer(ctx context.Context, id []byte) (_ *payments.Custo
}, nil
}
// AddPaymentMethod add payment method to defined customer
func (s *service) AddPaymentMethod(ctx context.Context, params payments.AddPaymentMethodParams) (*payments.PaymentMethod, error) {
cparams := &stripe.PaymentMethodParams{
Type: stripe.String(string(stripe.PaymentMethodTypeCard)),
Card: &stripe.PaymentMethodCardParams{Token: &params.Token},
}
method, err := s.client.PaymentMethods.New(cparams)
if err != nil {
return nil, err
}
pparams := &stripe.PaymentMethodAttachParams{
Customer: &params.CustomerID,
}
paymentMethod, err := s.client.PaymentMethods.Attach(method.ID, pparams)
if err != nil {
return nil, err
}
return &payments.PaymentMethod{
CustomerID: []byte(paymentMethod.Customer.ID),
ID: []byte(paymentMethod.ID),
Card: payments.Card{
ExpYear: int64(paymentMethod.Card.ExpYear),
ExpMonth: int64(paymentMethod.Card.ExpMonth),
Brand: string(paymentMethod.Card.Brand),
LastFour: paymentMethod.Card.Last4,
Country: paymentMethod.Card.Country,
},
}, nil
}
// GetCustomerDefaultPaymentMethod retrieves customer default payment method from stripe network
func (s *service) GetCustomerDefaultPaymentMethod(ctx context.Context, customerID []byte) (_ *payments.PaymentMethod, err error) {
defer mon.Task()(&ctx)(&err)

View File

@ -73,7 +73,7 @@ func (db *ConsoleDB) UserPayments() console.UserPayments {
// ProjectPayments is a getter for console.ProjectPayments repository
func (db *ConsoleDB) ProjectPayments() console.ProjectPayments {
return &projectpayments{db.methods}
return &projectPayments{db.db, db.methods}
}
// ProjectInvoiceStamps is a getter for console.ProjectInvoiceStamps repository

View File

@ -11,7 +11,7 @@ model value_attribution (
)
create value_attribution ()
delete value_attribution (
delete value_attribution (
where value_attribution.project_id = ?
where value_attribution.bucket_name = ?
)
@ -273,24 +273,36 @@ read all (
orderby asc project.name
)
// add is default, make composite key
model project_payment (
key project_id
key id
field id blob
field project_id project.id cascade
field payer_id user_payment.user_id cascade
field payment_method_id blob
field is_default bool ( updatable )
field created_at timestamp ( autoinsert )
)
create project_payment ( )
update project_payment ( where project_payment.id = ? )
delete project_payment ( where project_payment.id = ? )
read one (
select project_payment
where project_payment.id = ? )
read one (
select project_payment
where project_payment.project_id = ?
where project_payment.is_default = true
)
read all (
select project_payment
where project_payment.project_id = ?
)
read one (
read all (
select project_payment
where project_payment.payer_id = ?
)

View File

@ -537,11 +537,13 @@ CREATE TABLE user_payments (
UNIQUE ( customer_id )
);
CREATE TABLE project_payments (
id bytea NOT NULL,
project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
payer_id bytea NOT NULL REFERENCES user_payments( user_id ) ON DELETE CASCADE,
payment_method_id bytea NOT NULL,
is_default boolean NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( project_id )
PRIMARY KEY ( id )
);
CREATE INDEX bucket_name_project_id_interval_start_interval_seconds ON bucket_bandwidth_rollups ( bucket_name, project_id, interval_start, interval_seconds );
CREATE UNIQUE INDEX bucket_id_rollup ON bucket_usages ( bucket_id, rollup_end_time );
@ -876,11 +878,13 @@ CREATE TABLE user_payments (
UNIQUE ( customer_id )
);
CREATE TABLE project_payments (
id BLOB NOT NULL,
project_id BLOB NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
payer_id BLOB NOT NULL REFERENCES user_payments( user_id ) ON DELETE CASCADE,
payment_method_id BLOB NOT NULL,
is_default INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL,
PRIMARY KEY ( project_id )
PRIMARY KEY ( id )
);
CREATE INDEX bucket_name_project_id_interval_start_interval_seconds ON bucket_bandwidth_rollups ( bucket_name, project_id, interval_start, interval_seconds );
CREATE UNIQUE INDEX bucket_id_rollup ON bucket_usages ( bucket_id, rollup_end_time );
@ -4897,17 +4901,39 @@ func (f UserPayment_CreatedAt_Field) value() interface{} {
func (UserPayment_CreatedAt_Field) _Column() string { return "created_at" }
type ProjectPayment struct {
Id []byte
ProjectId []byte
PayerId []byte
PaymentMethodId []byte
IsDefault bool
CreatedAt time.Time
}
func (ProjectPayment) _Table() string { return "project_payments" }
type ProjectPayment_Update_Fields struct {
IsDefault ProjectPayment_IsDefault_Field
}
type ProjectPayment_Id_Field struct {
_set bool
_null bool
_value []byte
}
func ProjectPayment_Id(v []byte) ProjectPayment_Id_Field {
return ProjectPayment_Id_Field{_set: true, _value: v}
}
func (f ProjectPayment_Id_Field) value() interface{} {
if !f._set || f._null {
return nil
}
return f._value
}
func (ProjectPayment_Id_Field) _Column() string { return "id" }
type ProjectPayment_ProjectId_Field struct {
_set bool
_null bool
@ -4965,6 +4991,25 @@ func (f ProjectPayment_PaymentMethodId_Field) value() interface{} {
func (ProjectPayment_PaymentMethodId_Field) _Column() string { return "payment_method_id" }
type ProjectPayment_IsDefault_Field struct {
_set bool
_null bool
_value bool
}
func ProjectPayment_IsDefault(v bool) ProjectPayment_IsDefault_Field {
return ProjectPayment_IsDefault_Field{_set: true, _value: v}
}
func (f ProjectPayment_IsDefault_Field) value() interface{} {
if !f._set || f._null {
return nil
}
return f._value
}
func (ProjectPayment_IsDefault_Field) _Column() string { return "is_default" }
type ProjectPayment_CreatedAt_Field struct {
_set bool
_null bool
@ -5503,24 +5548,28 @@ func (obj *postgresImpl) Create_Project(ctx context.Context,
}
func (obj *postgresImpl) Create_ProjectPayment(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
project_payment_project_id ProjectPayment_ProjectId_Field,
project_payment_payer_id ProjectPayment_PayerId_Field,
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field) (
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field,
project_payment_is_default ProjectPayment_IsDefault_Field) (
project_payment *ProjectPayment, err error) {
__now := obj.db.Hooks.Now().UTC()
__id_val := project_payment_id.value()
__project_id_val := project_payment_project_id.value()
__payer_id_val := project_payment_payer_id.value()
__payment_method_id_val := project_payment_payment_method_id.value()
__is_default_val := project_payment_is_default.value()
__created_at_val := __now
var __embed_stmt = __sqlbundle_Literal("INSERT INTO project_payments ( project_id, payer_id, payment_method_id, created_at ) VALUES ( ?, ?, ?, ? ) RETURNING project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.created_at")
var __embed_stmt = __sqlbundle_Literal("INSERT INTO project_payments ( id, project_id, payer_id, payment_method_id, is_default, created_at ) VALUES ( ?, ?, ?, ?, ?, ? ) RETURNING project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at")
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __project_id_val, __payer_id_val, __payment_method_id_val, __created_at_val)
obj.logStmt(__stmt, __id_val, __project_id_val, __payer_id_val, __payment_method_id_val, __is_default_val, __created_at_val)
project_payment = &ProjectPayment{}
err = obj.driver.QueryRow(__stmt, __project_id_val, __payer_id_val, __payment_method_id_val, __created_at_val).Scan(&project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.CreatedAt)
err = obj.driver.QueryRow(__stmt, __id_val, __project_id_val, __payer_id_val, __payment_method_id_val, __is_default_val, __created_at_val).Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
@ -6414,20 +6463,20 @@ func (obj *postgresImpl) All_Project_By_ProjectMember_MemberId_OrderBy_Asc_Proje
}
func (obj *postgresImpl) Get_ProjectPayment_By_ProjectId(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
func (obj *postgresImpl) Get_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
project_payment *ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.created_at FROM project_payments WHERE project_payments.project_id = ?")
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.id = ?")
var __values []interface{}
__values = append(__values, project_payment_project_id.value())
__values = append(__values, project_payment_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
project_payment = &ProjectPayment{}
err = obj.driver.QueryRow(__stmt, __values...).Scan(&project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.CreatedAt)
err = obj.driver.QueryRow(__stmt, __values...).Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
@ -6435,14 +6484,14 @@ func (obj *postgresImpl) Get_ProjectPayment_By_ProjectId(ctx context.Context,
}
func (obj *postgresImpl) Get_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
func (obj *postgresImpl) Get_ProjectPayment_By_ProjectId_And_IsDefault_Equal_True(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
project_payment *ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.created_at FROM project_payments WHERE project_payments.payer_id = ? LIMIT 2")
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.project_id = ? AND project_payments.is_default = true LIMIT 2")
var __values []interface{}
__values = append(__values, project_payment_payer_id.value())
__values = append(__values, project_payment_project_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
@ -6461,13 +6510,13 @@ func (obj *postgresImpl) Get_ProjectPayment_By_PayerId(ctx context.Context,
}
project_payment = &ProjectPayment{}
err = __rows.Scan(&project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.CreatedAt)
err = __rows.Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
if __rows.Next() {
return nil, tooManyRows("ProjectPayment_By_PayerId")
return nil, tooManyRows("ProjectPayment_By_ProjectId_And_IsDefault_Equal_True")
}
if err := __rows.Err(); err != nil {
@ -6478,6 +6527,72 @@ func (obj *postgresImpl) Get_ProjectPayment_By_PayerId(ctx context.Context,
}
func (obj *postgresImpl) All_ProjectPayment_By_ProjectId(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
rows []*ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.project_id = ?")
var __values []interface{}
__values = append(__values, project_payment_project_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__rows, err := obj.driver.Query(__stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
defer __rows.Close()
for __rows.Next() {
project_payment := &ProjectPayment{}
err = __rows.Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
rows = append(rows, project_payment)
}
if err := __rows.Err(); err != nil {
return nil, obj.makeErr(err)
}
return rows, nil
}
func (obj *postgresImpl) All_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
rows []*ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.payer_id = ?")
var __values []interface{}
__values = append(__values, project_payment_payer_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__rows, err := obj.driver.Query(__stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
defer __rows.Close()
for __rows.Next() {
project_payment := &ProjectPayment{}
err = __rows.Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
rows = append(rows, project_payment)
}
if err := __rows.Err(); err != nil {
return nil, obj.makeErr(err)
}
return rows, nil
}
func (obj *postgresImpl) Get_ProjectInvoiceStamp_By_ProjectId_And_StartDate(ctx context.Context,
project_invoice_stamp_project_id ProjectInvoiceStamp_ProjectId_Field,
project_invoice_stamp_start_date ProjectInvoiceStamp_StartDate_Field) (
@ -7779,6 +7894,46 @@ func (obj *postgresImpl) Update_Project_By_Id(ctx context.Context,
return project, nil
}
func (obj *postgresImpl) Update_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
update ProjectPayment_Update_Fields) (
project_payment *ProjectPayment, err error) {
var __sets = &__sqlbundle_Hole{}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE project_payments SET "), __sets, __sqlbundle_Literal(" WHERE project_payments.id = ? RETURNING project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at")}}
__sets_sql := __sqlbundle_Literals{Join: ", "}
var __values []interface{}
var __args []interface{}
if update.IsDefault._set {
__values = append(__values, update.IsDefault.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("is_default = ?"))
}
if len(__sets_sql.SQLs) == 0 {
return nil, emptyUpdate()
}
__args = append(__args, project_payment_id.value())
__values = append(__values, __args...)
__sets.SQL = __sets_sql
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
project_payment = &ProjectPayment{}
err = obj.driver.QueryRow(__stmt, __values...).Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, obj.makeErr(err)
}
return project_payment, nil
}
func (obj *postgresImpl) Update_ApiKey_By_Id(ctx context.Context,
api_key_id ApiKey_Id_Field,
update ApiKey_Update_Fields) (
@ -8168,6 +8323,32 @@ func (obj *postgresImpl) Delete_Project_By_Id(ctx context.Context,
}
func (obj *postgresImpl) Delete_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
deleted bool, err error) {
var __embed_stmt = __sqlbundle_Literal("DELETE FROM project_payments WHERE project_payments.id = ?")
var __values []interface{}
__values = append(__values, project_payment_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__res, err := obj.driver.Exec(__stmt, __values...)
if err != nil {
return false, obj.makeErr(err)
}
__count, err := __res.RowsAffected()
if err != nil {
return false, obj.makeErr(err)
}
return __count > 0, nil
}
func (obj *postgresImpl) Delete_ProjectMember_By_MemberId_And_ProjectId(ctx context.Context,
project_member_member_id ProjectMember_MemberId_Field,
project_member_project_id ProjectMember_ProjectId_Field) (
@ -8990,23 +9171,27 @@ func (obj *sqlite3Impl) Create_Project(ctx context.Context,
}
func (obj *sqlite3Impl) Create_ProjectPayment(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
project_payment_project_id ProjectPayment_ProjectId_Field,
project_payment_payer_id ProjectPayment_PayerId_Field,
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field) (
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field,
project_payment_is_default ProjectPayment_IsDefault_Field) (
project_payment *ProjectPayment, err error) {
__now := obj.db.Hooks.Now().UTC()
__id_val := project_payment_id.value()
__project_id_val := project_payment_project_id.value()
__payer_id_val := project_payment_payer_id.value()
__payment_method_id_val := project_payment_payment_method_id.value()
__is_default_val := project_payment_is_default.value()
__created_at_val := __now
var __embed_stmt = __sqlbundle_Literal("INSERT INTO project_payments ( project_id, payer_id, payment_method_id, created_at ) VALUES ( ?, ?, ?, ? )")
var __embed_stmt = __sqlbundle_Literal("INSERT INTO project_payments ( id, project_id, payer_id, payment_method_id, is_default, created_at ) VALUES ( ?, ?, ?, ?, ?, ? )")
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __project_id_val, __payer_id_val, __payment_method_id_val, __created_at_val)
obj.logStmt(__stmt, __id_val, __project_id_val, __payer_id_val, __payment_method_id_val, __is_default_val, __created_at_val)
__res, err := obj.driver.Exec(__stmt, __project_id_val, __payer_id_val, __payment_method_id_val, __created_at_val)
__res, err := obj.driver.Exec(__stmt, __id_val, __project_id_val, __payer_id_val, __payment_method_id_val, __is_default_val, __created_at_val)
if err != nil {
return nil, obj.makeErr(err)
}
@ -9946,20 +10131,20 @@ func (obj *sqlite3Impl) All_Project_By_ProjectMember_MemberId_OrderBy_Asc_Projec
}
func (obj *sqlite3Impl) Get_ProjectPayment_By_ProjectId(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
func (obj *sqlite3Impl) Get_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
project_payment *ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.created_at FROM project_payments WHERE project_payments.project_id = ?")
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.id = ?")
var __values []interface{}
__values = append(__values, project_payment_project_id.value())
__values = append(__values, project_payment_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
project_payment = &ProjectPayment{}
err = obj.driver.QueryRow(__stmt, __values...).Scan(&project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.CreatedAt)
err = obj.driver.QueryRow(__stmt, __values...).Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
@ -9967,14 +10152,14 @@ func (obj *sqlite3Impl) Get_ProjectPayment_By_ProjectId(ctx context.Context,
}
func (obj *sqlite3Impl) Get_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
func (obj *sqlite3Impl) Get_ProjectPayment_By_ProjectId_And_IsDefault_Equal_True(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
project_payment *ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.created_at FROM project_payments WHERE project_payments.payer_id = ? LIMIT 2")
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.project_id = ? AND project_payments.is_default = 1 LIMIT 2")
var __values []interface{}
__values = append(__values, project_payment_payer_id.value())
__values = append(__values, project_payment_project_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
@ -9993,13 +10178,13 @@ func (obj *sqlite3Impl) Get_ProjectPayment_By_PayerId(ctx context.Context,
}
project_payment = &ProjectPayment{}
err = __rows.Scan(&project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.CreatedAt)
err = __rows.Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
if __rows.Next() {
return nil, tooManyRows("ProjectPayment_By_PayerId")
return nil, tooManyRows("ProjectPayment_By_ProjectId_And_IsDefault_Equal_True")
}
if err := __rows.Err(); err != nil {
@ -10010,6 +10195,72 @@ func (obj *sqlite3Impl) Get_ProjectPayment_By_PayerId(ctx context.Context,
}
func (obj *sqlite3Impl) All_ProjectPayment_By_ProjectId(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
rows []*ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.project_id = ?")
var __values []interface{}
__values = append(__values, project_payment_project_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__rows, err := obj.driver.Query(__stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
defer __rows.Close()
for __rows.Next() {
project_payment := &ProjectPayment{}
err = __rows.Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
rows = append(rows, project_payment)
}
if err := __rows.Err(); err != nil {
return nil, obj.makeErr(err)
}
return rows, nil
}
func (obj *sqlite3Impl) All_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
rows []*ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.payer_id = ?")
var __values []interface{}
__values = append(__values, project_payment_payer_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__rows, err := obj.driver.Query(__stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
defer __rows.Close()
for __rows.Next() {
project_payment := &ProjectPayment{}
err = __rows.Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
rows = append(rows, project_payment)
}
if err := __rows.Err(); err != nil {
return nil, obj.makeErr(err)
}
return rows, nil
}
func (obj *sqlite3Impl) Get_ProjectInvoiceStamp_By_ProjectId_And_StartDate(ctx context.Context,
project_invoice_stamp_project_id ProjectInvoiceStamp_ProjectId_Field,
project_invoice_stamp_start_date ProjectInvoiceStamp_StartDate_Field) (
@ -11371,6 +11622,56 @@ func (obj *sqlite3Impl) Update_Project_By_Id(ctx context.Context,
return project, nil
}
func (obj *sqlite3Impl) Update_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
update ProjectPayment_Update_Fields) (
project_payment *ProjectPayment, err error) {
var __sets = &__sqlbundle_Hole{}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE project_payments SET "), __sets, __sqlbundle_Literal(" WHERE project_payments.id = ?")}}
__sets_sql := __sqlbundle_Literals{Join: ", "}
var __values []interface{}
var __args []interface{}
if update.IsDefault._set {
__values = append(__values, update.IsDefault.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("is_default = ?"))
}
if len(__sets_sql.SQLs) == 0 {
return nil, emptyUpdate()
}
__args = append(__args, project_payment_id.value())
__values = append(__values, __args...)
__sets.SQL = __sets_sql
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
project_payment = &ProjectPayment{}
_, err = obj.driver.Exec(__stmt, __values...)
if err != nil {
return nil, obj.makeErr(err)
}
var __embed_stmt_get = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE project_payments.id = ?")
var __stmt_get = __sqlbundle_Render(obj.dialect, __embed_stmt_get)
obj.logStmt("(IMPLIED) "+__stmt_get, __args...)
err = obj.driver.QueryRow(__stmt_get, __args...).Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, obj.makeErr(err)
}
return project_payment, nil
}
func (obj *sqlite3Impl) Update_ApiKey_By_Id(ctx context.Context,
api_key_id ApiKey_Id_Field,
update ApiKey_Update_Fields) (
@ -11800,6 +12101,32 @@ func (obj *sqlite3Impl) Delete_Project_By_Id(ctx context.Context,
}
func (obj *sqlite3Impl) Delete_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
deleted bool, err error) {
var __embed_stmt = __sqlbundle_Literal("DELETE FROM project_payments WHERE project_payments.id = ?")
var __values []interface{}
__values = append(__values, project_payment_id.value())
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
__res, err := obj.driver.Exec(__stmt, __values...)
if err != nil {
return false, obj.makeErr(err)
}
__count, err := __res.RowsAffected()
if err != nil {
return false, obj.makeErr(err)
}
return __count > 0, nil
}
func (obj *sqlite3Impl) Delete_ProjectMember_By_MemberId_And_ProjectId(ctx context.Context,
project_member_member_id ProjectMember_MemberId_Field,
project_member_project_id ProjectMember_ProjectId_Field) (
@ -12176,13 +12503,13 @@ func (obj *sqlite3Impl) getLastProjectPayment(ctx context.Context,
pk int64) (
project_payment *ProjectPayment, err error) {
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.created_at FROM project_payments WHERE _rowid_ = ?")
var __embed_stmt = __sqlbundle_Literal("SELECT project_payments.id, project_payments.project_id, project_payments.payer_id, project_payments.payment_method_id, project_payments.is_default, project_payments.created_at FROM project_payments WHERE _rowid_ = ?")
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, pk)
project_payment = &ProjectPayment{}
err = obj.driver.QueryRow(__stmt, pk).Scan(&project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.CreatedAt)
err = obj.driver.QueryRow(__stmt, pk).Scan(&project_payment.Id, &project_payment.ProjectId, &project_payment.PayerId, &project_payment.PaymentMethodId, &project_payment.IsDefault, &project_payment.CreatedAt)
if err != nil {
return nil, obj.makeErr(err)
}
@ -12857,6 +13184,26 @@ func (rx *Rx) All_ProjectMember_By_MemberId(ctx context.Context,
return tx.All_ProjectMember_By_MemberId(ctx, project_member_member_id)
}
func (rx *Rx) All_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
rows []*ProjectPayment, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.All_ProjectPayment_By_PayerId(ctx, project_payment_payer_id)
}
func (rx *Rx) All_ProjectPayment_By_ProjectId(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
rows []*ProjectPayment, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.All_ProjectPayment_By_ProjectId(ctx, project_payment_project_id)
}
func (rx *Rx) All_Project_By_CreatedAt_Less_OrderBy_Asc_CreatedAt(ctx context.Context,
project_created_at_less Project_CreatedAt_Field) (
rows []*Project, err error) {
@ -13179,15 +13526,17 @@ func (rx *Rx) Create_ProjectMember(ctx context.Context,
}
func (rx *Rx) Create_ProjectPayment(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
project_payment_project_id ProjectPayment_ProjectId_Field,
project_payment_payer_id ProjectPayment_PayerId_Field,
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field) (
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field,
project_payment_is_default ProjectPayment_IsDefault_Field) (
project_payment *ProjectPayment, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Create_ProjectPayment(ctx, project_payment_project_id, project_payment_payer_id, project_payment_payment_method_id)
return tx.Create_ProjectPayment(ctx, project_payment_id, project_payment_project_id, project_payment_payer_id, project_payment_payment_method_id, project_payment_is_default)
}
@ -13401,6 +13750,16 @@ func (rx *Rx) Delete_ProjectMember_By_MemberId_And_ProjectId(ctx context.Context
return tx.Delete_ProjectMember_By_MemberId_And_ProjectId(ctx, project_member_member_id, project_member_project_id)
}
func (rx *Rx) Delete_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
deleted bool, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Delete_ProjectPayment_By_Id(ctx, project_payment_id)
}
func (rx *Rx) Delete_Project_By_Id(ctx context.Context,
project_id Project_Id_Field) (
deleted bool, err error) {
@ -13630,24 +13989,24 @@ func (rx *Rx) Get_ProjectInvoiceStamp_By_ProjectId_And_StartDate(ctx context.Con
return tx.Get_ProjectInvoiceStamp_By_ProjectId_And_StartDate(ctx, project_invoice_stamp_project_id, project_invoice_stamp_start_date)
}
func (rx *Rx) Get_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
func (rx *Rx) Get_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
project_payment *ProjectPayment, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Get_ProjectPayment_By_PayerId(ctx, project_payment_payer_id)
return tx.Get_ProjectPayment_By_Id(ctx, project_payment_id)
}
func (rx *Rx) Get_ProjectPayment_By_ProjectId(ctx context.Context,
func (rx *Rx) Get_ProjectPayment_By_ProjectId_And_IsDefault_Equal_True(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
project_payment *ProjectPayment, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Get_ProjectPayment_By_ProjectId(ctx, project_payment_project_id)
return tx.Get_ProjectPayment_By_ProjectId_And_IsDefault_Equal_True(ctx, project_payment_project_id)
}
func (rx *Rx) Get_Project_By_Id(ctx context.Context,
@ -13898,6 +14257,17 @@ func (rx *Rx) Update_PendingAudits_By_NodeId(ctx context.Context,
return tx.Update_PendingAudits_By_NodeId(ctx, pending_audits_node_id, update)
}
func (rx *Rx) Update_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
update ProjectPayment_Update_Fields) (
project_payment *ProjectPayment, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Update_ProjectPayment_By_Id(ctx, project_payment_id, update)
}
func (rx *Rx) Update_Project_By_Id(ctx context.Context,
project_id Project_Id_Field,
update Project_Update_Fields) (
@ -13964,6 +14334,14 @@ type Methods interface {
project_member_member_id ProjectMember_MemberId_Field) (
rows []*ProjectMember, err error)
All_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
rows []*ProjectPayment, err error)
All_ProjectPayment_By_ProjectId(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
rows []*ProjectPayment, err error)
All_Project_By_CreatedAt_Less_OrderBy_Asc_CreatedAt(ctx context.Context,
project_created_at_less Project_CreatedAt_Field) (
rows []*Project, err error)
@ -14146,9 +14524,11 @@ type Methods interface {
project_member *ProjectMember, err error)
Create_ProjectPayment(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
project_payment_project_id ProjectPayment_ProjectId_Field,
project_payment_payer_id ProjectPayment_PayerId_Field,
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field) (
project_payment_payment_method_id ProjectPayment_PaymentMethodId_Field,
project_payment_is_default ProjectPayment_IsDefault_Field) (
project_payment *ProjectPayment, err error)
Create_RegistrationToken(ctx context.Context,
@ -14244,6 +14624,10 @@ type Methods interface {
project_member_project_id ProjectMember_ProjectId_Field) (
deleted bool, err error)
Delete_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
deleted bool, err error)
Delete_Project_By_Id(ctx context.Context,
project_id Project_Id_Field) (
deleted bool, err error)
@ -14340,11 +14724,11 @@ type Methods interface {
project_invoice_stamp_start_date ProjectInvoiceStamp_StartDate_Field) (
project_invoice_stamp *ProjectInvoiceStamp, err error)
Get_ProjectPayment_By_PayerId(ctx context.Context,
project_payment_payer_id ProjectPayment_PayerId_Field) (
Get_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field) (
project_payment *ProjectPayment, err error)
Get_ProjectPayment_By_ProjectId(ctx context.Context,
Get_ProjectPayment_By_ProjectId_And_IsDefault_Equal_True(ctx context.Context,
project_payment_project_id ProjectPayment_ProjectId_Field) (
project_payment *ProjectPayment, err error)
@ -14458,6 +14842,11 @@ type Methods interface {
update PendingAudits_Update_Fields) (
pending_audits *PendingAudits, err error)
Update_ProjectPayment_By_Id(ctx context.Context,
project_payment_id ProjectPayment_Id_Field,
update ProjectPayment_Update_Fields) (
project_payment *ProjectPayment, err error)
Update_Project_By_Id(ctx context.Context,
project_id Project_Id_Field,
update Project_Update_Fields) (

View File

@ -264,11 +264,13 @@ CREATE TABLE user_payments (
UNIQUE ( customer_id )
);
CREATE TABLE project_payments (
id bytea NOT NULL,
project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
payer_id bytea NOT NULL REFERENCES user_payments( user_id ) ON DELETE CASCADE,
payment_method_id bytea NOT NULL,
is_default boolean NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( project_id )
PRIMARY KEY ( id )
);
CREATE INDEX bucket_name_project_id_interval_start_interval_seconds ON bucket_bandwidth_rollups ( bucket_name, project_id, interval_start, interval_seconds );
CREATE UNIQUE INDEX bucket_id_rollup ON bucket_usages ( bucket_id, rollup_end_time );

View File

@ -264,11 +264,13 @@ CREATE TABLE user_payments (
UNIQUE ( customer_id )
);
CREATE TABLE project_payments (
id BLOB NOT NULL,
project_id BLOB NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
payer_id BLOB NOT NULL REFERENCES user_payments( user_id ) ON DELETE CASCADE,
payment_method_id BLOB NOT NULL,
is_default INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL,
PRIMARY KEY ( project_id )
PRIMARY KEY ( id )
);
CREATE INDEX bucket_name_project_id_interval_start_interval_seconds ON bucket_bandwidth_rollups ( bucket_name, project_id, interval_start, interval_seconds );
CREATE UNIQUE INDEX bucket_id_rollup ON bucket_usages ( bucket_id, rollup_end_time );

View File

@ -346,18 +346,42 @@ func (m *lockedProjectPayments) Create(ctx context.Context, info console.Project
return m.db.Create(ctx, info)
}
func (m *lockedProjectPayments) GetByPayerID(ctx context.Context, payerID uuid.UUID) (*console.ProjectPayment, error) {
func (m *lockedProjectPayments) Delete(ctx context.Context, projectPaymentID uuid.UUID) error {
m.Lock()
defer m.Unlock()
return m.db.Delete(ctx, projectPaymentID)
}
func (m *lockedProjectPayments) GetByID(ctx context.Context, projectPaymentID uuid.UUID) (*console.ProjectPayment, error) {
m.Lock()
defer m.Unlock()
return m.db.GetByID(ctx, projectPaymentID)
}
func (m *lockedProjectPayments) GetByPayerID(ctx context.Context, payerID uuid.UUID) ([]*console.ProjectPayment, error) {
m.Lock()
defer m.Unlock()
return m.db.GetByPayerID(ctx, payerID)
}
func (m *lockedProjectPayments) GetByProjectID(ctx context.Context, projectID uuid.UUID) (*console.ProjectPayment, error) {
func (m *lockedProjectPayments) GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]*console.ProjectPayment, error) {
m.Lock()
defer m.Unlock()
return m.db.GetByProjectID(ctx, projectID)
}
func (m *lockedProjectPayments) GetDefaultByProjectID(ctx context.Context, projectID uuid.UUID) (*console.ProjectPayment, error) {
m.Lock()
defer m.Unlock()
return m.db.GetDefaultByProjectID(ctx, projectID)
}
func (m *lockedProjectPayments) Update(ctx context.Context, info console.ProjectPayment) error {
m.Lock()
defer m.Unlock()
return m.db.Update(ctx, info)
}
// Projects is a getter for Projects repository
func (m *lockedConsole) Projects() console.Projects {
m.Lock()

View File

@ -927,6 +927,22 @@ func (db *DB) PostgresMigration() *migrate.Migration {
`UPDATE nodes SET disqualified=NULL WHERE disqualified IS NOT NULL AND audit_reputation_alpha / (audit_reputation_alpha + audit_reputation_beta) >= 0.6;`,
},
},
{
Description: "Add unique id for project payments. Add is_default property",
Version: 40,
Action: migrate.SQL{
`DROP TABLE project_payments CASCADE`,
`CREATE TABLE project_payments (
id bytea NOT NULL,
project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
payer_id bytea NOT NULL REFERENCES user_payments( user_id ) ON DELETE CASCADE,
payment_method_id bytea NOT NULL,
is_default boolean NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( id )
);`,
},
},
},
}
}

View File

@ -7,53 +7,95 @@ import (
"context"
"github.com/skyrings/skyring-common/tools/uuid"
"github.com/zeebo/errs"
"storj.io/storj/satellite/console"
dbx "storj.io/storj/satellite/satellitedb/dbx"
)
// projectpayments is the an implementation of console.ProjectPayments.
// projectPayments is the an implementation of console.ProjectPayments.
// Allows to work with project payment info storage
type projectpayments struct {
db dbx.Methods
type projectPayments struct {
db *dbx.DB
methods dbx.Methods
}
func (pp *projectPayments) Delete(ctx context.Context, projectPaymentID uuid.UUID) error {
_, err := pp.methods.Delete_ProjectPayment_By_Id(ctx, dbx.ProjectPayment_Id(projectPaymentID[:]))
return err
}
func (pp *projectPayments) GetByID(ctx context.Context, projectPaymentID uuid.UUID) (*console.ProjectPayment, error) {
dbxInfo, err := pp.methods.Get_ProjectPayment_By_Id(ctx, dbx.ProjectPayment_Id(projectPaymentID[:]))
if err != nil {
return nil, err
}
return fromDBXProjectPayment(ctx, dbxInfo)
}
func (pp *projectPayments) Update(ctx context.Context, info console.ProjectPayment) error {
updateFields := dbx.ProjectPayment_Update_Fields{
IsDefault: dbx.ProjectPayment_IsDefault(info.IsDefault),
}
_, err := pp.methods.Update_ProjectPayment_By_Id(ctx, dbx.ProjectPayment_Id(info.ID[:]), updateFields)
return err
}
func (pp *projectPayments) GetDefaultByProjectID(ctx context.Context, projectID uuid.UUID) (*console.ProjectPayment, error) {
dbxInfo, err := pp.methods.Get_ProjectPayment_By_ProjectId_And_IsDefault_Equal_True(ctx, dbx.ProjectPayment_ProjectId(projectID[:]))
if err != nil {
return nil, err
}
return fromDBXProjectPayment(ctx, dbxInfo)
}
// Create stores new project payment info into db
func (infos *projectpayments) Create(ctx context.Context, info console.ProjectPayment) (*console.ProjectPayment, error) {
dbxInfo, err := infos.db.Create_ProjectPayment(ctx,
func (pp *projectPayments) Create(ctx context.Context, info console.ProjectPayment) (*console.ProjectPayment, error) {
id, err := uuid.New()
if err != nil {
return nil, err
}
dbxInfo, err := pp.methods.Create_ProjectPayment(ctx,
dbx.ProjectPayment_Id(id[:]),
dbx.ProjectPayment_ProjectId(info.ProjectID[:]),
dbx.ProjectPayment_PayerId(info.PayerID[:]),
dbx.ProjectPayment_PaymentMethodId(info.PaymentMethodID))
dbx.ProjectPayment_PaymentMethodId(info.PaymentMethodID),
dbx.ProjectPayment_IsDefault(info.IsDefault))
if err != nil {
return nil, err
}
return fromDBXProjectPayment(dbxInfo)
return fromDBXProjectPayment(ctx, dbxInfo)
}
// GetByProjectID retrieves project payment info from db by projectID
func (infos *projectpayments) GetByProjectID(ctx context.Context, projectID uuid.UUID) (*console.ProjectPayment, error) {
dbxInfo, err := infos.db.Get_ProjectPayment_By_ProjectId(ctx, dbx.ProjectPayment_ProjectId(projectID[:]))
func (pp *projectPayments) GetByProjectID(ctx context.Context, projectID uuid.UUID) ([]*console.ProjectPayment, error) {
dbxInfos, err := pp.methods.All_ProjectPayment_By_ProjectId(ctx, dbx.ProjectPayment_ProjectId(projectID[:]))
if err != nil {
return nil, err
}
return fromDBXProjectPayment(dbxInfo)
return fromDBXProjectPaymentSlice(ctx, dbxInfos)
}
// GetByPayerID retrieves project payment info from db by payerID(userID)
func (infos *projectpayments) GetByPayerID(ctx context.Context, payerID uuid.UUID) (*console.ProjectPayment, error) {
dbxInfo, err := infos.db.Get_ProjectPayment_By_PayerId(ctx, dbx.ProjectPayment_PayerId(payerID[:]))
func (pp *projectPayments) GetByPayerID(ctx context.Context, payerID uuid.UUID) ([]*console.ProjectPayment, error) {
dbxInfos, err := pp.methods.All_ProjectPayment_By_PayerId(ctx, dbx.ProjectPayment_PayerId(payerID[:]))
if err != nil {
return nil, err
}
return fromDBXProjectPayment(dbxInfo)
return fromDBXProjectPaymentSlice(ctx, dbxInfos)
}
// fromDBXProjectPayment is a helper method to convert from *dbx.ProjectPayment to *console.ProjectPayment
func fromDBXProjectPayment(dbxInfo *dbx.ProjectPayment) (*console.ProjectPayment, error) {
func fromDBXProjectPayment(ctx context.Context, dbxInfo *dbx.ProjectPayment) (_ *console.ProjectPayment, err error) {
defer mon.Task()(&ctx)(&err)
projectID, err := bytesToUUID(dbxInfo.ProjectId)
if err != nil {
return nil, err
@ -64,10 +106,36 @@ func fromDBXProjectPayment(dbxInfo *dbx.ProjectPayment) (*console.ProjectPayment
return nil, err
}
id, err := bytesToUUID(dbxInfo.Id)
if err != nil {
return nil, err
}
return &console.ProjectPayment{
ID: id,
ProjectID: projectID,
PayerID: payerID,
PaymentMethodID: dbxInfo.PaymentMethodId,
CreatedAt: dbxInfo.CreatedAt,
IsDefault: dbxInfo.IsDefault,
}, nil
}
func fromDBXProjectPaymentSlice(ctx context.Context, dbxInfos []*dbx.ProjectPayment) (_ []*console.ProjectPayment, err error) {
defer mon.Task()(&ctx)(&err)
var projectPayments []*console.ProjectPayment
var errors []error
// Generating []dbo from []dbx and collecting all errors
for _, paymentMethodDBX := range dbxInfos {
projectPayment, err := fromDBXProjectPayment(ctx, paymentMethodDBX)
if err != nil {
errors = append(errors, err)
continue
}
projectPayments = append(projectPayments, projectPayment)
}
return projectPayments, errs.Combine(errors...)
}

View File

@ -0,0 +1,345 @@
-- AUTOGENERATED BY gopkg.in/spacemonkeygo/dbx.v1
-- DO NOT EDIT
CREATE TABLE accounting_rollups (
id bigserial NOT NULL,
node_id bytea NOT NULL,
start_time timestamp with time zone NOT NULL,
put_total bigint NOT NULL,
get_total bigint NOT NULL,
get_audit_total bigint NOT NULL,
get_repair_total bigint NOT NULL,
put_repair_total bigint NOT NULL,
at_rest_total double precision NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE accounting_timestamps (
name text NOT NULL,
value timestamp with time zone NOT NULL,
PRIMARY KEY ( name )
);
CREATE TABLE bucket_bandwidth_rollups (
bucket_name bytea NOT NULL,
project_id bytea NOT NULL,
interval_start timestamp NOT NULL,
interval_seconds integer NOT NULL,
action integer NOT NULL,
inline bigint NOT NULL,
allocated bigint NOT NULL,
settled bigint NOT NULL,
PRIMARY KEY ( bucket_name, project_id, interval_start, action )
);
CREATE TABLE bucket_storage_tallies (
bucket_name bytea NOT NULL,
project_id bytea NOT NULL,
interval_start timestamp NOT NULL,
inline bigint NOT NULL,
remote bigint NOT NULL,
remote_segments_count integer NOT NULL,
inline_segments_count integer NOT NULL,
object_count integer NOT NULL,
metadata_size bigint NOT NULL,
PRIMARY KEY ( bucket_name, project_id, interval_start )
);
CREATE TABLE bucket_usages (
id bytea NOT NULL,
bucket_id bytea NOT NULL,
rollup_end_time timestamp with time zone NOT NULL,
remote_stored_data bigint NOT NULL,
inline_stored_data bigint NOT NULL,
remote_segments integer NOT NULL,
inline_segments integer NOT NULL,
objects integer NOT NULL,
metadata_size bigint NOT NULL,
repair_egress bigint NOT NULL,
get_egress bigint NOT NULL,
audit_egress bigint NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE certRecords (
publickey bytea NOT NULL,
id bytea NOT NULL,
update_at timestamp with time zone NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE injuredsegments (
path text NOT NULL,
data bytea NOT NULL,
attempted timestamp,
PRIMARY KEY ( path )
);
CREATE TABLE irreparabledbs (
segmentpath bytea NOT NULL,
segmentdetail bytea NOT NULL,
pieces_lost_count bigint NOT NULL,
seg_damaged_unix_sec bigint NOT NULL,
repair_attempt_count bigint NOT NULL,
PRIMARY KEY ( segmentpath )
);
CREATE TABLE nodes (
id bytea NOT NULL,
address text NOT NULL,
last_net text NOT NULL,
protocol integer NOT NULL,
type integer NOT NULL,
email text NOT NULL,
wallet text NOT NULL,
free_bandwidth bigint NOT NULL,
free_disk bigint NOT NULL,
major bigint NOT NULL,
minor bigint NOT NULL,
patch bigint NOT NULL,
hash text NOT NULL,
timestamp timestamp with time zone NOT NULL,
release boolean NOT NULL,
latency_90 bigint NOT NULL,
audit_success_count bigint NOT NULL,
total_audit_count bigint NOT NULL,
uptime_success_count bigint NOT NULL,
total_uptime_count bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
last_contact_success timestamp with time zone NOT NULL,
last_contact_failure timestamp with time zone NOT NULL,
contained boolean NOT NULL,
disqualified timestamp with time zone,
audit_reputation_alpha double precision NOT NULL,
audit_reputation_beta double precision NOT NULL,
uptime_reputation_alpha double precision NOT NULL,
uptime_reputation_beta double precision NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE offers (
id serial NOT NULL,
name text NOT NULL,
description text NOT NULL,
award_credit_in_cents integer NOT NULL,
invitee_credit_in_cents integer NOT NULL,
award_credit_duration_days integer NOT NULL,
invitee_credit_duration_days integer NOT NULL,
redeemable_cap integer NOT NULL,
num_redeemed integer NOT NULL,
expires_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
status integer NOT NULL,
type integer NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE pending_audits (
node_id bytea NOT NULL,
piece_id bytea NOT NULL,
stripe_index bigint NOT NULL,
share_size bigint NOT NULL,
expected_share_hash bytea NOT NULL,
reverify_count bigint NOT NULL,
PRIMARY KEY ( node_id )
);
CREATE TABLE projects (
id bytea NOT NULL,
name text NOT NULL,
description text NOT NULL,
usage_limit bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE registration_tokens (
secret bytea NOT NULL,
owner_id bytea,
project_limit integer NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( secret ),
UNIQUE ( owner_id )
);
CREATE TABLE reset_password_tokens (
secret bytea NOT NULL,
owner_id bytea NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( secret ),
UNIQUE ( owner_id )
);
CREATE TABLE serial_numbers (
id serial NOT NULL,
serial_number bytea NOT NULL,
bucket_id bytea NOT NULL,
expires_at timestamp NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE storagenode_bandwidth_rollups (
storagenode_id bytea NOT NULL,
interval_start timestamp NOT NULL,
interval_seconds integer NOT NULL,
action integer NOT NULL,
allocated bigint NOT NULL,
settled bigint NOT NULL,
PRIMARY KEY ( storagenode_id, interval_start, action )
);
CREATE TABLE storagenode_storage_tallies (
id bigserial NOT NULL,
node_id bytea NOT NULL,
interval_end_time timestamp with time zone NOT NULL,
data_total double precision NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE users (
id bytea NOT NULL,
email text NOT NULL,
full_name text NOT NULL,
short_name text,
password_hash bytea NOT NULL,
status integer NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE value_attributions (
project_id bytea NOT NULL,
bucket_name bytea NOT NULL,
partner_id bytea NOT NULL,
last_updated timestamp NOT NULL,
PRIMARY KEY ( project_id, bucket_name )
);
CREATE TABLE api_keys (
id bytea NOT NULL,
project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
head bytea NOT NULL,
name text NOT NULL,
secret bytea NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( id ),
UNIQUE ( head ),
UNIQUE ( name, project_id )
);
CREATE TABLE bucket_metainfos (
id bytea NOT NULL,
project_id bytea NOT NULL REFERENCES projects( id ),
name bytea NOT NULL,
path_cipher integer NOT NULL,
created_at timestamp with time zone NOT NULL,
default_segment_size integer NOT NULL,
default_encryption_cipher_suite integer NOT NULL,
default_encryption_block_size integer NOT NULL,
default_redundancy_algorithm integer NOT NULL,
default_redundancy_share_size integer NOT NULL,
default_redundancy_required_shares integer NOT NULL,
default_redundancy_repair_shares integer NOT NULL,
default_redundancy_optimal_shares integer NOT NULL,
default_redundancy_total_shares integer NOT NULL,
PRIMARY KEY ( id ),
UNIQUE ( name, project_id )
);
CREATE TABLE project_invoice_stamps (
project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
invoice_id bytea NOT NULL,
start_date timestamp with time zone NOT NULL,
end_date timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( project_id, start_date, end_date ),
UNIQUE ( invoice_id )
);
CREATE TABLE project_members (
member_id bytea NOT NULL REFERENCES users( id ) ON DELETE CASCADE,
project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( member_id, project_id )
);
CREATE TABLE used_serials (
serial_number_id integer NOT NULL REFERENCES serial_numbers( id ) ON DELETE CASCADE,
storage_node_id bytea NOT NULL,
PRIMARY KEY ( serial_number_id, storage_node_id )
);
CREATE TABLE user_credits (
id serial NOT NULL,
user_id bytea NOT NULL REFERENCES users( id ),
offer_id integer NOT NULL REFERENCES offers( id ),
referred_by bytea REFERENCES users( id ),
credits_earned_in_cents integer NOT NULL,
credits_used_in_cents integer NOT NULL,
expires_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( id )
);
CREATE TABLE user_payments (
user_id bytea NOT NULL REFERENCES users( id ) ON DELETE CASCADE,
customer_id bytea NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( user_id ),
UNIQUE ( customer_id )
);
CREATE TABLE project_payments (
id bytea NOT NULL,
project_id bytea NOT NULL REFERENCES projects( id ) ON DELETE CASCADE,
payer_id bytea NOT NULL REFERENCES user_payments( user_id ) ON DELETE CASCADE,
payment_method_id bytea NOT NULL,
is_default boolean NOT NULL,
created_at timestamp with time zone NOT NULL,
PRIMARY KEY ( id )
);
CREATE INDEX bucket_name_project_id_interval_start_interval_seconds ON bucket_bandwidth_rollups ( bucket_name, project_id, interval_start, interval_seconds );
CREATE UNIQUE INDEX bucket_id_rollup ON bucket_usages ( bucket_id, rollup_end_time );
CREATE INDEX node_last_ip ON nodes ( last_net );
CREATE UNIQUE INDEX serial_number ON serial_numbers ( serial_number );
CREATE INDEX serial_numbers_expires_at_index ON serial_numbers ( expires_at );
CREATE INDEX storagenode_id_interval_start_interval_seconds ON storagenode_bandwidth_rollups ( storagenode_id, interval_start, interval_seconds );
---
INSERT INTO "accounting_rollups"("id", "node_id", "start_time", "put_total", "get_total", "get_audit_total", "get_repair_total", "put_repair_total", "at_rest_total") VALUES (1, E'\\367M\\177\\251]t/\\022\\256\\214\\265\\025\\224\\204:\\217\\212\\0102<\\321\\374\\020&\\271Qc\\325\\261\\354\\246\\233'::bytea, '2019-02-09 00:00:00+00', 1000, 2000, 3000, 4000, 0, 5000);
INSERT INTO "accounting_timestamps" VALUES ('LastAtRestTally', '0001-01-01 00:00:00+00');
INSERT INTO "accounting_timestamps" VALUES ('LastRollup', '0001-01-01 00:00:00+00');
INSERT INTO "accounting_timestamps" VALUES ('LastBandwidthTally', '0001-01-01 00:00:00+00');
INSERT INTO "nodes"("id", "address", "last_net", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "uptime_success_count", "total_uptime_count", "created_at", "updated_at", "last_contact_success", "last_contact_failure", "contained", "disqualified", "audit_reputation_alpha", "audit_reputation_beta", "uptime_reputation_alpha", "uptime_reputation_beta") VALUES (E'\\153\\313\\233\\074\\327\\177\\136\\070\\346\\001', '127.0.0.1:55516', '', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 0, 5, 0, 5, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch', false, NULL, 50, 5, 100, 5);
INSERT INTO "nodes"("id", "address", "last_net", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "uptime_success_count", "total_uptime_count", "created_at", "updated_at", "last_contact_success", "last_contact_failure", "contained", "disqualified", "audit_reputation_alpha", "audit_reputation_beta", "uptime_reputation_alpha", "uptime_reputation_beta") VALUES (E'\\006\\223\\250R\\221\\005\\365\\377v>0\\266\\365\\216\\255?\\347\\244\\371?2\\264\\262\\230\\007<\\001\\262\\263\\237\\247n', '127.0.0.1:55518', '', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 0, 0, 3, 3, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch', false, NULL, 50, 0, 100, 0);
INSERT INTO "nodes"("id", "address", "last_net", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "uptime_success_count", "total_uptime_count", "created_at", "updated_at", "last_contact_success", "last_contact_failure", "contained", "disqualified", "audit_reputation_alpha", "audit_reputation_beta", "uptime_reputation_alpha", "uptime_reputation_beta") VALUES (E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014', '127.0.0.1:55517', '', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 0, 0, 0, 0, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch', false, NULL, 50, 0, 100, 0);
INSERT INTO "nodes"("id", "address", "last_net", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "uptime_success_count", "total_uptime_count", "created_at", "updated_at", "last_contact_success", "last_contact_failure", "contained", "disqualified", "audit_reputation_alpha", "audit_reputation_beta", "uptime_reputation_alpha", "uptime_reputation_beta") VALUES (E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\015', '127.0.0.1:55519', '', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 1, 2, 1, 2, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch', false, NULL, 50, 1, 100, 1);
INSERT INTO "nodes"("id", "address", "last_net", "protocol", "type", "email", "wallet", "free_bandwidth", "free_disk", "major", "minor", "patch", "hash", "timestamp", "release","latency_90", "audit_success_count", "total_audit_count", "uptime_success_count", "total_uptime_count", "created_at", "updated_at", "last_contact_success", "last_contact_failure", "contained", "disqualified", "audit_reputation_alpha", "audit_reputation_beta", "uptime_reputation_alpha", "uptime_reputation_beta") VALUES (E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\016', '127.0.0.1:55520', '', 0, 4, '', '', -1, -1, 0, 1, 0, '', 'epoch', false, 0, 300, 400, 300, 400, '2019-02-14 08:07:31.028103+00', '2019-02-14 08:07:31.108963+00', 'epoch', 'epoch', false, NULL, 300, 100, 300, 100);
INSERT INTO "projects"("id", "name", "description", "usage_limit","created_at") VALUES (E'\\022\\217/\\014\\376!K\\023\\276\\031\\311}m\\236\\205\\300'::bytea, 'ProjectName', 'projects description', 0, '2019-02-14 08:28:24.254934+00');
INSERT INTO "users"("id", "full_name", "short_name", "email", "password_hash", "status", "created_at") VALUES (E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, 'Noahson', 'William', '1email1@mail.test', E'some_readable_hash'::bytea, 1, '2019-02-14 08:28:24.614594+00');
INSERT INTO "projects"("id", "name", "description", "usage_limit", "created_at") VALUES (E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea, 'projName1', 'Test project 1', 0, '2019-02-14 08:28:24.636949+00');
INSERT INTO "project_members"("member_id", "project_id", "created_at") VALUES (E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea, '2019-02-14 08:28:24.677953+00');
INSERT INTO "irreparabledbs" ("segmentpath", "segmentdetail", "pieces_lost_count", "seg_damaged_unix_sec", "repair_attempt_count") VALUES ('\x49616d5365676d656e746b6579696e666f30', '\x49616d5365676d656e7464657461696c696e666f30', 10, 1550159554, 10);
INSERT INTO "injuredsegments" ("path", "data") VALUES ('0', '\x0a0130120100');
INSERT INTO "injuredsegments" ("path", "data") VALUES ('here''s/a/great/path', '\x0a136865726527732f612f67726561742f70617468120a0102030405060708090a');
INSERT INTO "injuredsegments" ("path", "data") VALUES ('yet/another/cool/path', '\x0a157965742f616e6f746865722f636f6f6c2f70617468120a0102030405060708090a');
INSERT INTO "injuredsegments" ("path", "data") VALUES ('so/many/iconic/paths/to/choose/from', '\x0a23736f2f6d616e792f69636f6e69632f70617468732f746f2f63686f6f73652f66726f6d120a0102030405060708090a');
INSERT INTO "certrecords" VALUES (E'0Y0\\023\\006\\007*\\206H\\316=\\002\\001\\006\\010*\\206H\\316=\\003\\001\\007\\003B\\000\\004\\360\\267\\227\\377\\253u\\222\\337Y\\324C:GQ\\010\\277v\\010\\315D\\271\\333\\337.\\203\\023=C\\343\\014T%6\\027\\362?\\214\\326\\017U\\334\\000\\260\\224\\260J\\221\\304\\331F\\304\\221\\236zF,\\325\\326l\\215\\306\\365\\200\\022', E'L\\301|\\200\\247}F|1\\320\\232\\037n\\335\\241\\206\\244\\242\\207\\204.\\253\\357\\326\\352\\033Dt\\202`\\022\\325', '2019-02-14 08:07:31.335028+00');
INSERT INTO "bucket_usages" ("id", "bucket_id", "rollup_end_time", "remote_stored_data", "inline_stored_data", "remote_segments", "inline_segments", "objects", "metadata_size", "repair_egress", "get_egress", "audit_egress") VALUES (E'\\153\\313\\233\\074\\327\\177\\136\\070\\346\\001",'::bytea, E'\\366\\146\\032\\321\\316\\161\\070\\133\\302\\271",'::bytea, '2019-03-06 08:28:24.677953+00', 10, 11, 12, 13, 14, 15, 16, 17, 18);
INSERT INTO "registration_tokens" ("secret", "owner_id", "project_limit", "created_at") VALUES (E'\\070\\127\\144\\013\\332\\344\\102\\376\\306\\056\\303\\130\\106\\132\\321\\276\\321\\274\\170\\264\\054\\333\\221\\116\\154\\221\\335\\070\\220\\146\\344\\216'::bytea, null, 1, '2019-02-14 08:28:24.677953+00');
INSERT INTO "serial_numbers" ("id", "serial_number", "bucket_id", "expires_at") VALUES (1, E'0123456701234567'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014/testbucket'::bytea, '2019-03-06 08:28:24.677953+00');
INSERT INTO "used_serials" ("serial_number_id", "storage_node_id") VALUES (1, E'\\006\\223\\250R\\221\\005\\365\\377v>0\\266\\365\\216\\255?\\347\\244\\371?2\\264\\262\\230\\007<\\001\\262\\263\\237\\247n');
INSERT INTO "storagenode_bandwidth_rollups" ("storagenode_id", "interval_start", "interval_seconds", "action", "allocated", "settled") VALUES (E'\\006\\223\\250R\\221\\005\\365\\377v>0\\266\\365\\216\\255?\\347\\244\\371?2\\264\\262\\230\\007<\\001\\262\\263\\237\\247n', '2019-03-06 08:00:00.000000+00', 3600, 1, 1024, 2024);
INSERT INTO "storagenode_storage_tallies" VALUES (1, E'\\3510\\323\\225"~\\036<\\342\\330m\\0253Jhr\\246\\233K\\246#\\2303\\351\\256\\275j\\212UM\\362\\207', '2019-02-14 08:16:57.812849+00', 1000);
INSERT INTO "bucket_bandwidth_rollups" ("bucket_name", "project_id", "interval_start", "interval_seconds", "action", "inline", "allocated", "settled") VALUES (E'testbucket'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea,'2019-03-06 08:00:00.000000+00', 3600, 1, 1024, 2024, 3024);
INSERT INTO "bucket_storage_tallies" ("bucket_name", "project_id", "interval_start", "inline", "remote", "remote_segments_count", "inline_segments_count", "object_count", "metadata_size") VALUES (E'testbucket'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea,'2019-03-06 08:00:00.000000+00', 4024, 5024, 0, 0, 0, 0);
INSERT INTO "bucket_bandwidth_rollups" ("bucket_name", "project_id", "interval_start", "interval_seconds", "action", "inline", "allocated", "settled") VALUES (E'testbucket'::bytea, E'\\170\\160\\157\\370\\274\\366\\113\\364\\272\\235\\301\\243\\321\\102\\321\\136'::bytea,'2019-03-06 08:00:00.000000+00', 3600, 1, 1024, 2024, 3024);
INSERT INTO "bucket_storage_tallies" ("bucket_name", "project_id", "interval_start", "inline", "remote", "remote_segments_count", "inline_segments_count", "object_count", "metadata_size") VALUES (E'testbucket'::bytea, E'\\170\\160\\157\\370\\274\\366\\113\\364\\272\\235\\301\\243\\321\\102\\321\\136'::bytea,'2019-03-06 08:00:00.000000+00', 4024, 5024, 0, 0, 0, 0);
INSERT INTO "reset_password_tokens" ("secret", "owner_id", "created_at") VALUES (E'\\070\\127\\144\\013\\332\\344\\102\\376\\306\\056\\303\\130\\106\\132\\321\\276\\321\\274\\170\\264\\054\\333\\221\\116\\154\\221\\335\\070\\220\\146\\344\\216'::bytea, E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, '2019-05-08 08:28:24.677953+00');
INSERT INTO "pending_audits" ("node_id", "piece_id", "stripe_index", "share_size", "expected_share_hash", "reverify_count") VALUES (E'\\153\\313\\233\\074\\327\\177\\136\\070\\346\\001'::bytea, E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, 5, 1024, E'\\070\\127\\144\\013\\332\\344\\102\\376\\306\\056\\303\\130\\106\\132\\321\\276\\321\\274\\170\\264\\054\\333\\221\\116\\154\\221\\335\\070\\220\\146\\344\\216'::bytea, 1);
INSERT INTO "offers" ("id", "name", "description", "award_credit_in_cents", "invitee_credit_in_cents", "award_credit_duration_days", "invitee_credit_duration_days", "redeemable_cap", "expires_at", "created_at", "num_redeemed", "status", "type") VALUES (1, 'testOffer', 'Test offer 1', 0, 0, 14, 14, 50, '2019-03-14 08:28:24.636949+00', '2019-02-14 08:28:24.636949+00', 0, 0, 0);
INSERT INTO "api_keys" ("id", "project_id", "head", "name", "secret", "created_at") VALUES (E'\\334/\\302;\\225\\355O\\323\\276f\\247\\354/6\\241\\033'::bytea, E'\\022\\217/\\014\\376!K\\023\\276\\031\\311}m\\236\\205\\300'::bytea, E'\\111\\142\\147\\304\\132\\375\\070\\163\\270\\160\\251\\370\\126\\063\\351\\037\\257\\071\\143\\375\\351\\320\\253\\232\\220\\260\\075\\173\\306\\307\\115\\136'::bytea, 'key 2', E'\\254\\011\\315\\333\\273\\365\\001\\071\\024\\154\\253\\332\\301\\216\\361\\074\\221\\367\\251\\231\\274\\333\\300\\367\\001\\272\\327\\111\\315\\123\\042\\016'::bytea, '2019-02-14 08:28:24.267934+00');
INSERT INTO "user_payments" ("user_id", "customer_id", "created_at") VALUES (E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, E'\\022\\217/\\014\\376!K\\023\\276'::bytea, '2019-06-01 08:28:24.267934+00');
INSERT INTO "project_invoice_stamps" ("project_id", "invoice_id", "start_date", "end_date", "created_at") VALUES (E'\\022\\217/\\014\\376!K\\023\\276\\031\\311}m\\236\\205\\300'::bytea, E'\\363\\311\\033w\\222\\303,'::bytea, '2019-06-01 08:28:24.267934+00', '2019-06-29 08:28:24.267934+00', '2019-06-01 08:28:24.267934+00');
INSERT INTO "value_attributions" ("project_id", "bucket_name", "partner_id", "last_updated") VALUES (E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, E''::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea,'2019-02-14 08:07:31.028103+00');
INSERT INTO "user_credits" ("id", "user_id", "offer_id", "referred_by", "credits_earned_in_cents", "credits_used_in_cents", "expires_at", "created_at") VALUES (1, E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, 1, E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, 200, 0, '2019-10-01 08:28:24.267934+00', '2019-06-01 08:28:24.267934+00');
INSERT INTO "bucket_metainfos" ("id", "project_id", "name", "created_at", "path_cipher", "default_segment_size", "default_encryption_cipher_suite", "default_encryption_block_size", "default_redundancy_algorithm", "default_redundancy_share_size", "default_redundancy_required_shares", "default_redundancy_repair_shares", "default_redundancy_optimal_shares", "default_redundancy_total_shares") VALUES (E'\\334/\\302;\\225\\355O\\323\\276f\\247\\354/6\\241\\033'::bytea, E'\\022\\217/\\014\\376!K\\023\\276\\031\\311}m\\236\\205\\300'::bytea, E'testbucketuniquename'::bytea, '2019-06-14 08:28:24.677953+00', 1, 65536, 1, 8192, 1, 4096, 4, 6, 8, 10);
-- NEW DATA --
INSERT INTO "project_payments" ("id", "project_id", "payer_id", "payment_method_id", "is_default","created_at") VALUES (E'\\334/\\302;\\225\\355O\\323\\276f\\247\\354/6\\241\\033'::bytea, E'\\363\\342\\363\\371>+F\\256\\263\\300\\273|\\342N\\347\\014'::bytea, E'\\363\\311\\033w\\222\\303Ci\\265\\343U\\303\\312\\204",'::bytea, E'\\022\\217/\\014\\376!K\\023\\276'::bytea, true, '2019-06-01 08:28:24.267934+00');

3
web/satellite/.env Normal file
View File

@ -0,0 +1,3 @@
VUE_APP_SEGMENTID="segment id"
VUE_APP_STRIPE_PUBLIC_KEY=pk_test

View File

@ -1 +1,2 @@
VUE_APP_SEGMENTID="segment id"
VUE_APP_SEGMENTID="segment id"
VUE_APP_STRIPE_PUBLIC_KEY=pk_test

View File

@ -8,6 +8,7 @@
</head>
<body style="margin: 0px !important; height: 100vh; zoom: 100%">
<div id="app"></div>
<script src="https://js.stripe.com/v3/"></script>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -19,6 +19,7 @@
"graphql": "^14.0.2",
"graphql-tag": "^2.10.0",
"moment": "^2.24.0",
"stripe": "^7.0.1",
"vue": "^2.5.17",
"vue-clipboards": "^1.2.4",
"vue-property-decorator": "^7.0.0",
@ -30,6 +31,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@types/jest": "^23.1.4",
"@types/sinon": "^5.0.5",
"@types/stripe": "^6.26.0",
"@vue/cli-plugin-babel": "^3.0.5",
"@vue/cli-plugin-typescript": "^3.6.0",
"@vue/cli-plugin-unit-jest": "^3.6.3",

View File

@ -26,6 +26,10 @@
'sortTeamMemberByDropdownButton',
'notificationArea',
'successfulRegistrationPopup',
'deletePaymentMethodButton',
'deletePaymentMethodDialog',
'makeDefaultPaymentMethodButton',
'makeDefaultPaymentDialog'
]
};
},
@ -41,6 +45,7 @@
}
target = target.parentNode;
}
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
}
}

View File

@ -0,0 +1,139 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import apollo from '@/utils/apolloManager';
import gql from 'graphql-tag';
export async function addProjectPaymentMethodRequest(projectID: string, cardToken: string, makeDefault: boolean): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
addPaymentMethod(
projectID: "${projectID}",
cardToken: "${cardToken}",
isDefault: ${makeDefault}
)
}
`),
fetchPolicy: 'no-cache',
errorPolicy: 'all'
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;
}
export async function setDefaultPaymentMethodRequest(projectID: string, paymentID: string): Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
setDefaultPaymentMethod(
projectID: "${projectID}",
id: "${paymentID}"
)
}
`),
fetchPolicy: 'no-cache',
errorPolicy: 'all'
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;
}
export async function deletePaymentMethodRequest(paymentID: string):Promise<RequestResponse<null>> {
let result: RequestResponse<null> = {
errorMessage: '',
isSuccess: false,
data: null
};
let response: any = await apollo.mutate(
{
mutation: gql(`
mutation {
deletePaymentMethod(
id: "${paymentID}"
)
}
`),
fetchPolicy: 'no-cache',
errorPolicy: 'all'
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
}
return result;
}
// fetchProjectInvoices retrieves project invoices
export async function fetchProjectPaymentMethods(projectID: string): Promise<RequestResponse<PaymentMethod[]>> {
let result: RequestResponse<PaymentMethod[]> = {
errorMessage: '',
isSuccess: false,
data: [] as PaymentMethod[]
};
let response: any = await apollo.query(
{
query: gql(`
query {
project(id: "${projectID}") {
paymentMethods {
id,
expYear,
expMonth,
brand,
lastFour,
holderName,
addedAt,
isDefault
}
}
}`
),
fetchPolicy: 'no-cache',
errorPolicy: 'all'
}
);
if (response.errors) {
result.errorMessage = response.errors[0].message;
} else {
result.isSuccess = true;
result.data = response.data.project.paymentMethods;
}
return result;
}

View File

@ -40,8 +40,8 @@
display: block;
position: relative;
padding-left: 20px;
height: 25px;
width: 25px;
height: 23px;
width: 23px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;

View File

@ -19,16 +19,17 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import {
APP_STATE_ACTIONS,
PROJETS_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
API_KEYS_ACTIONS,
PROJECT_USAGE_ACTIONS,
BUCKET_USAGE_ACTIONS
} from '@/utils/constants/actionNames';
import { Component, Vue } from 'vue-property-decorator';
import {
APP_STATE_ACTIONS,
PROJETS_ACTIONS,
NOTIFICATION_ACTIONS,
PM_ACTIONS,
API_KEYS_ACTIONS,
PROJECT_USAGE_ACTIONS,
BUCKET_USAGE_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS
} from '@/utils/constants/actionNames';
@Component({
computed: {
@ -46,6 +47,7 @@
const keysResponse = await this.$store.dispatch(API_KEYS_ACTIONS.FETCH);
const usageResponse = await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP);
const bucketsResponse = await this.$store.dispatch(BUCKET_USAGE_ACTIONS.FETCH, 1);
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
if (!pmResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch project members');
@ -62,6 +64,10 @@
if (!bucketsResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + bucketsResponse.errorMessage);
}
if (!paymentMethodsResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch payment methods: ' + paymentMethodsResponse.errorMessage);
}
}
},
})

View File

@ -1,160 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="add-payment-popup-overflow" v-on:keyup.enter="onDoneClick" v-on:keyup.esc="onCloseClick">
<div class="add-payment-popup-container">
<h1 class="add-payment-popup-container__title">Add Payment Method</h1>
<div class="add-payment-popup-container__chosen-card-container">
<Card
isChosen
lastDigits="0000"
fullName="Shawn Wilkinson"
expireLabel="Storj Labs"
expireDate="12/2020" />
<Button
label="Default"
width="91px"
height="36px"
isDisabled />
</div>
<div class="add-payment-popup-container__border"></div>
<div class="add-payment-popup-container__expanded-area"></div>
<div class="add-payment-popup-container__footer">
<div class="add-payment-popup-container__footer__new-card-button" @click="onNewCardClick">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 7.26316V3.87134C20 3.40351 19.7938 2.93567 19.4845 2.5848C19.1753 2.23392 18.7629 2 18.3505 2H1.64948C1.23711 2 0.824742 2.23392 0.515464 2.5848C0.206186 2.93567 0 3.40351 0 3.87134V7.26316H20Z" fill="#2683FF"/>
<path d="M0 9.36816V16.1852C0 16.5862 0.206186 16.9872 0.515464 17.288C0.824742 17.5887 1.23711 17.7892 1.64948 17.7892H18.3505C18.7629 17.7892 19.1753 17.5887 19.4845 17.288C19.7938 16.9872 20 16.5862 20 16.1852V9.36816H0ZM5.36083 15.1827H2.68041V13.8794H5.36083V15.1827ZM10.7217 15.1827H6.70103V13.8794H10.7217V15.1827Z" fill="#2683FF"/>
</svg>
<p class="add-payment-popup-container__footer__new-card-button__label">+ New Card</p>
</div>
<Button
label="Done"
width="205px"
height="48px" />
</div>
<div class="cross" @click="onCloseClick">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L15.7071 1.70711ZM0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L0.292893 14.2929ZM1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417 -0.0976311 1.31658 0.292893 1.70711L1.70711 0.292893ZM14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L14.2929 15.7071ZM14.2929 0.292893L0.292893 14.2929L1.70711 15.7071L15.7071 1.70711L14.2929 0.292893ZM0.292893 1.70711L14.2929 15.7071L15.7071 14.2929L1.70711 0.292893L0.292893 1.70711Z" fill="#384B65"/>
</svg>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import Card from '@/components/project/CardChoiceItem.vue'
@Component({
data: function () {
return {};
},
methods: {
onDoneClick: function (): void {
},
onCloseClick: function (): void {
},
onNewCardClick: function (): void {
}
},
components: {
Button,
Card,
}
})
export default class AddPaymentMethodPopup extends Vue {}
</script>
<style scoped lang="scss">
.add-payment-popup-overflow {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(134, 134, 148, 0.4);
z-index: 1121;
display: flex;
justify-content: center;
align-items: center;
}
.add-payment-popup-container {
position: relative;
width: 810px;
height: 416px;
background-color: white;
border-radius: 6px;
padding: 90px 102px 57px 88px;
&__title {
font-family: 'font_bold';
font-size: 32px;
line-height: 39px;
color: #384B65;
margin: 0;
}
&__chosen-card-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-top: 60px;
}
&__border {
margin-top: 20px;
width: 100%;
height: 1px;
background-color: rgba(169, 181, 193, 0.5);
}
&__expanded-area {
width: 100%;
height: 200px;
overflow-y: auto;
}
&__footer {
width: 100%;
display: flex;
margin-top: 10px;
align-items: flex-start;
justify-content: space-between;
&__new-card-button {
height: 48px;
display: flex;
align-items: center;
cursor: pointer;
&__label {
margin-left: 20px;
font-family: 'font_bold';
font-size: 16px;
line-height: 23px;
color: #354049;
}
}
}
}
.cross {
position: absolute;
top: 50px;
right: 50px;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>

View File

@ -4,53 +4,30 @@
<template>
<div class="payment-methods-container">
<p class="payment-methods-container__title">Credit or Debit Cards</p>
<div class="payment-methods-container__card-container">
<div class="payment-methods-container__card-container__info-area">
<img class="payment-methods-container__card-container__info-area__card-logo" src="../../../static/images/Logo.svg">
<div class="payment-methods-container__card-container__info-area__info-container">
<h1>xxxx 0000</h1>
<h2>Shawn Wilkinson</h2>
</div>
<div class="payment-methods-container__card-container__info-area__expire-container">
<h2>Expires</h2>
<h1>12/2020</h1>
</div>
<h3 class="payment-methods-container__card-container__info-area__added-text">Added on 29 May 2019</h3>
</div>
<div class="payment-methods-container__card-container__default-button" v-if="true">
<p class="payment-methods-container__card-container__default-button__label">Default</p>
</div>
<div class="payment-methods-container__card-container__button-area" v-if="false">
<div class="payment-methods-container__card-container__button-area__make-button">
<p class="payment-methods-container__card-container__button-area__make-button__label">Make Default</p>
</div>
<svg class="payment-methods-container__card-container__button-area__delete-button"
width="34"
height="34"
viewBox="0 0 34 34"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect width="34" height="34" rx="17" fill="#EB5757"/>
<path d="M19.7834 11.9727V11.409C19.7834 10.6576 19.1215 10 18.2706 10H16.0014C15.1504 10 14.4886 10.6576 14.4886 11.409V11.9727H10.7065V13.1938H12.0302V22.3057C12.0302 23.5269 12.9758 24.4662 14.0158 24.4662H20.1616C21.2962 24.4662 22.1471 23.5269 22.1471 22.3057V13.1938H23.4709V11.9727H19.7834ZM16.6632 22.3057H15.3395V14.2271H16.6632V22.3057ZM18.9324 22.3057H17.6087V14.2271H18.9324V22.3057Z" fill="white"/>
</svg>
<div class="scroll-container">
<NewPaymentMethodPopup/>
<div v-for="method in paymentMethods" class="payment-methods-container__card-container">
<CardComponent :paymentMethod="method"/>
</div>
</div>
<Button
class="payment-methods-container__add-button"
label="Add Card"
width="140px"
height="48px"
isDisabled />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import CardComponent from '@/components/project/paymentMethods/CardComponent.vue';
import NewPaymentMethodPopup from '@/components/project/paymentMethods/NewPaymentMethodComponent.vue';
@Component({
methods: {},
computed: {
paymentMethods: function (): PaymentMethod[] {
return this.$store.state.projectPaymentsMethodsModule.paymentMethods;
}
},
components: {
Button,
NewPaymentMethodPopup,
CardComponent
}
})
@ -59,7 +36,8 @@
<style scoped lang="scss">
.payment-methods-container {
position: relative;
&__title {
font-family: 'font_bold';
font-size: 24px;
@ -68,123 +46,16 @@
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
&__card-container {
width: calc(100% - 80px);
margin-top: 37px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 50px 40px 50px 40px;
background-color: white;
border-radius: 6px;
&__info-area {
width: 75%;
display: flex;
align-items: center;
justify-content: space-between;
&__card-logo {
height: 50px;
width: 50px;
}
&__info-container {
h1 {
font-family: 'font_bold';
font-size: 16px;
line-height: 21px;
color: #61666B;
}
h2 {
font-family: 'font_regular';
font-size: 16px;
line-height: 21px;
color: #61666B;
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
}
&__expire-container {
h1 {
font-family: 'font_bold';
font-size: 16px;
line-height: 21px;
color: #61666B;
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
h2 {
font-family: 'font_regular';
font-size: 16px;
line-height: 21px;
color: #61666B;
}
}
&__added-text {
font-family: 'font_regular';
font-size: 16px;
line-height: 21px;
color: #61666B;
}
}
&__default-button {
width: 100px;
height: 34px;
border-radius: 6px;
background-color: #F5F6FA;
display: flex;
justify-content: center;
align-items: center;
&__label {
font-family: 'font_medium';
font-size: 16px;
line-height: 23px;
color: #AFB7C1;
}
}
&__button-area {
width: 20%;
display: flex;
justify-content: space-between;
align-items: center;
&__make-button {
width: 134px;
height: 34px;
border-radius: 6px;
background-color: #DFEDFF;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
&__label {
font-family: 'font_medium';
font-size: 16px;
line-height: 23px;
color: #2683FF;
}
}
svg {
cursor: pointer;
}
}
}
&__add-button {
margin-top: 35px;
}
.scroll-container {
height: 720px;
overflow-y: scroll;
overflow-x: hidden;
}
}
::-webkit-scrollbar {
width: 0;
}
</style>

View File

@ -0,0 +1,225 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payment-methods-container__card-container">
<div class="payment-methods-container__card-container__info-area">
<img class="payment-methods-container__card-container__info-area__card-logo" src="@/../static/images/Logo.svg">
<div class="payment-methods-container__card-container__info-area__info-container">
<h1>xxxx {{paymentMethod.lastFour}}</h1>
<h2>{{paymentMethod.holderName}}</h2>
</div>
<div class="payment-methods-container__card-container__info-area__expire-container">
<h2>Expires</h2>
<h1>{{paymentMethod.expMonth}}/{{paymentMethod.expYear}}</h1>
</div>
<h3 class="payment-methods-container__card-container__info-area__added-text">Added on {{formatDate(paymentMethod.addedAt)}}</h3>
</div>
<div class="payment-methods-container__card-container__default-button" v-if="paymentMethod.isDefault">
<p class="payment-methods-container__card-container__default-button__label">Default</p>
</div>
<div class="payment-methods-container__card-container__button-area" v-if="!paymentMethod.isDefault">
<div class="make-default-container">
<div class="payment-methods-container__card-container__button-area__make-button" v-on:click="onMakeDefaultClick(paymentMethod.id)" id="makeDefaultPaymentMethodButton">
<p class="payment-methods-container__card-container__button-area__make-button__label" >Make Default</p>
</div>
<MakeDefaultPaymentMethodDialog :paymentMethodID="paymentMethod.id" v-if="isSetDefaultPaymentMethodPopupShown"/>
</div>
<div v-on:click="onDeletePaymentMethodClick" id="deletePaymentMethodButton">
<svg class="payment-methods-container__card-container__button-area__delete-button"
width="34"
height="34"
viewBox="0 0 34 34"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect width="34" height="34" rx="17" fill="#EB5757"/>
<path d="M19.7834 11.9727V11.409C19.7834 10.6576 19.1215 10 18.2706 10H16.0014C15.1504 10 14.4886 10.6576 14.4886 11.409V11.9727H10.7065V13.1938H12.0302V22.3057C12.0302 23.5269 12.9758 24.4662 14.0158 24.4662H20.1616C21.2962 24.4662 22.1471 23.5269 22.1471 22.3057V13.1938H23.4709V11.9727H19.7834ZM16.6632 22.3057H15.3395V14.2271H16.6632V22.3057ZM18.9324 22.3057H17.6087V14.2271H18.9324V22.3057Z" fill="white"/>
</svg>
</div>
<DeletePaymentMethodDialog :paymentMethodID="paymentMethod.id" v-if="isDeletePaymentMethodPopupShown"/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import { APP_STATE_ACTIONS, } from '@/utils/constants/actionNames';
import DeletePaymentMethodDialog from '@/components/project/paymentMethods/DeletePaymentMethodDialog.vue';
import MakeDefaultPaymentMethodDialog from '@/components/project/paymentMethods/MakeDefaultPaymentMethodDialog.vue';
@Component({
props: {
paymentMethod: {
type: Object,
default: {}
},
},
methods: {
formatDate: function (d: string): string {
return new Date(d).toLocaleDateString('en-US', {timeZone: 'UTC'});
},
onMakeDefaultClick: async function () {
if ((this as any).getSetDefaultPaymentMethodID == this.$props.paymentMethod.id) {
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
return;
}
this.$store.dispatch(APP_STATE_ACTIONS.SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP, this.$props.paymentMethod.id);
},
onDeletePaymentMethodClick: async function() {
if ((this as any).getDeletePaymentMethodID == this.$props.paymentMethod.id) {
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
return;
}
this.$store.dispatch(APP_STATE_ACTIONS.SHOW_DELETE_PAYMENT_METHOD_POPUP, this.$props.paymentMethod.id);
}
},
computed: {
getDeletePaymentMethodID: function(): string {
return this.$store.state.appStateModule.appState.deletePaymentMethodID;
},
getSetDefaultPaymentMethodID: function(): string {
return this.$store.state.appStateModule.appState.setDefaultPaymentMethodID;
},
isDeletePaymentMethodPopupShown: function (): boolean {
return this.$store.state.appStateModule.appState.deletePaymentMethodID == this.$props.paymentMethod.id;
},
isSetDefaultPaymentMethodPopupShown: function(): boolean {
return this.$store.state.appStateModule.appState.setDefaultPaymentMethodID == this.$props.paymentMethod.id;
},
},
components: {
MakeDefaultPaymentMethodDialog,
Button,
DeletePaymentMethodDialog,
}
})
export default class CardComponent extends Vue {}
</script>
<style scoped lang="scss">
.payment-methods-container__card-container {
width: calc(100% - 80px);
margin-top: 24px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 25px 40px 25px 40px;
background-color: white;
border-radius: 6px;
&__info-area {
width: 75%;
display: flex;
align-items: center;
justify-content: space-between;
&__card-logo {
height: 70px;
width: 85px;
}
&__info-container {
h1 {
font-family: 'font_bold';
font-size: 16px;
line-height: 21px;
color: #61666B;
}
h2 {
font-family: 'font_regular';
font-size: 16px;
line-height: 21px;
color: #61666B;
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
}
&__expire-container {
h1 {
font-family: 'font_bold';
font-size: 16px;
line-height: 21px;
color: #61666B;
margin-block-start: 0.5em;
margin-block-end: 0.5em;
}
h2 {
font-family: 'font_regular';
font-size: 16px;
line-height: 21px;
color: #61666B;
}
}
&__added-text {
font-family: 'font_regular';
font-size: 16px;
line-height: 21px;
color: #61666B;
}
}
&__default-button {
width: 100px;
height: 34px;
border-radius: 6px;
background-color: #F5F6FA;
display: flex;
justify-content: center;
align-items: center;
&__label {
font-family: 'font_medium';
font-size: 16px;
line-height: 23px;
color: #AFB7C1;
}
}
&__button-area {
width: 20%;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
&__make-button {
width: 134px;
height: 34px;
border-radius: 6px;
background-color: #DFEDFF;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
&__label {
font-family: 'font_medium';
font-size: 16px;
line-height: 23px;
color: #2683FF;
}
}
svg {
cursor: pointer;
}
}
}
.make-default-container {
position: relative;
}
</style>

View File

@ -0,0 +1,127 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="dialog-container" id="deletePaymentMethodDialog">
<div class="delete-container">
<h1>Confirm Delete Card</h1>
<h2>Are you sure you want to remove your card?</h2>
<div class="button-container">
<Button height="48px" width="128px" label="Cancel" isWhite :on-press="onCancelClick"/>
<Button class="delete-button" height="48px" width="128px" label="Delete" :on-press="onDeleteClick"/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import {
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS
} from '@/utils/constants/actionNames';
@Component({
props: {
paymentMethodID: {
type: String,
default: ''
},
},
methods: {
onCancelClick: function () {
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
},
onDeleteClick: async function () {
const response = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.DELETE, this.$props.paymentMethodID);
if (!response.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, response.errorMessage);
}
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
if (!paymentMethodsResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch payment methods: ' + paymentMethodsResponse.errorMessage);
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Successfully delete payment method');
}
},
components: {
Button,
}
})
export default class DeletePaymentMethodDialog extends Vue {
}
</script>
<style scoped lang="scss">
.dialog-container{
background-image: url('../../../../static/images/container.svg');
background-size: cover;
background-repeat: no-repeat;
z-index: 1;
position: absolute;
top: 40px;
right: -38px;
height: 223px;
width: 351px;
}
h1 {
font-family: 'font_bold';
font-size: 16px;
line-height: 21px;
color: #384B65;
}
h2 {
font-family: 'font_regular';
font-size: 12px;
color: #384B65;
}
.delete-container {
display: flex;
flex-direction: column;
padding: 25px 32px 33px 32px;
box-shadow: 0px 4px 20px rgba(204, 208, 214, 0.25);
margin-top: 12px;
}
.button-container {
display: flex;
flex-direction: row;
margin-top: 25px;
}
.delete-button {
margin-left: 11px;
/*&:hover {*/
/*&.container {*/
/*box-shadow: none;*/
/*background-color: #d24949;*/
/*}*/
/*}*/
}
.delete-button.container {
background-color: #EB5757;
&:hover {
box-shadow: none;
background-color: #d24949;
}
}
.delete-button.label {
color: white;
}
</style>

View File

@ -0,0 +1,110 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="dialog-container" id="makeDefaultPaymentDialog">
<div class="delete-container">
<h1>Update Default Card</h1>
<h2>We will automatically charge your default card at the close of the current billing period</h2>
<div class="button-container">
<Button height="48px" width="128px" label="Cancel" isWhite :on-press="onCancelClick"/>
<Button class="delete-button" height="48px" width="128px" label="Update" :on-press="onUpdateClick"/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import {
APP_STATE_ACTIONS,
NOTIFICATION_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS
} from '@/utils/constants/actionNames';
@Component({
props: {
paymentMethodID: {
type: String,
default: ''
},
},
methods: {
onCancelClick: function () {
this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
},
onUpdateClick: async function () {
const result = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.SET_DEFAULT, this.$props.paymentMethodID);
if (!result.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, result.errorMessage);
return;
}
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
if (!paymentMethodsResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch payment methods: ' + paymentMethodsResponse.errorMessage);
}
this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Successfully set default payment method');
}
},
components: {
Button,
}
})
export default class MakeDefaultPaymentMethodDialog extends Vue {
}
</script>
<style scoped lang="scss">
.dialog-container {
background-image: url('../../../../static/images/ContainerCentered.svg');
background-size: cover;
background-repeat: no-repeat;
z-index: 1;
position: absolute;
left: 50%;
transform: translate(-50%);
top: 40px;
height: 240px;
width: 351px;
}
h1 {
font-family: 'font_bold';
font-size: 16px;
line-height: 21px;
color: #384B65;
}
h2 {
font-family: 'font_regular';
font-size: 12px;
color: #384B65;
}
.delete-container {
display: flex;
flex-direction: column;
padding: 25px 32px 33px 32px;
box-shadow: 0px 4px 20px rgba(204, 208, 214, 0.25);
margin-top: 12px;
}
.button-container {
display: flex;
flex-direction: row;
margin-top: 25px;
}
.delete-button {
margin-left: 11px;
}
</style>

View File

@ -0,0 +1,228 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="add-payment-popup-overflow" v-on:keyup.enter="onDoneClick" v-on:keyup.esc="onCloseClick">
<div class="add-payment-popup-container">
<div class="card-form-input">
<img src="../../../../static/images/Card.svg"/>
<form id="payment-form">
<div class="form-row">
<div id="card-element">
<!-- A Stripe Element will be inserted here. -->
</div>
<!-- Used to display form errors. -->
<div id="card-errors" role="alert"></div>
</div>
<div class="checkbox-container" v-if="projectPaymentMethodsCount > 0">
<Checkbox @setData="toggleMakeDefault"/>
<h2>Make Default</h2>
</div>
<Button
label="Save"
width="135px"
height="48px"
:on-press="onSaveClick"/>
</form>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/common/Button.vue';
import {
NOTIFICATION_ACTIONS,
PROJECT_PAYMENT_METHODS_ACTIONS
} from '@/utils/constants/actionNames';
import Checkbox from '@/components/common/Checkbox.vue';
@Component(
{
data: function () {
return {
makeDefault: false,
};
},
mounted: function () {
if (!window['Stripe']) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Stripe library not loaded');
return;
}
const stripe = window['Stripe'](process.env.VUE_APP_STRIPE_PUBLIC_KEY);
if (!stripe) {
console.error('Unable to initialize stripe');
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to initialize stripe');
return;
}
const elements = stripe.elements();
if (!elements) {
console.error('Unable to instantiate elements');
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to instantiate elements');
return;
}
const card = elements.create('card');
if (!card) {
console.error('Unable to create card');
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to create card');
return;
}
card.mount('#card-element');
card.addEventListener('change', function (event) {
const displayError = document.getElementById('card-errors') as HTMLElement;
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
const form = document.getElementById('payment-form') as HTMLElement;
let self = this;
form.addEventListener('submit', function (event) {
event.preventDefault();
stripe.createToken(card).then(async function (result: any) {
if (result.token.card.funding == 'prepaid') {
self.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Prepaid cards not supported');
return;
}
const input = {
token: result.token.id,
makeDefault: self.$data.makeDefault} as AddPaymentMethodInput;
const response = await self.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.ADD, input);
if (!response.isSuccess) {
self.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, response.error);
}
await self.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
self.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Card successfully added');
card.clear();
});
});
},
computed: {
projectPaymentMethodsCount: function () {
if (this.$store.state.projectPaymentsMethodsModule.paymentMethods) {
return this.$store.state.projectPaymentsMethodsModule.paymentMethods.length;
} else {
return 0;
}
}
},
methods: {
toggleMakeDefault: function (value: boolean) {
this.$data.makeDefault = value;
},
onSaveClick: function () {
const form = document.getElementById('payment-form') as HTMLElement;
const saveEvent = new CustomEvent('submit', {'bubbles': true});
form.dispatchEvent(saveEvent);
}
},
components: {
Button,
Checkbox,
}
}
)
export default class AddNewPaymentMethodPopup extends Vue {
}
</script>
<style scoped lang="scss">
.StripeElement {
box-sizing: border-box;
width: 484px;
padding: 13px 12px;
border: 1px solid transparent;
border-radius: 4px;
background-color: white;
box-shadow: 0 1px 3px 0 #e6ebf1;
-webkit-transition: box-shadow 150ms ease;
transition: box-shadow 150ms ease;
}
.StripeElement--focus {
box-shadow: 0 1px 3px 0 #cfd7df;
}
.StripeElement--invalid {
border-color: #fa755a;
}
.StripeElement--webkit-autofill {
background-color: #fefde5 !important;
}
.card-form-input {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
form {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
}
img {
margin-top: 7px;
margin-right: 25px;
margin-left: -20px;
}
}
.checkbox-container {
display: flex;
justify-content: center;
align-items: center;
h2 {
font-family: 'font_regular';
font-size: 12px;
line-height: 18px;
color: #384B65;
}
}
.add-payment-popup-overflow {
margin-top: 37px;
}
.add-payment-popup-container {
width: calc(100% - 80px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 25px 40px 25px 40px;
background-color: white;
border-radius: 6px;
}
</style>

View File

@ -12,6 +12,7 @@ import { notificationsModule } from '@/store/modules/notifications';
import { appStateModule } from '@/store/modules/appState';
import { apiKeysModule } from '@/store/modules/apiKeys';
import { bucketUsageModule, usageModule, creditUsageModule } from '@/store/modules/usage';
import { projectPaymentsMethodsModule } from '@/store/modules/paymentMethods';
Vue.use(Vuex);
@ -26,6 +27,7 @@ const store = new Vuex.Store({
apiKeysModule,
usageModule,
bucketUsageModule,
projectPaymentsMethodsModule,
creditUsageModule
}
});

View File

@ -22,6 +22,8 @@ export const appStateModule = {
isSuccessfulProjectCreationPopupShown: false,
isEditProfilePopupShown: false,
isChangePasswordPopupShown: false,
deletePaymentMethodID: '',
setDefaultPaymentMethodID: '',
},
},
mutations: {
@ -75,12 +77,20 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.TOGGLE_EDIT_PROFILE_POPUP](state: any): void {
state.appState.isEditProfilePopupShown = !state.appState.isEditProfilePopupShown;
},
[APP_STATE_MUTATIONS.SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP](state: any, id: string): void {
state.appState.setDefaultPaymentMethodID = id;
},
[APP_STATE_MUTATIONS.SHOW_DELETE_PAYMENT_METHOD_POPUP](state: any, id: string): void {
state.appState.deletePaymentMethodID = id;
},
// Mutation that closes each popup/dropdown
[APP_STATE_MUTATIONS.CLOSE_ALL](state: any): void {
state.appState.isProjectsDropdownShown = false;
state.appState.isAccountDropdownShown = false;
state.appState.isSortProjectMembersByPopupShown = false;
state.appState.isSuccessfulRegistrationPopupShown = false;
state.appState.setDefaultPaymentMethodID = '';
state.appState.deletePaymentMethodID = '';
},
[APP_STATE_MUTATIONS.CHANGE_STATE](state: any, newFetchState: AppState): void {
state.appState.fetchState = newFetchState;
@ -164,6 +174,20 @@ export const appStateModule = {
[APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP]: function ({commit}: any): void {
commit(APP_STATE_MUTATIONS.TOGGLE_EDIT_PROFILE_POPUP);
},
[APP_STATE_ACTIONS.SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP]: function ({commit, state}: any, methodID: string): void {
if (!state.appState.setDefaultPaymentMethodID) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP, methodID);
},
[APP_STATE_ACTIONS.SHOW_DELETE_PAYMENT_METHOD_POPUP]: function ({commit, state}: any, methodID: string): void {
if (!state.appState.deletePaymentMethodID) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.SHOW_DELETE_PAYMENT_METHOD_POPUP, methodID);
},
[APP_STATE_ACTIONS.CLOSE_POPUPS]: function ({commit}: any): void {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
},

View File

@ -0,0 +1,56 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { PROJECT_PAYMENT_METHODS_MUTATIONS } from '@/store/mutationConstants';
import { PROJECT_PAYMENT_METHODS_ACTIONS } from '@/utils/constants/actionNames';
import {
addProjectPaymentMethodRequest,
deletePaymentMethodRequest,
fetchProjectPaymentMethods,
setDefaultPaymentMethodRequest
} from '@/api/paymentMethods';
export const projectPaymentsMethodsModule = {
state: {
paymentMethods: [] as PaymentMethod[],
},
mutations: {
[PROJECT_PAYMENT_METHODS_MUTATIONS.FETCH](state: any, invoices: PaymentMethod[]) {
state.paymentMethods = invoices;
},
[PROJECT_PAYMENT_METHODS_MUTATIONS.CLEAR](state: any) {
state.paymentMethods = [] as PaymentMethod[];
}
},
actions: {
[PROJECT_PAYMENT_METHODS_ACTIONS.ADD]: async function ({commit, rootGetters, state}, input: AddPaymentMethodInput): Promise<RequestResponse<null>> {
const projectID = rootGetters.selectedProject.id;
if (state.paymentMethods.length == 0) {
input.makeDefault = true;
}
return await addProjectPaymentMethodRequest(projectID, input.token, input.makeDefault);
},
[PROJECT_PAYMENT_METHODS_ACTIONS.FETCH]: async function ({commit, rootGetters}): Promise<RequestResponse<PaymentMethod[]>> {
const projectId = rootGetters.selectedProject.id;
let result = await fetchProjectPaymentMethods(projectId);
if (result.isSuccess) {
commit(PROJECT_PAYMENT_METHODS_MUTATIONS.FETCH, result.data);
}
return result;
},
[PROJECT_PAYMENT_METHODS_ACTIONS.CLEAR]: function ({commit}) {
commit(PROJECT_PAYMENT_METHODS_MUTATIONS.CLEAR);
},
[PROJECT_PAYMENT_METHODS_ACTIONS.SET_DEFAULT]: async function ({commit, rootGetters}, projectPaymentID: string) {
const projectID = rootGetters.selectedProject.id;
return await setDefaultPaymentMethodRequest(projectID, projectPaymentID);
},
[PROJECT_PAYMENT_METHODS_ACTIONS.DELETE]: async function ({commit}, projectPaymentID: string) {
return await deletePaymentMethodRequest(projectPaymentID);
}
},
};

View File

@ -77,6 +77,13 @@ export const APP_STATE_MUTATIONS = {
TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP: 'TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP',
TOGGLE_EDIT_PROFILE_POPUP: 'TOGGLE_EDIT_PROFILE_POPUP',
TOGGLE_CHANGE_PASSWORD_POPUP: 'TOGGLE_CHANGE_PASSWORD_POPUP',
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'SHOW_DELETE_PAYMENT_METHOD_POPUP',
SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP',
CLOSE_ALL: 'CLOSE_ALL',
CHANGE_STATE: 'CHANGE_STATE',
};
export const PROJECT_PAYMENT_METHODS_MUTATIONS = {
FETCH: 'FETCH',
CLEAR: 'CLEAR',
};

19
web/satellite/src/types/invoices.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
// PaymentMethod holds card information to display
declare type PaymentMethod = {
id: string,
expYear: number,
expMonth: number,
brand: string,
lastFour: string,
holderName: string,
addedAt: Date,
isDefault: boolean,
}
declare type AddPaymentMethodInput = {
token: string,
makeDefault: boolean,
}

View File

@ -14,6 +14,10 @@ export const APP_STATE_ACTIONS = {
TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP: 'toggleSuccessfulProjectCreationPopup',
TOGGLE_EDIT_PROFILE_POPUP: 'toggleEditProfilePopup',
TOGGLE_CHANGE_PASSWORD_POPUP: 'toggleChangePasswordPopup',
SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'showSetDefaultPaymentMethodPopup',
CLOSE_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'closeSetDefaultPaymentMethodPopup',
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'showDeletePaymentMethodPopup',
CLOSE_DELETE_PAYMENT_METHOD_POPUP: 'closeDeletePaymentMethodPopup',
CLOSE_POPUPS: 'closePopups',
CHANGE_STATE: 'changeFetchState',
};
@ -80,6 +84,14 @@ export const BUCKET_USAGE_ACTIONS = {
CLEAR: 'clearBucketUsages'
};
export const PROJECT_PAYMENT_METHODS_ACTIONS = {
ADD: 'addProjectPaymentMethod',
FETCH: 'fetchProjectPaymentMethods',
CLEAR: 'clearProjectPaymentMethods',
SET_DEFAULT: 'setDefaultPaymentMethod',
DELETE: 'deletePaymentMethod'
};
export const CREDIT_USAGE_ACTIONS = {
FETCH: 'fetchCreditUsage',
}
};

View File

@ -33,8 +33,8 @@ import {AppState} from "../utils/constants/appStateEnum";
PROJETS_ACTIONS,
USER_ACTIONS,
PROJECT_USAGE_ACTIONS,
BUCKET_USAGE_ACTIONS
} from '@/utils/constants/actionNames';
BUCKET_USAGE_ACTIONS, PROJECT_PAYMENT_METHODS_ACTIONS
} from "@/utils/constants/actionNames";
import ROUTES from '@/utils/constants/routerConstants';
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
import { AppState } from '../utils/constants/appStateEnum';
@ -82,6 +82,11 @@ import {AppState} from "../utils/constants/appStateEnum";
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch buckets: ' + bucketsResponse.errorMessage);
}
const paymentMethodsResponse = await this.$store.dispatch(PROJECT_PAYMENT_METHODS_ACTIONS.FETCH);
if (!paymentMethodsResponse.isSuccess) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, 'Unable to fetch payment methods: ' + paymentMethodsResponse.errorMessage);
}
this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
}, 800);
},

View File

@ -17,8 +17,7 @@ body {
display: flex;
justify-content: flex-start;
flex-direction: column;
align-items: flex-start;
padding: 60px 0px 0px 104px;
align-items: flex-start; padding: 60px 0px 0px 104px;
background-color: #F5F6FA;
.register-input {
@ -173,8 +172,8 @@ body {
display: block;
position: relative;
padding-left: 20px;
height: 25px;
width: 25px;
height: 23px;
width: 23px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;

View File

@ -0,0 +1,35 @@
<svg width="113" height="90" viewBox="0 0 113 90" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<path d="M86.0972 64.0932L26.9457 64.329C23.7672 64.329 21.2089 61.7346 21.2089 58.5898L21.1313 23.0537C21.1313 19.8303 23.6897 17.2359 26.7907 17.2359L85.9422 17C89.1207 17 91.679 19.5944 91.679 22.7392L91.8341 58.3539C91.7565 61.4987 89.2757 64.0932 86.0972 64.0932Z" fill="#2683FF"/>
</g>
<path d="M83.9628 62.1047L26.6351 62.3301C23.5546 62.3301 21.0751 59.8507 21.0751 56.8453L21 22.8845C21 19.804 23.4794 17.3245 26.4848 17.3245L83.8125 17.0991C86.893 17.0991 89.3724 19.5786 89.3724 22.5839L89.5227 56.6199C89.4476 59.6253 87.0433 62.1047 83.9628 62.1047Z" fill="#2683FF"/>
<path d="M71.9968 51.5822L71.02 51.5859L71.0303 54.2908L72.007 54.2871L71.9968 51.5822Z" fill="#2683FF"/>
<path d="M73.6496 51.5822L72.6729 51.5859L72.6831 54.2908L73.6599 54.2871L73.6496 51.5822Z" fill="white"/>
<path d="M75.2282 51.5075L74.2515 51.5112L74.2617 54.2161L75.2385 54.2124L75.2282 51.5075Z" fill="white"/>
<path d="M76.8801 51.508L75.9033 51.5117L75.9135 54.2166L76.8902 54.2129L76.8801 51.508Z" fill="white"/>
<path d="M78.4582 51.5076L77.4814 51.5112L77.4916 54.2161L78.4684 54.2124L78.4582 51.5076Z" fill="white"/>
<path d="M80.111 51.5076L79.1343 51.5112L79.1445 54.2161L80.1212 54.2124L80.111 51.5076Z" fill="white"/>
<path d="M83.3412 40.5925L29.4697 40.7959L29.4814 43.8764L83.3528 43.673L83.3412 40.5925Z" fill="white"/>
<path d="M37.6648 34.8313H30.2264C29.8508 34.8313 29.4751 34.4557 29.4751 34.0048V28.7454C29.4751 28.2946 29.7756 27.9189 30.1513 27.9189H37.5896C37.9653 27.9189 38.341 28.2946 38.341 28.7454V34.0048C38.4161 34.4557 38.1156 34.8313 37.6648 34.8313Z" fill="white"/>
<path d="M36.371 27.8969L36.2207 27.8975L36.2476 34.8313L36.3979 34.8308L36.371 27.8969Z" fill="#2683FF"/>
<path d="M31.5668 27.9199L31.4165 27.9204L31.4419 34.8328L31.5921 34.8322L31.5668 27.9199Z" fill="#2683FF"/>
<path d="M38.4163 30.1703L36.3125 30.1782L36.3131 30.3285L38.4168 30.3205L38.4163 30.1703Z" fill="#2683FF"/>
<path d="M38.4163 31.7489L36.3125 31.7568L36.3131 31.9071L38.4168 31.8992L38.4163 31.7489Z" fill="#2683FF"/>
<path d="M38.491 33.1005L36.3872 33.1084L36.3878 33.2587L38.4915 33.2507L38.491 33.1005Z" fill="#2683FF"/>
<path d="M31.5037 29.9433L29.2832 29.9517L29.2838 30.1019L31.5043 30.0936L31.5037 29.9433Z" fill="#2683FF"/>
<path d="M31.5037 31.9794L29.2832 31.9878L29.2838 32.1381L31.5043 32.1297L31.5037 31.9794Z" fill="#2683FF"/>
<path d="M31.5037 33.4565L29.2832 33.4648L29.2838 33.6151L31.5043 33.6067L31.5037 33.4565Z" fill="#2683FF"/>
<path d="M35.0358 29.1917L32.7817 29.2002L32.7823 29.3505L35.0363 29.342L35.0358 29.1917Z" fill="#2683FF"/>
<path d="M35.0353 33.5506L32.7812 33.5591L32.7818 33.7094L35.0359 33.7008L35.0353 33.5506Z" fill="#2683FF"/>
<defs>
<filter id="filter0_d" x="0.181719" y="0.240297" width="112.602" height="89.2283" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4.18993"/>
<feGaussianBlur stdDeviation="10.4748"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.14902 0 0 0 0 0.513726 0 0 0 0 1 0 0 0 0.4 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,7 @@
<svg width="331" height="211" viewBox="0 0 331 211" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M331 205C331 208.314 328.314 211 325 211L6.00001 211C2.6863 211 1.77983e-05 208.313 1.75321e-05 205L2.32832e-06 15.7512C2.0621e-06 12.4375 2.6863 9.75124 6.00001 9.75124L158.565 9.75144C159.269 9.75144 159.921 9.38114 160.282 8.77648L164.389 1.89675C165.165 0.596804 167.047 0.596804 167.823 1.89675L171.93 8.77652C172.291 9.38117 172.943 9.75147 173.647 9.75147L325 9.75167C328.314 9.75168 331 12.438 331 15.7517L331 205Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M331 205C331 208.314 328.314 211 325 211L6.00001 211C2.6863 211 1.77983e-05 208.313 1.75321e-05 205L2.32832e-06 15.7512C2.0621e-06 12.4375 2.6863 9.75124 6.00001 9.75124L158.565 9.75144C159.269 9.75144 159.921 9.38114 160.282 8.77648L164.389 1.89675C165.165 0.596804 167.047 0.596804 167.823 1.89675L171.93 8.77652C172.291 9.38117 172.943 9.75147 173.647 9.75147L325 9.75167C328.314 9.75168 331 12.438 331 15.7517L331 205Z" fill="white"/>
<path d="M325 211L325 212L325 211ZM331 205L332 205L331 205ZM6.00001 211L6.00001 210L6.00001 211ZM1.75321e-05 205L-0.999982 205L1.75321e-05 205ZM2.32832e-06 15.7512L-0.999998 15.7512L2.32832e-06 15.7512ZM6.00001 9.75124L6.00001 8.75124H6.00001L6.00001 9.75124ZM325 9.75167L325 8.75167L325 9.75167ZM331 15.7517L330 15.7517L331 15.7517ZM171.93 8.77652L172.788 8.26399L171.93 8.77652ZM173.647 9.75147L173.647 10.7515L173.647 9.75147ZM164.389 1.89675L165.247 2.40927L164.389 1.89675ZM167.823 1.89675L166.965 2.40926L167.823 1.89675ZM158.565 9.75144L158.565 8.75144L158.565 9.75144ZM160.282 8.77648L159.424 8.26396L160.282 8.77648ZM325 212C328.866 212 332 208.866 332 205L330 205C330 207.761 327.761 210 325 210L325 212ZM6.00001 212L325 212L325 210L6.00001 210L6.00001 212ZM-0.999982 205C-0.999982 208.866 2.13402 212 6.00001 212L6.00001 210C3.23859 210 1.00002 207.761 1.00002 205L-0.999982 205ZM-0.999998 15.7512L-0.999982 205L1.00002 205L1 15.7512L-0.999998 15.7512ZM6.00001 8.75124C2.13401 8.75124 -0.999998 11.8852 -0.999998 15.7512L1 15.7512C1 12.9898 3.23858 10.7512 6.00001 10.7512L6.00001 8.75124ZM158.565 8.75144L6.00001 8.75124L6.00001 10.7512L158.565 10.7514L158.565 8.75144ZM161.141 9.289L165.247 2.40927L163.53 1.38422L159.424 8.26396L161.141 9.289ZM166.965 2.40926L171.071 9.28903L172.788 8.26399L168.682 1.38422L166.965 2.40926ZM325 8.75167L173.647 8.75147L173.647 10.7515L325 10.7517L325 8.75167ZM332 15.7517C332 11.8857 328.866 8.75168 325 8.75167L325 10.7517C327.761 10.7517 330 12.9902 330 15.7517L332 15.7517ZM332 205L332 15.7517L330 15.7517L330 205L332 205ZM171.071 9.28903C171.612 10.196 172.591 10.7515 173.647 10.7515L173.647 8.75147C173.295 8.75147 172.969 8.56632 172.788 8.26399L171.071 9.28903ZM165.247 2.40927C165.635 1.75929 166.577 1.75929 166.965 2.40926L168.682 1.38422C167.518 -0.565687 164.694 -0.565687 163.53 1.38422L165.247 2.40927ZM158.565 10.7514C159.621 10.7514 160.6 10.196 161.141 9.289L159.424 8.26396C159.243 8.56629 158.917 8.75144 158.565 8.75144L158.565 10.7514Z" fill="#C1C1C1" fill-opacity="0.3" mask="url(#path-1-inside-1)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,7 @@
<svg width="331" height="211" viewBox="0 0 331 211" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M331 205C331 208.314 328.314 211 325 211L6.00001 211C2.6863 211 1.77983e-05 208.313 1.75321e-05 205L2.32832e-06 15.7512C2.0621e-06 12.4375 2.6863 9.75124 6.00001 9.75124L274.722 9.75161C275.426 9.75161 276.078 9.38131 276.439 8.77665L280.545 1.89675C281.321 0.596805 283.204 0.59682 283.98 1.89675L288.087 8.77668C288.447 9.38132 289.1 9.75162 289.804 9.75162L325 9.75167C328.314 9.75168 331 12.438 331 15.7517L331 205Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M331 205C331 208.314 328.314 211 325 211L6.00001 211C2.6863 211 1.77983e-05 208.313 1.75321e-05 205L2.32832e-06 15.7512C2.0621e-06 12.4375 2.6863 9.75124 6.00001 9.75124L274.722 9.75161C275.426 9.75161 276.078 9.38131 276.439 8.77665L280.545 1.89675C281.321 0.596805 283.204 0.59682 283.98 1.89675L288.087 8.77668C288.447 9.38132 289.1 9.75162 289.804 9.75162L325 9.75167C328.314 9.75168 331 12.438 331 15.7517L331 205Z" fill="white"/>
<path d="M325 211L325 212L325 211ZM331 205L332 205L331 205ZM6.00001 211L6.00001 210L6.00001 211ZM1.75321e-05 205L-0.999982 205L1.75321e-05 205ZM2.32832e-06 15.7512L-0.999998 15.7512L2.32832e-06 15.7512ZM6.00001 9.75124L6.00001 8.75124L6.00001 8.75124L6.00001 9.75124ZM325 9.75167L325 8.75167L325 9.75167ZM331 15.7517L330 15.7517L331 15.7517ZM288.087 8.77668L288.945 8.26415L288.087 8.77668ZM289.804 9.75162L289.804 10.7516L289.804 9.75162ZM280.545 1.89675L281.404 2.40927L280.545 1.89675ZM283.98 1.89675L283.121 2.40927L283.98 1.89675ZM274.722 9.75161L274.722 10.7516L274.722 9.75161ZM276.439 8.77665L277.298 9.28917L276.439 8.77665ZM325 212C328.866 212 332 208.866 332 205L330 205C330 207.761 327.761 210 325 210L325 212ZM6.00001 212L325 212L325 210L6.00001 210L6.00001 212ZM-0.999982 205C-0.999982 208.866 2.13402 212 6.00001 212L6.00001 210C3.23859 210 1.00002 207.761 1.00002 205L-0.999982 205ZM-0.999998 15.7512L-0.999982 205L1.00002 205L1 15.7512L-0.999998 15.7512ZM6.00001 8.75124C2.13401 8.75124 -0.999998 11.8852 -0.999998 15.7512L1 15.7512C1 12.9898 3.23858 10.7512 6.00001 10.7512L6.00001 8.75124ZM274.722 8.75161L6.00001 8.75124L6.00001 10.7512L274.722 10.7516L274.722 8.75161ZM277.298 9.28917L281.404 2.40927L279.687 1.38422L275.58 8.26413L277.298 9.28917ZM283.121 2.40927L287.228 9.28919L288.945 8.26415L284.839 1.38422L283.121 2.40927ZM325 8.75167L289.804 8.75162L289.804 10.7516L325 10.7517L325 8.75167ZM332 15.7517C332 11.8857 328.866 8.75168 325 8.75167L325 10.7517C327.761 10.7517 330 12.9902 330 15.7517L332 15.7517ZM332 205L332 15.7517L330 15.7517L330 205L332 205ZM287.228 9.28919C287.769 10.1962 288.748 10.7516 289.804 10.7516L289.804 8.75162C289.452 8.75162 289.126 8.56647 288.945 8.26415L287.228 9.28919ZM281.404 2.40927C281.792 1.75931 282.733 1.7593 283.121 2.40927L284.839 1.38422C283.675 -0.56567 280.851 -0.565686 279.687 1.38422L281.404 2.40927ZM274.722 10.7516C275.778 10.7516 276.756 10.1961 277.298 9.28917L275.58 8.26413C275.4 8.56646 275.074 8.75161 274.722 8.75161L274.722 10.7516Z" fill="#C1C1C1" fill-opacity="0.3" mask="url(#path-1-inside-1)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB