From 24130552c9772a8426ff3530495068be85a4591e Mon Sep 17 00:00:00 2001 From: ShivamVerma380 Date: Sun, 18 Jan 2026 13:50:51 +0530 Subject: [PATCH 1/4] Removed maven dependency of aws and s3 --- divinespark-backend/pom.xml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/divinespark-backend/pom.xml b/divinespark-backend/pom.xml index 370a885..95b4dd1 100644 --- a/divinespark-backend/pom.xml +++ b/divinespark-backend/pom.xml @@ -27,7 +27,7 @@ - 21 + 17 @@ -124,18 +124,6 @@ spring-boot-starter-mail - - software.amazon.awssdk - s3 - 2.24.9 - - - - software.amazon.awssdk - auth - 2.24.9 - - From 95c353e2b1e8e7a0ba9729d64bf6a84caaea58d9 Mon Sep 17 00:00:00 2001 From: Shivam Verma Date: Fri, 30 Jan 2026 14:07:37 +0530 Subject: [PATCH 2/4] Merging main changes to prod (#83) * Add GitHub Actions CI for Spring Boot tests (#68) * Trigger CI * Update Java version to 21 in workflow (#69) * Rs/fix workflow location (#71) * Fix workflow path so GitHub Actions can detect it * Updated junit run * Updated yml * Added blog feature * Added an event-ticker * Blog issue ! * gallery section implementation with imageurl for future scaling * added installment logic * Installment implementation * Fixed installment issue completly * InstallmentAdmin * Refactor SecurityConfig for improved authorization rules * added the blog and review endpoints --------- Co-authored-by: RuchaChavan21 Co-authored-by: nehan1311 Co-authored-by: sujaljain272004 <160271734+sujaljain272004@users.noreply.github.com> --- .github/workflows/spring-tests.yml | 20 +++ .../controller/admin/AdminBlogController.java | 61 +++++++ .../admin/AdminEventController.java | 44 +++++ .../admin/AdminProgramController.java | 44 +++++ .../AdminSessionInstallmentController.java | 30 ++++ .../admin/AdminSessionPaymentController.java | 32 ++++ .../publicapi/PublicEventController.java | 26 +++ .../publicapi/PublicProgramController.java | 25 +++ .../controller/user/BlogController.java | 28 +++ .../controller/user/BookingController.java | 21 +++ .../user/PaymentVerificationController.java | 27 +++ .../user/PaymentWebhookController.java | 2 + .../controller/user/UserController.java | 23 ++- .../user/UserInstallmentController.java | 54 ++++++ .../dto/AdminInstallmentResponse.java | 33 ++++ .../dto/AdminSessionBookingResponse.java | 19 +- .../dto/AdminSessionOverviewResponse.java | 105 +++++++++++ .../AdminSessionUserInstallmentResponse.java | 56 ++++++ .../dto/AdminSessionUserPaymentResponse.java | 105 +++++++++++ .../dto/AdminSessionUserResponse.java | 8 +- .../divinespark/dto/BlogCreateRequest.java | 50 ++++++ .../divinespark/dto/BlogDetailResponse.java | 52 ++++++ .../com/divinespark/dto/BlogListResponse.java | 71 ++++++++ .../dto/DonationCreateRequest.java | 6 +- .../com/divinespark/dto/EventRequest.java | 57 ++++++ .../divinespark/dto/EventTickerResponse.java | 53 ++++++ .../dto/InstallmentPayResponse.java | 24 +++ .../InstallmentPaymentInitiateResponse.java | 46 +++++ .../divinespark/dto/InstallmentResponse.java | 34 ++++ .../com/divinespark/dto/ProgramRequest.java | 54 ++++++ .../com/divinespark/dto/ProgramResponse.java | 41 +++++ .../dto/SessionListItemResponse.java | 61 +++++++ .../divinespark/dto/UserBookingResponse.java | 13 +- .../dto/UserSessionBookingResponse.java | 37 ++++ .../java/com/divinespark/entity/Blog.java | 127 +++++++++++++ .../java/com/divinespark/entity/Booking.java | 77 +++++++- .../java/com/divinespark/entity/Event.java | 84 +++++++++ .../com/divinespark/entity/Installment.java | 111 ++++++++++++ .../java/com/divinespark/entity/Program.java | 95 ++++++++++ .../divinespark/entity/enums/BlogStatus.java | 6 + .../entity/enums/BookingStatus.java | 8 + .../entity/enums/InstallmentStatus.java | 6 + .../divinespark/entity/enums/PaymentType.java | 7 + .../repository/BlogRepository.java | 28 +++ .../repository/BookingRepository.java | 110 +++++++----- .../repository/EventRepository.java | 23 +++ .../repository/InstallmentRepository.java | 41 +++++ .../repository/ProgramRepository.java | 13 ++ .../divinespark/security/SecurityConfig.java | 37 ++-- .../service/AdminInstallmentService.java | 8 + .../service/AdminSessionPaymentService.java | 7 + .../com/divinespark/service/BlogService.java | 27 +++ .../service/BookingCleanupService.java | 10 +- .../divinespark/service/BookingService.java | 4 + .../com/divinespark/service/EventService.java | 20 +++ .../service/InstallementService.java | 19 ++ .../divinespark/service/ProgramService.java | 20 +++ .../divinespark/service/RazorpayService.java | 2 +- .../divinespark/service/SessionService.java | 6 + .../impl/AdminInstallmentServiceImpl.java | 64 +++++++ .../impl/AdminSessionPaymentServiceImpl.java | 90 ++++++++++ .../service/impl/BlogServiceImpl.java | 166 +++++++++++++++++ .../service/impl/BookingServiceImpl.java | 39 +++- .../service/impl/EventServiceImpl.java | 86 +++++++++ .../service/impl/InstallmentServiceImpl.java | 170 ++++++++++++++++++ .../service/impl/PaymentServiceImpl.java | 138 ++++++++++---- .../service/impl/ProgramServiceImpl.java | 75 ++++++++ .../service/impl/RazorpayServiceImpl.java | 8 +- .../service/impl/SessionServiceImpl.java | 147 +++++++++++---- 69 files changed, 3075 insertions(+), 166 deletions(-) create mode 100644 .github/workflows/spring-tests.yml create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminBlogController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminEventController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminProgramController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionInstallmentController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionPaymentController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicEventController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicProgramController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/user/BlogController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentVerificationController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/controller/user/UserInstallmentController.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/AdminInstallmentResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionOverviewResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserInstallmentResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserPaymentResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/BlogCreateRequest.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/BlogDetailResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/BlogListResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/EventRequest.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/EventTickerResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPayResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPaymentInitiateResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/InstallmentResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/ProgramRequest.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/ProgramResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/SessionListItemResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/dto/UserSessionBookingResponse.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/Blog.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/Event.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/Installment.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/Program.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/enums/BlogStatus.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/enums/BookingStatus.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/enums/InstallmentStatus.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/entity/enums/PaymentType.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/repository/BlogRepository.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/repository/EventRepository.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/repository/InstallmentRepository.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/repository/ProgramRepository.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/AdminInstallmentService.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/AdminSessionPaymentService.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/BlogService.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/EventService.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/InstallementService.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/ProgramService.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/impl/AdminInstallmentServiceImpl.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/impl/AdminSessionPaymentServiceImpl.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/impl/BlogServiceImpl.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/impl/EventServiceImpl.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/impl/InstallmentServiceImpl.java create mode 100644 divinespark-backend/src/main/java/com/divinespark/service/impl/ProgramServiceImpl.java diff --git a/.github/workflows/spring-tests.yml b/.github/workflows/spring-tests.yml new file mode 100644 index 0000000..b2ef8a2 --- /dev/null +++ b/.github/workflows/spring-tests.yml @@ -0,0 +1,20 @@ +jobs: + build: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: divinespark-backend # <--- Add this line + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Build and test + run: mvn -B test diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminBlogController.java b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminBlogController.java new file mode 100644 index 0000000..173fd79 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminBlogController.java @@ -0,0 +1,61 @@ +package com.divinespark.controller.admin; + +import com.divinespark.dto.BlogCreateRequest; +import com.divinespark.dto.BlogDetailResponse; +import com.divinespark.dto.BlogListResponse; +import com.divinespark.service.BlogService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/admin/blogs") +@PreAuthorize("hasRole('ADMIN')") +public class AdminBlogController { + + private final BlogService blogService; + + public AdminBlogController(BlogService blogService) { + this.blogService = blogService; + } + + // 🔹 GET all blogs (admin view: includes drafts + published) + @GetMapping + public List getAllBlogs() { + return blogService.getAllBlogsForAdmin(); + } + + // 🔹 GET single blog by ID (admin edit view) + @GetMapping("/{id}") + public BlogDetailResponse getBlog(@PathVariable Long id) { + return blogService.getBlogByIdForAdmin(id); + } + + // 🔹 CREATE blog + @PostMapping + public void createBlog(@RequestBody BlogCreateRequest request) { + blogService.createBlog(request); + } + + // 🔹 UPDATE blog + @PutMapping("/{id}") + public void updateBlog( + @PathVariable Long id, + @RequestBody BlogCreateRequest request + ) { + blogService.updateBlog(id, request); + } + + // 🔹 PUBLISH / UNPUBLISH blog + @PatchMapping("/{id}/publish") + public void publishBlog(@PathVariable Long id) { + blogService.publishBlog(id); + } + + // 🔹 DELETE blog + @DeleteMapping("/{id}") + public void deleteBlog(@PathVariable Long id) { + blogService.deleteBlog(id); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminEventController.java b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminEventController.java new file mode 100644 index 0000000..59d4295 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminEventController.java @@ -0,0 +1,44 @@ +package com.divinespark.controller.admin; + +import com.divinespark.dto.EventRequest; +import com.divinespark.service.EventService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/admin/events") +@PreAuthorize("hasRole('ADMIN')") +public class AdminEventController { + + private final EventService service; + + public AdminEventController(EventService service) { + this.service = service; + } + + @PostMapping + public ResponseEntity create(@Valid @RequestBody EventRequest request) { + return ResponseEntity.ok(service.create(request)); + } + + @PutMapping("/{id}") + public ResponseEntity update( + @PathVariable Long id, + @Valid @RequestBody EventRequest request) { + return ResponseEntity.ok(service.update(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + service.delete(id); + return ResponseEntity.noContent().build(); + } + + @GetMapping + public ResponseEntity list() { + return ResponseEntity.ok(service.getAllAdmin()); + } +} + diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminProgramController.java b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminProgramController.java new file mode 100644 index 0000000..c881c9b --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminProgramController.java @@ -0,0 +1,44 @@ +package com.divinespark.controller.admin; + +import com.divinespark.dto.ProgramRequest; +import com.divinespark.service.ProgramService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/admin/programs") +@PreAuthorize("hasRole('ADMIN')") +public class AdminProgramController { + + private final ProgramService service; + + public AdminProgramController(ProgramService service) { + this.service = service; + } + + @PostMapping + public ResponseEntity create(@Valid @RequestBody ProgramRequest request) { + return ResponseEntity.ok(service.create(request)); + } + + @PutMapping("/{id}") + public ResponseEntity update( + @PathVariable Long id, + @Valid @RequestBody ProgramRequest request) { + return ResponseEntity.ok(service.update(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id) { + service.delete(id); + return ResponseEntity.noContent().build(); + } + + @GetMapping + public ResponseEntity list() { + return ResponseEntity.ok(service.getAllAdmin()); + } +} + diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionInstallmentController.java b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionInstallmentController.java new file mode 100644 index 0000000..12d93cd --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionInstallmentController.java @@ -0,0 +1,30 @@ +package com.divinespark.controller.admin; + +import com.divinespark.dto.AdminSessionUserInstallmentResponse; +import com.divinespark.service.AdminInstallmentService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/admin/sessions") +public class AdminSessionInstallmentController { + + private final AdminInstallmentService adminInstallmentService; + + public AdminSessionInstallmentController(AdminInstallmentService adminInstallmentService) { + this.adminInstallmentService = adminInstallmentService; + } + + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/{sessionId}/installments") + public ResponseEntity> getSessionInstallments( + @PathVariable Long sessionId + ) { + return ResponseEntity.ok( + adminInstallmentService.getSessionInstallments(sessionId) + ); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionPaymentController.java b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionPaymentController.java new file mode 100644 index 0000000..e1f7088 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/admin/AdminSessionPaymentController.java @@ -0,0 +1,32 @@ +package com.divinespark.controller.admin; + + + +import com.divinespark.dto.AdminSessionOverviewResponse; +import com.divinespark.service.AdminSessionPaymentService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/admin/sessions") +@PreAuthorize("hasRole('ADMIN')") +public class AdminSessionPaymentController { + + private final AdminSessionPaymentService service; + + public AdminSessionPaymentController( + AdminSessionPaymentService service + ) { + this.service = service; + } + + @GetMapping("/{sessionId}/overview") + public ResponseEntity overview( + @PathVariable Long sessionId + ) { + return ResponseEntity.ok( + service.getSessionOverview(sessionId) + ); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicEventController.java b/divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicEventController.java new file mode 100644 index 0000000..25d22d0 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicEventController.java @@ -0,0 +1,26 @@ +package com.divinespark.controller.publicapi; + +import com.divinespark.dto.EventTickerResponse; +import com.divinespark.service.EventService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/public/events") +public class PublicEventController { + + private final EventService service; + + public PublicEventController(EventService service) { + this.service = service; + } + + @GetMapping("/ticker") + public ResponseEntity> getTicker() { + return ResponseEntity.ok(service.getTickerEvents()); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicProgramController.java b/divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicProgramController.java new file mode 100644 index 0000000..91202c7 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/publicapi/PublicProgramController.java @@ -0,0 +1,25 @@ +package com.divinespark.controller.publicapi; + +import com.divinespark.service.ProgramService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/public/programs") +public class PublicProgramController { + + private final ProgramService service; + + public PublicProgramController(ProgramService service) { + this.service = service; + } + + @GetMapping + public ResponseEntity getByCategory( + @RequestParam String category) { + return ResponseEntity.ok(service.getByCategory(category)); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/user/BlogController.java b/divinespark-backend/src/main/java/com/divinespark/controller/user/BlogController.java new file mode 100644 index 0000000..4943629 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/user/BlogController.java @@ -0,0 +1,28 @@ +package com.divinespark.controller.user; + +import com.divinespark.dto.*; +import com.divinespark.service.BlogService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/blogs") +public class BlogController { + + private final BlogService blogService; + + public BlogController(BlogService blogService) { + this.blogService = blogService; + } + + @GetMapping + public List getBlogs() { + return blogService.getPublishedBlogs(); + } + + @GetMapping("/{slug}") + public BlogDetailResponse getBlog(@PathVariable String slug) { + return blogService.getBlogBySlug(slug); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/user/BookingController.java b/divinespark-backend/src/main/java/com/divinespark/controller/user/BookingController.java index 75b6490..d540353 100644 --- a/divinespark-backend/src/main/java/com/divinespark/controller/user/BookingController.java +++ b/divinespark-backend/src/main/java/com/divinespark/controller/user/BookingController.java @@ -2,10 +2,13 @@ import com.divinespark.dto.ReviewCreateRequest; import com.divinespark.dto.UserBookingResponse; +import com.divinespark.dto.UserSessionBookingResponse; +import com.divinespark.entity.Booking; import com.divinespark.security.CustomUserDetails; import com.divinespark.service.BookingService; import com.divinespark.service.ReviewService; import com.divinespark.service.UserService; +import org.springframework.data.jpa.repository.Query; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; @@ -49,5 +52,23 @@ public ResponseEntity cancelBooking( return ResponseEntity.noContent().build(); } + @PreAuthorize("hasRole('USER')") + @GetMapping("/session/{sessionId}/me") + public ResponseEntity getMyBookingForSession( + @PathVariable Long sessionId, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + return ResponseEntity.ok( + bookingService.getMyBookingForSession( + userDetails.getId(), + sessionId + ) + ); + } + + + + + } diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentVerificationController.java b/divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentVerificationController.java new file mode 100644 index 0000000..06b776c --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentVerificationController.java @@ -0,0 +1,27 @@ +package com.divinespark.controller.user; + +import com.divinespark.service.InstallementService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/payments") +public class PaymentVerificationController { + + private final InstallementService installmentService; + + public PaymentVerificationController(InstallementService installmentService) { + this.installmentService = installmentService; + } + + @PostMapping("/installment/verify") + public ResponseEntity verifyInstallment( + @RequestParam String razorpayOrderId + ) { + installmentService.markInstallmentPaid(razorpayOrderId); + return ResponseEntity.ok().build(); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentWebhookController.java b/divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentWebhookController.java index c4efdeb..f7bc0b0 100644 --- a/divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentWebhookController.java +++ b/divinespark-backend/src/main/java/com/divinespark/controller/user/PaymentWebhookController.java @@ -44,6 +44,8 @@ public ResponseEntity handleWebhook( @RequestBody String payload, @RequestHeader("X-Razorpay-Signature") String signature) { + System.out.println("🔥🔥 RAZORPAY WEBHOOK HIT 🔥🔥"); + try { razorpaySignatureUtil.verify(payload, signature, webhookSecret); } catch (Exception e) { diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/user/UserController.java b/divinespark-backend/src/main/java/com/divinespark/controller/user/UserController.java index c62315d..7df30ef 100644 --- a/divinespark-backend/src/main/java/com/divinespark/controller/user/UserController.java +++ b/divinespark-backend/src/main/java/com/divinespark/controller/user/UserController.java @@ -2,6 +2,7 @@ import com.divinespark.dto.*; import com.divinespark.security.CustomUserDetails; +import com.divinespark.service.InstallementService; import com.divinespark.service.SessionService; import com.divinespark.service.UserService; import org.springframework.http.ResponseEntity; @@ -17,10 +18,12 @@ public class UserController { private final SessionService sessionService; private final UserService userService; + private final InstallementService installementService; - public UserController(SessionService sessionService, UserService userService) { + public UserController(SessionService sessionService, UserService userService, InstallementService installementService) { this.sessionService = sessionService; this.userService = userService; + this.installementService = installementService; } @GetMapping("/sessions") @@ -67,6 +70,24 @@ public ResponseEntity initiatePayment( ); } + @PreAuthorize("hasRole('USER')") + @PostMapping("/sessions/{sessionId}/pay/installments") + public ResponseEntity payInInstallments( + @PathVariable Long sessionId, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + + InstallmentPaymentInitiateResponse response = + sessionService.initiateInstallmentPayment( + sessionId, + userDetails.getId() + ); + + return ResponseEntity.ok(response); + } + + + @GetMapping("/profile") public ResponseEntity getMyProfile( @AuthenticationPrincipal CustomUserDetails userDetails) { diff --git a/divinespark-backend/src/main/java/com/divinespark/controller/user/UserInstallmentController.java b/divinespark-backend/src/main/java/com/divinespark/controller/user/UserInstallmentController.java new file mode 100644 index 0000000..6ffd7fb --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/controller/user/UserInstallmentController.java @@ -0,0 +1,54 @@ +package com.divinespark.controller.user; + +import com.divinespark.dto.InstallmentPayResponse; +import com.divinespark.dto.InstallmentResponse; +import com.divinespark.security.CustomUserDetails; + +import com.divinespark.service.InstallementService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/installments") +public class UserInstallmentController { + + private final InstallementService installmentService; + + public UserInstallmentController(InstallementService installmentService) { + this.installmentService = installmentService; + } + + @PreAuthorize("hasRole('USER')") + @GetMapping("/booking/{bookingId}") + public ResponseEntity> getInstallmentsByBooking( + @PathVariable Long bookingId, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + + return ResponseEntity.ok( + installmentService.getInstallmentsByBooking( + bookingId, + userDetails.getId() + ) + ); + } + + @PreAuthorize("hasRole('USER')") + @PostMapping("/{installmentId}/pay") + public ResponseEntity payInstallment( + @PathVariable Long installmentId, + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + return ResponseEntity.ok( + installmentService.payInstallment( + installmentId, + userDetails.getId() + ) + ); + } + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/AdminInstallmentResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/AdminInstallmentResponse.java new file mode 100644 index 0000000..b648b28 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/AdminInstallmentResponse.java @@ -0,0 +1,33 @@ +package com.divinespark.dto; + +import com.divinespark.entity.enums.InstallmentStatus; +import java.time.OffsetDateTime; + +public class AdminInstallmentResponse { + + private Long installmentId; + private int installmentNumber; + private double amount; + private InstallmentStatus status; + private OffsetDateTime paidAt; + + public AdminInstallmentResponse( + Long installmentId, + int installmentNumber, + double amount, + InstallmentStatus status, + OffsetDateTime paidAt + ) { + this.installmentId = installmentId; + this.installmentNumber = installmentNumber; + this.amount = amount; + this.status = status; + this.paidAt = paidAt; + } + + public Long getInstallmentId() { return installmentId; } + public int getInstallmentNumber() { return installmentNumber; } + public double getAmount() { return amount; } + public InstallmentStatus getStatus() { return status; } + public OffsetDateTime getPaidAt() { return paidAt; } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionBookingResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionBookingResponse.java index c95fbde..119a6fa 100644 --- a/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionBookingResponse.java +++ b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionBookingResponse.java @@ -1,6 +1,8 @@ package com.divinespark.dto; -import java.time.LocalDateTime; +import com.divinespark.entity.enums.BookingStatus; + +import java.time.OffsetDateTime; public class AdminSessionBookingResponse { @@ -9,8 +11,8 @@ public class AdminSessionBookingResponse { private String email; private String username; private String phoneNumber; - private String bookingStatus; - private LocalDateTime bookedAt; + private BookingStatus bookingStatus; + private OffsetDateTime bookedAt; public AdminSessionBookingResponse( Long bookingId, @@ -18,8 +20,8 @@ public AdminSessionBookingResponse( String username, String phoneNumber, String email, - String bookingStatus, - LocalDateTime bookedAt) { + BookingStatus bookingStatus, + OffsetDateTime bookedAt) { this.bookingId = bookingId; this.userId = userId; @@ -30,12 +32,15 @@ public AdminSessionBookingResponse( this.bookedAt = bookedAt; } + + + public Long getBookingId() { return bookingId; } public Long getUserId() { return userId; } public String getEmail() { return email; } public String getUsername() { return username; } - public String getBookingStatus() { return bookingStatus; } - public LocalDateTime getBookedAt() { return bookedAt; } + public BookingStatus getBookingStatus() { return bookingStatus; } + public OffsetDateTime getBookedAt() { return bookedAt; } public String getPhoneNumber() { return phoneNumber; } } diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionOverviewResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionOverviewResponse.java new file mode 100644 index 0000000..6e26d61 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionOverviewResponse.java @@ -0,0 +1,105 @@ +package com.divinespark.dto; + +import java.util.List; + +public class AdminSessionOverviewResponse { + + private Long sessionId; + private String sessionTitle; + + private int totalBookings; + private int fullyPaid; + private int partiallyPaid; + private int pending; + + private double totalCollected; + private double expectedRevenue; + + private List users; + + public Long getSessionId() { + return sessionId; + } + + public void setSessionId(Long sessionId) { + this.sessionId = sessionId; + } + + public String getSessionTitle() { + return sessionTitle; + } + + public void setSessionTitle(String sessionTitle) { + this.sessionTitle = sessionTitle; + } + + public int getTotalBookings() { + return totalBookings; + } + + public void setTotalBookings(int totalBookings) { + this.totalBookings = totalBookings; + } + + public int getFullyPaid() { + return fullyPaid; + } + + public void setFullyPaid(int fullyPaid) { + this.fullyPaid = fullyPaid; + } + + public int getPartiallyPaid() { + return partiallyPaid; + } + + public void setPartiallyPaid(int partiallyPaid) { + this.partiallyPaid = partiallyPaid; + } + + public int getPending() { + return pending; + } + + public void setPending(int pending) { + this.pending = pending; + } + + public double getTotalCollected() { + return totalCollected; + } + + public void setTotalCollected(double totalCollected) { + this.totalCollected = totalCollected; + } + + public double getExpectedRevenue() { + return expectedRevenue; + } + + public void setExpectedRevenue(double expectedRevenue) { + this.expectedRevenue = expectedRevenue; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public AdminSessionOverviewResponse(Long sessionId, String sessionTitle, int totalBookings, int fullyPaid, int partiallyPaid, int pending, double totalCollected, double expectedRevenue, List users) { + this.sessionId = sessionId; + this.sessionTitle = sessionTitle; + this.totalBookings = totalBookings; + this.fullyPaid = fullyPaid; + this.partiallyPaid = partiallyPaid; + this.pending = pending; + this.totalCollected = totalCollected; + this.expectedRevenue = expectedRevenue; + this.users = users; + } + + public AdminSessionOverviewResponse() {} +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserInstallmentResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserInstallmentResponse.java new file mode 100644 index 0000000..3df881e --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserInstallmentResponse.java @@ -0,0 +1,56 @@ +package com.divinespark.dto; + + +import com.divinespark.entity.enums.BookingStatus; +import java.util.List; + +public class AdminSessionUserInstallmentResponse { + + private Long bookingId; + private Long userId; + private String username; + private String email; + private String contactNumber; + + private double totalAmount; + private double paidAmount; + private double remainingAmount; + + private BookingStatus bookingStatus; + private List installments; + + public AdminSessionUserInstallmentResponse( + Long bookingId, + Long userId, + String username, + String email, + String contactNumber, + double totalAmount, + double paidAmount, + double remainingAmount, + BookingStatus bookingStatus, + List installments + ) { + this.bookingId = bookingId; + this.userId = userId; + this.username = username; + this.email = email; + this.contactNumber = contactNumber; + this.totalAmount = totalAmount; + this.paidAmount = paidAmount; + this.remainingAmount = remainingAmount; + this.bookingStatus = bookingStatus; + this.installments = installments; + } + + public Long getBookingId() { return bookingId; } + public Long getUserId() { return userId; } + public String getUsername() { return username; } + public String getEmail() { return email; } + public String getContactNumber() { return contactNumber; } + public double getTotalAmount() { return totalAmount; } + public double getPaidAmount() { return paidAmount; } + public double getRemainingAmount() { return remainingAmount; } + public BookingStatus getBookingStatus() { return bookingStatus; } + public List getInstallments() { return installments; } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserPaymentResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserPaymentResponse.java new file mode 100644 index 0000000..50d0b3c --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserPaymentResponse.java @@ -0,0 +1,105 @@ +package com.divinespark.dto; + +public class AdminSessionUserPaymentResponse { + + private Long bookingId; + private String userEmail; + private String username; + private String contactNumber; + + private String paymentType; // FULL / INSTALLMENT + private String bookingStatus; // CONFIRMED / PARTIALLY_PAID / PENDING + + private double totalAmount; + private double paidAmount; + private double remainingAmount; + + public Long getBookingId() { + return bookingId; + } + + public void setBookingId(Long bookingId) { + this.bookingId = bookingId; + } + + public String getUserEmail() { + return userEmail; + } + + public void setUserEmail(String userEmail) { + this.userEmail = userEmail; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getContactNumber() { + return contactNumber; + } + + public void setContactNumber(String contactNumber) { + this.contactNumber = contactNumber; + } + + public String getPaymentType() { + return paymentType; + } + + public void setPaymentType(String paymentType) { + this.paymentType = paymentType; + } + + public String getBookingStatus() { + return bookingStatus; + } + + public void setBookingStatus(String bookingStatus) { + this.bookingStatus = bookingStatus; + } + + public double getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(double totalAmount) { + this.totalAmount = totalAmount; + } + + public double getPaidAmount() { + return paidAmount; + } + + public void setPaidAmount(double paidAmount) { + this.paidAmount = paidAmount; + } + + public double getRemainingAmount() { + return remainingAmount; + } + + public void setRemainingAmount(double remainingAmount) { + this.remainingAmount = remainingAmount; + } + + public AdminSessionUserPaymentResponse(Long bookingId, String userEmail, String username, String contactNumber, String paymentType, String bookingStatus, double paidAmount, double totalAmount, double remainingAmount) { + this.bookingId = bookingId; + this.userEmail = userEmail; + this.username = username; + this.contactNumber = contactNumber; + this.paymentType = paymentType; + this.bookingStatus = bookingStatus; + this.paidAmount = paidAmount; + this.totalAmount = totalAmount; + this.remainingAmount = remainingAmount; + } + + public AdminSessionUserPaymentResponse() {} + + + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserResponse.java index 5a8f9fe..3edf7d7 100644 --- a/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserResponse.java +++ b/divinespark-backend/src/main/java/com/divinespark/dto/AdminSessionUserResponse.java @@ -1,14 +1,16 @@ package com.divinespark.dto; +import com.divinespark.entity.enums.BookingStatus; + public class AdminSessionUserResponse { private Long userId; private String email; private String username; private String contactNumber; - private String bookingStatus; + private BookingStatus bookingStatus; - public AdminSessionUserResponse(Long userId, String email, String bookingStatus, String username, String contactNumber) { + public AdminSessionUserResponse(Long userId, String email, BookingStatus bookingStatus, String username, String contactNumber) { this.userId = userId; this.email = email; this.bookingStatus = bookingStatus; @@ -24,7 +26,7 @@ public String getEmail() { return email; } - public String getBookingStatus() { + public BookingStatus getBookingStatus() { return bookingStatus; } diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/BlogCreateRequest.java b/divinespark-backend/src/main/java/com/divinespark/dto/BlogCreateRequest.java new file mode 100644 index 0000000..b42b876 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/BlogCreateRequest.java @@ -0,0 +1,50 @@ +package com.divinespark.dto; + +public class BlogCreateRequest { + + private String title; + private String excerpt; + private String content; + private String authorName; + private String authorRole; + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getAuthorRole() { + return authorRole; + } + + public void setAuthorRole(String authorRole) { + this.authorRole = authorRole; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getExcerpt() { + return excerpt; + } + + public void setExcerpt(String excerpt) { + this.excerpt = excerpt; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/BlogDetailResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/BlogDetailResponse.java new file mode 100644 index 0000000..6b8a3e5 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/BlogDetailResponse.java @@ -0,0 +1,52 @@ +package com.divinespark.dto; + +import java.time.OffsetDateTime; + +public class BlogDetailResponse { + + private String title; + private String content; + private String authorName; + private String authorRole; + private OffsetDateTime createdAt; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getAuthorRole() { + return authorRole; + } + + public void setAuthorRole(String authorRole) { + this.authorRole = authorRole; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/BlogListResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/BlogListResponse.java new file mode 100644 index 0000000..da4c3a4 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/BlogListResponse.java @@ -0,0 +1,71 @@ +package com.divinespark.dto; + +import java.time.OffsetDateTime; + +public class BlogListResponse { + private String title; + private String slug; + private String excerpt; + private String authorName; + private String authorRole; + private OffsetDateTime createdAt; + private Long id; + + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + + + public String getAuthorRole() { + return authorRole; + } + + public void setAuthorRole(String authorRole) { + this.authorRole = authorRole; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } + + public String getExcerpt() { + return excerpt; + } + + public void setExcerpt(String excerpt) { + this.excerpt = excerpt; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/DonationCreateRequest.java b/divinespark-backend/src/main/java/com/divinespark/dto/DonationCreateRequest.java index e0f7238..b9a7c6d 100644 --- a/divinespark-backend/src/main/java/com/divinespark/dto/DonationCreateRequest.java +++ b/divinespark-backend/src/main/java/com/divinespark/dto/DonationCreateRequest.java @@ -1,11 +1,11 @@ package com.divinespark.dto; public class DonationCreateRequest { - private double amount; + private int amount; private String note; - public double getAmount() { return amount; } - public void setAmount(double amount) { this.amount = amount; } + public int getAmount() { return amount; } + public void setAmount(int amount) { this.amount = amount; } public String getNote() { return note; } public void setNote(String note) { this.note = note; } } diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/EventRequest.java b/divinespark-backend/src/main/java/com/divinespark/dto/EventRequest.java new file mode 100644 index 0000000..5cae862 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/EventRequest.java @@ -0,0 +1,57 @@ +package com.divinespark.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import java.time.OffsetDateTime; + +public class EventRequest { + + @NotBlank + private String title; + + @NotBlank + @Size(max = 300) + private String description; + + @NotNull + private OffsetDateTime startTime; + + @NotNull + @Min(1) + private Integer durationMinutes; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getDurationMinutes() { + return durationMinutes; + } + + public void setDurationMinutes(Integer durationMinutes) { + this.durationMinutes = durationMinutes; + } + + public OffsetDateTime getStartTime() { + return startTime; + } + + public void setStartTime(OffsetDateTime startTime) { + this.startTime = startTime; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/EventTickerResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/EventTickerResponse.java new file mode 100644 index 0000000..8585a07 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/EventTickerResponse.java @@ -0,0 +1,53 @@ +package com.divinespark.dto; + +import java.time.OffsetDateTime; + +public class EventTickerResponse { + + private Long id; + private String title; + private String description; + private OffsetDateTime startTime; + private Integer durationMinutes; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public OffsetDateTime getStartTime() { + return startTime; + } + + public void setStartTime(OffsetDateTime startTime) { + this.startTime = startTime; + } + + public Integer getDurationMinutes() { + return durationMinutes; + } + + public void setDurationMinutes(Integer durationMinutes) { + this.durationMinutes = durationMinutes; + } +} + diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPayResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPayResponse.java new file mode 100644 index 0000000..9041b4f --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPayResponse.java @@ -0,0 +1,24 @@ +package com.divinespark.dto; + +public class InstallmentPayResponse { + + private Long bookingId; + private Long installmentId; + private String razorpayOrderId; + private int amount; // in paise + private String currency = "INR"; + + public Long getBookingId() { return bookingId; } + public void setBookingId(Long bookingId) { this.bookingId = bookingId; } + + public Long getInstallmentId() { return installmentId; } + public void setInstallmentId(Long installmentId) { this.installmentId = installmentId; } + + public String getRazorpayOrderId() { return razorpayOrderId; } + public void setRazorpayOrderId(String razorpayOrderId) { this.razorpayOrderId = razorpayOrderId; } + + public int getAmount() { return amount; } + public void setAmount(int amount) { this.amount = amount; } + + public String getCurrency() { return currency; } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPaymentInitiateResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPaymentInitiateResponse.java new file mode 100644 index 0000000..af2db92 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentPaymentInitiateResponse.java @@ -0,0 +1,46 @@ +package com.divinespark.dto; + +public class InstallmentPaymentInitiateResponse { + + private Long bookingId; + private Long installmentId; + private String razorpayOrderId; + private double amount; + private String currency = "INR"; + + public Long getBookingId() { + return bookingId; + } + + public void setBookingId(Long bookingId) { + this.bookingId = bookingId; + } + + public Long getInstallmentId() { + return installmentId; + } + + public void setInstallmentId(Long installmentId) { + this.installmentId = installmentId; + } + + public String getRazorpayOrderId() { + return razorpayOrderId; + } + + public void setRazorpayOrderId(String razorpayOrderId) { + this.razorpayOrderId = razorpayOrderId; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public String getCurrency() { + return currency; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentResponse.java new file mode 100644 index 0000000..f167c72 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/InstallmentResponse.java @@ -0,0 +1,34 @@ +package com.divinespark.dto; + +import com.divinespark.entity.enums.InstallmentStatus; + +import java.time.OffsetDateTime; + +public class InstallmentResponse { + + private Long id; + private int installmentNumber; + private double amount; + private InstallmentStatus status; + private OffsetDateTime paidAt; + + public InstallmentResponse( + Long id, + int installmentNumber, + double amount, + InstallmentStatus status, + OffsetDateTime paidAt + ) { + this.id = id; + this.installmentNumber = installmentNumber; + this.amount = amount; + this.status = status; + this.paidAt = paidAt; + } + + public Long getId() { return id; } + public int getInstallmentNumber() { return installmentNumber; } + public double getAmount() { return amount; } + public InstallmentStatus getStatus() { return status; } + public OffsetDateTime getPaidAt() { return paidAt; } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/ProgramRequest.java b/divinespark-backend/src/main/java/com/divinespark/dto/ProgramRequest.java new file mode 100644 index 0000000..bfbcf48 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/ProgramRequest.java @@ -0,0 +1,54 @@ +package com.divinespark.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class ProgramRequest { + + @NotBlank + private String title; + + @NotBlank + @Size(max = 300) + private String description; + + @NotBlank + private String category; + // ENERGY_WORKSHOPS / SPIRITUAL_TRIPS + + @NotBlank + private String imageUrl; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} + diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/ProgramResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/ProgramResponse.java new file mode 100644 index 0000000..0d8d4b4 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/ProgramResponse.java @@ -0,0 +1,41 @@ +package com.divinespark.dto; + +public class ProgramResponse { + + private Long id; + private String title; + private String description; + private String imageUrl; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/SessionListItemResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/SessionListItemResponse.java new file mode 100644 index 0000000..c531a97 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/SessionListItemResponse.java @@ -0,0 +1,61 @@ +package com.divinespark.dto; + +import com.divinespark.entity.enums.SessionStatus; +import com.divinespark.entity.enums.SessionType; + +import java.time.OffsetDateTime; + +public class SessionListItemResponse { + + private Long id; + private String title; + private String description; + private SessionType type; + private double price; + private OffsetDateTime startTime; + private OffsetDateTime endTime; + private SessionStatus status; + private int maxSeats; + private int availableSeats; + private String guideName; + private boolean hasThumbnail; + + + // ---------- getters & setters ---------- + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public SessionType getType() { return type; } + public void setType(SessionType type) { this.type = type; } + + public double getPrice() { return price; } + public void setPrice(double price) { this.price = price; } + + public OffsetDateTime getStartTime() { return startTime; } + public void setStartTime(OffsetDateTime startTime) { this.startTime = startTime; } + + public OffsetDateTime getEndTime() { return endTime; } + public void setEndTime(OffsetDateTime endTime) { this.endTime = endTime; } + + public SessionStatus getStatus() { return status; } + public void setStatus(SessionStatus status) { this.status = status; } + + public int getMaxSeats() { return maxSeats; } + public void setMaxSeats(int maxSeats) { this.maxSeats = maxSeats; } + + public int getAvailableSeats() { return availableSeats; } + public void setAvailableSeats(int availableSeats) { this.availableSeats = availableSeats; } + + public String getGuideName() { return guideName; } + public void setGuideName(String guideName) { this.guideName = guideName; } + + public boolean isHasThumbnail() { return hasThumbnail; } + public void setHasThumbnail(boolean hasThumbnail) { this.hasThumbnail = hasThumbnail; } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/UserBookingResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/UserBookingResponse.java index b154034..4550a34 100644 --- a/divinespark-backend/src/main/java/com/divinespark/dto/UserBookingResponse.java +++ b/divinespark-backend/src/main/java/com/divinespark/dto/UserBookingResponse.java @@ -1,5 +1,7 @@ package com.divinespark.dto; +import com.divinespark.entity.enums.BookingStatus; + import java.time.LocalDateTime; public class UserBookingResponse { @@ -8,7 +10,7 @@ public class UserBookingResponse { private Long sessionId; private String sessionTitle; private String sessionType; - private String bookingStatus; + private BookingStatus status; private LocalDateTime startTime; private LocalDateTime endTime; private String whatsLink; // nullable @@ -41,11 +43,12 @@ public void setSessionType(String sessionType) { this.sessionType = sessionType; } - public String getBookingStatus() { - return bookingStatus; + public BookingStatus getStatus() { + return status; } - public void setBookingStatus(String bookingStatus) { - this.bookingStatus = bookingStatus; + + public void setStatus(BookingStatus status) { + this.status = status; } public LocalDateTime getStartTime() { diff --git a/divinespark-backend/src/main/java/com/divinespark/dto/UserSessionBookingResponse.java b/divinespark-backend/src/main/java/com/divinespark/dto/UserSessionBookingResponse.java new file mode 100644 index 0000000..c9330f4 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/dto/UserSessionBookingResponse.java @@ -0,0 +1,37 @@ +package com.divinespark.dto; + +import com.divinespark.entity.enums.BookingStatus; +import com.divinespark.entity.enums.PaymentType; + +public class UserSessionBookingResponse { + + private Long bookingId; + private BookingStatus bookingStatus; + private PaymentType paymentType; + private double totalAmount; + private double paidAmount; + private double remainingAmount; + + public UserSessionBookingResponse( + Long bookingId, + BookingStatus bookingStatus, + PaymentType paymentType, + double totalAmount, + double paidAmount, + double remainingAmount + ) { + this.bookingId = bookingId; + this.bookingStatus = bookingStatus; + this.paymentType = paymentType; + this.totalAmount = totalAmount; + this.paidAmount = paidAmount; + this.remainingAmount = remainingAmount; + } + + public Long getBookingId() { return bookingId; } + public BookingStatus getBookingStatus() { return bookingStatus; } + public PaymentType getPaymentType() { return paymentType; } + public double getTotalAmount() { return totalAmount; } + public double getPaidAmount() { return paidAmount; } + public double getRemainingAmount() { return remainingAmount; } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/Blog.java b/divinespark-backend/src/main/java/com/divinespark/entity/Blog.java new file mode 100644 index 0000000..c2c8fec --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/Blog.java @@ -0,0 +1,127 @@ +package com.divinespark.entity; + +import com.divinespark.entity.enums.BlogStatus; +import jakarta.persistence.*; + +import java.time.OffsetDateTime; + +@Entity +@Table( + name = "blogs", + indexes = { + @Index(name = "idx_blog_slug", columnList = "slug"), + @Index(name = "idx_blog_status", columnList = "status") + } +) +public class Blog { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 200) + private String title; + + @Column(nullable = false, unique = true, length = 220) + private String slug; + + @Column(length = 500) + private String excerpt; + + @Column(columnDefinition = "TEXT", nullable = false) + private String content; + + // Author details (denormalized, intentional) + @Column(nullable = false) + private String authorName; + + @Column(nullable = false) + private String authorRole; // Admin / Editorial Team + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private BlogStatus status = BlogStatus.DRAFT; + + @Column(nullable = false, updatable = false) + private OffsetDateTime createdAt = OffsetDateTime.now(); + + private OffsetDateTime updatedAt; + + // ---------------- Getters & Setters ---------------- + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public String getExcerpt() { + return excerpt; + } + + public void setExcerpt(String excerpt) { + this.excerpt = excerpt; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getAuthorRole() { + return authorRole; + } + + public void setAuthorRole(String authorRole) { + this.authorRole = authorRole; + } + + public BlogStatus getStatus() { + return status; + } + + public void setStatus(BlogStatus status) { + this.status = status; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public OffsetDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(OffsetDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/Booking.java b/divinespark-backend/src/main/java/com/divinespark/entity/Booking.java index dd37218..1182cde 100644 --- a/divinespark-backend/src/main/java/com/divinespark/entity/Booking.java +++ b/divinespark-backend/src/main/java/com/divinespark/entity/Booking.java @@ -1,7 +1,10 @@ package com.divinespark.entity; +import com.divinespark.entity.enums.BookingStatus; +import com.divinespark.entity.enums.PaymentType; import jakarta.persistence.*; -import java.time.LocalDateTime; + +import java.time.OffsetDateTime; @Entity @Table(name = "bookings", @@ -25,15 +28,32 @@ public class Booking { private String whatsappLink; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private PaymentType paymentType; + + @Column(nullable = false) + private double totalAmount; + + @Column(nullable = false) + private double paidAmount; + + @Column(nullable = false) + private double remainingAmount; + + @Enumerated(EnumType.STRING) @Column(nullable = false) - private String status; // CONFIRMED + private BookingStatus bookingStatus; + @Column(nullable = false, updatable = false) - private LocalDateTime createdAt; + private OffsetDateTime createdAt = OffsetDateTime.now(); + + @PrePersist void onCreate() { - createdAt = LocalDateTime.now(); + createdAt = OffsetDateTime.now(); } public Long getId() { return id; } @@ -41,9 +61,54 @@ void onCreate() { public void setUser(User user) { this.user = user; } public Session getSession() { return session; } public void setSession(Session session) { this.session = session; } - public String getStatus() { return status; } - public void setStatus(String status) { this.status = status; } + public BookingStatus getBookingStatus() { + return bookingStatus; + } + + public void setBookingStatus(BookingStatus bookingStatus) { + this.bookingStatus = bookingStatus; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } + + public double getPaidAmount() { + return paidAmount; + } + + public void setPaidAmount(double paidAmount) { + this.paidAmount = paidAmount; + } + + public PaymentType getPaymentType() { + return paymentType; + } + + public void setPaymentType(PaymentType paymentType) { + this.paymentType = paymentType; + } + + public double getRemainingAmount() { + return remainingAmount; + } + + public void setRemainingAmount(double remainingAmount) { + this.remainingAmount = remainingAmount; + } + + public double getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(double totalAmount) { + this.totalAmount = totalAmount; + } public String getWhatsappLink() { return whatsappLink; diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/Event.java b/divinespark-backend/src/main/java/com/divinespark/entity/Event.java new file mode 100644 index 0000000..334707c --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/Event.java @@ -0,0 +1,84 @@ +package com.divinespark.entity; + +import jakarta.persistence.*; + +import java.time.OffsetDateTime; + +@Entity +@Table(name = "events") +public class Event { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + @Column(nullable = false, length = 300) + private String description; + // 1–2 lines max + + @Column(nullable = false) + private OffsetDateTime startTime; + + @Column(nullable = false) + private Integer durationMinutes; + // auto-expiry logic uses this + + @Column(nullable = false, updatable = false) + private OffsetDateTime createdAt; + + @PrePersist + public void onCreate() { + this.createdAt = OffsetDateTime.now(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public OffsetDateTime getStartTime() { + return startTime; + } + + public void setStartTime(OffsetDateTime startTime) { + this.startTime = startTime; + } + + public Integer getDurationMinutes() { + return durationMinutes; + } + + public void setDurationMinutes(Integer durationMinutes) { + this.durationMinutes = durationMinutes; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/Installment.java b/divinespark-backend/src/main/java/com/divinespark/entity/Installment.java new file mode 100644 index 0000000..b5c0760 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/Installment.java @@ -0,0 +1,111 @@ +package com.divinespark.entity; + +import com.divinespark.entity.enums.InstallmentStatus; +import jakarta.persistence.*; + +import java.time.OffsetDateTime; + +@Entity +@Table( + name = "installments", + indexes = { + @Index(name = "idx_installment_booking", columnList = "booking_id"), + @Index(name = "idx_installment_status", columnList = "status") + } +) +public class Installment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "booking_id", nullable = false) + private Booking booking; + + @Column(nullable = false) + private int installmentNumber; // 1,2,3... + + @Column(nullable = false) + private double amount; + + @Column(nullable = false) + private OffsetDateTime dueDate; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private InstallmentStatus status = InstallmentStatus.PENDING; + + private OffsetDateTime paidAt; + + private String razorpayOrderId; + + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public Booking getBooking() { + return booking; + } + + public void setBooking(Booking booking) { + this.booking = booking; + } + + public OffsetDateTime getDueDate() { + return dueDate; + } + + public void setDueDate(OffsetDateTime dueDate) { + this.dueDate = dueDate; + } + + public int getInstallmentNumber() { + return installmentNumber; + } + + public void setInstallmentNumber(int installmentNumber) { + this.installmentNumber = installmentNumber; + } + + public OffsetDateTime getPaidAt() { + return paidAt; + } + + public void setPaidAt(OffsetDateTime paidAt) { + this.paidAt = paidAt; + } + + public String getRazorpayOrderId() { + return razorpayOrderId; + } + + public void setRazorpayOrderId(String razorpayOrderId) { + this.razorpayOrderId = razorpayOrderId; + } + + public InstallmentStatus getStatus() { + return status; + } + + public void setStatus(InstallmentStatus status) { + this.status = status; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public void setAmount(int amount) { + this.amount = amount; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/Program.java b/divinespark-backend/src/main/java/com/divinespark/entity/Program.java new file mode 100644 index 0000000..6de14af --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/Program.java @@ -0,0 +1,95 @@ +package com.divinespark.entity; + +import jakarta.persistence.*; + +import java.time.OffsetDateTime; + +@Entity +@Table(name = "programs") +public class Program { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + @Column(nullable = false, length = 300) + private String description; + + @Column(nullable = false) + private String category; + // ENERGY_WORKSHOPS or SPIRITUAL_TRIPS + + @Column(nullable = false) + private String imageUrl; + // e.g. /programs/energy/sound-healing.jpg + + @Column(nullable = false) + private boolean active = true; + + @Column(nullable = false, updatable = false) + private OffsetDateTime createdAt; + + @PrePersist + public void onCreate() { + this.createdAt = OffsetDateTime.now(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/enums/BlogStatus.java b/divinespark-backend/src/main/java/com/divinespark/entity/enums/BlogStatus.java new file mode 100644 index 0000000..e4567ac --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/enums/BlogStatus.java @@ -0,0 +1,6 @@ +package com.divinespark.entity.enums; + +public enum BlogStatus { + DRAFT, + PUBLISHED +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/enums/BookingStatus.java b/divinespark-backend/src/main/java/com/divinespark/entity/enums/BookingStatus.java new file mode 100644 index 0000000..cb70bbd --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/enums/BookingStatus.java @@ -0,0 +1,8 @@ +package com.divinespark.entity.enums; + +public enum BookingStatus { + PENDING, + PARTIALLY_PAID, + CONFIRMED, + CANCELLED +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/enums/InstallmentStatus.java b/divinespark-backend/src/main/java/com/divinespark/entity/enums/InstallmentStatus.java new file mode 100644 index 0000000..fb5d6a5 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/enums/InstallmentStatus.java @@ -0,0 +1,6 @@ +package com.divinespark.entity.enums; + +public enum InstallmentStatus { + PENDING, + PAID +} diff --git a/divinespark-backend/src/main/java/com/divinespark/entity/enums/PaymentType.java b/divinespark-backend/src/main/java/com/divinespark/entity/enums/PaymentType.java new file mode 100644 index 0000000..0f41302 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/entity/enums/PaymentType.java @@ -0,0 +1,7 @@ +package com.divinespark.entity.enums; + +public enum PaymentType { + FULL, + INSTALLMENT, + FREE +} diff --git a/divinespark-backend/src/main/java/com/divinespark/repository/BlogRepository.java b/divinespark-backend/src/main/java/com/divinespark/repository/BlogRepository.java new file mode 100644 index 0000000..47f6707 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/repository/BlogRepository.java @@ -0,0 +1,28 @@ +package com.divinespark.repository; + +import com.divinespark.entity.Blog; +import com.divinespark.entity.enums.BlogStatus; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface BlogRepository extends JpaRepository { + + // Public blogs (blog listing page) + List findByStatusOrderByCreatedAtDesc(BlogStatus status); + + // Blog detail page (SEO friendly) + Optional findBySlugAndStatus(String slug, BlogStatus status); + + // Admin checks + boolean existsBySlug(String slug); + + List findAllByOrderByCreatedAtDesc(); + + + + ; + + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/repository/BookingRepository.java b/divinespark-backend/src/main/java/com/divinespark/repository/BookingRepository.java index feddd56..7055b98 100644 --- a/divinespark-backend/src/main/java/com/divinespark/repository/BookingRepository.java +++ b/divinespark-backend/src/main/java/com/divinespark/repository/BookingRepository.java @@ -3,23 +3,19 @@ import com.divinespark.dto.AdminSessionBookingResponse; import com.divinespark.dto.AdminSessionUserResponse; import com.divinespark.entity.Booking; +import com.divinespark.entity.enums.BookingStatus; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.List; import java.util.Optional; public interface BookingRepository extends JpaRepository { - boolean existsByUserIdAndSessionIdAndStatus( - Long userId, - Long sessionId, - String status - ); - - + // ---------- USER BOOKINGS ---------- @Query(""" SELECT b FROM Booking b JOIN FETCH b.session @@ -28,64 +24,82 @@ boolean existsByUserIdAndSessionIdAndStatus( """) List findUserBookingsWithSession(Long userId); - Optional findById(Long id); + Optional findByUser_IdAndSession_Id( + Long userId, + Long sessionId + ); + boolean existsByUser_IdAndSession_Id( + Long userId, + Long sessionId + ); + boolean existsByUser_IdAndSession_IdAndBookingStatus( + Long userId, + Long sessionId, + BookingStatus bookingStatus + ); + + // ---------- ADMIN ---------- @Query(""" - SELECT new com.divinespark.dto.AdminSessionUserResponse( - u.id, - u.email, - b.status, - u.username, - u.contactNumber - ) - FROM Booking b - JOIN b.user u - WHERE b.session.id = :sessionId -""") + SELECT new com.divinespark.dto.AdminSessionUserResponse( + u.id, + u.email, + b.bookingStatus, + u.username, + u.contactNumber + ) + FROM Booking b + JOIN b.user u + WHERE b.session.id = :sessionId + """) List findUsersBySessionId( @Param("sessionId") Long sessionId ); - @Query(""" - SELECT new com.divinespark.dto.AdminSessionBookingResponse( - b.id, - u.id, - u.username, - u.contactNumber, - u.email, - b.status, - b.createdAt - ) - FROM Booking b - JOIN b.user u - WHERE b.session.id = :sessionId -""") + SELECT new com.divinespark.dto.AdminSessionBookingResponse( + b.id, + u.id, + u.username, + u.contactNumber, + u.email, + b.bookingStatus, + b.createdAt + ) + FROM Booking b + JOIN b.user u + WHERE b.session.id = :sessionId + """) List findBookingsBySessionId( @Param("sessionId") Long sessionId ); - Optional findByUserIdAndSessionIdAndStatus( - Long userId, - Long sessionId, - String status - ); - - List findByStatusAndCreatedAtBefore( - String status, - LocalDateTime cutoffTime + List findByBookingStatusAndCreatedAtBefore( + BookingStatus status, + OffsetDateTime cutoffTime ); - boolean existsByUserIdAndSessionId(Long userId, Long sessionId); - boolean existsByUserIdAndSessionIdAndStatusNot( - Long userId, - Long sessionId, - String status - ); + @Query(""" +SELECT b FROM Booking b +JOIN FETCH b.user +JOIN FETCH b.session +WHERE b.session.id = :sessionId +""") + List findDetailedBookingsBySessionId( + @Param("sessionId") Long sessionId + ); + @Query(""" + SELECT b FROM Booking b + JOIN FETCH b.user + WHERE b.session.id = :sessionId +""") + List findBookingEntitiesBySessionId( + @Param("sessionId") Long sessionId + ); } diff --git a/divinespark-backend/src/main/java/com/divinespark/repository/EventRepository.java b/divinespark-backend/src/main/java/com/divinespark/repository/EventRepository.java new file mode 100644 index 0000000..30dc8bb --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/repository/EventRepository.java @@ -0,0 +1,23 @@ +package com.divinespark.repository; + +import com.divinespark.entity.Event; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.OffsetDateTime; +import java.util.List; + +public interface EventRepository + extends JpaRepository { + + @Query(""" + SELECT w FROM Event w + WHERE (w.startTime + (w.durationMinutes * 1 minute)) > :now + ORDER BY w.startTime ASC + """) + List findActiveForTicker(@Param("now") OffsetDateTime now); + + List findAllByOrderByCreatedAtDesc(); +} + diff --git a/divinespark-backend/src/main/java/com/divinespark/repository/InstallmentRepository.java b/divinespark-backend/src/main/java/com/divinespark/repository/InstallmentRepository.java new file mode 100644 index 0000000..694b352 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/repository/InstallmentRepository.java @@ -0,0 +1,41 @@ +package com.divinespark.repository; + +import java.util.List; +import java.util.Optional; + +import com.divinespark.entity.Installment; +import com.divinespark.entity.enums.InstallmentStatus; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface InstallmentRepository extends JpaRepository { + + List findByBookingIdOrderByInstallmentNumber(Long bookingId); + + Optional findByBookingIdAndInstallmentNumber(Long bookingId, InstallmentStatus status); + + boolean existsByBookingIdAndStatus(Long bookingId, InstallmentStatus status); + + Optional findByRazorpayOrderId(String razorpayOrderId); + + + List findByBooking_IdAndStatus( + Long bookingId, + InstallmentStatus status + ); + + @Modifying + @Query(""" + update Installment i + set i.razorpayOrderId = :orderId + where i.id = :id +""") + void updateOrderId( + @Param("id") Long id, + @Param("orderId") String orderId + ); + + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/repository/ProgramRepository.java b/divinespark-backend/src/main/java/com/divinespark/repository/ProgramRepository.java new file mode 100644 index 0000000..9432240 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/repository/ProgramRepository.java @@ -0,0 +1,13 @@ +package com.divinespark.repository; + +import com.divinespark.entity.Program; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ProgramRepository extends JpaRepository { + + List findByCategoryAndActiveTrueOrderByCreatedAtDesc(String category); + + List findAllByOrderByCreatedAtDesc(); +} diff --git a/divinespark-backend/src/main/java/com/divinespark/security/SecurityConfig.java b/divinespark-backend/src/main/java/com/divinespark/security/SecurityConfig.java index 9ddb3da..ef9dd34 100644 --- a/divinespark-backend/src/main/java/com/divinespark/security/SecurityConfig.java +++ b/divinespark-backend/src/main/java/com/divinespark/security/SecurityConfig.java @@ -50,27 +50,43 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/v3/api-docs/**", "/api/v1/auth/**", "/api/v1/payments/webhook", - "/api/v1/user/**", - "api/v1/public/**" + "/api/v1/public/**", + "/api/v1/blogs/**", + "/api/v1/user/review/**" ).permitAll() - // Public session browsing - .requestMatchers(HttpMethod.GET, "/api/v1/sessions/**", "/api/v1/admin/thumbnail/").permitAll() + // User APIs + .requestMatchers("/api/v1/user/**", "/api/v1/installments/**, ", "/api/v1/payments").hasRole("USER") - // Protected session actions - .requestMatchers(HttpMethod.POST, "/api/v1/sessions/**").hasRole("USER") + // Public session browsing + .requestMatchers(HttpMethod.GET, "/api/v1/sessions/**").permitAll() - // Admin + // Admin APIs .requestMatchers("/api/v1/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) - - .oauth2Login(oauth -> oauth - .successHandler(oAuth2SuccessHandler) + // THIS PREVENTS GOOGLE REDIRECTS +// .exceptionHandling(ex -> ex +// .authenticationEntryPoint((request, response, authException) -> { +// response.setStatus(401); +// response.setContentType("application/json"); +// response.getWriter().write(""" +// { +// "error": "Unauthorized", +// "message": "JWT token missing or invalid" +// } +// """); +// }) +// ) + + // OAuth ONLY for browser login + .oauth2Login(oauth -> + oauth.successHandler(oAuth2SuccessHandler) ) + // JWT filter .addFilterBefore( jwtAuthFilter, UsernamePasswordAuthenticationFilter.class @@ -79,6 +95,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti return http.build(); } + @Bean public CorsConfigurationSource corsConfigurationSource() { diff --git a/divinespark-backend/src/main/java/com/divinespark/service/AdminInstallmentService.java b/divinespark-backend/src/main/java/com/divinespark/service/AdminInstallmentService.java new file mode 100644 index 0000000..adb71e4 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/AdminInstallmentService.java @@ -0,0 +1,8 @@ +package com.divinespark.service; + +import com.divinespark.dto.AdminSessionUserInstallmentResponse; +import java.util.List; + +public interface AdminInstallmentService { + List getSessionInstallments(Long sessionId); +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/AdminSessionPaymentService.java b/divinespark-backend/src/main/java/com/divinespark/service/AdminSessionPaymentService.java new file mode 100644 index 0000000..5b06ea5 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/AdminSessionPaymentService.java @@ -0,0 +1,7 @@ +package com.divinespark.service; + +import com.divinespark.dto.AdminSessionOverviewResponse; + +public interface AdminSessionPaymentService { + AdminSessionOverviewResponse getSessionOverview(Long sessionId); +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/BlogService.java b/divinespark-backend/src/main/java/com/divinespark/service/BlogService.java new file mode 100644 index 0000000..72fec4a --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/BlogService.java @@ -0,0 +1,27 @@ +package com.divinespark.service; + +import com.divinespark.dto.BlogCreateRequest; +import com.divinespark.dto.BlogDetailResponse; +import com.divinespark.dto.BlogListResponse; + +import java.util.List; + +public interface BlogService { + + void createBlog(BlogCreateRequest request); + + void updateBlog(Long id, BlogCreateRequest request); + + void publishBlog(Long id); + + void deleteBlog(Long id); + + List getPublishedBlogs(); + + BlogDetailResponse getBlogBySlug(String slug); + + List getAllBlogsForAdmin(); + + BlogDetailResponse getBlogByIdForAdmin(Long id); + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/BookingCleanupService.java b/divinespark-backend/src/main/java/com/divinespark/service/BookingCleanupService.java index 263adc4..af7c0fe 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/BookingCleanupService.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/BookingCleanupService.java @@ -2,6 +2,7 @@ import com.divinespark.entity.Booking; +import com.divinespark.entity.enums.BookingStatus; import com.divinespark.repository.BookingRepository; import com.divinespark.repository.PaymentRepository; import org.springframework.scheduling.annotation.Scheduled; @@ -9,6 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.List; @Service @@ -26,16 +28,16 @@ public BookingCleanupService(BookingRepository bookingRepository, PaymentReposit @Scheduled(fixedRate = 5 * 60 * 1000) //5 min public void cleanupStalePendingBookings() { - LocalDateTime cutoff = LocalDateTime.now().minusMinutes(15); + OffsetDateTime cutoff = OffsetDateTime.now().minusMinutes(15); - List staleBookings = bookingRepository.findByStatusAndCreatedAtBefore( - "PENDING" , + List staleBookings = bookingRepository.findByBookingStatusAndCreatedAtBefore( + BookingStatus.PENDING, cutoff ); for(Booking booking : staleBookings) { - booking.setStatus("PENDING"); + booking.setBookingStatus(BookingStatus.PENDING); paymentRepository .findTopByBookingIdOrderByCreatedAtDesc(booking.getId()) diff --git a/divinespark-backend/src/main/java/com/divinespark/service/BookingService.java b/divinespark-backend/src/main/java/com/divinespark/service/BookingService.java index 1f3868d..b16625f 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/BookingService.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/BookingService.java @@ -1,6 +1,7 @@ package com.divinespark.service; import com.divinespark.dto.UserBookingResponse; +import com.divinespark.dto.UserSessionBookingResponse; import jakarta.servlet.http.HttpServletResponse; import java.util.List; @@ -11,4 +12,7 @@ public interface BookingService { void cancelBooking(Long bookingId, Long userId); long getTotalBookings(); public void downloadSessionUsers(Long sessionId, HttpServletResponse response); + + UserSessionBookingResponse getMyBookingForSession(Long userId, Long sessionId); + } diff --git a/divinespark-backend/src/main/java/com/divinespark/service/EventService.java b/divinespark-backend/src/main/java/com/divinespark/service/EventService.java new file mode 100644 index 0000000..5f2957b --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/EventService.java @@ -0,0 +1,20 @@ +package com.divinespark.service; + +import com.divinespark.dto.EventRequest; +import com.divinespark.dto.EventTickerResponse; +import com.divinespark.entity.Event; + +import java.util.List; + +public interface EventService { + + Event create(EventRequest request); + + Event update(Long id, EventRequest request); + + void delete(Long id); + + List getAllAdmin(); + + List getTickerEvents(); +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/InstallementService.java b/divinespark-backend/src/main/java/com/divinespark/service/InstallementService.java new file mode 100644 index 0000000..fbe7acf --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/InstallementService.java @@ -0,0 +1,19 @@ +package com.divinespark.service; + +import com.divinespark.dto.AdminSessionOverviewResponse; +import com.divinespark.dto.InstallmentPayResponse; +import com.divinespark.dto.InstallmentResponse; + +import java.util.List; + +public interface InstallementService { + + InstallmentPayResponse payInstallment(Long installmentId, Long userId); + + List getInstallmentsByBooking(Long bookingId, Long userId); + + void markInstallmentPaid(String razorpayOrderId); + + + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/ProgramService.java b/divinespark-backend/src/main/java/com/divinespark/service/ProgramService.java new file mode 100644 index 0000000..540e6fb --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/ProgramService.java @@ -0,0 +1,20 @@ +package com.divinespark.service; + +import com.divinespark.dto.ProgramRequest; +import com.divinespark.dto.ProgramResponse; +import com.divinespark.entity.Program; + +import java.util.List; + +public interface ProgramService { + + Program create(ProgramRequest request); + + Program update(Long id, ProgramRequest request); + + void delete(Long id); + + List getAllAdmin(); + + List getByCategory(String category); +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/RazorpayService.java b/divinespark-backend/src/main/java/com/divinespark/service/RazorpayService.java index b3c956d..44870e3 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/RazorpayService.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/RazorpayService.java @@ -3,5 +3,5 @@ import com.divinespark.dto.RazorpayOrderResponse; public interface RazorpayService { - RazorpayOrderResponse createOrder(double amount, Long bookingId); + RazorpayOrderResponse createOrder(int amount, Long bookingId); } diff --git a/divinespark-backend/src/main/java/com/divinespark/service/SessionService.java b/divinespark-backend/src/main/java/com/divinespark/service/SessionService.java index f45eaca..9d17c34 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/SessionService.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/SessionService.java @@ -42,4 +42,10 @@ public interface SessionService { List getBookingsBySession(Long sessionId); public String getWhatsappLinkIfConfirmed(Long sessionId, Long userId); + + public InstallmentPaymentInitiateResponse initiateInstallmentPayment( + Long sessionId, + Long userId + ); } + diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/AdminInstallmentServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/AdminInstallmentServiceImpl.java new file mode 100644 index 0000000..c46e0b9 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/AdminInstallmentServiceImpl.java @@ -0,0 +1,64 @@ +package com.divinespark.service.impl; + +import com.divinespark.dto.AdminInstallmentResponse; +import com.divinespark.dto.AdminSessionUserInstallmentResponse; +import com.divinespark.entity.Booking; +import com.divinespark.repository.BookingRepository; +import com.divinespark.repository.InstallmentRepository; +import com.divinespark.service.AdminInstallmentService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class AdminInstallmentServiceImpl implements AdminInstallmentService { + + private final BookingRepository bookingRepository; + private final InstallmentRepository installmentRepository; + + public AdminInstallmentServiceImpl( + BookingRepository bookingRepository, + InstallmentRepository installmentRepository + ) { + this.bookingRepository = bookingRepository; + this.installmentRepository = installmentRepository; + } + + @Override + public List getSessionInstallments(Long sessionId) { + + List bookings = + bookingRepository.findBookingEntitiesBySessionId(sessionId); + + + return bookings.stream().map(booking -> { + + List installments = + installmentRepository + .findByBookingIdOrderByInstallmentNumber(booking.getId()) + .stream() + .map(i -> new AdminInstallmentResponse( + i.getId(), + i.getInstallmentNumber(), + i.getAmount(), + i.getStatus(), + i.getPaidAt() + )) + .collect(Collectors.toList()); + + return new AdminSessionUserInstallmentResponse( + booking.getId(), + booking.getUser().getId(), + booking.getUser().getUsername(), + booking.getUser().getEmail(), + booking.getUser().getContactNumber(), + booking.getTotalAmount(), + booking.getPaidAmount(), + booking.getRemainingAmount(), + booking.getBookingStatus(), + installments + ); + }).collect(Collectors.toList()); + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/AdminSessionPaymentServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/AdminSessionPaymentServiceImpl.java new file mode 100644 index 0000000..f781618 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/AdminSessionPaymentServiceImpl.java @@ -0,0 +1,90 @@ +package com.divinespark.service.impl; + + + +import com.divinespark.dto.*; +import com.divinespark.entity.Booking; +import com.divinespark.entity.enums.BookingStatus; +import com.divinespark.repository.BookingRepository; +import com.divinespark.repository.SessionRepository; +import com.divinespark.service.AdminSessionPaymentService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class AdminSessionPaymentServiceImpl + implements AdminSessionPaymentService { + + private final BookingRepository bookingRepository; + private final SessionRepository sessionRepository; + + public AdminSessionPaymentServiceImpl( + BookingRepository bookingRepository, + SessionRepository sessionRepository + ) { + this.bookingRepository = bookingRepository; + this.sessionRepository = sessionRepository; + } + + @Override + public AdminSessionOverviewResponse getSessionOverview(Long sessionId) { + + var session = sessionRepository.findById(sessionId) + .orElseThrow(() -> new RuntimeException("Session not found")); + + List bookings = + bookingRepository.findDetailedBookingsBySessionId(sessionId); + + AdminSessionOverviewResponse res = + new AdminSessionOverviewResponse(); + + res.setSessionId(sessionId); + res.setSessionTitle(session.getTitle()); + + int fullyPaid = 0, partial = 0, pending = 0; + double collected = 0, expected = 0; + + List users = + new ArrayList<>(); + + for (Booking b : bookings) { + + expected += b.getTotalAmount(); + collected += b.getPaidAmount(); + + if (b.getBookingStatus() == BookingStatus.CONFIRMED) + fullyPaid++; + else if (b.getBookingStatus() == BookingStatus.PARTIALLY_PAID) + partial++; + else + pending++; + + AdminSessionUserPaymentResponse u = + new AdminSessionUserPaymentResponse(); + + u.setBookingId(b.getId()); + u.setUserEmail(b.getUser().getEmail()); + u.setUsername(b.getUser().getUsername()); + u.setContactNumber(b.getUser().getContactNumber()); + u.setPaymentType(b.getPaymentType().name()); + u.setBookingStatus(b.getBookingStatus().name()); + u.setTotalAmount(b.getTotalAmount()); + u.setPaidAmount(b.getPaidAmount()); + u.setRemainingAmount(b.getRemainingAmount()); + + users.add(u); + } + + res.setTotalBookings(bookings.size()); + res.setFullyPaid(fullyPaid); + res.setPartiallyPaid(partial); + res.setPending(pending); + res.setTotalCollected(collected); + res.setExpectedRevenue(expected); + res.setUsers(users); + + return res; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/BlogServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/BlogServiceImpl.java new file mode 100644 index 0000000..d8d22e2 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/BlogServiceImpl.java @@ -0,0 +1,166 @@ +package com.divinespark.service.impl; + +import com.divinespark.dto.*; +import com.divinespark.entity.Blog; +import com.divinespark.entity.enums.BlogStatus; +import com.divinespark.repository.BlogRepository; +import com.divinespark.service.BlogService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.text.Normalizer; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +@Service +@Transactional +public class BlogServiceImpl implements BlogService { + + private final BlogRepository blogRepository; + + public BlogServiceImpl(BlogRepository blogRepository) { + this.blogRepository = blogRepository; + } + + @Override + public void createBlog(BlogCreateRequest request) { + + String slug = generateSlug(request.getTitle()); + + if (blogRepository.existsBySlug(slug)) { + throw new RuntimeException("Blog with same title already exists"); + } + + Blog blog = new Blog(); + blog.setTitle(request.getTitle()); + blog.setSlug(slug); + blog.setExcerpt(request.getExcerpt()); + blog.setContent(request.getContent()); + blog.setAuthorName(request.getAuthorName()); + blog.setAuthorRole(request.getAuthorRole()); + blog.setStatus(BlogStatus.DRAFT); + + blogRepository.save(blog); + } + + @Override + public void updateBlog(Long id, BlogCreateRequest request) { + + Blog blog = blogRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Blog not found")); + + blog.setTitle(request.getTitle()); + blog.setExcerpt(request.getExcerpt()); + blog.setContent(request.getContent()); + blog.setAuthorName(request.getAuthorName()); + blog.setAuthorRole(request.getAuthorRole()); + blog.setUpdatedAt(OffsetDateTime.now()); + } + + @Override + public void publishBlog(Long id) { + + Blog blog = blogRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Blog not found")); + + blog.setStatus(BlogStatus.PUBLISHED); + blog.setUpdatedAt(OffsetDateTime.now()); + } + + @Override + public void deleteBlog(Long id) { + blogRepository.deleteById(id); + } + + @Override + public List getPublishedBlogs() { + + List blogs = blogRepository + .findByStatusOrderByCreatedAtDesc(BlogStatus.PUBLISHED); + + List response = new ArrayList<>(); + + for (Blog b : blogs) { + BlogListResponse dto = new BlogListResponse(); + dto.setTitle(b.getTitle()); + dto.setSlug(b.getSlug()); + dto.setExcerpt(b.getExcerpt()); + dto.setAuthorName(b.getAuthorName()); + dto.setAuthorRole(b.getAuthorRole()); + dto.setCreatedAt(b.getCreatedAt()); + response.add(dto); + } + + return response; + } + + @Override + public BlogDetailResponse getBlogBySlug(String slug) { + + Blog blog = blogRepository + .findBySlugAndStatus(slug, BlogStatus.PUBLISHED) + .orElseThrow(() -> new RuntimeException("Blog not found")); + + BlogDetailResponse res = new BlogDetailResponse(); + res.setTitle(blog.getTitle()); + res.setContent(blog.getContent()); + res.setAuthorName(blog.getAuthorName()); + res.setAuthorRole(blog.getAuthorRole()); + res.setCreatedAt(blog.getCreatedAt()); + + return res; + } + + // ---------- SLUG GENERATOR ---------- + private String generateSlug(String input) { + String slug = Normalizer.normalize(input, Normalizer.Form.NFD) + .replaceAll("[^\\w\\s-]", "") + .trim() + .replaceAll("\\s+", "-") + .toLowerCase(Locale.ENGLISH); + + return slug; + } + + @Override + public List getAllBlogsForAdmin() { + + List blogs = blogRepository.findAllByOrderByCreatedAtDesc(); + List response = new ArrayList<>(); + + for (Blog b : blogs) { + BlogListResponse dto = new BlogListResponse(); + dto.setId(b.getId()); // ⭐ THIS WAS MISSING + dto.setTitle(b.getTitle()); + dto.setSlug(b.getSlug()); + dto.setExcerpt(b.getExcerpt()); + dto.setAuthorName(b.getAuthorName()); + dto.setAuthorRole(b.getAuthorRole()); + dto.setCreatedAt(b.getCreatedAt()); + response.add(dto); + } + + return response; + } + + + @Override + public BlogDetailResponse getBlogByIdForAdmin(Long id) { + + Blog blog = blogRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Blog not found")); + + BlogDetailResponse res = new BlogDetailResponse(); + res.setTitle(blog.getTitle()); + res.setContent(blog.getContent()); + res.setAuthorName(blog.getAuthorName()); + res.setAuthorRole(blog.getAuthorRole()); + res.setCreatedAt(blog.getCreatedAt()); + + return res; + } + + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/BookingServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/BookingServiceImpl.java index ad33785..2084333 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/impl/BookingServiceImpl.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/BookingServiceImpl.java @@ -2,8 +2,11 @@ import com.divinespark.dto.AdminSessionUserResponse; import com.divinespark.dto.UserBookingResponse; +import com.divinespark.dto.UserSessionBookingResponse; import com.divinespark.entity.Booking; import com.divinespark.entity.Session; +import com.divinespark.entity.enums.BookingStatus; +import com.divinespark.entity.enums.SessionStatus; import com.divinespark.repository.BookingRepository; import com.divinespark.service.BookingService; import com.divinespark.service.EmailService; @@ -45,15 +48,16 @@ public List getUserBookings(Long userId) { dto.setSessionId(session.getId()); dto.setSessionTitle(session.getTitle()); dto.setSessionType(session.getType().name()); - dto.setBookingStatus(booking.getStatus()); + dto.setStatus(booking.getBookingStatus()); dto.setStartTime(session.getStartTime().toLocalDateTime()); dto.setEndTime(session.getEndTime().toLocalDateTime()); - if ( - "CONFIRMED".equals(booking.getStatus()) && - "UPCOMING".equals(session.getStatus().name()) - ) { + (booking.getBookingStatus() == BookingStatus.CONFIRMED || + booking.getBookingStatus() == BookingStatus.PARTIALLY_PAID) && + session.getStatus() == SessionStatus.UPCOMING + ) + { dto.setJoinLink(session.getWhatsLink()); } @@ -75,11 +79,11 @@ public void cancelBooking(Long bookingId, Long userId) { throw new RuntimeException("Unauthorized cancellation"); } - if ("CANCELLED".equals(booking.getStatus())) { + if ("CANCELLED".equals(booking.getBookingStatus())) { throw new RuntimeException("Booking already cancelled"); } - booking.setStatus("CANCELLED"); + booking.setBookingStatus(BookingStatus.CANCELLED); Session session = booking.getSession(); session.getAvailableSeats().incrementAndGet(); @@ -128,4 +132,25 @@ public void downloadSessionUsers(Long sessionId, HttpServletResponse response) { } } + @Override + public UserSessionBookingResponse getMyBookingForSession( + Long userId, + Long sessionId + ) { + + Booking booking = bookingRepository + .findByUser_IdAndSession_Id(userId, sessionId) + .orElseThrow(() -> new RuntimeException("No booking found")); + + return new UserSessionBookingResponse( + booking.getId(), + booking.getBookingStatus(), + booking.getPaymentType(), + booking.getTotalAmount(), + booking.getPaidAmount(), + booking.getRemainingAmount() + ); + } + + } diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/EventServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/EventServiceImpl.java new file mode 100644 index 0000000..4267333 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/EventServiceImpl.java @@ -0,0 +1,86 @@ + package com.divinespark.service.impl; + + import com.divinespark.dto.EventRequest; + import com.divinespark.dto.EventTickerResponse; + import com.divinespark.entity.Event; + import com.divinespark.repository.EventRepository; + import com.divinespark.service.EventService; + import org.springframework.stereotype.Service; + import org.springframework.transaction.annotation.Transactional; + + import java.time.OffsetDateTime; + import java.util.Comparator; + import java.util.List; + + @Service + @Transactional + public class EventServiceImpl implements EventService { + + private final EventRepository repository; + + public EventServiceImpl(EventRepository repository) { + this.repository = repository; + } + + @Override + public Event create(EventRequest request) { + Event event = new Event(); + mapRequest(event, request); + return repository.save(event); + } + + @Override + public Event update(Long id, EventRequest request) { + Event event = repository.findById(id) + .orElseThrow(() -> new RuntimeException("Workshop/Event not found")); + + mapRequest(event, request); + return repository.save(event); + } + + @Override + public void delete(Long id) { + repository.deleteById(id); + } + + @Override + @Transactional(readOnly = true) + public List getAllAdmin() { + return repository.findAllByOrderByCreatedAtDesc(); + } + + @Override + @Transactional(readOnly = true) + public List getTickerEvents() { + + OffsetDateTime now = OffsetDateTime.now(); + + return repository.findAll().stream() + .filter(event -> + event.getStartTime() + .plusMinutes(event.getDurationMinutes()) + .isAfter(now) + ) + .sorted(Comparator.comparing(Event::getStartTime)) + .limit(10) + .map(this::mapToTicker) + .toList(); + } + + private void mapRequest(Event event, EventRequest request) { + event.setTitle(request.getTitle()); + event.setDescription(request.getDescription()); + event.setStartTime(request.getStartTime()); + event.setDurationMinutes(request.getDurationMinutes()); + } + + private EventTickerResponse mapToTicker(Event event) { + EventTickerResponse dto = new EventTickerResponse(); + dto.setId(event.getId()); + dto.setTitle(event.getTitle()); + dto.setDescription(event.getDescription()); + dto.setStartTime(event.getStartTime()); + dto.setDurationMinutes(event.getDurationMinutes()); + return dto; + } + } diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/InstallmentServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/InstallmentServiceImpl.java new file mode 100644 index 0000000..0bd71bf --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/InstallmentServiceImpl.java @@ -0,0 +1,170 @@ +package com.divinespark.service.impl; + + +import com.divinespark.dto.InstallmentPayResponse; +import com.divinespark.dto.InstallmentResponse; +import com.divinespark.dto.RazorpayOrderResponse; +import com.divinespark.entity.Booking; +import com.divinespark.entity.Installment; +import com.divinespark.entity.Payment; +import com.divinespark.entity.enums.BookingStatus; +import com.divinespark.entity.enums.InstallmentStatus; +import com.divinespark.repository.BookingRepository; +import com.divinespark.repository.InstallmentRepository; +import com.divinespark.repository.PaymentRepository; +import com.divinespark.service.InstallementService; +import com.divinespark.service.RazorpayService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.stream.Collectors; + + +@Service +public class InstallmentServiceImpl implements InstallementService { + + private final InstallmentRepository installmentRepository; + private final BookingRepository bookingRepository; + private final PaymentRepository paymentRepository; + private final RazorpayService razorpayService; + + public InstallmentServiceImpl( + InstallmentRepository installmentRepository, + BookingRepository bookingRepository, + PaymentRepository paymentRepository, + RazorpayService razorpayService + ) { + this.installmentRepository = installmentRepository; + this.bookingRepository = bookingRepository; + this.paymentRepository = paymentRepository; + this.razorpayService = razorpayService; + } + + @Override + @Transactional + public InstallmentPayResponse payInstallment(Long installmentId, Long userId) { + + Installment installment = installmentRepository.findById(installmentId) + .orElseThrow(() -> new RuntimeException("Installment not found")); + + Booking booking = installment.getBooking(); + + // Security check + if (!booking.getUser().getId().equals(userId)) { + throw new RuntimeException("Access denied"); + } + + // Already paid + if (installment.getStatus() == InstallmentStatus.PAID) { + throw new RuntimeException("Installment already paid"); + } + + // Enforce order: previous installment must be PAID + if (installment.getInstallmentNumber() > 1) { + installmentRepository.findByBooking_IdAndStatus( + booking.getId(), + InstallmentStatus.PENDING + ).stream() + .filter(i -> i.getInstallmentNumber() < installment.getInstallmentNumber()) + .findAny() + .ifPresent(i -> { + throw new RuntimeException("Previous installment not paid"); + }); + } + + // Create Razorpay order for THIS installment only + int amountInPaise = (int) Math.round(installment.getAmount() * 100); + + RazorpayOrderResponse order = + razorpayService.createOrder(amountInPaise, booking.getId()); + + installment.setRazorpayOrderId(order.getOrderId()); + installmentRepository.save(installment); + + // Track payment (important for admin + webhook) + Payment payment = new Payment(); + payment.setBookingId(booking.getId()); + payment.setAmount(installment.getAmount()); + payment.setGatewayOrderId(order.getOrderId()); + payment.setStatus("CREATED"); + paymentRepository.save(payment); + + InstallmentPayResponse res = new InstallmentPayResponse(); + res.setBookingId(booking.getId()); + res.setInstallmentId(installment.getId()); + res.setRazorpayOrderId(order.getOrderId()); + res.setAmount(amountInPaise); + + return res; + } + + @Override + public List getInstallmentsByBooking( + Long bookingId, + Long userId + ) { + + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new RuntimeException("Booking not found")); + + // SECURITY CHECK (VERY IMPORTANT) + if (!booking.getUser().getId().equals(userId)) { + throw new RuntimeException("Access denied"); + } + + List installments = + installmentRepository.findByBookingIdOrderByInstallmentNumber(bookingId); + + return installments.stream() + .map(i -> new InstallmentResponse( + i.getId(), + i.getInstallmentNumber(), + i.getAmount(), + i.getStatus(), + i.getPaidAt() + )) + .collect(Collectors.toList()); + } + + + @Transactional + public void markInstallmentPaid(String razorpayOrderId) { + + Installment installment = installmentRepository + .findByRazorpayOrderId(razorpayOrderId) + .orElseThrow(() -> new RuntimeException("Installment not found")); + + if (installment.getStatus() == InstallmentStatus.PAID) { + return; + } + + // Mark installment paid + installment.setStatus(InstallmentStatus.PAID); + installment.setPaidAt(OffsetDateTime.now()); + installmentRepository.save(installment); + + Booking booking = installment.getBooking(); + + // Update booking amounts + booking.setPaidAmount( + booking.getPaidAmount() + installment.getAmount() + ); + booking.setRemainingAmount( + booking.getTotalAmount() - booking.getPaidAmount() + ); + + // Update booking status + boolean allPaid = installmentRepository + .findByBookingIdOrderByInstallmentNumber(booking.getId()) + .stream() + .allMatch(i -> i.getStatus() == InstallmentStatus.PAID); + + booking.setBookingStatus( + allPaid ? BookingStatus.CONFIRMED : BookingStatus.PARTIALLY_PAID + ); + } + + +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/PaymentServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/PaymentServiceImpl.java index 7f27f5e..066fef0 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/impl/PaymentServiceImpl.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/PaymentServiceImpl.java @@ -1,17 +1,19 @@ package com.divinespark.service.impl; import com.divinespark.dto.AdminPaymentResponse; -import com.divinespark.entity.Booking; -import com.divinespark.entity.Payment; -import com.divinespark.entity.Session; -import com.divinespark.entity.User; +import com.divinespark.entity.*; +import com.divinespark.entity.enums.BookingStatus; +import com.divinespark.entity.enums.InstallmentStatus; +import com.divinespark.entity.enums.PaymentType; import com.divinespark.repository.BookingRepository; +import com.divinespark.repository.InstallmentRepository; import com.divinespark.repository.PaymentRepository; import com.divinespark.service.EmailService; import com.divinespark.service.PaymentService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.OffsetDateTime; import java.util.List; @Service @@ -20,26 +22,30 @@ public class PaymentServiceImpl implements PaymentService { private final PaymentRepository paymentRepository; private final BookingRepository bookingRepo; private final EmailService emailService; + private final InstallmentRepository installmentRepository; public PaymentServiceImpl( PaymentRepository paymentRepository, BookingRepository bookingRepo, - EmailService emailService) { + EmailService emailService, + InstallmentRepository installmentRepository) { this.paymentRepository = paymentRepository; this.bookingRepo = bookingRepo; this.emailService = emailService; + this.installmentRepository = installmentRepository; } + // ================= FAILURE HANDLING ================= + @Transactional public void handlePaymentFailure(String gatewayOrderId) { - Payment payment = paymentRepository - .findByGatewayOrderId(gatewayOrderId); + Payment payment = + paymentRepository.findByGatewayOrderId(gatewayOrderId); - if ("FAILED".equals(payment.getStatus())) { - return; - } + if (payment == null) return; + if ("FAILED".equals(payment.getStatus())) return; payment.setStatus("FAILED"); paymentRepository.save(payment); @@ -48,65 +54,123 @@ public void handlePaymentFailure(String gatewayOrderId) { .findById(payment.getBookingId()) .orElseThrow(() -> new RuntimeException("Booking not found")); - booking.setStatus("FAILED"); + booking.setBookingStatus(BookingStatus.CANCELLED); bookingRepo.save(booking); } + // ================= SUCCESS HANDLING (FULL PAYMENT) ================= + @Transactional public void handlePaymentSuccess(String gatewayOrderId) { - Payment payment = paymentRepository - .findByGatewayOrderId(gatewayOrderId); + Payment payment = + paymentRepository.findByGatewayOrderId(gatewayOrderId); - if ("SUCCESS".equals(payment.getStatus())) { - return; - } + if (payment == null) return; + if ("SUCCESS".equals(payment.getStatus())) return; payment.setStatus("SUCCESS"); paymentRepository.save(payment); Booking booking = bookingRepo .findById(payment.getBookingId()) - .orElseThrow(); + .orElseThrow(() -> new RuntimeException("Booking not found")); - booking.setStatus("CONFIRMED"); - bookingRepo.save(booking); + if (booking.getPaymentType() == PaymentType.FULL) { + booking.setBookingStatus(BookingStatus.CONFIRMED); + bookingRepo.save(booking); + } } + // ================= ADMIN ================= + @Override public List getAllPaymentsForAdmin() { return paymentRepository.fetchAdminPayments(); } + // ================= WEBHOOK (FULL + INSTALLMENT) ================= + @Override @Transactional public boolean handlePaymentCaptured(String razorpayOrderId, int amount) { - Payment payment = paymentRepository - .findByGatewayOrderId(razorpayOrderId); - - if (payment == null) { - throw new RuntimeException("Payment not found"); + // 1️⃣ INSTALLMENT PAYMENT (CHECK FIRST) + Installment installment = + installmentRepository.findByRazorpayOrderId(razorpayOrderId) + .orElse(null); + + if (installment != null) { + + if (installment.getStatus() == InstallmentStatus.PAID) { + return true; // idempotent + } + + installment.setStatus(InstallmentStatus.PAID); + installment.setPaidAt(OffsetDateTime.now()); + installmentRepository.save(installment); + + Booking booking = installment.getBooking(); + + double paidAmount = installmentRepository + .findByBooking_IdAndStatus( + booking.getId(), + InstallmentStatus.PAID + ) + .stream() + .mapToDouble(Installment::getAmount) + .sum(); + + booking.setPaidAmount(paidAmount); + booking.setRemainingAmount( + Math.max(booking.getTotalAmount() - paidAmount, 0) + ); + + booking.setBookingStatus( + booking.getRemainingAmount() == 0 + ? BookingStatus.CONFIRMED + : BookingStatus.PARTIALLY_PAID + ); + + bookingRepo.save(booking); + + // Payment record (for admin / audit) + Payment p = new Payment(); + p.setBookingId(booking.getId()); + p.setAmount(installment.getAmount()); + p.setGatewayOrderId(razorpayOrderId); + p.setStatus("SUCCESS"); + paymentRepository.save(p); + + return true; } - if ("SUCCESS".equals(payment.getStatus())) { - return false; - } + // 2️⃣ FULL PAYMENT (CHECK AFTER) + Payment payment = + paymentRepository.findByGatewayOrderId(razorpayOrderId); - payment.setStatus("SUCCESS"); - paymentRepository.save(payment); + if (payment != null) { - Booking booking = bookingRepo - .findById(payment.getBookingId()) - .orElseThrow(() -> new RuntimeException("Booking not found")); + if ("SUCCESS".equals(payment.getStatus())) { + return true; + } - booking.setStatus("CONFIRMED"); + payment.setStatus("SUCCESS"); + paymentRepository.save(payment); - // Zoom registration for access control (no join_url dependency) - Session session = booking.getSession(); - User user = booking.getUser(); + Booking booking = bookingRepo.findById(payment.getBookingId()) + .orElseThrow(); + + booking.setPaidAmount(booking.getTotalAmount()); + booking.setRemainingAmount(0); + booking.setBookingStatus(BookingStatus.CONFIRMED); + bookingRepo.save(booking); + + return true; + } - bookingRepo.save(booking); return false; } + + } diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/ProgramServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/ProgramServiceImpl.java new file mode 100644 index 0000000..b5a3a62 --- /dev/null +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/ProgramServiceImpl.java @@ -0,0 +1,75 @@ +package com.divinespark.service.impl; + +import com.divinespark.dto.ProgramRequest; +import com.divinespark.dto.ProgramResponse; +import com.divinespark.entity.Program; +import com.divinespark.repository.ProgramRepository; +import com.divinespark.service.ProgramService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +public class ProgramServiceImpl implements ProgramService { + + private final ProgramRepository repository; + + public ProgramServiceImpl(ProgramRepository repository) { + this.repository = repository; + } + + @Override + public Program create(ProgramRequest request) { + Program program = new Program(); + mapRequest(program, request); + return repository.save(program); + } + + @Override + public Program update(Long id, ProgramRequest request) { + Program program = repository.findById(id) + .orElseThrow(() -> new RuntimeException("Program not found")); + + mapRequest(program, request); + return repository.save(program); + } + + @Override + public void delete(Long id) { + repository.deleteById(id); + } + + @Override + @Transactional(readOnly = true) + public List getAllAdmin() { + return repository.findAllByOrderByCreatedAtDesc(); + } + + @Override + @Transactional(readOnly = true) + public List getByCategory(String category) { + return repository + .findByCategoryAndActiveTrueOrderByCreatedAtDesc(category) + .stream() + .map(this::mapToResponse) + .toList(); + } + + private void mapRequest(Program program, ProgramRequest request) { + program.setTitle(request.getTitle()); + program.setDescription(request.getDescription()); + program.setCategory(request.getCategory()); + program.setImageUrl(request.getImageUrl()); + } + + private ProgramResponse mapToResponse(Program program) { + ProgramResponse res = new ProgramResponse(); + res.setId(program.getId()); + res.setTitle(program.getTitle()); + res.setDescription(program.getDescription()); + res.setImageUrl(program.getImageUrl()); + return res; + } +} diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java index 8a384d4..4b2c1a1 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java @@ -18,7 +18,7 @@ public RazorpayServiceImpl(RazorpayConfig razorpayConfig) { } @Override - public RazorpayOrderResponse createOrder(double amount, Long bookingId) { + public RazorpayOrderResponse createOrder(int amountInPaise, Long referenceId) { try { RazorpayClient client = new RazorpayClient( @@ -27,16 +27,15 @@ public RazorpayOrderResponse createOrder(double amount, Long bookingId) { ); JSONObject orderRequest = new JSONObject(); - orderRequest.put("amount", (int) (amount * 100)); // paise + orderRequest.put("amount", amountInPaise); // already paise orderRequest.put("currency", razorpayConfig.getCurrency()); - orderRequest.put("receipt", "booking_" + bookingId); + orderRequest.put("receipt", "ref_" + referenceId); orderRequest.put("payment_capture", 1); Order order = client.orders.create(orderRequest); RazorpayOrderResponse response = new RazorpayOrderResponse(); response.setOrderId(order.get("id")); - System.out.println("RAZORPAY ORDER ID => " + order.get("id")); return response; @@ -44,4 +43,5 @@ public RazorpayOrderResponse createOrder(double amount, Long bookingId) { throw new RuntimeException("Failed to create Razorpay order", e); } } + } diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/SessionServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/SessionServiceImpl.java index 44b1c8f..d48a342 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/impl/SessionServiceImpl.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/SessionServiceImpl.java @@ -2,8 +2,7 @@ import com.divinespark.dto.*; import com.divinespark.entity.*; -import com.divinespark.entity.enums.SessionStatus; -import com.divinespark.entity.enums.SessionType; +import com.divinespark.entity.enums.*; import com.divinespark.exception.BusinessException; import com.divinespark.repository.*; import com.divinespark.service.*; @@ -15,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -29,6 +29,7 @@ public class SessionServiceImpl implements SessionService { private final PaymentRepository paymentRepository; private final SessionRepository sessionRepository; private final RazorpayService razorpayService; + private final InstallmentRepository installmentRepository; public SessionServiceImpl( SessionRepository repo, @@ -36,7 +37,8 @@ public SessionServiceImpl( UserRepository userRepo, PaymentRepository paymentRepository, SessionRepository sessionRepository, - RazorpayService razorpayService) { + RazorpayService razorpayService, + InstallmentRepository installmentRepository) { this.repo = repo; this.bookingRepository = bookingRepository; @@ -44,6 +46,7 @@ public SessionServiceImpl( this.paymentRepository = paymentRepository; this.sessionRepository = sessionRepository; this.razorpayService = razorpayService; + this.installmentRepository = installmentRepository; } // ================= ADMIN ================= @@ -165,12 +168,14 @@ public void joinFreeSession(Long sessionId, Long userId) { Booking booking = new Booking(); booking.setSession(session); booking.setUser(user); - booking.setStatus("CONFIRMED"); - bookingRepository.save(booking); + booking.setPaymentType(PaymentType.FREE); + booking.setBookingStatus(BookingStatus.CONFIRMED); + bookingRepository.save(booking); session.getAvailableSeats().decrementAndGet(); } + @Override public PaymentInitiateResponse initiatePaidSession(Long sessionId, Long userId) { @@ -187,34 +192,123 @@ public PaymentInitiateResponse initiatePaidSession(Long sessionId, Long userId) .orElseThrow(() -> new RuntimeException("User not found")); Booking booking = bookingRepository - .findByUserIdAndSessionIdAndStatus(userId, sessionId, "PENDING") + .findByUser_IdAndSession_Id(userId, sessionId) .orElseGet(() -> { Booking b = new Booking(); b.setSession(session); b.setUser(user); - b.setStatus("PENDING"); - return bookingRepository.save(b); + b.setPaymentType(PaymentType.FULL); + b.setBookingStatus(BookingStatus.PENDING); + bookingRepository.save(b); + session.getAvailableSeats().decrementAndGet(); + return b; }); + // BLOCK DUPLICATE PAYMENTS + if (booking.getBookingStatus() == BookingStatus.CONFIRMED) { + throw new BusinessException("Session already paid"); + } + RazorpayOrderResponse order = - razorpayService.createOrder(session.getPrice(), booking.getId()); + razorpayService.createOrder( + (int) Math.round(session.getPrice() * 100), + booking.getId() + ); Payment payment = new Payment(); payment.setBookingId(booking.getId()); payment.setAmount(session.getPrice()); payment.setGatewayOrderId(order.getOrderId()); payment.setStatus("CREATED"); + paymentRepository.save(payment); PaymentInitiateResponse res = new PaymentInitiateResponse(); res.setBookingId(booking.getId()); res.setOrderId(order.getOrderId()); - res.setAmount(session.getPrice() * 100); + res.setAmount((int) Math.round(session.getPrice())); res.setCurrency("INR"); return res; } + + + @Override + @Transactional + public InstallmentPaymentInitiateResponse initiateInstallmentPayment( + Long sessionId, + Long userId + ) { + + Session session = sessionRepository.findById(sessionId) + .orElseThrow(() -> new RuntimeException("Session not found")); + + if (session.getType() != SessionType.PAID) { + throw new RuntimeException("Not a paid session"); + } + + User user = userRepo.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found")); + + // Create booking + Booking booking = new Booking(); + booking.setUser(user); + booking.setSession(session); + booking.setPaymentType(PaymentType.INSTALLMENT); + booking.setTotalAmount(session.getPrice()); + booking.setPaidAmount(0.0); + booking.setRemainingAmount(session.getPrice()); + booking.setBookingStatus(BookingStatus.PENDING); + booking.setWhatsappLink(session.getWhatsLink()); + + bookingRepository.save(booking); + session.getAvailableSeats().decrementAndGet(); + + // Create installments (3 parts) + double installmentAmount = session.getPrice() / 3.0; + + List installments = new ArrayList<>(); + + for (int i = 1; i <= 3; i++) { + Installment inst = new Installment(); + inst.setBooking(booking); + inst.setInstallmentNumber(i); + inst.setAmount(installmentAmount); + inst.setDueDate(OffsetDateTime.now().plusMonths(i - 1)); + inst.setStatus(InstallmentStatus.PENDING); + installments.add(inst); + } + + installmentRepository.saveAll(installments); + + // First installment ONLY + Installment firstInstallment = installments.get(0); + + // Charge ONLY installment amount + RazorpayOrderResponse order = + razorpayService.createOrder( + (int) Math.round(firstInstallment.getAmount() * 100), + booking.getId() + ); + + firstInstallment.setRazorpayOrderId(order.getOrderId()); + installmentRepository.save(firstInstallment); + + // Response + InstallmentPaymentInitiateResponse res = + new InstallmentPaymentInitiateResponse(); + + res.setBookingId(booking.getId()); + res.setInstallmentId(firstInstallment.getId()); + res.setRazorpayOrderId(order.getOrderId()); + res.setAmount((int) Math.round(firstInstallment.getAmount() * 100)); + + return res; + } + + + @Override public List getUsersBySession(Long sessionId) { return bookingRepository.findUsersBySessionId(sessionId); @@ -309,30 +403,23 @@ public SessionDetailResponse getSessionDetails(Long sessionId) { @Override public String getWhatsappLinkIfConfirmed(Long sessionId, Long userId) { - // 1. Fetch CONFIRMED booking for user + session Booking booking = bookingRepository - .findByUserIdAndSessionIdAndStatus( - userId, - sessionId, - "CONFIRMED" - ) - .orElseThrow(() -> new ExpressionException( - "You are not allowed to access this session" - )); - - // 2. Fetch session from booking (already mapped) - String whatsappLink = booking - .getSession() - .getWhatsLink(); - - // 3. Safety check (admin forgot to add link) + .findByUser_IdAndSession_Id(userId, sessionId) + .orElseThrow(() -> new ExpressionException("Access denied")); + + if ( + booking.getBookingStatus() != BookingStatus.CONFIRMED && + booking.getBookingStatus() != BookingStatus.PARTIALLY_PAID + ) { + throw new ExpressionException("Access denied"); + } + + String whatsappLink = booking.getSession().getWhatsLink(); + if (whatsappLink == null || whatsappLink.isBlank()) { - throw new IllegalStateException( - "WhatsApp group link not configured for this session" - ); + throw new IllegalStateException("WhatsApp link not configured"); } - // 4. Return link return whatsappLink; } From f4630f8ea316041e224590d9b82569ea181884ff Mon Sep 17 00:00:00 2001 From: Shivam Verma Date: Fri, 30 Jan 2026 14:09:26 +0530 Subject: [PATCH 3/4] Title (#84) * Add GitHub Actions CI for Spring Boot tests (#68) * Trigger CI * Update Java version to 21 in workflow (#69) * Rs/fix workflow location (#71) * Fix workflow path so GitHub Actions can detect it * Updated junit run * Updated yml * Added blog feature * Added an event-ticker * Blog issue ! * gallery section implementation with imageurl for future scaling * added installment logic * Installment implementation * Fixed installment issue completly * InstallmentAdmin * Refactor SecurityConfig for improved authorization rules * added the blog and review endpoints --------- Co-authored-by: RuchaChavan21 Co-authored-by: nehan1311 Co-authored-by: sujaljain272004 <160271734+sujaljain272004@users.noreply.github.com> From 007057d99813068ef44fa717fa91aaab818447d6 Mon Sep 17 00:00:00 2001 From: Neha Date: Thu, 19 Mar 2026 19:40:26 +0530 Subject: [PATCH 4/4] fixed bug --- .../service/impl/RazorpayServiceImpl.java | 218 ++++++------------ 1 file changed, 67 insertions(+), 151 deletions(-) diff --git a/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java b/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java index 9ae72dd..0b6263a 100644 --- a/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java +++ b/divinespark-backend/src/main/java/com/divinespark/service/impl/RazorpayServiceImpl.java @@ -1,178 +1,94 @@ package com.divinespark.service.impl; -import com.divinespark.dto.AdminPaymentResponse; -import com.divinespark.entity.*; -import com.divinespark.entity.enums.BookingStatus; -import com.divinespark.entity.enums.InstallmentStatus; -import com.divinespark.entity.enums.PaymentType; -import com.divinespark.repository.BookingRepository; -import com.divinespark.repository.InstallmentRepository; -import com.divinespark.repository.PaymentRepository; -import com.divinespark.service.EmailService; -import com.divinespark.service.PaymentService; +import com.divinespark.config.RazorpayConfig; +import com.divinespark.dto.RazorpayOrderResponse; +import com.divinespark.service.RazorpayService; +import com.razorpay.Order; +import com.razorpay.RazorpayClient; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.time.OffsetDateTime; -import java.util.List; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Base64; @Service -public class PaymentServiceImpl implements PaymentService { - - private final PaymentRepository paymentRepository; - private final BookingRepository bookingRepo; - private final EmailService emailService; - private final InstallmentRepository installmentRepository; - - public PaymentServiceImpl( - PaymentRepository paymentRepository, - BookingRepository bookingRepo, - EmailService emailService, - InstallmentRepository installmentRepository) { - - this.paymentRepository = paymentRepository; - this.bookingRepo = bookingRepo; - this.emailService = emailService; - this.installmentRepository = installmentRepository; - } - - // ================= FAILURE HANDLING ================= - - @Transactional - public void handlePaymentFailure(String gatewayOrderId) { - - Payment payment = - paymentRepository.findByGatewayOrderId(gatewayOrderId) - .orElseThrow(() -> new RuntimeException("Payment not found")); +public class RazorpayServiceImpl implements RazorpayService { - if (payment == null) return; - if ("FAILED".equals(payment.getStatus())) return; + private final RazorpayConfig razorpayConfig; + @Value("${razorpay.key.secret}") + private String razorpaySecret; - payment.setStatus("FAILED"); - paymentRepository.save(payment); - Booking booking = bookingRepo - .findById(payment.getBookingId()) - .orElseThrow(() -> new RuntimeException("Booking not found")); - - booking.setBookingStatus(BookingStatus.CANCELLED); - bookingRepo.save(booking); + public RazorpayServiceImpl(RazorpayConfig razorpayConfig) { + this.razorpayConfig = razorpayConfig; } - // ================= SUCCESS HANDLING (FULL PAYMENT) ================= + @Override + public RazorpayOrderResponse createOrder(int amountInPaise, Long referenceId) { - @Transactional - public void handlePaymentSuccess(String gatewayOrderId) { + try { + RazorpayClient client = new RazorpayClient( + razorpayConfig.getKeyId(), + razorpayConfig.getKeySecret() + ); - Payment payment = - paymentRepository.findByGatewayOrderId(gatewayOrderId) - .orElseThrow(() -> new RuntimeException("Payment not found")); + JSONObject orderRequest = new JSONObject(); + orderRequest.put("amount", amountInPaise); // already paise + orderRequest.put("currency", razorpayConfig.getCurrency()); + orderRequest.put("receipt", "ref_" + referenceId); + orderRequest.put("payment_capture", 1); - if (payment == null) return; - if ("SUCCESS".equals(payment.getStatus())) return; + Order order = client.orders.create(orderRequest); - payment.setStatus("SUCCESS"); - paymentRepository.save(payment); + RazorpayOrderResponse response = new RazorpayOrderResponse(); + response.setOrderId(order.get("id")); - Booking booking = bookingRepo - .findById(payment.getBookingId()) - .orElseThrow(() -> new RuntimeException("Booking not found")); + return response; - if (booking.getPaymentType() == PaymentType.FULL) { - booking.setBookingStatus(BookingStatus.CONFIRMED); - bookingRepo.save(booking); + } catch (Exception e) { + throw new RuntimeException("Failed to create Razorpay order", e); } } - // ================= ADMIN ================= - - @Override - public List getAllPaymentsForAdmin() { - return paymentRepository.fetchAdminPayments(); - } - - // ================= WEBHOOK (FULL + INSTALLMENT) ================= - @Override - @Transactional - public boolean handlePaymentCaptured(String razorpayOrderId, int amount) { - - // 1️ INSTALLMENT PAYMENT (CHECK FIRST) - Installment installment = - installmentRepository.findByRazorpayOrderId(razorpayOrderId) - .orElse(null); - - if (installment != null) { - - if (installment.getStatus() == InstallmentStatus.PAID) { - return true; // idempotent - } - - installment.setStatus(InstallmentStatus.PAID); - installment.setPaidAt(OffsetDateTime.now()); - installmentRepository.save(installment); - - Booking booking = installment.getBooking(); - - double paidAmount = installmentRepository - .findByBooking_IdAndStatus( - booking.getId(), - InstallmentStatus.PAID - ) - .stream() - .mapToDouble(Installment::getAmount) - .sum(); - - booking.setPaidAmount(paidAmount); - booking.setRemainingAmount( - Math.max(booking.getTotalAmount() - paidAmount, 0) - ); - - booking.setBookingStatus( - booking.getRemainingAmount() == 0 - ? BookingStatus.CONFIRMED - : BookingStatus.PARTIALLY_PAID - ); - - bookingRepo.save(booking); - - // Payment record (for admin / audit) - Payment p = new Payment(); - p.setBookingId(booking.getId()); - p.setAmount(installment.getAmount()); - p.setGatewayOrderId(razorpayOrderId); - p.setStatus("SUCCESS"); - paymentRepository.save(p); - - return true; + public boolean verifySignature( + String razorpayOrderId, + String razorpayPaymentId, + String razorpaySignature + ) { + try { + String payload = razorpayOrderId + "|" + razorpayPaymentId; + + Mac mac = Mac.getInstance("HmacSHA256"); + SecretKeySpec secretKey = + new SecretKeySpec( + razorpaySecret.getBytes(StandardCharsets.UTF_8), + "HmacSHA256" + ); + + mac.init(secretKey); + byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); + + // 🔥 HEX encoding (NOT Base64) + String generatedSignature = bytesToHex(hash); + + return generatedSignature.equals(razorpaySignature); + + } catch (Exception e) { + e.printStackTrace(); + return false; } + } - // 2 FULL PAYMENT (CHECK AFTER) - Payment payment = - paymentRepository.findByGatewayOrderId(razorpayOrderId) - .orElseThrow(() -> new RuntimeException("Payment not found")); - - if (payment != null) { - - if ("SUCCESS".equals(payment.getStatus())) { - return true; - } - - payment.setStatus("SUCCESS"); - paymentRepository.save(payment); - - Booking booking = bookingRepo.findById(payment.getBookingId()) - .orElseThrow(); - - booking.setPaidAmount(booking.getTotalAmount()); - booking.setRemainingAmount(0); - booking.setBookingStatus(BookingStatus.CONFIRMED); - bookingRepo.save(booking); - - return true; + private String bytesToHex(byte[] bytes) { + StringBuilder hex = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + hex.append(String.format("%02x", b)); } - - return false; + return hex.toString(); }