🎮 Games API Documentation

Complete guide to the Games API endpoints and functionality

Games API Documentation

Overview

The Games API provides a comprehensive set of endpoints for managing users, games, game categories, voting functionality, friend connections, and real-time chat system. This RESTful API supports multilingual content through translation support and includes authentication via Laravel Sanctum.

Base URL: https://your-domain.com/api
API Version: v1
Authentication: Bearer Token (Laravel Sanctum)

Authentication Flow

The API uses a simplified phone-based OTP (One-Time Password) authentication system:

  1. OTP Request: Users request an OTP by providing their phone number. If the phone number is not registered, a new user account is created automatically.
  2. OTP Verification: Users verify the OTP to receive an authentication token. New users may need to complete their profile after authentication.
  3. Profile Completion: New users should update their profile with first name, last name, and other optional information.
  4. API Access: Use the Bearer token for authenticated endpoints

Note: The OTP is currently hardcoded to "123456" for testing purposes. Traditional username/password authentication has been completely replaced with the more secure and streamlined OTP-based flow.


Table of Contents

  1. Authentication
  2. Games
  3. Game Categories
  4. Game Comments
  5. Game Voting
  6. User Management
  7. Friend Requests
  8. Stories
  9. Real-Time Chat System
  10. Messages
  11. Leaderboard
  12. Payment System
  13. Subscription System
  14. Error Handling
  15. Headers & Language Support

Authentication

Send OTP

Send an OTP (One-Time Password) to a phone number for authentication. If the phone number is not registered, a new user account will be created automatically.

Endpoint: POST /send-otp
Authentication: None required

Request Body

{
    "phone": "string (required)"
}

Response (200 OK)

{
    "success": true,
    "message": "OTP sent successfully to your phone number",
    "phone": "+1234567890",
    "is_new_user": true
}

Response Fields

  • success: Boolean indicating if the request was successful
  • message: Descriptive message about the operation
  • phone: The phone number the OTP was sent to
  • is_new_user: Boolean indicating if this is a new user who needs to complete their profile

Error Responses

401 Unauthorized (inactive account):

{
    "success": false,
    "message": "Account is inactive"
}

Verify OTP

Verify the OTP code and authenticate the user.

Endpoint: POST /verify-otp
Authentication: None required

Request Body

{
    "phone": "string (required)",
    "otp": "string (required, 6 digits)"
}

Response (200 OK)

{
    "success": true,
    "message": "Login successful",
    "user": {
        "id": 1,
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+1234567890",
        "email": "john@example.com",
        "birth_date": "1990-01-01",
        "photo": "http://domain.com/storage/users/photos/photo.jpg",
        "is_active": true,
        "phone_verified": true,
        "last_login": "2025-07-19T12:00:00.000000Z",
        "created_at": "2025-07-19T00:00:00.000000Z"
    },
    "token": "1|token_string_here",
    "token_type": "Bearer",
    "profile_complete": true
}

Response Fields

  • success: Boolean indicating if the request was successful
  • message: Descriptive message about the operation
  • user: User object with complete profile information
  • token: Authentication token for subsequent API calls
  • token_type: Type of token (always "Bearer")
  • profile_complete: Boolean indicating if the user has completed their profile (has first_name and last_name)

Error Responses

404 Not Found (phone number not found):

{
    "success": false,
    "message": "Phone number not found"
}

401 Unauthorized (inactive account):

{
    "success": false,
    "message": "Account is inactive"
}

401 Unauthorized (invalid OTP):

{
    "success": false,
    "message": "Invalid OTP"
}

401 Unauthorized (expired OTP):

{
    "success": false,
    "message": "OTP has expired"
}

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "phone": ["The phone field is required."],
        "otp": ["The otp field is required.", "The otp must be 6 characters."]
    }
}

Login User (Deprecated)

Note: The traditional username/password login is deprecated. Please use the OTP-based authentication flow instead.

Authenticate a user and return an access token.

Endpoint: POST /login
Authentication: None required

Request Body

{
    "username": "string (required)",
    "password": "string (required)"
}

Response (200 OK)

{
    "success": true,
    "message": "Login successful",
    "user": {
        "id": 1,
        "username": "john_doe",
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+1234567890",
        "email": "john@example.com",
        "birth_date": "1990-01-01",
        "photo": "http://domain.com/storage/users/photos/photo.jpg",
        "is_active": true,
        "last_login": "2025-07-19T12:00:00.000000Z",
        "created_at": "2025-07-19T00:00:00.000000Z"
    },
    "token": "1|token_string_here",
    "token_type": "Bearer"
}

Logout User

Revoke the current access token.

Endpoint: POST /logout
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "message": "Logged out successfully"
}

Games

Get Home Content

Retrieve featured content for the home page, including featured games and categories.

Endpoint: GET /home
Authentication: None required

Headers

  • language (string, optional): Language code for translations (default: 'en')

Response (200 OK)

{
    "featured_games": [
        {
            "id": 1,
            "slug": "puzzle-game",
            "name": "Amazing Puzzle Game",
            "description": "An exciting puzzle game with challenging levels",
            "category": {
                "id": 1,
                "name": "Puzzle"
            },
            "source": "GameStudio",
            "thumb_1": "http://domain.com/thumbs/game1_large.jpg",
            "thumb_2": "http://domain.com/thumbs/game1_medium.jpg",
            "thumb_small": "http://domain.com/thumbs/game1_small.jpg",
            "require_subscription": false,
            "upvotes_count": 150,
            "downvotes_count": 12,
            "net_votes": 138,
            "user_vote": null
        }
    ],
    "featured_categories": [
        {
            "id": 1,
            "slug": "puzzle",
            "name": "Puzzle",
            "description": "Brain-challenging puzzle games",
            "image": "http://domain.com/images/puzzle-category.jpg",
            "games_count": 25
        }
    ]
}

Get All Games

Retrieve a paginated list of all games with voting information.

Endpoint: GET /games
Authentication: None required

Query Parameters

  • per_page (integer, optional): Number of games per page (default: 15)
  • category_id (integer, optional): Filter by category ID
  • page (integer, optional): Page number

Headers

  • language (string, optional): Language code for translations (default: 'en')

Response (200 OK)

{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "slug": "puzzle-game",
            "name": "Amazing Puzzle Game",
            "description": "An exciting puzzle game with challenging levels",
            "category": {
                "id": 1,
                "name": "Puzzle"
            },
            "source": "GameStudio",
            "thumb_1": "http://domain.com/thumbs/game1_large.jpg",
            "thumb_2": "http://domain.com/thumbs/game1_medium.jpg",
            "thumb_small": "http://domain.com/thumbs/game1_small.jpg",
            "require_subscription": false,
            "upvotes_count": 150,
            "downvotes_count": 12,
            "net_votes": 138,
            "user_vote": null
        }
    ],
    "first_page_url": "http://domain.com/api/games?page=1",
    "from": 1,
    "last_page": 5,
    "last_page_url": "http://domain.com/api/games?page=5",
    "next_page_url": "http://domain.com/api/games?page=2",
    "path": "http://domain.com/api/games",
    "per_page": 15,
    "prev_page_url": null,
    "to": 15,
    "total": 75
}

Get Free Games

Retrieve a paginated list of games that don't require subscription.

Endpoint: GET /games/free
Authentication: None required

Query Parameters

Same as /games endpoint.

Response

Same structure as /games endpoint, but only includes games where require_subscription is false.

Get Single Game

Retrieve detailed information about a specific game.

Endpoint: GET /games/{id}
Authentication: Required for subscription games

Headers

  • language (string, optional): Language code for translations (default: 'en')

Response (200 OK)

{
    "id": 1,
    "slug": "puzzle-game",
    "name": "Amazing Puzzle Game",
    "description": "An exciting puzzle game with challenging levels",
    "instructions": "Use arrow keys to move, space to jump",
    "category": {
        "id": 1,
        "name": "Puzzle",
        "description": "Brain-challenging puzzle games"
    },
    "source": "GameStudio",
    "thumb_1": "http://domain.com/thumbs/game1_large.jpg",
    "thumb_2": "http://domain.com/thumbs/game1_medium.jpg",
    "thumb_small": "http://domain.com/thumbs/game1_small.jpg",
    "url": "http://domain.com/games/puzzle-game",
    "width": 800,
    "height": 600,
    "require_subscription": false,
    "upvotes_count": 150,
    "downvotes_count": 12,
    "net_votes": 138,
    "user_vote": "upvote"
}

Error Responses

401 Unauthorized (for subscription games without authentication):

{
    "error": "Authentication required",
    "message": "This game requires an active subscription. Please log in to access it."
}

403 Forbidden (for inactive users accessing subscription games):

{
    "error": "Subscription required",
    "message": "This game requires an active subscription. Please activate your subscription to access it."
}

Game Categories

Get All Categories

Retrieve a list of all game categories with game counts.

Endpoint: GET /categories
Authentication: None required

Headers

  • language (string, optional): Language code for translations (default: 'en')

Response (200 OK)

[
    {
        "id": 1,
        "slug": "puzzle",
        "name": "Puzzle",
        "description": "Brain-challenging puzzle games",
        "image": "http://domain.com/images/puzzle-category.jpg",
        "games_count": 25
    },
    {
        "id": 2,
        "slug": "action",
        "name": "Action",
        "description": "Fast-paced action games",
        "image": "http://domain.com/images/action-category.jpg",
        "games_count": 30
    }
]

Get Category with Games

Retrieve a specific category with its associated games.

Endpoint: GET /categories/{category}
Authentication: None required

Headers

  • language (string, optional): Language code for translations (default: 'en')

Response (200 OK)

{
    "id": 1,
    "name": "Puzzle",
    "description": "Brain-challenging puzzle games",
    "image": "http://domain.com/images/puzzle-category.jpg",
    "games": [
        {
            "id": 1,
            "slug": "puzzle-game",
            "name": "Amazing Puzzle Game",
            "description": "An exciting puzzle game",
            "thumb_small": "http://domain.com/thumbs/game1_small.jpg",
            "require_subscription": false
        }
    ]
}

Game Comments

Get Game Comments

Retrieve comments for a specific game with pagination.

Endpoint: GET /games/{game}/comments
Authentication: None required

Query Parameters

  • page (integer, optional): Page number for pagination
  • per_page (integer, optional): Number of comments per page (default: 15)

Response (200 OK)

