Update Payment VA

This commit is contained in:
aditya.siregar
2024-10-15 11:52:34 +07:00
parent d458f0649d
commit 764ea68bf9
17 changed files with 697 additions and 81 deletions
+229
View File
@@ -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
}
+14
View File
@@ -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)
}