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.
224 lines
8.6 KiB
PHP
224 lines
8.6 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Enums\PaymentGateway;
|
||
use App\Enums\ShipmentStatus;
|
||
use App\Enums\TransactionStatus;
|
||
use App\Enums\TransactionType;
|
||
use App\Models\Shipment;
|
||
use App\Models\ShipmentTrackingEvent;
|
||
use App\Models\Wallet;
|
||
use App\Models\WalletTransaction;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Log;
|
||
|
||
class OrderPaymentService
|
||
{
|
||
/**
|
||
* پرداخت از کیف پول
|
||
*/
|
||
public function payFromWallet(Shipment $shipment, Wallet $wallet): array
|
||
{
|
||
// بررسی موجودی
|
||
if (!$wallet->hasSufficientBalance($shipment->total_fee)) {
|
||
return [
|
||
'success' => false,
|
||
'message' => 'موجودی کیف پول کافی نیست.',
|
||
'required' => $shipment->total_fee,
|
||
'available' => $wallet->balance,
|
||
];
|
||
}
|
||
|
||
try {
|
||
return DB::transaction(function () use ($shipment, $wallet) {
|
||
// ۱. ثبت تراکنش کسر از کیف پول
|
||
$transaction = WalletTransaction::create([
|
||
'wallet_id' => $wallet->id,
|
||
'amount' => -$shipment->total_fee,
|
||
'balance_before' => $wallet->balance,
|
||
'balance_after' => $wallet->balance - $shipment->total_fee,
|
||
'type' => TransactionType::ORDER_PAYMENT,
|
||
'status' => TransactionStatus::COMPLETED,
|
||
'gateway' => PaymentGateway::WALLET,
|
||
'description' => "پرداخت سفارش {$shipment->awb_no}",
|
||
'transactionable_type' => Shipment::class,
|
||
'transactionable_id' => $shipment->id,
|
||
'ip_address' => request()->ip(),
|
||
'user_agent' => request()->userAgent(),
|
||
]);
|
||
|
||
// ۲. بهروزرسانی موجودی کیف پول
|
||
$wallet->update([
|
||
'balance' => $wallet->balance - $shipment->total_fee,
|
||
'total_withdrawn' => $wallet->total_withdrawn + $shipment->total_fee,
|
||
]);
|
||
|
||
// ۳. تغییر status سفارش به processed
|
||
$shipment->update(['status' => ShipmentStatus::Processed]);
|
||
|
||
// ۴. ثبت tracking event (با همه فیلدهای احتمالی)
|
||
$this->createTrackingEvent($shipment, 'پرداخت تکمیل شد (از کیف پول)');
|
||
|
||
Log::info('Order paid from wallet', [
|
||
'shipment_id' => $shipment->id,
|
||
'awb_no' => $shipment->awb_no,
|
||
'amount' => $shipment->total_fee,
|
||
'wallet_id' => $wallet->id,
|
||
]);
|
||
|
||
return [
|
||
'success' => true,
|
||
'message' => 'پرداخت با موفقیت انجام شد.',
|
||
'transaction_id' => $transaction->id,
|
||
'new_status' => 'processed',
|
||
];
|
||
});
|
||
} catch (\Exception $e) {
|
||
Log::error('Wallet payment failed', [
|
||
'shipment_id' => $shipment->id,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
|
||
return [
|
||
'success' => false,
|
||
'message' => 'خطا در پرداخت: ' . $e->getMessage(),
|
||
];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* شروع پرداخت از درگاه
|
||
*/
|
||
public function initiateGatewayPayment(Shipment $shipment, Wallet $wallet): array
|
||
{
|
||
try {
|
||
// ۱. ایجاد تراکنش pending
|
||
$transaction = WalletTransaction::create([
|
||
'wallet_id' => $wallet->id,
|
||
'amount' => $shipment->total_fee,
|
||
'balance_before' => $wallet->balance,
|
||
'balance_after' => $wallet->balance,
|
||
'type' => TransactionType::ORDER_PAYMENT,
|
||
'status' => TransactionStatus::PENDING,
|
||
'gateway' => PaymentGateway::ZARINPAL,
|
||
'description' => "پرداخت سفارش {$shipment->awb_no}",
|
||
'transactionable_type' => Shipment::class,
|
||
'transactionable_id' => $shipment->id,
|
||
'ip_address' => request()->ip(),
|
||
'user_agent' => request()->userAgent(),
|
||
'metadata' => [
|
||
'shipment_id' => $shipment->id,
|
||
'awb_no' => $shipment->awb_no,
|
||
'is_order_payment' => true,
|
||
],
|
||
]);
|
||
|
||
// ۲. درخواست به درگاه
|
||
$gatewayService = app()->make(\App\Services\ZarinpalService::class);
|
||
|
||
if (config('ifnex.zarinpal.merchant_id') === 'fake-merchant-id-for-testing'
|
||
|| config('ifnex.zarinpal.sandbox', true)) {
|
||
$gatewayService = app()->make(\App\Services\MockZarinpalService::class);
|
||
}
|
||
|
||
$paymentData = $gatewayService->requestPayment(
|
||
amount: $shipment->total_fee,
|
||
description: "پرداخت سفارش {$shipment->awb_no}",
|
||
mobile: $shipment->user->phone,
|
||
email: $shipment->user->email
|
||
);
|
||
|
||
// ۳. ذخیره authority در metadata
|
||
$metadata = $transaction->metadata ?? [];
|
||
$metadata['zarinpal_authority'] = $paymentData['authority'];
|
||
$transaction->update(['metadata' => $metadata]);
|
||
|
||
return [
|
||
'success' => true,
|
||
'payment_url' => $paymentData['payment_url'],
|
||
'transaction_id' => $transaction->id,
|
||
'authority' => $paymentData['authority'],
|
||
];
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('Gateway payment initiation failed', [
|
||
'shipment_id' => $shipment->id,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
|
||
return [
|
||
'success' => false,
|
||
'message' => 'خطا در اتصال به درگاه: ' . $e->getMessage(),
|
||
];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* تکمیل پرداخت بعد از callback موفق
|
||
*/
|
||
public function completeGatewayPayment(WalletTransaction $transaction): bool
|
||
{
|
||
try {
|
||
return DB::transaction(function () use ($transaction) {
|
||
$shipment = Shipment::find($transaction->transactionable_id);
|
||
|
||
if (!$shipment || $shipment->status !== ShipmentStatus::Approved) {
|
||
return false;
|
||
}
|
||
|
||
// ۱. بهروزرسانی تراکنش
|
||
$transaction->update([
|
||
'status' => TransactionStatus::COMPLETED,
|
||
'balance_after' => $transaction->wallet->balance,
|
||
]);
|
||
|
||
// ۲. تغییر status سفارش
|
||
$shipment->update(['status' => ShipmentStatus::Processed]);
|
||
|
||
// ۳. ثبت tracking event
|
||
$this->createTrackingEvent($shipment, 'پرداخت تکمیل شد (از درگاه بانکی)');
|
||
|
||
Log::info('Order paid via gateway', [
|
||
'shipment_id' => $shipment->id,
|
||
'awb_no' => $shipment->awb_no,
|
||
'transaction_id' => $transaction->id,
|
||
]);
|
||
|
||
return true;
|
||
});
|
||
} catch (\Exception $e) {
|
||
Log::error('Complete gateway payment failed', [
|
||
'transaction_id' => $transaction->id,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ساخت Tracking Event با ساختار دقیق جدول
|
||
*/
|
||
private function createTrackingEvent(Shipment $shipment, string $description): void
|
||
{
|
||
try {
|
||
ShipmentTrackingEvent::create([
|
||
'shipment_id' => $shipment->id,
|
||
'event_date' => now()->toDateString(), // YYYY-MM-DD
|
||
'event_time' => now()->format('H:i:s'), // HH:MM:SS ← فیلد جدید
|
||
'event_description' => $description,
|
||
'location' => 'سیستم',
|
||
'delivery_status' => 'processed', // ← اصلاح شد (status وجود نداشت)
|
||
'source' => 'manual', // مقدار معتبر enum TrackingSource
|
||
]);
|
||
|
||
} catch (\Exception $e) {
|
||
// اگر tracking event ثبت نشد، فقط لاگ کن (تراکنش را خراب نکن)
|
||
Log::warning('Failed to create tracking event', [
|
||
'shipment_id' => $shipment->id,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
}
|
||
}
|
||
} |