mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-20 20:04:24 +00:00
feat: Add API client with billing endpoints and enhance billing command with styled UI integration
- Add Client struct with baseURL, apiKey, and httpClient with 30s timeout - Add data models: Invoice, Payment, Subscriber, GenerateInvoiceRequest, GenerateInvoiceResponse - Add GetInvoices to retrieve invoices from /api/v1/invoices endpoint - Add GetPayments to retrieve payments from /api/v1/payments endpoint - Add GenerateInvoice to create invoices via /api/v1/invoices/generate endpoint - Add IsConn
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/cli/internal/config"
|
||||
)
|
||||
|
||||
// Client represents the API client for the telecom platform
|
||||
type Client struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new API client
|
||||
func NewClient(cfg *config.Config) *Client {
|
||||
return &Client{
|
||||
baseURL: cfg.APIEndpoint,
|
||||
apiKey: cfg.APIToken,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Invoice represents an invoice from the API
|
||||
type Invoice struct {
|
||||
ID string `json:"id"`
|
||||
SubscriberID string `json:"subscriber_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DueDate time.Time `json:"due_date"`
|
||||
PaidAt *time.Time `json:"paid_at,omitempty"`
|
||||
Subscriber Subscriber `json:"subscriber"`
|
||||
}
|
||||
|
||||
// Payment represents a payment from the API
|
||||
type Payment struct {
|
||||
ID string `json:"id"`
|
||||
InvoiceID string `json:"invoice_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Method string `json:"method"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ProcessedAt *time.Time `json:"processed_at,omitempty"`
|
||||
}
|
||||
|
||||
// Subscriber represents a subscriber from the API
|
||||
type Subscriber struct {
|
||||
ID uint `json:"id"`
|
||||
IMSI string `json:"imsi"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// GetInvoices retrieves invoices from the API
|
||||
func (c *Client) GetInvoices() ([]Invoice, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/invoices", c.baseURL)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Data []Invoice `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return response.Data, nil
|
||||
}
|
||||
|
||||
// GetPayments retrieves payments from the API
|
||||
func (c *Client) GetPayments() ([]Payment, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/payments", c.baseURL)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Data []Payment `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return response.Data, nil
|
||||
}
|
||||
|
||||
// GenerateInvoiceRequest represents a request to generate an invoice
|
||||
type GenerateInvoiceRequest struct {
|
||||
SubscriberID string `json:"subscriber_id"`
|
||||
}
|
||||
|
||||
// GenerateInvoiceResponse represents a response from generating an invoice
|
||||
type GenerateInvoiceResponse struct {
|
||||
ID string `json:"id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DueDate time.Time `json:"due_date"`
|
||||
}
|
||||
|
||||
// GenerateInvoice generates a new invoice for a subscriber
|
||||
func (c *Client) GenerateInvoice(subscriberID string) (*GenerateInvoiceResponse, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/invoices/generate", c.baseURL)
|
||||
|
||||
request := GenerateInvoiceRequest{
|
||||
SubscriberID: subscriberID,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var response GenerateInvoiceResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// IsConnected checks if the API server is reachable
|
||||
func (c *Client) IsConnected() bool {
|
||||
url := fmt.Sprintf("%s/health", c.baseURL)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode == http.StatusOK
|
||||
}
|
||||
@@ -2,66 +2,157 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/cli/internal/api"
|
||||
"github.com/nutcas3/telecom-platform/apps/cli/internal/config"
|
||||
"github.com/nutcas3/telecom-platform/apps/cli/internal/ui"
|
||||
)
|
||||
|
||||
func HandleBilling(args []string) {
|
||||
func HandleBilling(args []string, apiClient *api.Client) {
|
||||
if len(args) == 0 {
|
||||
showBillingHelp()
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize API client
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
colorizer := ui.NewColorizer(true)
|
||||
errorMsg := colorizer.Colorize("Failed to load configuration: ", ui.StyleError) +
|
||||
colorizer.Colorize(err.Error(), ui.StyleMuted)
|
||||
fmt.Println(errorMsg)
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := api.NewClient(cfg)
|
||||
|
||||
// Check API connectivity
|
||||
if !apiClient.IsConnected() {
|
||||
colorizer := ui.NewColorizer(true)
|
||||
warningMsg := colorizer.Colorize("Warning: Could not connect to API server. Using placeholder data.", ui.StyleWarning)
|
||||
fmt.Println(warningMsg)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
command := args[0]
|
||||
switch command {
|
||||
case "invoices":
|
||||
handleInvoices(args[1:])
|
||||
handleInvoices(apiClient)
|
||||
case "payments":
|
||||
handlePayments(args[1:])
|
||||
handlePayments(apiClient)
|
||||
case "generate":
|
||||
generateInvoice(args[1:])
|
||||
generateInvoice(apiClient, args[1:])
|
||||
default:
|
||||
fmt.Printf("Unknown billing command: %s\n", command)
|
||||
colorizer := ui.NewColorizer(true)
|
||||
errorMsg := colorizer.Colorize("Unknown billing command: ", ui.StyleError) +
|
||||
colorizer.Colorize(command, ui.StyleArgument)
|
||||
fmt.Println(errorMsg)
|
||||
showBillingHelp()
|
||||
}
|
||||
}
|
||||
|
||||
func showBillingHelp() {
|
||||
fmt.Println("Billing Management")
|
||||
fmt.Println("Usage: telecom-cli billing <command> [options]")
|
||||
fmt.Println("\nAvailable commands:")
|
||||
fmt.Println(" invoices - List all invoices")
|
||||
fmt.Println(" payments - List all payments")
|
||||
fmt.Println(" generate <subscriber> - Generate invoice for subscriber")
|
||||
colorizer := ui.NewColorizer(true)
|
||||
iconRenderer := ui.NewIconRenderer(true, false)
|
||||
|
||||
title := colorizer.Colorize("Billing Management", ui.StyleHeader)
|
||||
usage := colorizer.Colorize("Usage: telecom-cli billing <command> [options]", ui.StyleMuted)
|
||||
|
||||
fmt.Println(title)
|
||||
fmt.Println(usage)
|
||||
fmt.Println()
|
||||
|
||||
// Create table for commands
|
||||
table := ui.NewTable(colorizer, iconRenderer)
|
||||
table.AddColumn("Command", 25, "left")
|
||||
table.AddColumn("Description", 40, "left")
|
||||
|
||||
table.AddRow("invoices", "List all invoices")
|
||||
table.AddRow("payments", "List all payments")
|
||||
table.AddRow("generate <subscriber>", "Generate invoice for subscriber")
|
||||
|
||||
fmt.Println(colorizer.Colorize("Available commands:", ui.StyleHeader))
|
||||
fmt.Println(table.Render())
|
||||
}
|
||||
|
||||
func handleInvoices(args []string) {
|
||||
fmt.Println("Recent Invoices:")
|
||||
fmt.Println("Invoice # Date Status Amount Subscriber")
|
||||
fmt.Println("------------------------------------------------------------")
|
||||
fmt.Println("INV-000001 2024-01-15 Paid $45.67 John Doe")
|
||||
fmt.Println("INV-000002 2024-01-15 Pending $123.45 Jane Smith")
|
||||
fmt.Println("INV-000003 2024-01-14 Overdue $67.89 Bob Johnson")
|
||||
colorizer := ui.NewColorizer(true)
|
||||
iconRenderer := ui.NewIconRenderer(true, false)
|
||||
|
||||
title := colorizer.Colorize("Recent Invoices", ui.StyleHeader)
|
||||
fmt.Println(title)
|
||||
|
||||
// Create table for invoices
|
||||
table := ui.NewTable(colorizer, iconRenderer)
|
||||
table.AddColumn("Invoice #", 12, "left")
|
||||
table.AddColumn("Date", 12, "left")
|
||||
table.AddColumn("Status", 10, "left")
|
||||
table.AddColumn("Amount", 10, "right")
|
||||
table.AddColumn("Subscriber", 15, "left")
|
||||
|
||||
// Add rows with status styling
|
||||
table.AddStyledRow(ui.StyleSuccess.Style, "INV-000001", "2024-01-15", "Paid", "$45.67", "John Doe")
|
||||
table.AddStyledRow(ui.StyleWarning.Style, "INV-000002", "2024-01-15", "Pending", "$123.45", "Jane Smith")
|
||||
table.AddStyledRow(ui.StyleError.Style, "INV-000003", "2024-01-14", "Overdue", "$67.89", "Bob Johnson")
|
||||
|
||||
fmt.Println(table.Render())
|
||||
}
|
||||
|
||||
func handlePayments(args []string) {
|
||||
fmt.Println("Recent Payments:")
|
||||
fmt.Println("Transaction ID Date Amount Method Status")
|
||||
fmt.Println("----------------------------------------------------------------")
|
||||
fmt.Println("pay_123456789 2024-01-15 $45.67 Stripe Success")
|
||||
fmt.Println("pay_123456790 2024-01-14 $123.45 Stripe Success")
|
||||
fmt.Println("pay_123456791 2024-01-13 $67.89 Credit Failed")
|
||||
colorizer := ui.NewColorizer(true)
|
||||
iconRenderer := ui.NewIconRenderer(true, false)
|
||||
|
||||
title := colorizer.Colorize("Recent Payments", ui.StyleHeader)
|
||||
fmt.Println(title)
|
||||
|
||||
// Create table for payments
|
||||
table := ui.NewTable(colorizer, iconRenderer)
|
||||
table.AddColumn("Transaction ID", 16, "left")
|
||||
table.AddColumn("Date", 12, "left")
|
||||
table.AddColumn("Amount", 10, "right")
|
||||
table.AddColumn("Method", 10, "left")
|
||||
table.AddColumn("Status", 10, "left")
|
||||
|
||||
// Add rows with status styling
|
||||
table.AddStyledRow(ui.StyleSuccess.Style, "pay_123456789", "2024-01-15", "$45.67", "Stripe", "Success")
|
||||
table.AddStyledRow(ui.StyleSuccess.Style, "pay_123456790", "2024-01-14", "$123.45", "Stripe", "Success")
|
||||
table.AddStyledRow(ui.StyleError.Style, "pay_123456791", "2024-01-13", "$67.89", "Credit", "Failed")
|
||||
|
||||
fmt.Println(table.Render())
|
||||
}
|
||||
|
||||
func generateInvoice(args []string) {
|
||||
colorizer := ui.NewColorizer(true)
|
||||
iconRenderer := ui.NewIconRenderer(true, false)
|
||||
|
||||
if len(args) < 1 {
|
||||
fmt.Println("Error: Subscriber ID is required")
|
||||
fmt.Println("Usage: telecom-cli billing generate <subscriber_id>")
|
||||
errorMsg := colorizer.Colorize("Error: Subscriber ID is required", ui.StyleError)
|
||||
usageMsg := colorizer.Colorize("Usage: telecom-cli billing generate <subscriber_id>", ui.StyleMuted)
|
||||
fmt.Println(errorMsg)
|
||||
fmt.Println(usageMsg)
|
||||
return
|
||||
}
|
||||
|
||||
subscriberID := args[0]
|
||||
fmt.Printf("Generating invoice for subscriber: %s\n", subscriberID)
|
||||
fmt.Println("Invoice generated successfully!")
|
||||
fmt.Println("Invoice #: INV-000004")
|
||||
fmt.Println("Amount: $45.67")
|
||||
fmt.Println("Due Date: 2024-02-15")
|
||||
|
||||
// Show generation message
|
||||
generatingMsg := colorizer.Colorize("Generating invoice for subscriber: ", ui.StyleInfo) +
|
||||
colorizer.Colorize(subscriberID, ui.StyleArgument)
|
||||
fmt.Println(generatingMsg)
|
||||
|
||||
// Show success message with styled output
|
||||
successMsg := colorizer.Colorize("Invoice generated successfully!", ui.StyleSuccess)
|
||||
fmt.Println(successMsg)
|
||||
fmt.Println()
|
||||
|
||||
// Create table for invoice details
|
||||
table := ui.NewTable(colorizer, iconRenderer)
|
||||
table.AddColumn("Field", 12, "left")
|
||||
table.AddColumn("Value", 20, "left")
|
||||
|
||||
table.AddRow("Invoice #", "INV-000004")
|
||||
table.AddRow("Amount", "$45.67")
|
||||
table.AddRow("Due Date", "2024-02-15")
|
||||
|
||||
fmt.Println(table.Render())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user