payment-poc/domain/providers/wspay/service.go

417 lines
12 KiB
Go
Raw Normal View History

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"
"fmt"
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"
2024-04-01 18:29:24 +00:00
"log/slog"
2023-07-27 20:46:37 +00:00
"net/http"
2024-04-01 18:29:24 +00:00
"payment-poc/domain/database"
"payment-poc/domain/state"
2023-07-10 08:10:13 +00:00
"strconv"
)
type Service struct {
2023-07-27 20:46:37 +00:00
ShopId string
ShopSecret string
BackendUrl string
2023-07-10 08:10:13 +00:00
}
2023-07-31 07:21:54 +00:00
func (s *Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
var request = StatusCheckRequest{
Version: "2.0",
2024-04-01 18:29:24 +00:00
ShopID: s.ShopId,
2023-07-31 07:21:54 +00:00
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),
)
2023-07-10 08:10:13 +00:00
if err != nil {
2023-07-31 07:21:54 +00:00
return database.PaymentEntry{}, err
}
var response StatusCheckResponse
err = readResponse(httpResponse, &response)
if err != nil {
return database.PaymentEntry{}, err
}
2024-04-01 18:29:24 +00:00
if CompareStatusCheckReturnSignature(response.Signature, s.ShopId, s.ShopSecret, response.ActionSuccess, response.ApprovalCode, entry.WsPayOrderId) == nil {
newValue := int64(response.Amount * 100)
entry.Amount = &newValue
2023-07-31 07:21:54 +00:00
newState := determineState(response)
if entry.State != newState && newState != "" {
2024-04-01 18:29:24 +00:00
slog.Info("Updated state", "entry_id", entry.Id.String(), "state", entry.State, "new_state", newState)
2023-07-31 07:21:54 +00:00
entry.State = newState
}
2024-04-01 18:29:24 +00:00
entry.WsPayOrderId = response.WsPayOrderId
2023-07-31 07:21:54 +00:00
} 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
2023-07-10 08:10:13 +00:00
}
2023-07-31 07:21:54 +00:00
}
func (s *Service) CreatePaymentUrl(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
entry, url, err := s.InitializePayment(entry)
if err != nil {
return entry, "", err
}
return entry, url, 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",
2024-04-01 18:29:24 +00:00
WsPayOrderId: entry.WsPayOrderId,
ShopID: s.ShopId,
2023-07-27 20:46:37 +00:00
ApprovalCode: *entry.ApprovalCode,
STAN: *entry.STAN,
2024-04-01 18:29:24 +00:00
Amount: strconv.FormatInt(amount, 10),
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.WsPayOrderId, *entry.STAN, *entry.ApprovalCode, amount),
2023-07-27 20:46:37 +00:00
}
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
}
2024-04-01 18:29:24 +00:00
if CompareCompletionReturnSignature(response.Signature, s.ShopId, s.ShopSecret, *entry.STAN, response.ActionSuccess, response.ApprovalCode, entry.WsPayOrderId) == nil {
2023-07-27 20:46:37 +00:00
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",
2024-04-01 18:29:24 +00:00
WsPayOrderId: entry.WsPayOrderId,
ShopID: s.ShopId,
2023-07-27 20:46:37 +00:00
ApprovalCode: *entry.ApprovalCode,
STAN: *entry.STAN,
2024-04-01 18:29:24 +00:00
Amount: strconv.FormatInt(entry.TotalAmount, 10),
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.WsPayOrderId, *entry.STAN, *entry.ApprovalCode, entry.TotalAmount),
2023-07-27 20:46:37 +00:00
}
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
}
2024-04-01 18:29:24 +00:00
if CompareCompletionReturnSignature(response.Signature, s.ShopId, s.ShopSecret, *entry.STAN, response.ActionSuccess, response.ApprovalCode, entry.WsPayOrderId) == nil {
entry.State = state.StateVoided
2023-07-27 20:46:37 +00:00
} 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
}
func (s *Service) InitializePayment(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
formattedAmount := fmt.Sprintf("%d,%02d", entry.TotalAmount/100, entry.TotalAmount%100)
var request = CreateTransaction{
2023-07-27 20:46:37 +00:00
ShopID: s.ShopId,
ShoppingCardID: entry.Id.String(),
2023-07-27 20:46:37 +00:00
Version: "2.0",
TotalAmount: formattedAmount,
ReturnUrl: s.BackendUrl + "/wspay/success",
ReturnErrorUrl: s.BackendUrl + "/wspay/error",
CancelUrl: s.BackendUrl + "/wspay/cancel",
2023-07-27 20:46:37 +00:00
Signature: CalculateFormSignature(s.ShopId, s.ShopSecret, entry.Id.String(), entry.TotalAmount),
}
httpResponse, err := createRequest(
"POST",
"https://formtest.wspay.biz/api/create-transaction",
map[string]string{"content-type": "application/json"},
toJson(request),
)
if err != nil {
return database.PaymentEntry{}, "", err
}
var response TransactionResponse
err = readResponse(httpResponse, &response)
if err != nil {
return database.PaymentEntry{}, "", err
}
if response.TransactionId == nil {
return database.PaymentEntry{}, "", errors.New("received bad response")
}
entry.State = state.StateInitialized
return entry, *response.PaymentFormUrl, nil
2023-07-10 08:10:13 +00:00
}
2023-07-31 07:21:54 +00:00
func (s *Service) HandleSuccessResponse(c *gin.Context, provider *database.PaymentEntryProvider) (string, error) {
2023-07-27 20:46:37 +00:00
response := FormReturn{}
if err := c.ShouldBind(&response); err != nil {
return "", err
}
2023-07-31 07:21:54 +00:00
entry, err := provider.FetchById(uuid.MustParse(response.ShoppingCartID))
2023-07-27 20:46:37 +00:00
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
2024-04-01 18:29:24 +00:00
entry.WsPayOrderId = response.WsPayOrderId
2023-07-27 20:46:37 +00:00
2023-07-31 07:21:54 +00:00
if _, err := provider.UpdateEntry(entry); err != nil {
2023-07-27 20:46:37 +00:00
return "", err
}
return "/entries/" + entry.Id.String(), nil
2023-07-10 08:10:13 +00:00
}
2023-07-31 07:21:54 +00:00
func (s *Service) HandleErrorResponse(c *gin.Context, provider *database.PaymentEntryProvider, paymentState state.PaymentState) (string, error) {
2023-07-27 20:46:37 +00:00
response := FormError{}
if err := c.ShouldBind(&response); err != nil {
return "", err
}
2023-07-31 07:21:54 +00:00
entry, err := provider.FetchById(uuid.MustParse(response.ShoppingCartID))
2023-07-27 20:46:37 +00:00
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
2023-07-31 07:21:54 +00:00
if _, err := provider.UpdateEntry(entry); err != nil {
2023-07-27 20:46:37 +00:00
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))
}
2024-04-01 18:29:24 +00:00
func CalculateCompletionSignature(shopId string, secret string, wsPayOrderId string, stan string, approvalCode string, amount int64) string {
2023-07-27 10:09:38 +00:00
/**
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
*/
2024-04-01 18:29:24 +00:00
signature := shopId + wsPayOrderId + secret + stan + secret + approvalCode + secret + strconv.FormatInt(amount, 10) + secret + wsPayOrderId
2023-07-27 10:09:38 +00:00
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
2024-04-01 18:29:24 +00:00
func CompareCompletionReturnSignature(signature string, shopId string, secret string, stan string, actionSuccess string, approvalCode string, wsPayOrderId string) error {
2023-07-27 10:09:38 +00:00
/**
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.
*/
2024-04-01 18:29:24 +00:00
calculatedSignature := shopId + secret + stan + actionSuccess + secret + approvalCode + wsPayOrderId
2023-07-27 10:09:38 +00:00
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
2024-04-01 18:29:24 +00:00
func CompareStatusCheckReturnSignature(signature string, shopId string, secret string, actionSuccess string, approvalCode string, wsPayOrderId string) error {
2023-07-31 07:21:54 +00:00
/**
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
*/
2024-04-01 18:29:24 +00:00
calculatedSignature := shopId + secret + actionSuccess + approvalCode + secret + shopId + approvalCode + wsPayOrderId
2023-07-31 07:21:54 +00:00
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))
}
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
}