From 0c8c9f67c6923d2063683ee6509fbec7a9af89aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Thu, 12 Feb 2026 07:40:52 +0900 Subject: [PATCH] =?UTF-8?q?docs:E-Sign=20=EA=B8=B0=EC=88=A0=EC=84=A4?= =?UTF-8?q?=EA=B3=84=20=EB=AC=B8=EC=84=9C=20+=20=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EB=93=9C=20PPTX=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- projects/e-sign/esign-storyboard.pptx | Bin 0 -> 536133 bytes projects/e-sign/storyboard-config.json | 302 ++++++++ projects/e-sign/technical-design.md | 950 +++++++++++++++++++++++++ 3 files changed, 1252 insertions(+) create mode 100644 projects/e-sign/esign-storyboard.pptx create mode 100644 projects/e-sign/storyboard-config.json create mode 100644 projects/e-sign/technical-design.md diff --git a/projects/e-sign/esign-storyboard.pptx b/projects/e-sign/esign-storyboard.pptx new file mode 100644 index 0000000000000000000000000000000000000000..a407e0f8ea7a102e64241f8354aa04028b309086 GIT binary patch literal 536133 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¬0U{c1 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 alveZ|V0aiC`x*G}_dfszzaM-zy!#)4C", "fontSize": 8, "align": "center"} + ] + }, + { + "taskName": "계약 생성", + "route": "/esign/create", + "screenName": "μƒˆ 계약 생성", + "screenId": "ES_CREATE", + "descriptions": [ + { "title": "계약 정보 μž…λ ₯", "content": "계약 제λͺ©(ν•„μˆ˜), μ„€λͺ…(선택), μ„œλͺ… μˆœμ„œ 선택(μƒλŒ€λ°© λ¨Όμ €/μž‘μ„±μž λ¨Όμ €), μ„œλͺ… κΈ°ν•œ(κΈ°λ³Έ 7일)", "markerX": 1.8, "markerY": 1.5 }, + { "title": "PDF 파일 μ—…λ‘œλ“œ", "content": "λ“œλž˜κ·Έ&λ“œλ‘­ μ˜μ—­μœΌλ‘œ PDF μ—…λ‘œλ“œ. μ΅œλŒ€ 20MB. μ—…λ‘œλ“œ μ‹œ SHA-256 ν•΄μ‹œ μžλ™ 생성", "markerX": 1.8, "markerY": 2.6 }, + { "title": "μž‘μ„±μž(κ°‘) 정보", "content": "이름, 이메일(ν•„μˆ˜), μ „ν™”λ²ˆν˜Έ(선택). 둜그인 μ‚¬μš©μž 정보 μžλ™ μž…λ ₯", "markerX": 1.8, "markerY": 3.4 }, + { "title": "μƒλŒ€λ°©(을) 정보", "content": "이름, 이메일(ν•„μˆ˜), μ „ν™”λ²ˆν˜Έ(선택). μ„œλͺ… μš”μ²­ 이메일 λ°œμ†‘ λŒ€μƒ. API: POST /api/v1/esign/contracts", "markerX": 4.5, "markerY": 3.4 } + ], + "wireframeElements": [ + {"type": "rect", "x": 1.6, "y": 1.3, "w": 5.4, "h": 0.35, "fill": "1e293b", "text": "μƒˆ 계약 생성", "color": "FFFFFF", "fontSize": 12, "bold": true, "align": "left"}, + {"type": "rect", "x": 1.6, "y": 1.8, "w": 5.4, "h": 0.6, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 1.85, "w": 1.0, "h": 0.2, "text": "계약 제λͺ© *", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.8, "y": 1.85, "w": 4.1, "h": 0.25, "fill": "f1f5f9", "text": "계약 제λͺ©μ„ μž…λ ₯ν•˜μ„Έμš”", "fontSize": 8, "color": "94a3b8", "align": "left"}, + {"type": "rect", "x": 1.7, "y": 2.15, "w": 1.0, "h": 0.2, "text": "μ„œλͺ… μˆœμ„œ", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.8, "y": 2.15, "w": 2.0, "h": 0.25, "fill": "f1f5f9", "text": "μƒλŒ€λ°© λ¨Όμ € β–Ό", "fontSize": 8, "align": "left"}, + {"type": "rect", "x": 5.0, "y": 2.15, "w": 0.8, "h": 0.2, "text": "μ„œλͺ… κΈ°ν•œ", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 5.9, "y": 2.15, "w": 1.0, "h": 0.25, "fill": "f1f5f9", "text": "7일 β–Ό", "fontSize": 8, "align": "center"}, + {"type": "rect", "x": 1.6, "y": 2.55, "w": 5.4, "h": 0.7, "fill": "f1f5f9"}, + {"type": "rect", "x": 3.3, "y": 2.7, "w": 2.0, "h": 0.2, "text": "PDF νŒŒμΌμ„ 여기에 λ“œλž˜κ·Έν•˜μ„Έμš”", "fontSize": 8, "color": "64748b", "align": "center"}, + {"type": "rect", "x": 3.7, "y": 2.95, "w": 1.2, "h": 0.25, "fill": "0d9488", "text": "파일 선택", "color": "FFFFFF", "fontSize": 8, "align": "center"}, + {"type": "rect", "x": 1.6, "y": 3.4, "w": 2.6, "h": 1.2, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 3.45, "w": 2.4, "h": 0.25, "fill": "e0f2fe", "text": "μž‘μ„±μž (κ°‘) 정보", "fontSize": 8, "bold": true, "align": "center"}, + {"type": "rect", "x": 1.7, "y": 3.75, "w": 0.6, "h": 0.18, "text": "이름 *", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 2.4, "y": 3.75, "w": 1.7, "h": 0.2, "fill": "f1f5f9", "text": "κΉ€κ°‘μˆœ", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 1.7, "y": 4.0, "w": 0.6, "h": 0.18, "text": "이메일 *", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 2.4, "y": 4.0, "w": 1.7, "h": 0.2, "fill": "f1f5f9", "text": "kim@sam.kr", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 1.7, "y": 4.25, "w": 0.6, "h": 0.18, "text": "μ „ν™”λ²ˆν˜Έ", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 2.4, "y": 4.25, "w": 1.7, "h": 0.2, "fill": "f1f5f9", "text": "010-1234-5678", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 4.4, "y": 3.4, "w": 2.6, "h": 1.2, "fill": "FFFFFF"}, + {"type": "rect", "x": 4.5, "y": 3.45, "w": 2.4, "h": 0.25, "fill": "fee2e2", "text": "μƒλŒ€λ°© (을) 정보", "fontSize": 8, "bold": true, "align": "center"}, + {"type": "rect", "x": 4.5, "y": 3.75, "w": 0.6, "h": 0.18, "text": "이름 *", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 5.2, "y": 3.75, "w": 1.7, "h": 0.2, "fill": "f1f5f9", "text": "", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 4.5, "y": 4.0, "w": 0.6, "h": 0.18, "text": "이메일 *", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 5.2, "y": 4.0, "w": 1.7, "h": 0.2, "fill": "f1f5f9", "text": "", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 5.6, "y": 4.7, "w": 1.4, "h": 0.35, "fill": "0d9488", "text": "계약 생성", "color": "FFFFFF", "fontSize": 9, "bold": true, "align": "center"} + ] + }, + { + "taskName": "계약 상세", + "route": "/esign/{id}", + "screenName": "계약 상세", + "screenId": "ES_DETAIL", + "descriptions": [ + { "title": "계약 κΈ°λ³Έ 정보", "content": "κ³„μ•½μ½”λ“œ, 제λͺ©, μ„€λͺ…, μ„œλͺ… μˆœμ„œ, κΈ°ν•œ, μƒνƒœ λ°°μ§€ ν‘œμ‹œ. μƒνƒœλ³„ 색상 ꡬ뢄", "markerX": 1.8, "markerY": 1.5 }, + { "title": "μ„œλͺ…μž ν˜„ν™© μΉ΄λ“œ", "content": "μž‘μ„±μž(κ°‘)/μƒλŒ€λ°©(을) 각각의 μ„œλͺ… μƒνƒœ, 인증 μ‹œκ°, μ„œλͺ… μ‹œκ° ν‘œμ‹œ. ν”„λ‘œκ·Έλ ˆμŠ€ λ°”λ‘œ μ§„ν–‰λ₯  μ‹œκ°ν™”", "markerX": 1.8, "markerY": 2.5 }, + { "title": "감사 좔적 νƒ€μž„λΌμΈ", "content": "계약 μƒμ„±β†’λ°œμ†‘β†’μ—΄λžŒβ†’μΈμ¦β†’μ„œλͺ… λ“± λͺ¨λ“  이벀트λ₯Ό νƒ€μž„λΌμΈ ν˜•νƒœλ‘œ ν‘œμ‹œ. IP, μ‹œκ° 포함", "markerX": 1.8, "markerY": 3.5 }, + { "title": "μ•‘μ…˜ λ²„νŠΌ μ˜μ—­", "content": "μƒνƒœμ— 따라 λ°œμ†‘/λ¦¬λ§ˆμΈλ”/μ·¨μ†Œ/λ‹€μš΄λ‘œλ“œ/검증 λ²„νŠΌ 동적 ν‘œμ‹œ", "markerX": 5.2, "markerY": 1.5 } + ], + "wireframeElements": [ + {"type": "rect", "x": 1.6, "y": 1.3, "w": 3.8, "h": 0.35, "fill": "1e293b", "text": "계약 상세 - ES-20260212-A3F", "color": "FFFFFF", "fontSize": 11, "bold": true, "align": "left"}, + {"type": "rect", "x": 5.5, "y": 1.3, "w": 0.55, "h": 0.3, "fill": "3b82f6", "text": "λ°œμ†‘", "color": "FFFFFF", "fontSize": 8, "align": "center"}, + {"type": "rect", "x": 6.1, "y": 1.3, "w": 0.55, "h": 0.3, "fill": "f59e0b", "text": "λ¦¬λ§ˆμΈλ”", "color": "FFFFFF", "fontSize": 7, "align": "center"}, + {"type": "rect", "x": 6.7, "y": 1.3, "w": 0.3, "h": 0.3, "fill": "dc2626", "text": "μ·¨μ†Œ", "color": "FFFFFF", "fontSize": 7, "align": "center"}, + {"type": "rect", "x": 1.6, "y": 1.8, "w": 5.4, "h": 0.6, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 1.85, "w": 0.6, "h": 0.18, "text": "제λͺ©", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.4, "y": 1.85, "w": 2.5, "h": 0.18, "text": "μ†Œν”„νŠΈμ›¨μ–΄ 개발 μš©μ—­ κ³„μ•½μ„œ", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 5.3, "y": 1.85, "w": 0.3, "h": 0.18, "text": "μƒνƒœ", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 5.7, "y": 1.82, "w": 0.7, "h": 0.22, "fill": "dbeafe", "text": "μ„œλͺ…λŒ€κΈ°", "fontSize": 7, "color": "1d4ed8", "align": "center"}, + {"type": "rect", "x": 1.7, "y": 2.1, "w": 0.6, "h": 0.18, "text": "κΈ°ν•œ", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.4, "y": 2.1, "w": 1.5, "h": 0.18, "text": "2026-02-19 (D-7)", "fontSize": 8, "color": "dc2626", "align": "left"}, + {"type": "rect", "x": 1.6, "y": 2.55, "w": 2.6, "h": 0.8, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 2.6, "w": 2.4, "h": 0.22, "fill": "e0f2fe", "text": "μž‘μ„±μž (κ°‘) - κΉ€κ°‘μˆœ", "fontSize": 8, "bold": true, "align": "center"}, + {"type": "rect", "x": 1.7, "y": 2.85, "w": 0.8, "h": 0.15, "text": "μƒνƒœ: λŒ€κΈ°μ€‘", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 4.4, "y": 2.55, "w": 2.6, "h": 0.8, "fill": "FFFFFF"}, + {"type": "rect", "x": 4.5, "y": 2.6, "w": 2.4, "h": 0.22, "fill": "fee2e2", "text": "μƒλŒ€λ°© (을) - 박을동", "fontSize": 8, "bold": true, "align": "center"}, + {"type": "rect", "x": 4.5, "y": 2.85, "w": 1.2, "h": 0.15, "text": "μƒνƒœ: 이메일 λ°œμ†‘λ¨", "fontSize": 7, "color": "f59e0b", "align": "left"}, + {"type": "rect", "x": 1.6, "y": 3.5, "w": 5.4, "h": 0.25, "fill": "1e293b", "text": " 감사 좔적 둜그 (Audit Trail)", "color": "FFFFFF", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 1.6, "y": 3.75, "w": 5.4, "h": 0.2, "fill": "FFFFFF", "text": " 02-12 10:00 계약 생성 κΉ€κ°‘μˆœ 192.168.1.100", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 1.6, "y": 3.95, "w": 5.4, "h": 0.2, "fill": "f8fafc", "text": " 02-12 10:05 μ„œλͺ… μš”μ²­ λ°œμ†‘ μ‹œμŠ€ν…œ -", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 1.6, "y": 4.15, "w": 5.4, "h": 0.2, "fill": "FFFFFF", "text": " 02-12 11:20 μ„œλͺ… 링크 접속 박을동 121.xxx.xxx.55", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 1.6, "y": 4.35, "w": 5.4, "h": 0.2, "fill": "f8fafc", "text": " 02-12 11:21 OTP 인증 μ™„λ£Œ 박을동 121.xxx.xxx.55", "fontSize": 7, "align": "left"} + ] + }, + { + "taskName": "μ„œλͺ… μœ„μΉ˜ μ§€μ •", + "route": "/esign/{id}/fields", + "screenName": "μ„œλͺ… μœ„μΉ˜ μ§€μ •", + "screenId": "ES_FIELDS", + "descriptions": [ + { "title": "PDF λ·°μ–΄ (PDF.js)", "content": "원본 PDFλ₯Ό λΈŒλΌμš°μ €μ—μ„œ λ Œλ”λ§. νŽ˜μ΄μ§€ λ„€λΉ„κ²Œμ΄μ…˜ 제곡. ν™•λŒ€/μΆ•μ†Œ κ°€λŠ₯", "markerX": 1.8, "markerY": 1.8 }, + { "title": "μ„œλͺ… ν•„λ“œ μΆ”κ°€", "content": "μ„œλͺ…μž 선택 ν›„ PDF μœ„μ— ν΄λ¦­ν•˜μ—¬ ν•„λ“œ μΆ”κ°€. νƒ€μž…: μ„œλͺ…/도μž₯/ν…μŠ€νŠΈ/λ‚ μ§œ/μ²΄ν¬λ°•μŠ€", "markerX": 2.5, "markerY": 3.0 }, + { "title": "ν•„λ“œ 속성 νŒ¨λ„", "content": "μš°μΈ‘μ— μ„ νƒλœ ν•„λ“œμ˜ 속성 ν‘œμ‹œ. ν•„λ“œ νƒ€μž…, 라벨, ν•„μˆ˜ μ—¬λΆ€, μ’Œν‘œκ°’(%) νŽΈμ§‘", "markerX": 5.5, "markerY": 2.5 }, + { "title": "μ €μž₯ λ²„νŠΌ", "content": "μ„€μ • μ™„λ£Œ ν›„ μ €μž₯ β†’ API: POST /api/v1/esign/contracts/{id}/fields", "markerX": 5.5, "markerY": 4.5 } + ], + "wireframeElements": [ + {"type": "rect", "x": 1.6, "y": 1.3, "w": 5.4, "h": 0.35, "fill": "1e293b", "text": "μ„œλͺ… μœ„μΉ˜ μ§€μ • - μ†Œν”„νŠΈμ›¨μ–΄ 개발 μš©μ—­ κ³„μ•½μ„œ", "color": "FFFFFF", "fontSize": 10, "bold": true, "align": "left"}, + {"type": "rect", "x": 1.6, "y": 1.8, "w": 3.8, "h": 3.0, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 1.85, "w": 3.6, "h": 0.3, "fill": "f1f5f9", "text": "β—€ 1 / 3 νŽ˜μ΄μ§€ β–Ά πŸ” 100%", "fontSize": 8, "align": "center"}, + {"type": "rect", "x": 1.8, "y": 2.25, "w": 3.4, "h": 2.3, "fill": "f8fafc"}, + {"type": "rect", "x": 2.0, "y": 2.4, "w": 3.0, "h": 0.15, "fill": "e2e8f0", "text": "제 1 μ‘° (λͺ©μ )", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 2.0, "y": 2.6, "w": 3.0, "h": 0.08, "fill": "e2e8f0"}, + {"type": "rect", "x": 2.0, "y": 2.72, "w": 3.0, "h": 0.08, "fill": "e2e8f0"}, + {"type": "rect", "x": 2.2, "y": 3.7, "w": 1.2, "h": 0.5, "fill": "bfdbfe", "text": "κ°‘ μ„œλͺ…\n(ν΄λ¦­ν•˜μ—¬ 이동)", "fontSize": 7, "color": "1d4ed8", "align": "center"}, + {"type": "rect", "x": 3.8, "y": 3.7, "w": 1.2, "h": 0.5, "fill": "fecaca", "text": "을 μ„œλͺ…\n(ν΄λ¦­ν•˜μ—¬ 이동)", "fontSize": 7, "color": "dc2626", "align": "center"}, + {"type": "rect", "x": 5.6, "y": 1.8, "w": 1.4, "h": 0.3, "fill": "1e293b", "text": "ν•„λ“œ 속성", "color": "FFFFFF", "fontSize": 8, "bold": true, "align": "center"}, + {"type": "rect", "x": 5.6, "y": 2.1, "w": 1.4, "h": 2.0, "fill": "FFFFFF"}, + {"type": "rect", "x": 5.65, "y": 2.15, "w": 0.5, "h": 0.15, "text": "μ„œλͺ…μž", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 6.2, "y": 2.15, "w": 0.75, "h": 0.18, "fill": "f1f5f9", "text": "μž‘μ„±μž(κ°‘)", "fontSize": 7, "align": "center"}, + {"type": "rect", "x": 5.65, "y": 2.4, "w": 0.5, "h": 0.15, "text": "νƒ€μž…", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 6.2, "y": 2.4, "w": 0.75, "h": 0.18, "fill": "f1f5f9", "text": "μ„œλͺ… β–Ό", "fontSize": 7, "align": "center"}, + {"type": "rect", "x": 5.65, "y": 2.65, "w": 0.5, "h": 0.15, "text": "라벨", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 6.2, "y": 2.65, "w": 0.75, "h": 0.18, "fill": "f1f5f9", "text": "κ°‘ μ„œλͺ…", "fontSize": 7, "align": "center"}, + {"type": "rect", "x": 5.65, "y": 2.9, "w": 0.5, "h": 0.15, "text": "ν•„μˆ˜", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 6.2, "y": 2.9, "w": 0.75, "h": 0.18, "text": "β˜‘ ν•„μˆ˜ ν•­λͺ©", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 5.6, "y": 4.2, "w": 0.65, "h": 0.3, "fill": "e2e8f0", "text": "+ κ°‘ ν•„λ“œ", "fontSize": 7, "color": "1d4ed8", "align": "center"}, + {"type": "rect", "x": 6.3, "y": 4.2, "w": 0.65, "h": 0.3, "fill": "e2e8f0", "text": "+ 을 ν•„λ“œ", "fontSize": 7, "color": "dc2626", "align": "center"}, + {"type": "rect", "x": 5.6, "y": 4.6, "w": 1.4, "h": 0.35, "fill": "0d9488", "text": "ν•„λ“œ μ„€μ • μ €μž₯", "color": "FFFFFF", "fontSize": 9, "bold": true, "align": "center"} + ] + }, + { + "taskName": "μ„œλͺ… μš”μ²­ λ°œμ†‘", + "route": "/esign/{id}/send", + "screenName": "μ„œλͺ… μš”μ²­ λ°œμ†‘", + "screenId": "ES_SEND", + "descriptions": [ + { "title": "λ°œμ†‘ μ „ 체크리슀트", "content": "μ„œλͺ… ν•„λ“œ μ„€μ • μ—¬λΆ€, μ„œλͺ…μž 정보 μ™„λ£Œ μ—¬λΆ€, PDF 무결성 검증 κ²°κ³Όλ₯Ό 체크리슀트둜 ν‘œμ‹œ", "markerX": 1.8, "markerY": 1.8 }, + { "title": "μ„œλͺ… μˆœμ„œ 확인", "content": "μ„€μ •λœ μ„œλͺ… μˆœμ„œμ™€ 각 μ„œλͺ…μž 정보λ₯Ό μ‹œκ°μ μœΌλ‘œ 확인. μˆœμ„œ λ³€κ²½ λΆˆκ°€ (생성 μ‹œ κ²°μ •)", "markerX": 1.8, "markerY": 2.8 }, + { "title": "λ°œμ†‘ 확인 λ²„νŠΌ", "content": "μ΅œμ’… 확인 ν›„ λ°œμ†‘ β†’ μƒνƒœ draftβ†’pending, 첫 μ„œλͺ…μžμ—κ²Œ 이메일 λ°œμ†‘", "markerX": 4.0, "markerY": 4.3 } + ], + "wireframeElements": [ + {"type": "rect", "x": 1.6, "y": 1.3, "w": 5.4, "h": 0.35, "fill": "1e293b", "text": "μ„œλͺ… μš”μ²­ λ°œμ†‘", "color": "FFFFFF", "fontSize": 12, "bold": true, "align": "left"}, + {"type": "rect", "x": 1.6, "y": 1.8, "w": 5.4, "h": 0.8, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 1.85, "w": 2.0, "h": 0.22, "text": "λ°œμ†‘ μ „ 확인사항", "fontSize": 9, "bold": true, "align": "left"}, + {"type": "rect", "x": 1.8, "y": 2.1, "w": 4.0, "h": 0.15, "text": "βœ… μ„œλͺ… ν•„λ“œκ°€ μ„€μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€ (κ°‘: 1개, 을: 1개)", "fontSize": 8, "color": "16a34a", "align": "left"}, + {"type": "rect", "x": 1.8, "y": 2.28, "w": 4.0, "h": 0.15, "text": "βœ… μ„œλͺ…μž 정보가 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€", "fontSize": 8, "color": "16a34a", "align": "left"}, + {"type": "rect", "x": 1.8, "y": 2.46, "w": 4.0, "h": 0.15, "text": "βœ… PDF λ¬Έμ„œ 무결성이 ν™•μΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€ (SHA-256)", "fontSize": 8, "color": "16a34a", "align": "left"}, + {"type": "rect", "x": 1.6, "y": 2.8, "w": 5.4, "h": 1.3, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 2.85, "w": 2.0, "h": 0.22, "text": "μ„œλͺ… μˆœμ„œ", "fontSize": 9, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.0, "y": 3.15, "w": 1.8, "h": 0.7, "fill": "fee2e2"}, + {"type": "rect", "x": 2.1, "y": 3.2, "w": 0.3, "h": 0.25, "fill": "dc2626", "text": "1", "color": "FFFFFF", "fontSize": 10, "bold": true, "align": "center"}, + {"type": "rect", "x": 2.5, "y": 3.2, "w": 1.2, "h": 0.18, "text": "μƒλŒ€λ°© (을)", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.5, "y": 3.42, "w": 1.2, "h": 0.15, "text": "박을동", "fontSize": 8, "align": "left"}, + {"type": "rect", "x": 2.5, "y": 3.6, "w": 1.2, "h": 0.15, "text": "park@corp.com", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 4.0, "y": 3.45, "w": 0.6, "h": 0.2, "text": "β†’", "fontSize": 14, "color": "64748b", "align": "center"}, + {"type": "rect", "x": 4.8, "y": 3.15, "w": 1.8, "h": 0.7, "fill": "e0f2fe"}, + {"type": "rect", "x": 4.9, "y": 3.2, "w": 0.3, "h": 0.25, "fill": "3b82f6", "text": "2", "color": "FFFFFF", "fontSize": 10, "bold": true, "align": "center"}, + {"type": "rect", "x": 5.3, "y": 3.2, "w": 1.2, "h": 0.18, "text": "μž‘μ„±μž (κ°‘)", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 5.3, "y": 3.42, "w": 1.2, "h": 0.15, "text": "κΉ€κ°‘μˆœ", "fontSize": 8, "align": "left"}, + {"type": "rect", "x": 5.3, "y": 3.6, "w": 1.2, "h": 0.15, "text": "kim@sam.kr", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.5, "y": 4.3, "w": 3.5, "h": 0.4, "fill": "0d9488", "text": "μ„œλͺ… μš”μ²­ λ°œμ†‘", "color": "FFFFFF", "fontSize": 11, "bold": true, "align": "center"} + ] + }, + { + "taskName": "본인인증 (OTP)", + "route": "/esign/sign/{token}", + "screenName": "본인인증 (μ„œλͺ…μžμš©)", + "screenId": "ES_AUTH", + "descriptions": [ + { "title": "계약 정보 확인", "content": "계약 제λͺ©, μ„œλͺ…μž 이름/이메일, μ„œλͺ… κΈ°ν•œ ν‘œμ‹œ. 토큰 기반 쑰회 (λΉ„λ‘œκ·ΈμΈ)", "markerX": 2.8, "markerY": 1.8 }, + { "title": "OTP λ°œμ†‘ λ²„νŠΌ", "content": "λ“±λ‘λœ μ΄λ©”μΌλ‘œ 6자리 인증 μ½”λ“œ λ°œμ†‘. 5λΆ„ 유효, μ΅œλŒ€ 5회 μ‹œλ„", "markerX": 2.8, "markerY": 3.0 }, + { "title": "OTP μž…λ ₯ 폼", "content": "6자리 숫자 μž…λ ₯ (λŒ€ν˜• 폰트). 인증 성곡 μ‹œ sign_session_token λ°œκΈ‰ β†’ μ„œλͺ… ν™”λ©΄ 이동", "markerX": 2.8, "markerY": 3.7 } + ], + "wireframeElements": [ + {"type": "rect", "x": 2.3, "y": 1.15, "w": 4.0, "h": 0.4, "text": "SAM E-Sign", "fontSize": 16, "bold": true, "align": "center"}, + {"type": "rect", "x": 2.3, "y": 1.5, "w": 4.0, "h": 0.2, "text": "μ „μžκ³„μ•½ μ„œλͺ…", "fontSize": 9, "color": "64748b", "align": "center"}, + {"type": "rect", "x": 2.5, "y": 1.9, "w": 3.6, "h": 3.0, "fill": "FFFFFF"}, + {"type": "rect", "x": 2.6, "y": 1.95, "w": 3.4, "h": 0.25, "text": "계약 정보 확인", "fontSize": 10, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.6, "y": 2.25, "w": 3.4, "h": 0.35, "fill": "f8fafc"}, + {"type": "rect", "x": 2.7, "y": 2.27, "w": 0.8, "h": 0.12, "text": "계약 제λͺ©", "fontSize": 6, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.7, "y": 2.4, "w": 3.2, "h": 0.15, "text": "μ†Œν”„νŠΈμ›¨μ–΄ 개발 μš©μ—­ κ³„μ•½μ„œ", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.6, "y": 2.65, "w": 3.4, "h": 0.35, "fill": "f8fafc"}, + {"type": "rect", "x": 2.7, "y": 2.67, "w": 0.8, "h": 0.12, "text": "μ„œλͺ…μž", "fontSize": 6, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.7, "y": 2.8, "w": 3.2, "h": 0.15, "text": "박을동 (park@corp.com)", "fontSize": 8, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.6, "y": 3.05, "w": 3.4, "h": 0.35, "fill": "f8fafc"}, + {"type": "rect", "x": 2.7, "y": 3.07, "w": 0.8, "h": 0.12, "text": "μ„œλͺ… κΈ°ν•œ", "fontSize": 6, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.7, "y": 3.2, "w": 3.2, "h": 0.15, "text": "2026-02-19", "fontSize": 8, "bold": true, "color": "dc2626", "align": "left"}, + {"type": "rect", "x": 2.6, "y": 3.5, "w": 3.4, "h": 0.3, "text": "본인인증을 μœ„ν•΄ λ“±λ‘λœ μ΄λ©”μΌλ‘œ 인증 μ½”λ“œλ₯Ό λ°œμ†‘ν•©λ‹ˆλ‹€.", "fontSize": 8, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.6, "y": 3.85, "w": 3.4, "h": 0.4, "fill": "3b82f6", "text": "인증 μ½”λ“œ λ°œμ†‘", "color": "FFFFFF", "fontSize": 10, "bold": true, "align": "center"}, + {"type": "rect", "x": 2.6, "y": 4.35, "w": 3.4, "h": 0.2, "text": "OTP μž…λ ₯ ν™”λ©΄ (λ°œμ†‘ ν›„ μ „ν™˜)", "fontSize": 7, "color": "94a3b8", "align": "center"}, + {"type": "rect", "x": 3.2, "y": 4.55, "w": 2.2, "h": 0.3, "fill": "f1f5f9", "text": "4 8 2 9 1 7", "fontSize": 14, "align": "center"} + ] + }, + { + "taskName": "μ„œλͺ… μˆ˜ν–‰", + "route": "/esign/sign/{token}/sign", + "screenName": "μ „μžμ„œλͺ… μˆ˜ν–‰ (μ„œλͺ…μžμš©)", + "screenId": "ES_SIGN", + "descriptions": [ + { "title": "λ¬Έμ„œ 확인 단계", "content": "κ³„μ•½μ„œ PDF λ‹€μš΄λ‘œλ“œ 링크 제곡. λ™μ˜ μ²΄ν¬λ°•μŠ€λ‘œ μ „μžμ„œλͺ… 법적 효λ ₯ λ™μ˜ 확인", "markerX": 2.8, "markerY": 1.8 }, + { "title": "μ„œλͺ… μž…λ ₯ (SignaturePad)", "content": "μΊ”λ²„μŠ€μ— ν„°μΉ˜/마우슀둜 μ„œλͺ… μž…λ ₯. 'μ§€μš°κΈ°' λ²„νŠΌμœΌλ‘œ μ΄ˆκΈ°ν™”. 3단계: λ¬Έμ„œν™•μΈβ†’μ„œλͺ…→확인", "markerX": 2.8, "markerY": 2.8 }, + { "title": "μ„œλͺ… 확인/제좜", "content": "μž…λ ₯된 μ„œλͺ… 미리보기. 'λ‹€μ‹œ μ„œλͺ…' λ˜λŠ” 'μ„œλͺ… 제좜' 선택. base64 PNG둜 전솑", "markerX": 2.8, "markerY": 3.8 }, + { "title": "거절 κΈ°λŠ₯", "content": "상단 '거절' λ²„νŠΌμœΌλ‘œ μ„œλͺ… 거절 κ°€λŠ₯. 거절 μ‚¬μœ  μž…λ ₯ ν•„μˆ˜", "markerX": 5.5, "markerY": 1.3 } + ], + "wireframeElements": [ + {"type": "rect", "x": 1.6, "y": 1.2, "w": 5.4, "h": 0.4, "fill": "FFFFFF"}, + {"type": "rect", "x": 1.7, "y": 1.25, "w": 3.0, "h": 0.15, "text": "μ†Œν”„νŠΈμ›¨μ–΄ 개발 μš©μ—­ κ³„μ•½μ„œ", "fontSize": 9, "bold": true, "align": "left"}, + {"type": "rect", "x": 1.7, "y": 1.42, "w": 2.0, "h": 0.12, "text": "박을동 λ‹˜μ˜ μ „μžμ„œλͺ…", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 6.2, "y": 1.25, "w": 0.7, "h": 0.28, "fill": "FFFFFF", "text": "거절", "fontSize": 8, "color": "dc2626", "align": "center"}, + {"type": "rect", "x": 2.3, "y": 1.8, "w": 4.0, "h": 1.5, "fill": "FFFFFF"}, + {"type": "rect", "x": 2.4, "y": 1.85, "w": 2.0, "h": 0.22, "text": "계약 λ¬Έμ„œ 확인", "fontSize": 10, "bold": true, "align": "left"}, + {"type": "rect", "x": 2.5, "y": 2.15, "w": 3.8, "h": 0.5, "fill": "f1f5f9"}, + {"type": "rect", "x": 3.5, "y": 2.2, "w": 1.5, "h": 0.15, "text": "PDF λ¬Έμ„œ", "fontSize": 8, "color": "64748b", "align": "center"}, + {"type": "rect", "x": 3.3, "y": 2.4, "w": 1.8, "h": 0.25, "fill": "3b82f6", "text": "λ¬Έμ„œ μ—΄κΈ° / λ‹€μš΄λ‘œλ“œ", "color": "FFFFFF", "fontSize": 8, "align": "center"}, + {"type": "rect", "x": 2.5, "y": 2.75, "w": 0.2, "h": 0.2, "fill": "3b82f6"}, + {"type": "rect", "x": 2.75, "y": 2.75, "w": 3.5, "h": 0.35, "text": "μœ„ κ³„μ•½μ„œμ˜ λ‚΄μš©μ„ ν™•μΈν•˜μ˜€μœΌλ©°, μ „μžμ„œλͺ…에\nλ™μ˜ν•©λ‹ˆλ‹€. μ „μžμ„œλͺ…은 법적 효λ ₯을 κ°€μ§‘λ‹ˆλ‹€.", "fontSize": 7, "align": "left"}, + {"type": "rect", "x": 2.3, "y": 3.35, "w": 4.0, "h": 1.5, "fill": "FFFFFF"}, + {"type": "rect", "x": 2.4, "y": 3.4, "w": 1.5, "h": 0.22, "text": "μ„œλͺ… μž…λ ₯", "fontSize": 10, "bold": true, "align": "left"}, + {"type": "rect", "x": 5.5, "y": 3.4, "w": 0.7, "h": 0.2, "text": "μ§€μš°κΈ°", "fontSize": 8, "color": "64748b", "align": "right"}, + {"type": "rect", "x": 2.4, "y": 3.65, "w": 3.0, "h": 0.15, "text": "μ•„λž˜ μ˜μ—­μ— μ„œλͺ…을 μž…λ ₯ν•΄ μ£Όμ„Έμš”.", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 2.5, "y": 3.85, "w": 3.6, "h": 0.9, "fill": "FFFFFF"}, + {"type": "rect", "x": 2.3, "y": 4.85, "w": 1.9, "h": 0.3, "fill": "e2e8f0", "text": "이전", "fontSize": 9, "align": "center"}, + {"type": "rect", "x": 4.3, "y": 4.85, "w": 2.0, "h": 0.3, "fill": "3b82f6", "text": "μ„œλͺ… 확인", "color": "FFFFFF", "fontSize": 9, "bold": true, "align": "center"} + ] + }, + { + "taskName": "μ„œλͺ… μ™„λ£Œ", + "route": "/esign/sign/{token}/done", + "screenName": "μ„œλͺ… μ™„λ£Œ (μ„œλͺ…μžμš©)", + "screenId": "ES_DONE", + "descriptions": [ + { "title": "μ™„λ£Œ μƒνƒœ ν‘œμ‹œ", "content": "μ„œλͺ… μ™„λ£Œ μ‹œ 녹색 체크 μ•„μ΄μ½˜ + λ©”μ‹œμ§€. λͺ¨λ“  μ„œλͺ… μ™„λ£Œ μ‹œ 계약 체결 μ•ˆλ‚΄", "markerX": 3.5, "markerY": 2.0 }, + { "title": "거절 μƒνƒœ ν‘œμ‹œ", "content": "μ„œλͺ… 거절 μ‹œ λΉ¨κ°„ X μ•„μ΄μ½˜ + λ©”μ‹œμ§€. 계약 λ‹΄λ‹Ήμž μ•Œλ¦Ό λ°œμ†‘ μ•ˆλ‚΄", "markerX": 3.5, "markerY": 2.8 }, + { "title": "계약 μš”μ•½ 정보", "content": "계약 제λͺ©, μ„œλͺ…μž 이름, μ„œλͺ… μΌμ‹œλ₯Ό μΉ΄λ“œ ν˜•νƒœλ‘œ ν‘œμ‹œ", "markerX": 3.5, "markerY": 3.5 } + ], + "wireframeElements": [ + {"type": "rect", "x": 2.8, "y": 1.2, "w": 3.0, "h": 0.35, "text": "SAM E-Sign", "fontSize": 16, "bold": true, "align": "center"}, + {"type": "rect", "x": 2.8, "y": 1.7, "w": 3.0, "h": 3.0, "fill": "FFFFFF"}, + {"type": "rect", "x": 3.8, "y": 1.85, "w": 0.8, "h": 0.8, "fill": "dcfce7"}, + {"type": "rect", "x": 3.95, "y": 2.0, "w": 0.5, "h": 0.5, "text": "βœ“", "fontSize": 22, "color": "16a34a", "bold": true, "align": "center"}, + {"type": "rect", "x": 2.9, "y": 2.75, "w": 2.8, "h": 0.25, "text": "μ„œλͺ…이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€", "fontSize": 12, "bold": true, "align": "center"}, + {"type": "rect", "x": 2.9, "y": 3.05, "w": 2.8, "h": 0.35, "text": "μ„œλͺ…이 μ •μƒμ μœΌλ‘œ μ ‘μˆ˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€.\nλ‹€λ₯Έ μ„œλͺ…μžμ˜ μ„œλͺ…이 μ™„λ£Œλ˜λ©΄\nμ•Œλ €λ“œλ¦¬κ² μŠ΅λ‹ˆλ‹€.", "fontSize": 8, "color": "64748b", "align": "center"}, + {"type": "rect", "x": 3.0, "y": 3.55, "w": 2.6, "h": 0.8, "fill": "f8fafc"}, + {"type": "rect", "x": 3.1, "y": 3.6, "w": 0.5, "h": 0.15, "text": "계약", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 4.0, "y": 3.6, "w": 1.5, "h": 0.15, "text": "μ†Œν”„νŠΈμ›¨μ–΄ 개발 μš©μ—­", "fontSize": 8, "bold": true, "align": "right"}, + {"type": "rect", "x": 3.1, "y": 3.8, "w": 0.5, "h": 0.15, "text": "μ„œλͺ…μž", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 4.0, "y": 3.8, "w": 1.5, "h": 0.15, "text": "박을동", "fontSize": 8, "bold": true, "align": "right"}, + {"type": "rect", "x": 3.1, "y": 4.0, "w": 0.5, "h": 0.15, "text": "μ„œλͺ…μΌμ‹œ", "fontSize": 7, "color": "64748b", "align": "left"}, + {"type": "rect", "x": 4.0, "y": 4.0, "w": 1.5, "h": 0.15, "text": "2026-02-12 11:23", "fontSize": 8, "bold": true, "align": "right"}, + {"type": "rect", "x": 3.2, "y": 4.55, "w": 2.2, "h": 0.2, "text": "SAM E-Sign μ „μžκ³„μ•½ μ„œλͺ… μ‹œμŠ€ν…œ", "fontSize": 7, "color": "94a3b8", "align": "center"} + ] + } + ] +} diff --git a/projects/e-sign/technical-design.md b/projects/e-sign/technical-design.md new file mode 100644 index 0000000..99f81e3 --- /dev/null +++ b/projects/e-sign/technical-design.md @@ -0,0 +1,950 @@ +# μ „μžκ³„μ•½ μ„œλͺ… μ†”λ£¨μ…˜ (E-Sign) - 기술 섀계 λ¬Έμ„œ + +> **ν”„λ‘œμ νŠΈλͺ…**: SAM E-Sign (κ°€μΉ­) +> **μž‘μ„±μΌ**: 2026-02-12 +> **버전**: v1.0 Draft +> **μž‘μ„±μž**: DX μΆ”μ§„νŒ€ + +--- + +## 1. ν”„λ‘œμ νŠΈ κ°œμš” + +### 1.1 λͺ©μ  + +λͺ¨λ‘μ‹ΈμΈκ³Ό μœ μ‚¬ν•œ **κ°„νŽΈ μ „μžκ³„μ•½ μ„œλͺ… μ†”λ£¨μ…˜**을 자체 κ΅¬μΆ•ν•œλ‹€. +두 λ‹Ήμ‚¬μž(계약 μƒμ„±μž A, μƒλŒ€λ°© B)κ°€ 온라인으둜 κ³„μ•½μ„œμ— μ„œλͺ…ν•˜κ³ , +μ„œλͺ… μ™„λ£Œλœ λ¬Έμ„œλ₯Ό 법적 효λ ₯이 μžˆλŠ” ν˜•νƒœλ‘œ λ³΄κ΄€ν•˜λŠ” μ‹œμŠ€ν…œμ΄λ‹€. + +### 1.2 핡심 κ°€μΉ˜ + +| κ°€μΉ˜ | μ„€λͺ… | +|------|------| +| **κ°„νŽΈν•¨** | PDF μ—…λ‘œλ“œ β†’ μ„œλͺ… μœ„μΉ˜ μ§€μ • β†’ 링크 λ°œμ†‘, 3단계 μ™„λ£Œ | +| **λ³΄μ•ˆ** | λ¬Έμ„œ ν•΄μ‹œ 검증, 본인인증, 감사 좔적(Audit Trail) | +| **법적 효λ ₯** | μ „μžμ„œλͺ…법 제2쑰에 λΆ€ν•©ν•˜λŠ” μ „μžμ„œλͺ… μš”κ±΄ μΆ©μ‘± | + +### 1.3 λ²”μœ„ (v1) + +| 포함 | 미포함 (v2 이후) | +|------|------------------| +| 2인 μ„œλͺ… (μƒμ„±μž/μƒλŒ€λ°©) | Nλͺ… λ‹€μžκ°„ μ„œλͺ… | +| PDF λ¬Έμ„œ 기반 | μ›Œλ“œ/ν•œκΈ€ λ¬Έμ„œ 직접 νŽΈμ§‘ | +| 이메일 OTP 인증 | 카카였/PASS 본인인증 | +| 순차 μ„œλͺ… (Aβ†’B λ˜λŠ” Bβ†’A) | λ™μ‹œ μ„œλͺ… | +| μΊ”λ²„μŠ€ 직접 μ„œλͺ… | κ³΅μΈμΈμ¦μ„œ 연동 | +| 감사 좔적 둜그 | 블둝체인 기반 곡증 | +| μ™„λ£Œ λ¬Έμ„œ PDF λ‹€μš΄λ‘œλ“œ | API μ™ΈλΆ€ 연동 | + +### 1.4 기술 μŠ€νƒ + +| μ˜μ—­ | 기술 | λΉ„κ³  | +|------|------|------| +| Backend | Laravel 11 (PHP 8.3) | SAM API ν”„λ‘œμ νŠΈ ν™•μž₯ | +| Frontend | HTMX + Alpine.js + Tailwind CSS | SAM MNG ν”„λ‘œμ νŠΈ ν™•μž₯ | +| Database | MySQL 8.0 | κΈ°μ‘΄ SAM DB 곡유 | +| PDF λ Œλ”λ§ | pdf.js (ν”„λ‘ νŠΈ) | λΈŒλΌμš°μ € PDF ν‘œμ‹œ | +| μ„œλͺ… 캑처 | signature_pad.js | ν„°μΉ˜/마우슀 μ„œλͺ… | +| PDF ν•©μ„± | FPDI + FPDF (λ°±μ—”λ“œ) | 원본 PDF에 μ„œλͺ… μ‚½μž… | +| 파일 μ €μž₯ | Laravel Storage (local/S3) | μ•”ν˜Έν™” μ €μž₯ | +| μ•Œλ¦Ό | Laravel Notification (Mail) | 이메일 λ°œμ†‘ | + +--- + +## 2. μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜ + +### 2.1 전체 ꡬ쑰 + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ μ‚¬μš©μž (λΈŒλΌμš°μ €) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 계약 μƒμ„±μž (A) β”‚ μƒλŒ€λ°© (B) β”‚ +β”‚ - 둜그인 μ‚¬μš©μž β”‚ - λΉ„λ‘œκ·ΈμΈ (토큰 기반 μ ‘κ·Ό) β”‚ +β”‚ - κ³„μ•½μ„œ μ—…λ‘œλ“œ β”‚ - 이메일 링크둜 접속 β”‚ +β”‚ - μ„œλͺ… μœ„μΉ˜ μ§€μ • β”‚ - 본인인증 ν›„ μ„œλͺ… β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Nginx (sam-nginx-1) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ MNG (sam-mng-1) β”‚ β”‚ API (sam-api-1) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ - 계약 관리 ν™”λ©΄ β”‚ β”‚ - 계약 CRUD API β”‚ β”‚ +β”‚ β”‚ - PDF λ·°μ–΄/μ„œλͺ… UI β”‚ β”‚ - μ„œλͺ… 처리 API β”‚ β”‚ +β”‚ β”‚ - λŒ€μ‹œλ³΄λ“œ β”‚ β”‚ - 인증 API (OTP) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ - PDF ν•©μ„± μ„œλΉ„μŠ€ β”‚ β”‚ +β”‚ β”‚ HTMX + Alpine.js β”‚ β”‚ - μ•Œλ¦Ό μ„œλΉ„μŠ€ (이메일) β”‚ β”‚ +β”‚ β”‚ + pdf.js β”‚ β”‚ - 감사 둜그 μ„œλΉ„μŠ€ β”‚ β”‚ +β”‚ β”‚ + signature_pad β”‚ β”‚ - λ¬Έμ„œ ν•΄μ‹œ 검증 μ„œλΉ„μŠ€ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ MySQL (sam-mysql-1) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ - contracts β”‚ β”‚ +β”‚ β”‚ - contract_signers β”‚ β”‚ +β”‚ β”‚ - contract_sign_fields β”‚ β”‚ +β”‚ β”‚ - contract_audit_logs β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ File Storage β”‚ β”‚ +β”‚ β”‚ - 원본 PDF (μ•”ν˜Έν™”) β”‚ β”‚ +β”‚ β”‚ - μ„œλͺ… 이미지 β”‚ β”‚ +β”‚ β”‚ - μ™„λ£Œ PDF β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 2.2 μ„œλΉ„μŠ€ λ ˆμ΄μ–΄ ꡬ쑰 + +``` +app/Services/ESign/ +β”œβ”€β”€ ContractService.php # 계약 CRUD, μƒνƒœ 관리 +β”œβ”€β”€ SignatureService.php # μ„œλͺ… 처리, 이미지 μ €μž₯ +β”œβ”€β”€ PdfService.php # PDF ν•©μ„±, ν•΄μ‹œ 생성/검증 +β”œβ”€β”€ AuthenticationService.php # OTP 생성/검증, 토큰 관리 +β”œβ”€β”€ NotificationService.php # 이메일 λ°œμ†‘ (μ„œλͺ… μš”μ²­, μ™„λ£Œ μ•Œλ¦Ό) +└── AuditLogService.php # 감사 좔적 둜그 기둝 +``` + +--- + +## 3. 핡심 ν”Œλ‘œμš° + +### 3.1 계약 생성 ν”Œλ‘œμš° + +``` +[A: 계약 μƒμ„±μž] + +1. 둜그인 μƒνƒœμ—μ„œ "μƒˆ 계약" 클릭 +2. 계약 정보 μž…λ ₯ + - 계약 제λͺ© + - 계약 μ„€λͺ… (선택) + - μ„œλͺ… κΈ°ν•œ (κΈ°λ³Έ 7일) +3. PDF 파일 μ—…λ‘œλ“œ + β†’ μ„œλ²„: 파일 μ €μž₯ + SHA-256 ν•΄μ‹œ 생성 +4. μ„œλͺ… μœ„μΉ˜ μ§€μ • ν™”λ©΄μœΌλ‘œ 이동 + β†’ pdf.js둜 PDF λ Œλ”λ§ + β†’ λ“œλž˜κ·Έ&λ“œλ‘­μœΌλ‘œ μ„œλͺ…λž€ 배치 + β†’ A의 μ„œλͺ…λž€ (νŒŒλž€μƒ‰) + β†’ B의 μ„œλͺ…λž€ (빨간색) + β†’ λ‚ μ§œ ν•„λ“œ, ν…μŠ€νŠΈ ν•„λ“œ μΆ”κ°€ κ°€λŠ₯ +5. μƒλŒ€λ°©(B) 정보 μž…λ ₯ + - 이름 + - 이메일 (ν•„μˆ˜) + - μ „ν™”λ²ˆν˜Έ (선택) +6. μ„œλͺ… μˆœμ„œ 선택 + - B λ¨Όμ € β†’ A 확인 μ„œλͺ… + - A λ¨Όμ € β†’ B 확인 μ„œλͺ… +7. "μ„œλͺ… μš”μ²­ λ°œμ†‘" 클릭 + β†’ μ„œλ²„: contract μƒνƒœλ₯Ό 'pending'으둜 λ³€κ²½ + β†’ μ„œλ²„: μƒλŒ€λ°©μ—κ²Œ 이메일 λ°œμ†‘ (μ„œλͺ… 링크 포함) + β†’ 감사 둜그: 'contract_created', 'sign_requested' +``` + +### 3.2 μ„œλͺ… μˆ˜ν–‰ ν”Œλ‘œμš° (μƒλŒ€λ°© B) + +``` +[B: μ„œλͺ… μƒλŒ€λ°©] + +1. μ΄λ©”μΌμ—μ„œ μ„œλͺ… 링크 클릭 + β†’ URL: /esign/sign/{access_token} + β†’ μ„œλ²„: 토큰 μœ νš¨μ„± 검증 (만료, μ‚¬μš© μ—¬λΆ€) +2. 본인인증 게이트 + - μ΄λ©”μΌλ‘œ 6자리 OTP λ°œμ†‘ + - OTP μž…λ ₯ (3회 μ œν•œ, 5λΆ„ 유효) + β†’ μ„œλ²„: 인증 성곡 μ‹œ μ„Έμ…˜μ— verified μƒνƒœ μ €μž₯ + β†’ 감사 둜그: 'identity_verified' +3. κ³„μ•½μ„œ μ—΄λžŒ + β†’ pdf.js둜 PDF λ Œλ”λ§ + β†’ μ„œλͺ…이 ν•„μš”ν•œ μœ„μΉ˜μ— ν•˜μ΄λΌμ΄νŠΈ ν‘œμ‹œ + β†’ 감사 둜그: 'document_viewed' +4. μ„œλͺ… μˆ˜ν–‰ + - μ„œλͺ…λž€ 클릭 β†’ μΊ”λ²„μŠ€ μ„œλͺ… λͺ¨λ‹¬ νŒμ—… + - ν„°μΉ˜/마우슀둜 μ„œλͺ… + - "μ„œλͺ… μ™„λ£Œ" 클릭 + β†’ μ„œλ²„: μ„œλͺ… 이미지 μ €μž₯ (PNG, base64β†’file) + β†’ μ„œλ²„: μ„œλͺ… μ‹œκ°, IP, User-Agent 기둝 + β†’ 감사 둜그: 'signed' +5. λ™μ˜ 확인 + - [βœ“] λ³Έ κ³„μ•½μ„œμ˜ λ‚΄μš©μ„ ν™•μΈν•˜μ˜€μœΌλ©° μ„œλͺ…에 λ™μ˜ν•©λ‹ˆλ‹€ + - [βœ“] μ „μžμ„œλͺ…μ˜ 법적 효λ ₯에 λ™μ˜ν•©λ‹ˆλ‹€ + - "μ΅œμ’… 제좜" 클릭 + β†’ μ„œλ²„: signer μƒνƒœλ₯Ό 'signed'둜 λ³€κ²½ + β†’ μ„œλ²„: λ‹€μŒ μ„œλͺ…μž(A)μ—κ²Œ μ•Œλ¦Ό λ°œμ†‘ + β†’ 감사 둜그: 'consent_agreed', 'submission_completed' +``` + +### 3.3 μ΅œμ’… μ„œλͺ… ν”Œλ‘œμš° (μƒμ„±μž A) + +``` +[A: 계약 μƒμ„±μž - μ΅œμ’… μ„œλͺ…] + +1. μ•Œλ¦Ό μˆ˜μ‹  (이메일 λ˜λŠ” λŒ€μ‹œλ³΄λ“œ) + "μƒλŒ€λ°© OOOλ‹˜μ΄ μ„œλͺ…을 μ™„λ£Œν–ˆμŠ΅λ‹ˆλ‹€" +2. 본인인증 (동일 OTP 절차) +3. κ³„μ•½μ„œ μ—΄λžŒ (B의 μ„œλͺ…이 ν‘œμ‹œλœ μƒνƒœ) +4. A의 μ„œλͺ…λž€μ— μ„œλͺ… +5. μ΅œμ’… 제좜 + β†’ μ„œλ²„: contract μƒνƒœλ₯Ό 'completed'둜 λ³€κ²½ + β†’ μ„œλ²„: PDF ν•©μ„± (원본 + Aμ„œλͺ… + Bμ„œλͺ… + 감사정보) + β†’ μ„œλ²„: μ™„λ£Œ PDF에 SHA-256 ν•΄μ‹œ 생성 + β†’ μ„œλ²„: μ–‘μͺ½μ— μ™„λ£Œ μ•Œλ¦Ό + PDF λ‹€μš΄λ‘œλ“œ 링크 λ°œμ†‘ + β†’ 감사 둜그: 'contract_completed' +``` + +### 3.4 μƒνƒœ 전이 λ‹€μ΄μ–΄κ·Έλž¨ + +``` + β”Œβ”€β”€β”€β”€β”€β”€β”€β” + β”‚ draft β”‚ κ³„μ•½μ„œ μž‘μ„± 쀑 (μ €μž₯만, λ°œμ†‘ μ „) + β””β”€β”€β”€β”¬β”€β”€β”€β”˜ + β”‚ μ„œλͺ… μš”μ²­ λ°œμ†‘ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ pending β”‚ μ„œλͺ… μš”μ²­λ¨, 첫 μ„œλͺ…μž λŒ€κΈ° + β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ + β”‚ 첫 번째 μ„œλͺ… μ™„λ£Œ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ partially_signed β”‚ ν•œμͺ½λ§Œ μ„œλͺ… μ™„λ£Œ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ 두 번째 μ„œλͺ… μ™„λ£Œ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ completed β”‚ μ–‘μͺ½ μ„œλͺ… μ™„λ£Œ, PDF 합성됨 + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + + [만료 μ‹œ] + pending / partially_signed ──→ expired (μ„œλͺ… κΈ°ν•œ 초과) + + [μ·¨μ†Œ μ‹œ] + draft / pending ──→ cancelled (μƒμ„±μžκ°€ μ·¨μ†Œ) + + [거절 μ‹œ] + pending / partially_signed ──→ rejected (μ„œλͺ…μžκ°€ 거절) +``` + +--- + +## 4. λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆ + +### 4.1 ER λ‹€μ΄μ–΄κ·Έλž¨ (ν…μŠ€νŠΈ) + +``` +contracts (1) ──── (N) contract_signers + β”‚ β”‚ + β”‚ β”‚ + (1) (1) + β”‚ β”‚ + (N) (N) +contract_sign_fields contract_audit_logs +``` + +### 4.2 contracts (κ³„μ•½μ„œ) + +```sql +CREATE TABLE contracts ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + + -- 계약 정보 + contract_code VARCHAR(30) NOT NULL UNIQUE, -- 'ESIGN-2026-000001' + title VARCHAR(255) NOT NULL, -- 계약 제λͺ© + description TEXT NULL, -- 계약 μ„€λͺ… + sign_order_type ENUM('counterpart_first', 'creator_first') DEFAULT 'counterpart_first', + + -- λ¬Έμ„œ 파일 + original_file_path VARCHAR(500) NOT NULL, -- 원본 PDF 경둜 (μ•”ν˜Έν™” μ €μž₯) + original_file_name VARCHAR(255) NOT NULL, -- 원본 파일λͺ… + original_file_hash VARCHAR(64) NOT NULL, -- SHA-256 ν•΄μ‹œ + original_file_size INT UNSIGNED NOT NULL, -- 파일 크기 (bytes) + signed_file_path VARCHAR(500) NULL, -- μ„œλͺ… μ™„λ£Œ PDF 경둜 + signed_file_hash VARCHAR(64) NULL, -- μ„œλͺ… μ™„λ£Œ PDF ν•΄μ‹œ + + -- μƒνƒœ + status ENUM('draft', 'pending', 'partially_signed', 'completed', 'expired', 'cancelled', 'rejected') + DEFAULT 'draft', + expires_at DATETIME NOT NULL, -- μ„œλͺ… κΈ°ν•œ + completed_at DATETIME NULL, -- μ™„λ£Œ μ‹œκ° + + -- μƒμ„±μž + created_by BIGINT UNSIGNED NOT NULL, -- μƒμ„±μž user_id + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted_at TIMESTAMP NULL, -- soft delete + + INDEX idx_tenant_status (tenant_id, status), + INDEX idx_created_by (created_by), + INDEX idx_expires_at (expires_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +### 4.3 contract_signers (μ„œλͺ…μž) + +```sql +CREATE TABLE contract_signers ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + contract_id BIGINT UNSIGNED NOT NULL, + + -- μ„œλͺ…μž 정보 + role ENUM('creator', 'counterpart') NOT NULL, + sign_order TINYINT UNSIGNED NOT NULL DEFAULT 1, -- μ„œλͺ… μˆœμ„œ (1 or 2) + name VARCHAR(100) NOT NULL, + email VARCHAR(255) NOT NULL, + phone VARCHAR(20) NULL, + + -- μ ‘κ·Ό 토큰 + access_token VARCHAR(128) NOT NULL UNIQUE, -- μ„œλͺ… 링크용 1νšŒμ„± 토큰 + token_expires_at DATETIME NOT NULL, -- 토큰 만료 μ‹œκ° + + -- 인증 정보 + otp_code VARCHAR(10) NULL, -- OTP μ½”λ“œ (ν•΄μ‹œ μ €μž₯) + otp_expires_at DATETIME NULL, -- OTP 만료 μ‹œκ° + otp_attempts TINYINT UNSIGNED DEFAULT 0, -- OTP μ‹œλ„ 횟수 + auth_verified_at DATETIME NULL, -- 본인인증 μ™„λ£Œ μ‹œκ° + auth_method VARCHAR(20) DEFAULT 'email_otp', -- 인증 방식 + + -- μ„œλͺ… 정보 + signature_image_path VARCHAR(500) NULL, -- μ„œλͺ… 이미지 경둜 + signed_at DATETIME NULL, -- μ„œλͺ… μ‹œκ° + consent_agreed_at DATETIME NULL, -- λ™μ˜ μ‹œκ° + + -- μ„œλͺ… μ‹œμ  ν™˜κ²½ 정보 + sign_ip_address VARCHAR(45) NULL, -- IPv4/IPv6 + sign_user_agent VARCHAR(500) NULL, -- λΈŒλΌμš°μ € 정보 + + -- μƒνƒœ + status ENUM('waiting', 'notified', 'authenticated', 'signed', 'rejected') + DEFAULT 'waiting', + rejected_reason TEXT NULL, -- 거절 μ‚¬μœ  + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE, + INDEX idx_access_token (access_token), + INDEX idx_contract_role (contract_id, role) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +### 4.4 contract_sign_fields (μ„œλͺ… μœ„μΉ˜/ν•„λ“œ) + +```sql +CREATE TABLE contract_sign_fields ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + contract_id BIGINT UNSIGNED NOT NULL, + signer_id BIGINT UNSIGNED NOT NULL, -- contract_signers.id + + -- μœ„μΉ˜ 정보 + page_number INT UNSIGNED NOT NULL, -- PDF νŽ˜μ΄μ§€ 번호 (1λΆ€ν„°) + position_x DECIMAL(8,2) NOT NULL, -- X μ’Œν‘œ (pt λ‹¨μœ„) + position_y DECIMAL(8,2) NOT NULL, -- Y μ’Œν‘œ (pt λ‹¨μœ„) + width DECIMAL(8,2) NOT NULL, -- λ„ˆλΉ„ (pt) + height DECIMAL(8,2) NOT NULL, -- 높이 (pt) + + -- ν•„λ“œ 정보 + field_type ENUM('signature', 'stamp', 'text', 'date', 'checkbox') NOT NULL DEFAULT 'signature', + field_label VARCHAR(100) NULL, -- ν•„λ“œ 라벨 (예: "κ°‘ μ„œλͺ…") + field_value TEXT NULL, -- μž…λ ₯된 κ°’ (ν…μŠ€νŠΈ/λ‚ μ§œ) + is_required BOOLEAN DEFAULT TRUE, + + sort_order INT UNSIGNED DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE, + FOREIGN KEY (signer_id) REFERENCES contract_signers(id) ON DELETE CASCADE, + INDEX idx_contract_page (contract_id, page_number) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +### 4.5 contract_audit_logs (감사 좔적 둜그) + +```sql +CREATE TABLE contract_audit_logs ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + contract_id BIGINT UNSIGNED NOT NULL, + signer_id BIGINT UNSIGNED NULL, -- NULL이면 μ‹œμŠ€ν…œ 이벀트 + + -- 이벀트 정보 + action VARCHAR(50) NOT NULL, + -- κ°€λŠ₯ν•œ κ°’: + -- 'contract_created' : κ³„μ•½μ„œ 생성 + -- 'document_uploaded' : PDF μ—…λ‘œλ“œ + -- 'fields_configured' : μ„œλͺ… μœ„μΉ˜ μ„€μ • + -- 'sign_requested' : μ„œλͺ… μš”μ²­ λ°œμ†‘ + -- 'link_accessed' : μ„œλͺ… 링크 접속 + -- 'otp_sent' : OTP λ°œμ†‘ + -- 'otp_verified' : OTP 인증 성곡 + -- 'otp_failed' : OTP 인증 μ‹€νŒ¨ + -- 'document_viewed' : κ³„μ•½μ„œ μ—΄λžŒ + -- 'signed' : μ„œλͺ… μˆ˜ν–‰ + -- 'consent_agreed' : λ™μ˜ 체크 + -- 'submission_completed' : μ΅œμ’… 제좜 + -- 'contract_completed' : 계약 μ™„λ£Œ (μ–‘μͺ½ μ„œλͺ…) + -- 'pdf_generated' : μ™„λ£Œ PDF 생성 + -- 'document_downloaded' : λ¬Έμ„œ λ‹€μš΄λ‘œλ“œ + -- 'contract_cancelled' : 계약 μ·¨μ†Œ + -- 'contract_rejected' : μ„œλͺ… 거절 + -- 'contract_expired' : 계약 만료 + -- 'reminder_sent' : λ¦¬λ§ˆμΈλ” λ°œμ†‘ + + -- ν™˜κ²½ 정보 + ip_address VARCHAR(45) NULL, + user_agent VARCHAR(500) NULL, + metadata JSON NULL, -- μΆ”κ°€ 데이터 (μœ μ—°ν•œ ν™•μž₯) + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + FOREIGN KEY (contract_id) REFERENCES contracts(id) ON DELETE CASCADE, + INDEX idx_contract_action (contract_id, action), + INDEX idx_created_at (created_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +## 5. API λͺ…μ„Έ + +### 5.1 계약 관리 API + +#### 계약 λͺ©λ‘ 쑰회 +``` +GET /api/v1/esign/contracts +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| page | int | N | νŽ˜μ΄μ§€ 번호 (κΈ°λ³Έ 1) | +| size | int | N | νŽ˜μ΄μ§€ 크기 (κΈ°λ³Έ 20) | +| status | string | N | μƒνƒœ ν•„ν„° | +| search | string | N | 제λͺ© 검색 | +| date_from | string | N | μ‹œμž‘μΌ | +| date_to | string | N | μ’…λ£ŒμΌ | + +**Response 200:** +```json +{ + "data": [ + { + "id": 1, + "contract_code": "ESIGN-2026-000001", + "title": "μ†Œν”„νŠΈμ›¨μ–΄ 개발 μš©μ—­ κ³„μ•½μ„œ", + "status": "pending", + "signers": [ + { "name": "κΉ€κ°‘μˆœ", "role": "creator", "status": "waiting" }, + { "name": "박을동", "role": "counterpart", "status": "notified" } + ], + "expires_at": "2026-02-19T23:59:59", + "created_at": "2026-02-12T10:00:00" + } + ], + "meta": { "total": 25, "page": 1, "size": 20 } +} +``` + +#### 계약 생성 +``` +POST /api/v1/esign/contracts +Content-Type: multipart/form-data +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| title | string | Y | 계약 제λͺ© | +| description | string | N | 계약 μ„€λͺ… | +| document | file | Y | PDF 파일 (max 20MB) | +| expires_days | int | N | μ„œλͺ… κΈ°ν•œ 일수 (κΈ°λ³Έ 7) | +| sign_order_type | string | N | 'counterpart_first' λ˜λŠ” 'creator_first' | +| counterpart_name | string | Y | μƒλŒ€λ°© 이름 | +| counterpart_email | string | Y | μƒλŒ€λ°© 이메일 | +| counterpart_phone | string | N | μƒλŒ€λ°© μ „ν™”λ²ˆν˜Έ | + +**Response 201:** +```json +{ + "data": { + "id": 1, + "contract_code": "ESIGN-2026-000001", + "status": "draft", + "original_file_hash": "a1b2c3d4e5f6...", + "signers": [ + { "id": 1, "role": "creator", "name": "κΉ€κ°‘μˆœ" }, + { "id": 2, "role": "counterpart", "name": "박을동" } + ] + } +} +``` + +#### 계약 상세 쑰회 +``` +GET /api/v1/esign/contracts/{id} +``` + +#### 계약 μ·¨μ†Œ +``` +POST /api/v1/esign/contracts/{id}/cancel +``` + +#### 계약 톡계 +``` +GET /api/v1/esign/contracts/stats +``` + +**Response 200:** +```json +{ + "data": { + "total": 50, + "draft": 3, + "pending": 10, + "partially_signed": 5, + "completed": 28, + "expired": 3, + "cancelled": 1 + } +} +``` + +### 5.2 μ„œλͺ… ν•„λ“œ API + +#### μ„œλͺ… μœ„μΉ˜ μ„€μ • +``` +POST /api/v1/esign/contracts/{id}/fields +Content-Type: application/json +``` + +```json +{ + "fields": [ + { + "signer_id": 1, + "page_number": 3, + "position_x": 120.5, + "position_y": 650.0, + "width": 150, + "height": 60, + "field_type": "signature", + "field_label": "κ°‘ (μƒμ„±μž) μ„œλͺ…", + "is_required": true + }, + { + "signer_id": 2, + "page_number": 3, + "position_x": 350.5, + "position_y": 650.0, + "width": 150, + "height": 60, + "field_type": "signature", + "field_label": "을 (μƒλŒ€λ°©) μ„œλͺ…", + "is_required": true + }, + { + "signer_id": 1, + "page_number": 3, + "position_x": 120.5, + "position_y": 720.0, + "width": 100, + "height": 25, + "field_type": "date", + "field_label": "μ„œλͺ…일", + "is_required": true + } + ] +} +``` + +#### μ„œλͺ… μœ„μΉ˜ 쑰회 +``` +GET /api/v1/esign/contracts/{id}/fields +``` + +### 5.3 μ„œλͺ… μš”μ²­ API + +#### μ„œλͺ… μš”μ²­ λ°œμ†‘ +``` +POST /api/v1/esign/contracts/{id}/send +``` +> μƒλŒ€λ°©μ—κ²Œ 이메일 λ°œμ†‘, μƒνƒœλ₯Ό `pending`으둜 λ³€κ²½ + +#### λ¦¬λ§ˆμΈλ” λ°œμ†‘ +``` +POST /api/v1/esign/contracts/{id}/remind +``` + +### 5.4 μ„œλͺ… μˆ˜ν–‰ API (토큰 기반, λΉ„λ‘œκ·ΈμΈ) + +#### μ„œλͺ… νŽ˜μ΄μ§€ 접속 +``` +GET /api/v1/esign/sign/{access_token} +``` +> 토큰 검증 β†’ 계약 정보 + μ„œλͺ… ν•„λ“œ λ°˜ν™˜ + +#### OTP λ°œμ†‘ μš”μ²­ +``` +POST /api/v1/esign/sign/{access_token}/otp/send +``` + +**Response 200:** +```json +{ + "message": "μΈμ¦μ½”λ“œκ°€ μ΄λ©”μΌλ‘œ λ°œμ†‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€", + "expires_in": 300, + "remaining_attempts": 3 +} +``` + +#### OTP 인증 +``` +POST /api/v1/esign/sign/{access_token}/otp/verify +``` + +```json +{ + "otp_code": "482917" +} +``` + +**Response 200 (성곡):** +```json +{ + "verified": true, + "sign_session_token": "eyJ..." +} +``` + +**Response 401 (μ‹€νŒ¨):** +```json +{ + "verified": false, + "remaining_attempts": 2, + "message": "μΈμ¦μ½”λ“œκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€" +} +``` + +#### μ„œλͺ… 제좜 +``` +POST /api/v1/esign/sign/{access_token}/submit +Authorization: Bearer {sign_session_token} +Content-Type: application/json +``` + +```json +{ + "signatures": [ + { + "field_id": 1, + "signature_image": "data:image/png;base64,iVBORw0KGgo...", + "field_type": "signature" + }, + { + "field_id": 3, + "field_value": "2026-02-12", + "field_type": "date" + } + ], + "consent_electronic_signature": true, + "consent_contract_content": true +} +``` + +#### μ„œλͺ… 거절 +``` +POST /api/v1/esign/sign/{access_token}/reject +``` + +```json +{ + "reason": "계약 쑰건 μˆ˜μ •μ΄ ν•„μš”ν•©λ‹ˆλ‹€" +} +``` + +### 5.5 λ¬Έμ„œ API + +#### 원본 PDF 쑰회 (인증 ν›„) +``` +GET /api/v1/esign/sign/{access_token}/document +Authorization: Bearer {sign_session_token} +``` +> Content-Type: application/pdf + +#### μ™„λ£Œ PDF λ‹€μš΄λ‘œλ“œ +``` +GET /api/v1/esign/contracts/{id}/download +``` +> 둜그인 μ‚¬μš©μžλ§Œ μ ‘κ·Ό, μ™„λ£Œ μƒνƒœμΈ κ³„μ•½λ§Œ + +#### λ¬Έμ„œ 무결성 검증 +``` +GET /api/v1/esign/contracts/{id}/verify +``` + +**Response 200:** +```json +{ + "original_hash": "a1b2c3...", + "signed_hash": "d4e5f6...", + "original_integrity": true, + "signed_integrity": true, + "verification_time": "2026-02-12T15:30:00" +} +``` + +### 5.6 감사 둜그 API + +#### 감사 둜그 쑰회 +``` +GET /api/v1/esign/contracts/{id}/audit-logs +``` + +**Response 200:** +```json +{ + "data": [ + { + "action": "contract_created", + "signer_name": "κΉ€κ°‘μˆœ", + "ip_address": "192.168.1.100", + "created_at": "2026-02-12T10:00:00" + }, + { + "action": "sign_requested", + "signer_name": null, + "metadata": { "sent_to": "park@example.com" }, + "created_at": "2026-02-12T10:05:00" + } + ] +} +``` + +--- + +## 6. λ³΄μ•ˆ 섀계 + +### 6.1 λ¬Έμ„œ 무결성 (Document Integrity) + +```php +// μ—…λ‘œλ“œ μ‹œ ν•΄μ‹œ 생성 +$hash = hash_file('sha256', $uploadedFile->getRealPath()); + +// 검증 μ‹œ ν•΄μ‹œ 비ꡐ +$currentHash = hash_file('sha256', Storage::path($contract->original_file_path)); +$isValid = hash_equals($contract->original_file_hash, $currentHash); +``` + +- 원본 PDF μ—…λ‘œλ“œ μ‹œ SHA-256 ν•΄μ‹œ 생성 및 DB μ €μž₯ +- μ„œλͺ… μ™„λ£Œ PDF 생성 μ‹œμ—λ„ 별도 ν•΄μ‹œ μ €μž₯ +- λ¬Έμ„œ λ‹€μš΄λ‘œλ“œ/μ—΄λžŒ μ‹œ ν•΄μ‹œ λΉ„κ΅λ‘œ μœ„λ³€μ‘° μ—¬λΆ€ 확인 + +### 6.2 μ„œλͺ…μž 인증 (Signer Authentication) + +``` +인증 ν”Œλ‘œμš°: +1. μ„œλͺ… 링크 접속 (access_token 검증) +2. OTP λ°œμ†‘ (이메일) +3. OTP μž…λ ₯ (6자리, 5λΆ„ 유효, 3회 μ œν•œ) +4. 인증 성곡 β†’ sign_session_token λ°œκΈ‰ (JWT, 30λΆ„ 유효) +5. 이후 λͺ¨λ“  μ„œλͺ… API 호좜 μ‹œ sign_session_token ν•„μš” +``` + +**OTP λ³΄μ•ˆ κ·œμΉ™:** + +| κ·œμΉ™ | κ°’ | μ„€λͺ… | +|------|-----|------| +| OTP 길이 | 6자리 숫자 | λ¬΄μž‘μœ„ 생성 | +| 유효 μ‹œκ°„ | 5λΆ„ | 초과 μ‹œ μž¬λ°œμ†‘ ν•„μš” | +| μ‹œλ„ μ œν•œ | 3회 | 초과 μ‹œ 토큰 λ¬΄νš¨ν™” | +| μž¬λ°œμ†‘ 간격 | 60초 | 연속 λ°œμ†‘ λ°©μ§€ | +| μ €μž₯ 방식 | bcrypt ν•΄μ‹œ | DB에 평문 μ €μž₯ κΈˆμ§€ | + +### 6.3 μ ‘κ·Ό μ œμ–΄ (Access Control) + +**토큰 μ •μ±…:** + +| 토큰 | μš©λ„ | μœ νš¨κΈ°κ°„ | νŠΉμ„± | +|------|------|---------|------| +| access_token | μ„œλͺ… 링크 URL | 계약 λ§Œλ£ŒμΌκΉŒμ§€ | 128자 랜덀, URL-safe | +| sign_session_token | OTP 인증 ν›„ μ„Έμ…˜ | 30λΆ„ | JWT, κ°±μ‹  λΆˆκ°€ | + +**μ ‘κ·Ό κ·œμΉ™:** +- μ„œλͺ… λ§ν¬λŠ” ν•΄λ‹Ή μ„œλͺ…μžλ§Œ μ ‘κ·Ό κ°€λŠ₯ (토큰 + 이메일 검증) +- μ™„λ£Œ/μ·¨μ†Œ/만료 μƒνƒœ 계약은 μ„œλͺ… μ ‘κ·Ό 차단 +- μ„œλͺ… μˆœμ„œκ°€ μ•„λ‹Œ μ„œλͺ…μžλŠ” λŒ€κΈ° ν™”λ©΄ ν‘œμ‹œ +- λͺ¨λ“  API 호좜 μ‹œ IP/UA 기둝 + +### 6.4 감사 좔적 (Audit Trail) + +λͺ¨λ“  μ£Όμš” ν–‰μœ„λ₯Ό `contract_audit_logs`에 기둝: + +``` +기둝 λŒ€μƒ ν–‰μœ„: +- κ³„μ•½μ„œ 생성/μˆ˜μ •/μ‚­μ œ +- PDF μ—…λ‘œλ“œ +- μ„œλͺ… μš”μ²­ λ°œμ†‘ +- μ„œλͺ… 링크 접속 (성곡/μ‹€νŒ¨) +- OTP λ°œμ†‘/검증 (성곡/μ‹€νŒ¨) +- κ³„μ•½μ„œ μ—΄λžŒ +- μ„œλͺ… μˆ˜ν–‰ +- λ™μ˜ 체크 +- λ¬Έμ„œ λ‹€μš΄λ‘œλ“œ +- 계약 μ·¨μ†Œ/거절/만료 +``` + +**감사 λ‘œκ·ΈλŠ” μ‚­μ œ λΆˆκ°€** (soft delete 미적용) + +### 6.5 파일 λ³΄μ•ˆ + +``` +μ €μž₯ ꡬ쑰: +storage/app/esign/ +β”œβ”€β”€ originals/ # 원본 PDF (AES-256 μ•”ν˜Έν™”) +β”‚ └── {contract_id}/ +β”‚ └── {hash}.pdf.enc +β”œβ”€β”€ signatures/ # μ„œλͺ… 이미지 +β”‚ └── {signer_id}/ +β”‚ └── {timestamp}.png +└── completed/ # μ™„λ£Œ PDF + └── {contract_id}/ + └── {contract_code}_signed.pdf +``` + +- 원본 PDFλŠ” AES-256으둜 μ•”ν˜Έν™” μ €μž₯ +- μ„œλͺ… μ΄λ―Έμ§€λŠ” 별도 디렉토리에 격리 +- μ™„λ£Œ PDFλŠ” 감사 증적 νŽ˜μ΄μ§€λ₯Ό ν¬ν•¨ν•˜μ—¬ 생성 +- 파일 κ²½λ‘œμ— 직접 μ ‘κ·Ό λΆˆκ°€ (Controllerλ₯Ό ν†΅ν•œ 슀트리밍만 ν—ˆμš©) + +### 6.6 μ™„λ£Œ PDF에 ν¬ν•¨λ˜λŠ” 감사 정보 + +μ„œλͺ… μ™„λ£Œ PDF의 λ§ˆμ§€λ§‰ νŽ˜μ΄μ§€μ— μžλ™ μΆ”κ°€: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ μ „μžμ„œλͺ… 감사 증적 (Audit Trail) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 계약 번호: ESIGN-2026-000001 β”‚ +β”‚ λ¬Έμ„œ ν•΄μ‹œ: a1b2c3d4e5f6... β”‚ +β”‚ β”‚ +β”‚ [μ„œλͺ…μž A - κ°‘] β”‚ +β”‚ 이름: κΉ€κ°‘μˆœ β”‚ +β”‚ 인증 방식: 이메일 OTP β”‚ +β”‚ 인증 μ‹œκ°: 2026-02-12 14:30:22 KST β”‚ +β”‚ μ„œλͺ… μ‹œκ°: 2026-02-12 14:32:15 KST β”‚ +β”‚ IP: 203.xxx.xxx.100 β”‚ +β”‚ β”‚ +β”‚ [μ„œλͺ…μž B - 을] β”‚ +β”‚ 이름: 박을동 β”‚ +β”‚ 인증 방식: 이메일 OTP β”‚ +β”‚ 인증 μ‹œκ°: 2026-02-12 11:20:05 KST β”‚ +β”‚ μ„œλͺ… μ‹œκ°: 2026-02-12 11:23:41 KST β”‚ +β”‚ IP: 121.xxx.xxx.55 β”‚ +β”‚ β”‚ +β”‚ 계약 μ™„λ£Œ: 2026-02-12 14:32:15 KST β”‚ +β”‚ λ³Έ λ¬Έμ„œλŠ” μ „μžμ„œλͺ…법에 μ˜κ±°ν•˜μ—¬ β”‚ +β”‚ 법적 효λ ₯을 κ°€μ§‘λ‹ˆλ‹€. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## 7. ν™”λ©΄ λͺ©λ‘ + +### 7.1 계약 μƒμ„±μž(A) ν™”λ©΄ + +| # | ν™”λ©΄ID | ν™”λ©΄λͺ… | 경둜 | μ„€λͺ… | +|---|--------|--------|------|------| +| 1 | ES_DASH | λŒ€μ‹œλ³΄λ“œ | /esign | 계약 ν˜„ν™© 톡계 + λͺ©λ‘ | +| 2 | ES_CREATE | 계약 생성 | /esign/create | PDF μ—…λ‘œλ“œ + 정보 μž…λ ₯ | +| 3 | ES_FIELDS | μ„œλͺ… μœ„μΉ˜ μ§€μ • | /esign/{id}/fields | PDF μœ„μ— μ„œλͺ…λž€ 배치 | +| 4 | ES_SEND | μ„œλͺ… μš”μ²­ λ°œμ†‘ | /esign/{id}/send | μƒλŒ€λ°© 정보 μž…λ ₯ + λ°œμ†‘ | +| 5 | ES_DETAIL | 계약 상세 | /esign/{id} | μ§„ν–‰ μƒνƒœ + 감사 둜그 | + +### 7.2 μ„œλͺ… μƒλŒ€λ°©(B) ν™”λ©΄ + +| # | ν™”λ©΄ID | ν™”λ©΄λͺ… | 경둜 | μ„€λͺ… | +|---|--------|--------|------|------| +| 6 | ES_AUTH | 본인인증 | /esign/sign/{token} | OTP 인증 게이트 | +| 7 | ES_SIGN | μ„œλͺ… μˆ˜ν–‰ | /esign/sign/{token}/sign | PDF μ—΄λžŒ + μ„œλͺ… | +| 8 | ES_DONE | μ„œλͺ… μ™„λ£Œ | /esign/sign/{token}/done | μ™„λ£Œ μ•ˆλ‚΄ | + +--- + +## 8. κ΅¬ν˜„ λ‘œλ“œλ§΅ + +### Phase 1: κΈ°λ³Έ κΈ°λŠ₯ (2μ£Ό) + +| μ£Όμ°¨ | μž‘μ—… | λ‹΄λ‹Ή | +|------|------|------| +| 1μ£Όμ°¨ | DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 생성 | API | +| 1μ£Όμ°¨ | Contract λͺ¨λΈ/μ„œλΉ„μŠ€/컨트둀러 | API | +| 1μ£Όμ°¨ | PDF μ—…λ‘œλ“œ + ν•΄μ‹œ 생성 | API | +| 1μ£Όμ°¨ | λŒ€μ‹œλ³΄λ“œ + 계약 생성 ν™”λ©΄ | MNG | +| 2μ£Όμ°¨ | μ„œλͺ… μœ„μΉ˜ μ§€μ • ν™”λ©΄ (pdf.js + λ“œλž˜κ·Έ) | MNG | +| 2μ£Όμ°¨ | OTP 인증 + μ„œλͺ… 캑처 (signature_pad) | MNG + API | +| 2μ£Όμ°¨ | 이메일 λ°œμ†‘ (μ„œλͺ… μš”μ²­/μ™„λ£Œ) | API | +| 2μ£Όμ°¨ | PDF ν•©μ„± (FPDI + μ„œλͺ… 이미지) | API | + +### Phase 2: λ³΄μ•ˆ κ°•ν™” (1μ£Ό) + +| μž‘μ—… | λ‹΄λ‹Ή | +|------|------| +| 감사 좔적 둜그 전체 κ΅¬ν˜„ | API | +| 파일 μ•”ν˜Έν™” μ €μž₯ (AES-256) | API | +| μ™„λ£Œ PDF 감사 증적 νŽ˜μ΄μ§€ μΆ”κ°€ | API | +| λ¬Έμ„œ 무결성 검증 API | API | +| 토큰 만료/μ‚¬μš© 횟수 μ œν•œ | API | +| Rate Limiting (OTP λ°œμ†‘ λ“±) | API | + +### Phase 3: UX κ°œμ„  (1μ£Ό) + +| μž‘μ—… | λ‹΄λ‹Ή | +|------|------| +| λ¦¬λ§ˆμΈλ” μžλ™ λ°œμ†‘ (만료 3일 μ „) | API (Scheduler) | +| 만료 μžλ™ 처리 배치 | API (Scheduler) | +| λͺ¨λ°”일 λ°˜μ‘ν˜• μ„œλͺ… UI | MNG | +| 계약 λͺ©λ‘ ν•„ν„°/μ •λ ¬/검색 | MNG + API | +| μ„œλͺ… 거절 + μ‚¬μœ  μž…λ ₯ | MNG + API | + +### Phase 4: ν™•μž₯ κΈ°λŠ₯ (v2, μΆ”ν›„) + +| κΈ°λŠ₯ | μ„€λͺ… | +|------|------| +| SMS 인증 | Coolsms/NHN Cloud 연동 | +| 카카였 μ•Œλ¦Όν†‘ | 카카였 λΉ„μ¦ˆλ©”μ‹œμ§€ 연동 | +| λ‹€μžκ°„ μ„œλͺ… (3인 이상) | signers ν…Œμ΄λΈ” ν™•μž₯ | +| ν…œν”Œλ¦Ώ 관리 | 자주 μ“°λŠ” κ³„μ•½μ„œ 양식 μ €μž₯ | +| μ™ΈλΆ€ API 제곡 | 타 μ‹œμŠ€ν…œμ—μ„œ μ „μžκ³„μ•½ 호좜 | +| 블둝체인 곡증 | 계약 ν•΄μ‹œλ₯Ό 블둝체인에 기둝 | + +--- + +## 9. 법적 고렀사항 + +### 9.1 μ „μžμ„œλͺ…법 μš”κ±΄ + +ν•œκ΅­ μ „μžμ„œλͺ…법 제2쑰에 λ”°λ₯Έ μ „μžμ„œλͺ… μš”κ±΄: + +| μš”κ±΄ | μΆ©μ‘± λ°©μ•ˆ | +|------|-----------| +| μ„œλͺ…μž 확인 | 이메일 OTP 본인인증 | +| μ„œλͺ… μ˜μ‚¬ 확인 | λ™μ˜ μ²΄ν¬λ°•μŠ€ 2개 (λ‚΄μš© 확인 + 법적 효λ ₯ λ™μ˜) | +| λ¬Έμ„œ λ³€κ²½ 감지 | SHA-256 ν•΄μ‹œ 비ꡐ | +| μ„œλͺ… ν›„ λ³€κ²½ λΆˆκ°€ | μ„œλͺ… μ™„λ£Œ ν›„ 계약 μˆ˜μ • 차단 + 별도 PDF 생성 | + +### 9.2 κ°œμΈμ •λ³΄λ³΄ν˜Έ + +| ν•­λͺ© | 쑰치 | +|------|------| +| μˆ˜μ§‘ 정보 | 이름, 이메일, μ „ν™”λ²ˆν˜Έ (선택), IP, μ„œλͺ… 이미지 | +| 보관 κΈ°κ°„ | 계약 μ™„λ£Œ ν›„ 5λ…„ (μ „μžμƒκ±°λž˜λ²•) | +| μ•”ν˜Έν™” | 파일 AES-256, OTP bcrypt, 톡신 HTTPS | +| μ ‘κ·Ό ν†΅μ œ | 계약 λ‹Ήμ‚¬μž + ν…Œλ„ŒνŠΈ κ΄€λ¦¬μžλ§Œ μ ‘κ·Ό | + +--- + +*이 λ¬Έμ„œλŠ” SAM E-Sign μ†”λ£¨μ…˜μ˜ 초기 기술 μ„€κ³„μž…λ‹ˆλ‹€. κ΅¬ν˜„ κ³Όμ •μ—μ„œ 상세 λ‚΄μš©μ΄ 변경될 수 μžˆμŠ΅λ‹ˆλ‹€.*