Console add stripe service (#2080)
This commit is contained in:
parent
8c0c518621
commit
6809129e6f
1
go.mod
1
go.mod
@ -97,6 +97,7 @@ require (
|
||||
github.com/spf13/viper v1.2.1
|
||||
github.com/streadway/amqp v0.0.0-20180806233856-70e15c650864 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/stripe/stripe-go v60.17.0+incompatible
|
||||
github.com/tidwall/gjson v1.1.3 // indirect
|
||||
github.com/tidwall/match v0.0.0-20171002075945-1731857f09b1 // indirect
|
||||
github.com/vivint/infectious v0.0.0-20190108171102-2455b059135b
|
||||
|
2
go.sum
2
go.sum
@ -361,6 +361,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stripe/stripe-go v60.17.0+incompatible h1:h6fW1VPzFekkc1mVUfdmi7B4opnm153GtiNfb3bvtrQ=
|
||||
github.com/stripe/stripe-go v60.17.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
|
||||
github.com/tidwall/gjson v1.1.3 h1:u4mspaByxY+Qk4U1QYYVzGFI8qxN/3jtEV0ZDb2vRic=
|
||||
github.com/tidwall/gjson v1.1.3/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
|
||||
github.com/tidwall/match v0.0.0-20171002075945-1731857f09b1 h1:pWIN9LOlFRCJFqWIOEbHLvY0WWJddsjH2FQ6N0HKZdU=
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"storj.io/storj/satellite/console/consoleauth"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/payments/localpayments"
|
||||
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
||||
)
|
||||
|
||||
@ -50,6 +51,7 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
log,
|
||||
&consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")},
|
||||
db.Console(),
|
||||
localpayments.NewService(nil),
|
||||
console.TestPasswordCost,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"storj.io/storj/satellite/console/consoleauth"
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/payments/localpayments"
|
||||
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
||||
)
|
||||
|
||||
@ -34,6 +35,7 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
log,
|
||||
&consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")},
|
||||
db.Console(),
|
||||
localpayments.NewService(nil),
|
||||
console.TestPasswordCost,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
@ -45,6 +45,7 @@ type Config struct {
|
||||
Address string `help:"server address of the graphql api gateway and frontend app" default:"127.0.0.1:8081"`
|
||||
StaticDir string `help:"path to static resources" default:""`
|
||||
ExternalAddress string `help:"external endpoint of the satellite if hosted" default:""`
|
||||
StripeKey string `help:"stripe api key" default:""`
|
||||
|
||||
// TODO: remove after Vanguard release
|
||||
AuthToken string `help:"auth token needed for access to registration token creation endpoint" default:""`
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"storj.io/storj/pkg/auth"
|
||||
"storj.io/storj/pkg/macaroon"
|
||||
"storj.io/storj/satellite/console/consoleauth"
|
||||
"storj.io/storj/satellite/payments"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -57,34 +58,33 @@ const (
|
||||
type Service struct {
|
||||
Signer
|
||||
|
||||
store DB
|
||||
log *zap.Logger
|
||||
pm payments.Service
|
||||
store DB
|
||||
|
||||
passwordCost int
|
||||
}
|
||||
|
||||
// NewService returns new instance of Service
|
||||
func NewService(log *zap.Logger, signer Signer, store DB, passwordCost int) (*Service, error) {
|
||||
func NewService(log *zap.Logger, signer Signer, store DB, pm payments.Service, passwordCost int) (*Service, error) {
|
||||
if signer == nil {
|
||||
return nil, errs.New("signer can't be nil")
|
||||
}
|
||||
|
||||
if store == nil {
|
||||
return nil, errs.New("store can't be nil")
|
||||
}
|
||||
|
||||
if log == nil {
|
||||
return nil, errs.New("log can't be nil")
|
||||
}
|
||||
|
||||
if passwordCost == 0 {
|
||||
passwordCost = bcrypt.DefaultCost
|
||||
}
|
||||
|
||||
return &Service{
|
||||
log: log,
|
||||
Signer: signer,
|
||||
store: store,
|
||||
log: log,
|
||||
pm: pm,
|
||||
passwordCost: passwordCost,
|
||||
}, nil
|
||||
}
|
||||
@ -119,19 +119,35 @@ func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret R
|
||||
return nil, errs.New(internalErrMsg)
|
||||
}
|
||||
|
||||
u, err = s.store.Users().Insert(ctx, &User{
|
||||
Email: user.Email,
|
||||
FullName: user.FullName,
|
||||
ShortName: user.ShortName,
|
||||
PasswordHash: hash,
|
||||
})
|
||||
// store data
|
||||
tx, err := s.store.BeginTx(ctx)
|
||||
if err != nil {
|
||||
return nil, errs.New(internalErrMsg)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.store.RegistrationTokens().UpdateOwner(ctx, registrationToken.Secret, u.ID)
|
||||
err = withTx(tx, func(tx DBTx) error {
|
||||
u, err = tx.Users().Insert(ctx,
|
||||
&User{
|
||||
Email: user.Email,
|
||||
FullName: user.FullName,
|
||||
ShortName: user.ShortName,
|
||||
PasswordHash: hash,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errs.New(internalErrMsg)
|
||||
}
|
||||
|
||||
err = tx.RegistrationTokens().UpdateOwner(ctx, registrationToken.Secret, u.ID)
|
||||
if err != nil {
|
||||
return errs.New(internalErrMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.New(internalErrMsg)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
@ -441,39 +457,35 @@ func (s *Service) CreateProject(ctx context.Context, projectInfo ProjectInfo) (p
|
||||
return
|
||||
}
|
||||
|
||||
project := &Project{
|
||||
Description: projectInfo.Description,
|
||||
Name: projectInfo.Name,
|
||||
}
|
||||
|
||||
transaction, err := s.store.BeginTx(ctx)
|
||||
tx, err := s.store.BeginTx(ctx)
|
||||
if err != nil {
|
||||
return nil, errs.New(internalErrMsg)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = withTx(tx, func(tx DBTx) (err error) {
|
||||
p, err = tx.Projects().Insert(ctx,
|
||||
&Project{
|
||||
Description: projectInfo.Description,
|
||||
Name: projectInfo.Name,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
err = errs.Combine(err, transaction.Rollback())
|
||||
return
|
||||
return errs.New(internalErrMsg)
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
_, err = tx.ProjectMembers().Insert(ctx, auth.User.ID, p.ID)
|
||||
if err != nil {
|
||||
err = errs.New(internalErrMsg)
|
||||
return errs.New(internalErrMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
prj, err := transaction.Projects().Insert(ctx, project)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.New(internalErrMsg)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = transaction.ProjectMembers().Insert(ctx, auth.User.ID, prj.ID)
|
||||
if err != nil {
|
||||
return nil, errs.New(internalErrMsg)
|
||||
}
|
||||
|
||||
return prj, nil
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// DeleteProject is a method for deleting project by id
|
||||
@ -985,3 +997,18 @@ func (s *Service) isProjectMember(ctx context.Context, userID uuid.UUID, project
|
||||
|
||||
return isProjectMember{}, ErrNoMembership.New(unauthorizedErrMsg)
|
||||
}
|
||||
|
||||
// withTx is a helper function for executing db operations
|
||||
// in transaction scope
|
||||
func withTx(tx DBTx, cb func(tx DBTx) error) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = errs.Combine(err, tx.Rollback())
|
||||
return
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
}()
|
||||
|
||||
return cb(tx)
|
||||
}
|
||||
|
111
satellite/payments/localpayments/local.go
Normal file
111
satellite/payments/localpayments/local.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package localpayments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"time"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/satellite/payments"
|
||||
)
|
||||
|
||||
// storjCreationDate is a Storj creation date. TODO: correct values
|
||||
var storjCreationDate = time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// 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",
|
||||
ExpMonth: 12,
|
||||
ExpYear: 2022,
|
||||
LastFour: "1488",
|
||||
},
|
||||
CreatedAt: storjCreationDate,
|
||||
}
|
||||
|
||||
// StorjCustomer is a predefined customer
|
||||
// which is linked with every user by default
|
||||
var storjCustomer = payments.Customer{
|
||||
ID: []byte("0"),
|
||||
Name: "Storj",
|
||||
Email: "storj@example.com",
|
||||
CreatedAt: storjCreationDate,
|
||||
}
|
||||
|
||||
// internalPaymentsErr is a wrapper for local payments service errors
|
||||
var internalPaymentsErr = errs.Class("internal payments error")
|
||||
|
||||
// DB is internal payment methods storage
|
||||
type DB interface {
|
||||
// TODO: add method to retrieve invoice information from project invoice stamp
|
||||
}
|
||||
|
||||
// service is internal payments.Service implementation
|
||||
type service struct {
|
||||
db DB
|
||||
}
|
||||
|
||||
// NewService create new instance of local payments service
|
||||
func NewService(db DB) payments.Service {
|
||||
return &service{db: db}
|
||||
}
|
||||
|
||||
// CreateCustomer creates new payments.Customer with random id to satisfy unique db constraint
|
||||
func (*service) CreateCustomer(ctx context.Context, params payments.CreateCustomerParams) (*payments.Customer, error) {
|
||||
var b [8]byte
|
||||
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
return nil, internalPaymentsErr.New("error creating customer")
|
||||
}
|
||||
|
||||
return &payments.Customer{
|
||||
ID: b[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCustomer always returns default storjCustomer
|
||||
func (*service) GetCustomer(ctx context.Context, id []byte) (*payments.Customer, error) {
|
||||
return &storjCustomer, nil
|
||||
}
|
||||
|
||||
// GetCustomerDefaultPaymentMethod always returns defaultPaymentMethod
|
||||
func (*service) GetCustomerDefaultPaymentMethod(ctx context.Context, customerID []byte) (*payments.PaymentMethod, error) {
|
||||
return &defaultPaymentMethod, nil
|
||||
}
|
||||
|
||||
// GetCustomerPaymentsMethods always returns payments.Customer list with defaultPaymentMethod
|
||||
func (*service) GetCustomerPaymentsMethods(ctx context.Context, customerID []byte) ([]payments.PaymentMethod, error) {
|
||||
return []payments.PaymentMethod{defaultPaymentMethod}, nil
|
||||
}
|
||||
|
||||
// GetPaymentMethod always returns defaultPaymentMethod or error
|
||||
func (*service) GetPaymentMethod(ctx context.Context, id []byte) (*payments.PaymentMethod, error) {
|
||||
if string(id) == "0" {
|
||||
return &defaultPaymentMethod, nil
|
||||
}
|
||||
|
||||
return nil, internalPaymentsErr.New("only one payments method exists, with id \"0\"")
|
||||
}
|
||||
|
||||
// CreateProjectInvoice creates invoice from provided params
|
||||
func (*service) CreateProjectInvoice(ctx context.Context, params payments.CreateProjectInvoiceParams) (*payments.Invoice, error) {
|
||||
// TODO: fill data
|
||||
return &payments.Invoice{}, nil
|
||||
}
|
||||
|
||||
// GetInvoice retrieves invoice information from project invoice stamp by invoice id
|
||||
// and returns invoice
|
||||
func (*service) GetInvoice(ctx context.Context, id []byte) (*payments.Invoice, error) {
|
||||
// TODO: get project invoice stamp by invoice id from the db and fill data
|
||||
return &payments.Invoice{}, nil
|
||||
}
|
104
satellite/payments/service.go
Normal file
104
satellite/payments/service.go
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package payments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Service is interfaces that defines behavior for working with payments
|
||||
type Service interface {
|
||||
CreateCustomer(ctx context.Context, params CreateCustomerParams) (*Customer, 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)
|
||||
GetPaymentMethod(ctx context.Context, id []byte) (*PaymentMethod, error)
|
||||
CreateProjectInvoice(ctx context.Context, params CreateProjectInvoiceParams) (*Invoice, error)
|
||||
GetInvoice(ctx context.Context, id []byte) (*Invoice, error)
|
||||
}
|
||||
|
||||
// CreateCustomerParams contains info needed to create new customer
|
||||
type CreateCustomerParams struct {
|
||||
Email string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Customer contains customer info
|
||||
type Customer struct {
|
||||
ID []byte
|
||||
Name string
|
||||
Email string
|
||||
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// Card contains credit card info
|
||||
type Card struct {
|
||||
Country string
|
||||
Brand string
|
||||
Name string
|
||||
ExpMonth int64
|
||||
ExpYear int64
|
||||
LastFour string
|
||||
}
|
||||
|
||||
// PaymentMethod contains payment method description.
|
||||
// Credit cards are the only allowed payment methods so far
|
||||
type PaymentMethod struct {
|
||||
ID []byte
|
||||
CustomerID []byte
|
||||
|
||||
Card Card
|
||||
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// CreateProjectInvoiceParams contains info needed to create project invoice
|
||||
type CreateProjectInvoiceParams struct {
|
||||
ProjectName string
|
||||
CustomerID string
|
||||
PaymentMethodID string
|
||||
|
||||
Storage float64
|
||||
Egress float64
|
||||
ObjectCount float64
|
||||
|
||||
StartDate time.Time
|
||||
EndDate time.Time
|
||||
}
|
||||
|
||||
// Currency is type for allowed currency
|
||||
type Currency string
|
||||
|
||||
const (
|
||||
// CurrencyUSD is USA default currency
|
||||
CurrencyUSD Currency = "usd"
|
||||
)
|
||||
|
||||
// LineItem contains invoice line item info
|
||||
type LineItem struct {
|
||||
Key string
|
||||
Quantity int64
|
||||
Amount int64
|
||||
}
|
||||
|
||||
// CustomField represents custom field/value field
|
||||
type CustomField struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Invoice holds invoice information
|
||||
type Invoice struct {
|
||||
ID []byte
|
||||
PaymentMethodID []byte
|
||||
|
||||
Amount int64
|
||||
Currency Currency
|
||||
LineItems []LineItem
|
||||
CustomFields []CustomField
|
||||
|
||||
CreatedAt time.Time
|
||||
}
|
324
satellite/payments/stripepayments/stripe.go
Normal file
324
satellite/payments/stripepayments/stripe.go
Normal file
@ -0,0 +1,324 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package stripepayments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/stripe/stripe-go"
|
||||
"github.com/stripe/stripe-go/client"
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"storj.io/storj/satellite/payments"
|
||||
)
|
||||
|
||||
// stripeErr is a wrapper for stripe err
|
||||
var stripeErr = errs.Class("stripe error")
|
||||
|
||||
// service is payments.Service implementation which
|
||||
// works with stripe network through stripe-go client
|
||||
type service struct {
|
||||
log *zap.Logger
|
||||
client *client.API
|
||||
}
|
||||
|
||||
// NewService creates new instance of StripeService initialized with API key
|
||||
func NewService(log *zap.Logger, apiKey string) payments.Service {
|
||||
stripe.DefaultLeveledLogger = log.Sugar()
|
||||
|
||||
sc := new(client.API)
|
||||
sc.Init(apiKey, nil)
|
||||
|
||||
return &service{
|
||||
log: log,
|
||||
client: sc,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCustomer creates new customer from CustomerParams struct
|
||||
// sets default payment to one of the predefined testing VISA credit cards
|
||||
func (s *service) CreateCustomer(ctx context.Context, params payments.CreateCustomerParams) (*payments.Customer, error) {
|
||||
cparams := &stripe.CustomerParams{
|
||||
Email: stripe.String(params.Email),
|
||||
Name: stripe.String(params.Name),
|
||||
}
|
||||
|
||||
// TODO: delete after migrating from test environment
|
||||
err := cparams.SetSource("tok_visa")
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
cus, err := s.client.Customers.New(cparams)
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
return &payments.Customer{
|
||||
ID: []byte(cus.ID),
|
||||
Name: cus.Name,
|
||||
Email: cus.Email,
|
||||
CreatedAt: time.Unix(cus.Created, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCustomer retrieves customer object from stripe network
|
||||
func (s *service) GetCustomer(ctx context.Context, id []byte) (*payments.Customer, error) {
|
||||
cus, err := s.client.Customers.Get(string(id), nil)
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
return &payments.Customer{
|
||||
ID: []byte(cus.ID),
|
||||
Name: cus.Name,
|
||||
Email: cus.Email,
|
||||
CreatedAt: time.Unix(cus.Created, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCustomerDefaultPaymentMethod retrieves customer default payment method from stripe network
|
||||
func (s *service) GetCustomerDefaultPaymentMethod(ctx context.Context, customerID []byte) (*payments.PaymentMethod, error) {
|
||||
cus, err := s.client.Customers.Get(string(customerID), nil)
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
if cus.DefaultSource == nil {
|
||||
return nil, stripeErr.New("no default payment method attached to customer")
|
||||
}
|
||||
|
||||
pm, err := s.client.PaymentMethods.Get(cus.DefaultSource.ID, nil)
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
if pm.Type != stripe.PaymentMethodTypeCard {
|
||||
return nil, stripeErr.New("payment method other than cards are not allowed")
|
||||
}
|
||||
|
||||
return &payments.PaymentMethod{
|
||||
ID: []byte(pm.ID),
|
||||
CustomerID: []byte(cus.ID),
|
||||
Card: payments.Card{
|
||||
Country: pm.Card.Country,
|
||||
Brand: string(pm.Card.Brand),
|
||||
Name: pm.BillingDetails.Name,
|
||||
ExpMonth: int64(pm.Card.ExpMonth),
|
||||
ExpYear: int64(pm.Card.ExpYear),
|
||||
LastFour: pm.Card.Last4,
|
||||
},
|
||||
CreatedAt: time.Unix(pm.Created, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCustomerPaymentsMethods retrieves all payments method attached to particular customer
|
||||
func (s *service) GetCustomerPaymentsMethods(ctx context.Context, customerID []byte) ([]payments.PaymentMethod, error) {
|
||||
var err error
|
||||
|
||||
pmparams := &stripe.PaymentMethodListParams{}
|
||||
pmparams.Filters.AddFilter("customer", "", string(customerID))
|
||||
pmparams.Filters.AddFilter("type", "", "card")
|
||||
|
||||
iterator := s.client.PaymentMethods.List(pmparams)
|
||||
if err = iterator.Err(); err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
var paymentMethods []payments.PaymentMethod
|
||||
for iterator.Next() {
|
||||
pm := iterator.PaymentMethod()
|
||||
if pm.Type != stripe.PaymentMethodTypeCard {
|
||||
continue
|
||||
}
|
||||
|
||||
paymentMethods = append(paymentMethods, payments.PaymentMethod{
|
||||
ID: []byte(pm.ID),
|
||||
CustomerID: customerID,
|
||||
Card: payments.Card{
|
||||
Country: pm.Card.Country,
|
||||
Brand: string(pm.Card.Brand),
|
||||
Name: pm.BillingDetails.Name,
|
||||
ExpMonth: int64(pm.Card.ExpMonth),
|
||||
ExpYear: int64(pm.Card.ExpYear),
|
||||
LastFour: pm.Card.Last4,
|
||||
},
|
||||
CreatedAt: time.Unix(pm.Created, 0),
|
||||
})
|
||||
}
|
||||
|
||||
return paymentMethods, nil
|
||||
}
|
||||
|
||||
// GetPaymentMethod retrieve payment method object from stripe network
|
||||
func (s *service) GetPaymentMethod(ctx context.Context, id []byte) (*payments.PaymentMethod, error) {
|
||||
pm, err := s.client.PaymentMethods.Get(string(id), nil)
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
if pm.Type != stripe.PaymentMethodTypeCard {
|
||||
return nil, stripeErr.New("payment method other than cards are not allowed")
|
||||
}
|
||||
|
||||
// TODO: check if name is always returned
|
||||
var customerID []byte
|
||||
if pm.Customer != nil {
|
||||
customerID = []byte(pm.Customer.ID)
|
||||
}
|
||||
|
||||
return &payments.PaymentMethod{
|
||||
ID: []byte(pm.ID),
|
||||
CustomerID: customerID,
|
||||
Card: payments.Card{
|
||||
Country: pm.Card.Country,
|
||||
Brand: string(pm.Card.Brand),
|
||||
Name: pm.BillingDetails.Name,
|
||||
ExpMonth: int64(pm.Card.ExpMonth),
|
||||
ExpYear: int64(pm.Card.ExpYear),
|
||||
LastFour: pm.Card.Last4,
|
||||
},
|
||||
CreatedAt: time.Unix(pm.Created, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateProjectInvoice creates new project invoice on stripe network from input params.
|
||||
// Included line items:
|
||||
// - Storage
|
||||
// - Egress
|
||||
// - ObjectsCount
|
||||
// Created invoice has AutoAdvance property set to true, so it will be finalized
|
||||
// (no further editing) and attempted to be paid in 1 hour after creation
|
||||
func (s *service) CreateProjectInvoice(ctx context.Context, params payments.CreateProjectInvoiceParams) (*payments.Invoice, error) {
|
||||
// create line items
|
||||
_, err := s.client.InvoiceItems.New(&stripe.InvoiceItemParams{
|
||||
Customer: stripe.String(params.CustomerID),
|
||||
Description: stripe.String("Storage"),
|
||||
Quantity: stripe.Int64(int64(params.Storage)),
|
||||
UnitAmount: stripe.Int64(100),
|
||||
Currency: stripe.String(string(stripe.CurrencyUSD)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = s.client.InvoiceItems.New(&stripe.InvoiceItemParams{
|
||||
Customer: stripe.String(params.CustomerID),
|
||||
Description: stripe.String("Egress"),
|
||||
Quantity: stripe.Int64(int64(params.Egress)),
|
||||
UnitAmount: stripe.Int64(100),
|
||||
Currency: stripe.String(string(stripe.CurrencyUSD)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
_, err = s.client.InvoiceItems.New(&stripe.InvoiceItemParams{
|
||||
Customer: stripe.String(params.CustomerID),
|
||||
Description: stripe.String("ObjectsCount"),
|
||||
Quantity: stripe.Int64(int64(params.ObjectCount)),
|
||||
UnitAmount: stripe.Int64(100),
|
||||
Currency: stripe.String(string(stripe.CurrencyUSD)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
// TODO: fetch card info manually?
|
||||
// create invoice
|
||||
invoiceParams := &stripe.InvoiceParams{
|
||||
Customer: stripe.String(params.CustomerID),
|
||||
DefaultPaymentMethod: stripe.String(params.PaymentMethodID),
|
||||
Description: stripe.String(fmt.Sprintf("Invoice for usage of %s", params.ProjectName)),
|
||||
CustomFields: []*stripe.InvoiceCustomFieldParams{
|
||||
{
|
||||
Name: stripe.String("Billing period"),
|
||||
Value: stripe.String(timeRangeString(params.StartDate, params.EndDate)),
|
||||
},
|
||||
{
|
||||
Name: stripe.String("Project Name"),
|
||||
Value: stripe.String(params.ProjectName),
|
||||
},
|
||||
},
|
||||
AutoAdvance: stripe.Bool(true),
|
||||
}
|
||||
|
||||
inv, err := s.client.Invoices.New(invoiceParams)
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
// TODO: check for more items
|
||||
var lineItems []payments.LineItem
|
||||
for _, item := range inv.Lines.Data {
|
||||
lineItems = append(lineItems, payments.LineItem{
|
||||
Key: item.Description,
|
||||
Quantity: item.Quantity,
|
||||
Amount: item.Amount,
|
||||
})
|
||||
}
|
||||
|
||||
var customFields []payments.CustomField
|
||||
for _, field := range inv.CustomFields {
|
||||
customFields = append(customFields, payments.CustomField{
|
||||
Name: field.Name,
|
||||
Value: field.Value,
|
||||
})
|
||||
}
|
||||
|
||||
return &payments.Invoice{
|
||||
ID: []byte(inv.ID),
|
||||
Amount: inv.AmountDue,
|
||||
Currency: payments.Currency(inv.Currency),
|
||||
LineItems: lineItems,
|
||||
CustomFields: customFields,
|
||||
CreatedAt: time.Unix(inv.Created, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetInvoice retrieves an invoice from stripe network by invoiceID
|
||||
func (s *service) GetInvoice(ctx context.Context, id []byte) (*payments.Invoice, error) {
|
||||
inv, err := s.client.Invoices.Get(string(id), nil)
|
||||
if err != nil {
|
||||
return nil, stripeErr.Wrap(err)
|
||||
}
|
||||
|
||||
// TODO: check for more items
|
||||
var lineItems []payments.LineItem
|
||||
for _, item := range inv.Lines.Data {
|
||||
lineItems = append(lineItems, payments.LineItem{
|
||||
Key: item.Description,
|
||||
Quantity: item.Quantity,
|
||||
Amount: item.Amount,
|
||||
})
|
||||
}
|
||||
|
||||
var customFields []payments.CustomField
|
||||
for _, field := range inv.CustomFields {
|
||||
customFields = append(customFields, payments.CustomField{
|
||||
Name: field.Name,
|
||||
Value: field.Value,
|
||||
})
|
||||
}
|
||||
|
||||
return &payments.Invoice{
|
||||
ID: []byte(inv.ID),
|
||||
Amount: inv.AmountDue,
|
||||
Currency: payments.Currency(inv.Currency),
|
||||
LineItems: lineItems,
|
||||
CustomFields: customFields,
|
||||
CreatedAt: time.Unix(inv.Created, 0),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// timeRangeString helper function to create string representation of time range
|
||||
func timeRangeString(start, end time.Time) string {
|
||||
return fmt.Sprintf("%d/%d/%d - %d/%d/%d",
|
||||
start.UTC().Month(), start.UTC().Day(), start.UTC().Year(),
|
||||
end.UTC().Month(), end.UTC().Day(), end.UTC().Year())
|
||||
}
|
@ -51,6 +51,9 @@ import (
|
||||
"storj.io/storj/satellite/mailservice/simulate"
|
||||
"storj.io/storj/satellite/metainfo"
|
||||
"storj.io/storj/satellite/orders"
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/localpayments"
|
||||
"storj.io/storj/satellite/payments/stripepayments"
|
||||
"storj.io/storj/satellite/vouchers"
|
||||
"storj.io/storj/storage"
|
||||
"storj.io/storj/storage/boltdb"
|
||||
@ -551,10 +554,19 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, config *Config, ve
|
||||
return nil, errs.New("Auth token secret required")
|
||||
}
|
||||
|
||||
// TODO: change mock implementation to using mock stripe backend
|
||||
var pmService payments.Service
|
||||
if consoleConfig.StripeKey != "" {
|
||||
pmService = stripepayments.NewService(peer.Log.Named("stripe:service"), consoleConfig.StripeKey)
|
||||
} else {
|
||||
pmService = localpayments.NewService(nil)
|
||||
}
|
||||
|
||||
peer.Console.Service, err = console.NewService(
|
||||
peer.Log.Named("console:service"),
|
||||
&consoleauth.Hmac{Secret: []byte(consoleConfig.AuthTokenSecret)},
|
||||
peer.DB.Console(),
|
||||
pmService,
|
||||
consoleConfig.PasswordCost,
|
||||
)
|
||||
|
||||
|
3
scripts/testdata/satellite-config.yaml.lock
vendored
3
scripts/testdata/satellite-config.yaml.lock
vendored
@ -34,6 +34,9 @@
|
||||
# path to static resources
|
||||
# console.static-dir: ""
|
||||
|
||||
# stripe api key
|
||||
# console.stripe-key: ""
|
||||
|
||||
# satellite database connection string
|
||||
# database: "postgres://"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user