2023-07-06 11:29:06 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-07-27 10:09:38 +00:00
|
|
|
"bytes"
|
2023-07-06 11:29:06 +00:00
|
|
|
"embed"
|
2023-07-27 10:09:38 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2023-07-10 08:10:13 +00:00
|
|
|
"fmt"
|
2023-07-06 11:29:06 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
2023-07-10 08:10:13 +00:00
|
|
|
"github.com/google/uuid"
|
2023-07-06 11:29:06 +00:00
|
|
|
"github.com/joho/godotenv"
|
2023-07-26 07:51:29 +00:00
|
|
|
"github.com/stripe/stripe-go/v72"
|
|
|
|
"github.com/stripe/stripe-go/v72/checkout/session"
|
2023-07-27 08:11:40 +00:00
|
|
|
"github.com/stripe/stripe-go/v72/paymentintent"
|
2023-07-10 08:10:13 +00:00
|
|
|
"html/template"
|
2023-07-27 10:09:38 +00:00
|
|
|
"io"
|
2023-07-06 11:29:06 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
2023-07-10 08:10:13 +00:00
|
|
|
"payment-poc/migration"
|
2023-07-26 07:51:29 +00:00
|
|
|
"payment-poc/state"
|
|
|
|
stripe2 "payment-poc/stripe"
|
2023-07-27 08:11:40 +00:00
|
|
|
"payment-poc/viva"
|
2023-07-10 08:10:13 +00:00
|
|
|
"payment-poc/wspay"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2023-07-06 11:29:06 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
//go:embed db/dev/*.sql
|
|
|
|
var devMigrations embed.FS
|
|
|
|
|
2023-07-10 08:10:13 +00:00
|
|
|
var BackendUrl string
|
2023-07-27 08:11:40 +00:00
|
|
|
var WsPayShopId string
|
|
|
|
var WsPayShopSecret string
|
|
|
|
|
|
|
|
var VivaMerchantId string
|
|
|
|
var VivaApiKey string
|
|
|
|
var VivaSourceCode string
|
|
|
|
var VivaClientId string
|
|
|
|
var VivaClientSecret string
|
2023-07-10 08:10:13 +00:00
|
|
|
|
2023-07-06 11:29:06 +00:00
|
|
|
func init() {
|
|
|
|
godotenv.Load()
|
2023-07-10 08:10:13 +00:00
|
|
|
|
|
|
|
BackendUrl = envMustExist("BACKEND_URL")
|
2023-07-27 10:09:38 +00:00
|
|
|
|
2023-07-27 08:11:40 +00:00
|
|
|
WsPayShopId = envMustExist("WSPAY_SHOP_ID")
|
|
|
|
WsPayShopSecret = envMustExist("WSPAY_SHOP_SECRET")
|
|
|
|
|
|
|
|
VivaMerchantId = envMustExist("VIVA_WALLET_MERCHANT_ID")
|
|
|
|
VivaApiKey = envMustExist("VIVA_WALLET_API_KEY")
|
|
|
|
VivaSourceCode = envMustExist("VIVA_WALLET_SOURCE_CODE")
|
|
|
|
VivaClientId = envMustExist("VIVA_WALLET_CLIENT_ID")
|
|
|
|
VivaClientSecret = envMustExist("VIVA_WALLET_CLIENT_SECRET")
|
|
|
|
|
2023-07-26 07:51:29 +00:00
|
|
|
stripe.Key = envMustExist("STRIPE_KEY")
|
2023-07-10 08:10:13 +00:00
|
|
|
|
2023-07-06 11:29:06 +00:00
|
|
|
log.SetPrefix("")
|
|
|
|
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
client, err := connectToDb()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("couldn't connect to db: %v", err)
|
|
|
|
}
|
|
|
|
if err := migration.InitializeMigrations(client, devMigrations); err != nil {
|
|
|
|
log.Fatalf("couldn't execute migrations: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
g := gin.Default()
|
2023-07-10 08:10:13 +00:00
|
|
|
g.Use(gin.BasicAuth(getAccounts()))
|
|
|
|
|
|
|
|
g.SetFuncMap(template.FuncMap{
|
|
|
|
"formatCurrency": func(current int64) string {
|
|
|
|
return fmt.Sprintf("%d,%02d", current/100, current%100)
|
|
|
|
},
|
2023-07-27 08:11:40 +00:00
|
|
|
"formatCurrency2": func(current int64) string {
|
|
|
|
return fmt.Sprintf("%d.%02d", current/100, current%100)
|
|
|
|
},
|
2023-07-26 07:51:29 +00:00
|
|
|
"formatState": func(stt state.PaymentState) string {
|
|
|
|
switch stt {
|
|
|
|
case state.StateCanceled:
|
2023-07-10 08:10:13 +00:00
|
|
|
return "Otkazano"
|
2023-07-27 08:11:40 +00:00
|
|
|
case state.StateVoided:
|
|
|
|
return "Otkazano sa strane administratora"
|
2023-07-26 07:51:29 +00:00
|
|
|
case state.StateAccepted:
|
2023-07-10 08:10:13 +00:00
|
|
|
return "Prihvačeno"
|
2023-07-26 07:51:29 +00:00
|
|
|
case state.StateError:
|
2023-07-10 08:10:13 +00:00
|
|
|
return "Greška"
|
2023-07-26 07:51:29 +00:00
|
|
|
case state.StateInitialized:
|
2023-07-10 08:10:13 +00:00
|
|
|
return "Inicijalna izrada"
|
2023-07-26 07:51:29 +00:00
|
|
|
case state.StateCanceledInitialization:
|
2023-07-10 08:10:13 +00:00
|
|
|
return "Otkazano tijekom izrade"
|
2023-07-26 07:51:29 +00:00
|
|
|
case state.StateCompleted:
|
2023-07-10 08:10:13 +00:00
|
|
|
return "Završeno"
|
|
|
|
}
|
2023-07-26 07:51:29 +00:00
|
|
|
return "nepoznato stanje '" + string(stt) + "'"
|
2023-07-10 08:10:13 +00:00
|
|
|
},
|
|
|
|
"omitempty": func(value string) string {
|
|
|
|
if value == "" {
|
|
|
|
return "-"
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
},
|
|
|
|
})
|
2023-07-06 11:29:06 +00:00
|
|
|
|
|
|
|
g.NoRoute(func(c *gin.Context) {
|
|
|
|
c.JSON(http.StatusNotFound, gin.H{"message": "no action on given url", "created": time.Now()})
|
|
|
|
})
|
|
|
|
g.NoMethod(func(c *gin.Context) {
|
|
|
|
c.JSON(http.StatusMethodNotAllowed, gin.H{"message": "no action on given method", "created": time.Now()})
|
|
|
|
})
|
2023-07-10 08:10:13 +00:00
|
|
|
|
|
|
|
wspayService := wspay.Service{
|
|
|
|
DB: client,
|
|
|
|
}
|
2023-07-26 07:51:29 +00:00
|
|
|
stripeService := stripe2.Service{
|
|
|
|
DB: client,
|
|
|
|
}
|
2023-07-27 08:11:40 +00:00
|
|
|
vivaService := viva.Service{
|
|
|
|
DB: client,
|
|
|
|
ClientId: VivaClientId,
|
|
|
|
ClientSecret: VivaClientSecret,
|
|
|
|
SourceCode: VivaSourceCode,
|
|
|
|
MerchantId: VivaMerchantId,
|
|
|
|
ApiKey: VivaApiKey,
|
|
|
|
}
|
2023-07-10 08:10:13 +00:00
|
|
|
|
|
|
|
g.LoadHTMLGlob("./templates/*.gohtml")
|
|
|
|
|
2023-07-06 11:29:06 +00:00
|
|
|
g.GET("/", func(c *gin.Context) {
|
2023-07-26 07:51:29 +00:00
|
|
|
wspayEntries, _ := wspayService.FetchAll()
|
|
|
|
stripeEntries, _ := stripeService.FetchAll()
|
2023-07-27 08:11:40 +00:00
|
|
|
vivaEntries, _ := vivaService.FetchAll()
|
|
|
|
c.HTML(200, "index.gohtml", gin.H{"WsPay": wspayEntries, "Stripe": stripeEntries, "Viva": vivaEntries})
|
2023-07-26 07:51:29 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
g.GET("/methods", func(c *gin.Context) {
|
|
|
|
amount, err := strconv.ParseFloat(c.Query("amount"), 64)
|
|
|
|
if err != nil {
|
|
|
|
amount = 10.00
|
|
|
|
}
|
|
|
|
|
|
|
|
c.HTML(200, "methods.gohtml", gin.H{"Amount": amount})
|
|
|
|
})
|
|
|
|
|
|
|
|
setupWsPayEndpoints(g.Group("wspay"), wspayService)
|
|
|
|
setupStripeEndpoints(g.Group("stripe"), stripeService)
|
2023-07-27 08:11:40 +00:00
|
|
|
setupVivaEndpoints(g.Group("viva"), vivaService)
|
2023-07-26 07:51:29 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-07-27 08:11:40 +00:00
|
|
|
func setupVivaEndpoints(g *gin.RouterGroup, vivaService viva.Service) {
|
|
|
|
g.GET("", func(c *gin.Context) {
|
|
|
|
amount, err := strconv.ParseFloat(c.Query("amount"), 64)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
entry, err := vivaService.CreateEntry(int64(amount * 100))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("Created initial viva entry (ammount=%d)", amount)
|
|
|
|
|
|
|
|
entry, err = vivaService.CreatePaymentOrder(entry)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
vivaService.Update(entry)
|
|
|
|
c.Redirect(http.StatusSeeOther, "https://demo.vivapayments.com/web/checkout?ref="+entry.OrderId)
|
|
|
|
})
|
|
|
|
|
|
|
|
g.POST("complete/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
amount, err := strconv.ParseFloat(c.PostForm("amount"), 64)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
entry, err := vivaService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if int64(amount*100) > entry.TotalAmount || int64(amount*100) < 1 {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if entry.State == state.StateInitialized || entry.State == state.StateAccepted {
|
|
|
|
entry, err = vivaService.CompleteTransaction(entry, int64(amount*100))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vivaService.Update(entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(http.StatusSeeOther, "/viva/info/"+id.String())
|
|
|
|
})
|
|
|
|
|
|
|
|
g.POST("cancel/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
entry, err := vivaService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if entry.State == state.StateInitialized || entry.State == state.StateAccepted {
|
|
|
|
entry, err = vivaService.CancelTransaction(entry)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
vivaService.Update(entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(http.StatusSeeOther, "/viva/info/"+id.String())
|
|
|
|
})
|
|
|
|
|
|
|
|
g.GET("success", func(c *gin.Context) {
|
|
|
|
transactionId := uuid.MustParse(c.Query("t"))
|
|
|
|
orderId := viva.OrderId(c.Query("s"))
|
|
|
|
lang := c.Query("lang")
|
|
|
|
eventId := c.Query("eventId")
|
|
|
|
eci := c.Query("eci")
|
|
|
|
|
|
|
|
log.Printf("Received success response for viva payment %s", orderId)
|
|
|
|
entry, err := vivaService.FetchByOrderId(orderId)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Couldn't find payment info for viva payment %s", orderId)
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.State = state.StateAccepted
|
|
|
|
entry.ECI = eci
|
|
|
|
entry.Lang = lang
|
|
|
|
entry.EventId = eventId
|
|
|
|
entry.TransactionId = transactionId
|
|
|
|
|
|
|
|
if err := vivaService.Update(entry); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Viva payment %s received correctly, returning redirect", entry.OrderId)
|
|
|
|
c.Redirect(http.StatusTemporaryRedirect, "/")
|
|
|
|
})
|
|
|
|
g.GET("error", func(c *gin.Context) {
|
|
|
|
transactionId := uuid.MustParse(c.Query("t"))
|
|
|
|
orderId := viva.OrderId(c.Query("s"))
|
|
|
|
lang := c.Query("lang")
|
|
|
|
eventId := c.Query("eventId")
|
|
|
|
eci := c.Query("eci")
|
|
|
|
|
|
|
|
log.Printf("Received error response for viva payment %s", orderId)
|
|
|
|
entry, err := vivaService.FetchByOrderId(orderId)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Couldn't find payment info for viva payment %s", orderId)
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.State = state.StateAccepted
|
|
|
|
entry.ECI = eci
|
|
|
|
entry.Lang = lang
|
|
|
|
entry.EventId = eventId
|
|
|
|
entry.TransactionId = transactionId
|
|
|
|
|
|
|
|
if err := vivaService.Update(entry); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Viva payment %s received correctly, returning redirect", entry.OrderId)
|
|
|
|
c.Redirect(http.StatusTemporaryRedirect, "/")
|
|
|
|
})
|
|
|
|
g.GET("info/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
entry, err := vivaService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.HTML(200, "viva_info.gohtml", gin.H{"Entry": entry})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-26 07:51:29 +00:00
|
|
|
func setupStripeEndpoints(g *gin.RouterGroup, stripeService stripe2.Service) {
|
|
|
|
g.GET("", func(c *gin.Context) {
|
|
|
|
amount, err := strconv.ParseFloat(c.Query("amount"), 64)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
entry, err := stripeService.CreateEntry(int64(amount * 100))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("Created initial stripe entry (ammount=%d)", amount)
|
|
|
|
|
|
|
|
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(BackendUrl + "/stripe/success?token=" + entry.Id.String()),
|
|
|
|
CancelURL: stripe.String(BackendUrl + "/stripe/cancel?token=" + entry.Id.String()),
|
|
|
|
}
|
|
|
|
result, err := session.New(params)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
entry.PaymentIntentId = result.PaymentIntent.ID
|
|
|
|
stripeService.Update(entry)
|
|
|
|
|
|
|
|
c.Redirect(http.StatusSeeOther, result.URL)
|
|
|
|
})
|
|
|
|
|
2023-07-27 08:11:40 +00:00
|
|
|
g.POST("complete/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
amount, err := strconv.ParseFloat(c.PostForm("amount"), 64)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
entry, err := stripeService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if int64(amount*100) > entry.TotalAmount || int64(amount*100) < 1 {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if entry.State == state.StateInitialized || entry.State == state.StateAccepted {
|
|
|
|
params := &stripe.PaymentIntentCaptureParams{
|
|
|
|
AmountToCapture: stripe.Int64(int64(amount * 100)),
|
|
|
|
}
|
|
|
|
pi, err := paymentintent.Capture(entry.PaymentIntentId, params)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("received state on completion: %v", pi.Status)
|
|
|
|
if pi.Status == stripe.PaymentIntentStatusSucceeded || pi.Status == stripe.PaymentIntentStatusProcessing {
|
|
|
|
entry.TotalAmount = pi.Amount
|
|
|
|
entry.State = state.StateCompleted
|
|
|
|
stripeService.Update(entry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(http.StatusSeeOther, "/stripe/info/"+id.String())
|
|
|
|
})
|
|
|
|
|
|
|
|
g.POST("cancel/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
entry, err := stripeService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if entry.State == state.StateInitialized || entry.State == state.StateAccepted {
|
|
|
|
params := &stripe.PaymentIntentCancelParams{}
|
|
|
|
pi, err := paymentintent.Cancel(entry.PaymentIntentId, params)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("received state on completion: %v", pi.Status)
|
|
|
|
if pi.Status == stripe.PaymentIntentStatusCanceled {
|
|
|
|
entry.State = state.StateCanceled
|
|
|
|
stripeService.Update(entry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(http.StatusSeeOther, "/stripe/info/"+id.String())
|
|
|
|
})
|
|
|
|
|
2023-07-26 07:51:29 +00:00
|
|
|
g.GET("success", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Query("token"))
|
|
|
|
|
|
|
|
log.Printf("Received success response for stripe payment %s", id)
|
|
|
|
entry, err := stripeService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Couldn't find payment info for stripe payment %s", id)
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.State = state.StateAccepted
|
|
|
|
|
|
|
|
if err := stripeService.Update(entry); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Stripe payment %s received correctly, returning redirect", id)
|
|
|
|
c.Redirect(http.StatusTemporaryRedirect, "/")
|
|
|
|
})
|
|
|
|
g.GET("error", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Query("token"))
|
|
|
|
|
|
|
|
log.Printf("Received error response for stripe payment %s", id)
|
|
|
|
entry, err := stripeService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Couldn't find payment info for stripe payment %s", id)
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.State = state.StateError
|
|
|
|
|
|
|
|
if err := stripeService.Update(entry); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("Stripe payment %s received correctly, returning redirect", id)
|
|
|
|
c.Redirect(http.StatusTemporaryRedirect, "/")
|
|
|
|
})
|
|
|
|
g.GET("info/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
entry, err := stripeService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.HTML(200, "stripe_info.gohtml", gin.H{"Entry": entry})
|
2023-07-10 08:10:13 +00:00
|
|
|
})
|
2023-07-26 07:51:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func setupWsPayEndpoints(g *gin.RouterGroup, wspayService wspay.Service) {
|
|
|
|
g.GET("", func(c *gin.Context) {
|
2023-07-10 08:10:13 +00:00
|
|
|
amount, err := strconv.ParseFloat(c.Query("amount"), 64)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-07-27 08:11:40 +00:00
|
|
|
entry, err := wspayService.CreateEntry(WsPayShopId, int64(amount*100))
|
2023-07-10 08:10:13 +00:00
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Created initial wspay form (ammount=%d)", amount)
|
|
|
|
|
2023-07-10 08:10:13 +00:00
|
|
|
form := wspay.WsPayForm{
|
2023-07-27 08:11:40 +00:00
|
|
|
ShopID: WsPayShopId,
|
2023-07-10 08:10:13 +00:00
|
|
|
ShoppingCartID: entry.ShoppingCartID,
|
|
|
|
Version: "2.0",
|
|
|
|
TotalAmount: entry.TotalAmount,
|
2023-07-26 07:51:29 +00:00
|
|
|
ReturnURL: BackendUrl + "/wspay/success",
|
|
|
|
ReturnErrorURL: BackendUrl + "/wspay/error",
|
|
|
|
CancelURL: BackendUrl + "/wspay/cancel",
|
2023-07-27 08:11:40 +00:00
|
|
|
Signature: wspay.CalculateFormSignature(WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.TotalAmount),
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-26 07:51:29 +00:00
|
|
|
c.HTML(200, "wspay.gohtml", gin.H{"Action": wspay.AuthorisationForm, "Form": form})
|
2023-07-10 08:10:13 +00:00
|
|
|
})
|
2023-07-27 10:09:38 +00:00
|
|
|
g.POST("complete/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
amount, err := strconv.ParseFloat(c.PostForm("amount"), 64)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
entry, err := wspayService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if int64(amount*100) > entry.TotalAmount || int64(amount*100) < 1 {
|
|
|
|
c.AbortWithError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if entry.State == state.StateAccepted {
|
|
|
|
var request = wspay.WsPayCompletionRequest{
|
|
|
|
Version: "2.0",
|
|
|
|
WsPayOrderId: entry.ShoppingCartID,
|
|
|
|
ShopId: entry.ShopID,
|
|
|
|
ApprovalCode: entry.ApprovalCode,
|
|
|
|
STAN: entry.STAN,
|
|
|
|
Amount: int64(amount * 100),
|
|
|
|
Signature: wspay.CalculateCompletionSignature(WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, entry.ApprovalCode, int64(amount*100)),
|
|
|
|
}
|
|
|
|
|
|
|
|
content, _ := json.Marshal(&request)
|
|
|
|
response, err := http.Post("https://test.wspay.biz/api/services/completion", "application/json", bytes.NewBuffer(content))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if response.StatusCode == http.StatusOK {
|
|
|
|
transactionResponse := wspay.WsPayCompletionResponse{}
|
|
|
|
content, err := io.ReadAll(response.Body)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(content, &transactionResponse); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
log.Printf("Received transaction response: success=%s, errorMessage=%s, approvalCode=%s",
|
|
|
|
transactionResponse.ActionSuccess, transactionResponse.ErrorMessage, transactionResponse.ApprovalCode,
|
|
|
|
)
|
|
|
|
if wspay.CompareCompletionReturnSignature(transactionResponse.Signature, WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, transactionResponse.ActionSuccess, transactionResponse.ApprovalCode) != nil {
|
|
|
|
entry.TotalAmount = int64(amount * 100)
|
|
|
|
entry.State = state.StateCompleted
|
|
|
|
wspayService.Update(entry)
|
|
|
|
} else {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, errors.New("received invalid signature"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, errors.New("received wrong status, expected 200 received "+strconv.FormatInt(int64(response.StatusCode), 10)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(http.StatusSeeOther, "/wspay/info/"+id.String())
|
|
|
|
})
|
|
|
|
g.POST("cancel/:id", func(c *gin.Context) {
|
|
|
|
id := uuid.MustParse(c.Param("id"))
|
|
|
|
entry, err := wspayService.FetchById(id)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusNotFound, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if entry.State == state.StateAccepted {
|
|
|
|
var request = wspay.WsPayCompletionRequest{
|
|
|
|
Version: "2.0",
|
|
|
|
WsPayOrderId: entry.ShoppingCartID,
|
|
|
|
ShopId: entry.ShopID,
|
|
|
|
ApprovalCode: entry.ApprovalCode,
|
|
|
|
STAN: entry.STAN,
|
|
|
|
Amount: entry.TotalAmount,
|
|
|
|
Signature: wspay.CalculateCompletionSignature(WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, entry.ApprovalCode, entry.TotalAmount),
|
|
|
|
}
|
|
|
|
|
|
|
|
content, _ := json.Marshal(&request)
|
|
|
|
response, err := http.Post("https://test.wspay.biz/api/services/void", "application/json", bytes.NewBuffer(content))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if response.StatusCode == http.StatusOK {
|
|
|
|
transactionResponse := wspay.WsPayCompletionResponse{}
|
|
|
|
content, err := io.ReadAll(response.Body)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(content, &transactionResponse); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
log.Printf("Received transaction response: success=%s, errorMessage=%s, approvalCode=%s",
|
|
|
|
transactionResponse.ActionSuccess, transactionResponse.ErrorMessage, transactionResponse.ApprovalCode,
|
|
|
|
)
|
|
|
|
if wspay.CompareCompletionReturnSignature(transactionResponse.Signature, WsPayShopId, WsPayShopSecret, entry.ShoppingCartID, entry.STAN, transactionResponse.ActionSuccess, transactionResponse.ApprovalCode) != nil {
|
|
|
|
entry.State = state.StateCanceled
|
|
|
|
wspayService.Update(entry)
|
|
|
|
} else {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, errors.New("received invalid signature"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, errors.New("received wrong status, expected 200 received "+strconv.FormatInt(int64(response.StatusCode), 10)))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(http.StatusSeeOther, "/wspay/info/"+id.String())
|
|
|
|
})
|
2023-07-26 07:51:29 +00:00
|
|
|
g.GET("success", func(c *gin.Context) {
|
2023-07-10 08:10:13 +00:00
|
|
|
response := wspay.WsPayFormReturn{}
|
|
|
|
if err := c.ShouldBind(&response); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Received success response for transaction %s", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID)
|
|
|
|
if err != nil {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Couldn't find payment info for transaction %s", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
2023-07-10 08:56:25 +00:00
|
|
|
|
2023-07-27 08:11:40 +00:00
|
|
|
if err := wspay.CompareFormReturnSignature(response.Signature, WsPayShopId, WsPayShopSecret, response.ShoppingCartID, response.Success, response.ApprovalCode); err != nil {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Invalid signature for transaction %s", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
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
|
|
|
|
|
2023-07-26 07:51:29 +00:00
|
|
|
entry.State = state.StateAccepted
|
2023-07-10 08:10:13 +00:00
|
|
|
|
|
|
|
if err := wspayService.Update(entry); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Query("iframe") != "" {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Transaction %s received correctly, returning iframe response", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
c.HTML(200, "iframe_handler.gohtml", gin.H{})
|
|
|
|
} else {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Transaction %s received correctly, returning redirect", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
c.Redirect(http.StatusTemporaryRedirect, "/")
|
|
|
|
}
|
|
|
|
})
|
2023-07-26 07:51:29 +00:00
|
|
|
g.GET("error", func(c *gin.Context) {
|
2023-07-10 08:10:13 +00:00
|
|
|
response := wspay.WsPayFormError{}
|
|
|
|
if err := c.ShouldBind(&response); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Received error response for transaction %s", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID)
|
|
|
|
if err != nil {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Couldn't find payment info for transaction %s", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
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
|
|
|
|
|
2023-07-26 07:51:29 +00:00
|
|
|
entry.State = state.StateError
|
2023-07-10 08:10:13 +00:00
|
|
|
|
|
|
|
if err := wspayService.Update(entry); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Query("iframe") != "" {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Transaction %s received correctly, returning iframe response", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
c.HTML(200, "iframe_handler.gohtml", gin.H{})
|
|
|
|
} else {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Transaction %s received correctly, returning redirect", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
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
|
|
|
|
}
|
2023-07-26 07:51:29 +00:00
|
|
|
c.HTML(200, "wspay_info.gohtml", gin.H{"Entry": entry})
|
2023-07-10 08:10:13 +00:00
|
|
|
})
|
2023-07-26 07:51:29 +00:00
|
|
|
g.GET("cancel", func(c *gin.Context) {
|
2023-07-10 08:10:13 +00:00
|
|
|
response := wspay.WsPayFormCancel{}
|
|
|
|
if err := c.ShouldBind(&response); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Received error response for transaction %s", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID)
|
|
|
|
if err != nil {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Couldn't find payment info for transaction %s", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
2023-07-26 07:51:29 +00:00
|
|
|
entry.State = state.StateCanceledInitialization
|
2023-07-10 08:10:13 +00:00
|
|
|
|
|
|
|
if err := wspayService.Update(entry); err != nil {
|
|
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Query("iframe") != "" {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Transaction %s received correctly, returning iframe response", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
c.HTML(200, "iframe_handler.gohtml", gin.H{})
|
|
|
|
} else {
|
2023-07-10 08:56:25 +00:00
|
|
|
log.Printf("Transaction %s received correctly, returning redirect", response.ShoppingCartID)
|
2023-07-10 08:10:13 +00:00
|
|
|
c.Redirect(http.StatusTemporaryRedirect, "/")
|
|
|
|
}
|
2023-07-06 11:29:06 +00:00
|
|
|
})
|
2023-07-10 08:10:13 +00:00
|
|
|
}
|