feat: add referral
This commit is contained in:
@@ -148,8 +148,8 @@ public function completeSession(Request $request)
|
|||||||
'duration' => $data['duration'] ?? $template->duration,
|
'duration' => $data['duration'] ?? $template->duration,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Increase XP
|
// Increase XP (also credits the referrer's 10% share)
|
||||||
auth()->user()->increment('xp', 10);
|
auth()->user()->awardXp(10);
|
||||||
|
|
||||||
return response()->json(['message' => 'Session completed', 'session' => $session]);
|
return response()->json(['message' => 'Session completed', 'session' => $session]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public function storeUserMood(Request $request)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->increment('xp', 10);
|
$user->awardXp(10);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Mood saved successfully',
|
'message' => 'Mood saved successfully',
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ public function loginV2(Request $request)
|
|||||||
'identifier' => $response['uuid'] ?? null,
|
'identifier' => $response['uuid'] ?? null,
|
||||||
'password' => config('app.default_password'),
|
'password' => config('app.default_password'),
|
||||||
// 'name' => trim(($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? '')),
|
// 'name' => trim(($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? '')),
|
||||||
'mobile' => $mobile,
|
'mobile' => $mobile,
|
||||||
'first_name' => $response['first_name'] ?? null,
|
'first_name' => $response['first_name'] ?? null,
|
||||||
'last_name' => $response['last_name'] ?? null
|
'last_name' => $response['last_name'] ?? null,
|
||||||
|
'referral_code' => $response['referral_code'] ?? null,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
// Only update name and mobile for existing user
|
// Only update name and mobile for existing user
|
||||||
@@ -71,10 +72,13 @@ public function loginV2(Request $request)
|
|||||||
$user->last_name = $response['last_name'] ?? $user->last_name;
|
$user->last_name = $response['last_name'] ?? $user->last_name;
|
||||||
// $user->name = trim(($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? ''));
|
// $user->name = trim(($response['first_name'] ?? '') . ' ' . ($response['last_name'] ?? ''));
|
||||||
$user->mobile = $mobile ?? $user->mobile;
|
$user->mobile = $mobile ?? $user->mobile;
|
||||||
|
$user->referral_code = $response['referral_code'] ?? $user->referral_code;
|
||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link the referral relationship and reward the referrer (کد معرف)
|
||||||
|
$this->linkReferral($user, $response);
|
||||||
|
|
||||||
$access_token = $user->createToken('user')->plainTextToken;
|
$access_token = $user->createToken('user')->plainTextToken;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -111,6 +115,7 @@ private function syncUserFromStatus(array $response, ?User $user = null): User
|
|||||||
'mobile' => $mobile,
|
'mobile' => $mobile,
|
||||||
'first_name' => $response['first_name'] ?? null,
|
'first_name' => $response['first_name'] ?? null,
|
||||||
'last_name' => $response['last_name'] ?? null,
|
'last_name' => $response['last_name'] ?? null,
|
||||||
|
'referral_code' => $response['referral_code'] ?? null,
|
||||||
'email_verified_at' => $response['email_verified_at'] ?? null
|
'email_verified_at' => $response['email_verified_at'] ?? null
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
@@ -118,13 +123,68 @@ private function syncUserFromStatus(array $response, ?User $user = null): User
|
|||||||
$user->first_name = $response['first_name'] ?? $user->first_name;
|
$user->first_name = $response['first_name'] ?? $user->first_name;
|
||||||
$user->last_name = $response['last_name'] ?? $user->last_name;
|
$user->last_name = $response['last_name'] ?? $user->last_name;
|
||||||
$user->mobile = $mobile ?? $user->mobile;
|
$user->mobile = $mobile ?? $user->mobile;
|
||||||
|
$user->referral_code = $response['referral_code'] ?? $user->referral_code;
|
||||||
$user->email_verified_at = $response['email_verified_at'] ?? $user->email_verified_at;
|
$user->email_verified_at = $response['email_verified_at'] ?? $user->email_verified_at;
|
||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link the referral relationship and reward the referrer (کد معرف)
|
||||||
|
$this->linkReferral($user, $response);
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the approagency referrer (by uuid/identifier) to a local
|
||||||
|
* meditation user, record the relationship once, and grant the referrer
|
||||||
|
* their per-invite points. The free-month subscription milestone is
|
||||||
|
* handled in approagency, where products/plans/transactions are managed.
|
||||||
|
*/
|
||||||
|
private function linkReferral(User $user, array $response): void
|
||||||
|
{
|
||||||
|
if ($user->referred_by) {
|
||||||
|
return; // already linked — never reward twice
|
||||||
|
}
|
||||||
|
|
||||||
|
$referrerUuid = $response['referrer_uuid'] ?? null;
|
||||||
|
if (!$referrerUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$referrer = User::where('identifier', $referrerUuid)->first();
|
||||||
|
if (!$referrer || $referrer->id === $user->id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->referred_by = $referrer->id;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
// 100 points per successful invite
|
||||||
|
$referrer->increment('referral_points', User::REFERRAL_POINTS_PER_INVITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Referral dashboard data (دعوت دوستان page).
|
||||||
|
*/
|
||||||
|
public function referral(Request $request)
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$invites = $user->referrals()->count();
|
||||||
|
$target = User::REFERRAL_SUBSCRIPTION_TARGET;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'referral_code' => $user->referral_code,
|
||||||
|
'referral_points' => (int) $user->referral_points,
|
||||||
|
'successful_invites' => $invites,
|
||||||
|
'subscription_target' => $target,
|
||||||
|
'remaining_to_subscription' => max(0, $target - $invites),
|
||||||
|
'rewards' => [
|
||||||
|
'points_per_invite' => User::REFERRAL_POINTS_PER_INVITE,
|
||||||
|
'friend_points_share_percent' => (int) (User::REFERRAL_SHARE * 100),
|
||||||
|
'subscription_invite_target' => $target,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function loginForeginer(Request $request)
|
public function loginForeginer(Request $request)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ class User extends Authenticatable
|
|||||||
'male' => 1,
|
'male' => 1,
|
||||||
'female' => 2,
|
'female' => 2,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Referral program (کد معرف)
|
||||||
|
const REFERRAL_POINTS_PER_INVITE = 100; // points granted to the referrer per successful invite
|
||||||
|
const REFERRAL_SHARE = 0.10; // referrer keeps 10% of each friend's earned xp, forever
|
||||||
|
const REFERRAL_SUBSCRIPTION_TARGET = 10; // successful invites needed for the free-month milestone
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
*
|
*
|
||||||
@@ -69,6 +75,32 @@ public function otpTokens()
|
|||||||
return $this->hasMany(OtpTokens::class);
|
return $this->hasMany(OtpTokens::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function referrer()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'referred_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function referrals()
|
||||||
|
{
|
||||||
|
return $this->hasMany(User::class, 'referred_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Award xp to this user and, if they were referred, credit the
|
||||||
|
* referrer their permanent 10% share of the earned points.
|
||||||
|
*/
|
||||||
|
public function awardXp(int $amount): void
|
||||||
|
{
|
||||||
|
$this->increment('xp', $amount);
|
||||||
|
|
||||||
|
if ($this->referred_by) {
|
||||||
|
$share = (int) floor($amount * self::REFERRAL_SHARE);
|
||||||
|
if ($share > 0) {
|
||||||
|
self::where('id', $this->referred_by)->increment('referral_points', $share);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function transactions()
|
public function transactions()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Transaction::class);
|
return $this->hasMany(Transaction::class);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
// Own shareable code, mirrored from approagency (source of truth).
|
||||||
|
if (!Schema::hasColumn('users', 'referral_code')) {
|
||||||
|
$table->string('referral_code', 12)->nullable()->after('identifier');
|
||||||
|
}
|
||||||
|
// Local meditation user who referred this user (resolved from approagency's referrer_uuid).
|
||||||
|
if (!Schema::hasColumn('users', 'referred_by')) {
|
||||||
|
$table->foreignId('referred_by')->nullable()->after('referral_code')
|
||||||
|
->constrained('users')->nullOnDelete();
|
||||||
|
}
|
||||||
|
// Points earned through referrals (100 per invite + 10% of friends' xp).
|
||||||
|
if (!Schema::hasColumn('users', 'referral_points')) {
|
||||||
|
$table->unsignedInteger('referral_points')->default(0)->after('referred_by');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('users', 'referred_by')) {
|
||||||
|
$table->dropForeign(['referred_by']);
|
||||||
|
$table->dropColumn('referred_by');
|
||||||
|
}
|
||||||
|
foreach (['referral_code', 'referral_points'] as $column) {
|
||||||
|
if (Schema::hasColumn('users', $column)) {
|
||||||
|
$table->dropColumn($column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
+4
-1
@@ -78,9 +78,12 @@
|
|||||||
Route::post('/profile', [UserController::class, 'updateProfile']);
|
Route::post('/profile', [UserController::class, 'updateProfile']);
|
||||||
Route::get('/profile', [UserController::class, 'profile']);
|
Route::get('/profile', [UserController::class, 'profile']);
|
||||||
|
|
||||||
////-- leader board
|
////-- leader board
|
||||||
Route::get('/leader-board', [UserController::class, 'leaderBoard']);
|
Route::get('/leader-board', [UserController::class, 'leaderBoard']);
|
||||||
|
|
||||||
|
////-- referral (دعوت دوستان)
|
||||||
|
Route::get('/referral', [UserController::class, 'referral']);
|
||||||
|
|
||||||
///-- mood routes
|
///-- mood routes
|
||||||
|
|
||||||
Route::post('/moods/today', [MoodController::class, 'storeUserMood']);
|
Route::post('/moods/today', [MoodController::class, 'storeUserMood']);
|
||||||
|
|||||||
Reference in New Issue
Block a user