# Course Purchase Module

Complete course purchase system with validation, order management, and payment tracking.

## Files Structure

```
src/modules/users/coursePurchase/
├── purchase.validation.js      # Request body validation schemas
├── purchase.service.js         # Business logic and database operations
├── purchase.controller.js       # HTTP request handlers
├── purchase.routes.js          # Route definitions
└── README.md                   # This file
```

## API Endpoints

### 1. Initiate Purchase
**Endpoint**: `POST /api/v1/coursePurchase/initiate`  
**Authentication**: Required (Mentee role)  
**Status Code**: 201 Created

**Request Body**:
```json
{
  "courseId": 5,
  "purchasedBy": "SELF",
  "notes": "Optional notes about purchase"
}
```

**Request Validation**:
- `courseId` (required): Positive integer
- `purchasedBy` (optional): "SELF" or "ADMIN" (default: "SELF")
- `notes` (optional): Max 500 characters

**Business Validations**:
✓ User profile is completed (email verified, firstName, lastName, disciplineCode)  
✓ User account is not suspended  
✓ User is not already enrolled in the course  
✓ User hasn't already purchased this course (pending or success)  
✓ Course exists and is purchasable (not free, not archived)  

**Response** (Success):
```json
{
  "statusCode": 201,
  "success": true,
  "message": "Purchase initiated successfully. Ready for payment.",
  "data": {
    "purchase": {
      "purchaseId": 1,
      "orderId": "ORD-20260608-ABC123",
      "invoiceNumber": "INV-20260608-ABC123",
      "courseId": 5,
      "courseName": "Web Development Fundamentals",
      "courseCode": "WEB-101",
      "amount": 5000.00,
      "taxAmount": 900.00,
      "discountAmount": 0.00,
      "totalAmount": 5900.00,
      "currency": "INR",
      "purchaseStatus": "PENDING",
      "purchasedBy": "SELF",
      "createdAt": "2026-06-08T10:30:00Z"
    }
  }
}
```

**Response** (Validation Errors):
```json
{
  "statusCode": 400,
  "success": false,
  "message": "Profile is incomplete. Missing fields: Email, Discipline Code. Please complete your profile first.",
  "data": null
}
```

---

### 2. Check Purchase Status
**Endpoint**: `GET /api/v1/coursePurchase/check-status/:courseId`  
**Authentication**: Required (Mentee role)  
**Status Code**: 200 OK

**URL Parameters**:
- `courseId` (required): Course ID to check

**Response**:
```json
{
  "statusCode": 200,
  "success": true,
  "message": "Purchase status retrieved",
  "data": {
    "courseId": 5,
    "alreadyPurchased": true,
    "purchaseId": 1,
    "purchaseStatus": "SUCCESS",
    "purchaseDate": "2026-06-08T10:30:00Z"
  }
}
```

---

### 3. Get User Purchases
**Endpoint**: `GET /api/v1/coursePurchase/my-purchases?page=1&limit=10`  
**Authentication**: Required (Mentee role)  
**Status Code**: 200 OK

**Query Parameters**:
- `page` (optional): Page number (default: 1)
- `limit` (optional): Records per page (default: 10, max: 50)

**Response**:
```json
{
  "statusCode": 200,
  "success": true,
  "message": "User purchases retrieved",
  "data": {
    "pagination": {
      "total": 5,
      "page": 1,
      "limit": 10,
      "totalPages": 1
    },
    "purchases": [
      {
        "purchaseId": 1,
        "orderId": "ORD-20260608-ABC123",
        "invoiceNumber": "INV-20260608-ABC123",
        "courseId": 5,
        "courseName": "Web Development Fundamentals",
        "courseCode": "WEB-101",
        "amount": 5000.00,
        "taxAmount": 900.00,
        "discountAmount": 0.00,
        "totalAmount": 5900.00,
        "currency": "INR",
        "purchaseStatus": "PENDING",
        "paymentGateway": null,
        "paymentDate": null,
        "enrollmentStatus": "NOT_ENROLLED",
        "purchasedBy": "SELF",
        "createdAt": "2026-06-08T10:30:00Z",
        "updatedAt": "2026-06-08T10:30:00Z"
      }
    ]
  }
}
```

---

### 4. Get Purchase Details
**Endpoint**: `GET /api/v1/coursePurchase/:purchaseId`  
**Authentication**: Required (Mentee role)  
**Status Code**: 200 OK

**URL Parameters**:
- `purchaseId` (required): Purchase ID to fetch

**Response**:
```json
{
  "statusCode": 200,
  "success": true,
  "message": "Purchase details retrieved",
  "data": {
    "purchaseId": 1,
    "orderId": "ORD-20260608-ABC123",
    "invoiceNumber": "INV-20260608-ABC123",
    "courseId": 5,
    "courseName": "Web Development Fundamentals",
    "courseCode": "WEB-101",
    "amount": 5000.00,
    "taxAmount": 900.00,
    "discountAmount": 0.00,
    "totalAmount": 5900.00,
    "currency": "INR",
    "purchaseStatus": "SUCCESS",
    "paymentGateway": "razorpay",
    "paymentDate": "2026-06-08T11:00:00Z",
    "refundAmount": 0.00,
    "refundReason": null,
    "refundDate": null,
    "purchasedBy": "SELF",
    "notes": null,
    "enrollment": {
      "enrollmentId": 1,
      "status": "ACTIVE",
      "enrollmentDate": "2026-06-08T11:00:00Z",
      "courseStartDate": "2026-06-08T00:00:00Z"
    },
    "createdAt": "2026-06-08T10:30:00Z",
    "updatedAt": "2026-06-08T11:00:00Z"
  }
}
```

