payment-poc/main.go

406 lines
12 KiB
Go
Raw Normal View History

2023-07-06 11:29:06 +00:00
package main
import (
"embed"
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-10 08:10:13 +00:00
"html/template"
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-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
var ShopId string
var ShopSecret string
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-26 07:51:29 +00:00
ShopId = envMustExist("WSPAY_SHOP_ID")
ShopSecret = envMustExist("WSPAY_SHOP_SECRET")
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-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-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-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()
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: &currency,
ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{
Name: &productName,
Description: &productDescription,
},
UnitAmount: &entry.TotalAmount,
},
Quantity: stripe.Int64(1),
},
},
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
PaymentIntentData: &stripe.CheckoutSessionPaymentIntentDataParams{
CaptureMethod: stripe.String("manual"),
},
SuccessURL: stripe.String(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})
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
}
entry, err := wspayService.CreateEntry(ShopId, int64(amount*100))
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{
ShopID: ShopId,
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-10 08:10:13 +00:00
Signature: wspay.CalculateFormSignature(ShopId, ShopSecret, entry.ShoppingCartID, entry.TotalAmount),
}
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-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-10 08:10:13 +00:00
if err := wspay.CompareFormReturnSignature(response.Signature, ShopId, ShopSecret, 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
}