// Copyright (C) 2019 Storj Labs, Inc. // See LICENSE for copying information. package stripecoinpayments import ( "context" "strconv" "time" "github.com/stripe/stripe-go" "storj.io/common/uuid" "storj.io/storj/satellite/payments" "storj.io/storj/satellite/payments/coinpayments" ) const ( // StripeDepositTransactionDescription is the description for Stripe // balance transactions representing STORJ deposits. StripeDepositTransactionDescription = "STORJ deposit" // StripeDepositBonusTransactionDescription is the description for Stripe // balance transactions representing bonuses received for STORJ deposits. StripeDepositBonusTransactionDescription = "STORJ deposit bonus" ) // ensure that storjTokens implements payments.StorjTokens. var _ payments.StorjTokens = (*storjTokens)(nil) // storjTokens implements payments.StorjTokens. // // architecture: Service type storjTokens struct { 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, userID uuid.UUID, amount int64) (_ *payments.Transaction, err error) { defer mon.Task()(&ctx, userID, amount)(&err) customerID, err := tokens.service.db.Customers().GetCustomerID(ctx, 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) } rate, err := tokens.service.GetRate(ctx, coinpayments.CurrencySTORJ, coinpayments.CurrencyUSD) if err != nil { return nil, Error.Wrap(err) } tokenAmount := convertFromCents(rate, amount).SetPrec(payments.STORJTokenPrecision) tx, err := tokens.service.coinPayments.Transactions().Create(ctx, &coinpayments.CreateTX{ Amount: *tokenAmount, CurrencyIn: coinpayments.CurrencySTORJ, CurrencyOut: coinpayments.CurrencySTORJ, 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) } if err = tokens.service.db.Transactions().LockRate(ctx, tx.ID, rate); err != nil { return nil, Error.Wrap(err) } cpTX, err := tokens.service.db.Transactions().Insert(ctx, Transaction{ ID: tx.ID, AccountID: userID, Address: tx.Address, Amount: tx.Amount, Status: coinpayments.StatusPending, Key: key, Timeout: tx.Timeout, }, ) if err != nil { return nil, Error.Wrap(err) } return &payments.Transaction{ ID: payments.TransactionID(tx.ID), Amount: *payments.TokenAmountFromBigFloat(&tx.Amount), Rate: *rate, Address: tx.Address, Status: payments.TransactionStatusPending, Timeout: tx.Timeout, Link: tx.CheckoutURL, CreatedAt: cpTX.CreatedAt, }, nil } // ListTransactionInfos fetches all transactions from the database for specified user, reconstructing checkout link. func (tokens *storjTokens) ListTransactionInfos(ctx context.Context, userID uuid.UUID) (_ []payments.TransactionInfo, err error) { defer mon.Task()(&ctx, userID)(&err) txs, err := tokens.service.db.Transactions().ListAccount(ctx, userID) if err != nil { return nil, Error.Wrap(err) } var infos []payments.TransactionInfo for _, tx := range txs { link := coinpayments.GetCheckoutURL(tx.Key, tx.ID) var status payments.TransactionStatus switch tx.Status { case coinpayments.StatusPending: status = payments.TransactionStatusPending case coinpayments.StatusReceived: status = payments.TransactionStatusPaid case coinpayments.StatusCancelled: status = payments.TransactionStatusCancelled default: // unknown status = payments.TransactionStatus(tx.Status.String()) } rate, err := tokens.service.db.Transactions().GetLockedRate(ctx, tx.ID) if err != nil { return nil, err } infos = append(infos, payments.TransactionInfo{ ID: []byte(tx.ID), Amount: *payments.TokenAmountFromBigFloat(&tx.Amount), Received: *payments.TokenAmountFromBigFloat(&tx.Received), AmountCents: convertToCents(rate, &tx.Amount), ReceivedCents: convertToCents(rate, &tx.Received), Address: tx.Address, Status: status, Link: link, ExpiresAt: tx.CreatedAt.Add(tx.Timeout), CreatedAt: tx.CreatedAt, }, ) } return infos, nil } // ListDepositBonuses returns all deposit bonuses from Stripe associated with user. func (tokens *storjTokens) ListDepositBonuses(ctx context.Context, userID uuid.UUID) (_ []payments.DepositBonus, err error) { defer mon.Task()(&ctx, userID)(&err) cusID, err := tokens.service.db.Customers().GetCustomerID(ctx, userID) if err != nil { return nil, err } var bonuses []payments.DepositBonus it := tokens.service.stripeClient.CustomerBalanceTransactions().List(&stripe.CustomerBalanceTransactionListParams{Customer: stripe.String(cusID)}) for it.Next() { tx := it.CustomerBalanceTransaction() if tx.Type != stripe.CustomerBalanceTransactionTypeAdjustment { continue } if tx.Description != StripeDepositBonusTransactionDescription { continue } percentage := int64(10) percentageStr, ok := tx.Metadata["percentage"] if ok { percentage, err = strconv.ParseInt(percentageStr, 10, 64) if err != nil { return nil, err } } bonuses = append(bonuses, payments.DepositBonus{ TransactionID: []byte(tx.Metadata["txID"]), AmountCents: -tx.Amount, Percentage: percentage, CreatedAt: time.Unix(tx.Created, 0), }, ) } return bonuses, nil }