Organization + added logs

This commit is contained in:
Borna Rajković committed 2023-07-31 10:01:37 +02:00
1 parent fe6f3b6672
commit 7b2523e2b1
8 files changed
+33 -15

No files matched your search

+386
View File
@@ -0,0 +1,386 @@
package wspay
import (
"bytes"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"errors"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"io"
"log"
"net/http"
"payment-poc/database"
"payment-poc/state"
"strconv"
)
type Service struct {
ShopId string
ShopSecret string
BackendUrl string
}
func (s *Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
var request = StatusCheckRequest{
Version: "2.0",
ShopId: s.ShopId,
ShoppingCartId: entry.Id.String(),
Signature: CalculateStatusCheckSignature(s.ShopId, s.ShopSecret, entry.Id.String()),
}
httpResponse, err := createRequest(
"POST",
"https://test.wspay.biz/api/services/statusCheck",
map[string]string{"content-type": "application/json"},
toJson(request),
)
if err != nil {
return database.PaymentEntry{}, err
}
var response StatusCheckResponse
err = readResponse(httpResponse, &response)
if err != nil {
return database.PaymentEntry{}, err
}
if CompareStatusCheckReturnSignature(response.Signature, s.ShopId, s.ShopSecret, response.ActionSuccess, response.ApprovalCode, entry.Id.String()) != nil {
entry.Amount = &response.Amount
newState := determineState(response)
if entry.State != newState && newState != "" {
log.Printf("Updated state for %s: %s -> %s", entry.Id.String(), entry.State, newState)
entry.State = newState
}
entry.State = state.StateCompleted
} else {
return database.PaymentEntry{}, errors.New("invalid signature")
}
return entry, nil
}
func determineState(response StatusCheckResponse) state.PaymentState {
if response.Completed == "1" {
return state.StateCompleted
} else if response.Voided == "1" {
return state.StateVoided
} else if response.Refunded == "1" {
return state.StateVoided
} else if response.Authorized == "1" {
return state.StateAccepted
} else {
return state.StateInitialized
}
}
func (s *Service) CreatePaymentUrl(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
return entry, "/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, provider *database.PaymentEntryProvider) (string, error) {
response := FormReturn{}
if err := c.ShouldBind(&response); err != nil {
return "", err
}
entry, err := 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 := provider.UpdateEntry(entry); err != nil {
return "", err
}
return "/entries/" + entry.Id.String(), nil
}
func (s *Service) HandleErrorResponse(c *gin.Context, provider *database.PaymentEntryProvider, paymentState state.PaymentState) (string, error) {
response := FormError{}
if err := c.ShouldBind(&response); err != nil {
return "", err
}
entry, err := 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 := 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 CompareStatusCheckReturnSignature(signature string, shopId string, secret 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
ActionSuccess
ApprovalCode
SecretKey
ShopID
ApprovalCode
WsPayOrderId
*/
calculatedSignature := shopId + secret + actionSuccess + approvalCode + secret + shopId + 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 CalculateStatusCheckSignature(shopId string, secret string, cartId string) string {
/**
Represents a signature created from string formatted from following values in a following order using
SHA512 algorithm:
ShopID
SecretKey
ShoppingCartID
SecretKey
ShopID
ShoppingCartID
*/
signature := shopId + secret + cartId + secret + shopId + cartId
hash := sha512.New()
hash.Write([]byte(signature))
return hex.EncodeToString(hash.Sum(nil))
}
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
}
+160
View File
@@ -0,0 +1,160 @@
package wspay
const AuthorisationForm = "https://formtest.wspay.biz/authorization.aspx"
type Form struct {
// required args
ShopID string
ShoppingCartID string
Version string
TotalAmount int64
ReturnURL string
ReturnErrorURL string
CancelURL string
Signature string
// optional args
Lang string
CustomerFirstName string
CustomerLastName string
CustomerAddress string
CustomerCity string
CustomerZIP string
CustomerCountry string
CustomerPhone string
PaymentPlan string
CreditCardName string
PaymentMethod string
IntAmount int64
IntCurrency string
ReturnMethod string
CurrencyCode int
}
type FormReturn struct {
CustomerFirstName string `form:"CustomerFirstname"`
CustomerSurname string `form:"CustomerSurname"`
CustomerAddress string `form:"CustomerAddress"`
CustomerCity string `form:"CustomerCity"`
CustomerZIP string `form:"CustomerZIP"`
CustomerCountry string `form:"CustomerCountry"`
CustomerPhone string `form:"CustomerPhone"`
CustomerEmail string `form:"CustomerEmail"`
ShoppingCartID string `form:"ShoppingCartID"`
Lang string `form:"Lang"`
DateTime string `form:"DateTime"` //yyyymmddHHMMss
Amount string `form:"Amount"` // eg. 123,43
ECI string `form:"ECI"`
STAN string `form:"STAN"`
Partner string `form:"Partner"`
WsPayOrderId string `form:"WsPayOrderId"`
PaymentType string `form:"PaymentType"`
CreditCardNumber string `form:"CreditCardNumber"` // masked number
PaymentPlan string `form:"PaymentPlan"`
ShopPostedPaymentPlan string `form:"ShopPostedPaymentPlan"`
ShopPostedLang string `form:"ShopPostedLang"`
ShopPostedCreditCardName string `form:"ShopPostedCreditCardName"`
Success int `form:"Success"`
ApprovalCode string `form:"ApprovalCode"`
ErrorMessage string `form:"ErrorMessage"`
ShopPostedPaymentMethod string `form:"ShopPostedPaymentMethod"`
Signature string `form:"Signature"`
}
type FormError struct {
CustomerFirstName string
CustomerSurname string
CustomerAddress string
CustomerCity string
CustomerZIP string
CustomerCountry string
CustomerPhone string
CustomerEmail string
ShoppingCartID string
Lang string
DateTime string //yyyymmddHHMMss
Amount string // eg. 123,43
ECI string
PaymentType string
PaymentPlan string
ShopPostedPaymentPlan string
ShopPostedLang string
ShopPostedCreditCardName string
Success int
ApprovalCode string
ErrorMessage string
ShopPostedPaymentMethod string
ErrorCodes string
Signature string
}
type FormCancel struct {
ResponseCode int
ShoppingCartID string
ApprovalCode string
Success int
Signature string
}
type CompletionRequest struct {
Version string
WsPayOrderId string
ShopId string
ApprovalCode string
STAN string
Amount int64
Signature string
}
type CompletionResponse struct {
WsPayOrderId string
ShopId string
ApprovalCode string
STAN string
ErrorMessage string
ActionSuccess string
Signature string
}
type StatusCheckRequest struct {
Version string
ShopId string
ShoppingCartId string
Signature string
}
type StatusCheckResponse struct {
WsPayOrderId string
Signature string
STAN string
ApprovalCode string
ShopID string
ShoppingCartID string
Amount int64
CurrencyCode string
ActionSuccess string
Success string // deprecated
Authorized string
Completed string
Voided string
Refunded string
PaymentPlan string
Partner string
OnSite int
CreditCardName string
CreditCardNumber string
ECI string
CustomerFirstName string
CustomerLastName string
CustomerCity string
CustomerZIP string
CustomerCountry string
CustomerPhone string
CustomerEmail string
TransactionDateTime string // yyyymmddHHMMss
IsLessThan30DaysFromTransaction bool
CanBeCompleted bool
CanBeVoided bool
CanBeRefunded bool
ExpirationDate string
}