package wspay import ( "bytes" "crypto/sha512" "encoding/hex" "encoding/json" "errors" "github.com/gin-gonic/gin" "github.com/google/uuid" "io" "net/http" "payment-poc/database" "payment-poc/state" "strconv" ) type Service struct { Provider *database.PaymentEntryProvider ShopId string ShopSecret string BackendUrl string } func (s *Service) CreatePaymentUrl(amount int64) (string, error) { entry, err := s.Provider.CreateEntry(database.PaymentEntry{ Gateway: state.GatewayWsPay, State: state.StateInitialized, TotalAmount: amount, }) if err != nil { return "", err } return "/wspay/initialize/" + entry.Id.String(), nil } 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") } } 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 } 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 } 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 } 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)) } 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)) } 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") } } 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") } } 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 }