Added support for stripe

This commit is contained in:
Borna Rajković 2023-07-26 09:51:29 +02:00
parent 3de5c8b0f1
commit cefc5314f2
16 changed files with 365 additions and 72 deletions

View File

@ -37,3 +37,18 @@ CREATE TABLE IF NOT EXISTS "wspay"
PRIMARY KEY (id),
CONSTRAINT unique_id UNIQUE ("shopping_card_id")
);
CREATE TABLE IF NOT EXISTS "stripe"
(
"id" uuid NOT NULL,
"total_amount" int NOT NULL,
"lang" varchar(128) DEFAULT '',
"payment_intent_id" varchar(256) DEFAULT '',
"payment_state" varchar(256) DEFAULT '',
PRIMARY KEY (id)
);

View File

@ -2,7 +2,7 @@ version: '3.1'
services:
backend:
image: registry.bbr-dev.info/payment-poc/backend:latest
image: registry.s2internal.com/opgdirekt/payment-poc/backend:latest
restart: on-failure
depends_on:
- database

1
go.mod
View File

@ -26,6 +26,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/stripe/stripe-go/v72 v72.122.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect

9
go.sum
View File

@ -61,6 +61,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -68,6 +69,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stripe/stripe-go/v72 v72.122.0 h1:eRXWqnEwGny6dneQ5BsxGzUCED5n180u8n665JHlut8=
github.com/stripe/stripe-go/v72 v72.122.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
@ -75,14 +78,19 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
@ -92,6 +100,7 @@ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cn
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

211
main.go
View File

