feat(core): integrate Kavenegar SMS notifications and audit logging
Implement a comprehensive notification system using Kavenegar SMS
gateway and enhance system traceability through audit logging and
detailed shipment status history.
- SMS Integration:
- Add Kavenegar SMS service with configurable API keys and sender
numbers via system settings.
- Implement automated SMS notifications for shipment approval,
rejection, successful payments, and tracking updates.
- Add administrative UI in Filament to manage SMS gateway settings
and toggle specific notification types.
- Audit & Tracking:
- Apply `Auditable` trait to core models (User, Shipment, Wallet,
etc.) to track changes.
- Refactor `ShipmentStatusHistory` to include status transitions
(`from_status` to `to_status`) and specific reasons for changes.
- Implement `ShipmentObserver` to automate notification triggers
on status changes.
- Database & Config:
- Add migrations for enhanced shipment status history tracking.
- Update `.env.example` and `config/ifnex.php` with Kavenegar
configuration parameters.
This commit is contained in:
parent
7a8ff49baa
commit
d6e04d53fa
@ -78,6 +78,13 @@ ZARINPAL_FRONTEND_SUCCESS_URL=http://localhost:8080/payment/success
|
||||
ZARINPAL_FRONTEND_FAILURE_URL=http://localhost:8080/payment/failed
|
||||
ZARINPAL_EXPIRY_MINUTES=30
|
||||
|
||||
# Kavenegar SMS Gateway
|
||||
KAVENEGAR_API_KEY=
|
||||
KAVENEGAR_SENDER=10008566
|
||||
KAVENEGAR_SEND_SHIPMENT_APPROVED=true
|
||||
KAVENEGAR_SEND_PAYMENT_SUCCESS=true
|
||||
KAVENEGAR_SEND_TRACKING_UPDATE=false
|
||||
|
||||
# Wallet Settings
|
||||
WALLET_MIN_DEPOSIT=10000
|
||||
WALLET_MAX_DEPOSIT=500000000
|
||||
|
||||
@ -22,6 +22,13 @@ class IfnexSettingsPage extends Page
|
||||
public float $cny_to_irr = 0;
|
||||
public float $eur_to_irr = 0;
|
||||
|
||||
public string $kavenegar_api_key = '';
|
||||
public string $kavenegar_sender = '10008566';
|
||||
public bool $kavenegar_send_shipment_approved = true;
|
||||
public bool $kavenegar_send_shipment_rejected = true;
|
||||
public bool $kavenegar_send_payment_success = true;
|
||||
public bool $kavenegar_send_tracking_update = false;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$settings = SystemSetting::all()->pluck('value', 'key')->toArray();
|
||||
@ -33,6 +40,13 @@ class IfnexSettingsPage extends Page
|
||||
if (isset($settings['usd_to_irr'])) $this->usd_to_irr = (float) $settings['usd_to_irr'];
|
||||
if (isset($settings['cny_to_irr'])) $this->cny_to_irr = (float) $settings['cny_to_irr'];
|
||||
if (isset($settings['eur_to_irr'])) $this->eur_to_irr = (float) $settings['eur_to_irr'];
|
||||
|
||||
if (isset($settings['kavenegar_api_key'])) $this->kavenegar_api_key = (string) $settings['kavenegar_api_key'];
|
||||
if (isset($settings['kavenegar_sender'])) $this->kavenegar_sender = (string) $settings['kavenegar_sender'];
|
||||
if (isset($settings['kavenegar_send_shipment_approved'])) $this->kavenegar_send_shipment_approved = (bool) $settings['kavenegar_send_shipment_approved'];
|
||||
if (isset($settings['kavenegar_send_shipment_rejected'])) $this->kavenegar_send_shipment_rejected = (bool) $settings['kavenegar_send_shipment_rejected'];
|
||||
if (isset($settings['kavenegar_send_payment_success'])) $this->kavenegar_send_payment_success = (bool) $settings['kavenegar_send_payment_success'];
|
||||
if (isset($settings['kavenegar_send_tracking_update'])) $this->kavenegar_send_tracking_update = (bool) $settings['kavenegar_send_tracking_update'];
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
@ -68,6 +82,31 @@ class IfnexSettingsPage extends Page
|
||||
['value' => $this->eur_to_irr, 'updated_by' => $userId]
|
||||
);
|
||||
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => 'kavenegar_api_key'],
|
||||
['value' => $this->kavenegar_api_key, 'updated_by' => $userId]
|
||||
);
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => 'kavenegar_sender'],
|
||||
['value' => $this->kavenegar_sender, 'updated_by' => $userId]
|
||||
);
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => 'kavenegar_send_shipment_approved'],
|
||||
['value' => (int) $this->kavenegar_send_shipment_approved, 'updated_by' => $userId]
|
||||
);
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => 'kavenegar_send_shipment_rejected'],
|
||||
['value' => (int) $this->kavenegar_send_shipment_rejected, 'updated_by' => $userId]
|
||||
);
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => 'kavenegar_send_payment_success'],
|
||||
['value' => (int) $this->kavenegar_send_payment_success, 'updated_by' => $userId]
|
||||
);
|
||||
SystemSetting::updateOrCreate(
|
||||
['key' => 'kavenegar_send_tracking_update'],
|
||||
['value' => (int) $this->kavenegar_send_tracking_update, 'updated_by' => $userId]
|
||||
);
|
||||
|
||||
\Filament\Notifications\Notification::make()
|
||||
->title('Success')
|
||||
->body('Settings saved successfully.')
|
||||
|
||||
@ -589,12 +589,6 @@ class ShipmentResource extends Resource
|
||||
]),
|
||||
]);
|
||||
}
|
||||
public static function afterSave(\Filament\Resources\Pages\Page $page, \Illuminate\Database\Eloquent\Model $record): void
|
||||
{
|
||||
if ($page instanceof \Filament\Resources\Pages\EditRecord) {
|
||||
ShipmentUpdatedNotification::notify($record);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
|
||||
@ -72,7 +72,9 @@ class CustomerFinancialController extends Controller
|
||||
});
|
||||
|
||||
// دریافت تراکنشهای اخیر
|
||||
$recent_transactions = WalletTransaction::where('user_id', $customer->id)
|
||||
$recent_transactions = WalletTransaction::whereHas('wallet', function ($q) use ($customer) {
|
||||
$q->where('user_id', $customer->id);
|
||||
})
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(10)
|
||||
->get()
|
||||
|
||||
@ -219,6 +219,13 @@ class PaymentController extends Controller
|
||||
$shipment = \App\Models\Shipment::find($metadata['shipment_id']);
|
||||
if ($shipment && $shipment->status === \App\Enums\ShipmentStatus::Approved) {
|
||||
$shipment->update(['status' => \App\Enums\ShipmentStatus::Processed]);
|
||||
|
||||
if (SystemSetting::get('kavenegar_api_key') && (bool) SystemSetting::get('kavenegar_send_payment_success', true)) {
|
||||
$shipment->user->notify(new \App\Notifications\PaymentSuccessSms(
|
||||
$shipment->awb_no,
|
||||
(int) $transaction->amount
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -74,10 +74,10 @@ class StaffOrderController extends Controller
|
||||
// ثبت در تاریخچه تغییرات
|
||||
ShipmentStatusHistory::create([
|
||||
'shipment_id' => $shipment->id,
|
||||
'old_status' => $oldStatus->value,
|
||||
'new_status' => ShipmentStatus::Approved->value,
|
||||
'from_status' => $oldStatus->value,
|
||||
'to_status' => ShipmentStatus::Approved->value,
|
||||
'reason' => $validated['notes'] ?? 'تأیید توسط کارمند',
|
||||
'changed_by' => $user->id,
|
||||
'notes' => $validated['notes'] ?? 'تأیید توسط کارمند',
|
||||
]);
|
||||
});
|
||||
|
||||
@ -126,10 +126,10 @@ class StaffOrderController extends Controller
|
||||
// ثبت در تاریخچه تغییرات
|
||||
ShipmentStatusHistory::create([
|
||||
'shipment_id' => $shipment->id,
|
||||
'old_status' => $oldStatus->value,
|
||||
'new_status' => ShipmentStatus::Cancelled->value,
|
||||
'from_status' => $oldStatus->value,
|
||||
'to_status' => ShipmentStatus::Cancelled->value,
|
||||
'reason' => 'رد شده: ' . $validated['reason'],
|
||||
'changed_by' => $user->id,
|
||||
'notes' => 'رد شده: ' . $validated['reason'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -5,10 +5,11 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class CommitmentForm extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, Auditable;
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
|
||||
@ -5,9 +5,11 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class Shipment extends Model
|
||||
{
|
||||
use Auditable;
|
||||
protected $fillable = [
|
||||
'awb_no',
|
||||
'forwarder',
|
||||
@ -35,6 +37,7 @@ class Shipment extends Model
|
||||
'net_rial',
|
||||
'invoice_total_usd',
|
||||
'cod_amount',
|
||||
'declared_value',
|
||||
// Import Invoice Fields
|
||||
'brand_fee',
|
||||
'report_fee',
|
||||
@ -65,6 +68,7 @@ class Shipment extends Model
|
||||
// Customs
|
||||
'reason_for_export',
|
||||
'content_description',
|
||||
'customer_notes',
|
||||
// User
|
||||
'user_id',
|
||||
];
|
||||
|
||||
@ -3,9 +3,11 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class ShipmentItem extends Model
|
||||
{
|
||||
use Auditable;
|
||||
protected $fillable = [
|
||||
'shipment_id',
|
||||
'row_number',
|
||||
|
||||
@ -4,9 +4,11 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class ShipmentPackage extends Model
|
||||
{
|
||||
use Auditable;
|
||||
protected $fillable = [
|
||||
'shipment_id',
|
||||
'package_no',
|
||||
|
||||
@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
class ShipmentStatusHistory extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'shipment_id', 'from_status', 'to_status', 'reason', 'changed_by',
|
||||
'shipment_id', 'from_status', 'to_status', 'reason', 'notes', 'changed_by',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
|
||||
@ -8,10 +8,11 @@ use Illuminate\Notifications\DatabaseNotification;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable, HasRoles;
|
||||
use HasApiTokens, HasFactory, Notifiable, HasRoles, Auditable;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
|
||||
@ -6,10 +6,11 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class Wallet extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, Auditable;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
|
||||
@ -9,10 +9,11 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use App\Enums\TransactionType;
|
||||
use App\Enums\TransactionStatus;
|
||||
use App\Enums\PaymentGateway;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class WalletTransaction extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, Auditable;
|
||||
|
||||
protected $fillable = [
|
||||
'wallet_id',
|
||||
|
||||
23
04_Laravel/app/Notifications/Channels/SmsChannel.php
Normal file
23
04_Laravel/app/Notifications/Channels/SmsChannel.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Channels;
|
||||
|
||||
use App\Services\KavenegarSmsService;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class SmsChannel
|
||||
{
|
||||
public function __construct(private KavenegarSmsService $smsService) {}
|
||||
|
||||
public function send(object $notifiable, Notification $notification): void
|
||||
{
|
||||
$phone = $notifiable->phone;
|
||||
$message = $notification->toSms($notifiable);
|
||||
|
||||
if (empty($phone) || empty($message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->smsService->send($phone, $message);
|
||||
}
|
||||
}
|
||||
34
04_Laravel/app/Notifications/PaymentSuccessSms.php
Normal file
34
04_Laravel/app/Notifications/PaymentSuccessSms.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class PaymentSuccessSms extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(public string $awbNo, public int $amount) {}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['database', 'sms'];
|
||||
}
|
||||
|
||||
public function toSms(User $notifiable): ?string
|
||||
{
|
||||
return "پرداخت سفارش شما با موفقیت انجام شد.\nشماره پیگیری: {$this->awbNo}\nمبلغ: " . number_format($this->amount) . " ریال";
|
||||
}
|
||||
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'title' => 'پرداخت موفق',
|
||||
'body' => "پرداخت سفارش {$this->awbNo} با موفقیت انجام شد. مبلغ: " . number_format($this->amount) . " ریال",
|
||||
'awb_no' => $this->awbNo,
|
||||
'amount' => $this->amount,
|
||||
];
|
||||
}
|
||||
}
|
||||
34
04_Laravel/app/Notifications/ShipmentApprovedSms.php
Normal file
34
04_Laravel/app/Notifications/ShipmentApprovedSms.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ShipmentApprovedSms extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(public string $awbNo, public string $trackingUrl) {}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['database', 'sms'];
|
||||
}
|
||||
|
||||
public function toSms(User $notifiable): ?string
|
||||
{
|
||||
return "سفارش شما با موفقیت تأیید شد.\nشماره پیگیری: {$this->awbNo}\nلینک پیگیری: {$this->trackingUrl}";
|
||||
}
|
||||
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'title' => 'تأیید سفارش',
|
||||
'body' => "سفارش {$this->awbNo} تأیید شد.",
|
||||
'awb_no' => $this->awbNo,
|
||||
'tracking_url' => $this->trackingUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
34
04_Laravel/app/Notifications/ShipmentRejectedSms.php
Normal file
34
04_Laravel/app/Notifications/ShipmentRejectedSms.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ShipmentRejectedSms extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(public string $awbNo, public string $reason) {}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['database', 'sms'];
|
||||
}
|
||||
|
||||
public function toSms(User $notifiable): ?string
|
||||
{
|
||||
return "سفارش شما رد شد.\nشماره پیگیری: {$this->awbNo}\nدلیل: {$this->reason}";
|
||||
}
|
||||
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'title' => 'رد سفارش',
|
||||
'body' => "سفارش {$this->awbNo} رد شد. دلیل: {$this->reason}",
|
||||
'awb_no' => $this->awbNo,
|
||||
'reason' => $this->reason,
|
||||
];
|
||||
}
|
||||
}
|
||||
35
04_Laravel/app/Notifications/TrackingUpdatedSms.php
Normal file
35
04_Laravel/app/Notifications/TrackingUpdatedSms.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class TrackingUpdatedSms extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(public string $awbNo, public string $status, public string $location) {}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['database', 'sms'];
|
||||
}
|
||||
|
||||
public function toSms(User $notifiable): ?string
|
||||
{
|
||||
return "وضعیت سفارش شما تغییر کرد.\nشماره پیگیری: {$this->awbNo}\nوضعیت: {$this->status}\nمحل: {$this->location}";
|
||||
}
|
||||
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'title' => 'بهروزرسانی رهگیری',
|
||||
'body' => "وضعیت سفارش {$this->awbNo} به {$this->status} تغییر کرد.",
|
||||
'awb_no' => $this->awbNo,
|
||||
'status' => $this->status,
|
||||
'location' => $this->location,
|
||||
];
|
||||
}
|
||||
}
|
||||
45
04_Laravel/app/Observers/ShipmentObserver.php
Normal file
45
04_Laravel/app/Observers/ShipmentObserver.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Shipment;
|
||||
use App\Notifications\ShipmentUpdatedNotification;
|
||||
use App\Notifications\ShipmentApprovedSms;
|
||||
use App\Notifications\ShipmentRejectedSms;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class ShipmentObserver
|
||||
{
|
||||
public function updated(Shipment $shipment): void
|
||||
{
|
||||
$changes = $shipment->getChanges();
|
||||
$ignored = ['updated_at'];
|
||||
|
||||
$changedFields = array_filter(
|
||||
array_keys($changes),
|
||||
fn ($key) => !in_array($key, $ignored)
|
||||
);
|
||||
|
||||
if (empty($changedFields)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ShipmentUpdatedNotification::notify($shipment);
|
||||
|
||||
$oldStatus = $shipment->getOriginal('status');
|
||||
$newStatus = $shipment->status;
|
||||
|
||||
if ($oldStatus !== $newStatus) {
|
||||
$trackingUrl = url("/track/{$shipment->awb_no}");
|
||||
$smsEnabled = !empty(SystemSetting::get('kavenegar_api_key'));
|
||||
|
||||
if ($smsEnabled && $newStatus === \App\Enums\ShipmentStatus::Approved && (bool) SystemSetting::get('kavenegar_send_shipment_approved', true)) {
|
||||
$shipment->user->notify(new ShipmentApprovedSms($shipment->awb_no, $trackingUrl));
|
||||
}
|
||||
|
||||
if ($smsEnabled && $newStatus === \App\Enums\ShipmentStatus::Cancelled && (bool) SystemSetting::get('kavenegar_send_shipment_rejected', true)) {
|
||||
$shipment->user->notify(new ShipmentRejectedSms($shipment->awb_no, 'سفارش شما رد شد.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,8 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Models\Shipment;
|
||||
use App\Observers\ShipmentObserver;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -21,5 +23,8 @@ class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
// هک برای رفع مشکل دیسک اکسل در لاراول ۱۱/۱۲
|
||||
config(['excel.temporary_files.disk' => 'local']);
|
||||
|
||||
// ثبت Observer برای اطلاعیههای تغییر سفارش
|
||||
Shipment::observe(ShipmentObserver::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\SystemSetting;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
@ -13,19 +14,20 @@ class KavenegarSmsService
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apiKey = config('services.kavenegar.api_key', '');
|
||||
$this->sender = config('services.kavenegar.sender', '');
|
||||
$this->apiKey = (string) SystemSetting::get('kavenegar_api_key', config('ifnex.kavenegar.api_key', ''));
|
||||
$this->sender = (string) SystemSetting::get('kavenegar_sender', config('ifnex.kavenegar.sender', '10008566'));
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return !empty($this->apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* ارسال کد تأیید به شماره موبایل
|
||||
*/
|
||||
public function sendVerificationCode(string $phone): array
|
||||
{
|
||||
$code = Str::random(5, '0123456789');
|
||||
$cacheKey = "sms_verify_{$phone}";
|
||||
|
||||
// ذخیره کد در cache به مدت ۱۰ دقیقه
|
||||
Cache::put($cacheKey, $code, now()->addMinutes(10));
|
||||
|
||||
$message = "کد تأیید IFNEX: {$code}";
|
||||
@ -33,9 +35,6 @@ class KavenegarSmsService
|
||||
return $this->send($phone, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی کد تأیید
|
||||
*/
|
||||
public function verifyCode(string $phone, string $code): bool
|
||||
{
|
||||
$cacheKey = "sms_verify_{$phone}";
|
||||
@ -49,12 +48,9 @@ class KavenegarSmsService
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ارسال پیامک
|
||||
*/
|
||||
public function send(string $receptor, string $message): array
|
||||
{
|
||||
if (empty($this->apiKey)) {
|
||||
if (!$this->isEnabled()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'API Key Kavenegar تنظیم نشده است',
|
||||
@ -91,12 +87,9 @@ class KavenegarSmsService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ارسال پیامک گروهی
|
||||
*/
|
||||
public function sendBulk(array $receptors, string $message): array
|
||||
{
|
||||
if (empty($this->apiKey)) {
|
||||
if (!$this->isEnabled()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'API Key Kavenegar تنظیم نشده است',
|
||||
@ -133,11 +126,15 @@ class KavenegarSmsService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی وضعیت پیامک ارسال شده
|
||||
*/
|
||||
public function checkStatus(string $messageId): array
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'API Key Kavenegar تنظیم نشده است',
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(10)
|
||||
->get("https://api.kavenegar.com/v1/{$this->apiKey}/select.json", [
|
||||
|
||||
@ -19,10 +19,23 @@ class TrackingService
|
||||
|
||||
public function addTrackingEvent(int $shipment_id, array $data): ShipmentTrackingEvent
|
||||
{
|
||||
return ShipmentTrackingEvent::create(array_merge($data, [
|
||||
$event = ShipmentTrackingEvent::create(array_merge($data, [
|
||||
'shipment_id' => $shipment_id,
|
||||
'source' => 'manual',
|
||||
]));
|
||||
|
||||
if (SystemSetting::get('kavenegar_api_key') && (bool) SystemSetting::get('kavenegar_send_tracking_update', false)) {
|
||||
$shipment = Shipment::with('user')->find($shipment_id);
|
||||
if ($shipment && $shipment->user && $shipment->user->phone) {
|
||||
$shipment->user->notify(new \App\Notifications\TrackingUpdatedSms(
|
||||
$shipment->awb_no,
|
||||
$event->event_description ?? 'بهروزرسانی',
|
||||
$event->location ?? ''
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
public function getShipmentWithTracking(string $awb_no)
|
||||
|
||||
@ -143,6 +143,20 @@ return [
|
||||
'low_balance_threshold' => env('LOW_BALANCE_THRESHOLD', 500000), // ۵۰۰,۰۰۰ ریال
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Kavenegar SMS Gateway (درگاه پیامک کاوهنگار)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'kavenegar' => [
|
||||
'api_key' => env('KAVENEGAR_API_KEY', ''),
|
||||
'sender' => env('KAVENEGAR_SENDER', '10008566'),
|
||||
'send_shipment_approved' => env('KAVENEGAR_SEND_SHIPMENT_APPROVED', true),
|
||||
'send_shipment_rejected' => env('KAVENEGAR_SEND_SHIPMENT_REJECTED', true),
|
||||
'send_payment_success' => env('KAVENEGAR_SEND_PAYMENT_SUCCESS', true),
|
||||
'send_tracking_update' => env('KAVENEGAR_SEND_TRACKING_UPDATE', false),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit & Logging (ثبت فعالیتها و لاگ)
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<?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::create('shipment_status_history', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('shipment_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('from_status')->nullable();
|
||||
$table->string('to_status');
|
||||
$table->unsignedBigInteger('changed_by')->nullable();
|
||||
$table->text('reason')->nullable();
|
||||
$table->timestamp('changed_at')->useCurrent();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('changed_by')->references('id')->on('users')->nullOnDelete();
|
||||
$table->index(['shipment_id', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('shipment_status_history');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
<?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('shipment_status_history', function (Blueprint $table) {
|
||||
$table->text('reason')->nullable()->after('to_status');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shipment_status_history', function (Blueprint $table) {
|
||||
$table->dropColumn('reason');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
<x-filament::page>
|
||||
<div class="space-y-6">
|
||||
<x-filament::card>
|
||||
<h2 class="text-lg font-medium text-gray-900 mb-4">تنظیمات مالی</h2>
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">VAT Rate</label>
|
||||
@ -39,12 +40,47 @@
|
||||
<input type="number" wire:model="eur_to_irr" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
</div>
|
||||
</div>
|
||||
</x-filament::card>
|
||||
|
||||
<x-filament::card>
|
||||
<h2 class="text-lg font-medium text-gray-900 mb-4">تنظیمات SMS (کاوهنگار)</h2>
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">API Key</label>
|
||||
<input type="text" wire:model="kavenegar_api_key" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Sender Number</label>
|
||||
<input type="text" wire:model="kavenegar_sender" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" wire:model="kavenegar_send_shipment_approved" id="kavenegar_send_shipment_approved" class="rounded border-gray-300">
|
||||
<label for="kavenegar_send_shipment_approved" class="text-sm font-medium text-gray-700">ارسال SMS هنگام تأیید سفارش</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" wire:model="kavenegar_send_shipment_rejected" id="kavenegar_send_shipment_rejected" class="rounded border-gray-300">
|
||||
<label for="kavenegar_send_shipment_rejected" class="text-sm font-medium text-gray-700">ارسال SMS هنگام رد سفارش</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" wire:model="kavenegar_send_payment_success" id="kavenegar_send_payment_success" class="rounded border-gray-300">
|
||||
<label for="kavenegar_send_payment_success" class="text-sm font-medium text-gray-700">ارسال SMS هنگام پرداخت موفق</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" wire:model="kavenegar_send_tracking_update" id="kavenegar_send_tracking_update" class="rounded border-gray-300">
|
||||
<label for="kavenegar_send_tracking_update" class="text-sm font-medium text-gray-700">ارسال SMS هنگام بهروزرسانی رهگیری</label>
|
||||
</div>
|
||||
</div>
|
||||
</x-filament::card>
|
||||
|
||||
<div class="mt-6">
|
||||
<button type="button" wire:click="save" class="filament-button filament-button-size-md inline-flex items-center justify-center py-2 px-4 rounded-md text-sm font-medium text-white bg-primary-600 hover:bg-primary-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</x-filament::card>
|
||||
</div>
|
||||
</x-filament::page>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user