1091 lines
43 KiB
PHP
1091 lines
43 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\SettlementTransaction;
|
||
use App\Models\RegularContract;
|
||
use App\Models\Park;
|
||
use App\Models\PriceA;
|
||
use App\Models\BatJobLog;
|
||
use App\Models\Device;
|
||
use App\Models\User;
|
||
use App\Services\ShjThirteenService;
|
||
use App\Services\ShjEightService;
|
||
use App\Services\ShjMailSendService;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Carbon\Carbon;
|
||
|
||
/**
|
||
* SHJ-4B 定期契約更新処理サービス
|
||
*
|
||
* SHJ-4Aで登録された決済情報を基に定期契約の更新処理を実行する
|
||
* ウェルネットのPUSH通知を契機とした決済トランザクション処理
|
||
*/
|
||
class ShjFourBService
|
||
{
|
||
/**
|
||
* 金額比較結果の定数
|
||
*/
|
||
const AMOUNT_MATCH = 'match'; // 金額一致
|
||
const AMOUNT_SHORTAGE = 'shortage'; // 授受過少
|
||
const AMOUNT_EXCESS = 'excess'; // 授受超過
|
||
|
||
/**
|
||
* 契約フラグの状態定数
|
||
*/
|
||
const CONTRACT_FLAG_PROCESSING = 0; // 処理中
|
||
const CONTRACT_FLAG_UPDATED = 1; // 更新済
|
||
const CONTRACT_FLAG_ERROR = 2; // エラー状態
|
||
|
||
/**
|
||
* SHJ-8 バッチ処理ログ作成サービス
|
||
*
|
||
* @var ShjEightService
|
||
*/
|
||
protected $shjEightService;
|
||
|
||
/**
|
||
* SHJ-7 メール送信サービス
|
||
*
|
||
* @var ShjMailSendService
|
||
*/
|
||
protected $mailSendService;
|
||
|
||
/**
|
||
* コンストラクタ
|
||
*
|
||
* @param ShjEightService $shjEightService
|
||
* @param ShjMailSendService $mailSendService
|
||
*/
|
||
public function __construct(
|
||
ShjEightService $shjEightService,
|
||
ShjMailSendService $mailSendService
|
||
) {
|
||
$this->shjEightService = $shjEightService;
|
||
$this->mailSendService = $mailSendService;
|
||
}
|
||
|
||
/**
|
||
* 決済トランザクション処理メイン実行
|
||
*
|
||
* 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, $result['message']);
|
||
return $result;
|
||
}
|
||
|
||
if ($contractResult['already_processed']) {
|
||
// 登録済みの場合
|
||
$result = $this->handleAlreadyProcessed($settlement, $contractResult);
|
||
$contract = $contractResult['contract'];
|
||
$this->createBatchLog($settlement, $contract, null, true, $result['message']);
|
||
return $result;
|
||
}
|
||
|
||
$contract = $contractResult['contract'];
|
||
|
||
// 【判断1】授受状態チェック
|
||
$statusResult = $this->judgeReceiptStatus($settlement, $contract);
|
||
|
||
if (!$statusResult['valid']) {
|
||
// 授受状態が異常な場合
|
||
$result = $this->handleInvalidStatus($settlement, $contract, $statusResult);
|
||
$this->createBatchLog($settlement, $contract, null, true, $result['message']);
|
||
return $result;
|
||
}
|
||
|
||
// 【判断2】金額チェック
|
||
$amountResult = $this->judgeAmountComparison($settlement, $contract);
|
||
|
||
// 【処理3】契約更新処理実行
|
||
$updateResult = $this->executeContractUpdate($settlement, $contract, $amountResult);
|
||
|
||
// 副作用処理実行
|
||
$sideEffectResult = $this->executeSideEffects($settlement, $contract, $amountResult, $updateResult);
|
||
|
||
$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;
|
||
$this->createBatchLog($settlement, $contract, $amountResult, true, null, $mailCommentSuffix);
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 決済トランザクション取得
|
||
*
|
||
* @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',
|
||
'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'); // 文档要求の第二条件追加
|
||
})
|
||
->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
|
||
{
|
||
// 授受状態の基本チェック
|
||
$statusChecks = [
|
||
'settlement_status' => $settlement->status === 'received',
|
||
'pay_date_exists' => !empty($settlement->pay_date),
|
||
'settlement_amount_valid' => $settlement->settlement_amount > 0,
|
||
'contract_not_cancelled' => $contract->contract_cancel_flag != 1,
|
||
];
|
||
|
||
$allValid = array_reduce($statusChecks, function($carry, $check) {
|
||
return $carry && $check;
|
||
}, true);
|
||
|
||
if (!$allValid) {
|
||
$failedChecks = array_keys(array_filter($statusChecks, function($check) {
|
||
return !$check;
|
||
}));
|
||
|
||
Log::warning('SHJ-4B 判断1: 授受状態異常', [
|
||
'settlement_transaction_id' => $settlement->settlement_transaction_id,
|
||
'contract_id' => $contract->contract_id,
|
||
'failed_checks' => $failedChecks,
|
||
'status_checks' => $statusChecks,
|
||
]);
|
||
|
||
return [
|
||
'valid' => false,
|
||
'reason' => '授受状態異常',
|
||
'failed_checks' => $failedChecks,
|
||
'message' => '決済トランザクションまたは契約の状態が更新処理に適していません',
|
||
];
|
||
}
|
||
|
||
Log::info('SHJ-4B 判断1: 授受状態正常', [
|
||
'settlement_transaction_id' => $settlement->settlement_transaction_id,
|
||
'contract_id' => $contract->contract_id,
|
||
'status_checks' => $statusChecks,
|
||
]);
|
||
|
||
return [
|
||
'valid' => true,
|
||
'reason' => '授受状態正常',
|
||
'status_checks' => $statusChecks,
|
||
'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,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* パターンA/B判断
|
||
*
|
||
* 月を跨らない(パターンA)vs 月を跨る(パターンB)の判定
|
||
*
|
||
* @param object $contract
|
||
* @param SettlementTransaction $settlement
|
||
* @return array
|
||
*/
|
||
private function judgeContractPattern($contract, SettlementTransaction $settlement): array
|
||
{
|
||
$payDate = Carbon::parse($settlement->pay_date);
|
||
$contractStart = !empty($contract->contract_periods) ? Carbon::parse($contract->contract_periods) : null;
|
||
$contractEnd = !empty($contract->contract_periode) ? Carbon::parse($contract->contract_periode) : null;
|
||
|
||
// パターン判定ロジック
|
||
$isPatternB = false; // デフォルトはパターンA
|
||
$patternReason = 'パターンA(月を跨らない)';
|
||
|
||
if ($contractEnd) {
|
||
// 支払日が契約終了月の翌月以降の場合、パターンB(跨月)
|
||
if ($payDate->month > $contractEnd->month || $payDate->year > $contractEnd->year) {
|
||
$isPatternB = true;
|
||
$patternReason = 'パターンB(月を跨る)';
|
||
}
|
||
}
|
||
|
||
Log::info('SHJ-4B パターン判定', [
|
||
'contract_id' => $contract->contract_id,
|
||
'pay_date' => $payDate->format('Y-m-d'),
|
||
'contract_periods' => $contractStart?->format('Y-m-d'),
|
||
'contract_periode' => $contractEnd?->format('Y-m-d'),
|
||
'pattern' => $isPatternB ? 'B' : 'A',
|
||
'reason' => $patternReason,
|
||
]);
|
||
|
||
return [
|
||
'pattern' => $isPatternB ? 'B' : 'A',
|
||
'is_pattern_b' => $isPatternB,
|
||
'reason' => $patternReason,
|
||
'pay_date' => $payDate,
|
||
'contract_start' => $contractStart,
|
||
'contract_end' => $contractEnd,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 新規契約判定
|
||
*
|
||
* @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;
|
||
}
|
||
|
||
/**
|
||
* 【処理3】決済授受および写真削除 + 定期契約マスタ、定期予約マスタ更新
|
||
*
|
||
* @param SettlementTransaction $settlement
|
||
* @param object $contract
|
||
* @param array $amountResult
|
||
* @return array
|
||
*/
|
||
private function executeContractUpdate(
|
||
SettlementTransaction $settlement,
|
||
$contract,
|
||
array $amountResult
|
||
): array {
|
||
$updateData = [];
|
||
$updated = false;
|
||
|
||
try {
|
||
// パターンA/B判定
|
||
$pattern = $this->judgeContractPattern($contract, $settlement);
|
||
|
||
DB::transaction(function() use ($settlement, $contract, $amountResult, $pattern, &$updateData, &$updated) {
|
||
// 基本更新項目
|
||
$updateData = [
|
||
'contract_payment_day' => Carbon::parse($settlement->pay_date)->format('Y-m-d H:i:s'),
|
||
'contract_updated_at' => now(),
|
||
];
|
||
|
||
// 金額比較結果に基づく contract_flag 設定
|
||
switch ($amountResult['comparison']) {
|
||
case self::AMOUNT_MATCH:
|
||
$updateData['contract_flag'] = self::CONTRACT_FLAG_UPDATED;
|
||
$updateData['contract_money'] = $settlement->settlement_amount;
|
||
break;
|
||
|
||
case self::AMOUNT_SHORTAGE:
|
||
case self::AMOUNT_EXCESS:
|
||
$updateData['contract_flag'] = self::CONTRACT_FLAG_ERROR;
|
||
$updateData['contract_money'] = $settlement->settlement_amount;
|
||
break;
|
||
}
|
||
|
||
// パターンBの場合の特殊処理
|
||
if ($pattern['is_pattern_b']) {
|
||
// 契約期間の延長処理等
|
||
if ($pattern['contract_end']) {
|
||
$newEndDate = $pattern['contract_end']->addMonth();
|
||
$updateData['contract_periode'] = $newEndDate->format('Y-m-d');
|
||
}
|
||
}
|
||
|
||
// 【定期契約マスタ更新】
|
||
$affectedRows = DB::table('regular_contract')
|
||
->where('contract_id', $contract->contract_id)
|
||
->update($updateData);
|
||
|
||
$updated = $affectedRows > 0;
|
||
|
||
// 【定期予約マスタ更新】(reserve_idが設定されている場合)
|
||
if (!empty($contract->reserve_id)) {
|
||
$reserveUpdateData = [
|
||
'updated_at' => now(),
|
||
];
|
||
|
||
// 金額一致の場合、予約を有効化
|
||
if ($amountResult['comparison'] === self::AMOUNT_MATCH) {
|
||
$reserveUpdateData['valid_flag'] = 1;
|
||
|
||
// パターンBの場合、予約期間も延長
|
||
if ($pattern['is_pattern_b'] && $pattern['contract_end']) {
|
||
$reserveUpdateData['reserve_end'] = $pattern['contract_end']->format('Y-m-d');
|
||
}
|
||
}
|
||
|
||
$reserveAffectedRows = DB::table('reserve')
|
||
->where('reserve_id', $contract->reserve_id)
|
||
->update($reserveUpdateData);
|
||
|
||
Log::info('SHJ-4B 定期予約マスタ更新完了', [
|
||
'reserve_id' => $contract->reserve_id,
|
||
'contract_id' => $contract->contract_id,
|
||
'reserve_update_data' => $reserveUpdateData,
|
||
'reserve_affected_rows' => $reserveAffectedRows,
|
||
]);
|
||
}
|
||
|
||
Log::info('SHJ-4B 定期契約マスタ更新完了', [
|
||
'contract_id' => $contract->contract_id,
|
||
'settlement_transaction_id' => $settlement->settlement_transaction_id,
|
||
'update_data' => $updateData,
|
||
'affected_rows' => $affectedRows,
|
||
'pattern' => $pattern['pattern'],
|
||
]);
|
||
});
|
||
|
||
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
|
||
* @return array
|
||
*/
|
||
private function executeSideEffects(
|
||
SettlementTransaction $settlement,
|
||
$contract,
|
||
array $amountResult,
|
||
array $updateResult
|
||
): array {
|
||
$sideEffects = [];
|
||
|
||
try {
|
||
// 【処理3】写真削除処理(金額一致かつ更新成功の場合)
|
||
if ($amountResult['comparison'] === self::AMOUNT_MATCH && $updateResult['updated']) {
|
||
$sideEffects['photo_deletion'] = $this->executePhotoDeletion($contract);
|
||
}
|
||
|
||
// 【新規のみ】SHJ-13実行処理
|
||
if ($updateResult['updated'] && $amountResult['comparison'] === self::AMOUNT_MATCH) {
|
||
$isNewContract = $this->isNewContract($contract);
|
||
if ($isNewContract) {
|
||
$sideEffects['shj13_trigger'] = $this->triggerShjThirteen($contract);
|
||
}
|
||
}
|
||
|
||
// 【処理4】異常時のオペレーターキュー登録処理
|
||
if ($amountResult['comparison'] !== self::AMOUNT_MATCH) {
|
||
$sideEffects['operator_queue'] = $this->registerToOperatorQueue($settlement, $contract, $amountResult);
|
||
}
|
||
|
||
// 【処理5】利用者メール送信処理
|
||
if ($updateResult['updated']) {
|
||
$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'],
|
||
'failed_checks' => $statusResult['failed_checks'],
|
||
]);
|
||
|
||
// TODO: オペレーターキューへの登録や管理者通知
|
||
|
||
return [
|
||
'success' => false,
|
||
'settlement_transaction_id' => $settlement->settlement_transaction_id,
|
||
'contract_id' => $contract->contract_id,
|
||
'result' => 'invalid_status',
|
||
'reason' => $statusResult['reason'],
|
||
'failed_checks' => $statusResult['failed_checks'],
|
||
'message' => $statusResult['message'],
|
||
'action_required' => 'オペレーターによる手動処理が必要です',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 写真削除処理
|
||
*
|
||
* @param object $contract
|
||
* @return array
|
||
*/
|
||
private function executePhotoDeletion($contract): array
|
||
{
|
||
// TODO: 実際の写真削除ロジックを実装
|
||
// 現在はプレースホルダー
|
||
|
||
Log::info('SHJ-4B 写真削除処理実行', [
|
||
'contract_id' => $contract->contract_id,
|
||
'user_id' => $contract->user_id,
|
||
]);
|
||
|
||
return [
|
||
'executed' => true,
|
||
'method' => 'placeholder',
|
||
'message' => '写真削除処理は実装予定です',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* SHJ-13実行処理(新規のみ)
|
||
*
|
||
* ShjThirteenServiceを使用した契約台数追加処理
|
||
*
|
||
* @param object $contract
|
||
* @return array
|
||
*/
|
||
private function triggerShjThirteen($contract): array
|
||
{
|
||
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' => $contract->zone_id,
|
||
]);
|
||
|
||
try {
|
||
// 契約データ準備
|
||
$contractData = [
|
||
'contract_id' => $contract->contract_id,
|
||
'park_id' => $contract->park_id,
|
||
'psection_id' => $contract->psection_id,
|
||
'ptype_id' => $contract->ptype_id,
|
||
'zone_id' => $contract->zone_id,
|
||
];
|
||
|
||
// 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): 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'],
|
||
]);
|
||
|
||
// 共通処理「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 {
|
||
// TODO: OperatorQue モデルを使用したキューへの登録処理を実装
|
||
|
||
Log::info('SHJ-4B オペレーターキュー登録処理実行', [
|
||
'settlement_transaction_id' => $settlement->settlement_transaction_id,
|
||
'contract_id' => $contract->contract_id,
|
||
'amount_comparison' => $amountResult['comparison'],
|
||
'difference' => $amountResult['difference'],
|
||
]);
|
||
|
||
return [
|
||
'registered' => true,
|
||
'method' => 'placeholder',
|
||
'message' => 'オペレーターキュー登録処理は実装予定です',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 【処理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 メール送信結果コメント(例: "/メール正常終了件数:1" or "/メール異常終了件数:1、{error_info}")
|
||
* @return void
|
||
*/
|
||
private function createBatchLog(
|
||
SettlementTransaction $settlement,
|
||
$contract = null,
|
||
?array $amountResult = null,
|
||
bool $isSuccess = true,
|
||
?string $errorMessage = null,
|
||
?string $mailCommentSuffix = null
|
||
): void {
|
||
try {
|
||
$device = Device::orderBy('device_id')->first();
|
||
$deviceId = $device ? $device->device_id : 1;
|
||
$today = now()->format('Y/m/d');
|
||
|
||
// ステータスコメント生成(内部変数.バッチコメント)
|
||
if ($errorMessage) {
|
||
// エラー時
|
||
$statusComment = "支払いステータスチェック:エラー(決済トランザクションID:{$settlement->settlement_transaction_id}) - {$errorMessage}";
|
||
} elseif ($amountResult) {
|
||
// 正常処理時
|
||
$contractId = $contract ? $contract->contract_id : 'N/A';
|
||
|
||
switch ($amountResult['comparison']) {
|
||
case self::AMOUNT_MATCH:
|
||
$statusComment = "支払いステータスチェック:OK(決済トランザクションID:{$settlement->settlement_transaction_id}、契約ID:{$contractId}、金額一致)";
|
||
break;
|
||
case self::AMOUNT_SHORTAGE:
|
||
$statusComment = "支払いステータスチェック:請求金額より授受金額が少ないです(決済トランザクションID:{$settlement->settlement_transaction_id}、契約ID:{$contractId}、差額:{$amountResult['difference']}円)";
|
||
break;
|
||
case self::AMOUNT_EXCESS:
|
||
$statusComment = "支払いステータスチェック:請求金額より授受金額が多いです(決済トランザクションID:{$settlement->settlement_transaction_id}、契約ID:{$contractId}、差額:{$amountResult['difference']}円)";
|
||
break;
|
||
default:
|
||
$statusComment = "支払いステータスチェック:処理完了(決済トランザクションID:{$settlement->settlement_transaction_id}、契約ID:{$contractId})";
|
||
}
|
||
} else {
|
||
// その他のケース(対象なし、登録済み等)
|
||
$contractId = $contract ? $contract->contract_id : 'N/A';
|
||
$statusComment = "支払いステータスチェック:処理完了(決済トランザクションID:{$settlement->settlement_transaction_id}、契約ID:{$contractId})";
|
||
}
|
||
|
||
// メール送信結果をバッチコメントに追加
|
||
if ($mailCommentSuffix) {
|
||
$statusComment .= $mailCommentSuffix;
|
||
}
|
||
|
||
// SHJ-8サービス呼び出し
|
||
$this->shjEightService->execute(
|
||
$deviceId,
|
||
'SHJ-4B',
|
||
'SHJ-4支払いステータスチェック',
|
||
'success',
|
||
$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,
|
||
]);
|
||
}
|
||
}
|
||
}
|