2023-07-10 08:10:13 +00:00
|
|
|
package wspay
|
|
|
|
|
|
|
|
import (
|
2023-07-27 20:46:37 +00:00
|
|
|
"bytes"
|
2023-07-10 08:10:13 +00:00
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/hex"
|
2023-07-27 20:46:37 +00:00
|
|
|
"encoding/json"
|
2023-07-10 08:10:13 +00:00
|
|
|
"errors"
|
2023-07-27 20:46:37 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
2023-07-10 08:10:13 +00:00
|
|
|
"github.com/google/uuid"
|
2023-07-27 20:46:37 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"payment-poc/database"
|
2023-07-26 07:51:29 +00:00
|
|
|
"payment-poc/state"
|
2023-07-10 08:10:13 +00:00
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Service struct {
|
2023-07-27 20:46:37 +00:00
|
|
|
Provider *database.PaymentEntryProvider
|
|
|
|
ShopId string
|
|
|
|
ShopSecret string
|
|
|
|
BackendUrl string
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 20:46:37 +00:00
|
|
|
func (s *Service) CreatePaymentUrl(amount int64) (string, error) {
|
|
|
|
entry, err := s.Provider.CreateEntry(database.PaymentEntry{
|
2023-07-30 10:14:05 +00:00
|
|
|
Gateway: state.GatewayWsPay,
|
2023-07-27 20:46:37 +00:00
|
|
|
State: state.StateInitialized,
|
|
|
|
TotalAmount: amount,
|
|
|
|
})
|
2023-07-10 08:10:13 +00:00
|
|
|
if err != nil {
|
2023-07-27 20:46:37 +00:00
|
|
|
return "", err
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
2023-07-27 20:46:37 +00:00
|
|
|
return "/wspay/initialize/" + entry.Id.String(), nil
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 20:46:37 +00:00
|
|
|
func (s *Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error) {
|
|
|
|
if entry.State == state.StateAccepted {
|
|
|
|
var request = CompletionRequest{
|
|
|
|
Version: "2.0",
|
|
|
|
WsPayOrderId: entry.Id.String(),
|
|
|
|
ShopId: s.ShopId,
|
|
|
|
ApprovalCode: *entry.ApprovalCode,
|
|
|
|
STAN: *entry.STAN,
|
|
|
|
Amount: amount,
|
|
|
|
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, *entry.ApprovalCode, amount),
|
|
|
|
}
|
|
|
|
|
|
|
|
httpResponse, err := createRequest(
|
|
|
|
"POST",
|
|
|
|
"https://test.wspay.biz/api/services/completion",
|
|
|
|
map[string]string{"content-type": "application/json"},
|
|
|
|
toJson(request),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return database.PaymentEntry{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var response CompletionResponse
|
|
|
|
err = readResponse(httpResponse, &response)
|
|
|
|
if err != nil {
|
|
|
|
return database.PaymentEntry{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if CompareCompletionReturnSignature(response.Signature, s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, response.ActionSuccess, response.ApprovalCode) != nil {
|
|
|
|
entry.Amount = &amount
|
|
|
|
entry.State = state.StateCompleted
|
|
|
|
} else {
|
|
|
|
return database.PaymentEntry{}, errors.New("invalid signature")
|
|
|
|
}
|
|
|
|
return entry, nil
|
|
|
|
} else {
|
|
|
|
return database.PaymentEntry{}, errors.New("payment is in invalid state")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
|
|
|
if entry.State == state.StateAccepted {
|
|
|
|
var request = CompletionRequest{
|
|
|
|
Version: "2.0",
|
|
|
|
WsPayOrderId: entry.Id.String(),
|
|
|
|
ShopId: s.ShopId,
|
|
|
|
ApprovalCode: *entry.ApprovalCode,
|
|
|
|
STAN: *entry.STAN,
|
|
|
|
Amount: entry.TotalAmount,
|
|
|
|
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, *entry.ApprovalCode, entry.TotalAmount),
|
|
|
|
}
|
|
|
|
|
|
|
|
httpResponse, err := createRequest(
|
|
|
|
"POST",
|
|
|
|
"https://test.wspay.biz/api/services/void",
|
|
|
|
map[string]string{"content-type": "application/json"},
|
|
|
|
toJson(request),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return database.PaymentEntry{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var response CompletionResponse
|
|
|
|
err = readResponse(httpResponse, &response)
|
|
|
|
if err != nil {
|
|
|
|
return database.PaymentEntry{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if CompareCompletionReturnSignature(response.Signature, s.ShopId, s.ShopSecret, entry.Id.String(), *entry.STAN, response.ActionSuccess, response.ApprovalCode) != nil {
|
|
|
|
entry.State = state.StateCanceled
|
|
|
|
} else {
|
|
|
|
return database.PaymentEntry{}, errors.New("invalid signature")
|
|
|
|
}
|
|
|
|
return entry, nil
|
|
|
|
} else {
|
|
|
|
return database.PaymentEntry{}, errors.New("payment is in invalid state")
|
|
|
|
}
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 20:46:37 +00:00
|
|
|
func (s *Service) InitializePayment(entry database.PaymentEntry) Form {
|
|
|
|
form := Form{
|
|
|
|
ShopID: s.ShopId,
|
|
|
|
ShoppingCartID: entry.Id.String(),
|
|
|
|
Version: "2.0",
|
|
|
|
TotalAmount: entry.TotalAmount,
|
|
|
|
ReturnURL: s.BackendUrl + "/wspay/success",
|
|
|
|
ReturnErrorURL: s.BackendUrl + "/wspay/error",
|
|
|
|
CancelURL: s.BackendUrl + "/wspay/cancel",
|
|
|
|
Signature: CalculateFormSignature(s.ShopId, s.ShopSecret, entry.Id.String(), entry.TotalAmount),
|
|
|
|
}
|
|
|
|
return form
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 20:46:37 +00:00
|
|
|
func (s *Service) HandleSuccessResponse(c *gin.Context) (string, error) {
|
|
|
|
response := FormReturn{}
|
|
|
|
if err := c.ShouldBind(&response); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
entry, err := s.Provider.FetchById(uuid.MustParse(response.ShoppingCartID))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := CompareFormReturnSignature(response.Signature, s.ShopId, s.ShopSecret, response.ShoppingCartID, response.Success, response.ApprovalCode); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.Lang = &response.Lang
|
|
|
|
entry.ECI = &response.ECI
|
|
|
|
entry.STAN = &response.STAN
|
|
|
|
entry.Success = &response.Success
|
|
|
|
entry.ApprovalCode = &response.ApprovalCode
|
|
|
|
entry.State = state.StateAccepted
|
|
|
|
|
|
|
|
if _, err := s.Provider.UpdateEntry(entry); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return "/entries/" + entry.Id.String(), nil
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-27 20:46:37 +00:00
|
|
|
func (s *Service) HandleErrorResponse(c *gin.Context, paymentState state.PaymentState) (string, error) {
|
|
|
|
response := FormError{}
|
|
|
|
if err := c.ShouldBind(&response); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
entry, err := s.Provider.FetchById(uuid.MustParse(response.ShoppingCartID))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := CompareFormReturnSignature(response.Signature, s.ShopId, s.ShopSecret, response.ShoppingCartID, response.Success, response.ApprovalCode); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.Lang = &response.Lang
|
|
|
|
entry.ECI = &response.ECI
|
|
|
|
entry.Success = &response.Success
|
|
|
|
entry.ApprovalCode = &response.ApprovalCode
|
|
|
|
entry.Error = &response.ErrorMessage
|
|
|
|
entry.State = paymentState
|
|
|
|
|
|
|
|
if _, err := s.Provider.UpdateEntry(entry); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return "/entries/" + entry.Id.String(), nil
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func CalculateFormSignature(shopId string, secret string, cartId string, amount int64) string {
|
|
|
|
/**
|
|
|
|
Represents a signature created from string formatted from following values in a following order using
|
|
|
|
SHA512 algorithm:
|
|
|
|
ShopID
|
|
|
|
SecretKey
|
|
|
|
ShoppingCartID
|
|
|
|
SecretKey
|
|
|
|
TotalAmount
|
|
|
|
SecretKey
|
|
|
|
*/
|
|
|
|
signature := shopId + secret + cartId + secret + strconv.FormatInt(amount, 10) + secret
|
|
|
|
hash := sha512.New()
|
|
|
|
hash.Write([]byte(signature))
|
|
|
|
return hex.EncodeToString(hash.Sum(nil))
|
|
|
|
}
|
|
|
|
|
2023-07-27 10:09:38 +00:00
|
|
|
func CalculateCompletionSignature(shopId string, secret string, cartId string, stan string, approvalCode string, amount int64) string {
|
|
|
|
/**
|
|
|
|
Represents a signature created from string formatted from following values in a following order using
|
|
|
|
SHA512 algorithm:
|
|
|
|
ShopID
|
|
|
|
WsPayOrderId
|
|
|
|
SecretKey
|
|
|
|
STAN
|
|
|
|
SecretKey
|
|
|
|
ApprovalCode
|
|
|
|
SecretKey
|
|
|
|
Amount
|
|
|
|
SecretKey
|
|
|
|
WsPayOrderId
|
|
|
|
*/
|
|
|
|
signature := shopId + cartId + secret + stan + secret + approvalCode + secret + strconv.FormatInt(amount, 10) + secret + cartId
|
|
|
|
hash := sha512.New()
|
|
|
|
hash.Write([]byte(signature))
|
|
|
|
return hex.EncodeToString(hash.Sum(nil))
|
|
|
|
}
|
|
|
|
|
2023-07-10 08:10:13 +00:00
|
|
|
func CompareFormReturnSignature(signature string, shopId string, secret string, cartId string, success int, approvalCode string) error {
|
|
|
|
/**
|
|
|
|
Represents a signature created from string formatted from following values in a following order using
|
|
|
|
SHA512 algorithm:
|
|
|
|
ShopID
|
|
|
|
SecretKey
|
|
|
|
ShoppingCartID
|
|
|
|
SecretKey
|
|
|
|
Success
|
|
|
|
SecretKey
|
|
|
|
ApprovalCode
|
|
|
|
SecretKey
|
|
|
|
Merchant should validate this signature to make sure that the request is originating from WSPayForm.
|
|
|
|
*/
|
|
|
|
calculatedSignature := shopId + secret + cartId + secret + strconv.FormatInt(int64(success), 10) + secret + approvalCode + secret
|
|
|
|
hash := sha512.New()
|
|
|
|
hash.Write([]byte(calculatedSignature))
|
|
|
|
if hex.EncodeToString(hash.Sum(nil)) == signature {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return errors.New("signature mismatch")
|
|
|
|
}
|
|
|
|
}
|
2023-07-27 10:09:38 +00:00
|
|
|
|
|
|
|
func CompareCompletionReturnSignature(signature string, shopId string, secret string, stan string, actionSuccess string, approvalCode string, cartId string) error {
|
|
|
|
/**
|
|
|
|
Represents a signature created from string formatted from following values in a following order using
|
|
|
|
SHA512 algorithm:
|
|
|
|
ShopID
|
|
|
|
SecretKey
|
|
|
|
STAN
|
|
|
|
ActionSuccess
|
|
|
|
SecretKey
|
|
|
|
ApprovalCode
|
|
|
|
WsPayOrderId
|
|
|
|
Merchant should validate this signature to make sure that the request is originating from WSPayForm.
|
|
|
|
*/
|
|
|
|
calculatedSignature := shopId + secret + stan + actionSuccess + secret + approvalCode + cartId
|
|
|
|
hash := sha512.New()
|
|
|
|
hash.Write([]byte(calculatedSignature))
|
|
|
|
if hex.EncodeToString(hash.Sum(nil)) == signature {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return errors.New("signature mismatch")
|
|
|
|
}
|
|
|
|
}
|
2023-07-27 20:46:37 +00:00
|
|
|
|
|
|
|
func readResponse[T any](httpResponse *http.Response, response T) error {
|
|
|
|
if httpResponse.StatusCode == http.StatusOK {
|
|
|
|
content, err := io.ReadAll(httpResponse.Body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return json.Unmarshal(content, response)
|
|
|
|
} else {
|
|
|
|
return errors.New("received wrong status, expected 200 received " + strconv.FormatInt(int64(httpResponse.StatusCode), 10))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createRequest(method string, url string, headers map[string]string, content []byte) (*http.Response, error) {
|
|
|
|
request, err := http.NewRequest(method, url, bytes.NewReader(content))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for key, value := range headers {
|
|
|
|
request.Header.Add(key, value)
|
|
|
|
}
|
|
|
|
return http.DefaultClient.Do(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
func toJson[T any](request T) []byte {
|
|
|
|
response, err := json.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return response
|
|
|
|
}
|