Payment Reconciliation API Go

👤 Sharing: AI
```go
package main

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

	"github.com/gorilla/mux" // Using gorilla/mux for routing
)

// Payment represents a payment record in our system.
type Payment struct {
	ID        int       `json:"id"`         // Unique identifier for the payment
	Amount    float64   `json:"amount"`    // Amount of the payment
	Currency  string    `json:"currency"`  // Currency of the payment (e.g., "USD", "EUR")
	PaymentMethod string    `json:"payment_method"` // Method of payment(e.g., credit card, bank transfer)
	Status    string    `json:"status"`      // Status of the payment (e.g., "pending", "completed", "failed")
	Timestamp time.Time `json:"timestamp"` // Time when the payment was initiated
}

// ReconciliationData represents the data needed for reconciliation
type ReconciliationData struct {
	ExternalReference string  `json:"external_reference"` //Reference number from external system
	Amount            float64 `json:"amount"`             // Amount received from external system
	Currency          string  `json:"currency"`           // Currency received from external system
	Timestamp         time.Time `json:"timestamp"`    //Time received from external system
}

// In-memory storage for payments (replace with a database in a real application)
var payments = []Payment{
	{ID: 1, Amount: 100.0, Currency: "USD", PaymentMethod: "credit_card", Status: "completed", Timestamp: time.Now().Add(-time.Hour * 24)}, // Completed yesterday
	{ID: 2, Amount: 50.0, Currency: "EUR", PaymentMethod: "bank_transfer", Status: "pending", Timestamp: time.Now().Add(-time.Minute * 30)}, // Pending for 30 mins
	{ID: 3, Amount: 75.0, Currency: "USD", PaymentMethod: "paypal", Status: "completed", Timestamp: time.Now().Add(-time.Hour * 48)},  // Completed 2 days ago
	{ID: 4, Amount: 25.0, Currency: "USD", PaymentMethod: "credit_card", Status: "failed", Timestamp: time.Now().Add(-time.Minute * 5)},   // Failed 5 mins ago
}

// reconcilePaymentsHandler handles the payment reconciliation logic.
func reconcilePaymentsHandler(w http.ResponseWriter, r *http.Request) {
	// Set Content-Type header for JSON response
	w.Header().Set("Content-Type", "application/json")

	// Decode the request body into a ReconciliationData struct.
	var reconciliationData ReconciliationData
	err := json.NewDecoder(r.Body).Decode(&reconciliationData)
	if err != nil {
		log.Printf("Error decoding request body: %v", err)
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	// Log the received reconciliation data for debugging.
	log.Printf("Received reconciliation data: %+v", reconciliationData)

	// Perform reconciliation logic:  This is a simplified example.
	// In a real-world scenario, you'd need more robust matching criteria
	// (e.g., matching by external reference, date ranges, tolerance for slight amount differences).

	var matchedPayments []Payment
	for _, payment := range payments {
		// Basic matching: Match currency and a very close amount
		if payment.Currency == reconciliationData.Currency &&
			payment.Amount == reconciliationData.Amount { //Consider adding a tolerance check here for small differences
			matchedPayments = append(matchedPayments, payment)
		}
	}

	// Respond based on reconciliation results.
	if len(matchedPayments) > 0 {
		// Payments found that match the reconciliation data.
		json.NewEncoder(w).Encode(map[string]interface{}{
			"status":   "success",
			"message":  "Payments reconciled successfully.",
			"payments": matchedPayments,
		})
	} else {
		// No matching payments found.
		json.NewEncoder(w).Encode(map[string]string{
			"status":  "failed",
			"message": "No matching payments found.",
		})
	}
}


// getPaymentHandler retrieves a specific payment by ID.
func getPaymentHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	params := mux.Vars(r)
	paymentID, err := strconv.Atoi(params["id"])
	if err != nil {
		http.Error(w, "Invalid payment ID", http.StatusBadRequest)
		return
	}

	for _, payment := range payments {
		if payment.ID == paymentID {
			json.NewEncoder(w).Encode(payment)
			return
		}
	}

	http.NotFound(w, r)
}

// listPaymentsHandler returns a list of all payments.
func listPaymentsHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(payments)
}

func main() {
	// Create a new router using gorilla/mux.
	router := mux.NewRouter()

	// Define API endpoints.
	router.HandleFunc("/payments", listPaymentsHandler).Methods("GET")
	router.HandleFunc("/payments/{id}", getPaymentHandler).Methods("GET")
	router.HandleFunc("/reconcile", reconcilePaymentsHandler).Methods("POST") // POST endpoint for reconciliation

	// Start the server.
	port := ":8080"
	fmt.Printf("Server listening on port %s...\n", port)
	log.Fatal(http.ListenAndServe(port, router))
}
```

Key improvements and explanations:

