Reorganized code + migration to go1.22

This commit is contained in:
brajkovic committed 2024-04-01 20:29:24 +02:00
1 parent e3d77d55b9
commit 7369777098
25 files changed
+825 -605

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
package mock
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"log/slog"
database2 "payment-poc/domain/database"
"payment-poc/domain/state"
)
type Service struct {
BackendUrl string
}
func (s Service) UpdatePayment(entry database2.PaymentEntry) (updatedEntry database2.PaymentEntry, err error) {
return entry, nil
}
func (s Service) CreatePaymentUrl(entry database2.PaymentEntry) (updateEntry database2.PaymentEntry, url string, err error) {
return entry, "/mock/gateway/" + entry.Id.String(), nil
}
func (s Service) CompleteTransaction(entry database2.PaymentEntry, amount int64) (database2.PaymentEntry, error) {
entry.Amount = &amount
entry.State = state.StateCompleted
return entry, nil
}
func (s Service) CancelTransaction(entry database2.PaymentEntry) (database2.PaymentEntry, error) {
entry.State = state.StateVoided
return entry, nil
}
func (s Service) HandleResponse(c *gin.Context, provider *database2.PaymentEntryProvider, paymentState state.PaymentState) (string, error) {
id := uuid.MustParse(c.Query("id"))
entry, err := provider.FetchById(id)
if err != nil {
return "", err
}
entry.State = paymentState
_, err = provider.UpdateEntry(entry)
slog.Info("received authorization response", "entry_id", entry.Id.String(), "state", entry.State)
return "/entries/" + id.String(), err
}
+10
View File
@@ -0,0 +1,10 @@
package providers
import "payment-poc/domain/database"
type PaymentProvider interface {
CreatePaymentUrl(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, url string, err error)
CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error)
CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error)
UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error)
}
+159
View File
@@ -0,0 +1,159 @@
package stripe
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stripe/stripe-go/v72"
"github.com/stripe/stripe-go/v72/checkout/session"
"github.com/stripe/stripe-go/v72/paymentintent"
"log/slog"
database2 "payment-poc/domain/database"
"payment-poc/domain/state"
)
type Service struct {
ApiKey string
BackendUrl string
}
func (s *Service) UpdatePayment(entry database2.PaymentEntry) (updatedEntry database2.PaymentEntry, err error) {
client := paymentintent.Client{
B: stripe.GetBackend(stripe.APIBackend),
Key: s.ApiKey,
}
pi, err := client.Get(*entry.PaymentIntentId, nil)
if err != nil {
return entry, err
}
newState := determineState(pi.Status)
if entry.State != newState && newState != "" {
slog.Info("updated state", "entry_id", entry.Id.String(), "state", entry.State, "new_state", newState)
if pi.AmountReceived > 0 {
entry.Amount = &pi.AmountReceived
}
entry.State = newState
}
return entry, nil
}
func determineState(status stripe.PaymentIntentStatus) state.PaymentState {
switch status {
case stripe.PaymentIntentStatusCanceled:
return state.StateCanceled
case stripe.PaymentIntentStatusProcessing:
return state.StatePending
case stripe.PaymentIntentStatusRequiresAction:
return state.StatePending
case stripe.PaymentIntentStatusRequiresCapture:
return state.StateAccepted
case stripe.PaymentIntentStatusRequiresConfirmation:
return state.StatePending
case stripe.PaymentIntentStatusRequiresPaymentMethod:
return state.StateVoided
case stripe.PaymentIntentStatusSucceeded:
return state.StateCompleted
}
return ""
}
func (s *Service) CreatePaymentUrl(entry database2.PaymentEntry) (database2.PaymentEntry, string, error) {
entry, url, err := s.InitializePayment(entry)
if err != nil {
return entry, "", err
}
return entry, url, nil
}
func (s *Service) InitializePayment(entry database2.PaymentEntry) (database2.PaymentEntry, string, error) {
currency := string(stripe.CurrencyEUR)
productName := "Example product"
productDescription := "Simple example product"
params := &stripe.CheckoutSessionParams{
LineItems: []*stripe.CheckoutSessionLineItemParams{
{
PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
Currency: &currency,
ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
Name: &productName,
Description: &productDescription,
},
UnitAmount: &entry.TotalAmount,
},
Quantity: stripe.Int64(1),
},
},
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
PaymentIntentData: &stripe.CheckoutSessionPaymentIntentDataParams{
CaptureMethod: stripe.String("manual"),
},
SuccessURL: stripe.String(s.BackendUrl + "/stripe/success?token=" + entry.Id.String()),
CancelURL: stripe.String(s.BackendUrl + "/stripe/cancel?token=" + entry.Id.String()),
}
client := session.Client{
B: stripe.GetBackend(stripe.APIBackend),
Key: s.ApiKey,
}
result, err := client.New(params)
if err != nil {
return database2.PaymentEntry{}, "", err
}
entry.State = state.StateInitialized
entry.PaymentIntentId = &result.PaymentIntent.ID
return entry, result.URL, nil
}
func (s *Service) CompleteTransaction(entry database2.PaymentEntry, amount int64) (database2.PaymentEntry, error) {
params := &stripe.PaymentIntentCaptureParams{
AmountToCapture: stripe.Int64(amount),
}
client := paymentintent.Client{
B: stripe.GetBackend(stripe.APIBackend),
Key: s.ApiKey,
}
pi, err := client.Capture(*entry.PaymentIntentId, params)
if err != nil {
return database2.PaymentEntry{}, err
}
slog.Info("received state on completion", "entry_id", entry.Id.String(), "state", entry.State, "new_state", pi.Status)
newState := determineState(pi.Status)
entry.State = newState
if newState == state.StateCompleted || newState == state.StatePending {
entry.Amount = &pi.AmountReceived
}
return entry, nil
}
func (s *Service) CancelTransaction(entry database2.PaymentEntry) (database2.PaymentEntry, error) {
params := &stripe.PaymentIntentCancelParams{}
client := paymentintent.Client{
B: stripe.GetBackend(stripe.APIBackend),
Key: s.ApiKey,
}
pi, err := client.Cancel(*entry.PaymentIntentId, params)
if err != nil {
return database2.PaymentEntry{}, err
}
slog.Info("received state on completion", "entry_id", entry.Id.String(), "state", entry.State, "new_state", pi.Status)
if pi.Status == stripe.PaymentIntentStatusCanceled {
entry.State = state.StateCanceled
}
return entry, nil
}
func (s *Service) HandleResponse(c *gin.Context, provider *database2.PaymentEntryProvider, paymentState state.PaymentState) (string, error) {
id := uuid.MustParse(c.Query("token"))
entry, err := provider.FetchById(id)
if err != nil {
return "", err
}
entry.State = paymentState
if _, err := provider.UpdateEntry(entry); err != nil {
return "", err
}
slog.Info("received authorization response", "entry_id", entry.Id.String(), "state", entry.State)
return "/entries/" + entry.Id.String(), nil
}
+276
View File
@@ -0,0 +1,276 @@
package viva
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"io"
"log/slog"
"net/http"
"net/url"
database2 "payment-poc/domain/database"
"payment-poc/domain/state"
"strconv"
"time"
)
type Service struct {
ClientId string
ClientSecret string
SourceCode string
MerchantId string
ApiKey string
token string
expiration time.Time
}
func (s *Service) UpdatePayment(entry database2.PaymentEntry) (updatedEntry database2.PaymentEntry, err error) {
token, err := s.oAuthToken()
httpResponse, err := createRequest(
"GET",
"https://demo-api.vivapayments.com/checkout/v2/transactions/"+entry.TransactionId.String(),
map[string]string{"authorization": "Bearer " + token, "content-type": "application/json"},
[]byte{},
)
if err != nil {
return database2.PaymentEntry{}, err
}
var response TransactionStatusResponse
err = readResponse(httpResponse, &response)
if err != nil {
return database2.PaymentEntry{}, err
}
newState := determineStatus(response.StatusId)
if entry.State != newState && newState != "" {
slog.Info("updated state", "entry_id", entry.Id.String(), "state", entry.State, "new_state", newState)
entry.State = newState
if entry.State == state.StateCompleted {
amount := int64(response.Amount * 100)
entry.Amount = &amount
}
}
return entry, nil
}
func determineStatus(id TransactionStatus) state.PaymentState {
switch id {
case PaymentPreauthorized:
return state.StateAccepted
case PaymentPending:
return state.StatePending
case PaymentSuccessful:
return state.StateCompleted
case PaymentUnsuccessful:
return state.StateError
case PaymentRefunded:
return state.StateVoided
case PaymentVoided:
return state.StateVoided
}
slog.Info("unknown transaction status", "status", string(id))
return ""
}
func (s *Service) CreatePaymentUrl(entry database2.PaymentEntry) (database2.PaymentEntry, string, error) {
entry, err := s.InitializePayment(entry)
if err != nil {
return entry, "", err
}
return entry, "https://demo.vivapayments.com/web/checkout?ref=" + string(*entry.OrderId), nil
}
func (s *Service) InitializePayment(entry database2.PaymentEntry) (database2.PaymentEntry, error) {
token, err := s.oAuthToken()
if err != nil {
return database2.PaymentEntry{}, err
}
request := OrderRequest{
Amount: entry.TotalAmount,
Description: "Example payment",
MerchantDescription: "Example payment",
PreAuth: true,
AllowRecurring: false,
Source: s.SourceCode,
}
httpResponse, err := createRequest(
"POST",
"https://demo-api.vivapayments.com/checkout/v2/orders",
map[string]string{"authorization": "Bearer " + token, "content-type": "application/json"},
toJson(request),
)
if err != nil {
return database2.PaymentEntry{}, err
}
var response OrderResponse
err = readResponse(httpResponse, &response)
if err != nil {
return database2.PaymentEntry{}, err
}
entry.State = state.StateInitialized
entry.OrderId = &response.OrderId
return entry, nil
}
func (s *Service) CompleteTransaction(entry database2.PaymentEntry, amount int64) (database2.PaymentEntry, error) {
completionRequest := TransactionCompleteRequest{
Amount: amount,
CustomerDescription: "Example transaction",
}
httpResponse, err := createRequest(
"POST",
"https://demo.vivapayments.com/api/transactions/"+entry.TransactionId.String(),
map[string]string{"authorization": "Basic " + s.basicAuth(),
"content-type": "application/json",
},
toJson(completionRequest),
)
if err != nil {
return database2.PaymentEntry{}, err
}
var response TransactionResponse
err = readResponse(httpResponse, &response)
if err != nil {
return database2.PaymentEntry{}, err
}
if response.StatusId == "F" {
paidAmount := response.Amount * 100
entry.Amount = &paidAmount
entry.State = state.StateCompleted
} else {
return database2.PaymentEntry{}, errors.New("received invalid status = " + response.StatusId)
}
return entry, nil
}
func (s *Service) CancelTransaction(entry database2.PaymentEntry) (database2.PaymentEntry, error) {
amount := strconv.FormatInt(entry.TotalAmount, 10)
httpResponse, err := createRequest(
"DELETE",
"https://demo.vivapayments.com/api/transactions/"+entry.TransactionId.String()+"?amount="+amount,
map[string]string{"authorization": "Basic " + s.basicAuth(),
"content-type": "application/json",
},
nil,
)
if err != nil {
return database2.PaymentEntry{}, err
}
var response TransactionResponse
err = readResponse(httpResponse, &response)
if err != nil {
return database2.PaymentEntry{}, err
}
if response.StatusId == "F" {
paidAmount := int64(0)
entry.Amount = &paidAmount
entry.State = state.StateVoided
} else {
return database2.PaymentEntry{}, errors.New("received invalid status = " + response.StatusId)
}
return entry, nil
}
func (s *Service) oAuthToken() (string, error) {
if s.token != "" && s.expiration.After(time.Now()) {
return s.token, nil
}
return s.fetchOAuthToken()
}
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
}
func (s *Service) fetchOAuthToken() (string, error) {
form := url.Values{
"grant_type": []string{"client_credentials"},
}
httpResponse, err := createRequest(
"POST",
"https://demo-accounts.vivapayments.com/connect/token",
map[string]string{"content-type": "application/x-www-form-urlencoded", "authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte(s.ClientId+":"+s.ClientSecret))},
[]byte(form.Encode()),
)
var response OAuthResponse
err = readResponse(httpResponse, &response)
if err != nil {
return "", err
}
s.token = response.AccessToken
s.expiration = time.Now().Add(time.Duration(response.ExpiresIn) * time.Second)
return s.token, nil
}
func (s *Service) basicAuth() string {
return base64.StdEncoding.EncodeToString([]byte(s.MerchantId + ":" + s.ApiKey))
}
func (s *Service) HandleResponse(c *gin.Context, provider *database2.PaymentEntryProvider, state state.PaymentState) (string, error) {
transactionId := uuid.MustParse(c.Query("t"))
orderId := database2.OrderId(c.Query("s"))
lang := c.Query("lang")
eventId := c.Query("eventId")
eci := c.Query("eci")
slog.Info("received error response from viva payment", "order_id", orderId)
entry, err := provider.FetchByOrderId(orderId)
if err != nil {
slog.Error("couldn't find payment info for viva payment", "order_id", orderId)
return "", err
}
entry.State = state
entry.ECI = &eci
entry.Lang = &lang
entry.EventId = &eventId
entry.TransactionId = &transactionId
if _, err := provider.UpdateEntry(entry); err != nil {
return "", err
}
slog.Info("received authorization response", "entry_id", entry.Id.String(), "state", entry.State)
return "/entries/" + entry.Id.String(), nil
}
+57
View File
@@ -0,0 +1,57 @@
package viva
import (
"payment-poc/domain/database"
)
type OrderRequest struct {
Amount int64 `json:"amount"`
Description string `json:"customerTrns"`
MerchantDescription string `json:"merchantTrns"`
PreAuth bool `json:"preauth"`
AllowRecurring bool `json:"allowRecurring"`
Source string `json:"sourceCode"`
}
type OrderResponse struct {
OrderId database.OrderId `json:"orderCode"`
}
type OAuthResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
type TransactionCompleteRequest struct {
Amount int64 `json:"amount"`
CustomerDescription string `json:"customerTrns"`
}
type TransactionResponse struct {
Amount int64 `json:"Amount"`
StatusId string `json:"StatusId"`
ErrorCode int64 `json:"ErrorCode"`
ErrorText string `json:"ErrorText"`
EventId int64 `json:"EventId"`
Success bool `json:"Success"`
}
type TransactionStatus string
const (
PaymentSuccessful TransactionStatus = "C"
PaymentPending TransactionStatus = "A"
PaymentPreauthorized TransactionStatus = "F"
PaymentUnsuccessful TransactionStatus = "E"
PaymentRefunded TransactionStatus = "R"
PaymentVoided TransactionStatus = "X"
)
type TransactionStatusResponse struct {
Email string `json:"email"`
Amount float64 `json:"amount"`
OrderCode database.OrderId `json:"orderCode"`
StatusId TransactionStatus `json:"statusId"`
FullName string `json:"fullName"`
CardNumber string `json:"cardNumber"`
}
+416
View File
@@ -0,0 +1,416 @@
package wspay
import (
"bytes"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"io"
"log/slog"
"net/http"
"payment-poc/domain/database"
"payment-poc/domain/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.WsPayOrderId) == nil {
newValue := int64(response.Amount * 100)
entry.Amount = &newValue
newState := determineState(response)
if entry.State != newState && newState != "" {
slog.Info("Updated state", "entry_id", entry.Id.String(), "state", entry.State, "new_state", newState)
entry.State = newState
}
entry.WsPayOrderId = response.WsPayOrderId
} 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) {
entry, url, err := s.InitializePayment(entry)
if err != nil {
return entry, "", err
}
return entry, url, 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.WsPayOrderId,
ShopID: s.ShopId,
ApprovalCode: *entry.ApprovalCode,
STAN: *entry.STAN,
Amount: strconv.FormatInt(amount, 10),
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.WsPayOrderId, *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.STAN, response.ActionSuccess, response.ApprovalCode, entry.WsPayOrderId) == 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.WsPayOrderId,
ShopID: s.ShopId,
ApprovalCode: *entry.ApprovalCode,
STAN: *entry.STAN,
Amount: strconv.FormatInt(entry.TotalAmount, 10),
Signature: CalculateCompletionSignature(s.ShopId, s.ShopSecret, entry.WsPayOrderId, *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.STAN, response.ActionSuccess, response.ApprovalCode, entry.WsPayOrderId) == nil {
entry.State = state.StateVoided
} 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) (database.PaymentEntry, string, error) {
formattedAmount := fmt.Sprintf("%d,%02d", entry.TotalAmount/100, entry.TotalAmount%100)
var request = CreateTransaction{
ShopID: s.ShopId,
ShoppingCardID: entry.Id.String(),
Version: "2.0",
TotalAmount: formattedAmount,
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),
}
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
}
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
entry.WsPayOrderId = response.WsPayOrderId
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, wsPayOrderId 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 + wsPayOrderId + secret + stan + secret + approvalCode + secret + strconv.FormatInt(amount, 10) + secret + wsPayOrderId
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, wsPayOrderId 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 + wsPayOrderId
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, wsPayOrderId 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 + wsPayOrderId
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
}
+145
View File
@@ -0,0 +1,145 @@
package wspay
type CreateTransaction struct {
ShopID string `json:"ShopID"`
ShoppingCardID string `json:"ShoppingCartID"`
Version string `json:"Version"`
TotalAmount string `json:"TotalAmount"`
ReturnUrl string `json:"ReturnURL"`
ReturnErrorUrl string `json:"ReturnErrorURL"`
CancelUrl string `json:"CancelURL"`
Signature string `json:"Signature"`
}
type TransactionResponse struct {
TransactionId *string `json:"TransactionId"`
PaymentFormUrl *string `json:"PaymentFormUrl"`
}
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 string
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 float64
CurrencyCode int
ActionSuccess string
Success string // deprecated
Authorized string
Completed string
Voided string
Refunded string
PaymentPlan string
Partner string
OnSite string
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
}