package api import ( "errors" "github.com/gin-gonic/gin" "github.com/google/uuid" "log/slog" "net/http" "payment-poc/domain/database" "payment-poc/domain/providers" "payment-poc/domain/providers/mock" stripe2 "payment-poc/domain/providers/stripe" "payment-poc/domain/providers/viva" wspay2 "payment-poc/domain/providers/wspay" "payment-poc/domain/state" "strconv" "time" ) func NoMethod() gin.HandlerFunc { return func(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{ "status": 404, "created": time.Now(), "message": "no handler for method '" + c.Request.Method + "'", }) } } func NoRoute() gin.HandlerFunc { return func(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{ "status": 404, "created": time.Now(), "message": "no handler for " + c.Request.Method + " '" + c.Request.URL.RequestURI() + "'", }) } } func RefreshPayment(entryProvider *database.PaymentEntryProvider, paymentGateways map[state.PaymentGateway]providers.PaymentProvider) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) entry, err := entryProvider.FetchById(id) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } if paymentGateway, ok := paymentGateways[entry.Gateway]; ok { slog.Info("fetching payment info", "entry_id", entry.Id.String(), "state", entry.State) entry, err = paymentGateway.UpdatePayment(entry) if err == nil { entryProvider.UpdateEntry(entry) slog.Info("fetched payment info", "entry_id", entry.Id.String(), "state", entry.State) } c.Redirect(http.StatusSeeOther, "/entries/"+id.String()) } else { if err != nil { c.AbortWithError(http.StatusInternalServerError, errors.New("payment gateway not supported: "+string(entry.Gateway))) return } } } } func CancelPayment(entryProvider *database.PaymentEntryProvider, paymentGateways map[state.PaymentGateway]providers.PaymentProvider) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) entry, err := entryProvider.FetchById(id) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } if paymentGateway, ok := paymentGateways[entry.Gateway]; ok { slog.Info("canceling payment", "entry_id", id.String(), "state", entry.State) entry, err = paymentGateway.CancelTransaction(entry) if err == nil { entryProvider.UpdateEntry(entry) slog.Info("canceled payment", "entry_id", entry.Id.String(), "state", entry.State) c.Redirect(http.StatusSeeOther, "/entries/"+id.String()) } else { c.AbortWithError(http.StatusInternalServerError, err) return } } else { if err != nil { c.AbortWithError(http.StatusInternalServerError, errors.New("payment gateway not supported: "+string(entry.Gateway))) return } } } } func CompletePayment(entryProvider *database.PaymentEntryProvider, paymentGateways map[state.PaymentGateway]providers.PaymentProvider) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) entry, err := entryProvider.FetchById(id) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } if paymentGateway, ok := paymentGateways[entry.Gateway]; ok { amount, err := fetchAmount(c.PostForm("amount")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } slog.Info("completing payment with amount", "entry_id", id.String(), "state", entry.State, "amount", float64(amount)/100.0) entry, err = paymentGateway.CompleteTransaction(entry, amount) if err == nil { entryProvider.UpdateEntry(entry) slog.Info("completed payment", "entry_id", id.String(), "state", entry.State) c.Redirect(http.StatusSeeOther, "/entries/"+id.String()) } else { c.AbortWithError(http.StatusInternalServerError, err) return } } else { if err != nil { c.AbortWithError(http.StatusInternalServerError, errors.New("payment gateway not supported: "+string(entry.Gateway))) return } } } } func GetEntry(provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) entry, err := provider.FetchById(id) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } c.HTML(200, "info.gohtml", gin.H{"Entry": entry}) } } func InitializePayment(entryProvider *database.PaymentEntryProvider, paymentGateways map[state.PaymentGateway]providers.PaymentProvider) gin.HandlerFunc { return func(c *gin.Context) { gateway, err := fetchGateway(c.Param("gateway")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } if paymentGateway, contains := paymentGateways[gateway]; contains { amount, err := fetchAmount(c.Query("amount")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } entry, err := entryProvider.CreateEntry(database.PaymentEntry{ Gateway: gateway, State: state.StatePreinitialized, TotalAmount: amount, }) slog.Info("creating payment", "entry_id", entry.Id.String(), "state", entry.State, "gateway", gateway, "amount", float64(amount)/100.0) if entry, url, err := paymentGateway.CreatePaymentUrl(entry); err == nil { slog.Info("created redirect url", "entry_id", entry.Id.String(), "state", entry.State) entryProvider.UpdateEntry(entry) c.Redirect(http.StatusSeeOther, url) } else { c.AbortWithError(http.StatusBadRequest, err) return } } else { c.AbortWithError(http.StatusBadRequest, errors.New("unsupported payment gateway: "+string(gateway))) return } } } func GetGateways(gateways map[state.PaymentGateway]providers.PaymentProvider) gin.HandlerFunc { return 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, "Gateways": mapGateways(gateways)}) } } func GetIndex(provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { entries, _ := provider.FetchAll() c.HTML(200, "index.gohtml", gin.H{"Entries": entries}) } } func VivaOnFailure(vivaService viva.Service, provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { url, err := vivaService.HandleResponse(c, provider, state.StateError) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func VivaOnSuccess(vivaService viva.Service, provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { url, err := vivaService.HandleResponse(c, provider, state.StateAccepted) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func StripeOnFailure(stripeService stripe2.Service, provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { url, err := stripeService.HandleResponse(c, provider, state.StateError) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func StripeOnSuccess(stripeService stripe2.Service, provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { url, err := stripeService.HandleResponse(c, provider, state.StateAccepted) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func WsPayOnFailure(wspayService wspay2.Service, provider *database.PaymentEntryProvider, finalState state.PaymentState) gin.HandlerFunc { return func(c *gin.Context) { url, err := wspayService.HandleErrorResponse(c, provider, finalState) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func WsPayOnSuccess(wspayService wspay2.Service, provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { url, err := wspayService.HandleSuccessResponse(c, provider) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func MockOnFailure(mockService mock.Service, provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { url, err := mockService.HandleResponse(c, provider, state.StateError) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func MockOnSuccess(mockService mock.Service, provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { url, err := mockService.HandleResponse(c, provider, state.StateAccepted) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, url) } } func MockOpenGateway(provider *database.PaymentEntryProvider) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) entry, err := provider.FetchById(id) if err != nil { c.AbortWithError(http.StatusBadRequest, err) return } c.HTML(http.StatusOK, "mock_gateway.gohtml", gin.H{"Entry": entry}) } } func mapGateways(gateways map[state.PaymentGateway]providers.PaymentProvider) map[string]string { providerMap := map[string]string{} for key := range gateways { providerMap[string(key)] = mapGatewayName(key) } return providerMap } func mapGatewayName(key state.PaymentGateway) string { switch key { case state.GatewayStripe: return "Stripe" case state.GatewayVivaWallet: return "Viva wallet" case state.GatewayWsPay: return "WsPay" case state.GatewayMock: return "mock" } return "" } func fetchGateway(gateway string) (state.PaymentGateway, error) { switch gateway { case string(state.GatewayWsPay): return state.GatewayWsPay, nil case string(state.GatewayStripe): return state.GatewayStripe, nil case string(state.GatewayVivaWallet): return state.GatewayVivaWallet, nil case string(state.GatewayMock): return state.GatewayMock, nil } return "", errors.New("unknown gateway: " + gateway) } func fetchAmount(amount string) (int64, error) { if amount, err := strconv.ParseFloat(amount, 64); err == nil { return int64(amount * 100), nil } else { return 0, err } }