Group Purchase
Base URL: https://apinexgate.glueauth.com/api/v1/
Short Description: The Group Purchase API enables collaborative buying where multiple users join together to purchase products at discounted group prices. Users can create new groups, join existing groups, transfer between groups, and track their participations. The system automatically handles seat management, expiration, and order creation when groups are completed.
Hints:
- Groups automatically expire based on product's
groupTimeLimitHourssetting - Groups complete automatically when all seats are filled
- Users can join the same group multiple times to buy more seats (Hybrid Approach)
- Transfer between groups only allowed for same product, shop, and price
- Empty groups are automatically soft-deleted after all participants transfer out
- Group codes are auto-generated with format: GP-XXXXXX (6 random characters)
- Only WALLET payment method supported for group purchases
- Purchase and transfer history tracked for each participant
- Seats are released when users transfer out of groups
Standard Response Format
All API responses follow a consistent structure using our Globe Response Builder pattern:
Success Response Structure
{
"success": true,
"httpStatus": "OK",
"message": "Operation completed successfully",
"action_time": "2025-10-02T10:30:45",
"data": {
// Actual response data goes here
}
}
Error Response Structure
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Error description",
"action_time": "2025-10-02T10:30:45",
"data": "Error description"
}
Standard Response Fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true for successful operations, false for errors |
httpStatus |
string | HTTP status name (OK, BAD_REQUEST, NOT_FOUND, etc.) |
message |
string | Human-readable message describing the operation result |
action_time |
string | ISO 8601 timestamp of when the response was generated |
data |
object/string | Response payload for success, error details for failures |
HTTP Method Badge Standards
- GET - GET - Green (Safe, read-only operations)
- POST - POST - Blue (Create new resources)
Endpoints
1. Get Available Groups for Product
Purpose: Retrieves all available (open, not expired, not full) group purchase instances for a specific product.
Endpoint: GET {base_url}/group-purchases/product/{productId}/available
Access Level: π Public (No Authentication Required)
Authentication: None
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| productId | string (UUID) | Yes | Unique identifier of the product | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Available groups retrieved successfully",
"action_time": "2025-10-02T14:30:45",
"data": [
{
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopName": "TechWorld Electronics",
"groupPrice": 80000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 4,
"seatsRemaining": 6,
"totalParticipants": 3,
"progressPercentage": 40.00,
"status": "OPEN",
"expiresAt": "2025-10-02T20:30:45",
"isExpired": false,
"isUserMember": false,
"participants": [
{
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"contributionPercentage": 50.00
},
{
"userId": "user2345-6789-01bc-def1-234567890123",
"userName": "jane_smith",
"userProfilePicture": "https://cdn.nextgate.com/profiles/jane.jpg",
"quantity": 1,
"contributionPercentage": 25.00
},
{
"userId": "user3456-7890-12cd-ef12-345678901234",
"userName": "bob_wilson",
"userProfilePicture": null,
"quantity": 1,
"contributionPercentage": 25.00
}
]
},
{
"groupInstanceId": "gp234567-8901-bcde-f123-456789012345",
"groupCode": "GP-B2Y8M5",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopName": "TechWorld Electronics",
"groupPrice": 80000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 7,
"seatsRemaining": 3,
"totalParticipants": 5,
"progressPercentage": 70.00,
"status": "OPEN",
"expiresAt": "2025-10-02T18:45:30",
"isExpired": false,
"isUserMember": true,
"participants": [
{
"userId": "user4567-8901-23de-f234-567890123456",
"userName": "alice_brown",
"userProfilePicture": "https://cdn.nextgate.com/profiles/alice.jpg",
"quantity": 3,
"contributionPercentage": 42.86
}
]
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| groupInstanceId | Unique identifier for the group |
| groupCode | Human-readable group code (e.g., GP-A3X7K9) |
| productName | Name of the product in this group |
| productImage | Product image URL |
| shopName | Shop selling this product |
| groupPrice | Discounted price per unit in TZS |
| savingsPercentage | Percentage saved vs regular price |
| currency | Currency code (TZS) |
| totalSeats | Maximum number of seats in this group |
| seatsOccupied | Number of seats currently filled |
| seatsRemaining | Available seats remaining |
| totalParticipants | Number of unique participants |
| progressPercentage | Group completion percentage (0-100) |
| status | Group status (OPEN, COMPLETED, FAILED, DELETED) |
| expiresAt | When the group expires |
| isExpired | Whether the group has expired |
| isUserMember | Whether authenticated user is in this group |
| participants | Array of participant previews |
| participants[].userId | Participant's user ID |
| participants[].userName | Participant's username |
| participants[].userProfilePicture | Participant's profile picture URL |
| participants[].quantity | Number of seats this participant holds |
| participants[].contributionPercentage | Percentage of total seats this participant holds |
Error Response Examples:
Not Found - Product Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Product not found",
"action_time": "2025-10-02T14:30:45",
"data": "Product not found"
}
2. Get Group by ID
Purpose: Retrieves detailed information about a specific group purchase instance including all participants and their histories.
Endpoint: GET {base_url}/group-purchases/{groupId}
Access Level: π Protected (Requires Authentication)
Authentication: Bearer Token required in Authorization header
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| groupId | string (UUID) | Yes | Unique identifier of the group | Valid UUID format |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Group retrieved successfully",
"action_time": "2025-10-02T14:35:45",
"data": {
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productId": "prod1234-5678-90ab-cdef-123456789012",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopId": "shop1234-5678-90ab-cdef-123456789012",
"shopName": "TechWorld Electronics",
"shopLogo": "https://cdn.nextgate.com/shops/techworld-logo.jpg",
"regularPrice": 150000.00,
"groupPrice": 80000.00,
"savingsAmount": 70000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 4,
"seatsRemaining": 6,
"totalParticipants": 3,
"progressPercentage": 40.00,
"status": "OPEN",
"isExpired": false,
"isFull": false,
"initiatorId": "user1234-5678-90ab-cdef-123456789012",
"initiatorName": "john_doe",
"durationHours": 24,
"createdAt": "2025-10-01T20:30:45",
"expiresAt": "2025-10-02T20:30:45",
"completedAt": null,
"maxPerCustomer": 5,
"isUserMember": true,
"myParticipantId": "part1234-5678-90ab-cdef-123456789012",
"myQuantity": 2,
"participants": [
{
"participantId": "part1234-5678-90ab-cdef-123456789012",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"totalPaid": 160000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-01T20:30:45",
"contributionPercentage": 50.00,
"purchaseCount": 1,
"hasTransferred": false,
"purchaseHistory": [
{
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"quantity": 2,
"amountPaid": 160000.00,
"purchasedAt": "2025-10-01T20:30:45",
"transactionId": "txn_1234567890abcdef"
}
],
"transferHistory": []
},
{
"participantId": "part2345-6789-01bc-def1-234567890123",
"userId": "user2345-6789-01bc-def1-234567890123",
"userName": "jane_smith",
"userProfilePicture": "https://cdn.nextgate.com/profiles/jane.jpg",
"quantity": 1,
"totalPaid": 80000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-01T21:15:20",
"contributionPercentage": 25.00,
"purchaseCount": 1,
"hasTransferred": false
},
{
"participantId": "part3456-7890-12cd-ef12-345678901234",
"userId": "user3456-7890-12cd-ef12-345678901234",
"userName": "bob_wilson",
"userProfilePicture": null,
"quantity": 1,
"totalPaid": 80000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-02T08:45:10",
"contributionPercentage": 25.00,
"purchaseCount": 1,
"hasTransferred": false
}
]
}
}
Success Response Fields:
| Field | Description |
|---|---|
| groupInstanceId | Unique identifier for the group |
| groupCode | Human-readable group code |
| productId | Product unique identifier |
| productName | Product name |
| productImage | Product image URL |
| shopId | Shop identifier |
| shopName | Shop name |
| shopLogo | Shop logo URL |
| regularPrice | Original product price in TZS |
| groupPrice | Discounted group price in TZS |
| savingsAmount | Amount saved per unit (regularPrice - groupPrice) |
| savingsPercentage | Percentage saved |
| currency | Currency code (TZS) |
| totalSeats | Maximum seats in group |
| seatsOccupied | Filled seats |
| seatsRemaining | Available seats |
| totalParticipants | Unique participants count |
| progressPercentage | Completion percentage |
| status | OPEN, COMPLETED, FAILED, or DELETED |
| isExpired | Whether group expired |
| isFull | Whether all seats filled |
| initiatorId | User who created the group |
| initiatorName | Initiator's username |
| durationHours | Group duration in hours |
| createdAt | Group creation timestamp |
| expiresAt | Expiration timestamp |
| completedAt | Completion timestamp (null if not completed) |
| maxPerCustomer | Maximum seats per customer |
| isUserMember | Whether authenticated user is member |
| myParticipantId | User's participant ID (if member) |
| myQuantity | User's seat quantity (if member) |
| participants | Array of detailed participant information |
| participants[].participantId | Participant unique identifier |
| participants[].userId | User ID |
| participants[].userName | Username |
| participants[].userProfilePicture | Profile picture URL |
| participants[].quantity | Number of seats held |
| participants[].totalPaid | Total amount paid in TZS |
| participants[].status | ACTIVE, TRANSFERRED_OUT, or REFUNDED |
| participants[].joinedAt | Join timestamp |
| participants[].contributionPercentage | Percentage of total seats |
| participants[].purchaseCount | Number of purchases made |
| participants[].hasTransferred | Whether participated in transfers |
| participants[].purchaseHistory | Purchase records (only shown to participant owner) |
| participants[].transferHistory | Transfer records (only shown to participant owner) |
Error Response Examples:
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Group not found with ID: gp123456-7890-abcd-ef12-345678901234",
"action_time": "2025-10-02T14:35:45",
"data": "Group not found with ID: gp123456-7890-abcd-ef12-345678901234"
}
3. Get Group by Code
Purpose: Retrieves group information using the human-readable group code instead of UUID.
Endpoint: GET {base_url}/group-purchases/code/{groupCode}
Access Level: π Protected (Requires Authentication)
Authentication: Bearer Token required in Authorization header
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
Path Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| groupCode | string | Yes | Group code (e.g., GP-A3X7K9) | Format: GP-XXXXXX (6 alphanumeric characters) |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Group retrieved successfully",
"action_time": "2025-10-02T14:40:45",
"data": {
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productId": "prod1234-5678-90ab-cdef-123456789012",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopId": "shop1234-5678-90ab-cdef-123456789012",
"shopName": "TechWorld Electronics",
"shopLogo": "https://cdn.nextgate.com/shops/techworld-logo.jpg",
"regularPrice": 150000.00,
"groupPrice": 80000.00,
"savingsAmount": 70000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 4,
"seatsRemaining": 6,
"totalParticipants": 3,
"progressPercentage": 40.00,
"status": "OPEN",
"isExpired": false,
"isFull": false,
"initiatorId": "user1234-5678-90ab-cdef-123456789012",
"initiatorName": "john_doe",
"durationHours": 24,
"createdAt": "2025-10-01T20:30:45",
"expiresAt": "2025-10-02T20:30:45",
"completedAt": null,
"maxPerCustomer": 5,
"isUserMember": false,
"myParticipantId": null,
"myQuantity": null,
"participants": []
}
}
Success Response Fields:
| Field | Description |
|---|---|
| All fields | Same as "Get Group by ID" response |
Error Response Examples:
Not Found (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "Group not found with code: GP-INVALID",
"action_time": "2025-10-02T14:40:45",
"data": "Group not found with code: GP-INVALID"
}
4. Get My Groups
Purpose: Retrieves all groups that the authenticated user is a member of, optionally filtered by status.
Endpoint: GET {base_url}/group-purchases/my-groups
Access Level: π Protected (Requires Authentication)
Authentication: Bearer Token required in Authorization header
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
Query Parameters:
| Parameter | Type | Required | Description | Validation | Default |
|---|---|---|---|---|---|
| status | string | No | Filter by group status | enum: OPEN, COMPLETED, FAILED, DELETED | null (all statuses) |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "My groups retrieved successfully",
"action_time": "2025-10-02T14:45:45",
"data": [
{
"groupInstanceId": "gp123456-7890-abcd-ef12-345678901234",
"groupCode": "GP-A3X7K9",
"productName": "Premium Wireless Headphones",
"productImage": "https://cdn.nextgate.com/products/headphones-001.jpg",
"shopName": "TechWorld Electronics",
"groupPrice": 80000.00,
"savingsPercentage": 46.67,
"currency": "TZS",
"totalSeats": 10,
"seatsOccupied": 8,
"seatsRemaining": 2,
"totalParticipants": 5,
"progressPercentage": 80.00,
"status": "OPEN",
"expiresAt": "2025-10-02T20:30:45",
"isExpired": false,
"isUserMember": true,
"participants": [
{
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"contributionPercentage": 25.00
}
]
},
{
"groupInstanceId": "gp234567-8901-bcde-f123-456789012345",
"groupCode": "GP-B2Y8M5",
"productName": "Smart Watch Series 5",
"productImage": "https://cdn.nextgate.com/products/watch-005.jpg",
"shopName": "Gadget Hub",
"groupPrice": 250000.00,
"savingsPercentage": 28.57,
"currency": "TZS",
"totalSeats": 15,
"seatsOccupied": 15,
"seatsRemaining": 0,
"totalParticipants": 8,
"progressPercentage": 100.00,
"status": "COMPLETED",
"expiresAt": "2025-10-01T18:20:30",
"isExpired": false,
"isUserMember": true,
"participants": []
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| All fields | Same as "Get Available Groups for Product" response |
5. Get My Participations
Purpose: Retrieves all active participations of the authenticated user across all groups with detailed participant information.
Endpoint: GET {base_url}/group-purchases/my-participations
Access Level: π Protected (Requires Authentication)
Authentication: Bearer Token required in Authorization header
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "My participations retrieved successfully",
"action_time": "2025-10-02T14:50:45",
"data": [
{
"participantId": "part1234-5678-90ab-cdef-123456789012",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 2,
"totalPaid": 160000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-01T20:30:45",
"purchaseCount": 1,
"hasTransferred": false,
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"purchaseHistory": [
{
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"quantity": 2,
"amountPaid": 160000.00,
"purchasedAt": "2025-10-01T20:30:45",
"transactionId": "txn_1234567890abcdef"
}
],
"transferHistory": []
},
{
"participantId": "part2345-6789-01bc-def1-234567890123",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 3,
"totalPaid": 750000.00,
"status": "ACTIVE",
"joinedAt": "2025-10-02T10:15:20",
"purchaseCount": 2,
"hasTransferred": true,
"checkoutSessionId": "checkout2-3456-7890-1bcd-ef2345678901",
"purchaseHistory": [
{
"checkoutSessionId": "checkout2-3456-7890-1bcd-ef2345678901",
"quantity": 1,
"amountPaid": 250000.00,
"purchasedAt": "2025-10-02T10:15:20",
"transactionId": "txn_2345678901bcdef0"
},
{
"checkoutSessionId": "checkout3-4567-8901-2cde-f34567890123",
"quantity": 2,
"amountPaid": 500000.00,
"purchasedAt": "2025-10-02T12:30:15",
"transactionId": "txn_3456789012cdef01"
}
],
"transferHistory": [
{
"fromGroupId": "gp345678-9012-cdef-1234-567890123456",
"fromGroupCode": null,
"toGroupId": "gp234567-8901-bcde-f123-456789012345",
"toGroupCode": null,
"transferredAt": "2025-10-02T11:45:30",
"reason": "Transferred 1 seats from group GP-C3Z9N7"
}
]
}
]
}
Success Response Fields:
| Field | Description |
|---|---|
| participantId | Participant unique identifier |
| userId | User ID of the participant |
| userName | Username |
| userProfilePicture | Profile picture URL |
| quantity | Total seats held |
| totalPaid | Total amount paid in TZS |
| status | ACTIVE, TRANSFERRED_OUT, or REFUNDED |
| joinedAt | When user joined this group |
| purchaseCount | Number of purchases made in this group |
| hasTransferred | Whether user has transfer history |
| checkoutSessionId | Original checkout session ID |
| purchaseHistory | Array of all purchases in this group |
| purchaseHistory[].checkoutSessionId | Checkout session for this purchase |
| purchaseHistory[].quantity | Seats purchased |
| purchaseHistory[].amountPaid | Amount paid for this purchase |
| purchaseHistory[].purchasedAt | Purchase timestamp |
| purchaseHistory[].transactionId | Payment transaction ID |
| transferHistory | Array of all transfers involving this participation |
| transferHistory[].fromGroupId | Source group ID |
| transferHistory[].toGroupId | Target group ID |
| transferHistory[].transferredAt | Transfer timestamp |
| transferHistory[].reason | Transfer reason/description |
6. Transfer Seats Between Groups
Purpose: Transfers seats from one group to another. Allows users to move their purchases between compatible groups (same product, shop, and price).
Endpoint: POST {base_url}/group-purchases/transfer
Access Level: π Protected (Requires Authentication)
Authentication: Bearer Token required in Authorization header
Request Headers:
| Header | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer token for authenticated user |
| Content-Type | string | Yes | Must be application/json |
Request JSON Sample:
{
"sourceGroupId": "gp123456-7890-abcd-ef12-345678901234",
"targetGroupId": "gp234567-8901-bcde-f123-456789012345",
"quantity": 2
}
Request Body Parameters:
| Parameter | Type | Required | Description | Validation |
|---|---|---|---|---|
| sourceGroupId | string (UUID) | Yes | Group to transfer from | Valid UUID, user must be active member |
| targetGroupId | string (UUID) | Yes | Group to transfer to | Valid UUID, must be different from source |
| quantity | integer | Yes | Number of seats to transfer | Min: 1, cannot exceed user's quantity in source group |
Success Response JSON Sample:
{
"success": true,
"httpStatus": "OK",
"message": "Seats transferred successfully",
"action_time": "2025-10-02T15:00:45",
"data": {
"participantId": "part2345-6789-01bc-def1-234567890123",
"userId": "user1234-5678-90ab-cdef-123456789012",
"userName": "john_doe",
"userProfilePicture": "https://cdn.nextgate.com/profiles/john.jpg",
"quantity": 3,
"totalPaid": 0.00,
"status": "ACTIVE",
"joinedAt": "2025-10-02T15:00:45",
"purchaseCount": 0,
"hasTransferred": true,
"checkoutSessionId": "checkout1-2345-6789-0abc-def123456789",
"purchaseHistory": [],
"transferHistory": [
{
"fromGroupId": "gp123456-7890-abcd-ef12-345678901234",
"fromGroupCode": null,
"toGroupId": "gp234567-8901-bcde-f123-456789012345",
"toGroupCode": null,
"transferredAt": "2025-10-02T15:00:45",
"reason": "Transferred 2 seats from group GP-A3X7K9"
}
]
}
}
Success Response Fields:
| Field | Description |
|---|---|
| participantId | Updated participant ID in target group |
| userId | User ID |
| userName | Username |
| userProfilePicture | Profile picture URL |
| quantity | New total quantity in target group |
| totalPaid | Total paid (0 for transfers) |
| status | Participant status (ACTIVE) |
| joinedAt | Join timestamp (current time if new to target) |
| purchaseCount | Purchase count (0 for pure transfers) |
| hasTransferred | Always true for transferred participants |
| checkoutSessionId | Original checkout session ID |
| purchaseHistory | Purchase history (shown only to owner) |
| transferHistory | Transfer history including this transfer |
Error Response Examples:
Bad Request - Same Source and Target (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Source and target groups must be different",
"action_time": "2025-10-02T15:00:45",
"data": "Source and target groups must be different"
}
Bad Request - Insufficient Seats (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Not enough seats to transfer. You have: 1, requested: 2",
"action_time": "2025-10-02T15:00:45",
"data": "Not enough seats to transfer. You have: 1, requested: 2"
}
Bad Request - Target Group Full (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Not enough seats available. Requested: 2, Available: 1",
"action_time": "2025-10-02T15:00:45",
"data": "Not enough seats available. Requested: 2, Available: 1"
}
Bad Request - Product Mismatch (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot transfer between groups with different products",
"action_time": "2025-10-02T15:00:45",
"data": "Cannot transfer between groups with different products"
}
Bad Request - Price Mismatch (400):
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot transfer. Price mismatch: 80000.00 vs 75000.00",
"action_time": "2025-10-02T15:00:45",
"data": "Cannot transfer. Price mismatch: 80000.00 vs 75000.00"
}
Not Found - Not a Participant (404):
{
"success": false,
"httpStatus": "NOT_FOUND",
"message": "You are not a participant in the source group",
"action_time": "2025-10-02T15:00:45",
"data": "You are not a participant in the source group"
}
Group Purchase Workflow
1. Creating a New Group
When a user makes a GROUP_PURCHASE checkout and no groupInstanceId is provided in metadata:
Flow:
- User creates checkout session with
sessionType: GROUP_PURCHASE - User processes payment (WALLET only)
- Payment completes successfully
- System automatically creates new group instance
- User becomes first participant (initiator)
- Group gets unique code (e.g., GP-A3X7K9)
- Group status set to OPEN
- Expiration set based on product's
groupTimeLimitHours
2. Joining an Existing Group
When a user makes a GROUP_PURCHASE checkout with groupInstanceId in metadata:
Flow:
- User finds available group (via product page or group code)
- User creates checkout session with
sessionType: GROUP_PURCHASE - User includes
groupInstanceIdin checkout metadata - User processes payment (WALLET only)
- Payment completes successfully
- System adds user to existing group
- Group's
seatsOccupiedincreases - Group's
totalParticipantsincreases (if new member)
Note: User can join same group multiple times to buy more seats (Hybrid Approach)
3. Group Completion
When a group fills all seats:
Automatic Actions:
- Group status changes to COMPLETED
completedAttimestamp recorded- Orders created for all participants
- Inventory permanently deducted
- No new participants allowed
4. Group Expiration
When a group reaches expiration time without filling:
Automatic Actions:
- Group status changes to FAILED
- All participants refunded
- Inventory holds released
- Participant status changes to REFUNDED
5. Transferring Between Groups
Users can transfer seats between compatible groups:
Compatibility Requirements:
- Same product
- Same shop
- Same group price
- Target group must be OPEN
- Target group not expired
- Target group has available seats
Transfer Types:
Partial Transfer:
- Transfer some seats, keep some in source
- User remains ACTIVE in both groups
- Source group seats reduced
- Target group seats increased
Full Transfer:
- Transfer all seats from source
- Source participant status β TRANSFERRED_OUT
- Source group participants count decreased
- If source group becomes empty β soft deleted
Group Status Definitions
| Status | Description | Can Join? | Can Transfer From? | Can Transfer To? |
|---|---|---|---|---|
| OPEN | Active, accepting participants | Yes | Yes | Yes |
| COMPLETED | All seats filled, orders created | No | No | No |
| FAILED | Expired without filling, refunds issued | No | No | No |
| DELETED | Soft deleted (empty or admin action) | No | No | No |
Participant Status Definitions
| Status | Description | Still in Group? | Can Buy More? | Can Transfer? |
|---|---|---|---|---|
| ACTIVE | Currently participating in group | Yes | Yes | Yes |
| TRANSFERRED_OUT | Left this group via transfer | No | No | No |
| REFUNDED | Group failed, money refunded | No | No | No |
Purchase History Tracking
Each participant maintains detailed purchase history:
Tracked Information:
- Checkout session ID
- Quantity purchased
- Amount paid
- Purchase timestamp
- Transaction ID
Use Cases:
- User buys 2 seats initially
- User joins same group again, buys 3 more seats
- Purchase history shows 2 separate purchases
- Total quantity: 5 seats
- Total paid: sum of both purchases
Transfer History Tracking
Each transfer is recorded in participant history:
Tracked Information:
- Source group ID and code
- Target group ID and code
- Transfer timestamp
- Transfer reason/description
Transfer Scenarios:
Scenario 1: Partial Transfer
- User has 5 seats in Group A
- Transfers 2 seats to Group B
- Group A participant: 3 seats, ACTIVE status
- Group B participant: 2 seats, ACTIVE status, transfer history added
Scenario 2: Full Transfer
- User has 3 seats in Group A
- Transfers all 3 seats to Group B
- Group A participant: 0 seats, TRANSFERRED_OUT status
- Group B participant: 3 seats, ACTIVE status, transfer history added
Scenario 3: Multiple Transfers
- User transfers from Group A to Group B
- Later transfers from Group B to Group C
- Full transfer history maintained in each participation
Group Expiration and Cleanup
Automatic Expiration
Scheduled Job runs periodically to:
- Find groups with status=OPEN and expiresAt < now
- Change status to FAILED
- Initiate refunds for all participants
- Update participant status to REFUNDED
- Release inventory holds
Soft Deletion
Groups are soft-deleted when:
- All participants transfer out (empty group)
- Admin manually deletes group
Soft Delete Actions:
isDeletedset to truestatuschanged to DELETEDdeletedAttimestamp recordeddeletedByuser ID recordeddeleteReasonstored- Group still queryable but excluded from active lists
Business Rules
Maximum Seats Per Customer
If product has maxPerCustomer limit:
- Single purchase cannot exceed limit
- Multiple purchases in same group respect limit
- Transfer to group validates combined quantity
Example:
- Product has maxPerCustomer = 5
- User has 3 seats in Group A
- Tries to transfer to Group B where they have 3 seats
- Transfer rejected (3 + 3 = 6 > 5)
Group Price Lock
Group price is snapshot at creation:
- Price stored in group instance
- Transfers validate price match
- Product price changes don't affect existing groups
Inventory Management
During Group Lifecycle:
- Seats held in inventory when purchased
- Holds maintained until group completes or fails
- Completed: inventory permanently deducted
- Failed: inventory holds released
Transfer Impact:
- No inventory change during transfer
- Total inventory hold remains same
- Just moves between groups
Integration with Checkout Sessions
Creating New Group
Checkout Session Requirements:
sessionType: GROUP_PURCHASE- Exactly 1 item
- WALLET payment only
- No
groupInstanceIdin metadata - Status: PAYMENT_COMPLETED
After Payment Success:
GroupPurchaseInstanceEntity group = groupPurchaseService.createGroupInstance(checkoutSession);
Joining Existing Group
Checkout Session Requirements:
sessionType: GROUP_PURCHASE- Exactly 1 item
- WALLET payment only
groupInstanceIdin metadata- Status: PAYMENT_COMPLETED
After Payment Success:
UUID groupId = (UUID) checkoutSession.getMetadata().get("groupInstanceId");
GroupPurchaseInstanceEntity group = groupPurchaseService.joinGroup(groupId, checkoutSession);
Error Handling Best Practices
Common Error Scenarios
Product Not Available for Group Buying:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group buying is not enabled for this product"
}
Action: Check product has groupBuyingEnabled: true
Group Expired:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group has expired at: 2025-10-02T20:30:45"
}
Action: Find another available group or create new group
Group Full:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Group is full. Seats occupied: 10/10"
}
Action: Find another available group or create new group
Quantity Exceeds Group Size:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Quantity (8) exceeds group max size (5)"
}
Action: Reduce quantity or create multiple purchases
Transfer Between Incompatible Groups:
{
"success": false,
"httpStatus": "BAD_REQUEST",
"message": "Cannot transfer between groups from different shops"
}
Action: Only transfer between compatible groups
Quick Reference Guide
Endpoint Summary
| Endpoint | Method | Purpose |
|---|---|---|
/group-purchases/product/{productId}/available |
GET | Get available groups for product |
/group-purchases/{groupId} |
GET | Get group details by ID |
/group-purchases/code/{groupCode} |
GET | Get group details by code |
/group-purchases/my-groups |
GET | Get user's groups |
/group-purchases/my-participations |
GET | Get user's participations |
/group-purchases/transfer |
POST | Transfer seats between groups |
Common HTTP Status Codes
200 OK: Successful operation400 Bad Request: Validation error or business rule violation401 Unauthorized: Authentication required404 Not Found: Group, product, or participation not found
Group Code Format
- Pattern:
GP-XXXXXX - Length: 9 characters (GP- + 6 alphanumeric)
- Example:
GP-A3X7K9,GP-B2Y8M5 - Auto-generated at group creation
Participant Contribution Calculation
contributionPercentage = (participantQuantity / totalSeatsOccupied) Γ 100
Progress Calculation
progressPercentage = (seatsOccupied / totalSeats) Γ 100
Savings Calculation
savingsAmount = regularPrice - groupPrice
savingsPercentage = (savingsAmount / regularPrice) Γ 100
Testing
Test Scenarios
Scenario 1: Create and Complete Group
- User A creates group (2 seats)
- User B joins group (3 seats)
- User C joins group (5 seats)
- Group auto-completes
- Orders created for all participants
Scenario 2: Transfer Between Groups
- User A in Group 1 (3 seats)
- User A transfers 2 seats to Group 2
- User A remains in Group 1 (1 seat)
- User A now in Group 2 (2 seats)
Scenario 3: Group Expiration
- Create group with 1-minute expiration
- Wait for expiration
- Scheduled job processes
- Status β FAILED
- Participants refunded
Scenario 4: Hybrid Approach - Multiple Purchases
- User A creates group (2 seats)
- User A joins same group again (3 seats)
- User A total: 5 seats
- Purchase history shows 2 records
Β© 2025 NexGate. All rights reserved.
No comments to display
No comments to display