package yookassa import ( "context" "errors" "fmt" "log/slog" "net/http" resty "github.com/go-resty/resty/v2" "payouts/internal/models" "payouts/internal/service/database/orm" "payouts/internal/service/yookassa/config" ) const IdempotenceHeader = "Idempotence-Key" type yookassaService struct { conf config.YooKassa ctx context.Context client *resty.Client } func NewYookassaService(conf config.YooKassa) (Service, error) { client := resty.New() client.SetBaseURL(conf.BaseUrl) client.SetBasicAuth(conf.ApiPaymentKey, conf.ApiPaymentSecret) client.SetTimeout(conf.Timeout) if conf.Retry.Enabled { client. SetRetryCount(conf.Retry.Count). SetRetryWaitTime(conf.Retry.WaitTime). SetRetryMaxWaitTime(conf.Retry.MaxWaitTime). AddRetryCondition( func(r *resty.Response, err error) bool { // Retry on network errors if err != nil { return true } // Retry on specific status codes return r.StatusCode() == 429 || // Too Many Requests r.StatusCode() >= 500 // Server errors }, ) } svc := &yookassaService{ conf: conf, client: client, ctx: context.Background(), } return svc, nil } func (y *yookassaService) getParams(options ...Optional) *params { params := ¶ms{ ctx: y.ctx, } for _, opt := range options { opt(params) } return params } // GetSbpBanks implements [Service]. func (y *yookassaService) GetSbpBanks(opts ...Optional) ([]models.SBPBank, error) { params := y.getParams(opts...) yResp := &SBPBankResponse{} yError := &Error{} restyResp, err := y.client.R(). SetContext(params.ctx). SetResult(yResp). SetError(yError). Get("/sbp_banks") slog.Debug(fmt.Sprintf("Got response from yookassa: %v", restyResp)) if err != nil { return nil, errors.Join(errors.New("failed to perform yookassa api post"), err) } if restyResp.StatusCode() != http.StatusOK { yError.Status = restyResp.StatusCode() return nil, yError } return yResp.Items, nil } // CreatePayout implements [Service]. func (y *yookassaService) CreatePayout(req models.PayoutReq, userSession *orm.User, idempotenceKey string, opts ...Optional) (*models.PayoutResp, error) { params := y.getParams(opts...) yReq := &PayoutRequest{ Amount: Amount{ Value: fmt.Sprintf("%.2f", req.Amount), Currency: "RUB", }, PayoutDestinationData: PayoutDestination{ Type: req.PayoutType, }, } yResp := &PayoutResponse{} yError := &Error{} switch req.PayoutType { case models.TypeSBP: yReq.PayoutDestinationData.Phone = userSession.Phone yReq.PayoutDestinationData.BankID = req.BankID case models.TypeYooMoney: yReq.PayoutDestinationData.AccountNumber = req.AccountNumber default: return nil, errors.New("unsupported payout type") } restyResp, err := y.client.R(). SetContext(params.ctx). SetHeader(IdempotenceHeader, idempotenceKey). SetBody(yReq). SetResult(yResp). SetError(yError). Post("/payouts") slog.Debug(fmt.Sprintf("Got response from yookassa: %v", restyResp)) if err != nil { return nil, errors.Join(errors.New("failed to perform yookassa api post"), err) } if restyResp.StatusCode() != http.StatusOK { yError.Status = restyResp.StatusCode() return nil, yError } return &models.PayoutResp{ID: yResp.ID, Status: yResp.Status}, nil } // GetConfig implements [Service]. func (y *yookassaService) GetConfig() config.YooKassa { return y.conf }