This commit is contained in:
aditya.siregar 2024-10-27 19:48:10 +07:00
parent 758df918ae
commit 081a1aa27b
13 changed files with 190 additions and 35 deletions

View File

@ -12,6 +12,13 @@ type LinkQuRequest struct {
OrderItems []OrderItem
}
type LinkQuCallback struct {
PartnerReff string
PaymentReff string
Status string
Signature string
}
type LinkQuQRISResponse struct {
Time int `json:"time"`
Amount int64 `json:"amount"`
@ -56,3 +63,36 @@ type LinkQuPaymentVAResponse struct {
Signature string `json:"signature"`
UrlCallback string `json:"url_callback"`
}
type LinkQuCheckStatusResponse struct {
ResponseCode string `json:"rc"`
ResponseDesc string `json:"rd"`
Total int64 `json:"total"`
Balance int64 `json:"balance"`
Data LinkQuCheckStatusData `json:"data"`
Request map[string]interface{} `json:"request"`
LastUpdate string `json:"lastUpdate"`
DataAdditional map[string]interface{} `json:"dataadditional"`
}
type LinkQuCheckStatusData struct {
InquiryReff int64 `json:"inquiry_reff"`
PaymentReff int64 `json:"payment_reff"`
PartnerReff string `json:"partner_reff"`
Reference string `json:"reference"`
ProductID string `json:"id_produk"`
ProductName string `json:"nama_produk"`
ProductGroup string `json:"grup_produk"`
Debitted int64 `json:"debitted"`
Amount int64 `json:"amount"`
AmountFee int64 `json:"amountfee"`
Info1 string `json:"info1"`
Info2 string `json:"info2"`
Info3 string `json:"info3"`
Info4 string `json:"info4"`
StatusTrx string `json:"status_trx"`
StatusDesc string `json:"status_desc"`
StatusPaid string `json:"status_paid"`
Balance int64 `json:"balance"`
TipsQRIS int64 `json:"tips_qris"`
}

View File

@ -7,6 +7,7 @@ import (
"furtuna-be/internal/handlers/request"
"furtuna-be/internal/handlers/response"
"furtuna-be/internal/services"
"furtuna-be/internal/utils"
"net/http"
"time"
@ -56,7 +57,7 @@ func (h *Handler) Inquiry(c *gin.Context) {
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Data: MapOrderToCreateOrderResponse(order),
Data: MapOrderToCreateOrderResponse(order, req),
})
}
@ -88,7 +89,7 @@ func (h *Handler) Execute(c *gin.Context) {
})
}
func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse) response.CreateOrderResponse {
func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse, req request.CustomerOrder) response.CreateOrderResponse {
order := orderResponse.Order
orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems))
for i, item := range order.OrderItems {
@ -115,6 +116,8 @@ func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse) response
Total: order.Total,
VisitDate: order.VisitDate.Format("2006-01-02"),
SiteName: order.Site.Name,
BankCode: req.BankCode,
BankName: utils.BankName(req.BankCode),
}
}

View File

