Код ревью golang кода

Я стараюсь писать код правильно и был бы очень благодарен опытным программистам которые помогли бы найти ошибки которые я допустил в коде, и в целом как можно было бы улучшить код представленный ниже?

Немного пояснения - эта программа для проведения платежей на сайте (Платёжная система).

Данный код сначала загружает все переменные из .env файла, после раз в 5 вызываем функцию fetchData(). Данная функция получает ID сессии с помощью getBankSessionID(). Дальше код проходится по списку этих платежей, выбирает только пополнения, и проверяет обрабатывался ли до этот платёж, если да, то переходим к следующему платежу, если нет то пытаемся провести платёж с помощью функции ProcessPayment() на вход которой идёт время платежа и его сумма.

В функции ProcessPayment() мы отправляем запрос уже нашу api которая проводит платёж, и пишет нам статут операции (Оплата прошла, Платёж уже был оплачен, Не верный ключ, Неизвестная ошибка и т.д.), и заносим статус в файл с логами

Что скажете, где можно провести оптимизацию и рефакторинг?

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "log"
    "os"
    "os/exec"
    "strconv"
    "sync"
    "time"

    "github.com/joho/godotenv"
    "github.com/shopspring/decimal"
    "github.com/valyala/fasthttp"
)

type Response struct {
    ResultCode string      `json:"resultCode"`
    Payload    []Operation `json:"payload"`
}

type Operation struct {
    ID        string    `json:"id"`
    Type      string    `json:"type"`
    Amount    Amount    `json:"amount"`
    CreatedAt CreatedAt `json:"operationTime"`
}

type CreatedAt struct {
    Milliseconds int64 `json:"milliseconds"`
}

type Amount struct {
    Sum decimal.Decimal `json:"value"`
}

type Payment struct {
    Text   string `json:"text"`
    Status string `json:"status"`
}

type Session struct {
    Value    string `json:"value"`
    Status string `json:"status"`
}

const (
    parallelism = 4
    requestRate = 5 * time.Second

    ResultOK                     = "OK"
    ResultInsufficientPrivileges = "INSUFFICIENT_PRIVILEGES"

    StatusPaid  = "paid"
    StatusMade  = "made"
    StatusError = "error"

    reset  = "\033[0m"
    red    = "\033[31m"
    green  = "\033[32m"
    yellow = "\033[33m"
)

var (
    // Конфигурация для bank
    bankAccount   string
    bankWUID      string
    bankCategory  string

    bankHost      string
    bankPath      string

    // Конфигурация для системы
    systemKey  string
    systemHost string
    systemPath string

    client              = &fasthttp.Client{MaxConnsPerHost: parallelism}
    jobChan             = make(chan struct{}, parallelism)
    processedPaymentIDs sync.Map
)

func getBankSessionID() (string, error) {
    cmd := exec.Command("bin/getPSID")

    output, err := cmd.Output()
    if err != nil {
        return "", fmt.Errorf("не удалось выполнить команду: %w", err)
    }

    var session Session
    err = json.Unmarshal(output, &session)
    if err != nil {
        return "", fmt.Errorf("не удалось распарсить JSON: %w", err)
    }

    if session.Status != "OK" {
        return "", errors.New("не удалось получить сессию: " + session.Value)
    }

    return session.Value, nil
}

