Skip to content

Commit

Permalink
satellite/payments: token deposit (#3283)
Browse files Browse the repository at this point in the history
  • Loading branch information
rikysya committed Oct 17, 2019
1 parent afdd340 commit 24e72f3
Show file tree
Hide file tree
Showing 21 changed files with 1,197 additions and 23 deletions.
2 changes: 2 additions & 0 deletions satellite/payments/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ type Accounts interface {

// CreditCards exposes all needed functionality to manage account credit cards.
CreditCards() CreditCards
// StorjTokens exposes all storj token related functionality.
StorjTokens() StorjTokens
}
6 changes: 2 additions & 4 deletions satellite/payments/coinpayments/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func (infos *TransactionInfos) UnmarshalJSON(b []byte) error {

// CreateTX defines parameters for transaction creating.
type CreateTX struct {
Amount float64
Amount big.Float
CurrencyIn Currency
CurrencyOut Currency
BuyerEmail string
Expand All @@ -263,10 +263,8 @@ type Transactions struct {

// Create creates new transaction.
func (t Transactions) Create(ctx context.Context, params CreateTX) (*Transaction, error) {
amount := strconv.FormatFloat(params.Amount, 'f', -1, 64)

values := make(url.Values)
values.Set("amount", amount)
values.Set("amount", params.Amount.Text('f', int(params.Amount.Prec())))
values.Set("currency1", params.CurrencyIn.String())
values.Set("currency2", params.CurrencyOut.String())
values.Set("buyer_email", params.BuyerEmail)
Expand Down
30 changes: 30 additions & 0 deletions satellite/payments/coinpayments/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package coinpayments

import (
"net/url"

"github.com/zeebo/errs"
)

// ErrNoAuthorizationKey is error that indicates that there is no authorization key.
var ErrNoAuthorizationKey = Error.New("no authorization key")

// GetTransacationKeyFromURL parses provided raw url string
// and extracts authorization key from it. Returns ErrNoAuthorizationKey if
// there is no authorization key and error if rawurl cannot be parsed.
func GetTransacationKeyFromURL(rawurl string) (string, error) {
u, err := url.Parse(rawurl)
if err != nil {
return "", errs.Wrap(err)
}

key := u.Query().Get("key")
if key == "" {
return "", ErrNoAuthorizationKey
}

return key, nil
}
13 changes: 9 additions & 4 deletions satellite/payments/stripecoinpayments/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ func (accounts *accounts) Setup(ctx context.Context, email string) (err error) {
}

if _, err := accounts.service.stripeClient.Customers.New(params); err != nil {
return ErrorStripe.Wrap(err)
return Error.Wrap(err)
}

// TODO: delete customer from stripe, if db insertion fails
return accounts.service.customers.Insert(ctx, accounts.userID, email)
return Error.Wrap(accounts.service.customers.Insert(ctx, accounts.userID, email))
}

// Balance returns an integer amount in cents that represents the current balance of payment account.
Expand All @@ -45,13 +45,18 @@ func (accounts *accounts) Balance(ctx context.Context) (_ int64, err error) {

customerID, err := accounts.service.customers.GetCustomerID(ctx, accounts.userID)
if err != nil {
return 0, err
return 0, Error.Wrap(err)
}

c, err := accounts.service.stripeClient.Customers.Get(customerID, nil)
if err != nil {
return 0, ErrorStripe.Wrap(err)
return 0, Error.Wrap(err)
}

return c.Balance, nil
}

// StorjTokens exposes all storj token related functionality.
func (accounts *accounts) StorjTokens() payments.StorjTokens {
return &storjTokens{service: accounts.service}
}
8 changes: 4 additions & 4 deletions satellite/payments/stripecoinpayments/creditcards.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (creditCards *creditCards) List(ctx context.Context) (cards []payments.Cred

customerID, err := creditCards.service.customers.GetCustomerID(ctx, creditCards.userID)
if err != nil {
return nil, err
return nil, Error.Wrap(err)
}

params := &stripe.PaymentMethodListParams{
Expand All @@ -46,7 +46,7 @@ func (creditCards *creditCards) List(ctx context.Context) (cards []payments.Cred
}

if err = paymentMethodsIterator.Err(); err != nil {
return nil, ErrorStripe.Wrap(err)
return nil, Error.Wrap(err)
}

return cards, nil
Expand All @@ -68,7 +68,7 @@ func (creditCards *creditCards) Add(ctx context.Context, cardToken string) (err

card, err := creditCards.service.stripeClient.PaymentMethods.New(cardParams)
if err != nil {
return ErrorStripe.Wrap(err)
return Error.Wrap(err)
}

attachParams := &stripe.PaymentMethodAttachParams{
Expand All @@ -78,7 +78,7 @@ func (creditCards *creditCards) Add(ctx context.Context, cardToken string) (err
_, err = creditCards.service.stripeClient.PaymentMethods.Attach(card.ID, attachParams)
if err != nil {
// TODO: handle created but not attached card manually?
return ErrorStripe.Wrap(err)
return Error.Wrap(err)
}

return nil
Expand Down
34 changes: 24 additions & 10 deletions satellite/payments/stripecoinpayments/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,45 @@ import (
"gopkg.in/spacemonkeygo/monkit.v2"

"storj.io/storj/satellite/payments"
"storj.io/storj/satellite/payments/coinpayments"
)

var mon = monkit.Package()

// ErrorStripe is stripe error type.
var ErrorStripe = errs.Class("stripe API error")
// Error defines stripecoinpayments service error.
var Error = errs.Class("stripecoinpayments service error")

// Config stores needed information for payment service initialization
// Config stores needed information for payment service initialization.
type Config struct {
secretKey string
StripeSecretKey string
CoinpaymentsPublicKey string
CoinpaymentsPrivateKey string
}

// Service is an implementation for payment service via Stripe and Coinpayments.
type Service struct {
customers CustomersDB
stripeClient *client.API
customers CustomersDB
transactionsDB TransactionsDB
stripeClient *client.API
coinpayments coinpayments.Client
}

// NewService creates a Service instance.
func NewService(config Config, customers CustomersDB) *Service {
stripeClient := client.New(config.secretKey, nil)
func NewService(config Config, customers CustomersDB, transactionsDB TransactionsDB) *Service {
stripeClient := client.New(config.StripeSecretKey, nil)

coinpaymentsClient := coinpayments.NewClient(
coinpayments.Credentials{
PublicKey: config.CoinpaymentsPublicKey,
PrivateKey: config.CoinpaymentsPrivateKey,
},
)

return &Service{
customers: customers,
stripeClient: stripeClient,
customers: customers,
transactionsDB: transactionsDB,
stripeClient: stripeClient,
coinpayments: *coinpaymentsClient,
}
}

Expand Down
83 changes: 83 additions & 0 deletions satellite/payments/stripecoinpayments/tokens.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package stripecoinpayments

import (
"context"
"math/big"

"github.com/skyrings/skyring-common/tools/uuid"

"storj.io/storj/satellite/payments"
"storj.io/storj/satellite/payments/coinpayments"
)

// ensure that storjTokens implements payments.StorjTokens.
var _ payments.StorjTokens = (*storjTokens)(nil)

// storjTokens implements payments.StorjTokens.
type storjTokens struct {
userID uuid.UUID
service *Service
}

// Deposit creates new deposit transaction with the given amount returning
// ETH wallet address where funds should be sent. There is one
// hour limit to complete the transaction. Transaction is saved to DB with
// reference to the user who made the deposit.
func (tokens *storjTokens) Deposit(ctx context.Context, amount big.Float) (_ *payments.Transaction, err error) {
defer mon.Task()(&ctx, amount)(&err)

customerID, err := tokens.service.customers.GetCustomerID(ctx, tokens.userID)
if err != nil {
return nil, Error.Wrap(err)
}

c, err := tokens.service.stripeClient.Customers.Get(customerID, nil)
if err != nil {
return nil, Error.Wrap(err)
}

tx, err := tokens.service.coinpayments.Transactions().Create(ctx,
coinpayments.CreateTX{
Amount: amount,
CurrencyIn: coinpayments.CurrencyLTCT,
CurrencyOut: coinpayments.CurrencyLTCT,
BuyerEmail: c.Email,
},
)
if err != nil {
return nil, Error.Wrap(err)
}

key, err := coinpayments.GetTransacationKeyFromURL(tx.CheckoutURL)
if err != nil {
return nil, Error.Wrap(err)
}

cpTX, err := tokens.service.transactionsDB.Insert(ctx,
Transaction{
ID: tx.ID,
AccountID: tokens.userID,
Address: tx.Address,
Amount: tx.Amount,
Received: big.Float{},
Status: coinpayments.StatusPending,
Key: key,
},
)
if err != nil {
return nil, Error.Wrap(err)
}

return &payments.Transaction{
ID: payments.TransactionID(tx.ID),
AccountID: tokens.userID,
Amount: tx.Amount,
Received: big.Float{},
Address: tx.Address,
Status: payments.TransactionStatusPending,
CreatedAt: cpTX.CreatedAt,
}, nil
}
35 changes: 35 additions & 0 deletions satellite/payments/stripecoinpayments/transactions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package stripecoinpayments

import (
"context"
"math/big"
"time"

"github.com/skyrings/skyring-common/tools/uuid"

"storj.io/storj/satellite/payments/coinpayments"
)

// TransactionsDB is an interface which defines functionality
// of DB which stores coinpayments transactions.
//
// architecture: Database
type TransactionsDB interface {
// Insert inserts new coinpayments transaction into DB.
Insert(ctx context.Context, tx Transaction) (*Transaction, error)
}

// Transaction defines coinpayments transaction info that is stored in the DB.
type Transaction struct {
ID coinpayments.TransactionID
AccountID uuid.UUID
Address string
Amount big.Float
Received big.Float
Status coinpayments.Status
Key string
CreatedAt time.Time
}
60 changes: 60 additions & 0 deletions satellite/payments/stripecoinpayments/transactions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package stripecoinpayments_test

import (
"math/big"
"testing"

"github.com/skyrings/skyring-common/tools/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"storj.io/storj/internal/testcontext"
"storj.io/storj/satellite"
"storj.io/storj/satellite/payments/coinpayments"
"storj.io/storj/satellite/payments/stripecoinpayments"
"storj.io/storj/satellite/satellitedb/satellitedbtest"
)

func TestTransactionsDB(t *testing.T) {
satellitedbtest.Run(t, func(t *testing.T, db satellite.DB) {
ctx := testcontext.New(t)
defer ctx.Cleanup()

transactions := db.CoinpaymentsTransactions()

t.Run("insert", func(t *testing.T) {
amount, received := new(big.Float).SetPrec(1000), new(big.Float).SetPrec(1000)

amount, ok := amount.SetString("2.0000000000000000005")
require.True(t, ok)
received, ok = received.SetString("1.0000000000000000003")
require.True(t, ok)

createTx := stripecoinpayments.Transaction{
ID: "testID",
AccountID: uuid.UUID{1, 2, 3},
Address: "testAddress",
Amount: *amount,
Received: *received,
Status: coinpayments.StatusReceived,
Key: "testKey",
}

tx, err := transactions.Insert(ctx, createTx)
require.NoError(t, err)
require.NotNil(t, tx)

assert.Equal(t, createTx.ID, tx.ID)
assert.Equal(t, createTx.AccountID, tx.AccountID)
assert.Equal(t, createTx.Address, tx.Address)
assert.Equal(t, createTx.Amount, tx.Amount)
assert.Equal(t, createTx.Received, tx.Received)
assert.Equal(t, createTx.Status, tx.Status)
assert.Equal(t, createTx.Key, tx.Key)
assert.False(t, tx.CreatedAt.IsZero())
})
})
}

0 comments on commit 24e72f3

Please sign in to comment.