Implement yookassa client
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package payout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"payouts/internal/api/common"
|
||||
"payouts/internal/config"
|
||||
"payouts/internal/models"
|
||||
"payouts/internal/service/cache"
|
||||
@@ -99,18 +101,36 @@ func (p *payoutHandler) checkAllowedIpCallback(ipStr string) bool {
|
||||
|
||||
// GetSbpBanks implements [Handler].
|
||||
func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) {
|
||||
panic("unimplemented")
|
||||
w.Header().Set("Content-type", "application/json")
|
||||
|
||||
banksResp, err := p.yooKassa.GetSbpBanks(yookassa.WithContext(r.Context()))
|
||||
|
||||
if err != nil {
|
||||
status := http.StatusBadRequest
|
||||
var yError *yookassa.Error
|
||||
if errors.As(err, &yError) {
|
||||
status = yError.Status
|
||||
}
|
||||
common.ErrorResponse(w, "failed to retrieve sbp banks", err, status)
|
||||
return
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
err = encoder.Encode(banksResp)
|
||||
if err != nil {
|
||||
common.ErrorResponse(w, "failed to encode response", err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
w.Header().Set("Content-type", "application/json")
|
||||
|
||||
userSession, err := p.getSession(r)
|
||||
if err != nil {
|
||||
errResponse("unauthorized", err, http.StatusUnauthorized)
|
||||
common.ErrorResponse(w, "unauthorized", err, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -120,8 +140,7 @@ func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&payoutReq)
|
||||
if err != nil {
|
||||
slog.Error("Failed to decode request body", slog.String("error", err.Error()))
|
||||
errResponse("failed to decode request body", err, http.StatusBadRequest)
|
||||
common.ErrorResponse(w, "failed to decode request body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,34 +150,78 @@ func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
||||
IdempotenceKey: idempotenceKey,
|
||||
Type: payoutReq.PayoutType.String(),
|
||||
Amount: payoutReq.Amount,
|
||||
Status: orm.StatusCreated,
|
||||
Status: models.StatusCreated.String(),
|
||||
}
|
||||
err = p.dbService.CreatePayout(payoutModel, database.WithContext(r.Context()))
|
||||
if err != nil {
|
||||
common.ErrorResponse(w, "failed to create payout data", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
slog.Debug(fmt.Sprintf("Received create payload request: %v from user %v", payoutReq, userSession))
|
||||
|
||||
payoutResp, err := p.yooKassa.CreatePayout(payoutReq, userSession, idempotenceKey, yookassa.WithContext(r.Context()))
|
||||
if err != nil {
|
||||
slog.Error("Failed to create payout request", slog.String("error", err.Error()))
|
||||
errResponse("failed to create payout request", err, http.StatusBadRequest)
|
||||
status := http.StatusBadRequest
|
||||
var yError *yookassa.Error
|
||||
if errors.As(err, &yError) {
|
||||
status = yError.Status
|
||||
}
|
||||
common.ErrorResponse(w, "failed to create payout request", err, status)
|
||||
return
|
||||
}
|
||||
|
||||
updatedRows, err := p.dbService.UpdatePayoutById(payoutModel.ID, orm.Payout{
|
||||
PayoutID: payoutResp.ID,
|
||||
Status: payoutResp.Status.String(),
|
||||
}, database.WithContext(r.Context()))
|
||||
|
||||
if err != nil || updatedRows == 0 {
|
||||
common.ErrorResponse(w, "failed to update payout data", err, http.StatusInternalServerError, slog.String("id", fmt.Sprintf("%d", payoutModel.ID)))
|
||||
return
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.Encode(payoutResp)
|
||||
err = encoder.Encode(payoutResp)
|
||||
if err != nil {
|
||||
common.ErrorResponse(w, "failed to encode response", err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *payoutHandler) delayedPayoutUpdate(ctx context.Context, payoutData *yookassa.PayoutResponse) {
|
||||
<-ctx.Done()
|
||||
slog.Info("Updating payout data received from callback")
|
||||
|
||||
updatedRows, err := p.dbService.UpdatePayoutByPayoutID(payoutData.ID, orm.Payout{
|
||||
Status: payoutData.Status.String(),
|
||||
}, database.WithContext(context.Background()))
|
||||
|
||||
if err != nil || updatedRows == 0 {
|
||||
slog.Error("Failed to update paylout data", slog.String("error", fmt.Sprintf("%v", err)), slog.Int("rows_updated", updatedRows))
|
||||
} else {
|
||||
slog.Info("Successfully updated payout data", slog.String("payout_id", payoutData.ID), slog.String("new_status", payoutData.Status.String()))
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if p.yookassaConf.CheckAllowedCallbackAddress && !p.checkAllowedIpCallback(r.RemoteAddr) {
|
||||
common.ErrorResponse(w, "unallowed", nil, http.StatusForbidden, common.Reason("Callback came from unallowed ip: %s", r.RemoteAddr))
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info(fmt.Sprintf("Received callback from %s with object %v with headers %v", r.RemoteAddr, inData, r.Header))
|
||||
payoutData := &yookassa.PayoutResponse{}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(payoutData)
|
||||
|
||||
if err != nil {
|
||||
common.ErrorResponse(w, "bad request", nil, http.StatusBadRequest, common.Reason("Failed to decode payload data: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), p.yookassaConf.CallbackProcessTimeout)
|
||||
go p.delayedPayoutUpdate(ctx, payoutData)
|
||||
|
||||
slog.Debug(fmt.Sprintf("Received callback from %s with object %v with headers %v", r.RemoteAddr, payoutData, r.Header))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user