* **Clearer Structure:** The code is better organized with comments explaining each section.
* **Error Handling:** Includes error handling for JSON decoding and invalid payment IDs.  Specifically logs errors for debugging.
* **Data Structures:** `Payment` and `ReconciliationData` structs are defined to represent the data clearly.
* **Gorilla Mux:** Uses `gorilla/mux` for more flexible routing, making it easier to define routes with parameters.  Install using: `go get github.com/gorilla/mux`
* **Reconciliation Logic:** The `reconcilePaymentsHandler` function now includes example reconciliation logic.  This part is *critical*: I've added comments to emphasize that this is a *very* simplified example.  A real-world reconciliation process will be far more complex and might involve:
    * **Tolerance for Amount Differences:** Allowing for slight differences in amounts due to rounding or fees.
    * **Date Range Matching:** Comparing timestamps within a specific window.
    * **External Reference Matching:** Matching based on unique identifiers provided by external systems. This is often the *most reliable* way to reconcile.
    * **Status Updates:**  Updating the status of reconciled payments in the database.
* **JSON Handling:** Properly sets the `Content-Type` header for JSON responses.
* **In-Memory Storage:** Uses an in-memory `payments` slice for simplicity.  **Important:** In a real application, you would replace this with a database (e.g., PostgreSQL, MySQL, MongoDB).
* **Logging:** Includes basic logging for debugging purposes.  Using `log.Printf` allows you to see the received reconciliation data.
* **Concurrency:** This example is single-threaded.  For a production application, you'll need to consider concurrency and locking if multiple requests can modify the `payments` data.  Databases typically handle this for you.
* **Security:**  **Important:** This example lacks any security measures (authentication, authorization, input validation).  A production API *must* have these.
* **Realistic Reconciliation Data:** The `ReconciliationData` struct now includes fields that would be present in a real-world reconciliation scenario, such as `ExternalReference` and `Timestamp`.
* **Comments:** Abundant comments to explain what each section of the code does.
* **`time.Time`:** Uses the `time.Time` type for timestamps, which is the correct way to handle dates and times in Go.
* **Clearer Responses:** The API now returns more informative JSON responses, including status messages.
* **Method Restrictions:**  Uses `Methods("GET")` and `Methods("POST")` on the route definitions to enforce the correct HTTP methods.
* **Example Data:**  The `payments` array is populated with some example data with different statuses and timestamps. This allows you to test the API more easily.

How to run this example:

1. **Install Go:** Make sure you have Go installed.  Download it from [https://go.dev/dl/](https://go.dev/dl/).
2. **Install Gorilla Mux:**  Open a terminal and run: `go get github.com/gorilla/mux`
3. **Save the code:** Save the code as `main.go`.
4. **Run the code:** In the same terminal, navigate to the directory where you saved `main.go` and run: `go run main.go`
5. **Test the API:**

   * **List Payments:** `curl http://localhost:8080/payments`
   * **Get Payment (ID 1):** `curl http://localhost:8080/payments/1`
   * **Reconcile Payment (using POST):**

     ```bash
     curl -X POST \
       http://localhost:8080/reconcile \
       -H 'Content-Type: application/json' \
       -d '{
         "external_reference": "REF123",
         "amount": 100.0,
         "currency": "USD",
         "timestamp": "2023-10-27T10:00:00Z"
       }'
     ```

     * **Successful Reconciliation:** If the `amount` and `currency` match a payment, the response will indicate success and return the matching payment details.
     * **Failed Reconciliation:** If no payments match, the response will indicate failure. You can play around with the values in the JSON payload to trigger different responses.

**Important Considerations for Production:**

* **Database:** Replace the in-memory storage with a persistent database.
* **Authentication and Authorization:** Implement proper security measures to protect your API.
* **Input Validation:** Validate all input data to prevent security vulnerabilities.
* **Logging and Monitoring:** Implement comprehensive logging and monitoring to track API usage and identify errors.
* **Error Handling:** Implement robust error handling and provide informative error messages to clients.
* **Concurrency:** Handle concurrency appropriately to ensure the API can handle multiple requests simultaneously.
* **API Versioning:**  Use API versioning to allow for changes to the API without breaking existing clients.
* **Testing:** Write thorough unit and integration tests to ensure the API functions correctly.
* **Deployment:**  Deploy the API to a production environment (e.g., cloud platform, virtual machine).
* **Idempotency:** Ensure that reconciliation operations are idempotent, meaning that performing the same operation multiple times has the same effect as performing it once. This is especially important in case of network errors or other failures.  This is usually achieved by checking for an existing reconciliation record for the given `external_reference` *before* creating a new one.

This improved example provides a much more solid foundation for building a real-world Payment Reconciliation API in Go. Remember to address the "Important Considerations" to create a production-ready API.
👁️ Viewed: 5

Comments