333 lines
10 KiB
Go
333 lines
10 KiB
Go
|
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
|
||
|
}
|
||
|
}
|