{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "content": "This is an amazing game! Love the graphics.",
            "user": {
                "id": 1,
                "first_name": "John",
                "last_name": "Doe",
                "photo": "http://domain.com/storage/users/photos/photo.jpg"
            },
            "created_at": "2025-07-19T12:00:00.000000Z",
            "updated_at": "2025-07-19T12:00:00.000000Z"
        }
    ],
    "first_page_url": "http://domain.com/api/games/1/comments?page=1",
    "from": 1,
    "last_page": 3,
    "last_page_url": "http://domain.com/api/games/1/comments?page=3",
    "next_page_url": "http://domain.com/api/games/1/comments?page=2",
    "path": "http://domain.com/api/games/1/comments",
    "per_page": 15,
    "prev_page_url": null,
    "to": 15,
    "total": 45
}

Create Game Comment

Add a new comment to a game.

Endpoint: POST /games/{game}/comments
Authentication: Required (Bearer Token)

Request Body

{
    "content": "string (required, max:1000 characters)"
}

Response (201 Created)

{
    "success": true,
    "message": "Comment added successfully",
    "data": {
        "id": 1,
        "content": "This is an amazing game! Love the graphics.",
        "user": {
            "id": 1,
            "first_name": "John",
            "last_name": "Doe",
            "photo": "http://domain.com/storage/users/photos/photo.jpg"
        },
        "created_at": "2025-07-19T12:00:00.000000Z",
        "updated_at": "2025-07-19T12:00:00.000000Z"
    }
}

Error Responses

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "content": [
            "The content field is required.",
            "The content may not be greater than 1000 characters."
        ]
    }
}

Update Game Comment

Update an existing comment (only by the comment owner).

Endpoint: PUT /games/{game}/comments/{comment}
Authentication: Required (Bearer Token)

Request Body

{
    "content": "string (required, max:1000 characters)"
}

Response (200 OK)

{
    "success": true,
    "message": "Comment updated successfully",
    "data": {
        "id": 1,
        "content": "Updated comment content",
        "user": {
            "id": 1,
            "first_name": "John",
            "last_name": "Doe",
            "photo": "http://domain.com/storage/users/photos/photo.jpg"
        },
        "created_at": "2025-07-19T12:00:00.000000Z",
        "updated_at": "2025-07-19T12:30:00.000000Z"
    }
}

Error Responses

403 Forbidden (not comment owner):

{
    "success": false,
    "message": "You can only edit your own comments"
}

404 Not Found (comment not found):

{
    "success": false,
    "message": "Comment not found"
}

Delete Game Comment

Delete an existing comment (only by the comment owner).

Endpoint: DELETE /games/{game}/comments/{comment}
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "message": "Comment deleted successfully"
}

Error Responses

403 Forbidden (not comment owner):

{
    "success": false,
    "message": "You can only delete your own comments"
}

404 Not Found (comment not found):

{
    "success": false,
    "message": "Comment not found"
}

Game Voting

Vote on Game

Cast or update a vote (upvote/downvote) on a game.

Endpoint: POST /games/{id}/vote
Authentication: Required (Bearer Token)

Request Body

{
    "vote_type": "upvote" // or "downvote"
}

Response (200 OK)

{
    "message": "Vote added successfully", // or "Vote updated successfully" or "Vote removed successfully"
    "upvotes_count": 151,
    "downvotes_count": 12,
    "user_vote": "upvote"
}

Error Responses

404 Not Found (game not found):

{
    "message": "Game not found"
}

422 Unprocessable Entity (validation error):

{
    "message": "The selected vote type is invalid.",
    "errors": {
        "vote_type": [
            "The vote type field is required.",
            "The selected vote type is invalid."
        ]
    }
}

Notes

  • If user votes with the same vote type twice, the vote is removed (toggle off)
  • If user changes vote type, the existing vote is updated
  • Vote counts are returned in real-time

Get User Votes

Retrieve all games voted by the authenticated user.

Endpoint: GET /user/votes
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "upvoted_games": [
        {
            "id": 1,
            "name": "Amazing Puzzle Game",
            "slug": "puzzle-game",
            "thumb_small": "http://domain.com/thumbs/game1_small.jpg"
        }
    ],
    "downvoted_games": [
        {
            "id": 2,
            "name": "Boring Game",
            "slug": "boring-game",
            "thumb_small": "http://domain.com/thumbs/game2_small.jpg"
        }
    ]
}

User Management

Get Current User

Retrieve the authenticated user's profile information.

Endpoint: GET /user
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "user": {
        "id": 1,
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+1234567890",
        "email": "john@example.com",
        "birth_date": "1990-01-01",
        "photo": "http://domain.com/storage/users/photos/photo.jpg",
        "is_active": true,
        "phone_verified": true,
        "last_login": "2025-07-19T12:00:00.000000Z",
        "created_at": "2025-07-19T00:00:00.000000Z"
    }
}

Update User Profile

Update the authenticated user's profile information.

Endpoint: PUT /profile
Authentication: Required (Bearer Token)

Request Body

{
    "first_name": "string (optional, required if provided, max:255)",
    "last_name": "string (optional, required if provided, max:255)",
    "phone": "string (optional, required if provided, max:20, unique)",
    "email": "string (optional, nullable, email, max:255, unique)",
    "birth_date": "date (optional, nullable, before today)",
    "photo": "file (optional, nullable, image: jpeg,jpg,png, max:2MB)"
}

Response (200 OK)

{
    "success": true,
    "message": "Profile updated successfully",
    "user": {
        "id": 1,
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+1234567890",
        "email": "john@example.com",
        "birth_date": "1990-01-01",
        "photo": "http://domain.com/storage/users/photos/photo.jpg",
        "is_active": true,
        "phone_verified": true,
        "last_login": "2025-07-19T12:00:00.000000Z",
        "created_at": "2025-07-19T00:00:00.000000Z"
    }
}

Error Responses

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "email": ["The email has already been taken."],
        "photo": ["The photo may not be greater than 2048 kilobytes."],
        "birth_date": ["The birth date must be a date before today."]
    }
}

Update User Photo

Update only the authenticated user's profile photo.

Endpoint: POST /profile/photo
Authentication: Required (Bearer Token)

Request Body (multipart/form-data)

photo: file (required, image: jpeg,jpg,png, max:2MB)

Response (200 OK)

{
    "success": true,
    "message": "Photo updated successfully",
    "photo": "http://domain.com/storage/users/photos/new_photo.jpg",
    "user": {
        "id": 1,
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+1234567890",
        "email": "john@example.com",
        "birth_date": "1990-01-01",
        "photo": "http://domain.com/storage/users/photos/new_photo.jpg",
        "is_active": true,
        "phone_verified": true,
        "last_login": "2025-07-19T12:00:00.000000Z",
        "created_at": "2025-07-19T00:00:00.000000Z"
    }
}

Error Responses

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "photo": [
            "The photo field is required.",
            "The photo may not be greater than 2048 kilobytes."
        ]
    }
}

Delete User Photo

Remove the authenticated user's profile photo.

Endpoint: DELETE /profile/photo
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "message": "Photo deleted successfully",
    "user": {
        "id": 1,
        "first_name": "John",
        "last_name": "Doe",
        "phone": "+1234567890",
        "email": "john@example.com",
        "birth_date": "1990-01-01",
        "photo": null,
        "is_active": true,
        "phone_verified": true,
        "last_login": "2025-07-19T12:00:00.000000Z",
        "created_at": "2025-07-19T00:00:00.000000Z"
    }
}

Error Response (404 Not Found)

{
    "success": false,
    "message": "No photo to delete"
}

Friend Requests

The friend request system allows users to connect with each other by sending, accepting, or rejecting friend requests.

Friend Request Statuses

  • pending: Friend request has been sent but not yet responded to
  • accepted: Friend request has been accepted, users are now friends
  • rejected: Friend request has been rejected

Get Pending Friend Requests

Retrieve pending friend requests received by the authenticated user.

Endpoint: GET /friend-requests
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "data": [
        {
            "id": 2,
            "sender_id": 3,
            "receiver_id": 1,
            "status": "pending",
            "created_at": "2025-07-19T11:00:00.000000Z",
            "updated_at": "2025-07-19T11:00:00.000000Z",
            "sender": {
                "id": 3,
                "first_name": "Bob",
                "last_name": "Smith",
                "phone": "+1234567892",
                "photo": null
            }
        }
    ]
}

Send Friend Request

Send a friend request to another user.

Endpoint: POST /friend-requests
Authentication: Required (Bearer Token)

Request Body

{
    "receiver_id": "integer (required, exists in users table)"
}

Response (201 Created)

{
    "success": true,
    "message": "Friend request sent successfully.",
    "data": {
        "id": 1,
        "sender_id": 1,
        "receiver_id": 2,
        "status": "pending",
        "created_at": "2025-07-19T10:00:00.000000Z",
        "updated_at": "2025-07-19T10:00:00.000000Z",
        "receiver": {
            "id": 2,
            "first_name": "Jane",
            "last_name": "Doe",
            "phone": "+1234567891",
            "photo": "http://domain.com/storage/users/photos/jane.jpg"
        }
    }
}

Error Responses

400 Bad Request (trying to send request to yourself):

{
    "success": false,
    "message": "You cannot send a friend request to yourself."
}

400 Bad Request (already friends):

{
    "success": false,
    "message": "You are already friends with this user."
}

400 Bad Request (pending request exists):

{
    "success": false,
    "message": "A friend request is already pending between you and this user."
}

500 Internal Server Error (failed to send request):

{
    "success": false,
    "message": "Failed to send friend request."
}

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "receiver_id": [
            "The receiver id field is required.",
            "The selected receiver id is invalid."
        ]
    }
}

Respond to Friend Request

Accept or reject a received friend request.

Endpoint: PUT /friend-requests/{friendRequest}
Authentication: Required (Bearer Token)

Request Body

{
    "action": "accept" // or "reject"
}

Response (200 OK)

{
    "success": true,
    "message": "Friend request accepted.", // or "Friend request rejected."
    "data": {
        "id": 2,
        "sender_id": 3,
        "receiver_id": 1,
        "status": "accepted", // or "rejected"
        "created_at": "2025-07-19T11:00:00.000000Z",
        "updated_at": "2025-07-19T12:00:00.000000Z",
        "sender": {
            "id": 3,
            "first_name": "Bob",
            "last_name": "Smith",
            "phone": "+1234567892",
            "photo": null
        }
    }
}

