180 lines
5.2 KiB
PHP
180 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\SmsBilling;
|
|
|
|
use App\Models\Setting;
|
|
use App\Models\SmsLedger;
|
|
use App\Models\SmsSendLog;
|
|
use App\Models\SmsWallet;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class SmsWalletService
|
|
{
|
|
public const SUPER_ADMIN_MOBILE = '09128081392';
|
|
|
|
public const TYPE_CHARGE = 'charge';
|
|
public const TYPE_DEBIT = 'debit';
|
|
public const TYPE_ALERT_DEBIT = 'alert_debit';
|
|
|
|
/** Billing/panel فعال است مگر sms_panel صریحاً false باشد. */
|
|
public static function isBillingEnabled(): bool
|
|
{
|
|
$value = Setting::where('key', 'sms_panel')->value('value');
|
|
|
|
if ($value === null || trim((string) $value) === '') {
|
|
return true;
|
|
}
|
|
|
|
$normalized = strtolower(trim((string) $value));
|
|
|
|
return ! in_array($normalized, ['false', '0', 'no', 'off'], true);
|
|
}
|
|
|
|
public function getWallet(): SmsWallet
|
|
{
|
|
return SmsWallet::query()->firstOrCreate([], [
|
|
'balance' => 0,
|
|
'price_per_part' => 0,
|
|
'is_blocked' => false,
|
|
'alert_days_sent' => 0,
|
|
]);
|
|
}
|
|
|
|
public function isBlocked(): bool
|
|
{
|
|
if (! self::isBillingEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
return (bool) $this->getWallet()->is_blocked;
|
|
}
|
|
|
|
public function setBlocked(bool $blocked): SmsWallet
|
|
{
|
|
$wallet = $this->getWallet();
|
|
$wallet->is_blocked = $blocked;
|
|
$wallet->save();
|
|
|
|
return $wallet->fresh();
|
|
}
|
|
|
|
public function updatePricePerPart(int $pricePerPart): SmsWallet
|
|
{
|
|
$wallet = $this->getWallet();
|
|
$wallet->price_per_part = max(0, $pricePerPart);
|
|
$wallet->save();
|
|
|
|
return $wallet;
|
|
}
|
|
|
|
public function charge(int $amount, ?int $adminUserId = null, ?string $note = null): SmsWallet
|
|
{
|
|
if ($amount <= 0) {
|
|
throw new \InvalidArgumentException('مبلغ شارژ باید بزرگتر از صفر باشد.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($amount, $adminUserId, $note) {
|
|
$wallet = SmsWallet::query()->lockForUpdate()->firstOrCreate([], [
|
|
'balance' => 0,
|
|
'price_per_part' => 0,
|
|
'is_blocked' => false,
|
|
'alert_days_sent' => 0,
|
|
]);
|
|
|
|
$wallet->balance += $amount;
|
|
|
|
if ($wallet->balance > 0) {
|
|
$wallet->alert_days_sent = 0;
|
|
$wallet->last_alert_date = null;
|
|
}
|
|
|
|
$wallet->save();
|
|
|
|
SmsLedger::create([
|
|
'type' => self::TYPE_CHARGE,
|
|
'amount' => $amount,
|
|
'balance_after' => $wallet->balance,
|
|
'note' => $note,
|
|
'created_by' => $adminUserId,
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
return $wallet;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return array{log: SmsSendLog, wallet: SmsWallet}
|
|
*/
|
|
public function debitSuccessfulSms(
|
|
string $receiver,
|
|
string $body,
|
|
int $parts,
|
|
string $provider,
|
|
?string $source = null,
|
|
bool $isAlert = false
|
|
): array {
|
|
return DB::transaction(function () use ($receiver, $body, $parts, $provider, $source, $isAlert) {
|
|
$wallet = SmsWallet::query()->lockForUpdate()->firstOrCreate([], [
|
|
'balance' => 0,
|
|
'price_per_part' => 0,
|
|
'is_blocked' => false,
|
|
'alert_days_sent' => 0,
|
|
]);
|
|
|
|
$pricePerPart = (int) $wallet->price_per_part;
|
|
$totalAmount = $parts * $pricePerPart;
|
|
|
|
$wallet->balance -= $totalAmount;
|
|
$wallet->save();
|
|
|
|
$log = SmsSendLog::create([
|
|
'receiver' => $receiver,
|
|
'body' => $body,
|
|
'parts' => $parts,
|
|
'price_per_part' => $pricePerPart,
|
|
'total_amount' => $totalAmount,
|
|
'provider' => $provider,
|
|
'source' => $source,
|
|
'is_alert' => $isAlert,
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
SmsLedger::create([
|
|
'type' => $isAlert ? self::TYPE_ALERT_DEBIT : self::TYPE_DEBIT,
|
|
'amount' => -$totalAmount,
|
|
'balance_after' => $wallet->balance,
|
|
'price_per_part' => $pricePerPart,
|
|
'parts' => $parts,
|
|
'sms_send_log_id' => $log->id,
|
|
'note' => $isAlert ? 'هشدار موجودی پیامک' : null,
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
return ['log' => $log, 'wallet' => $wallet->fresh()];
|
|
});
|
|
}
|
|
|
|
public static function isSmsPanelSuperAdmin(?object $user): bool
|
|
{
|
|
if (! $user) {
|
|
return false;
|
|
}
|
|
|
|
return self::normalizeMobile((string) ($user->mobile ?? '')) === self::SUPER_ADMIN_MOBILE;
|
|
}
|
|
|
|
public static function normalizeMobile(string $mobile): string
|
|
{
|
|
$digits = preg_replace('/\D/', '', $mobile) ?? '';
|
|
|
|
if (str_starts_with($digits, '98') && strlen($digits) === 12) {
|
|
$digits = '0'.substr($digits, 2);
|
|
} elseif (strlen($digits) === 10 && str_starts_with($digits, '9')) {
|
|
$digits = '0'.$digits;
|
|
}
|
|
|
|
return $digits;
|
|
}
|
|
}
|