package main import ( "embed" "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/joho/godotenv" "github.com/stripe/stripe-go/v72" "github.com/stripe/stripe-go/v72/checkout/session" "html/template" "log" "net/http" "payment-poc/migration" "payment-poc/state" stripe2 "payment-poc/stripe" "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("WSPAY_SHOP_ID") ShopSecret = envMustExist("WSPAY_SHOP_SECRET") stripe.Key = envMustExist("STRIPE_KEY") 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() 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(stt state.PaymentState) string { switch stt { case state.StateCanceled: return "Otkazano" case state.StateAccepted: return "Prihvačeno" case state.StateError: return "Greška" case state.StateInitialized: return "Inicijalna izrada" case state.StateCanceledInitialization: return "Otkazano tijekom izrade" case state.StateCompleted: return "Završeno" } return "nepoznato stanje '" + string(stt) + "'" }, "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()}) }) 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, } stripeService := stripe2.Service{ DB: client, } g.LoadHTMLGlob("./templates/*.gohtml") g.GET("/", func(c *gin.Context) { wspayEntries, _ := wspayService.FetchAll() stripeEntries, _ := stripeService.FetchAll() c.HTML(200, "index.gohtml", gin.H{"WsPay": wspayEntries, "Stripe": stripeEntries}) }) 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) 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 } 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) }) 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}) }) } func setupWsPayEndpoints(g *gin.RouterGroup, wspayService wspay.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 := wspayService.CreateEntry(ShopId, int64(amount*100)) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } log.Printf("Created initial wspay form (ammount=%d)", amount) form := wspay.WsPayForm{ ShopID: ShopId, ShoppingCartID: entry.ShoppingCartID, Version: "2.0", TotalAmount: entry.TotalAmount, ReturnURL: BackendUrl + "/wspay/success", ReturnErrorURL: BackendUrl + "/wspay/error", CancelURL: BackendUrl + "/wspay/cancel", Signature: wspay.CalculateFormSignature(ShopId, ShopSecret, entry.ShoppingCartID, entry.TotalAmount), } c.HTML(200, "wspay.gohtml", gin.H{"Action": wspay.AuthorisationForm, "Form": form}) }) g.GET("success", func(c *gin.Context) { response := wspay.WsPayFormReturn{} if err := c.ShouldBind(&response); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } log.Printf("Received success response for transaction %s", response.ShoppingCartID) entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID) if err != nil { log.Printf("Couldn't find payment info for transaction %s", response.ShoppingCartID) c.AbortWithError(http.StatusInternalServerError, err) return } if err := wspay.CompareFormReturnSignature(response.Signature, ShopId, ShopSecret, response.ShoppingCartID, response.Success, response.ApprovalCode); err != nil { log.Printf("Invalid signature for transaction %s", response.ShoppingCartID) 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 = state.StateAccepted if err := wspayService.Update(entry); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } if c.Query("iframe") != "" { log.Printf("Transaction %s received correctly, returning iframe response", response.ShoppingCartID) c.HTML(200, "iframe_handler.gohtml", gin.H{}) } else { log.Printf("Transaction %s received correctly, returning redirect", response.ShoppingCartID) c.Redirect(http.StatusTemporaryRedirect, "/") } }) g.GET("error", func(c *gin.Context) { response := wspay.WsPayFormError{} if err := c.ShouldBind(&response); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } log.Printf("Received error response for transaction %s", response.ShoppingCartID) entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID) if err != nil { log.Printf("Couldn't find payment info for transaction %s", response.ShoppingCartID) 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 = state.StateError if err := wspayService.Update(entry); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } if c.Query("iframe") != "" { log.Printf("Transaction %s received correctly, returning iframe response", response.ShoppingCartID) c.HTML(200, "iframe_handler.gohtml", gin.H{}) } else { log.Printf("Transaction %s received correctly, returning redirect", response.ShoppingCartID) 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, "wspay_info.gohtml", gin.H{"Entry": entry}) }) g.GET("cancel", func(c *gin.Context) { response := wspay.WsPayFormCancel{} if err := c.ShouldBind(&response); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } log.Printf("Received error response for transaction %s", response.ShoppingCartID) entry, err := wspayService.FetchByShoppingCartID(response.ShoppingCartID) if err != nil { log.Printf("Couldn't find payment info for transaction %s", response.ShoppingCartID) c.AbortWithError(http.StatusInternalServerError, err) return } entry.State = state.StateCanceledInitialization if err := wspayService.Update(entry); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } if c.Query("iframe") != "" { log.Printf("Transaction %s received correctly, returning iframe response", response.ShoppingCartID) c.HTML(200, "iframe_handler.gohtml", gin.H{}) } else { log.Printf("Transaction %s received correctly, returning redirect", response.ShoppingCartID) c.Redirect(http.StatusTemporaryRedirect, "/") } }) }