check status | sync status

This commit is contained in:
2026-07-04 15:52:01 +03:30
parent 1f8bd224a3
commit 8002679ca2
13 changed files with 255 additions and 50 deletions

View File

@@ -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',
};
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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'];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Services\Payments\Commands;
use App\Models\FibTransaction;
use App\Services\Payments\SyncFibPaymentStatusService;
use Illuminate\Console\Command;
class SyncFibPaymentStatuses extends Command
{
protected $signature = 'payments:sync-fib-statuses';
protected $description = 'Poll FIB for status updates on all pending payments';
public function handle(SyncFibPaymentStatusService $service)
{
$this->info('Syncing FIB payment statuses...');
$service->run();
$this->info('Done.');
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Services\Payments\Controllers;
use App\Http\Controllers\Controller;
use App\Services\FIB\CheckPaymentStatusService;
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 FIBTempController extends Controller
{
use ApiResponse;
public $check_status_service;
public function __construct(CheckPaymentStatusService $check_status_service)
{
$this->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;
}
}
}

View File

@@ -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 [

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Services\Payments;
use App\Enums\PaymentStatusEnum;
use App\Models\FibTransaction;
use App\Models\Payment;
use App\Models\Transaction;
use App\Services\FIB\CheckPaymentStatusService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class SyncFibPaymentStatusService
{
public function __construct(
protected CheckPaymentStatusService $checkPaymentStatusService
)
{
}
public function run(): void
{
$fib_transactions = FibTransaction::query()
->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,
};
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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(),],
]);
}
}

View File

@@ -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']);
});
});

View File

@@ -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();