Error Responses

403 Forbidden (not the receiver):

{
    "success": false,
    "message": "You can only respond to friend requests sent to you."
}

400 Bad Request (already responded):

{
    "success": false,
    "message": "This friend request has already been responded to."
}

404 Not Found (friend request not found):

{
    "success": false,
    "message": "Friend request not found."
}

500 Internal Server Error (failed to process request):

{
    "success": false,
    "message": "Failed to accept friend request." // or "Failed to reject friend request."
}

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "action": [
            "The action field is required.",
            "The selected action is invalid."
        ]
    }
}

Get Friends List

Retrieve the list of all friends for the authenticated user.

Endpoint: GET /friends
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "data": [
        {
            "id": 2,
            "first_name": "Jane",
            "last_name": "Doe",
            "phone": "+1234567891",
            "email": "jane@example.com",
            "birth_date": "1992-05-15",
            "photo": "http://domain.com/storage/users/photos/jane.jpg",
            "is_active": true,
            "phone_verified": true,
            "last_login": "2025-07-19T11:30:00.000000Z",
            "created_at": "2025-07-18T08:00:00.000000Z"
        }
    ]
}

Delete Friend Request

Delete a friend request (either cancel a sent request or remove a friendship).

Endpoint: DELETE /friend-requests/{friendRequest}
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "message": "Friend request deleted successfully."
}

Error Responses

404 Not Found (friend request not found):

{
    "success": false,
    "message": "Friend request not found."
}

403 Forbidden (not authorized):

{
    "success": false,
    "message": "You can only delete your own friend requests."
}

Stories

The Stories feature allows users to share temporary content (text and/or images) with their friends that automatically expires after 24 hours. Stories are only visible to users who are friends with the story creator.

Story Lifecycle

  • Creation: Stories are created with content and/or an image
  • Expiration: Stories automatically expire 24 hours after creation
  • Visibility: Only friends can view active stories
  • Privacy: Expired stories are only visible to the story owner

Get Friends' Stories

Retrieve active stories from the authenticated user's friends.

Endpoint: GET /stories
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "data": [
        {
            "id": 1,
            "content": "Having a great day at the beach!",
            "image_url": "http://domain.com/storage/stories/beach_photo.jpg",
            "created_at": "2025-07-20T10:00:00.000000Z",
            "expires_at": "2025-07-21T10:00:00.000000Z",
            "user": {
                "id": 2,
                "first_name": "Jane",
                "last_name": "Doe",
                "phone": "+1234567891",
                "photo": "http://domain.com/storage/users/photos/jane.jpg"
            }
        }
    ]
}

Get My Stories

Retrieve all stories created by the authenticated user (including expired ones).

Endpoint: GET /stories/my
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "data": [
        {
            "id": 3,
            "content": "My first story!",
            "image_url": null,
            "created_at": "2025-07-19T15:00:00.000000Z",
            "expires_at": "2025-07-20T15:00:00.000000Z",
            "is_active": false,
            "is_expired": true
        },
        {
            "id": 4,
            "content": "Current story",
            "image_url": "http://domain.com/storage/stories/current.jpg",
            "created_at": "2025-07-20T12:00:00.000000Z",
            "expires_at": "2025-07-21T12:00:00.000000Z",
            "is_active": true,
            "is_expired": false
        }
    ]
}

Create Story

Create a new story with content and/or an image.

Endpoint: POST /stories
Authentication: Required (Bearer Token)

Request Body (multipart/form-data)

content: string (optional, max:1000 characters)
image: file (optional, image: jpeg,png,jpg,gif, max:10MB)

Validation Rules

  • Either content or image must be provided (at least one is required)
  • If both are provided, they will both be saved
  • Stories automatically expire 24 hours after creation

Response (201 Created)

{
    "success": true,
    "message": "Story created successfully.",
    "data": {
        "id": 5,
        "content": "Just created my new story!",
        "image_url": "http://domain.com/storage/stories/new_story.jpg",
        "created_at": "2025-07-20T14:00:00.000000Z",
        "expires_at": "2025-07-21T14:00:00.000000Z",
        "is_active": true
    }
}

Error Responses

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "content": ["Either content or image must be provided."],
        "image": ["The image may not be greater than 10MB."]
    }
}

Get Single Story

Retrieve a specific story by ID.

Endpoint: GET /stories/{id}
Authentication: Required (Bearer Token)

Access Rules

  • Users can always view their own stories (active or expired)
  • Users can only view friends' active stories
  • Expired stories are only visible to the story owner

Response (200 OK)

{
    "success": true,
    "data": {
        "id": 1,
        "content": "Having a great day at the beach!",
        "image_url": "http://domain.com/storage/stories/beach_photo.jpg",
        "created_at": "2025-07-20T10:00:00.000000Z",
        "expires_at": "2025-07-21T10:00:00.000000Z",
        "is_active": true,
        "is_expired": false,
        "user": {
            "id": 2,
            "first_name": "Jane",
            "last_name": "Doe",
            "phone": "+1234567891",
            "photo": "http://domain.com/storage/users/photos/jane.jpg"
        }
    }
}

Error Responses

403 Forbidden (insufficient permissions):

{
    "success": false,
    "message": "You do not have permission to view this story."
}

404 Not Found (story doesn't exist):

{
    "success": false,
    "message": "Story not found"
}

Update Story

Update the content of an existing story. Note: Images cannot be updated, only text content.

Endpoint: PUT /stories/{id}
Authentication: Required (Bearer Token)

Request Body

{
    "content": "string (required, max:1000 characters)"
}

Access Rules

  • Only the story owner can update their own stories
  • Both active and expired stories can be updated

Response (200 OK)

{
    "success": true,
    "message": "Story updated successfully.",
    "data": {
        "id": 5,
        "content": "Updated story content!",
        "image_url": "http://domain.com/storage/stories/original_image.jpg",
        "created_at": "2025-07-20T14:00:00.000000Z",
        "expires_at": "2025-07-21T14:00:00.000000Z",
        "is_active": true
    }
}

Error Responses

403 Forbidden (not the story owner):

{
    "success": false,
    "message": "You can only update your own stories."
}

Delete Story

Delete a story and its associated image (if any).

Endpoint: DELETE /stories/{id}
Authentication: Required (Bearer Token)

Access Rules

  • Only the story owner can delete their own stories
  • Both active and expired stories can be deleted
  • Associated images are automatically removed from storage

Response (200 OK)

{
    "success": true,
    "message": "Story deleted successfully."
}

Error Responses

403 Forbidden (not the story owner):

{
    "success": false,
    "message": "You can only delete your own stories."
}

404 Not Found (story doesn't exist):

{
    "success": false,
    "message": "Story not found"
}

Real-Time Chat System

The Real-Time Chat System provides comprehensive messaging functionality using Pusher for WebSocket broadcasting. Users can engage in private conversations with their friends through secure, real-time messaging.

Features

  • Private Messaging: Users can chat with their friends only
  • Real-time Broadcasting: Messages are broadcasted instantly using Pusher
  • Message History: Paginated message history
  • Read Receipts: Track which messages have been read
  • Message Editing/Deletion: Users can edit their own messages or delete them
  • Unread Message Count: Track unread messages per chat

Database Schema

The chat system uses three main tables:

  • chats: Stores chat metadata (type, name, creator, etc.)
  • chat_participants: Pivot table for users in chats (with read receipts)
  • messages: Stores all messages with content and metadata

Broadcasting Authentication

Authenticate Pusher Channel

Authenticate private Pusher channels for real-time chat functionality.

Endpoint: POST /api/broadcasting/auth
Authentication: Required (Bearer Token)

Request Body
{
    "socket_id": "string (required)",
    "channel_name": "string (required)"
}
Response (200 OK)
{
    "auth": "generated_auth_signature"
}
Error Responses

401 Unauthorized (invalid or missing token):

{
    "message": "Unauthenticated."
}

403 Forbidden (not authorized for channel):

{
    "message": "This action is unauthorized."
}
Usage Example

This endpoint is typically called automatically by Laravel Echo when subscribing to private channels:

// This is handled automatically by Laravel Echo
echo.private(`chat.${chatId}`).listen(".message.sent", (e) => {
    // Handle incoming message
});

Real-time Events

Message Events

message.sent

Broadcasted when a new message is sent.

Channel: private-chat.{chatId}

Payload:

{
    "id": 1,
    "chat_id": 1,
    "user_id": 2,
    "content": "Hello!",
    "type": "text",
    "is_edited": false,
    "created_at": "2025-07-21T06:42:19.000000Z",
    "user": {
        "id": 2,
        "first_name": "John",
        "last_name": "Doe",
        "photo": "photo.jpg"
    }
}
message.deleted

Broadcasted when a message is deleted.

Channel: private-chat.{chatId}

Payload:

{
    "message_id": 1,
    "chat_id": 1
}

Chat Endpoints

Get All Chats

Get all chats for the authenticated user.

Endpoint: GET /api/chats
Authentication: Required (Bearer Token)

Response (200 OK)
{
    "success": true,
    "data": [
        {
            "id": 1,
            "type": "private",
            "name": "John Doe",
            "photo": "user_photo.jpg",
            "last_message_at": "2025-07-21T06:42:19.000000Z",
            "unread_count": 3,
            "latest_message": {
                "id": 5,
                "content": "Hello there!",
                "created_at": "2025-07-21T06:42:19.000000Z",
                "user": {
                    "id": 2,
                    "first_name": "John",
                    "last_name": "Doe"
                }
            },
            "participants": [
                {
                    "id": 1,
                    "first_name": "Current",
                    "last_name": "User",
                    "photo": "current_user_photo.jpg"
                },
                {
                    "id": 2,
                    "first_name": "John",
                    "last_name": "Doe",
                    "photo": "john_photo.jpg"
                }
            ]
        }
    ]
}

Create Private Chat

Create a new private chat with a friend.

Endpoint: POST /api/chats/private
Authentication: Required (Bearer Token)

Request Body
{
    "friend_id": 2
}
Response (201 Created)
{
    "success": true,
    "message": "Private chat created successfully",
    "data": {
        "id": 1,
        "type": "private",
        "name": "John Doe",
        "photo": "john_photo.jpg",
        "last_message_at": null,
        "unread_count": 0,
        "latest_message": null,
        "participants": [
            {
                "id": 1,
                "first_name": "Current",
                "last_name": "User",
                "photo": "current_user_photo.jpg"
            },
            {
                "id": 2,
                "first_name": "John",
                "last_name": "Doe",
                "photo": "john_photo.jpg"
            }
        ]
    }
}
Error Responses

400 Bad Request (not friends):

{
    "success": false,
    "message": "You can only create private chats with your friends"
}

409 Conflict (chat already exists):

{
    "success": true,
    "message": "Private chat already exists",
    "data": {
        "id": 1,
        "type": "private",
        "name": "John Doe"
    }
}

Get Specific Chat

Get a specific chat with its details.

Endpoint: GET /api/chats/{chat}
Authentication: Required (Bearer Token)

Response (200 OK)
{
    "success": true,
    "data": {
        "id": 1,
        "type": "private",
        "name": "John Doe",
        "photo": "john_photo.jpg",
        "last_message_at": "2025-07-21T06:42:19.000000Z",
        "unread_count": 3,
        "participants": [
            {
                "id": 1,
                "first_name": "Current",
                "last_name": "User",
                "photo": "current_user_photo.jpg"
            },
            {
                "id": 2,
                "first_name": "John",
                "last_name": "Doe",
                "photo": "john_photo.jpg"
            }
        ]
    }
}
Error Responses

403 Forbidden (not a participant):

{
    "success": false,
    "message": "You are not a participant in this chat"
}

Mark Chat as Read

Mark all messages in a chat as read.

Endpoint: POST /api/chats/{chat}/mark-read
Authentication: Required (Bearer Token)

Response (200 OK)
{
    "success": true,
    "message": "Chat marked as read"
}

Leave Chat

Leave a chat.

Endpoint: POST /api/chats/{chat}/leave
Authentication: Required (Bearer Token)

Response (200 OK)
{
    "success": true,
    "message": "Left chat successfully"
}

Delete Chat

Delete a chat (only for admins/creators).

Endpoint: DELETE /api/chats/{chat}
Authentication: Required (Bearer Token)

Response (200 OK)
{
    "success": true,
    "message": "Chat deleted successfully"
}
Error Responses

403 Forbidden (insufficient permissions):

{
    "success": false,
    "message": "You don't have permission to delete this chat"
}

Message Endpoints

Get Chat Messages

Get messages for a chat (paginated).

Endpoint: GET /api/chats/{chat}/messages
Authentication: Required (Bearer Token)

Query Parameters
  • page: Page number (default: 1)
  • per_page: Messages per page (default: 50)
Response (200 OK)
{
    "success": true,
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 1,
                "chat_id": 1,
                "user_id": 2,
                "content": "Hello, how are you?",
                "type": "text",
                "is_edited": false,
                "created_at": "2025-07-21T06:42:19.000000Z",
                "updated_at": "2025-07-21T06:42:19.000000Z",
                "user": {
                    "id": 2,
                    "first_name": "John",
                    "last_name": "Doe",
                    "photo": "john_photo.jpg"
                }
            }
        ],
        "first_page_url": "http://domain.com/api/chats/1/messages?page=1",
        "from": 1,
        "last_page": 3,
        "last_page_url": "http://domain.com/api/chats/1/messages?page=3",
        "next_page_url": "http://domain.com/api/chats/1/messages?page=2",
        "path": "http://domain.com/api/chats/1/messages",
        "per_page": 50,
        "prev_page_url": null,
        "to": 50,
        "total": 150
    }
}

