426 lines
13 KiB
PHP
426 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Cnt;
|
|
use App\Models\Order;
|
|
use App\Models\Setting;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
|
|
class MontzarService
|
|
{
|
|
private function config(): ConfigService
|
|
{
|
|
return app(ConfigService::class);
|
|
}
|
|
|
|
private function baseUrl(): string
|
|
{
|
|
return rtrim(
|
|
$this->config()->Config('montzar_base_url') ?: 'https://montpar.ir',
|
|
'/'
|
|
);
|
|
}
|
|
|
|
private function endpoint(string $path): string
|
|
{
|
|
return $this->baseUrl() . '/' . ltrim($path, '/');
|
|
}
|
|
|
|
public function montzarLogin(): string
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)
|
|
->acceptJson()
|
|
->post($this->endpoint('/server/auth/login'), [
|
|
'mobile' => $this->config()->Config('montzar_mobile'),
|
|
'password' => $this->config()->Config('montzar_password'),
|
|
'provider' => 'customer',
|
|
'type_app' => 'client',
|
|
])
|
|
->json();
|
|
} catch (\Throwable $e) {
|
|
Log::error('Montzar login request failed', [
|
|
'base_url' => $this->baseUrl(),
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
throw new UnprocessableEntityHttpException(
|
|
'سرویس موقتا در دسترس نیست، لطفاً با پشتیبانی تماس بگیرید.'
|
|
);
|
|
}
|
|
|
|
$token = is_array($response)
|
|
? ($response['access_token'] ?? $response['token'] ?? null)
|
|
: null;
|
|
|
|
if (!$token) {
|
|
Log::error('Montzar login failed', [
|
|
'base_url' => $this->baseUrl(),
|
|
'mobile' => $this->config()->Config('montzar_mobile'),
|
|
'response' => $response,
|
|
]);
|
|
// 422 تا فرانت toast نشان دهد؛ جزئیات فنی فقط در لاگ میماند
|
|
throw new UnprocessableEntityHttpException(
|
|
'سرویس موقتا در دسترس نیست، لطفاً با پشتیبانی تماس بگیرید.'
|
|
);
|
|
}
|
|
|
|
Setting::updateOrCreate(
|
|
['key' => 'montzar_token'],
|
|
['value' => $token, 'is_active' => 1]
|
|
);
|
|
|
|
return $token;
|
|
}
|
|
|
|
public function getToken(): string
|
|
{
|
|
$token = trim((string) $this->config()->Config('montzar_token'));
|
|
|
|
if (!$token) {
|
|
$token = $this->montzarLogin();
|
|
}
|
|
|
|
return $token;
|
|
}
|
|
|
|
public function productList(): array
|
|
{
|
|
$token = $this->getToken();
|
|
|
|
$response = Http::timeout(7)
|
|
->acceptJson()
|
|
->withOptions(Controller::httpClientOptions(
|
|
defined('CURLOPT_TCP_FASTOPEN') ? [(int) constant('CURLOPT_TCP_FASTOPEN') => 1] : []
|
|
))
|
|
->withToken($token)
|
|
->get($this->endpoint('/server/customer/product/list'))
|
|
->json();
|
|
|
|
if ((isset($response['message']) && $response['message'] === 'no authorised') || $response === null) {
|
|
$token = $this->montzarLogin();
|
|
|
|
$response = Http::timeout(7)
|
|
->acceptJson()
|
|
->withToken($token)
|
|
->get($this->endpoint('/server/customer/product/list'))
|
|
->json();
|
|
}
|
|
|
|
if (!is_array($response)) {
|
|
throw new \Exception('Montzar product list response is not array');
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* ثبت سفارش در منتظر — قبل از ایجاد سفارش/تراکنش محلی صدا زده میشود.
|
|
*
|
|
* @return array{ok: bool, provider_id: int|null, error: string|null}
|
|
*/
|
|
public function submitOrder(array &$data): array
|
|
{
|
|
$payload = $this->makeOrderPayload($data);
|
|
$token = $this->getToken();
|
|
|
|
$httpResponse = $this->postToMontzar(
|
|
$this->endpoint('/server/customer/order'),
|
|
$token,
|
|
$payload
|
|
);
|
|
|
|
$body = $this->decodeMontzarHttpBody($httpResponse);
|
|
|
|
if (is_array($body) && ($body['message'] ?? null) === 'no authorised') {
|
|
$token = $this->montzarLogin();
|
|
|
|
$httpResponse = $this->postToMontzar(
|
|
$this->endpoint('/server/customer/order'),
|
|
$token,
|
|
$payload
|
|
);
|
|
|
|
$body = $this->decodeMontzarHttpBody($httpResponse);
|
|
}
|
|
|
|
$this->logMontzarOrderResponse($httpResponse, $body);
|
|
|
|
$parsed = $this->parseMontzarOrderResponse($body, (int) $data['direction']);
|
|
|
|
if (!$parsed['ok']) {
|
|
Log::warning('Montzar order rejected', [
|
|
'base_url' => $this->baseUrl(),
|
|
'status' => $httpResponse->status(),
|
|
'body' => $body,
|
|
'raw' => $httpResponse->body(),
|
|
'payload' => $payload,
|
|
'reason' => $parsed['error'],
|
|
]);
|
|
|
|
throw new UnprocessableEntityHttpException(
|
|
$this->formatMontzarErrorMessage($parsed['error'], $httpResponse)
|
|
);
|
|
}
|
|
|
|
return $parsed;
|
|
}
|
|
|
|
public function applyParsedOrder(array &$data, array $parsed): void
|
|
{
|
|
$this->syncOrderWithMontzarResponse($data, $parsed);
|
|
}
|
|
|
|
public function sendOrder(array &$data)
|
|
{
|
|
$parsed = $this->submitOrder($data);
|
|
$this->applyParsedOrder($data, $parsed);
|
|
|
|
return null;
|
|
}
|
|
|
|
private function makeOrderPayload(array $data): array
|
|
{
|
|
$mazaneh = ((int) $data['direction'] === 1)
|
|
? $this->config()->Config('montzar_truly_buy_mazaneh')
|
|
: $this->config()->Config('montzar_truly_sell_mazaneh');
|
|
|
|
$montzarPrice = (float) $this->config()->Config('montzar_live_price');
|
|
|
|
$cash = (int) round(
|
|
$data['total_price'] ?? ($montzarPrice * (float) $data['amount'])
|
|
);
|
|
|
|
$customerName = auth('customer')->check()
|
|
? auth('customer')->user()->name
|
|
: ($data['customer']['name'] ?? '');
|
|
|
|
return [
|
|
'mazaneh' => (int) $mazaneh,
|
|
'amount' => (float) $data['amount'],
|
|
'description' => $this->config()->Config('name_fa') . ' - ' . $customerName,
|
|
'direction' => (int) $data['direction'],
|
|
'product_id' => (int) ($this->config()->Config('montzar_product_id') ?: 25),
|
|
'cash' => $cash,
|
|
'input_mode' => 'gold',
|
|
];
|
|
}
|
|
|
|
private function postToMontzar(string $url, string $token, array $payload)
|
|
{
|
|
return Http::timeout(15)
|
|
->acceptJson()
|
|
->withToken($token)
|
|
->post($url, $payload);
|
|
}
|
|
|
|
private function decodeMontzarHttpBody($httpResponse): ?array
|
|
{
|
|
$body = $httpResponse->json();
|
|
|
|
if (is_array($body)) {
|
|
return $body;
|
|
}
|
|
|
|
$raw = trim((string) $httpResponse->body());
|
|
|
|
if ($raw === '') {
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode($raw, true);
|
|
|
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
|
return $decoded;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function logMontzarOrderResponse($httpResponse, $body): void
|
|
{
|
|
try {
|
|
$value = $body === null
|
|
? $httpResponse->body()
|
|
: json_encode($body, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
|
|
|
// مربوط به منتزر: settings
|
|
$this->config()->UpdateConfig('montzar_order_response', $value);
|
|
|
|
// عمومی: اگر در const/Cnt داری همانجا بماند
|
|
Cnt::where('key', 'last_order_log')->update(['value' => $value]);
|
|
} catch (\Exception $e) {
|
|
Log::warning('Could not persist montzar order response', [
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function formatMontzarErrorMessage(?string $error, $httpResponse): string
|
|
{
|
|
if ($error && $error !== 'پاسخ نامعتبر از سرور منتظر (اتصال یا فرمت JSON)') {
|
|
return $error;
|
|
}
|
|
|
|
$status = $httpResponse->status();
|
|
$raw = trim((string) $httpResponse->body());
|
|
|
|
if ($raw === '') {
|
|
return "خطا در ارتباط با منتظر (HTTP {$status})";
|
|
}
|
|
|
|
if (stripos($raw, '<!DOCTYPE') !== false || stripos($raw, '<html') !== false) {
|
|
return "خطا از سرویس منتظر (HTTP {$status}). پاسخ JSON دریافت نشد.";
|
|
}
|
|
|
|
return mb_substr($raw, 0, 300);
|
|
}
|
|
|
|
private function parseMontzarOrderResponse($body, int $direction): array
|
|
{
|
|
if (!is_array($body)) {
|
|
return [
|
|
'ok' => false,
|
|
'provider_id' => null,
|
|
'error' => 'پاسخ نامعتبر از سرور منتظر (اتصال یا فرمت JSON)',
|
|
];
|
|
}
|
|
|
|
if (!empty($body['message']) && is_string($body['message'])) {
|
|
return [
|
|
'ok' => false,
|
|
'provider_id' => null,
|
|
'error' => $body['message'],
|
|
];
|
|
}
|
|
|
|
$providerId = $this->extractProviderOrderId($body);
|
|
|
|
if ($providerId !== null) {
|
|
return [
|
|
'ok' => true,
|
|
'provider_id' => $providerId,
|
|
'error' => null,
|
|
];
|
|
}
|
|
|
|
$sideKey = $direction === 1 ? 'buy' : 'sell';
|
|
|
|
if (($body[$sideKey] ?? null) === true) {
|
|
return [
|
|
'ok' => true,
|
|
'provider_id' => null,
|
|
'error' => null,
|
|
];
|
|
}
|
|
|
|
foreach ($body as $item) {
|
|
if (!is_array($item)) {
|
|
continue;
|
|
}
|
|
|
|
if (!empty($item['id']) && is_numeric($item['id'])) {
|
|
return [
|
|
'ok' => true,
|
|
'provider_id' => (int) $item['id'],
|
|
'error' => null,
|
|
];
|
|
}
|
|
|
|
if (!empty($item['order']['id'])) {
|
|
return [
|
|
'ok' => true,
|
|
'provider_id' => (int) $item['order']['id'],
|
|
'error' => null,
|
|
];
|
|
}
|
|
|
|
if (isset($item['sts']) && in_array((int) $item['sts'], [1, 2], true)) {
|
|
return [
|
|
'ok' => true,
|
|
'provider_id' => !empty($item['id']) ? (int) $item['id'] : null,
|
|
'error' => null,
|
|
];
|
|
}
|
|
}
|
|
|
|
return [
|
|
'ok' => false,
|
|
'provider_id' => null,
|
|
'error' => 'سفارشات توسط ارائه دهنده بسته شده است.',
|
|
];
|
|
}
|
|
|
|
private function extractProviderOrderId(array $body): ?int
|
|
{
|
|
if (!empty($body['id'])) {
|
|
return (int) $body['id'];
|
|
}
|
|
|
|
if (!empty($body['order']['id'])) {
|
|
return (int) $body['order']['id'];
|
|
}
|
|
|
|
foreach ([1, 2] as $index) {
|
|
if (isset($body[$index]) && is_array($body[$index])) {
|
|
if (!empty($body[$index]['id'])) {
|
|
return (int) $body[$index]['id'];
|
|
}
|
|
|
|
if (!empty($body[$index]['order']['id'])) {
|
|
return (int) $body[$index]['order']['id'];
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (['buy', 'sell'] as $sideKey) {
|
|
if (!isset($body[$sideKey]) || !is_array($body[$sideKey])) {
|
|
continue;
|
|
}
|
|
|
|
if (!empty($body[$sideKey]['id'])) {
|
|
return (int) $body[$sideKey]['id'];
|
|
}
|
|
|
|
if (!empty($body[$sideKey]['order']['id'])) {
|
|
return (int) $body[$sideKey]['order']['id'];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function syncOrderWithMontzarResponse(array &$data, array $parsed): void
|
|
{
|
|
$montzarDiff = (float) $this->config()->Config('montzar_diff');
|
|
$montzarPrice = (float) $this->config()->Config('montzar_live_price');
|
|
|
|
$totalPriceMontzar = ((int) $data['direction'] === 1)
|
|
? ($montzarPrice + $montzarDiff)
|
|
: ($montzarPrice - $montzarDiff);
|
|
|
|
$providerOrderId = $parsed['provider_id'] ?? null;
|
|
|
|
if (isset($data['order']) && $data['order'] instanceof Order) {
|
|
if ($providerOrderId !== null && Schema::hasColumn('orders', 'provider_id')) {
|
|
$data['order']->provider_id = $providerOrderId;
|
|
}
|
|
|
|
if (Schema::hasColumn('orders', 'total_price_montzar')) {
|
|
$data['order']->total_price_montzar = $totalPriceMontzar;
|
|
}
|
|
|
|
$data['order']->save();
|
|
}
|
|
|
|
$data['total_price_montzar'] = $totalPriceMontzar;
|
|
|
|
if ($providerOrderId !== null) {
|
|
$data['provider_id'] = $providerOrderId;
|
|
}
|
|
}
|
|
} |