2020-06-12 15:47:16 +01:00
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"time"
|
|
|
|
|
2021-07-30 23:11:36 +01:00
|
|
|
"github.com/spf13/cobra"
|
2020-06-12 15:47:16 +01:00
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"storj.io/common/uuid"
|
2021-07-30 23:11:36 +01:00
|
|
|
"storj.io/private/process"
|
2020-06-12 15:47:16 +01:00
|
|
|
"storj.io/storj/satellite"
|
2023-04-19 20:48:36 +01:00
|
|
|
"storj.io/storj/satellite/analytics"
|
2023-04-06 12:41:14 +01:00
|
|
|
"storj.io/storj/satellite/payments/stripe"
|
2020-06-12 15:47:16 +01:00
|
|
|
"storj.io/storj/satellite/satellitedb"
|
|
|
|
)
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
func runBillingCmd(ctx context.Context, cmdFunc func(context.Context, *stripe.Service, satellite.DB) error) error {
|
2020-06-12 15:47:16 +01:00
|
|
|
// Open SatelliteDB for the Payment Service
|
|
|
|
logger := zap.L()
|
2020-12-04 10:24:39 +00:00
|
|
|
db, err := satellitedb.Open(ctx, logger.Named("db"), runCfg.Database, satellitedb.Options{ApplicationName: "satellite-billing"})
|
2020-06-12 15:47:16 +01:00
|
|
|
if err != nil {
|
|
|
|
return errs.New("error connecting to master database on satellite: %+v", err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
err = errs.Combine(err, db.Close())
|
|
|
|
}()
|
|
|
|
|
|
|
|
payments, err := setupPayments(logger, db)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-03-04 22:55:48 +00:00
|
|
|
return cmdFunc(ctx, payments, db)
|
2020-06-12 15:47:16 +01:00
|
|
|
}
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
func setupPayments(log *zap.Logger, db satellite.DB) (*stripe.Service, error) {
|
2020-06-12 15:47:16 +01:00
|
|
|
pc := runCfg.Payments
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
var stripeClient stripe.Client
|
2020-06-15 11:54:42 +01:00
|
|
|
switch pc.Provider {
|
2023-03-03 18:10:01 +00:00
|
|
|
case "": // just new mock, only used in testing binaries
|
2023-04-06 12:41:14 +01:00
|
|
|
stripeClient = stripe.NewStripeMock(
|
2020-10-08 18:14:09 +01:00
|
|
|
db.StripeCoinPayments().Customers(),
|
|
|
|
db.Console().Users(),
|
|
|
|
)
|
2020-06-15 11:54:42 +01:00
|
|
|
case "stripecoinpayments":
|
2023-04-06 12:41:14 +01:00
|
|
|
stripeClient = stripe.NewStripeClient(log, pc.StripeCoinPayments)
|
2023-03-03 18:10:01 +00:00
|
|
|
default:
|
|
|
|
return nil, errs.New("invalid stripe coin payments provider %q", pc.Provider)
|
2020-06-15 11:54:42 +01:00
|
|
|
}
|
2020-06-12 15:47:16 +01:00
|
|
|
|
2022-12-01 07:40:52 +00:00
|
|
|
prices, err := pc.UsagePrice.ToModel()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
priceOverrides, err := pc.UsagePriceOverrides.ToModels()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
return stripe.NewService(
|
2020-06-12 15:47:16 +01:00
|
|
|
log.Named("payments.stripe:service"),
|
|
|
|
stripeClient,
|
|
|
|
pc.StripeCoinPayments,
|
|
|
|
db.StripeCoinPayments(),
|
2022-05-10 20:19:53 +01:00
|
|
|
db.Wallets(),
|
|
|
|
db.Billing(),
|
2020-06-12 15:47:16 +01:00
|
|
|
db.Console().Projects(),
|
2023-01-30 22:11:12 +00:00
|
|
|
db.Console().Users(),
|
2020-06-12 15:47:16 +01:00
|
|
|
db.ProjectAccounting(),
|
2022-12-01 07:40:52 +00:00
|
|
|
prices,
|
|
|
|
priceOverrides,
|
2023-01-30 22:11:12 +00:00
|
|
|
pc.PackagePlans.Packages,
|
2023-04-19 20:48:36 +01:00
|
|
|
pc.BonusRate,
|
|
|
|
analytics.NewService(log.Named("analytics:service"), runCfg.Analytics, runCfg.Console.SatelliteName),
|
|
|
|
)
|
2020-06-12 15:47:16 +01:00
|
|
|
}
|
|
|
|
|
2022-10-04 15:18:59 +01:00
|
|
|
// parseYearMonth parses year and month from the provided string and returns a corresponding time.Time for the first day
|
|
|
|
// of the month. The input year and month should be iso8601 format (yyyy-mm).
|
|
|
|
func parseYearMonth(yearMonth string) (time.Time, error) {
|
|
|
|
// parse using iso8601 yyyy-mm
|
|
|
|
t, err := time.Parse("2006-01", yearMonth)
|
2020-06-12 15:47:16 +01:00
|
|
|
if err != nil {
|
2022-10-04 15:18:59 +01:00
|
|
|
return time.Time{}, errs.New("invalid date specified. accepted format is yyyy-mm: %v", err)
|
2020-06-12 15:47:16 +01:00
|
|
|
}
|
2022-10-04 15:18:59 +01:00
|
|
|
return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC), nil
|
2020-06-12 15:47:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// userData contains the uuid and email of a satellite user.
|
|
|
|
type userData struct {
|
2021-10-26 14:30:19 +01:00
|
|
|
ID uuid.UUID
|
|
|
|
Email string
|
|
|
|
SignupPromoCode string
|
2020-06-12 15:47:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// generateStripeCustomers creates missing stripe-customers for users in our database.
|
|
|
|
func generateStripeCustomers(ctx context.Context) (err error) {
|
2023-04-06 12:41:14 +01:00
|
|
|
return runBillingCmd(ctx, func(ctx context.Context, payments *stripe.Service, db satellite.DB) error {
|
2020-06-12 15:47:16 +01:00
|
|
|
accounts := payments.Accounts()
|
|
|
|
|
2021-03-04 22:55:48 +00:00
|
|
|
cusDB := db.StripeCoinPayments().Customers().Raw()
|
|
|
|
|
2022-02-22 14:41:20 +00:00
|
|
|
rows, err := cusDB.Query(ctx, "SELECT id, email, signup_promo_code FROM users WHERE id NOT IN (SELECT user_id FROM stripe_customers) AND users.status=1")
|
2020-06-12 15:47:16 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
err = errs.Combine(err, rows.Close())
|
|
|
|
}()
|
|
|
|
|
|
|
|
var n int64
|
|
|
|
for rows.Next() {
|
|
|
|
n++
|
|
|
|
var user userData
|
|
|
|
err := rows.Scan(&user.ID, &user.Email)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-10-26 14:30:19 +01:00
|
|
|
_, err = accounts.Setup(ctx, user.ID, user.Email, user.SignupPromoCode)
|
2020-06-12 15:47:16 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
zap.L().Info("Ensured Stripe-Customer", zap.Int64("created", n))
|
|
|
|
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
2021-07-07 20:59:19 +01:00
|
|
|
|
2021-07-30 23:11:36 +01:00
|
|
|
func cmdApplyFreeTierCoupons(cmd *cobra.Command, args []string) (err error) {
|
|
|
|
ctx, _ := process.Ctx(cmd)
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
return runBillingCmd(ctx, func(ctx context.Context, payments *stripe.Service, _ satellite.DB) error {
|
2021-07-30 23:11:36 +01:00
|
|
|
return payments.ApplyFreeTierCoupons(ctx)
|
|
|
|
})
|
|
|
|
}
|