diff --git a/payment/api/api.go b/payment/api/api.go index a5b1415f..4d12bc6a 100644 --- a/payment/api/api.go +++ b/payment/api/api.go @@ -2,11 +2,11 @@ package api import ( db "wss-payment/internal/database" - "wss-payment/pkg/idpay" + "wss-payment/pkg/payment" ) // API contains the data needed to operate the endpoints type API struct { Database db.PaymentDatabase - PaymentService idpay.PaymentService + PaymentService payment.Service } diff --git a/payment/api/idpay.go b/payment/api/idpay.go index e0cd3929..c78cd9d4 100644 --- a/payment/api/idpay.go +++ b/payment/api/idpay.go @@ -8,7 +8,7 @@ import ( "gorm.io/gorm" "net/http" "wss-payment/internal/database" - "wss-payment/pkg/idpay" + "wss-payment/pkg/payment" ) // CreateTransaction initiates a transaction for a user @@ -21,6 +21,7 @@ func (api *API) CreateTransaction(c *gin.Context) { return } logger := log.WithField("body", body) + logger.Trace("creating transaction") // If buying goods is zero, something's up... if len(body.BuyingGoods) == 0 { logger.Warn("empty buying_goods") @@ -32,25 +33,26 @@ func (api *API) CreateTransaction(c *gin.Context) { for i := range body.BuyingGoods { goods[i] = database.Good{Name: body.BuyingGoods[i]} } - payment := database.Payment{ + databasePayment := database.Payment{ UserID: body.UserID, ToPayAmount: body.ToPayAmount, Discount: body.Discount, Description: body.Description, BoughtGoods: goods, } - err = api.Database.InitiateTransaction(&payment) + err = api.Database.InitiateTransaction(&databasePayment) if err != nil { logger.WithError(err).Error("cannot put the transaction in database") c.JSON(http.StatusInternalServerError, "cannot put the transaction in database: "+err.Error()) return } - // Initiate the request in idpay - idpayResult, err := api.PaymentService.CreateTransaction(c.Request.Context(), idpay.TransactionCreationRequest{ - OrderID: payment.OrderID.String(), - Name: body.Name, - Phone: body.Phone, - Mail: body.Mail, + logger = logger.WithField("orderID", databasePayment.OrderID) + logger.Debug("created transaction in database") + // Initiate the request in payment service + paymentResult, err := api.PaymentService.CreateTransaction(c.Request.Context(), payment.TransactionCreationRequest{ + OrderID: databasePayment.OrderID.String(), + UsersPhone: body.Phone, + UsersMail: body.Mail, Description: body.Description, Callback: body.CallbackURL, Amount: body.ToPayAmount, @@ -59,16 +61,16 @@ func (api *API) CreateTransaction(c *gin.Context) { logger.WithError(err).Error("cannot start idpay transaction") c.JSON(http.StatusInternalServerError, "cannot start idpay transaction: "+err.Error()) // Mark the transaction in database as failed - api.Database.MarkAsFailed(payment.OrderID) + api.Database.MarkAsFailed(databasePayment.OrderID) return } // Now we return back the order ID and link and stuff to the other service c.JSON(http.StatusCreated, createTransactionResponse{ - OrderID: payment.OrderID, - ID: idpayResult.ID, - RedirectURL: idpayResult.Link, + OrderID: databasePayment.OrderID, + ID: paymentResult.ServiceOrderID, + RedirectURL: paymentResult.RedirectLink, }) - return + logger.WithField("result", paymentResult).Info("created transaction") } // GetTransaction verifies a transaction if not already and then returns the transaction @@ -87,9 +89,10 @@ func (api *API) GetTransaction(c *gin.Context) { return } logger := log.WithField("OrderID", body.OrderID) + logger.Trace("getting transaction") // Now get the transaction from database - payment := database.Payment{OrderID: orderUUID} - err = api.Database.GetPayment(&payment) + databasePayment := database.Payment{OrderID: orderUUID} + err = api.Database.GetPayment(&databasePayment) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { logger.Warn("payment not found") @@ -101,22 +104,24 @@ func (api *API) GetTransaction(c *gin.Context) { return } // Check if it's verified and verify it - if payment.PaymentStatus == database.PaymentStatusInitiated { + if databasePayment.PaymentStatus == database.PaymentStatusInitiated { + logger.Debug("verifying transaction") // Send the verification request - result, err := api.PaymentService.VerifyTransaction(c.Request.Context(), idpay.TransactionVerificationRequest{ - OrderID: payment.OrderID.String(), - ID: payment.ID.String, + result, err := api.PaymentService.VerifyTransaction(c.Request.Context(), payment.TransactionVerificationRequest{ + OrderID: databasePayment.OrderID.String(), + ServiceOrderID: databasePayment.ServiceOrderID.String, }) if err != nil { logger.WithError(err).Error("cannot verify transaction") c.JSON(http.StatusInternalServerError, "cannot verify transaction: "+err.Error()) return } + logger.WithField("verification-result", result).Info("transaction verification complete") // Check the result and update database if result.PaymentOK { - err = api.Database.MarkPaymentAsOK(&payment, result.TrackID, result.PaymentTrackID) + err = api.Database.MarkPaymentAsOK(&databasePayment, result.TrackID) } else { - err = api.Database.MarkPaymentAsFailed(&payment) + err = api.Database.MarkPaymentAsFailed(&databasePayment) } if err != nil { // NIGGA @@ -127,21 +132,20 @@ func (api *API) GetTransaction(c *gin.Context) { } // Return the converted struct result := getTransactionResponse{ - OrderID: payment.OrderID, - UserID: payment.UserID, - ToPayAmount: payment.ToPayAmount, - Discount: payment.Discount, - Description: payment.Description, - ID: payment.ID.String, - TrackID: payment.TrackID.String, - PaymentTrackID: payment.PaymentTrackID.String, - PaymentStatus: payment.PaymentStatus, - BoughtGoods: make([]string, len(payment.BoughtGoods)), - CreatedAt: payment.CreatedAt, - VerifiedAt: payment.VerifiedAt.Time, + OrderID: databasePayment.OrderID, + UserID: databasePayment.UserID, + ToPayAmount: databasePayment.ToPayAmount, + Discount: databasePayment.Discount, + Description: databasePayment.Description, + ID: databasePayment.ServiceOrderID.String, + TrackID: databasePayment.TrackID.String, + PaymentStatus: databasePayment.PaymentStatus, + BoughtGoods: make([]string, len(databasePayment.BoughtGoods)), + CreatedAt: databasePayment.CreatedAt, + VerifiedAt: databasePayment.VerifiedAt.Time, } - for i := range payment.BoughtGoods { - result.BoughtGoods[i] = payment.BoughtGoods[i].Name + for i := range databasePayment.BoughtGoods { + result.BoughtGoods[i] = databasePayment.BoughtGoods[i].Name } c.JSON(http.StatusOK, result) } diff --git a/payment/api/types.go b/payment/api/types.go index 819af930..72d5287d 100644 --- a/payment/api/types.go +++ b/payment/api/types.go @@ -73,12 +73,10 @@ type getTransactionResponse struct { Discount uint64 `json:"discount"` // An optional description about this payment Description string `json:"description,omitempty"` - // The ID which is returned from idpay after we have initiated the transaction + // The ID which is returned from payment service after we have initiated the transaction ID string `json:"id,omitempty"` - // The track ID which idpay returns to us after verification + // The track ID which payment service returns to us after verification TrackID string `json:"track_id,omitempty"` - // The payment track ID which idpay returns to us after verification - PaymentTrackID string `json:"payment_track_id,omitempty"` // What is the status of this payment? PaymentStatus database.PaymentStatus `json:"payment_status"` // List of the Goos which this user has bought in this payment diff --git a/payment/cmd/payment/main.go b/payment/cmd/payment/main.go index 01770d66..229e4bbb 100644 --- a/payment/cmd/payment/main.go +++ b/payment/cmd/payment/main.go @@ -14,13 +14,14 @@ import ( "wss-payment/api" db "wss-payment/internal/database" "wss-payment/pkg/idpay" + "wss-payment/pkg/payment" ) func main() { // Create the data needed endpointApi := new(api.API) endpointApi.Database = setupDatabase() - endpointApi.PaymentService = idpay.Mock{ // TODO: remove + endpointApi.PaymentService = payment.ServiceMock{ // TODO: remove FailCreation: false, FailVerification: false, PaymentVerificationOk: false, diff --git a/payment/go.mod b/payment/go.mod index 8b71b523..2bd21026 100644 --- a/payment/go.mod +++ b/payment/go.mod @@ -6,6 +6,7 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/go-faster/errors v0.7.1 github.com/google/uuid v1.5.0 + github.com/sirupsen/logrus v1.9.3 gorm.io/driver/postgres v1.5.4 gorm.io/gorm v1.25.5 ) @@ -35,15 +36,14 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.6.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/payment/go.sum b/payment/go.sum index 898b752b..e1765c04 100644 --- a/payment/go.sum +++ b/payment/go.sum @@ -31,7 +31,6 @@ github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqR github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -92,26 +91,25 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= -golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/payment/internal/database/payment.go b/payment/internal/database/payment.go index fac12e31..eaad951e 100644 --- a/payment/internal/database/payment.go +++ b/payment/internal/database/payment.go @@ -29,21 +29,19 @@ func (db PaymentDatabase) GetPayment(payment *Payment) error { return db.db.Model(&Payment{}).Preload("BoughtGoods").First(payment).Error } -// MarkPaymentAsOK will mark a payment as successful and then updates its track ID, payment track ID and verified at time. +// MarkPaymentAsOK will mark a payment as successful and then updates its track ID and verified at time. // This function will also update the values in payment argument to the ones which will be inserted in database. -func (db PaymentDatabase) MarkPaymentAsOK(payment *Payment, trackID string, paymentTrackID string) error { +func (db PaymentDatabase) MarkPaymentAsOK(payment *Payment, trackID string) error { verifiedAt := sql.NullTime{Valid: true, Time: time.Now()} err := db.db.Model(&Payment{OrderID: payment.OrderID}).Updates(&Payment{ - TrackID: sql.NullString{Valid: true, String: trackID}, - PaymentTrackID: sql.NullString{Valid: true, String: paymentTrackID}, - PaymentStatus: PaymentStatusSuccess, - VerifiedAt: verifiedAt, + TrackID: sql.NullString{Valid: true, String: trackID}, + PaymentStatus: PaymentStatusSuccess, + VerifiedAt: verifiedAt, }).Error if err != nil { return err } payment.TrackID = sql.NullString{Valid: true, String: trackID} - payment.PaymentTrackID = sql.NullString{Valid: true, String: paymentTrackID} payment.PaymentStatus = PaymentStatusSuccess payment.VerifiedAt = verifiedAt return nil diff --git a/payment/internal/database/types.go b/payment/internal/database/types.go index e15870ef..25bc20c5 100644 --- a/payment/internal/database/types.go +++ b/payment/internal/database/types.go @@ -43,12 +43,10 @@ type Payment struct { Discount uint64 `gorm:"not null"` // An optional description about this payment Description string `gorm:"not null"` - // The ID which is returned from idpay after we have initiated the transaction - ID sql.NullString - // The track ID which idpay returns to us after verification + // The Order ID which is returned from payment service after we have initiated the transaction + ServiceOrderID sql.NullString + // The track ID which payment service returns to us after verification TrackID sql.NullString - // The payment track ID which idpay returns to us after verification - PaymentTrackID sql.NullString // What is the status of this payment? PaymentStatus PaymentStatus `gorm:"not null"` // List of the Goos which this user has bought in this payment diff --git a/payment/pkg/idpay/mock.go b/payment/pkg/idpay/mock.go deleted file mode 100644 index 273b01a8..00000000 --- a/payment/pkg/idpay/mock.go +++ /dev/null @@ -1,38 +0,0 @@ -package idpay - -import ( - "context" - "github.com/go-faster/errors" - "wss-payment/pkg/util" -) - -// Mock is a mock for IDPay -type Mock struct { - FailCreation bool - FailVerification bool - PaymentVerificationOk bool -} - -func (idpay Mock) CreateTransaction(context.Context, TransactionCreationRequest) (TransactionCreationResult, error) { - if idpay.FailCreation { - return TransactionCreationResult{}, errors.New("mock failed!") - } - id := util.RandomID() - return TransactionCreationResult{ - ID: id, - Link: "https://example.com/" + id, - }, nil - -} - -// VerifyTransaction will verify a previously made transaction and report errors if there was a problem with it -func (idpay Mock) VerifyTransaction(context.Context, TransactionVerificationRequest) (TransactionVerificationResult, error) { - if idpay.FailVerification { - return TransactionVerificationResult{}, errors.New("mock failed!") - } - return TransactionVerificationResult{ - TrackID: util.RandomID(), - PaymentTrackID: util.RandomID(), - PaymentOK: idpay.PaymentVerificationOk, - }, nil -} diff --git a/payment/pkg/payment/mock.go b/payment/pkg/payment/mock.go new file mode 100644 index 00000000..76b46652 --- /dev/null +++ b/payment/pkg/payment/mock.go @@ -0,0 +1,37 @@ +package payment + +import ( + "context" + "github.com/go-faster/errors" + "wss-payment/pkg/util" +) + +// ServiceMock is a mock for a payment service +type ServiceMock struct { + FailCreation bool + FailVerification bool + PaymentVerificationOk bool +} + +func (service ServiceMock) CreateTransaction(context.Context, TransactionCreationRequest) (TransactionCreationResult, error) { + if service.FailCreation { + return TransactionCreationResult{}, errors.New("mock failed!") + } + id := util.RandomID() + return TransactionCreationResult{ + ServiceOrderID: id, + RedirectLink: "https://example.com/" + id, + }, nil + +} + +// VerifyTransaction will verify a previously made transaction and report errors if there was a problem with it +func (service ServiceMock) VerifyTransaction(context.Context, TransactionVerificationRequest) (TransactionVerificationResult, error) { + if service.FailVerification { + return TransactionVerificationResult{}, errors.New("mock failed!") + } + return TransactionVerificationResult{ + TrackID: util.RandomID(), + PaymentOK: service.PaymentVerificationOk, + }, nil +} diff --git a/payment/pkg/idpay/payment.go b/payment/pkg/payment/payment.go similarity index 61% rename from payment/pkg/idpay/payment.go rename to payment/pkg/payment/payment.go index 72ce565a..853d0d8a 100644 --- a/payment/pkg/idpay/payment.go +++ b/payment/pkg/payment/payment.go @@ -1,9 +1,11 @@ -package idpay +package payment -import "context" +import ( + "context" +) -// PaymentService should represent a payment service just like IDPay -type PaymentService interface { +// Service should represent a payment service just like IDPay +type Service interface { CreateTransaction(context.Context, TransactionCreationRequest) (TransactionCreationResult, error) VerifyTransaction(context.Context, TransactionVerificationRequest) (TransactionVerificationResult, error) } diff --git a/payment/pkg/payment/types.go b/payment/pkg/payment/types.go new file mode 100644 index 00000000..441342e5 --- /dev/null +++ b/payment/pkg/payment/types.go @@ -0,0 +1,33 @@ +package payment + +type TransactionCreationRequest struct { + // This must be a randomly generated ID + OrderID string + UsersPhone string + UsersMail string + Description string + // Where should the user redirect after the payment is done + Callback string + Amount uint64 +} + +type TransactionCreationResult struct { + // An ID which is returned from the payment service + ServiceOrderID string + // Where we should redirect the user? + RedirectLink string +} + +type TransactionVerificationRequest struct { + // The order ID which we stored in our database + OrderID string + // The ID which the payment service returned when we created this order + ServiceOrderID string +} + +type TransactionVerificationResult struct { + // The track ID which we can track this verification + TrackID string + // Was the payment OK? + PaymentOK bool +}