---

## Data Storage

### CoursePurchase Model Fields
The purchase endpoint creates records in the `CoursePurchases` table with:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| purchaseId | INTEGER | Yes (PK) | Auto-incremented purchase ID |
| userId | BIGINT.UNSIGNED | Yes (FK) | User ID from users table |
| courseId | BIGINT.UNSIGNED | Yes (FK) | Course ID from courses table |
| streamId | STRING(40) | No (FK) | Stream ID from streams table |
| productVerticalId | STRING(40) | No (FK) | Product vertical from product_verticals |
| orderId | STRING(100) | Yes | Unique order ID (ORD-{timestamp}-{random}) |
| invoiceNumber | STRING(100) | Yes | Unique invoice number (INV-{date}-{random}) |
| paymentGateway | STRING(50) | No | Payment provider (razorpay, stripe, etc.) |
| gatewayOrderId | STRING(255) | No | Order ID from payment gateway |
| gatewayPaymentId | STRING(255) | No | Payment ID from payment gateway |
| gatewaySignature | STRING(500) | No | Payment signature for verification |
| amount | DECIMAL(10,2) | Yes | Base course price |
| taxAmount | DECIMAL(10,2) | No | Tax amount (default: 0) |
| discountAmount | DECIMAL(10,2) | No | Discount applied (default: 0) |
| totalAmount | DECIMAL(10,2) | Yes | Final amount = amount + tax - discount |
| currency | STRING(3) | No | Currency code (default: INR) |
| purchaseStatus | ENUM | Yes | PENDING, SUCCESS, FAILED, CANCELLED, REFUNDED, PARTIAL_REFUNDED |
| paymentDate | DATE | No | When payment was completed |
| refundAmount | DECIMAL(10,2) | No | Amount refunded |
| refundReason | STRING(500) | No | Reason for refund |
| refundDate | DATE | No | When refund was processed |
| purchasedBy | ENUM | Yes | SELF (user) or ADMIN (staff) |
| notes | TEXT | No | Additional notes about purchase |
| createdAt | DATE | No | Auto timestamp |
| updatedAt | DATE | No | Auto timestamp |

### Indexes Created
- userId
- courseId
- streamId
- productVerticalId
- orderId
- purchaseStatus
- paymentDate

---

## Service Methods

### CoursePurchaseService

#### `initiatePurchase(userId, purchaseData)`
Initiates a purchase with full validation chain.

**Parameters**:
- `userId` (number): User ID from JWT
- `purchaseData` (object):
  - `courseId` (number): Course ID
  - `purchasedBy` (string): "SELF" or "ADMIN"
  - `notes` (string): Optional notes

**Returns**: Purchase object with all details

**Throws**: ApiError if validation fails

---

#### `validatePurchaseEligibility(userId, courseId)`
Runs all eligibility checks before allowing purchase.

**Checks**:
1. Profile completed (email verified, firstName, lastName, disciplineCode)
2. User not suspended
3. Course exists and is purchasable
4. No existing pending/success purchase
5. No existing active enrollment

**Returns**: { isEligible: true, course, user }

**Throws**: ApiError if any check fails

---

#### `checkPurchaseStatus(userId, courseId)`
Quick check if user already purchased a course.

**Returns**: { alreadyPurchased, purchaseId, purchaseStatus, purchaseDate }

---

#### `getUserPurchases(userId, page, limit)`
Get paginated purchase history.

**Returns**: Paginated list with pagination metadata

---

#### `getPurchaseDetails(purchaseId, userId)`
Get detailed purchase with course info and enrollment status.

**Returns**: Complete purchase object

---

## Error Handling

### Validation Errors
- **400 Bad Request**: Profile incomplete, course not found, invalid data
- **409 Conflict**: Already purchased/enrolled, duplicate purchase

### Authorization Errors
- **401 Unauthorized**: Missing or invalid token
- **403 Forbidden**: User suspended, inactive account

### Server Errors
- **500 Internal Server Error**: Database errors, unexpected failures

---

## Integration Points

### Dependencies
- **AuthService**: `isProfileCompleted()`, `isUserValid()` - Profile and account validation
- **Database Models**: User, Course, CoursePurchase, UserCourseEnrollment
- **Utilities**: ApiError, ApiResponse, asyncHandler, validateRequest

### Future Integrations
- **Razorpay Service**: Payment processing and verification
- **EnrollmentService**: Auto-enrollment after payment success
- **NotificationService**: Send purchase confirmation emails
- **ReportingService**: Generate invoices and tax reports

---

## Current Status

✅ **Implemented**:
- Purchase initiation with full validation
- Eligibility checking
- Purchase history retrieval
- Order and invoice number generation
- Database record creation

🔄 **TODO**:
- Razorpay payment gateway integration
- Payment verification endpoint
- Auto-enrollment after payment success
- Refund processing
- Invoice generation and PDF export
- Email confirmations
- Admin purchase dashboard

---

## Testing Checklist

- [ ] Initiate purchase with valid course
- [ ] Check all validation errors trigger correctly
- [ ] Verify profile incomplete error
- [ ] Verify suspended user error
- [ ] Verify already purchased error
- [ ] Check purchase status endpoint
- [ ] Get my purchases with pagination
- [ ] Get purchase details
- [ ] Verify order ID uniqueness
- [ ] Verify invoice number uniqueness
- [ ] Test with different user roles
- [ ] Test missing JWT token
