Sync SMS panel with SuperAdmin and mirror local wallet/logs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,14 +7,103 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Models\SmsLedger;
|
use App\Models\SmsLedger;
|
||||||
use App\Models\SmsSendLog;
|
use App\Models\SmsSendLog;
|
||||||
use App\Services\SmsBilling\SmsWalletService;
|
use App\Services\SmsBilling\SmsWalletService;
|
||||||
|
use App\Services\SmsBilling\SuperAdminSmsClient;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class SmsPanelController extends Controller
|
class SmsPanelController extends Controller
|
||||||
{
|
{
|
||||||
|
private function sa(): SuperAdminSmsClient
|
||||||
|
{
|
||||||
|
return app(SuperAdminSmsClient::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function useSuperAdmin(): bool
|
||||||
|
{
|
||||||
|
return $this->sa()->enabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function abortSaUnavailable(): void
|
||||||
|
{
|
||||||
|
abort(502, 'ارتباط با کیف پول پیامک سوپرادمین برقرار نشد.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $chargeInfo
|
||||||
|
* @return array{card_number: string, sheba: string, account_holder: string, note: string}
|
||||||
|
*/
|
||||||
|
private function depositFromChargeInfo(array $chargeInfo): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'card_number' => (string) ($chargeInfo['card'] ?? ''),
|
||||||
|
'sheba' => (string) ($chargeInfo['iban'] ?? ''),
|
||||||
|
'account_holder' => (string) ($chargeInfo['account_holder'] ?? ''),
|
||||||
|
'note' => (string) ($chargeInfo['instruction'] ?? ''),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $data
|
||||||
|
*/
|
||||||
|
private function mirrorLocalFromSa(array $data): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$wallet = is_array($data['wallet'] ?? null) ? $data['wallet'] : $data;
|
||||||
|
|
||||||
|
$balance = null;
|
||||||
|
if (isset($wallet['balance'])) {
|
||||||
|
$balance = (int) $wallet['balance'];
|
||||||
|
} elseif (isset($data['balance'])) {
|
||||||
|
$balance = (int) $data['balance'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$price = null;
|
||||||
|
if (isset($wallet['price_per_part'])) {
|
||||||
|
$price = (int) $wallet['price_per_part'];
|
||||||
|
} elseif (isset($data['price_per_part'])) {
|
||||||
|
$price = (int) $data['price_per_part'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$blocked = null;
|
||||||
|
if (is_array($data['wallet'] ?? null) && array_key_exists('is_blocked', $wallet)) {
|
||||||
|
$blocked = (bool) $wallet['is_blocked'];
|
||||||
|
} elseif (array_key_exists('is_blocked', $data)) {
|
||||||
|
$blocked = (bool) $data['is_blocked'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($balance === null && $price === null && $blocked === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app(SmsWalletService::class)->mirrorFromSuperAdmin($balance, $price, $blocked);
|
||||||
|
|
||||||
|
if (isset($data['charge_info']) && is_array($data['charge_info'])) {
|
||||||
|
SmsWalletService::setDepositInfo($this->depositFromChargeInfo($data['charge_info']));
|
||||||
|
}
|
||||||
|
if (isset($data['deposit']) && is_array($data['deposit'])) {
|
||||||
|
SmsWalletService::setDepositInfo($data['deposit']);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function overview()
|
public function overview()
|
||||||
{
|
{
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$data = $this->sa()->overview();
|
||||||
|
if ($data === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
$charge = is_array($data['charge_info'] ?? null) ? $data['charge_info'] : [];
|
||||||
|
$data['deposit'] = $this->depositFromChargeInfo($charge);
|
||||||
|
$this->mirrorLocalFromSa($data);
|
||||||
|
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
|
|
||||||
$wallet = app(SmsWalletService::class)->getWallet();
|
$wallet = app(SmsWalletService::class)->getWallet();
|
||||||
|
|
||||||
$monthStart = Carbon::now()->startOfMonth();
|
$monthStart = Carbon::now()->startOfMonth();
|
||||||
@@ -67,6 +156,27 @@ class SmsPanelController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$balanceData = $this->sa()->balance();
|
||||||
|
$charge = $this->sa()->chargeInfo();
|
||||||
|
if ($balanceData === null || $charge === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mirrorLocalFromSa(array_merge($balanceData, [
|
||||||
|
'charge_info' => $charge,
|
||||||
|
'deposit' => $this->depositFromChargeInfo($charge),
|
||||||
|
]));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'enabled' => true,
|
||||||
|
'balance' => (int) ($balanceData['balance'] ?? 0),
|
||||||
|
'is_blocked' => (bool) ($balanceData['is_blocked'] ?? false),
|
||||||
|
'deposit' => $this->depositFromChargeInfo($charge),
|
||||||
|
'source' => 'superadmin',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$balance = 0;
|
$balance = 0;
|
||||||
$isBlocked = false;
|
$isBlocked = false;
|
||||||
|
|
||||||
@@ -96,6 +206,28 @@ class SmsPanelController extends Controller
|
|||||||
'note' => 'nullable|string|max:2000',
|
'note' => 'nullable|string|max:2000',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$result = $this->sa()->updateChargeInfo([
|
||||||
|
'card' => (string) ($data['card_number'] ?? ''),
|
||||||
|
'iban' => (string) ($data['sheba'] ?? ''),
|
||||||
|
'account_holder' => (string) ($data['account_holder'] ?? ''),
|
||||||
|
'instruction' => (string) ($data['note'] ?? ''),
|
||||||
|
]);
|
||||||
|
if ($result === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
$charge = is_array($result['charge_info'] ?? null) ? $result['charge_info'] : [];
|
||||||
|
$deposit = $this->depositFromChargeInfo($charge);
|
||||||
|
$this->mirrorLocalFromSa(array_merge($result, ['deposit' => $deposit]));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => (string) ($result['message'] ?? 'اطلاعات واریز بهروزرسانی شد.'),
|
||||||
|
'deposit' => $deposit,
|
||||||
|
'source' => 'superadmin',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$deposit = SmsWalletService::setDepositInfo($data);
|
$deposit = SmsWalletService::setDepositInfo($data);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -111,6 +243,21 @@ class SmsPanelController extends Controller
|
|||||||
'note' => 'nullable|string|max:500',
|
'note' => 'nullable|string|max:500',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$result = $this->sa()->charge((int) $data['amount'], $data['note'] ?? null);
|
||||||
|
if ($result === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mirrorLocalFromSa($result);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => (string) ($result['message'] ?? 'کیف پول با موفقیت شارژ شد.'),
|
||||||
|
'balance' => (int) ($result['balance'] ?? 0),
|
||||||
|
'source' => 'superadmin',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$wallet = app(SmsWalletService::class)->charge(
|
$wallet = app(SmsWalletService::class)->charge(
|
||||||
(int) $data['amount'],
|
(int) $data['amount'],
|
||||||
auth()->id(),
|
auth()->id(),
|
||||||
@@ -129,6 +276,21 @@ class SmsPanelController extends Controller
|
|||||||
'price_per_part' => 'required|integer|min:0',
|
'price_per_part' => 'required|integer|min:0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$result = $this->sa()->updatePrice((int) $data['price_per_part']);
|
||||||
|
if ($result === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mirrorLocalFromSa($result);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => (string) ($result['message'] ?? 'قیمت هر پارت بهروزرسانی شد.'),
|
||||||
|
'price_per_part' => (int) ($result['price_per_part'] ?? 0),
|
||||||
|
'source' => 'superadmin',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$wallet = app(SmsWalletService::class)->updatePricePerPart((int) $data['price_per_part']);
|
$wallet = app(SmsWalletService::class)->updatePricePerPart((int) $data['price_per_part']);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -143,6 +305,21 @@ class SmsPanelController extends Controller
|
|||||||
'is_blocked' => 'required|boolean',
|
'is_blocked' => 'required|boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$result = $this->sa()->setBlocked((bool) $data['is_blocked']);
|
||||||
|
if ($result === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mirrorLocalFromSa($result);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => (string) ($result['message'] ?? ''),
|
||||||
|
'is_blocked' => (bool) ($result['is_blocked'] ?? false),
|
||||||
|
'source' => 'superadmin',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$wallet = app(SmsWalletService::class)->setBlocked((bool) $data['is_blocked']);
|
$wallet = app(SmsWalletService::class)->setBlocked((bool) $data['is_blocked']);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -153,6 +330,15 @@ class SmsPanelController extends Controller
|
|||||||
|
|
||||||
public function logsTable(Request $request)
|
public function logsTable(Request $request)
|
||||||
{
|
{
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$data = $this->sa()->logsTable($request->all());
|
||||||
|
if ($data === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
|
|
||||||
$records = SmsSendLog::query()->orderByDesc('id');
|
$records = SmsSendLog::query()->orderByDesc('id');
|
||||||
|
|
||||||
if ($request->filled('from_date')) {
|
if ($request->filled('from_date')) {
|
||||||
@@ -183,6 +369,15 @@ class SmsPanelController extends Controller
|
|||||||
|
|
||||||
public function ledgerTable(Request $request)
|
public function ledgerTable(Request $request)
|
||||||
{
|
{
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$data = $this->sa()->ledgerTable($request->all());
|
||||||
|
if ($data === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
|
|
||||||
$records = SmsLedger::query()->orderByDesc('id');
|
$records = SmsLedger::query()->orderByDesc('id');
|
||||||
|
|
||||||
if ($request->filled('from_date')) {
|
if ($request->filled('from_date')) {
|
||||||
@@ -215,6 +410,15 @@ class SmsPanelController extends Controller
|
|||||||
$from = $request->input('from_date', Carbon::now()->startOfMonth()->toDateString());
|
$from = $request->input('from_date', Carbon::now()->startOfMonth()->toDateString());
|
||||||
$to = $request->input('to_date', Carbon::now()->toDateString());
|
$to = $request->input('to_date', Carbon::now()->toDateString());
|
||||||
|
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$data = $this->sa()->report($from, $to);
|
||||||
|
if ($data === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
|
|
||||||
$summary = SmsSendLog::query()
|
$summary = SmsSendLog::query()
|
||||||
->whereDate('created_at', '>=', $from)
|
->whereDate('created_at', '>=', $from)
|
||||||
->whereDate('created_at', '<=', $to)
|
->whereDate('created_at', '<=', $to)
|
||||||
@@ -250,6 +454,18 @@ class SmsPanelController extends Controller
|
|||||||
'to_date' => 'nullable|date',
|
'to_date' => 'nullable|date',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($this->useSuperAdmin()) {
|
||||||
|
$data = $this->sa()->chargeReport(
|
||||||
|
$request->input('from_date'),
|
||||||
|
$request->input('to_date')
|
||||||
|
);
|
||||||
|
if ($data === null) {
|
||||||
|
$this->abortSaUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
|
|
||||||
$baseQuery = SmsLedger::query()->where('type', SmsWalletService::TYPE_CHARGE);
|
$baseQuery = SmsLedger::query()->where('type', SmsWalletService::TYPE_CHARGE);
|
||||||
|
|
||||||
if ($request->filled('from_date')) {
|
if ($request->filled('from_date')) {
|
||||||
|
|||||||
@@ -146,6 +146,27 @@ class SmsGatewayService
|
|||||||
'parts' => $parts,
|
'parts' => $parts,
|
||||||
'source' => $source,
|
'source' => $source,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel period: mirror balance + local send log/ledger from SuperAdmin.
|
||||||
|
try {
|
||||||
|
$this->walletService->mirrorConsumeFromSuperAdmin(
|
||||||
|
$to,
|
||||||
|
$text,
|
||||||
|
$parts,
|
||||||
|
$providerName,
|
||||||
|
$source,
|
||||||
|
$isAlert,
|
||||||
|
(int) ($consumed['balanceRials'] ?? 0),
|
||||||
|
(int) ($consumed['costRials'] ?? 0),
|
||||||
|
(int) ($consumed['pricePerPartRials'] ?? 0)
|
||||||
|
);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('SmsGateway: local mirror after SuperAdmin consume failed', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -91,6 +91,38 @@ class SmsWalletService
|
|||||||
return $wallet->fresh();
|
return $wallet->fresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep local MySQL wallet mirrored with SuperAdmin during the parallel period.
|
||||||
|
*/
|
||||||
|
public function mirrorFromSuperAdmin(?int $balance = null, ?int $pricePerPart = null, ?bool $isBlocked = null): SmsWallet
|
||||||
|
{
|
||||||
|
return DB::transaction(function () use ($balance, $pricePerPart, $isBlocked) {
|
||||||
|
$wallet = SmsWallet::query()->lockForUpdate()->firstOrCreate([], [
|
||||||
|
'balance' => 0,
|
||||||
|
'price_per_part' => 0,
|
||||||
|
'is_blocked' => false,
|
||||||
|
'alert_days_sent' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($balance !== null) {
|
||||||
|
$wallet->balance = $balance;
|
||||||
|
}
|
||||||
|
if ($pricePerPart !== null) {
|
||||||
|
$wallet->price_per_part = max(0, $pricePerPart);
|
||||||
|
}
|
||||||
|
if ($isBlocked !== null) {
|
||||||
|
$wallet->is_blocked = $isBlocked;
|
||||||
|
}
|
||||||
|
if ($wallet->balance > 0) {
|
||||||
|
$wallet->alert_days_sent = 0;
|
||||||
|
$wallet->last_alert_date = null;
|
||||||
|
}
|
||||||
|
$wallet->save();
|
||||||
|
|
||||||
|
return $wallet->fresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function updatePricePerPart(int $pricePerPart): SmsWallet
|
public function updatePricePerPart(int $pricePerPart): SmsWallet
|
||||||
{
|
{
|
||||||
$wallet = $this->getWallet();
|
$wallet = $this->getWallet();
|
||||||
@@ -136,6 +168,74 @@ class SmsWalletService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After SuperAdmin consume: set local balance to SA value and write local send log/ledger
|
||||||
|
* so the product SMS panel stays in sync during the parallel period.
|
||||||
|
*/
|
||||||
|
public function mirrorConsumeFromSuperAdmin(
|
||||||
|
string $receiver,
|
||||||
|
string $body,
|
||||||
|
int $parts,
|
||||||
|
string $provider,
|
||||||
|
?string $source,
|
||||||
|
bool $isAlert,
|
||||||
|
int $balanceAfter,
|
||||||
|
int $costRials,
|
||||||
|
int $pricePerPart
|
||||||
|
): void {
|
||||||
|
DB::transaction(function () use (
|
||||||
|
$receiver,
|
||||||
|
$body,
|
||||||
|
$parts,
|
||||||
|
$provider,
|
||||||
|
$source,
|
||||||
|
$isAlert,
|
||||||
|
$balanceAfter,
|
||||||
|
$costRials,
|
||||||
|
$pricePerPart
|
||||||
|
) {
|
||||||
|
$wallet = SmsWallet::query()->lockForUpdate()->firstOrCreate([], [
|
||||||
|
'balance' => 0,
|
||||||
|
'price_per_part' => 0,
|
||||||
|
'is_blocked' => false,
|
||||||
|
'alert_days_sent' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$wallet->balance = $balanceAfter;
|
||||||
|
if ($pricePerPart > 0) {
|
||||||
|
$wallet->price_per_part = $pricePerPart;
|
||||||
|
}
|
||||||
|
if ($wallet->balance > 0) {
|
||||||
|
$wallet->alert_days_sent = 0;
|
||||||
|
$wallet->last_alert_date = null;
|
||||||
|
}
|
||||||
|
$wallet->save();
|
||||||
|
|
||||||
|
$log = SmsSendLog::create([
|
||||||
|
'receiver' => $receiver,
|
||||||
|
'body' => $body,
|
||||||
|
'parts' => $parts,
|
||||||
|
'price_per_part' => $pricePerPart,
|
||||||
|
'total_amount' => $costRials,
|
||||||
|
'provider' => $provider,
|
||||||
|
'source' => $source,
|
||||||
|
'is_alert' => $isAlert,
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
SmsLedger::create([
|
||||||
|
'type' => $isAlert ? self::TYPE_ALERT_DEBIT : self::TYPE_DEBIT,
|
||||||
|
'amount' => -$costRials,
|
||||||
|
'balance_after' => $balanceAfter,
|
||||||
|
'price_per_part' => $pricePerPart,
|
||||||
|
'parts' => $parts,
|
||||||
|
'sms_send_log_id' => $log->id,
|
||||||
|
'note' => $isAlert ? 'هشدار موجودی پیامک' : 'مصرف ارسال (superadmin)',
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{log: SmsSendLog, wallet: SmsWallet}
|
* @return array{log: SmsSendLog, wallet: SmsWallet}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Services\SmsBilling;
|
namespace App\Services\SmsBilling;
|
||||||
|
|
||||||
|
use Illuminate\Http\Client\Response;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SuperAdmin SMS wallet client (status / consume).
|
* SuperAdmin SMS wallet client (status / consume / panel sync).
|
||||||
*
|
*
|
||||||
* Preferred: SUPERADMIN_SMS_BASE_URL + SUPERADMIN_SMS_SHARED_SECRET (+ optional DOMAIN)
|
* Preferred auth: shared HMAC + domain (one secret for all sold versions).
|
||||||
* Fallback: SUPERADMIN_REMOTE_TOKEN
|
* Env: SUPERADMIN_SMS_BASE_URL + SUPERADMIN_SMS_SHARED_SECRET
|
||||||
|
* Optional: SUPERADMIN_SMS_DOMAIN (else host of APP_URL)
|
||||||
|
*
|
||||||
|
* Fallback: SUPERADMIN_REMOTE_TOKEN (per-tenant Bearer) if shared secret missing.
|
||||||
*/
|
*/
|
||||||
class SuperAdminSmsClient
|
class SuperAdminSmsClient
|
||||||
{
|
{
|
||||||
@@ -36,50 +40,24 @@ class SuperAdminSmsClient
|
|||||||
*/
|
*/
|
||||||
public function status(int $parts = 1, bool $isAlert = false): ?array
|
public function status(int $parts = 1, bool $isAlert = false): ?array
|
||||||
{
|
{
|
||||||
if (! $this->enabled()) {
|
$data = $this->requestJson('GET', '/api/remote/sms/status', 'status', [
|
||||||
|
'parts' => max(1, $parts),
|
||||||
|
'isAlert' => $isAlert ? '1' : '0',
|
||||||
|
], null, 4, 'status');
|
||||||
|
|
||||||
|
if ($data === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return [
|
||||||
$path = '/api/remote/sms/status';
|
'allowed' => (bool) ($data['allowed'] ?? false),
|
||||||
$response = Http::withHeaders($this->authHeaders('GET', $path))
|
'blocked' => (bool) ($data['blocked'] ?? false),
|
||||||
->acceptJson()
|
'reason' => isset($data['reason']) ? (string) $data['reason'] : null,
|
||||||
->timeout(4)
|
'balanceRials' => (int) ($data['balanceRials'] ?? 0),
|
||||||
->get($this->url('status'), [
|
'pricePerPartRials' => (int) ($data['pricePerPartRials'] ?? 0),
|
||||||
'parts' => max(1, $parts),
|
'estimatedCostRials' => (int) ($data['estimatedCostRials'] ?? 0),
|
||||||
'isAlert' => $isAlert ? '1' : '0',
|
'balanceLevel' => (string) ($data['balanceLevel'] ?? 'unknown'),
|
||||||
]);
|
];
|
||||||
|
|
||||||
if (! $response->successful()) {
|
|
||||||
Log::warning('SuperAdmin SMS status failed', [
|
|
||||||
'status' => $response->status(),
|
|
||||||
'body' => $response->body(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $response->json();
|
|
||||||
if (! is_array($data)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'allowed' => (bool) ($data['allowed'] ?? false),
|
|
||||||
'blocked' => (bool) ($data['blocked'] ?? false),
|
|
||||||
'reason' => isset($data['reason']) ? (string) $data['reason'] : null,
|
|
||||||
'balanceRials' => (int) ($data['balanceRials'] ?? 0),
|
|
||||||
'pricePerPartRials' => (int) ($data['pricePerPartRials'] ?? 0),
|
|
||||||
'estimatedCostRials' => (int) ($data['estimatedCostRials'] ?? 0),
|
|
||||||
'balanceLevel' => (string) ($data['balanceLevel'] ?? 'unknown'),
|
|
||||||
];
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
Log::warning('SuperAdmin SMS status exception', [
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,27 +71,136 @@ class SuperAdminSmsClient
|
|||||||
?string $source = null,
|
?string $source = null,
|
||||||
bool $isAlert = false
|
bool $isAlert = false
|
||||||
): ?array {
|
): ?array {
|
||||||
if (! $this->enabled()) {
|
$data = $this->requestJson('POST', '/api/remote/sms/consume', 'consume', [], [
|
||||||
|
'receiver' => $receiver,
|
||||||
|
'body' => mb_substr($body, 0, 1000),
|
||||||
|
'parts' => max(1, $parts),
|
||||||
|
'provider' => $provider,
|
||||||
|
'source' => $source,
|
||||||
|
'isAlert' => $isAlert,
|
||||||
|
], 6, 'consume');
|
||||||
|
|
||||||
|
if ($data === null || !($data['ok'] ?? false)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ok' => true,
|
||||||
|
'balanceRials' => (int) ($data['balanceRials'] ?? 0),
|
||||||
|
'costRials' => (int) ($data['costRials'] ?? 0),
|
||||||
|
'pricePerPartRials' => (int) ($data['pricePerPartRials'] ?? 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function overview(): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('GET', '/api/remote/sms/overview', 'overview', [], null, 6, 'overview');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function balance(): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('GET', '/api/remote/sms/balance', 'balance', [], null, 4, 'balance');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function chargeInfo(): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('GET', '/api/remote/sms/charge-info', 'charge-info', [], null, 4, 'charge-info');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateChargeInfo(array $payload): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('PUT', '/api/remote/sms/charge-info', 'charge-info', [], $payload, 6, 'charge-info-update');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function charge(int $amount, ?string $note = null): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('POST', '/api/remote/sms/charge', 'charge', [], [
|
||||||
|
'amount' => $amount,
|
||||||
|
'note' => $note,
|
||||||
|
], 8, 'charge');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePrice(int $pricePerPart): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('PUT', '/api/remote/sms/price', 'price', [], [
|
||||||
|
'price_per_part' => max(0, $pricePerPart),
|
||||||
|
], 6, 'price');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBlocked(bool $isBlocked): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('PUT', '/api/remote/sms/block', 'block', [], [
|
||||||
|
'is_blocked' => $isBlocked,
|
||||||
|
], 6, 'block');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logsTable(array $payload): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('POST', '/api/remote/sms/logs/table', 'logs/table', [], $payload, 10, 'logs-table');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ledgerTable(array $payload): ?array
|
||||||
|
{
|
||||||
|
return $this->requestJson('POST', '/api/remote/sms/ledger/table', 'ledger/table', [], $payload, 10, 'ledger-table');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function report(?string $fromDate = null, ?string $toDate = null): ?array
|
||||||
|
{
|
||||||
|
$query = array_filter([
|
||||||
|
'from_date' => $fromDate,
|
||||||
|
'to_date' => $toDate,
|
||||||
|
], static fn ($v) => $v !== null && $v !== '');
|
||||||
|
|
||||||
|
return $this->requestJson('GET', '/api/remote/sms/report', 'report', $query, null, 8, 'report');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function chargeReport(?string $fromDate = null, ?string $toDate = null): ?array
|
||||||
|
{
|
||||||
|
$query = array_filter([
|
||||||
|
'from_date' => $fromDate,
|
||||||
|
'to_date' => $toDate,
|
||||||
|
], static fn ($v) => $v !== null && $v !== '');
|
||||||
|
|
||||||
|
return $this->requestJson('GET', '/api/remote/sms/report/charges', 'report/charges', $query, null, 8, 'charge-report');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $query
|
||||||
|
* @param array<string, mixed>|null $body
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
private function requestJson(
|
||||||
|
string $method,
|
||||||
|
string $canonicalPath,
|
||||||
|
string $relativeUrl,
|
||||||
|
array $query = [],
|
||||||
|
?array $body = null,
|
||||||
|
int $timeout = 6,
|
||||||
|
string $label = 'request'
|
||||||
|
): ?array {
|
||||||
|
if (!$this->enabled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$path = '/api/remote/sms/consume';
|
$pending = Http::withHeaders($this->authHeaders($method, $canonicalPath))
|
||||||
$payload = [
|
|
||||||
'receiver' => $receiver,
|
|
||||||
'body' => mb_substr($body, 0, 1000),
|
|
||||||
'parts' => max(1, $parts),
|
|
||||||
'provider' => $provider,
|
|
||||||
'source' => $source,
|
|
||||||
'isAlert' => $isAlert,
|
|
||||||
];
|
|
||||||
$response = Http::withHeaders($this->authHeaders('POST', $path))
|
|
||||||
->acceptJson()
|
->acceptJson()
|
||||||
->timeout(6)
|
->timeout($timeout);
|
||||||
->post($this->url('consume'), $payload);
|
|
||||||
|
|
||||||
if (! $response->successful()) {
|
$url = $this->url($relativeUrl);
|
||||||
Log::error('SuperAdmin SMS consume failed', [
|
$method = strtoupper($method);
|
||||||
|
|
||||||
|
/** @var Response $response */
|
||||||
|
$response = match ($method) {
|
||||||
|
'GET' => $pending->get($url, $query),
|
||||||
|
'POST' => $pending->post($url, $body ?? []),
|
||||||
|
'PUT' => $pending->put($url, $body ?? []),
|
||||||
|
'PATCH' => $pending->patch($url, $body ?? []),
|
||||||
|
default => throw new \InvalidArgumentException("Unsupported method {$method}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!$response->successful()) {
|
||||||
|
Log::warning("SuperAdmin SMS {$label} failed", [
|
||||||
'status' => $response->status(),
|
'status' => $response->status(),
|
||||||
'body' => $response->body(),
|
'body' => $response->body(),
|
||||||
]);
|
]);
|
||||||
@@ -122,17 +209,10 @@ class SuperAdminSmsClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
$data = $response->json();
|
$data = $response->json();
|
||||||
if (! is_array($data) || ! ($data['ok'] ?? false)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return is_array($data) ? $data : null;
|
||||||
'ok' => true,
|
|
||||||
'balanceRials' => (int) ($data['balanceRials'] ?? 0),
|
|
||||||
'costRials' => (int) ($data['costRials'] ?? 0),
|
|
||||||
];
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Log::error('SuperAdmin SMS consume exception', [
|
Log::warning("SuperAdmin SMS {$label} exception", [
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user