added pargar

This commit is contained in:
2026-07-05 16:43:34 +03:30
parent 694fddc909
commit 7ac7bbdb90
27 changed files with 1341 additions and 493 deletions
@@ -0,0 +1,241 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\PargarSyncLog;
use App\Services\PargarSyncService;
use Illuminate\Http\Request;
class PargarController extends Controller
{
protected function sync(): PargarSyncService
{
return app(PargarSyncService::class);
}
public function table(Request $request)
{
$query = PargarSyncLog::with('customer')->orderByDesc('id');
\App\DB\Te\TableEngine::create($query, $request)
->initSearch(['desc', 'type'])
->select(['*'])
->export($query);
return response()->json($query);
}
public function show($id)
{
return response()->json(PargarSyncLog::with('customer')->findOrFail($id));
}
public function customerSyncStatus(Request $request)
{
$panelTotal = Customer::whereNull('deleted_at')->count();
$panelRegistered = Customer::whereNull('deleted_at')->whereNotNull('pargar_account_id')->count();
$panelOnly = $panelTotal - $panelRegistered;
$panelOnlyQuery = Customer::whereNull('deleted_at')
->whereNull('pargar_account_id')
->select('id', 'name', 'mobile', 'created_at');
if ($search = $request->input('search')) {
$panelOnlyQuery->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('mobile', 'like', "%{$search}%");
});
}
$panelOnlyList = $panelOnlyQuery->orderBy('id')->paginate(20);
$inboundOnlyQuery = PargarSyncLog::query()
->where('type', 'customer')
->where('sync_status', PargarSyncService::STATUS_SUCCESS)
->whereNotNull('pargar_ref')
->latest('id');
$inboundOnlyCount = (clone $inboundOnlyQuery)->count();
$inboundSample = $inboundOnlyQuery->limit(100)->get(['id', 'customer_id', 'pargar_ref', 'created_at']);
return response()->json([
'panel_total' => $panelTotal,
'panel_registered' => $panelRegistered,
'panel_only' => $panelOnly,
'pargar_total' => $panelRegistered + $inboundOnlyCount,
'pargar_only_count' => max(0, $inboundOnlyCount),
'panel_only_list' => $panelOnlyList,
'pargar_only_list' => [
'data' => $inboundSample->map(fn ($row) => [
'code' => $row->pargar_ref,
'name' => $row->customer?->name ?? '',
'tel' => $row->customer?->mobile ?? '',
'gid' => null,
'group' => '',
])->values(),
'total' => $inboundOnlyCount,
'current_page' => 1,
'last_page' => 1,
],
]);
}
public function syncPanelToPargar()
{
$customers = Customer::whereNull('deleted_at')
->whereNull('pargar_account_id')
->get(['id', 'name', 'mobile']);
$success = 0;
$failed = 0;
$errors = [];
foreach ($customers as $customer) {
$result = $this->sync()->registerCustomer($customer);
if ($result['ok'] ?? false) {
$success++;
} else {
$failed++;
$errors[] = [
'name' => $customer->name,
'mobile' => $customer->mobile,
'error' => $result['message'] ?? '',
];
}
}
return response()->json([
'ok' => true,
'success' => $success,
'failed' => $failed,
'errors' => $errors,
]);
}
public function importFromPargar(Request $request)
{
$validated = $request->validate([
'customers' => 'required|array',
'customers.*.code' => 'required',
'customers.*.name' => 'required|string',
'customers.*.tel' => 'nullable|string',
]);
$success = 0;
$failed = 0;
$errors = [];
foreach ($validated['customers'] as $item) {
$tel = trim((string) ($item['tel'] ?? ''));
$mobile = $tel !== '' ? '0' . ltrim($tel, '0') : '';
$fullName = trim($item['name']);
$accountId = (string) $item['code'];
$existing = Customer::whereNull('deleted_at')
->where('pargar_account_id', $accountId)
->first();
if ($existing) {
$success++;
continue;
}
if ($mobile !== '') {
$byMobile = Customer::whereNull('deleted_at')->where('mobile', $mobile)->first();
if ($byMobile) {
$byMobile->pargar_account_id = $accountId;
if ($fullName !== '') {
$byMobile->fname = $fullName;
$byMobile->name = $fullName;
}
$byMobile->save();
$success++;
continue;
}
}
try {
$customer = new Customer();
$customer->fname = $fullName;
$customer->lname = '';
$customer->name = $fullName;
$customer->mobile = $mobile;
$customer->active = true;
$customer->pargar_account_id = $accountId;
$customer->referral_id = $this->uniqueReferralId();
$customer->type = 2;
if ($mobile !== '') {
$customer->password = $mobile;
}
$customer->save();
$success++;
} catch (\Throwable $e) {
$failed++;
$errors[] = ['name' => $item['name'], 'code' => $item['code'], 'error' => $e->getMessage()];
}
}
return response()->json([
'ok' => true,
'success' => $success,
'failed' => $failed,
'errors' => $errors,
]);
}
public function rollbackImport(Request $request)
{
$baseQuery = fn () => Customer::whereNotNull('pargar_account_id')
->where(function ($q) {
$q->whereNull('name')->orWhere('name', '');
});
$count = $baseQuery()->count();
$sample = $baseQuery()
->select('id', 'fname', 'name', 'mobile', 'pargar_account_id', 'created_at')
->orderByDesc('id')
->limit(20)
->get();
if ($request->boolean('dry_run')) {
return response()->json([
'dry_run' => true,
'count' => $count,
'sample' => $sample,
]);
}
$deleted = $baseQuery()->forceDelete();
return response()->json([
'ok' => true,
'deleted' => $deleted,
]);
}
public function retrySanad($id)
{
$log = PargarSyncLog::with('customer')->findOrFail($id);
$result = $this->sync()->retry($log);
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
}
public function registerCustomer($customerId)
{
$customer = Customer::findOrFail($customerId);
$result = $this->sync()->registerCustomer($customer);
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
}
private function uniqueReferralId(): string
{
do {
$referral = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyz'), 0, 4);
} while (Customer::where('referral_id', $referral)->exists());
return $referral;
}
}
@@ -128,7 +128,7 @@ class WalletController extends Controller
$this->recordAccountingTransaction($customer, $Withdraw, (int) $user->id, (float) $data['value'], 0, $data['note'] ?? null);
if($this->Config('send_to_tahesab') == "true"){
if (app(ConfigService::class)->isTahesabAccounting()) {
try {
$data['direction'] = $deposit ? 0 : 1;
$data['withdraw_id'] = $Withdraw->id;
@@ -201,7 +201,7 @@ class WalletController extends Controller
$this->recordAccountingTransaction($customer, $Withdraw, (int) $user->id, 0, (float) $data['value'], $data['note'] ?? null);
//send to tahesab
if($this->Config('send_to_tahesab') == "true"){
if (app(ConfigService::class)->isTahesabAccounting()) {
try {
$data['direction'] = $deposit ? 1 : 0;
$data['withdraw_id'] = $Withdraw->id;
@@ -344,7 +344,7 @@ class WalletController extends Controller
($data['note'] ?? null) ?: ('شارژ ' . $product->title)
);
if ($this->Config('send_to_tahesab') == 'true') {
if (app(ConfigService::class)->isTahesabAccounting()) {
try {
$data['direction'] = $deposit ? 1 : 0;
$data['withdraw_id'] = $Withdraw->id;
@@ -548,7 +548,7 @@ class WalletController extends Controller
'*', Withdraw::$SELECT_STS_STR, Withdraw::$SELECT_DAJ
])->export($records);
if ($this->Config('send_to_tahesab') === 'true' && isset($records['rows']) && is_array($records['rows'])) {
if (app(ConfigService::class)->isTahesabAccounting() && isset($records['rows']) && is_array($records['rows'])) {
$withdrawIds = collect($records['rows'])->pluck('id')->filter()->values()->all();
if ($withdrawIds !== []) {
$failedByWithdraw = \App\Models\Tahesab::query()
@@ -17,7 +17,10 @@ use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use App\Models\Price;
use App\Http\Controllers\Admin\ProductController;
use App\Http\Controllers\Admin\GiftCardController;
use App\Http\Controllers\Admin\TahesabController;
use App\Services\AccountingLedgerService;
use App\Services\ConfigService;
use App\Services\PargarSyncService;
use App\Services\ProductPriceService;
@@ -342,11 +345,55 @@ class CartController extends Controller
// $product->decrement('inventory', $item->quantity);
}
$this->syncCartOrderAccounting($order, $cart);
return response()->json([
'message' => 'true',
]);
}
/**
* ثبت سبد در دفترکل داخلی + حسابداری بیرونی (مستقل از is_store؛ فقط accounting_provider).
*/
private function syncCartOrderAccounting(Order $order, Cart $cart): void
{
$config = app(ConfigService::class);
try {
app(AccountingLedgerService::class)->recordTrade($order);
} catch (\Throwable $e) {
report($e);
}
$primaryProduct = Product::find($cart->items->first()?->product_id);
$metalId = max(1, (int) ($primaryProduct->metal_id ?? 1));
if ($config->isTahesabAccounting()) {
try {
app(TahesabController::class)->storeTrade([
'customer_id' => $order->customer_id,
'amount' => $order->amount,
'direction' => (int) ($order->direction ?? 1),
'price' => $order->price,
'total_price' => $order->total_price,
'pay_at' => $order->pay_at ?? $order->created_at,
'product' => ['metal_id' => $metalId],
'order' => ['id' => $order->id],
]);
} catch (\Throwable $e) {
report($e);
}
}
if ($config->isPargarAccounting()) {
try {
app(PargarSyncService::class)->recordTrade($order, true);
} catch (\Throwable $e) {
report($e);
}
}
}
/**
* تسویه سبد فروشگاهی — قیمت نقدی هر محصول، بدون فرمول آبشده/گرم.
*/
@@ -476,6 +523,8 @@ class CartController extends Controller
Product::where('id', $item->product_id)->decrement('inventory', $item->quantity);
}
$this->syncCartOrderAccounting($order, $cart);
return response()->json([
'message' => 'true',
]);
@@ -14,6 +14,7 @@ use App\Models\BankAccount;
use App\Models\Price;
use App\Models\Setting;
use App\Http\Controllers\Admin\TahesabController;
use App\Services\ConfigService;
use App\Services\AssetSummaryService;
use App\Services\WalletBalanceService;
use App\Services\ZaarboxPriceService;
@@ -272,7 +273,7 @@ class WalletController extends Controller
$userMobile = $customer->mobile; // User's mobile number
$userName = $customer->name; // User's name
$withdrawAmount = $data['value']; // amount
if($this->Config('send_to_tahesab') == "true") {
if (app(ConfigService::class)->isTahesabAccounting()) {
try {
$result = app(TahesabController::class)->wallet([
"customer_id" => auth('customer')->id(),
@@ -0,0 +1,384 @@
<?php
namespace App\Http\Controllers\Pargar;
use App\Http\Controllers\Controller;
use App\Http\Middleware\PargarAuth;
use App\Models\Cart;
use App\Models\Customer;
use App\Models\Order;
use App\Models\PargarSyncLog;
use App\Models\Product;
use App\Services\ConfigService;
use App\Services\PargarSyncService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Morilog\Jalali\Jalalian;
use Rooyesh\Wallet\interfaces\TransactionInterface;
use Rooyesh\Wallet\interfaces\WalletInterface;
class PargarInboundController extends Controller
{
protected function sync(): PargarSyncService
{
return app(PargarSyncService::class);
}
public function login(Request $request)
{
if (! app(ConfigService::class)->isPargarAccounting()) {
return response()->json([
'success' => false,
'data' => null,
'message' => 'Pargar integration is disabled',
], 503);
}
$username = (string) $request->input('username', '');
$password = (string) $request->input('password', '');
$expectedUser = trim((string) $this->Config('pargar_username'));
$expectedPass = trim((string) $this->Config('pargar_password'));
if ($username !== $expectedUser || $password !== $expectedPass || $expectedUser === '') {
return response()->json([
'success' => false,
'data' => null,
'message' => 'Invalid credentials!',
]);
}
$token = PargarAuth::issueToken($username);
return response()->json([
'success' => true,
'data' => ['token' => $token],
'message' => 'You are logged in successfully.',
]);
}
public function upsertCustomers(Request $request)
{
$customers = $request->input('Customers', []);
if (! is_array($customers) || $customers === []) {
return response()->json([
'success' => false,
'message' => 'Customers list is required',
'data' => null,
], 422);
}
$messages = [];
foreach ($customers as $row) {
if (! is_array($row)) {
continue;
}
$accountId = trim((string) ($row['AccountId'] ?? ''));
$fullName = trim((string) ($row['FullName'] ?? ''));
$mobile = $this->normalizeMobile((string) ($row['CellPhone'] ?? ''));
if ($accountId === '' || $mobile === '') {
$messages[] = 'ردیف نامعتبر: AccountId یا CellPhone خالی است';
continue;
}
$customer = Customer::whereNull('deleted_at')
->where('mobile', $mobile)
->first();
if ($customer) {
if ((string) ($customer->pargar_account_id ?? '') === $accountId) {
$messages[] = 'کد حساب مشتری وجود دارد';
continue;
}
$customer->pargar_account_id = $accountId;
if ($fullName !== '') {
$customer->fname = $fullName;
$customer->name = $fullName;
}
$customer->save();
$messages[] = "کد حسابداری مشتری {$fullName} ویرایش شد";
continue;
}
$customer = new Customer();
$customer->fname = $fullName !== '' ? $fullName : 'مشتری';
$customer->name = $customer->fname;
$customer->mobile = $mobile;
$customer->pargar_account_id = $accountId;
$customer->active = true;
$customer->type = 2;
$customer->password = $mobile;
$customer->referral_id = $this->uniqueReferralId();
$customer->save();
$messages[] = "مشتری با کد حسابداری {$accountId} ایجاد شد";
}
return response()->json([
'success' => true,
'message' => implode(' | ', $messages),
'data' => null,
]);
}
public function listOrders(Request $request)
{
$orders = Order::query()
->where('sts', 2)
->whereNull('pargar_confirmed_at')
->limit(100)
->get();
$documents = $orders->map(function (Order $order) {
$customer = Customer::find((int) ($order->customer_id ?? 0));
return [
'OrderId' => (string) $order->id,
'OrderType' => (int) ($order->direction ?? 1) === 1 ? 1 : -1,
'PriceType' => 1,
'CustomerAccountId' => (int) ($customer->pargar_account_id ?? 0),
'ItemId' => 11,
'ItemName' => $this->pargarItemName($order),
'OrderDate' => $order->pay_at
? Jalalian::fromDateTime($order->pay_at)->format('Y/m/d-H:i:s')
: Jalalian::fromDateTime($order->created_at)->format('Y/m/d-H:i:s'),
'Fee' => (float) ($order->price ?? 0),
'Count' => 1,
'Weight' => (float) ($order->amount ?? 0),
'Price' => (float) ($order->total_price ?? 0),
'Carat' => 750,
'Description' => (string) ($order->description ?? ''),
];
})->values();
return response()->json([
'state' => 'true',
'msg' => '',
'data' => [
'documents' => $documents,
'duplicated' => 'false',
'result' => 'OK',
],
]);
}
public function confirmOrders(Request $request)
{
$orderIds = $request->input('OrderIds', []);
if (! is_array($orderIds)) {
return response()->json([
'success' => false,
'message' => 'OrderIds must be an array',
'data' => null,
], 422);
}
$this->sync()->confirmOrders($orderIds);
return response()->json([
'success' => true,
'message' => 'تاییدیه اسناد با موفقیت دریافت شد',
'data' => null,
]);
}
public function storeDocuments(Request $request)
{
$data = $request->all();
$accountId = trim((string) ($data['accountId'] ?? ''));
$transactions = $data['transactions'] ?? [];
$customer = $accountId !== ''
? Customer::where('pargar_account_id', $accountId)->first()
: null;
PargarSyncLog::create([
'customer_id' => $customer?->id,
'desc' => 'دریافت اسناد از پرگار',
'type' => 'document',
'sync_status' => PargarSyncService::STATUS_SUCCESS,
'pargar_ref' => $accountId,
'data' => json_encode($data, JSON_UNESCAPED_UNICODE),
]);
return response()->json([
'success' => true,
'message' => 'اسناد مشتری با موفقیت دریافت شد',
'data' => null,
]);
}
public function storeMande(Request $request)
{
$data = $request->all();
$accountId = trim((string) ($data['AccountId'] ?? ''));
$customer = Customer::where('pargar_account_id', $accountId)->first();
if (! $customer) {
return response()->json([
'success' => false,
'message' => 'مشتری یافت نشد',
'data' => null,
], 422);
}
$goldInv = isset($data['GoldInv']) ? (float) str_replace('/', '.', (string) $data['GoldInv']) : 0;
$rialInv = isset($data['RialInv']) ? (float) str_replace('/', '.', (string) $data['RialInv']) : 0;
$wallet = $customer->wallet->where('fund_id', null)->first();
if ($wallet) {
$goldDiff = $goldInv - (float) $wallet->gold;
$rialDiff = $rialInv - (float) $wallet->cash;
} else {
$goldDiff = $goldInv;
$rialDiff = $rialInv;
}
if (abs($goldDiff) >= 0.01 || abs($rialDiff) >= 1) {
$walletInterface = new WalletInterface($customer->id, 2);
new TransactionInterface($walletInterface, [
'cash' => $rialDiff,
'gold' => $goldDiff,
'expire_at' => now(),
'active' => true,
'start_at' => now(),
], 22, $goldDiff > 0, $rialDiff > 0);
}
PargarSyncLog::create([
'customer_id' => $customer->id,
'desc' => 'دریافت مانده از پرگار',
'type' => 'mande',
'amount' => $goldInv,
'sync_status' => PargarSyncService::STATUS_SUCCESS,
'pargar_ref' => $accountId,
'data' => json_encode($data, JSON_UNESCAPED_UNICODE),
]);
return response()->json([
'success' => true,
'message' => 'مانده مشتری با موفقیت دریافت شد',
'data' => null,
]);
}
public function listNewCustomers(Request $request)
{
$customers = Customer::query()
->whereNull('deleted_at')
->whereNull('pargar_account_id')
->limit(100)
->get(['id', 'fname', 'lname', 'mobile']);
if ($customers->isEmpty()) {
return response()->json([
'success' => true,
'message' => 'مشتری جدید وجود ندارد',
'data' => null,
]);
}
$rows = $customers->map(function (Customer $customer) {
$nameParts = preg_split('/\s+/', trim((string) $customer->name), 2) ?: ['', ''];
return [
'id' => (string) $customer->id,
'name' => $nameParts[0] ?? '',
'family' => $nameParts[1] ?? '',
'mobile' => (string) $customer->mobile,
];
});
return response()->json([
'state' => 'true',
'msg' => '',
'customer' => $rows,
]);
}
public function confirmCustomers(Request $request)
{
$rows = $request->input('customers', []);
if (! is_array($rows)) {
return response()->json([
'success' => false,
'message' => 'customers must be an array',
'data' => null,
], 422);
}
foreach ($rows as $row) {
if (! is_array($row)) {
continue;
}
$customerId = $row['customer_id'] ?? $row['id_customer'] ?? null;
$accountId = $row['accounting_id'] ?? null;
if ($customerId === null || $accountId === null) {
continue;
}
$customer = Customer::find($customerId);
if ($customer) {
$customer->pargar_account_id = (string) $accountId;
$customer->save();
$log = PargarSyncLog::query()
->where('customer_id', $customer->id)
->where('type', 'customer')
->where('sync_status', PargarSyncService::STATUS_PENDING)
->latest('id')
->first();
if ($log) {
$this->sync()->markSuccess($log, (string) $accountId);
}
}
}
return response()->json([
'success' => true,
'message' => 'تاییدیه ثبت مشتری با موفقیت دریافت شد',
'data' => null,
]);
}
private function normalizeMobile(string $mobile): string
{
$mobile = trim($mobile);
if ($mobile === '') {
return '';
}
return '0' . ltrim($mobile, '0');
}
private function uniqueReferralId(): string
{
do {
$referral = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyz'), 0, 4);
} while (Customer::where('referral_id', $referral)->exists());
return $referral;
}
private function pargarItemName(Order $order): string
{
return $this->isCartOrder($order) ? 'سبد خرید' : 'آبشده';
}
private function isCartOrder(Order $order): bool
{
if (Schema::hasTable('cart') && Schema::hasColumn('cart', 'order_id')) {
return Cart::where('order_id', $order->id)->exists();
}
return PargarSyncLog::where('order_id', $order->id)
->where('desc', 'سبد خرید')
->exists();
}
}
@@ -198,13 +198,21 @@ class SingleOrderController extends Controller
}
}
if ($config->getString('send_to_tahesab', 'false') === 'true') {
if ($config->isTahesabAccounting()) {
try {
$data['tahesab'] = app(TahesabController::class)->storeTrade($data);
} catch (\Exception $e) {
report($e);
}
}
if ($config->isPargarAccounting()) {
try {
app(\App\Services\PargarSyncService::class)->recordTrade($data['order'] ?? null);
} catch (\Throwable $e) {
report($e);
}
}
// $response['reward'] = $this->give_order_reward($data, $wallet, $wallet_interface);
if ((int) $data['direction'] === 1 && $customer) {
$referrer = Customer::where('referral_id', $customer->referral_to_id)->first();
+1 -1
View File
@@ -66,7 +66,7 @@ class Kernel extends HttpKernel
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'validation'=>ValidationMiddleware::class,
'request.validation' => \App\Http\Middleware\ValidateRequests::class,
'LogRequests' => \App\Http\Middleware\LogRequests::class,
'pargar.auth' => \App\Http\Middleware\PargarAuth::class,
'sms.panel.super' => \App\Http\Middleware\EnsureSmsPanelSuperAdmin::class,
];
}
-66
View File
@@ -1,66 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use App\Models\Pargar;
use App\Jobs\LogRouteCustomerJob;
use Illuminate\Support\Facades\Http;
class LogRequests
{
public function handle(Request $request, Closure $next)
{
// زمان شروع بر حسب میلی‌ثانیه
$start = microtime(true);
try {
// لاگ قبل از اجرای کنترلر
$Pargar = new Pargar();
$Pargar->ip = $request->ip();
$Pargar->m = $request->route()?->getName() ?? 'not_found';
//get action name from controller
if ($route = $request->route()) {
// تلاش برای گرفتن نام اکشن (کنترلر و متد)
$Pargar->function_name = $route->getActionName();
} else {
$Pargar->function_name = 'not_found';
}
$Pargar->finance_data = json_encode($request->all(), JSON_UNESCAPED_UNICODE);
$Pargar->finance_head = json_encode($request->headers->all(), JSON_UNESCAPED_UNICODE);
$Pargar->url = $request->fullUrl();
$Pargar->method = $request->method();
$Pargar->save();
} catch (\Throwable $e) {
$Pargar = null; // برای اطمینان از عدم خطا در ادامه
}
// اجرای درخواست اصلی
$response = $next($request);
try {
if ($Pargar) {
// if ($route = $request->route()) {
// try {
// $Pargar->m = $route
// ? $route->getActionName() . ' (' . (new \ReflectionClass($route->getController()))->getFileName() . ')'
// : 'unknown';
// } catch (\Throwable $e) {
// $Pargar->m = 'unknown';
// }
//
// }
$Pargar->response = $response->getContent();
$Pargar->ms = round((microtime(true) - $start) * 1000, 2); // زمان در میلی‌ثانیه
$Pargar->save();
}
} catch (\Throwable $e) {}
return $response;
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Http\Middleware;
use App\Services\ConfigService;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class PargarAuth
{
public function handle(Request $request, Closure $next)
{
if (! app(ConfigService::class)->isPargarAccounting()) {
return response()->json([
'success' => false,
'message' => 'Pargar integration is disabled',
'data' => null,
], 503);
}
$auth = $request->header('Authorization') ?? $request->header('pargar');
$token = is_string($auth) ? trim(str_replace('Bearer ', '', $auth)) : '';
if ($token === '' || ! $this->tokenValid($token)) {
return response()->json([
'success' => false,
'message' => 'Invalid credentials!',
'data' => null,
], 401);
}
return $next($request);
}
public static function issueToken(string $username): string
{
$token = bin2hex(random_bytes(32));
Cache::put(self::cacheKey($token), $username, now()->addDay());
return $token;
}
public static function tokenValid(string $token): bool
{
$config = app(ConfigService::class);
$static = trim($config->getString('pargar_api_token', ''));
if ($static !== '' && hash_equals($static, $token)) {
return true;
}
return Cache::has(self::cacheKey($token));
}
private static function cacheKey(string $token): string
{
return 'pargar_api_token:' . $token;
}
}
+1
View File
@@ -47,6 +47,7 @@ class Customer extends Authenticatable implements JWTSubject
'total_referrals',
'credential_id',
'moshtari_code',
'pargar_account_id',
];
/**
-28
View File
@@ -1,28 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Pargar extends Model
{
use HasFactory;
protected $table = 'pargar_reqasts';
protected $fillable = [
'm',
'route',
'ip',
'data',
'finance_head',
'finance_data',
'response',
'function_name',
];
protected $casts = [
'data' => 'json',
'finance_head' => 'json',
'finance_data' => 'json',
'response' => 'json',
];
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class PargarSyncLog extends Model
{
protected $table = 'pargar_sync_logs';
protected $fillable = [
'customer_id',
'amount',
'desc',
'order_id',
'withdraw_id',
'date',
'type',
'sync_status',
'sync_log',
'pargar_ref',
'res',
'data',
];
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
}
public function order(): BelongsTo
{
return $this->belongsTo(Order::class);
}
}
+3
View File
@@ -3,6 +3,8 @@
namespace App\Observers;
use App\Models\Customer;
use App\Services\ConfigService;
use App\Services\PargarSyncService;
use App\Services\TahesabSyncService;
class CustomerObserver
@@ -10,5 +12,6 @@ class CustomerObserver
public function created(Customer $customer): void
{
app(TahesabSyncService::class)->registerOnCreate($customer);
app(PargarSyncService::class)->registerOnCreate($customer);
}
}
+4 -7
View File
@@ -7,7 +7,6 @@ use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvi
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use App\Services\ConfigService;
class RouteServiceProvider extends ServiceProvider
{
@@ -56,12 +55,10 @@ class RouteServiceProvider extends ServiceProvider
->namespace($this->namespace)
->group(base_path('routes/customer.php'));
if (app(ConfigService::class)->isStore()) {
Route::prefix('pargar')
->middleware('LogRequests')
->namespace($this->namespace)
->group(base_path('routes/pargar.php'));
}
Route::prefix('pargar')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/pargar.php'));
Route::middleware('web')
->namespace($this->namespace)
+32
View File
@@ -685,4 +685,36 @@ class ConfigService
throw new UnprocessableEntityHttpException($this->productTradingClosedMessage($product));
}
}
public const ACCOUNTING_NONE = 'none';
public const ACCOUNTING_TAHESAB = 'tahesab';
public const ACCOUNTING_PARGAR = 'pargar';
/** none | tahesab | pargar — از setting accounting_provider */
public function accountingProvider(): string
{
$provider = strtolower(trim($this->getString('accounting_provider', '')));
if (in_array($provider, [self::ACCOUNTING_NONE, self::ACCOUNTING_TAHESAB, self::ACCOUNTING_PARGAR], true)) {
return $provider;
}
if ($this->getString('send_to_pargar', 'false') === 'true') {
return self::ACCOUNTING_PARGAR;
}
if ($this->getString('send_to_tahesab', 'false') === 'true') {
return self::ACCOUNTING_TAHESAB;
}
return self::ACCOUNTING_NONE;
}
public function isTahesabAccounting(): bool
{
return $this->accountingProvider() === self::ACCOUNTING_TAHESAB;
}
public function isPargarAccounting(): bool
{
return $this->accountingProvider() === self::ACCOUNTING_PARGAR;
}
}
+269
View File
@@ -0,0 +1,269 @@
<?php
namespace App\Services;
use App\Models\Customer;
use App\Models\Order;
use App\Models\PargarSyncLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
class PargarSyncService
{
public const STATUS_PENDING = 'pending';
public const STATUS_SUCCESS = 'success';
public const STATUS_FAILED = 'failed';
public function buildLog(mixed $response = null, ?string $extra = null): string
{
$parts = array_filter([
$extra,
is_array($response) ? json_encode($response, JSON_UNESCAPED_UNICODE) : (is_string($response) ? $response : null),
]);
return implode("\n", $parts);
}
public function markSuccess(PargarSyncLog $log, ?string $ref = null, mixed $response = null, ?array $payload = null): void
{
$update = [
'pargar_ref' => $ref,
'res' => is_array($response)
? json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
: (is_string($response) ? $response : null),
'sync_status' => self::STATUS_SUCCESS,
'sync_log' => null,
];
if ($payload !== null) {
$update['data'] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
PargarSyncLog::where('id', $log->id)->update($update);
}
public function markFailed(PargarSyncLog $log, mixed $response = null, ?string $extra = null): void
{
PargarSyncLog::where('id', $log->id)->update([
'res' => is_array($response)
? json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
: (is_string($response) ? $response : null),
'sync_status' => self::STATUS_FAILED,
'sync_log' => $this->buildLog($response, $extra),
]);
}
public function isFailed(PargarSyncLog $log): bool
{
return ($log->sync_status ?? null) === self::STATUS_FAILED
|| (empty($log->pargar_ref) && ($log->sync_status ?? null) !== self::STATUS_SUCCESS);
}
public function registerOnCreate(Customer $customer): void
{
if (! empty($customer->pargar_account_id) || ! $this->isSyncEnabled()) {
return;
}
$customerId = (int) $customer->id;
dispatch(function () use ($customerId) {
$service = app(self::class);
if (! $service->isSyncEnabled()) {
return;
}
$customer = Customer::find($customerId);
if ($customer === null || ! empty($customer->pargar_account_id)) {
return;
}
try {
$service->queueCustomerExport($customer);
} catch (\Throwable $e) {
Log::warning('Pargar auto-queue on customer create failed', [
'customer_id' => $customerId,
'error' => $e->getMessage(),
]);
}
})->afterResponse();
}
public function queueCustomerExport(Customer $customer): PargarSyncLog
{
$existing = PargarSyncLog::query()
->where('customer_id', $customer->id)
->where('type', 'customer')
->where('sync_status', self::STATUS_PENDING)
->latest('id')
->first();
if ($existing) {
return $existing;
}
return PargarSyncLog::create([
'customer_id' => $customer->id,
'desc' => 'در انتظار دریافت توسط پرگار',
'type' => 'customer',
'sync_status' => self::STATUS_PENDING,
'data' => json_encode(['handler' => 'exportCustomer'], JSON_UNESCAPED_UNICODE),
]);
}
public function registerCustomer(Customer $customer): array
{
$customer->refresh();
if (! empty($customer->pargar_account_id)) {
return [
'ok' => true,
'message' => 'مشتری قبلاً در پرگار ثبت شده است',
'pargar_account_id' => $customer->pargar_account_id,
];
}
$this->queueCustomerExport($customer);
return [
'ok' => true,
'message' => 'مشتری در صف دریافت پرگار قرار گرفت',
'pargar_account_id' => null,
];
}
public function recordTrade(?Order $order, bool $fromCart = false): ?PargarSyncLog
{
if (! $order || ! $order->id || ! $this->isSyncEnabled()) {
return null;
}
$existing = PargarSyncLog::where('order_id', $order->id)->first();
if ($existing) {
return $existing;
}
$direction = (int) ($order->direction ?? 0);
$desc = $fromCart
? 'سبد خرید'
: ($direction === 1 ? 'خرید آبشده' : 'فروش آبشده');
return PargarSyncLog::create([
'customer_id' => $order->customer_id,
'amount' => $order->amount,
'desc' => $desc,
'order_id' => $order->id,
'date' => $order->pay_at ?? $order->created_at,
'type' => 'order',
'sync_status' => self::STATUS_PENDING,
'data' => json_encode([
'handler' => 'exportOrder',
'direction' => $direction,
'price' => $order->price,
'total_price' => $order->total_price,
], JSON_UNESCAPED_UNICODE),
]);
}
public function confirmOrders(array $orderIds): array
{
$confirmed = 0;
foreach ($orderIds as $orderId) {
$order = Order::find($orderId);
if (! $order) {
continue;
}
$order->pargar_confirmed_at = now();
$order->save();
$log = PargarSyncLog::where('order_id', $order->id)->latest('id')->first();
if ($log) {
$this->markSuccess($log, (string) $order->id, ['OrderId' => (string) $order->id]);
}
$confirmed++;
}
return ['confirmed' => $confirmed];
}
public function retry(PargarSyncLog $log): array
{
if (! $this->isFailed($log) && ! empty($log->pargar_ref)) {
return ['ok' => false, 'message' => 'این سند قبلاً در پرگار ثبت شده است'];
}
$payload = json_decode((string) $log->data, true) ?: [];
$handler = $payload['handler'] ?? $this->inferHandler($log);
if ($handler === 'exportOrder' && $log->order_id) {
Order::where('id', $log->order_id)->update(['pargar_confirmed_at' => null]);
PargarSyncLog::where('id', $log->id)->update([
'sync_status' => self::STATUS_PENDING,
'sync_log' => null,
'pargar_ref' => null,
]);
$log->refresh();
return [
'ok' => true,
'message' => 'سفارش دوباره در صف ارسال به پرگار قرار گرفت',
'pargar' => $log,
];
}
if ($handler === 'exportCustomer' && $log->customer_id) {
PargarSyncLog::where('id', $log->id)->update([
'sync_status' => self::STATUS_PENDING,
'sync_log' => null,
'pargar_ref' => null,
]);
$log->refresh();
return [
'ok' => true,
'message' => 'مشتری دوباره در صف دریافت پرگار قرار گرفت',
'pargar' => $log,
];
}
return [
'ok' => false,
'message' => $log->sync_log ?: 'ارسال مجدد به پرگار ناموفق بود',
'pargar' => $log,
];
}
private function inferHandler(PargarSyncLog $log): string
{
if ($log->order_id) {
return 'exportOrder';
}
if ($log->type === 'customer') {
return 'exportCustomer';
}
if ($log->type === 'mande') {
return 'importMande';
}
return 'importDocument';
}
public function isSyncEnabled(): bool
{
$config = app(ConfigService::class);
return $config->isPargarAccounting()
&& trim($config->getString('pargar_username', '')) !== ''
&& trim($config->getString('pargar_password', '')) !== '';
}
public function hasSyncTable(): bool
{
return Schema::hasTable('pargar_sync_logs');
}
}
+2 -2
View File
@@ -168,7 +168,7 @@ class ReferralGiftService
'withdraw_id' => $withdraw->id,
]);
if ($config->getString('send_to_tahesab', 'false') === 'true') {
if ($config->isTahesabAccounting()) {
try {
app(TahesabController::class)->referrerGoldReward(
$customer,
@@ -226,7 +226,7 @@ class ReferralGiftService
'withdraw_id' => $withdraw->id,
]);
if ($config->getString('send_to_tahesab', 'false') === 'true') {
if ($config->isTahesabAccounting()) {
try {
app(TahesabController::class)->goldReward([
'customer_id' => $customer->id,
+5 -10
View File
@@ -198,16 +198,11 @@ class TahesabSyncService
{
$config = app(ConfigService::class);
if ($config->getString('send_to_tahesab', 'false') !== 'true') {
return false;
}
$host = trim($config->getString('tahesab_host', ''));
$port = trim($config->getString('tahesab_port', ''));
$token = trim($config->getString('tahesab_token', ''));
$db = trim($config->getString('tahesab_db', ''));
return $host !== '' && $port !== '' && $token !== '' && $db !== '';
return $config->isTahesabAccounting()
&& trim($config->getString('tahesab_host', '')) !== ''
&& trim($config->getString('tahesab_port', '')) !== ''
&& trim($config->getString('tahesab_token', '')) !== ''
&& trim($config->getString('tahesab_db', '')) !== '';
}
private function hasSyncColumns(): bool