132 lines
3.2 KiB
Go
132 lines
3.2 KiB
Go
package payout
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"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"
|
|
yookassaConf "payouts/internal/service/yookassa/config"
|
|
)
|
|
|
|
const (
|
|
BaseRoute = "/payout"
|
|
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
|
|
yookassaConf yookassaConf.YooKassa
|
|
}
|
|
|
|
// 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,
|
|
yookassaConf: p.YooKassa.GetConfig(),
|
|
}, 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
|
|
|
|
}
|
|
|
|
func (p *payoutHandler) checkAllowedIpCallback(ipStr string) bool {
|
|
ipWithoutPort, _, _ := net.SplitHostPort(ipStr)
|
|
|
|
ip := net.ParseIP(ipWithoutPort)
|
|
if ip == nil {
|
|
slog.Error(fmt.Sprintf("Invalid IP: %s", ipStr))
|
|
return false
|
|
}
|
|
for _, subnetStr := range p.yookassaConf.AllowedCallbackSubnets {
|
|
_, ipNet, err := net.ParseCIDR(subnetStr)
|
|
if err != nil {
|
|
slog.Error(fmt.Sprintf("Invalid subnet CIDR: %v", err))
|
|
continue
|
|
}
|
|
if ipNet.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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("unauthorized", err, http.StatusUnauthorized)
|
|
}
|
|
|
|
panic("unimplemented")
|
|
}
|
|
|
|
// PaymentCallback implements [Handler].
|
|
func (p *payoutHandler) PayoutCallback(w http.ResponseWriter, r *http.Request) {
|
|
inData := map[string]any{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
decoder.Decode(&inData)
|
|
|
|
// todo: check also the X-real-ip and/or X-Forwarded-For
|
|
if !p.checkAllowedIpCallback(r.RemoteAddr) {
|
|
slog.Error(fmt.Sprintf("Callback came from unallowed ip: %s", r.RemoteAddr))
|
|
http.Error(w, "unallowed", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
slog.Info(fmt.Sprintf("Received callback from %s with object %v with headers %v", r.RemoteAddr, inData, r.Header))
|
|
}
|