2023-10-08 15:59:42 +07:00
package order
import (
2024-06-04 02:59:31 +07:00
"encoding/json"
"errors"
2023-10-08 15:59:42 +07:00
"furtuna-be/internal/common/logger"
2024-06-04 02:59:31 +07:00
order2 "furtuna-be/internal/constants/order"
2023-10-08 15:59:42 +07:00
"furtuna-be/internal/entity"
"furtuna-be/internal/repository"
2024-06-04 02:59:31 +07:00
"furtuna-be/internal/utils/generator"
2023-10-08 15:59:42 +07:00
"go.uber.org/zap"
2024-06-04 02:59:31 +07:00
"golang.org/x/net/context"
"gorm.io/gorm"
"strconv"
"time"
2023-10-08 15:59:42 +07:00
)
type OrderService struct {
2024-06-04 02:59:31 +07:00
repo repository . Order
crypt repository . Crypto
product repository . Product
midtrans repository . Midtrans
payment repository . Payment
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
func NewOrderService ( repo repository . Order , product repository . Product , crypt repository . Crypto ,
midtrans repository . Midtrans , payment repository . Payment ) * OrderService {
2023-10-08 15:59:42 +07:00
return & OrderService {
2024-06-04 02:59:31 +07:00
repo : repo ,
product : product ,
crypt : crypt ,
midtrans : midtrans ,
payment : payment ,
2023-10-08 15:59:42 +07:00
}
}
2024-06-04 02:59:31 +07:00
func ( s * OrderService ) CreateOrder ( ctx context . Context , req * entity . OrderRequest ) ( * entity . OrderResponse , error ) {
productIDs := make ( [ ] int64 , len ( req . OrderItems ) )
for i , item := range req . OrderItems {
productIDs [ i ] = item . ProductID
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
products , err := s . product . GetProductsByIDs ( ctx , productIDs , req . PartnerID )
if err != nil {
logger . ContextLogger ( ctx ) . Error ( "error when getting products by IDs" , zap . Error ( err ) )
return nil , err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
productMap := make ( map [ int64 ] * entity . ProductDB )
for _ , product := range products {
productMap [ product . ID ] = product
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
totalAmount := 0.0
for _ , item := range req . OrderItems {
product , ok := productMap [ item . ProductID ]
if ! ok {
logger . ContextLogger ( ctx ) . Error ( "product not found" , zap . Int64 ( "productID" , item . ProductID ) )
return nil , errors . New ( "product not found" )
}
totalAmount += product . Price * float64 ( item . Quantity )
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
order := & entity . Order {
PartnerID : req . PartnerID ,
RefID : generator . GenerateUUID ( ) ,
Status : order2 . New . String ( ) ,
Amount : totalAmount ,
PaymentType : req . PaymentMethod ,
CreatedBy : req . CreatedBy ,
OrderItems : [ ] entity . OrderItem { } ,
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
for _ , item := range req . OrderItems {
order . OrderItems = append ( order . OrderItems , entity . OrderItem {
ItemID : item . ProductID ,
ItemType : productMap [ item . ProductID ] . Type ,
Price : productMap [ item . ProductID ] . Price ,
Quantity : item . Quantity ,
CreatedBy : req . CreatedBy ,
} )
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
order , err = s . repo . Create ( ctx , order )
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger . ContextLogger ( ctx ) . Error ( "error when creating order" , zap . Error ( err ) )
2023-10-08 15:59:42 +07:00
return nil , err
}
2024-06-04 02:59:31 +07:00
token , err := s . crypt . GenerateJWTOrder ( order )
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger . ContextLogger ( ctx ) . Error ( "error when create token" , zap . Error ( err ) )
return nil , err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
return & entity . OrderResponse {
Order : order ,
Token : token ,
} , nil
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
func ( s * OrderService ) Execute ( ctx context . Context , req * entity . OrderExecuteRequest ) ( * entity . ExecuteOrderResponse , error ) {
partnerID , orderID , err := s . crypt . ValidateJWTOrder ( req . Token )
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger . ContextLogger ( ctx ) . Error ( "error when validating JWT order" , zap . Error ( err ) )
return nil , err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
order , err := s . repo . FindByID ( ctx , orderID )
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
if errors . Is ( err , gorm . ErrRecordNotFound ) {
logger . ContextLogger ( ctx ) . Error ( "order not found" , zap . Int64 ( "orderID" , orderID ) )
return nil , errors . New ( "order not found" )
}
logger . ContextLogger ( ctx ) . Error ( "error when finding order by ID" , zap . Error ( err ) )
2023-10-08 15:59:42 +07:00
return nil , err
}
2024-06-04 02:59:31 +07:00
// 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 ) )
return nil , err
}
2023-10-08 15:59:42 +07:00
2024-06-04 02:59:31 +07:00
if payment != nil {
return s . createExecuteOrderResponse ( order , payment ) , nil
}
if order . PartnerID != partnerID {
logger . ContextLogger ( ctx ) . Error ( "partner ID mismatch" , zap . Int64 ( "orderID" , orderID ) , zap . Int64 ( "tokenPartnerID" , partnerID ) , zap . Int64 ( "orderPartnerID" , order . PartnerID ) )
return nil , errors . New ( "partner ID mismatch" )
}
if order . Status != "NEW" {
return nil , errors . New ( "invalid state" )
}
resp := & entity . ExecuteOrderResponse {
Order : order ,
}
if order . PaymentType != "CASH" {
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 )
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger . ContextLogger ( ctx ) . Error ( "error when updating order status" , zap . Error ( err ) )
2023-10-08 15:59:42 +07:00
return nil , err
}
2024-06-04 02:59:31 +07:00
return resp , nil
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
func ( s * OrderService ) createExecuteOrderResponse ( order * entity . Order , payment * entity . Payment ) * entity . ExecuteOrderResponse {
var metadata map [ string ] string
if err := json . Unmarshal ( payment . RequestMetadata , & metadata ) ; err != nil {
logger . ContextLogger ( context . Background ( ) ) . Error ( "error unmarshaling request metadata" , zap . Error ( err ) )
return & entity . ExecuteOrderResponse {
Order : order ,
}
}
return & entity . ExecuteOrderResponse {
Order : order ,
PaymentToken : metadata [ "payment_token" ] ,
RedirectURL : metadata [ "payment_redirect_url" ] ,
}
}
func ( s * OrderService ) processNonCashPayment ( ctx context . Context , order * entity . Order , partnerID , createdBy int64 ) ( * entity . MidtransResponse , error ) {
paymentRequest := entity . MidtransRequest {
PaymentReferenceID : generator . GenerateUUIDV4 ( ) ,
TotalAmount : int64 ( order . Amount ) ,
OrderItems : order . OrderItems ,
}
paymentResponse , err := s . midtrans . CreatePayment ( paymentRequest )
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger . ContextLogger ( ctx ) . Error ( "error when creating payment" , zap . Error ( err ) )
2023-10-08 15:59:42 +07:00
return nil , err
}
2024-06-04 02:59:31 +07:00
requestMetadata , err := json . Marshal ( map [ string ] string {
"partner_id" : strconv . FormatInt ( partnerID , 10 ) ,
"created_by" : strconv . FormatInt ( createdBy , 10 ) ,
"payment_token" : paymentResponse . Token ,
"payment_redirect_url" : paymentResponse . RedirectURL ,
} )
if err != nil {
logger . ContextLogger ( ctx ) . Error ( "error when marshaling request metadata" , zap . Error ( err ) )
return nil , err
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
payment := & entity . Payment {
PartnerID : strconv . FormatInt ( partnerID , 10 ) ,
OrderID : strconv . FormatInt ( order . ID , 10 ) ,
ReferenceID : paymentRequest . PaymentReferenceID ,
Channel : "xendit" ,
PaymentType : order . PaymentType ,
Amount : order . Amount ,
State : "pending" ,
CreatedAt : time . Now ( ) ,
UpdatedAt : time . Now ( ) ,
RequestMetadata : requestMetadata ,
2023-10-08 15:59:42 +07:00
}
2024-06-04 02:59:31 +07:00
_ , err = s . payment . Create ( ctx , payment )
2023-10-08 15:59:42 +07:00
if err != nil {
2024-06-04 02:59:31 +07:00
logger . ContextLogger ( ctx ) . Error ( "error when creating payment record" , zap . Error ( err ) )
2023-10-08 15:59:42 +07:00
return nil , err
}
2024-06-04 02:59:31 +07:00
return paymentResponse , nil
2023-10-08 15:59:42 +07:00
}