This repository has been archived by the owner on Jul 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Support for subtracting negative numbers * Add command for listing balances. Calculate system balances * Scaffold topup-balance command * Implement system topup feature * Wire cmd command; fix currency ticker for internal payments * Fix test cases * Fix money.String() for negative numbers
- Loading branch information
Showing
20 changed files
with
1,052 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/olekukonko/tablewriter" | ||
"github.com/oxygenpay/oxygen/internal/app" | ||
"github.com/oxygenpay/oxygen/internal/money" | ||
"github.com/oxygenpay/oxygen/internal/service/wallet" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var listBalancesCommand = &cobra.Command{ | ||
Use: "list-balances", | ||
Short: "List all balances including system balances", | ||
Run: listBalances, | ||
} | ||
|
||
func listBalances(_ *cobra.Command, _ []string) { | ||
var ( | ||
ctx = context.Background() | ||
cfg = resolveConfig() | ||
service = app.New(ctx, cfg) | ||
walletsService = service.Locator().WalletService() | ||
blockchainService = service.Locator().BlockchainService() | ||
logger = service.Logger() | ||
) | ||
|
||
opts := wallet.ListAllBalancesOpts{ | ||
WithUSD: true, | ||
WithSystemBalances: true, | ||
HideEmpty: true, | ||
} | ||
|
||
balances, err := walletsService.ListAllBalances(ctx, opts) | ||
if err != nil { | ||
logger.Error().Err(err).Msg("Unable to list wallets") | ||
} | ||
|
||
t := tablewriter.NewWriter(os.Stdout) | ||
defer t.Render() | ||
|
||
t.SetBorder(false) | ||
t.SetAutoWrapText(false) | ||
t.SetHeaderAlignment(tablewriter.ALIGN_LEFT) | ||
t.SetAlignment(tablewriter.ALIGN_LEFT) | ||
|
||
t.SetHeader([]string{"id", "type", "entity Id", "currency", "test", "amount", "usd"}) | ||
|
||
add := func(b *wallet.Balance) { | ||
currency, err := blockchainService.GetCurrencyByTicker(b.Currency) | ||
if err != nil { | ||
logger.Error().Err(err) | ||
return | ||
} | ||
|
||
t.Append(balanceAsRow(currency, b)) | ||
} | ||
|
||
for _, b := range balances[wallet.EntityTypeMerchant] { | ||
add(b) | ||
} | ||
for _, b := range balances[wallet.EntityTypeWallet] { | ||
add(b) | ||
} | ||
for _, b := range balances[wallet.EntityTypeSystem] { | ||
add(b) | ||
} | ||
} | ||
|
||
func balanceAsRow(currency money.CryptoCurrency, b *wallet.Balance) []string { | ||
isTest := b.NetworkID != currency.NetworkID | ||
|
||
line := fmt.Sprintf( | ||
"%d,%s,%d,%s,%t,%s,%s", | ||
b.ID, | ||
b.EntityType, | ||
b.EntityID, | ||
b.Currency, | ||
isTest, | ||
b.Amount.String(), | ||
b.UsdAmount.String(), | ||
) | ||
|
||
return strings.Split(line, ",") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package cmd | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"fmt" | ||
"os" | ||
"strings" | ||
|
||
"github.com/oxygenpay/oxygen/internal/app" | ||
"github.com/oxygenpay/oxygen/internal/money" | ||
"github.com/oxygenpay/oxygen/internal/service/processing" | ||
"github.com/oxygenpay/oxygen/internal/service/wallet" | ||
"github.com/oxygenpay/oxygen/internal/util" | ||
"github.com/samber/lo" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var topupBalanceCommand = &cobra.Command{ | ||
Use: "topup-balance", | ||
Short: "Topup Merchant's balance using 'system' funds", | ||
Run: topupBalance, | ||
} | ||
|
||
var topupBalanceArgs = struct { | ||
MerchantID *int64 | ||
Ticker *string | ||
Amount *string | ||
Comment *string | ||
IsTest *bool | ||
}{ | ||
MerchantID: util.Ptr(int64(0)), | ||
Ticker: util.Ptr(""), | ||
Amount: util.Ptr(""), | ||
Comment: util.Ptr(""), | ||
IsTest: util.Ptr(false), | ||
} | ||
|
||
func topupBalance(_ *cobra.Command, _ []string) { | ||
var ( | ||
ctx = context.Background() | ||
cfg = resolveConfig() | ||
service = app.New(ctx, cfg) | ||
blockchainService = service.Locator().BlockchainService() | ||
merchantService = service.Locator().MerchantService() | ||
walletService = service.Locator().WalletService() | ||
processingService = service.Locator().ProcessingService() | ||
logger = service.Logger() | ||
exit = func(err error, message string) { logger.Fatal().Err(err).Msg(message) } | ||
) | ||
|
||
// 1. Get input | ||
currency, err := blockchainService.GetCurrencyByTicker(*topupBalanceArgs.Ticker) | ||
if err != nil { | ||
exit(err, "invalid ticker") | ||
} | ||
|
||
amount, err := money.CryptoFromStringFloat(currency.Ticker, *topupBalanceArgs.Amount, currency.Decimals) | ||
if err != nil { | ||
exit(err, "invalid amount") | ||
} | ||
|
||
merchant, err := merchantService.GetByID(ctx, *topupBalanceArgs.MerchantID, false) | ||
if err != nil { | ||
exit(err, "invalid merchant id") | ||
} | ||
|
||
if *topupBalanceArgs.Comment == "" { | ||
exit(nil, "comment should not be empty") | ||
} | ||
|
||
isTest := *topupBalanceArgs.IsTest | ||
comment := *topupBalanceArgs.Comment | ||
|
||
// 2. Locate system balance | ||
balances, err := walletService.ListAllBalances(ctx, wallet.ListAllBalancesOpts{WithSystemBalances: true}) | ||
if err != nil { | ||
exit(err, "unable to list balances") | ||
} | ||
|
||
systemBalance, found := lo.Find(balances[wallet.EntityTypeSystem], func(b *wallet.Balance) bool { | ||
tickerMatches := b.Currency == currency.Ticker | ||
networkMatches := b.NetworkID == currency.ChooseNetwork(isTest) | ||
|
||
return tickerMatches && networkMatches | ||
}) | ||
|
||
if !found { | ||
exit(err, "unable to locate system balance") | ||
} | ||
|
||
logger.Info(). | ||
Str("amount", amount.String()). | ||
Str("currency", currency.Ticker). | ||
Str("merchant.name", merchant.Name). | ||
Int64("merchant.id", merchant.ID). | ||
Str("merchant.uuid", merchant.UUID.String()). | ||
Str("system_balance", systemBalance.Amount.String()). | ||
Msg("Performing internal topup from the system balance") | ||
|
||
// 3. Confirm | ||
if !confirm("Are you sure you want to continue?") { | ||
logger.Info().Msg("Aborting.") | ||
return | ||
} | ||
|
||
// 4. Perform topup | ||
logger.Info().Msg("Sending...") | ||
|
||
input := processing.TopupInput{ | ||
Currency: currency, | ||
Amount: amount, | ||
Comment: comment, | ||
IsTest: isTest, | ||
} | ||
|
||
out, err := processingService.TopupMerchantFromSystem(ctx, merchant.ID, input) | ||
if err != nil { | ||
exit(err, "unable to topup the balance") | ||
} | ||
|
||
logger. | ||
Info(). | ||
Int64("payment.id", out.Payment.ID). | ||
Int64("tx.id", out.Transaction.ID). | ||
Str("tx.usd_amount", out.Transaction.USDAmount.String()). | ||
Str("merchant.balance", out.MerchantBalance.Amount.String()). | ||
Msg("Done") | ||
} | ||
|
||
func topupBalanceSetup(cmd *cobra.Command) { | ||
f := cmd.Flags() | ||
|
||
f.Int64Var(topupBalanceArgs.MerchantID, "merchant-id", 0, "Merchant ID") | ||
f.StringVar(topupBalanceArgs.Ticker, "ticker", "", "Ticker") | ||
f.StringVar(topupBalanceArgs.Amount, "amount", "0", "Amount") | ||
f.StringVar(topupBalanceArgs.Comment, "comment", "", "Comment") | ||
f.BoolVar(topupBalanceArgs.IsTest, "is-test", false, "Test balance") | ||
|
||
for _, name := range []string{"merchant-id", "ticker", "amount", "comment"} { | ||
if err := cmd.MarkFlagRequired(name); err != nil { | ||
panic(name + ": " + err.Error()) | ||
} | ||
} | ||
} | ||
|
||
func confirm(message string) bool { | ||
reader := bufio.NewReader(os.Stdin) | ||
fmt.Printf("%s (y/n): ", message) | ||
|
||
response, err := reader.ReadString('\n') | ||
if err != nil { | ||
return false | ||
} | ||
|
||
response = strings.ToLower(strings.TrimSpace(response)) | ||
|
||
return response == "y" || response == "yes" | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.