Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1296777
Merge pull request #66 from RuchaChavan21/sv/unit-tests-add
RuchaChavan21 Jan 24, 2026
b2bf113
Add GitHub Actions CI for Spring Boot tests (#68)
ShivamVerma380 Jan 24, 2026
7dec930
Trigger CI
ShivamVerma380 Jan 24, 2026
5c3d0e5
Merge branch 'main' of https://github.com/RuchaChavan21/DivineSpark-b…
ShivamVerma380 Jan 24, 2026
8d88142
Update Java version to 21 in workflow (#69)
ShivamVerma380 Jan 24, 2026
de7bc54
Rs/fix workflow location (#71)
ShivamVerma380 Jan 24, 2026
0074d88
Added blog feature
RuchaChavan21 Jan 24, 2026
2351a75
Merge pull request #73 from RuchaChavan21/feature/blog
nehan1311 Jan 24, 2026
bb5f63e
Added an event-ticker
nehan1311 Jan 26, 2026
c201d06
Merge pull request #74 from RuchaChavan21/feature/event-ticker
RuchaChavan21 Jan 26, 2026
4d95f59
Blog issue !
sujaljain272004 Jan 26, 2026
8cc6a8d
Merge pull request #75 from RuchaChavan21/fix/time-conversion
nehan1311 Jan 26, 2026
dcaa15f
gallery section implementation with imageurl for future scaling
nehan1311 Jan 26, 2026
6339c43
Merge pull request #76 from RuchaChavan21/feature/programs
RuchaChavan21 Jan 26, 2026
3d2a2ae
added installment logic
RuchaChavan21 Jan 26, 2026
5c464e7
Merge pull request #77 from RuchaChavan21/feature/installment
RuchaChavan21 Jan 27, 2026
83b85b8
Installment implementation
RuchaChavan21 Jan 27, 2026
ceb0883
Merge pull request #78 from RuchaChavan21/feature/installment
nehan1311 Jan 27, 2026
3ec7111
Fixed installment issue completly
RuchaChavan21 Jan 27, 2026
b399f08
Merge pull request #79 from RuchaChavan21/feature/installment
nehan1311 Jan 27, 2026
7d34a50
InstallmentAdmin
sujaljain272004 Jan 28, 2026
4610dd0
Merge pull request #80 from RuchaChavan21/feature/installment
RuchaChavan21 Jan 28, 2026
7af94c0
Refactor SecurityConfig for improved authorization rules
RuchaChavan21 Jan 28, 2026
5e0140a
Merge pull request #81 from RuchaChavan21/RuchaChavan21-patch-1
nehan1311 Jan 28, 2026
aa3522f
added the blog and review endpoints
nehan1311 Jan 28, 2026
f4d5942
Merge pull request #82 from RuchaChavan21/nehan1311-patch-1
RuchaChavan21 Jan 29, 2026
9656042
implement installment payments with booking and admin improvement
RuchaChavan21 Feb 1, 2026
9de43cb
Merge pull request #85 from RuchaChavan21/feature/installment-payments
nehan1311 Feb 1, 2026
8e42da3
fixed whatapp link issue
RuchaChavan21 Feb 1, 2026
4da8b10
Merge pull request #86 from RuchaChavan21/feature/installment-payments
nehan1311 Feb 1, 2026
a153cb0
fixed razorpay, already booked and installment issue
RuchaChavan21 Feb 4, 2026
9cdd471
Merge pull request #87 from RuchaChavan21/fix/razorpay-fix
nehan1311 Feb 4, 2026
17efe60
Removed duration compulsion in event
RuchaChavan21 Feb 4, 2026
3197a53
Removed duration compulsion in event (#88)
RuchaChavan21 Feb 4, 2026
4ab74ee
allow public access to get review and blog
RuchaChavan21 Feb 7, 2026
1af23e7
Add README for DivineSpark platform
RuchaChavan21 Feb 10, 2026
2815348
Merge pull request #90 from RuchaChavan21/RuchaChavan21-patch-2
nehan1311 Feb 10, 2026
6ea25b8
Blog error fix
sujaljain272004 Feb 10, 2026
b1546e9
Merge pull request #91 from RuchaChavan21/fix/blogError
RuchaChavan21 Feb 10, 2026
f5d2f8c
Merge branch 'main' of https://github.com/RuchaChavan21/DivineSpark-b…
RuchaChavan21 Feb 10, 2026
597adeb
fixed contact
RuchaChavan21 Mar 19, 2026
6df714f
Merge branch 'prod' into fix/number
nehan1311 Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# 🌟 DivineSpark — Live Session Booking & Payments Platform

DivineSpark is a **production-ready, client-facing web platform** for managing and booking live spiritual and wellness sessions.
The system is designed with real-world requirements such as **secure authentication, role-based access, installment payments, admin control, and operational automation**.

---

## 🚀 Key Features

### 🔐 Authentication & Security
- Email-based authentication with JWT
- Role-based access control (USER / ADMIN)
- Secured APIs using Spring Security
- Controlled access to session join links based on payment eligibility

---

### 📅 Session Management
- Admin can create, update, and delete sessions
- Supports both **Free** and **Paid** sessions
- Seat availability management
- Users can view and book upcoming sessions

---

### 💳 Payments & Installments
- Integrated Razorpay for secure online payments
- Supports:
- One-time full payments
- Installment-based payments
- Tracks:
- Paid amount
- Remaining amount
- Installment status per booking
- Booking lifecycle states:
- `PENDING`
- `PARTIALLY_PAID`
- `CONFIRMED`
- `CANCELLED`

---

### 🔗 Controlled Session Access
- Session join (WhatsApp) links are exposed only when eligibility is verified
- Access allowed only for `CONFIRMED` or `PARTIALLY_PAID` users
- Prevents unauthorized sharing or misuse of session links

---

### 🛠 Admin Features
- View users registered for each session
- Monitor installment payments per user
- Approve or reject user reviews
- Manage blogs, reviews, and events
- Export user, booking, and payment data as CSV for reporting

---

### ⚙️ Automation & Maintenance
- Designed to support scheduled CRON jobs
- Periodic database cleanup and archival (e.g., every 3 months)
- Helps reduce storage cost and maintain long-term performance

---

## 🧱 Tech Stack

### Backend
- Java 17
- Spring Boot
- Spring Security (JWT)
- Spring Data JPA
- MySQL
- Razorpay Payment Gateway

### Frontend
- React
- TypeScript
- Axios
- Mobile-friendly and responsive UI

### Tools & Concepts
- REST APIs
- Webhooks for payment events
- Cron jobs for automation
- CSV export for admin reporting

---

## 🏗 Architecture Overview

- Stateless backend with JWT-based authentication
- Clear separation of layers:
- Controllers
- Services
- Repositories
- Payment handling via:
- Frontend verification (primary)
- Webhook-based confirmation (fallback)
- Booking records preserved as immutable financial history
- Business rules enforced at the service layer

---

## 🔄 Booking & Payment Flow

1. User browses available sessions
2. Selects free or paid session
3. For paid sessions:
- Chooses full payment or installment option
4. Payment processed via Razorpay
5. Booking status updated based on payment progress
6. Session access granted only to eligible users

---

## 🧪 Testing & Validation

- Manual testing of:
- Free session booking
- Full payment flow
- Installment payments
- Cancelled booking re-booking
- Admin workflows
- Payment edge cases handled:
- Webhook retries
- Partial payments
- Duplicate payment prevention

---

## 📦 Project Status

- Core features implemented
- Backend is production-ready
- Continuous enhancements in progress

---

## 📌 Planned Enhancements
- Automated installment reminder notifications
- Admin payment reconciliation dashboard
- Advanced analytics and reporting
- Full CRON-based archival pipeline
- Cloud deployment with monitoring

---

## 👩‍💻 Project Context

DivineSpark is a **client-facing production project**, built with a strong focus on real-world payment flows, secure access control, operational automation, and long-term maintainability.

---

## 📄 License

This project is proprietary and developed for a client use case.
Reuse or redistribution requires prior permission.
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@ public PaymentVerificationController(InstallementService installmentService) {

@PostMapping("/installment/verify")
public ResponseEntity<Void> verifyInstallment(
@RequestParam String razorpayOrderId
@RequestParam String razorpayOrderId,
@RequestParam String razorpayPaymentId,
@RequestParam String razorpaySignature
) {
installmentService.markInstallmentPaid(razorpayOrderId);
System.out.println(">>> VERIFY API HIT <<<");
System.out.println("orderId = " + razorpayOrderId);
System.out.println("paymentId = " + razorpayPaymentId);
System.out.println("signature = " + razorpaySignature);

installmentService.verifyAndMarkInstallmentPaid(
razorpayOrderId,
razorpayPaymentId,
razorpaySignature
);

return ResponseEntity.ok().build();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public ResponseEntity<Void> submitReview(
return ResponseEntity.ok().build();
}

@PreAuthorize("hasRole('USER')")
@GetMapping
public ResponseEntity<ReviewResponse> getMyReview(
@AuthenticationPrincipal CustomUserDetails userDetails
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ public class BlogDetailResponse {
private String authorRole;
private OffsetDateTime createdAt;


private Boolean isPublished;

public Boolean getIsPublished() {
return isPublished;
}
public void setIsPublished(Boolean isPublished) {
this.isPublished = isPublished;
}

public String getTitle() {
return title;
}
Expand Down Expand Up @@ -49,4 +59,4 @@ public OffsetDateTime getCreatedAt() {
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ public class BlogListResponse {
private Long id;


private Boolean isPublished;

public Boolean getIsPublished() {
return isPublished;
}
public void setIsPublished(Boolean isPublished) {
this.isPublished = isPublished;
}


public String getAuthorName() {
return authorName;
}
Expand Down Expand Up @@ -68,4 +78,4 @@ public String getTitle() {
public void setTitle(String title) {
this.title = title;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class SessionCreateRequest {
private double price;
private String whatsappGroupLink;

// Frontend sends OffsetDateTime with +05:30
// Frontend sends OffsetDateTime with +05:30
private OffsetDateTime startTime;
private OffsetDateTime endTime;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import java.time.OffsetDateTime;

@Entity
@Table(name = "bookings",
uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "session_id"}))
@Table(name = "bookings")
public class Booking {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class Event {
@Column(nullable = false)
private OffsetDateTime startTime;

@Column(nullable = false)
@Column
private Integer durationMinutes;
// auto-expiry logic uses this

Expand Down Expand Up @@ -81,4 +81,4 @@ public OffsetDateTime getCreatedAt() {
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,18 @@ public interface BookingRepository extends JpaRepository<Booking, Long> {
""")
List<Booking> findUserBookingsWithSession(Long userId);

Optional<Booking> findByUser_IdAndSession_Id(
Long userId,
Long sessionId
);
Optional<Booking> findByUserIdAndSessionId(Long userId, Long sessionId);

boolean existsByUser_IdAndSession_Id(
Long userId,
Long sessionId
);

boolean existsByUser_IdAndSession_IdAndBookingStatus(
Optional<Booking> findFirstByUser_IdAndSession_IdAndBookingStatusInOrderByCreatedAtDesc(
Long userId,
Long sessionId,
BookingStatus bookingStatus
List<BookingStatus> statuses
);




// ---------- ADMIN ----------
@Query("""
SELECT new com.divinespark.dto.AdminSessionUserResponse(
Expand Down Expand Up @@ -101,5 +97,12 @@ List<Booking> findBookingEntitiesBySessionId(
@Param("sessionId") Long sessionId
);

boolean existsByUserIdAndSessionIdAndBookingStatusIn(
Long userId,
Long sessionId,
List<BookingStatus> statuses
);



}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@ void updateOrderId(
@Param("orderId") String orderId
);

@Modifying
@Query("delete from Installment i where i.booking.id = :bookingId")
void deleteByBookingId(@Param("bookingId") Long bookingId);


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

public interface PaymentRepository extends JpaRepository<Payment, Long> {

Payment findByGatewayOrderId(String gatewayOrderId);
Optional<Payment> findByGatewayOrderId(String gatewayOrderId);

Optional<Payment> findTopByBookingIdOrderByCreatedAtDesc(Long bookingId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
Expand Down Expand Up @@ -55,18 +53,28 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/api/v1/user/review/**"
).permitAll()

// User APIs
.requestMatchers("/api/v1/user/**", "/api/v1/installments/**, ", "/api/v1/payments").hasRole("USER")
// PUBLIC: session browsing & reviews (NO LOGIN)
.requestMatchers(
HttpMethod.GET,
"/api/v1/sessions/**",
"/api/v1/user/review/**",
"/api/v1/blogs/**"
).permitAll()

// Public session browsing
.requestMatchers(HttpMethod.GET, "/api/v1/sessions/**").permitAll()
// User APIs (LOGIN REQUIRED)
.requestMatchers(
"/api/v1/user/**",
"/api/v1/installments/**",
"/api/v1/payments/**"
).hasRole("USER")

// Admin APIs
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")

.anyRequest().authenticated()
)


// THIS PREVENTS GOOGLE REDIRECTS
// .exceptionHandling(ex -> ex
// .authenticationEntryPoint((request, response, authException) -> {
Expand Down Expand Up @@ -123,4 +131,4 @@ public CorsConfigurationSource corsConfigurationSource() {
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
}
}
Loading