158 lines
4.7 KiB
PHP
158 lines
4.7 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Services\SmsBilling;
|
||
|
|
|
||
|
|
use App\Exceptions\SmsBlockedException;
|
||
|
|
use App\Models\Setting;
|
||
|
|
use App\Models\User;
|
||
|
|
use App\Services\SmsBilling\Contracts\SmsProviderInterface;
|
||
|
|
use App\Services\SmsBilling\Providers\CentralSmsApiProvider;
|
||
|
|
use App\Services\SmsBilling\Providers\MeliPayamakProvider;
|
||
|
|
use Illuminate\Support\Facades\Log;
|
||
|
|
|
||
|
|
class SmsGatewayService
|
||
|
|
{
|
||
|
|
/** @var list<SmsProviderInterface> */
|
||
|
|
private array $providers;
|
||
|
|
|
||
|
|
public function __construct(
|
||
|
|
private SmsWalletService $walletService,
|
||
|
|
private SmsSegmentCalculator $segmentCalculator,
|
||
|
|
private SmsAlertService $alertService,
|
||
|
|
MeliPayamakProvider $meliPayamakProvider,
|
||
|
|
CentralSmsApiProvider $centralSmsApiProvider,
|
||
|
|
) {
|
||
|
|
$this->providers = [
|
||
|
|
$meliPayamakProvider,
|
||
|
|
$centralSmsApiProvider,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function send(
|
||
|
|
string $to,
|
||
|
|
string $message,
|
||
|
|
?string $source = null,
|
||
|
|
bool $isAlert = false
|
||
|
|
): SmsSendResult {
|
||
|
|
$config = $this->buildProviderConfig();
|
||
|
|
$billingEnabled = SmsWalletService::isBillingEnabled();
|
||
|
|
|
||
|
|
if ($billingEnabled && $this->walletService->isBlocked()) {
|
||
|
|
return SmsSendResult::blocked();
|
||
|
|
}
|
||
|
|
|
||
|
|
$provider = $this->resolveProvider($config);
|
||
|
|
|
||
|
|
if (! $provider) {
|
||
|
|
Log::warning('SmsGateway: no SMS provider configured');
|
||
|
|
|
||
|
|
return SmsSendResult::failed('سرویس پیامک پیکربندی نشده است.');
|
||
|
|
}
|
||
|
|
|
||
|
|
$text = trim($message);
|
||
|
|
|
||
|
|
if ($text === '') {
|
||
|
|
return SmsSendResult::failed('متن پیامک خالی است.');
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$result = $provider->send($to, $text, $config);
|
||
|
|
|
||
|
|
if (! ($result['success'] ?? false)) {
|
||
|
|
Log::warning('SmsGateway: provider send failed', [
|
||
|
|
'provider' => $this->providerLabel($provider),
|
||
|
|
'to' => $to,
|
||
|
|
'raw' => $result['raw'] ?? null,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return SmsSendResult::failed('ارسال پیامک ناموفق بود.');
|
||
|
|
}
|
||
|
|
|
||
|
|
$parts = $this->segmentCalculator->countParts($text);
|
||
|
|
|
||
|
|
if ($parts <= 0) {
|
||
|
|
return SmsSendResult::failed('محاسبه پارت پیامک نامعتبر است.');
|
||
|
|
}
|
||
|
|
|
||
|
|
$providerName = $this->providerLabel($provider);
|
||
|
|
|
||
|
|
if ($billingEnabled) {
|
||
|
|
$debit = $this->walletService->debitSuccessfulSms(
|
||
|
|
$to,
|
||
|
|
$text,
|
||
|
|
$parts,
|
||
|
|
$providerName,
|
||
|
|
$source,
|
||
|
|
$isAlert
|
||
|
|
);
|
||
|
|
|
||
|
|
if (! $isAlert) {
|
||
|
|
$this->alertService->maybeSendLowBalanceAlerts($debit['wallet']);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return SmsSendResult::success($providerName);
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
Log::error('SmsGateway: exception during send', [
|
||
|
|
'to' => $to,
|
||
|
|
'message' => $e->getMessage(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
return SmsSendResult::failed($e->getMessage());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function sendOrThrow(
|
||
|
|
string $to,
|
||
|
|
string $message,
|
||
|
|
?string $source = null,
|
||
|
|
bool $isAlert = false
|
||
|
|
): SmsSendResult {
|
||
|
|
$result = $this->send($to, $message, $source, $isAlert);
|
||
|
|
|
||
|
|
if ($result->blocked) {
|
||
|
|
throw new SmsBlockedException();
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function buildProviderConfig(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'melipayamak_user' => Setting::where('key', 'melipayamak_user')->value('value'),
|
||
|
|
'melipayamak_password' => Setting::where('key', 'melipayamak_password')->value('value'),
|
||
|
|
'melipayamak_from' => Setting::where('key', 'melipayamak_from')->value('value'),
|
||
|
|
'name_fa' => Setting::where('key', 'name_fa')->value('value'),
|
||
|
|
'name_en' => Setting::where('key', 'name_en')->value('value'),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
private function resolveProvider(array $config): ?SmsProviderInterface
|
||
|
|
{
|
||
|
|
$meliUser = trim((string) ($config['melipayamak_user'] ?? ''));
|
||
|
|
|
||
|
|
if (strlen($meliUser) > 1) {
|
||
|
|
return $this->providerByClass(MeliPayamakProvider::class);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->providerByClass(CentralSmsApiProvider::class);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function providerByClass(string $class): ?SmsProviderInterface
|
||
|
|
{
|
||
|
|
foreach ($this->providers as $provider) {
|
||
|
|
if ($provider instanceof $class) {
|
||
|
|
return $provider;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function providerLabel(SmsProviderInterface $provider): string
|
||
|
|
{
|
||
|
|
return $provider->name();
|
||
|
|
}
|
||
|
|
}
|