krgm.so-manager-dev.com/app/Console/Commands/ShjFourBCheckCommand.php
Your Name dc86028777
All checks were successful
Deploy main / deploy (push) Successful in 23s
- SHJ-4A: Wellnet決済PUSH受信処理
- SHJ-4B: 定期契約更新バッチ処理
2025-09-12 19:24:55 +09:00

317 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\SettlementTransaction;
use App\Models\Batch\BatchLog;
use App\Jobs\ProcessSettlementJob;
use App\Services\ShjFourBService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Carbon\Carbon;
/**
* SHJ-4B チェックコマンド
*
* 未処理の決済トランザクションを検索し、ProcessSettlementJobをディスパッチする兜底処理
*
* 実行方法:
* php artisan shj4b:check
* php artisan shj4b:check --dry-run # 実際の処理は行わず、対象のみ表示
* php artisan shj4b:check --limit=50 # 処理件数制限
*
* Cron設定例10分毎実行:
* 0,10,20,30,40,50 * * * * cd /path/to/project && php artisan shj4b:check > /dev/null 2>&1
*/
class ShjFourBCheckCommand extends Command
{
/**
* コマンド名と説明
*
* @var string
*/
protected $signature = 'shj4b:check
{--dry-run : 実際の処理を行わず対象のみ表示}
{--limit=100 : 処理する最大件数}
{--hours=24 : 指定時間以内の決済のみ対象}';
/**
* コマンドの説明
*
* @var string
*/
protected $description = 'SHJ-4B 兜底チェック - 未処理の決済トランザクションを検索してProcessSettlementJobをディスパッチ';
/**
* SHJ-4B サービス
*
* @var ShjFourBService
*/
protected $shjFourBService;
/**
* コンストラクタ
*/
public function __construct(ShjFourBService $shjFourBService)
{
parent::__construct();
$this->shjFourBService = $shjFourBService;
}
/**
* コマンド実行
*
* @return int
*/
public function handle()
{
$startTime = now();
$isDryRun = $this->option('dry-run');
$limit = (int) $this->option('limit');
$hours = (int) $this->option('hours');
$this->info("SHJ-4B チェックコマンド開始");
$this->info("実行モード: " . ($isDryRun ? "ドライラン(実際の処理なし)" : "本実行"));
$this->info("処理制限: {$limit}");
$this->info("対象期間: {$hours}時間以内");
// バッチログ作成
$batch = BatchLog::createBatchLog(
'shj4b_check',
BatchLog::STATUS_START,
[
'command' => 'shj4b:check',
'options' => [
'dry_run' => $isDryRun,
'limit' => $limit,
'hours' => $hours,
],
'start_time' => $startTime,
],
'SHJ-4B チェックコマンド開始'
);
try {
// 未処理の決済トランザクション取得
$unprocessedSettlements = $this->getUnprocessedSettlements($hours, $limit);
$this->info("未処理決済トランザクション: " . $unprocessedSettlements->count() . "");
if ($unprocessedSettlements->isEmpty()) {
$this->info("処理対象なし");
$batch->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => 'SHJ-4B チェック完了 - 処理対象なし',
'success_count' => 0,
]);
return 0;
}
// 対象一覧表示
$this->displayTargets($unprocessedSettlements);
if ($isDryRun) {
$this->info("ドライランモードのため、実際の処理はスキップします");
$batch->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => 'SHJ-4B チェック完了 - ドライラン',
'success_count' => 0,
'parameters' => json_encode(['targets' => $unprocessedSettlements->pluck('settlement_transaction_id')->toArray()]),
]);
return 0;
}
// 実際の処理実行
$processed = $this->processSettlements($unprocessedSettlements);
$this->info("処理完了: {$processed['success']}件成功, {$processed['failed']}件失敗");
$batch->update([
'status' => $processed['failed'] > 0 ? BatchLog::STATUS_ERROR : BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => "SHJ-4B チェック完了 - 成功:{$processed['success']}件, 失敗:{$processed['failed']}",
'success_count' => $processed['success'],
'error_count' => $processed['failed'],
'parameters' => json_encode($processed),
]);
return $processed['failed'] > 0 ? 1 : 0;
} catch (\Throwable $e) {
$this->error("SHJ-4B チェック処理でエラーが発生しました: " . $e->getMessage());
Log::error('SHJ-4B チェックコマンドエラー', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
$batch->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => 'SHJ-4B チェック失敗: ' . $e->getMessage(),
'error_details' => $e->getTraceAsString(),
'error_count' => 1,
]);
return 1;
}
}
/**
* 未処理の決済トランザクション取得
*
* @param int $hours
* @param int $limit
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getUnprocessedSettlements(int $hours, int $limit)
{
$cutoffTime = Carbon::now()->subHours($hours);
// 条件:
// 1. 指定時間以内に作成された
// 2. contract_payment_numberがnullでない
// 3. まだregular_contractのsettlement_transaction_idに関連付けられていない
// 4. ProcessSettlementJobが実行されていないbatch_logで確認
$query = SettlementTransaction::where('created_at', '>=', $cutoffTime)
->whereNotNull('contract_payment_number')
->whereNotNull('pay_date')
->whereNotNull('settlement_amount')
->orderBy('created_at', 'asc');
$settlements = $query->limit($limit)->get();
// 既に処理済みのものを除外
$unprocessed = $settlements->filter(function ($settlement) {
return !$this->isAlreadyProcessed($settlement);
});
return $unprocessed;
}
/**
* 既に処理済みかチェック
*
* @param SettlementTransaction $settlement
* @return bool
*/
private function isAlreadyProcessed(SettlementTransaction $settlement): bool
{
// 1. regular_contractの同一contract_payment_numberが既に処理済みかチェック
$linkedContract = DB::table('regular_contract')
->where('contract_payment_number', $settlement->contract_payment_number)
->whereNotNull('contract_payment_day')
->exists();
if ($linkedContract) {
return true;
}
// 2. batch_logで処理完了記録があるかチェック
$processedInBatch = BatchLog::where('process_name', 'shj4b')
->where('status', BatchLog::STATUS_SUCCESS)
->where('parameters', 'like', '%"settlement_transaction_id":' . $settlement->settlement_transaction_id . '%')
->exists();
if ($processedInBatch) {
return true;
}
// 3. 現在キューに入っているかチェック(簡易版)
// 注: より正確にはRedis/DBキューの内容を確認する必要がある
$recentJobDispatched = BatchLog::where('process_name', 'shj4b')
->where('parameters', 'like', '%"settlement_transaction_id":' . $settlement->settlement_transaction_id . '%')
->where('created_at', '>=', Carbon::now()->subHours(1))
->exists();
return $recentJobDispatched;
}
/**
* 対象一覧表示
*
* @param \Illuminate\Database\Eloquent\Collection $settlements
*/
private function displayTargets($settlements)
{
$this->info("対象の決済トランザクション:");
$this->table(
['ID', '契約支払番号', '決済金額', '支払日', '作成日時'],
$settlements->map(function ($settlement) {
return [
$settlement->settlement_transaction_id,
$settlement->contract_payment_number,
number_format($settlement->settlement_amount) . '円',
Carbon::parse($settlement->pay_date)->format('Y-m-d H:i:s'),
$settlement->created_at->format('Y-m-d H:i:s'),
];
})->toArray()
);
}
/**
* 決済処理実行
*
* @param \Illuminate\Database\Eloquent\Collection $settlements
* @return array
*/
private function processSettlements($settlements): array
{
$success = 0;
$failed = 0;
$results = [];
foreach ($settlements as $settlement) {
try {
$this->info("処理中: 決済トランザクションID {$settlement->settlement_transaction_id}");
// ProcessSettlementJobをディスパッチ
ProcessSettlementJob::dispatch(
$settlement->settlement_transaction_id,
[
'source' => 'shj4b_check_command',
'triggered_at' => now()->toISOString(),
]
);
$success++;
$results[] = [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'status' => 'dispatched',
'message' => 'ProcessSettlementJobディスパッチ成功',
];
$this->info("✓ 成功: {$settlement->settlement_transaction_id}");
} catch (\Throwable $e) {
$failed++;
$results[] = [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'status' => 'failed',
'error' => $e->getMessage(),
];
$this->error("✗ 失敗: {$settlement->settlement_transaction_id} - {$e->getMessage()}");
Log::error('SHJ-4B チェック 個別処理失敗', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'error' => $e->getMessage(),
]);
}
}
return [
'success' => $success,
'failed' => $failed,
'results' => $results,
'total' => $settlements->count(),
];
}
}