package customer import ( "context" "errors" "time" "tm/pkg/logger" "tm/pkg/authorization" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" ) // Service defines business logic for customer operations type Service interface { // Core customer operations CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *uuid.UUID) (*Customer, error) GetCustomerByID(ctx context.Context, id uuid.UUID) (*Customer, error) UpdateCustomer(ctx context.Context, id uuid.UUID, form *UpdateCustomerForm, updatedBy *uuid.UUID) (*Customer, error) DeleteCustomer(ctx context.Context, id uuid.UUID, deletedBy *uuid.UUID) error // Customer listing and search ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error) GetCustomersByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, int64, error) GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error) GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error) // Customer management UpdateCustomerStatus(ctx context.Context, id uuid.UUID, form *UpdateCustomerStatusForm, updatedBy *uuid.UUID) error UpdateCustomerVerification(ctx context.Context, id uuid.UUID, form *UpdateCustomerVerificationForm, updatedBy *uuid.UUID) error // Mobile-specific operations (simplified) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *uuid.UUID) (*Customer, error) UpdateCustomerMobile(ctx context.Context, id uuid.UUID, form *UpdateCustomerMobileForm, updatedBy *uuid.UUID) (*Customer, error) // Business operations VerifyCustomer(ctx context.Context, id uuid.UUID, verifiedBy *uuid.UUID) error SuspendCustomer(ctx context.Context, id uuid.UUID, reason string, suspendedBy *uuid.UUID) error ActivateCustomer(ctx context.Context, id uuid.UUID, activatedBy *uuid.UUID) error // Authentication operations Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error) GetProfile(ctx context.Context, customerID uuid.UUID) (*Customer, error) Logout(ctx context.Context, customerID uuid.UUID, accessToken string) error } // customerService implements the Service interface type customerService struct { repository Repository logger logger.Logger authService authorization.AuthorizationService } // NewCustomerService creates a new customer service func NewCustomerService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService) Service { return &customerService{ repository: repository, logger: logger, authService: authService, } } // CreateCustomer creates a new customer func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *uuid.UUID) (*Customer, error) { // Check if email already exists existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) if existingCustomer != nil { return nil, errors.New("customer with this email already exists") } // Check if company name already exists (for company customers) if form.Type == string(CustomerTypeCompany) && form.CompanyName != nil { existingCustomer, _ = s.repository.GetByCompanyName(ctx, *form.CompanyName) if existingCustomer != nil { return nil, errors.New("company with this name already exists") } } // Check if registration number already exists (for company customers) if form.Type == string(CustomerTypeCompany) && form.RegistrationNumber != nil { existingCustomer, _ = s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) if existingCustomer != nil { return nil, errors.New("company with this registration number already exists") } } // Check if tax ID already exists (for company customers) if form.Type == string(CustomerTypeCompany) && form.TaxID != nil { existingCustomer, _ = s.repository.GetByTaxID(ctx, *form.TaxID) if existingCustomer != nil { return nil, errors.New("company with this tax ID already exists") } } // Parse company ID if provided var companyID *uuid.UUID if form.CompanyID != nil { parsedID, err := uuid.Parse(*form.CompanyID) if err != nil { return nil, errors.New("invalid company ID") } companyID = &parsedID } // Create customer customer := &Customer{ ID: uuid.New(), Type: CustomerType(form.Type), Status: CustomerStatusActive, CompanyID: companyID, FirstName: form.FirstName, LastName: form.LastName, FullName: form.FullName, Username: form.Username, Email: form.Email, Password: "", // Will be set after hashing Phone: form.Phone, Mobile: form.Mobile, CompanyName: form.CompanyName, RegistrationNumber: form.RegistrationNumber, TaxID: form.TaxID, Industry: form.Industry, Address: s.convertAddressForm(form.Address), BusinessType: form.BusinessType, EmployeeCount: form.EmployeeCount, AnnualRevenue: form.AnnualRevenue, FoundedYear: form.FoundedYear, ContactPerson: s.convertContactPersonForm(form.ContactPerson), IsVerified: false, IsCompliant: false, Language: s.getDefaultLanguage(form.Language), Currency: s.getDefaultCurrency(form.Currency), Timezone: s.getDefaultTimezone(form.Timezone), CreatedAt: time.Now().Unix(), UpdatedAt: time.Now().Unix(), CreatedBy: createdBy, } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost) if err != nil { s.logger.Error("Failed to hash password", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to process password") } customer.Password = string(hashedPassword) // Save to database err = s.repository.Create(ctx, customer) if err != nil { s.logger.Error("Failed to create customer", map[string]interface{}{ "error": err.Error(), "email": form.Email, "type": form.Type, "created_by": createdBy.String(), }) return nil, err } s.logger.Info("Customer created successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "type": customer.Type, "created_by": createdBy.String(), }) return customer, nil } // GetCustomerByID retrieves a customer by ID func (s *customerService) GetCustomerByID(ctx context.Context, id uuid.UUID) (*Customer, error) { customer, err := s.repository.GetByID(ctx, id) if err != nil { s.logger.Error("Failed to get customer by ID", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return nil, err } s.logger.Info("Customer retrieved successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, }) return customer, nil } // UpdateCustomer updates a customer func (s *customerService) UpdateCustomer(ctx context.Context, id uuid.UUID, form *UpdateCustomerForm, updatedBy *uuid.UUID) (*Customer, error) { // Get existing customer customer, err := s.repository.GetByID(ctx, id) if err != nil { s.logger.Error("Failed to get customer for update", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return nil, errors.New("customer not found") } // Check if email already exists (if changing email) if form.Email != nil && *form.Email != customer.Email { existingCustomer, _ := s.repository.GetByEmail(ctx, *form.Email) if existingCustomer != nil { return nil, errors.New("customer with this email already exists") } customer.Email = *form.Email } // Check if username already exists (if changing username) if form.Username != nil && *form.Username != customer.Username { existingCustomer, _ := s.repository.GetByUsername(ctx, *form.Username) if existingCustomer != nil { return nil, errors.New("customer with this username already exists") } customer.Username = *form.Username } // Check if company name already exists (if changing company name) if form.CompanyName != nil && *form.CompanyName != *customer.CompanyName { existingCustomer, _ := s.repository.GetByCompanyName(ctx, *form.CompanyName) if existingCustomer != nil { return nil, errors.New("company with this name already exists") } customer.CompanyName = form.CompanyName } // Check if registration number already exists (if changing) if form.RegistrationNumber != nil && *form.RegistrationNumber != *customer.RegistrationNumber { existingCustomer, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber) if existingCustomer != nil { return nil, errors.New("company with this registration number already exists") } customer.RegistrationNumber = form.RegistrationNumber } // Check if tax ID already exists (if changing) if form.TaxID != nil && *form.TaxID != *customer.TaxID { existingCustomer, _ := s.repository.GetByTaxID(ctx, *form.TaxID) if existingCustomer != nil { return nil, errors.New("company with this tax ID already exists") } customer.TaxID = form.TaxID } // Update fields if provided if form.Type != nil { customer.Type = CustomerType(*form.Type) } if form.CompanyID != nil { parsedID, err := uuid.Parse(*form.CompanyID) if err != nil { return nil, errors.New("invalid company ID") } customer.CompanyID = &parsedID } if form.FirstName != nil { customer.FirstName = form.FirstName } if form.LastName != nil { customer.LastName = form.LastName } if form.FullName != nil { customer.FullName = form.FullName } if form.Phone != nil { customer.Phone = form.Phone } if form.Mobile != nil { customer.Mobile = form.Mobile } if form.Industry != nil { customer.Industry = form.Industry } if form.Address != nil { customer.Address = s.convertAddressForm(form.Address) } if form.BusinessType != nil { customer.BusinessType = form.BusinessType } if form.EmployeeCount != nil { customer.EmployeeCount = form.EmployeeCount } if form.AnnualRevenue != nil { customer.AnnualRevenue = form.AnnualRevenue } if form.FoundedYear != nil { customer.FoundedYear = form.FoundedYear } if form.ContactPerson != nil { customer.ContactPerson = s.convertContactPersonForm(form.ContactPerson) } if form.Language != nil { customer.Language = *form.Language } if form.Currency != nil { customer.Currency = *form.Currency } if form.Timezone != nil { customer.Timezone = *form.Timezone } // Set updated by customer.UpdatedBy = updatedBy customer.UpdatedAt = time.Now().Unix() // Update in database err = s.repository.Update(ctx, customer) if err != nil { s.logger.Error("Failed to update customer", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return nil, errors.New("failed to update customer") } s.logger.Info("Customer updated successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "updated_by": updatedBy.String(), }) return customer, nil } // DeleteCustomer deletes a customer (soft delete) func (s *customerService) DeleteCustomer(ctx context.Context, id uuid.UUID, deletedBy *uuid.UUID) error { // Get customer to check if exists customer, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("customer not found") } // Set updated by before deletion customer.UpdatedBy = deletedBy // Delete customer (soft delete) err = s.repository.Delete(ctx, id) if err != nil { s.logger.Error("Failed to delete customer", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return errors.New("failed to delete customer") } s.logger.Info("Customer deleted successfully", map[string]interface{}{ "customer_id": id.String(), "deleted_by": deletedBy.String(), }) return nil } // ListCustomers lists customers with search and filters func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error) { // Set defaults limit := 20 if form.Limit != nil { limit = *form.Limit } offset := 0 if form.Offset != nil { offset = *form.Offset } search := "" if form.Search != nil { search = *form.Search } sortBy := "created_at" if form.SortBy != nil { sortBy = *form.SortBy } sortOrder := "desc" if form.SortOrder != nil { sortOrder = *form.SortOrder } // Parse company ID if provided var companyID *uuid.UUID if form.CompanyID != nil { parsedID, err := uuid.Parse(*form.CompanyID) if err != nil { return nil, errors.New("invalid company ID") } companyID = &parsedID } // Get customers customers, err := s.repository.Search(ctx, search, form.Type, form.Status, companyID, form.Industry, form.IsVerified, form.IsCompliant, form.Language, form.Currency, limit, offset, sortBy, sortOrder) if err != nil { s.logger.Error("Failed to list customers", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to list customers") } // Convert to responses var customerResponses []*CustomerResponse for _, customer := range customers { customerResponses = append(customerResponses, customer.ToResponse()) } // TODO: Implement proper count for pagination metadata total := int64(len(customers)) // This should be a separate count query totalPages := (total + int64(limit) - 1) / int64(limit) return &CustomerListResponse{ Customers: customerResponses, Total: total, Limit: limit, Offset: offset, TotalPages: int(totalPages), }, nil } // GetCustomersByCompanyID retrieves customers by company ID with pagination func (s *customerService) GetCustomersByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, int64, error) { customers, err := s.repository.GetByCompanyID(ctx, companyID, limit, offset) if err != nil { s.logger.Error("Failed to get customers by company ID", map[string]interface{}{ "error": err.Error(), "company_id": companyID.String(), }) return nil, 0, errors.New("failed to get customers by company") } // Get total count total, err := s.repository.CountByCompanyID(ctx, companyID) if err != nil { s.logger.Error("Failed to count customers by company ID", map[string]interface{}{ "error": err.Error(), "company_id": companyID.String(), }) return customers, 0, nil // Return customers even if count fails } return customers, total, nil } // GetCustomersByType retrieves customers by type with pagination func (s *customerService) GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error) { // This would need to be implemented in the repository // For now, we'll use the search method customerTypeStr := string(customerType) customers, err := s.repository.Search(ctx, "", &customerTypeStr, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") if err != nil { s.logger.Error("Failed to get customers by type", map[string]interface{}{ "error": err.Error(), "customer_type": customerType, }) return nil, 0, errors.New("failed to get customers by type") } // Get total count total, err := s.repository.CountByType(ctx, customerType) if err != nil { s.logger.Error("Failed to count customers by type", map[string]interface{}{ "error": err.Error(), "customer_type": customerType, }) return customers, 0, nil } return customers, total, nil } // GetCustomersByStatus retrieves customers by status with pagination func (s *customerService) GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error) { // This would need to be implemented in the repository // For now, we'll use the search method statusStr := string(status) customers, err := s.repository.Search(ctx, "", nil, &statusStr, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc") if err != nil { s.logger.Error("Failed to get customers by status", map[string]interface{}{ "error": err.Error(), "status": status, }) return nil, 0, errors.New("failed to get customers by status") } // Get total count total, err := s.repository.CountByStatus(ctx, status) if err != nil { s.logger.Error("Failed to count customers by status", map[string]interface{}{ "error": err.Error(), "status": status, }) return customers, 0, nil } return customers, total, nil } // UpdateCustomerStatus updates customer status func (s *customerService) UpdateCustomerStatus(ctx context.Context, id uuid.UUID, form *UpdateCustomerStatusForm, updatedBy *uuid.UUID) error { // Get customer to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("customer not found") } // Update status status := CustomerStatus(form.Status) err = s.repository.UpdateStatus(ctx, id, status) if err != nil { s.logger.Error("Failed to update customer status", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), "status": form.Status, }) return errors.New("failed to update customer status") } s.logger.Info("Customer status updated successfully", map[string]interface{}{ "customer_id": id.String(), "status": form.Status, "updated_by": updatedBy.String(), }) return nil } // UpdateCustomerVerification updates customer verification status func (s *customerService) UpdateCustomerVerification(ctx context.Context, id uuid.UUID, form *UpdateCustomerVerificationForm, updatedBy *uuid.UUID) error { // Get customer to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("customer not found") } // Update verification err = s.repository.UpdateVerification(ctx, id, form.IsVerified, form.IsCompliant, form.ComplianceNotes) if err != nil { s.logger.Error("Failed to update customer verification", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return errors.New("failed to update customer verification") } s.logger.Info("Customer verification updated successfully", map[string]interface{}{ "customer_id": id.String(), "is_verified": form.IsVerified, "is_compliant": form.IsCompliant, "updated_by": updatedBy.String(), }) return nil } // CreateCustomerMobile creates a new customer via mobile app (simplified) func (s *customerService) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *uuid.UUID) (*Customer, error) { // Check if email already exists existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) if existingCustomer != nil { return nil, errors.New("customer with this email already exists") } // Create customer with mobile form customer := &Customer{ ID: uuid.New(), Type: CustomerType(form.Type), Status: CustomerStatusActive, FirstName: form.FirstName, LastName: form.LastName, FullName: form.FullName, Username: form.Username, Email: form.Email, Password: "", // Will be set after hashing Phone: form.Phone, Mobile: form.Mobile, CompanyName: form.CompanyName, Industry: form.Industry, IsVerified: false, IsCompliant: false, Language: "en", Currency: "USD", Timezone: "UTC", CreatedAt: time.Now().Unix(), UpdatedAt: time.Now().Unix(), CreatedBy: createdBy, } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost) if err != nil { s.logger.Error("Failed to hash password for mobile customer", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to process password") } customer.Password = string(hashedPassword) // Save to database err = s.repository.Create(ctx, customer) if err != nil { s.logger.Error("Failed to create customer via mobile", map[string]interface{}{ "error": err.Error(), "email": form.Email, "type": form.Type, "created_by": createdBy.String(), }) return nil, err } s.logger.Info("Customer created via mobile successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "type": customer.Type, "created_by": createdBy.String(), }) return customer, nil } // UpdateCustomerMobile updates a customer via mobile app (simplified) func (s *customerService) UpdateCustomerMobile(ctx context.Context, id uuid.UUID, form *UpdateCustomerMobileForm, updatedBy *uuid.UUID) (*Customer, error) { // Get existing customer customer, err := s.repository.GetByID(ctx, id) if err != nil { return nil, errors.New("customer not found") } // Update fields if provided if form.FirstName != nil { customer.FirstName = form.FirstName } if form.LastName != nil { customer.LastName = form.LastName } if form.FullName != nil { customer.FullName = form.FullName } if form.Phone != nil { customer.Phone = form.Phone } if form.Mobile != nil { customer.Mobile = form.Mobile } if form.CompanyName != nil { customer.CompanyName = form.CompanyName } if form.Industry != nil { customer.Industry = form.Industry } // Set updated by customer.UpdatedBy = updatedBy // Update in database err = s.repository.Update(ctx, customer) if err != nil { s.logger.Error("Failed to update customer via mobile", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return nil, errors.New("failed to update customer") } s.logger.Info("Customer updated via mobile successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "updated_by": updatedBy.String(), }) return customer, nil } // VerifyCustomer verifies a customer func (s *customerService) VerifyCustomer(ctx context.Context, id uuid.UUID, verifiedBy *uuid.UUID) error { // Get customer to check if exists customer, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("customer not found") } // Update verification err = s.repository.UpdateVerification(ctx, id, true, customer.IsCompliant, customer.ComplianceNotes) if err != nil { s.logger.Error("Failed to verify customer", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return errors.New("failed to verify customer") } s.logger.Info("Customer verified successfully", map[string]interface{}{ "customer_id": id.String(), "verified_by": verifiedBy.String(), }) return nil } // SuspendCustomer suspends a customer func (s *customerService) SuspendCustomer(ctx context.Context, id uuid.UUID, reason string, suspendedBy *uuid.UUID) error { // Get customer to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("customer not found") } // Update status to suspended err = s.repository.UpdateStatus(ctx, id, CustomerStatusSuspended) if err != nil { s.logger.Error("Failed to suspend customer", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return errors.New("failed to suspend customer") } s.logger.Info("Customer suspended successfully", map[string]interface{}{ "customer_id": id.String(), "reason": reason, "suspended_by": suspendedBy.String(), }) return nil } // ActivateCustomer activates a customer func (s *customerService) ActivateCustomer(ctx context.Context, id uuid.UUID, activatedBy *uuid.UUID) error { // Get customer to check if exists _, err := s.repository.GetByID(ctx, id) if err != nil { return errors.New("customer not found") } // Update status to active err = s.repository.UpdateStatus(ctx, id, CustomerStatusActive) if err != nil { s.logger.Error("Failed to activate customer", map[string]interface{}{ "error": err.Error(), "customer_id": id.String(), }) return errors.New("failed to activate customer") } s.logger.Info("Customer activated successfully", map[string]interface{}{ "customer_id": id.String(), "activated_by": activatedBy.String(), }) return nil } // Login handles customer authentication func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) { s.logger.Info("Customer login attempt", map[string]interface{}{ "username": form.Username, }) // Get customer by username customer, err := s.repository.GetByUsername(ctx, form.Username) if err != nil { s.logger.Warn("Customer login failed - username not found", map[string]interface{}{ "username": form.Username, }) return nil, errors.New("invalid credentials") } // Check if customer is active if customer.Status != CustomerStatusActive { s.logger.Warn("Customer login failed - account not active", map[string]interface{}{ "username": form.Username, "customer_id": customer.ID.String(), "status": string(customer.Status), }) return nil, errors.New("account is not active") } // Verify password err = bcrypt.CompareHashAndPassword([]byte(customer.Password), []byte(form.Password)) if err != nil { s.logger.Warn("Customer login failed - wrong password", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, }) return nil, errors.New("invalid credentials") } // Generate JWT tokens using authorization service companyID := "" if customer.CompanyID != nil { companyID = customer.CompanyID.String() } tokenPair, err := s.authService.GenerateTokenPair( customer.ID.String(), customer.Email, "customer", // Role for customers companyID, ) if err != nil { s.logger.Error("Failed to generate JWT tokens", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) return nil, errors.New("failed to generate authentication tokens") } s.logger.Info("Customer login successful", map[string]interface{}{ "username": form.Username, "customer_id": customer.ID.String(), "customer_type": string(customer.Type), }) return &AuthResponse{ Customer: customer.ToResponse(), AccessToken: tokenPair.AccessToken, RefreshToken: tokenPair.RefreshToken, ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now }, nil } // RefreshToken handles token refresh for customers func (s *customerService) RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error) { s.logger.Info("Customer token refresh attempt", map[string]interface{}{ "refresh_token": refreshToken[:10] + "...", // Log only first 10 chars for security }) // Validate refresh token and generate new access token newAccessToken, err := s.authService.RefreshAccessToken(refreshToken) if err != nil { s.logger.Warn("Failed to refresh access token", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("invalid or expired refresh token") } // Parse the new access token to get customer information validationResult, err := s.authService.ValidateAccessToken(newAccessToken) if err != nil { s.logger.Error("Failed to validate new access token", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("failed to generate valid access token") } if !validationResult.Valid { s.logger.Error("New access token validation failed", map[string]interface{}{ "error": validationResult.Error, }) return nil, errors.New("failed to generate valid access token") } // Get customer information customerID, err := uuid.Parse(validationResult.Claims.UserID) if err != nil { s.logger.Error("Failed to parse customer ID from token", map[string]interface{}{ "error": err.Error(), }) return nil, errors.New("invalid token format") } customer, err := s.repository.GetByID(ctx, customerID) if err != nil { s.logger.Error("Failed to get customer for token refresh", map[string]interface{}{ "error": err.Error(), "customer_id": customerID.String(), }) return nil, errors.New("customer not found") } // Check if customer is still active if customer.Status != CustomerStatusActive { return nil, errors.New("customer account is inactive") } s.logger.Info("Access token refreshed successfully", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, "customer_type": string(customer.Type), }) return &AuthResponse{ Customer: customer.ToResponse(), AccessToken: newAccessToken, RefreshToken: refreshToken, // Return the same refresh token ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now }, nil } // GetProfile retrieves customer profile information func (s *customerService) GetProfile(ctx context.Context, customerID uuid.UUID) (*Customer, error) { s.logger.Info("Getting customer profile", map[string]interface{}{ "customer_id": customerID.String(), }) customer, err := s.repository.GetByID(ctx, customerID) if err != nil { s.logger.Error("Failed to get customer profile", map[string]interface{}{ "error": err.Error(), "customer_id": customerID.String(), }) return nil, errors.New("customer not found") } s.logger.Info("Customer profile retrieved successfully", map[string]interface{}{ "customer_id": customerID.String(), "customer_type": string(customer.Type), }) return customer, nil } // Logout handles customer logout func (s *customerService) Logout(ctx context.Context, customerID uuid.UUID, accessToken string) error { s.logger.Info("Customer logout", map[string]interface{}{ "customer_id": customerID.String(), "access_token": accessToken[:10] + "...", // Log only first 10 chars for security }) // Invalidate the access token err := s.authService.InvalidateToken(accessToken) if err != nil { s.logger.Error("Failed to invalidate access token", map[string]interface{}{ "error": err.Error(), "customer_id": customerID.String(), }) // Don't return error here as the customer should still be logged out } s.logger.Info("Customer logout successful", map[string]interface{}{ "customer_id": customerID.String(), }) return nil } // Helper methods for converting forms to domain objects func (s *customerService) convertAddressForm(form *AddressForm) *Address { if form == nil { return nil } return &Address{ Street: form.Street, City: form.City, State: form.State, PostalCode: form.PostalCode, Country: form.Country, AddressType: form.AddressType, IsDefault: form.IsDefault, } } func (s *customerService) convertContactPersonForm(form *ContactPersonForm) *ContactPerson { if form == nil { return nil } return &ContactPerson{ FirstName: form.FirstName, LastName: form.LastName, FullName: form.FullName, Position: form.Position, Email: form.Email, Phone: form.Phone, Mobile: form.Mobile, IsPrimary: form.IsPrimary, } } func (s *customerService) getDefaultLanguage(language *string) string { if language != nil { return *language } return "en" } func (s *customerService) getDefaultCurrency(currency *string) string { if currency != nil { return *currency } return "USD" } func (s *customerService) getDefaultTimezone(timezone *string) string { if timezone != nil { return *timezone } return "UTC" }