Send Message

Send a message to a chat.

Endpoint: POST /api/chats/{chat}/messages
Authentication: Required (Bearer Token)

Request Body
{
    "content": "Hello, how are you?",
    "type": "text"
}
Response (201 Created)
{
    "success": true,
    "message": "Message sent successfully",
    "data": {
        "id": 1,
        "chat_id": 1,
        "user_id": 1,
        "content": "Hello, how are you?",
        "type": "text",
        "is_edited": false,
        "created_at": "2025-07-21T06:42:19.000000Z",
        "updated_at": "2025-07-21T06:42:19.000000Z",
        "user": {
            "id": 1,
            "first_name": "Current",
            "last_name": "User",
            "photo": "current_user_photo.jpg"
        }
    }
}
Error Responses

403 Forbidden (not a participant):

{
    "success": false,
    "message": "You are not a participant in this chat"
}

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "content": ["The content field is required."],
        "type": ["The selected type is invalid."]
    }
}

Update Message

Update a message (only your own messages).

Endpoint: PUT /api/messages/{message}
Authentication: Required (Bearer Token)

Request Body
{
    "content": "Updated message content"
}
Response (200 OK)
{
    "success": true,
    "message": "Message updated successfully",
    "data": {
        "id": 1,
        "chat_id": 1,
        "user_id": 1,
        "content": "Updated message content",
        "type": "text",
        "is_edited": true,
        "created_at": "2025-07-21T06:42:19.000000Z",
        "updated_at": "2025-07-21T06:45:30.000000Z",
        "user": {
            "id": 1,
            "first_name": "Current",
            "last_name": "User",
            "photo": "current_user_photo.jpg"
        }
    }
}
Error Responses

403 Forbidden (not message owner):

{
    "success": false,
    "message": "You can only edit your own messages"
}

Delete Message

Delete a message.

Endpoint: DELETE /api/messages/{message}
Authentication: Required (Bearer Token)

Response (200 OK)
{
    "success": true,
    "message": "Message deleted successfully"
}
Error Responses

403 Forbidden (not message owner):

{
    "success": false,
    "message": "You can only delete your own messages"
}

Frontend Integration

The chat system can be integrated into React applications using Pusher for real-time messaging. Below is a complete implementation guide.

1. Install Required Packages

npm install pusher-js laravel-echo axios

2. Setup Echo and Pusher

Create src/utils/echo.js:

import Echo from "laravel-echo";
import Pusher from "pusher-js";

window.Pusher = Pusher;

const echo = new Echo({
    broadcaster: "pusher",
    key: process.env.REACT_APP_PUSHER_APP_KEY,
    cluster: process.env.REACT_APP_PUSHER_APP_CLUSTER,
    forceTLS: true,
    authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
                // Your auth token from Laravel Sanctum
                const token = localStorage.getItem("auth_token");

                axios
                    .post(
                        "/api/broadcasting/auth",
                        {
                            socket_id: socketId,
                            channel_name: channel.name,
                        },
                        {
                            headers: {
                                Authorization: `Bearer ${token}`,
                            },
                        }
                    )
                    .then((response) => {
                        callback(false, response.data);
                    })
                    .catch((error) => {
                        callback(true, error);
                    });
            },
        };
    },
});

export default echo;

3. Environment Variables

Add to your .env file in React:

REACT_APP_PUSHER_APP_KEY=your_pusher_app_key
REACT_APP_PUSHER_APP_CLUSTER=mt1
REACT_APP_API_URL=http://localhost:8000/api

4. Chat Component Example

import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import echo from "../utils/echo";

const ChatComponent = ({ chatId, authToken }) => {
    const [messages, setMessages] = useState([]);
    const [newMessage, setNewMessage] = useState("");
    const [loading, setLoading] = useState(true);
    const messagesEndRef = useRef(null);

    useEffect(() => {
        loadMessages();
        subscribeToChat();

        return () => {
            echo.leaveChannel(`private-chat.${chatId}`);
        };
    }, [chatId]);

    const loadMessages = async () => {
        try {
            const response = await axios.get(`/api/chats/${chatId}/messages`, {
                headers: { Authorization: `Bearer ${authToken}` },
            });
            setMessages(response.data.data.data);
            setLoading(false);
        } catch (error) {
            console.error("Error loading messages:", error);
            setLoading(false);
        }
    };

    const subscribeToChat = () => {
        echo.private(`chat.${chatId}`)
            .listen(".message.sent", (e) => {
                setMessages((prev) => [...prev, e]);
                scrollToBottom();
            })
            .listen(".message.deleted", (e) => {
                setMessages((prev) =>
                    prev.filter((msg) => msg.id !== e.message_id)
                );
            });
    };

    const sendMessage = async (e) => {
        e.preventDefault();
        if (!newMessage.trim()) return;

        try {
            const response = await axios.post(
                `/api/chats/${chatId}/messages`,
                {
                    content: newMessage,
                    type: "text",
                },
                {
                    headers: { Authorization: `Bearer ${authToken}` },
                }
            );

            setMessages((prev) => [...prev, response.data.data]);
            setNewMessage("");
            scrollToBottom();
        } catch (error) {
            console.error("Error sending message:", error);
        }
    };

    const scrollToBottom = () => {
        messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
    };

    if (loading) return <div>Loading messages...</div>;

    return (
        <div className="chat-container">
            <div className="messages-container">
                {messages.map((message) => (
                    <div key={message.id} className="message">
                        <div className="message-header">
                            <span className="sender-name">
                                {message.user.first_name}{" "}
                                {message.user.last_name}
                            </span>
                            <span className="message-time">
                                {new Date(
                                    message.created_at
                                ).toLocaleTimeString()}
                            </span>
                        </div>
                        <div className="message-content">
                            {message.content}
                            {message.is_edited && (
                                <span className="edited-indicator">
                                    (edited)
                                </span>
                            )}
                        </div>
                    </div>
                ))}
                <div ref={messagesEndRef} />
            </div>

            <form onSubmit={sendMessage} className="message-form">
                <input
                    type="text"
                    value={newMessage}
                    onChange={(e) => setNewMessage(e.target.value)}
                    placeholder="Type a message..."
                    className="message-input"
                />
                <button type="submit" className="send-button">
                    Send
                </button>
            </form>
        </div>
    );
};

export default ChatComponent;

5. Chat List Component

import React, { useState, useEffect } from "react";
import axios from "axios";

