diff --git a/main.go b/main.go index 92262e2..bf4fdbc 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,10 @@ package main import ( + "bytes" "embed" + "encoding/json" + "errors" "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -10,6 +13,7 @@ import ( "github.com/stripe/stripe-go/v72/checkout/session" "github.com/stripe/stripe-go/v72/paymentintent" "html/template" + "io" "log" "net/http" "payment-poc/migration" @@ -39,6 +43,7 @@ func init() { godotenv.Load() BackendUrl = envMustExist("BACKEND_URL") + WsPayShopId = envMustExist("WSPAY_SHOP_ID") WsPayShopSecret = envMustExist("WSPAY_SHOP_SECRET") @@ -493,6 +498,124 @@ func setupWsPayEndpoints(g *gin.RouterGroup, wspayService wspay.Service) { c.HTML(200, "wspay.gohtml", gin.H{"Action": wspay.AuthorisationForm, "Form": form}) }) + g.POST("complete/:id", func(c *gin.Context) { + id := uuid.MustParse(c.Param("id")) + amount, err := strconv.ParseFloat(c.PostForm("amount"), 64) + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + entry, err := wspayService.FetchById(id) + if err != nil { + c.AbortWithError(http.StatusNotFound, err) + return + } + if int64(amount*100) > entry.TotalAmount || int64(amount*100) < 1 { + c.AbortWithError(http.StatusBadRequest, err) + return + } + if entry.State == state.StateAccepted { + var request = wspay.WsPayCompletionRequest{ + Version: "2.0", + WsPayOrderId: entry.ShoppingCartID, + ShopId: entry.ShopID, + ApprovalCode: entry.ApprovalCode, + STAN: entry.STAN, + Amount: int64(amount * 100), + Signature: wspay.CalculateCompletionSignature(WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, entry.ApprovalCode, int64(amount*100)), + } + + content, _ := json.Marshal(&request) + response, err := http.Post("https://test.wspay.biz/api/services/completion", "application/json", bytes.NewBuffer(content)) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + if response.StatusCode == http.StatusOK { + transactionResponse := wspay.WsPayCompletionResponse{} + content, err := io.ReadAll(response.Body) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + if err := json.Unmarshal(content, &transactionResponse); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } else { + log.Printf("Received transaction response: success=%s, errorMessage=%s, approvalCode=%s", + transactionResponse.ActionSuccess, transactionResponse.ErrorMessage, transactionResponse.ApprovalCode, + ) + if wspay.CompareCompletionReturnSignature(transactionResponse.Signature, WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, transactionResponse.ActionSuccess, transactionResponse.ApprovalCode) != nil { + entry.TotalAmount = int64(amount * 100) + entry.State = state.StateCompleted + wspayService.Update(entry) + } else { + c.AbortWithError(http.StatusInternalServerError, errors.New("received invalid signature")) + return + } + } + } else { + c.AbortWithError(http.StatusInternalServerError, errors.New("received wrong status, expected 200 received "+strconv.FormatInt(int64(response.StatusCode), 10))) + return + } + } + + c.Redirect(http.StatusSeeOther, "/wspay/info/"+id.String()) + }) + g.POST("cancel/:id", func(c *gin.Context) { + id := uuid.MustParse(c.Param("id")) + entry, err := wspayService.FetchById(id) + if err != nil { + c.AbortWithError(http.StatusNotFound, err) + return + } + if entry.State == state.StateAccepted { + var request = wspay.WsPayCompletionRequest{ + Version: "2.0", + WsPayOrderId: entry.ShoppingCartID, + ShopId: entry.ShopID, + ApprovalCode: entry.ApprovalCode, + STAN: entry.STAN, + Amount: entry.TotalAmount, + Signature: wspay.CalculateCompletionSignature(WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, entry.ApprovalCode, entry.TotalAmount), + } + + content, _ := json.Marshal(&request) + response, err := http.Post("https://test.wspay.biz/api/services/void", "application/json", bytes.NewBuffer(content)) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + if response.StatusCode == http.StatusOK { + transactionResponse := wspay.WsPayCompletionResponse{} + content, err := io.ReadAll(response.Body) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + if err := json.Unmarshal(content, &transactionResponse); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } else { + log.Printf("Received transaction response: success=%s, errorMessage=%s, approvalCode=%s", + transactionResponse.ActionSuccess, transactionResponse.ErrorMessage, transactionResponse.ApprovalCode, + ) + if wspay.CompareCompletionReturnSignature(transactionResponse.Signature, WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, transactionResponse.ActionSuccess, transactionResponse.ApprovalCode) != nil { + entry.State = state.StateCanceled + wspayService.Update(entry) + } else { + c.AbortWithError(http.StatusInternalServerError, errors.New("received invalid signature")) + return + } + } + } else { + c.AbortWithError(http.StatusInternalServerError, errors.New("received wrong status, expected 200 received "+strconv.FormatInt(int64(response.StatusCode), 10))) + return + } + } + + c.Redirect(http.StatusSeeOther, "/wspay/info/"+id.String()) + }) g.GET("success", func(c *gin.Context) { response := wspay.WsPayFormReturn{} if err := c.ShouldBind(&response); err != nil { diff --git a/templates/wspay_info.gohtml b/templates/wspay_info.gohtml index c6d3bdb..f20f233 100644 --- a/templates/wspay_info.gohtml +++ b/templates/wspay_info.gohtml @@ -45,5 +45,18 @@ Stanje: {{formatState .Entry.State}} + + {{if eq .Entry.State "accepted"}} +
+
+ + +
+ +
+
+ +
+ {{end}} \ No newline at end of file diff --git a/wspay/service.go b/wspay/service.go index b878804..8fdcbfb 100644 --- a/wspay/service.go +++ b/wspay/service.go @@ -74,6 +74,27 @@ func CalculateFormSignature(shopId string, secret string, cartId string, amount 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 @@ -97,3 +118,26 @@ func CompareFormReturnSignature(signature string, shopId string, secret string, 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") + } +} diff --git a/wspay/wspay.go b/wspay/wspay.go index 1abf3d8..6596c69 100644 --- a/wspay/wspay.go +++ b/wspay/wspay.go @@ -102,7 +102,7 @@ type WsPayCompletionRequest struct { ShopId string ApprovalCode string STAN string - Amount string + Amount int64 Signature string }