Update Tranaction

This commit is contained in:
aditya.siregar 2024-07-31 23:54:17 +07:00
parent 4c1a819365
commit a4bad0c088
9 changed files with 186 additions and 21 deletions

View File

@ -25,7 +25,7 @@ postgresql:
max-idle-connections-in-second: 600 max-idle-connections-in-second: 600
max-open-connections-in-second: 600 max-open-connections-in-second: 600
connection-max-life-time-in-second: 600 connection-max-life-time-in-second: 600
debug: false debug: true
oss: oss:
access_key_id: e50b31e5eddf63c0ZKB2 access_key_id: e50b31e5eddf63c0ZKB2

View File

@ -17,6 +17,7 @@ const (
errUserIsNotFound ErrType = "User is not found" errUserIsNotFound ErrType = "User is not found"
errInvalidLogin ErrType = "User email or password is invalid" errInvalidLogin ErrType = "User email or password is invalid"
errUnauthorized ErrType = "Unauthorized" errUnauthorized ErrType = "Unauthorized"
errInsufficientBalance ErrType = "Insufficient Balance"
) )
var ( var (
@ -32,6 +33,7 @@ var (
ErrorInternalServer = NewServiceException(errInternalServer) ErrorInternalServer = NewServiceException(errInternalServer)
ErrorUserIsNotFound = NewServiceException(errUserIsNotFound) ErrorUserIsNotFound = NewServiceException(errUserIsNotFound)
ErrorUserInvalidLogin = NewServiceException(errInvalidLogin) ErrorUserInvalidLogin = NewServiceException(errInvalidLogin)
ErrorInsufficientBalance = NewServiceException(errInsufficientBalance)
) )
type Error interface { type Error interface {
@ -105,6 +107,9 @@ func (s *ServiceException) MapErrorsToCode() Code {
case errInvalidLogin: case errInvalidLogin:
return BadRequest return BadRequest
case errInsufficientBalance:
return BadRequest
default: default:
return BadRequest return BadRequest
} }

View File

@ -50,3 +50,8 @@ type TransactionList struct {
PartnerName string PartnerName string
Amount int64 Amount int64
} }
type TransactionApproval struct {
TransactionID string
Status string
}

View File

@ -18,6 +18,7 @@ func (h *TransactionHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc)
route := group.Group("/transaction") route := group.Group("/transaction")
route.GET("/search", jwt, h.Search) route.GET("/search", jwt, h.Search)
route.POST("/approval", jwt, h.Approval)
} }
func New(service services.Transaction) *TransactionHandler { func New(service services.Transaction) *TransactionHandler {
@ -63,6 +64,36 @@ func (h *TransactionHandler) Search(c *gin.Context) {
}) })
} }
func (h *TransactionHandler) Approval(c *gin.Context) {
var req request.ApprovalRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
ctx := request.GetMyContext(c)
if !ctx.IsAdmin() {
response.ErrorWrapper(c, errors.ErrorBadRequest)
return
}
if err := req.Validate(); err != nil {
response.ErrorWrapper(c, errors.NewError(errors.ErrorBadRequest.ErrorType(), err.Error()))
return
}
err := h.service.Approval(ctx, req.ToEntity())
if err != nil {
response.ErrorWrapper(c, err)
return
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
})
}
func (h *TransactionHandler) ToTransactionListResponse(transactions []*entity.TransactionList, totalCount, limit, offset int) response.TransactionListResponse { func (h *TransactionHandler) ToTransactionListResponse(transactions []*entity.TransactionList, totalCount, limit, offset int) response.TransactionListResponse {
responseItems := make([]response.TransactionListItem, len(transactions)) responseItems := make([]response.TransactionListItem, len(transactions))
for i, transaction := range transactions { for i, transaction := range transactions {

View File

@ -4,6 +4,7 @@ import (
"furtuna-be/internal/common/mycontext" "furtuna-be/internal/common/mycontext"
"furtuna-be/internal/constants/transaction" "furtuna-be/internal/constants/transaction"
"furtuna-be/internal/entity" "furtuna-be/internal/entity"
"github.com/go-playground/validator/v10"
) )
type Transaction struct { type Transaction struct {
@ -19,9 +20,41 @@ type TransactionSearch struct {
Type string `form:"type,default="` Type string `form:"type,default="`
} }
type ApprovalRequest struct {
Status string `json:"status" validate:"required,approvalStatus"`
TransactionID string `json:"transaction_id" validate:"required"`
}
func (a *ApprovalRequest) ToEntity() *entity.TransactionApproval {
return &entity.TransactionApproval{
Status: a.Status,
TransactionID: a.TransactionID,
}
}
func approvalStatus(fl validator.FieldLevel) bool {
status := fl.Field().String()
return status == "APPROVE" || status == "REJECT"
}
func (a *ApprovalRequest) Validate() error {
validate := validator.New()
err := validate.RegisterValidation("approvalStatus", approvalStatus)
if err != nil {
return err
}
if err := validate.Struct(a); err != nil {
return err
}
return nil
}
func (t *TransactionSearch) ToEntity(ctx mycontext.Context) entity.TransactionSearch { func (t *TransactionSearch) ToEntity(ctx mycontext.Context) entity.TransactionSearch {
return entity.TransactionSearch{ return entity.TransactionSearch{
PartnerID: ctx.GetPartnerID(), PartnerID: ctx.GetPartnerID(),
SiteID: ctx.GetSiteID(),
Type: t.Type, Type: t.Type,
Status: t.Status, Status: t.Status,
Limit: t.Limit, Limit: t.Limit,

View File

@ -208,6 +208,8 @@ type License interface {
} }
type TransactionRepository interface { type TransactionRepository interface {
FindByID(ctx context.Context, id string) (*entity.Transaction, error)
Create(ctx context.Context, trx *gorm.DB, transaction *entity.Transaction) (*entity.Transaction, error) Create(ctx context.Context, trx *gorm.DB, transaction *entity.Transaction) (*entity.Transaction, error)
GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error) GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error)
Update(ctx context.Context, trx *gorm.DB, transaction *entity.Transaction) (*entity.Transaction, error)
} }

View File

@ -37,8 +37,8 @@ func (r *TransactionRepository) Create(ctx context.Context, trx *gorm.DB, transa
} }
// Update updates an existing transaction in the database. // Update updates an existing transaction in the database.
func (r *TransactionRepository) Update(ctx context.Context, transaction *entity.Transaction) (*entity.Transaction, error) { func (r *TransactionRepository) Update(ctx context.Context, trx *gorm.DB, transaction *entity.Transaction) (*entity.Transaction, error) {
if err := r.db.WithContext(ctx).Save(transaction).Error; err != nil { if err := trx.WithContext(ctx).Save(transaction).Error; err != nil {
zap.L().Error("error when updating transaction", zap.Error(err)) zap.L().Error("error when updating transaction", zap.Error(err))
return nil, err return nil, err
} }
@ -137,22 +137,30 @@ func (r *TransactionRepository) GetTransactionList(ctx mycontext.Context, req en
query := r.db.Table("transactions t"). query := r.db.Table("transactions t").
Select("t.id, t.transaction_type, t.status, t.created_at, s.name as site_name, p.name as partner_name, t.amount"). Select("t.id, t.transaction_type, t.status, t.created_at, s.name as site_name, p.name as partner_name, t.amount").
Joins("left join sites s on t.site_id = s.id"). Joins("left join sites s on t.site_id = s.id").
Joins("left join partners p on t.partner_id = p.id"). Joins("left join partners p on t.partner_id = p.id")
Where("t.partner_id = ?", req.PartnerID)
if req.SiteID != nil { if req.SiteID != nil {
query = query.Where("t.site_id = ?", req.SiteID) query = query.Where("t.site_id = ?", req.SiteID)
} }
if req.Type != "" { if req.Type != "" {
query = query.Where("t.transaction_type = ?", req.Type) query = query.Where("t.transaction_type = ?", req.Type)
} }
if req.Status != "" { if req.Status != "" {
query = query.Where("t.status = ?", req.Status) query = query.Where("t.status = ?", req.Status)
} }
if req.Date != "" { if req.Date != "" {
query = query.Where("DATE(t.created_at) = ?", req.Date) query = query.Where("DATE(t.created_at) = ?", req.Date)
} }
if req.PartnerID != nil {
query = query.Where("t.partner_id = ?", req.PartnerID)
}
query = query.Order("t.created_at DESC")
query = query.Count(&total) query = query.Count(&total)
if req.Offset > 0 { if req.Offset > 0 {

View File

@ -54,7 +54,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet), repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet),
SiteSvc: site.NewSiteService(repo.Site), SiteSvc: site.NewSiteService(repo.Site),
LicenseSvc: service.NewLicenseService(repo.License), LicenseSvc: service.NewLicenseService(repo.License),
Transaction: transaction.New(repo.Transaction), Transaction: transaction.New(repo.Transaction, repo.Wallet, repo.Trx),
Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction), Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction),
} }
} }
@ -146,6 +146,7 @@ type License interface {
type Transaction interface { type Transaction interface {
GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error) GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error)
Approval(ctx mycontext.Context, req *entity.TransactionApproval) error
} }
type Balance interface { type Balance interface {

View File

@ -1,6 +1,7 @@
package transaction package transaction
import ( import (
errors2 "furtuna-be/internal/common/errors"
"furtuna-be/internal/common/logger" "furtuna-be/internal/common/logger"
"furtuna-be/internal/common/mycontext" "furtuna-be/internal/common/mycontext"
"furtuna-be/internal/entity" "furtuna-be/internal/entity"
@ -11,11 +12,18 @@ import (
type TransactionService struct { type TransactionService struct {
repo repository.TransactionRepository repo repository.TransactionRepository
wallet repository.WalletRepository
trx repository.TransactionManager
} }
func New(repo repository.TransactionRepository) *TransactionService { func New(repo repository.TransactionRepository,
wallet repository.WalletRepository,
trx repository.TransactionManager,
) *TransactionService {
return &TransactionService{ return &TransactionService{
repo: repo, repo: repo,
wallet: wallet,
trx: trx,
} }
} }
@ -29,3 +37,75 @@ func (s *TransactionService) GetTransactionList(ctx mycontext.Context,
return transactions, total, nil return transactions, total, nil
} }
func (s *TransactionService) Approval(ctx mycontext.Context, req *entity.TransactionApproval) error {
// Start a transaction
trx, _ := s.trx.Begin(ctx)
// Retrieve the transaction by ID
transaction, err := s.repo.FindByID(ctx, req.TransactionID)
if err != nil {
logger.ContextLogger(ctx).Error("error when retrieving transaction by ID", zap.Error(err))
trx.Rollback()
return errors2.ErrorInternalServer
}
if transaction.Status != "WAITING_APPROVAL" {
return errors2.NewError(errors2.ErrorBadRequest.ErrorType(),
"invalid state, transaction already approved or rejected")
}
// Retrieve the wallet associated with the transaction's partner ID
wallet, err := s.wallet.GetForUpdate(ctx, trx, transaction.PartnerID)
if err != nil {
logger.ContextLogger(ctx).Error("error retrieving wallet by partner ID", zap.Error(err))
trx.Rollback()
return errors2.ErrorInternalServer
}
// Approve or Reject the transaction
switch req.Status {
case "APPROVE":
if wallet.AuthBalance < transaction.Amount {
trx.Rollback()
return errors2.ErrorInsufficientBalance
}
wallet.AuthBalance -= transaction.Amount
case "REJECT":
if wallet.AuthBalance < transaction.Amount {
trx.Rollback()
return errors2.ErrorInsufficientBalance
}
wallet.AuthBalance -= transaction.Amount
wallet.Balance += transaction.Amount
default:
trx.Rollback()
return errors2.ErrorBadRequest
}
// Update the wallet with the new balances
if _, err := s.wallet.Update(ctx, trx, wallet); err != nil {
logger.ContextLogger(ctx).Error("error updating wallet balance", zap.Error(err))
trx.Rollback()
return errors2.ErrorInternalServer
}
// Update the transaction status and persist changes
transaction.Status = req.Status
transaction.UpdatedBy = ctx.RequestedBy()
if _, err := s.repo.Update(ctx, trx, transaction); err != nil {
logger.ContextLogger(ctx).Error("error updating transaction status", zap.Error(err))
trx.Rollback()
return errors2.ErrorInternalServer
}
if err := trx.Commit().Error; err != nil {
logger.ContextLogger(ctx).Error("error committing transaction", zap.Error(err))
return errors2.ErrorInternalServer
}
return nil
}