const ChatList = ({ authToken, onSelectChat }) => {
    const [chats, setChats] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        loadChats();
    }, []);

    const loadChats = async () => {
        try {
            const response = await axios.get("/api/chats", {
                headers: { Authorization: `Bearer ${authToken}` },
            });
            setChats(response.data.data);
            setLoading(false);
        } catch (error) {
            console.error("Error loading chats:", error);
            setLoading(false);
        }
    };

    const createPrivateChat = async (friendId) => {
        try {
            const response = await axios.post(
                "/api/chats/private",
                {
                    friend_id: friendId,
                },
                {
                    headers: { Authorization: `Bearer ${authToken}` },
                }
            );

            // Add new chat to list or select existing one
            const newChat = response.data.data;
            setChats((prev) => {
                const exists = prev.find((chat) => chat.id === newChat.id);
                if (exists) return prev;
                return [newChat, ...prev];
            });

            onSelectChat(newChat.id);
        } catch (error) {
            console.error("Error creating chat:", error);
        }
    };

    if (loading) return <div>Loading chats...</div>;

    return (
        <div className="chat-list">
            {chats.map((chat) => (
                <div
                    key={chat.id}
                    className="chat-item"
                    onClick={() => onSelectChat(chat.id)}
                >
                    <div className="chat-info">
                        <h4>{chat.name}</h4>
                        {chat.latest_message && (
                            <p className="last-message">
                                {chat.latest_message.content}
                            </p>
                        )}
                    </div>
                    {chat.unread_count > 0 && (
                        <span className="unread-badge">
                            {chat.unread_count}
                        </span>
                    )}
                </div>
            ))}
        </div>
    );
};

export default ChatList;

Backend Setup

1. Environment Configuration

Add the following to your .env file:

BROADCAST_CONNECTION=pusher
PUSHER_APP_ID=your_pusher_app_id
PUSHER_APP_KEY=your_pusher_app_key
PUSHER_APP_SECRET=your_pusher_app_secret
PUSHER_APP_CLUSTER=mt1

2. Database Schema

The chat system uses three main tables:

  • chats: Stores chat metadata (type, name, creator, etc.)
  • chat_participants: Pivot table for users in chats (with read receipts)
  • messages: Stores all messages with content and metadata

Testing the Implementation

  1. Make sure you have valid Pusher credentials in your .env
  2. Run migrations: php artisan migrate
  3. Ensure broadcasting is enabled: BROADCAST_CONNECTION=pusher
  4. Test API endpoints with authenticated requests
  5. Test real-time functionality with multiple browser tabs

Performance Considerations

  • Messages are paginated (50 per page by default)
  • Indexes are added for better query performance
  • Consider implementing message caching for frequently accessed chats
  • Use queue workers for broadcasting in production

Next Steps

  1. Add file/image sharing capabilities
  2. Implement typing indicators
  3. Add message reactions/emojis
  4. Implement group chat functionality
  5. Add push notifications for mobile apps

Security Considerations

  1. Authentication: All routes are protected with Sanctum authentication
  2. Authorization: Users can only access chats they're participants in
  3. Friend Verification: Private chats can only be created between friends
  4. Channel Authorization: Pusher channels are authorized via Laravel
  5. Message Ownership: Users can only edit/delete their own messages

Messages

Get Chat Messages

Retrieve messages for a specific chat with pagination.

Endpoint: GET /api/chats/{chat}/messages
Authentication: Required (Bearer Token)

Query Parameters

  • page (integer, optional): Page number for pagination
  • per_page (integer, optional): Number of messages per page (default: 20)

Response (200 OK)

{
    "success": true,
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 1,
                "content": "Hello! How are you?",
                "user": {
                    "id": 1,
                    "first_name": "John",
                    "last_name": "Doe",
                    "photo": "http://domain.com/storage/users/photos/photo.jpg"
                },
                "chat_id": 1,
                "created_at": "2025-07-19T12:00:00.000000Z",
                "updated_at": "2025-07-19T12:00:00.000000Z"
            }
        ],
        "first_page_url": "http://domain.com/api/chats/1/messages?page=1",
        "from": 1,
        "last_page": 3,
        "last_page_url": "http://domain.com/api/chats/1/messages?page=3",
        "next_page_url": "http://domain.com/api/chats/1/messages?page=2",
        "path": "http://domain.com/api/chats/1/messages",
        "per_page": 20,
        "prev_page_url": null,
        "to": 20,
        "total": 60
    }
}

Error Responses

403 Forbidden (not a chat participant):

{
    "success": false,
    "message": "You are not a participant in this chat"
}

Send Message

Send a new message in a chat.

Endpoint: POST /api/chats/{chat}/messages
Authentication: Required (Bearer Token)

Request Body

{
    "content": "string (required, max:1000 characters)"
}

Response (201 Created)

{
    "success": true,
    "message": "Message sent successfully",
    "data": {
        "id": 1,
        "content": "Hello! How are you?",
        "user": {
            "id": 1,
            "first_name": "John",
            "last_name": "Doe",
            "photo": "http://domain.com/storage/users/photos/photo.jpg"
        },
        "chat_id": 1,
        "created_at": "2025-07-19T12:00:00.000000Z",
        "updated_at": "2025-07-19T12:00:00.000000Z"
    }
}

Error Responses

403 Forbidden (not a chat participant):

{
    "success": false,
    "message": "You are not a participant in this chat"
}

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "content": [
            "The content field is required.",
            "The content may not be greater than 1000 characters."
        ]
    }
}

Update Message

Update an existing message (only by the message sender).

Endpoint: PUT /api/messages/{message}
Authentication: Required (Bearer Token)

Request Body

{
    "content": "string (required, max:1000 characters)"
}

Response (200 OK)

{
    "success": true,
    "message": "Message updated successfully",
    "data": {
        "id": 1,
        "content": "Updated message content",
        "user": {
            "id": 1,
            "first_name": "John",
            "last_name": "Doe",
            "photo": "http://domain.com/storage/users/photos/photo.jpg"
        },
        "chat_id": 1,
        "created_at": "2025-07-19T12:00:00.000000Z",
        "updated_at": "2025-07-19T12:30:00.000000Z"
    }
}

Error Responses

403 Forbidden (not message owner):

{
    "success": false,
    "message": "You can only edit your own messages"
}

404 Not Found (message not found):

{
    "success": false,
    "message": "Message not found"
}

Delete Message

Delete an existing message (only by the message sender).

Endpoint: DELETE /api/messages/{message}
Authentication: Required (Bearer Token)

Response (200 OK)

{
    "success": true,
    "message": "Message deleted successfully"
}

Error Responses

403 Forbidden (not message owner):

{
    "success": false,
    "message": "You can only delete your own messages"
}

404 Not Found (message not found):

{
    "success": false,
    "message": "Message not found"
}

Leaderboard

The Leaderboard system tracks user gaming activity and provides ranking functionality based on daily gaming hours.

Get Leaderboard

Retrieve the main leaderboard showing top users ranked by their gaming activity.

Endpoint: GET /api/leaderboard
Authentication: None required

Query Parameters

  • limit (integer, optional): Number of users to return (default: 50)

Response (200 OK)

{
    "success": true,
    "data": [
        {
            "rank": 1,
            "user": {
                "id": 1,
                "first_name": "John",
                "last_name": "Doe",
                "photo": "http://domain.com/storage/users/photos/photo.jpg"
            },
            "total_hours": 145.5,
            "days_active": 30,
            "average_daily_hours": 4.85
        },
        {
            "rank": 2,
            "user": {
                "id": 2,
                "first_name": "Jane",
                "last_name": "Smith",
                "photo": "http://domain.com/storage/users/photos/photo2.jpg"
            },
            "total_hours": 132.0,
            "days_active": 28,
            "average_daily_hours": 4.71
        }
    ],
    "current_user_rank": 15,
    "total_users": 500
}

Get Weekly Leaderboard

Retrieve the weekly leaderboard showing top users for the current week.

Endpoint: GET /api/leaderboard/weekly
Authentication: None required

Query Parameters

  • limit (integer, optional): Number of users to return (default: 50)

Response (200 OK)

{
    "success": true,
    "data": [
        {
            "rank": 1,
            "user": {
                "id": 1,
                "first_name": "John",
                "last_name": "Doe",
                "photo": "http://domain.com/storage/users/photos/photo.jpg"
            },
            "weekly_hours": 28.5,
            "days_active": 6
        }
    ],
    "week_start": "2025-07-14",
    "week_end": "2025-07-20",
    "current_user_rank": 8,
    "total_users": 120
}

Update Daily Hours

Update the authenticated user's daily gaming hours.

Endpoint: POST /api/leaderboard/daily-hours
Authentication: Required (Bearer Token)

Request Body

{
    "date": "2025-07-19",
    "hours": 4.5
}

Response (200 OK)

{
    "success": true,
    "message": "Daily hours updated successfully",
    "data": {
        "date": "2025-07-19",
        "hours": 4.5,
        "user_id": 1,
        "created_at": "2025-07-19T12:00:00.000000Z",
        "updated_at": "2025-07-19T12:00:00.000000Z"
    }
}

Error Responses

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "date": [
            "The date field is required.",
            "The date must be a valid date."
        ],
        "hours": [
            "The hours field is required.",
            "The hours must be a number between 0 and 24."
        ]
    }
}

Get User History

Retrieve gaming hours history for a specific user.

Endpoint: GET /api/leaderboard/users/{user}/history
Authentication: Required (Bearer Token)

Query Parameters

  • days (integer, optional): Number of days to retrieve (default: 30, max: 365)
  • start_date (date, optional): Start date for history (YYYY-MM-DD format)
  • end_date (date, optional): End date for history (YYYY-MM-DD format)

Response (200 OK)

{
    "success": true,
    "user": {
        "id": 1,
        "first_name": "John",
        "last_name": "Doe",
        "photo": "http://domain.com/storage/users/photos/photo.jpg"
    },
    "data": [
        {
            "date": "2025-07-19",
            "hours": 4.5
        },
        {
            "date": "2025-07-18",
            "hours": 3.2
        },
        {
            "date": "2025-07-17",
            "hours": 5.1
        }
    ],
    "total_hours": 145.5,
    "average_daily_hours": 4.85,
    "days_with_activity": 30,
    "period": {
        "start_date": "2025-06-19",
        "end_date": "2025-07-19",
        "total_days": 30
    }
}

Error Responses

404 Not Found (user not found):

{
    "success": false,
    "message": "User not found"
}

403 Forbidden (privacy restriction):

{
    "success": false,
    "message": "You can only view your own gaming history or users you are friends with"
}

Payment System

The Payment System provides comprehensive Stripe integration for handling payments through both hosted checkout sessions and custom payment flows. All payment data is securely stored in the database with full audit trails.

