• Explore
  • About Us
  • Log In
  • Get Started
  • Explore
  • About Us
  • Log In
  • Get Started

Finance Transaction Error Handling

You are a backend engineer at a rapidly growing finance startup. The payment processing service in your company is critical and must provide detailed error information for debugging and logging purposes. Currently, the service returns generic error messages, which makes diagnosing issues difficult. Your task is to improve the error handling mechanism by defining a custom error type in Go. This custom error type should include metadata such as an error code, a detailed message, and a timestamp. Additionally, you will simulate a transaction that may fail due to insufficient funds. If the transaction fails, the custom error should be returned; otherwise, a success message should be logged.

Requirements

  • Custom Error Definition:
    • Create a struct CustomError with the following fields:
      • Code (int): A unique identifier for the error.
      • Message (string): A detailed description of the error.
      • Timestamp (time.Time): The time when the error occurred.
  • Error Interface Implementation:
    • Implement the Error() method for CustomError so that it satisfies Go’s error interface. The method should return a formatted string including all metadata.
  • Error Constructor:
    • Implement a function NewCustomError(code int, message string) *CustomError that initializes and returns a new CustomError with the current timestamp.
  • Transaction Simulation:
    • Write a function simulateTransaction() that simulates a payment transaction. Use a global variable simulateFailure to decide the outcome:
      • If simulateFailure is true, simulate a failure due to insufficient funds by returning a custom error (error code 1001 and message "Insufficient funds").
      • If simulateFailure is false, simulate a successful transaction by returning nil.
  • Logging:
    • In the main function, call simulateTransaction(). If an error is returned, log the error details; if not, log a success message.
  • Testing:
    • Copy and run unit tests to verify:
      • The correctness of the CustomError (fields and formatted error string).
      • The behavior of simulateTransaction() in both failure and success scenarios.

Expected output

When you run the program, you should see one of the following outputs:

  • If the transaction fails (i.e., simulateFailure is true):
[ERROR] Transaction failed: Code: 1001, Message: Insufficient funds, Timestamp: 2025-02-13T15:04:05Z
  • If the transaction succeeds (i.e., simulateFailure is false):
[INFO] Transaction succeeded.

Program Structure

.
├── go.mod
├── main.go
└── main_test.go

Your program should have the following structure and implement these functions:

main.go

package main

import (
	"fmt"
	"time"
)

// simulateFailure controls whether the transaction simulation should fail.
var simulateFailure = true

// CustomError represents a structured error with metadata.
type CustomError struct {
	// Define necessary fields based on requirements
}

// Add a new method here that implements the error interface

// NewCustomError creates a new CustomError instance.
func NewCustomError(code int, message string) *CustomError {
	// Initialize and return a new CustomError instance
	return nil
}

// simulateTransaction simulates a payment transaction.
// It returns a custom error if the transaction fails (simulateFailure is true),
// or nil if the transaction succeeds.
func simulateTransaction() error {
	if simulateFailure {
		return NewCustomError(1001, "Insufficient funds")
	}
	return nil
}

func main() {
	err := simulateTransaction()
	if err != nil {
		fmt.Printf("[ERROR] Transaction failed: %s\n", err.Error())
	} else {
		fmt.Println("[INFO] Transaction succeeded.")
	}
}

Copy the following test file to main_test.go to verify your implementation. All test cases must pass.

main_test.go

package main

import (
	"strings"
	"testing"
	"time"
)

func TestNewCustomError(t *testing.T) {
	err := NewCustomError(1001, "Insufficient funds")
	if err.Code != 1001 {
		t.Errorf("Expected code 1001, got %d", err.Code)
	}
	if err.Message != "Insufficient funds" {
		t.Errorf("Expected message 'Insufficient funds', got '%s'", err.Message)
	}
	if err.Timestamp.IsZero() {
		t.Error("Expected non-zero timestamp")
	}

	// Check the error string format.
	errorStr := err.Error()
	if !strings.Contains(errorStr, "Code: 1001") ||
		!strings.Contains(errorStr, "Message: Insufficient funds") ||
		!strings.Contains(errorStr, "Timestamp:") {
		t.Error("Error string format is incorrect")
	}
}

func TestSimulateTransaction_Failure(t *testing.T) {
	// Force failure.
	simulateFailure = true
	err := simulateTransaction()
	if err == nil {
		t.Error("Expected an error but got nil")
	} else {
		if _, ok := err.(*CustomError); !ok {
			t.Error("Expected error to be of type *CustomError")
		}
	}
}

func TestSimulateTransaction_Success(t *testing.T) {
	// Force success.
	simulateFailure = false
	err := simulateTransaction()
	if err != nil {
		t.Errorf("Expected no error, but got: %v", err)
	}
}

Acceptance Criteria

  • The CustomError struct is defined with fields for error code, message, and timestamp.
  • error interface should be implemented correctly.
  • The NewCustomError function initializes and returns a valid CustomError instance.
  • The simulateTransaction() function uses the global simulateFailure variable to determine whether to return a custom error or nil.
  • The main function logs the correct output based on the transaction result.
  • Unit tests in main_test.go must pass.

Hints

  • Use Go’s struct and method syntax to define and implement the custom error type.
  • Utilize time.Now() to capture the current timestamp when an error is created.
  • Format your error string using fmt.Sprintf for clarity.
  • Use a global variable to control the simulation outcome, allowing deterministic testing.

    Golang (Go) for Production Systems

    Unlock All Exercises

  • Getting Started
    • Important - Please Read
  • Error Handling
    • Finance Transaction Error
    • Resilient Retry Mechanism
    • Graceful Panic Recovery
    • Error Wrapping and Unwrapping
    • Aggregated Validation Errors
    • Transaction Rollback on Failure
  • Interfaces
    • Design a Payment Gateway Interface
    • Pluggable Logging System
    • Configurable Notification System
    • Dynamic Data Serializer
    • Dynamic Plugin System
  • Concurrency
    • File Word Counter
    • Traffic Lights Controller
    • Parking Lot Manager
    • Real-time Auction System
    • Safe Bank Account Balance Update
    • Build a Buffered Logger
    • Build a TTL Cache
    • Build an Elevator Control System
    • Debug and Fix Race Conditions - Part 1
    • Debug and Fix Race Conditions - Part 2
    • Debug and Fix Race Conditions - Part 3
    • Dynamic Feature Flag System
    • Real-Time Multiplayer Matchmaking
    • Lazy Database Connection
    • Distributed Task Deduplication
    • High Performance Request Counter
  • Core Networking
    • Simple TCP Echo Server
    • TCP Number Guessing Game
    • Looking Up Domain Information
    • UDP Time Broadcast Service
    • UDP Ping-Pong Client and Server
    • Building a Simple TCP Chat Server and Client
  • Winding Up
    • Final Notes