From 54dd4a5ca7601233d598d5c0f59fa410f440f84d Mon Sep 17 00:00:00 2001 From: devzeeh <148837352+devzeeh@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:36:00 +0800 Subject: [PATCH 1/7] feat: implement merchant dashboard, transaction history, and account management features --- backend/cmd/app/main.go | 2 + backend/internal/admin/admin_merchant.go | 137 +++-- backend/internal/auth/merchant_signup.go | 38 +- backend/internal/merchant/account.go | 178 ++++++- backend/internal/pkg/smtpbody/smtp.go | 39 ++ docs/unicard.sql | 15 +- frontend/assets/admin/merchant_info.js | 95 +++- frontend/assets/js/merchant_account.js | 497 ++++++++++++++++-- frontend/assets/js/merchant_dashboard.js | 29 + frontend/assets/js/merchant_signup.js | 6 +- frontend/assets/js/merchant_transactions.js | 29 + frontend/templates/admin/addCards.html | 16 +- frontend/templates/admin/admin_sidebar.html | 64 +-- frontend/templates/admin/merchant_info.html | 37 +- frontend/templates/admin/transactions.html | 12 +- frontend/templates/auth/merchant_signup.html | 4 +- .../templates/merchant/merchant_account.html | 70 ++- 17 files changed, 1082 insertions(+), 186 deletions(-) diff --git a/backend/cmd/app/main.go b/backend/cmd/app/main.go index a52703e..5ff529f 100644 --- a/backend/cmd/app/main.go +++ b/backend/cmd/app/main.go @@ -125,6 +125,8 @@ func main() { mux.HandleFunc("GET /v1/merchant/{username}/incomes", merchantHandler.IncomeHandler) mux.HandleFunc("GET /merchant/{username}/account", merchantHandler.MerchantAccountView) mux.HandleFunc("GET /v1/merchant/{username}/account", merchantHandler.MerchantAccountDataHandler) + mux.HandleFunc("POST /v1/merchant/{username}/update-bank", merchantHandler.UpdateBankDetails) + mux.HandleFunc("POST /v1/merchant/{username}/upload-document", merchantHandler.UploadDocument) mux.HandleFunc("POST /v1/merchant/{username}/withdraw", merchantHandler.WithdrawHandler) // super admin endpoints diff --git a/backend/internal/admin/admin_merchant.go b/backend/internal/admin/admin_merchant.go index 29ceb2c..ec1761f 100644 --- a/backend/internal/admin/admin_merchant.go +++ b/backend/internal/admin/admin_merchant.go @@ -9,6 +9,7 @@ import ( "os" "strconv" "strings" + "time" jsonwrite "unicard-go/backend/internal/pkg/handler" smtp "unicard-go/backend/internal/pkg/smtpbody" structs "unicard-go/backend/internal/pkg/structs" @@ -190,9 +191,9 @@ func (h *Handler) MerchantManagementDataHandler(w http.ResponseWriter, r *http.R } type ApproveMerchantRequest struct { - CommissionRate string `json:"commissionRate" validate:"required"` - TerminalSn string `json:"terminalSn" validate:"required"` - DeviceName string `json:"deviceName"` + CommissionRate string `json:"commissionRate" validate:"required"` + TerminalSn string `json:"terminalSn" validate:"required"` + DeviceName string `json:"deviceName"` } func (h *Handler) ApproveMerchantHandler(w http.ResponseWriter, r *http.Request) { @@ -226,9 +227,9 @@ func (h *Handler) ApproveMerchantHandler(w http.ResponseWriter, r *http.Request) return } - // Get merchant user_id, email, and owner_name for the notification email - var merchantUserID, merchantEmail, ownerName string - err = tx.QueryRow("SELECT user_id, business_email, owner_name FROM merchants WHERE merchant_id = ?", merchantID).Scan(&merchantUserID, &merchantEmail, &ownerName) + // Get merchant user_id, email, owner_name, and business_name for the notification email and welcome tx + var merchantUserID, merchantEmail, ownerName, businessName string + err = tx.QueryRow("SELECT user_id, business_email, owner_name, business_name FROM merchants WHERE merchant_id = ?", merchantID).Scan(&merchantUserID, &merchantEmail, &ownerName, &businessName) if err != nil { jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Merchant not found"}) return @@ -239,6 +240,8 @@ func (h *Handler) ApproveMerchantHandler(w http.ResponseWriter, r *http.Request) _, err = tx.Exec(` UPDATE merchants SET status = 'active', + document_status = 'approved', + message = 'Congratulations! Your UniCard Merchant Account is now fully active.', commission_rate = 2.00, approved_by = ?, approved_at = CURRENT_TIMESTAMP @@ -268,6 +271,24 @@ func (h *Handler) ApproveMerchantHandler(w http.ResponseWriter, r *http.Request) return } + // Format business name safely for transaction ID (remove spaces, uppercase) + /*safeBusinessName := strings.ToUpper(strings.ReplaceAll(businessName, " ", "")) + if len(safeBusinessName) > 15 { + safeBusinessName = safeBusinessName[:15] + }*/ + + // Insert welcome transaction + welcomeTxnID := fmt.Sprintf("Welcome %s-%d", businessName, time.Now().UnixMilli()) + _, err = tx.Exec(` + INSERT INTO transactions + (transaction_id, merchant_id, transaction_type, amount, points_earned, service_fee, status, description) + VALUES (?, ?, 'payment', NULL, NULL, NULL, 'completed', 'Welcome to UniCard! Your merchant account is now approved and ready to accept transactions.')`, + welcomeTxnID, merchantID) + if err != nil { + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to create welcome transaction"}) + return + } + if err := tx.Commit(); err != nil { jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to finalize approval"}) return @@ -338,7 +359,7 @@ func (h *Handler) RejectMerchantHandler(w http.ResponseWriter, r *http.Request) return } - _, err = tx.Exec("UPDATE merchants SET status = 'rejected' WHERE merchant_id = ?", merchantID) + _, err = tx.Exec("UPDATE merchants SET status = 'rejected', document_status = 'rejected', message = ? WHERE merchant_id = ?", req.Reason, merchantID) if err != nil { jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to reject merchant"}) return @@ -419,7 +440,7 @@ func (h *Handler) SuspendMerchantHandler(w http.ResponseWriter, r *http.Request) return } - _, err = tx.Exec("UPDATE merchants SET status = 'suspended' WHERE merchant_id = ?", merchantID) + _, err = tx.Exec("UPDATE merchants SET status = 'suspended', message = ? WHERE merchant_id = ?", req.Reason, merchantID) if err != nil { jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to suspend merchant"}) return @@ -481,8 +502,8 @@ func (h *Handler) DeleteMerchantHandler(w http.ResponseWriter, r *http.Request) } defer tx.Rollback() - var merchantUserID string - err = tx.QueryRow("SELECT user_id FROM merchants WHERE merchant_id = ?", merchantID).Scan(&merchantUserID) + var merchantUserID, ownerName, merchantEmail string + err = tx.QueryRow("SELECT user_id, owner_name, business_email FROM merchants WHERE merchant_id = ?", merchantID).Scan(&merchantUserID, &ownerName, &merchantEmail) if err != nil { jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Merchant not found"}) return @@ -495,15 +516,15 @@ func (h *Handler) DeleteMerchantHandler(w http.ResponseWriter, r *http.Request) return } - // Delete from merchants - _, err = tx.Exec("DELETE FROM merchants WHERE merchant_id = ?", merchantID) + // Soft delete from merchants: Set status to 'deleted' and update message + _, err = tx.Exec("UPDATE merchants SET status = 'deleted', message = 'Your merchant account has been permanently deleted.' WHERE merchant_id = ?", merchantID) if err != nil { jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to delete merchant"}) return } - // Delete from users - _, err = tx.Exec("DELETE FROM users WHERE user_id = ?", merchantUserID) + // Soft delete from users: Set status to 'inactive' + _, err = tx.Exec("UPDATE users SET status = 'inactive' WHERE user_id = ?", merchantUserID) if err != nil { jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to delete user"}) return @@ -514,28 +535,59 @@ func (h *Handler) DeleteMerchantHandler(w http.ResponseWriter, r *http.Request) return } + // Send deletion email to merchant + go func(email, name string) { + smtpHost := os.Getenv("SMTP_HOST") + smtpPort := 587 + smtpEmail := os.Getenv("SMTP_EMAIL") + smtpSender := os.Getenv("SMTP_SENDER") + smtpPass := os.Getenv("SMTP_PASSWORD") + if smtpHost == "" || smtpEmail == "" { + log.Println("SMTP credentials not configured, skipping deletion email") + return + } + + m := gomail.NewMessage() + m.SetHeader("From", fmt.Sprintf("%s <%s>", smtpSender, smtpEmail)) + m.SetHeader("To", email) + m.SetHeader("Subject", "Unicard Account Deleted") + + htmlBody := fmt.Sprintf(smtp.MerchantDeletedEmail(), name) + m.SetBody("text/html", htmlBody) + + d := gomail.NewDialer(smtpHost, smtpPort, smtpEmail, smtpPass) + if err := d.DialAndSend(m); err != nil { + log.Printf("Failed to send deletion email to %s: %v", email, err) + } else { + log.Printf("Deletion email sent successfully to %s", email) + } + }(merchantEmail, ownerName) + jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{Success: true, Message: "Merchant deleted successfully"}) } type MerchantDetailsData struct { - MerchantID string - UserID string - BusinessName string - BusinessType string - RegistrationNum string - BusinessAddress string - OwnerName string - BusinessEmail string - BusinessPhone string - Status string - CommissionRate float64 - SettlementBank string - SettlementName string - SettlementAcct string - CreatedAt string - DtiDocument string - BirDocument string - OtherDocument string + MerchantID string + UserID string + BusinessName string + BusinessType string + RegistrationNum string + BusinessAddress string + City string + PostalCode string + OwnerName string + BusinessEmail string + BusinessPhone string + Status string + CommissionRate float64 + SettlementBank string + SettlementName string + SettlementAcct string + CreatedAt string + BusinessDocument string + BirDocument string + OtherDocument string + DocumentStatus string } type MerchantInfoViewData struct { @@ -570,19 +622,19 @@ func (h *Handler) MerchantInfoDataHandler(w http.ResponseWriter, r *http.Request var m MerchantDetailsData var commRate sql.NullFloat64 - var setBank, setName, setAcct, regNum, dtiDoc, birDoc, otherDoc sql.NullString + var setBank, setName, setAcct, regNum, dtiDoc, birDoc, otherDoc, city, postal, docStatus sql.NullString err := h.DB.QueryRow(` SELECT merchant_id, user_id, business_name, business_type, business_registration_number, - business_address, owner_name, business_email, business_phone, status, + business_address, city, postal_code, owner_name, business_email, business_phone, status, commission_rate, settlement_bank_name, settlement_account_name, settlement_account_number, created_at, - dti_document, bir_document, other_document + business_document, bir_document, other_document, document_status FROM merchants WHERE merchant_id = ?`, merchantID).Scan( &m.MerchantID, &m.UserID, &m.BusinessName, &m.BusinessType, ®Num, - &m.BusinessAddress, &m.OwnerName, &m.BusinessEmail, &m.BusinessPhone, &m.Status, + &m.BusinessAddress, &city, &postal, &m.OwnerName, &m.BusinessEmail, &m.BusinessPhone, &m.Status, &commRate, &setBank, &setName, &setAcct, &m.CreatedAt, - &dtiDoc, &birDoc, &otherDoc, + &dtiDoc, &birDoc, &otherDoc, &docStatus, ) if err != nil { @@ -604,6 +656,12 @@ func (h *Handler) MerchantInfoDataHandler(w http.ResponseWriter, r *http.Request if regNum.Valid { m.RegistrationNum = regNum.String } + if city.Valid { + m.City = city.String + } + if postal.Valid { + m.PostalCode = postal.String + } if commRate.Valid { m.CommissionRate = commRate.Float64 @@ -618,7 +676,7 @@ func (h *Handler) MerchantInfoDataHandler(w http.ResponseWriter, r *http.Request m.SettlementAcct = setAcct.String } if dtiDoc.Valid { - m.DtiDocument = dtiDoc.String + m.BusinessDocument = dtiDoc.String } if birDoc.Valid { m.BirDocument = birDoc.String @@ -626,6 +684,9 @@ func (h *Handler) MerchantInfoDataHandler(w http.ResponseWriter, r *http.Request if otherDoc.Valid { m.OtherDocument = otherDoc.String } + if docStatus.Valid { + m.DocumentStatus = docStatus.String + } jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ Success: true, diff --git a/backend/internal/auth/merchant_signup.go b/backend/internal/auth/merchant_signup.go index 896806f..25058e4 100644 --- a/backend/internal/auth/merchant_signup.go +++ b/backend/internal/auth/merchant_signup.go @@ -20,20 +20,20 @@ import ( ) type MerchantSignupRequest struct { - BusinessName string `json:"businessName" validate:"required"` - BusinessType string `json:"businessType" validate:"required"` - BusinessAddress string `json:"businessAddress" validate:"required"` - OwnerName string `json:"ownerName" validate:"required"` - BusinessPhone string `json:"businessPhone" validate:"required"` - BusinessEmail string `json:"businessEmail" validate:"required,email"` - Password string `json:"password" validate:"required,min=6"` - DtiDocument string `json:"dtiDocument"` - BirDocument string `json:"birDocument"` - OtherDocument string `json:"otherDocument"` + BusinessName string `json:"businessName" validate:"required"` + BusinessType string `json:"businessType" validate:"required"` + BusinessAddress string `json:"businessAddress" validate:"required"` + OwnerName string `json:"ownerName" validate:"required"` + BusinessPhone string `json:"businessPhone" validate:"required"` + BusinessEmail string `json:"businessEmail" validate:"required,email"` + Password string `json:"password" validate:"required,min=6"` + BusinessDocument string `json:"businessDocument"` + BirDocument string `json:"birDocument"` + OtherDocument string `json:"otherDocument"` } // Helper to save base64 to file -func saveBase64ToFile(b64data, merchantID, docType string) string { +func saveBase64ToFile(b64data string) string { if b64data == "" { return "" } @@ -53,9 +53,9 @@ func saveBase64ToFile(b64data, merchantID, docType string) string { } // Create directory if not exists os.MkdirAll("./storage/documents", os.ModePerm) - fileName := fmt.Sprintf("%s_%s_%d%s", merchantID, docType, time.Now().Unix(), ext) + fileName := fmt.Sprintf("%d%s", time.Now().Unix(), ext) filePath := filepath.Join("./storage/documents", fileName) - + err = os.WriteFile(filePath, data, 0644) if err != nil { return "" @@ -184,23 +184,23 @@ func (h *Handler) MerchantSignupHandler(w http.ResponseWriter, r *http.Request) regNum := fmt.Sprintf("UCBZ-%s-%010d", time.Now().Format("010205"), nReg.Int64()) // Save Documents - dtiPath := saveBase64ToFile(req.DtiDocument, merchantID, "DTI") - birPath := saveBase64ToFile(req.BirDocument, merchantID, "BIR") - otherPath := saveBase64ToFile(req.OtherDocument, merchantID, "OTHER") + bizDocPath := saveBase64ToFile(req.BusinessDocument) + birPath := saveBase64ToFile(req.BirDocument) + otherPath := saveBase64ToFile(req.OtherDocument) // Insert Merchant with placeholder 'PENDING' for settlement fields fixedCommissionRate := 2.00 merchStmt := `INSERT INTO merchants ( merchant_id, business_name, business_type, business_registration_number, business_address, user_id, owner_name, business_email, business_phone, commission_rate, - status, dti_document, bir_document, other_document + status, business_document, bir_document, other_document ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - + _, err = tx.ExecContext(ctx, merchStmt, merchantID, req.BusinessName, req.BusinessType, regNum, req.BusinessAddress, userID, req.OwnerName, req.BusinessEmail, req.BusinessPhone, fixedCommissionRate, "pending approval", - dtiPath, birPath, otherPath, + bizDocPath, birPath, otherPath, ) if err != nil { diff --git a/backend/internal/merchant/account.go b/backend/internal/merchant/account.go index 7e47b67..541c8fb 100644 --- a/backend/internal/merchant/account.go +++ b/backend/internal/merchant/account.go @@ -1,8 +1,15 @@ package merchant import ( + "encoding/json" + "fmt" + "io" "log" "net/http" + "os" + "path/filepath" + "strings" + "time" jsonwrite "unicard-go/backend/internal/pkg/handler" ) @@ -34,6 +41,8 @@ type BusinessBankDetails struct { type AccountSummary struct { MerchantID string `json:"merchant_id"` AccountStatus string `json:"account_status"` + DocumentStatus string `json:"document_status"` + AccountMessage string `json:"account_message"` MemberSince string `json:"member_since"` BusinessDetails BusinessDetails `json:"business_details"` BusinessBankDetails BusinessBankDetails `json:"business_bank_details"` @@ -71,7 +80,7 @@ func (h *Handler) MerchantAccountDataHandler(w http.ResponseWriter, r *http.Requ var ( merchantID, accountStatus, businessName, businessType, businessStructure, businessEmail, businessPhone, businessAddress, city, - postalCode, accName, bankName, accNumber, businessDoc, birDoc, + postalCode, accName, bankName, accNumber, businessDoc, birDoc, otherDoc, docStatus, docMessage, createdAtStr string ) @@ -103,6 +112,7 @@ func (h *Handler) MerchantAccountDataHandler(w http.ResponseWriter, r *http.Requ COALESCE(m.business_structure, ''), COALESCE(m.business_document, ''), COALESCE(m.bir_document, ''), + COALESCE(m.other_document, ''), COALESCE(m.document_status, ''), COALESCE(m.message, '') @@ -127,6 +137,7 @@ func (h *Handler) MerchantAccountDataHandler(w http.ResponseWriter, r *http.Requ &businessStructure, &businessDoc, &birDoc, + &otherDoc, &docStatus, &docMessage, ) @@ -155,12 +166,7 @@ func (h *Handler) MerchantAccountDataHandler(w http.ResponseWriter, r *http.Requ // Business Registration (DTI or SEC) if businessDoc != "" { - registrationLabel := "DTI Registration" - - // NOW checking the correct column! - if businessStructure == "corporation" || businessStructure == "partnership" { - registrationLabel = "SEC Registration" - } + registrationLabel := "DTI/SEC Registration" documents = append(documents, BusinessDocument{ DocumentType: registrationLabel, @@ -175,17 +181,29 @@ func (h *Handler) MerchantAccountDataHandler(w http.ResponseWriter, r *http.Requ if birDoc != "" { documents = append(documents, BusinessDocument{ DocumentType: "BIR Certificate", - Status: docStatus, // Quotes removed! - Message: docMessage, // Quotes removed! + Status: docStatus, + Message: docMessage, DocumentURL: birDoc, }) } + // Other Document + if otherDoc != "" { + documents = append(documents, BusinessDocument{ + DocumentType: "Other Document", + Status: docStatus, + Message: docMessage, + DocumentURL: otherDoc, + }) + } + // 5. Construct the final struct responseData := AccountSummary{ - MerchantID: merchantID, - AccountStatus: accountStatus, - MemberSince: memberSince, + MerchantID: merchantID, + AccountStatus: accountStatus, + DocumentStatus: docStatus, + AccountMessage: docMessage, + MemberSince: memberSince, BusinessDetails: BusinessDetails{ BusinessName: businessName, @@ -213,3 +231,139 @@ func (h *Handler) MerchantAccountDataHandler(w http.ResponseWriter, r *http.Requ Data: responseData, }) } + +func (h *Handler) UpdateBankDetails(w http.ResponseWriter, r *http.Request) { + log.Println("UpdateBankDetails running...") + username := r.PathValue("username") + if username == "" { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Username required"}) + return + } + + var req struct { + BankName string `json:"bank_name"` + AccountHolderName string `json:"account_holder_name"` + AccountNumber string `json:"account_number"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Invalid request payload"}) + return + } + + var merchantID string + err := h.DB.QueryRow("SELECT merchant_id FROM merchants WHERE user_id = (SELECT user_id FROM users WHERE username=?)", username).Scan(&merchantID) + if err != nil { + log.Println("Error finding merchant for update:", err) + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Merchant not found"}) + return + } + + _, err = h.DB.Exec("UPDATE merchants SET settlement_bank_name=?, settlement_account_name=?, settlement_account_number=? WHERE merchant_id = ?", req.BankName, req.AccountHolderName, req.AccountNumber, merchantID) + + if err != nil { + log.Println("Update error:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to update bank details"}) + return + } + + // Insert a system transaction to log the update + sysTxnID := fmt.Sprintf("SYS-SETTLE-%d", time.Now().UnixMilli()) + _, _ = h.DB.Exec(` + INSERT INTO transactions + (transaction_id, merchant_id, transaction_type, amount, points_earned, service_fee, status, description) + VALUES (?, ?, 'payment', NULL, NULL, NULL, 'completed', 'Settlement bank details were updated by the merchant.')`, + sysTxnID, merchantID) + + jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{Success: true, Message: "Bank details updated"}) +} + +func (h *Handler) UploadDocument(w http.ResponseWriter, r *http.Request) { + log.Println("UploadDocument running...") + username := r.PathValue("username") + if username == "" { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Username required"}) + return + } + + err := r.ParseMultipartForm(4 << 20) // Limit memory to 4MB + if err != nil { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "File too large"}) + return + } + + docType := r.FormValue("document_type") + file, handler, err := r.FormFile("document") + if err != nil { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Failed to read file"}) + return + } + defer file.Close() + + if handler.Size > 4*1024*1024 { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "File too large. Maximum size is 4MB."}) + return + } + + ext := strings.ToLower(filepath.Ext(handler.Filename)) + validExts := map[string]bool{ + ".jpg": true, + ".jpeg": true, + ".png": true, + ".pdf": true, + } + if !validExts[ext] { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{Success: false, Message: "Invalid file format. Only pictures, PDF, and Word docs are allowed."}) + return + } + + // Ensure uploads directory exists + uploadDir := "storage/documents" + os.MkdirAll(uploadDir, os.ModePerm) + + // Save file with auto-generated filename to eliminate raw filename + filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext) + filePath := filepath.Join(uploadDir, filename) + dst, err := os.Create(filePath) + if err != nil { + log.Println("File create error:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Internal server error"}) + return + } + defer dst.Close() + + if _, err := io.Copy(dst, file); err != nil { + log.Println("File copy error:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Internal server error"}) + return + } + + dbPath := "/" + strings.ReplaceAll(filePath, "\\", "/") + + col := "business_document" + if docType == "BIR Certificate" { + col = "bir_document" + } else if docType == "Other Document" { + col = "other_document" + } + + // Remove old file if it exists + var oldDbPath *string + qOld := fmt.Sprintf("SELECT %s FROM merchants WHERE user_id = (SELECT user_id FROM users WHERE username=?)", col) + if err := h.DB.QueryRow(qOld, username).Scan(&oldDbPath); err == nil && oldDbPath != nil { + oldFile := strings.TrimPrefix(*oldDbPath, "/") + if oldFile != "" { + os.Remove(oldFile) // Best effort delete + } + } + + query := fmt.Sprintf("UPDATE merchants SET %s=?, document_status='Pending' WHERE user_id = (SELECT user_id FROM users WHERE username=?)", col) + _, err = h.DB.Exec(query, dbPath, username) + if err != nil { + log.Println("DB update error:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{Success: false, Message: "Failed to update DB"}) + return + } + + jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{Success: true, Message: "File uploaded successfully"}) +} diff --git a/backend/internal/pkg/smtpbody/smtp.go b/backend/internal/pkg/smtpbody/smtp.go index caec117..668f5d1 100644 --- a/backend/internal/pkg/smtpbody/smtp.go +++ b/backend/internal/pkg/smtpbody/smtp.go @@ -285,6 +285,45 @@ func EmailVerificationBody() string {
+