@ -1,6 +1,9 @@
package mdtrns
package linkqu
import (
"encoding/json"
"fmt"
"furtuna-be/internal/entity"
"furtuna-be/internal/handlers/request"
"furtuna-be/internal/handlers/response"
"furtuna-be/internal/services"
@ -13,7 +16,7 @@ type Handler struct {
}
func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
route := group.Group("/midtrans")
route := group.Group("/linkqu")
route.POST("/callback", h.Callback)
}
@ -25,35 +28,24 @@ func NewHandler(service services.Order) *Handler {
}
func (h *Handler) Callback(c *gin.Context) {
var callbackData request.MidtransCallbackRequest
var callbackData request.LinQuCallback
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"}
data, _ := json.Marshal(callbackData)
fmt.Println(string(data))
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())
err := h.service.ProcessLinkQuCallback(c, &entity.LinkQuCallback{
PaymentReff: callbackData.PartnerReff2,
Status: callbackData.Status,
Signature: callbackData.Signature,
PartnerReff: callbackData.PartnerReff,
})
if err != nil {
c.JSON(http.StatusUnauthorized, response.BaseResponse{
c.JSON(http.StatusBadRequest, response.BaseResponse{
Success: false,
Status: http.StatusBadRequest,
Message: err.Error(),
@ -63,9 +55,10 @@ func (h *Handler) Callback(c *gin.Context) {
}
c.JSON(http.StatusOK, response.BaseResponse{
Success: true,
Status: http.StatusOK,
Message: "order",
Success: true,
Status: http.StatusOK,
Message: "order",
Response: "00",
})
}

View File

@ -37,3 +37,22 @@ func (m *MidtransCallbackRequest) ToEntity() *entity.CallbackRequest {
TransactionStatus: m.TransactionStatus,
}
}
type LinQuCallback struct {
Amount int `json:"amount"`
SerialNumber string `json:"serialnumber"`
Type string `json:"type"`
PaymentReff int64 `json:"payment_reff"`
VaCode string `json:"va_code"`
PartnerReff string `json:"partner_reff"`
PartnerReff2 string `json:"partner_reff2"`
AdditionalFee int `json:"additionalfee"`
Balance int `json:"balance"`
CreditBalance int `json:"credit_balance"`
TransactionTime string `json:"transaction_time"`
VaNumber string `json:"va_number"`
CustomerName string `json:"customer_name"`
Username string `json:"username"`
Status string `json:"status"`
Signature string `json:"signature"`
}

View File

@ -9,4 +9,5 @@ type BaseResponse struct {
ErrorDetail interface{} `json:"error_detail,omitempty"`
Data interface{} `json:"data,omitempty"`
PagingMeta *PagingMeta `json:"meta,omitempty"`
Response string `json:"response,omitempty"`
}

View File

@ -96,6 +96,8 @@ type CreateOrderResponse struct {
CreatedAt time.Time `json:"created_at"`
OrderItems []CreateOrderItemResponse `json:"order_items"`
Token string `json:"token"`
BankCode string `json:"bank_code"`
BankName string `json:"bank_name"`
}
type PrintDetailResponse struct {

View File

@ -227,3 +227,47 @@ func cleanString(s string) string {
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
return strings.ToLower(reg.ReplaceAllString(s, ""))
}
func (s *LinkQuService) CheckPaymentStatus(partnerReff string) (*entity.LinkQuCheckStatusResponse, error) {
path := "/transaction/payment/checkstatus"
method := "GET"
url := fmt.Sprintf("%s%s%s?username=%s&partnerreff=%s",
s.config.LinkQuBaseURL(), "/linkqu-partner", path, s.config.LinkQuUsername(), partnerReff)
httpReq, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("client-id", s.config.LinkQuClientID())
httpReq.Header.Set("client-secret", s.config.LinkQuClientSecret())
resp, err := s.client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
}
// Parse response
var checkStatusResp entity.LinkQuCheckStatusResponse
if err := json.Unmarshal(body, &checkStatusResp); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if checkStatusResp.ResponseCode != "00" {
return nil, fmt.Errorf("error when checking payment status, status code %s", checkStatusResp.ResponseCode)
}
return &checkStatusResp, nil
}

View File

@ -6,6 +6,7 @@ import (
"furtuna-be/internal/common/mycontext"
"furtuna-be/internal/repository/brevo"
"furtuna-be/internal/repository/license"
"furtuna-be/internal/repository/linkqu"
mdtrns "furtuna-be/internal/repository/midtrans"
"furtuna-be/internal/repository/orders"
"furtuna-be/internal/repository/oss"
@ -49,6 +50,7 @@ type RepoManagerImpl struct {
License License
Transaction TransactionRepository
PG PaymentGateway
LinkQu LinkQu
}
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
@ -71,6 +73,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
License: license.NewLicenseRepository(db),
Transaction: transactions.NewTransactionRepository(db),
PG: pg.NewPaymentGatewayRepo(&cfg.Midtrans, &cfg.LinkQu),
LinkQu: linkqu.NewLinkQuService(&cfg.LinkQu),
}
}
@ -218,9 +221,10 @@ type TransactionRepository interface {
Update(ctx context.Context, trx *gorm.DB, transaction *entity.Transaction) (*entity.Transaction, error)
}
type LinQu interface {
type LinkQu interface {
CreateQrisPayment(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuQRISResponse, error)
CreatePaymentVA(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuPaymentVAResponse, error)
CheckPaymentStatus(partnerReff string) (*entity.LinkQuCheckStatusResponse, error)
}
type PaymentGateway interface {

View File

@ -3,6 +3,7 @@ package routes
import (
"furtuna-be/internal/handlers/http/balance"
"furtuna-be/internal/handlers/http/license"
linkqu "furtuna-be/internal/handlers/http/linqu"
mdtrns "furtuna-be/internal/handlers/http/midtrans"
"furtuna-be/internal/handlers/http/order"
"furtuna-be/internal/handlers/http/oss"
@ -60,6 +61,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
license.NewHandler(serviceManager.LicenseSvc),
transaction.New(serviceManager.Transaction),
balance.NewHandler(serviceManager.Balance),
linkqu.NewHandler(serviceManager.OrderSvc),
}
for _, handler := range serverRoutes {

View File

@ -51,9 +51,9 @@ func (u *AuthServiceImpl) AuthenticateUser(ctx context.Context, email, password
return nil, errors.ErrorUserIsNotFound
}
//if ok := u.crypto.CompareHashAndPassword(user.Password, password); !ok {
// //return nil, errors.ErrorUserInvalidLogin
//}
if ok := u.crypto.CompareHashAndPassword(user.Password, password); !ok {
return nil, errors.ErrorUserInvalidLogin
}
signedToken, err := u.crypto.GenerateJWT(user.ToUser())

View File

@ -32,6 +32,7 @@ type OrderService struct {
transaction repository.TransactionRepository
txmanager repository.TransactionManager
wallet repository.WalletRepository
linkquRepo repository.LinkQu
cfg Config
}
@ -42,6 +43,7 @@ func NewOrderService(
txmanager repository.TransactionManager,
wallet repository.WalletRepository, cfg Config,
transaction repository.TransactionRepository,
linkquRepo repository.LinkQu,
) *OrderService {
return &OrderService{
repo: repo,
@ -53,6 +55,7 @@ func NewOrderService(
wallet: wallet,
cfg: cfg,
transaction: transaction,
linkquRepo: linkquRepo,
}
}
@ -558,9 +561,9 @@ func (s *OrderService) processPayment(ctx context.Context, tx *gorm.DB, req *ent
func updatePaymentState(status string) string {
switch status {
case "settlement", "capture":
case "settlement", "capture", "paid", "settle":
return "PAID"
case "expire", "deny", "cancel", "failure":
case "expire", "deny", "cancel", "failure", "EXPIRED":
return "EXPIRED"
default:
return status
@ -680,3 +683,31 @@ func (s *OrderService) GetPrintDetail(ctx mycontext.Context, id int64) (*entity.
return order, nil
}
func (s *OrderService) ProcessLinkQuCallback(ctx context.Context, req *entity.LinkQuCallback) 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()
pay, err := s.linkquRepo.CheckPaymentStatus(req.PaymentReff)
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
if pay.ResponseCode != "00" {
return nil
}
err = s.processPayment(ctx, tx, &entity.CallbackRequest{
TransactionID: req.PartnerReff,
TransactionStatus: pay.Data.StatusPaid,
})
if err != nil {
return fmt.Errorf("failed to process payment: %w", err)
}
return tx.Commit().Error
}

View File

@ -47,7 +47,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
UserSvc: users.NewUserService(repo.User),
StudioSvc: studio.NewStudioService(repo.Studio),
ProductSvc: product.NewProductService(repo.Product),
OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Crypto, repo.PG, repo.Payment, repo.Trx, repo.Wallet, &cfg.Order, repo.Transaction),
OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Crypto, repo.PG, repo.Payment, repo.Trx, repo.Wallet, &cfg.Order, repo.Transaction, repo.LinkQu),
OSSSvc: oss.NewOSSService(repo.OSS),
PartnerSvc: partner.NewPartnerService(
repo.Partner, users.NewUserService(repo.User), repo.Trx, repo.Wallet, repo.User),
@ -114,6 +114,7 @@ type Order interface {
GetPaymentDistribution(ctx mycontext.Context, req entity.OrderSearch) ([]entity.PaymentTypeDistribution, error)
GetByID(ctx mycontext.Context, id int64, referenceID string) (*entity.Order, error)
GetPrintDetail(ctx mycontext.Context, id int64) (*entity.OrderPrintDetail, error)
ProcessLinkQuCallback(ctx context.Context, req *entity.LinkQuCallback) error
}
type OSSService interface {

View File

@ -1 +1,16 @@
package utils
func BankName(bankCode string) string {
bankCodeMap := map[string]string{
"001": "Bank Central Asia (BCA)",
"002": "Bank Rakyat Indonesia (BRI)",
"008": "Bank Mandiri",
"009": "Bank Negara Indonesia (BNI)",
"014": "Bank Tabungan Negara (BTN)",
}
if bankName, exists := bankCodeMap[bankCode]; exists {
return bankName
}
return "Bank code not recognized"
}