diff --git a/internal/common/errors/errors.go b/internal/common/errors/errors.go index 2d912a3..ab6bfcc 100644 --- a/internal/common/errors/errors.go +++ b/internal/common/errors/errors.go @@ -5,44 +5,48 @@ import "net/http" type ErrType string const ( - errRequestTimeOut ErrType = "Request Timeout to 3rd Party" - errConnectTimeOut ErrType = "Connect Timeout to 3rd Party" - errFailedExternalCall ErrType = "Failed response from 3rd Party call" - errExternalCall ErrType = "error on 3rd Party call" - errInvalidRequest ErrType = "Invalid Request" - errBadRequest ErrType = "Bad Request" - errOrderNotFound ErrType = "Astria order is not found" - errCheckoutIDNotDefined ErrType = "Checkout client id not found" - errInternalServer ErrType = "Internal Server error" - errExternalServer ErrType = "External Server error" - errUserIsNotFound ErrType = "User is not found" - errInvalidLogin ErrType = "User email or password is invalid" - errUnauthorized ErrType = "Unauthorized" - errInsufficientBalance ErrType = "Insufficient Balance" - errInactivePartner ErrType = "Partner's license is invalid or has expired. Please contact Admin Support." - errTicketAlreadyUsed ErrType = "Ticket Already Used." - errProductIsRequired ErrType = "Product" - errEmailAndPhoneNumberRequired ErrType = "Email or Phone is required" + errRequestTimeOut ErrType = "Request Timeout to 3rd Party" + errConnectTimeOut ErrType = "Connect Timeout to 3rd Party" + errFailedExternalCall ErrType = "Failed response from 3rd Party call" + errExternalCall ErrType = "error on 3rd Party call" + errInvalidRequest ErrType = "Invalid Request" + errBadRequest ErrType = "Bad Request" + errOrderNotFound ErrType = "Astria order is not found" + errCheckoutIDNotDefined ErrType = "Checkout client id not found" + errInternalServer ErrType = "Internal Server error" + errExternalServer ErrType = "External Server error" + errUserIsNotFound ErrType = "User is not found" + errInvalidLogin ErrType = "User email or password is invalid" + errUnauthorized ErrType = "Unauthorized" + errInsufficientBalance ErrType = "Insufficient Balance" + errInactivePartner ErrType = "Partner's license is invalid or has expired. Please contact Admin Support." + errTicketAlreadyUsed ErrType = "Ticket Already Used." + errProductIsRequired ErrType = "Product" + errEmailAndPhoneNumberRequired ErrType = "Email or Phone is required" + errEmailAlreadyRegistered ErrType = "Email is already registered" + errPhoneNumberAlreadyRegistered ErrType = "Phone is already registered" ) var ( - ErrorBadRequest = NewServiceException(errBadRequest) - ErrorInvalidRequest = NewServiceException(errInvalidRequest) - ErrorExternalRequest = NewServiceException(errExternalServer) - ErrorUnauthorized = NewServiceException(errUnauthorized) - ErrorOrderNotFound = NewServiceException(errOrderNotFound) - ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined) - ErrorRequestTimeout = NewServiceException(errRequestTimeOut) - ErrorExternalCall = NewServiceException(errExternalCall) - ErrorFailedExternalCall = NewServiceException(errFailedExternalCall) - ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut) - ErrorInternalServer = NewServiceException(errInternalServer) - ErrorUserIsNotFound = NewServiceException(errUserIsNotFound) - ErrorUserInvalidLogin = NewServiceException(errInvalidLogin) - ErrorInsufficientBalance = NewServiceException(errInsufficientBalance) - ErrorInvalidLicense = NewServiceException(errInactivePartner) - ErrorTicketInvalidOrAlreadyUsed = NewServiceException(errTicketAlreadyUsed) - ErrorPhoneNumberEmailIsRequired = NewServiceException(errEmailAndPhoneNumberRequired) + ErrorBadRequest = NewServiceException(errBadRequest) + ErrorInvalidRequest = NewServiceException(errInvalidRequest) + ErrorExternalRequest = NewServiceException(errExternalServer) + ErrorUnauthorized = NewServiceException(errUnauthorized) + ErrorOrderNotFound = NewServiceException(errOrderNotFound) + ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined) + ErrorRequestTimeout = NewServiceException(errRequestTimeOut) + ErrorExternalCall = NewServiceException(errExternalCall) + ErrorFailedExternalCall = NewServiceException(errFailedExternalCall) + ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut) + ErrorInternalServer = NewServiceException(errInternalServer) + ErrorUserIsNotFound = NewServiceException(errUserIsNotFound) + ErrorUserInvalidLogin = NewServiceException(errInvalidLogin) + ErrorInsufficientBalance = NewServiceException(errInsufficientBalance) + ErrorInvalidLicense = NewServiceException(errInactivePartner) + ErrorTicketInvalidOrAlreadyUsed = NewServiceException(errTicketAlreadyUsed) + ErrorPhoneNumberEmailIsRequired = NewServiceException(errEmailAndPhoneNumberRequired) + ErrorPhoneNumberIsAlreadyRegistered = NewServiceException(errPhoneNumberAlreadyRegistered) + ErrorEmailIsAlreadyRegistered = NewServiceException(errEmailAlreadyRegistered) ) type Error interface { diff --git a/internal/entity/customer.go b/internal/entity/customer.go new file mode 100644 index 0000000..cab87f6 --- /dev/null +++ b/internal/entity/customer.go @@ -0,0 +1,2 @@ +package entity + diff --git a/internal/entity/in_progress_order.go b/internal/entity/in_progress_order.go new file mode 100644 index 0000000..0465c95 --- /dev/null +++ b/internal/entity/in_progress_order.go @@ -0,0 +1,29 @@ +package entity + +import "time" + +type InProgressOrder struct { + ID string + PartnerID int64 + CustomerID *int64 + CustomerName string + CreatedBy int64 + PaymentType string + PaymentProvider string + OrderItems []InProgressOrderItem + Payment Payment + User User + Source string + OrderType string + TableNumber string + CreatedAt time.Time + UpdatedAt time.Time +} + +type InProgressOrderItem struct { + ID int64 + InProgressOrderID int64 + ItemID int64 + Quantity int + Product *Product +} diff --git a/internal/entity/order.go b/internal/entity/order.go index 42aa965..4661286 100644 --- a/internal/entity/order.go +++ b/internal/entity/order.go @@ -5,24 +5,29 @@ import ( ) type Order struct { - ID int64 `gorm:"primaryKey;autoIncrement;column:id"` - PartnerID int64 `gorm:"type:int;column:partner_id"` - Status string `gorm:"type:varchar;column:status"` - Amount float64 `gorm:"type:numeric;not null;column:amount"` - Total float64 `gorm:"type:numeric;not null;column:total"` - Fee float64 `gorm:"type:numeric;not null;column:fee"` - CustomerID *int64 - InquiryID *string - Site *Site `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"` - CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"` - CreatedBy int64 `gorm:"type:int;column:created_by"` - PaymentType string `gorm:"type:varchar;column:payment_type"` - UpdatedBy int64 `gorm:"type:int;column:updated_by"` - OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` - Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` - User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"` - Source string `gorm:"type:varchar;column:source"` + ID int64 `gorm:"primaryKey;autoIncrement;column:id"` + PartnerID int64 `gorm:"type:int;column:partner_id"` + Status string `gorm:"type:varchar;column:status"` + Amount float64 `gorm:"type:numeric;not null;column:amount"` + Total float64 `gorm:"type:numeric;not null;column:total"` + Fee float64 `gorm:"type:numeric;not null;column:fee"` + CustomerID *int64 + CustomerName string + InquiryID *string + Site *Site `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"` + CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"` + CreatedBy int64 `gorm:"type:int;column:created_by"` + PaymentType string `gorm:"type:varchar;column:payment_type"` + PaymentProvider string `gorm:"type:varchar;column:payment_provider"` + UpdatedBy int64 `gorm:"type:int;column:updated_by"` + OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` + Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"` + User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"` + Source string `gorm:"type:varchar;column:source"` + OrderType string `gorm:"type:varchar;column:order_type"` + TableNumber string + InProgressOrderID string } type OrderDB struct { @@ -97,6 +102,9 @@ type OrderRequest struct { CustomerName string CustomerEmail string CustomerPhoneNumber string + TableNumber string + PaymentProvider string + OrderType string } type OrderItemRequest struct { @@ -111,11 +119,7 @@ type OrderExecuteRequest struct { } func (o *Order) SetExecutePaymentStatus() { - if o.PaymentType == "CASH" { - o.Status = "PAID" - return - } - o.Status = "PENDING" + o.Status = "PAID" } type CallbackRequest struct { diff --git a/internal/entity/order_inquiry.go b/internal/entity/order_inquiry.go index 35da00f..0ba0836 100644 --- a/internal/entity/order_inquiry.go +++ b/internal/entity/order_inquiry.go @@ -23,6 +23,9 @@ type OrderInquiry struct { UpdatedAt time.Time `json:"updated_at"` ExpiresAt time.Time `json:"expires_at"` OrderItems []OrderItem `json:"order_items"` + PaymentProvider string `json:"payment_provider"` + TableNumber string `json:"table_number"` + OrderType string `json:"order_type"` } type OrderCalculation struct { @@ -48,6 +51,9 @@ func NewOrderInquiry( customerName string, customerPhoneNumber string, customerEmail string, + paymentProvider string, + tableNumber string, + orderType string, ) *OrderInquiry { return &OrderInquiry{ ID: constants.GenerateUUID(), @@ -66,6 +72,9 @@ func NewOrderInquiry( CustomerName: customerName, CustomerEmail: customerEmail, CustomerPhoneNumber: customerPhoneNumber, + PaymentProvider: paymentProvider, + TableNumber: tableNumber, + OrderType: orderType, } } @@ -80,22 +89,24 @@ func (oi *OrderInquiry) AddOrderItem(item OrderItemRequest, product *Product) { }) } -func (i *OrderInquiry) ToOrder(paymentMethod string) *Order { +func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order { now := time.Now() order := &Order{ - PartnerID: i.PartnerID, - CustomerID: &i.CustomerID, - InquiryID: &i.ID, - Status: constants.StatusPaid, - Amount: i.Amount, - Fee: i.Fee, - Total: i.Total, - PaymentType: paymentMethod, - Source: i.Source, - CreatedBy: i.CreatedBy, - CreatedAt: now, - OrderItems: make([]OrderItem, len(i.OrderItems)), + PartnerID: i.PartnerID, + CustomerID: &i.CustomerID, + InquiryID: &i.ID, + Status: constants.StatusPaid, + Amount: i.Amount, + Fee: i.Fee, + Total: i.Total, + PaymentType: paymentMethod, + PaymentProvider: paymentProvider, + Source: i.Source, + CreatedBy: i.CreatedBy, + CreatedAt: now, + OrderItems: make([]OrderItem, len(i.OrderItems)), + OrderType: i.OrderType, } for idx, item := range i.OrderItems { diff --git a/internal/entity/user.go b/internal/entity/user.go index 19cf26a..8b66731 100644 --- a/internal/entity/user.go +++ b/internal/entity/user.go @@ -30,27 +30,29 @@ type User struct { } type Customer struct { - ID int64 - Name string - Email string - Password string - Phone string - Points int - Status userstatus.UserStatus - NIK string - UserType string - CreatedAt time.Time - UpdatedAt time.Time - RoleID role.Role - PhoneNumber string - RoleName string - PartnerID *int64 - SiteID *int64 - SiteName string - PartnerName string - ResetPassword bool - CustomerID string - BirthDate time.Time + ID int64 + Name string + Email string + Password string + Phone string + Points int + Status userstatus.UserStatus + NIK string + UserType string + CreatedAt time.Time + UpdatedAt time.Time + RoleID role.Role + PhoneNumber string + RoleName string + PartnerID *int64 + SiteID *int64 + SiteName string + PartnerName string + ResetPassword bool + CustomerID string + BirthDate time.Time + VerificationID string + OTP string } type AuthenticateUser struct { @@ -117,3 +119,9 @@ func (u User) HashedPassword(password string) (string, error) { return string(hashedPassword), nil } + +func (c Customer) HashedPassword() string { + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(c.Password), bcrypt.DefaultCost) + + return string(hashedPassword) +} diff --git a/internal/handlers/http/customerauth/auth.go b/internal/handlers/http/customerauth/auth.go index 44d4399..f79d6d4 100644 --- a/internal/handlers/http/customerauth/auth.go +++ b/internal/handlers/http/customerauth/auth.go @@ -1,6 +1,8 @@ package customerauth import ( + auth2 "enaklo-pos-be/internal/handlers/request" + "enaklo-pos-be/internal/services/v2/customer" "fmt" "net/http" "strings" @@ -8,7 +10,6 @@ import ( "github.com/gin-gonic/gin" "enaklo-pos-be/internal/common/errors" - auth2 "enaklo-pos-be/internal/handlers/request" "enaklo-pos-be/internal/handlers/response" "enaklo-pos-be/internal/services" ) @@ -16,6 +17,7 @@ import ( type AuthHandler struct { service services.Auth userService services.User + customerSvc customer.Service } func (a *AuthHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { @@ -24,12 +26,14 @@ func (a *AuthHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { authRoute.POST("/forgot-password", a.ForgotPassword) authRoute.POST("/reset-password", jwt, a.ResetPassword) authRoute.POST("/register", a.Register) + authRoute.POST("/verify", a.VerifyRegistration) } -func NewAuthHandler(service services.Auth, userService services.User) *AuthHandler { +func NewAuthHandler(service services.Auth, userService services.User, customerSvc customer.Service) *AuthHandler { return &AuthHandler{ service: service, userService: userService, + customerSvc: customerSvc, } } @@ -147,31 +151,47 @@ func (h *AuthHandler) ResetPassword(c *gin.Context) { } func (h *AuthHandler) Register(c *gin.Context) { - var req auth2.UserRegister + var req auth2.CustomerRegister if err := c.ShouldBindJSON(&req); err != nil { response.ErrorWrapper(c, errors.ErrorBadRequest) return } ctx := auth2.GetMyContext(c) - res, err := h.userService.Create(ctx, req.ToEntity()) + customer, err := h.customerSvc.RegistrationMember(ctx, req.ToEntity()) if err != nil { response.ErrorWrapper(c, err) return } - resp := response.UserRegister{ - ID: res.ID, - Name: res.Name, - Email: res.Email, - Status: string(res.Status), - CreatedAt: res.CreatedAt, - UpdatedAt: res.UpdatedAt, + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.CustomerRegistrationResp{ + EmailVerificationRequired: true, + PhoneVerificationRequired: false, + VerificationID: customer.VerificationID, + }, + }) +} + +func (h *AuthHandler) VerifyRegistration(c *gin.Context) { + var req auth2.VerifyEmailRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + ctx := auth2.GetMyContext(c) + err := h.customerSvc.VerifyOTP(ctx, req.VerificationID, req.OTPCode) + if err != nil { + response.ErrorWrapper(c, err) + return } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, - Data: resp, + Message: "Email verification successful", }) } diff --git a/internal/handlers/http/inprogress_order.go b/internal/handlers/http/inprogress_order.go new file mode 100644 index 0000000..7d54c2a --- /dev/null +++ b/internal/handlers/http/inprogress_order.go @@ -0,0 +1,173 @@ +package http + +import ( + "enaklo-pos-be/internal/common/errors" + "enaklo-pos-be/internal/entity" + "enaklo-pos-be/internal/handlers/request" + "enaklo-pos-be/internal/handlers/response" + "enaklo-pos-be/internal/services/v2/inprogress_order" + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "net/http" + "strconv" +) + +type InProgressOrderHandler struct { + service inprogress_order.InProgressOrderService +} + +func NewInProgressOrderHandler(service inprogress_order.InProgressOrderService) *InProgressOrderHandler { + return &InProgressOrderHandler{ + service: service, + } +} + +func (h *InProgressOrderHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/inprogress-order") + route.POST("/save", jwt, h.Save) + route.GET("/list", jwt, h.GetByPartnerID) +} + +type CreateInProgressOrderRequest struct { + CustomerID *int64 `json:"customer_id"` + CustomerName string `json:"customer_name" validate:"required_without=CustomerID"` + CustomerEmail string `json:"customer_email"` + CustomerPhoneNumber string `json:"customer_phone_number"` + PaymentMethod string `json:"payment_method"` + OrderItems []InProgressOrderItemRequest `json:"order_items" validate:"required,min=1,dive"` + OrderType string `json:"order_type"` + PaymentProvider string `json:"payment_provider"` + TableNumber string `json:"table_number"` + InProgressOrderID string `json:"in_progress_order_id"` +} + +type InProgressOrderItemRequest struct { + ProductID int64 `json:"product_id" validate:"required"` + Quantity int `json:"quantity" validate:"required,min=1"` +} + +type UpdateInProgressOrderRequest struct { + Status string `json:"status" validate:"required"` + Amount float64 `json:"amount" validate:"required,min=0"` + Fee float64 `json:"fee" validate:"min=0"` + Total float64 `json:"total" validate:"required,min=0"` + PaymentType string `json:"payment_type" validate:"required"` + OrderItems []InProgressOrderItemRequest `json:"order_items" validate:"required,min=1,dive"` +} + +func (h *InProgressOrderHandler) Save(c *gin.Context) { + ctx := request.GetMyContext(c) + userID := ctx.RequestedBy() + partnerID := ctx.GetPartnerID() + + var req CreateInProgressOrderRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + validate := validator.New() + if err := validate.Struct(req); err != nil { + response.ErrorWrapper(c, err) + return + } + + orderItems := make([]entity.InProgressOrderItem, len(req.OrderItems)) + for i, item := range req.OrderItems { + orderItems[i] = entity.InProgressOrderItem{ + ItemID: item.ProductID, + Quantity: item.Quantity, + } + } + + order := &entity.InProgressOrder{ + PartnerID: *partnerID, + CustomerID: req.CustomerID, + CustomerName: req.CustomerName, + CreatedBy: userID, + OrderItems: orderItems, + TableNumber: req.TableNumber, + OrderType: req.OrderType, + ID: req.InProgressOrderID, + } + + _, err := h.service.Save(ctx, order) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusCreated, response.BaseResponse{ + Success: true, + Status: http.StatusCreated, + }) +} + +func mapToInProgressOrderResponse(order *entity.InProgressOrder) map[string]interface{} { + orderItems := make([]map[string]interface{}, len(order.OrderItems)) + for i, item := range order.OrderItems { + orderItems[i] = map[string]interface{}{ + "id": item.ID, + "item_id": item.ItemID, + "quantity": item.Quantity, + "name": item.Product.Name, + "price": item.Product.Price, + "image": item.Product.Image, + } + } + + return map[string]interface{}{ + "id": order.ID, + "partner_id": order.PartnerID, + "customer_id": order.CustomerID, + "customer_name": order.CustomerName, + "payment_type": order.PaymentType, + "source": order.Source, + "created_by": order.CreatedBy, + "created_at": order.CreatedAt, + "updated_at": order.UpdatedAt, + "order_items": orderItems, + "table_number": order.TableNumber, + } +} + +func (h *InProgressOrderHandler) GetByPartnerID(c *gin.Context) { + ctx := request.GetMyContext(c) + + limitStr := c.DefaultQuery("limit", "10") + offsetStr := c.DefaultQuery("offset", "0") + + limit, err := strconv.Atoi(limitStr) + if err != nil || limit < 0 { + limit = 10 + } + + offset, err := strconv.Atoi(offsetStr) + if err != nil || offset < 0 { + offset = 0 + } + + orders, err := h.service.GetOrdersByPartnerID(ctx, *ctx.GetPartnerID(), limit, offset) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + orderResponses := make([]map[string]interface{}, len(orders)) + for i, order := range orders { + orderResponses[i] = mapToInProgressOrderResponse(order) + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: map[string]interface{}{ + "orders": orderResponses, + "pagination": map[string]interface{}{ + "limit": limit, + "offset": offset, + "count": len(orders), + }, + }, + }) +} diff --git a/internal/handlers/http/order.go b/internal/handlers/http/order.go index 4a2512d..df48f1c 100644 --- a/internal/handlers/http/order.go +++ b/internal/handlers/http/order.go @@ -35,6 +35,17 @@ type InquiryRequest struct { CustomerPhoneNumber string `json:"customer_phone_number"` PaymentMethod string `json:"payment_method" validate:"required"` OrderItems []OrderItemRequest `json:"order_items" validate:"required,min=1,dive"` + OrderType string `json:"order_type"` + PaymentProvider string `json:"payment_provider"` + TableNumber string `json:"table_number"` +} + +func (o *InquiryRequest) GetPaymentProvider() string { + if o.PaymentMethod == "CASH" { + return "CASH" + } + + return o.PaymentProvider } type OrderItemRequest struct { @@ -43,8 +54,10 @@ type OrderItemRequest struct { } type ExecuteRequest struct { - PaymentMethod string `json:"payment_method" validate:"required"` - Token string `json:"token"` + PaymentMethod string `json:"payment_method" validate:"required"` + PaymentProvider string `json:"payment_provider"` + InProgressOrderID string `json:"in_progress_order_id"` + Token string `json:"token"` } func (h *Handler) Inquiry(c *gin.Context) { @@ -82,6 +95,9 @@ func (h *Handler) Inquiry(c *gin.Context) { CustomerName: req.CustomerName, CustomerEmail: req.CustomerEmail, CustomerPhoneNumber: req.CustomerPhoneNumber, + OrderType: req.OrderType, + PaymentProvider: req.GetPaymentProvider(), + TableNumber: req.TableNumber, } result, err := h.service.CreateOrderInquiry(ctx, orderReq) @@ -112,7 +128,7 @@ func (h *Handler) Execute(c *gin.Context) { return } - result, err := h.service.ExecuteOrderInquiry(ctx, req.Token, req.PaymentMethod) + result, err := h.service.ExecuteOrderInquiry(ctx, req.Token, req.PaymentMethod, req.PaymentProvider, req.InProgressOrderID) if err != nil { response.ErrorWrapper(c, err) return diff --git a/internal/handlers/request/customer.go b/internal/handlers/request/customer.go new file mode 100644 index 0000000..edcde02 --- /dev/null +++ b/internal/handlers/request/customer.go @@ -0,0 +1,49 @@ +package request + +import ( + "enaklo-pos-be/internal/entity" + "github.com/go-playground/validator/v10" + "strings" + "time" +) + +type CustomerRegister struct { + Name string `json:"name" validate:"required" binding:"required"` + Email string `json:"email" validate:"required" binding:"required"` + PhoneNumber string `json:"phone_number" validate:"required" binding:"required"` + BirthDate string `json:"birth_date" validate:"required" binding:"required"` + Password string `json:"password" validate:"required" binding:"required"` +} + +func (c *CustomerRegister) Validate() error { + validate := validator.New() + if err := validate.Struct(c); err != nil { + return err + } + + return nil +} + +func (c *CustomerRegister) GetBirthdate() (time.Time, error) { + parsedDate, err := time.Parse("02-01-2006", c.BirthDate) + if err != nil { + return time.Time{}, err + } + return parsedDate, nil +} + +func (c *CustomerRegister) ToEntity() *entity.Customer { + birthdate, _ := c.GetBirthdate() + return &entity.Customer{ + Name: c.Name, + Email: strings.ToLower(c.Email), + PhoneNumber: c.PhoneNumber, + Password: c.Password, + BirthDate: birthdate, + } +} + +type VerifyEmailRequest struct { + VerificationID string `json:"verification_id" binding:"required"` + OTPCode string `json:"otp_code" binding:"required"` +} diff --git a/internal/handlers/request/order.go b/internal/handlers/request/order.go index c9123e8..349a23f 100644 --- a/internal/handlers/request/order.go +++ b/internal/handlers/request/order.go @@ -7,11 +7,13 @@ import ( ) type Order struct { - CustomerName string `json:"customer_name"` - CustomerPhone string `json:"customer_phone"` - CustomerEmail string `json:"customer_email"` - PaymentMethod string `json:"payment_method"` - OrderItems []OrderItem `json:"order_items"` + CustomerName string `json:"customer_name"` + CustomerPhone string `json:"customer_phone"` + CustomerEmail string `json:"customer_email"` + PaymentMethod string `json:"payment_method"` + PaymentProvider string `json:"payment_provider"` + TableNumber string `json:"table_number"` + OrderItems []OrderItem `json:"order_items"` } type CustomerOrder struct { diff --git a/internal/handlers/request/user.go b/internal/handlers/request/user.go index 2c415a4..06a5d1a 100644 --- a/internal/handlers/request/user.go +++ b/internal/handlers/request/user.go @@ -174,7 +174,5 @@ func (u *UserRegister) ToEntity() *entity.User { Email: strings.ToLower(u.Email), PhoneNumber: u.PhoneNumber, Password: u.Password, - RoleID: role.Customer, - UserType: "CUSTOMER", } } diff --git a/internal/handlers/response/customer.go b/internal/handlers/response/customer.go index f480a77..76c9f1e 100644 --- a/internal/handlers/response/customer.go +++ b/internal/handlers/response/customer.go @@ -33,3 +33,9 @@ func MapToCustomerListResponse(customers *entity.MemberList) []CustomerResponse return responseList } + +type CustomerRegistrationResp struct { + EmailVerificationRequired bool `json:"email_verification_required"` + PhoneVerificationRequired bool `json:"phone_verification_required"` + VerificationID string `json:"verification_id"` +} diff --git a/internal/handlers/response/user.go b/internal/handlers/response/user.go index 5509b77..585c7b4 100644 --- a/internal/handlers/response/user.go +++ b/internal/handlers/response/user.go @@ -46,10 +46,10 @@ type CustomerList struct { } type UserRegister struct { - ID int64 `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Status string `json:"status"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` + ID int64 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + PhoneNumber string `json:"phone_number"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` } diff --git a/internal/repository/In_progress_orde_repo.go b/internal/repository/In_progress_orde_repo.go new file mode 100644 index 0000000..1d35f62 --- /dev/null +++ b/internal/repository/In_progress_orde_repo.go @@ -0,0 +1,267 @@ +package repository + +import ( + "enaklo-pos-be/internal/common/mycontext" + "enaklo-pos-be/internal/constants" + "enaklo-pos-be/internal/entity" + "enaklo-pos-be/internal/repository/models" + "github.com/pkg/errors" + "gorm.io/gorm" + time2 "time" +) + +type InProgressOrderRepository interface { + CreateOrUpdate(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) + GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) +} + +type inprogressOrderRepository struct { + db *gorm.DB +} + +func NewInProgressOrderRepository(db *gorm.DB) *inprogressOrderRepository { + return &inprogressOrderRepository{db: db} +} + +func (r *inprogressOrderRepository) CreateOrUpdate(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) { + isUpdate := order.ID != "" + + tx := r.db.Begin() + if tx.Error != nil { + return nil, errors.Wrap(tx.Error, "failed to begin transaction") + } + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + orderDB := r.toInProgressOrderDBModel(order) + + if isUpdate { + var existingOrder models.InProgressOrderDB + if err := tx.First(&existingOrder, order.ID).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "order not found for update") + } + + if err := tx.Model(&orderDB).Updates(orderDB).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to update order") + } + + if err := tx.Where("in_progress_order_id = ?", order.ID).Delete(&models.InProgressOrderItemDB{}).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to delete existing order items") + } + } else { + if err := tx.Create(&orderDB).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to insert order") + } + + order.ID = orderDB.ID + } + + var itemIDs []int64 + for i := range order.OrderItems { + itemIDs = append(itemIDs, order.OrderItems[i].ItemID) + } + + var products []models.ProductDB + if len(itemIDs) > 0 { + if err := tx.Where("id IN ?", itemIDs).Find(&products).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to fetch products") + } + } + + productMap := make(map[int64]models.ProductDB) + for _, product := range products { + productMap[product.ID] = product + } + + for i := range order.OrderItems { + item := &order.OrderItems[i] + + itemDB := r.toOrderItemDBModel(item, orderDB.ID) + + if err := tx.Create(&itemDB).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to insert order item") + } + + item.ID = itemDB.ID + + if product, exists := productMap[item.ItemID]; exists { + item.Product = r.toDomainProductModel(&product) + } + } + + if err := tx.Commit().Error; err != nil { + return nil, errors.Wrap(err, "failed to commit transaction") + } + + return order, nil +} + +func (r *inprogressOrderRepository) GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) { + var ordersDB []models.InProgressOrderDB + query := r.db.Where("partner_id = ?", partnerID).Order("created_at DESC") + + if limit > 0 { + query = query.Limit(limit) + } + + if offset > 0 { + query = query.Offset(offset) + } + + if err := query.Preload("OrderItems.Product").Find(&ordersDB).Error; err != nil { + return nil, errors.Wrap(err, "failed to find orders by partner ID") + } + + orders := make([]*entity.InProgressOrder, 0, len(ordersDB)) + for _, orderDB := range ordersDB { + order := r.toDomainOrderModel(&orderDB) + order.OrderItems = make([]entity.InProgressOrderItem, 0, len(orderDB.OrderItems)) + + for _, itemDB := range orderDB.OrderItems { + item := r.toDomainOrderItemModel(&itemDB) + + orderItem := entity.InProgressOrderItem{ + ID: item.ID, + ItemID: item.ItemID, + Quantity: item.Quantity, + } + + if itemDB.Product.ID > 0 { + productDomain := r.toDomainProductModel(&itemDB.Product) + orderItem.Product = productDomain + } + + order.OrderItems = append(order.OrderItems, orderItem) + } + + orders = append(orders, order) + } + + return orders, nil +} + +func (r *inprogressOrderRepository) toInProgressOrderDBModel(order *entity.InProgressOrder) models.InProgressOrderDB { + now := time2.Now() + return models.InProgressOrderDB{ + ID: constants.GenerateUUID(), + PartnerID: order.PartnerID, + CustomerID: order.CustomerID, + CustomerName: order.CustomerName, + PaymentType: order.PaymentType, + CreatedBy: order.CreatedBy, + CreatedAt: now, + UpdatedAt: now, + TableNumber: order.TableNumber, + OrderType: order.OrderType, + } +} + +func (r *inprogressOrderRepository) toDomainOrderModel(dbModel *models.InProgressOrderDB) *entity.InProgressOrder { + return &entity.InProgressOrder{ + ID: dbModel.ID, + PartnerID: dbModel.PartnerID, + CustomerID: dbModel.CustomerID, + CustomerName: dbModel.CustomerName, + PaymentType: dbModel.PaymentType, + CreatedBy: dbModel.CreatedBy, + OrderItems: []entity.InProgressOrderItem{}, + TableNumber: dbModel.TableNumber, + OrderType: dbModel.OrderType, + CreatedAt: dbModel.CreatedAt, + UpdatedAt: dbModel.UpdatedAt, + } +} + +func (r *inprogressOrderRepository) toOrderItemDBModel(item *entity.InProgressOrderItem, inprogressOrderID string) models.InProgressOrderItemDB { + return models.InProgressOrderItemDB{ + ID: item.ID, + InProgressOrderIO: inprogressOrderID, + ItemID: item.ItemID, + Quantity: item.Quantity, + } +} + +func (r *inprogressOrderRepository) toDomainOrderItemModel(dbModel *models.InProgressOrderItemDB) *entity.OrderItem { + return &entity.OrderItem{ + ID: dbModel.ID, + ItemID: dbModel.ItemID, + Quantity: dbModel.Quantity, + CreatedBy: dbModel.CreatedBy, + CreatedAt: dbModel.CreatedAt, + } +} + +func (r *inprogressOrderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) models.OrderInquiryDB { + return models.OrderInquiryDB{ + ID: inquiry.ID, + PartnerID: inquiry.PartnerID, + CustomerID: &inquiry.CustomerID, + Status: inquiry.Status, + Amount: inquiry.Amount, + Fee: inquiry.Fee, + Total: inquiry.Total, + PaymentType: inquiry.PaymentType, + Source: inquiry.Source, + CreatedBy: inquiry.CreatedBy, + CreatedAt: inquiry.CreatedAt, + UpdatedAt: inquiry.UpdatedAt, + ExpiresAt: inquiry.ExpiresAt, + CustomerName: inquiry.CustomerName, + CustomerPhoneNumber: inquiry.CustomerPhoneNumber, + CustomerEmail: inquiry.CustomerEmail, + PaymentProvider: inquiry.PaymentProvider, + OrderType: inquiry.OrderType, + TableNumber: inquiry.TableNumber, + } +} + +func (r *inprogressOrderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiryDB) *entity.OrderInquiry { + inquiry := &entity.OrderInquiry{ + ID: dbModel.ID, + PartnerID: dbModel.PartnerID, + Status: dbModel.Status, + Amount: dbModel.Amount, + Fee: dbModel.Fee, + Total: dbModel.Total, + PaymentType: dbModel.PaymentType, + Source: dbModel.Source, + CreatedBy: dbModel.CreatedBy, + CreatedAt: dbModel.CreatedAt, + ExpiresAt: dbModel.ExpiresAt, + OrderItems: []entity.OrderItem{}, + } + + if dbModel.CustomerID != nil { + inquiry.CustomerID = *dbModel.CustomerID + } + + inquiry.UpdatedAt = dbModel.UpdatedAt + + return inquiry +} + +func (r *inprogressOrderRepository) toDomainProductModel(productDB *models.ProductDB) *entity.Product { + if productDB == nil { + return nil + } + + return &entity.Product{ + ID: productDB.ID, + Name: productDB.Name, + Description: productDB.Description, + Price: productDB.Price, + CreatedAt: productDB.CreatedAt, + UpdatedAt: productDB.UpdatedAt, + Type: productDB.Type, + Image: productDB.Image, + } +} diff --git a/internal/repository/customer_repo.go b/internal/repository/customer_repo.go index 3a069e1..9afdcac 100644 --- a/internal/repository/customer_repo.go +++ b/internal/repository/customer_repo.go @@ -4,8 +4,11 @@ import ( "enaklo-pos-be/internal/common/mycontext" "enaklo-pos-be/internal/entity" "enaklo-pos-be/internal/repository/models" + "fmt" + "github.com/google/uuid" "github.com/pkg/errors" "gorm.io/gorm" + "math/rand" "time" ) @@ -14,9 +17,10 @@ type CustomerRepo interface { FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error) FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error) FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error) - AddPoints(ctx mycontext.Context, id int64, points int) error + AddPoints(ctx mycontext.Context, id int64, points int, reference string) error FindSequence(ctx mycontext.Context, partnerID int64) (int64, error) GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error) + VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error) } type customerRepository struct { @@ -28,13 +32,53 @@ func NewCustomerRepository(db *gorm.DB) *customerRepository { } func (r *customerRepository) Create(ctx mycontext.Context, customer *entity.Customer) (*entity.Customer, error) { - customerDB := r.toCustomerDBModel(customer) + tx := r.db.Begin() + if tx.Error != nil { + return nil, errors.Wrap(tx.Error, "failed to begin transaction") + } - if err := r.db.Create(&customerDB).Error; err != nil { + customerDB := r.toCustomerDBModel(customer) + if err := tx.Omit("CustomerID").Create(&customerDB).Error; err != nil { + tx.Rollback() return nil, errors.Wrap(err, "failed to insert customer") } + customerPoints := models.CustomerPointsDB{ + CustomerID: uint64(customerDB.ID), + TotalPoints: 0, + AvailablePoints: 0, + LastUpdated: time.Now(), + } + + if err := tx.Create(&customerPoints).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to create initial customer points") + } + + otpCode := r.generateOTPCode() + expiresAt := time.Now().Add(15 * time.Minute) + + verificationCode := models.CustomerVerificationCodeDB{ + CustomerID: uint64(customerDB.ID), + Code: otpCode, + Type: "EMAIL", + ExpiresAt: expiresAt, + IsUsed: false, + VerificationID: uuid.New(), + } + + if err := tx.Create(&verificationCode).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to create verification code") + } + + if err := tx.Commit().Error; err != nil { + return nil, errors.Wrap(err, "failed to commit transaction") + } + customer.ID = customerDB.ID + customer.VerificationID = verificationCode.VerificationID.String() + customer.OTP = otpCode return customer, nil } @@ -84,22 +128,45 @@ func (r *customerRepository) FindByEmail(ctx mycontext.Context, email string) (* return customer, nil } -func (r *customerRepository) AddPoints(ctx mycontext.Context, id int64, points int) error { - now := time.Now() +func (r *customerRepository) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error { + tx := r.db.Begin() + if tx.Error != nil { + return errors.Wrap(tx.Error, "failed to begin transaction") + } - result := r.db.Model(&models.CustomerDB{}). - Where("id = ?", id). + result := tx.Model(&models.CustomerPointsDB{}). + Where("customer_id = ?", customerID). Updates(map[string]interface{}{ - "points": gorm.Expr("points + ?", points), - "updated_at": now, + "total_points": gorm.Expr("total_points + ?", points), + "available_points": gorm.Expr("available_points + ?", points), + "last_updated": time.Now(), }) if result.Error != nil { - return errors.Wrap(result.Error, "failed to add points to customer") + tx.Rollback() + return errors.Wrap(result.Error, "failed to update customer points") } if result.RowsAffected == 0 { - return errors.New("customer not found") + tx.Rollback() + return errors.New("customer points record not found") + } + + pointTransaction := models.CustomerPointTransactionDB{ + CustomerID: customerID, + Reference: reference, + PointsEarned: points, + TransactionDate: time.Now(), + Status: "SUCCESS", + } + + if err := tx.Create(&pointTransaction).Error; err != nil { + tx.Rollback() + return errors.Wrap(err, "failed to create point transaction record") + } + + if err := tx.Commit().Error; err != nil { + return errors.Wrap(err, "failed to commit transaction") } return nil @@ -107,15 +174,15 @@ func (r *customerRepository) AddPoints(ctx mycontext.Context, id int64, points i func (r *customerRepository) toCustomerDBModel(customer *entity.Customer) models.CustomerDB { return models.CustomerDB{ - ID: customer.ID, - Name: customer.Name, - Email: customer.Email, - Phone: customer.Phone, - Points: customer.Points, - CreatedAt: customer.CreatedAt, - UpdatedAt: customer.UpdatedAt, - CustomerID: customer.CustomerID, - BirthDate: customer.BirthDate, + ID: customer.ID, + Name: customer.Name, + Email: customer.Email, + Phone: customer.Phone, + Points: customer.Points, + CreatedAt: customer.CreatedAt, + UpdatedAt: customer.UpdatedAt, + BirthDate: customer.BirthDate, + Password: customer.Password, } } @@ -232,3 +299,59 @@ func (r *customerRepository) GetAllCustomers(ctx mycontext.Context, req entity.M return customers, int(totalCount), nil } + +func (r *customerRepository) generateOTPCode() string { + rand.Seed(time.Now().UnixNano()) + otpCode := fmt.Sprintf("%06d", rand.Intn(1000000)) + return otpCode +} + +func (r *customerRepository) VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error) { + var verificationCode models.CustomerVerificationCodeDB + if err := r.db.Where("verification_id = ? AND is_used = false", verificationHash).First(&verificationCode).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, errors.New("invalid or expired verification code") + } + return 0, errors.Wrap(err, "failed to find verification code") + } + + if time.Now().After(verificationCode.ExpiresAt) { + return 0, errors.New("verification code has expired") + } + + if verificationCode.Code != otpCode { + return 0, errors.New("invalid verification code") + } + + tx := r.db.Begin() + if tx.Error != nil { + return 0, errors.Wrap(tx.Error, "failed to begin transaction") + } + + if err := tx.Model(&verificationCode).Updates(map[string]interface{}{ + "is_used": true, + }).Error; err != nil { + tx.Rollback() + return 0, errors.Wrap(err, "failed to mark verification code as used") + } + + if verificationCode.Type == "EMAIL" { + if err := tx.Model(&models.CustomerDB{}).Where("id = ?", verificationCode.CustomerID). + Update("is_email_verified", true).Error; err != nil { + tx.Rollback() + return 0, errors.Wrap(err, "failed to update customer verification status") + } + } else if verificationCode.Type == "PHONE" { + if err := tx.Model(&models.CustomerDB{}).Where("id = ?", verificationCode.CustomerID). + Update("is_phone_verified", true).Error; err != nil { + tx.Rollback() + return 0, errors.Wrap(err, "failed to update customer verification status") + } + } + + if err := tx.Commit().Error; err != nil { + return 0, errors.Wrap(err, "failed to commit transaction") + } + + return int64(verificationCode.CustomerID), nil +} diff --git a/internal/repository/models/customer.go b/internal/repository/models/customer.go index ae43218..a80c83d 100644 --- a/internal/repository/models/customer.go +++ b/internal/repository/models/customer.go @@ -1,19 +1,23 @@ package models import ( + "github.com/google/uuid" "time" ) type CustomerDB struct { - ID int64 `gorm:"primaryKey;column:id"` - Name string `gorm:"column:name"` - Email string `gorm:"column:email"` - Phone string `gorm:"column:phone"` - Points int `gorm:"column:points"` - CreatedAt time.Time `gorm:"column:created_at"` - UpdatedAt time.Time `gorm:"column:updated_at"` - CustomerID string `gorm:"column:customer_id"` - BirthDate time.Time `gorm:"column:birth_date"` + ID int64 `gorm:"primaryKey;column:id"` + Name string `gorm:"column:name"` + Email string `gorm:"column:email"` + Phone string `gorm:"column:phone"` + Points int `gorm:"column:points"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` + CustomerID string `gorm:"column:customer_id"` + BirthDate time.Time `gorm:"column:birth_date"` + Password string `gorm:"column:password"` + IsEmailVerified bool `gorm:"column:is_email_verified"` + IsPhoneVerified bool `gorm:"column:is_phone_verified"` } func (CustomerDB) TableName() string { @@ -30,3 +34,45 @@ type PartnerMemberSequence struct { func (PartnerMemberSequence) TableName() string { return "partner_member_sequences" } + +type CustomerPointsDB struct { + ID uint64 `gorm:"column:id;primaryKey;autoIncrement"` + CustomerID uint64 `gorm:"column:customer_id;not null"` + TotalPoints int `gorm:"column:total_points;not null;default:0"` + AvailablePoints int `gorm:"column:available_points;not null;default:0"` + LastUpdated time.Time `gorm:"column:last_updated;default:CURRENT_TIMESTAMP"` +} + +func (CustomerPointsDB) TableName() string { + return "customer_points" +} + +type CustomerPointTransactionDB struct { + ID uint64 `gorm:"column:id;primaryKey;autoIncrement"` + CustomerID int64 `gorm:"column:customer_id;not null"` + Reference string `gorm:"column:transaction_id"` + PointsEarned int `gorm:"column:points_earned;not null"` + TransactionDate time.Time `gorm:"column:transaction_date;not null"` + ExpirationDate *time.Time `gorm:"column:expiration_date"` + Status string `gorm:"column:status;default:active"` + CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"` +} + +func (CustomerPointTransactionDB) TableName() string { + return "customer_point_transactions" +} + +type CustomerVerificationCodeDB struct { + ID uint64 `gorm:"column:id;primaryKey;autoIncrement"` + CustomerID uint64 `gorm:"column:customer_id;not null"` + Code string `gorm:"column:code;not null"` + Type string `gorm:"column:type;not null"` + ExpiresAt time.Time `gorm:"column:expires_at;not null"` + IsUsed bool `gorm:"column:is_used;default:false"` + CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"` + VerificationID uuid.UUID `gorm:"column:verification_id;type:uuid;default:uuid_generate_v4()"` +} + +func (CustomerVerificationCodeDB) TableName() string { + return "customer_verification_codes" +} diff --git a/internal/repository/models/in_progress_order.go b/internal/repository/models/in_progress_order.go new file mode 100644 index 0000000..a78b19e --- /dev/null +++ b/internal/repository/models/in_progress_order.go @@ -0,0 +1,35 @@ +package models + +import "time" + +type InProgressOrderDB struct { + ID string `gorm:"primaryKey;column:id"` + PartnerID int64 `gorm:"column:partner_id"` + CustomerID *int64 `gorm:"column:customer_id"` + CustomerName string `gorm:"column:customer_name"` + PaymentType string `gorm:"column:payment_type"` + CreatedBy int64 `gorm:"column:created_by"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` + TableNumber string `gorm:"column:table_number"` + OrderItems []InProgressOrderItemDB `gorm:"foreignKey:InProgressOrderIO"` + OrderType string `gorm:"column:order_type"` +} + +type InProgressOrderItemDB struct { + ID int64 `gorm:"primaryKey;column:id"` + InProgressOrderIO string `gorm:"column:in_progress_order_id"` + ItemID int64 `gorm:"column:item_id"` + Quantity int `gorm:"column:quantity"` + CreatedBy int64 `gorm:"column:created_by"` + CreatedAt time.Time `gorm:"column:created_at"` + Product ProductDB `gorm:"foreignKey:ItemID;references:ID"` +} + +func (InProgressOrderItemDB) TableName() string { + return "in_progress_order_items" +} + +func (InProgressOrderDB) TableName() string { + return "in_progress_order" +} diff --git a/internal/repository/models/order.go b/internal/repository/models/order.go index 738faa0..15535c6 100644 --- a/internal/repository/models/order.go +++ b/internal/repository/models/order.go @@ -58,6 +58,9 @@ type OrderInquiryDB struct { UpdatedAt time.Time `gorm:"column:updated_at"` ExpiresAt time.Time `gorm:"column:expires_at"` InquiryItems []InquiryItemDB `gorm:"foreignKey:InquiryID"` + PaymentProvider string `gorm:"column:payment_provider"` + TableNumber string `gorm:"column:table_number"` + OrderType string `gorm:"column:order_type"` } func (OrderInquiryDB) TableName() string { diff --git a/internal/repository/models/product.go b/internal/repository/models/product.go index eed9b8f..0164eb1 100644 --- a/internal/repository/models/product.go +++ b/internal/repository/models/product.go @@ -15,6 +15,7 @@ type ProductDB struct { Status string `gorm:"column:status"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` + Image string `gorm:"column:image"` } func (ProductDB) TableName() string { diff --git a/internal/repository/orde_repo.go b/internal/repository/orde_repo.go index 84cbe23..31693ec 100644 --- a/internal/repository/orde_repo.go +++ b/internal/repository/orde_repo.go @@ -61,6 +61,18 @@ func (r *orderRepository) Create(ctx mycontext.Context, order *entity.Order) (*e item.ID = itemDB.ID } + if order.InProgressOrderID != "" { + if err := tx.Where("in_progress_order_id = ?", order.InProgressOrderID).Delete(&models.InProgressOrderItemDB{}).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to delete in-progress order items") + } + + if err := tx.Where("id = ?", order.InProgressOrderID).Delete(&models.InProgressOrderDB{}).Error; err != nil { + tx.Rollback() + return nil, errors.Wrap(err, "failed to delete in-progress order") + } + } + if err := tx.Commit().Error; err != nil { return nil, errors.Wrap(err, "failed to commit transaction") } @@ -263,6 +275,9 @@ func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) mo CustomerName: inquiry.CustomerName, CustomerPhoneNumber: inquiry.CustomerPhoneNumber, CustomerEmail: inquiry.CustomerEmail, + PaymentProvider: inquiry.PaymentProvider, + OrderType: inquiry.OrderType, + TableNumber: inquiry.TableNumber, } } diff --git a/internal/repository/orders/order.go b/internal/repository/orders/order.go index fdef267..6d20982 100644 --- a/internal/repository/orders/order.go +++ b/internal/repository/orders/order.go @@ -141,9 +141,8 @@ func (b *OrderRepository) GetAllHystoryOrders(ctx context.Context, req entity.Or query := b.db.Table("orders"). Select("orders.id as id, users.name as employee, sites.name as site, orders.created_at as timestamp, orders.created_at as booking_time, STRING_AGG(ticket_summary.name || ' x' || ticket_summary.total_qty, ', ') AS tickets, orders.payment_type as payment_type, orders.status as status, orders.amount as amount, orders.visit_date as visit_date, orders.ticket_status as ticket_status, orders.source as source"). - Joins("left join (SELECT items.order_id, products.name, SUM(items.qty) AS total_qty FROM order_items items LEFT JOIN products ON items.item_id = products.id GROUP BY items.order_id, products.name) AS ticket_summary ON orders.id = ticket_summary.order_id"). + Joins("left join (SELECT items.order_id, products.name, SUM(items.quantity) AS total_qty FROM order_items items LEFT JOIN products ON items.item_id = products.id GROUP BY items.order_id, products.name) AS ticket_summary ON orders.id = ticket_summary.order_id"). Joins("left join users on orders.created_by = users.id"). - Joins("left join sites on orders.site_id = sites.id"). Where("orders.payment_type != ?", "NEW") if req.PaymentType != "" { @@ -176,7 +175,7 @@ func (b *OrderRepository) GetAllHystoryOrders(ctx context.Context, req entity.Or } if req.SiteID != nil { - query = query.Where("orders.site_id = ?", req.SiteID) + query = query.Where("orders.partner_id = ?", req.SiteID) } if req.Source != "" { @@ -253,10 +252,6 @@ func (r *OrderRepository) SumAmount(ctx mycontext.Context, req entity.OrderSearc query = query.Where("orders.partner_id = ?", req.PartnerID) } - if req.SiteID != nil { - query = query.Where("orders.site_id = ?", req.SiteID) - } - if err := query.Scan(&amount).Error; err != nil { logger.ContextLogger(ctx).Error("error when get cash amount", zap.Error(err)) return nil, err diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 0144ed5..e63e723 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -52,11 +52,12 @@ type RepoManagerImpl struct { PG PaymentGateway LinkQu LinkQu - OrderRepo OrderRepository - CustomerRepo CustomerRepo - ProductRepo ProductRepository - TransactionRepo TransactionRepo - MemberRepository MemberRepository + OrderRepo OrderRepository + InProgressOrderRepo InProgressOrderRepository + CustomerRepo CustomerRepo + ProductRepo ProductRepository + TransactionRepo TransactionRepo + MemberRepository MemberRepository } func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { @@ -81,11 +82,12 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { PG: pg.NewPaymentGatewayRepo(&cfg.Midtrans, &cfg.LinkQu), LinkQu: linkqu.NewLinkQuService(&cfg.LinkQu), - OrderRepo: NeworderRepository(db), - CustomerRepo: NewCustomerRepository(db), - ProductRepo: NewproductRepository(db), - TransactionRepo: NewTransactionRepository(db), - MemberRepository: NewMemberRepository(db), + OrderRepo: NeworderRepository(db), + CustomerRepo: NewCustomerRepository(db), + ProductRepo: NewproductRepository(db), + TransactionRepo: NewTransactionRepository(db), + MemberRepository: NewMemberRepository(db), + InProgressOrderRepo: NewInProgressOrderRepository(db), } } diff --git a/internal/routes/customer_routes.go b/internal/routes/customer_routes.go index f15600b..a366600 100644 --- a/internal/routes/customer_routes.go +++ b/internal/routes/customer_routes.go @@ -19,7 +19,7 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan serverRoutes := []HTTPHandlerRoutes{ discovery.NewHandler(serviceManager.DiscoverService), - customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc), + customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc, serviceManager.CustomerV2Svc), customerorder.NewHandler(serviceManager.OrderSvc), } diff --git a/internal/routes/routes.go b/internal/routes/routes.go index 69a3aef..626e89f 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -80,6 +80,7 @@ func RegisterPrivateRoutesV2(app *app.Server, serviceManager *services.ServiceMa http2.NewOrderHandler(serviceManager.OrderV2Svc), http2.NewMemberRegistrationHandler(serviceManager.MemberRegistrationSvc), http2.NewCustomerHandler(serviceManager.CustomerV2Svc), + http2.NewInProgressOrderHandler(serviceManager.InProgressSvc), } for _, handler := range serverRoutes { diff --git a/internal/services/auth/init.go b/internal/services/auth/init.go index bbfd77a..de0f380 100644 --- a/internal/services/auth/init.go +++ b/internal/services/auth/init.go @@ -83,7 +83,6 @@ func (u *AuthServiceImpl) AuthenticateUser(ctx context.Context, email, password } func (u *AuthServiceImpl) SendPasswordResetLink(ctx context.Context, email string) error { - // Check if the user exists user, err := u.authRepo.CheckExistsUserAccount(ctx, email) if err != nil { logger.ContextLogger(ctx).Error("error when getting user", zap.Error(err)) diff --git a/internal/services/member/member_registration.go b/internal/services/member/member_registration.go index 0fc8794..6f671e0 100644 --- a/internal/services/member/member_registration.go +++ b/internal/services/member/member_registration.go @@ -162,7 +162,6 @@ func (s *memberSvc) ResendOTP( ) (*entity.ResendOTPResponse, error) { logger.ContextLogger(ctx).Info("resending OTP", zap.String("token", token)) - // Get registration by token registration, err := s.repo.GetRegistrationByToken(ctx, token) if err != nil { logger.ContextLogger(ctx).Error("failed to get registration", zap.Error(err)) @@ -211,7 +210,7 @@ func (s *memberSvc) sendRegistrationOTP( Recipient: registration.Email, Subject: "Enaklo - Registration Verification Code", TemplateName: "member_registration_otp", - TemplatePath: "/templates/member_registration_otp.html", + TemplatePath: "templates/member_registration_otp.html", Data: emailData, }) diff --git a/internal/services/order/order.go b/internal/services/order/order.go index cc66e45..e3f9266 100644 --- a/internal/services/order/order.go +++ b/internal/services/order/order.go @@ -240,32 +240,6 @@ func (s *OrderService) Execute(ctx mycontext.Context, req *entity.OrderExecuteRe Order: order, } - if order.PaymentType != "CASH" { - if order.PaymentType == "VA" { - paymentResponse, err := s.processVAPayment(ctx, order, partnerID, req.CreatedBy) - if err != nil { - return nil, err - } - resp.VirtualAccount = paymentResponse.VirtualAccountNumber - resp.BankName = paymentResponse.BankName - resp.BankCode = paymentResponse.BankCode - } - if order.PaymentType == "QRIS" { - paymentResponse, err := s.processQRPayment(ctx, order, partnerID, req.CreatedBy) - if err != nil { - return nil, err - } - resp.QRCode = paymentResponse.QRCodeURL - } else { - paymentResponse, err := s.processNonCashPayment(ctx, order, partnerID, req.CreatedBy) - if err != nil { - return nil, err - } - resp.PaymentToken = paymentResponse.Token - resp.RedirectURL = paymentResponse.RedirectURL - } - } - order.SetExecutePaymentStatus() order, err = s.repo.Update(ctx, order) if err != nil { diff --git a/internal/services/service.go b/internal/services/service.go index 8e7baca..cb7180e 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -16,6 +16,7 @@ import ( "enaklo-pos-be/internal/services/transaction" "enaklo-pos-be/internal/services/users" customerSvc "enaklo-pos-be/internal/services/v2/customer" + "enaklo-pos-be/internal/services/v2/inprogress_order" orderSvc "enaklo-pos-be/internal/services/v2/order" productSvc "enaklo-pos-be/internal/services/v2/product" @@ -47,12 +48,14 @@ type ServiceManagerImpl struct { CustomerV2Svc customerSvc.Service ProductV2Svc productSvc.Service MemberRegistrationSvc member.RegistrationService + InProgressSvc inprogress_order.InProgressOrderService } func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl { - custSvcV2 := customerSvc.New(repo.CustomerRepo) + custSvcV2 := customerSvc.New(repo.CustomerRepo, repo.EmailService) productSvcV2 := productSvc.New(repo.ProductRepo) + inprogressOrder := inprogress_order.NewInProgressOrderService(repo.InProgressOrderRepo) return &ServiceManagerImpl{ AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License), @@ -72,6 +75,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService), MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2), CustomerV2Svc: custSvcV2, + InProgressSvc: inprogressOrder, } } diff --git a/internal/services/v2/customer/customer.go b/internal/services/v2/customer/customer.go index 50660b1..e7de76e 100644 --- a/internal/services/v2/customer/customer.go +++ b/internal/services/v2/customer/customer.go @@ -1,6 +1,8 @@ package customer import ( + "context" + errors2 "enaklo-pos-be/internal/common/errors" "enaklo-pos-be/internal/common/logger" "enaklo-pos-be/internal/common/mycontext" "enaklo-pos-be/internal/constants" @@ -8,7 +10,9 @@ import ( "enaklo-pos-be/internal/utils" "github.com/pkg/errors" "go.uber.org/zap" + "log" "strings" + "time" ) type Repository interface { @@ -16,26 +20,35 @@ type Repository interface { FindByID(ctx mycontext.Context, id int64) (*entity.Customer, error) FindByPhone(ctx mycontext.Context, phone string) (*entity.Customer, error) FindByEmail(ctx mycontext.Context, email string) (*entity.Customer, error) - AddPoints(ctx mycontext.Context, id int64, points int) error + AddPoints(ctx mycontext.Context, id int64, points int, reference string) error FindSequence(ctx mycontext.Context, partnerID int64) (int64, error) GetAllCustomers(ctx mycontext.Context, req entity.MemberSearch) (entity.MemberList, int, error) + VerifyOTP(ctx mycontext.Context, verificationHash string, otpCode string) (int64, error) } type Service interface { ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error) - AddPoints(ctx mycontext.Context, customerID int64, points int) error + AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) CustomerCheck(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (*entity.CustomerCheckResponse, error) GetAllCustomers(ctx mycontext.Context, req *entity.MemberSearch) (*entity.MemberList, int, error) + RegistrationMember(ctx mycontext.Context, req *entity.Customer) (*entity.Customer, error) + VerifyOTP(ctx mycontext.Context, verificationID, otpCode string) error +} + +type EmailService interface { + SendEmailTransactional(ctx context.Context, param entity.SendEmailNotificationParam) error } type customerSvc struct { - repo Repository + repo Repository + notification EmailService } -func New(repo Repository) Service { +func New(repo Repository, notification EmailService) Service { return &customerSvc{ - repo: repo, + repo: repo, + notification: notification, } } @@ -106,12 +119,68 @@ func (s *customerSvc) ResolveCustomer(ctx mycontext.Context, req *entity.Custome return customer.ID, nil } -func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int) error { +func (s *customerSvc) RegistrationMember(ctx mycontext.Context, req *entity.Customer) (*entity.Customer, error) { + if req.Email == "" && req.PhoneNumber == "" { + return nil, errors2.ErrorPhoneNumberEmailIsRequired + } + + if req.PhoneNumber != "" { + customer, err := s.repo.FindByPhone(ctx, req.PhoneNumber) + if err != nil && !strings.Contains(err.Error(), "not found") { + return nil, errors2.ErrorInternalServer + } + + if customer != nil { + return nil, errors2.ErrorPhoneNumberIsAlreadyRegistered + } + } + + if req.Email != "" { + customer, err := s.repo.FindByEmail(ctx, req.Email) + if err != nil && !strings.Contains(err.Error(), "not found") { + return nil, errors2.ErrorInternalServer + } + + if customer != nil { + return nil, errors2.ErrorEmailIsAlreadyRegistered + } + } + + newCustomer := &entity.Customer{ + Name: req.Name, + Email: req.Email, + Phone: req.PhoneNumber, + CreatedAt: constants.TimeNow(), + UpdatedAt: constants.TimeNow(), + BirthDate: req.BirthDate, + Password: req.HashedPassword(), + } + + customer, err := s.repo.Create(ctx, newCustomer) + if err != nil { + logger.ContextLogger(ctx).Error("failed to create customer", zap.Error(err)) + return nil, errors2.ErrorInternalServer + } + + errs := s.sendRegistrationOTP(ctx, &entity.MemberRegistration{ + Name: customer.Name, + Email: customer.Email, + OTP: customer.OTP, + }) + + if err != nil { + logger.ContextLogger(ctx).Error("failed to send OTP", zap.Error(errs)) + } + + return customer, nil +} + +func (s *customerSvc) AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error { if points <= 0 { return nil } - err := s.repo.AddPoints(ctx, customerID, points) + err := s.repo.AddPoints(ctx, customerID, points, reference) if err != nil { return errors.Wrap(err, "failed to add points to customer") } @@ -202,3 +271,75 @@ func (s *customerSvc) GetAllCustomers(ctx mycontext.Context, req *entity.MemberS return &customers, totalCount, nil } + +func (s *customerSvc) sendRegistrationOTP( + ctx mycontext.Context, + registration *entity.MemberRegistration, +) error { + emailData := map[string]interface{}{ + "UserName": registration.Name, + "OTPCode": registration.OTP, + } + + err := s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{ + Sender: "noreply@enaklo.co.id", + Recipient: registration.Email, + Subject: "Enaklo - Registration Verification Code", + TemplateName: "member_registration_otp", + TemplatePath: "templates/member_registration_otp.html", + Data: emailData, + }) + + if err != nil { + return err + } + + return nil +} + +func (s *customerSvc) VerifyOTP(ctx mycontext.Context, verificationID, otpCode string) error { + customerID, err := s.repo.VerifyOTP(ctx, verificationID, otpCode) + if err != nil { + return errors.Wrap(err, "verification failed") + } + + customer, _ := s.repo.FindByID(ctx, customerID) + + go func(customer *entity.Customer) { + newCtx := context.Background() + + defer func() { + if r := recover(); r != nil { + log.Printf("Recovered from panic in sendWelcomeEmail: %v", r) + } + }() + + s.sendWelcomeEmail(newCtx, customer) + }(customer) + + return nil +} + +func (s *customerSvc) sendWelcomeEmail( + ctx context.Context, + customer *entity.Customer, +) error { + + welcomeData := map[string]interface{}{ + "UserName": customer.Name, + "MemberID": customer.CustomerID, + "PointsName": "EnakPoint", + "PointsBalance": customer.Points, + "RedeemLink": "https://enaklo.co.id/redeem", + "CurrentDate": time.Now().Format("01-2006"), + } + + return s.notification.SendEmailTransactional(ctx, entity.SendEmailNotificationParam{ + Sender: "noreply@enaklo.co.id", + Recipient: customer.Email, + Subject: "Welcome to Enaklo Membership Program", + TemplateName: "welcome_member", + TemplatePath: "templates/welcome_member.html", + Data: welcomeData, + }) +} diff --git a/internal/services/v2/inprogress_order/in_progress_order.go b/internal/services/v2/inprogress_order/in_progress_order.go new file mode 100644 index 0000000..1856c62 --- /dev/null +++ b/internal/services/v2/inprogress_order/in_progress_order.go @@ -0,0 +1,51 @@ +package inprogress_order + +import ( + "enaklo-pos-be/internal/common/logger" + "enaklo-pos-be/internal/common/mycontext" + "enaklo-pos-be/internal/entity" + "enaklo-pos-be/internal/repository" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type InProgressOrderService interface { + Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) + GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) +} + +type inProgressOrderSvc struct { + repo repository.InProgressOrderRepository +} + +func NewInProgressOrderService(repo repository.InProgressOrderRepository) InProgressOrderService { + return &inProgressOrderSvc{ + repo: repo, + } +} + +func (s *inProgressOrderSvc) Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) { + createdOrder, err := s.repo.CreateOrUpdate(ctx, order) + if err != nil { + logger.ContextLogger(ctx).Error("failed to create in-progress order", + zap.Error(err), + zap.Int64("partnerID", order.PartnerID)) + return nil, errors.Wrap(err, "failed to create in-progress order") + } + + return createdOrder, nil +} + +func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) { + orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset) + if err != nil { + logger.ContextLogger(ctx).Error("failed to get in-progress orders by partner ID", + zap.Error(err), + zap.Int64("partnerID", partnerID), + zap.Int("limit", limit), + zap.Int("offset", offset)) + return nil, errors.Wrap(err, "failed to get in-progress orders") + } + + return orders, nil +} diff --git a/internal/services/v2/order/create_order_inquiry.go b/internal/services/v2/order/create_order_inquiry.go index e3022a2..b9895e1 100644 --- a/internal/services/v2/order/create_order_inquiry.go +++ b/internal/services/v2/order/create_order_inquiry.go @@ -51,6 +51,9 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context, req.CustomerName, req.CustomerPhoneNumber, req.CustomerEmail, + req.PaymentProvider, + req.TableNumber, + req.OrderType, ) for _, item := range req.OrderItems { diff --git a/internal/services/v2/order/execute_order.go b/internal/services/v2/order/execute_order.go index b1f5554..2c29d9b 100644 --- a/internal/services/v2/order/execute_order.go +++ b/internal/services/v2/order/execute_order.go @@ -10,13 +10,14 @@ import ( ) func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context, - token string, paymentMethod string) (*entity.OrderResponse, error) { + token string, paymentMethod, paymentProvider, inprogressOrderID string) (*entity.OrderResponse, error) { inquiry, err := s.validateInquiry(ctx, token) if err != nil { return nil, err } - order := inquiry.ToOrder(paymentMethod) + order := inquiry.ToOrder(paymentMethod, paymentProvider) + order.InProgressOrderID = inprogressOrderID savedOrder, err := s.repo.Create(ctx, order) if err != nil { @@ -51,7 +52,7 @@ func (s *orderSvc) processPostOrderActions( } if order.CustomerID != nil && *order.CustomerID > 0 { - err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/1000)) + err = s.addCustomerPoints(ctx, *order.CustomerID, int(order.Total/1000), fmt.Sprintf("TRX #%s", trx.ID)) if err != nil { logger.ContextLogger(ctx).Error("error when adding points", zap.Error(err)) } @@ -78,8 +79,8 @@ func (s *orderSvc) createTransaction(ctx mycontext.Context, order *entity.Order, return transaction, err } -func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int) error { - return s.customer.AddPoints(ctx, customerID, points) +func (s *orderSvc) addCustomerPoints(ctx mycontext.Context, customerID int64, points int, reference string) error { + return s.customer.AddPoints(ctx, customerID, points, reference) } func (s *orderSvc) sendTransactionReceipt(ctx mycontext.Context, order *entity.Order, transaction *entity.Transaction, paymentMethod string) error { diff --git a/internal/services/v2/order/order.go b/internal/services/v2/order/order.go index 6a66502..6de092b 100644 --- a/internal/services/v2/order/order.go +++ b/internal/services/v2/order/order.go @@ -21,7 +21,7 @@ type ProductService interface { type CustomerService interface { ResolveCustomer(ctx mycontext.Context, req *entity.CustomerResolutionRequest) (int64, error) - AddPoints(ctx mycontext.Context, customerID int64, points int) error + AddPoints(ctx mycontext.Context, customerID int64, points int, reference string) error GetCustomer(ctx mycontext.Context, id int64) (*entity.Customer, error) } @@ -42,7 +42,7 @@ type Service interface { CreateOrderInquiry(ctx mycontext.Context, req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) ExecuteOrderInquiry(ctx mycontext.Context, - token string, paymentMethod string) (*entity.OrderResponse, error) + token string, paymentMethod, paymentProvider, inProgressOrderID string) (*entity.OrderResponse, error) } type Config interface { diff --git a/templates/member_registration_otp.html b/templates/member_registration_otp.html index e6af64a..8e579a0 100644 --- a/templates/member_registration_otp.html +++ b/templates/member_registration_otp.html @@ -168,7 +168,7 @@