isStore()) { return $this->storeRedeem($request); } $user = auth()->user(); $count = (int) $request->count; $amount = $request->amount; if(!$user){ throw new UnprocessableEntityHttpException('auth failed!'); } if((!is_numeric($request->count) || $count < 1) || (!is_numeric($amount) || $amount <= 0)){ return response()->json(['message' => 'تعداد کد را چک کنید!'], 400); } $expiryError = $this->validateGiftExpiryDate($request->expiry_date); if ($expiryError !== null) { return response()->json(['message' => $expiryError], 400); } $customer = Customer::select('id', 'name', 'mobile')->where('mobile', $request->mobile)->first(); $gift = $this->createGiftCardWithUniqueCodes($request, $customer); $createdCodes = $gift->giftcardcodes->pluck('code')->all(); return response()->json([ 'customer' => $customer ?? null, 'order' => $gift, 'orders' => [$gift], 'codes' => $createdCodes, 'total' => $count * $amount, ]); } public function useGiftCard(Request $request){ $user = auth()->user(); if(!$user){ throw new UnprocessableEntityHttpException('auth failed!'); } if (!app(ConfigService::class)->getBool('has_page_gift_card', false)) { throw new UnprocessableEntityHttpException('قابلیت کارت هدیه غیرفعال است.'); } $redeem = $request->code; $giftcode = GiftCardCodes::where('code', $redeem)->first(); if(!$giftcode){ return response()->json(['message' => 'کد هدیه یافت نشد!'], 404); } $gift = GiftCard::find($giftcode->giftcard_id); if(!$gift){ return response()->json(['message' => 'کد هدیه یافت نشد!'], 404); } if($gift->expiry_date && $gift->expiry_date <= now()){ $gift->sts = 3; $giftcode->sts = 3; $giftcode->save(); $gift->save(); return response()->json(['message' => 'کد هدیه منقضی شده است!'], 400); } if((int) $giftcode->sts !== 1){ return response()->json(['message' => 'کد هدیه قبلا استفاده شده است!'], 400); } if((int) $gift->count > 0 && (int) $gift->used_count >= (int) $gift->count){ return response()->json(['message' => 'کد هدیه قبلا استفاده شده است!'], 400); } $unit = (int) $giftcode->unit; $message = null; if($unit === 1){ //add IRR to user wallet $Withdraw = Withdraw::create([ 'customer_id' => auth('customer')->id(), "cash" => $giftcode->amount, "note" => "کارت هدیه", 'sts' => 1, 'order_id' => $giftcode->id, ]); $Withdraw->tracked_number = $giftcode->code; // توکن تراکنش برابر کد گیفت کارت if ($Withdraw->cash == $giftcode->amount) { if ($Withdraw->order_id){ $this->verifyOrder($Withdraw->id); } $Withdraw->zify_code = $Withdraw->tracked_number; $Withdraw->tracked_number = $giftcode->id;//gift card id $Withdraw->sts = 6; $Withdraw->save(); $wallet_interface_fund = new WalletInterface($user->id, $user->type); $transaction = new TransactionInterface($wallet_interface_fund, $_inp = [ "cash" => $giftcode->amount, 'expire_at' => now(), 'withdraw_id' => $Withdraw->id, 'active' => true ], 3, false, true); try { app(PargarSyncService::class)->queueWalletDocument( $user, 'cash', (float) $giftcode->amount, [ 'withdraw_id' => $Withdraw->id, 'note' => 'کارت هدیه', 'source' => 'gift_card', ] ); } catch (\Throwable $e) { report($e); } } $amount = number_format((float) $giftcode->amount); $message = "$user->name عزیز کیف پول شما به مقدار $amount ریال به واسطه کارت هدیه از طرف $gift->name شارژ شد. قلک ریال"; }elseif ($unit === 2){ // شارژ کیف فلزی (طلا / مس — فیلد gold کیف اصلی) $goldAmount = (float) $giftcode->amount; if ($goldAmount <= 0) { return response()->json(['message' => 'مقدار کارت هدیه نامعتبر است!'], 400); } $configService = app(ConfigService::class); $representative = $configService->mainWalletRepresentativeProduct(); $Withdraw = Withdraw::create([ 'customer_id' => $user->id, 'gold' => $goldAmount, 'note' => 'کارت هدیه', 'sts' => 6, 'tracked_number' => $giftcode->id, ]); $wallet_interface = new WalletInterface($user->id, $user->type); new TransactionInterface($wallet_interface, [ 'cash' => 0, 'gold' => $goldAmount, 'expire_at' => now(), 'note' => 'کارت هدیه', 'withdraw_id' => $Withdraw->id, 'product_id' => $representative?->id, 'active' => true, ], 7, true, false); $syncPayload = [ 'customer_id' => $user->id, 'value' => $goldAmount, 'direction' => 1, 'withdraw_id' => $Withdraw->id, 'note' => 'کارت هدیه', ]; if ($representative) { $syncPayload['product_id'] = $representative->id; $syncPayload['product'] = [ 'id' => $representative->id, 'title' => $representative->title, 'group_type' => $representative->group_type, 'metal_id' => $representative->metal_id, 'amount' => $representative->amount, 'unit' => $representative->unit, 'trade_by_quantity' => $representative->trade_by_quantity, ]; } if ($configService->isTahesabAccounting()) { try { app(TahesabController::class)->walletGold($syncPayload); } catch (\Throwable $e) { report($e); } } if ($configService->isZarinAccounting()) { try { app(ZarinController::class)->walletGold($syncPayload); } catch (\Throwable $e) { report($e); } } try { app(PargarSyncService::class)->queueWalletDocument( $user, 'gold', $goldAmount, [ 'withdraw_id' => $Withdraw->id, 'note' => 'کارت هدیه', 'product_id' => $representative?->id, 'source' => 'gift_card', ] ); } catch (\Throwable $e) { report($e); } $weightUnit = self::tradeWeightUnit(); $metalLabel = self::mainWalletMetalLabel(); $formattedGold = self::formatSmsGoldWeight($goldAmount); $message = "$user->name عزیز کیف $metalLabel شما به مقدار $formattedGold $weightUnit به واسطه کارت هدیه از طرف $gift->name شارژ شد. قلک مس"; try { app(\App\Services\CustomerAppNotifyService::class)->notifyCustomer( (int) $user->id, 'واریز کارت هدیه', "کیف $metalLabel شما به اندازه $formattedGold $weightUnit شارژ شد.", 'success' ); } catch (\Throwable $e) { report($e); } } else { return response()->json(['message' => 'نوع کارت هدیه پشتیبانی نمی‌شود.'], 400); } if((int) $gift->used_count + 1 >= (int) $gift->count){ $gift->sts = 2; } $gift->used_count = (int) $gift->used_count + 1; $gift->save(); $giftcode->assigned_to = $user->id; $giftcode->sts = 2; $giftcode->used_at = now(); $giftcode->save(); if ($message) { $this->Send($user->mobile, $message); } return response()->json([ 'message' => 'کد هدیه با موفقیت استفاده شد!', 'code' => $giftcode->code, 'amount' => $giftcode->amount, 'unit' => $giftcode->unit, ]); } /** * لیست کدهای قابل‌استفاده (اختصاصی + عمومی تک‌کد) و تاریخچهٔ مصرف‌شدهٔ مشتری. */ public function mine(Request $request) { $user = auth()->user(); if (! $user) { throw new UnprocessableEntityHttpException('auth failed!'); } if (! app(ConfigService::class)->getBool('has_page_gift_card', false)) { throw new UnprocessableEntityHttpException('قابلیت کارت هدیه غیرفعال است.'); } $customerId = (int) $user->id; $mobile = $user->mobile ?? null; $available = []; $seenCodes = []; $personalGifts = GiftCard::query() ->where(function ($q) use ($customerId, $mobile) { $q->where('customer_id', $customerId); if ($mobile) { $q->orWhere('mobile', $mobile); } }) ->where(function ($q) { $q->whereNull('sts')->orWhere('sts', '!=', 3); }) ->where(function ($q) { $q->whereNull('expiry_date')->orWhere('expiry_date', '>', now()); }) ->with(['giftcardcodes' => function ($q) { $q->where('sts', 1); }]) ->orderByDesc('id') ->get(); foreach ($personalGifts as $gift) { foreach ($gift->giftcardcodes as $code) { if ($this->isCodeExpired($code, $gift)) { continue; } if (isset($seenCodes[$code->code])) { continue; } $seenCodes[$code->code] = true; $available[] = $this->mapCustomerGiftItem($gift, $code, 'personal'); } } $publicGifts = GiftCard::query() ->where(function ($q) { $q->whereNull('customer_id')->orWhere('customer_id', 0); }) ->where(function ($q) { $q->whereNull('mobile')->orWhere('mobile', ''); }) ->where('use_once', 0) ->where(function ($q) { $q->whereNull('sts')->orWhere('sts', '!=', 3); }) ->where(function ($q) { $q->whereNull('expiry_date')->orWhere('expiry_date', '>', now()); }) ->with('giftcardcodes') ->orderByDesc('id') ->get(); foreach ($publicGifts as $gift) { if ((int) $gift->count > 0 && (int) $gift->used_count >= (int) $gift->count) { continue; } $codes = $gift->giftcardcodes ->filter(function ($code) use ($gift) { return (int) $code->sts !== 2 && (int) $code->sts !== 3 && ! $this->isCodeExpired($code, $gift); }) ->values(); // عمومی: فقط پویش تک‌کد مشترک (جلوگیری از افشای بچ تصادفی) if ($codes->count() !== 1 && empty($gift->unique_code)) { continue; } $code = $codes->first(); if (! $code) { continue; } if ($this->customerAlreadyUsedCode($code, $customerId, $gift)) { continue; } if (isset($seenCodes[$code->code])) { continue; } $seenCodes[$code->code] = true; $available[] = $this->mapCustomerGiftItem($gift, $code, 'public'); } $history = []; $historyCodes = GiftCardCodes::query() ->whereNotNull('assigned_to') ->where('assigned_to', '!=', '') ->where(function ($q) use ($customerId) { $id = (string) $customerId; $q->where('assigned_to', $customerId) ->orWhere('assigned_to', $id) ->orWhere('assigned_to', '['.$id.']') ->orWhere('assigned_to', 'like', '%"'.$id.'"%') ->orWhere('assigned_to', 'like', '%['.$id.',%') ->orWhere('assigned_to', 'like', '%,'.$id.']%') ->orWhere('assigned_to', 'like', '%,'.$id.',%'); }) ->with('giftcard') ->orderByDesc('used_at') ->orderByDesc('updated_at') ->limit(100) ->get(); foreach ($historyCodes as $code) { $gift = $code->giftcard; if (! $gift || ! $this->customerAlreadyUsedCode($code, $customerId, $gift)) { continue; } $history[] = $this->mapCustomerGiftItem($gift, $code, $this->resolveCodeScope($gift, $customerId, $mobile)); } return response()->json([ 'available' => $available, 'history' => $history, ]); } /** * یک رکورد giftcards + N کد یکتا در giftcard_codes (نه N رکورد جدا). */ private function createGiftCardWithUniqueCodes(Request $request, ?Customer $customer = null): GiftCard { $count = (int) $request->count; $amount = $request->amount; $unit = (int) $request->unit; $unitAmount = $unit === 3 ? $amount / 100 : $amount; $gift = GiftCard::create([ 'customer_id' => $customer?->id, 'use_once' => $request->use_once ? 1 : 0, 'count' => $count, 'amount' => $unitAmount, 'mobile' => $customer?->mobile ?? $request->mobile ?? null, 'name' => ($customer ? $customer->name : $request->name) ?? null, 'total' => $unit === 3 ? null : $count * $amount, 'sts' => 1, 'unit' => $request->unit, 'expiry_date' => $request->expiry_date, 'created_at' => now(), 'updated_at' => now(), 'description' => $request->description, ]); $codeRows = []; for ($i = 0; $i < $count; $i++) { do { $uniqueCode = strtoupper(bin2hex(random_bytes(4))); } while (GiftCardCodes::where('code', $uniqueCode)->exists()); $codeRows[] = [ 'giftcard_id' => $gift->id, 'use_once' => $request->use_once ? 1 : 0, 'code' => $uniqueCode, 'sts' => 1, 'unit' => $request->unit, 'amount' => $unitAmount, 'expiry_date' => $request->expiry_date, 'created_at' => now(), ]; } GiftCardCodes::insert($codeRows); if ($count === 1) { $gift->unique_code = $codeRows[0]['code']; $gift->save(); } return $gift->load('giftcardcodes'); } /** * تاریخ انقضا نباید قبل از امروز باشد (امروز مجاز است). */ private function validateGiftExpiryDate(mixed $expiryDate): ?string { if ($expiryDate === null || $expiryDate === '') { return 'تاریخ انقضا الزامی است.'; } try { $expiryDay = \Carbon\Carbon::parse($expiryDate)->startOfDay(); } catch (\Throwable $e) { return 'تاریخ انقضا نامعتبر است.'; } if ($expiryDay->lt(now()->startOfDay())) { return 'تاریخ انقضا نمی‌تواند قبل از امروز باشد.'; } return null; } private function isCodeExpired(GiftCardCodes $code, GiftCard $gift): bool { if ($code->expiry_date && $code->expiry_date <= now()) { return true; } if ($gift->expiry_date && $gift->expiry_date <= now()) { return true; } return false; } private function customerAlreadyUsedCode(GiftCardCodes $code, int $customerId, GiftCard $gift): bool { $assigned = $code->assigned_to; if ($assigned === null || $assigned === '') { return false; } if ((int) $gift->unit === 3 || (is_string($assigned) && str_starts_with(trim($assigned), '['))) { $ids = json_decode($assigned, true); if (! is_array($ids)) { return false; } return in_array($customerId, $ids, false) || in_array((string) $customerId, $ids, false); } return (string) $assigned === (string) $customerId; } private function resolveCodeScope(GiftCard $gift, int $customerId, ?string $mobile): string { if ((int) $gift->customer_id === $customerId) { return 'personal'; } if ($mobile && $gift->mobile && (string) $gift->mobile === (string) $mobile) { return 'personal'; } return 'public'; } private function mapCustomerGiftItem(GiftCard $gift, GiftCardCodes $code, string $scope): array { $unit = (int) ($code->unit ?? $gift->unit); return [ 'id' => $code->id, 'giftcard_id' => $gift->id, 'code' => $code->code, 'name' => $gift->name, 'description' => $gift->description, 'amount' => $code->amount ?? $gift->amount, 'unit' => $unit, 'kind' => $unit === 3 ? 'discount' : 'gift', 'scope' => $scope, 'sts' => (int) $code->sts, 'expiry_date' => $code->expiry_date ?? $gift->expiry_date, 'used_at' => $code->used_at, ]; } public function useDiscount(Request $request) { if (! app(ConfigService::class)->isStore()) { return response()->json(['message' => 'این قابلیت فقط برای فروشگاه فعال است'], 403); } $result = $this->applyDiscount((string) $request->input('code'), auth('customer')->id()); if (! empty($result['message'])) { return response()->json(['message' => $result['message']], 400); } return response()->json(['amount' => $result['amount']]); } public function validateDiscount(Request $request) { if (! app(ConfigService::class)->isStore()) { return response()->json(['message' => 'این قابلیت فقط برای فروشگاه فعال است'], 403); } $giftcode = $this->verifyDiscount((string) $request->input('code')); if (! empty($giftcode['message'])) { return response()->json(['message' => $giftcode['message']], 400); } return response()->json(['giftcard' => $giftcode], 200); } private function storeRedeem(Request $request) { $user = auth()->user(); $count = (int) $request->count; $amount = $request->amount; if (! $user) { throw new UnprocessableEntityHttpException('auth failed!'); } if ((! is_numeric($request->count) || $count < 1) || (! is_numeric($amount) || $amount <= 0)) { return response()->json(['message' => 'تعداد کد را چک کنید!'], 400); } $expiryError = $this->validateGiftExpiryDate($request->expiry_date); if ($expiryError !== null) { return response()->json(['message' => $expiryError], 400); } if (isset($request->code) && GiftCardCodes::where('code', $request->code)->where('unit', $request->unit)->exists()) { return response()->json(['message' => 'کد تکراری میباشد!'], 400); } $unitAmount = $request->unit == 3 ? $amount / 100 : $amount; $unit = (int) $request->unit; // کارت هدیه (ریالی/فلزی): همیشه N کد یکتا؛ تخفیف با use_once=0 می‌تواند تک‌کد مشترک باشد $createUniqueCodes = $request->use_once || $unit === 1 || $unit === 2; if ($createUniqueCodes) { $gift = $this->createGiftCardWithUniqueCodes($request); } else { do { $unique_code = strtoupper(bin2hex(random_bytes(4))); } while (GiftCardCodes::where('code', $unique_code)->exists()); $gift = GiftCard::create([ 'use_once' => $request->use_once ? 1 : 0, 'count' => $count, 'unique_code' => ! empty($request->code) ? $request->code : $unique_code, 'amount' => $unitAmount, 'name' => $request->name, 'total' => $request->unit == 3 ? null : $count * $amount, 'sts' => 1, 'unit' => $request->unit, 'expiry_date' => $request->expiry_date, 'created_at' => now(), 'updated_at' => now(), 'description' => $request->description, ]); GiftCardCodes::insert([ 'giftcard_id' => $gift->id, 'use_once' => $request->use_once ? 1 : 0, 'code' => ! empty($request->code) ? $request->code : $unique_code, 'sts' => 1, 'unit' => $request->unit, 'amount' => $unitAmount, 'expiry_date' => $request->expiry_date, 'created_at' => now(), ]); } return response()->json([ 'order' => $gift, 'codes' => $gift->giftcardcodes, 'total' => $count * $amount, ]); } public function applyDiscount(string $code, $customerId): array { $giftcode = $this->verifyDiscount($code); if (! empty($giftcode['message'])) { return ['message' => $giftcode['message']]; } $giftcode = GiftCardCodes::where('code', $code)->first(); $gift = GiftCard::find($giftcode->giftcard_id); $idsArray = json_decode($giftcode->assigned_to, true); if (! is_array($idsArray)) { $idsArray = []; } $idsArray[] = $customerId; if ($gift->use_once == 1) { $giftcode->sts = 2; } $giftcode->assigned_to = json_encode($idsArray); $giftcode->save(); $gift->used_count += 1; if ($gift->used_count + 1 > $gift->count) { $gift->sts = 2; } $gift->save(); return ['amount' => $giftcode->amount]; } private function verifyDiscount(string $code) { $giftcode = GiftCardCodes::where('code', $code)->first(); if (! $giftcode) { return ['message' => 'کد تخفیف یافت نشد!']; } $gift = GiftCard::find($giftcode->giftcard_id); $customer_id = auth('customer')->id(); if ($gift->unit !== 3 || $giftcode->unit !== 3) { return ['message' => 'کد تخفیف صحیح نمیباشد!']; } if ($gift->use_once === 0) { $stored_ids = json_decode($giftcode->assigned_to, true); if (is_array($stored_ids) && in_array($customer_id, $stored_ids)) { return ['message' => 'شما قبلا از تخفیف خود استفاده کرده اید!']; } } elseif ($gift->use_once === 1) { if (GiftCardCodes::where('giftcard_id', $gift->id)->where('assigned_to', '['.auth('customer')->id().']')->exists()) { return ['message' => 'شما قبلا از تخفیف خود استفاده کرده اید!']; } } if ($gift->expiry_date <= now() || $gift->sts === 3) { $gift->sts = 3; $giftcode->sts = 3; $giftcode->save(); $gift->save(); return ['message' => 'کد تخفیف منقضی شده است!']; } if ($giftcode->sts === 2) { return ['message' => 'کد تخفیف قبلا استفاده شده است!']; } if ($gift->used_count + 1 > $gift->count) { return ['message' => 'تعداد استفاده کد تخفیف به پایان رسیده است!']; } return $giftcode; } public function table(Request $request) { $user = auth()->user(); if(!$user){ throw new UnprocessableEntityHttpException('auth failed!'); } $records = GiftCard::with([ 'giftcardcodes', 'giftcardcodes.assignedTo' ]); TableEngine::create($records, $request->all()) ->initSearch([]) ->select([ '*', GiftCard::$SELECT_STS_STR ])->export($records); return response()->json($records); } }