From e26372f483c0029f8bec900039bc63f4de5b72c0 Mon Sep 17 00:00:00 2001 From: Ivan Moroz Date: Tue, 2 Jun 2026 15:36:12 +0300 Subject: [PATCH] CONV-232: Capture credits on successful conversion Co-Authored-By: Claude Sonnet 4.6 --- app/Jobs/ProcessConversionJob.php | 47 +++++++++++++++++++ .../Jobs/ProcessConversionJobCreditTest.php | 1 + .../Feature/Jobs/ProcessConversionJobTest.php | 4 ++ 3 files changed, 52 insertions(+) diff --git a/app/Jobs/ProcessConversionJob.php b/app/Jobs/ProcessConversionJob.php index ca999df..8912cd7 100644 --- a/app/Jobs/ProcessConversionJob.php +++ b/app/Jobs/ProcessConversionJob.php @@ -5,6 +5,8 @@ namespace App\Jobs; use App\Actions\Conversions\RecordConversionResultFileAction; +use App\Contracts\Billing\CreditLedger; +use App\Enums\ConversionCreditChargeStatus; use App\Enums\ConversionStatus; use App\Models\ConversionJob; use App\Support\Conversions\ConverterDriverRegistry; @@ -14,6 +16,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Throwable; @@ -31,6 +34,7 @@ public function __construct( public function handle( ConverterDriverRegistry $drivers, RecordConversionResultFileAction $recorder, + CreditLedger $creditLedger, ): void { $job = ConversionJob::find($this->conversionJobId); @@ -64,6 +68,8 @@ public function handle( 'progress' => 100, 'completed_at' => now(), ])->save(); + + $this->captureCredits($job, $creditLedger); } catch (Throwable $exception) { $job->forceFill([ 'status' => ConversionStatus::Failed, @@ -72,7 +78,48 @@ public function handle( 'completed_at' => now(), ])->save(); + $this->markChargeFailed($job); + report($exception); } } + + private function captureCredits(ConversionJob $job, CreditLedger $creditLedger): void + { + $charge = $job->creditCharge; + + if ($charge === null) { + return; + } + + DB::transaction(function () use ($job, $charge, $creditLedger) { + $creditLedger->spend( + user: $job->user, + amount: $charge->estimated_amount, + reason: 'conversion_completed', + meta: [ + 'conversion_job_id' => $job->id, + 'converter_key' => $job->converter_key, + ], + ); + + $charge->forceFill([ + 'captured_amount' => $charge->estimated_amount, + 'status' => ConversionCreditChargeStatus::Captured, + ])->save(); + }); + } + + private function markChargeFailed(ConversionJob $job): void + { + $charge = $job->creditCharge; + + if ($charge === null) { + return; + } + + $charge->forceFill([ + 'status' => ConversionCreditChargeStatus::Failed, + ])->save(); + } } diff --git a/tests/Feature/Jobs/ProcessConversionJobCreditTest.php b/tests/Feature/Jobs/ProcessConversionJobCreditTest.php index 4201c51..94dd5fc 100644 --- a/tests/Feature/Jobs/ProcessConversionJobCreditTest.php +++ b/tests/Feature/Jobs/ProcessConversionJobCreditTest.php @@ -42,6 +42,7 @@ (new ProcessConversionJob($job->id))->handle( app(ConverterDriverRegistry::class), app(RecordConversionResultFileAction::class), + app(CreditLedger::class), ); expect(app(CreditLedger::class)->balance($user))->toBe(9); diff --git a/tests/Feature/Jobs/ProcessConversionJobTest.php b/tests/Feature/Jobs/ProcessConversionJobTest.php index 001ed6e..1dad88f 100644 --- a/tests/Feature/Jobs/ProcessConversionJobTest.php +++ b/tests/Feature/Jobs/ProcessConversionJobTest.php @@ -3,6 +3,7 @@ declare(strict_types=1); use App\Actions\Conversions\RecordConversionResultFileAction; +use App\Contracts\Billing\CreditLedger; use App\Enums\ConversionStatus; use App\Jobs\ProcessConversionJob; use App\Models\ConversionJob; @@ -43,6 +44,7 @@ (new ProcessConversionJob($conversionJob->id))->handle( app(ConverterDriverRegistry::class), app(RecordConversionResultFileAction::class), + app(CreditLedger::class), ); $fresh = $conversionJob->fresh(); @@ -85,6 +87,7 @@ (new ProcessConversionJob($conversionJob->id))->handle( app(ConverterDriverRegistry::class), app(RecordConversionResultFileAction::class), + app(CreditLedger::class), ); $fresh = $conversionJob->fresh(); @@ -118,6 +121,7 @@ (new ProcessConversionJob($conversionJob->id))->handle( app(ConverterDriverRegistry::class), app(RecordConversionResultFileAction::class), + app(CreditLedger::class), ); expect($conversionJob->fresh()->status)->toBe(ConversionStatus::Completed);