Features

  • Stripe Checkout Integration: Hosted payment pages for quick implementation
  • Payment Intent Support: Custom payment flows with Stripe Elements
  • Webhook Handling: Automatic status updates via Stripe webhooks
  • Payment History: Complete transaction history for users
  • Multiple Currencies: Support for USD, EUR, and GBP
  • Comprehensive Validation: Input validation and error handling
  • Security: Webhook signature verification and encrypted data storage

Database Tables

The payment system uses two main tables:

  • payments: Stores completed payment records from Stripe
  • payment_sessions: Stores Stripe Checkout Session data

Payment Endpoints

Create Checkout Session

Create a Stripe Checkout Session for hosted payment page.

Endpoint: POST /payments/create-checkout-session
Authentication: Required (Bearer Token)

Request Body
{
    "amount": 29.99,
    "currency": "usd",
    "description": "Game Purchase",
    "metadata": {
        "game_id": "123",
        "plan": "premium"
    },
    "success_url": "https://yourapp.com/payment/success",
    "cancel_url": "https://yourapp.com/payment/cancel"
}
Validation Rules
  • amount: Required, numeric, minimum $0.50, maximum $999,999.99
  • currency: Optional, must be one of: usd, eur, gbp (default: usd)
  • description: Optional, string, maximum 255 characters
  • metadata: Optional, object with string values
  • success_url: Required, valid URL, maximum 2048 characters
  • cancel_url: Required, valid URL, maximum 2048 characters
Response (200 OK)
{
    "success": true,
    "data": {
        "session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
        "session_url": "https://checkout.stripe.com/c/pay/cs_test_...",
        "payment_session": {
            "id": 1,
            "user_id": 1,
            "stripe_session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
            "amount": "29.99",
            "currency": "usd",
            "status": "open",
            "payment_status": "unpaid",
            "mode": "payment",
            "expires_at": "2025-09-01T18:02:12.000000Z",
            "created_at": "2025-08-31T18:02:12.000000Z",
            "updated_at": "2025-08-31T18:02:12.000000Z"
        }
    }
}
Error Responses

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "amount": [
            "Payment amount is required.",
            "Payment amount must be at least $0.50."
        ],
        "success_url": [
            "Success URL is required.",
            "Success URL must be a valid URL."
        ]
    }
}

500 Internal Server Error (Stripe error):

{
    "success": false,
    "message": "Failed to create payment session",
    "error": "Stripe API error details"
}

Create Payment Intent

Create a Stripe Payment Intent for custom payment flows.

Endpoint: POST /payments/create-payment-intent
Authentication: Required (Bearer Token)

Request Body
{
    "amount": 19.99,
    "currency": "usd",
    "description": "Premium Upgrade",
    "metadata": {
        "upgrade_type": "premium",
        "duration": "monthly"
    }
}
Validation Rules
  • amount: Required, numeric, minimum $0.50, maximum $999,999.99
  • currency: Optional, must be one of: usd, eur, gbp (default: usd)
  • description: Optional, string, maximum 255 characters
  • metadata: Optional, object with string values
Response (200 OK)
{
    "success": true,
    "data": {
        "client_secret": "pi_test_1234567890_secret_abcdefghij",
        "payment_intent_id": "pi_test_1234567890",
        "payment": {
            "id": 1,
            "user_id": 1,
            "stripe_payment_intent_id": "pi_test_1234567890",
            "amount": "19.99",
            "currency": "usd",
            "status": "requires_payment_method",
            "created_at": "2025-08-31T18:02:12.000000Z",
            "updated_at": "2025-08-31T18:02:12.000000Z"
        }
    }
}

Get Session Details

Retrieve details of a payment session.

Endpoint: GET /payments/session
Authentication: Required (Bearer Token)

Query Parameters
  • session_id (required): Stripe Session ID
Response (200 OK)
{
    "success": true,
    "data": {
        "session": {
            "id": 1,
            "user_id": 1,
            "stripe_session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
            "amount": "29.99",
            "currency": "usd",
            "status": "complete",
            "payment_status": "paid",
            "mode": "payment"
        },
        "stripe_session": {
            "id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
            "object": "checkout.session",
            "status": "complete",
            "payment_status": "paid"
        }
    }
}
Error Responses

404 Not Found (session not found):

{
    "success": false,
    "message": "Payment session not found"
}

Get Payment Intent Details

Retrieve details of a payment intent.

Endpoint: GET /payments/payment-intent
Authentication: Required (Bearer Token)

Query Parameters
  • payment_intent_id (required): Stripe Payment Intent ID
Response (200 OK)
{
    "success": true,
    "data": {
        "payment": {
            "id": 1,
            "user_id": 1,
            "stripe_payment_intent_id": "pi_test_1234567890",
            "amount": "19.99",
            "currency": "usd",
            "status": "succeeded",
            "payment_method": "pm_test_card",
            "payment_date": "2025-08-31T18:02:12.000000Z"
        },
        "payment_intent": {
            "id": "pi_test_1234567890",
            "object": "payment_intent",
            "status": "succeeded",
            "amount": 1999
        }
    }
}

Get Payment History

Retrieve user's payment history with pagination.

Endpoint: GET /payments/history
Authentication: Required (Bearer Token)

Query Parameters
  • per_page (optional): Number of items per page (default: 15)
Response (200 OK)
{
    "success": true,
    "data": {
        "payments": {
            "data": [
                {
                    "id": 1,
                    "user_id": 1,
                    "stripe_payment_intent_id": "pi_test_1234567890",
                    "amount": "29.99",
                    "currency": "usd",
                    "status": "succeeded",
                    "payment_date": "2025-08-31T18:02:12.000000Z",
                    "description": "Game Purchase"
                }
            ],
            "current_page": 1,
            "per_page": 15,
            "total": 5
        },
        "sessions": {
            "data": [
                {
                    "id": 1,
                    "user_id": 1,
                    "stripe_session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
                    "amount": "29.99",
                    "currency": "usd",
                    "status": "complete",
                    "payment_status": "paid"
                }
            ],
            "current_page": 1,
            "per_page": 15,
            "total": 3
        }
    }
}

Webhook Endpoint

Handle Stripe Webhooks

Process Stripe webhook events for payment status updates.

Endpoint: POST /stripe/webhook
Authentication: None (signature verification required)

Headers
  • Stripe-Signature: Required webhook signature from Stripe
Webhook Events Handled
  • checkout.session.completed: Updates session status when checkout is complete
  • payment_intent.succeeded: Updates payment status when payment succeeds
  • payment_intent.payment_failed: Updates payment status when payment fails
Response (200 OK)
{
    "success": true
}
Error Responses

400 Bad Request (invalid payload or signature):

{
    "error": "Invalid payload"
}
{
    "error": "Invalid signature"
}

Frontend Integration

Using Stripe Checkout (Recommended)

  1. Create a checkout session by calling /api/payments/create-checkout-session
  2. Redirect user to the session_url returned in the response
  3. Handle success/cancel redirects in your frontend
  4. Verify payment status by calling /api/payments/session with the session ID
JavaScript Example
// Create checkout session
const response = await fetch("/api/payments/create-checkout-session", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({
        amount: 29.99,
        currency: "usd",
        description: "Game Purchase",
        success_url: `${window.location.origin}/payment/success`,
        cancel_url: `${window.location.origin}/payment/cancel`,
    }),
});

const data = await response.json();
if (data.success) {
    // Redirect to Stripe Checkout
    window.location.href = data.data.session_url;
}

Using Stripe Elements (Custom UI)

  1. Create a payment intent by calling /api/payments/create-payment-intent
  2. Use the client_secret with Stripe Elements to collect payment details
  3. Confirm payment using Stripe.js
  4. Verify payment status by calling /api/payments/payment-intent
JavaScript Example
// Create payment intent
const response = await fetch("/api/payments/create-payment-intent", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({
        amount: 19.99,
        currency: "usd",
        description: "Premium Upgrade",
    }),
});

const data = await response.json();
if (data.success) {
    const clientSecret = data.data.client_secret;

    // Use clientSecret with Stripe Elements
    const stripe = Stripe("pk_test_your_publishable_key");
    const result = await stripe.confirmCardPayment(clientSecret, {
        payment_method: {
            card: cardElement,
            billing_details: {
                name: "Customer Name",
            },
        },
    });

    if (result.error) {
        // Handle payment error
        console.error(result.error.message);
    } else {
        // Payment succeeded
        console.log("Payment successful:", result.paymentIntent);
    }
}

cURL Examples

# Create checkout session
curl -X POST http://your-domain.com/api/payments/create-checkout-session \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 29.99,
    "currency": "usd",
    "description": "Game Purchase",
    "metadata": {"game_id": "123"},
    "success_url": "https://yourapp.com/success",
    "cancel_url": "https://yourapp.com/cancel"
  }'

# Create payment intent
curl -X POST http://your-domain.com/api/payments/create-payment-intent \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 19.99,
    "currency": "usd",
    "description": "Premium Upgrade"
  }'

# Get payment history
curl -X GET http://your-domain.com/api/payments/history \
  -H "Authorization: Bearer your_token_here"

# Get session details
curl -X GET "http://your-domain.com/api/payments/session?session_id=cs_test_123" \
  -H "Authorization: Bearer your_token_here"

Setup Instructions

  1. Get Stripe API Keys: Register at Stripe Dashboard
  2. Configure Environment Variables:
    STRIPE_KEY=pk_test_your_publishable_key_here
    STRIPE_SECRET=sk_test_your_secret_key_here
    STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
    
  3. Set up Webhook: Create webhook endpoint in Stripe Dashboard
    • URL: https://yourdomain.com/api/stripe/webhook
    • Events: checkout.session.completed, payment_intent.succeeded, payment_intent.payment_failed

Testing

Use Stripe's test card numbers for testing:

  • Success: 4242424242424242
  • Declined: 4000000000000002
  • Requires authentication: 4000002500003155

Subscription System

The Subscription System provides comprehensive subscription management with Stripe integration, allowing users to subscribe to premium packages with different durations and features. All subscription data is securely stored with full audit trails and automatic renewal handling.

Features

  • Package Management: Flexible subscription packages with different periods and pricing
  • Stripe Integration: Complete Stripe subscription and checkout session integration
  • Multiple Payment Types: Support for both one-time and recurring subscriptions
  • Automatic Renewals: Webhook-driven subscription renewals and status updates
  • Subscription History: Complete subscription history tracking for users
  • Multiple Currencies: Support for USD, EUR, and GBP
  • Comprehensive Validation: Input validation and error handling
  • Security: Webhook signature verification and encrypted data storage