@ -6,10 +6,14 @@ import (
"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"
@ -27,8 +31,9 @@ func init() {
godotenv.Load()
BackendUrl = envMustExist("BACKEND_URL")
ShopId = envMustExist("SHOP_ID")
ShopSecret = envMustExist("SHOP_SECRET")
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)
@ -50,22 +55,22 @@ func main() {
"formatCurrency": func(current int64) string {
return fmt.Sprintf("%d,%02d", current/100, current%100)
},
"formatState": func(state wspay.PaymentState) string {
switch state {
case wspay.StateCanceled:
"formatState": func(stt state.PaymentState) string {
switch stt {
case state.StateCanceled:
return "Otkazano"
case wspay.StateAccepted:
case state.StateAccepted:
return "Prihvačeno"
case wspay.StateError:
case state.StateError:
return "Greška"
case wspay.StateInitialized:
case state.StateInitialized:
return "Inicijalna izrada"
case wspay.StateCanceledInitialization:
case state.StateCanceledInitialization:
return "Otkazano tijekom izrade"
case wspay.StateCompleted:
case state.StateCompleted:
return "Završeno"
}
return "nepoznato stanje '" + string(state) + "'"
return "nepoznato stanje '" + string(stt) + "'"
},
"omitempty": func(value string) string {
if value == "" {
@ -85,15 +90,151 @@ func main() {
wspayService := wspay.Service{
DB: client,
}
stripeService := stripe2.Service{
DB: client,
}
g.LoadHTMLGlob("./templates/*.gohtml")
g.GET("/", func(c *gin.Context) {
entries, err := wspayService.FetchAll()
log.Printf("%v", err)
c.HTML(200, "index.gohtml", gin.H{"Entries": entries})
wspayEntries, _ := wspayService.FetchAll()
stripeEntries, _ := stripeService.FetchAll()
c.HTML(200, "index.gohtml", gin.H{"WsPay": wspayEntries, "Stripe": stripeEntries})
})
g.GET("/initial", func(c *gin.Context) {
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})
})
}
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)
@ -113,15 +254,15 @@ func main() {
ShoppingCartID: entry.ShoppingCartID,
Version: "2.0",
TotalAmount: entry.TotalAmount,
ReturnURL: BackendUrl + "/initial/success",
ReturnErrorURL: BackendUrl + "/initial/error",
CancelURL: BackendUrl + "/initial/cancel",
ReturnURL: BackendUrl + "/wspay/success",
ReturnErrorURL: BackendUrl + "/wspay/error",
CancelURL: BackendUrl + "/wspay/cancel",
Signature: wspay.CalculateFormSignature(ShopId, ShopSecret, entry.ShoppingCartID, entry.TotalAmount),
}
c.HTML(200, "initial.gohtml", gin.H{"Action": wspay.AuthorisationForm, "Form": form})
c.HTML(200, "wspay.gohtml", gin.H{"Action": wspay.AuthorisationForm, "Form": form})
})
g.GET("/initial/success", func(c *gin.Context) {
g.GET("success", func(c *gin.Context) {
response := wspay.WsPayFormReturn{}
if err := c.ShouldBind(&response); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -161,7 +302,7 @@ func main() {
entry.ApprovalCode = response.ApprovalCode
entry.ErrorMessage = response.ErrorMessage
entry.State = wspay.StateAccepted
entry.State = state.StateAccepted
if err := wspayService.Update(entry); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -176,10 +317,7 @@ func main() {
c.Redirect(http.StatusTemporaryRedirect, "/")
}
})
g.GET("/iframe", func(c *gin.Context) {
c.HTML(200, "iframe_handler.gohtml", gin.H{})
})
g.GET("/initial/error", func(c *gin.Context) {
g.GET("error", func(c *gin.Context) {
response := wspay.WsPayFormError{}
if err := c.ShouldBind(&response); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -212,7 +350,7 @@ func main() {
entry.ErrorMessage = response.ErrorMessage
entry.ErrorCodes = response.ErrorCodes
entry.State = wspay.StateError
entry.State = state.StateError
if err := wspayService.Update(entry); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -234,9 +372,9 @@ func main() {
c.AbortWithError(http.StatusNotFound, err)
return
}
c.HTML(200, "info.gohtml", gin.H{"Entry": entry})
c.HTML(200, "wspay_info.gohtml", gin.H{"Entry": entry})
})
g.GET("/initial/cancel", func(c *gin.Context) {
g.GET("cancel", func(c *gin.Context) {
response := wspay.WsPayFormCancel{}
if err := c.ShouldBind(&response); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -249,7 +387,7 @@ func main() {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
entry.State = wspay.StateCanceledInitialization
entry.State = state.StateCanceledInitialization
if err := wspayService.Update(entry); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -264,19 +402,4 @@ func main() {
c.Redirect(http.StatusTemporaryRedirect, "/")
}
})
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
}

View File

@ -17,10 +17,10 @@ docker-dev:
docker-prod:
docker image build -t registry.bbr-dev.info/payment-poc/backend:$(VERSION) .
docker tag registry.bbr-dev.info/payment-poc/backend:$(VERSION) registry.bbr-dev.info/payment-poc/backend:latest
docker image push registry.bbr-dev.info/payment-poc/backend:$(VERSION)
docker image push registry.bbr-dev.info/payment-poc/backend:latest
docker image build -t registry.s2internal.com/opgdirekt/payment-poc/backend:$(VERSION) .
docker tag registry.s2internal.com/opgdirekt/payment-poc/backend:$(VERSION) registry.s2internal.com/opgdirekt/payment-poc/backend:latest
docker image push registry.s2internal.com/opgdirekt/payment-poc/backend:$(VERSION)
docker image push registry.s2internal.com/opgdirekt/payment-poc/backend:latest
release:
git tag $(VERSION)

18
state/model.go Normal file
View File

@ -0,0 +1,18 @@
package state
type PaymentState string
const (
// initial state
StateInitialized PaymentState = "initialized"
// state on response
StateAccepted PaymentState = "accepted"
StateError PaymentState = "error"
StateCanceledInitialization PaymentState = "canceled_initialization"
// state after confirmation
StateCompleted PaymentState = "completed"
StateVoided PaymentState = "voided"
StateCanceled PaymentState = "canceled"
)

16
stripe/model.go Normal file
View File

@ -0,0 +1,16 @@
package stripe
import (
"github.com/google/uuid"
"payment-poc/state"
)
type StripeDb struct {
Id uuid.UUID `db:"id"`
TotalAmount int64 `db:"total_amount"`
Lang string `db:"lang"`
PaymentIntentId string `db:"payment_intent_id"`
State state.PaymentState `db:"payment_state"`
}

46
stripe/service.go Normal file
View File

@ -0,0 +1,46 @@
package stripe
import (
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"payment-poc/state"
)
type Service struct {
DB *sqlx.DB
}
func (s *Service) CreateEntry(totalAmount int64) (StripeDb, error) {
id := uuid.Must(uuid.NewRandom())
entry := StripeDb{
Id: id,
TotalAmount: totalAmount,
State: state.StateInitialized,
}
_, err := s.DB.Exec(`INSERT INTO "stripe" ("id", "total_amount", "payment_state") VALUES ($1, $2, $3)`,
&entry.Id, &entry.TotalAmount, &entry.State,
)
if err != nil {
return StripeDb{}, err
}
return s.FetchById(id)
}
func (s *Service) FetchAll() ([]StripeDb, error) {
var entries []StripeDb
err := s.DB.Select(&entries, `SELECT * FROM "stripe"`)
return entries, err
}
func (s *Service) FetchById(id uuid.UUID) (StripeDb, error) {
entry := StripeDb{}
err := s.DB.Get(&entry, `SELECT * FROM "stripe" WHERE "id" = $1`, id)
return entry, err
}
func (s *Service) Update(entry StripeDb) error {
_, err := s.DB.Exec(`UPDATE "stripe" set "payment_intent_id" = $2, "payment_state" = $3 WHERE "id" = $1`,
&entry.Id, &entry.PaymentIntentId, &entry.State,
)
return err
}

View File

@ -13,6 +13,9 @@
tr > td:nth-child(2) {
text-align: right;
}
tr > th:nth-child(2) {
text-align: right;
}
td, th {
padding: 0 8px;
}
@ -24,7 +27,7 @@
<body class="container">
<h2>Novo plačanje</h2>
<form method="get" action="/initial">
<form method="get" action="/methods">
<div class="mb-3">
<label class="form-label" for="amount">Vrijednost</label>
<input class="form-control" id="amount" required name="amount" type="number" step="0.01" min="0">
@ -33,7 +36,7 @@
</form>
<div>
<h2>Plačanja</h2>
<h2>WsPay</h2>
<table class="table">
<thead>
<tr>
@ -43,9 +46,29 @@
</tr>
</thead>
<tbody>
{{range .Entries}}
{{range .WsPay}}
<tr>
<td><a class="link-primary" href="/info/{{.Id}}">{{.Id}}</a></td>
<td><a class="link-primary" href="/wspay/info/{{.Id}}">{{.Id}}</a></td>
<td>{{formatCurrency .TotalAmount}}</td>
<td>{{formatState .State}}</td>
</tr>
{{end}}
</tbody>
</table>
<h2>Stripe</h2>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Vrijednost</th>
<th>Stanje</th>
</tr>
</thead>
<tbody>
{{range .Stripe}}
<tr>
<td><a class="link-primary" href="/stripe/info/{{.Id}}">{{.Id}}</a></td>
<td>{{formatCurrency .TotalAmount}}</td>
<td>{{formatState .State}}</td>
</tr>

29
templates/methods.gohtml Normal file
View File

@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Index</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<style>
th {text-align: left}
tr > td:nth-child(2) {
text-align: right;
}
td, th {
padding: 0 8px;
}
h2 {
margin-top: 16px;
}
</style>
</head>
<body class="container">
<h2>Izaberi metodu plačanja</h2>
<a class="btn btn-success" href="/wspay?amount={{.Amount}}">WsPay</a>
<a class="btn btn-success" href="/stripe?amount={{.Amount}}">Stripe</a>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Info</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<style>
th {text-align: left}
h2 {
margin-top: 16px;
}
</style>
</head>
<body class="container">
<h2>Plačanje {{.Entry.Id}}</h2>
<table class="table">
<tr><th>Id: </th><td>{{.Entry.Id}}</td></tr>
<tr><th>Ukupna vrijednost: </th><td>{{formatCurrency .Entry.TotalAmount}}</td></tr>
<tr><th>Jezik: </th><td>{{omitempty .Entry.Lang}}</td></tr>
<tr><th>Stanje: </th><td>{{formatState .Entry.State}}</td></tr>
</table>
</body>
</html>

View File

@ -2,26 +2,10 @@ package wspay
import (
"github.com/google/uuid"
"payment-poc/state"
"time"
)
type PaymentState string
const (
// initial state
StateInitialized PaymentState = "initialized"
// state on response
StateAccepted PaymentState = "accepted"
StateError PaymentState = "error"
StateCanceledInitialization PaymentState = "canceled_initialization"
// state after confirmation
StateCompleted PaymentState = "completed"
StateVoided PaymentState = "voided"
StateCanceled PaymentState = "canceled"
)
type WsPayDb struct {
Id uuid.UUID `db:"id"`
ShopID string `db:"shop_id"`
@ -53,5 +37,5 @@ type WsPayDb struct {
ErrorMessage string `db:"error_message"`
ErrorCodes string `db:"error_codes"`
State PaymentState `db:"payment_state"`
State state.PaymentState `db:"payment_state"`
}

View File

@ -6,6 +6,7 @@ import (
"errors"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"payment-poc/state"
"strconv"
)
@ -20,7 +21,7 @@ func (s *Service) CreateEntry(shopId string, totalAmount int64) (WsPayDb, error)
ShopID: shopId,
ShoppingCartID: id.String(),
TotalAmount: totalAmount,
State: StateInitialized,
State: state.StateInitialized,
}
_, err := s.DB.Exec(`INSERT INTO "wspay" ("id", "shop_id", "shopping_card_id", "total_amount", "payment_state") VALUES ($1, $2, $3, $4, $5)`,
&entry.Id, &entry.ShopID, &entry.ShoppingCartID, &entry.TotalAmount, &entry.State,