2024-10-15 11:52:34 +07:00

230 lines
6.6 KiB
Go

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, ""))
}