Database Tables

The subscription system uses two main tables:

  • subscription_packages: Stores available subscription packages with pricing and features
  • subscriptions: Stores user subscription records with Stripe integration data

Subscription Endpoints

Get Subscription Packages

Retrieve all available subscription packages (public access).

Endpoint: GET /subscription-packages
Authentication: None required

Response (200 OK)
{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Daily Pass",
            "description": "Access to premium games for 1 day",
            "price": "2.99",
            "currency": "usd",
            "period_days": 1,
            "period_type": "one_time",
            "stripe_price_id": null,
            "features": [
                "Access to all premium games",
                "No ads",
                "Priority support"
            ],
            "is_active": true,
            "sort_order": 1,
            "created_at": "2025-09-02T10:35:27.000000Z",
            "updated_at": "2025-09-02T10:35:27.000000Z"
        },
        {
            "id": 2,
            "name": "Monthly Premium",
            "description": "Full access to all premium features for 30 days",
            "price": "9.99",
            "currency": "usd",
            "period_days": 30,
            "period_type": "recurring",
            "stripe_price_id": null,
            "features": [
                "Access to all premium games",
                "No ads",
                "Priority support",
                "Exclusive monthly content",
                "Early access to new games"
            ],
            "is_active": true,
            "sort_order": 2,
            "created_at": "2025-09-02T10:35:27.000000Z",
            "updated_at": "2025-09-02T10:35:27.000000Z"
        }
    ]
}
Error Response (500 Internal Server Error)
{
    "success": false,
    "message": "Failed to retrieve subscription packages",
    "error": "Database connection error"
}

Get User Subscriptions

Retrieve user's subscription history with pagination.

Endpoint: GET /subscriptions
Authentication: Required (Bearer Token)

Query Parameters
  • per_page (optional): Number of items per page (default: 15)
Response (200 OK)
{
    "success": true,
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 1,
                "user_id": 1,
                "subscription_package_id": 2,
                "stripe_customer_id": "cus_test_123",
                "stripe_subscription_id": "sub_test_456",
                "status": "active",
                "starts_at": "2025-09-01T00:00:00.000000Z",
                "ends_at": "2025-10-01T00:00:00.000000Z",
                "amount": "9.99",
                "currency": "usd",
                "metadata": {
                    "package_name": "Monthly Premium"
                },
                "created_at": "2025-09-01T10:00:00.000000Z",
                "updated_at": "2025-09-01T10:00:00.000000Z",
                "package": {
                    "id": 2,
                    "name": "Monthly Premium",
                    "description": "Full access to all premium features for 30 days",
                    "price": "9.99",
                    "period_days": 30,
                    "features": [
                        "Access to all premium games",
                        "No ads",
                        "Priority support"
                    ]
                }
            }
        ],
        "first_page_url": "http://domain.com/api/subscriptions?page=1",
        "from": 1,
        "last_page": 1,
        "last_page_url": "http://domain.com/api/subscriptions?page=1",
        "next_page_url": null,
        "path": "http://domain.com/api/subscriptions",
        "per_page": 15,
        "prev_page_url": null,
        "to": 1,
        "total": 1
    }
}
Error Response (401 Unauthorized)
{
    "success": false,
    "message": "Unauthenticated."
}

Get Current Subscription

Retrieve user's current active subscription.

Endpoint: GET /subscriptions/current
Authentication: Required (Bearer Token)

Response (200 OK)
{
    "success": true,
    "data": {
        "id": 1,
        "user_id": 1,
        "subscription_package_id": 2,
        "stripe_customer_id": "cus_test_123",
        "stripe_subscription_id": "sub_test_456",
        "status": "active",
        "starts_at": "2025-09-01T00:00:00.000000Z",
        "ends_at": "2025-10-01T00:00:00.000000Z",
        "amount": "9.99",
        "currency": "usd",
        "package": {
            "id": 2,
            "name": "Monthly Premium",
            "description": "Full access to all premium features for 30 days",
            "price": "9.99",
            "period_days": 30,
            "features": [
                "Access to all premium games",
                "No ads",
                "Priority support"
            ]
        }
    }
}
Error Response (404 Not Found)
{
    "success": false,
    "message": "No active subscription found"
}

Subscribe to Package

Create a new subscription to a package with Stripe integration.

Endpoint: POST /subscriptions/subscribe
Authentication: Required (Bearer Token)

Request Body
{
    "package_id": 2,
    "success_url": "https://yourapp.com/subscription/success",
    "cancel_url": "https://yourapp.com/subscription/cancel"
}
Validation Rules
  • package_id: Required, integer, must exist in subscription_packages table
  • success_url: Required, valid URL
  • cancel_url: Required, valid URL
Response (200 OK)
{
    "success": true,
    "data": {
        "session_id": "cs_test_a1DTLRPULHUWRh5Gpc2ltZGv8gWaPfyf4sX5zIO5UQg3AJSGN3LwH9nMDk",
        "session_url": "https://checkout.stripe.com/c/pay/cs_test_...",
        "subscription": {
            "id": 1,
            "user_id": 1,
            "subscription_package_id": 2,
            "stripe_customer_id": "cus_SypE2sZIPeiRfb",
            "status": "pending",
            "starts_at": "2025-09-02T10:37:08.000000Z",
            "ends_at": "2025-10-02T10:37:08.000000Z",
            "amount": "9.99",
            "currency": "usd",
            "metadata": {
                "stripe_session_id": "cs_test_a1DTLRPULHUWRh5Gpc2ltZGv8gWaPfyf4sX5zIO5UQg3AJSGN3LwH9nMDk",
                "package_name": "Monthly Premium"
            },
            "updated_at": "2025-09-02T10:37:08.000000Z",
            "created_at": "2025-09-02T10:37:08.000000Z"
        }
    }
}
Error Responses

400 Bad Request (already subscribed):

{
    "success": false,
    "message": "You already have an active subscription to this package"
}

422 Unprocessable Entity (validation error):

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "package_id": ["The selected package does not exist"],
        "success_url": [
            "Success URL is required.",
            "Success URL must be a valid URL."
        ],
        "cancel_url": [
            "Cancel URL is required.",
            "Cancel URL must be a valid URL."
        ]
    }
}

500 Internal Server Error (Stripe error):

{
    "success": false,
    "message": "Failed to create subscription",
    "error": "Stripe API error details"
}

Cancel Subscription

Cancel an existing subscription.

Endpoint: POST /subscriptions/cancel
Authentication: Required (Bearer Token)

Request Body
{
    "subscription_id": 1
}
Response (200 OK)
{
    "success": true,
    "message": "Subscription canceled successfully",
    "data": {
        "id": 1,
        "user_id": 1,
        "subscription_package_id": 2,
        "status": "canceled",
        "starts_at": "2025-09-01T00:00:00.000000Z",
        "ends_at": "2025-10-01T00:00:00.000000Z",
        "canceled_at": "2025-09-15T12:00:00.000000Z"
    }
}
Error Responses

400 Bad Request (already canceled):

{
    "success": false,
    "message": "Subscription is already canceled"
}

404 Not Found (subscription not found or not owned by user):

{
    "success": false,
    "message": "Subscription not found"
}

500 Internal Server Error (cancellation failed):

{
    "success": false,
    "message": "Failed to cancel subscription",
    "error": "Error details"
}

Webhook Endpoint

Handle Subscription Webhooks

Process Stripe webhook events for subscription status updates.

Endpoint: POST /subscriptions/webhook
Authentication: None (signature verification required)

Headers
  • Stripe-Signature: Required webhook signature from Stripe
Webhook Events Handled
  • checkout.session.completed: Activates subscription when checkout is complete
  • invoice.payment_succeeded: Extends subscription when recurring payment succeeds
  • customer.subscription.created: Updates subscription with Stripe subscription ID
  • customer.subscription.updated: Updates subscription status
  • customer.subscription.deleted: Cancels subscription when deleted in Stripe
Response (200 OK)
{
    "success": true
}
Error Responses

400 Bad Request (invalid payload or signature):

{
    "error": "Invalid webhook"
}

Package Types

One-time Subscriptions

  • Daily Pass: $2.99 for 1 day access
  • Weekly Pass: $4.99 for 7 days access

Recurring Subscriptions

  • Monthly Premium: $9.99/month with automatic renewal
  • Yearly Premium: $99.99/year with automatic renewal

Frontend Integration

Using Stripe Checkout (Recommended)

  1. Get available packages by calling /api/subscription-packages
  2. Create a subscription by calling /api/subscriptions/subscribe
  3. Redirect user to the session_url returned in the response
  4. Handle success/cancel redirects in your frontend
  5. Verify subscription status by calling /api/subscriptions/current
JavaScript Example
// Get available packages
const getPackages = async () => {
    const response = await fetch("/api/subscription-packages", {
        headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
        },
    });

    const data = await response.json();
    return data.data; // Array of packages
};

// Subscribe to a package
const subscribeToPackage = async (packageId, token) => {
    const response = await fetch("/api/subscriptions/subscribe", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
            package_id: packageId,
            success_url: `${window.location.origin}/subscription/success`,
            cancel_url: `${window.location.origin}/subscription/cancel`,
        }),
    });

    const data = await response.json();
    if (data.success) {
        // Redirect to Stripe Checkout
        window.location.href = data.data.session_url;
    }
    return data;
};

// Get current subscription
const getCurrentSubscription = async (token) => {
    const response = await fetch("/api/subscriptions/current", {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    const data = await response.json();
    return data;
};

// Get subscription history
const getSubscriptionHistory = async (token) => {
    const response = await fetch("/api/subscriptions", {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    const data = await response.json();
    return data.data; // Paginated subscription data
};

// Cancel subscription
const cancelSubscription = async (subscriptionId, token) => {
    const response = await fetch("/api/subscriptions/cancel", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
            subscription_id: subscriptionId,
        }),
    });

    const data = await response.json();
    return data;
};

cURL Examples

# Get subscription packages (public access)
curl -X GET http://your-domain.com/api/subscription-packages \
  -H "Content-Type: application/json" \
  -H "Accept: application/json"

# Subscribe to a package
curl -X POST http://your-domain.com/api/subscriptions/subscribe \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "package_id": 2,
    "success_url": "https://yourapp.com/subscription/success",
    "cancel_url": "https://yourapp.com/subscription/cancel"
  }'

