Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions backend/cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ func main() {
mux.HandleFunc("POST /v1/forgot-password/send-otp", authHandler.ForgotPasswordSendOTP)
mux.HandleFunc("POST /v1/forgot-password/verify-otp", authHandler.ForgotPasswordVerifyOTP)
mux.HandleFunc("POST /v1/reset-password", authHandler.ResetPassword)
mux.HandleFunc("GET /dashboard", userHandler.DashboardView)
mux.HandleFunc("GET /v1/user/dashboard", userHandler.DashboardHandler)
mux.HandleFunc("GET /{username}", userHandler.DashboardView)
mux.HandleFunc("GET /v1/user/{username}", userHandler.DashboardHandler)
//mux.HandleFunc("GET /transaction", userHandler.TransactionView)
//mux.HandleFunc("GET /topup", userHandler.TopupView)
//mux.HandleFunc("GET /profile", userHandler.ProfileView)
Expand All @@ -92,23 +92,24 @@ func main() {
//mux.HandleFunc("GET /logout",)

// super admin endpoints
mux.HandleFunc("GET /admin/dashboard", adminHanlder.AdminDashboardView)
mux.HandleFunc("GET /v1/admin/dashboard-data", adminHanlder.AdminDashboardDataHandler)
mux.HandleFunc("GET /admin/merchants", adminHanlder.MerchantManagementView)
mux.HandleFunc("GET /v1/admin/merchants-data", adminHanlder.MerchantManagementDataHandler)
mux.HandleFunc("GET /admin/terminals", adminHanlder.TerminalRegistryView)
mux.HandleFunc("GET /v1/admin/terminals-data", adminHanlder.TerminalRegistryDataHandler)
mux.HandleFunc("POST /v1/admin/terminals/add", adminHanlder.AddTerminalHandler)
mux.HandleFunc("GET /admin/settings", adminHanlder.SystemSettingsView)
mux.HandleFunc("POST /v1/admin/merchants/add", adminHanlder.AddMerchantHandler)
mux.HandleFunc("GET /admin/card-inventory", adminHanlder.CardInventoryView)
mux.HandleFunc("GET /v1/admin/card-inventory-data", adminHanlder.CardInventoryDataHandler)
mux.HandleFunc("GET /admin/addcard", adminHanlder.AddCardsView)
mux.HandleFunc("GET /admin/deactivatecard", adminHanlder.DeactivateView)
mux.HandleFunc("POST /v1/admin/addcardauth", adminHanlder.AddCardHandler)
mux.HandleFunc("POST /v1/admin/deactivatecardauth", adminHanlder.DeactivateCardHanlder)
mux.HandleFunc("POST /v1/admin/deletecardauth", adminHanlder.DeleteCardHandler)
mux.HandleFunc("GET /admin/delete-cards", adminHanlder.DeleteCardView)
mux.HandleFunc("GET /admin/{username}", adminHanlder.AdminDashboardView)
mux.HandleFunc("GET /v1/admin/{username}/dashboard-data", adminHanlder.AdminDashboardDataHandler)
mux.HandleFunc("GET /admin/{username}/merchants", adminHanlder.MerchantManagementView)
mux.HandleFunc("GET /v1/admin/{username}/merchants-data", adminHanlder.MerchantManagementDataHandler)
mux.HandleFunc("GET /admin/{username}/terminals", adminHanlder.TerminalRegistryView)
mux.HandleFunc("GET /v1/admin/{username}/terminals-data", adminHanlder.TerminalRegistryDataHandler)
mux.HandleFunc("GET /v1/admin/{username}/terminals/unassigned", adminHanlder.GetUnassignedTerminalsHandler)
mux.HandleFunc("POST /v1/admin/{username}/terminals/add", adminHanlder.AddTerminalHandler)
mux.HandleFunc("GET /admin/{username}/settings", adminHanlder.SystemSettingsView)
mux.HandleFunc("POST /v1/admin/{username}/merchants/add", adminHanlder.AddMerchantHandler)
mux.HandleFunc("GET /admin/{username}/card-inventory", adminHanlder.CardInventoryView)
mux.HandleFunc("GET /v1/admin/{username}/card-inventory-data", adminHanlder.CardInventoryDataHandler)
mux.HandleFunc("GET /admin/{username}/addcard", adminHanlder.AddCardsView)
mux.HandleFunc("GET /admin/{username}/deactivatecard", adminHanlder.DeactivateView)
mux.HandleFunc("POST /v1/admin/{username}/addcardauth", adminHanlder.AddCardHandler)
mux.HandleFunc("POST /v1/admin/{username}/deactivatecardauth", adminHanlder.DeactivateCardHanlder)
mux.HandleFunc("POST /v1/admin/{username}/deletecardauth", adminHanlder.DeleteCardHandler)
mux.HandleFunc("GET /admin/{username}/delete-cards", adminHanlder.DeleteCardView)


// Wrap mux with custom handler for root redirect
Expand Down
6 changes: 5 additions & 1 deletion backend/internal/admin/add_card.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import (
// AddCardsView renders the addCards.html template after checking the admin session.
func (h *Handler) AddCardsView(w http.ResponseWriter, r *http.Request) {
fmt.Println("AddCardsView running...")
h.Tpl.ExecuteTemplate(w, "addCards.html", nil)
data := AdminPageData{
Page: "addcard",
Username: r.PathValue("username"),
}
h.Tpl.ExecuteTemplate(w, "addCards.html", data)
}

// AddCardHandler handles card creation and returns JSON response.
Expand Down
67 changes: 36 additions & 31 deletions backend/internal/admin/add_merchant.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log"
"math/big"
"net/http"
"strings"
"time"
jsonwrite "unicard-go/backend/internal/pkg/handler"

Expand All @@ -19,17 +20,17 @@ import (
type AddMerchantRequest struct {
BusinessName string `json:"businessName" validate:"required" db:"business_name"`
BusinessType string `json:"businessType" validate:"required" db:"business_type"`
RegistrationNum string `json:"registrationNum" validate:"required" db:"registration_num"`
RegistrationNum string `json:"registrationNum" db:"registration_num"`
BusinessAddress string `json:"businessAddress" validate:"required" db:"business_address"`
OwnerName string `json:"ownerName" validate:"required" db:"owner_name"`
BusinessEmail string `json:"businessEmail" validate:"required,email" db:"business_email"`
BusinessPhone string `json:"businessPhone" validate:"required" db:"business_phone"`
CommissionRate string `json:"commissionRate" validate:"required" db:"commission_rate"`
CommissionRate string `json:"commissionRate" db:"commission_rate"`
SettlementName string `json:"settlementName" validate:"required" db:"settlement_name"`
SettlementAccount string `json:"settlementAccount" validate:"required" db:"settlement_account_number"`
SettlementBank string `json:"settlementBank" validate:"required" db:"settlement_bank_name"`
TerminalSN string `json:"terminalSn" validate:"required" db:"terminal_sn"`
DeviceName string `json:"deviceName" validate:"required" db:"device_name"`
DeviceName string `json:"deviceName" db:"device_name"`
}

// AddMerchantHandler creates new merchants and their corresponding owner users in bulk
Expand Down Expand Up @@ -76,7 +77,7 @@ func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) {

merchStmt, err := tx.Prepare(`INSERT INTO merchants (
merchant_id, business_name, business_type, business_registration_number, business_address,
owner_user_id, owner_name, business_email, business_phone, commission_rate,
user_id, owner_name, business_email, business_phone, commission_rate,
settlement_account_name, settlement_account_number, settlement_bank_name, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
Expand All @@ -90,9 +91,8 @@ func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) {
}
defer merchStmt.Close()

termStmt, err := tx.Prepare(`INSERT INTO terminals (
terminal_id, terminal_sn, merchant_id, device_name, status
) VALUES (?, ?, ?, ?, ?)`)
// Update terminal status to active and assign merchant_id
termStmt, err := tx.Prepare(`UPDATE terminals SET merchant_id = ?, location_details = ?, status = 'active' WHERE terminal_sn = ?`)
if err != nil {
tx.Rollback()
log.Printf("Error preparing terminal stmt: %v", err)
Expand All @@ -105,6 +105,20 @@ func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) {
defer termStmt.Close()

for i, req := range reqs {
// Clean and format string fields
req.BusinessName = strings.Title(strings.ToLower(strings.TrimSpace(req.BusinessName)))
req.BusinessAddress = strings.Title(strings.ToLower(strings.TrimSpace(req.BusinessAddress)))
req.OwnerName = strings.Title(strings.ToLower(strings.TrimSpace(req.OwnerName)))
req.SettlementName = strings.Title(strings.ToLower(strings.TrimSpace(req.SettlementName)))

// Some fields don't need title case but should be trimmed
req.BusinessEmail = strings.ToLower(strings.TrimSpace(req.BusinessEmail))
req.BusinessPhone = strings.TrimSpace(req.BusinessPhone)
req.TerminalSN = strings.TrimSpace(req.TerminalSN)
req.DeviceName = strings.TrimSpace(req.DeviceName)

reqs[i] = req // update back to slice

err := Validate.Struct(req)
if err != nil {
tx.Rollback()
Expand All @@ -116,17 +130,14 @@ func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) {
fieldMessages := map[string]string{
"BusinessName": "Business name is required",
"BusinessType": "Business type is required",
"RegistrationNum": "Registration number is required",
"BusinessAddress": "Business address is required",
"OwnerName": "Owner name is required",
"BusinessEmail": "A valid business email is required",
"BusinessPhone": "Business phone number is required",
"CommissionRate": "Commission rate is required",
"SettlementName": "Settlement name is required",
"SettlementAccount": "Settlement account number is required",
"SettlementBank": "Settlement bank name is required",
"TerminalSN": "Terminal serial number is required",
"DeviceName": "Device name is required",
}
if customMsg, ok := fieldMessages[firstErr.Field()]; ok {
msg = fmt.Sprintf("Merchant #%d: %s", i+1, customMsg)
Expand All @@ -140,12 +151,12 @@ func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) {
}

// Generate IDs (Format: YYMMminsecxxxxx where xxxxx is 5 random numbers)
timestamp := time.Now().Format("06010405") // YYMMDDHH
timestamp := time.Now().Format("01020605") // MMDDYYss

nUser, _ := rand.Int(rand.Reader, big.NewInt(100000))
nUser, _ := rand.Int(rand.Reader, big.NewInt(10000)) // max 9999
userID := fmt.Sprintf("UNI-%s%04d", timestamp, nUser.Int64())

nMerchant, _ := rand.Int(rand.Reader, big.NewInt(100000))
nMerchant, _ := rand.Int(rand.Reader, big.NewInt(10000))
merchantID := fmt.Sprintf("MCH-%s%04d", timestamp, nMerchant.Int64())

// Create user for the merchant owner
Expand Down Expand Up @@ -173,9 +184,16 @@ func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) {
return
}

res, err := merchStmt.Exec(
merchantID, req.BusinessName, req.BusinessType, req.RegistrationNum, req.BusinessAddress,
userID, req.OwnerName, req.BusinessEmail, req.BusinessPhone, req.CommissionRate,
// Generate registration number (UCBZ-MMDDss-xxxxxxxxxx)
nReg, _ := rand.Int(rand.Reader, big.NewInt(10000000000))
regNum := fmt.Sprintf("UCBZ-%s-%010d", time.Now().Format("010205"), nReg.Int64())

// Set commission rate
fixedCommissionRate := 2.00

_, err = merchStmt.Exec(
merchantID, req.BusinessName, req.BusinessType, regNum, req.BusinessAddress,
userID, req.OwnerName, req.BusinessEmail, req.BusinessPhone, fixedCommissionRate,
req.SettlementName, req.SettlementAccount, req.SettlementBank, "active",
)

Expand All @@ -189,21 +207,8 @@ func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) {
return
}

internalMerchantID, err := res.LastInsertId()
if err != nil {
tx.Rollback()
log.Printf("Error getting last insert ID for merchant %d: %v", i+1, err)
jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{
Success: false,
Message: "Database error",
})
return
}

nTerminal, _ := rand.Int(rand.Reader, big.NewInt(100000))
terminalID := fmt.Sprintf("TRM-%s%04d", timestamp, nTerminal.Int64())

_, err = termStmt.Exec(terminalID, req.TerminalSN, internalMerchantID, req.DeviceName, "active")
// Update the existing terminal
_, err = termStmt.Exec(userID, req.BusinessAddress, req.TerminalSN)
if err != nil {
tx.Rollback()
log.Printf("Error creating terminal %d: %v", i+1, err)
Expand Down
11 changes: 10 additions & 1 deletion backend/internal/admin/admin_dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ import (
structs "unicard-go/backend/internal/pkg/structs"
)

type AdminPageData struct {
Page string
Username string
}

// AdminDashboardView renders the platform_overview.html template after checking the admin session.
func (h *Handler) AdminDashboardView(w http.ResponseWriter, r *http.Request) {
log.Println("AdminDashboardView running...")
h.Tpl.ExecuteTemplate(w, "admin_dashboard.html", nil)
data := AdminPageData{
Page: "dashboard",
Username: r.PathValue("username"),
}
h.Tpl.ExecuteTemplate(w, "admin_dashboard.html", data)
}

// AdminDashboardDataHandler handles the request for admin dashboard data and returns JSON response.
Expand Down
20 changes: 18 additions & 2 deletions backend/internal/admin/admin_merchant.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import (

func (h *Handler) MerchantManagementView(w http.ResponseWriter, r *http.Request) {
log.Println("MerchantManagementView running...")
h.Tpl.ExecuteTemplate(w, "admin_merchant.html", nil)
data := AdminPageData{
Page: "merchants",
Username: r.PathValue("username"),
}
h.Tpl.ExecuteTemplate(w, "admin_merchant.html", data)
}

func (h *Handler) MerchantManagementDataHandler(w http.ResponseWriter, r *http.Request) {
Expand All @@ -23,6 +27,8 @@ func (h *Handler) MerchantManagementDataHandler(w http.ResponseWriter, r *http.R
limitStr := r.URL.Query().Get("limit")
search := r.URL.Query().Get("search")
sortOrder := r.URL.Query().Get("sort") // desc or asc
category := r.URL.Query().Get("category")
status := r.URL.Query().Get("status")

page := 1
limit := 10
Expand All @@ -46,6 +52,16 @@ func (h *Handler) MerchantManagementDataHandler(w http.ResponseWriter, r *http.R
args = append(args, searchPattern, searchPattern, searchPattern)
}

if category != "" {
conditions = append(conditions, `business_type = ?`)
args = append(args, category)
}

if status != "" {
conditions = append(conditions, `status = ?`)
args = append(args, status)
}

whereClause := ""
if len(conditions) > 0 {
whereClause = " WHERE " + strings.Join(conditions, " AND ")
Expand Down Expand Up @@ -113,7 +129,7 @@ func (h *Handler) MerchantManagementDataHandler(w http.ResponseWriter, r *http.R
termQuery := fmt.Sprintf(`
SELECT m.merchant_id, t.terminal_id, t.terminal_sn, t.device_name, t.status
FROM terminals t
JOIN merchants m ON t.merchant_id = m.id
JOIN merchants m ON t.merchant_id = m.user_id
WHERE m.merchant_id IN (%s)`, strings.Join(placeholders, ","))

termRows, err := h.DB.Query(termQuery, termArgs...)
Expand Down
Loading