Update Payment VA
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
package linkqu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"furtuna-be/internal/entity"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LinkQuConfig interface {
|
||||
LinkQuBaseURL() string
|
||||
LinkQuClientID() string
|
||||
LinkQuClientSecret() string
|
||||
LinkQuSignatureKey() string
|
||||
LinkQuUsername() string
|
||||
LinkQuPIN() string
|
||||
LinkQuCallbackURL() string
|
||||
}
|
||||
|
||||
type LinkQuService struct {
|
||||
config LinkQuConfig
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type CreateQRISRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
PartnerReff string `json:"partner_reff"`
|
||||
CustomerID string `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
Expired string `json:"expired"`
|
||||
Username string `json:"username"`
|
||||
Pin string `json:"pin"`
|
||||
CustomerPhone string `json:"customer_phone"`
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
Signature string `json:"signature"`
|
||||
ClientID string `json:"client_id"`
|
||||
URLCallback string `json:"url_callback"`
|
||||
BankCode string `json:"bank_code"`
|
||||
}
|
||||
|
||||
func NewLinkQuService(config LinkQuConfig) *LinkQuService {
|
||||
return &LinkQuService{
|
||||
config: config,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LinkQuService) constructQRISPayload(req entity.LinkQuRequest) CreateQRISRequest {
|
||||
return CreateQRISRequest{
|
||||
Amount: req.TotalAmount,
|
||||
PartnerReff: req.PaymentReferenceID,
|
||||
CustomerID: req.CustomerID,
|
||||
CustomerName: req.CustomerName,
|
||||
Expired: time.Now().Add(1 * time.Hour).Format("20060102150405"),
|
||||
Username: s.config.LinkQuUsername(),
|
||||
Pin: s.config.LinkQuPIN(),
|
||||
CustomerPhone: req.CustomerPhone,
|
||||
CustomerEmail: req.CustomerEmail,
|
||||
ClientID: s.config.LinkQuClientID(),
|
||||
URLCallback: s.config.LinkQuCallbackURL(),
|
||||
BankCode: req.BankCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LinkQuService) CreateQrisPayment(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuQRISResponse, error) {
|
||||
path := "/transaction/create/va"
|
||||
method := "POST"
|
||||
|
||||
req := s.constructQRISPayload(linkQuRequest)
|
||||
|
||||
if req.Expired == "" {
|
||||
req.Expired = time.Now().Add(1 * time.Hour).Format("20060102150405")
|
||||
}
|
||||
|
||||
paramOrder := []string{"Amount", "Expired", "PartnerReff", "CustomerID", "CustomerName", "CustomerEmail", "ClientID"}
|
||||
|
||||
signature, err := s.generateSignature(path, method, req, paramOrder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate signature: %w", err)
|
||||
}
|
||||
req.Signature = signature
|
||||
|
||||
reqBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s%s", s.config.LinkQuBaseURL(), "linkqu-partner", path)
|
||||
httpReq, err := http.NewRequest(method, url, bytes.NewBuffer(reqBody))
|
||||
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()
|
||||
|
||||
// Read response body
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
// Check for non-200 status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var qrisResp entity.LinkQuQRISResponse
|
||||
if err := json.Unmarshal(body, &qrisResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if qrisResp.ResponseCode != "00" {
|
||||
return nil, fmt.Errorf("error when create qris linkqu, status code %s", qrisResp.ResponseCode)
|
||||
}
|
||||
|
||||
return &qrisResp, nil
|
||||
}
|
||||
|
||||
func (s *LinkQuService) CreatePaymentVA(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuPaymentVAResponse, error) {
|
||||
path := "/transaction/create/va"
|
||||
method := "POST"
|
||||
|
||||
req := s.constructQRISPayload(linkQuRequest)
|
||||
|
||||
if req.Expired == "" {
|
||||
req.Expired = time.Now().Add(1 * time.Hour).Format("20060102150405")
|
||||
}
|
||||
|
||||
paramOrder := []string{"Amount", "Expired", "BankCode", "PartnerReff", "CustomerID", "CustomerName", "CustomerEmail", "ClientID"}
|
||||
|
||||
signature, err := s.generateSignature(path, method, req, paramOrder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate signature: %w", err)
|
||||
}
|
||||
req.Signature = signature
|
||||
|
||||
reqBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s%s", s.config.LinkQuBaseURL(), "linkqu-partner", path)
|
||||
httpReq, err := http.NewRequest(method, url, bytes.NewBuffer(reqBody))
|
||||
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()
|
||||
|
||||
// Read response body
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
// Check for non-200 status code
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var qrisResp entity.LinkQuPaymentVAResponse
|
||||
if err := json.Unmarshal(body, &qrisResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if qrisResp.ResponseCode != "00" {
|
||||
return nil, fmt.Errorf("error when create qris linkqu, status code %s", qrisResp.ResponseCode)
|
||||
}
|
||||
|
||||
return &qrisResp, nil
|
||||
}
|
||||
|
||||
func (s *LinkQuService) generateSignature(path, method string, req interface{}, paramOrder []string) (string, error) {
|
||||
var values []string
|
||||
reqValue := reflect.ValueOf(req)
|
||||
|
||||
for _, param := range paramOrder {
|
||||
field := reqValue.FieldByNameFunc(func(fieldName string) bool {
|
||||
return strings.EqualFold(fieldName, param)
|
||||
})
|
||||
|
||||
if field.IsValid() {
|
||||
values = append(values, fmt.Sprintf("%v", field.Interface()))
|
||||
} else {
|
||||
return "", fmt.Errorf("field %s not found in request struct", param)
|
||||
}
|
||||
}
|
||||
|
||||
secondValue := strings.Join(values, "")
|
||||
secondValue = cleanString(secondValue)
|
||||
|
||||
signToString := path + method + secondValue
|
||||
|
||||
h := hmac.New(sha256.New, []byte(s.config.LinkQuSignatureKey()))
|
||||
h.Write([]byte(signToString))
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func cleanString(s string) string {
|
||||
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
return strings.ToLower(reg.ReplaceAllString(s, ""))
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package pg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"furtuna-be/internal/entity"
|
||||
"furtuna-be/internal/repository/linkqu"
|
||||
mdtrns "furtuna-be/internal/repository/midtrans"
|
||||
)
|
||||
|
||||
type PaymentGatewayRepo struct {
|
||||
midtransService *mdtrns.ClientService
|
||||
linkquService *linkqu.LinkQuService
|
||||
}
|
||||
|
||||
func NewPaymentGatewayRepo(midtransConfig mdtrns.MidtransConfig, linkquConfig linkqu.LinkQuConfig) *PaymentGatewayRepo {
|
||||
return &PaymentGatewayRepo{
|
||||
midtransService: mdtrns.New(midtransConfig),
|
||||
linkquService: linkqu.NewLinkQuService(linkquConfig),
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *PaymentGatewayRepo) CreatePayment(request entity.PaymentRequest) (*entity.PaymentResponse, error) {
|
||||
return repo.createMidtransPayment(request)
|
||||
}
|
||||
|
||||
func (repo *PaymentGatewayRepo) CreateQRISPayment(request entity.PaymentRequest) (*entity.PaymentResponse, error) {
|
||||
switch request.Provider {
|
||||
case "MIDTRANS":
|
||||
return repo.createMidtransQRISPayment(request)
|
||||
case "LINKQU":
|
||||
return repo.createLinkQuQRISPayment(request)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported payment method for QRIS: %s", request.Provider)
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *PaymentGatewayRepo) CreatePaymentVA(request entity.PaymentRequest) (*entity.PaymentResponse, error) {
|
||||
resp, err := repo.linkquService.CreatePaymentVA(entity.LinkQuRequest{
|
||||
TotalAmount: request.TotalAmount,
|
||||
PaymentReferenceID: request.PaymentReferenceID,
|
||||
CustomerID: request.CustomerID,
|
||||
CustomerName: request.CustomerName,
|
||||
CustomerEmail: request.CustomerEmail,
|
||||
BankCode: request.BankCode,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.PaymentResponse{
|
||||
VirtualAccountNumber: resp.VirtualAccount,
|
||||
BankName: resp.BankName,
|
||||
BankCode: resp.BankCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *PaymentGatewayRepo) createMidtransPayment(request entity.PaymentRequest) (*entity.PaymentResponse, error) {
|
||||
midtransReq := entity.MidtransRequest{
|
||||
PaymentReferenceID: request.PaymentReferenceID,
|
||||
PaymentMethod: request.Provider,
|
||||
TotalAmount: request.TotalAmount,
|
||||
}
|
||||
|
||||
resp, err := repo.midtransService.CreatePayment(midtransReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.PaymentResponse{
|
||||
Token: resp.Token,
|
||||
RedirectURL: resp.RedirectURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *PaymentGatewayRepo) createLinkQuPayment(request entity.PaymentRequest) (*entity.PaymentResponse, error) {
|
||||
linkquReq := entity.LinkQuRequest{
|
||||
PaymentReferenceID: request.PaymentReferenceID,
|
||||
TotalAmount: request.TotalAmount,
|
||||
CustomerID: request.CustomerID,
|
||||
CustomerName: request.CustomerName,
|
||||
CustomerPhone: request.CustomerPhone,
|
||||
CustomerEmail: request.CustomerEmail,
|
||||
}
|
||||
|
||||
resp, err := repo.linkquService.CreateQrisPayment(linkquReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.PaymentResponse{
|
||||
Token: resp.PartnerReff2,
|
||||
RedirectURL: resp.ImageQRIS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *PaymentGatewayRepo) createMidtransQRISPayment(request entity.PaymentRequest) (*entity.PaymentResponse, error) {
|
||||
midtransReq := entity.MidtransRequest{
|
||||
PaymentReferenceID: request.PaymentReferenceID,
|
||||
PaymentMethod: "QRIS",
|
||||
TotalAmount: request.TotalAmount,
|
||||
}
|
||||
|
||||
resp, err := repo.midtransService.CreateQrisPayment(midtransReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.PaymentResponse{
|
||||
QRCodeURL: resp.QrCodeUrl,
|
||||
OrderID: resp.OrderID,
|
||||
Amount: resp.Amount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *PaymentGatewayRepo) createLinkQuQRISPayment(request entity.PaymentRequest) (*entity.PaymentResponse, error) {
|
||||
linkquReq := entity.LinkQuRequest{
|
||||
PaymentReferenceID: request.PaymentReferenceID,
|
||||
TotalAmount: request.TotalAmount,
|
||||
CustomerID: request.CustomerID,
|
||||
CustomerName: request.CustomerName,
|
||||
CustomerPhone: request.CustomerPhone,
|
||||
CustomerEmail: request.CustomerEmail,
|
||||
}
|
||||
|
||||
resp, err := repo.linkquService.CreateQrisPayment(linkquReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.PaymentResponse{
|
||||
QRCodeURL: resp.ImageQRIS,
|
||||
OrderID: resp.PartnerReff,
|
||||
Amount: resp.Amount,
|
||||
}, nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"furtuna-be/internal/repository/oss"
|
||||
"furtuna-be/internal/repository/partners"
|
||||
"furtuna-be/internal/repository/payment"
|
||||
pg "furtuna-be/internal/repository/payment_gateway"
|
||||
"furtuna-be/internal/repository/products"
|
||||
"furtuna-be/internal/repository/sites"
|
||||
"furtuna-be/internal/repository/studios"
|
||||
@@ -47,6 +48,7 @@ type RepoManagerImpl struct {
|
||||
EmailService EmailService
|
||||
License License
|
||||
Transaction TransactionRepository
|
||||
PG PaymentGateway
|
||||
}
|
||||
|
||||
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
||||
@@ -68,6 +70,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
||||
EmailService: brevo.New(&cfg.Brevo),
|
||||
License: license.NewLicenseRepository(db),
|
||||
Transaction: transactions.NewTransactionRepository(db),
|
||||
PG: pg.NewPaymentGatewayRepo(&cfg.Midtrans, &cfg.LinkQu),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,3 +217,14 @@ type TransactionRepository interface {
|
||||
GetTransactionList(ctx mycontext.Context, req entity.TransactionSearch) ([]*entity.TransactionList, int, error)
|
||||
Update(ctx context.Context, trx *gorm.DB, transaction *entity.Transaction) (*entity.Transaction, error)
|
||||
}
|
||||
|
||||
type LinQu interface {
|
||||
CreateQrisPayment(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuQRISResponse, error)
|
||||
CreatePaymentVA(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuPaymentVAResponse, error)
|
||||
}
|
||||
|
||||
type PaymentGateway interface {
|
||||
CreatePayment(request entity.PaymentRequest) (*entity.PaymentResponse, error)
|
||||
CreateQRISPayment(request entity.PaymentRequest) (*entity.PaymentResponse, error)
|
||||
CreatePaymentVA(request entity.PaymentRequest) (*entity.PaymentResponse, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user