305 lines
9.1 KiB
Go
305 lines
9.1 KiB
Go
package linkqu
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"enaklo-pos-be/internal/entity"
|
|
"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"`
|
|
}
|
|
|
|
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"`
|
|
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(),
|
|
}
|
|
}
|
|
|
|
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(),
|
|
BankCode: req.BankCode,
|
|
}
|
|
}
|
|
|
|
func (s *LinkQuService) CreateQrisPayment(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuQRISResponse, error) {
|
|
path := "/transaction/create/qris"
|
|
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 , %s", qrisResp.ResponseCode, qrisResp.ResponseDesc)
|
|
}
|
|
|
|
return &qrisResp, nil
|
|
}
|
|
|
|
func (s *LinkQuService) CreatePaymentVA(linkQuRequest entity.LinkQuRequest) (*entity.LinkQuPaymentVAResponse, error) {
|
|
path := "/transaction/create/va"
|
|
method := "POST"
|
|
|
|
req := s.constructVAPayload(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, ""))
|
|
}
|
|
|
|
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
|
|
}
|