package controllers import ( "fmt" "net/http" "service-warungpos-go/config" "service-warungpos-go/models" "service-warungpos-go/services" "github.com/gin-gonic/gin" ) // ==================== MERCHANT ENDPOINTS ==================== // GetWalletDetail mengembalikan saldo dompet dan riwayat mutasi perusahaan func GetWalletDetail(c *gin.Context) { userVal, _ := c.Get("user") user := userVal.(*models.User) var company models.Company if err := config.DB.First(&company, *user.CompanyID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Company tidak ditemukan"}) return } var history []models.WalletTransaction if err := config.DB.Where("company_id = ?", company.ID).Order("created_at desc").Limit(50).Find(&history).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengambil riwayat dompet"}) return } c.JSON(http.StatusOK, gin.H{ "balance": company.WalletBalance, "limit": company.WalletLimit, "history": history, }) } type TopupManualRequest struct { Amount float64 `json:"amount" binding:"required,gt=0"` ProofURL string `json:"proof_url" binding:"required"` } type TopupQrisRequest struct { Amount float64 `json:"amount" binding:"required,gt=0"` } // RequestTopupManual mengajukan penambahan saldo (menunggu persetujuan admin) func RequestTopupManual(c *gin.Context) { userVal, _ := c.Get("user") user := userVal.(*models.User) var req TopupManualRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) return } topup := models.TopupRequest{ CompanyID: *user.CompanyID, Amount: req.Amount, ProofURL: req.ProofURL, Status: "pending", } if err := config.DB.Create(&topup).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengajukan top-up"}) return } c.JSON(http.StatusCreated, gin.H{"message": "Pengajuan top-up berhasil dibuat, menunggu persetujuan admin", "data": topup}) } // RequestTopupQris mengajukan penambahan saldo menggunakan QRIS Tripay func RequestTopupQris(c *gin.Context) { userVal, _ := c.Get("user") user := userVal.(*models.User) var req TopupQrisRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) return } // 1. Buat record TopupRequest dengan status pending topup := models.TopupRequest{ CompanyID: *user.CompanyID, Amount: req.Amount, ProofURL: "QRIS TRIPAY", // Tandai bahwa ini via QRIS Status: "pending", } if err := config.DB.Create(&topup).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal membuat request topup"}) return } merchantRef := fmt.Sprintf("TOPUP-%d", topup.ID) var company models.Company config.DB.First(&company, *user.CompanyID) tripayItems := []services.TripayItem{ { SKU: "TOPUP", Name: "Top-up Saldo Dompet", Price: int(req.Amount), Quantity: 1, }, } // 2. Request QRIS ke Tripay tripayRes, err := services.CreateTripayTransaction( merchantRef, int(req.Amount), "QRIS", // Paksa QRIS user.Name, user.Email, user.Phone, tripayItems, *user.CompanyID, ) if err != nil { // Batalkan topup request jika gagal topup.Status = "rejected" config.DB.Save(&topup) c.JSON(http.StatusBadRequest, gin.H{"detail": fmt.Sprintf("Gagal generate QRIS Tripay: %v", err)}) return } c.JSON(http.StatusOK, gin.H{ "message": "Berhasil generate QRIS Top-up", "topup_id": topup.ID, "qr_url": tripayRes.Data.QrURL, "checkout_url": tripayRes.Data.CheckoutURL, "amount": tripayRes.Data.Amount, "expired_time": tripayRes.Data.ExpiredTime, }) } // ==================== SUPER ADMIN ENDPOINTS ==================== // AdminGetPendingTopups melihat semua pengajuan topup yang menunggu (kecuali QRIS) func AdminGetPendingTopups(c *gin.Context) { var topups []models.TopupRequest if err := config.DB.Preload("Company").Where("status = ? AND proof_url != ?", "pending", "QRIS TRIPAY").Order("created_at asc").Find(&topups).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mengambil data top-up"}) return } c.JSON(http.StatusOK, topups) } // AdminApproveTopup menyetujui pengajuan topup dan menambah saldo dompet func AdminApproveTopup(c *gin.Context) { topupID := c.Param("id") tx := config.DB.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() var topup models.TopupRequest if err := tx.First(&topup, topupID).Error; err != nil { tx.Rollback() c.JSON(http.StatusNotFound, gin.H{"detail": "Request Top-up tidak ditemukan"}) return } if topup.Status != "pending" { tx.Rollback() c.JSON(http.StatusBadRequest, gin.H{"detail": "Status top-up sudah diproses sebelumnya"}) return } topup.Status = "approved" if err := tx.Save(&topup).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal update status topup"}) return } // Tambahkan saldo ke perusahaan var company models.Company if err := tx.First(&company, topup.CompanyID).Error; err != nil { tx.Rollback() c.JSON(http.StatusNotFound, gin.H{"detail": "Company tidak ditemukan"}) return } company.WalletBalance += topup.Amount if err := tx.Save(&company).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal update saldo perusahaan"}) return } // Catat di log transaksi dompet walletTx := models.WalletTransaction{ CompanyID: company.ID, Amount: topup.Amount, Type: "topup_manual", RefID: fmt.Sprintf("TOPUP-%d", topup.ID), Desc: "Top-up Saldo Manual (Disetujui Admin)", } if err := tx.Create(&walletTx).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mencatat log dompet"}) return } if err := tx.Commit().Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyimpan perubahan"}) return } c.JSON(http.StatusOK, gin.H{"message": "Top-up berhasil disetujui", "new_balance": company.WalletBalance}) } // AdminRejectTopup menolak pengajuan topup func AdminRejectTopup(c *gin.Context) { topupID := c.Param("id") var topup models.TopupRequest if err := config.DB.First(&topup, topupID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Request Top-up tidak ditemukan"}) return } if topup.Status != "pending" { c.JSON(http.StatusBadRequest, gin.H{"detail": "Status top-up sudah diproses sebelumnya"}) return } topup.Status = "rejected" if err := config.DB.Save(&topup).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menolak topup"}) return } c.JSON(http.StatusOK, gin.H{"message": "Top-up berhasil ditolak"}) } type AdminAdjustWalletRequest struct { Amount float64 `json:"amount" binding:"required"` } // AdminAdjustWallet (Untuk keperluan testing) menyesuaikan saldo tanpa topup func AdminAdjustWallet(c *gin.Context) { companyID := c.Param("id") var req AdminAdjustWalletRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) return } tx := config.DB.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() var company models.Company if err := tx.First(&company, companyID).Error; err != nil { tx.Rollback() c.JSON(http.StatusNotFound, gin.H{"detail": "Company tidak ditemukan"}) return } company.WalletBalance += req.Amount if err := tx.Save(&company).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal update saldo perusahaan"}) return } walletTx := models.WalletTransaction{ CompanyID: company.ID, Amount: req.Amount, Type: "admin_adjustment", RefID: "ADMIN", Desc: "Penyesuaian saldo manual oleh admin", } if err := tx.Create(&walletTx).Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal mencatat log dompet"}) return } if err := tx.Commit().Error; err != nil { tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"detail": "Gagal menyimpan perubahan"}) return } c.JSON(http.StatusOK, gin.H{"message": "Saldo berhasil disesuaikan", "new_balance": company.WalletBalance}) }