shjEightService = $shjEightService; $this->mailSendService = $mailSendService; $this->shjFourCService = $shjFourCService; } /** * 決済トランザクション処理メイン実行 * * SHJ-4Bの3段階判断処理を実装: * 【判断0】対象契約取得判定 * 【判断1】授受状態チェック * 【判断2】金額チェック * * @param int $settlementTransactionId 決済トランザクションID * @param array $context 追加のコンテキスト情報 * @return array 処理結果 */ public function processSettlementTransaction(int $settlementTransactionId, array $context = []): array { $startTime = now(); Log::info('SHJ-4B 決済トランザクション処理開始', [ 'settlement_transaction_id' => $settlementTransactionId, 'context' => $context, 'start_time' => $startTime, ]); try { // 【前処理】決済トランザクション取得 $settlement = $this->getSettlementTransaction($settlementTransactionId); // 【処理1】定期契約マスタの対象レコード取得 // 【判断0】取得判定(登録済み判定を含む) $contractResult = $this->judgeTargetContract($settlement); if (!$contractResult['found']) { // 対象レコードなしの場合 $result = $this->handleNoTargetRecord($settlement, $contractResult); $this->createBatchLog($settlement, null, null, true, null, null, null, $result['message']); return $result; } if ($contractResult['already_processed']) { // 登録済みの場合 $result = $this->handleAlreadyProcessed($settlement, $contractResult); $contract = $contractResult['contract']; $this->createBatchLog($settlement, $contract, null, true, null, null, null, $result['message']); return $result; } $contract = $contractResult['contract']; // 【判断1】授受状態チェック $statusResult = $this->judgeReceiptStatus($settlement, $contract); if (!$statusResult['valid']) { // 授受済みの場合(仕様: JOB5メール送信 → JOB6バッチログ → 終了) $result = $this->handleInvalidStatus($settlement, $contract, $statusResult); // 【JOB5】メール送信(授受済みでも送信) $mailResult = $this->sendUserNotificationMail($settlement, $contract); $mailCommentSuffix = $mailResult['batch_comment_suffix'] ?? null; // 【JOB6】バッチログ(授受済みメッセージを直接statusCommentとして渡す) $this->createBatchLog( $settlement, $contract, null, true, null, $mailCommentSuffix, null, $result['message'] ); return $result; } // 【JOB3-0】SHJ-4C 車室割り当て(新規のみ) // 仕様:定期契約継続フラグ(update_flag) = 2(新規)の場合に呼び出す // ※異常があったとしても、処理は継続する $shjFourCResult = null; if ((int) $contract->update_flag === 2) { $shjFourCResult = $this->triggerShjFourC($contract); } // 【JOB3-1】契約更新処理実行(SHJ-4C結果を含む) // 仕様フロー順: JOB3-0 → JOB3-1/3-4 → JOB3-STEP1(金額チェック) $updateResult = $this->executeContractUpdate($settlement, $contract, $shjFourCResult); // 【判断2】金額チェック(仕様のJOB3-STEP1) $amountResult = $this->judgeAmountComparison($settlement, $contract); // 副作用処理実行(SHJ-4C結果をSHJ-13に渡す) $sideEffectResult = $this->executeSideEffects($settlement, $contract, $amountResult, $updateResult, $shjFourCResult); $result = [ 'success' => true, 'settlement_transaction_id' => $settlementTransactionId, 'contract_id' => $contract->contract_id, 'contract_payment_number' => $settlement->contract_payment_number, 'amount_comparison' => $amountResult['comparison'], 'contract_updated' => $updateResult['updated'], 'side_effects' => $sideEffectResult, 'execution_time' => now()->diffInSeconds($startTime), ]; Log::info('SHJ-4B 決済トランザクション処理完了', $result); // 【処理6】バッチ処理ログ作成(SHJ-8呼び出し) $mailCommentSuffix = $sideEffectResult['user_mail']['batch_comment_suffix'] ?? null; $photoDeletionResult = $sideEffectResult['photo_deletion'] ?? null; $this->createBatchLog($settlement, $contract, $amountResult, true, null, $mailCommentSuffix, $photoDeletionResult); return $result; } catch (\Throwable $e) { Log::error('SHJ-4B 決済トランザクション処理失敗', [ 'settlement_transaction_id' => $settlementTransactionId, 'context' => $context, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); // エラー時のバッチログ作成 try { $settlement = SettlementTransaction::find($settlementTransactionId); if ($settlement) { $this->createBatchLog($settlement, null, null, false, $e->getMessage()); } } catch (\Throwable $logError) { Log::error('SHJ-4B バッチログ作成エラー', ['error' => $logError->getMessage()]); } throw $e; } } /** * ウェルネットPUSHデータ処理入口 * * 仕様順序: JOB1(SQL-1 契約検索) → JOB2(SQL-2 重複チェック → SQL-3 INSERT) → 既存処理 * 既存の processSettlementTransaction(id) は再処理・後方互換の入口として残す * * @param array $wellnetData ウェルネットからの生データ * @return array 処理結果 */ public function processWellnetPush(array $wellnetData): array { $contractPaymentNumber = $wellnetData['contract_payment_number']; Log::info('SHJ-4B ウェルネットPUSHデータ処理開始', [ 'contract_payment_number' => $contractPaymentNumber, ]); // 【JOB1】SQL-1: 定期契約マスタの対象レコード取得(judgeTargetContractと同一条件) $contract = DB::table('regular_contract as T1') ->join('park as T2', 'T1.park_id', '=', 'T2.park_id') ->join('price_a as T4', function($join) { $join->on('T1.price_parkplaceid', '=', 'T4.price_parkplaceid') ->on('T1.park_id', '=', 'T4.park_id'); }) ->where('T1.contract_payment_number', $contractPaymentNumber) ->where('T1.contract_cancel_flag', '!=', 1) ->whereNotNull('T1.contract_flag') ->select('T1.contract_id') ->first(); // JOB1-STEP1: 対象レコード判定 if (!$contract) { Log::warning('SHJ-4B JOB1: 受付番号不正(対象契約なし)', [ 'contract_payment_number' => $contractPaymentNumber, ]); // 仕様: JOB6 バッチログ → 終了(settlement_transaction未作成) $statusComment = "受付番号が不正です。(受付番号:{$contractPaymentNumber})"; $this->createWellnetPushBatchLog($statusComment); return [ 'success' => true, 'result' => 'no_target', 'message' => $statusComment, ]; } // 【JOB2】SQL-2: 重複チェック // 補足: 受付番号は contract_payment_number(rno)で照合する $existCount = DB::table('settlement_transaction') ->where('contract_payment_number', $contractPaymentNumber) ->count(); if ($existCount >= 1) { Log::info('SHJ-4B SQL-2: 既に登録済み', [ 'contract_payment_number' => $contractPaymentNumber, ]); return [ 'success' => true, 'result' => 'already_registered', 'message' => '決済トランザクションは既に登録済みです', ]; } // 【SQL-3】決済トランザクション挿入(.定期契約ID を使用) $settlementId = DB::table('settlement_transaction')->insertGetId([ 'contract_id' => $contract->contract_id, 'status' => $wellnetData['status'] ?? null, 'pay_code' => $wellnetData['pay_code'] ?? null, 'contract_payment_number' => $contractPaymentNumber, 'corp_code' => $wellnetData['corp_code'] ?? null, 'mms_date' => $wellnetData['mms_date'] ?? null, 'cvs_code' => $wellnetData['cvs_code'] ?? null, 'shop_code' => $wellnetData['shop_code'] ?? null, 'pay_date' => $wellnetData['pay_date'] ?? null, 'settlement_amount' => $wellnetData['settlement_amount'] ?? null, 'stamp_flag' => $wellnetData['stamp_flag'] ?? null, 'md5_string' => $wellnetData['md5_string'] ?? null, 'created_at' => now(), 'updated_at' => now(), ]); Log::info('SHJ-4B SQL-3: 決済トランザクション登録', [ 'settlement_transaction_id' => $settlementId, 'contract_payment_number' => $contractPaymentNumber, 'contract_id' => $contract->contract_id, ]); // JOB2-STEP2以降: 既存フローへ return $this->processSettlementTransaction($settlementId); } /** * 決済トランザクション取得 * * @param int $settlementTransactionId * @return SettlementTransaction * @throws \RuntimeException */ private function getSettlementTransaction(int $settlementTransactionId): SettlementTransaction { $settlement = SettlementTransaction::find($settlementTransactionId); if (!$settlement) { throw new \RuntimeException("SettlementTransaction not found: {$settlementTransactionId}"); } Log::info('SHJ-4B 決済トランザクション取得成功', [ 'settlement_transaction_id' => $settlementTransactionId, 'contract_payment_number' => $settlement->contract_payment_number, 'settlement_amount' => $settlement->settlement_amount, 'pay_date' => $settlement->pay_date, ]); return $settlement; } /** * 【処理1】定期契約マスタの対象レコード取得 * 【判断0】取得判定(登録済み判定を含む) * * @param SettlementTransaction $settlement * @return array */ private function judgeTargetContract(SettlementTransaction $settlement): array { Log::info('SHJ-4B 処理1: 定期契約マスタの対象レコード取得開始', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_payment_number' => $settlement->contract_payment_number, ]); // 仕様書要件のSQL構造に基づく対象レコード取得 // regular_contract T1 inner join park T2 inner join price_a T4 $contractQuery = DB::table('regular_contract as T1') ->select([ 'T1.contract_id', 'T1.old_contract_id', 'T1.park_id', 'T1.user_id', 'T1.contract_flag', 'T1.billing_amount', 'T4.price_ptypeid as ptype_id', 'T1.psection_id', 'T1.zone_id', 'T1.update_flag', 'T1.reserve_id', 'T1.contract_payment_number', 'T1.contract_payment_day', 'T1.contract_periods', 'T1.contract_periode', 'T1.contract_created_at', 'T1.contract_cancel_flag', 'T2.park_name', 'T2.immediate_use_permit', 'T2.update_grace_period_start_date', 'T2.update_grace_period_end_date', 'T2.parking_start_grace_period', 'T4.price_month', 'T4.price' ]) ->join('park as T2', 'T1.park_id', '=', 'T2.park_id') ->join('price_a as T4', function($join) { $join->on('T1.price_parkplaceid', '=', 'T4.price_parkplaceid') ->on('T1.park_id', '=', 'T4.park_id'); // 仕様書要件の第2条件 }) ->where('T1.contract_payment_number', $settlement->contract_payment_number) ->where('T1.contract_cancel_flag', '!=', 1) // 解約されていない ->whereNotNull('T1.contract_flag') // 状態が設定済み ->first(); if (!$contractQuery) { Log::warning('SHJ-4B 判断0: 対象レコードなし', [ 'contract_payment_number' => $settlement->contract_payment_number, 'settlement_transaction_id' => $settlement->settlement_transaction_id, ]); return [ 'found' => false, 'contract' => null, 'reason' => '対象レコードなし', 'message' => "受付番号が不正です。(受付番号:{$settlement->contract_payment_number})", ]; } // 登録済み判定 $isAlreadyProcessed = $this->checkAlreadyProcessed($contractQuery, $settlement); if ($isAlreadyProcessed['processed']) { Log::info('SHJ-4B 判断0: 登録済み検出', [ 'contract_id' => $contractQuery->contract_id, 'contract_payment_number' => $settlement->contract_payment_number, 'reason' => $isAlreadyProcessed['reason'], ]); return [ 'found' => true, 'contract' => $contractQuery, 'already_processed' => true, 'reason' => '登録済み', 'message' => "この決済は既に処理済みです: " . $isAlreadyProcessed['reason'], ]; } Log::info('SHJ-4B 判断0: 対象契約取得成功', [ 'contract_id' => $contractQuery->contract_id, 'contract_payment_number' => $settlement->contract_payment_number, 'billing_amount' => $contractQuery->billing_amount, 'contract_flag' => $contractQuery->contract_flag, 'park_name' => $contractQuery->park_name, 'price_month' => $contractQuery->price_month, ]); return [ 'found' => true, 'contract' => $contractQuery, 'already_processed' => false, 'reason' => '対象契約取得成功', 'message' => "契約ID {$contractQuery->contract_id} を取得しました", ]; } /** * 登録済み判定 * * 複数の条件で既に処理済みかを判定 * * @param object $contract * @param SettlementTransaction $settlement * @return array */ private function checkAlreadyProcessed($contract, SettlementTransaction $settlement): array { // 条件1: contract_payment_dayが既に設定済みで、今回の支払日以降 if (!empty($contract->contract_payment_day)) { $existingPaymentDate = Carbon::parse($contract->contract_payment_day); $currentPaymentDate = Carbon::parse($settlement->pay_date); if ($existingPaymentDate->gte($currentPaymentDate)) { return [ 'processed' => true, 'reason' => "既に支払日 {$existingPaymentDate->format('Y-m-d')} が設定済み", ]; } } // 条件2: 同一の決済条件(contract_payment_number + pay_date + settlement_amount)が // 既に他のsettlement_transactionで処理済み $existingTransaction = SettlementTransaction::where('contract_payment_number', $settlement->contract_payment_number) ->where('pay_date', $settlement->pay_date) ->where('settlement_amount', $settlement->settlement_amount) ->where('settlement_transaction_id', '!=', $settlement->settlement_transaction_id) ->first(); if ($existingTransaction) { return [ 'processed' => true, 'reason' => "同一条件の決済トランザクション {$existingTransaction->settlement_transaction_id} が既に存在", ]; } // 条件3: bat_job_logで同一決済の処理完了記録があるか // status_commentに決済トランザクションIDが含まれているかチェック $existingBatchLog = BatJobLog::where('process_name', 'SHJ-4B') ->where('status', 'success') ->where('status_comment', 'like', '%settlement_transaction_id:' . $settlement->settlement_transaction_id . '%') ->exists(); if ($existingBatchLog) { return [ 'processed' => true, 'reason' => "bat_job_logに処理完了記録が存在", ]; } return [ 'processed' => false, 'reason' => '未処理', ]; } /** * 【判断1】授受状態チェック * * @param SettlementTransaction $settlement * @param object $contract * @return array */ private function judgeReceiptStatus(SettlementTransaction $settlement, $contract): array { // 仕様JOB2-STEP2: 授受フラグ = 0 の場合、処理続行 if ((int) $contract->contract_flag === 0) { Log::info('SHJ-4B 判断1: 授受フラグ=0 処理続行', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contract->contract_id, ]); return [ 'valid' => true, 'reason' => '授受フラグ未処理', 'message' => '授受フラグチェックに合格しました', ]; } // その他の場合(授受済み) $message = sprintf( '支払いステータスチェック:授受済みです。(定期契約ID:%s)', $contract->contract_id ); Log::warning('SHJ-4B 判断1: 授受済み', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contract->contract_id, 'contract_flag' => $contract->contract_flag, ]); return [ 'valid' => false, 'reason' => '授受済み', 'message' => $message, ]; } /** * 【判断2】金額チェック * * @param SettlementTransaction $settlement * @param object $contract * @return array */ private function judgeAmountComparison(SettlementTransaction $settlement, $contract): array { // 仕様書要件: 請求額=授受額の厳密比較 $billingAmount = (int) $contract->billing_amount; // 整数として比較 $settlementAmount = (int) $settlement->settlement_amount; // 整数として比較 $difference = $settlementAmount - $billingAmount; if ($difference === 0) { $comparison = self::AMOUNT_MATCH; $result = '正常(金額一致)'; } elseif ($difference < 0) { $comparison = self::AMOUNT_SHORTAGE; $result = '授受過少'; } else { $comparison = self::AMOUNT_EXCESS; $result = '授受超過'; } Log::info('SHJ-4B 判断2: 金額チェック完了', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contract->contract_id, 'billing_amount' => $billingAmount, 'settlement_amount' => $settlementAmount, 'difference' => $difference, 'comparison' => $comparison, 'result' => $result, ]); return [ 'comparison' => $comparison, 'result' => $result, 'billing_amount' => $billingAmount, 'settlement_amount' => $settlementAmount, 'difference' => $difference, 'message' => "請求額: {$billingAmount}円, 授受額: {$settlementAmount}円, 結果: {$result}", ]; } /** * 登録済み処理 * * @param SettlementTransaction $settlement * @param array $contractResult * @return array */ private function handleAlreadyProcessed(SettlementTransaction $settlement, array $contractResult): array { Log::info('SHJ-4B 登録済み処理', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contractResult['contract']->contract_id, 'reason' => $contractResult['reason'], ]); return [ 'success' => true, 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contractResult['contract']->contract_id, 'contract_payment_number' => $settlement->contract_payment_number, 'result' => 'already_processed', 'reason' => $contractResult['reason'], 'message' => $contractResult['message'], 'skipped' => true, ]; } /** * 【SQL-4】シール印刷可能日を計算(仕様パターンA/B準拠) * * パターン判定: * - 即利用許可=1 → 入金日の年月日 * - 更新期間開始日 <= 更新期間終了日(パターンA): * - A-1: 駐輪開始猶予期間 > 本日の日 → 入金日の年月 + 猶予期間日 * - A-2: それ以外 → 入金日の年月日 * - 更新期間開始日 > 更新期間終了日(パターンB)→ 入金日の年月日 * * @param SettlementTransaction $settlement * @param object $contract * @return string Y-m-d形式の日付 */ private function calculatePrintableDate(SettlementTransaction $settlement, $contract): string { $payDate = Carbon::parse($settlement->pay_date); // 契約後即利用許可 = 1 if ((int) ($contract->immediate_use_permit ?? 0) === 1) { return $payDate->format('Y-m-d'); } $startDate = (int) ($contract->update_grace_period_start_date ?? 0); $endDate = (int) ($contract->update_grace_period_end_date ?? 0); $gracePeriod = (int) ($contract->parking_start_grace_period ?? 0); $todayDay = (int) now()->format('d'); // パターンA: 更新期間開始日 <= 更新期間終了日 if ($startDate <= $endDate) { if ($gracePeriod > $todayDay) { // A-1: 入金日の年月 + 猶予期間日 return $payDate->format('Y-m') . '-' . str_pad($gracePeriod, 2, '0', STR_PAD_LEFT); } // A-2: 入金日の年月日 return $payDate->format('Y-m-d'); } // パターンB: 全サブケース同一 → 入金日の年月日 return $payDate->format('Y-m-d'); } /** * 新規契約判定 * * @param object $contract * @return bool */ private function isNewContract($contract): bool { if (empty($contract->contract_created_at)) { return false; } $createdAt = Carbon::parse($contract->contract_created_at); $thirtyDaysAgo = Carbon::now()->subDays(30); // 作成から30日以内を新規とみなす(調整可能) $isNew = $createdAt->gte($thirtyDaysAgo); Log::info('SHJ-4B 新規契約判定', [ 'contract_id' => $contract->contract_id, 'contract_created_at' => $createdAt->format('Y-m-d H:i:s'), 'is_new' => $isNew, 'days_since_created' => $createdAt->diffInDays(Carbon::now()), ]); return $isNew; } /** * 【JOB3-1】決済授受および写真削除 + 定期契約マスタ、定期予約マスタ更新 * * @param SettlementTransaction $settlement * @param object $contract * @param array|null $shjFourCResult SHJ-4C車室割り当て結果(新規のみ) * @return array */ private function executeContractUpdate( SettlementTransaction $settlement, $contract, ?array $shjFourCResult = null ): array { $updateData = []; $updated = false; try { DB::transaction(function() use ($settlement, $contract, $shjFourCResult, &$updateData, &$updated) { // 【SQL-4】基本更新項目(仕様準拠) $updateData = [ 'contract_payment_day' => Carbon::parse($settlement->pay_date)->format('Y-m-d H:i:s'), 'contract_money' => $settlement->settlement_amount, 'contact_shop_code' => $settlement->shop_code, 'contract_cvs_class' => $settlement->cvs_code, 'contract_flag' => 1, 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_permission' => 1, 'printable_date' => $this->calculatePrintableDate($settlement, $contract), 'contract_payment_number' => $settlement->contract_payment_number, 'updated_at' => now(), ]; // 【新規】SHJ-4Cの結果を反映(仕様SQL-4準拠) if ((int) $contract->update_flag === 2 && $shjFourCResult && ($shjFourCResult['success'] ?? false) && !empty($shjFourCResult['zone_id']) && !empty($shjFourCResult['pplace_no'])) { $updateData['zone_id'] = $shjFourCResult['zone_id']; $updateData['pplace_no'] = $shjFourCResult['pplace_no']; } // 【SQL-4】定期契約マスタ更新 $affectedRows = DB::table('regular_contract') ->where('contract_id', $contract->contract_id) ->update($updateData); $updated = $affectedRows > 0; // 【SQL-5】契約元の定期契約マスタを更新(旧契約の更新済フラグ) if (!empty($contract->old_contract_id)) { DB::table('regular_contract') ->where('contract_id', $contract->old_contract_id) ->update(['contract_renewal' => 1, 'updated_at' => now()]); Log::info('SHJ-4B SQL-5 旧契約更新済フラグ更新', [ 'old_contract_id' => $contract->old_contract_id, 'contract_id' => $contract->contract_id, ]); } // 【SQL-8】定期予約マスタ更新(reserve_idがnull以外の場合) if (!empty($contract->reserve_id)) { DB::table('reserve') ->where('reserve_id', $contract->reserve_id) ->update([ 'contract_id' => $contract->contract_id, 'contract_created_at' => now(), 'valid_flag' => 0, 'updated_at' => now(), ]); Log::info('SHJ-4B 定期予約マスタ更新完了', [ 'reserve_id' => $contract->reserve_id, 'contract_id' => $contract->contract_id, ]); } Log::info('SHJ-4B 定期契約マスタ更新完了', [ 'contract_id' => $contract->contract_id, 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'update_data' => $updateData, 'affected_rows' => $affectedRows, ]); }); return [ 'updated' => $updated, 'update_data' => $updateData, 'message' => $updated ? '契約更新に成功しました' : '契約更新対象が見つかりませんでした', ]; } catch (\Throwable $e) { Log::error('SHJ-4B 契約更新処理失敗', [ 'contract_id' => $contract->contract_id, 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'error' => $e->getMessage(), ]); throw $e; } } /** * 副作用処理実行 * * 決済授受および写真削除、新規連動等の処理 * * @param SettlementTransaction $settlement * @param object $contract * @param array $amountResult * @param array $updateResult * @param array|null $shjFourCResult SHJ-4C車室割り当て結果(新規のみ) * @return array */ private function executeSideEffects( SettlementTransaction $settlement, $contract, array $amountResult, array $updateResult, ?array $shjFourCResult = null ): array { $sideEffects = []; try { // 【JOB3-2】写真削除処理(SQL-4/SQL-5の後、金額比較結果に関わらず実行) $sideEffects['photo_deletion'] = $this->executePhotoDeletion($contract); // 【JOB3-3】写真削除バッチログ(独立SHJ-8呼出し) $this->createPhotoDeletionBatchLog($sideEffects['photo_deletion']); // 【JOB3-STEP1】SHJ-13契約台数追加(新規 かつ shortage以外) // 仕様:shortage → SHJ-13呼出なし、match/excess → SHJ-13呼出 if ((int) $contract->update_flag === 2 && $amountResult['comparison'] !== self::AMOUNT_SHORTAGE) { $sideEffects['shj13_trigger'] = $this->triggerShjThirteen($contract, $shjFourCResult); } // 【処理4】異常時のオペレーターキュー登録処理 if ($amountResult['comparison'] !== self::AMOUNT_MATCH) { $sideEffects['operator_queue'] = $this->registerToOperatorQueue($settlement, $contract, $amountResult); } // 【JOB5】利用者メール送信処理(仕様: フロー到達時は一律送信) $sideEffects['user_mail'] = $this->sendUserNotificationMail($settlement, $contract, $amountResult); Log::info('SHJ-4B 副作用処理完了', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contract->contract_id, 'side_effects' => array_keys($sideEffects), ]); return $sideEffects; } catch (\Throwable $e) { Log::error('SHJ-4B 副作用処理失敗', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contract->contract_id, 'error' => $e->getMessage(), ]); // 副作用処理の失敗はメイン処理を止めない return ['error' => $e->getMessage()]; } } /** * 対象レコードなしの場合の処理 * * @param SettlementTransaction $settlement * @param array $contractResult * @return array */ private function handleNoTargetRecord(SettlementTransaction $settlement, array $contractResult): array { Log::warning('SHJ-4B 対象レコードなし処理', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_payment_number' => $settlement->contract_payment_number, 'reason' => $contractResult['reason'], ]); // TODO: 必要に応じて管理者通知やオペレーターキューへの登録 return [ 'success' => true, // エラーではなく、正常な結果として扱う 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_payment_number' => $settlement->contract_payment_number, 'result' => 'no_target', 'reason' => $contractResult['reason'], 'message' => $contractResult['message'], 'action_required' => '管理者による手動確認が必要です', ]; } /** * 授受状態異常の場合の処理 * * @param SettlementTransaction $settlement * @param RegularContract $contract * @param array $statusResult * @return array */ private function handleInvalidStatus( SettlementTransaction $settlement, $contract, array $statusResult ): array { Log::warning('SHJ-4B 授受状態異常処理', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contract->contract_id, 'reason' => $statusResult['reason'], ]); return [ 'success' => true, // 授受済みは仕様上エラーではなく正常分岐 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'contract_id' => $contract->contract_id, 'result' => 'already_received', 'reason' => $statusResult['reason'], 'message' => $statusResult['message'], ]; } /** * 写真削除処理 * * @param object $contract * @return array */ private function executePhotoDeletion($contract): array { // 【SQL-6】利用者の写真ファイル名取得 $userData = DB::table('regular_contract as T1') ->join('user as T2', 'T1.user_id', '=', 'T2.user_seq') ->where('T1.contract_payment_number', $contract->contract_payment_number) ->where('T1.contract_flag', 1) ->select('T2.user_id', 'T2.user_seq', 'T2.user_name', 'T2.photo_filename1', 'T2.photo_filename2') ->first(); if (!$userData) { Log::info('SHJ-4B 写真削除: 対象利用者なし', [ 'contract_id' => $contract->contract_id, ]); return ['executed' => false, 'message' => '対象利用者なし']; } $deletedFiles = []; // ファイル削除 foreach (['photo_filename1', 'photo_filename2'] as $field) { if (!empty($userData->$field)) { $path = 'photo/' . $userData->$field; if (Storage::disk('public')->exists($path)) { Storage::disk('public')->delete($path); $deletedFiles[] = $userData->$field; } } } // 【SQL-7】利用者の写真ファイル名をNULLに更新 $userUpdateQuery = DB::table('user'); if (!empty($userData->user_id)) { // 仕様書上の利用者ID(user.user_id)で更新 $userUpdateQuery->where('user_id', $userData->user_id); } else { // user_id未設定データの後方互換 $userUpdateQuery->where('user_seq', $userData->user_seq); } $userUpdateQuery->update([ 'photo_filename1' => null, 'photo_filename2' => null, 'updated_at' => now(), ]); Log::info('SHJ-4B 写真削除処理完了', [ 'user_seq' => $userData->user_seq, 'deleted_files' => $deletedFiles, ]); return [ 'executed' => true, 'user_id' => $userData->user_id, 'user_seq' => $userData->user_seq, 'user_name' => $userData->user_name, 'photo_filename1' => $userData->photo_filename1, 'photo_filename2' => $userData->photo_filename2, 'deleted_files' => $deletedFiles, ]; } /** * ウェルネットPUSH処理用バッチログ作成(settlement_transaction未作成時) * * processWellnetPush内でJOB1不合格(受付番号不正)の場合に使用 * * @param string $statusComment ステータスコメント * @return void */ private function createWellnetPushBatchLog(string $statusComment): void { try { $device = Device::orderBy('device_id')->first(); $deviceId = $device ? $device->device_id : 1; $today = now()->format('Y/m/d'); $this->shjEightService->execute( $deviceId, 'SHJ-4B', 'SHJ-4支払いステータスチェック', 'success', $statusComment, $today, $today ); } catch (\Exception $e) { Log::error('SHJ-4B ウェルネットPUSHバッチログ作成エラー', [ 'error' => $e->getMessage(), ]); } } /** * 【JOB3-3】写真削除バッチログ作成(独立SHJ-8呼出し) * * @param array $photoDeletionResult executePhotoDeletion()の戻り値 * @return void */ private function createPhotoDeletionBatchLog(array $photoDeletionResult): void { try { $device = Device::orderBy('device_id')->first(); $deviceId = $device ? $device->device_id : 1; $today = now()->format('Y/m/d'); if ($photoDeletionResult['executed'] ?? false) { $status = 'success'; $userId = $photoDeletionResult['user_id'] ?? ''; $userName = $photoDeletionResult['user_name'] ?? ''; $file1 = $photoDeletionResult['photo_filename1'] ?? ''; $file2 = $photoDeletionResult['photo_filename2'] ?? ''; $statusComment = "利用者ID:{$userId}、利用者名:{$userName}、ファイル名:{$file1},{$file2}"; } else { $status = 'error'; $statusComment = $photoDeletionResult['message'] ?? '写真削除対象なし'; } $this->shjEightService->execute( $deviceId, 'SHJ-4B', 'SHJ-4B本人確認写真削除', $status, $statusComment, $today, $today ); } catch (\Exception $e) { Log::error('SHJ-4B JOB3-3 写真削除バッチログ作成エラー', [ 'error' => $e->getMessage(), ]); } } /** * 【JOB3-0】SHJ-4C 車室割り当て処理(新規のみ) * * 仕様:定期契約継続フラグ = 2(新規)の場合に呼び出す * ※異常があったとしても、処理は継続する * * @param object $contract 契約データ * @return array|null 処理結果(異常時はnull) */ private function triggerShjFourC($contract): ?array { Log::info('SHJ-4B SHJ-4C車室割り当て処理実行', [ 'contract_id' => $contract->contract_id, 'park_id' => $contract->park_id, 'ptype_id' => $contract->ptype_id, 'psection_id' => $contract->psection_id, ]); try { // SHJ-4Cサービス実行 $result = $this->shjFourCService->executeRoomAllocation( (int) $contract->park_id, (int) $contract->ptype_id, (int) $contract->psection_id ); Log::info('SHJ-4B SHJ-4C車室割り当て処理完了', [ 'contract_id' => $contract->contract_id, 'result' => $result, ]); return $result; } catch (\Throwable $e) { // ※異常があったとしても、処理は継続する Log::error('SHJ-4B SHJ-4C車室割り当て処理エラー', [ 'contract_id' => $contract->contract_id, 'error' => $e->getMessage(), ]); return null; } } /** * SHJ-13実行処理(新規のみ) * * ShjThirteenServiceを使用した契約台数追加処理 * 仕様:パラメーター4 ゾーンID = 【SHJ-4C.ゾーンID】 * * @param object $contract * @param array|null $shjFourCResult SHJ-4C車室割り当て結果 * @return array */ private function triggerShjThirteen($contract, ?array $shjFourCResult = null): array { // 仕様準拠:ゾーンID = SHJ-4C.ゾーンID $zoneId = ($shjFourCResult && !empty($shjFourCResult['zone_id'])) ? $shjFourCResult['zone_id'] : $contract->zone_id; Log::info('SHJ-4B SHJ-13実行処理', [ 'contract_id' => $contract->contract_id, 'park_id' => $contract->park_id, 'psection_id' => $contract->psection_id, 'ptype_id' => $contract->ptype_id, 'zone_id' => $zoneId, ]); try { // 契約データ準備(仕様:パラメーター4 ゾーンID = SHJ-4C.ゾーンID) $contractData = [ 'contract_id' => $contract->contract_id, 'park_id' => $contract->park_id, 'psection_id' => $contract->psection_id, 'ptype_id' => $contract->ptype_id, 'zone_id' => $zoneId, ]; // ShjThirteenService実行 $shjThirteenService = app(ShjThirteenService::class); $result = $shjThirteenService->execute($contractData); Log::info('SHJ-4B SHJ-13実行完了', [ 'contract_id' => $contract->contract_id, 'result' => $result, ]); return $result; } catch (\Throwable $e) { Log::error('SHJ-4B SHJ-13実行エラー', [ 'contract_id' => $contract->contract_id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); return [ 'result' => 1, 'error_code' => $e->getCode() ?: 1999, 'error_message' => $e->getMessage(), 'stack_trace' => $e->getTraceAsString(), ]; } } /** * 【処理5】利用者メール送信処理 * * SHJ-4B仕様準拠: * 1. 利用者マスタよりメールアドレス、予備メールアドレスを取得 * 2. SHJ-7メール送信を呼び出し(使用プログラムID: 205) * 3. 処理結果判定: * - result = 0 (正常): バッチコメントに "/メール正常終了件数:1" を追加 * - その他: バッチコメントに "/メール異常終了件数:1、" + error_info を追加 * * @param SettlementTransaction $settlement * @param object $contract * @param array $amountResult * @return array 処理結果 ['success' => bool, 'mail_status' => string, 'batch_comment_suffix' => string] */ private function sendUserNotificationMail(SettlementTransaction $settlement, $contract, ?array $amountResult = null): array { try { // 【処理5】利用者マスタよりメールアドレス、予備メールアドレスを取得する $user = User::select('user_name', 'user_primemail', 'user_submail') ->where('user_seq', $contract->user_id) ->first(); if (!$user) { Log::error('SHJ-4B 利用者メール送信処理: 利用者情報取得失敗', [ 'user_id' => $contract->user_id, 'contract_id' => $contract->contract_id, ]); return [ 'success' => false, 'mail_status' => 'user_not_found', 'batch_comment_suffix' => '/メール異常終了件数:1、利用者情報取得失敗' ]; } $mailAddress = $user->user_primemail ?? ''; $backupMailAddress = $user->user_submail ?? ''; Log::info('SHJ-4B 利用者メール送信処理開始', [ 'contract_id' => $contract->contract_id, 'user_id' => $contract->user_id, 'user_name' => $user->user_name, 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'mail_address' => $mailAddress, 'amount_comparison' => $amountResult['comparison'] ?? 'already_received', ]); // 共通処理「SHJ-7メール送信」を呼び出し // 使用プログラムID: 205(仕様書準拠) $mailResult = $this->mailSendService->executeMailSend( $mailAddress, $backupMailAddress, 205 // SHJ-4B仕様: 使用プログラムID = 205 ); // SHJ-7仕様準拠: result === 0 が正常、それ以外は異常 if (($mailResult['result'] ?? 1) === 0) { // 【正常終了】バッチコメントに "/メール正常終了件数:1" を設定 Log::info('SHJ-4B 利用者メール送信成功', [ 'contract_id' => $contract->contract_id, 'user_id' => $contract->user_id, 'mail_address' => $mailAddress, ]); return [ 'success' => true, 'mail_status' => 'sent', 'batch_comment_suffix' => '/メール正常終了件数:1' ]; } else { // 【異常終了】バッチコメントに "/メール異常終了件数:1、" + error_info を設定 $errorInfo = $mailResult['error_info'] ?? 'メール送信失敗'; Log::error('SHJ-4B 利用者メール送信失敗', [ 'contract_id' => $contract->contract_id, 'user_id' => $contract->user_id, 'mail_address' => $mailAddress, 'error_info' => $errorInfo, ]); return [ 'success' => false, 'mail_status' => 'failed', 'batch_comment_suffix' => "/メール異常終了件数:1、{$errorInfo}" ]; } } catch (\Exception $e) { Log::error('SHJ-4B 利用者メール送信処理例外エラー', [ 'contract_id' => $contract->contract_id, 'user_id' => $contract->user_id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); return [ 'success' => false, 'mail_status' => 'exception', 'batch_comment_suffix' => '/メール異常終了件数:1、システムエラー: ' . $e->getMessage() ]; } } /** * オペレーターキューへの登録 * * @param SettlementTransaction $settlement * @param object $contract * @param array $amountResult * @return array */ private function registerToOperatorQueue( SettlementTransaction $settlement, $contract, array $amountResult ): array { // 【SQL-9】キュー種別: shortage=8(支払い催促), excess=6(返金処理) $queClass = $amountResult['comparison'] === self::AMOUNT_SHORTAGE ? 8 : 6; $queId = DB::table('operator_que')->insertGetId([ 'que_class' => $queClass, 'user_id' => $contract->user_id, 'contract_id' => $contract->contract_id, 'park_id' => $contract->park_id, 'que_comment' => '収納請求金額照合エラー', 'que_status' => 1, 'que_status_comment' => '', 'work_instructions' => '', 'operator_id' => 'SHJ-4', 'created_at' => now(), 'updated_at' => now(), ]); Log::info('SHJ-4B オペレーターキュー登録完了', [ 'que_id' => $queId, 'que_class' => $queClass, 'contract_id' => $contract->contract_id, ]); return [ 'registered' => true, 'que_id' => $queId, 'que_class' => $queClass, ]; } /** * 【処理6】バッチ処理ログ作成 * * SHJ-8サービスを呼び出してbat_job_logに記録 * * @param SettlementTransaction $settlement * @param object|null $contract * @param array|null $amountResult * @param bool $isSuccess * @param string|null $errorMessage * @param string|null $mailCommentSuffix メール送信結果コメント * @param array|null $photoDeletionResult 写真削除結果(SQL-6情報含む) * @return void */ private function createBatchLog( SettlementTransaction $settlement, $contract = null, ?array $amountResult = null, bool $isSuccess = true, ?string $errorMessage = null, ?string $mailCommentSuffix = null, ?array $photoDeletionResult = null, ?string $statusCommentOverride = null ): void { try { $device = Device::orderBy('device_id')->first(); $deviceId = $device ? $device->device_id : 1; $today = now()->format('Y/m/d'); // ステータスコメント生成(内部変数.バッチコメント) if ($statusCommentOverride) { // 直接指定(授受済み等) $statusComment = $statusCommentOverride; } elseif ($errorMessage) { // エラー時 $statusComment = "支払いステータスチェック:エラー(決済トランザクションID:{$settlement->settlement_transaction_id}) - {$errorMessage}"; } elseif ($amountResult) { // 正常処理時 switch ($amountResult['comparison']) { case self::AMOUNT_MATCH: // 仕様: OK + 利用者情報 + 写真ファイル名 $userId = $photoDeletionResult['user_id'] ?? ''; $userName = $photoDeletionResult['user_name'] ?? ''; $file1 = $photoDeletionResult['photo_filename1'] ?? ''; $file2 = $photoDeletionResult['photo_filename2'] ?? ''; $statusComment = "支払いステータスチェック:OK、(決済トランザクションID:{$settlement->settlement_transaction_id}、利用者ID:{$userId}、利用者名:{$userName}、ファイル名:{$file1},{$file2})"; break; case self::AMOUNT_SHORTAGE: $statusComment = "支払いステータスチェック:請求金額より授受金額が少ないです。(決済トランザクションID:{$settlement->settlement_transaction_id})"; break; case self::AMOUNT_EXCESS: $statusComment = "支払いステータスチェック:請求金額より授受金額が多いです。(決済トランザクションID:{$settlement->settlement_transaction_id})"; break; default: $statusComment = "支払いステータスチェック:処理完了(決済トランザクションID:{$settlement->settlement_transaction_id})"; } } else { // その他のケース(対象なし、登録済み等) $statusComment = "支払いステータスチェック:処理完了(決済トランザクションID:{$settlement->settlement_transaction_id})"; } // メール送信結果をバッチコメントに追加 if ($mailCommentSuffix) { $statusComment .= $mailCommentSuffix; } // 重複判定用マーカー追加(checkAlreadyProcessed/ShjFourBCheckCommand互換) $statusComment .= " settlement_transaction_id:{$settlement->settlement_transaction_id}"; // SHJ-8サービス呼び出し(仕様JOB6: success/error動的判定) $this->shjEightService->execute( $deviceId, 'SHJ-4B', 'SHJ-4支払いステータスチェック', $isSuccess ? 'success' : 'error', $statusComment, $today, $today ); Log::info('SHJ-4B バッチ処理ログ作成完了', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'status_comment' => $statusComment, ]); } catch (\Exception $e) { Log::error('SHJ-4B バッチ処理ログ作成エラー', [ 'error' => $e->getMessage(), 'settlement_transaction_id' => $settlement->settlement_transaction_id, ]); } } }