fix:E-Sign 서명 페이지 Blade/JSX 충돌 수정 및 내부 API 전환
- sign.blade.php: style={{}} → STYLES 상수로 Blade {{ }} 파싱 충돌 해결
- sign.blade.php, done.blade.php: 외부 API 호출 → MNG 내부 엔드포인트로 변경
- EsignPublicController: submitSignature, rejectContract, downloadDocument 추가
- routes/web.php: submit, reject, document API 라우트 추가
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,10 @@
|
||||
use App\Models\ESign\EsignSigner;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class EsignPublicController extends Controller
|
||||
{
|
||||
@@ -178,6 +180,134 @@ public function verifyOtp(Request $request, string $token): JsonResponse
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 서명 제출
|
||||
*/
|
||||
public function submitSignature(Request $request, string $token): JsonResponse
|
||||
{
|
||||
$signer = $this->findSigner($token);
|
||||
if (! $signer) {
|
||||
return response()->json(['success' => false, 'message' => '유효하지 않은 서명 링크입니다.'], 404);
|
||||
}
|
||||
|
||||
if (! in_array($signer->status, ['authenticated', 'viewing'])) {
|
||||
return response()->json(['success' => false, 'message' => '서명할 수 없는 상태입니다.'], 400);
|
||||
}
|
||||
|
||||
$contract = EsignContract::withoutGlobalScopes()->find($signer->contract_id);
|
||||
if (! $contract || ! in_array($contract->status, ['pending', 'partially_signed'])) {
|
||||
return response()->json(['success' => false, 'message' => '서명할 수 없는 계약 상태입니다.'], 400);
|
||||
}
|
||||
|
||||
$signatureImage = $request->input('signature_image');
|
||||
if (! $signatureImage) {
|
||||
return response()->json(['success' => false, 'message' => '서명 이미지가 필요합니다.'], 422);
|
||||
}
|
||||
|
||||
// 서명 이미지 저장
|
||||
$imageData = base64_decode($signatureImage);
|
||||
$imagePath = "esign/{$contract->tenant_id}/signatures/{$contract->id}_{$signer->id}_" . time() . '.png';
|
||||
Storage::disk('local')->put($imagePath, $imageData);
|
||||
|
||||
// 서명자 상태 업데이트
|
||||
$signer->update([
|
||||
'signature_image_path' => $imagePath,
|
||||
'signed_at' => now(),
|
||||
'consent_agreed_at' => now(),
|
||||
'sign_ip_address' => $request->ip(),
|
||||
'sign_user_agent' => $request->userAgent(),
|
||||
'status' => 'signed',
|
||||
]);
|
||||
|
||||
// 감사 로그
|
||||
EsignAuditLog::create([
|
||||
'tenant_id' => $contract->tenant_id,
|
||||
'contract_id' => $contract->id,
|
||||
'signer_id' => $signer->id,
|
||||
'action' => 'signed',
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
// 모든 서명자가 서명 완료했는지 확인
|
||||
$allSigners = EsignSigner::withoutGlobalScopes()
|
||||
->where('contract_id', $contract->id)
|
||||
->get();
|
||||
$allSigned = $allSigners->every(fn ($s) => $s->status === 'signed');
|
||||
|
||||
if ($allSigned) {
|
||||
$contract->update([
|
||||
'status' => 'completed',
|
||||
'completed_at' => now(),
|
||||
]);
|
||||
} else {
|
||||
$contract->update(['status' => 'partially_signed']);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => '서명이 완료되었습니다.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 계약 거절
|
||||
*/
|
||||
public function rejectContract(Request $request, string $token): JsonResponse
|
||||
{
|
||||
$signer = $this->findSigner($token);
|
||||
if (! $signer) {
|
||||
return response()->json(['success' => false, 'message' => '유효하지 않은 서명 링크입니다.'], 404);
|
||||
}
|
||||
|
||||
$reason = $request->input('reason', '');
|
||||
$contract = EsignContract::withoutGlobalScopes()->find($signer->contract_id);
|
||||
|
||||
$signer->update([
|
||||
'status' => 'rejected',
|
||||
'rejected_reason' => $reason,
|
||||
]);
|
||||
|
||||
$contract->update(['status' => 'rejected']);
|
||||
|
||||
EsignAuditLog::create([
|
||||
'tenant_id' => $contract->tenant_id,
|
||||
'contract_id' => $contract->id,
|
||||
'signer_id' => $signer->id,
|
||||
'action' => 'rejected',
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'metadata' => ['reason' => $reason],
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
return response()->json(['success' => true, 'message' => '서명이 거절되었습니다.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 계약 문서 다운로드
|
||||
*/
|
||||
public function downloadDocument(string $token): StreamedResponse|JsonResponse
|
||||
{
|
||||
$signer = $this->findSigner($token);
|
||||
if (! $signer) {
|
||||
return response()->json(['success' => false, 'message' => '유효하지 않은 서명 링크입니다.'], 404);
|
||||
}
|
||||
|
||||
$contract = EsignContract::withoutGlobalScopes()->find($signer->contract_id);
|
||||
if (! $contract || ! $contract->original_file_path) {
|
||||
return response()->json(['success' => false, 'message' => '문서를 찾을 수 없습니다.'], 404);
|
||||
}
|
||||
|
||||
if (! Storage::disk('local')->exists($contract->original_file_path)) {
|
||||
return response()->json(['success' => false, 'message' => '문서 파일이 존재하지 않습니다.'], 404);
|
||||
}
|
||||
|
||||
$fileName = $contract->original_file_name ?: ($contract->title . '.pdf');
|
||||
|
||||
return Storage::disk('local')->download($contract->original_file_path, $fileName, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
]);
|
||||
}
|
||||
|
||||
// ─── Private ───
|
||||
|
||||
private function findSigner(string $token): ?EsignSigner
|
||||
|
||||
Reference in New Issue
Block a user