package callback import ( "context" "fmt" "gitea.pena/PenaSide/customer/internal/interface/broker/tariff" "gitea.pena/PenaSide/customer/internal/interface/client" "gitea.pena/PenaSide/customer/internal/interface/repository" "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" "gitea.pena/PenaSide/tariffs/pkg/grpc_clients" "google.golang.org/protobuf/types/known/timestamppb" "log" "sync" "time" "gitea.pena/PenaSide/customer/internal/errors" "gitea.pena/PenaSide/customer/internal/models" "go.uber.org/zap" tb "gopkg.in/tucnak/telebot.v2" ) type accountRepository interface { FindByUserID(context.Context, string) (*models.Account, errors.Error) } type historyService interface { CreateHistory(ctx context.Context, history *models.History) (*models.History, errors.Error) } type walletService interface { ReplenishAccountWallet(context.Context, *models.ReplenishAccountWallet) (*models.Account, errors.Error) } type PaymentCallbackServiceDeps struct { Logger *zap.Logger AccountRepository *repository.AccountRepository WalletService walletService HistoryService historyService Notifier *tb.Bot NotifyChannel int64 AdminURL string HistoryRepo *repository.HistoryRepository TariffGrpcClient *grpc_clients.TariffGRPCClient DiscountClient *client.DiscountClient CurrencyClient *client.CurrencyClient Producer *tariff.Producer } type PaymentCallbackService struct { logger *zap.Logger accountRepository *repository.AccountRepository walletService walletService historyService historyService notifier *tb.Bot notifyChannel int64 adminURL string historyRepo *repository.HistoryRepository tariffGrpcClient *grpc_clients.TariffGRPCClient discountClient *client.DiscountClient currencyClient *client.CurrencyClient producer *tariff.Producer } func NewPaymentCallbackService(deps PaymentCallbackServiceDeps) *PaymentCallbackService { if deps.Logger == nil { log.Panicln("logger is nil on ") } if deps.AccountRepository == nil { log.Panicln("AccountRepository is nil on ") } if deps.WalletService == nil { log.Panicln("WalletService is nil on ") } if deps.HistoryService == nil { log.Panicln("HistoryService is nil on ") } return &PaymentCallbackService{ logger: deps.Logger, accountRepository: deps.AccountRepository, walletService: deps.WalletService, historyService: deps.HistoryService, notifier: deps.Notifier, notifyChannel: deps.NotifyChannel, adminURL: deps.AdminURL, } } func (receiver *PaymentCallbackService) SuccessEvent(ctx context.Context, event *models.PaymentEvent) errors.Error { account, err := receiver.accountRepository.FindByUserID(ctx, event.UserID) if err != nil { receiver.logger.Error("failed to get account on of ", zap.Error(err)) return err } if account.Wallet.LastPaymentID == event.PaymentID { receiver.logger.Info("payment already was executed on of ", zap.String("paymentID", event.PaymentID)) return nil } if _, err := receiver.walletService.ReplenishAccountWallet(ctx, &models.ReplenishAccountWallet{ Cash: event.Amount, Currency: event.Currency, PaymentID: event.PaymentID, Account: account, }); err != nil { receiver.logger.Error("failed to replenish wallet on of ", zap.Error(err)) return err } //go func() { if _, err := receiver.historyService.CreateHistory(ctx, &models.History{ UserID: account.UserID, Comment: event.Message, Key: event.Key, RawDetails: models.RawDetails{ Price: event.Amount, Comment: event.Type + ":" + event.Currency, }, }); err != nil { receiver.logger.Error("failed to create history on of ", zap.Error(err)) return err } //}() go func() { if _, err := receiver.notifier.Send(tb.ChatID(receiver.notifyChannel), fmt.Sprintf(`Внесены деньги %.2f, пользователем %s`, float64(event.Amount/100), receiver.adminURL+"/users/"+account.UserID)); err != nil { fmt.Println("TBOER", err) } }() return nil } func (receiver *PaymentCallbackService) FailureEvent(ctx context.Context, event *models.PaymentEvent) errors.Error { if _, err := receiver.historyService.CreateHistory(ctx, &models.History{ UserID: event.UserID, Comment: event.Message, Key: event.Key, // RawDetails: fmt.Sprintf("%d%s", event.Amount, event.Currency), }); err != nil { receiver.logger.Error("failed to create history on of ", zap.Error(err)) return err } return nil } func (receiver *PaymentCallbackService) CartPurchaseEvent(ctx context.Context, event *models.PaymentEvent) errors.Error { account, err := receiver.accountRepository.FindByUserID(ctx, event.UserID) if err != nil { receiver.logger.Error("failed to get account on of ", zap.Error(err)) return err } if len(account.Cart) == 0 { return nil } tariffs := make([]models.Tariff, len(account.Cart)) for index, tariffID := range account.Cart { resp, err := receiver.tariffGrpcClient.GetTariff(ctx, tariffID) if err != nil { receiver.logger.Error("failed to get tariff on of ", zap.Error(err), zap.String("tariffID", tariffID)) return errors.New( fmt.Errorf("failed to get tariff on of : %w", err), errors.ErrInternalError, ) } pbTariff := resp.Tariff privileges := make([]models.Privilege, 0, len(pbTariff.Privileges)) for _, pbPriv := range pbTariff.Privileges { privileges = append(privileges, models.Privilege{ ID: pbPriv.Id, Name: pbPriv.Name, PrivilegeID: pbPriv.PrivilegeId, ServiceKey: pbPriv.ServiceKey, Description: pbPriv.Description, Amount: uint64(pbPriv.Amount), Type: models.PrivilegeType(pbPriv.Type), Value: pbPriv.Value, Price: uint64(pbPriv.Price), }) } tariffs[index] = models.Tariff{ ID: pbTariff.Id, Name: pbTariff.Name, Price: uint64(pbTariff.Price), IsCustom: pbTariff.IsCustom, Privileges: privileges, Deleted: pbTariff.IsDeleted, } } tariffsAmount := utils.CalculateCartPurchasesAmount(tariffs) discountResponse, err := receiver.discountClient.Apply(ctx, &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 { receiver.logger.Error("failed to apply discount on of ", zap.Error(err)) return err } for _, applied := range discountResponse.AppliedDiscounts { if applied.Condition.User != nil && *applied.Condition.User != "" { _, err := receiver.discountClient.DeleteDiscount(ctx, &discount.GetDiscountByIDRequest{ ID: applied.ID, }) if err != nil { receiver.logger.Error("failed to delete discount on of ", zap.Error(err)) return err } } } request := models.WithdrawAccountWallet{ Money: int64(discountResponse.Price), Account: account, } if validate.IsStringEmpty(request.Account.Wallet.Currency) { request.Account.Wallet.Currency = models.InternalCurrencyKey } if request.Account.Wallet.Currency == models.InternalCurrencyKey { _, err = receiver.accountRepository.ChangeWallet(ctx, 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 { receiver.logger.Error("failed to change wallet on of ", zap.Error(err)) return err } } else { cash, err := receiver.currencyClient.Translate(ctx, &models.TranslateCurrency{ Money: request.Money, From: models.InternalCurrencyKey, To: request.Account.Wallet.Currency, }) if err != nil { receiver.logger.Error("failed to translate currency on of ", zap.Error(err)) return err } _, err = receiver.accountRepository.ChangeWallet(ctx, 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 { receiver.logger.Error("failed to change wallet (non-internal currency) on of ", zap.Error(err)) return err } } if _, err := receiver.historyRepo.Insert(ctx, &models.History{ Key: models.CustomerHistoryKeyPayCart, UserID: account.UserID, Comment: "Успешная оплата корзины", RawDetails: models.RawDetails{ Tariffs: tariffs, Price: int64(discountResponse.Price), }, }); err != nil { receiver.logger.Error("failed to insert history on of ", zap.Error(err)) return err } 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, account.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 errors.New(fmt.Errorf("failed to send tariffs to broker"), errors.ErrInternalError) } if _, err := receiver.accountRepository.ClearCart(ctx, account.UserID); err != nil { receiver.logger.Error("failed to clear cart on of ", zap.Error(err)) return err } return nil }