diff --git a/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php b/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php index ccf1e46..c7dda72 100644 --- a/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php +++ b/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php @@ -106,7 +106,9 @@ class CommitmentFormController extends Controller 'file_type' => strtoupper(pathinfo($form->file_path, PATHINFO_EXTENSION)), 'direction' => $form->direction, 'upload_status' => $upload ? $upload->status : 'pending', - 'uploaded_file_url' => $upload && $upload->uploaded_file_path ? asset('storage/' . $upload->uploaded_file_path) : null, + 'uploaded_file_url' => $upload && $upload->uploaded_file_path + ? route('customer.commitment-forms.download', ['shipment' => $shipment->id, 'form' => $form->id]) + : null, 'uploaded_at' => $upload ? $upload->created_at : null, 'notes' => $upload ? $upload->notes : null, ]; @@ -141,7 +143,8 @@ class CommitmentFormController extends Controller try { $file = $request->file('file'); - $path = $file->store("commitment-forms/{$shipment->id}", 'public'); + // 🛡️ ذخیره در دیسک امن (غیرعمومی) — فقط از طریق route محافظت‌شده قابل دانلود + $path = $file->store("commitment-forms/{$shipment->id}", 'secure'); $upload = ShipmentCommitmentForm::updateOrCreate( [ @@ -163,7 +166,10 @@ class CommitmentFormController extends Controller 'message' => 'فایل با موفقیت آپلود شد.', 'data' => [ 'id' => $upload->id, - 'file_url' => asset('storage/' . $path), + 'file_url' => route('customer.commitment-forms.download', [ + 'shipment' => $shipment->id, + 'form' => $form->id, + ]), 'file_type' => $upload->uploaded_file_type, 'status' => $upload->status, 'uploaded_at' => $upload->created_at, @@ -176,5 +182,38 @@ class CommitmentFormController extends Controller ], 500); } } + + /** + * دانلود فرم تعهدنامه امزاشده (محافظت‌شده با بررسی مالکیت). + * GET /api/v1/customer/orders/{shipment}/commitment-forms/{form}/download + */ + public function downloadSigned(Shipment $shipment, CommitmentForm $form) + { + $user = auth()->user(); + + if ($shipment->user_id !== $user->id) { + return response()->json([ + 'success' => false, + 'message' => 'شما به این سفارش دسترسی ندارید.', + ], 403); + } + + $upload = ShipmentCommitmentForm::where('shipment_id', $shipment->id) + ->where('commitment_form_id', $form->id) + ->firstOrFail(); + + if (!$upload->uploaded_file_path) { + return response()->json([ + 'success' => false, + 'message' => 'فایلی برای این تعهدنامه آپلود نشده است.', + ], 404); + } + + return Storage::disk('secure')->download( + $upload->uploaded_file_path, + 'commitment-' . $form->id . '-' . $shipment->awb_no . '.' . $upload->uploaded_file_type + ); + } +} } diff --git a/04_Laravel/app/Http/Controllers/ShipmentPdfController.php b/04_Laravel/app/Http/Controllers/ShipmentPdfController.php index 621d23f..fbe2b32 100644 --- a/04_Laravel/app/Http/Controllers/ShipmentPdfController.php +++ b/04_Laravel/app/Http/Controllers/ShipmentPdfController.php @@ -15,8 +15,10 @@ class ShipmentPdfController extends Controller /** * تولید AWB PDF (برای همه نوع محموله‌ها) */ - public function awb(Shipment $shipment) + public function awb(Shipment $shipment) { + $this->authorize('view', $shipment); + try { Log::info('Generating AWB PDF', ['shipment_id' => $shipment->id]); @@ -47,6 +49,7 @@ class ShipmentPdfController extends Controller */ public function invoice(Shipment $shipment) { + $this->authorize('view', $shipment); try { // بارگذاری items برای بررسی $shipment->loadMissing(['items']); @@ -109,6 +112,7 @@ class ShipmentPdfController extends Controller */ public function label(Shipment $shipment) { + $this->authorize('view', $shipment); try { $content = $this->pdf->label($shipment); @@ -134,6 +138,7 @@ class ShipmentPdfController extends Controller */ public function importInvoice(Shipment $shipment, Request $request) { + $this->authorize('view', $shipment); try { // دریافت پارامترها از درخواست $options = $request->only([ diff --git a/04_Laravel/app/Policies/ShipmentPolicy.php b/04_Laravel/app/Policies/ShipmentPolicy.php new file mode 100644 index 0000000..a5d118d --- /dev/null +++ b/04_Laravel/app/Policies/ShipmentPolicy.php @@ -0,0 +1,23 @@ +hasAnyRole(['super_admin', 'admin', 'staff'])) { + return true; + } + + return $shipment->user_id === $user->id; + } +} \ No newline at end of file diff --git a/04_Laravel/app/Services/OrderPaymentService.php b/04_Laravel/app/Services/OrderPaymentService.php index 704a517..f6a4078 100644 --- a/04_Laravel/app/Services/OrderPaymentService.php +++ b/04_Laravel/app/Services/OrderPaymentService.php @@ -93,6 +93,16 @@ class OrderPaymentService public function initiateGatewayPayment(Shipment $shipment, Wallet $wallet): array { try { + // 🛡️ محافظ Production: اگه در محیط تولید، درگاه Mock فعال باشد، رد کن + if (app()->environment('production') + && (config('ifnex.zarinpal.merchant_id') === 'fake-merchant-id-for-testing' + || config('ifnex.zarinpal.sandbox', true))) { + throw new \RuntimeException( + 'درگاه پرداخت در محیط تولید به‌درستی تنظیم نشده. ' + . 'ZARINPAL_MERCHANT_ID واقعی یا ZARINPAL_SANDBOX=false در .env تنظیم کنید.' + ); + } + // ۱. ایجاد تراکنش pending $transaction = WalletTransaction::create([ 'wallet_id' => $wallet->id, diff --git a/04_Laravel/config/filesystems.php b/04_Laravel/config/filesystems.php index 37d8fca..02e1ad8 100644 --- a/04_Laravel/config/filesystems.php +++ b/04_Laravel/config/filesystems.php @@ -38,7 +38,7 @@ return [ 'report' => false, ], - 'public' => [ + 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', @@ -47,6 +47,15 @@ return [ 'report' => false, ], + // 🛡️ دیسک امن برای فایل‌های حساس (تعهدنامه‌های امزاشده). + // بدون url/serve → قابل دسترسی عمومی نیست؛ فقط از طریق route محافظت‌شده. + 'secure' => [ + 'driver' => 'local', + 'root' => storage_path('app/secure'), + 'throw' => false, + 'report' => false, + ], + 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), diff --git a/04_Laravel/routes/api.php b/04_Laravel/routes/api.php index 4787fd9..d6c3f4c 100644 --- a/04_Laravel/routes/api.php +++ b/04_Laravel/routes/api.php @@ -64,6 +64,14 @@ Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () { Route::post('/wallet/{wallet}/unfreeze', [WalletController::class, 'unfreeze']); Route::get('/wallet/{wallet}/activity-log', [WalletController::class, 'activityLog']); + // ─── Discount Codes (نیازمند احراز هویت) ─── + Route::get('/discount-codes', [DiscountCodeController::class, 'index']); + Route::post('/discount-codes/validate', [DiscountCodeController::class, 'validate']); + + // ─── Commitment Forms (نیازمند احراز هویت) ─── + Route::get('/commitment-forms', [CommitmentFormController::class, 'index']); + Route::get('/commitment-forms/{direction}', [CommitmentFormController::class, 'byDirection']); + // ─── Customer Orders API (جدید) ─── Route::prefix('customer')->group(function () { // پروفایل و آمار @@ -88,7 +96,8 @@ Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () { // تعهدنامه‌ها Route::get('/orders/{shipment}/commitment-forms', [CommitmentFormController::class, 'shipmentForms']); Route::post('/orders/{shipment}/commitment-forms/{form}/upload', [CommitmentFormController::class, 'uploadSigned']); - + Route::get('/orders/{shipment}/commitment-forms/{form}/download', [CommitmentFormController::class, 'downloadSigned']) + ->name('customer.commitment-forms.download'); // نوتیفیکیشن‌ها Route::get('/notifications', [CustomerOrderController::class, 'notifications']); Route::post('/notifications/{notification}/read', [CustomerOrderController::class, 'markNotificationRead']); @@ -150,15 +159,9 @@ Route::any('/v1/payment/callback', [PaymentController::class, 'callback']) ->name('payment.callback'); // ══════════════════════════════════════════════════════════════ -// APIهای عمومی (بدون auth) +// APIهای عمومی (بدون auth) — فقط استعلام قیمت همگانی است // ══════════════════════════════════════════════════════════════ Route::post('/v1/calculate', [PricingController::class, 'calculate']); -Route::get('/v1/discount-codes', [DiscountCodeController::class, 'index']); -Route::post('/v1/discount-codes/validate', [DiscountCodeController::class, 'validate']); - -// API فایل‌های تعهدنامه (عمومی) -Route::get('/v1/commitment-forms', [CommitmentFormController::class, 'index']); -Route::get('/v1/commitment-forms/{direction}', [CommitmentFormController::class, 'byDirection']); // API تأیید موبایل (عمومی) Route::post('/v1/verify/send-code', [MobileVerificationController::class, 'sendCode']);