From 1f8bd224a33289b6897ad6b8d469cc72d51310a0 Mon Sep 17 00:00:00 2001 From: hasanzaki Date: Sun, 7 Jun 2026 21:36:06 +0330 Subject: [PATCH 1/3] stage --- backend/app/Enums/PaymentStatusEnum.php | 5 +- backend/app/Models/User.php | 2 +- .../Services/FIB/DTO/FIBPaymentCreateDTO.php | 14 +-- backend/app/Services/FIB/FIBClient.php | 86 ------------- .../Payments/DTO/PaymentCreateDTO.php | 3 - .../app/Services/Payments/PaymentService.php | 115 ++++++++++++++++-- .../Controllers/Payment/PaymentController.php | 19 +++ .../Requests/Payment/FIB/InquiryRequest.php | 30 +++++ .../Requests/Payment/FIB/StoreRequest.php | 8 +- .../Resources/Payment/InquiryResource.php | 30 +++++ ...5_142646_add_columns_to_payments_table.php | 34 ++++++ backend/routes/api.php | 2 + 12 files changed, 237 insertions(+), 111 deletions(-) delete mode 100755 backend/app/Services/FIB/FIBClient.php create mode 100644 backend/app/User/Requests/Payment/FIB/InquiryRequest.php create mode 100755 backend/app/User/Resources/Payment/InquiryResource.php create mode 100755 backend/database/migrations/2026_06_05_142646_add_columns_to_payments_table.php diff --git a/backend/app/Enums/PaymentStatusEnum.php b/backend/app/Enums/PaymentStatusEnum.php index 78be8b94..24e0e9a6 100644 --- a/backend/app/Enums/PaymentStatusEnum.php +++ b/backend/app/Enums/PaymentStatusEnum.php @@ -2,7 +2,7 @@ namespace App\Enums; -enum PaymentStatusEnum:int +enum PaymentStatusEnum: int { case Unpaid = 0; case Cancelled = 1; @@ -10,6 +10,8 @@ enum PaymentStatusEnum:int case Paid = 3; case Refunded = 4; + case PendingVerification = 5; + public function label(): string { return match ($this) { @@ -18,6 +20,7 @@ enum PaymentStatusEnum:int self::Expired => 'expired', self::Paid => 'paid', self::Refunded => 'refunded', + self::PendingVerification => 'pending_verification', }; } } diff --git a/backend/app/Models/User.php b/backend/app/Models/User.php index 20e0ad4a..fc04ec71 100755 --- a/backend/app/Models/User.php +++ b/backend/app/Models/User.php @@ -24,7 +24,7 @@ class User extends Authenticatable public function documents(): MorphToMany { - return $this->morphToMany(Document::class, 'documentable'); + return $this->morphToMany(Document::class, 'documents'); } public function validateForPassportPasswordGrant(string $password): bool diff --git a/backend/app/Services/FIB/DTO/FIBPaymentCreateDTO.php b/backend/app/Services/FIB/DTO/FIBPaymentCreateDTO.php index 13ab44c2..a119f71d 100644 --- a/backend/app/Services/FIB/DTO/FIBPaymentCreateDTO.php +++ b/backend/app/Services/FIB/DTO/FIBPaymentCreateDTO.php @@ -26,13 +26,13 @@ class FIBPaymentCreateDTO public static function fromResponse($data): FIBPaymentCreateDTO { return new self([ - 'paymentId' => $data->paymentId, - 'readableCode' => $data->readableCode, - 'qrCode' => $data->qrCode, - 'validUntil' => $data->validUntil, - 'personalAppLink' => $data->personalAppLink, - 'businessAppLink' => $data->businessAppLink, - 'corporateAppLink' => $data->corporateAppLink, + 'paymentId' => $data['paymentId'], + 'readableCode' => $data['readableCode'], + 'qrCode' => $data['qrCode'], + 'validUntil' => $data['validUntil'], + 'personalAppLink' => $data['personalAppLink'], + 'businessAppLink' => $data['businessAppLink'], + 'corporateAppLink' => $data['corporateAppLink'], ]); } } diff --git a/backend/app/Services/FIB/FIBClient.php b/backend/app/Services/FIB/FIBClient.php deleted file mode 100755 index be22b64c..00000000 --- a/backend/app/Services/FIB/FIBClient.php +++ /dev/null @@ -1,86 +0,0 @@ -contentType('application/json') - ->withToken($this->getAccessToken()) - ->timeout(30); - } - - public function getAccessToken() - { - try { - if ($this->isNotProduction()) { - return json_decode( - file_get_contents(storage_path('app/mock/fib/token.json')) - ); - } - $response = Http::withOptions([ - 'proxy' => 'http://host.docker.internal:10808', - ])->asForm()->post('https://fib-stage.fib.iq/auth/realms/fib-online-shop/protocol/openid-connect/token', [ - 'grant_type' => 'client_credentials', - 'client_id' => 'ipay-test-payment', - 'client_secret' => '8dae3180-f39c-448e-8b45-a01c8f4c8c15', - ]); - - if ($response->failed()) { - throw new \Exception( - 'Unable to authenticate with FIB: ' . - $response->body() - ); - } - - $data = $response->json(); - - if (!isset($data['access_token'])) { - throw new \Exception('FIB token missing in response'); - } - - return $data['access_token']; - } catch (\Exception $exception) { - return $exception->getMessage(); - } - } - - public function createPaymentService() - { - if ($this->isNotProduction()) { - return json_decode( - file_get_contents(storage_path('app/mock/fib/payment_create_success.json')) - ); - } - $response = $this->client()->post("https://fib-stage.fib.iq/protected/v1/payments"); - if ($response->failed()) { - throw new \Exception( - 'Unable to authenticate with FIB: ' . - $response->body() - ); - } - - $data = $response->json(); - if (!isset($data['paymentId'])) { - throw new \Exception('FIB paymentId missing in response'); - } - return $data; - } - - public function isNotProduction() - { - return env('APP_ENV') === 'local'; - } -} diff --git a/backend/app/Services/Payments/DTO/PaymentCreateDTO.php b/backend/app/Services/Payments/DTO/PaymentCreateDTO.php index a3129a3a..6c986e19 100644 --- a/backend/app/Services/Payments/DTO/PaymentCreateDTO.php +++ b/backend/app/Services/Payments/DTO/PaymentCreateDTO.php @@ -9,7 +9,6 @@ class PaymentCreateDTO public string $username; public int $user_id; - public string $track_id; public string $amount; public string $currency; public string $callback_url; @@ -21,7 +20,6 @@ class PaymentCreateDTO { $this->username = $data['username']; $this->user_id = $data['user_id']; - $this->track_id = $data['track_id']; $this->amount = $data['amount']; $this->currency = $data['currency']; $this->callback_url = $data['callback_url']; @@ -35,7 +33,6 @@ class PaymentCreateDTO return new self([ 'username' => $request->username, 'user_id' => '2', - 'track_id' => uuid_create(), 'amount' => $request->amount, 'currency' => $request->currency, 'callback_url' => $request->callback_url, diff --git a/backend/app/Services/Payments/PaymentService.php b/backend/app/Services/Payments/PaymentService.php index ff4d0a21..8116ab6c 100644 --- a/backend/app/Services/Payments/PaymentService.php +++ b/backend/app/Services/Payments/PaymentService.php @@ -6,6 +6,8 @@ use App\Enums\PaymentStatusEnum; use App\Models\FibTransaction; use App\Models\Payment; use App\Models\Transaction; +use App\Models\UserGateway; +use App\Services\FIB\CheckPaymentStatusService; use App\Services\FIB\CreatePaymentService; use App\Services\FIB\DTO\FIBPaymentCreateDTO; use App\Services\Payments\DTO\PaymentCreateDTO; @@ -16,10 +18,16 @@ use Throwable; class PaymentService { protected CreatePaymentService $create_payment_service; + protected CheckPaymentStatusService $check_payment_status_service; - public function __construct(CreatePaymentService $create_payment_service) + + public function __construct( + CreatePaymentService $create_payment_service, + CheckPaymentStatusService $check_payment_status_service + ) { $this->create_payment_service = $create_payment_service; + $this->check_payment_status_service = $check_payment_status_service; } /** @@ -28,19 +36,33 @@ class PaymentService */ public function createPayment(PaymentCreateDTO $dto) { - return DB::transaction(function () use ($dto) { + $gateway = UserGateway::query() + ->where('id', $dto->user_gateway_id) + ->where('user_id', $dto->user_id) + ->firstOrFail(); + + return DB::transaction(function () use ($dto, $gateway) { $payment = Payment::query()->create([ 'user_id' => $dto->user_id, 'username' => $dto->username, - 'track_id' => $dto->track_id, + 'track_id' => $this->generateTrackId(), 'amount' => $dto->amount, 'currency' => $dto->currency, - 'callback_url' => $dto->callback_url, + 'callback_url' => 'https://user_callback', 'status_id' => PaymentStatusEnum::Unpaid->value, 'status_name' => PaymentStatusEnum::Unpaid->name, 'ip' => $dto->ip, - 'user_gateway_id' => $dto->user_gateway_id, + 'user_gateway_id' => $gateway->id, + ]); + + $this->create_payment_service->setInputParameters([ + 'monetaryValue' => [ + 'amount' => $dto->amount, + 'currency' => $dto->currency, + ], + 'statusCallbackUrl' => 'https://ipay/api/fib_callback', + 'description' => $dto->description ?? "Payment #{$payment->track_id}", ]); $response = $this->create_payment_service->run(); @@ -51,12 +73,12 @@ class PaymentService 'amount' => $payment->amount, 'currency' => $payment->currency, 'callback_url' => $payment->callback_url, - // 'description' => '', - // 'expires_in' => '', + // 'description' => '', + // 'expires_in' => '', 'payment_id' => $payment->id, 'payment_track_id' => $payment->track_id, 'user_gateway_id' => $payment->user_gateway_id, - // 'status' => '', + // 'status' => '', ]); FibTransaction::query()->create([ @@ -73,7 +95,82 @@ class PaymentService 'status_name' => PaymentStatusEnum::Unpaid->name, ]); - return $payment; + return [ + 'payment_id' => $payment->id, + 'track_id' => $payment->track_id, + 'qr_code' => $fib_response_dto->qrCode, + 'readable_code' => $fib_response_dto->readableCode, + 'personal_app_link' => $fib_response_dto->personalAppLink, + 'business_app_link' => $fib_response_dto->businessAppLink, + 'corporate_app_link' => $fib_response_dto->corporateAppLink, + 'valid_until' => $fib_response_dto->validUntil, + 'status' => PaymentStatusEnum::Unpaid->name, + ]; }); } + + private function generateTrackId(): string + { + do { + $id = uuid_create(); + } while (Payment::query()->where('track_id', $id)->exists()); + + return $id; + } + + public function checkPaymentStatus($track_id) + { + $user_id = auth()->id(); + return Payment::query()->where('track_id', $track_id) + ->where('user_id', 2) + ->firstOrFail(); + } + + public function verifyPayment($track_id) + { + $user_id = auth()->id(); + return DB::transaction(function () use ($track_id, $user_id) { + + $payment = Payment::query() + ->where('track_id', $track_id) + ->where('user_id', $user_id) + ->lockForUpdate() + ->firstOrFail(); + + if ($payment->status_id === PaymentStatusEnum::Paid->value) { + return [ + 'status' => 'already_verified', + 'paid' => true, + ]; + } + + if ($payment->status_id !== PaymentStatusEnum::PendingVerification->value) { + return [ + 'status' => $payment->status_name, + 'paid' => false, + ]; + } + + $payment->update([ + 'status_id' => PaymentStatusEnum::Paid->value, + 'status_name' => PaymentStatusEnum::Paid->name, + ]); + + Transaction::query() + ->where('payment_id', $payment->id) + ->update([ + 'status_id' => PaymentStatusEnum::Paid->value, + 'status_name' => PaymentStatusEnum::Paid->name, + ]); + + return [ + 'status' => 'verified', + 'paid' => true, + 'track_id' => $payment->track_id, + 'amount' => $payment->amount, + 'currency' => $payment->currency, + ]; + }); + + } } diff --git a/backend/app/User/Controllers/Payment/PaymentController.php b/backend/app/User/Controllers/Payment/PaymentController.php index 69e7cd97..6611ec51 100644 --- a/backend/app/User/Controllers/Payment/PaymentController.php +++ b/backend/app/User/Controllers/Payment/PaymentController.php @@ -6,9 +6,12 @@ use App\Http\Controllers\Controller; use App\Services\Payments\DTO\PaymentCreateDTO; use App\Services\Payments\PaymentService; use App\Traits\ApiResponse; +use App\User\Requests\Payment\FIB\InquiryRequest; use App\User\Requests\Payment\FIB\StoreRequest; +use App\User\Resources\Payment\InquiryResource; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Js; class PaymentController extends Controller @@ -32,4 +35,20 @@ class PaymentController extends Controller throw $exception; } } + + public function checkPaymentStatus(InquiryRequest $request): JsonResponse + { + try { + $result = $this->payment_service->checkPaymentStatus($request->track_id); + return $this->successResponse(new InquiryResource($result)); + } catch (\Exception $exception) { + throw $exception; + } + } + + public function verify() + { + + } + } diff --git a/backend/app/User/Requests/Payment/FIB/InquiryRequest.php b/backend/app/User/Requests/Payment/FIB/InquiryRequest.php new file mode 100644 index 00000000..c05ac90c --- /dev/null +++ b/backend/app/User/Requests/Payment/FIB/InquiryRequest.php @@ -0,0 +1,30 @@ + + */ + public function rules(): array + { + return [ + 'track_id' => ['required', 'uuid'], + ]; + } +} diff --git a/backend/app/User/Requests/Payment/FIB/StoreRequest.php b/backend/app/User/Requests/Payment/FIB/StoreRequest.php index 7ef61312..84c83c86 100644 --- a/backend/app/User/Requests/Payment/FIB/StoreRequest.php +++ b/backend/app/User/Requests/Payment/FIB/StoreRequest.php @@ -25,12 +25,12 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - 'username' => ['required'], - 'track_id' => ['required'], + 'username' => ['required', 'string'], 'amount' => ['required'], - 'currency' => ['required'], + 'currency' => ['required', 'string'], 'callback_url' => ['required'], - 'user_gateway_id' => ['required'], + 'user_gateway_id' => ['required', 'integer', 'exists:user_gateways,id'], + 'description' => ['nullable', 'string', 'max:500'], ]; } } diff --git a/backend/app/User/Resources/Payment/InquiryResource.php b/backend/app/User/Resources/Payment/InquiryResource.php new file mode 100755 index 00000000..05e4036c --- /dev/null +++ b/backend/app/User/Resources/Payment/InquiryResource.php @@ -0,0 +1,30 @@ + + */ + public function toArray(Request $request): array + { + return [ + "track_id" => $this->track_id, + "amount" => $this->amount, + "currency" => $this->currency, + "status_id" => $this->status_id, + "status_name" => $this->status_name, + 'payment_method' => $this->payment_method, + 'paid_currency' => $this->paid_currency, + 'paid_amount' => $this->paid_amount, + 'paid_at' => $this->paid_at, + 'verified_at' => $this->verified_at, + ]; + } +} diff --git a/backend/database/migrations/2026_06_05_142646_add_columns_to_payments_table.php b/backend/database/migrations/2026_06_05_142646_add_columns_to_payments_table.php new file mode 100755 index 00000000..0a427575 --- /dev/null +++ b/backend/database/migrations/2026_06_05_142646_add_columns_to_payments_table.php @@ -0,0 +1,34 @@ +string('payment_method')->nullable(); + $table->string('paid_currency')->nullable(); + $table->string('paid_amount')->nullable(); + $table->timestamp('paid_at')->nullable(); + $table->timestamp('verified_at')->nullable(); + $table->timestamp('refunded_at')->nullable(); + $table->string('refund_reason')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('payments', function (Blueprint $table) { + $table->dropColumn(['payment_method', 'paid_currency', 'paid_amount', + 'paid_at', 'verified_at', 'refunded_at', 'refund_reason',]); + }); + } +}; diff --git a/backend/routes/api.php b/backend/routes/api.php index 5eaa6efa..594f6249 100755 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -14,6 +14,8 @@ Route::prefix('v1')->group(function () { // ->middleware('auth:api') ->group(function () { Route::post('/create_payment', [PaymentController::class, 'createPayment'])->name('create_payment'); + Route::post('/inquiry', [PaymentController::class, 'checkPaymentStatus'])->name('inquiry'); + Route::post('/verify', [PaymentController::class, 'checkPaymentStatus'])->name('inquiry'); }); }); -- 2.49.1 From 8002679ca23f5557bf4c1ff1d72321a388fff0f1 Mon Sep 17 00:00:00 2001 From: hasanzaki Date: Sat, 4 Jul 2026 15:52:01 +0330 Subject: [PATCH 2/3] check status | sync status --- backend/app/Enums/PaymentStatusEnum.php | 21 ++-- backend/app/Models/FibTransaction.php | 11 ++ backend/app/Models/Transaction.php | 6 + .../FIB/CheckPaymentStatusService.php | 27 ++--- .../Commands/SyncFibPaymentStatuses.php | 20 ++++ .../Controllers/FIBTempController.php | 40 +++++++ .../app/Services/Payments/PaymentService.php | 24 ++-- .../Payments/SyncFibPaymentStatusService.php | 113 ++++++++++++++++++ .../Controllers/Payment/PaymentController.php | 2 +- backend/bootstrap/app.php | 12 +- .../database/seeders/PaymentStatusSeeder.php | 14 ++- backend/routes/api.php | 9 +- backend/routes/console.php | 6 + 13 files changed, 255 insertions(+), 50 deletions(-) create mode 100644 backend/app/Services/Payments/Commands/SyncFibPaymentStatuses.php create mode 100644 backend/app/Services/Payments/Controllers/FIBTempController.php create mode 100644 backend/app/Services/Payments/SyncFibPaymentStatusService.php diff --git a/backend/app/Enums/PaymentStatusEnum.php b/backend/app/Enums/PaymentStatusEnum.php index 24e0e9a6..551fdee3 100644 --- a/backend/app/Enums/PaymentStatusEnum.php +++ b/backend/app/Enums/PaymentStatusEnum.php @@ -4,23 +4,26 @@ namespace App\Enums; enum PaymentStatusEnum: int { - case Unpaid = 0; + case UNPAID = 0; case Cancelled = 1; case Expired = 2; - case Paid = 3; - case Refunded = 4; + case PAID = 3; + case REFUNDED = 4; case PendingVerification = 5; + case REFUND_REQUESTED = 6; + public function label(): string { return match ($this) { - self::Unpaid => 'unpaid', - self::Cancelled => 'cancelled', - self::Expired => 'expired', - self::Paid => 'paid', - self::Refunded => 'refunded', - self::PendingVerification => 'pending_verification', + self::UNPAID => 'UNPAID', + self::Cancelled => 'CANCELLED', + self::Expired => 'EXPIRED', + self::PAID => 'PAID', + self::REFUNDED => 'REFUNDED', + self::REFUND_REQUESTED => 'REFUND_REQUESTED', + self::PendingVerification => 'PENDING_VERIFICATION', }; } } diff --git a/backend/app/Models/FibTransaction.php b/backend/app/Models/FibTransaction.php index c208c0a7..897e527b 100644 --- a/backend/app/Models/FibTransaction.php +++ b/backend/app/Models/FibTransaction.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class FibTransaction extends Model { @@ -11,4 +12,14 @@ class FibTransaction extends Model use HasFactory; protected $guarded = ['id']; + + public function transaction(): BelongsTo + { + return $this->belongsTo(Transaction::class); + } + + public function payment(): BelongsTo + { + return $this->belongsTo(Payment::class); + } } diff --git a/backend/app/Models/Transaction.php b/backend/app/Models/Transaction.php index a685ed3d..3c909303 100644 --- a/backend/app/Models/Transaction.php +++ b/backend/app/Models/Transaction.php @@ -5,6 +5,7 @@ namespace App\Models; use Database\Factories\TransactionFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class Transaction extends Model { @@ -12,4 +13,9 @@ class Transaction extends Model use HasFactory; protected $guarded = ['id']; + + public function payment(): BelongsTo + { + return $this->belongsTo(Payment::class); + } } diff --git a/backend/app/Services/FIB/CheckPaymentStatusService.php b/backend/app/Services/FIB/CheckPaymentStatusService.php index 58218c1e..940a3520 100755 --- a/backend/app/Services/FIB/CheckPaymentStatusService.php +++ b/backend/app/Services/FIB/CheckPaymentStatusService.php @@ -11,31 +11,29 @@ class CheckPaymentStatusService { protected string $url; protected string $channelName; - protected AuthorizationService $authorizationService; + protected AuthorizationService $authorization_service; - public function __construct(AuthorizationService $authorizationService) + public function __construct(AuthorizationService $authorization_service) { - $this->url = config('fib.checkPaymentStatus.url'); + $this->url = 'https://fib-stage.fib.iq/protected/v1/payments/'; $this->channelName = 'fib_status'; - $this->authorizationService = $authorizationService; + $this->authorization_service = $authorization_service; } /** * @throws Exception */ - public function run(): array + public function run(string $fib_payment_id): array { try { - return $this->sendRequest(); - } - catch (Exception $e) { + return $this->sendRequest($fib_payment_id); + } catch (Exception $e) { Log::channel($this->channelName) ->error(get_class($this), [ 'message' => $e->getMessage(), ] ); - throw $e; } } @@ -44,15 +42,16 @@ class CheckPaymentStatusService * @throws ConnectionException * @throws Exception */ - public function sendRequest(): array + public function sendRequest(string $fib_payment_id): array { + $url = rtrim($this->url, '/') . "/{$fib_payment_id}/status"; return Http::withHeaders([ 'Authorization' => 'Bearer ' . $this->getToken(), - 'Content-Type' => 'application/json', - ]) + 'Accept' => 'application/json', + ]) ->throw() ->withoutVerifying() - ->post($this->url) + ->get($url) ->json(); } @@ -61,7 +60,7 @@ class CheckPaymentStatusService */ public function getToken(): string { - $data = $this->authorizationService->run(); + $data = $this->authorization_service->run(); return $data['access_token']; } } diff --git a/backend/app/Services/Payments/Commands/SyncFibPaymentStatuses.php b/backend/app/Services/Payments/Commands/SyncFibPaymentStatuses.php new file mode 100644 index 00000000..4db10d38 --- /dev/null +++ b/backend/app/Services/Payments/Commands/SyncFibPaymentStatuses.php @@ -0,0 +1,20 @@ +info('Syncing FIB payment statuses...'); + $service->run(); + $this->info('Done.'); + } +} diff --git a/backend/app/Services/Payments/Controllers/FIBTempController.php b/backend/app/Services/Payments/Controllers/FIBTempController.php new file mode 100644 index 00000000..5a5ce421 --- /dev/null +++ b/backend/app/Services/Payments/Controllers/FIBTempController.php @@ -0,0 +1,40 @@ +check_status_service = $check_status_service; + } + + public function checkStatus(Request $request): JsonResponse + { + try { + $result = $this->check_status_service->run('8a6e8f4d-b9fb-44f4-b086-12450939bf5c'); + return $this->successResponse($result); + } catch (\Exception $exception) { + throw $exception; + } + } + + +} diff --git a/backend/app/Services/Payments/PaymentService.php b/backend/app/Services/Payments/PaymentService.php index 8116ab6c..6b4defb4 100644 --- a/backend/app/Services/Payments/PaymentService.php +++ b/backend/app/Services/Payments/PaymentService.php @@ -50,8 +50,8 @@ class PaymentService 'amount' => $dto->amount, 'currency' => $dto->currency, 'callback_url' => 'https://user_callback', - 'status_id' => PaymentStatusEnum::Unpaid->value, - 'status_name' => PaymentStatusEnum::Unpaid->name, + 'status_id' => PaymentStatusEnum::UNPAID->value, + 'status_name' => PaymentStatusEnum::UNPAID->name, 'ip' => $dto->ip, 'user_gateway_id' => $gateway->id, ]); @@ -91,8 +91,8 @@ class PaymentService 'paymentId' => $fib_response_dto->paymentId, 'paymentMethod' => 'fib', 'validUntil' => $fib_response_dto->validUntil, - 'status_id' => PaymentStatusEnum::Unpaid->value, - 'status_name' => PaymentStatusEnum::Unpaid->name, + 'status_id' => PaymentStatusEnum::UNPAID->value, + 'status_name' => PaymentStatusEnum::UNPAID->name, ]); return [ @@ -104,7 +104,7 @@ class PaymentService 'business_app_link' => $fib_response_dto->businessAppLink, 'corporate_app_link' => $fib_response_dto->corporateAppLink, 'valid_until' => $fib_response_dto->validUntil, - 'status' => PaymentStatusEnum::Unpaid->name, + 'status' => PaymentStatusEnum::UNPAID->name, ]; }); } @@ -120,9 +120,9 @@ class PaymentService public function checkPaymentStatus($track_id) { - $user_id = auth()->id(); + $user_id = auth()->id() ?? 2; return Payment::query()->where('track_id', $track_id) - ->where('user_id', 2) + ->where('user_id', $user_id) ->firstOrFail(); } @@ -137,7 +137,7 @@ class PaymentService ->lockForUpdate() ->firstOrFail(); - if ($payment->status_id === PaymentStatusEnum::Paid->value) { + if ($payment->status_id == PaymentStatusEnum::PAID->value) { return [ 'status' => 'already_verified', 'paid' => true, @@ -152,15 +152,15 @@ class PaymentService } $payment->update([ - 'status_id' => PaymentStatusEnum::Paid->value, - 'status_name' => PaymentStatusEnum::Paid->name, + 'status_id' => PaymentStatusEnum::PAID->value, + 'status_name' => PaymentStatusEnum::PAID->name, ]); Transaction::query() ->where('payment_id', $payment->id) ->update([ - 'status_id' => PaymentStatusEnum::Paid->value, - 'status_name' => PaymentStatusEnum::Paid->name, + 'status_id' => PaymentStatusEnum::PAID->value, + 'status_name' => PaymentStatusEnum::PAID->name, ]); return [ diff --git a/backend/app/Services/Payments/SyncFibPaymentStatusService.php b/backend/app/Services/Payments/SyncFibPaymentStatusService.php new file mode 100644 index 00000000..e689ac1d --- /dev/null +++ b/backend/app/Services/Payments/SyncFibPaymentStatusService.php @@ -0,0 +1,113 @@ +whereIn('status_id', [ + PaymentStatusEnum::UNPAID->value, + ]) + ->with('transaction.payment') + ->get(); + + foreach ($fib_transactions as $fib_transaction) { + try { + $this->syncOne($fib_transaction); + } catch (\Exception $e) { + Log::channel('fib_status')->error('Failed to sync fib transaction', [ + 'fib_transaction_id' => $fib_transaction->id, + 'fib_payment_id' => $fib_transaction->paymentId, + 'error' => $e->getMessage(), + ]); + } + } + } + + public function syncOne(FibTransaction $fib_transaction) + { + + $fib_status_response = $this->checkPaymentStatusService->run($fib_transaction->paymentId); + if (!$fib_status_response['status']) { + Log::error('No status feild in fib ', []); + return false; + } + $mapped_status = $this->mapFibStatus($fib_status_response['status']); + +// if ($mapped_status->value == $fib_transaction->status) { +// return; +// } + + return DB::transaction(function () use ($fib_transaction, $fib_status_response, $mapped_status) { + $transaction = $fib_transaction->transaction; + $payment = $transaction->payment; + $fib_transaction->update([ + 'status_id' => $mapped_status->value, + 'status_name' => $fib_transaction->status, + 'paidAt' => $fib_status_response['paidAt'] ?? null, + 'paidAmount' => $fib_status_response['amount']['amount'] ?? null, + 'paidCurrency' => $fib_status_response['amount']['currency'] ?? null, + 'decliningReason' => $fib_status_response['decliningReason'] ?? null, + 'declinedAt' => $fib_status_response['declinedAt'] ?? null, + 'paidByName' => $fib_status_response['paidBy']['name'] ?? null, + 'paidByIban' => $fib_status_response['paidBy']['iban'] ?? null, + ]); + + $transaction->update([ + 'status_id' => $mapped_status->value, + 'status_name' => $mapped_status->label(), + ]); + + if ($mapped_status === PaymentStatusEnum::PAID) { + $payment->update([ + 'status_id' => PaymentStatusEnum::PendingVerification->value, + 'status_name' => PaymentStatusEnum::PendingVerification->label(), + 'payment_method' => 'FIB', + 'paid_currency' => $fib_transaction->paidCurrency, + 'paid_amount' => $fib_transaction->paidAmount, + 'paid_at' => $fib_transaction->paidAt, + ]); + #------ has to implement ------ +// $this->notifyMerchant($payment); + } else { + $payment->update([ + 'status_id' => $mapped_status->value, + 'status_name' => $mapped_status->label(), + 'paid_currency' => $fib_transaction->paidCurrency, + 'paid_amount' => $fib_transaction->paidAmount, + 'paid_at' => $fib_transaction->paidAt, + ]); + } + return $payment; + }); + } + + protected function mapFibStatus(string $fib_status_response): PaymentStatusEnum + { + return match (strtoupper($fib_status_response)) { + 'PAID' => PaymentStatusEnum::PAID, + 'UNPAID' => PaymentStatusEnum::UNPAID, + 'DECLINED' => PaymentStatusEnum::Cancelled, + 'EXPIRED' => PaymentStatusEnum::Expired, + 'REFUNDED' => PaymentStatusEnum::REFUNDED, + 'REFUND_REQUESTED' => PaymentStatusEnum::REFUND_REQUESTED, + default => PaymentStatusEnum::UNPAID, + }; + } +} diff --git a/backend/app/User/Controllers/Payment/PaymentController.php b/backend/app/User/Controllers/Payment/PaymentController.php index 6611ec51..94193580 100644 --- a/backend/app/User/Controllers/Payment/PaymentController.php +++ b/backend/app/User/Controllers/Payment/PaymentController.php @@ -36,7 +36,7 @@ class PaymentController extends Controller } } - public function checkPaymentStatus(InquiryRequest $request): JsonResponse + public function InquiryTransaction(InquiryRequest $request): JsonResponse { try { $result = $this->payment_service->checkPaymentStatus($request->track_id); diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index 31119ffe..61c12ea1 100755 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -3,12 +3,13 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; +use Illuminate\Support\Facades\Route; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( - web: __DIR__.'/../routes/web.php', - api: __DIR__.'/../routes/api.php', - commands: __DIR__.'/../routes/console.php', + web: __DIR__ . '/../routes/web.php', + api: __DIR__ . '/../routes/api.php', + commands: __DIR__ . '/../routes/console.php', health: '/up', then: function () { Route::middleware('web') @@ -23,6 +24,9 @@ return Application::configure(basePath: dirname(__DIR__)) ->withExceptions(function (Exceptions $exceptions): void { // }) + ->withCommands([ + __DIR__ . '/../app/Services/Payments/Commands', + ]) ->withEvents(discover: [ - __DIR__.'/../app/User/Listeners', + __DIR__ . '/../app/User/Listeners', ])->create(); diff --git a/backend/database/seeders/PaymentStatusSeeder.php b/backend/database/seeders/PaymentStatusSeeder.php index 875ff488..972a9449 100644 --- a/backend/database/seeders/PaymentStatusSeeder.php +++ b/backend/database/seeders/PaymentStatusSeeder.php @@ -6,7 +6,7 @@ use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; -class PaymentStatusSeeder extends Seeder +class PaymentStatusSeeder extends Seeder { /** * Run the database seeds. @@ -14,11 +14,13 @@ class PaymentStatusSeeder extends Seeder public function run(): void { DB::table('payment_statuses')->insert([ - ['id' => 0, 'name' => 'unpaid', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 1, 'name' => 'cancelled', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 2, 'name' => 'expired', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 3, 'name' => 'paid', 'created_at' => now(), 'updated_at' => now(),], - ['id' => 4, 'name' => 'refunded', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 0, 'name' => 'UNPAID', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 1, 'name' => 'CANCELLED', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 2, 'name' => 'EXPIRED', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 3, 'name' => 'PAID', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 4, 'name' => 'REFUNDED', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 5, 'name' => 'PENDING_VERIFICATION', 'created_at' => now(), 'updated_at' => now(),], + ['id' => 6, 'name' => 'REFUND_REQUESTED', 'created_at' => now(), 'updated_at' => now(),], ]); } } diff --git a/backend/routes/api.php b/backend/routes/api.php index 594f6249..ce49735a 100755 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -11,11 +11,12 @@ Route::get('/user', function (Request $request) { Route::prefix('v1')->group(function () { Route::prefix('payment') -// ->middleware('auth:api') ->group(function () { Route::post('/create_payment', [PaymentController::class, 'createPayment'])->name('create_payment'); - Route::post('/inquiry', [PaymentController::class, 'checkPaymentStatus'])->name('inquiry'); - Route::post('/verify', [PaymentController::class, 'checkPaymentStatus'])->name('inquiry'); + Route::post('/inquiry', [PaymentController::class, 'InquiryTransaction'])->name('inquiry'); + Route::post('/verify', [PaymentController::class, 'checkPaymentStatus'])->name('verify'); + Route::get('check_fib_status', [\App\Services\Payments\Controllers\FIBTempController::class, 'checkStatus']); }); - }); + + diff --git a/backend/routes/console.php b/backend/routes/console.php index 3c9adf1a..8216f640 100755 --- a/backend/routes/console.php +++ b/backend/routes/console.php @@ -2,7 +2,13 @@ use Illuminate\Foundation\Inspiring; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Schedule; +use App\Services\Payments\Commands\SyncFibPaymentStatuses; + + Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); + +Schedule::command(SyncFibPaymentStatuses::class)->everyMinute(); -- 2.49.1 From 98b9d5cbcaadd1d6137989b592585c5bad205f4e Mon Sep 17 00:00:00 2001 From: hasanzaki Date: Mon, 6 Jul 2026 14:02:27 +0330 Subject: [PATCH 3/3] add trancactions report --- .../MerchantTransactionController.php | 117 ++++++++++++++++++ backend/routes/expert.php | 37 +++--- backend/routes/web.php | 16 ++- 3 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 backend/app/User/Controllers/Transactions/MerchantTransactionController.php diff --git a/backend/app/User/Controllers/Transactions/MerchantTransactionController.php b/backend/app/User/Controllers/Transactions/MerchantTransactionController.php new file mode 100644 index 00000000..6e529fc9 --- /dev/null +++ b/backend/app/User/Controllers/Transactions/MerchantTransactionController.php @@ -0,0 +1,117 @@ +select([ + 'transactions.id', + 'transactions.amount', + 'transactions.currency', + 'transactions.status_id', + 'transactions.created_at', + + 'payments.id', + 'payments.payment_method', + 'payments.paid_amount', + 'payments.paid_currency', + 'payments.paid_at', + 'payments.status_name as payment_status', + 'payments.status_id as payment_status_id', + 'payments.verified_at', + 'payments.refunded_at', + 'payments.refund_reason', + 'payments.track_id', + + 'user_gateways.name', + ]) + ->join( + 'payments', + 'payments.id', + '=', + 'transactions.payment_id' + ) + ->leftJoin( + 'user_gateways', + 'user_gateways.id', + '=', + 'transactions.user_gateway_id' + ) + ->where('transactions.user_id', $merchantId); + + return DataTableFacade::run( + $query, + $request, + allowedFilters: ['*'], + allowedSortings: ['*'], + allowedSelects: ['payments.id as payment_id', 'payments.track_id', 'payments.status_id as payment_status_id', + 'payments.status_name as payment_status', + 'payment_method', 'paid_amount', 'paid_currency', 'paid_at', 'payment_track_id', + 'transactions.amount', + 'transactions.currency', + 'user_gateways.name as gateway_name', + 'payments.verified_at', + 'payments.refunded_at', + 'payments.refund_reason', + ] + ); + } + + public function stats(): JsonResponse + { + $merchantId = Auth::id(); + + $data = [ + 'total_transactions' => Payment::query()->where( + 'user_id', + $merchantId + )->count(), + + 'successful_transactions' => Payment::query()->where( + 'user_id', + $merchantId + ) + ->where('status_id', PaymentStatusEnum::PAID->value) + ->count(), + + 'failed_transactions' => Payment::query()->where( + 'user_id', + $merchantId + ) + ->whereIn('status_id', [ + PaymentStatusEnum::Cancelled->value, + PaymentStatusEnum::Expired->value + ]) + ->count(), + + 'total_paid_amount' => Payment::query() + ->where('user_id', $merchantId) + ->where( + 'status_id', + PaymentStatusEnum::PendingVerification->value + ) + ->selectRaw('SUM(CAST(paid_amount AS DECIMAL(20,2))) as total') + ->value('total'), + ]; + + return $this->successResponse($data); + } +} diff --git a/backend/routes/expert.php b/backend/routes/expert.php index 79cbd0c2..bffd7cd0 100755 --- a/backend/routes/expert.php +++ b/backend/routes/expert.php @@ -7,6 +7,7 @@ use App\Admin\Controllers\PermissionManagementController; use App\Admin\Controllers\ProfileController; use App\Admin\Controllers\RoleManagementController; use App\Admin\Controllers\UserManagementController; +use Illuminate\Support\Facades\Route; Route::prefix('auth') ->name('auth.') @@ -14,7 +15,7 @@ Route::prefix('auth') ->group(function () { Route::post('login', 'login')->name('login'); Route::post('logout', 'logout')->name('logout'); -}); + }); Route::prefix('documents') ->name('documents.') @@ -33,10 +34,10 @@ Route::prefix('user_management') ->controller(UserManagementController::class) ->group(function () { Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{user}', 'show')->name('show'); - Route::post('/{user}', 'update')->name('update'); - Route::delete('/{user}', 'destroy')->name('destroy'); + Route::post('/store', 'store')->name('store'); + Route::get('{user}', 'show')->name('show'); + Route::post('/update/{user}', 'update')->name('update'); + Route::delete('/delete/{user}', 'destroy')->name('destroy'); }); Route::prefix('roles') @@ -45,10 +46,10 @@ Route::prefix('roles') ->controller(RoleManagementController::class) ->group(function () { Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{role}', 'show')->name('show'); - Route::post('/{role}', 'update')->name('update'); - Route::delete('/{role}', 'destroy')->name('destroy'); + Route::post('/store', 'store')->name('store'); + Route::get('{role}', 'show')->name('show'); + Route::post('/update/{role}', 'update')->name('update'); + Route::delete('/delete/{role}', 'destroy')->name('destroy'); }); Route::prefix('permissions') @@ -57,22 +58,22 @@ Route::prefix('permissions') ->controller(PermissionManagementController::class) ->group(function () { Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{permission}', 'show')->name('show'); - Route::post('/{permission}', 'update')->name('update'); - Route::delete('/{permission}', 'destroy')->name('destroy'); + Route::post('/store', 'store')->name('store'); + Route::get('{permission}', 'show')->name('show'); + Route::post('/update/{permission}', 'update')->name('update'); + Route::delete('/delete/{permission}', 'destroy')->name('destroy'); }); -Route::prefix('expert') +Route::prefix('expert_manage') ->name('expert.') ->middleware(['auth:expert', 'permission:expert-management']) ->controller(ExpertManagementController::class) ->group(function () { Route::get('/', 'index')->name('index'); - Route::post('/', 'store')->name('store'); - Route::get('/{expert}', 'show')->name('show'); - Route::post('/{expert}', 'update')->name('update'); - Route::delete('/{expert}', 'destroy')->name('destroy'); + Route::post('/stores', 'store')->name('store'); + Route::get('{expert}', 'show')->name('show'); + Route::post('/update/{expert}', 'update')->name('update'); + Route::delete('/delete/{expert}', 'destroy')->name('destroy'); }); Route::prefix('profile') diff --git a/backend/routes/web.php b/backend/routes/web.php index 64cea66a..9542fa70 100755 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -1,15 +1,13 @@ name('Auth.') @@ -55,3 +53,13 @@ Route::prefix('city') ->group(function () { Route::get('list', 'list')->name('list'); }); + + +Route::prefix('merchant')->group(function () { + Route::prefix('transactions') + ->middleware(['auth']) + ->group(function () { + Route::get('/list', [MerchantTransactionController::class, 'index'])->name('index'); + Route::get('/stats', [MerchantTransactionController::class, 'stats'])->name('stats'); + }); +}); -- 2.49.1