github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/cmd/commands/cli/command_orders.go (about) 1 /* 2 * Copyright (C) 2021 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package cli 19 20 import ( 21 "encoding/json" 22 "errors" 23 "fmt" 24 "os" 25 "strconv" 26 "strings" 27 28 "github.com/mysteriumnetwork/node/config/remote" 29 "github.com/mysteriumnetwork/payments/exchange" 30 31 "github.com/mysteriumnetwork/node/cmd/commands/cli/clio" 32 "github.com/mysteriumnetwork/node/config" 33 "github.com/mysteriumnetwork/node/identity" 34 "github.com/mysteriumnetwork/node/tequilapi/contract" 35 ) 36 37 func (c *cliApp) order(args []string) (err error) { 38 var usage = strings.Join([]string{ 39 "Usage: order <action> [args]", 40 "Available actions:", 41 " " + usageOrderGateways, 42 " " + usageOrderCreate, 43 " " + usageOrderGet, 44 " " + usageOrderGetAll, 45 }, "\n") 46 47 if len(args) == 0 { 48 clio.Info(usage) 49 return errWrongArgumentCount 50 } 51 52 action := args[0] 53 actionArgs := args[1:] 54 55 switch action { 56 case "create": 57 return c.orderCreate(actionArgs) 58 case "get": 59 return c.orderGet(actionArgs) 60 case "get-all": 61 return c.orderGetAll(actionArgs) 62 case "invoice": 63 return c.invoice(actionArgs) 64 case "gateways": 65 return c.gateways(actionArgs) 66 default: 67 fmt.Println(usage) 68 return errUnknownSubCommand(args[0]) 69 } 70 } 71 72 const usageOrderGateways = "gateways" 73 74 func (c *cliApp) gateways(args []string) (err error) { 75 if len(args) > 0 { 76 clio.Info("Usage: " + usageOrderGateways) 77 return 78 } 79 80 resp, err := c.tequilapi.PaymentOrderGateways(exchange.CurrencyMYST) 81 if err != nil { 82 return fmt.Errorf("could not get currencies: %w", err) 83 } 84 85 for _, gw := range resp { 86 clio.Info("Gateway:", gw.Name) 87 clio.Info("Suggested minimum order:", gw.OrderOptions.Minimum) 88 clio.Info("Supported currencies:", strings.Join(gw.Currencies, ", ")) 89 } 90 91 return nil 92 } 93 94 const usageOrderCreate = "create <identity> <amount> <pay currency> <gateway> <country> [gw data: lightning_network=true,order=123]" 95 96 func (c *cliApp) orderCreate(args []string) (err error) { 97 if len(args) != 5 && len(args) != 6 { 98 clio.Info("Usage: " + usageOrderCreate) 99 return errWrongArgumentCount 100 } 101 102 argId := args[0] 103 argAmount := args[1] 104 argPayCurrency := args[2] 105 argGateway := args[3] 106 argCountry := args[4] 107 var argCallerData string 108 if len(args) > 5 { 109 argCallerData = args[5] 110 } 111 112 f, err := strconv.ParseFloat(argAmount, 64) 113 if err != nil { 114 return fmt.Errorf("could not parse amount: %w", err) 115 } 116 if f <= 0 { 117 return errors.New("top-up amount is required and must be greater than 0") 118 } 119 120 gws, err := c.tequilapi.PaymentOrderGateways(exchange.CurrencyMYST) 121 if err != nil { 122 clio.Info("Failed to get enabled gateways and their information") 123 return 124 } 125 126 if len(gws) == 0 { 127 clio.Warn("No payment gateways are enabled, can't create new orders") 128 return 129 } 130 131 gw, ok := findGateway(argGateway, gws) 132 if !ok { 133 clio.Error("Can't continue, no such gateway:", argGateway) 134 return 135 } 136 if gw.OrderOptions.Minimum != 0 && f <= gw.OrderOptions.Minimum { 137 return fmt.Errorf( 138 "top-up amount must be greater than %v%s", 139 gw.OrderOptions.Minimum, 140 config.GetString(config.FlagDefaultCurrency)) 141 } 142 143 data := map[string]interface{}{} 144 parts := strings.Split(argCallerData, ",") 145 for _, part := range parts { 146 kv := strings.Split(part, "=") 147 if len(kv) != 2 { 148 clio.Error("Gateway data wrong, example: lightning_network=true,custom_id=\"123 11\"") 149 return 150 } 151 152 if b, err := strconv.ParseBool(kv[1]); err == nil { 153 data[kv[0]] = b 154 continue 155 } 156 157 if b, err := strconv.ParseFloat(kv[1], 64); err == nil { 158 data[kv[0]] = b 159 continue 160 } 161 162 data[kv[0]] = kv[1] 163 } 164 165 callerData, err := json.Marshal(data) 166 if err != nil { 167 clio.Error("Failed to make caller data") 168 return 169 } 170 171 resp, err := c.tequilapi.OrderCreate( 172 identity.FromAddress(argId), 173 argGateway, 174 contract.PaymentOrderRequest{ 175 MystAmount: argAmount, 176 PayCurrency: argPayCurrency, 177 Country: argCountry, 178 CallerData: callerData, 179 }) 180 if err != nil { 181 return fmt.Errorf("could not create an order: %w", err) 182 } 183 184 printOrder(resp, c.config) 185 return nil 186 } 187 188 const usageOrderGet = "get <identity> <orderID>" 189 190 func (c *cliApp) orderGet(args []string) (err error) { 191 if len(args) != 2 { 192 clio.Info("Usage: " + usageOrderGet) 193 return 194 } 195 196 resp, err := c.tequilapi.OrderGet(identity.FromAddress(args[0]), args[1]) 197 if err != nil { 198 return fmt.Errorf("could not get an order: %w", err) 199 } 200 printOrder(resp, c.config) 201 return nil 202 } 203 204 const usageOrderGetAll = "get-all <identity>" 205 206 func (c *cliApp) orderGetAll(args []string) (err error) { 207 if len(args) != 1 { 208 clio.Info("Usage: " + usageOrderGetAll) 209 return errWrongArgumentCount 210 } 211 212 resp, err := c.tequilapi.OrderGetAll(identity.FromAddress(args[0])) 213 if err != nil { 214 return fmt.Errorf("could not get orders: %w", err) 215 } 216 217 if len(resp) == 0 { 218 clio.Info("No orders found") 219 return nil 220 } 221 222 for _, r := range resp { 223 clio.Info(fmt.Sprintf("Order ID '%s' is in state: '%s'", r.ID, r.Status)) 224 } 225 clio.Info( 226 fmt.Sprintf("To explore additional order information use: orders '%s'", usageOrderGet), 227 ) 228 return nil 229 } 230 231 const usageOrderInvoice = "invoice <identity> <orderID>" 232 233 func (c *cliApp) invoice(args []string) (err error) { 234 if len(args) != 2 { 235 clio.Info("Usage: " + usageOrderInvoice) 236 return 237 } 238 239 resp, err := c.tequilapi.OrderInvoice(identity.FromAddress(args[0]), args[1]) 240 if err != nil { 241 return fmt.Errorf("could not get an order invoice: %w", err) 242 } 243 filename := fmt.Sprintf("invoice-%v.pdf", args[1]) 244 clio.Info("Writing invoice to", filename) 245 return os.WriteFile(filename, resp, 0644) 246 } 247 248 func printOrder(o contract.PaymentOrderResponse, rc *remote.Config) { 249 clio.Info(fmt.Sprintf("Order ID '%s' is in state: '%s'", o.ID, o.Status)) 250 clio.Info(fmt.Sprintf("Pay: %s %s", o.PayAmount, o.PayCurrency)) 251 clio.Info(fmt.Sprintf("Receive MYST: %s", o.ReceiveMYST)) 252 clio.Info("Order details:") 253 clio.Info(fmt.Sprintf(" + MYST (%v): %s %s", o.ReceiveMYST, o.ItemsSubTotal, o.Currency)) 254 clio.Info(fmt.Sprintf(" + Tax (%s %%): %s %s", o.TaxRate, o.TaxSubTotal, o.Currency)) 255 clio.Info(fmt.Sprintf(" = Total: %s %s", o.OrderTotal, o.Currency)) 256 clio.Info("Data:", string(o.PublicGatewayData)) 257 } 258 259 func findGateway(name string, gws []contract.GatewaysResponse) (*contract.GatewaysResponse, bool) { 260 for _, gw := range gws { 261 if gw.Name == name { 262 return &gw, true 263 } 264 } 265 return nil, false 266 }