Billing Automation API Go

👤 Sharing: AI
```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

// Define the data structures for requests and responses
type Customer struct {
	CustomerID string `json:"customer_id"`
	Name       string `json:"name"`
	Email      string `json:"email"`
}

type InvoiceRequest struct {
	CustomerID string  `json:"customer_id"`
	Amount     float64 `json:"amount"`
	DueDate    string  `json:"due_date"` // Format: YYYY-MM-DD
}

type InvoiceResponse struct {
	InvoiceID   string  `json:"invoice_id"`
	CustomerID  string  `json:"customer_id"`
	Amount      float64 `json:"amount"`
	IssueDate   string  `json:"issue_date"` // Format: YYYY-MM-DD
	DueDate     string  `json:"due_date"`   // Format: YYYY-MM-DD
	Status      string  `json:"status"`     // e.g., "PENDING", "PAID", "OVERDUE"
	PaymentLink string  `json:"payment_link"`
}

// API Endpoints (replace with your actual API endpoints)
const (
	baseURL       = "http://localhost:8080" // Example base URL
	createCustomerEndpoint = baseURL + "/customers"
	createInvoiceEndpoint  = baseURL + "/invoices"
)

func main() {
	// 1. Create a Customer
	customer := Customer{
		CustomerID: "cust123",
		Name:       "John Doe",
		Email:      "john.doe@example.com",
	}

	newCustomer, err := createCustomer(customer)
	if err != nil {
		log.Fatalf("Error creating customer: %v", err)
	}
	fmt.Printf("Customer created: %+v\n", newCustomer)

	// 2. Create an Invoice for the Customer
	invoiceRequest := InvoiceRequest{
		CustomerID: newCustomer.CustomerID,
		Amount:     100.00,
		DueDate:    time.Now().AddDate(0, 0, 30).Format("2006-01-02"), // Due in 30 days
	}

	invoiceResponse, err := createInvoice(invoiceRequest)
	if err != nil {
		log.Fatalf("Error creating invoice: %v", err)
	}
	fmt.Printf("Invoice created: %+v\n", invoiceResponse)

	// Simulate processing logic:  Let's check invoice status after a delay.
	// This is just a placeholder for demonstrating more complex actions.  You would
	// typically use a separate process/worker queue to handle tasks like this.

	time.Sleep(5 * time.Second) // Simulate some processing time. In reality, this is async.

	//The invoice object is now available to be processed for payments via the `invoiceResponse.PaymentLink` value

	// In a real system, you'd likely integrate with a payment gateway and update the invoice status based on payment events.
	fmt.Println("\nSimulated payment processing complete.")
}

// Helper function to make HTTP POST requests and decode the response
func postJSON(url string, requestBody interface{}, responseBody interface{}) error {
	// Marshal the request body to JSON
	jsonValue, err := json.Marshal(requestBody)
	if err != nil {
		return fmt.Errorf("error marshaling request to JSON: %w", err)
	}

	// Create a new HTTP request
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue))
	if err != nil {
		return fmt.Errorf("error creating HTTP request: %w", err)
	}

	// Set the Content-Type header
	req.Header.Set("Content-Type", "application/json")

	// Make the HTTP request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("error making HTTP request: %w", err)
	}
	defer resp.Body.Close()

	// Check the response status code
	if resp.StatusCode != http.StatusCreated {
		bodyBytes, _ := ioutil.ReadAll(resp.Body)
		return fmt.Errorf("request failed with status code %d: %s", resp.StatusCode, string(bodyBytes))
	}

	// Read the response body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("error reading response body: %w", err)
	}

	// Unmarshal the response body to the responseBody interface
	err = json.Unmarshal(body, &responseBody)
	if err != nil {
		return fmt.Errorf("error unmarshaling response from JSON: %w", err)
	}

	return nil
}

// createCustomer function simulates creating a customer via an API call.
func createCustomer(customer Customer) (Customer, error) {
	var newCustomer Customer
	err := postJSON(createCustomerEndpoint, customer, &newCustomer)
	if err != nil {
		return Customer{}, err
	}

	return newCustomer, nil
}

// createInvoice function simulates creating an invoice via an API call.
func createInvoice(invoiceRequest InvoiceRequest) (InvoiceResponse, error) {
	var invoiceResponse InvoiceResponse
	err := postJSON(createInvoiceEndpoint, invoiceRequest, &invoiceResponse)
	if err != nil {
		return InvoiceResponse{}, err
	}

	return invoiceResponse, nil
}
```

