Organization + added logs
This commit is contained in:
1 parent
fe6f3b6672
commit
7b2523e2b1
8 files changed
+33
-15
No files matched your search
@@ -0,0 +1,44 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"payment-poc/database"
|
||||
"payment-poc/state"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
BackendUrl string
|
||||
}
|
||||
|
||||
func (s Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s Service) CreatePaymentUrl(entry database.PaymentEntry) (updateEntry database.PaymentEntry, url string, err error) {
|
||||
return entry, "/mock/gateway/" + entry.Id.String(), nil
|
||||
}
|
||||
|
||||
func (s Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error) {
|
||||
entry.Amount = &amount
|
||||
entry.State = state.StateCompleted
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s Service) CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
entry.State = state.StateVoided
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s Service) HandleResponse(c *gin.Context, provider *database.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)
|
||||
log.Printf("[%s:%s] received authorization response", entry.Id.String(), entry.State)
|
||||
return "/entries/" + id.String(), err
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
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"
|
||||
"payment-poc/database"
|
||||
"payment-poc/state"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
ApiKey string
|
||||
BackendUrl string
|
||||
}
|
||||
|
||||
func (s *Service) UpdatePayment(entry database.PaymentEntry) (updatedEntry database.PaymentEntry, err error) {
|
||||
pi, err := paymentintent.Get(*entry.PaymentIntentId, nil)
|
||||
if err != nil {
|
||||
return entry, err
|
||||
}
|
||||
newState := determineState(pi.Status)
|
||||
|
||||
if entry.State != newState && newState != "" {
|
||||
log.Printf("[%s] updated state for %s -> %s", entry.Id.String(), entry.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 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) InitializePayment(entry database.PaymentEntry) (database.PaymentEntry, string, error) {
|
||||
|
||||
currency := string(stripe.CurrencyEUR)
|
||||
productName := "Example product"
|
||||
productDescription := "Simple example product"
|
||||
|
||||
params := &stripe.CheckoutSessionParams{
|
||||
LineItems: []*stripe.CheckoutSessionLineItemParams{
|
||||
{
|
||||
PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
|
||||
Currency: ¤cy,
|
||||
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()),
|
||||
}
|
||||
result, err := session.New(params)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, "", err
|
||||
}
|
||||
entry.State = state.StateInitialized
|
||||
entry.PaymentIntentId = &result.PaymentIntent.ID
|
||||
|
||||
return entry, result.URL, nil
|
||||
}
|
||||
|
||||
func (s *Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.PaymentEntry, error) {
|
||||
params := &stripe.PaymentIntentCaptureParams{
|
||||
AmountToCapture: stripe.Int64(amount),
|
||||
}
|
||||
pi, err := paymentintent.Capture(*entry.PaymentIntentId, params)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
log.Printf("received state on completion: %v", 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 database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
params := &stripe.PaymentIntentCancelParams{}
|
||||
pi, err := paymentintent.Cancel(*entry.PaymentIntentId, params)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
log.Printf("received state on completion: %v", pi.Status)
|
||||
if pi.Status == stripe.PaymentIntentStatusCanceled {
|
||||
entry.State = state.StateCanceled
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) HandleResponse(c *gin.Context, provider *database.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
|
||||
}
|
||||
log.Printf("[%s:%s] received authorization response", entry.Id.String(), entry.State)
|
||||
return "/entries/" + entry.Id.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package viva
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"payment-poc/database"
|
||||
"payment-poc/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 database.PaymentEntry) (updatedEntry database.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 database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response TransactionStatusResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
newState := determineStatus(response.StatusId)
|
||||
|
||||
if entry.State != newState && newState != "" {
|
||||
log.Printf("[%s:%s] updated state %s -> %s", entry.Id.String(), entry.State, entry.State, newState)
|
||||
entry.State = newState
|
||||
}
|
||||
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
|
||||
}
|
||||
log.Printf("Unknonw transactionStatus: %s", string(id))
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Service) CreatePaymentUrl(entry database.PaymentEntry) (database.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 database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
token, err := s.oAuthToken()
|
||||
if err != nil {
|
||||
return database.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 database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response OrderResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
entry.State = state.StateInitialized
|
||||
entry.OrderId = &response.OrderId
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) CompleteTransaction(entry database.PaymentEntry, amount int64) (database.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": "Bearer " + s.basicAuth(),
|
||||
"content-type": "application/json",
|
||||
},
|
||||
toJson(completionRequest),
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response TransactionResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
if response.StatusId == "F" {
|
||||
paidAmount := int64(response.Amount * 100)
|
||||
entry.Amount = &paidAmount
|
||||
entry.State = state.StateCompleted
|
||||
} else {
|
||||
return database.PaymentEntry{}, errors.New("received invalid status = " + response.StatusId)
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Service) CancelTransaction(entry database.PaymentEntry) (database.PaymentEntry, error) {
|
||||
httpResponse, err := createRequest(
|
||||
"DELETE",
|
||||
"https://demo.vivapayments.com/api/transactions/"+entry.TransactionId.String(),
|
||||
map[string]string{"authorization": "Bearer " + s.basicAuth(),
|
||||
"content-type": "application/json",
|
||||
},
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
|
||||
var response TransactionResponse
|
||||
err = readResponse(httpResponse, &response)
|
||||
if err != nil {
|
||||
return database.PaymentEntry{}, err
|
||||
}
|
||||
if response.StatusId == "F" {
|
||||
paidAmount := int64(0)
|
||||
entry.Amount = &paidAmount
|
||||
entry.State = state.StateVoided
|
||||
} else {
|
||||
return database.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 *database.PaymentEntryProvider, state state.PaymentState) (string, error) {
|
||||
transactionId := uuid.MustParse(c.Query("t"))
|
||||
orderId := database.OrderId(c.Query("s"))
|
||||
lang := c.Query("lang")
|
||||
eventId := c.Query("eventId")
|
||||
eci := c.Query("eci")
|
||||
|
||||
log.Printf("[%s] received error response for viva payment", orderId)
|
||||
entry, err := provider.FetchByOrderId(orderId)
|
||||
if err != nil {
|
||||
log.Printf("[%s] couldn't find payment info for viva payment", 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
|
||||
}
|
||||
|
||||
log.Printf("[%s:%s] received authorization response", entry.Id.String(), entry.State)
|
||||
|
||||
return "/entries/" + entry.Id.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package viva
|
||||
|
||||
import "payment-poc/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 float64 `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 = "F"
|
||||
PaymentPending TransactionStatus = "A"
|
||||
PaymentPreauthorized TransactionStatus = "C"
|
||||
PaymentUnsuccessful TransactionStatus = "E"
|
||||
PaymentRefunded TransactionStatus = "R"
|
||||
PaymentVoided TransactionStatus = "X"
|
||||
)
|
||||
|
||||
type TransactionStatusResponse struct {
|
||||
Email string `json:"email"`
|
||||
Amount int `json:"amount"`
|
||||
OrderCode database.OrderId `json:"orderCode"`
|
||||
StatusId TransactionStatus `json:"statusId"`
|
||||
FullName string `json:"fullName"`
|
||||
CardNumber string `json:"cardNumber"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user