added zarin
This commit is contained in:
@@ -155,6 +155,16 @@ class WalletController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if (app(ConfigService::class)->isZarinAccounting()) {
|
||||
try {
|
||||
$data['direction'] = $deposit ? 0 : 1;
|
||||
$data['withdraw_id'] = $Withdraw->id;
|
||||
app(\App\Http\Controllers\Admin\ZarinController::class)->wallet($data);
|
||||
} catch (\Exception $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
app(PargarSyncService::class)->queueWalletDocument(
|
||||
$customer,
|
||||
@@ -243,6 +253,16 @@ class WalletController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if (app(ConfigService::class)->isZarinAccounting()) {
|
||||
try {
|
||||
$data['direction'] = $deposit ? 1 : 0;
|
||||
$data['withdraw_id'] = $Withdraw->id;
|
||||
app(\App\Http\Controllers\Admin\ZarinController::class)->walletGold($data);
|
||||
} catch (\Exception $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
app(PargarSyncService::class)->queueWalletDocument(
|
||||
$customer,
|
||||
@@ -408,6 +428,16 @@ class WalletController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if (app(ConfigService::class)->isZarinAccounting()) {
|
||||
try {
|
||||
$data['direction'] = $deposit ? 1 : 0;
|
||||
$data['withdraw_id'] = $Withdraw->id;
|
||||
app(\App\Http\Controllers\Admin\ZarinController::class)->walletGold($data);
|
||||
} catch (\Exception $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// فقط محصول متصل به کیف اصلی → سند طلای پرگار
|
||||
app(PargarSyncService::class)->queueWalletDocument(
|
||||
@@ -651,6 +681,32 @@ class WalletController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if (app(ConfigService::class)->isZarinAccounting() && isset($records['rows']) && is_array($records['rows'])) {
|
||||
$withdrawIds = collect($records['rows'])->pluck('id')->filter()->values()->all();
|
||||
if ($withdrawIds !== []) {
|
||||
$failedByWithdraw = \App\Models\Zarin::query()
|
||||
->whereIn('withdraw_id', $withdrawIds)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('zarin_id');
|
||||
if (\Illuminate\Support\Facades\Schema::hasColumn('zarin', 'sync_status')) {
|
||||
$query->orWhere('sync_status', \App\Services\ZarinSyncService::STATUS_FAILED);
|
||||
}
|
||||
})
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->unique('withdraw_id')
|
||||
->keyBy('withdraw_id');
|
||||
|
||||
foreach ($records['rows'] as &$row) {
|
||||
$zarin = $failedByWithdraw->get($row['id'] ?? null);
|
||||
if ($zarin) {
|
||||
$row['zarin_failed_id'] = $zarin->id;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json($records);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -277,6 +277,7 @@ class AuthController extends Controller
|
||||
// claim بدون کد حسابداری — صف export مثل create
|
||||
app(\App\Services\PargarSyncService::class)->registerOnCreate($customer);
|
||||
app(\App\Services\TahesabSyncService::class)->registerOnCreate($customer);
|
||||
app(\App\Services\ZarinSyncService::class)->registerOnCreate($customer);
|
||||
}
|
||||
|
||||
return response()->json($customer);
|
||||
|
||||
@@ -384,6 +384,26 @@ class CartController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->isZarinAccounting()) {
|
||||
try {
|
||||
app(\App\Http\Controllers\Admin\ZarinController::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,
|
||||
'id' => $primaryProduct?->id,
|
||||
],
|
||||
'order' => ['id' => $order->id],
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->isPargarAccounting()) {
|
||||
try {
|
||||
app(PargarSyncService::class)->recordTrade($order, true, [
|
||||
|
||||
@@ -411,6 +411,19 @@ class WalletController extends Controller
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
if (app(ConfigService::class)->isZarinAccounting()) {
|
||||
try {
|
||||
app(\App\Http\Controllers\Admin\ZarinController::class)->wallet([
|
||||
"customer_id" => auth('customer')->id(),
|
||||
"value" => $data['value'],
|
||||
"direction" => 1,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
$companyName = $this->Config('name_fa'); // Company name from configuration
|
||||
$message = "کاربر $userName با شماره موبایل $userMobile درخواست برداشت مبلغ " . number_format($withdrawAmount) . " ریال را دارد. با تشکر، $companyName";
|
||||
if (isset($request->otp)) {
|
||||
|
||||
@@ -218,6 +218,14 @@ class SingleOrderController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->isZarinAccounting()) {
|
||||
try {
|
||||
$data['zarin'] = app(\App\Http\Controllers\Admin\ZarinController::class)->storeTrade($data);
|
||||
} catch (\Exception $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->isPargarAccounting()) {
|
||||
try {
|
||||
$paymentProductId = (int) ($data['payment_product_id'] ?? 0);
|
||||
|
||||
@@ -360,6 +360,7 @@ class Product extends Model
|
||||
'amount', // The amount of the record.
|
||||
'unit', // The unit of the item.
|
||||
'group_type', // The unit of the item.
|
||||
'finance_p_name', // کد کار زرین (idKar)
|
||||
'file_id', // The ID of the associated file.
|
||||
'can_canceled', // Whether the record can be canceled (boolean).
|
||||
'active', // The active status of the record (boolean).
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Zarin extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'zarin';
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'order_id',
|
||||
'withdraw_id',
|
||||
'amount',
|
||||
'desc',
|
||||
'type',
|
||||
'date',
|
||||
'zarin_id',
|
||||
'sh_factor',
|
||||
'factor_number',
|
||||
'data',
|
||||
'res',
|
||||
'sync_status',
|
||||
'sync_log',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
static $STS = [
|
||||
1 => 'باز',
|
||||
2 => 'موفق',
|
||||
3 => 'ناموفق'
|
||||
];
|
||||
|
||||
static $DIRECTION = [
|
||||
1 => 'خرید',
|
||||
2 => 'فروش'
|
||||
];
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'customer_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,16 @@
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Customer;
|
||||
use App\Services\ConfigService;
|
||||
use App\Services\PargarSyncService;
|
||||
use App\Services\TahesabSyncService;
|
||||
use App\Services\ZarinSyncService;
|
||||
|
||||
class CustomerObserver
|
||||
{
|
||||
public function created(Customer $customer): void
|
||||
{
|
||||
app(TahesabSyncService::class)->registerOnCreate($customer);
|
||||
app(ZarinSyncService::class)->registerOnCreate($customer);
|
||||
app(PargarSyncService::class)->registerOnCreate($customer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,8 +728,8 @@ class ConfigService
|
||||
public const ACCOUNTING_NONE = 'none';
|
||||
public const ACCOUNTING_TAHESAB = 'tahesab';
|
||||
public const ACCOUNTING_PARGAR = 'pargar';
|
||||
public const ACCOUNTING_ZARIN = 'zarin';
|
||||
|
||||
/** none | tahesab | pargar — از setting accounting_provider */
|
||||
public function isConditionalOrderEnabled(): bool
|
||||
{
|
||||
return $this->getBool('enable_conditional_order', false);
|
||||
@@ -762,10 +762,11 @@ class ConfigService
|
||||
return max(0, $this->getInt('fee_conditional_order', 0));
|
||||
}
|
||||
|
||||
/** none | tahesab | pargar | zarin — از 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)) {
|
||||
if (in_array($provider, [self::ACCOUNTING_NONE, self::ACCOUNTING_TAHESAB, self::ACCOUNTING_PARGAR, self::ACCOUNTING_ZARIN], true)) {
|
||||
return $provider;
|
||||
}
|
||||
|
||||
@@ -789,6 +790,11 @@ class ConfigService
|
||||
return $this->accountingProvider() === self::ACCOUNTING_PARGAR;
|
||||
}
|
||||
|
||||
public function isZarinAccounting(): bool
|
||||
{
|
||||
return $this->accountingProvider() === self::ACCOUNTING_ZARIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hostname for WebOTP SMS (@domain #code) and other public links.
|
||||
* Only from the current HTTP request — no settings, no .env.
|
||||
|
||||
@@ -180,6 +180,18 @@ class ReferralGiftService
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->isZarinAccounting()) {
|
||||
try {
|
||||
app(\App\Http\Controllers\Admin\ZarinController::class)->referrerGoldReward(
|
||||
$customer,
|
||||
$giftForInviter,
|
||||
'پاداش بابت معرفی'
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
$currentGold = Controller::formatSmsGoldWeight((float) $walletInterface->wallet->refresh()->gold);
|
||||
$message = "سلام {$referrer->name} عزیز،\n\n{$giftForInviter} گرم طلا بابت معرفی {$customer->name} به حساب شما واریز شد.\nموجودی فعلی: {$currentGold} گرم.\n\nاطلاعات بیشتر: {$companyUrl}\n\nبا احترام،\n{$companyName}";
|
||||
Controller::Send($referrer->mobile, $message);
|
||||
@@ -238,6 +250,18 @@ class ReferralGiftService
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->isZarinAccounting()) {
|
||||
try {
|
||||
app(\App\Http\Controllers\Admin\ZarinController::class)->goldReward([
|
||||
'customer_id' => $customer->id,
|
||||
'value' => $giftForInvited,
|
||||
'desc' => 'پاداش بابت ثبت نام',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
|
||||
$currentGold = Controller::formatSmsGoldWeight((float) $walletInterface->wallet->refresh()->gold);
|
||||
$message = "سلام {$customer->name} عزیز،\n\n{$giftForInvited} گرم طلا بابت ثبت نام به حساب شما واریز شد.\nموجودی فعلی: {$currentGold} گرم.\n\nاطلاعات بیشتر: {$companyUrl}\n\nبا احترام،\n{$companyName}";
|
||||
Controller::Send($customer->mobile, $message);
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Http\Controllers\Admin\ZarinController;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Zarin;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ZarinSyncService
|
||||
{
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_SUCCESS = 'success';
|
||||
public const STATUS_FAILED = 'failed';
|
||||
|
||||
public function responseOk(mixed $response): bool
|
||||
{
|
||||
if (! is_array($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasNumericIdKey($response)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$data = $response['data'] ?? null;
|
||||
if (! is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$row = array_is_list($data) ? ($data[0] ?? null) : $data;
|
||||
|
||||
return is_array($row) && $this->hasNumericIdKey($row);
|
||||
}
|
||||
|
||||
private function hasNumericIdKey(array $row): bool
|
||||
{
|
||||
foreach (['idFactor', 'idSanad', 'pkRadifSanad', 'idHesab', 'OK'] as $key) {
|
||||
if (! array_key_exists($key, $row)) {
|
||||
continue;
|
||||
}
|
||||
$value = $row[$key];
|
||||
if ($value === null || $value === '' || $value === false) {
|
||||
continue;
|
||||
}
|
||||
if (is_numeric($value) || (is_string($value) && trim($value) !== '')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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(Zarin $row, array $response, ?array $payload = null): void
|
||||
{
|
||||
$flat = $this->flattenResponse($response);
|
||||
|
||||
$zarinId = $flat['OK']
|
||||
?? $flat['idFactor']
|
||||
?? $flat['idSanad']
|
||||
?? $flat['pkRadifSanad']
|
||||
?? $flat['idHesab']
|
||||
?? null;
|
||||
|
||||
$shFactor = $flat['Sh_factor']
|
||||
?? $flat['pkRadifSanad']
|
||||
?? $flat['idRadifSanad']
|
||||
?? null;
|
||||
|
||||
$update = [
|
||||
'sh_factor' => $shFactor !== null ? (string) $shFactor : null,
|
||||
'zarin_id' => $zarinId !== null ? (string) $zarinId : null,
|
||||
'factor_number' => isset($flat['Factor_Number']) ? (string) $flat['Factor_Number'] : (isset($flat['idFactor']) ? (string) $flat['idFactor'] : null),
|
||||
'res' => json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
||||
'sync_status' => self::STATUS_SUCCESS,
|
||||
'sync_log' => null,
|
||||
];
|
||||
|
||||
if ($payload !== null) {
|
||||
$update['data'] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
if ($this->hasSyncColumns()) {
|
||||
Zarin::where('id', $row->id)->update($update);
|
||||
} else {
|
||||
unset($update['sync_status'], $update['sync_log']);
|
||||
Zarin::where('id', $row->id)->update($update);
|
||||
}
|
||||
}
|
||||
|
||||
public function markFailed(Zarin $row, mixed $response = null, ?string $extra = null): void
|
||||
{
|
||||
$update = [
|
||||
'res' => is_array($response)
|
||||
? json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
|
||||
: (is_string($response) ? $response : null),
|
||||
];
|
||||
|
||||
if ($this->hasSyncColumns()) {
|
||||
$update['sync_status'] = self::STATUS_FAILED;
|
||||
$update['sync_log'] = $this->buildLog($response, $extra);
|
||||
}
|
||||
|
||||
Zarin::where('id', $row->id)->update($update);
|
||||
}
|
||||
|
||||
public function isFailed(Zarin $row): bool
|
||||
{
|
||||
if ($this->hasSyncColumns()) {
|
||||
return ($row->sync_status ?? null) === self::STATUS_FAILED
|
||||
|| (empty($row->zarin_id) && ($row->sync_status ?? null) !== self::STATUS_SUCCESS);
|
||||
}
|
||||
|
||||
return empty($row->zarin_id);
|
||||
}
|
||||
|
||||
public function registerOnCreate(Customer $customer): void
|
||||
{
|
||||
if (! empty($customer->moshtari_code) || ! $this->isSyncEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$customerId = (int) $customer->id;
|
||||
|
||||
dispatch(function () use ($customerId) {
|
||||
$service = app(ZarinSyncService::class);
|
||||
$customer = Customer::find($customerId);
|
||||
if ($customer === null || ! empty($customer->moshtari_code) || ! $service->isSyncEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $service->registerCustomer($customer);
|
||||
if (! ($result['ok'] ?? false)) {
|
||||
Log::warning('Zarin auto-register on customer create failed', [
|
||||
'customer_id' => $customerId,
|
||||
'message' => $result['message'] ?? null,
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Zarin auto-register on customer create threw', [
|
||||
'customer_id' => $customerId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
})->afterResponse();
|
||||
}
|
||||
|
||||
public function registerCustomer(Customer $customer): array
|
||||
{
|
||||
$customer->refresh();
|
||||
|
||||
if (! empty($customer->moshtari_code)) {
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'مشتری قبلاً در زرین ثبت شده است',
|
||||
'moshtari_code' => $customer->moshtari_code,
|
||||
];
|
||||
}
|
||||
|
||||
$controller = app(ZarinController::class);
|
||||
$result = $controller->createCustomerInZarin($customer->id);
|
||||
|
||||
if (is_string($result)) {
|
||||
return ['ok' => false, 'message' => $result];
|
||||
}
|
||||
|
||||
$customer->refresh();
|
||||
|
||||
if (empty($customer->moshtari_code)) {
|
||||
return ['ok' => false, 'message' => 'ثبت مشتری در زرین ناموفق بود'];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => 'مشتری با موفقیت در زرین ثبت شد',
|
||||
'moshtari_code' => $customer->moshtari_code,
|
||||
];
|
||||
}
|
||||
|
||||
public function retry(Zarin $row): array
|
||||
{
|
||||
if (! $this->isFailed($row) && ! empty($row->zarin_id)) {
|
||||
return ['ok' => false, 'message' => 'این سند قبلاً در زرین ثبت شده است'];
|
||||
}
|
||||
|
||||
$payload = json_decode((string) $row->data, true) ?: [];
|
||||
$handler = $payload['handler'] ?? $this->inferHandler($row);
|
||||
$controller = app(ZarinController::class);
|
||||
|
||||
$result = match ($handler) {
|
||||
'wallet' => $controller->wallet(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row),
|
||||
'walletGold' => $controller->walletGold(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row),
|
||||
'storeTrade' => $controller->storeTrade(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row),
|
||||
'goldReward' => $controller->goldReward(array_merge($payload, ['_retry_zarin_id' => $row->id]), $row),
|
||||
default => false,
|
||||
};
|
||||
|
||||
$row->refresh();
|
||||
|
||||
if ($result !== false && ! empty($row->zarin_id)) {
|
||||
return ['ok' => true, 'message' => 'سند با موفقیت در زرین ثبت شد', 'zarin' => $row];
|
||||
}
|
||||
|
||||
return [
|
||||
'ok' => false,
|
||||
'message' => $row->sync_log ?: 'ارسال مجدد به زرین ناموفق بود',
|
||||
'zarin' => $row,
|
||||
];
|
||||
}
|
||||
|
||||
private function inferHandler(Zarin $row): string
|
||||
{
|
||||
if ($row->order_id) {
|
||||
return 'storeTrade';
|
||||
}
|
||||
|
||||
if ($row->type === 'ريال') {
|
||||
return 'wallet';
|
||||
}
|
||||
|
||||
$desc = (string) $row->desc;
|
||||
|
||||
if (str_contains($desc, 'شارژ کیف') || str_contains($desc, 'برداشت از کیف')) {
|
||||
return 'walletGold';
|
||||
}
|
||||
|
||||
return 'goldReward';
|
||||
}
|
||||
|
||||
public function isSyncEnabled(): bool
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
|
||||
return $config->isZarinAccounting()
|
||||
&& trim($config->getString('zarin_api', '')) !== ''
|
||||
&& trim($config->getString('zarin_token', '')) !== '';
|
||||
}
|
||||
|
||||
private function flattenResponse(array $response): array
|
||||
{
|
||||
$data = $response['data'] ?? null;
|
||||
if (is_array($data)) {
|
||||
$row = array_is_list($data) ? ($data[0] ?? []) : $data;
|
||||
if (is_array($row)) {
|
||||
return array_merge($response, $row);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function hasSyncColumns(): bool
|
||||
{
|
||||
return Schema::hasTable('zarin') && Schema::hasColumn('zarin', 'sync_status');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user