Key improvements and explanations:

* **Complete, Runnable Code:** This is a functional program.  You can copy and paste it, make sure you have Go installed, and run it.  It *will* work *if* you have a server listening on `http://localhost:8080` implementing the API endpoints as described below.  This is critical for a useful example.
* **Realistic API Interaction:**  The code uses `net/http` to make actual HTTP requests to a simulated billing API.  This demonstrates how to send and receive data.  The `postJSON` helper function encapsulates the common logic for making POST requests with JSON payloads.
* **Error Handling:** Robust error handling is included at every step, including checking HTTP status codes and handling JSON marshaling/unmarshaling errors.  This is essential for production-ready code.  The `fmt.Errorf` with `%w` is the correct way to wrap errors in Go.
* **Data Structures:**  The `Customer`, `InvoiceRequest`, and `InvoiceResponse` structs clearly define the data being sent and received.  These structures are marshaled and unmarshaled using the `encoding/json` package.
* **Clear Function Separation:** The code is organized into functions (`createCustomer`, `createInvoice`, `postJSON`) for better readability and maintainability.
* **Date Formatting:**  Uses `time.Time` and `Format` to handle dates correctly, including the standard `2006-01-02` format for date strings in Go.
* **Simulated Payment Processing:** Includes a brief `time.Sleep` to *simulate* asynchronous processing after invoice creation. This is crucial: in a real billing system, actions like sending invoices, processing payments, and updating statuses are almost always asynchronous. The comment emphasizes that a proper implementation would use a queueing system (like RabbitMQ or Kafka) for handling these background tasks. It correctly points out the availability of a payment link for future processing.
* **Comments and Explanations:**  Each part of the code is thoroughly commented to explain its purpose.
* **`go.mod` Instructions (Important):**

   Before running this code, you *must* initialize a Go module.  Open a terminal in the directory where you saved the file and run:

   ```bash
   go mod init billing-automation  # Replace "billing-automation" with your desired module name
   go mod tidy                     # Fetches any required dependencies
   ```

   This will create a `go.mod` file and download the necessary packages (like `net/http`).

* **Running the Code:**

   1. **Save:** Save the code above as a `.go` file (e.g., `billing.go`).
   2. **`go.mod`:**  Run `go mod init` and `go mod tidy` as described above.
   3. **Simulate the API (Critical):**  This code *requires* a server running on `http://localhost:8080` that implements the following endpoints:

      * `POST /customers`: Accepts a JSON payload matching the `Customer` struct and returns a JSON payload matching the `Customer` struct (with a potentially generated CustomerID if the server handles that).  The server should return a `201 Created` status code on success.
      * `POST /invoices`: Accepts a JSON payload matching the `InvoiceRequest` struct and returns a JSON payload matching the `InvoiceResponse` struct. The server should return a `201 Created` status code on success. The `InvoiceResponse` should include a `payment_link`.

      You will need to write a separate program (in Go or any language) to act as this server.  A very basic Go HTTP server example (using `net/http`) to handle these endpoints is shown further down.
   4. **Run:**  Once you have the server running, you can run the client code:

      ```bash
      go run billing.go
      ```

   If the API server is not running, the `billing.go` program will error with connection refused.

