From 53505cf2ab6890fa66639b7ad43f4724f27cd325 Mon Sep 17 00:00:00 2001 From: "aditya.siregar" Date: Tue, 6 Aug 2024 16:21:55 +0700 Subject: [PATCH] Add QR Payment --- internal/entity/midtrans.go | 6 ++ internal/entity/order.go | 1 + internal/handlers/http/customerorder/order.go | 3 +- internal/handlers/request/order.go | 7 +- internal/handlers/response/order.go | 1 + internal/repository/midtrans/init.go | 47 +++++++++- internal/repository/payment/payment.go | 4 + internal/repository/repository.go | 1 + internal/services/order/order.go | 87 ++++++++++++++++--- internal/services/service.go | 2 +- 10 files changed, 141 insertions(+), 18 deletions(-) diff --git a/internal/entity/midtrans.go b/internal/entity/midtrans.go index b3166c1..4823994 100644 --- a/internal/entity/midtrans.go +++ b/internal/entity/midtrans.go @@ -11,3 +11,9 @@ type MidtransRequest struct { TotalAmount int64 OrderItems []OrderItem } + +type MidtransQrisResponse struct { + QrCodeUrl string + OrderID string + Amount int64 +} diff --git a/internal/entity/order.go b/internal/entity/order.go index c766f5a..f3e5c04 100644 --- a/internal/entity/order.go +++ b/internal/entity/order.go @@ -45,6 +45,7 @@ type OrderResponse struct { type ExecuteOrderResponse struct { Order *Order + QRCode string PaymentToken string RedirectURL string } diff --git a/internal/handlers/http/customerorder/order.go b/internal/handlers/http/customerorder/order.go index 7db90d5..46b593f 100644 --- a/internal/handlers/http/customerorder/order.go +++ b/internal/handlers/http/customerorder/order.go @@ -139,6 +139,7 @@ func MapOrderToExecuteOrderResponse(orderResponse *entity.ExecuteOrderResponse) OrderItems: orderItems, PaymentToken: orderResponse.PaymentToken, RedirectURL: orderResponse.RedirectURL, + QRcode: orderResponse.QRCode, } } @@ -199,7 +200,7 @@ func (h *Handler) Detail(c *gin.Context) { } ctx := request.GetMyContext(c) - order, err := h.service.GetByID(ctx, req.ID) + order, err := h.service.GetByID(ctx, req.ID, req.ReferenceID) if err != nil { response.ErrorWrapper(c, err) return diff --git a/internal/handlers/request/order.go b/internal/handlers/request/order.go index 86c43ea..b49ba0f 100644 --- a/internal/handlers/request/order.go +++ b/internal/handlers/request/order.go @@ -98,9 +98,10 @@ func (e Execute) ToOrderExecuteRequest(createdBy int64) *entity.OrderExecuteRequ } type OrderParamCustomer struct { - ID int64 `form:"id" json:"id" example:"10"` - Limit int `form:"limit" json:"limit" example:"10"` - Offset int `form:"offset" json:"offset" example:"0"` + ID int64 `form:"id" json:"id" example:"10"` + ReferenceID string `form:"reference_id" json:"reference_id" example:"10"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` } func (o *OrderParamCustomer) ToOrderEntity(ctx mycontext.Context) entity.OrderSearch { diff --git a/internal/handlers/response/order.go b/internal/handlers/response/order.go index 15e70d2..b9a6ac7 100644 --- a/internal/handlers/response/order.go +++ b/internal/handlers/response/order.go @@ -102,6 +102,7 @@ type ExecuteOrderResponse struct { OrderItems []CreateOrderItemResponse `json:"order_items"` PaymentToken string `json:"payment_token"` RedirectURL string `json:"redirect_url"` + QRcode string `json:"qr_code"` } type CreateOrderItemResponse struct { diff --git a/internal/repository/midtrans/init.go b/internal/repository/midtrans/init.go index 2a73dbd..a399269 100644 --- a/internal/repository/midtrans/init.go +++ b/internal/repository/midtrans/init.go @@ -39,10 +39,10 @@ func (c *ClientService) CreatePayment(order entity.MidtransRequest) (*entity.Mid Client: c.client, } - paymentMethod := []midtrans.PaymentType{} + var paymentMethod []midtrans.PaymentType - if order.PaymentMethod == "GOPAY" { - paymentMethod = append(paymentMethod, midtrans.SourceGopay) + if order.PaymentMethod == "QRIS" { + paymentMethod = []midtrans.PaymentType{midtrans.SourceGopay} } snapReq := &midtrans.SnapReq{ @@ -83,3 +83,44 @@ func (c ClientService) getProductItems(products []entity.OrderItem) []midtrans.I return items } + +func (c *ClientService) CreateQrisPayment(order entity.MidtransRequest) (*entity.MidtransQrisResponse, error) { + coreGateway := midtrans.CoreGateway{ + Client: c.client, + } + + req := &midtrans.ChargeReq{ + PaymentType: midtrans.SourceGopay, + TransactionDetails: midtrans.TransactionDetails{ + OrderID: order.PaymentReferenceID, + GrossAmt: order.TotalAmount, + }, + } + + // Request charge and retrieve response + resp, err := coreGateway.Charge(req) + if err != nil { + logger.GetLogger().Error(fmt.Sprintf("error when creating QRIS payment: %v", err)) + return nil, err + } + + // Extract QR code URL from response actions + var qrCodeURL string + for _, action := range resp.Actions { + if action.Name == "generate-qr-code" { + qrCodeURL = action.URL + break + } + } + + if qrCodeURL == "" { + logger.GetLogger().Error("error: QR code URL not provided in response") + return nil, fmt.Errorf("QR code URL not provided in response") + } + + return &entity.MidtransQrisResponse{ + QrCodeUrl: qrCodeURL, + OrderID: order.PaymentReferenceID, + Amount: order.TotalAmount, + }, nil +} diff --git a/internal/repository/payment/payment.go b/internal/repository/payment/payment.go index de4a9dc..e8d756f 100644 --- a/internal/repository/payment/payment.go +++ b/internal/repository/payment/payment.go @@ -72,6 +72,10 @@ func (r *PaymentRepository) FindByOrderAndPartnerID(ctx context.Context, orderID // FindByReferenceID retrieves a payment record by its reference ID func (r *PaymentRepository) FindByReferenceID(ctx context.Context, db *gorm.DB, referenceID string) (*entity.Payment, error) { payment := new(entity.Payment) + if db == nil { + db = r.db + } + 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 diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 6c321d9..fa31ad4 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -192,6 +192,7 @@ type WalletRepository interface { type Midtrans interface { CreatePayment(order entity.MidtransRequest) (*entity.MidtransResponse, error) + CreateQrisPayment(order entity.MidtransRequest) (*entity.MidtransQrisResponse, error) } type Payment interface { diff --git a/internal/services/order/order.go b/internal/services/order/order.go index 8d70620..29ffcaf 100644 --- a/internal/services/order/order.go +++ b/internal/services/order/order.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - errors2 "furtuna-be/internal/common/errors" "furtuna-be/internal/common/logger" "furtuna-be/internal/common/mycontext" order2 "furtuna-be/internal/constants/order" @@ -155,12 +154,20 @@ func (s *OrderService) Execute(ctx context.Context, req *entity.OrderExecuteRequ } if order.PaymentType != "CASH" { - paymentResponse, err := s.processNonCashPayment(ctx, order, partnerID, req.CreatedBy) - if err != nil { - return nil, err + 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 } - resp.PaymentToken = paymentResponse.Token - resp.RedirectURL = paymentResponse.RedirectURL } order.SetExecutePaymentStatus() @@ -217,7 +224,54 @@ func (s *OrderService) processNonCashPayment(ctx context.Context, order *entity. PartnerID: partnerID, OrderID: order.ID, ReferenceID: paymentRequest.PaymentReferenceID, - Channel: "XENDIT", + Channel: "MIDTRANS", + PaymentType: order.PaymentType, + Amount: order.Amount, + State: "PENDING", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + RequestMetadata: requestMetadata, + } + + _, err = s.payment.Create(ctx, payment) + if err != nil { + logger.ContextLogger(ctx).Error("error when creating payment record", zap.Error(err)) + return nil, err + } + + return paymentResponse, nil +} + +func (s *OrderService) processQRPayment(ctx context.Context, order *entity.Order, partnerID, createdBy int64) (*entity.MidtransQrisResponse, error) { + paymentRequest := entity.MidtransRequest{ + PaymentReferenceID: generator.GenerateUUIDV4(), + TotalAmount: int64(order.Amount), + OrderItems: order.OrderItems, + PaymentMethod: order.PaymentType, + } + + paymentResponse, err := s.midtrans.CreateQrisPayment(paymentRequest) + if err != nil { + logger.ContextLogger(ctx).Error("error when creating payment", zap.Error(err)) + return nil, err + } + + requestMetadata, err := json.Marshal(map[string]string{ + "partner_id": strconv.FormatInt(partnerID, 10), + "created_by": strconv.FormatInt(createdBy, 10), + "qr_code": paymentResponse.QrCodeUrl, + }) + + if err != nil { + logger.ContextLogger(ctx).Error("error when marshaling request metadata", zap.Error(err)) + return nil, err + } + + payment := &entity.Payment{ + PartnerID: partnerID, + OrderID: order.ID, + ReferenceID: paymentRequest.PaymentReferenceID, + Channel: "MIDTRANS", PaymentType: order.PaymentType, Amount: order.Amount, State: "PENDING", @@ -362,16 +416,29 @@ func (s OrderService) SumAmount(ctx mycontext.Context, req entity.OrderSearch) ( return data, nil } -func (s *OrderService) GetByID(ctx mycontext.Context, id int64) (*entity.Order, error) { +func (s *OrderService) GetByID(ctx mycontext.Context, id int64, referenceID string) (*entity.Order, error) { + if referenceID != "" { + payment, err := s.payment.FindByReferenceID(ctx, nil, referenceID) + if err != nil { + logger.ContextLogger(ctx).Error("error when getting payment by IDs", zap.Error(err)) + return nil, err + } + id = payment.OrderID + } + order, err := s.repo.FindByID(ctx, id) if err != nil { logger.ContextLogger(ctx).Error("error when getting products by IDs", zap.Error(err)) return nil, err } - if order.CreatedBy != ctx.RequestedBy() { - return nil, errors2.NewError(errors2.ErrorBadRequest.ErrorType(), "order not found") + if ctx.IsCasheer() { + return order, nil } + //if order.CreatedBy != ctx.RequestedBy() { + // return nil, errors2.NewError(errors2.ErrorBadRequest.ErrorType(), "order not found") + //} + return order, nil } diff --git a/internal/services/service.go b/internal/services/service.go index d287714..6765e57 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -119,7 +119,7 @@ type Order interface { SumAmount(ctx mycontext.Context, req entity.OrderSearch) (*entity.Order, error) GetDailySales(ctx mycontext.Context, req entity.OrderSearch) ([]entity.ProductDailySales, error) GetPaymentDistribution(ctx mycontext.Context, req entity.OrderSearch) ([]entity.PaymentTypeDistribution, error) - GetByID(ctx mycontext.Context, id int64) (*entity.Order, error) + GetByID(ctx mycontext.Context, id int64, referenceID string) (*entity.Order, error) } type OSSService interface {