Claim Bonus
Claim a one-time easter egg bonus
/members/me/features/discover/bonuses/{code}/claimOverview
Claim a one-time easter egg bonus. Easter eggs are special promotional bonuses that provide gift points to members. Each bonus can only be claimed once per member and awards non-tier-eligible gift points.
The system automatically validates:
- Bonus code exists and is active
- Member hasn't already claimed this bonus
- Awards appropriate points via the ledger system
Authentication
Requires a valid Clerk JWT token in the Authorization header.
Path Parameters
codepathstringrequiredThe easter egg bonus code to claim
Request
curl -X POST "http://localhost:3000/v1/members/me/features/discover/bonuses/WELCOME2024/claim" \
-H "Authorization: Bearer YOUR_CLERK_JWT"Response
Returns the details of the claimed bonus including points awarded and timestamp.
eggCodestringrequiredThe bonus code that was claimed
displayNamestringrequiredHuman-readable name of the bonus
pointsAwardednumberrequiredNumber of gift points awarded to the member
claimedAtstringrequiredISO 8601 timestamp when the bonus was claimed
Response
{
"eggCode": "WELCOME2024",
"displayName": "Welcome to Journey 2024",
"pointsAwarded": 5000,
"claimedAt": "2024-04-20T15:30:45Z"
}Gift Points vs Earning Points
Gift Points Characteristics
- Non-tier-eligible: Don't count toward tier progression
- Immediate availability: Added to wallet immediately
- Promotional source: Marked with
giftSource: 'promotion' - One-time only: Each easter egg can only be claimed once per member
Ledger Transaction
// The service creates a gift transaction with metadata
const giftResult = await ledgerFacade.giftPoints({
memberId,
amount: egg.pointValue,
reason: `Easter egg bonus: ${egg.friendlyName}`,
giftSource: 'promotion',
metadata: {
easterEggCode: egg.code,
easterEggId: egg.id,
easterEggDisplayName: egg.displayName,
},
});Use Cases
Claim Flow with Validation
const handleBonusClaim = async (code: string) => {
try {
// Check if already claimed
const status = await getBonusStatus(code);
if (status.claimed) {
return { success: false, message: 'Bonus already claimed' };
}
// Attempt claim
const result = await claimBonus(code);
// Show success message
showSuccessToast(`Claimed ${result.pointsAwarded.toLocaleString()} points!`);
// Refresh wallet balance
refreshWalletBalance();
return { success: true, result };
} catch (error) {
if (error.status === 409) {
return { success: false, message: 'Bonus already claimed' };
}
if (error.status === 404) {
return { success: false, message: 'Invalid bonus code' };
}
return { success: false, message: 'Failed to claim bonus' };
}
};Bonus Discovery UI
const BonusClaimCard = ({ bonus }) => {
const [claiming, setClaiming] = useState(false);
const [claimed, setClaimed] = useState(false);
const [claimResult, setClaimResult] = useState(null);
const handleClaim = async () => {
setClaiming(true);
try {
const result = await claimBonus(bonus.code);
setClaimResult(result);
setClaimed(true);
// Animate points addition
animatePointsIncrease(result.pointsAwarded);
} catch (error) {
showError('Failed to claim bonus');
} finally {
setClaiming(false);
}
};
return (
<div className="bonus-card">
<h3>{bonus.displayName}</h3>
<div className="points-value">
{bonus.pointValue.toLocaleString()} Points
</div>
{claimed ? (
<div className="claimed-status">
<CheckIcon />
<span>Claimed {claimResult?.claimedAt && formatDate(claimResult.claimedAt)}</span>
</div>
) : (
<button
onClick={handleClaim}
disabled={claiming}
className="claim-button"
>
{claiming ? <Spinner /> : 'Claim Bonus'}
</button>
)}
</div>
);
};Wallet Integration
const trackBonusClaim = async (claimResult: ClaimEasterEggResult) => {
// Track analytics event
analytics.track('Bonus Claimed', {
bonusCode: claimResult.eggCode,
pointsAwarded: claimResult.pointsAwarded,
claimedAt: claimResult.claimedAt
});
// Update local wallet state
updateWalletBalance(currentBalance + claimResult.pointsAwarded);
// Add transaction to activity feed
addTransaction({
type: 'gift',
amount: claimResult.pointsAwarded,
description: `Easter egg bonus: ${claimResult.displayName}`,
createdAt: claimResult.claimedAt
});
};Error Handling
Conflict Detection
The service automatically prevents double-claiming by checking for existing claims before processing. If a member attempts to claim the same bonus twice, they receive a 409 Conflict error.
Validation Flow
const claimWithRetry = async (code: string, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await claimBonus(code);
} catch (error) {
if (error.status === 409 || error.status === 404) {
// Don't retry for conflict or not found
throw error;
}
if (attempt === maxRetries) {
throw new Error('Failed to claim bonus after retries');
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
};Ledger Integration
Transaction Creation
When a bonus is claimed, the system:
- Validates the bonus code and member eligibility
- Creates a gift points transaction in the ledger
- Records the claim in the easter eggs system
- Returns claim details to the client
Metadata Tracking
// Transaction metadata includes:
{
easterEggCode: 'WELCOME2024',
easterEggId: 123,
easterEggDisplayName: 'Welcome to Journey 2024'
}This metadata allows for easy tracking and reporting of bonus claims across the platform.
Related Endpoints
- Get Bonus Status - Check if bonus is already claimed
- Get Wallet Balance - View updated points balance
- Get Wallet Activity - View claim transaction details
- Get Earn More Gallery - Properties with bonus campaigns