2019-10-10 18:12:23 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package stripecoinpayments
|
|
|
|
|
|
|
|
import (
|
2019-10-23 13:04:54 +01:00
|
|
|
"context"
|
|
|
|
"time"
|
|
|
|
|
2019-10-15 12:23:54 +01:00
|
|
|
"github.com/stripe/stripe-go/client"
|
2019-10-10 18:12:23 +01:00
|
|
|
"github.com/zeebo/errs"
|
2019-10-23 13:04:54 +01:00
|
|
|
"go.uber.org/zap"
|
2019-10-17 15:42:18 +01:00
|
|
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
2019-10-15 12:23:54 +01:00
|
|
|
|
|
|
|
"storj.io/storj/satellite/payments"
|
2019-10-17 15:04:50 +01:00
|
|
|
"storj.io/storj/satellite/payments/coinpayments"
|
2019-10-10 18:12:23 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var mon = monkit.Package()
|
|
|
|
|
2019-10-17 15:04:50 +01:00
|
|
|
// Error defines stripecoinpayments service error.
|
|
|
|
var Error = errs.Class("stripecoinpayments service error")
|
2019-10-10 18:12:23 +01:00
|
|
|
|
2019-10-17 15:04:50 +01:00
|
|
|
// Config stores needed information for payment service initialization.
|
2019-10-15 12:23:54 +01:00
|
|
|
type Config struct {
|
2019-10-23 13:04:54 +01:00
|
|
|
StripeSecretKey string `help:"stripe API secret key" default:""`
|
|
|
|
CoinpaymentsPublicKey string `help:"coinpayments API public key" default:""`
|
|
|
|
CoinpaymentsPrivateKey string `help:"coinpayments API preivate key key" default:""`
|
|
|
|
TransactionUpdateInterval time.Duration `help:"amount of time we wait before running next transaction update loop" devDefault:"1m" releaseDefault:"30m"`
|
2019-10-10 18:12:23 +01:00
|
|
|
}
|
|
|
|
|
2019-10-15 12:23:54 +01:00
|
|
|
// Service is an implementation for payment service via Stripe and Coinpayments.
|
|
|
|
type Service struct {
|
2019-10-23 13:04:54 +01:00
|
|
|
log *zap.Logger
|
2019-10-17 15:04:50 +01:00
|
|
|
customers CustomersDB
|
|
|
|
transactionsDB TransactionsDB
|
|
|
|
stripeClient *client.API
|
2019-10-23 13:04:54 +01:00
|
|
|
coinPayments *coinpayments.Client
|
2019-10-10 18:12:23 +01:00
|
|
|
}
|
|
|
|
|
2019-10-15 12:23:54 +01:00
|
|
|
// NewService creates a Service instance.
|
2019-10-23 13:04:54 +01:00
|
|
|
func NewService(log *zap.Logger, config Config, customers CustomersDB, transactionsDB TransactionsDB) *Service {
|
2019-10-17 15:04:50 +01:00
|
|
|
stripeClient := client.New(config.StripeSecretKey, nil)
|
|
|
|
|
2019-10-23 13:04:54 +01:00
|
|
|
coinPaymentsClient := coinpayments.NewClient(
|
2019-10-17 15:04:50 +01:00
|
|
|
coinpayments.Credentials{
|
|
|
|
PublicKey: config.CoinpaymentsPublicKey,
|
|
|
|
PrivateKey: config.CoinpaymentsPrivateKey,
|
|
|
|
},
|
|
|
|
)
|
2019-10-10 18:12:23 +01:00
|
|
|
|
2019-10-15 12:23:54 +01:00
|
|
|
return &Service{
|
2019-10-23 13:04:54 +01:00
|
|
|
log: log,
|
2019-10-17 15:04:50 +01:00
|
|
|
customers: customers,
|
|
|
|
transactionsDB: transactionsDB,
|
|
|
|
stripeClient: stripeClient,
|
2019-10-23 13:04:54 +01:00
|
|
|
coinPayments: coinPaymentsClient,
|
2019-10-10 18:12:23 +01:00
|
|
|
}
|
|
|
|
}
|
2019-10-11 16:00:35 +01:00
|
|
|
|
2019-10-15 12:23:54 +01:00
|
|
|
// Accounts exposes all needed functionality to manage payment accounts.
|
2019-10-17 15:42:18 +01:00
|
|
|
func (service *Service) Accounts() payments.Accounts {
|
2019-10-15 12:23:54 +01:00
|
|
|
return &accounts{service: service}
|
2019-10-11 16:00:35 +01:00
|
|
|
}
|
2019-10-23 13:04:54 +01:00
|
|
|
|
|
|
|
// updateTransactionsLoop updates all pending transactions in a loop.
|
|
|
|
func (service *Service) updateTransactionsLoop(ctx context.Context) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
const (
|
|
|
|
limit = 25
|
|
|
|
)
|
|
|
|
|
|
|
|
before := time.Now()
|
|
|
|
|
|
|
|
txsPage, err := service.transactionsDB.ListPending(ctx, 0, limit, before)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := service.updateTransactions(ctx, txsPage.IDList()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for txsPage.Next {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
txsPage, err = service.transactionsDB.ListPending(ctx, txsPage.NextOffset, limit, before)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := service.updateTransactions(ctx, txsPage.IDList()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// updateTransactions updates statuses and received amount for given transactions.
|
|
|
|
func (service *Service) updateTransactions(ctx context.Context, ids coinpayments.TransactionIDList) (err error) {
|
|
|
|
defer mon.Task()(&ctx, ids)(&err)
|
|
|
|
|
|
|
|
if len(ids) == 0 {
|
|
|
|
service.log.Debug("no transactions found, skipping update")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
infos, err := service.coinPayments.Transactions().ListInfos(ctx, ids)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var updates []TransactionUpdate
|
|
|
|
for id, info := range infos {
|
|
|
|
updates = append(updates,
|
|
|
|
TransactionUpdate{
|
|
|
|
TransactionID: id,
|
|
|
|
Status: info.Status,
|
|
|
|
Received: info.Received,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO: update stripe customer balance
|
|
|
|
}
|
|
|
|
|
|
|
|
return service.transactionsDB.Update(ctx, updates)
|
|
|
|
}
|