Transactions in go

I am developing an app for a desktop application for a gym so as I progress the app grows without focusing on oop and things like that, keeping it as simple as possible and making it work, so I have reached the limit of my knowledge, now I have made my transaction to register a user, There is no authentication or so since the user will register the client so I don’t know if I am handling my transaction correctly or what I can improve. In a few words, it would help me a lot if you gave me some feedback

type userRepository struct {
    storage *postgres.PgxStorage
}

func NewUserRepository(storage *postgres.PgxStorage) user.Repository {
    return &userRepository{storage: storage}
}

func (ur *userRepository) RegisterUser(ctx context.Context, register *entities.RegisterUsertx) (int32, error) {

    tx, err := ur.storage.DBPool.Begin(ctx)
    if err != nil {
        log.Printf("error beginning transaction: %v", err)
        return 0, fmt.Errorf("begin transaction: %w", err)
    }
    defer func() {
        if err != nil {
            tx.Rollback(ctx)
            log.Printf("transaction rolled back due to error: %v", err)
        }
    }()

    // Registrar usuario
    var userID int32
    query := "INSERT INTO users (name, lastname1, lastname2, email, phone, created_at) VALUES($1, $2, $3, $4, $5, $6) RETURNING id"
    err = tx.QueryRow(ctx, query, register.Name, register.Lastname1, register.Lastname2, register.Email, register.Phone, register.CreatedAt).Scan(&userID)
    if err != nil {
        log.Printf("error inserting user: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("insert user: %w", err)
    }
    register.ID = userID

    // Crear cuenta
    accountID := uuid.New()
    var account uuid.UUID
    query = "INSERT INTO accounts (user_id, account_id, account_type_id, created_at) VALUES($1, $2, $3, $4) RETURNING account_id"
    err = tx.QueryRow(ctx, query, userID, accountID, register.AccountTypeID, time.Now()).Scan(&account)
    if err != nil {
        log.Printf("error inserting account: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("insert account: %w", err)
    }

    // Obtener la duracion mediante el id de la suscripcion
    var subscriptionDuration int
    query = "SELECT subscription_day FROM subscription_costs WHERE id = $1"
    err = tx.QueryRow(ctx, query, register.SubscriptionCostID).Scan(&subscriptionDuration)
    if err != nil {
        log.Printf("error getting subscription duration: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("get subscription duration: %w", err)
    }

    startDate := time.Now()
    endDate := startDate.AddDate(0, 0, subscriptionDuration)

    // Crear subscripcion
    query = "INSERT INTO subscriptions (account_id, subscription_cost_id, start_date, end_date) VALUES($1, $2, $3, $4) RETURNING id"
    err = tx.QueryRow(ctx, query, account, register.SubscriptionCostID, startDate, endDate).Scan(&register.SubscriptionID)
    if err != nil {
        log.Printf("error inserting subscription: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("insert subscription: %w", err)
    }

    // Obtener el monto y comparar con el monto de la suscripcion
    var expectedCost float64
    query = "SELECT cost from subscription_costs where id = $1"
    err = tx.QueryRow(ctx, query, register.SubscriptionCostID).Scan(&expectedCost)
    if err != nil {
        log.Printf("error getting subscription cost: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("ammount: %w", err)
    }

    if register.Ammount != expectedCost {
        log.Printf("amount incorrect: expected %v, got %v", expectedCost, register.Ammount)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("amount incorrect: %w", err)
    }

    query = "INSERT INTO payments (account_id, payment_type_id, cost, payment_date) VALUES($1, $2, $3, $4)"
    _, err = tx.Exec(ctx, query, account, register.PaymentTypeID, register.Ammount, time.Now())
    if err != nil {
        log.Printf("error inserting payment: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("insert payment: %w", err)
    }

    // Obtener el id del status
    var statusPayment, statusAcccount int32

    query = `SELECT id FROM STATUS WHERE id = 5 OR id = 1`
    rows, err := tx.Query(ctx, query)
    if err != nil {
        log.Printf("error getting statuses: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("get statuses: %w", err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int32
        if err := rows.Scan(&id); err != nil {
            log.Printf("error scanning status: %v", err)
            tx.Rollback(ctx)
            return 0, fmt.Errorf("scan status: %w", err)
        }

        if id == 5 {
            statusPayment = id
        } else {
            statusAcccount = id
        }
    }
    if err := rows.Err(); err != nil {
        log.Printf("rows error: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("rows error: %w", err)
    }

    query = `SELECT id from status where id = 5`
    err = tx.QueryRow(ctx, query).Scan(&statusPayment)
    if err != nil {
        log.Printf("error getting status: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("get status: %w", err)
    }

    query = "UPDATE payments SET status_id = $1 WHERE account_id = $2"
    _, err = tx.Exec(ctx, query, statusPayment, &account)
    if err != nil {
        log.Printf("error updating payment status: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("insert status: %w", err)
    }

    query = "UPDATE accounts SET subscription_id = $1, status_id = $2 WHERE account_id = $3"
    _, err = tx.Exec(ctx, query, &register.SubscriptionID, statusAcccount, &account)
    if err != nil {
        log.Printf("error updating account status: %v", err)
        tx.Rollback(ctx)
        return 0, fmt.Errorf("get status: %w", err)
    }

    err = tx.Commit(ctx)
    if err != nil {
        log.Printf("error committing transaction: %v", err)
        return 0, fmt.Errorf("commit transaction: %w", err)
    }

    return userID, nil
}

Firstly you should know when you should use transaction, for example

  1. Batch Operations: When performing a batch of inserts, updates, or deletes that should only succeed if all operations succeed.
  2. Complex Operations: When a single logical operation involves multiple database operations.
  3. Consistency Guarantees: When you need to ensure data consistency across multiple tables or operations.

In your example, some are only query you do not need to use transaction
then you should know how to use it
just begin transaction
some operation failed
you call Rollback
from your codes, you have known how to use it.