Refactor the order and shipment lifecycle across WordPress and Laravel,
improving multi-package handling, payment automation, and frontend
reliability.
- Laravel:
- Rename `package_number` to `package_no` and `description` to
`content_description` in `ShipmentPackage` model and controller.
- Update `PaymentController` to automatically transition approved
shipments to `Processed` status upon successful payment.
- Adjust `OrderPaymentService` to validate against `Approved` status
instead of `PendingPayment`.
- Expose `/countries` endpoint as a public route (unauthenticated).
- Remove obsolete `read_excel.php` utility.
- WordPress (Bridge Plugin & Theme):
- Implement AJAX handler for wallet-based order payments.
- Update `ifnex-order-form.js` to support multi-package input names
and auto-select Iran based on shipment direction.
- Improve error handling and feedback in the order form and country
loading logic.
- Add automatic tracking submission when an `awb` parameter is
present in the URL.
- Update CSS with `!important` flags to ensure correct visibility
of form steps and dashboard elements.
- Implement cache-busting for plugin assets and prevent OPcache
stale files via header controls.
- Optimize theme logo loading with eager loading and explicit
dimensions.
673 lines
28 KiB
PHP
673 lines
28 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Api\Customer;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Shipment;
|
||
use App\Models\ShipmentItem;
|
||
use App\Models\ShipmentPackage;
|
||
use App\Models\Country;
|
||
use App\Enums\ShipmentStatus;
|
||
use App\Enums\ShipmentDirection;
|
||
use App\Enums\ShipmentType;
|
||
use App\Services\PriceCalculatorService;
|
||
use Illuminate\Http\JsonResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Str;
|
||
use Illuminate\Validation\ValidationException;
|
||
|
||
class CustomerOrderController extends Controller
|
||
{
|
||
public function __construct(
|
||
protected PriceCalculatorService $calculator
|
||
) {}
|
||
|
||
/**
|
||
* لیست سفارشات کاربر
|
||
* GET /api/v1/customer/orders
|
||
*/
|
||
public function index(Request $request): JsonResponse
|
||
{
|
||
$validated = $request->validate([
|
||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||
'status' => ['nullable', 'string'],
|
||
'page' => ['nullable', 'integer', 'min:1'],
|
||
]);
|
||
|
||
$user = Auth::user();
|
||
$query = Shipment::query()
|
||
->where('user_id', $user->id)
|
||
->with(['fromCountry', 'toCountry', 'items'])
|
||
->orderBy('created_at', 'desc');
|
||
|
||
if (!empty($validated['status'])) {
|
||
$query->where('status', $validated['status']);
|
||
}
|
||
|
||
$shipments = $query->paginate($validated['per_page'] ?? 15);
|
||
|
||
return response()->json([
|
||
'success' => true,
|
||
'data' => $shipments->map(function ($shipment) {
|
||
return $this->formatShipment($shipment);
|
||
}),
|
||
'pagination' => [
|
||
'current_page' => $shipments->currentPage(),
|
||
'last_page' => $shipments->lastPage(),
|
||
'per_page' => $shipments->perPage(),
|
||
'total' => $shipments->total(),
|
||
],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* ثبت سفارش جدید
|
||
* POST /api/v1/customer/orders
|
||
*/
|
||
public function store(Request $request): JsonResponse
|
||
{
|
||
try {
|
||
$validated = $request->validate([
|
||
// مسیر و نوع
|
||
'direction' => ['required', 'in:export,import'],
|
||
'type' => ['required', 'in:DOC_NORMAL,DOC_ECONOMY,PARCEL'],
|
||
'from_country_id' => ['required', 'exists:countries,id'],
|
||
'to_country_id' => ['required', 'exists:countries,id'],
|
||
|
||
// وزن و ابعاد (فیلدهای کلی سفارش - برای backward compatibility)
|
||
'weight' => ['nullable', 'numeric', 'min:0.1'],
|
||
'volumetric_weight' => ['nullable', 'numeric', 'min:0'],
|
||
'dimensions' => ['nullable', 'string', 'max:100'],
|
||
|
||
// بستهها (Multi-Package) — حداقل ۱، حداکثر ۱۰ بسته
|
||
'packages' => ['nullable', 'array', 'min:1', 'max:10'],
|
||
'packages.*.weight' => ['required_with:packages', 'numeric', 'min:0.1'],
|
||
'packages.*.dimensions' => ['nullable', 'string', 'max:50'],
|
||
'packages.*.description' => ['nullable', 'string', 'max:500'],
|
||
|
||
// اطلاعات فرستنده
|
||
'sender_name' => ['required', 'string', 'max:255'],
|
||
'sender_company' => ['nullable', 'string', 'max:255'],
|
||
'sender_phone' => ['required', 'string', 'max:50'],
|
||
'sender_email' => ['nullable', 'email', 'max:255'],
|
||
'sender_address' => ['required', 'string', 'max:1000'],
|
||
'sender_city' => ['nullable', 'string', 'max:255'],
|
||
'sender_zip' => ['nullable', 'string', 'max:20'],
|
||
'sender_id_number' => ['nullable', 'string', 'max:50'],
|
||
|
||
// اطلاعات گیرنده
|
||
'receiver_name' => ['required', 'string', 'max:255'],
|
||
'receiver_company' => ['nullable', 'string', 'max:255'],
|
||
'receiver_phone' => ['required', 'string', 'max:50'],
|
||
'receiver_email' => ['nullable', 'email', 'max:255'],
|
||
'receiver_address' => ['required', 'string', 'max:1000'],
|
||
'receiver_city' => ['nullable', 'string', 'max:255'],
|
||
'receiver_zip' => ['nullable', 'string', 'max:20'],
|
||
'receiver_id_number' => ['nullable', 'string', 'max:50'],
|
||
|
||
// خدمات اضافی
|
||
'extra_service' => ['nullable', 'numeric', 'min:0'],
|
||
'packing_cost' => ['nullable', 'numeric', 'min:0'],
|
||
'domestic_pickup' => ['nullable', 'numeric', 'min:0'],
|
||
'domestic_delivery' => ['nullable', 'numeric', 'min:0'],
|
||
'warehousing_cost' => ['nullable', 'numeric', 'min:0'],
|
||
|
||
// تخفیف
|
||
'discount_code' => ['nullable', 'string', 'max:50'],
|
||
|
||
// اقلام گمرکی
|
||
'items' => ['nullable', 'array', 'max:9'],
|
||
'items.*.description' => ['required_with:items', 'string', 'max:500'],
|
||
'items.*.hs_code' => ['required_with:items', 'string', 'max:20'],
|
||
'items.*.quantity' => ['required_with:items', 'integer', 'min:1'],
|
||
'items.*.unit_price' => ['required_with:items', 'numeric', 'min:0'],
|
||
]);
|
||
} catch (ValidationException $e) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'خطا در اعتبارسنجی اطلاعات',
|
||
'errors' => $e->errors(),
|
||
], 422);
|
||
}
|
||
|
||
$user = Auth::user();
|
||
|
||
// بررسی اینکه کاربر کیف پول فعال دارد
|
||
if (!$user->wallet || $user->wallet->is_frozen) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'کیف پول شما غیرفعال است. لطفاً با پشتیبانی تماس بگیرید.',
|
||
], 403);
|
||
}
|
||
|
||
// محاسبه وزن کل از بستهها (اگر وجود داره)
|
||
$packages = $validated['packages'] ?? [];
|
||
$totalWeight = 0;
|
||
$totalVolumetricWeight = 0;
|
||
|
||
if (!empty($packages)) {
|
||
foreach ($packages as $pkg) {
|
||
$totalWeight += (float) $pkg['weight'];
|
||
$volWeight = ShipmentPackage::calculateVolumetricWeight($pkg['dimensions'] ?? null);
|
||
$totalVolumetricWeight += $volWeight;
|
||
}
|
||
// Override وزن کلی سفارش
|
||
$validated['weight'] = $totalWeight;
|
||
$validated['volumetric_weight'] = $totalVolumetricWeight;
|
||
$validated['chargeable_weight'] = max($totalWeight, $totalVolumetricWeight);
|
||
} else {
|
||
// حالت قدیمی: یه بسته
|
||
$validated['chargeable_weight'] = max(
|
||
(float) $validated['weight'],
|
||
(float) ($validated['volumetric_weight'] ?? 0)
|
||
);
|
||
}
|
||
|
||
// دریافت کشور مقصد برای محاسبه قیمت
|
||
$destinationCountry = Country::findOrFail(
|
||
$validated['direction'] === 'export'
|
||
? $validated['to_country_id']
|
||
: $validated['from_country_id']
|
||
);
|
||
|
||
// محاسبه قیمت
|
||
try {
|
||
$pricingData = [
|
||
'direction' => $validated['direction'] === 'export' ? 'Outbound' : 'Inbound',
|
||
'type' => $validated['type'],
|
||
'country_iso' => $destinationCountry->iso_code,
|
||
'weight' => (float) $validated['weight'],
|
||
'volumetric_weight' => (float) ($validated['volumetric_weight'] ?? 0),
|
||
'extra_service' => (float) ($validated['extra_service'] ?? 0),
|
||
'packing_cost' => (float) ($validated['packing_cost'] ?? 0),
|
||
'domestic_pickup' => (float) ($validated['domestic_pickup'] ?? 0),
|
||
'domestic_delivery' => (float) ($validated['domestic_delivery'] ?? 0),
|
||
'warehousing_cost' => (float) ($validated['warehousing_cost'] ?? 0),
|
||
'discount_code' => $validated['discount_code'] ?? null,
|
||
];
|
||
|
||
$priceResult = $this->calculator->calculate($pricingData);
|
||
} catch (\Exception $e) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'خطا در محاسبه قیمت: ' . $e->getMessage(),
|
||
], 400);
|
||
}
|
||
|
||
// ساخت شماره AWB (فرمت: IFN-YYYY-XXXXX)
|
||
$awbNo = $this->generateAwbNumber();
|
||
|
||
// ذخیره سفارش
|
||
try {
|
||
$shipment = DB::transaction(function () use ($validated, $user, $priceResult, $awbNo) {
|
||
$chargeableWeight = max(
|
||
(float) $validated['weight'],
|
||
(float) ($validated['volumetric_weight'] ?? 0)
|
||
);
|
||
|
||
$shipment = Shipment::create([
|
||
'user_id' => $user->id,
|
||
'awb_no' => $awbNo,
|
||
'direction' => $validated['direction'],
|
||
'type' => $validated['type'],
|
||
'status' => ShipmentStatus::PendingApproval,
|
||
|
||
// وزن
|
||
'weight' => $validated['weight'],
|
||
'volumetric_weight' => $validated['volumetric_weight'] ?? null,
|
||
'chargeable_weight' => $chargeableWeight,
|
||
'dimensions' => $validated['dimensions'] ?? null,
|
||
|
||
// کشورها
|
||
'from_country_id' => $validated['from_country_id'],
|
||
'to_country_id' => $validated['to_country_id'],
|
||
|
||
// اطلاعات مالی (از محاسبه)
|
||
'shipping_price' => $priceResult['net_dirham'],
|
||
'extra_service' => $priceResult['extra_service'],
|
||
'packing_cost' => $priceResult['packing_cost'],
|
||
'domestic_pickup' => $priceResult['domestic_pickup'],
|
||
'domestic_delivery' => $priceResult['domestic_delivery'],
|
||
'warehousing_cost' => $priceResult['warehousing_cost'],
|
||
'vat_amount' => $priceResult['vat_amount'],
|
||
'discount' => $priceResult['discount_amount'],
|
||
'total_fee' => $priceResult['total_fee'],
|
||
'net_dirham' => $priceResult['net_dirham'],
|
||
'net_rial' => $priceResult['net_rial'],
|
||
|
||
// فرستنده
|
||
'sender_name' => $validated['sender_name'],
|
||
'sender_company' => $validated['sender_company'] ?? null,
|
||
'sender_phone' => $validated['sender_phone'],
|
||
'sender_email' => $validated['sender_email'] ?? null,
|
||
'sender_address' => $validated['sender_address'],
|
||
'sender_city' => $validated['sender_city'] ?? null,
|
||
'sender_zip' => $validated['sender_zip'] ?? null,
|
||
'sender_id_number' => $validated['sender_id_number'] ?? null,
|
||
|
||
// گیرنده
|
||
'receiver_name' => $validated['receiver_name'],
|
||
'receiver_company' => $validated['receiver_company'] ?? null,
|
||
'receiver_phone' => $validated['receiver_phone'],
|
||
'receiver_email' => $validated['receiver_email'] ?? null,
|
||
'receiver_address' => $validated['receiver_address'],
|
||
'receiver_city' => $validated['receiver_city'] ?? null,
|
||
'receiver_zip' => $validated['receiver_zip'] ?? null,
|
||
'receiver_id_number' => $validated['receiver_id_number'] ?? null,
|
||
]);
|
||
|
||
// ذخیره بستهها (Multi-Package)
|
||
if (!empty($validated['packages'])) {
|
||
foreach ($validated['packages'] as $index => $pkg) {
|
||
$volWeight = ShipmentPackage::calculateVolumetricWeight($pkg['dimensions'] ?? null);
|
||
$chargeable = max((float) $pkg['weight'], $volWeight);
|
||
|
||
ShipmentPackage::create([
|
||
'shipment_id' => $shipment->id,
|
||
'package_no' => $index + 1,
|
||
'weight' => $pkg['weight'],
|
||
'volumetric_weight' => $volWeight,
|
||
'chargeable_weight' => $chargeable,
|
||
'dimensions' => $pkg['dimensions'] ?? null,
|
||
'content_description' => $pkg['description'] ?? null,
|
||
]);
|
||
}
|
||
} else {
|
||
// حالت قدیمی: یه بسته با وزن کلی سفارش
|
||
ShipmentPackage::create([
|
||
'shipment_id' => $shipment->id,
|
||
'package_no' => 1,
|
||
'weight' => $validated['weight'],
|
||
'volumetric_weight' => $validated['volumetric_weight'] ?? 0,
|
||
'chargeable_weight' => $validated['chargeable_weight'],
|
||
'dimensions' => $validated['dimensions'] ?? null,
|
||
'content_description' => null,
|
||
]);
|
||
}
|
||
|
||
// ذخیره اقلام گمرکی
|
||
if (!empty($validated['items'])) {
|
||
foreach ($validated['items'] as $index => $item) {
|
||
ShipmentItem::create([
|
||
'shipment_id' => $shipment->id,
|
||
'row_number' => $index + 1,
|
||
'description' => $item['description'],
|
||
'hs_code' => $item['hs_code'],
|
||
'quantity' => $item['quantity'],
|
||
'unit_price' => $item['unit_price'],
|
||
'total_usd' => $item['quantity'] * $item['unit_price'],
|
||
]);
|
||
}
|
||
}
|
||
|
||
return $shipment;
|
||
});
|
||
|
||
return response()->json([
|
||
'success' => true,
|
||
'message' => 'سفارش شما با موفقیت ثبت شد. پس از تأیید کارمند، گزینه پرداخت برای شما فعال خواهد شد.',
|
||
'data' => $this->formatShipment($shipment->load(['fromCountry', 'toCountry', 'items'])),
|
||
'pricing' => $priceResult,
|
||
], 201);
|
||
|
||
} catch (\Exception $e) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'خطا در ثبت سفارش: ' . $e->getMessage(),
|
||
], 500);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* جزئیات یک سفارش
|
||
* GET /api/v1/customer/orders/{shipment}
|
||
*/
|
||
public function show(Shipment $shipment): JsonResponse
|
||
{
|
||
$user = Auth::user();
|
||
|
||
// بررسی مالکیت
|
||
if ($shipment->user_id !== $user->id) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'شما به این سفارش دسترسی ندارید.',
|
||
], 403);
|
||
}
|
||
|
||
$shipment->load(['fromCountry', 'toCountry', 'items', 'trackingEvents']);
|
||
|
||
return response()->json([
|
||
'success' => true,
|
||
'data' => $this->formatShipment($shipment, detailed: true),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* لغو سفارش (فقط در وضعیت pending_payment)
|
||
* POST /api/v1/customer/orders/{shipment}/cancel
|
||
*/
|
||
public function cancel(Shipment $shipment): JsonResponse
|
||
{
|
||
$user = Auth::user();
|
||
|
||
if ($shipment->user_id !== $user->id) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'شما به این سفارش دسترسی ندارید.',
|
||
], 403);
|
||
}
|
||
|
||
if (!$shipment->status->canBeCancelledByCustomer()) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'این سفارش در وضعیتی نیست که بتوان آن را لغو کرد.',
|
||
], 400);
|
||
}
|
||
|
||
$shipment->update(['status' => ShipmentStatus::Cancelled]);
|
||
|
||
return response()->json([
|
||
'success' => true,
|
||
'message' => 'سفارش با موفقیت لغو شد.',
|
||
'data' => $this->formatShipment($shipment->fresh()),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* پروفایل و آمار کاربر
|
||
* GET /api/v1/customer/profile
|
||
*/
|
||
public function profile(Request $request): JsonResponse
|
||
{
|
||
$user = Auth::user();
|
||
$user->load(['wallet']);
|
||
|
||
// محاسبه مجموع سفارشات در انتظار پرداخت (بدهی کاربر)
|
||
$pending_payments_total = Shipment::where('user_id', $user->id)
|
||
->where('status', ShipmentStatus::Approved)
|
||
->sum('total_fee');
|
||
|
||
// مانده حساب = موجودی کیف پول - بدهیها
|
||
$wallet_balance = $user->wallet ? $user->wallet->balance : 0;
|
||
$account_balance = $wallet_balance - $pending_payments_total;
|
||
|
||
// آمار سفارشات
|
||
$stats = [
|
||
'total_orders' => Shipment::where('user_id', $user->id)->count(),
|
||
'pending_approval' => Shipment::where('user_id', $user->id)
|
||
->where('status', ShipmentStatus::PendingApproval)
|
||
->count(),
|
||
'pending_payment' => Shipment::where('user_id', $user->id)
|
||
->where('status', ShipmentStatus::Approved)
|
||
->count(),
|
||
'pending_payment_total' => $pending_payments_total,
|
||
'active_orders' => Shipment::where('user_id', $user->id)
|
||
->whereIn('status', [
|
||
ShipmentStatus::Processed,
|
||
ShipmentStatus::PickedUp,
|
||
ShipmentStatus::InTransit,
|
||
ShipmentStatus::OutForDelivery,
|
||
])
|
||
->count(),
|
||
'delivered_orders' => Shipment::where('user_id', $user->id)
|
||
->where('status', ShipmentStatus::Delivered)
|
||
->count(),
|
||
'total_spent' => Shipment::where('user_id', $user->id)
|
||
->where('status', '!=', ShipmentStatus::PendingApproval)
|
||
->where('status', '!=', ShipmentStatus::Approved)
|
||
->where('status', '!=', ShipmentStatus::Cancelled)
|
||
->sum('total_fee'),
|
||
];
|
||
|
||
return response()->json([
|
||
'success' => true,
|
||
'user' => [
|
||
'id' => $user->id,
|
||
'name' => $user->name,
|
||
'email' => $user->email,
|
||
'phone' => $user->phone,
|
||
'member_since' => $user->created_at->format('Y-m-d'),
|
||
],
|
||
'wallet' => $user->wallet ? [
|
||
'balance' => $user->wallet->balance,
|
||
'is_frozen' => $user->wallet->is_frozen,
|
||
] : null,
|
||
'account_balance' => $account_balance,
|
||
'pending_payments_total' => $pending_payments_total,
|
||
'stats' => $stats,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* لیست کشورها برای فرم
|
||
* GET /api/v1/customer/countries
|
||
*/
|
||
public function countries(): JsonResponse
|
||
{
|
||
$countries = Country::select('id', 'name', 'iso_code', 'export_zone_parcel', 'import_zone_parcel')
|
||
->where('is_active', true)
|
||
->orderBy('name')
|
||
->get();
|
||
|
||
return response()->json([
|
||
'success' => true,
|
||
'data' => $countries,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* فرمت کردن Shipment برای خروجی API
|
||
*/
|
||
private function formatShipment(Shipment $shipment, bool $detailed = false): array
|
||
{
|
||
$data = [
|
||
'id' => $shipment->id,
|
||
'awb_no' => $shipment->awb_no,
|
||
'direction' => $shipment->direction?->value,
|
||
'type' => $shipment->type?->value,
|
||
'status' => [
|
||
'value' => $shipment->status?->value,
|
||
'label' => $shipment->status?->label(),
|
||
'color' => $shipment->status?->color(),
|
||
'is_paid' => $shipment->status?->isPaid(),
|
||
'can_cancel' => $shipment->status?->canBeCancelledByCustomer(),
|
||
'can_pay' => $shipment->status?->canBePaid(),
|
||
'is_approved' => $shipment->status?->isApproved(),
|
||
],
|
||
'from_country' => $shipment->fromCountry ? [
|
||
'id' => $shipment->fromCountry->id,
|
||
'name' => $shipment->fromCountry->name,
|
||
'iso_code' => $shipment->fromCountry->iso_code,
|
||
] : null,
|
||
'to_country' => $shipment->toCountry ? [
|
||
'id' => $shipment->toCountry->id,
|
||
'name' => $shipment->toCountry->name,
|
||
'iso_code' => $shipment->toCountry->iso_code,
|
||
] : null,
|
||
'weight' => $shipment->weight,
|
||
'chargeable_weight' => $shipment->chargeable_weight,
|
||
'total_fee' => $shipment->total_fee,
|
||
'net_rial' => $shipment->net_rial,
|
||
'net_dirham' => $shipment->net_dirham,
|
||
'created_at' => $shipment->created_at->toIso8601String(),
|
||
'created_at_jalali' => $this->toJalali($shipment->created_at),
|
||
];
|
||
|
||
if ($detailed) {
|
||
$data['sender'] = [
|
||
'name' => $shipment->sender_name,
|
||
'company' => $shipment->sender_company,
|
||
'phone' => $shipment->sender_phone,
|
||
'email' => $shipment->sender_email,
|
||
'address' => $shipment->sender_address,
|
||
'city' => $shipment->sender_city,
|
||
];
|
||
$data['receiver'] = [
|
||
'name' => $shipment->receiver_name,
|
||
'company' => $shipment->receiver_company,
|
||
'phone' => $shipment->receiver_phone,
|
||
'email' => $shipment->receiver_email,
|
||
'address' => $shipment->receiver_address,
|
||
'city' => $shipment->receiver_city,
|
||
];
|
||
$data['financial'] = [
|
||
'shipping_price' => $shipment->shipping_price,
|
||
'extra_service' => $shipment->extra_service,
|
||
'packing_cost' => $shipment->packing_cost,
|
||
'domestic_pickup' => $shipment->domestic_pickup,
|
||
'domestic_delivery' => $shipment->domestic_delivery,
|
||
'warehousing_cost' => $shipment->warehousing_cost,
|
||
'vat_amount' => $shipment->vat_amount,
|
||
'discount' => $shipment->discount,
|
||
'total_fee' => $shipment->total_fee,
|
||
];
|
||
$data['items'] = $shipment->items->map(fn($item) => [
|
||
'row' => $item->row_number,
|
||
'description' => $item->description,
|
||
'hs_code' => $item->hs_code,
|
||
'quantity' => $item->quantity,
|
||
'unit_price' => $item->unit_price,
|
||
'total' => $item->total_usd,
|
||
]);
|
||
$data['tracking_events'] = $shipment->trackingEvents->map(fn($event) => [
|
||
'date' => $event->event_date,
|
||
'description' => $event->event_description,
|
||
'location' => $event->location,
|
||
]);
|
||
}
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* تولید شماره AWB
|
||
*/
|
||
private function generateAwbNumber(): string
|
||
{
|
||
do {
|
||
$number = 'IFN-' . date('Y') . '-' . str_pad(random_int(10000, 99999), 5, '0', STR_PAD_LEFT);
|
||
} while (Shipment::where('awb_no', $number)->exists());
|
||
|
||
return $number;
|
||
}
|
||
|
||
/**
|
||
* تبدیل تاریخ به شمسی
|
||
*/
|
||
private function toJalali($date): string
|
||
{
|
||
if (!$date) return '—';
|
||
|
||
try {
|
||
return \Morilog\Jalali\Jalalian::fromCarbon($date)->format('Y/m/d H:i');
|
||
} catch (\Exception $e) {
|
||
return $date->format('Y-m-d H:i');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* پرداخت سفارش از کیف پول
|
||
* POST /api/v1/customer/orders/{shipment}/pay-wallet
|
||
*/
|
||
public function payFromWallet(Shipment $shipment): JsonResponse
|
||
{
|
||
$user = Auth::user();
|
||
|
||
// بررسی مالکیت
|
||
if ($shipment->user_id !== $user->id) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'شما به این سفارش دسترسی ندارید.',
|
||
], 403);
|
||
}
|
||
|
||
// بررسی وضعیت (فقط سفارشات تأیید شده قابل پرداخت هستند)
|
||
if ($shipment->status !== ShipmentStatus::Approved) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'این سفارش هنوز تأیید نشده است و قابل پرداخت نیست.',
|
||
], 400);
|
||
}
|
||
|
||
$wallet = $user->wallet;
|
||
if (!$wallet) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'کیف پول شما وجود ندارد.',
|
||
], 400);
|
||
}
|
||
|
||
$paymentService = app(\App\Services\OrderPaymentService::class);
|
||
$result = $paymentService->payFromWallet($shipment, $wallet);
|
||
|
||
if ($result['success']) {
|
||
return response()->json([
|
||
'success' => true,
|
||
'message' => $result['message'],
|
||
'data' => $this->formatShipment($shipment->fresh()->load(['fromCountry', 'toCountry'])),
|
||
]);
|
||
}
|
||
|
||
return response()->json($result, 400);
|
||
}
|
||
|
||
/**
|
||
* شروع پرداخت از درگاه
|
||
* POST /api/v1/customer/orders/{shipment}/pay-gateway
|
||
*/
|
||
public function payViaGateway(Request $request, Shipment $shipment): JsonResponse
|
||
{
|
||
$user = Auth::user();
|
||
|
||
// بررسی مالکیت
|
||
if ($shipment->user_id !== $user->id) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'شما به این سفارش دسترسی ندارید.',
|
||
], 403);
|
||
}
|
||
|
||
// بررسی وضعیت (فقط سفارشات تأیید شده قابل پرداخت هستند)
|
||
if ($shipment->status !== ShipmentStatus::Approved) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'این سفارش هنوز تأیید نشده است و قابل پرداخت نیست.',
|
||
], 400);
|
||
}
|
||
|
||
$validated = $request->validate([
|
||
'frontend_callback' => ['required', 'url', 'max:500'],
|
||
]);
|
||
|
||
$wallet = $user->wallet;
|
||
if (!$wallet) {
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => 'کیف پول شما وجود ندارد.',
|
||
], 400);
|
||
}
|
||
|
||
$paymentService = app(\App\Services\OrderPaymentService::class);
|
||
$result = $paymentService->initiateGatewayPayment($shipment, $wallet);
|
||
|
||
if ($result['success']) {
|
||
// ذخیره callback URL در metadata تراکنش
|
||
$transaction = WalletTransaction::find($result['transaction_id']);
|
||
if ($transaction) {
|
||
$metadata = $transaction->metadata ?? [];
|
||
$metadata['frontend_callback'] = $validated['frontend_callback'];
|
||
$transaction->update(['metadata' => $metadata]);
|
||
}
|
||
|
||
return response()->json([
|
||
'success' => true,
|
||
'payment_url' => $result['payment_url'],
|
||
'transaction_id' => $result['transaction_id'],
|
||
]);
|
||
}
|
||
|
||
return response()->json($result, 400);
|
||
}
|
||
} |