📝 Document the lottery
This commit is contained in:
403
README_LOTTERY.md
Normal file
403
README_LOTTERY.md
Normal file
@@ -0,0 +1,403 @@
|
||||
:bug# Lottery System API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The DysonNetwork Lottery System provides a daily lottery where users can purchase tickets with custom number selections. Each day features a new draw with random winning numbers. Users purchase tickets using ISP (Dyson Network Points), with results announced each morning.
|
||||
|
||||
The API is handled by the DysonNetwork.Pass service. Which means if you use it with the Gateway the `/api` should be replaced with `/pass`
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Daily Draws**: Automated draws at midnight UTC
|
||||
- **Custom Number Selection**: Users choose 5 unique numbers (0-99) + 1 special number (0-99)
|
||||
- **Flexible Pricing**: Base cost 10 ISP + extra ISP per multiplier (e.g., multiplier=2 costs 20 ISP)
|
||||
- **Daily Limits**: One ticket purchase per user per day
|
||||
- **Prize System**: Multiple prize tiers based on matches
|
||||
- **Instant Payment**: Tickets purchased using in-app points
|
||||
- **Historical Records**: Complete draw history and statistics
|
||||
|
||||
## Data Models
|
||||
|
||||
### LotteryDrawStatus Enum
|
||||
```csharp
|
||||
public enum LotteryDrawStatus
|
||||
{
|
||||
Pending = 0, // Ticket awaiting draw
|
||||
Drawn = 1 // Ticket has been processed in draw
|
||||
}
|
||||
```
|
||||
|
||||
### SnLottery Model
|
||||
```csharp
|
||||
public class SnLottery : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public SnAccount Account { get; set; } = null!;
|
||||
public Guid AccountId { get; set; }
|
||||
public List<int> RegionOneNumbers { get; set; } = new(); // 5 numbers (0-99)
|
||||
public int RegionTwoNumber { get; set; } // Special number (0-99)
|
||||
public int Multiplier { get; set; } = 1; // Prize multiplier (≥1)
|
||||
public LotteryDrawStatus DrawStatus { get; set; }
|
||||
public DateTime? DrawDate { get; set; } // Date when drawn
|
||||
}
|
||||
```
|
||||
|
||||
### SnLotteryRecord Model
|
||||
```csharp
|
||||
public class SnLotteryRecord : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime DrawDate { get; set; }
|
||||
public List<int> WinningRegionOneNumbers { get; set; } = new(); // 5 winning numbers
|
||||
public int WinningRegionTwoNumber { get; set; } // Winning special number
|
||||
public int TotalTickets { get; set; } // Total tickets processed
|
||||
public int TotalPrizesAwarded { get; set; } // Number of winning tickets
|
||||
public long TotalPrizeAmount { get; set; } // Total ISP prize amount
|
||||
}
|
||||
```
|
||||
|
||||
## Prize Structure
|
||||
|
||||
| Region 1 Matches | Base Prize (ISP) | Notes |
|
||||
|-----------------|------------------|-------|
|
||||
| 0 | 0 | No prize |
|
||||
| 1 | 10 | Minimum win |
|
||||
| 2 | 20 | Double minimum |
|
||||
| 3 | 50 | Five times minimum |
|
||||
| 4 | 100 | Ten times minimum |
|
||||
| 5 | 1000 | Maximum prize |
|
||||
|
||||
**Special Number Bonus**: If Region 2 number matches, multiply any prize by 10x.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
All endpoints require authentication via Bearer token.
|
||||
|
||||
### Purchase Ticket
|
||||
**POST** `/api/lotteries`
|
||||
|
||||
Creates a lottery order and deducts ISP from user's wallet.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"RegionOneNumbers": [5, 23, 47, 68, 89],
|
||||
"RegionTwoNumber": 42,
|
||||
"Multiplier": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "guid",
|
||||
"accountId": "guid",
|
||||
"createdAt": "2025-10-24T00:00:00Z",
|
||||
"status": "Paid",
|
||||
"currency": "isp",
|
||||
"amount": 10,
|
||||
"productIdentifier": "lottery"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation Rules:**
|
||||
- `RegionOneNumbers`: Exactly 5 unique integers between 0-99
|
||||
- `RegionTwoNumber`: Single integer between 0-99
|
||||
- `Multiplier`: Integer ≥ 1
|
||||
- User can only purchase 1 ticket per day
|
||||
|
||||
**Pricing:**
|
||||
- Base cost: 10 ISP
|
||||
- Additional cost: (Multiplier - 1) × 10 ISP
|
||||
- Total cost = (Multiplier × 10) ISP
|
||||
|
||||
### Get User Tickets
|
||||
**GET** `/api/lotteries`
|
||||
|
||||
Retrieves user's lottery tickets with pagination.
|
||||
|
||||
**Query Parameters:**
|
||||
- `offset` (optional, default 0): Page offset
|
||||
- `limit` (optional, default 20, max 100): Items per page
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "guid",
|
||||
"regionOneNumbers": [5, 23, 47, 68, 89],
|
||||
"regionTwoNumber": 42,
|
||||
"multiplier": 1,
|
||||
"drawStatus": "Pending",
|
||||
"drawDate": null,
|
||||
"createdAt": "2025-10-24T10:30:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Response Headers:**
|
||||
```
|
||||
X-Total: 42 // Total number of user's tickets
|
||||
```
|
||||
|
||||
### Get Specific Ticket
|
||||
**GET** `/api/lotteries/{id}`
|
||||
|
||||
Retrieves a specific lottery ticket by ID.
|
||||
|
||||
**Response:**
|
||||
Same structure as individual items from Get User Tickets.
|
||||
|
||||
**Error Responses:**
|
||||
- `404 Not Found`: Ticket doesn't exist or user doesn't own it
|
||||
|
||||
### Get Lottery Records
|
||||
**GET** `/api/lotteries/records`
|
||||
|
||||
Retrieves historical lottery draw results.
|
||||
|
||||
**Query Parameters:**
|
||||
- `startDate` (optional): Filter by draw date (YYYY-MM-DD)
|
||||
- `endDate` (optional): Filter by draw date (YYYY-MM-DD)
|
||||
- `offset` (optional, default 0): Page offset
|
||||
- `limit` (optional, default 20): Items per page
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "guid",
|
||||
"drawDate": "2025-10-24T00:00:00Z",
|
||||
"winningRegionOneNumbers": [7, 15, 23, 46, 82],
|
||||
"winningRegionTwoNumber": 19,
|
||||
"totalTickets": 245,
|
||||
"totalPrizesAwarded": 23,
|
||||
"totalPrizeAmount": 4820
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Frontend Integration (JavaScript/React)
|
||||
|
||||
```javascript
|
||||
// Purchase a lottery ticket
|
||||
async function purchaseLottery(numbers, specialNumber, multiplier = 1) {
|
||||
try {
|
||||
const response = await fetch('/api/lotteries', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${userToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
RegionOneNumbers: numbers, // Array of 5 unique numbers 0-99
|
||||
RegionTwoNumber: specialNumber, // Number 0-99
|
||||
Multiplier: multiplier // Optional, defaults to 1
|
||||
})
|
||||
});
|
||||
|
||||
const order = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
console.log('Ticket purchased successfully!', order);
|
||||
// Refresh user ISP balance
|
||||
updateWalletBalance();
|
||||
} else {
|
||||
console.error('Purchase failed:', order);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Network error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get user's tickets
|
||||
async function getUserTickets() {
|
||||
try {
|
||||
const response = await fetch('/api/lotteries?limit=20', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userToken}`
|
||||
}
|
||||
});
|
||||
|
||||
const tickets = await response.json();
|
||||
const totalTickets = response.headers.get('X-Total');
|
||||
|
||||
return { tickets, total: parseInt(totalTickets) };
|
||||
} catch (error) {
|
||||
console.error('Error fetching tickets:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get draw history
|
||||
async function getDrawHistory() {
|
||||
try {
|
||||
const response = await fetch('/api/lotteries/records', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userToken}`
|
||||
}
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching history:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mobile Integration (React Native/TypeScript)
|
||||
|
||||
```typescript
|
||||
interface LotteryTicket {
|
||||
id: string;
|
||||
regionOneNumbers: number[];
|
||||
regionTwoNumber: number;
|
||||
multiplier: number;
|
||||
drawStatus: 'Pending' | 'Drawn';
|
||||
drawDate?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface PurchaseRequest {
|
||||
RegionOneNumbers: number[];
|
||||
RegionTwoNumber: number;
|
||||
Multiplier: number;
|
||||
}
|
||||
|
||||
class LotteryService {
|
||||
private apiUrl = 'https://your-api-domain.com/api/lotteries';
|
||||
|
||||
async purchaseTicket(
|
||||
ticket: Omit<PurchaseRequest, 'RegionOneNumbers'> & { numbers: number[] },
|
||||
token: string
|
||||
): Promise<any> {
|
||||
const request: PurchaseRequest = {
|
||||
RegionOneNumbers: ticket.numbers,
|
||||
RegionTwoNumber: ticket.RegionTwoNumber,
|
||||
Multiplier: ticket.Multiplier
|
||||
};
|
||||
|
||||
const response = await fetch(this.apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async getTickets(token: string, offset = 0, limit = 20): Promise<LotteryTicket[]> {
|
||||
const response = await fetch(`${this.apiUrl}?offset=${offset}&limit=${limit}`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async getDrawRecords(token: string): Promise<any[]> {
|
||||
const response = await fetch(`${this.apiUrl}/records`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Number Validation
|
||||
|
||||
```javascript
|
||||
function validateLotteryNumbers(numbers, specialNumber, multiplier = 1) {
|
||||
// Validate region one numbers
|
||||
if (!Array.isArray(numbers) || numbers.length !== 5) {
|
||||
return { valid: false, error: 'Must select exactly 5 numbers' };
|
||||
}
|
||||
|
||||
const uniqueNumbers = new Set(numbers);
|
||||
if (uniqueNumbers.size !== 5) {
|
||||
return { valid: false, error: 'Numbers must be unique' };
|
||||
}
|
||||
|
||||
// Check range 0-99
|
||||
for (const num of numbers) {
|
||||
if (!Number.isInteger(num) || num < 0 || num > 99) {
|
||||
return { valid: false, error: 'Numbers must be integers between 0-99' };
|
||||
}
|
||||
}
|
||||
|
||||
// Validate special number
|
||||
if (!Number.isInteger(specialNumber) || specialNumber < 0 || specialNumber > 99) {
|
||||
return { valid: false, error: 'Special number must be between 0-99' };
|
||||
}
|
||||
|
||||
// Validate multiplier
|
||||
if (!Number.isInteger(multiplier) || multiplier < 1) {
|
||||
return { valid: false, error: 'Multiplier must be 1 or greater' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const validation = validateLotteryNumbers([5, 12, 23, 47, 89], 42, 2);
|
||||
if (!validation.valid) {
|
||||
console.error(validation.error);
|
||||
}
|
||||
```
|
||||
|
||||
## Daily Draw Schedule
|
||||
|
||||
- **Draw Time**: Every midnight UTC (00:00 UTC)
|
||||
- **Processing**: Only tickets from the previous day are included
|
||||
- **Prize Distribution**: Winners automatically receive ISP credits
|
||||
- **History**: Draws are preserved indefinitely
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Error Codes
|
||||
- `400 Bad Request`: Invalid request data (bad numbers, duplicate purchase, etc.)
|
||||
- `401 Unauthorized`: Missing or invalid authentication token
|
||||
- `404 Not Found`: Ticket doesn't exist or access denied
|
||||
- `403 Forbidden`: Insufficient permissions (admin endpoints)
|
||||
|
||||
### Error Response Format
|
||||
```json
|
||||
{
|
||||
"message": "You can only purchase one lottery per day.",
|
||||
"type": "ArgumentException",
|
||||
"statusCode": 400
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
### Test Cases
|
||||
1. **Valid Purchase**: Select valid numbers, verify wallet deduction
|
||||
2. **Invalid Numbers**: Try duplicate region one numbers, out-of-range values
|
||||
3. **Daily Limit**: Attempt second purchase in same day
|
||||
4. **Insufficient Funds**: Try purchase without enough ISP
|
||||
5. **Draw Processing**: Verify winning tickets receive correct prizes
|
||||
6. **Historical Data**: Check draw records match processed tickets
|
||||
|
||||
### Test Data Examples
|
||||
```javascript
|
||||
// Valid ticket
|
||||
{ numbers: [1, 15, 23, 67, 89], special: 42, multiplier: 1 }
|
||||
|
||||
// Invalid - duplicate numbers
|
||||
{ numbers: [1, 15, 23, 15, 89], special: 42, multiplier: 1 }
|
||||
|
||||
// Invalid - out of range
|
||||
{ numbers: [1, 15, 23, 67, 150], special: 42, multiplier: 1 }
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For API integration questions or support:
|
||||
- Check network documentation for authentication details
|
||||
- Contact Dyson Network development team for assistance
|
||||
- Monitor API response headers for pagination metadata
|
||||
Reference in New Issue
Block a user