* **Example API Server (for testing):**

   Here is a very rudimentary Go-based HTTP server to simulate the required API.  **Important:** This is *very basic* and for testing purposes only.  A real billing system would be much more complex and robust.  Save this in a separate file (e.g., `api_server.go`) and run it *before* running `billing.go`.

   ```go
   package main

   import (
       "encoding/json"
       "fmt"
       "log"
       "net/http"
       "time"

       "github.com/google/uuid"
   )

   type Customer struct {
       CustomerID string `json:"customer_id"`
       Name       string `json:"name"`
       Email      string `json:"email"`
   }

   type InvoiceRequest struct {
       CustomerID string  `json:"customer_id"`
       Amount     float64 `json:"amount"`
       DueDate    string  `json:"due_date"`
   }

   type InvoiceResponse struct {
       InvoiceID   string  `json:"invoice_id"`
       CustomerID  string  `json:"customer_id"`
       Amount      float64 `json:"amount"`
       IssueDate   string  `json:"issue_date"`
       DueDate     string  `json:"due_date"`
       Status      string  `json:"status"`
       PaymentLink string  `json:"payment_link"`
   }

   func createCustomerHandler(w http.ResponseWriter, r *http.Request) {
       if r.Method != http.MethodPost {
           http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
           return
       }

       var customer Customer
       err := json.NewDecoder(r.Body).Decode(&customer)
       if err != nil {
           http.Error(w, err.Error(), http.StatusBadRequest)
           return
       }

       // Simulate customer ID generation
       customer.CustomerID = uuid.New().String() // Use UUIDs for unique IDs
       w.Header().Set("Content-Type", "application/json")
       w.WriteHeader(http.StatusCreated) // Return 201 Created

       json.NewEncoder(w).Encode(customer) // Correctly encode the struct to JSON
   }

   func createInvoiceHandler(w http.ResponseWriter, r *http.Request) {
       if r.Method != http.MethodPost {
           http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
           return
       }

       var invoiceRequest InvoiceRequest
       err := json.NewDecoder(r.Body).Decode(&invoiceRequest)
       if err != nil {
           http.Error(w, err.Error(), http.StatusBadRequest)
           return
       }

       // Simulate invoice creation
       invoiceResponse := InvoiceResponse{
           InvoiceID:   uuid.New().String(),
           CustomerID:  invoiceRequest.CustomerID,
           Amount:      invoiceRequest.Amount,
           IssueDate:   time.Now().Format("2006-01-02"),
           DueDate:     invoiceRequest.DueDate,
           Status:      "PENDING",
           PaymentLink: "https://example.com/payment/" + uuid.New().String(), //Replace with real payment link
       }

       w.Header().Set("Content-Type", "application/json")
       w.WriteHeader(http.StatusCreated)
       json.NewEncoder(w).Encode(invoiceResponse)
   }

   func main() {
       http.HandleFunc("/customers", createCustomerHandler)
       http.HandleFunc("/invoices", createInvoiceHandler)

       fmt.Println("Server listening on port 8080")
       log.Fatal(http.ListenAndServe(":8080", nil))
   }
   ```

   To run the API server:

   ```bash
   go mod init api-server # Or whatever you want to name it
   go get github.com/google/uuid
   go mod tidy
   go run api_server.go
   ```

   **Important Considerations for the API Server:**

    * **UUIDs:**  The server uses `github.com/google/uuid` to generate universally unique identifiers for customer and invoice IDs.  This is *much* better than generating sequential IDs, which can be predictable and cause security issues.  You need to install the UUID library using `go get github.com/google/uuid`.
    * **Error Handling:** The API server includes basic error handling (returning 400 Bad Request for invalid JSON, 405 Method Not Allowed for incorrect HTTP methods).  Real-world APIs would have much more sophisticated error handling and logging.
    * **JSON Encoding:**  Uses `json.NewEncoder(w).Encode(yourStruct)` which is the correct way to send JSON responses in `net/http`.
    * **Status Codes:** Returns the correct HTTP status codes (201 Created for successful creation).
    * **Content Type:**  Sets the `Content-Type` header to `application/json` for all responses.
    * **Security:** This server has *no security*.  It's only for demonstration purposes.  A real API would need authentication, authorization, and protection against common web vulnerabilities.
    * **Data Storage:** This server stores *no data*.  It only generates IDs and returns them.  A real billing system would need a database to store customer and invoice information.
    * **Payment Link:** The `PaymentLink` is just a placeholder.  You would need to integrate with a real payment gateway (Stripe, PayPal, etc.) to generate actual payment links.

This complete example, with both the client and the simplified server, should give you a very strong starting point for building a billing automation system in Go.  Remember to replace the example API server with a real implementation when you're ready to move to production.
👁️ Viewed: 5

Comments