satellite/{payments/storjscan,satellitedb}: Add wallet implementation
Add storjscan wallets implementation to the satellite. The wallets interface allows you to add and claim new wallets as called by the API. The storjscan specific implementation of this interface uses a wallets DB to associate the user to a wallet address, as well as a storjscan client to request and associate new wallets to the satellite. Change-Id: I54081edb5545d4e3ee07cf1cce3d3e87cc00c4a1
This commit is contained in:
parent
cbaca8b17e
commit
270204f352
@ -539,10 +539,10 @@ func TestProjectCheckUsage_lastMonthUnappliedInvoice(t *testing.T) {
|
||||
err = planet.Satellites[0].DB.ProjectAccounting().CreateStorageTally(ctx, tally)
|
||||
require.NoError(t, err)
|
||||
|
||||
planet.Satellites[0].API.Payments.Service.SetNow(func() time.Time {
|
||||
planet.Satellites[0].API.Payments.StripeService.SetNow(func() time.Time {
|
||||
return oneMonthAhead
|
||||
})
|
||||
err = planet.Satellites[0].API.Payments.Service.PrepareInvoiceProjectRecords(ctx, now)
|
||||
err = planet.Satellites[0].API.Payments.StripeService.PrepareInvoiceProjectRecords(ctx, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://"+address.String()+"/api/projects/%s/usage", projectID), nil)
|
||||
|
@ -48,6 +48,7 @@ import (
|
||||
"storj.io/storj/satellite/overlay"
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/paymentsconfig"
|
||||
"storj.io/storj/satellite/payments/storjscan"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
"storj.io/storj/satellite/reputation"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
@ -127,10 +128,15 @@ type API struct {
|
||||
}
|
||||
|
||||
Payments struct {
|
||||
Accounts payments.Accounts
|
||||
Conversion *stripecoinpayments.ConversionService
|
||||
Service *stripecoinpayments.Service
|
||||
Stripe stripecoinpayments.StripeClient
|
||||
Accounts payments.Accounts
|
||||
DepositWallets payments.DepositWallets
|
||||
|
||||
StorjscanService *storjscan.Service
|
||||
StorjscanClient *storjscan.Client
|
||||
|
||||
Conversion *stripecoinpayments.ConversionService
|
||||
StripeService *stripecoinpayments.Service
|
||||
StripeClient stripecoinpayments.StripeClient
|
||||
}
|
||||
|
||||
REST struct {
|
||||
@ -485,7 +491,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
stripeClient = stripecoinpayments.NewStripeClient(log, pc.StripeCoinPayments)
|
||||
}
|
||||
|
||||
peer.Payments.Service, err = stripecoinpayments.NewService(
|
||||
peer.Payments.StripeService, err = stripecoinpayments.NewService(
|
||||
peer.Log.Named("payments.stripe:service"),
|
||||
stripeClient,
|
||||
pc.StripeCoinPayments,
|
||||
@ -501,11 +507,11 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
|
||||
peer.Payments.Stripe = stripeClient
|
||||
peer.Payments.Accounts = peer.Payments.Service.Accounts()
|
||||
peer.Payments.StripeClient = stripeClient
|
||||
peer.Payments.Accounts = peer.Payments.StripeService.Accounts()
|
||||
peer.Payments.Conversion = stripecoinpayments.NewConversionService(
|
||||
peer.Log.Named("payments.stripe:version"),
|
||||
peer.Payments.Service,
|
||||
peer.Payments.StripeService,
|
||||
pc.StripeCoinPayments.ConversionRatesCycleInterval)
|
||||
|
||||
peer.Services.Add(lifecycle.Item{
|
||||
@ -513,6 +519,16 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
Run: peer.Payments.Conversion.Run,
|
||||
Close: peer.Payments.Conversion.Close,
|
||||
})
|
||||
|
||||
peer.Payments.StorjscanClient = storjscan.NewClient(
|
||||
pc.Storjscan.Endpoint,
|
||||
pc.Storjscan.Credentials.PublicKey,
|
||||
pc.Storjscan.Credentials.PrivateKey)
|
||||
peer.Payments.StorjscanService, err = storjscan.NewService(peer.DB, peer.Payments.StorjscanClient)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
peer.Payments.DepositWallets = peer.Payments.StorjscanService
|
||||
}
|
||||
|
||||
{ // setup account management api keys
|
||||
@ -540,6 +556,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
|
||||
peer.Buckets.Service,
|
||||
peer.Marketing.PartnersService,
|
||||
peer.Payments.Accounts,
|
||||
peer.Payments.DepositWallets,
|
||||
peer.Analytics.Service,
|
||||
peer.Console.AuthTokens,
|
||||
consoleConfig.Config,
|
||||
|
@ -104,6 +104,8 @@ func TestGraphqlMutation(t *testing.T) {
|
||||
sat.API.Buckets.Service,
|
||||
partnersService,
|
||||
paymentsService.Accounts(),
|
||||
// TODO: do we need a payment deposit wallet here?
|
||||
nil,
|
||||
analyticsService,
|
||||
consoleauth.NewService(consoleauth.Config{
|
||||
TokenExpirationTime: 24 * time.Hour,
|
||||
|
@ -88,6 +88,8 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
sat.API.Buckets.Service,
|
||||
partnersService,
|
||||
paymentsService.Accounts(),
|
||||
// TODO: do we need a payment deposit wallet here?
|
||||
nil,
|
||||
analyticsService,
|
||||
consoleauth.NewService(consoleauth.Config{
|
||||
TokenExpirationTime: 24 * time.Hour,
|
||||
|
@ -123,6 +123,7 @@ type Service struct {
|
||||
buckets Buckets
|
||||
partners *rewards.PartnersService
|
||||
accounts payments.Accounts
|
||||
depositWallets payments.DepositWallets
|
||||
captchaHandler CaptchaHandler
|
||||
analytics *analytics.Service
|
||||
tokens *consoleauth.Service
|
||||
@ -169,13 +170,13 @@ type HcaptchaConfig struct {
|
||||
SecretKey string `help:"hCaptcha secret key"`
|
||||
}
|
||||
|
||||
// PaymentsService separates all payment related functionality.
|
||||
type PaymentsService struct {
|
||||
// Payments separates all payment related functionality.
|
||||
type Payments struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
// NewService returns new instance of Service.
|
||||
func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets Buckets, partners *rewards.PartnersService, accounts payments.Accounts, analytics *analytics.Service, tokens *consoleauth.Service, config Config) (*Service, error) {
|
||||
func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting accounting.ProjectAccounting, projectUsage *accounting.Service, buckets Buckets, partners *rewards.PartnersService, accounts payments.Accounts, depositWallets payments.DepositWallets, analytics *analytics.Service, tokens *consoleauth.Service, config Config) (*Service, error) {
|
||||
if store == nil {
|
||||
return nil, errs.New("store can't be nil")
|
||||
}
|
||||
@ -203,6 +204,7 @@ func NewService(log *zap.Logger, store DB, restKeys RESTKeys, projectAccounting
|
||||
buckets: buckets,
|
||||
partners: partners,
|
||||
accounts: accounts,
|
||||
depositWallets: depositWallets,
|
||||
captchaHandler: captchaHandler,
|
||||
analytics: analytics,
|
||||
tokens: tokens,
|
||||
@ -254,79 +256,79 @@ func (s *Service) getAuthAndAuditLog(ctx context.Context, operation string, extr
|
||||
}
|
||||
|
||||
// Payments separates all payment related functionality.
|
||||
func (s *Service) Payments() PaymentsService {
|
||||
return PaymentsService{service: s}
|
||||
func (s *Service) Payments() Payments {
|
||||
return Payments{service: s}
|
||||
}
|
||||
|
||||
// SetupAccount creates payment account for authorized user.
|
||||
func (paymentService PaymentsService) SetupAccount(ctx context.Context) (_ payments.CouponType, err error) {
|
||||
func (payment Payments) SetupAccount(ctx context.Context) (_ payments.CouponType, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "setup payment account")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "setup payment account")
|
||||
if err != nil {
|
||||
return payments.NoCoupon, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.Setup(ctx, auth.User.ID, auth.User.Email, auth.User.SignupPromoCode)
|
||||
return payment.service.accounts.Setup(ctx, auth.User.ID, auth.User.Email, auth.User.SignupPromoCode)
|
||||
}
|
||||
|
||||
// AccountBalance return account balance.
|
||||
func (paymentService PaymentsService) AccountBalance(ctx context.Context) (balance payments.Balance, err error) {
|
||||
func (payment Payments) AccountBalance(ctx context.Context) (balance payments.Balance, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get account balance")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "get account balance")
|
||||
if err != nil {
|
||||
return payments.Balance{}, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.Balance(ctx, auth.User.ID)
|
||||
return payment.service.accounts.Balance(ctx, auth.User.ID)
|
||||
}
|
||||
|
||||
// AddCreditCard is used to save new credit card and attach it to payment account.
|
||||
func (paymentService PaymentsService) AddCreditCard(ctx context.Context, creditCardToken string) (err error) {
|
||||
func (payment Payments) AddCreditCard(ctx context.Context, creditCardToken string) (err error) {
|
||||
defer mon.Task()(&ctx, creditCardToken)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "add credit card")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "add credit card")
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
err = paymentService.service.accounts.CreditCards().Add(ctx, auth.User.ID, creditCardToken)
|
||||
err = payment.service.accounts.CreditCards().Add(ctx, auth.User.ID, creditCardToken)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
paymentService.service.analytics.TrackCreditCardAdded(auth.User.ID, auth.User.Email)
|
||||
payment.service.analytics.TrackCreditCardAdded(auth.User.ID, auth.User.Email)
|
||||
|
||||
if !auth.User.PaidTier {
|
||||
// put this user into the paid tier and convert projects to upgraded limits.
|
||||
err = paymentService.service.store.Users().UpdatePaidTier(ctx, auth.User.ID, true,
|
||||
paymentService.service.config.UsageLimits.Bandwidth.Paid,
|
||||
paymentService.service.config.UsageLimits.Storage.Paid,
|
||||
paymentService.service.config.UsageLimits.Segment.Paid,
|
||||
paymentService.service.config.UsageLimits.Project.Paid,
|
||||
err = payment.service.store.Users().UpdatePaidTier(ctx, auth.User.ID, true,
|
||||
payment.service.config.UsageLimits.Bandwidth.Paid,
|
||||
payment.service.config.UsageLimits.Storage.Paid,
|
||||
payment.service.config.UsageLimits.Segment.Paid,
|
||||
payment.service.config.UsageLimits.Project.Paid,
|
||||
)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
projects, err := paymentService.service.store.Projects().GetOwn(ctx, auth.User.ID)
|
||||
projects, err := payment.service.store.Projects().GetOwn(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
for _, project := range projects {
|
||||
if project.StorageLimit == nil || *project.StorageLimit < paymentService.service.config.UsageLimits.Storage.Paid {
|
||||
if project.StorageLimit == nil || *project.StorageLimit < payment.service.config.UsageLimits.Storage.Paid {
|
||||
project.StorageLimit = new(memory.Size)
|
||||
*project.StorageLimit = paymentService.service.config.UsageLimits.Storage.Paid
|
||||
*project.StorageLimit = payment.service.config.UsageLimits.Storage.Paid
|
||||
}
|
||||
if project.BandwidthLimit == nil || *project.BandwidthLimit < paymentService.service.config.UsageLimits.Bandwidth.Paid {
|
||||
if project.BandwidthLimit == nil || *project.BandwidthLimit < payment.service.config.UsageLimits.Bandwidth.Paid {
|
||||
project.BandwidthLimit = new(memory.Size)
|
||||
*project.BandwidthLimit = paymentService.service.config.UsageLimits.Bandwidth.Paid
|
||||
*project.BandwidthLimit = payment.service.config.UsageLimits.Bandwidth.Paid
|
||||
}
|
||||
if project.SegmentLimit == nil || *project.SegmentLimit < paymentService.service.config.UsageLimits.Segment.Paid {
|
||||
*project.SegmentLimit = paymentService.service.config.UsageLimits.Segment.Paid
|
||||
if project.SegmentLimit == nil || *project.SegmentLimit < payment.service.config.UsageLimits.Segment.Paid {
|
||||
*project.SegmentLimit = payment.service.config.UsageLimits.Segment.Paid
|
||||
}
|
||||
err = paymentService.service.store.Projects().Update(ctx, &project)
|
||||
err = payment.service.store.Projects().Update(ctx, &project)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
@ -337,63 +339,63 @@ func (paymentService PaymentsService) AddCreditCard(ctx context.Context, creditC
|
||||
}
|
||||
|
||||
// MakeCreditCardDefault makes a credit card default payment method.
|
||||
func (paymentService PaymentsService) MakeCreditCardDefault(ctx context.Context, cardID string) (err error) {
|
||||
func (payment Payments) MakeCreditCardDefault(ctx context.Context, cardID string) (err error) {
|
||||
defer mon.Task()(&ctx, cardID)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "make credit card default")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "make credit card default")
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.CreditCards().MakeDefault(ctx, auth.User.ID, cardID)
|
||||
return payment.service.accounts.CreditCards().MakeDefault(ctx, auth.User.ID, cardID)
|
||||
}
|
||||
|
||||
// ProjectsCharges returns how much money current user will be charged for each project which he owns.
|
||||
func (paymentService PaymentsService) ProjectsCharges(ctx context.Context, since, before time.Time) (_ []payments.ProjectCharge, err error) {
|
||||
func (payment Payments) ProjectsCharges(ctx context.Context, since, before time.Time) (_ []payments.ProjectCharge, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "project charges")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "project charges")
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.ProjectCharges(ctx, auth.User.ID, since, before)
|
||||
return payment.service.accounts.ProjectCharges(ctx, auth.User.ID, since, before)
|
||||
}
|
||||
|
||||
// ListCreditCards returns a list of credit cards for a given payment account.
|
||||
func (paymentService PaymentsService) ListCreditCards(ctx context.Context) (_ []payments.CreditCard, err error) {
|
||||
func (payment Payments) ListCreditCards(ctx context.Context) (_ []payments.CreditCard, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "list credit cards")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "list credit cards")
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.CreditCards().List(ctx, auth.User.ID)
|
||||
return payment.service.accounts.CreditCards().List(ctx, auth.User.ID)
|
||||
}
|
||||
|
||||
// RemoveCreditCard is used to detach a credit card from payment account.
|
||||
func (paymentService PaymentsService) RemoveCreditCard(ctx context.Context, cardID string) (err error) {
|
||||
func (payment Payments) RemoveCreditCard(ctx context.Context, cardID string) (err error) {
|
||||
defer mon.Task()(&ctx, cardID)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "remove credit card")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "remove credit card")
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.CreditCards().Remove(ctx, auth.User.ID, cardID)
|
||||
return payment.service.accounts.CreditCards().Remove(ctx, auth.User.ID, cardID)
|
||||
}
|
||||
|
||||
// BillingHistory returns a list of billing history items for payment account.
|
||||
func (paymentService PaymentsService) BillingHistory(ctx context.Context) (billingHistory []*BillingHistoryItem, err error) {
|
||||
func (payment Payments) BillingHistory(ctx context.Context) (billingHistory []*BillingHistoryItem, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get billing history")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "get billing history")
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
invoices, couponUsages, err := paymentService.service.accounts.Invoices().ListWithDiscounts(ctx, auth.User.ID)
|
||||
invoices, couponUsages, err := payment.service.accounts.Invoices().ListWithDiscounts(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
@ -411,7 +413,7 @@ func (paymentService PaymentsService) BillingHistory(ctx context.Context) (billi
|
||||
})
|
||||
}
|
||||
|
||||
txsInfos, err := paymentService.service.accounts.StorjTokens().ListTransactionInfos(ctx, auth.User.ID)
|
||||
txsInfos, err := payment.service.accounts.StorjTokens().ListTransactionInfos(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
@ -430,7 +432,7 @@ func (paymentService PaymentsService) BillingHistory(ctx context.Context) (billi
|
||||
})
|
||||
}
|
||||
|
||||
charges, err := paymentService.service.accounts.Charges(ctx, auth.User.ID)
|
||||
charges, err := payment.service.accounts.Charges(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
@ -465,7 +467,7 @@ func (paymentService PaymentsService) BillingHistory(ctx context.Context) (billi
|
||||
})
|
||||
}
|
||||
|
||||
bonuses, err := paymentService.service.accounts.StorjTokens().ListDepositBonuses(ctx, auth.User.ID)
|
||||
bonuses, err := payment.service.accounts.StorjTokens().ListDepositBonuses(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
@ -492,29 +494,29 @@ func (paymentService PaymentsService) BillingHistory(ctx context.Context) (billi
|
||||
}
|
||||
|
||||
// TokenDeposit creates new deposit transaction for adding STORJ tokens to account balance.
|
||||
func (paymentService PaymentsService) TokenDeposit(ctx context.Context, amount int64) (_ *payments.Transaction, err error) {
|
||||
func (payment Payments) TokenDeposit(ctx context.Context, amount int64) (_ *payments.Transaction, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "token deposit")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "token deposit")
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
tx, err := paymentService.service.accounts.StorjTokens().Deposit(ctx, auth.User.ID, amount)
|
||||
tx, err := payment.service.accounts.StorjTokens().Deposit(ctx, auth.User.ID, amount)
|
||||
|
||||
return tx, Error.Wrap(err)
|
||||
}
|
||||
|
||||
// checkOutstandingInvoice returns if the payment account has any unpaid/outstanding invoices or/and invoice items.
|
||||
func (paymentService PaymentsService) checkOutstandingInvoice(ctx context.Context) (err error) {
|
||||
func (payment Payments) checkOutstandingInvoice(ctx context.Context) (err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get outstanding invoices")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "get outstanding invoices")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
invoices, err := paymentService.service.accounts.Invoices().List(ctx, auth.User.ID)
|
||||
invoices, err := payment.service.accounts.Invoices().List(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -526,7 +528,7 @@ func (paymentService PaymentsService) checkOutstandingInvoice(ctx context.Contex
|
||||
}
|
||||
}
|
||||
|
||||
hasItems, err := paymentService.service.accounts.Invoices().CheckPendingItems(ctx, auth.User.ID)
|
||||
hasItems, err := payment.service.accounts.Invoices().CheckPendingItems(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -538,28 +540,28 @@ func (paymentService PaymentsService) checkOutstandingInvoice(ctx context.Contex
|
||||
|
||||
// checkProjectInvoicingStatus returns if for the given project there are outstanding project records and/or usage
|
||||
// which have not been applied/invoiced yet (meaning sent over to stripe).
|
||||
func (paymentService PaymentsService) checkProjectInvoicingStatus(ctx context.Context, projectID uuid.UUID) (unpaidUsage bool, err error) {
|
||||
func (payment Payments) checkProjectInvoicingStatus(ctx context.Context, projectID uuid.UUID) (unpaidUsage bool, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
_, err = paymentService.service.getAuthAndAuditLog(ctx, "project charges")
|
||||
_, err = payment.service.getAuthAndAuditLog(ctx, "project charges")
|
||||
if err != nil {
|
||||
return false, Error.Wrap(err)
|
||||
}
|
||||
|
||||
return paymentService.service.accounts.CheckProjectInvoicingStatus(ctx, projectID)
|
||||
return payment.service.accounts.CheckProjectInvoicingStatus(ctx, projectID)
|
||||
}
|
||||
|
||||
// ApplyCouponCode applies a coupon code to a Stripe customer
|
||||
// and returns the coupon corresponding to the code.
|
||||
func (paymentService PaymentsService) ApplyCouponCode(ctx context.Context, couponCode string) (coupon *payments.Coupon, err error) {
|
||||
func (payment Payments) ApplyCouponCode(ctx context.Context, couponCode string) (coupon *payments.Coupon, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "apply coupon code")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "apply coupon code")
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
coupon, err = paymentService.service.accounts.Coupons().ApplyCouponCode(ctx, auth.User.ID, couponCode)
|
||||
coupon, err = payment.service.accounts.Coupons().ApplyCouponCode(ctx, auth.User.ID, couponCode)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
@ -568,15 +570,15 @@ func (paymentService PaymentsService) ApplyCouponCode(ctx context.Context, coupo
|
||||
}
|
||||
|
||||
// GetCoupon returns the coupon applied to the user's account.
|
||||
func (paymentService PaymentsService) GetCoupon(ctx context.Context) (coupon *payments.Coupon, err error) {
|
||||
func (payment Payments) GetCoupon(ctx context.Context) (coupon *payments.Coupon, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := paymentService.service.getAuthAndAuditLog(ctx, "get coupon")
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "get coupon")
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
coupon, err = paymentService.service.accounts.Coupons().GetByUserID(ctx, auth.User.ID)
|
||||
coupon, err = payment.service.accounts.Coupons().GetByUserID(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
@ -2601,17 +2603,43 @@ var ErrWalletNotClaimed = errs.Class("wallet is not claimed")
|
||||
|
||||
// ClaimWallet requests a new wallet for the users to be used for payments. If wallet is already claimed,
|
||||
// it will return with the info without error.
|
||||
func (paymentService PaymentsService) ClaimWallet(ctx context.Context) (WalletInfo, error) {
|
||||
panic("Not yet implemented")
|
||||
func (payment Payments) ClaimWallet(ctx context.Context) (_ WalletInfo, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := payment.service.getAuthAndAuditLog(ctx, "claim wallet")
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
address, err := payment.service.depositWallets.Claim(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
return WalletInfo{
|
||||
Address: address,
|
||||
Balance: nil, //TODO: populate with call to billing table
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWallet returns with the assigned wallet, or with ErrWalletNotClaimed if not yet claimed.
|
||||
func (paymentService PaymentsService) GetWallet(ctx context.Context) (WalletInfo, error) {
|
||||
panic("Not yet implemented")
|
||||
func (payment Payments) GetWallet(ctx context.Context) (_ WalletInfo, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
auth, err := GetAuth(ctx)
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
address, err := payment.service.depositWallets.Get(ctx, auth.User.ID)
|
||||
if err != nil {
|
||||
return WalletInfo{}, Error.Wrap(err)
|
||||
}
|
||||
return WalletInfo{
|
||||
Address: address,
|
||||
Balance: nil, //TODO: populate with call to billing table
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Transactions returns with all the native blockchain transactions.
|
||||
func (paymentService PaymentsService) Transactions(ctx context.Context) (TokenTransactions, error) {
|
||||
func (payment Payments) Transactions(ctx context.Context) (TokenTransactions, error) {
|
||||
panic("Not yet implemented")
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
package paymentsconfig
|
||||
|
||||
import (
|
||||
"storj.io/storj/satellite/payments/storjscan"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
)
|
||||
|
||||
@ -11,6 +12,7 @@ import (
|
||||
type Config struct {
|
||||
Provider string `help:"payments provider to use" default:""`
|
||||
StripeCoinPayments stripecoinpayments.Config
|
||||
Storjscan storjscan.Config
|
||||
StorageTBPrice string `help:"price user should pay for storing TB per month" default:"4" testDefault:"10"`
|
||||
EgressTBPrice string `help:"price user should pay for each TB of egress" default:"7" testDefault:"45"`
|
||||
SegmentPrice string `help:"price user should pay for each segment stored in network per month" default:"0.0000088" testDefault:"0.0000022"`
|
||||
|
@ -114,3 +114,54 @@ func (client *Client) Payments(ctx context.Context, from int64) (_ LatestPayment
|
||||
|
||||
return payments, nil
|
||||
}
|
||||
|
||||
// ClaimNewEthAddress claims a new ethereum wallet address for the given user.
|
||||
func (client *Client) ClaimNewEthAddress(ctx context.Context) (_ blockchain.Address, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
p := client.endpoint + "/api/v0/wallets/claim"
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p, nil)
|
||||
if err != nil {
|
||||
return blockchain.Address{}, ClientErr.Wrap(err)
|
||||
}
|
||||
|
||||
req.SetBasicAuth(client.identifier, client.secret)
|
||||
|
||||
resp, err := client.http.Do(req)
|
||||
if err != nil {
|
||||
return blockchain.Address{}, ClientErr.Wrap(err)
|
||||
}
|
||||
defer func() {
|
||||
err = errs.Combine(err, ClientErr.Wrap(resp.Body.Close()))
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var data struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return blockchain.Address{}, ClientErr.Wrap(err)
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusUnauthorized:
|
||||
return blockchain.Address{}, ClientErrUnauthorized.New("%s", data.Error)
|
||||
default:
|
||||
return blockchain.Address{}, ClientErr.New("%s", data.Error)
|
||||
}
|
||||
}
|
||||
|
||||
var addressHex string
|
||||
|
||||
if err = json.NewDecoder(resp.Body).Decode(&addressHex); err != nil {
|
||||
return blockchain.Address{}, ClientErr.Wrap(err)
|
||||
}
|
||||
|
||||
var address blockchain.Address
|
||||
|
||||
if err = address.UnmarshalJSON([]byte(addressHex)); err != nil {
|
||||
return blockchain.Address{}, ClientErr.Wrap(err)
|
||||
}
|
||||
return address, nil
|
||||
}
|
||||
|
64
satellite/payments/storjscan/service.go
Normal file
64
satellite/payments/storjscan/service.go
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (C) 2022 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package storjscan
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/private/blockchain"
|
||||
"storj.io/storj/satellite/payments"
|
||||
"storj.io/storj/satellite/payments/coinpayments"
|
||||
)
|
||||
|
||||
var (
|
||||
// Error defines storjscan service error.
|
||||
Error = errs.Class("storjscan service")
|
||||
)
|
||||
|
||||
// ensures that Wallets implements payments.Wallets.
|
||||
var _ payments.DepositWallets = (*Service)(nil)
|
||||
|
||||
// Config stores needed information for storjscan service initialization.
|
||||
type Config struct {
|
||||
Credentials coinpayments.Credentials
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
// Service is an implementation for payment service via Stripe and Coinpayments.
|
||||
//
|
||||
// architecture: Service
|
||||
type Service struct {
|
||||
walletsDB WalletsDB
|
||||
storjscanClient *Client
|
||||
}
|
||||
|
||||
// NewService creates a Service instance.
|
||||
func NewService(db DB, storjscanClient *Client) (*Service, error) {
|
||||
return &Service{
|
||||
walletsDB: db.Wallets(),
|
||||
storjscanClient: storjscanClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Claim gets a new crypto wallet and associates it with a user.
|
||||
func (service *Service) Claim(ctx context.Context, userID uuid.UUID) (_ blockchain.Address, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
address, err := service.storjscanClient.ClaimNewEthAddress(ctx)
|
||||
if err != nil {
|
||||
return blockchain.Address{}, err
|
||||
}
|
||||
err = service.walletsDB.Add(ctx, userID, address)
|
||||
return address, err
|
||||
}
|
||||
|
||||
// Get returns the crypto wallet address associated with the given user.
|
||||
func (service *Service) Get(ctx context.Context, userID uuid.UUID) (_ blockchain.Address, err error) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
return service.walletsDB.Get(ctx, userID)
|
||||
}
|
@ -83,6 +83,8 @@ func TestSignupCouponCodes(t *testing.T) {
|
||||
sat.API.Buckets.Service,
|
||||
partnersService,
|
||||
paymentsService.Accounts(),
|
||||
// TODO: do we need a payment deposit wallet here?
|
||||
nil,
|
||||
analyticsService,
|
||||
consoleauth.NewService(consoleauth.Config{
|
||||
TokenExpirationTime: 24 * time.Hour,
|
||||
|
@ -54,10 +54,10 @@ func TestService_InvoiceElementsProcessing(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
satellite.API.Payments.Service.SetNow(func() time.Time {
|
||||
satellite.API.Payments.StripeService.SetNow(func() time.Time {
|
||||
return time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
||||
})
|
||||
err := satellite.API.Payments.Service.PrepareInvoiceProjectRecords(ctx, period)
|
||||
err := satellite.API.Payments.StripeService.PrepareInvoiceProjectRecords(ctx, period)
|
||||
require.NoError(t, err)
|
||||
|
||||
start := time.Date(period.Year(), period.Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
@ -68,7 +68,7 @@ func TestService_InvoiceElementsProcessing(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numberOfProjects, len(recordsPage.Records))
|
||||
|
||||
err = satellite.API.Payments.Service.InvoiceApplyProjectRecords(ctx, period)
|
||||
err = satellite.API.Payments.StripeService.InvoiceApplyProjectRecords(ctx, period)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify that we applied all unapplied project records
|
||||
@ -94,7 +94,7 @@ func TestService_InvoiceUserWithManyProjects(t *testing.T) {
|
||||
// keep month + 1 because user needs to be created before calculation
|
||||
period := time.Date(time.Now().Year(), time.Now().Month()+1, 20, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
payments.Service.SetNow(func() time.Time {
|
||||
payments.StripeService.SetNow(func() time.Time {
|
||||
return time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
||||
})
|
||||
start := time.Date(period.Year(), period.Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
@ -148,7 +148,7 @@ func TestService_InvoiceUserWithManyProjects(t *testing.T) {
|
||||
require.Nil(t, projectRecord)
|
||||
}
|
||||
|
||||
err = payments.Service.PrepareInvoiceProjectRecords(ctx, period)
|
||||
err = payments.StripeService.PrepareInvoiceProjectRecords(ctx, period)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < len(projects); i++ {
|
||||
@ -166,10 +166,10 @@ func TestService_InvoiceUserWithManyProjects(t *testing.T) {
|
||||
}
|
||||
|
||||
// run all parts of invoice generation to see if there are no unexpected errors
|
||||
err = payments.Service.InvoiceApplyProjectRecords(ctx, period)
|
||||
err = payments.StripeService.InvoiceApplyProjectRecords(ctx, period)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = payments.Service.CreateInvoices(ctx, period)
|
||||
err = payments.StripeService.CreateInvoices(ctx, period)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@ -215,10 +215,10 @@ func TestService_ProjectsWithMembers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
satellite.API.Payments.Service.SetNow(func() time.Time {
|
||||
satellite.API.Payments.StripeService.SetNow(func() time.Time {
|
||||
return time.Date(period.Year(), period.Month()+1, 1, 0, 0, 0, 0, time.UTC)
|
||||
})
|
||||
err := satellite.API.Payments.Service.PrepareInvoiceProjectRecords(ctx, period)
|
||||
err := satellite.API.Payments.StripeService.PrepareInvoiceProjectRecords(ctx, period)
|
||||
require.NoError(t, err)
|
||||
|
||||
start := time.Date(period.Year(), period.Month(), 1, 0, 0, 0, 0, time.UTC)
|
||||
@ -281,7 +281,7 @@ func TestService_InvoiceItemsFromProjectRecord(t *testing.T) {
|
||||
Segments: tc.Segments,
|
||||
}
|
||||
|
||||
items := satellite.API.Payments.Service.InvoiceItemsFromProjectRecord("project name", record)
|
||||
items := satellite.API.Payments.StripeService.InvoiceItemsFromProjectRecord("project name", record)
|
||||
|
||||
require.Equal(t, tc.StorageQuantity, *items[0].Quantity)
|
||||
require.Equal(t, expectedStoragePrice, *items[0].UnitAmountDecimal)
|
||||
|
@ -45,7 +45,7 @@ func TestTokens_ListDepositBonuses(t *testing.T) {
|
||||
Description: stripe.String(stripecoinpayments.StripeDepositTransactionDescription),
|
||||
}
|
||||
txParams.AddMetadata("txID", txID)
|
||||
_, err = satellite.API.Payments.Stripe.CustomerBalanceTransactions().New(&txParams)
|
||||
_, err = satellite.API.Payments.StripeClient.CustomerBalanceTransactions().New(&txParams)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Credit the Stripe balance with a 13% bonus - $1.30
|
||||
@ -56,7 +56,7 @@ func TestTokens_ListDepositBonuses(t *testing.T) {
|
||||
}
|
||||
txParams.AddMetadata("txID", txID)
|
||||
txParams.AddMetadata("percentage", "13")
|
||||
bonusTx, err := satellite.API.Payments.Stripe.CustomerBalanceTransactions().New(&txParams)
|
||||
bonusTx, err := satellite.API.Payments.StripeClient.CustomerBalanceTransactions().New(&txParams)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add migrated deposit bonus to Stripe metadata
|
||||
@ -72,7 +72,7 @@ func TestTokens_ListDepositBonuses(t *testing.T) {
|
||||
customerParams.Metadata = map[string]string{
|
||||
"credit_" + credit.TransactionID.String(): string(b),
|
||||
}
|
||||
_, err = satellite.API.Payments.Stripe.Customers().Update(customerID, &customerParams)
|
||||
_, err = satellite.API.Payments.StripeClient.Customers().Update(customerID, &customerParams)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect the list to contain the 10% bonus from Stripe metadata and 13% bonus from Stripe balance
|
||||
|
@ -412,7 +412,7 @@ func TestTransactions_ApplyTransactionBalance(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the CoinPayments deposit is reflected in the Stripe customer balance.
|
||||
it := satellite.API.Payments.Stripe.CustomerBalanceTransactions().List(&stripe.CustomerBalanceTransactionListParams{Customer: stripe.String(cusID)})
|
||||
it := satellite.API.Payments.StripeClient.CustomerBalanceTransactions().List(&stripe.CustomerBalanceTransactionListParams{Customer: stripe.String(cusID)})
|
||||
require.NoError(t, it.Err())
|
||||
require.True(t, it.Next())
|
||||
cbt := it.CustomerBalanceTransaction()
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"storj.io/common/uuid"
|
||||
"storj.io/storj/private/blockchain"
|
||||
"storj.io/storj/satellite/payments/monetary"
|
||||
)
|
||||
|
||||
@ -25,6 +26,16 @@ type StorjTokens interface {
|
||||
ListDepositBonuses(ctx context.Context, userID uuid.UUID) ([]DepositBonus, error)
|
||||
}
|
||||
|
||||
// DepositWallets exposes all needed functionality to manage token deposit wallets.
|
||||
//
|
||||
// architecture: Service
|
||||
type DepositWallets interface {
|
||||
// Claim gets a new crypto wallet and associates it with a user.
|
||||
Claim(ctx context.Context, userID uuid.UUID) (blockchain.Address, error)
|
||||
// Get returns the crypto wallet address associated with the given user.
|
||||
Get(ctx context.Context, userID uuid.UUID) (blockchain.Address, error)
|
||||
}
|
||||
|
||||
// TransactionStatus defines allowed statuses
|
||||
// for deposit transactions.
|
||||
type TransactionStatus string
|
||||
|
6
scripts/testdata/satellite-config.yaml.lock
vendored
6
scripts/testdata/satellite-config.yaml.lock
vendored
@ -664,6 +664,12 @@ identity.key-path: /root/.local/share/storj/identity/satellite/identity.key
|
||||
# price user should pay for storing TB per month
|
||||
# payments.storage-tb-price: "4"
|
||||
|
||||
# payments.storjscan.credentials.private-key: ""
|
||||
|
||||
# payments.storjscan.credentials.public-key: ""
|
||||
|
||||
# payments.storjscan.endpoint: ""
|
||||
|
||||
# amount of time we wait before running next account balance update loop
|
||||
# payments.stripe-coin-payments.account-balance-update-interval: 2m0s
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user