func ProcessPayment(paidAt int64, sum decimal.Decimal) {
    var uri fasthttp.URI
    uri.SetScheme("https")
    uri.SetHost(systemHost)
    uri.SetPath(systemPath)

    q := uri.QueryArgs()
    q.Add("do", "pay_payment_v2")
    q.Add("key", systemKey)
    q.Add("paid_at", strconv.FormatInt(paidAt, 10))
    q.Add("sum", sum.StringFixed(2))

    req := fasthttp.AcquireRequest()
    resp := fasthttp.AcquireResponse()
    defer fasthttp.ReleaseRequest(req)
    defer fasthttp.ReleaseResponse(resp)

    req.SetRequestURI(uri.String())
    req.Header.SetMethod(fasthttp.MethodPost)
    req.Header.SetContentType("application/x-www-form-urlencoded")

    req.SetBody([]byte(q.String()))

    if err := fasthttp.Do(req, resp); err != nil {
        log.Println("Ошибка запроса:", err)
        return
    }

    statusCode := resp.StatusCode()
    if statusCode != fasthttp.StatusOK {
        log.Printf("⚠️ HTTP %d: %s\n", statusCode, resp.Body())
        return
    }

    var payment Payment
    if err := json.Unmarshal(resp.Body(), &payment); err != nil {
        log.Println("Ошибка декодирования JSON:", err)
        return
    }

    file, err := os.OpenFile("payments.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()

    logger := log.New(file, "", log.LstdFlags|log.Lshortfile)

    details := fmt.Sprintf("SUM: %s DATE: %d", sum.StringFixed(2), paidAt)
    switch payment.Status {
    case StatusMade:
        log.Println(green + payment.Text + reset)
        logger.Println("[INFO]", payment.Text, details)
    case StatusPaid:
        log.Println(yellow + payment.Text + reset)
        logger.Println("[WARN]", payment.Text, details)
    case StatusError:
        log.Println(red + payment.Text + reset)
        logger.Println("[ERROR]", payment.Text, details)
    default:
        log.Println(red + "Unknown error" + reset)
        logger.Println("[ERROR] Unknown error")
    }

}

func fetchData() {
    defer func() { <-jobChan }()

    now := time.Now().UTC()
    end := now.UnixMilli()
    start := now.Add(-24 * time.Hour).UnixMilli()

    var uri fasthttp.URI
    uri.SetScheme("https")
    uri.SetHost(bankHost)
    uri.SetPath(bankPath)

    BankSessionID, err := getBankSessionID()
    if err != nil {
        return
    }

    q := uri.QueryArgs()
    q.Add("start", strconv.FormatInt(start, 10))
    q.Add("end", strconv.FormatInt(end, 10))
    q.Add("account", bankAccount)
    q.Add("spendingCategory", bankCategory)
    q.Add("sessionid", BankSessionID)
    q.Add("wuid", bankWUID)

    req := fasthttp.AcquireRequest()
    resp := fasthttp.AcquireResponse()
    defer fasthttp.ReleaseRequest(req)
    defer fasthttp.ReleaseResponse(resp)

    req.SetRequestURI(uri.String())
    req.Header.SetMethod(fasthttp.MethodGet)


    if err := client.DoTimeout(req, resp, requestRate); err != nil {
        log.Println("Ошибка запроса:", err)
        return
    }

    statusCode := resp.StatusCode()
    if statusCode != fasthttp.StatusOK {
        log.Printf("⚠️ HTTP %d: %s\n", statusCode, resp.Body())
        return
    }

    var apiResponse Response
    if err := json.Unmarshal(resp.Body(), &apiResponse); err != nil {
        log.Println("Ошибка декодирования JSON:", err)
        return
    }

    if apiResponse.ResultCode != ResultOK {
        switch apiResponse.ResultCode {
        case ResultInsufficientPrivileges:
            log.Fatalln("⚠️ Сессия устарела")
            return
        default:
            log.Printf("⚠️ Не известная ошибка, код %s", apiResponse.ResultCode)
            return
        }
    }

    for _, op := range apiResponse.Payload {
        if op.Type != "Credit" {
            continue
        }

        if _, exists := processedPaymentIDs.LoadOrStore(op.ID, struct{}{}); exists {
            continue
        }

        
        paidAt := op.CreatedAt.Milliseconds / 1000
        sum := op.Amount.Sum

        go ProcessPayment(paidAt, sum)
    }
}

func init() {
    if err := godotenv.Load(); err != nil {
        log.Fatal("Ошибка загрузки .env файла:", err)
    }

    bankAccount = os.Getenv("BANK_ACCOUNT")
    bankWUID = os.Getenv("BANK_WUID")
    bankCategory = os.Getenv("BANK_CATEGORY")

    bankHost = os.Getenv("BANK_HOST")
    bankPath = os.Getenv("BANK_PATH")

    systemKey = os.Getenv("SYSTEM_KEY")
    systemHost = os.Getenv("SYSTEM_HOST")
    systemPath = os.Getenv("SYSTEM_PATH")
}

func main() {
    ticker := time.NewTicker(requestRate)
    defer ticker.Stop()

    log.Println("? Запуск клиента...")

    for {
        select {
        case jobChan <- struct{}{}:
            go fetchData()
        default:
            log.Println("⏳ Пропуск: все воркеры заняты")
            time.Sleep(time.Second)
        }

        <-ticker.C
    }
}

Ответы (0 шт):