Add callback and update user role
This commit is contained in:
parent
4d402e32c9
commit
04a89c5508
@ -15,6 +15,7 @@ type Context interface {
|
||||
IsSuperAdmin() bool
|
||||
IsCasheer() bool
|
||||
GetPartnerID() *int64
|
||||
GetSiteID() *int64
|
||||
}
|
||||
|
||||
type MyContextImpl struct {
|
||||
@ -24,6 +25,7 @@ type MyContextImpl struct {
|
||||
requestID string
|
||||
partnerID int64
|
||||
roleID int
|
||||
siteID int64
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) RequestedBy() int64 {
|
||||
@ -45,12 +47,20 @@ func (m *MyContextImpl) GetPartnerID() *int64 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) GetSiteID() *int64 {
|
||||
if m.siteID != 0 {
|
||||
return &m.siteID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMyContext(parent context.Context, claims *entity.JWTAuthClaims) (*MyContextImpl, error) {
|
||||
return &MyContextImpl{
|
||||
Context: parent,
|
||||
requestedBy: claims.UserID,
|
||||
partnerID: claims.PartnerID,
|
||||
roleID: claims.Role,
|
||||
siteID: claims.SiteID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ type UserDB struct {
|
||||
RoleID int64 `gorm:"column:role_id" json:"role_id"`
|
||||
RoleName string `gorm:"column:role_name" json:"role_name"`
|
||||
PartnerID *int64 `gorm:"column:partner_id" json:"partner_id"`
|
||||
SiteID *int64 `gorm:"column:site_id" json:"site_id"`
|
||||
PartnerName string `gorm:"column:partner_name" json:"partner_name"`
|
||||
PartnerStatus string `gorm:"column:partner_status" json:"partner_status"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
@ -50,6 +51,7 @@ func (u *UserDB) ToUser() *User {
|
||||
RoleName: u.RoleName,
|
||||
PartnerID: u.PartnerID,
|
||||
PartnerName: u.PartnerName,
|
||||
SiteID: u.SiteID,
|
||||
}
|
||||
|
||||
return userEntity
|
||||
@ -67,6 +69,7 @@ func (u *UserDB) ToUserRoleDB() *UserRoleDB {
|
||||
PartnerID: u.PartnerID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
SiteID: u.SiteID,
|
||||
}
|
||||
|
||||
return userRole
|
||||
|
||||
@ -8,6 +8,7 @@ type JWTAuthClaims struct {
|
||||
Email string `json:"email"`
|
||||
Role int `json:"role"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
SiteID int64 `json:"site_id"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ type OrderItem struct {
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
Product *Product `gorm:"foreignKey:ItemID;references:ID"`
|
||||
}
|
||||
|
||||
func (OrderItem) TableName() string {
|
||||
@ -75,3 +76,8 @@ func (o *Order) SetExecutePaymentStatus() {
|
||||
}
|
||||
o.Status = "PENDING"
|
||||
}
|
||||
|
||||
type CallbackRequest struct {
|
||||
TransactionStatus string `json:"transaction_status"`
|
||||
TransactionID string `json:"transaction_id"`
|
||||
}
|
||||
|
||||
@ -8,8 +8,8 @@ import (
|
||||
|
||||
type Payment struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey;column:id"`
|
||||
PartnerID string `gorm:"type:varchar;not null;column:partner_id"`
|
||||
OrderID string `gorm:"type:varchar;not null;column:order_id"`
|
||||
PartnerID int64 `gorm:"type:numeric;not null;column:partner_id"`
|
||||
OrderID int64 `gorm:"type:numeric;not null;column:order_id"`
|
||||
ReferenceID string `gorm:"type:varchar;not null;column:reference_id"`
|
||||
Channel string `gorm:"type:varchar;not null;column:channel"`
|
||||
PaymentType string `gorm:"type:varchar;not null;column:payment_type"`
|
||||
|
||||
@ -37,6 +37,11 @@ type ProductSearch struct {
|
||||
Offset int
|
||||
}
|
||||
|
||||
type ProductPOS struct {
|
||||
PartnerID int64
|
||||
SiteID int64
|
||||
}
|
||||
|
||||
type ProductList []*ProductDB
|
||||
|
||||
type ProductDB struct {
|
||||
|
||||
@ -21,6 +21,7 @@ type User struct {
|
||||
RoleID role.Role
|
||||
RoleName string
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
PartnerName string
|
||||
}
|
||||
|
||||
@ -39,6 +40,7 @@ type UserRoleDB struct {
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
RoleID int64 `gorm:"column:role_id"`
|
||||
PartnerID *int64 `gorm:"column:partner_id"`
|
||||
SiteID *int64 `gorm:"column:site_id"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
}
|
||||
@ -66,6 +68,7 @@ func (u *User) ToUserDB(createdBy int64) (*UserDB, error) {
|
||||
PartnerID: u.PartnerID,
|
||||
Status: userstatus.Active,
|
||||
CreatedBy: createdBy,
|
||||
SiteID: u.SiteID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
71
internal/handlers/http/midtrans/order.go
Normal file
71
internal/handlers/http/midtrans/order.go
Normal file
@ -0,0 +1,71 @@
|
||||
package mdtrns
|
||||
|
||||
import (
|
||||
"furtuna-be/internal/handlers/request"
|
||||
"furtuna-be/internal/handlers/response"
|
||||
"furtuna-be/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service services.Order
|
||||
}
|
||||
|
||||
func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
|
||||
route := group.Group("/midtrans")
|
||||
|
||||
route.POST("/callback", h.Callback)
|
||||
}
|
||||
|
||||
func NewHandler(service services.Order) *Handler {
|
||||
return &Handler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Callback(c *gin.Context) {
|
||||
var callbackData request.MidtransCallbackRequest
|
||||
if err := c.ShouldBindJSON(&callbackData); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
validStatuses := []string{"settlement", "expire", "deny", "cancel", "capture", "failure"}
|
||||
|
||||
isValidStatus := false
|
||||
for _, status := range validStatuses {
|
||||
if callbackData.TransactionStatus == status {
|
||||
isValidStatus = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isValidStatus {
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Message: "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := h.service.ProcessCallback(c, callbackData.ToEntity())
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, response.BaseResponse{
|
||||
Success: false,
|
||||
Status: http.StatusBadRequest,
|
||||
Message: err.Error(),
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Message: "order",
|
||||
})
|
||||
|
||||
}
|
||||
@ -108,6 +108,7 @@ func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse) response
|
||||
ItemID: item.ItemID,
|
||||
Quantity: item.Quantity,
|
||||
Price: item.Price,
|
||||
Name: item.Product.Name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +134,7 @@ func MapOrderToExecuteOrderResponse(orderResponse *entity.ExecuteOrderResponse)
|
||||
ItemID: item.ItemID,
|
||||
Quantity: item.Quantity,
|
||||
Price: item.Price,
|
||||
Name: item.Product.Name,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
|
||||
route := group.Group("/product")
|
||||
|
||||
route.POST("/", jwt, h.Create)
|
||||
route.GET("/pos", jwt, h.GetPOSProduct)
|
||||
route.GET("/list", jwt, h.GetAll)
|
||||
route.PUT("/:id", jwt, h.Update)
|
||||
route.GET("/:id", jwt, h.GetByID)
|
||||
@ -157,6 +158,37 @@ func (h *Handler) GetAll(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) GetPOSProduct(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
|
||||
var req request.ProductParam
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.ErrorWrapper(c, errors.ErrorBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsCasheer() {
|
||||
response.ErrorWrapper(c, errors.ErrorBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
products, err := h.service.GetProductPOS(c.Request.Context(), entity.ProductPOS{
|
||||
PartnerID: *ctx.GetPartnerID(),
|
||||
SiteID: *ctx.GetSiteID(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: h.toProductResponseList(products, int64(len(products)), req),
|
||||
})
|
||||
}
|
||||
|
||||
// Delete handles the deletion of a product by ID.
|
||||
// @Summary Delete a product by ID
|
||||
// @Description Delete a product based on the provided ID.
|
||||
@ -240,14 +272,18 @@ func (h *Handler) GetByID(c *gin.Context) {
|
||||
|
||||
func (h *Handler) toProductResponse(resp *entity.Product) response.Product {
|
||||
return response.Product{
|
||||
ID: resp.ID,
|
||||
Name: resp.Name,
|
||||
Type: resp.Type,
|
||||
Price: resp.Price,
|
||||
Status: resp.Status,
|
||||
Description: resp.Description,
|
||||
CreatedAt: resp.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: resp.CreatedAt.Format(time.RFC3339),
|
||||
ID: resp.ID,
|
||||
Name: resp.Name,
|
||||
Type: resp.Type,
|
||||
Price: resp.Price,
|
||||
Status: resp.Status,
|
||||
Description: resp.Description,
|
||||
CreatedAt: resp.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: resp.CreatedAt.Format(time.RFC3339),
|
||||
PartnerID: resp.PartnerID,
|
||||
SiteID: resp.SiteID,
|
||||
IsSeasonTicket: resp.IsSeasonTicket,
|
||||
IsWeekendTicket: resp.IsWeekendTicket,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"furtuna-be/internal/constants/role"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -64,6 +65,11 @@ func (h *Handler) Create(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if req.RoleID == role.Casheer && req.SiteID == nil {
|
||||
response.ErrorWrapper(c, errors.NewServiceException("site id is required for cashier"))
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.service.Create(ctx, req.ToEntity())
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
|
||||
39
internal/handlers/request/midtrans.go
Normal file
39
internal/handlers/request/midtrans.go
Normal file
@ -0,0 +1,39 @@
|
||||
package request
|
||||
|
||||
import "furtuna-be/internal/entity"
|
||||
|
||||
type MidtransCallbackRequest struct {
|
||||
VANumbers []VANumber `json:"va_numbers"`
|
||||
TransactionTime string `json:"transaction_time"`
|
||||
TransactionStatus string `json:"transaction_status"`
|
||||
TransactionID string `json:"transaction_id"`
|
||||
StatusMessage string `json:"status_message"`
|
||||
StatusCode string `json:"status_code"`
|
||||
SignatureKey string `json:"signature_key"`
|
||||
SettlementTime string `json:"settlement_time"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
OrderID string `json:"order_id"`
|
||||
MerchantID string `json:"merchant_id"`
|
||||
GrossAmount string `json:"gross_amount"`
|
||||
FraudStatus string `json:"fraud_status"`
|
||||
ExpiryTime string `json:"expiry_time"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
type VANumber struct {
|
||||
VANumber string `json:"va_number"`
|
||||
Bank string `json:"bank"`
|
||||
}
|
||||
|
||||
type MidtransCallbackBank struct {
|
||||
Bank string `json:"bank"`
|
||||
VaNumber string `json:"va_number"`
|
||||
BillerCode string `json:"biller_code"`
|
||||
}
|
||||
|
||||
func (m *MidtransCallbackRequest) ToEntity() *entity.CallbackRequest {
|
||||
return &entity.CallbackRequest{
|
||||
TransactionID: m.OrderID,
|
||||
TransactionStatus: m.TransactionStatus,
|
||||
}
|
||||
}
|
||||
@ -8,14 +8,15 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
PartnerID *int64 `json:"partner_id"`
|
||||
RoleID int64 `json:"role_id" validate:"required"`
|
||||
NIK string `json:"nik"`
|
||||
UserType string `json:"user_type"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
PartnerID *int64 `json:"partner_id"`
|
||||
SiteID *int64 `json:"site_id"`
|
||||
RoleID role.Role `json:"role_id" validate:"required"`
|
||||
NIK string `json:"nik"`
|
||||
UserType string `json:"user_type"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
}
|
||||
|
||||
func (e *User) Validate() error {
|
||||
@ -34,6 +35,7 @@ func (u *User) ToEntity() *entity.User {
|
||||
Password: u.Password,
|
||||
RoleID: role.Role(u.RoleID),
|
||||
PartnerID: u.PartnerID,
|
||||
SiteID: u.SiteID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -82,4 +82,5 @@ type CreateOrderItemResponse struct {
|
||||
ItemID int64 `json:"item_id"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
Price float64 `json:"price"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ func (r *AuthRepository) CheckExistsUserAccount(ctx context.Context, email strin
|
||||
|
||||
err := r.db.
|
||||
Table("users").
|
||||
Select("users.*, user_roles.role_id, user_roles.partner_id, roles.role_name, partners.name as partner_name, partners.status as partner_status").
|
||||
Select("users.*, user_roles.role_id, user_roles.partner_id, user_roles.site_id, roles.role_name, partners.name as partner_name, partners.status as partner_status").
|
||||
Where("users.email = ?", email).
|
||||
Joins("left join user_roles on users.id = user_roles.user_id").
|
||||
Joins("left join roles on user_roles.role_id = roles.role_id").
|
||||
|
||||
@ -51,6 +51,11 @@ func (c *CryptoImpl) GenerateJWT(user *entity.User) (string, error) {
|
||||
partnerID = *user.PartnerID
|
||||
}
|
||||
|
||||
siteID := int64(0)
|
||||
if user.SiteID != nil {
|
||||
siteID = *user.SiteID
|
||||
}
|
||||
|
||||
claims := &entity.JWTAuthClaims{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Subject: strconv.FormatInt(user.ID, 10),
|
||||
@ -63,6 +68,7 @@ func (c *CryptoImpl) GenerateJWT(user *entity.User) (string, error) {
|
||||
Email: user.Email,
|
||||
Role: int(user.RoleID),
|
||||
PartnerID: partnerID,
|
||||
SiteID: siteID,
|
||||
}
|
||||
|
||||
token, err := jwt.
|
||||
|
||||
@ -24,7 +24,7 @@ func (r *OrderRepository) Create(ctx context.Context, order *entity.Order) (*ent
|
||||
logger.ContextLogger(ctx).Error("error when creating order", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return order, nil
|
||||
return r.FindByID(ctx, order.ID)
|
||||
}
|
||||
|
||||
func (r *OrderRepository) UpdateStatus(ctx context.Context, orderID int64, status string) (*entity.Order, error) {
|
||||
@ -43,13 +43,36 @@ func (r *OrderRepository) UpdateStatus(ctx context.Context, orderID int64, statu
|
||||
|
||||
func (r *OrderRepository) FindByID(ctx context.Context, id int64) (*entity.Order, error) {
|
||||
var order entity.Order
|
||||
if err := r.db.WithContext(ctx).Preload("OrderItems").First(&order, id).Error; err != nil {
|
||||
|
||||
err := r.db.WithContext(ctx).Preload("OrderItems", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Product")
|
||||
}).First(&order, id).Error
|
||||
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when finding order by ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &order, nil
|
||||
}
|
||||
|
||||
func (r *OrderRepository) SetOrderStatus(ctx context.Context, db *gorm.DB, orderID int64, status string) error {
|
||||
var order entity.Order
|
||||
if err := db.WithContext(ctx).Preload("OrderItems").First(&order, orderID).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when finding order by ID", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
order.Status = status
|
||||
|
||||
if err := db.WithContext(ctx).Save(&order).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating order status", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *OrderRepository) Update(ctx context.Context, order *entity.Order) (*entity.Order, error) {
|
||||
if err := r.db.WithContext(ctx).Save(order).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating order", zap.Error(err))
|
||||
|
||||
@ -38,6 +38,14 @@ func (r *PaymentRepository) Update(ctx context.Context, payment *entity.Payment)
|
||||
return payment, nil
|
||||
}
|
||||
|
||||
func (r *PaymentRepository) UpdateWithTx(ctx context.Context, tx *gorm.DB, payment *entity.Payment) (*entity.Payment, error) {
|
||||
if err := tx.WithContext(ctx).Save(payment).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating payment", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return payment, nil
|
||||
}
|
||||
|
||||
// FindByID retrieves a payment record by its ID
|
||||
func (r *PaymentRepository) FindByID(ctx context.Context, id uuid.UUID) (*entity.Payment, error) {
|
||||
payment := new(entity.Payment)
|
||||
@ -62,9 +70,9 @@ func (r *PaymentRepository) FindByOrderAndPartnerID(ctx context.Context, orderID
|
||||
}
|
||||
|
||||
// FindByReferenceID retrieves a payment record by its reference ID
|
||||
func (r *PaymentRepository) FindByReferenceID(ctx context.Context, referenceID string) (*entity.Payment, error) {
|
||||
func (r *PaymentRepository) FindByReferenceID(ctx context.Context, db *gorm.DB, referenceID string) (*entity.Payment, error) {
|
||||
payment := new(entity.Payment)
|
||||
if err := r.db.WithContext(ctx).Where("reference_id = ?", referenceID).First(payment).Error; err != nil {
|
||||
if err := db.WithContext(ctx).Where("reference_id = ?", referenceID).First(payment).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when finding payment by reference ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -45,6 +45,16 @@ func (b *ProductRepository) GetProductByID(ctx context.Context, id int64) (*enti
|
||||
return product, nil
|
||||
}
|
||||
|
||||
func (b *ProductRepository) GetProductByPartnerIDAndSiteID(ctx context.Context, partnerID, siteID int64) (entity.ProductList, error) {
|
||||
var products []*entity.ProductDB
|
||||
if err := b.db.WithContext(ctx).Where("partner_id = ? AND site_id = ?", partnerID, siteID).Find(&products).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when finding product by partner ID and site id", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return products, nil
|
||||
}
|
||||
|
||||
func (b *ProductRepository) GetAllProducts(ctx context.Context, req entity.ProductSearch) (entity.ProductList, int, error) {
|
||||
var products []*entity.ProductDB
|
||||
var total int64
|
||||
|
||||
@ -112,6 +112,7 @@ type Product interface {
|
||||
CreateProduct(ctx context.Context, product *entity.ProductDB) (*entity.ProductDB, error)
|
||||
UpdateProduct(ctx context.Context, product *entity.ProductDB) (*entity.ProductDB, error)
|
||||
GetProductByID(ctx context.Context, id int64) (*entity.ProductDB, error)
|
||||
GetProductByPartnerIDAndSiteID(ctx context.Context, partnerID, siteID int64) (entity.ProductList, error)
|
||||
GetAllProducts(ctx context.Context, req entity.ProductSearch) (entity.ProductList, int, error)
|
||||
DeleteProduct(ctx context.Context, id int64) error
|
||||
GetProductsByIDs(ctx context.Context, ids []int64, partnerID int64) ([]*entity.ProductDB, error)
|
||||
@ -121,6 +122,7 @@ type Order interface {
|
||||
Create(ctx context.Context, order *entity.Order) (*entity.Order, error)
|
||||
FindByID(ctx context.Context, id int64) (*entity.Order, error)
|
||||
Update(ctx context.Context, order *entity.Order) (*entity.Order, error)
|
||||
SetOrderStatus(ctx context.Context, db *gorm.DB, orderID int64, status string) error
|
||||
}
|
||||
|
||||
type OSSRepository interface {
|
||||
@ -154,6 +156,8 @@ type TransactionManager interface {
|
||||
|
||||
type WalletRepository interface {
|
||||
Create(ctx context.Context, tx *gorm.DB, wallet *entity.Wallet) (*entity.Wallet, error)
|
||||
Update(ctx context.Context, db *gorm.DB, wallet *entity.Wallet) (*entity.Wallet, error)
|
||||
GetByPartnerID(ctx context.Context, db *gorm.DB, partnerID int64) (*entity.Wallet, error)
|
||||
}
|
||||
|
||||
type Midtrans interface {
|
||||
@ -163,5 +167,7 @@ type Midtrans interface {
|
||||
type Payment interface {
|
||||
Create(ctx context.Context, payment *entity.Payment) (*entity.Payment, error)
|
||||
Update(ctx context.Context, payment *entity.Payment) (*entity.Payment, error)
|
||||
UpdateWithTx(ctx context.Context, tx *gorm.DB, payment *entity.Payment) (*entity.Payment, error)
|
||||
FindByOrderAndPartnerID(ctx context.Context, orderID, partnerID int64) (*entity.Payment, error)
|
||||
FindByReferenceID(ctx context.Context, db *gorm.DB, referenceID string) (*entity.Payment, error)
|
||||
}
|
||||
|
||||
@ -28,14 +28,23 @@ func (r *WalletRepository) Create(ctx context.Context, tx *gorm.DB, wallet *enti
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
func (r *WalletRepository) Update(ctx context.Context, wallet *entity.Wallet) (*entity.Wallet, error) {
|
||||
if err := r.db.Save(wallet).Error; err != nil {
|
||||
func (r *WalletRepository) Update(ctx context.Context, db *gorm.DB, wallet *entity.Wallet) (*entity.Wallet, error) {
|
||||
if err := db.Save(wallet).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating wallet", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
func (r *WalletRepository) GetByPartnerID(ctx context.Context, db *gorm.DB, partnerID int64) (*entity.Wallet, error) {
|
||||
wallet := new(entity.Wallet)
|
||||
if err := db.WithContext(ctx).Where("partner_id = ?", partnerID).First(wallet).Error; err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when finding wallet by partner ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
func (r *WalletRepository) GetByID(ctx context.Context, id int64) (*entity.Wallet, error) {
|
||||
wallet := new(entity.Wallet)
|
||||
if err := r.db.First(wallet, id).Error; err != nil {
|
||||
|
||||
@ -2,6 +2,7 @@ package routes
|
||||
|
||||
import (
|
||||
"furtuna-be/internal/handlers/http/branch"
|
||||
mdtrns "furtuna-be/internal/handlers/http/midtrans"
|
||||
"furtuna-be/internal/handlers/http/order"
|
||||
"furtuna-be/internal/handlers/http/oss"
|
||||
"furtuna-be/internal/handlers/http/partner"
|
||||
@ -54,6 +55,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
|
||||
oss.NewOssHandler(serviceManager.OSSSvc),
|
||||
partner.NewHandler(serviceManager.PartnerSvc),
|
||||
site.NewHandler(serviceManager.SiteSvc),
|
||||
mdtrns.NewHandler(serviceManager.OrderSvc),
|
||||
}
|
||||
|
||||
for _, handler := range serverRoutes {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"furtuna-be/internal/common/logger"
|
||||
order2 "furtuna-be/internal/constants/order"
|
||||
"furtuna-be/internal/entity"
|
||||
@ -16,21 +18,29 @@ import (
|
||||
)
|
||||
|
||||
type OrderService struct {
|
||||
repo repository.Order
|
||||
crypt repository.Crypto
|
||||
product repository.Product
|
||||
midtrans repository.Midtrans
|
||||
payment repository.Payment
|
||||
repo repository.Order
|
||||
crypt repository.Crypto
|
||||
product repository.Product
|
||||
midtrans repository.Midtrans
|
||||
payment repository.Payment
|
||||
txmanager repository.TransactionManager
|
||||
wallet repository.WalletRepository
|
||||
}
|
||||
|
||||
func NewOrderService(repo repository.Order, product repository.Product, crypt repository.Crypto,
|
||||
midtrans repository.Midtrans, payment repository.Payment) *OrderService {
|
||||
func NewOrderService(
|
||||
repo repository.Order,
|
||||
product repository.Product, crypt repository.Crypto,
|
||||
midtrans repository.Midtrans, payment repository.Payment,
|
||||
txmanager repository.TransactionManager,
|
||||
wallet repository.WalletRepository) *OrderService {
|
||||
return &OrderService{
|
||||
repo: repo,
|
||||
product: product,
|
||||
crypt: crypt,
|
||||
midtrans: midtrans,
|
||||
payment: payment,
|
||||
repo: repo,
|
||||
product: product,
|
||||
crypt: crypt,
|
||||
midtrans: midtrans,
|
||||
payment: payment,
|
||||
txmanager: txmanager,
|
||||
wallet: wallet,
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +88,7 @@ func (s *OrderService) CreateOrder(ctx context.Context, req *entity.OrderRequest
|
||||
Price: productMap[item.ProductID].Price,
|
||||
Quantity: item.Quantity,
|
||||
CreatedBy: req.CreatedBy,
|
||||
Product: productMap[item.ProductID].ToProduct(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -116,7 +127,6 @@ func (s *OrderService) Execute(ctx context.Context, req *entity.OrderExecuteRequ
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check for existing payment to handle idempotency
|
||||
payment, err := s.payment.FindByOrderAndPartnerID(ctx, orderID, partnerID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.ContextLogger(ctx).Error("error getting payment data from db", zap.Error(err))
|
||||
@ -199,13 +209,13 @@ func (s *OrderService) processNonCashPayment(ctx context.Context, order *entity.
|
||||
}
|
||||
|
||||
payment := &entity.Payment{
|
||||
PartnerID: strconv.FormatInt(partnerID, 10),
|
||||
OrderID: strconv.FormatInt(order.ID, 10),
|
||||
PartnerID: partnerID,
|
||||
OrderID: order.ID,
|
||||
ReferenceID: paymentRequest.PaymentReferenceID,
|
||||
Channel: "xendit",
|
||||
Channel: "XENDIT",
|
||||
PaymentType: order.PaymentType,
|
||||
Amount: order.Amount,
|
||||
State: "pending",
|
||||
State: "PENDING",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
RequestMetadata: requestMetadata,
|
||||
@ -219,3 +229,70 @@ func (s *OrderService) processNonCashPayment(ctx context.Context, order *entity.
|
||||
|
||||
return paymentResponse, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) ProcessCallback(ctx context.Context, req *entity.CallbackRequest) error {
|
||||
tx, err := s.txmanager.Begin(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
err = s.processPayment(ctx, tx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process payment: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
func (s *OrderService) processPayment(ctx context.Context, tx *gorm.DB, req *entity.CallbackRequest) error {
|
||||
existingPayment, err := s.payment.FindByReferenceID(ctx, tx, req.TransactionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve payment: %w", err)
|
||||
}
|
||||
|
||||
existingPayment.State = updatePaymentState(req.TransactionStatus)
|
||||
_, err = s.payment.UpdateWithTx(ctx, tx, existingPayment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update payment: %w", err)
|
||||
}
|
||||
|
||||
if err := s.updateOrderStatus(ctx, tx, existingPayment.State, existingPayment.OrderID); err != nil {
|
||||
return fmt.Errorf("failed to update order status: %w", err)
|
||||
}
|
||||
|
||||
if existingPayment.State == "PAID" {
|
||||
if err := s.updateWalletBalance(ctx, tx, existingPayment.PartnerID, existingPayment.Amount); err != nil {
|
||||
return fmt.Errorf("failed to update wallet balance: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePaymentState(status string) string {
|
||||
switch status {
|
||||
case "settlement", "capture":
|
||||
return "PAID"
|
||||
case "expire", "deny", "cancel", "failure":
|
||||
return "EXPIRED"
|
||||
default:
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OrderService) updateOrderStatus(ctx context.Context, tx *gorm.DB, status string, orderID int64) error {
|
||||
if status != "PENDING" {
|
||||
return s.repo.SetOrderStatus(ctx, tx, orderID, status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrderService) updateWalletBalance(ctx context.Context, tx *gorm.DB, partnerID int64, amount float64) error {
|
||||
wallet, err := s.wallet.GetByPartnerID(ctx, tx, partnerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get wallet: %w", err)
|
||||
}
|
||||
wallet.Balance += amount
|
||||
_, err = s.wallet.Update(ctx, tx, wallet)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -70,6 +70,16 @@ func (s *ProductService) GetAll(ctx context.Context, search entity.ProductSearch
|
||||
return products.ToProductList(), total, nil
|
||||
}
|
||||
|
||||
func (s *ProductService) GetProductPOS(ctx context.Context, search entity.ProductPOS) ([]*entity.Product, error) {
|
||||
products, err := s.repo.GetProductByPartnerIDAndSiteID(ctx, search.PartnerID, search.SiteID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when get all products", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return products.ToProductList(), nil
|
||||
}
|
||||
|
||||
func (s *ProductService) Delete(ctx mycontext.Context, id int64) error {
|
||||
productDB, err := s.repo.GetProductByID(ctx, id)
|
||||
if err != nil {
|
||||
|
||||
@ -41,8 +41,9 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
||||
BranchSvc: branch.NewBranchService(repo.Branch),
|
||||
StudioSvc: studio.NewStudioService(repo.Studio),
|
||||
ProductSvc: product.NewProductService(repo.Product),
|
||||
OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Crypto, repo.Midtrans, repo.Payment),
|
||||
OSSSvc: oss.NewOSSService(repo.OSS),
|
||||
OrderSvc: order.NewOrderService(repo.Order, repo.Product,
|
||||
repo.Crypto, repo.Midtrans, repo.Payment, repo.Trx, repo.Wallet),
|
||||
OSSSvc: oss.NewOSSService(repo.OSS),
|
||||
PartnerSvc: partner.NewPartnerService(
|
||||
repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet),
|
||||
SiteSvc: site.NewSiteService(repo.Site),
|
||||
@ -90,12 +91,14 @@ type Product interface {
|
||||
Update(ctx mycontext.Context, id int64, productReq *entity.Product) (*entity.Product, error)
|
||||
GetByID(ctx context.Context, id int64) (*entity.Product, error)
|
||||
GetAll(ctx context.Context, search entity.ProductSearch) ([]*entity.Product, int, error)
|
||||
GetProductPOS(ctx context.Context, search entity.ProductPOS) ([]*entity.Product, error)
|
||||
Delete(ctx mycontext.Context, id int64) error
|
||||
}
|
||||
|
||||
type Order interface {
|
||||
CreateOrder(ctx context.Context, req *entity.OrderRequest) (*entity.OrderResponse, error)
|
||||
Execute(ctx context.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error)
|
||||
ProcessCallback(ctx context.Context, req *entity.CallbackRequest) error
|
||||
}
|
||||
|
||||
type OSSService interface {
|
||||
|
||||
@ -9,7 +9,7 @@ metadata:
|
||||
nginx.ingress.kubernetes.io/ingress-class: "nginx" # Add this line
|
||||
spec:
|
||||
rules:
|
||||
- host: "furtuna-be.app-dev.altru.id"
|
||||
- host: "furtuna-backend.app-dev.altru.id"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
@ -21,5 +21,5 @@ spec:
|
||||
number: 3300
|
||||
tls:
|
||||
- hosts:
|
||||
- "furtuna-be.app-dev.altru.id"
|
||||
secretName: furtuna-be-app-dev-biz-id-tls
|
||||
- "furtuna-backend.app-dev.altru.id"
|
||||
secretName: furtuna-backend-app-dev-biz-id-tls
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
CREATE TABLE public.payments
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
partner_id varchar NOT NULL,
|
||||
order_id varchar NOT NULL,
|
||||
partner_id numeric NOT NULL,
|
||||
order_id numeric NOT NULL,
|
||||
reference_id varchar NOT NULL,
|
||||
channel varchar NOT NULL,
|
||||
payment_type varchar NOT NULL,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user