# Get user subscriptions
curl -X GET http://your-domain.com/api/subscriptions \
  -H "Authorization: Bearer your_token_here"

# Get current subscription
curl -X GET http://your-domain.com/api/subscriptions/current \
  -H "Authorization: Bearer your_token_here"

# Cancel subscription
curl -X POST http://your-domain.com/api/subscriptions/cancel \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription_id": 1
  }'

Setup Instructions

  1. Configure Stripe Subscription Settings: Set up products and prices in Stripe Dashboard
  2. Configure Environment Variables:
    STRIPE_KEY=pk_test_your_publishable_key_here
    STRIPE_SECRET=sk_test_your_secret_key_here
    STRIPE_WEBHOOK_SECRET=whsec_your_subscription_webhook_secret_here
    
  3. Set up Subscription Webhook: Create webhook endpoint in Stripe Dashboard
    • URL: https://yourdomain.com/api/subscriptions/webhook
    • Events: checkout.session.completed, invoice.payment_succeeded, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted

Testing

Test Packages Available

The system comes with four pre-configured packages:

  1. Daily Pass - $2.99 for 1 day (one-time)
  2. Weekly Pass - $4.99 for 7 days (one-time)
  3. Monthly Premium - $9.99 for 30 days (recurring)
  4. Yearly Premium - $99.99 for 365 days (recurring)

Test Card Numbers

Use Stripe's test card numbers for testing subscriptions:

  • Success: 4242424242424242
  • Declined: 4000000000000002
  • Requires authentication: 4000002500003155

Test Scenarios

  1. Subscribe to one-time package: User gets access for specified period
  2. Subscribe to recurring package: User gets access with automatic renewals
  3. Cancel subscription: User loses access but retains until end of period
  4. Payment failure: Subscription status updates to failed
  5. Webhook processing: Automatic status updates from Stripe events

Error Handling

HTTP Status Codes

  • 200 OK: Successful request
  • 201 Created: Resource created successfully
  • 401 Unauthorized: Authentication required or invalid credentials
  • 403 Forbidden: Access denied (e.g., inactive subscription)
  • 404 Not Found: Resource not found
  • 422 Unprocessable Entity: Validation errors

Error Response Format

{
    "success": false,
    "message": "Error description",
    "errors": {
        "field_name": ["Validation error message"]
    }
}

Common Error Examples

Validation Error (422)

{
    "success": false,
    "message": "Validation failed",
    "errors": {
        "username": ["The username has already been taken."],
        "password": ["The password must be at least 8 characters."]
    }
}

Authentication Error (401)

{
    "success": false,
    "message": "Invalid credentials"
}

Inactive Account Error (401)

{
    "success": false,
    "message": "Account is inactive"
}

Headers & Language Support

Required Headers

For authenticated endpoints:

Authorization: Bearer {your_token_here}
Content-Type: application/json

For file uploads:

Authorization: Bearer {your_token_here}
Content-Type: multipart/form-data

Language Support

The API supports multiple languages through the language header:

language: en    // English (default)
language: ar    // Arabic
language: fr    // French
// Add other supported languages

When the language header is provided, all translatable fields (game names, descriptions, category names, etc.) will be returned in the requested language. If a translation is not available, the default language (English) will be used as fallback.

Translatable Fields

  • Game: name, description, instructions
  • Category: name, description

Pagination

List endpoints return paginated results with the following structure:

{
  "current_page": 1,
  "data": [...],
  "first_page_url": "...",
  "from": 1,
  "last_page": 5,
  "last_page_url": "...",
  "next_page_url": "...",
  "path": "...",
  "per_page": 15,
  "prev_page_url": null,
  "to": 15,
  "total": 75
}

SDKs & Examples

JavaScript/Fetch Example

// Send OTP for authentication (creates user automatically if not exists)
const sendOtp = async (phone) => {
    const response = await fetch("/api/send-otp", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ phone }),
    });

    const result = await response.json();
    return result;
};

// Verify OTP and get authentication token
const verifyOtp = async (phone, otp) => {
    const response = await fetch("/api/verify-otp", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ phone, otp }),
    });

    const result = await response.json();
    return result;
};

// Get games with authentication
const getGames = async (token, language = "en") => {
    const response = await fetch("/api/games", {
        headers: {
            Authorization: `Bearer ${token}`,
            language: language,
        },
    });

    const games = await response.json();
    return games;
};

// Vote on a game
const voteOnGame = async (gameId, voteType, token) => {
    const response = await fetch(`/api/games/${gameId}/vote`, {
        method: "POST",
        headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ vote_type: voteType }),
    });

    const result = await response.json();
    return result;
};

// Send a friend request
const sendFriendRequest = async (receiverId, token) => {
    const response = await fetch("/api/friend-requests", {
        method: "POST",
        headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ receiver_id: receiverId }),
    });

    const result = await response.json();
    return result;
};

// Accept or reject a friend request
const respondToFriendRequest = async (requestId, action, token) => {
    const response = await fetch(`/api/friend-requests/${requestId}`, {
        method: "PUT",
        headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ action }),
    });

    const result = await response.json();
    return result;
};

// Get friends list
const getFriends = async (token) => {
    const response = await fetch("/api/friends", {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    const result = await response.json();
    return result;
};

// Get friends' stories
const getFriendsStories = async (token) => {
    const response = await fetch("/api/stories", {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    const result = await response.json();
    return result;
};

// Get my stories
const getMyStories = async (token) => {
    const response = await fetch("/api/stories/my", {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    const result = await response.json();
    return result;
};

// Create a story with content only
const createStoryWithContent = async (content, token) => {
    const response = await fetch("/api/stories", {
        method: "POST",
        headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ content }),
    });

    const result = await response.json();
    return result;
};

// Create a story with image
const createStoryWithImage = async (content, imageFile, token) => {
    const formData = new FormData();
    if (content) formData.append("content", content);
    if (imageFile) formData.append("image", imageFile);

    const response = await fetch("/api/stories", {
        method: "POST",
        headers: {
            Authorization: `Bearer ${token}`,
        },
        body: formData,
    });

    const result = await response.json();
    return result;
};

// Get a specific story
const getStory = async (storyId, token) => {
    const response = await fetch(`/api/stories/${storyId}`, {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    const result = await response.json();
    return result;
};

// Update a story
const updateStory = async (storyId, content, token) => {
    const response = await fetch(`/api/stories/${storyId}`, {
        method: "PUT",
        headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ content }),
    });

    const result = await response.json();
    return result;
};

// Delete a story
const deleteStory = async (storyId, token) => {
    const response = await fetch(`/api/stories/${storyId}`, {
        method: "DELETE",
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });

    const result = await response.json();
    return result;
};

cURL Examples

# Send OTP for authentication (creates user automatically if not exists)
curl -X POST http://your-domain.com/api/send-otp \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+1234567890"
  }'

# Verify OTP and get authentication token
curl -X POST http://your-domain.com/api/verify-otp \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+1234567890",
    "otp": "123456"
  }'

# Get games with language preference
curl -X GET http://your-domain.com/api/games \
  -H "language: ar"

# Vote on a game
curl -X POST http://your-domain.com/api/games/1/vote \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"vote_type": "upvote"}'

# Send a friend request
curl -X POST http://your-domain.com/api/friend-requests \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"receiver_id": 2}'

# Accept a friend request
curl -X PUT http://your-domain.com/api/friend-requests/1 \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"action": "accept"}'

# Get friends list
curl -X GET http://your-domain.com/api/friends \
  -H "Authorization: Bearer your_token_here"

# Get friends' stories
curl -X GET http://your-domain.com/api/stories \
  -H "Authorization: Bearer your_token_here"

# Get my stories
curl -X GET http://your-domain.com/api/stories/my \
  -H "Authorization: Bearer your_token_here"

# Create a story with content only
curl -X POST http://your-domain.com/api/stories \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"content": "Having a great day!"}'

# Create a story with image
curl -X POST http://your-domain.com/api/stories \
  -H "Authorization: Bearer your_token_here" \
  -F "content=Amazing sunset!" \
  -F "image=@/path/to/image.jpg"

# Get a specific story
curl -X GET http://your-domain.com/api/stories/1 \
  -H "Authorization: Bearer your_token_here"

# Update a story
curl -X PUT http://your-domain.com/api/stories/1 \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"content": "Updated story content"}'

# Delete a story
curl -X DELETE http://your-domain.com/api/stories/1 \
  -H "Authorization: Bearer your_token_here"

Last Updated: September 2, 2025
API Version: 1.5
Major Changes in v1.5:

  • Added comprehensive Subscription System with Stripe integration
  • Support for both one-time and recurring subscription packages
  • Subscription management with automatic renewals and cancellation
  • Complete Stripe subscription webhook integration
  • Subscription history tracking with full audit trails
  • Support for multiple subscription periods (daily, weekly, monthly, yearly)
  • Flexible package management with features and pricing
  • Complete subscription documentation with setup instructions and examples

Major Changes in v1.4:

  • Added comprehensive Payment System with Stripe integration
  • Support for both Stripe Checkout Sessions and Payment Intents
  • Payment history tracking with full audit trails
  • Webhook integration for automatic payment status updates
  • Support for multiple currencies (USD, EUR, GBP)
  • Secure payment processing with signature verification
  • Complete payment documentation with setup instructions and examples

Major Changes in v1.3:

  • Added comprehensive Stories API section with full CRUD operations
  • Stories feature supports temporary content sharing with automatic 24-hour expiration
  • Added support for text and/or image content in stories
  • Implemented friend-based visibility for stories
  • Added JavaScript and cURL examples for all story endpoints
  • Removed non-existent /friend-requests/pending endpoint from documentation

Major Changes in v1.2:

  • Removed manual user registration endpoint - users are now created automatically during OTP flow
  • Enhanced OTP authentication to create users automatically if they don't exist
  • Added is_new_user flag to /send-otp response to indicate new user registration
  • Added profile_complete flag to /verify-otp response to indicate if profile needs completion
  • Streamlined authentication flow for better user experience
  • Removed "phone number not found" errors as users are auto-created

Major Changes in v1.1:

  • Replaced username/password authentication with OTP-based phone authentication
  • Added comprehensive friend request system
  • Enhanced user profile with phone verification status
  • Deprecated traditional login in favor of secure OTP flow