305 lines
9.1 KiB
Go
Raw Normal View History

2024-10-15 11:52:34 +07:00
package linkqu
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
2025-03-04 20:36:17 +07:00
"enaklo-pos-be/internal/entity"
2024-10-15 11:52:34 +07:00
"encoding/hex"
"encoding/json"
"fmt"
"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"`
2024-10-27 23:24:34 +07:00
}
type CreateVARequest 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"`
2024-10-15 11:52:34 +07:00
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(),
2024-10-27 23:24:34 +07:00
}
}
func (s *LinkQuService) constructVAPayload(req entity.LinkQuRequest) CreateVARequest {
return CreateVARequest{
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(),
2024-10-15 11:52:34 +07:00
BankCode: req.BankCode,
}
}
func (s *LinkQuService) CreateQrisPayment(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuQRISResponse, error) {
2024-10-28 10:58:07 +07:00
path := "/transaction/create/qris"
2024-10-15 11:52:34 +07:00
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" {
2024-10-27 23:11:12 +07:00
return nil, fmt.Errorf("error when create qris linkqu, status code %s , %s", qrisResp.ResponseCode, qrisResp.ResponseDesc)
2024-10-15 11:52:34 +07:00
}
return &qrisResp, nil
}
func (s *LinkQuService) CreatePaymentVA(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuPaymentVAResponse, error) {
path := "/transaction/create/va"
method := "POST"
2024-10-27 23:24:34 +07:00
req := s.constructVAPayload(linkQuRequest)
2024-10-15 11:52:34 +07:00
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, ""))
}
2024-10-27 19:48:10 +07:00
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
}