generated from PenaSide/GolangTemplate
592 lines
17 KiB
Go
592 lines
17 KiB
Go
package swagger
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.uber.org/zap"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/broker/tariff"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/client"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/repository"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/proto/discount"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/history"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils/transfer"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/echotools"
|
|
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/validate"
|
|
)
|
|
|
|
// TODO replace all errors.HTTP with api.error to return logging
|
|
|
|
const defaultCurrency = "RUB" // TODO move
|
|
|
|
type API2 struct {
|
|
logger *zap.Logger
|
|
history repository.HistoryRepository
|
|
account repository.AccountRepository
|
|
currency repository.CurrencyRepository
|
|
producer *tariff.Producer
|
|
consumer *tariff.Consumer
|
|
clients clients
|
|
grpc models.ConfigurationGRPC
|
|
}
|
|
|
|
type clients struct {
|
|
auth *client.AuthClient
|
|
hubadmin *client.HubadminClient
|
|
currency *client.CurrencyClient
|
|
discount *client.DiscountClient
|
|
payment *client.PaymentClient
|
|
}
|
|
|
|
var _ ServerInterface = (*API2)(nil)
|
|
|
|
func NewAPI2(logger *zap.Logger, db *mongo.Database, config *models.Config, consumer *tariff.Consumer, producer *tariff.Producer) API2 {
|
|
return API2{
|
|
logger: logger,
|
|
history: repository.NewHistoryRepository2(logger, db.Collection("histories")),
|
|
currency: repository.NewCurrencyRepository2(logger, db.Collection("currency_lists")),
|
|
account: repository.NewAccountRepository2(logger, db.Collection("accounts")),
|
|
consumer: consumer,
|
|
producer: producer,
|
|
grpc: config.GRPC,
|
|
clients: clients{
|
|
auth: client.NewAuthClient(client.AuthClientDeps{Logger: logger, URLs: &config.Service.AuthMicroservice.URL}),
|
|
hubadmin: client.NewHubadminClient(client.HubadminClientDeps{Logger: logger, URLs: &config.Service.HubadminMicroservice.URL}),
|
|
currency: client.NewCurrencyClient(client.CurrencyClientDeps{Logger: logger, URLs: &config.Service.CurrencyMicroservice.URL}),
|
|
discount: client.NewDiscountClient(client.DiscountClientDeps{Logger: logger, DiscountServiceHost: config.Service.DiscountMicroservice.HostGRPC}),
|
|
payment: client.NewPaymentClient(client.PaymentClientDeps{Logger: logger, PaymentServiceHost: config.Service.PaymentMicroservice.HostGRPC}),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (api *API2) error(ctx echo.Context, status int, message string, rest ...any) error {
|
|
if len(rest) > 0 {
|
|
message = fmt.Sprintf(message, rest...)
|
|
}
|
|
api.logger.Error(message)
|
|
return ctx.JSON(status, models.ResponseErrorHTTP{
|
|
StatusCode: status,
|
|
Message: message,
|
|
})
|
|
}
|
|
|
|
func (api *API2) noauth(ctx echo.Context) error {
|
|
return api.error(ctx, http.StatusUnauthorized, "failed to get jwt payload")
|
|
}
|
|
|
|
// Health
|
|
|
|
func (api *API2) GetHealth(ctx echo.Context) error {
|
|
return ctx.String(http.StatusOK, "OK")
|
|
}
|
|
|
|
// Account
|
|
|
|
func (api *API2) DeleteAccount(ctx echo.Context) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
account, err := api.account.Remove(ctx.Request().Context(), userID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, account)
|
|
}
|
|
|
|
func (api *API2) ChangeAccount(ctx echo.Context) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
request, bindErr := echotools.Bind[models.Name](ctx)
|
|
if bindErr != nil {
|
|
return api.error(ctx, http.StatusBadRequest, "failed to bind json: %w", bindErr)
|
|
}
|
|
|
|
account, err := api.account.UpdateName(ctx.Request().Context(), userID, request)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, account)
|
|
}
|
|
|
|
func (api *API2) SetAccountVerificationStatus(ctx echo.Context, userID string) error {
|
|
request, bindErr := echotools.Bind[models.SetAccountStatus](ctx)
|
|
if bindErr != nil {
|
|
return api.error(ctx, http.StatusBadRequest, "failed to bind json: %w", bindErr)
|
|
}
|
|
|
|
account, err := api.account.SetStatus(ctx.Request().Context(), userID, request.Status)
|
|
if err != nil {
|
|
api.logger.Error("failed to set status on <SetVerificationStatus> of <AccountService>", zap.Error(err))
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, account)
|
|
}
|
|
|
|
func (api *API2) GetAccount(ctx echo.Context) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, account)
|
|
}
|
|
|
|
func (api *API2) AddAccount(ctx echo.Context) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
|
|
if err != nil && err.Type() != errors.ErrNotFound {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
if account != nil {
|
|
return api.error(ctx, http.StatusBadRequest, "account exists")
|
|
}
|
|
|
|
user, err := api.clients.auth.GetUser(ctx.Request().Context(), userID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
account, err = api.account.Insert(ctx.Request().Context(), &models.Account{UserID: user.ID, Wallet: models.Wallet{Currency: defaultCurrency}})
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, account)
|
|
}
|
|
|
|
func (api *API2) DeleteDirectAccount(ctx echo.Context, userID string) error {
|
|
account, err := api.account.Remove(ctx.Request().Context(), userID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, account)
|
|
}
|
|
|
|
func (api *API2) GetDirectAccount(ctx echo.Context, userID string) error {
|
|
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, account)
|
|
}
|
|
|
|
func (api *API2) PaginationAccounts(ctx echo.Context, params PaginationAccountsParams) error {
|
|
if params.Page == nil || params.Limit == nil {
|
|
return api.error(ctx, http.StatusInternalServerError, "default values missing for PaginationAccounts")
|
|
}
|
|
|
|
page := int64(max(*params.Page, 1))
|
|
limit := min(int64(max(*params.Limit, 1)), models.DefaultLimit)
|
|
|
|
count, err := api.account.CountAll(ctx.Request().Context())
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
if count == 0 {
|
|
response := models.PaginationResponse[models.Account]{TotalPages: 0, Records: []models.Account{}}
|
|
return ctx.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
totalPages := int64(math.Ceil(float64(count) / float64(limit)))
|
|
|
|
accounts, err := api.account.FindMany(ctx.Request().Context(), page, limit)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
response := models.PaginationResponse[models.Account]{
|
|
TotalPages: totalPages,
|
|
Records: accounts,
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// Cart
|
|
|
|
func (api *API2) RemoveFromCart(ctx echo.Context, params RemoveFromCartParams) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
if validate.IsStringEmpty(params.Id) {
|
|
return api.error(ctx, http.StatusBadRequest, "empty item id")
|
|
}
|
|
|
|
cartItems, err := api.account.RemoveItemFromCart(ctx.Request().Context(), userID, params.Id)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, cartItems)
|
|
}
|
|
|
|
func (api *API2) Add2cart(ctx echo.Context, params Add2cartParams) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
token, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
if validate.IsStringEmpty(params.Id) {
|
|
return api.error(ctx, http.StatusBadRequest, "empty item id")
|
|
}
|
|
|
|
tariffID := params.Id
|
|
|
|
tariff, err := api.clients.hubadmin.GetTariff(ctx.Request().Context(), token, tariffID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
if tariff == nil {
|
|
return api.error(ctx, http.StatusNotFound, "tariff not found")
|
|
}
|
|
|
|
cartItems, err := api.account.AddItemToCart(ctx.Request().Context(), userID, tariffID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, cartItems)
|
|
}
|
|
|
|
func (api *API2) PayCart(ctx echo.Context) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
accessToken, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
api.logger.Info("account for pay", zap.Any("acc", account))
|
|
|
|
tariffs, err := api.clients.hubadmin.GetTariffs(ctx.Request().Context(), accessToken, account.Cart)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
api.logger.Info("tariffs for pay", zap.Any("acc", tariffs))
|
|
|
|
tariffsAmount := utils.CalculateCartPurchasesAmount(tariffs)
|
|
|
|
discountResponse, err := api.clients.discount.Apply(ctx.Request().Context(), &discount.ApplyDiscountRequest{
|
|
UserInformation: &discount.UserInformation{
|
|
ID: account.UserID,
|
|
Type: string(account.Status),
|
|
PurchasesAmount: uint64(account.Wallet.PurchasesAmount),
|
|
CartPurchasesAmount: tariffsAmount,
|
|
},
|
|
Products: transfer.TariffsToProductInformations(tariffs),
|
|
Date: timestamppb.New(time.Now()),
|
|
})
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
api.logger.Info("discountResponse for pay", zap.Any("acc", discount.ApplyDiscountRequest{
|
|
UserInformation: &discount.UserInformation{
|
|
ID: account.UserID,
|
|
Type: string(account.Status),
|
|
PurchasesAmount: uint64(account.Wallet.PurchasesAmount),
|
|
CartPurchasesAmount: tariffsAmount,
|
|
},
|
|
Products: transfer.TariffsToProductInformations(tariffs),
|
|
Date: timestamppb.New(time.Now()),
|
|
}))
|
|
|
|
if account.Wallet.Money < int64(discountResponse.Price) {
|
|
return api.error(ctx, http.StatusPaymentRequired, "insufficient funds: %d", int64(discountResponse.Price)-account.Wallet.Money)
|
|
}
|
|
|
|
// 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 := api.account.ChangeWallet(ctx.Request().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 errors.HTTP(ctx, err)
|
|
}
|
|
updatedAccount = accountx
|
|
} else {
|
|
cash, err := api.clients.currency.Translate(ctx.Request().Context(), &models.TranslateCurrency{
|
|
Money: request.Money,
|
|
From: models.InternalCurrencyKey,
|
|
To: request.Account.Wallet.Currency,
|
|
})
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
accountx, err := api.account.ChangeWallet(ctx.Request().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 errors.HTTP(ctx, err)
|
|
}
|
|
|
|
updatedAccount = accountx
|
|
}
|
|
|
|
if _, err := api.history.Insert(ctx.Request().Context(), &models.History{
|
|
Key: models.CustomerHistoryKeyPayCart,
|
|
UserID: account.UserID,
|
|
Comment: "Успешная оплата корзины",
|
|
RawDetails: tariffs,
|
|
}); err != nil {
|
|
return errors.HTTP(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 := api.producer.Send(ctx.Request().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 {
|
|
api.logger.Error("failed to send tariffs to broker on <Pay> of <CartService>", zap.Error(err))
|
|
}
|
|
return api.error(ctx, http.StatusInternalServerError, "failed to send tariffs to broker")
|
|
}
|
|
|
|
if _, err := api.account.ClearCart(ctx.Request().Context(), account.UserID); err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
updatedAccount.Cart = []string{}
|
|
|
|
return ctx.JSON(http.StatusOK, updatedAccount)
|
|
}
|
|
|
|
// Currency
|
|
|
|
func (api *API2) GetCurrencies(ctx echo.Context) error {
|
|
currencyList, err := api.currency.FindCurrenciesList(ctx.Request().Context(), models.DefaultCurrencyListName)
|
|
if err != nil && err.Type() != errors.ErrNotFound {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
if err != nil && err.Type() == errors.ErrNotFound {
|
|
return ctx.JSON(http.StatusOK, []string{})
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, currencyList)
|
|
}
|
|
|
|
func (api *API2) UpdateCurrencies(ctx echo.Context) error {
|
|
currenciesPtr, bindErr := echotools.Bind[[]string](ctx)
|
|
if bindErr != nil {
|
|
return api.error(ctx, http.StatusBadRequest, "faild to bind currencies")
|
|
}
|
|
|
|
currencies := *currenciesPtr
|
|
|
|
currencyList, err := api.currency.ReplaceCurrencies(ctx.Request().Context(), &models.CurrencyList{
|
|
Name: models.DefaultCurrencyListName,
|
|
Currencies: currencies,
|
|
})
|
|
if err != nil && err.Type() != errors.ErrNotFound {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
if err != nil && err.Type() == errors.ErrNotFound {
|
|
newCurrencyList, err := api.currency.Insert(ctx.Request().Context(), &models.CurrencyList{
|
|
Name: models.DefaultCurrencyListName,
|
|
Currencies: currencies,
|
|
})
|
|
if err != nil && err.Type() != errors.ErrNotFound {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
return ctx.JSON(http.StatusOK, newCurrencyList.Currencies)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, currencyList.Currencies)
|
|
}
|
|
|
|
// History
|
|
|
|
func (api *API2) GetHistory(ctx echo.Context, params GetHistoryParams) error {
|
|
dto := &history.GetHistories{
|
|
Type: params.Type,
|
|
Pagination: &models.Pagination{
|
|
Page: int64(*params.Page),
|
|
Limit: int64(*params.Limit),
|
|
},
|
|
}
|
|
|
|
count, err := api.history.CountAll(ctx.Request().Context(), dto)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
if count == 0 {
|
|
returnHistories := models.PaginationResponse[models.History]{TotalPages: 0, Records: []models.History{}}
|
|
return ctx.JSON(http.StatusOK, returnHistories)
|
|
}
|
|
|
|
totalPages := int64(math.Ceil(float64(count) / float64(dto.Pagination.Limit)))
|
|
|
|
histories, err := api.history.FindMany(ctx.Request().Context(), dto)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
returnHistories := models.PaginationResponse[models.History]{
|
|
TotalPages: totalPages,
|
|
Records: histories,
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, returnHistories)
|
|
}
|
|
|
|
// Wallet
|
|
|
|
func (api *API2) RequestMoney(ctx echo.Context) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
request, bindErr := echotools.Bind[models.GetPaymentLinkBody](ctx)
|
|
if bindErr != nil {
|
|
return api.error(ctx, http.StatusBadRequest, "faild to bind payment link")
|
|
}
|
|
|
|
if err := utils.ValidateGetPaymentLinkBody(request); err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
link, err := api.GetPaymentLink(ctx.Request().Context(), &models.GetPaymentLinkRequest{
|
|
Body: request,
|
|
UserID: userID,
|
|
ClientIP: ctx.RealIP(),
|
|
})
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, &models.GetPaymentLinkResponse{Link: link})
|
|
}
|
|
|
|
func (api *API2) ChangeCurrency(ctx echo.Context) error {
|
|
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
|
if !ok {
|
|
return api.noauth(ctx)
|
|
}
|
|
|
|
request, bindErr := echotools.Bind[models.ChangeCurrency](ctx)
|
|
if bindErr != nil {
|
|
return api.error(ctx, http.StatusBadRequest, "faild to bind currency")
|
|
}
|
|
|
|
if validate.IsStringEmpty(request.Currency) {
|
|
return api.error(ctx, http.StatusBadRequest, "empty currency")
|
|
}
|
|
|
|
currency := request.Currency
|
|
account, err := api.account.FindByUserID(ctx.Request().Context(), userID)
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
cash, err := api.clients.currency.Translate(ctx.Request().Context(), &models.TranslateCurrency{
|
|
Money: account.Wallet.Cash,
|
|
From: account.Wallet.Currency,
|
|
To: currency,
|
|
})
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
updatedAccount, err := api.account.ChangeWallet(ctx.Request().Context(), account.UserID, &models.Wallet{
|
|
Cash: cash,
|
|
Currency: currency,
|
|
Money: account.Wallet.Money,
|
|
})
|
|
if err != nil {
|
|
return errors.HTTP(ctx, err)
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, updatedAccount)
|
|
}
|