diff --git a/cmd/termcloud/main.go b/cmd/termcloud/main.go index 4e98a74..d4eb1ac 100644 --- a/cmd/termcloud/main.go +++ b/cmd/termcloud/main.go @@ -45,12 +45,14 @@ func main() { e.GET("/", h.RootHandler) e.POST("/api/accounts", h.CreateAccountHandler) + e.POST("/api/payments/by-account", h.CreatePaymentByAccountNumberHandler) api := e.Group("/api") api.Use(h.AuthMiddleware) api.GET("/account", h.GetAccountHandler) api.POST("/payments", h.CreatePaymentHandler) + api.POST("/topup", h.CreateTopUpHandler) api.GET("/payments", h.GetPaymentsHandler) api.GET("/payments/:id", h.GetPaymentHandler) api.POST("/payments/:id/confirm", h.ConfirmPaymentHandler) diff --git a/internal/cli/account.go b/internal/cli/account.go index fb46574..94d7f9f 100644 --- a/internal/cli/account.go +++ b/internal/cli/account.go @@ -47,14 +47,17 @@ func newAccountCreateCmd() *cobra.Command { } config.AccountNumber = account.AccountNumber + config.AccessToken = account.AccessToken if err := config.Save(); err != nil { return fmt.Errorf("failed to save config: %w", err) } fmt.Printf("Account created successfully!\n") fmt.Printf("Account Number: %s\n", account.AccountNumber) + fmt.Printf("Access Token: %s\n", account.AccessToken) fmt.Printf("Status: %s\n", getAccountStatus(account)) - fmt.Printf("\nTo activate your account, add funds using: tcman account top-up\n") + fmt.Printf("\nIMPORTANT: Save your account number and access token!\n") + fmt.Printf("You are now logged in. Add funds using: tcman account top-up 1\n") return nil }, @@ -142,17 +145,18 @@ func newAccountInfoCmd() *cobra.Command { func newAccountTopUpCmd() *cobra.Command { return &cobra.Command{ - Use: "top-up ", + Use: "top-up [account-number]", Short: "Add funds to your account via Bitcoin", - Args: cobra.ExactArgs(1), + Long: "Add funds to your account. If logged in, uses your account. If not logged in, provide account number.", + Args: cobra.RangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { amount, err := strconv.ParseFloat(args[0], 64) if err != nil { return fmt.Errorf("invalid amount: %w", err) } - if amount < 5.0 { - return fmt.Errorf("minimum top-up amount is $5.00 USD") + if amount < 1.0 { + return fmt.Errorf("minimum top-up amount is $1.00 USD") } config, err := LoadConfig() @@ -160,20 +164,29 @@ func newAccountTopUpCmd() *cobra.Command { return fmt.Errorf("failed to load config: %w", err) } - if !config.IsAuthenticated() { - return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'") + var accountNumber string + + if len(args) == 2 { + accountNumber = args[1] + } else if config.IsAuthenticated() { + accountNumber = config.AccountNumber + } else if config.AccountNumber != "" { + accountNumber = config.AccountNumber + } else { + return fmt.Errorf("account number required. Usage: tcman account top-up ") } - client := NewClient(config.ServerURL, config.AccessToken) + client := NewClient(config.ServerURL, "") ctx := context.Background() - payment, err := client.CreatePayment(ctx, amount) + payment, err := client.CreatePaymentByAccountNumber(ctx, accountNumber, amount) if err != nil { return fmt.Errorf("failed to create payment: %w", err) } fmt.Printf("Bitcoin Payment Created:\n") fmt.Printf(" Payment ID: %d\n", payment.ID) + fmt.Printf(" Account: %s\n", accountNumber) fmt.Printf(" Amount: $%.2f USD (%.8f BTC)\n", payment.USDAmount, payment.BTCAmount) fmt.Printf(" Address: %s\n", payment.BTCAddress) fmt.Printf(" Status: %s\n", strings.ToUpper(payment.Status)) @@ -181,7 +194,7 @@ func newAccountTopUpCmd() *cobra.Command { fmt.Printf(" Expires: %s\n", payment.ExpiresAt.Format("2006-01-02 15:04:05")) } fmt.Printf("\nSend exactly %.8f BTC to the address above.\n", payment.BTCAmount) - fmt.Printf("Check payment status with: tcman account payments\n") + fmt.Printf("Your payment will be confirmed manually after Bitcoin is received.\n") return nil }, diff --git a/internal/cli/client.go b/internal/cli/client.go index 64eda73..356a767 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -19,6 +19,7 @@ type Client struct { type Account struct { ID int64 `json:"id"` AccountNumber string `json:"accountNumber"` + AccessToken string `json:"accessToken,omitempty"` BalanceUSD float64 `json:"balanceUsd"` IsActive bool `json:"isActive"` CreatedAt time.Time `json:"createdAt"` @@ -134,6 +135,16 @@ func (c *Client) CreatePayment(ctx context.Context, usdAmount float64) (*Payment return &payment, err } +func (c *Client) CreatePaymentByAccountNumber(ctx context.Context, accountNumber string, usdAmount float64) (*Payment, error) { + req := map[string]interface{}{ + "account_number": accountNumber, + "usd_amount": usdAmount, + } + var payment Payment + err := c.makeRequest(ctx, "POST", "/api/payments/by-account", req, &payment) + return &payment, err +} + func (c *Client) GetPayment(ctx context.Context, paymentID int64) (*Payment, error) { var payment Payment path := fmt.Sprintf("/api/payments/%d", paymentID) diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 11c346f..26eca47 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -62,6 +62,28 @@ func (h *Handlers) CreateAccountHandler(c echo.Context) error { return c.JSON(201, account) } +func (h *Handlers) CreatePaymentByAccountNumberHandler(c echo.Context) error { + var req struct { + AccountNumber string `json:"account_number"` + USDAmount float64 `json:"usd_amount"` + } + if err := c.Bind(&req); err != nil || req.USDAmount < 1.0 || req.AccountNumber == "" { + return c.JSON(400, map[string]string{"error": "Account number required and minimum payment amount is $1.00 USD"}) + } + + account, err := h.accountService.GetAccountByNumber(context.Background(), req.AccountNumber) + if err != nil { + return c.JSON(404, map[string]string{"error": "Account not found"}) + } + + payment, err := h.accountService.CreatePayment(context.Background(), account.ID, req.USDAmount) + if err != nil { + return c.JSON(500, map[string]string{"error": "Failed to create payment"}) + } + + return c.JSON(201, payment) +} + func (h *Handlers) GetAccountHandler(c echo.Context) error { account := c.Get("account").(*db.Account) return c.JSON(200, account) @@ -85,6 +107,24 @@ func (h *Handlers) CreatePaymentHandler(c echo.Context) error { return c.JSON(201, payment) } +func (h *Handlers) CreateTopUpHandler(c echo.Context) error { + account := c.Get("account").(*db.Account) + + var req struct { + USDAmount float64 `json:"usd_amount"` + } + if err := c.Bind(&req); err != nil || req.USDAmount < 1.0 { + return c.JSON(400, map[string]string{"error": "Minimum top-up amount is $1.00 USD"}) + } + + payment, err := h.accountService.CreatePayment(context.Background(), account.ID, req.USDAmount) + if err != nil { + return c.JSON(500, map[string]string{"error": "Failed to create top-up"}) + } + + return c.JSON(201, payment) +} + func (h *Handlers) GetPaymentHandler(c echo.Context) error { paymentID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil {