package cart_client import ( "github.com/gofiber/fiber/v2" "go.uber.org/zap" "google.golang.org/protobuf/types/known/timestamppb" "penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw" "gitea.pena/PenaSide/customer/internal/errors" "gitea.pena/PenaSide/customer/internal/interface/broker/tariff" "gitea.pena/PenaSide/customer/internal/interface/client" "gitea.pena/PenaSide/customer/internal/interface/controller/http" "gitea.pena/PenaSide/customer/internal/interface/repository" "gitea.pena/PenaSide/customer/internal/models" "gitea.pena/PenaSide/customer/internal/proto/discount" "gitea.pena/PenaSide/customer/internal/utils" "gitea.pena/PenaSide/customer/internal/utils/transfer" "gitea.pena/PenaSide/customer/pkg/validate" "strings" "sync" "time" ) type Deps struct { MiddleWare *http.MiddleWare Logger *zap.Logger AccountRepo *repository.AccountRepository HistoryRepo *repository.HistoryRepository HubAdminClient *client.HubadminClient DiscountClient *client.DiscountClient CurrencyClient *client.CurrencyClient Producer *tariff.Producer } type CartController struct { middleWare *http.MiddleWare logger *zap.Logger accountRepo *repository.AccountRepository historyRepo *repository.HistoryRepository hubAdminClient *client.HubadminClient discountClient *client.DiscountClient currencyClient *client.CurrencyClient producer *tariff.Producer } func NewCartController(deps Deps) *CartController { return &CartController{ middleWare: deps.MiddleWare, logger: deps.Logger, accountRepo: deps.AccountRepo, historyRepo: deps.HistoryRepo, hubAdminClient: deps.HubAdminClient, discountClient: deps.DiscountClient, currencyClient: deps.CurrencyClient, producer: deps.Producer, } } func (receiver *CartController) Remove(ctx *fiber.Ctx) error { userID, ok := receiver.middleWare.ExtractUserID(ctx) if !ok || userID == "" { return receiver.middleWare.NoAuth(ctx) } // todo проверить менять или нет id := ctx.Query("id") if id == "" { return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "empty item id") } cartItems, err := receiver.accountRepo.RemoveItemFromCart(ctx.Context(), userID, id) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } return ctx.Status(fiber.StatusOK).JSON(cartItems) } func (receiver *CartController) Add2cart(ctx *fiber.Ctx) error { userID, ok := receiver.middleWare.ExtractUserID(ctx) if !ok || userID == "" { return receiver.middleWare.NoAuth(ctx) } token, ok := receiver.middleWare.ExtractToken(ctx) if !ok || token == "" { return receiver.middleWare.NoAuth(ctx) } hlogger := log_mw.ExtractLogger(ctx) tariffID := ctx.Query("id") if tariffID == "" { return receiver.middleWare.Error(ctx, fiber.StatusBadRequest, "empty item id") } tariff, err := receiver.hubAdminClient.GetTariff(ctx.Context(), token, tariffID) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } if tariff == nil { return receiver.middleWare.Error(ctx, fiber.StatusNotFound, "tariff not found") } cartItems, err := receiver.accountRepo.AddItemToCart(ctx.Context(), userID, tariffID) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } hlogger.Emit(models.InfoAddToCart{ CtxUserID: userID, CtxAccountID: cartItems.ID, CtxTariffID: tariffID, }) return ctx.Status(fiber.StatusOK).JSON(cartItems) } func (receiver *CartController) Pay(ctx *fiber.Ctx) error { userID, ok := receiver.middleWare.ExtractUserID(ctx) if !ok || userID == "" { return receiver.middleWare.NoAuth(ctx) } accessToken, ok := receiver.middleWare.ExtractToken(ctx) if !ok || accessToken == "" { return receiver.middleWare.NoAuth(ctx) } hlogger := log_mw.ExtractLogger(ctx) account, err := receiver.accountRepo.FindByUserID(ctx.Context(), userID) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } receiver.logger.Info("account for pay", zap.Any("acc", account)) if len(account.Cart) == 0 { return ctx.Status(fiber.StatusNotAcceptable).SendString("cart is empty") } tariffs, err := receiver.hubAdminClient.GetTariffs(ctx.Context(), accessToken, account.Cart) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } receiver.logger.Info("tariffs for pay", zap.Any("acc", tariffs)) tariffsAmount := utils.CalculateCartPurchasesAmount(tariffs) discountResponse, err := receiver.discountClient.Apply(ctx.Context(), &discount.ApplyDiscountRequest{ UserInformation: &discount.UserInformation{ ID: account.UserID, Type: string(account.Status), PurchasesAmount: uint64(account.Wallet.Spent), CartPurchasesAmount: tariffsAmount, }, Products: transfer.TariffsToProductInformations(tariffs), Date: timestamppb.New(time.Now()), }) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } receiver.logger.Info("discountResponse for pay", zap.Any("acc", discount.ApplyDiscountRequest{ UserInformation: &discount.UserInformation{ ID: account.UserID, Type: string(account.Status), PurchasesAmount: uint64(account.Wallet.Spent), CartPurchasesAmount: tariffsAmount, }, Products: transfer.TariffsToProductInformations(tariffs), Date: timestamppb.New(time.Now()), })) if account.Wallet.Money < int64(discountResponse.Price) { hlogger.Emit(models.InfoPayCart{ CtxUserID: userID, CtxAccountID: account.ID, KeySuccess: uint8(0), CtxPrice: int64(discountResponse.Price - uint64(account.Wallet.Money)), CtxTariff: strings.Join(account.Cart, ","), CtxDiscount: strings.Join(utils.GetAppliedDiscountsIDs(discountResponse.AppliedDiscounts), ","), CtxRowPrice: int64(tariffsAmount), CtxRowData: utils.MarshalRawDetails(models.RawDetails{Tariffs: tariffs, Price: int64(discountResponse.Price)}), }) return receiver.middleWare.Error(ctx, fiber.StatusPaymentRequired, "insufficient funds: %d", int64(discountResponse.Price)-account.Wallet.Money) } for _, applied := range discountResponse.AppliedDiscounts { if applied.Condition.User != nil && *applied.Condition.User != "" { _, err := receiver.discountClient.DeleteDiscount(ctx.Context(), &discount.GetDiscountByIDRequest{ ID: applied.ID, }) if err != nil { return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, "failed delete discount by id:%s", applied.ID) } } } // WithdrawAccountWalletMoney request := models.WithdrawAccountWallet{ Money: int64(discountResponse.Price), Account: account, } if validate.IsStringEmpty(request.Account.Wallet.Currency) { request.Account.Wallet.Currency = models.InternalCurrencyKey } var updatedAccount *models.Account if request.Account.Wallet.Currency == models.InternalCurrencyKey { accountx, err := receiver.accountRepo.ChangeWallet(ctx.Context(), request.Account.UserID, &models.Wallet{ Cash: request.Account.Wallet.Cash - request.Money, Money: request.Account.Wallet.Money - request.Money, Spent: request.Account.Wallet.Spent + request.Money, PurchasesAmount: request.Account.Wallet.PurchasesAmount, Currency: request.Account.Wallet.Currency, }) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } updatedAccount = accountx } else { cash, err := receiver.currencyClient.Translate(ctx.Context(), &models.TranslateCurrency{ Money: request.Money, From: models.InternalCurrencyKey, To: request.Account.Wallet.Currency, }) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } accountx, err := receiver.accountRepo.ChangeWallet(ctx.Context(), request.Account.UserID, &models.Wallet{ Cash: request.Account.Wallet.Cash - cash, Money: request.Account.Wallet.Money - request.Money, Spent: request.Account.Wallet.Spent + request.Money, PurchasesAmount: request.Account.Wallet.PurchasesAmount, Currency: request.Account.Wallet.Currency, }) if err != nil { return receiver.middleWare.ErrorOld(ctx, err) } updatedAccount = accountx } if _, err := receiver.historyRepo.Insert(ctx.Context(), &models.History{ Key: models.CustomerHistoryKeyPayCart, UserID: account.UserID, Comment: "Успешная оплата корзины", RawDetails: models.RawDetails{ Tariffs: tariffs, Price: int64(discountResponse.Price), }, }); err != nil { return receiver.middleWare.ErrorOld(ctx, err) } // TODO: обработать ошибки при отправке сообщений sendErrors := make([]errors.Error, 0) waitGroup := sync.WaitGroup{} mutex := sync.Mutex{} for _, tariff := range tariffs { waitGroup.Add(1) go func(currentTariff models.Tariff) { defer waitGroup.Done() if err := receiver.producer.Send(ctx.Context(), userID, ¤tTariff); err != nil { mutex.Lock() defer mutex.Unlock() sendErrors = append(sendErrors, err) } }(tariff) } waitGroup.Wait() if len(sendErrors) > 0 { for _, err := range sendErrors { receiver.logger.Error("failed to send tariffs to broker on of ", zap.Error(err)) } return receiver.middleWare.Error(ctx, fiber.StatusInternalServerError, "failed to send tariffs to broker") } if _, err := receiver.accountRepo.ClearCart(ctx.Context(), account.UserID); err != nil { return receiver.middleWare.ErrorOld(ctx, err) } updatedAccount.Cart = []string{} hlogger.Emit(models.InfoPayCart{ CtxUserID: userID, CtxAccountID: updatedAccount.ID, KeySuccess: uint8(1), CtxPrice: int64(discountResponse.Price), CtxTariff: strings.Join(account.Cart, ","), CtxDiscount: strings.Join(utils.GetAppliedDiscountsIDs(discountResponse.AppliedDiscounts), ","), CtxRowPrice: int64(tariffsAmount), CtxRowData: utils.MarshalRawDetails(models.RawDetails{Tariffs: tariffs, Price: int64(discountResponse.Price)}), }) return ctx.Status(fiber.StatusOK).JSON(updatedAccount) }