From 970e64745b1d233a19db1e36c1e0cdb005223300 Mon Sep 17 00:00:00 2001 From: alxeg Date: Fri, 13 Mar 2026 00:07:23 +0300 Subject: [PATCH] Reorganaze modules, add auth processing. --- cmd/payouts/main.go | 2 + config/payouts.properties | 2 + internal/api/module.go | 16 ++-- internal/api/payment/module.go | 17 ---- internal/api/payment/payment_handler.go | 54 ----------- internal/api/payout/module.go | 17 ++++ internal/api/payout/payment_handler.go | 91 +++++++++++++++++++ internal/models/payout.go | 46 ++++++++++ internal/service/database/db_service.go | 13 ++- internal/service/database/orm/payout.go | 1 + internal/service/yookassa/config/yookassa.go | 4 + internal/service/yookassa/module.go | 3 + .../yookassa-openapi-specification.yaml | 2 +- internal/service/yookassa/yookassa_service.go | 49 +++++++--- 14 files changed, 220 insertions(+), 97 deletions(-) delete mode 100644 internal/api/payment/module.go delete mode 100644 internal/api/payment/payment_handler.go create mode 100644 internal/api/payout/module.go create mode 100644 internal/api/payout/payment_handler.go create mode 100644 internal/models/payout.go create mode 100644 internal/service/database/orm/payout.go diff --git a/cmd/payouts/main.go b/cmd/payouts/main.go index bde76c5..95aad10 100644 --- a/cmd/payouts/main.go +++ b/cmd/payouts/main.go @@ -12,6 +12,7 @@ import ( "payouts/internal/log" "payouts/internal/service/cache" "payouts/internal/service/database" + "payouts/internal/service/yookassa" ) func main() { @@ -21,6 +22,7 @@ func main() { cache.Module, config.Module, database.Module, + yookassa.Module, log.Module, fx.WithLogger(func() fxevent.Logger { diff --git a/config/payouts.properties b/config/payouts.properties index cb9fa7f..cec6667 100644 --- a/config/payouts.properties +++ b/config/payouts.properties @@ -38,6 +38,8 @@ Cache.TTL = 24h # Yookassa related props # Base API Url YooKassa.BaseUrl = https://api.yookassa.ru/v3 +YooKassa.Timeout = 30s +YooKassa.Test = false # Base API key/secret YooKassa.ApiBaseKey = YooKassa.ApiBaseSecret = diff --git a/internal/api/module.go b/internal/api/module.go index 09d981c..bf2b71a 100644 --- a/internal/api/module.go +++ b/internal/api/module.go @@ -11,7 +11,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/fx" - "payouts/internal/api/payment" + "payouts/internal/api/payout" "payouts/internal/api/user" "payouts/internal/api/version" appConfig "payouts/internal/config" @@ -21,7 +21,7 @@ import ( // Module is a fx module var Module = fx.Options( user.Module, - payment.Module, + payout.Module, version.Module, monitoring.Module, @@ -37,9 +37,9 @@ type Params struct { AppConfig *appConfig.App - PaymentHandler payment.Handler - UserHandler user.Handler - Version version.Handler + PayoutHandler payout.Handler + UserHandler user.Handler + Version version.Handler Metrics monitoring.Metrics } @@ -66,9 +66,9 @@ func RegisterRoutes(p Params, lc fx.Lifecycle) { userRouter.HandleFunc(user.RegisterRoute, p.UserHandler.UserRegister).Methods(http.MethodPost) userRouter.HandleFunc(user.LoginRoute, p.UserHandler.UserLogin).Methods(http.MethodPost) - paymentRouter := apiRouter.PathPrefix(payment.BaseRoute).Subrouter() - paymentRouter.HandleFunc(payment.CreateRoute, p.PaymentHandler.PaymentCreate).Methods(http.MethodPost) - paymentRouter.HandleFunc(payment.CallbackRoute, p.PaymentHandler.PaymentCallback).Methods(http.MethodPost) + payoutRouter := apiRouter.PathPrefix(payout.BaseRoute).Subrouter() + payoutRouter.HandleFunc(payout.CreateRoute, p.PayoutHandler.PayoutCreate).Methods(http.MethodPost) + payoutRouter.HandleFunc(payout.CallbackRoute, p.PayoutHandler.PayoutCallback).Methods(http.MethodPost) // collect api metrics apiRouter.Use(p.Metrics.GetMiddleware()) diff --git a/internal/api/payment/module.go b/internal/api/payment/module.go deleted file mode 100644 index b41fc71..0000000 --- a/internal/api/payment/module.go +++ /dev/null @@ -1,17 +0,0 @@ -package payment - -import ( - "net/http" - - "go.uber.org/fx" -) - -var Module = fx.Options( - fx.Provide(NewPaymentHandler), -) - -type Handler interface { - GetSbpBanks(http.ResponseWriter, *http.Request) - PaymentCreate(http.ResponseWriter, *http.Request) - PaymentCallback(http.ResponseWriter, *http.Request) -} diff --git a/internal/api/payment/payment_handler.go b/internal/api/payment/payment_handler.go deleted file mode 100644 index 9f5e8a5..0000000 --- a/internal/api/payment/payment_handler.go +++ /dev/null @@ -1,54 +0,0 @@ -package payment - -import ( - "net/http" - - "go.uber.org/fx" - - "payouts/internal/config" - "payouts/internal/service/cache" - "payouts/internal/service/database" -) - -const ( - BaseRoute = "/payment" - CreateRoute = "/create" - CallbackRoute = "/callback" - BanksRoute = "/sbp/banks" -) - -type paymentHandler struct { - dbService database.Service - cacheService cache.Service -} - -// Params represents the module input params -type Params struct { - fx.In - - AppConfig *config.App - DbService database.Service - CacheService cache.Service -} - -func NewPaymentHandler(p Params) (Handler, error) { - return &paymentHandler{ - dbService: p.DbService, - cacheService: p.CacheService, - }, nil -} - -// GetSbpBanks implements [Handler]. -func (p *paymentHandler) GetSbpBanks(http.ResponseWriter, *http.Request) { - panic("unimplemented") -} - -// PaymentCreate implements [Handler]. -func (p *paymentHandler) PaymentCreate(http.ResponseWriter, *http.Request) { - panic("unimplemented") -} - -// PaymentCallback implements [Handler]. -func (p *paymentHandler) PaymentCallback(http.ResponseWriter, *http.Request) { - panic("unimplemented") -} diff --git a/internal/api/payout/module.go b/internal/api/payout/module.go new file mode 100644 index 0000000..c90f5ee --- /dev/null +++ b/internal/api/payout/module.go @@ -0,0 +1,17 @@ +package payout + +import ( + "net/http" + + "go.uber.org/fx" +) + +var Module = fx.Options( + fx.Provide(NewPayoutHandler), +) + +type Handler interface { + GetSbpBanks(http.ResponseWriter, *http.Request) + PayoutCreate(http.ResponseWriter, *http.Request) + PayoutCallback(http.ResponseWriter, *http.Request) +} diff --git a/internal/api/payout/payment_handler.go b/internal/api/payout/payment_handler.go new file mode 100644 index 0000000..66bc268 --- /dev/null +++ b/internal/api/payout/payment_handler.go @@ -0,0 +1,91 @@ +package payout + +import ( + "errors" + "net/http" + "regexp" + + "go.uber.org/fx" + + "payouts/internal/config" + "payouts/internal/service/cache" + "payouts/internal/service/database" + "payouts/internal/service/database/orm" + "payouts/internal/service/yookassa" +) + +const ( + BaseRoute = "/payment" + CreateRoute = "/create" + CallbackRoute = "/callback" + BanksRoute = "/sbp/banks" +) + +var authHeaderRe = regexp.MustCompile(`^Bearer\s+(.*)$`) + +type payoutHandler struct { + dbService database.Service + cacheService cache.Service + yooKassa yookassa.Service +} + +// Params represents the module input params +type Params struct { + fx.In + + AppConfig *config.App + DbService database.Service + YooKassa yookassa.Service + CacheService cache.Service +} + +func NewPayoutHandler(p Params) (Handler, error) { + return &payoutHandler{ + dbService: p.DbService, + cacheService: p.CacheService, + yooKassa: p.YooKassa, + }, nil +} + +func (p *payoutHandler) getSession(r *http.Request) (*orm.User, error) { + authHeaderValue := r.Header.Get("Authorization") + if len(authHeaderValue) == 0 { + return nil, errors.New("no valid auth header") + } + + matches := authHeaderRe.FindStringSubmatch(authHeaderValue) + if matches == nil { + return nil, errors.New("no valid auth header") + } + + sessionId := matches[1] + userSession, err := p.cacheService.GetSession(sessionId) + if err != nil { + return nil, errors.New("session not found") + } + return &userSession, nil + +} + +// GetSbpBanks implements [Handler]. +func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) { + panic("unimplemented") +} + +// PaymentCreate implements [Handler]. +func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) { + errResponse := func(message string, err error, status int) { + http.Error(w, errors.Join(errors.New(message), err).Error(), status) + } + + _, err := p.getSession(r) + if err != nil { + errResponse("unautiorized", err, http.StatusUnauthorized) + } + + panic("unimplemented") +} + +// PaymentCallback implements [Handler]. +func (p *payoutHandler) PayoutCallback(http.ResponseWriter, *http.Request) { +} diff --git a/internal/models/payout.go b/internal/models/payout.go new file mode 100644 index 0000000..9d04567 --- /dev/null +++ b/internal/models/payout.go @@ -0,0 +1,46 @@ +package models + +import ( + "fmt" + "strings" +) + +type PayoutType int64 + +const ( + SBP PayoutType = iota + YooMoney +) + +func (r PayoutType) String() string { + switch r { + case SBP: + return "spb" + case YooMoney: + return "yoo_money" + } + return "unknown" +} + +func (r PayoutType) MarshalText() (text []byte, err error) { + return []byte(r.String()), nil +} + +func (r *PayoutType) UnmarshalText(text []byte) (err error) { + s := strings.ToLower(string(text)) + switch s { + case "spb": + *r = SBP + case "yoo_money": + *r = YooMoney + default: + err = fmt.Errorf("invalid payment type: %s", s) + } + return err +} + +type PayoutReq struct { + PayoutType PayoutType `json:"payout_type"` + AccountNumber string `json:"account_number"` + Amount float32 `json:"amount"` +} diff --git a/internal/service/database/db_service.go b/internal/service/database/db_service.go index 9b52d82..dc8327b 100644 --- a/internal/service/database/db_service.go +++ b/internal/service/database/db_service.go @@ -17,6 +17,7 @@ import ( type dbService struct { dbType string db *gorm.DB + ctx context.Context } func NewDatabaseService(conf config.Database) (Service, error) { @@ -51,7 +52,9 @@ func NewDatabaseService(conf config.Database) (Service, error) { db.DB() db.AutoMigrate(&orm.User{}) } - result := &dbService{} + result := &dbService{ + ctx: context.Background(), + } result.dbType = conf.Type result.db = db @@ -59,9 +62,9 @@ func NewDatabaseService(conf config.Database) (Service, error) { } -func getParams(options ...Optional) *params { +func (d *dbService) getParams(options ...Optional) *params { params := ¶ms{ - ctx: context.Background(), + ctx: d.ctx, } for _, opt := range options { opt(params) @@ -71,14 +74,14 @@ func getParams(options ...Optional) *params { // AddUser implements [Service]. func (d *dbService) CreateUser(userModel orm.User, opts ...Optional) error { - p := getParams(opts...) + p := d.getParams(opts...) return gorm.G[orm.User](d.db).Create(p.ctx, &userModel) } // GetUser implements [Service]. func (d *dbService) GetUser(userModel orm.User, opts ...Optional) (orm.User, error) { - p := getParams(opts...) + p := d.getParams(opts...) userResp, err := gorm.G[orm.User](d.db).Where(&userModel).First(p.ctx) return userResp, err diff --git a/internal/service/database/orm/payout.go b/internal/service/database/orm/payout.go new file mode 100644 index 0000000..5800656 --- /dev/null +++ b/internal/service/database/orm/payout.go @@ -0,0 +1 @@ +package orm diff --git a/internal/service/yookassa/config/yookassa.go b/internal/service/yookassa/config/yookassa.go index 80011e7..26ab533 100644 --- a/internal/service/yookassa/config/yookassa.go +++ b/internal/service/yookassa/config/yookassa.go @@ -1,7 +1,11 @@ package config +import "time" + type YooKassa struct { BaseUrl string + Timeout time.Duration + Test bool ApiBaseKey string ApiBaseSecret string diff --git a/internal/service/yookassa/module.go b/internal/service/yookassa/module.go index c1433bb..6a0fdd1 100644 --- a/internal/service/yookassa/module.go +++ b/internal/service/yookassa/module.go @@ -6,6 +6,8 @@ import ( "go.uber.org/fx" "payouts/internal/config" + "payouts/internal/models" + "payouts/internal/service/database/orm" ) var Module = fx.Options( @@ -24,6 +26,7 @@ func WithContext(ctx context.Context) Optional { } type Service interface { + CreatePayout(models.PayoutReq, *orm.User, ...Optional) } type Param struct { diff --git a/internal/service/yookassa/yookassa-openapi-specification.yaml b/internal/service/yookassa/yookassa-openapi-specification.yaml index caab56e..e850627 100644 --- a/internal/service/yookassa/yookassa-openapi-specification.yaml +++ b/internal/service/yookassa/yookassa-openapi-specification.yaml @@ -46,7 +46,7 @@ paths: application/json: schema: type: "object" - title: "CreatePaymentRequest" + title: "CreatePayoutRequest" properties: amount: allOf: diff --git a/internal/service/yookassa/yookassa_service.go b/internal/service/yookassa/yookassa_service.go index bdec759..bd8e5f9 100644 --- a/internal/service/yookassa/yookassa_service.go +++ b/internal/service/yookassa/yookassa_service.go @@ -2,6 +2,9 @@ package yookassa import ( "context" + "net/http" + "payouts/internal/models" + "payouts/internal/service/database/orm" "payouts/internal/service/yookassa/config" "payouts/internal/service/yookassa/gen" @@ -11,6 +14,36 @@ import ( type yookassaService struct { conf config.YooKassa payClient *gen.Client + ctx context.Context +} + +func NewYookassaService(conf config.YooKassa) (Service, error) { + + svc := &yookassaService{ + conf: conf, + ctx: context.Background(), + } + payClient, err := gen.NewClient(conf.BaseUrl, svc, gen.WithClient(&http.Client{ + Timeout: conf.Timeout, + })) + + if err != nil { + return nil, err + } + svc.payClient = payClient + + // payClient.PaymentsPost() + return svc, nil +} + +func (y *yookassaService) getParams(options ...Optional) *params { + params := ¶ms{ + ctx: y.ctx, + } + for _, opt := range options { + opt(params) + } + return params } // BasicAuth implements [gen.SecuritySource]. @@ -26,17 +59,9 @@ func (y *yookassaService) OAuth2(ctx context.Context, operationName gen.Operatio return gen.OAuth2{}, ogenerrors.ErrSkipClientSecurity } -func NewYookassaService(conf config.YooKassa) (Service, error) { +// CreatePayout implements [Service]. +func (y *yookassaService) CreatePayout(req models.PayoutReq, userSession *orm.User, opts ...Optional) { + params := y.getParams(opts...) - svc := &yookassaService{ - conf: conf, - } - payClient, err := gen.NewClient(conf.BaseUrl, svc) - if err != nil { - return nil, err - } - svc.payClient = payClient - - // payClient.PaymentsPost() - return svc, nil + y.payClient.PayoutsPost(params.ctx, &gen.PayoutRequest{}, gen.PayoutsPostParams{}) }