From 5dbf7678638348c1b0d635c4a824df21d2a98207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borna=20Rajkovi=C4=87?= Date: Mon, 10 Jul 2023 10:10:13 +0200 Subject: [PATCH] Implemented gateway interactions --- .gitignore | 5 +- README.md | 6 +- db/dev/v1_0.sql | 39 ++++++ docker-compose-deploy.yml | 14 +- docker-compose.yml | 6 +- dockerfile | 8 +- go.mod | 2 +- main.go | 226 +++++++++++++++++++++++++++++++- makefile | 18 +-- templates/iframe_handler.gohtml | 35 +++++ templates/index.gohtml | 57 ++++++++ templates/info.gohtml | 49 +++++++ templates/initial.gohtml | 61 +++++++++ wspay/model.go | 57 ++++++++ wspay/service.go | 98 ++++++++++++++ wspay/wspay.go | 160 ++++++++++++++++++++++ 16 files changed, 811 insertions(+), 30 deletions(-) create mode 100644 templates/iframe_handler.gohtml create mode 100644 templates/index.gohtml create mode 100644 templates/info.gohtml create mode 100644 templates/initial.gohtml create mode 100644 wspay/model.go create mode 100644 wspay/service.go create mode 100644 wspay/wspay.go diff --git a/.gitignore b/.gitignore index 1110d50..efd3596 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea/** -template -.env \ No newline at end of file +payment-poc +.env +.env.docker \ No newline at end of file diff --git a/README.md b/README.md index f8c9352..2bcb85c 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ below, or create .env file and copy variables below ``` PSQL_HOST=localhost PSQL_PORT=5432 -PSQL_USER=template -PSQL_PASSWORD=templatePassword -PSQL_DB=template +PSQL_USER=payment-poc +PSQL_PASSWORD=paymentPassword +PSQL_DB=payment-poc ``` Also, database is required for template to start, so you can start it with `docker compose up -d` \ No newline at end of file diff --git a/db/dev/v1_0.sql b/db/dev/v1_0.sql index e69de29..7121dad 100644 --- a/db/dev/v1_0.sql +++ b/db/dev/v1_0.sql @@ -0,0 +1,39 @@ + +CREATE TABLE IF NOT EXISTS "wspay" +( + "id" uuid NOT NULL, + "shop_id" varchar(128) NOT NULL, + "shopping_card_id" varchar(128) NOT NULL, + "total_amount" int NOT NULL, + + "lang" varchar(128) DEFAULT '', + + "customer_first_name" varchar(128) DEFAULT '', + "customer_last_name" varchar(128) DEFAULT '', + "customer_address" varchar(128) DEFAULT '', + "customer_city" varchar(128) DEFAULT '', + "customer_zip" varchar(128) DEFAULT '', + "customer_country" varchar(128) DEFAULT '', + "customer_phone" varchar(128) DEFAULT '', + + "payment_plan" varchar(128) DEFAULT '', + "credit_card_name" varchar(128) DEFAULT '', + "credit_card_number" varchar(128) DEFAULT '', + "payment_method" varchar(128) DEFAULT '', + "currency_code" int DEFAULT 0, + + "date_time" timestamp DEFAULT current_timestamp, + + "eci" varchar(256) DEFAULT '', + "stan" varchar(256) DEFAULT '', + + "success" int DEFAULT 0, + "approval_code" varchar(256) DEFAULT '', + "error_message" varchar(256) DEFAULT '', + "error_codes" varchar(256) DEFAULT '', + + "payment_state" varchar(256) DEFAULT '', + + PRIMARY KEY (id), + CONSTRAINT unique_id UNIQUE ("shopping_card_id") +); diff --git a/docker-compose-deploy.yml b/docker-compose-deploy.yml index bdaa228..e2066cf 100644 --- a/docker-compose-deploy.yml +++ b/docker-compose-deploy.yml @@ -2,14 +2,14 @@ version: '3.1' services: backend: - image: registry.bbr-dev.info/template/backend:latest + image: registry.bbr-dev.info/payment-poc/backend:latest restart: on-failure depends_on: - database ports: - "5281:5281" networks: - - template + - payment-poc env_file: - .env.docker @@ -18,11 +18,11 @@ services: ports: - "5432:5432" environment: - - POSTGRES_USER=template - - POSTGRES_PASSWORD=templatePassword - - POSTGRES_DB=template + - POSTGRES_USER=payment-poc + - POSTGRES_PASSWORD=paymentPassword + - POSTGRES_DB=payment-poc networks: - - template + - payment-poc networks: - template: \ No newline at end of file + payment-poc: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index cfa85ef..d56291d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,6 @@ services: ports: - "5432:5432" environment: - - POSTGRES_USER=template - - POSTGRES_PASSWORD=templatePassword - - POSTGRES_DB=template \ No newline at end of file + - POSTGRES_USER=payment-poc + - POSTGRES_PASSWORD=paymentPassword + - POSTGRES_DB=payment-poc \ No newline at end of file diff --git a/dockerfile b/dockerfile index b3cc11b..ea3b9de 100644 --- a/dockerfile +++ b/dockerfile @@ -10,10 +10,12 @@ COPY go.mod go.sum ./ RUN go mod download COPY . . ENV CGO_ENABLED=0 -RUN go build -tags timetzdata template +RUN go build -tags timetzdata payment-poc ### Stage 2: Run ### FROM scratch +WORKDIR / COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -COPY --from=go-build /root/template /usr/bin/template -ENTRYPOINT ["template"] +COPY --from=go-build /root/payment-poc /usr/bin/payment-poc +ADD templates /templates +ENTRYPOINT ["payment-poc"] diff --git a/go.mod b/go.mod index 3471004..c866999 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module template +module payment-poc go 1.19 diff --git a/main.go b/main.go index 1aef89e..ed9bb34 100644 --- a/main.go +++ b/main.go @@ -2,19 +2,34 @@ package main import ( "embed" + "fmt" "github.com/gin-gonic/gin" + "github.com/google/uuid" "github.com/joho/godotenv" + "html/template" "log" "net/http" - "template/migration" + "payment-poc/migration" + "payment-poc/wspay" + "strconv" + "strings" "time" ) //go:embed db/dev/*.sql var devMigrations embed.FS +var BackendUrl string +var ShopId string +var ShopSecret string + func init() { godotenv.Load() + + BackendUrl = envMustExist("BACKEND_URL") + ShopId = envMustExist("SHOP_ID") + ShopSecret = envMustExist("SHOP_SECRET") + log.SetPrefix("") log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) } @@ -29,6 +44,36 @@ func main() { } g := gin.Default() + g.Use(gin.BasicAuth(getAccounts())) + + g.SetFuncMap(template.FuncMap{ + "formatCurrency": func(current int64) string { + return fmt.Sprintf("%d,%02d", current/100, current%100) + }, + "formatState": func(state wspay.PaymentState) string { + switch state { + case wspay.StateCanceled: + return "Otkazano" + case wspay.StateAccepted: + return "Prihvačeno" + case wspay.StateError: + return "Greška" + case wspay.StateInitialized: + return "Inicijalna izrada" + case wspay.StateCanceledInitialization: + return "Otkazano tijekom izrade" + case wspay.StateCompleted: + return "Završeno" + } + return "nepoznato stanje '" + string(state) + "'" + }, + "omitempty": func(value string) string { + if value == "" { + return "-" + } + return value + }, + }) g.NoRoute(func(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"message": "no action on given url", "created": time.Now()}) @@ -36,9 +81,186 @@ func main() { g.NoMethod(func(c *gin.Context) { c.JSON(http.StatusMethodNotAllowed, gin.H{"message": "no action on given method", "created": time.Now()}) }) + + wspayService := wspay.Service{ + DB: client, + } + + g.LoadHTMLGlob("./templates/*.gohtml") + g.GET("/", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "hello world", "created": time.Now()}) + entries, err := wspayService.FetchAll() + log.Printf("%v", err) + c.HTML(200, "index.gohtml", gin.H{"Entries": entries}) + }) + g.GET("/initial", func(c *gin.Context) { + amount, err := strconv.ParseFloat(c.Query("amount"), 64) + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + entry, err := wspayService.CreateEntry(ShopId, int64(amount*100)) + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + form := wspay.WsPayForm{ + ShopID: ShopId, + ShoppingCartID: entry.ShoppingCartID, + Version: "2.0", + TotalAmount: entry.TotalAmount, + ReturnURL: BackendUrl + "/initial/success", + ReturnErrorURL: BackendUrl + "/initial/error", + CancelURL: BackendUrl + "/initial/cancel", + Signature: wspay.CalculateFormSignature(ShopId, ShopSecret, entry.ShoppingCartID, entry.TotalAmount), + } + + c.HTML(200, "initial.gohtml", gin.H{"Action": wspay.AuthorisationForm, "Form": form}) + }) + g.GET("/initial/success", func(c *gin.Context) { + response := wspay.WsPayFormReturn{} + if err := c.ShouldBind(&response); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + if err := wspay.CompareFormReturnSignature(response.Signature, ShopId, ShopSecret, response.ShoppingCartID, response.Success, response.ApprovalCode); err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + entry.Lang = response.Lang + entry.CustomerFirstName = response.CustomerFirstName + entry.CustomerLastName = response.CustomerSurname + entry.CustomerAddress = response.CustomerAddress + entry.CustomerCity = response.CustomerCity + entry.CustomerZIP = response.CustomerZIP + entry.CustomerCountry = response.CustomerCountry + entry.CustomerPhone = response.CustomerPhone + + entry.PaymentPlan = response.PaymentPlan + entry.CreditCardNumber = response.CreditCardNumber + entry.DateTime = parseDateTime(response.DateTime) + + entry.ECI = response.ECI + entry.STAN = response.STAN + + entry.Success = response.Success + entry.ApprovalCode = response.ApprovalCode + entry.ErrorMessage = response.ErrorMessage + + entry.State = wspay.StateAccepted + + if err := wspayService.Update(entry); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if c.Query("iframe") != "" { + c.HTML(200, "iframe_handler.gohtml", gin.H{}) + } else { + c.Redirect(http.StatusTemporaryRedirect, "/") + } + }) + g.GET("/iframe", func(c *gin.Context) { + c.HTML(200, "iframe_handler.gohtml", gin.H{}) + }) + g.GET("/initial/error", func(c *gin.Context) { + response := wspay.WsPayFormError{} + if err := c.ShouldBind(&response); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + entry.Lang = response.Lang + entry.CustomerFirstName = response.CustomerFirstName + entry.CustomerLastName = response.CustomerSurname + entry.CustomerAddress = response.CustomerAddress + entry.CustomerCity = response.CustomerCity + entry.CustomerZIP = response.CustomerZIP + entry.CustomerCountry = response.CustomerCountry + entry.CustomerPhone = response.CustomerPhone + + entry.PaymentPlan = response.PaymentPlan + entry.DateTime = parseDateTime(response.DateTime) + + entry.ECI = response.ECI + + entry.Success = response.Success + entry.ApprovalCode = response.ApprovalCode + entry.ErrorMessage = response.ErrorMessage + entry.ErrorCodes = response.ErrorCodes + + entry.State = wspay.StateError + + if err := wspayService.Update(entry); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if c.Query("iframe") != "" { + c.HTML(200, "iframe_handler.gohtml", gin.H{}) + } else { + c.Redirect(http.StatusTemporaryRedirect, "/") + } + }) + g.GET("info/: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 + } + c.HTML(200, "info.gohtml", gin.H{"Entry": entry}) + }) + g.GET("/initial/cancel", func(c *gin.Context) { + response := wspay.WsPayFormCancel{} + if err := c.ShouldBind(&response); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + entry.State = wspay.StateCanceledInitialization + + if err := wspayService.Update(entry); err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if c.Query("iframe") != "" { + c.HTML(200, "iframe_handler.gohtml", gin.H{}) + } else { + c.Redirect(http.StatusTemporaryRedirect, "/") + } }) log.Fatal(http.ListenAndServe(":5281", g)) } + +func getAccounts() gin.Accounts { + auth := strings.Split(envMustExist("AUTH"), ":") + return gin.Accounts{auth[0]: auth[1]} +} + +func parseDateTime(dateTime string) time.Time { + t, err := time.Parse("20060102150405", dateTime) + if err != nil { + log.Printf("couldn't parse response time %s: %v", dateTime, err) + } + return t +} diff --git a/makefile b/makefile index 02434d2..8a790dc 100644 --- a/makefile +++ b/makefile @@ -10,17 +10,17 @@ setup: go get docker-dev: - docker image build -t registry.bbr-dev.info/template/backend:$(VERSION)-dev . - docker tag registry.bbr-dev.info/template/backend:$(VERSION)-dev registry.bbr-dev.info/template/backend:latest-dev - docker image push registry.bbr-dev.info/template/backend:$(VERSION)-dev - docker image push registry.bbr-dev.info/template/backend:latest-dev + docker image build -t registry.bbr-dev.info/payment-poc/backend:$(VERSION)-dev . + docker tag registry.bbr-dev.info/payment-poc/backend:$(VERSION)-dev registry.bbr-dev.info/payment-poc/backend:latest-dev + docker image push registry.bbr-dev.info/payment-poc/backend:$(VERSION)-dev + docker image push registry.bbr-dev.info/payment-poc/backend:latest-dev docker-prod: - docker image build -t registry.bbr-dev.info/template/backend:$(VERSION) . - docker tag registry.bbr-dev.info/template/backend:$(VERSION) registry.bbr-dev.info/template/backend:latest - docker image push registry.bbr-dev.info/template/backend:$(VERSION) - docker image push registry.bbr-dev.info/template/backend:latest + docker image build -t registry.bbr-dev.info/payment-poc/backend:$(VERSION) . + docker tag registry.bbr-dev.info/payment-poc/backend:$(VERSION) registry.bbr-dev.info/payment-poc/backend:latest + docker image push registry.bbr-dev.info/payment-poc/backend:$(VERSION) + docker image push registry.bbr-dev.info/payment-poc/backend:latest release: git tag $(VERSION) @@ -30,4 +30,4 @@ test: go test ./... clean: - rm -rf template + rm -rf payment-poc diff --git a/templates/iframe_handler.gohtml b/templates/iframe_handler.gohtml new file mode 100644 index 0000000..b8697dd --- /dev/null +++ b/templates/iframe_handler.gohtml @@ -0,0 +1,35 @@ + + + + + + + Obrada odgovora + + + + + + +

Obrada odgovora...

+
+ + + + + \ No newline at end of file diff --git a/templates/index.gohtml b/templates/index.gohtml new file mode 100644 index 0000000..c99faa9 --- /dev/null +++ b/templates/index.gohtml @@ -0,0 +1,57 @@ + + + + + + + Index + + + + + +

Novo plačanje

+ +
+
+ + +
+ +
+ +
+

Plačanja

+ + + + + + + + + + {{range .Entries}} + + + + + + {{end}} + +
IdVrijednostStanje
{{.Id}}{{formatCurrency .TotalAmount}}{{formatState .State}}
+
+ + \ No newline at end of file diff --git a/templates/info.gohtml b/templates/info.gohtml new file mode 100644 index 0000000..c6d3bdb --- /dev/null +++ b/templates/info.gohtml @@ -0,0 +1,49 @@ + + + + + + + Info + + + + + +

Plačanje {{.Entry.Id}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
CartId: {{.Entry.ShoppingCartID}}
Ukupna vrijednost: {{formatCurrency .Entry.TotalAmount}}
Jezik: {{omitempty .Entry.Lang}}
Ime: {{omitempty .Entry.CustomerFirstName}}
Prezime: {{omitempty .Entry.CustomerLastName}}
Adresa: {{omitempty .Entry.CustomerAddress}}
Grad: {{omitempty .Entry.CustomerCity}}
ZIP: {{omitempty .Entry.CustomerZIP}}
Zemlja: {{omitempty .Entry.CustomerCountry}}
Broj telefona: {{omitempty .Entry.CustomerPhone}}
Plan plačanja: {{omitempty .Entry.PaymentPlan}}
Ime kartice: {{omitempty .Entry.CreditCardName}}
Broj kartice: {{omitempty .Entry.CreditCardNumber}}
Metoda plačanja: {{omitempty .Entry.PaymentMethod}}
Oznaka valute: {{.Entry.CurrencyCode}}
Datum i vrijeme: {{.Entry.DateTime.Format "Jan 02, 2006 15:04:05 UTC"}}
Uspjeh: {{.Entry.Success}}
Kod: {{omitempty .Entry.ApprovalCode}}
Poruka greške: {{omitempty .Entry.ErrorMessage}}
Kodovi greške: {{omitempty .Entry.ErrorCodes}}
Stanje: {{formatState .Entry.State}}
+ + \ No newline at end of file diff --git a/templates/initial.gohtml b/templates/initial.gohtml new file mode 100644 index 0000000..5aa865c --- /dev/null +++ b/templates/initial.gohtml @@ -0,0 +1,61 @@ + + + + + + + Izradi plančanje + + + + + +

Započni proces plačanja

+ +
+ + + + + + + + + +
+ +

Započni normalni proces u iframe-u

+ +
+ + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/wspay/model.go b/wspay/model.go new file mode 100644 index 0000000..dee5742 --- /dev/null +++ b/wspay/model.go @@ -0,0 +1,57 @@ +package wspay + +import ( + "github.com/google/uuid" + "time" +) + +type PaymentState string + +const ( + // initial state + StateInitialized PaymentState = "initialized" + + // state on response + StateAccepted PaymentState = "accepted" + StateError PaymentState = "error" + StateCanceledInitialization PaymentState = "canceled_initialization" + + // state after confirmation + StateCompleted PaymentState = "completed" + StateVoided PaymentState = "voided" + StateCanceled PaymentState = "canceled" +) + +type WsPayDb struct { + Id uuid.UUID `db:"id"` + ShopID string `db:"shop_id"` + ShoppingCartID string `db:"shopping_card_id"` + TotalAmount int64 `db:"total_amount"` + Lang string `db:"lang"` + + CustomerFirstName string `db:"customer_first_name"` + CustomerLastName string `db:"customer_last_name"` + CustomerAddress string `db:"customer_address"` + CustomerCity string `db:"customer_city"` + CustomerZIP string `db:"customer_zip"` + CustomerCountry string `db:"customer_country"` + CustomerPhone string `db:"customer_phone"` + + PaymentPlan string `db:"payment_plan"` + CreditCardName string `db:"credit_card_name"` + CreditCardNumber string `db:"credit_card_number"` + PaymentMethod string `db:"payment_method"` + CurrencyCode int `db:"currency_code"` + + DateTime time.Time `db:"date_time"` + + ECI string `db:"eci"` + STAN string `db:"stan"` + + Success int `db:"success"` + ApprovalCode string `db:"approval_code"` + ErrorMessage string `db:"error_message"` + ErrorCodes string `db:"error_codes"` + + State PaymentState `db:"payment_state"` +} diff --git a/wspay/service.go b/wspay/service.go new file mode 100644 index 0000000..6c2a1d7 --- /dev/null +++ b/wspay/service.go @@ -0,0 +1,98 @@ +package wspay + +import ( + "crypto/sha512" + "encoding/hex" + "errors" + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + "strconv" +) + +type Service struct { + DB *sqlx.DB +} + +func (s *Service) CreateEntry(shopId string, totalAmount int64) (WsPayDb, error) { + id := uuid.Must(uuid.NewRandom()) + entry := WsPayDb{ + Id: id, + ShopID: shopId, + ShoppingCartID: id.String(), + TotalAmount: totalAmount, + State: StateInitialized, + } + _, err := s.DB.Exec(`INSERT INTO "wspay" ("id", "shop_id", "shopping_card_id", "total_amount", "payment_state") VALUES ($1, $2, $3, $4, $5)`, + &entry.Id, &entry.ShopID, &entry.ShoppingCartID, &entry.TotalAmount, &entry.State, + ) + if err != nil { + return WsPayDb{}, err + } + return s.FetchById(id) +} + +func (s *Service) FetchAll() ([]WsPayDb, error) { + var entries []WsPayDb + err := s.DB.Select(&entries, `SELECT * FROM "wspay"`) + return entries, err +} + +func (s *Service) FetchById(id uuid.UUID) (WsPayDb, error) { + entry := WsPayDb{} + err := s.DB.Get(&entry, `SELECT * FROM "wspay" WHERE "id" = $1`, id) + return entry, err +} + +func (s *Service) FetchByShoppingCartID(id string) (WsPayDb, error) { + entry := WsPayDb{} + err := s.DB.Get(&entry, `SELECT * FROM "wspay" WHERE "shopping_card_id" = $1`, id) + return entry, err +} + +func (s *Service) Update(entry WsPayDb) error { + _, err := s.DB.Exec(`UPDATE "wspay" set "lang" = $2, "customer_first_name" = $3, "customer_last_name" = $4, "customer_address" = $5, "customer_city" = $6, "customer_zip" = $7, "customer_country" = $8, "customer_phone" = $9, "payment_plan" = $10, "credit_card_name" = $11, "credit_card_number" = $12, "payment_method" = $13, "currency_code" = $14, "date_time" = $15, "eci" = $16, "stan" = $17, "success" = $18, "approval_code" = $19, "error_message" = $20, "error_codes" = $21, "payment_state" = $22 WHERE "id" = $1`, + &entry.Id, &entry.Lang, &entry.CustomerFirstName, &entry.CustomerLastName, &entry.CustomerAddress, &entry.CustomerCity, &entry.CustomerZIP, &entry.CustomerCountry, &entry.CustomerPhone, &entry.PaymentPlan, &entry.CreditCardName, &entry.CreditCardNumber, &entry.PaymentMethod, &entry.CurrencyCode, &entry.DateTime, &entry.ECI, &entry.STAN, &entry.Success, &entry.ApprovalCode, &entry.ErrorMessage, &entry.ErrorCodes, &entry.State, + ) + return err +} + +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 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") + } +} diff --git a/wspay/wspay.go b/wspay/wspay.go new file mode 100644 index 0000000..1abf3d8 --- /dev/null +++ b/wspay/wspay.go @@ -0,0 +1,160 @@ +package wspay + +const AuthorisationForm = "https://formtest.wspay.biz/authorization.aspx" + +type WsPayForm 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 WsPayFormReturn 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 WsPayFormError 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 WsPayFormCancel struct { + ResponseCode int + ShoppingCartID string + ApprovalCode string + Success int + Signature string +} + +type WsPayCompletionRequest struct { + Version string + WsPayOrderId string + ShopId string + ApprovalCode string + STAN string + Amount string + Signature string +} + +type WsPayCompletionResponse struct { + WsPayOrderId string + ShopId string + ApprovalCode string + STAN string + ErrorMessage string + ActionSuccess string + Signature string +} + +type WsPayStatusCheckRequest struct { + Version string + ShopId string + ShoppingCartId string + Signature string +} + +type WsPayStatusCheckResponse struct { + WsPayOrderId string + Signature string + STAN string + ApprovalCode string + ShopID string + ShoppingCartID string + Amount string + CurrencyCode string + ActionSuccess string + Success string // deprecated + Authorized int + Completed int + Voided int + Refunded int + 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 +}