From 0c7248b0238031901854c37f0c7761a6a4ca645b Mon Sep 17 00:00:00 2001 From: aryanchoudharyyyy Date: Mon, 25 May 2026 12:27:32 +0530 Subject: [PATCH] fix: resolve branch limit restriction and optimize validation (#129) --- RestroHub/build.gradle | 1 + .../logs/restroly-qrmenu-2026-05-17.0.log.gz | Bin 0 -> 27725 bytes .../logs/restroly-qrmenu-2026-05-18.0.log.gz | Bin 0 -> 799 bytes .../logs/restroly-qrmenu-2026-05-23.0.log.gz | Bin 0 -> 63355 bytes .../branch/controller/BranchController.java | 14 +++ .../branch/service/BranchServiceImpl.java | 7 ++ .../exception/GlobalExceptionHandler.java | 15 +++ .../controller/RestaurantController.java | 2 + .../service/RestaurantServiceImpl.java | 27 ++++- .../annotation/RequiresFeature.java | 13 +++ .../aspect/SubscriptionFeatureAspect.java | 79 ++++++++++++++ .../config/SubscriptionConfig.java | 89 +++++++++++++++ .../SubscriptionManagementController.java | 102 ++++++++++++++++++ .../dto/FeatureCheckResponse.java | 13 +++ .../dto/SubscriptionStatusResponse.java | 21 ++++ .../entity/RestaurantSubscription.java | 40 +++++++ .../subscription/entity/SubscriptionPlan.java | 47 ++++++++ .../subscription/enums/FeatureType.java | 10 ++ .../subscription/enums/SubscriptionType.java | 8 ++ .../exception/FeatureAccessException.java | 23 ++++ .../RestaurantSubscriptionRepository.java | 17 +++ .../SubscriptionPlanRepository.java | 14 +++ .../service/FeatureAccessService.java | 75 +++++++++++++ .../SubscriptionValidationService.java | 65 +++++++++++ 24 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 RestroHub/logs/restroly-qrmenu-2026-05-17.0.log.gz create mode 100644 RestroHub/logs/restroly-qrmenu-2026-05-18.0.log.gz create mode 100644 RestroHub/logs/restroly-qrmenu-2026-05-23.0.log.gz create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/annotation/RequiresFeature.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/aspect/SubscriptionFeatureAspect.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/config/SubscriptionConfig.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/controller/SubscriptionManagementController.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/FeatureCheckResponse.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/SubscriptionStatusResponse.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/RestaurantSubscription.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/SubscriptionPlan.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/FeatureType.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/SubscriptionType.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/exception/FeatureAccessException.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/RestaurantSubscriptionRepository.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/SubscriptionPlanRepository.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/FeatureAccessService.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/SubscriptionValidationService.java diff --git a/RestroHub/build.gradle b/RestroHub/build.gradle index 49e56a0f..8833a6fd 100644 --- a/RestroHub/build.gradle +++ b/RestroHub/build.gradle @@ -42,6 +42,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-tomcat' developmentOnly 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.boot:spring-boot-starter-aop' // Database runtimeOnly 'org.postgresql:postgresql:42.7.2' diff --git a/RestroHub/logs/restroly-qrmenu-2026-05-17.0.log.gz b/RestroHub/logs/restroly-qrmenu-2026-05-17.0.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..ddf3caf6c38f2898cf463a8e1bbb83a75bf9f81a GIT binary patch literal 27725 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 literal 0 HcmV?d00001 diff --git a/RestroHub/logs/restroly-qrmenu-2026-05-18.0.log.gz b/RestroHub/logs/restroly-qrmenu-2026-05-18.0.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..7cedb95066d6a0a2b6975548346f2ce9d018bef1 GIT binary patch literal 799 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 literal 0 HcmV?d00001 diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/branch/controller/BranchController.java b/RestroHub/src/main/java/com/restroly/qrmenu/branch/controller/BranchController.java index 6bfe74e3..83c7746a 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/branch/controller/BranchController.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/branch/controller/BranchController.java @@ -7,6 +7,8 @@ import com.restroly.qrmenu.common.generic.PageResponseDTO; import com.restroly.qrmenu.common.util.ApiConstants; +import com.restroly.qrmenu.subscription.enums.FeatureType; +import com.restroly.qrmenu.subscription.service.SubscriptionValidationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -45,6 +47,8 @@ public class BranchController { private final BranchService branchService; + private final SubscriptionValidationService subscriptionValidationService; + // ========== CREATE ========== @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) @@ -68,6 +72,16 @@ public ResponseEntity createBranch( @Valid @RequestBody BranchRequestDTO requestDTO) { log.info("REST request to create branch: {}", requestDTO.getName()); + Long restaurantId = requestDTO.getRestaurantId(); + + // 1. Check if their subscription plan allows the ADD_BRANCH feature at all + subscriptionValidationService.validateFeatureAccess(restaurantId, FeatureType.ADD_BRANCH); + + // 2. Count how many branches they currently have + long currentBranchCount = branchService.countBranchesByRestaurant(restaurantId); + + // 3. Check if adding one more branch exceeds their plan's maximum branch limit + subscriptionValidationService.validateBranchLimit(restaurantId, (int) currentBranchCount); BranchResponseDTO response = branchService.createBranch(requestDTO); URI location = URI.create("/" + ApiConstants.APP_NAME + SECURE_API_VERSION + diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/branch/service/BranchServiceImpl.java b/RestroHub/src/main/java/com/restroly/qrmenu/branch/service/BranchServiceImpl.java index 062596fc..69361029 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/branch/service/BranchServiceImpl.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/branch/service/BranchServiceImpl.java @@ -13,6 +13,8 @@ import com.restroly.qrmenu.restaurant.entity.Restaurant; import com.restroly.qrmenu.restaurant.repository.RestaurantRepository; +import com.restroly.qrmenu.subscription.enums.FeatureType; +import com.restroly.qrmenu.subscription.service.FeatureAccessService; import com.restroly.qrmenu.user.exception.DuplicateResourceException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -34,6 +36,7 @@ public class BranchServiceImpl implements BranchService { private final RestaurantRepository restaurantRepository; private final MenuRepository menuRepository; private final BranchMapper branchMapper; + private final FeatureAccessService featureAccessService; // ========== CREATE ========== @Override @@ -45,6 +48,10 @@ public BranchResponseDTO createBranch(BranchRequestDTO requestDTO) { .orElseThrow(() -> new ResourceNotFoundException( "Restaurant not found with ID: " + requestDTO.getRestaurantId())); + featureAccessService.assertFeatureAccess(requestDTO.getRestaurantId(), FeatureType.ADD_BRANCH); + long currentCount = countBranchesByRestaurant(requestDTO.getRestaurantId()); + featureAccessService.validateBranchLimit(requestDTO.getRestaurantId(), (int) currentCount); + // Check for duplicate branch name if (branchRepository.existsByNameAndRestaurant_RestId( requestDTO.getName(), requestDTO.getRestaurantId())) { diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/common/exception/GlobalExceptionHandler.java b/RestroHub/src/main/java/com/restroly/qrmenu/common/exception/GlobalExceptionHandler.java index 683ea172..3efdb04b 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/common/exception/GlobalExceptionHandler.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ // src/main/java/com/Restroly/qrmenu/common/exception/GlobalExceptionHandler.java package com.restroly.qrmenu.common.exception; +import com.restroly.qrmenu.subscription.exception.FeatureAccessException; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; @@ -22,13 +23,22 @@ import org.springframework.web.servlet.NoHandlerFoundException; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { + @ExceptionHandler(FeatureAccessException.class) + public ResponseEntity> handleFeatureAccess(FeatureAccessException ex) { + Map response = new HashMap<>(); + response.put("message", "Your current subscription does not support this feature"); + response.put("feature", ex.getFeature().name()); + return new ResponseEntity<>(response, HttpStatus.FORBIDDEN); + } @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity handleResourceNotFoundException( @@ -320,4 +330,9 @@ private ErrorResponse.ValidationError mapConstraintViolation(ConstraintViolation private String generateTraceId() { return UUID.randomUUID().toString().substring(0, 8); } + + + + + } \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/controller/RestaurantController.java b/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/controller/RestaurantController.java index 60255420..e5f13315 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/controller/RestaurantController.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/controller/RestaurantController.java @@ -76,6 +76,8 @@ public ResponseEntity createRestaurant( @ApiResponse(responseCode = "404", description = "Restaurant not found", content = @Content(schema = @Schema(implementation = ErrorResponse.class))) }) + @GetMapping(value = "/{restaurantId}") + @Operation(summary = "Get restaurant by ID", description = "Retrieves a restaurant by ID") public ResponseEntity getRestaurantById( @Parameter(description = "Long Id of the restaurant", required = true) @PathVariable Long restaurantId) { diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/service/RestaurantServiceImpl.java b/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/service/RestaurantServiceImpl.java index a89b0811..d1c2e67f 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/service/RestaurantServiceImpl.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/restaurant/service/RestaurantServiceImpl.java @@ -1,5 +1,6 @@ package com.restroly.qrmenu.restaurant.service; +import com.restroly.qrmenu.common.exception.BusinessException; import com.restroly.qrmenu.common.exception.ResourceAlreadyExistsException; import com.restroly.qrmenu.common.exception.ResourceNotFoundException; import com.restroly.qrmenu.restaurant.dto.RestaurantRequestDTO; @@ -10,10 +11,19 @@ import com.restroly.qrmenu.restaurant.repository.RestaurantRepository; +import com.restroly.qrmenu.subscription.entity.RestaurantSubscription; +import com.restroly.qrmenu.subscription.entity.SubscriptionPlan; +import com.restroly.qrmenu.subscription.enums.SubscriptionType; +import com.restroly.qrmenu.subscription.repository.RestaurantSubscriptionRepository; +import com.restroly.qrmenu.subscription.repository.SubscriptionPlanRepository; import lombok.*; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; @Service @@ -23,8 +33,8 @@ public class RestaurantServiceImpl implements RestaurantService{ private final RestaurantRepository restaurantRepository; private final RestaurantMapper restaurantMapper; - - private static final String RESTAURANT_NOT_FOUND_MSG = "RESTAURANT not found with id: %s"; + private final SubscriptionPlanRepository planRepository; + private final RestaurantSubscriptionRepository restaurantSubscriptionRepository; private static final String RESTAURANT_NOT_FOUND_MSG = "RESTAURANT not found with id: %s"; private static final String RESTAURANT_EXISTS_MSG = "RESTAURANT already exists with name: %s"; /** @@ -32,6 +42,7 @@ public class RestaurantServiceImpl implements RestaurantService{ * @return */ @Override + @Transactional public RestaurantResponseDTO createRestaurant(RestaurantRequestDTO requestDTO) { log.info("Creating new restaurant: {}", requestDTO.getName()); if (restaurantRepository.existsByNameIgnoreCase(requestDTO.getName())) { @@ -42,6 +53,18 @@ public RestaurantResponseDTO createRestaurant(RestaurantRequestDTO requestDTO) { Restaurant restaurant = restaurantMapper.toEntity(requestDTO); Restaurant savedRestaurant = restaurantRepository.save(restaurant); + SubscriptionPlan freePlan = planRepository + .findByTypeAndActiveTrue(SubscriptionType.FREE) + .orElseThrow(() -> new BusinessException("FREE plan not found in database.", HttpStatus.INTERNAL_SERVER_ERROR)); + RestaurantSubscription newSubscription = RestaurantSubscription.builder() + .restId(savedRestaurant.getRestId()) + .plan(freePlan) + .startDate(LocalDateTime.now()) + .endDate(LocalDateTime.now().plusYears(10)) // Free plan lasts 10 years + .active(true) + .build(); + + restaurantSubscriptionRepository.save(newSubscription); log.info("Successfully created food item with id: {}", savedRestaurant.getRestId()); return restaurantMapper.toResponseDTO(savedRestaurant); diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/annotation/RequiresFeature.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/annotation/RequiresFeature.java new file mode 100644 index 00000000..ad796730 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/annotation/RequiresFeature.java @@ -0,0 +1,13 @@ +package com.restroly.qrmenu.subscription.annotation; + +import com.restroly.qrmenu.subscription.enums.FeatureType; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequiresFeature { + FeatureType value(); + String restaurantIdParam() default "restId"; +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/aspect/SubscriptionFeatureAspect.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/aspect/SubscriptionFeatureAspect.java new file mode 100644 index 00000000..b485b57e --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/aspect/SubscriptionFeatureAspect.java @@ -0,0 +1,79 @@ +package com.restroly.qrmenu.subscription.aspect; + +import com.restroly.qrmenu.subscription.annotation.RequiresFeature; +import com.restroly.qrmenu.subscription.service.SubscriptionValidationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class SubscriptionFeatureAspect { + + private final SubscriptionValidationService validationService; + + @Before("@annotation(requiresFeature)") + public void checkFeatureAccess(JoinPoint joinPoint, RequiresFeature requiresFeature) { + Long restaurantId = resolveRestaurantId(joinPoint, requiresFeature.restaurantIdParam()); + + if (restaurantId == null) { + log.warn("Could not resolve restaurantId from param '{}', skipping feature check.", + requiresFeature.restaurantIdParam()); + return; + } + + log.debug("Checking feature {} for restaurantId={}", requiresFeature.value(), restaurantId); + validationService.validateFeatureAccess(restaurantId, requiresFeature.value()); + } + + private Long resolveRestaurantId(JoinPoint joinPoint, String paramName) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Parameter[] parameters = method.getParameters(); + Object[] args = joinPoint.getArgs(); + + for (int i = 0; i < parameters.length; i++) { + Parameter param = parameters[i]; + + // Match by Java parameter name + if (param.getName().equals(paramName)) { + return toLong(args[i]); + } + + // Match by @PathVariable name + PathVariable pv = param.getAnnotation(PathVariable.class); + if (pv != null && (pv.value().equals(paramName) || pv.name().equals(paramName))) { + return toLong(args[i]); + } + + // Match by @RequestParam name + RequestParam rp = param.getAnnotation(RequestParam.class); + if (rp != null && (rp.value().equals(paramName) || rp.name().equals(paramName))) { + return toLong(args[i]); + } + } + + return null; + } + + private Long toLong(Object value) { + if (value instanceof Long l) return l; + if (value instanceof Integer i) return i.longValue(); + if (value instanceof String s) { + try { return Long.parseLong(s); } catch (NumberFormatException ignored) {} + } + return null; + } +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/config/SubscriptionConfig.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/config/SubscriptionConfig.java new file mode 100644 index 00000000..68bdbd7e --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/config/SubscriptionConfig.java @@ -0,0 +1,89 @@ +package com.restroly.qrmenu.subscription.config; + +import com.restroly.qrmenu.subscription.entity.SubscriptionPlan; +import com.restroly.qrmenu.subscription.enums.FeatureType; +import com.restroly.qrmenu.subscription.enums.SubscriptionType; +import com.restroly.qrmenu.subscription.repository.SubscriptionPlanRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import java.math.BigDecimal; +import java.util.EnumSet; +import java.util.List; + +@Slf4j +@Configuration +@EnableAspectJAutoProxy // activates AOP proxy so @RequiresFeature aspect fires +@RequiredArgsConstructor +public class SubscriptionConfig { + + private final SubscriptionPlanRepository planRepository; + + /** + * Seeds default subscription plans on first startup if they don't exist yet. + * Each plan builds on the previous tier's features — FREE has none, + * BASIC adds WhatsApp, PRO adds AI + domains, ENTERPRISE unlocks everything. + */ + @Bean + public ApplicationRunner seedSubscriptionPlans() { + return args -> { + if (planRepository.count() > 0) { + log.info("Subscription plans already seeded, skipping."); + return; + } + + List plans = List.of( + SubscriptionPlan.builder() + .type(SubscriptionType.FREE) + .name("Free Plan") + .monthlyPrice(BigDecimal.ZERO) + .allowedFeatures(EnumSet.noneOf(FeatureType.class)) + .maxBranches(1) + .active(true) + .build(), + + SubscriptionPlan.builder() + .type(SubscriptionType.BASIC) + .name("Basic Plan") + .monthlyPrice(new BigDecimal("499.00")) + .allowedFeatures(EnumSet.of( + FeatureType.WHATSAPP_NOTIFICATION, + FeatureType.WEBSITE_TEMPLATE + )) + .maxBranches(2) + .active(true) + .build(), + + SubscriptionPlan.builder() + .type(SubscriptionType.PRO) + .name("Pro Plan") + .monthlyPrice(new BigDecimal("1499.00")) + .allowedFeatures(EnumSet.of( + FeatureType.WHATSAPP_NOTIFICATION, + FeatureType.AI_TRANSLATION, + FeatureType.WEBSITE_TEMPLATE, + FeatureType.ADD_BRANCH, + FeatureType.CUSTOM_DOMAIN + )) + .maxBranches(5) + .active(true) + .build(), + + SubscriptionPlan.builder() + .type(SubscriptionType.ENTERPRISE) + .name("Enterprise Plan") + .monthlyPrice(new BigDecimal("4999.00")) + .allowedFeatures(EnumSet.allOf(FeatureType.class)) + .maxBranches(Integer.MAX_VALUE) + .active(true) + .build() + ); + + planRepository.saveAll(plans); + log.info("Seeded {} subscription plans.", plans.size()); + }; + } +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/controller/SubscriptionManagementController.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/controller/SubscriptionManagementController.java new file mode 100644 index 00000000..ab0706c5 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/controller/SubscriptionManagementController.java @@ -0,0 +1,102 @@ +package com.restroly.qrmenu.subscription.controller; + +import com.restroly.qrmenu.subscription.dto.SubscriptionStatusResponse; +import com.restroly.qrmenu.subscription.entity.RestaurantSubscription; +import com.restroly.qrmenu.subscription.entity.SubscriptionPlan; +import com.restroly.qrmenu.subscription.enums.SubscriptionType; +import com.restroly.qrmenu.subscription.repository.RestaurantSubscriptionRepository; +import com.restroly.qrmenu.subscription.repository.SubscriptionPlanRepository; +import com.restroly.qrmenu.common.exception.ResourceNotFoundException; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; + +import static com.restroly.qrmenu.common.util.ApiConstants.SECURE_API_VERSION; + +@RestController +@RequestMapping(SECURE_API_VERSION + "/restaurants/{restId}/subscription") +@RequiredArgsConstructor +public class SubscriptionManagementController { + + private final RestaurantSubscriptionRepository subscriptionRepository; + private final SubscriptionPlanRepository planRepository; + + // --- GET CURRENT PLAN --- + @GetMapping + public ResponseEntity getActiveSubscriptionState(@PathVariable Long restId) { + return subscriptionRepository.findByRestIdAndActiveTrueAndEndDateAfter(restId, LocalDateTime.now()) + .map(sub -> SubscriptionStatusResponse.builder() + .restId(restId) + .planType(sub.getPlan().getType()) + .planName(sub.getPlan().getName()) + .expiresAt(sub.getEndDate()) + .active(true) + .maxBranches(sub.getPlan().getMaxBranches()) + .allowedFeatures(sub.getPlan().getAllowedFeatures()) + .build()) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + // --- CHANGE PLAN (UPGRADE/DOWNGRADE) --- + @PutMapping("/change") + @PreAuthorize("hasRole('ADMIN')") + @Transactional + public ResponseEntity transitionAccountTier( + @PathVariable Long restId, + @RequestParam SubscriptionType targetTier) { + + // 1. Find the target plan + SubscriptionPlan targetPlan = planRepository.findByTypeAndActiveTrue(targetTier) + .orElseThrow(() -> new ResourceNotFoundException("Target plan not found: " + targetTier)); + + // Variable to hold our dynamic success message + String actionMessage = "subscribed"; + + // 2. Find Current Plan & Check for Upgrade vs Downgrade + Optional currentSubOpt = subscriptionRepository + .findByRestIdAndActiveTrueAndEndDateAfter(restId, LocalDateTime.now()); + + if (currentSubOpt.isPresent()) { + RestaurantSubscription currentLease = currentSubOpt.get(); + SubscriptionType currentTier = currentLease.getPlan().getType(); + + // Duplicate Check + if (currentTier == targetTier) { + return ResponseEntity.badRequest().body(Map.of("message", "Already on " + targetTier.name() + " plan!")); + } + + // Determine Upgrade or Downgrade based on Enum position + if (targetTier.ordinal() > currentTier.ordinal()) { + actionMessage = "upgraded"; + } else { + actionMessage = "downgraded"; + } + + // Deactivate old plan + currentLease.setActive(false); + subscriptionRepository.save(currentLease); + } + + // 3. Save new plan + RestaurantSubscription newSubscription = RestaurantSubscription.builder() + .restId(restId) + .plan(targetPlan) + .startDate(LocalDateTime.now()) + .endDate(LocalDateTime.now().plusYears(1)) + .active(true) + .build(); + + subscriptionRepository.save(newSubscription); + + // 4. Return dynamic message + return ResponseEntity.ok(Map.of("message", "Successfully " + actionMessage + " to " + targetTier.name())); + } +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/FeatureCheckResponse.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/FeatureCheckResponse.java new file mode 100644 index 00000000..64342ef8 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/FeatureCheckResponse.java @@ -0,0 +1,13 @@ +package com.restroly.qrmenu.subscription.dto; + +import com.restroly.qrmenu.subscription.enums.FeatureType; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class FeatureCheckResponse { + private Long restId; + private FeatureType feature; + private boolean allowed; +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/SubscriptionStatusResponse.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/SubscriptionStatusResponse.java new file mode 100644 index 00000000..24feaece --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/dto/SubscriptionStatusResponse.java @@ -0,0 +1,21 @@ +package com.restroly.qrmenu.subscription.dto; + +import com.restroly.qrmenu.subscription.enums.FeatureType; +import com.restroly.qrmenu.subscription.enums.SubscriptionType; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; + +@Data +@Builder +public class SubscriptionStatusResponse { + private Long restId; + private SubscriptionType planType; + private String planName; + private LocalDateTime expiresAt; + private boolean active; + private int maxBranches; + private Set allowedFeatures; +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/RestaurantSubscription.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/RestaurantSubscription.java new file mode 100644 index 00000000..86fe38cb --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/RestaurantSubscription.java @@ -0,0 +1,40 @@ +package com.restroly.qrmenu.subscription.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "restaurant_subscriptions") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class RestaurantSubscription { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "rest_id", nullable = false) + private Long restId; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "plan_id", nullable = false) + private SubscriptionPlan plan; + + @Column(nullable = false) + private LocalDateTime startDate; + + @Column(nullable = false) + private LocalDateTime endDate; + + @Column(nullable = false) + private boolean active; + + public boolean isExpired() { + return LocalDateTime.now().isAfter(endDate); + } +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/SubscriptionPlan.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/SubscriptionPlan.java new file mode 100644 index 00000000..85324f56 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/entity/SubscriptionPlan.java @@ -0,0 +1,47 @@ +package com.restroly.qrmenu.subscription.entity; +import com.restroly.qrmenu.subscription.enums.FeatureType; +import com.restroly.qrmenu.subscription.enums.SubscriptionType; +import jakarta.persistence.*; +import lombok.*; + +import java.math.BigDecimal; +import java.util.Set; + +@Entity +@Table(name = "subscription_plans") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SubscriptionPlan { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, unique = true) + private SubscriptionType type; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private BigDecimal monthlyPrice; + + @ElementCollection(targetClass = FeatureType.class, fetch = FetchType.EAGER) + @CollectionTable( + name = "plan_features", + joinColumns = @JoinColumn(name = "plan_id") + ) + @Enumerated(EnumType.STRING) + @Column(name = "feature") + private Set allowedFeatures; + + @Column(nullable = false) + private int maxBranches; + + @Column(nullable = false) + private boolean active; +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/FeatureType.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/FeatureType.java new file mode 100644 index 00000000..ed99b69d --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/FeatureType.java @@ -0,0 +1,10 @@ +package com.restroly.qrmenu.subscription.enums; +public enum FeatureType { + WHATSAPP_NOTIFICATION, + AI_TRANSLATION, + ADD_BRANCH, + CUSTOM_DOMAIN, + WEBSITE_TEMPLATE, + ADVANCED_ANALYTICS, + PRIORITY_SUPPORT +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/SubscriptionType.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/SubscriptionType.java new file mode 100644 index 00000000..5077c715 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/enums/SubscriptionType.java @@ -0,0 +1,8 @@ +package com.restroly.qrmenu.subscription.enums; + +public enum SubscriptionType { + FREE, + BASIC, + PRO, + ENTERPRISE +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/exception/FeatureAccessException.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/exception/FeatureAccessException.java new file mode 100644 index 00000000..d941ab19 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/exception/FeatureAccessException.java @@ -0,0 +1,23 @@ +package com.restroly.qrmenu.subscription.exception; + +import com.restroly.qrmenu.subscription.enums.FeatureType; +import lombok.Getter; + +@Getter +public class FeatureAccessException extends RuntimeException { + + private final FeatureType feature; + private final String reason; + + public FeatureAccessException(FeatureType feature) { + super("Your current subscription does not support this feature: " + feature.name()); + this.feature = feature; + this.reason = "Your current subscription does not support this feature."; + } + + public FeatureAccessException(FeatureType feature, String reason) { + super(reason); + this.feature = feature; + this.reason = reason; + } +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/RestaurantSubscriptionRepository.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/RestaurantSubscriptionRepository.java new file mode 100644 index 00000000..a8d0fbe2 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/RestaurantSubscriptionRepository.java @@ -0,0 +1,17 @@ +package com.restroly.qrmenu.subscription.repository; + +import com.restroly.qrmenu.subscription.entity.RestaurantSubscription; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Repository +public interface RestaurantSubscriptionRepository extends JpaRepository { + + Optional findByRestIdAndActiveTrueAndEndDateAfter( + Long restaurantId, + LocalDateTime now + ); +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/SubscriptionPlanRepository.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/SubscriptionPlanRepository.java new file mode 100644 index 00000000..940835f2 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/repository/SubscriptionPlanRepository.java @@ -0,0 +1,14 @@ +package com.restroly.qrmenu.subscription.repository; + +import com.restroly.qrmenu.subscription.entity.SubscriptionPlan; +import com.restroly.qrmenu.subscription.enums.SubscriptionType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface SubscriptionPlanRepository extends JpaRepository { + + Optional findByTypeAndActiveTrue(SubscriptionType type); +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/FeatureAccessService.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/FeatureAccessService.java new file mode 100644 index 00000000..0b315718 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/FeatureAccessService.java @@ -0,0 +1,75 @@ +package com.restroly.qrmenu.subscription.service; + +import com.restroly.qrmenu.subscription.entity.RestaurantSubscription; +import com.restroly.qrmenu.subscription.enums.FeatureType; +import com.restroly.qrmenu.subscription.exception.FeatureAccessException; +import com.restroly.qrmenu.subscription.repository.RestaurantSubscriptionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class FeatureAccessService { + + private final RestaurantSubscriptionRepository subscriptionRepository; + + public boolean hasFeatureAccess(Long restId, FeatureType feature) { + Optional subscription = + subscriptionRepository.findByRestIdAndActiveTrueAndEndDateAfter( + restId, LocalDateTime.now() + ); + + return subscription + .map(sub -> { + + if (feature == FeatureType.ADD_BRANCH && sub.getPlan().getMaxBranches() > 0) { + return true; + } + + + return sub.getPlan().getAllowedFeatures().contains(feature); + }) + .orElse(false); + } + + public void assertFeatureAccess(Long restId, FeatureType feature) { + if (!hasFeatureAccess(restId, feature)) { + throw new FeatureAccessException(feature); + } + } + + public boolean canUseWhatsApp(Long restId) { + return hasFeatureAccess(restId, FeatureType.WHATSAPP_NOTIFICATION); + } + + public boolean canUseAiTranslation(Long restId) { + return hasFeatureAccess(restId, FeatureType.AI_TRANSLATION); + } + + public boolean canAddBranch(Long restId) { + return hasFeatureAccess(restId, FeatureType.ADD_BRANCH); + } + + public boolean canUseCustomDomain(Long restId) { + return hasFeatureAccess(restId, FeatureType.CUSTOM_DOMAIN); + } + + public boolean canUseWebsiteTemplate(Long restId) { + return hasFeatureAccess(restId, FeatureType.WEBSITE_TEMPLATE); + } + public void validateBranchLimit(Long restId, int currentBranchCount) { + Optional sub = subscriptionRepository.findByRestIdAndActiveTrueAndEndDateAfter(restId, LocalDateTime.now()); + + if (sub.isPresent()) { + int limit = sub.get().getPlan().getMaxBranches(); + if (currentBranchCount >= limit) { + throw new FeatureAccessException(FeatureType.ADD_BRANCH); + } + } else { + throw new FeatureAccessException(FeatureType.ADD_BRANCH); + } + } +} \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/SubscriptionValidationService.java b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/SubscriptionValidationService.java new file mode 100644 index 00000000..db32e9b2 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/subscription/service/SubscriptionValidationService.java @@ -0,0 +1,65 @@ +package com.restroly.qrmenu.subscription.service; + +import com.restroly.qrmenu.subscription.entity.RestaurantSubscription; +import com.restroly.qrmenu.subscription.enums.FeatureType; +import com.restroly.qrmenu.subscription.exception.FeatureAccessException; +import com.restroly.qrmenu.subscription.repository.RestaurantSubscriptionRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SubscriptionValidationService { + + private final RestaurantSubscriptionRepository subscriptionRepository; + + public void validateFeatureAccess(Long restId, FeatureType feature) { + Optional subscriptionOpt = + subscriptionRepository.findByRestIdAndActiveTrueAndEndDateAfter( + restId, LocalDateTime.now() + ); + + if (subscriptionOpt.isEmpty()) { + log.warn("No active subscription found for restaurantId={}", restId); + throw new FeatureAccessException(feature, "No active subscription found."); + } + + RestaurantSubscription subscription = subscriptionOpt.get(); + + + if (feature == FeatureType.ADD_BRANCH && subscription.getPlan().getMaxBranches() > 0) { + return; + } + + + if (!subscription.getPlan().getAllowedFeatures().contains(feature)) { + log.warn("Feature {} not allowed for restaurantId={}, plan={}", + feature, restId, subscription.getPlan().getType()); + throw new FeatureAccessException(feature); + } + } + public void validateBranchLimit(Long restaurantId, int currentBranchCount) { + Optional subscriptionOpt = + subscriptionRepository.findByRestIdAndActiveTrueAndEndDateAfter( + restaurantId, LocalDateTime.now() + ); + + if (subscriptionOpt.isEmpty()) { + throw new FeatureAccessException(FeatureType.ADD_BRANCH, "No active subscription found."); + } + + int maxBranches = subscriptionOpt.get().getPlan().getMaxBranches(); + + if (currentBranchCount >= maxBranches) { + throw new FeatureAccessException( + FeatureType.ADD_BRANCH, + String.format("Branch limit of %d reached for your current plan.", maxBranches) + ); + } + } +}