diff --git a/sam/docs/INDEX.md b/sam/docs/INDEX.md index b6751fd..10e0aca 100644 --- a/sam/docs/INDEX.md +++ b/sam/docs/INDEX.md @@ -155,14 +155,18 @@ docs/ | [SAM_ERP_회계관리_Storyboard_D1.6.md](plans/SAM_ERP_회계관리_Storyboard_D1.6.md) | ERP 회계관리 스토리보드 D1.6 (65p PDF → 마크다운 변환) | | [SAM_General_Rule_Storyboard_D1.0.md](plans/SAM_General_Rule_Storyboard_D1.0.md) | General Rule 스토리보드 D1.0 (43p PDF → 마크다운 변환, UIUX 공통 규칙) | | [production-deployment-plan.md](plans/production-deployment-plan.md) | 운영 환경 배포 계획 (CI/CD, 서버 아키텍처) | +| [attendance-management-plan.md](plans/attendance-management-plan.md) | 근태현황 개발 계획 (Phase 1~2, HTMX 기반) | +| [leave-management-plan.md](plans/leave-management-plan.md) | 휴가관리 모듈 개발 계획 (연차 발생/신청/승인/정책) | ### features/ - 기능별 문서 | 문서 | 설명 | |------|------| | [barobill-kakaotalk/README.md](features/barobill-kakaotalk/README.md) | 바로빌 카카오톡 (알림톡/친구톡) 연동 | +| [barobill-kakaotalk/esign-notification-guide.md](features/barobill-kakaotalk/esign-notification-guide.md) | 전자계약 알림톡/SMS 환경별 설정 가이드 | | [boards/README.md](features/boards/README.md) | 게시판 시스템 구현 | | [boards/mng-implementation.md](features/boards/mng-implementation.md) | MNG 게시판 구현 상세 | +| [hr/attendance-management-spec.md](features/hr/attendance-management-spec.md) | 근태관리 기획서 (화면/데이터/비즈니스규칙/API) | | [hr/hr-api-analysis.md](features/hr/hr-api-analysis.md) | HR API 분석 (근태/직원/부서) | | [quotes/README.md](features/quotes/README.md) | 견적 시스템 분석 (BOM 계산, 10단계 로직) | | [business-card-request.md](features/business-card-request.md) | 명함신청 관리 (3단계 워크플로우: 요청→제작의뢰→처리완료) | diff --git a/sam/docs/features/barobill-kakaotalk/README.md b/sam/docs/features/barobill-kakaotalk/README.md new file mode 100644 index 0000000..09f1909 --- /dev/null +++ b/sam/docs/features/barobill-kakaotalk/README.md @@ -0,0 +1,410 @@ +# 바로빌 카카오톡 (알림톡/친구톡) 연동 + +> **문서 버전**: 1.1 +> **작성일**: 2026-02-14 +> **최종 수정**: 2026-02-27 +> **상태**: 운영 중 (알림톡 + SMS + 환경별 분기 완료) +> **대상 프로젝트**: MNG + +--- + +## 1. 개요 + +### 1.1 목적 + +바로빌(Barobill) 플랫폼의 카카오톡 알림톡/친구톡 API를 SAM에 연동하여, +고객사에 카카오톡 메시지를 자동 또는 수동으로 발송하는 기능을 제공한다. + +### 1.2 사전 요구사항 + +| 항목 | 상태 | 설명 | +|------|------|------| +| 법인 명의 휴대폰 준비 | **완료** | 카카오톡 채널 가입에 법인 명의 번호 사용 | +| 카카오톡 채널 개설 | **완료** (2026-02-20) | 채널 ID: `@codebridge`, 채널명: (주)코드브릿지엑스 | +| 바로빌 카카오톡 서비스 신청 | **완료** (2026-02-20) | 바로빌 관리자 페이지에서 카카오톡 서비스 활성화 | +| 채널 연동 (바로빌↔카카오) | **완료** (2026-02-20) | 바로빌 관리 URL에서 채널 연동 처리 | +| 바로빌 파트너 과금 설정 | **완료** (2026-02-23) | 바로빌 측에서 파트너사 과금 설정 완료 | +| 알림톡 템플릿 v1 검수 | **완료** (2026-02-22) | `전자계약_서명요청`, `전자계약_리마인드` 2종 승인 | +| 알림톡 템플릿 v2 검수 | **완료** (2026-02-25) | 버튼 URL에 `#{토큰}` 변수 포함 3종 승인 | +| 알림톡 `전자계약_완료` | **완료** (2026-02-26) | 서명 완료 알림 발송용 템플릿 승인 | +| 역할 기반 알림 분기 | **완료** (2026-02-26) | 본사=이메일, 상대방=알림톡/SMS | +| 환경별 템플릿 분기 | **완료** (2026-02-27) | `_DEV` 접미사 개발 템플릿 등록 | +| DEV 템플릿 검수 | **심사 중** (2026-02-27 접수) | 개발서버용 3종 (`admin.codebridge-x.com`) | + +> 상세 등록 가이드: [카카오톡 알림톡 채널 및 템플릿 등록 가이드](../../guides/카카오톡-알림톡-채널-템플릿-등록.md) + +### 1.3 알림톡 vs 친구톡 + +| 구분 | 알림톡 | 친구톡 | +|------|--------|--------| +| **용도** | 정보성 메시지 (주문확인, 배송안내 등) | 광고성 메시지 (프로모션, 이벤트 등) | +| **수신 대상** | 모든 카카오톡 사용자 | 채널 친구 추가한 사용자만 | +| **템플릿** | 필수 (카카오 사전 검수) | 불필요 (자유 형식) | +| **광고 표시** | 불가 | 필수 (`(광고)` 표기) | +| **이미지 첨부** | 불가 | 가능 (이미지/와이드 이미지) | +| **비용** | 건당 약 8~9원 | 건당 약 15~20원 | +| **SMS 대체발송** | 설정 가능 | 설정 가능 | + +--- + +## 2. 아키텍처 + +### 2.1 시스템 구조 + +``` +SAM MNG (브라우저) + │ + ├─ [페이지] /barobill/kakaotalk/* ← Blade 뷰 + │ KakaotalkController (페이지 렌더링) + │ + ├─ [API] /api/admin/barobill/kakaotalk/* ← AJAX 호출 + │ BarobillKakaotalkController + │ + └─ [전자계약] /esign/* ← 자동 발송 + EsignApiController::sendAlimtalk() + │ + └─ BarobillService (SOAP 클라이언트) + │ + └─ 바로빌 KAKAOTALK.asmx (WSDL) + │ + └─ 카카오톡 서버 +``` + +### 2.2 바로빌 SOAP API 엔드포인트 + +| 환경 | WSDL URL | +|------|----------| +| **테스트** | `https://testws.baroservice.com/KAKAOTALK.asmx?WSDL` | +| **운영** | `https://ws.baroservice.com/KAKAOTALK.asmx?WSDL` | + +--- + +## 3. 전자계약 알림톡 연동 (핵심) + +### 3.1 발송 흐름 + +``` +전자계약 생성 (E-Sign) + │ + ├─ [1단계] EsignApiController::sendAlimtalk() + │ │ + │ ├─ 채널 ID 조회 (getKakaotalkChannelId) + │ ├─ 템플릿 본문 + 버튼 조회 (getTemplateData) + │ ├─ 변수 치환 (#{이름}, #{계약명}, #{기한}) + │ └─ SendATKakaotalkEx 호출 + │ + ├─ [2단계] 바로빌 접수 → SendKey 반환 + │ + ├─ [3단계] 3초 대기 후 GetSendKakaotalk으로 전달 결과 확인 + │ │ + │ ├─ ResultCode = 1 → 성공 + │ └─ ResultCode != 1 → 실패 (에러 반환) + │ + └─ [이메일 폴백] 알림톡 실패 시 이메일로 자동 전환 +``` + +### 3.2 등록된 템플릿 (v1 — 현재 운영) + +**`전자계약_서명요청`** + +``` + 안녕하세요, #{이름}님. + 전자계약 서명 요청이 도착했습니다. + + ■ 계약명: #{계약명} + ■ 서명 기한: #{기한} + + 아래 버튼을 눌러 계약서를 확인하고 서명해 주세요. +``` + +- 버튼: `계약서 확인하기` (WL) +- Url1/Url2: `https://mng.codebridge-x.com` + +**`전자계약_리마인드`** + +``` +안녕하세요, #{이름}님. +아직 서명이 완료되지 않은 전자계약이 있습니다. + + ■ 계약명: #{계약명} + ■ 서명 기한: #{기한} + + 기한 내에 서명을 완료해 주세요. +``` + +- 버튼: `계약서 확인하기` (WL) +- Url1/Url2: `https://mng.codebridge-x.com` + +### 3.3 등록 예정 템플릿 (v2 — 심사 중) + +> **2026-02-24 재등록**: 버튼 URL에 `#{토큰}` 변수를 포함하여 동적 서명 URL 지원 + +- Url1/Url2: `https://mng.codebridge-x.com/esign/sign/#{토큰}` + +v2 승인 후 코드 변경 필요: +- `EsignApiController::sendAlimtalk()`에서 동적 `$signUrl`을 버튼 URL로 전달 +- 현재 코드의 등록된 URL 그대로 사용 → 동적 URL 사용으로 전환 + +### 3.4 임시 우회: 로그인 페이지 서명 확인 + +v1 템플릿의 버튼 URL이 대시보드(`https://mng.codebridge-x.com`)로 고정되어 있어, +로그인 페이지에 전화번호 기반 서명 확인 기능을 추가하였다. + +``` +알림톡 버튼 클릭 → https://mng.codebridge-x.com → 로그인 페이지 + │ + └─ "전자계약 서명하기" 섹션 + │ + ├─ 전화번호 입력 + ├─ POST /esign/verify-phone + └─ 대기 중인 계약 조회 → /esign/sign/{token} 리다이렉트 +``` + +- 라우트: `POST /esign/verify-phone` +- 컨트롤러: `EsignPublicController::verifyPhone()` +- v2 템플릿 승인 후에도 유지 (비로그인 사용자 대응) + +### 3.5 관련 파일 + +| 파일 | 역할 | +|------|------| +| `app/Http/Controllers/ESign/EsignApiController.php` | `sendAlimtalk()`, `getTemplateData()` | +| `app/Http/Controllers/ESign/EsignPublicController.php` | `verifyPhone()` 전화번호 확인 | +| `app/Services/Barobill/BarobillService.php` | SOAP 클라이언트, `sendATKakaotalkEx()` | +| `resources/views/auth/login.blade.php` | 로그인 페이지 서명 확인 UI | +| `routes/web.php` | `/esign/verify-phone` 라우트 | + +--- + +## 4. 트러블슈팅 (실전 경험) + +> **경고: 아래 내용은 실제 연동 과정에서 발견한 핵심 이슈다. 반드시 숙지할 것.** + +### 4.1 바로빌 API 응답 구조 + +바로빌 SOAP 응답은 `stdClass` 객체로 반환된다. 배열이 아니므로 주의: + +```php +// ❌ 잘못된 접근 +$channels = $result['data']; // 배열이 아님 + +// ✅ 올바른 접근 +$data = $result['data']; // stdClass +$channels = is_array($data->KakaotalkChannel) + ? $data->KakaotalkChannel + : [$data->KakaotalkChannel]; // 1건이면 객체, N건이면 배열 +``` + +### 4.2 SendKey vs ResultCode (2단계 검증 필수) + +> **핵심**: 바로빌이 SendKey를 반환해도 **실제 카카오톡 전달이 실패할 수 있다.** + +``` +[1단계] SendATKakaotalkEx 호출 + → SendKey 반환 (예: BB_6648603713_AT_3044107_260224) + → 이것은 "접수 성공"이지 "전달 성공"이 아님! + +[2단계] 3초 후 GetSendKakaotalk(SendKey) 호출 + → ResultCode = 1: 전달 성공 ✅ + → ResultCode = 4: 템플릿 데이터 일치 오류 ❌ + → ResultCode != 1: 기타 실패 ❌ +``` + +```php +// 반드시 2단계 검증 필요 +if ($result['success'] && is_string($result['data'])) { + $sendKey = $result['data']; + sleep(3); // 카카오톡 전달 대기 + $sendResult = $barobill->getSendKakaotalk($member->biz_no, $sendKey); + $resultCode = $sendResult['data']->ResultCode ?? null; + if ($resultCode != 1) { + // 실패 처리! + } +} +``` + +### 4.3 템플릿 URL 정확 일치 규칙 + +> **핵심**: 버튼 URL은 등록된 템플릿의 URL과 **정확히 일치**해야 한다. 1글자라도 다르면 실패. + +| 등록된 URL | 전송 시 URL | 결과 | +|------------|------------|------| +| `https://mng.codebridge-x.com` | `https://mng.codebridge-x.com` | ResultCode=1 (성공) | +| `https://mng.codebridge-x.com` | `https://mng.codebridge-x.com/esign/sign/xxx` | ResultCode=4 (실패) | +| `https://mng.codebridge-x.com` | `https://mng.codebridge-x.com?sign=xxx` | ResultCode=4 (실패) | +| `https://mng.codebridge-x.com` | `https://mng.codebridge-x.com#sign=xxx` | ResultCode=4 (실패) | + +- 경로 추가: 실패 +- 쿼리 파라미터 추가: 실패 +- URL 프래그먼트(#) 추가: 실패 +- **동적 URL을 사용하려면 템플릿에 `#{변수}` 포함하여 재등록 필요** + +### 4.4 SmsReply 오류 (-31325) + +`SmsReply` 파라미터가 `'S'`(대체발송 사용)인데 `SmsSenderNum`이 비어있으면 `-31325` 오류 발생. + +```php +// ❌ 오류 발생 +'SmsReply' => empty($smsMessage) ? 'N' : 'S', // SmsSenderNum이 비어도 S로 설정 + +// ✅ 수정 +'SmsReply' => (empty($smsMessage) || empty($smsSenderNum)) ? 'N' : 'S', +``` + +### 4.5 SOAP 파라미터 구조 + +바로빌 SOAP API의 파라미터 구조에 주의: + +```php +// 올바른 구조 +$params = [ + 'CorpNum' => $bizNo, // 사업자번호 (하이픈 포함: 123-45-67890) + 'SenderID' => $barobillId, // 바로빌 계정 ID + 'YellowId' => $channelId, // 카카오 채널 ID (@codebridge) + 'TemplateName' => '전자계약_서명요청', + 'SendDT' => '', // 즉시발송: 빈 문자열 + 'SmsReply' => 'N', // SMS 발신번호 없으면 반드시 'N' + 'SmsSenderNum' => '', + 'KakaotalkMessage' => [ + 'ReceiverName' => $name, + 'ReceiverNum' => $phone, // 하이픈 없이: 01012345678 + 'Title' => '', + 'Message' => $message, // 템플릿 변수 치환 완료된 본문 + 'SmsMessage' => '', + 'SmsSubject' => '', + 'Buttons' => ['KakaotalkButton' => $buttons], // 버튼 배열 + ], +]; +``` + +### 4.6 에러 코드 정리 + +| 코드 | 메시지 | 원인 | 해결 | +|------|--------|------|------| +| 1 | 성공 | 정상 전달 | - | +| 4 | 템플릿 데이터 일치 오류 | 본문/버튼 URL이 등록 템플릿과 불일치 | 등록된 템플릿과 동일하게 전송 | +| -31325 | 대체문자 유형 오류 | SmsReply=S인데 SmsSenderNum 비어있음 | SmsReply를 N으로 설정 | +| 음수값 | 바로빌 API 오류 | 파라미터 오류 또는 서비스 미설정 | 바로빌 에러코드 문서 참조 | + +--- + +## 5. 구현 현황 + +### 5.1 완료된 항목 + +| 구분 | 파일 | 설명 | +|------|------|------| +| SOAP 서비스 | `app/Services/Barobill/BarobillService.php` | kakaotalk SOAP 클라이언트 + 15개 API 메서드 | +| 전자계약 알림톡 | `app/Http/Controllers/ESign/EsignApiController.php` | `sendAlimtalk()`, `getTemplateData()` | +| 서명 확인 | `app/Http/Controllers/ESign/EsignPublicController.php` | `verifyPhone()` 전화번호 기반 서명 확인 | +| API 컨트롤러 | `app/Http/Controllers/Api/Admin/Barobill/BarobillKakaotalkController.php` | 15개 API 엔드포인트 | +| 페이지 컨트롤러 | `app/Http/Controllers/Barobill/KakaotalkController.php` | 6개 관리 페이지 | +| 로그인 페이지 | `resources/views/auth/login.blade.php` | 전자계약 서명하기 섹션 | +| 라우트 | `routes/web.php` | `/esign/verify-phone`, `/barobill/kakaotalk/*` | +| 메뉴 등록 | DB (menus 테이블) | 로컬/서버 모두 등록 완료 | + +### 5.2 검증 완료 항목 + +| 항목 | 결과 | 날짜 | +|------|------|------| +| 채널 API 호출 | **성공** | 2026-02-22 | +| 템플릿 조회 | **성공** | 2026-02-22 | +| 알림톡 발송 (본문) | **성공** (ResultCode=1) | 2026-02-24 | +| 알림톡 버튼 URL | **성공** (등록된 URL 사용 시) | 2026-02-24 | +| 전달 결과 확인 (2단계) | **구현 완료** | 2026-02-24 | +| 로그인 페이지 서명 확인 | **성공** | 2026-02-24 | + +### 5.3 완료된 추가 항목 (2026-02-26~27) + +| 항목 | 상태 | 비고 | +|------|------|------| +| 템플릿 v2 승인 | **완료** | 버튼 URL에 `#{토큰}` 변수 포함 3종 승인 | +| `전자계약_완료` 템플릿 | **완료** | 서명 완료 알림 발송 — PDF 다운로드 버튼 | +| 역할 기반 알림 분기 | **완료** | 본사(creator)=이메일, 상대방(counterpart)=알림톡 | +| OTP SMS 발송 | **완료** | 상대방에게 SMS로 인증코드 발송 | +| 환경별 템플릿 분기 | **완료** | `resolveTemplateName()` — `_DEV` 접미사 자동 적용 | +| 서명 PDF 재생성 | **완료** | `downloadDocument()`에서 완료 계약 PDF 자동 재생성 | + +> 상세 가이드: [전자계약 알림톡/SMS 환경별 설정 가이드](./esign-notification-guide.md) + +### 5.4 대기 중인 항목 + +| 항목 | 상태 | 비고 | +|------|------|------| +| DEV 템플릿 검수 | **심사 중** | `admin.codebridge-x.com` 도메인 3종 | +| 친구톡 발송 | **대기** | 채널 친구 추가 후 가능 | +| 대량 발송 | **대기** | 단건 안정화 후 | + +--- + +## 6. v2 템플릿 승인 후 코드 변경 가이드 + +### 6.1 변경 대상 + +`EsignApiController::sendAlimtalk()` (약 1059~1063행) + +### 6.2 현재 코드 (v1) + +```php +// 등록된 버튼 URL을 그대로 사용 (동적 URL 사용 시 템플릿 불일치 오류) +$buttons = ! empty($templateButtons) ? $templateButtons : [ + ['Name' => '계약서 확인하기', 'ButtonType' => 'WL', + 'Url1' => 'https://mng.codebridge-x.com', 'Url2' => 'https://mng.codebridge-x.com'], +]; +``` + +### 6.3 변경 코드 (v2 승인 후) + +```php +// v2 템플릿: 버튼 URL에 동적 서명 URL 사용 +$buttons = [ + ['Name' => '계약서 확인하기', 'ButtonType' => 'WL', + 'Url1' => $signUrl, 'Url2' => $signUrl], +]; +``` + +- `$signUrl`은 1033행에서 이미 생성됨: `config('app.url').'/esign/sign/'.$signer->access_token` +- `getTemplateData()`에서 등록된 버튼 조회는 더 이상 필요 없음 (제거 가능) + +--- + +## 7. API 메서드 목록 + +### 7.1 BarobillService 카카오톡 메서드 + +| 메서드 | SOAP Action | 설명 | +|--------|-------------|------| +| `getKakaotalkChannels` | `GetKakaotalkChannels` | 채널 목록 조회 | +| `getKakaotalkChannelManagementUrl` | `GetKakaotalkChannelManagementURL` | 채널 관리 URL | +| `getKakaotalkTemplates` | `GetKakaotalkTemplates` | 템플릿 목록 조회 | +| `getKakaotalkTemplateManagementUrl` | `GetKakaotalkTemplateManagementURL` | 템플릿 관리 URL | +| `sendATKakaotalk` | `SendATKakaotalk` | 알림톡 단건 발송 | +| `sendATKakaotalkEx` | `SendATKakaotalkEx` | 알림톡 단건 발송 (버튼 포함) | +| `sendATKakaotalks` | `SendATKakaotalks` | 알림톡 대량 발송 | +| `sendFTKakaotalk` | `SendFTKakaotalk` | 친구톡 텍스트 단건 | +| `sendFTKakaotalks` | `SendFTKakaotalks` | 친구톡 텍스트 대량 | +| `sendFIKakaotalk` | `SendFIKakaotalk` | 친구톡 이미지 | +| `sendFWKakaotalk` | `SendFWKakaotalk` | 친구톡 와이드 이미지 | +| `getSendKakaotalk` | `GetSendKakaotalk` | 전송 결과 단건 조회 | +| `getSendKakaotalks` | `GetSendKakaotalks` | 전송 결과 다건 조회 | +| `cancelReservedKakaotalk` | `CancelReservedKakaotalk` | 예약 전송 취소 | + +--- + +## 8. 참고 자료 + +- [바로빌 API 문서](https://dev.barobill.co.kr) +- [카카오비즈니스 채널 관리](https://business.kakao.com) +- [카카오 알림톡 가이드](https://kakaobusiness.gitbook.io) +- 바로빌 템플릿 관리: 로그인 후 `https://www.barobill.co.kr` → 카카오톡 템플릿 관리 + +--- + +## 변경 이력 + +| 날짜 | 버전 | 변경 내용 | +|------|------|----------| +| 2026-02-27 | 1.1 | 역할 기반 알림, OTP SMS, 환경별 템플릿 분기, 완료 알림톡 추가 | +| 2026-02-24 | 1.0 | 전자계약 알림톡 연동 완료, 트러블슈팅 문서화, v2 템플릿 가이드 추가 | +| 2026-02-14 | 0.2 | 전자계약(E-Sign) 알림톡 연동 활용 계획 추가 | +| 2026-02-14 | 0.1 | 초안 작성 - 코드 구현 완료, 실 서비스 연동 대기 | diff --git a/sam/docs/features/barobill-kakaotalk/esign-notification-guide.md b/sam/docs/features/barobill-kakaotalk/esign-notification-guide.md new file mode 100644 index 0000000..dce2345 --- /dev/null +++ b/sam/docs/features/barobill-kakaotalk/esign-notification-guide.md @@ -0,0 +1,250 @@ +# 전자계약 알림톡/SMS 환경별 설정 가이드 + +> **작성일**: 2026-02-27 +> **상태**: 운영 중 +> **대상 프로젝트**: MNG + +--- + +## 1. 개요 + +### 1.1 목적 + +전자계약(E-Sign) 시스템의 카카오톡 알림톡, SMS, 이메일 발송을 **3개 환경(로컬/개발/운영)**에서 올바르게 설정하고 테스트하기 위한 가이드이다. + +### 1.2 핵심 원칙 + +- **역할 기반 알림**: 본사(creator)는 이메일, 상대방(counterpart)은 카카오톡/SMS +- **환경별 템플릿 분리**: 운영은 원본 템플릿, 개발은 `_DEV` 접미사 템플릿 사용 +- **URL 자동 분기**: `config('app.url')`로 환경별 도메인 자동 적용 + +--- + +## 2. 환경별 설정 + +### 2.1 도메인 및 APP_URL + +| 환경 | `APP_ENV` | `APP_URL` | 알림톡 버튼 URL 도메인 | +|------|-----------|-----------|----------------------| +| 로컬 (Docker) | `local` | `https://mng.sam.kr` | 로컬 — 알림톡 미사용 | +| 개발 서버 | `local` | `https://admin.codebridge-x.com` | `admin.codebridge-x.com` | +| 운영 서버 | `production` | `https://mng.codebridge-x.com` | `mng.codebridge-x.com` | + +### 2.2 바로빌 서버 모드 + +`barobill_members.server_mode` 컬럼으로 바로빌 API 엔드포인트를 결정한다: + +| server_mode | WSDL (카카오톡) | WSDL (SMS) | 용도 | +|-------------|----------------|------------|------| +| `test` | `testws.baroservice.com/KAKAOTALK.asmx` | `testws.baroservice.com/SMS.asmx` | 테스트 | +| `production` | `ws.baroservice.com/KAKAOTALK.asmx` | `ws.baroservice.com/SMS.asmx` | 실제 발송 | + +> `server_mode`는 환경(로컬/개발/운영)과 독립적이다. 개발서버에서도 `production` 모드로 실제 발송 가능. + +### 2.3 알림톡 템플릿 환경별 분기 + +코드에서 `resolveTemplateName()` 메서드가 `APP_ENV`에 따라 템플릿명을 자동 결정한다: + +```php +private function resolveTemplateName(string $baseName): string +{ + return $baseName . (app()->environment('production') ? '' : '_DEV'); +} +``` + +| 기본 템플릿명 | 운영 (`production`) | 개발/로컬 (기타) | +|-------------|--------------------|--------------------| +| `전자계약_서명요청` | `전자계약_서명요청` | `전자계약_서명요청_DEV` | +| `전자계약_완료` | `전자계약_완료` | `전자계약_완료_DEV` | +| `전자계약_리마인드` | `전자계약_리마인드` | `전자계약_리마인드_DEV` | + +--- + +## 3. 등록된 알림톡 템플릿 + +### 3.1 운영 템플릿 (mng.codebridge-x.com) + +| 템플릿명 | 용도 | 상태 | 버튼 URL | +|---------|------|------|---------| +| `전자계약_서명요청` | 서명 요청 알림 | 승인 완료 | `https://mng.codebridge-x.com/esign/sign/#{토큰}` | +| `전자계약_완료` | 서명 완료 알림 | 승인 완료 | `https://mng.codebridge-x.com/esign/sign/#{토큰}` | +| `전자계약_리마인드` | 서명 독촉 알림 | 승인 완료 | `https://mng.codebridge-x.com/esign/sign/#{토큰}` | + +### 3.2 개발 템플릿 (admin.codebridge-x.com) + +| 템플릿명 | 용도 | 상태 | 버튼 URL | +|---------|------|------|---------| +| `전자계약_서명요청_DEV` | 서명 요청 알림 | 심사 중 | `https://admin.codebridge-x.com/esign/sign/#{토큰}` | +| `전자계약_완료_DEV` | 서명 완료 알림 | 심사 중 | `https://admin.codebridge-x.com/esign/sign/#{토큰}` | +| `전자계약_리마인드_DEV` | 서명 독촉 알림 | 심사 중 | `https://admin.codebridge-x.com/esign/sign/#{토큰}` | + +> 개발 템플릿 본문은 운영 템플릿과 동일하며, 버튼 URL 도메인만 다르다. + +### 3.3 템플릿 변수 + +| 변수 | 용도 | 사용 템플릿 | +|------|------|-----------| +| `#{이름}` | 서명자 이름 | 서명요청, 완료, 리마인드 | +| `#{계약명}` | 계약 제목 | 서명요청, 완료, 리마인드 | +| `#{기한}` | 서명 기한 | 서명요청, 리마인드 | +| `#{완료일}` | 계약 완료일 | 완료 | +| `#{토큰}` | 서명자 액세스 토큰 | 버튼 URL | + +--- + +## 4. 역할 기반 알림 흐름 + +### 4.1 전체 흐름 + +``` +① 계약 발송 ─→ 본사: 이메일 / 상대방: 카카오톡 알림톡 +② OTP 인증 ─→ 본사: 이메일 / 상대방: SMS +③ 다음 서명자 ─→ 본사: 이메일 / 상대방: 카카오톡 알림톡 +④ 서명 완료 ─→ 본사: 이메일(PDF) / 상대방: 카카오톡(PDF 다운로드) +``` + +### 4.2 역할 판별 + +```php +$isCounterpart = $signer->role === EsignSigner::ROLE_COUNTERPART; +``` + +| 역할 | 상수 | 알림톡 | SMS(OTP) | 이메일 | +|------|------|--------|----------|--------| +| 본사 (creator) | `ROLE_CREATOR` | ❌ | ❌ | ✅ 항상 | +| 상대방 (counterpart) | `ROLE_COUNTERPART` | ✅ 우선 | ✅ OTP만 | ✅ 폴백 | + +### 4.3 이메일 폴백 조건 + +상대방(counterpart)에게도 이메일을 보내는 경우: +- 전화번호가 없을 때 (`$signer->phone` 없음) +- 알림톡 발송 실패 시 (`$alimtalkFailed = true`) +- 발송 방식이 `email` 또는 `both`일 때 + +### 4.4 완료 알림 특수 처리 + +완료 알림톡 버튼은 **서명 페이지가 아닌 문서 다운로드 URL**로 강제 변경된다: + +```php +// sendCompletionAlimtalk() 내부 +$documentUrl = config('app.url') . '/esign/sign/' . $signer->access_token . '/api/document'; + +// 버튼 URL 강제 변경 (서명페이지 → 문서 다운로드) +if (str_contains($btn[$urlKey], '/esign/sign/') && !str_contains($btn[$urlKey], '/api/document')) { + $btn[$urlKey] = $documentUrl; +} +``` + +--- + +## 5. SMS (OTP 인증) + +### 5.1 발송 조건 + +상대방(counterpart)이 `alimtalk` 또는 `both` 발송 방식이고 전화번호가 있을 때 SMS로 OTP 발송: + +```php +if (in_array($sendMethod, ['alimtalk', 'both']) + && $signer->phone + && $signer->role === EsignSigner::ROLE_COUNTERPART) { + $this->sendOtpViaSms($contract, $signer, $otpCode); +} +``` + +### 5.2 SMS 발송 파라미터 + +| 항목 | 값 | +|------|-----| +| API | `BarobillService::sendSMSMessage()` | +| 발신번호 | `barobill_members.manager_hp` | +| 수신번호 | `esign_signers.phone` | +| 메시지 | `[SAM] 전자계약 인증코드: {코드} (5분 이내 입력)` | +| OTP 유효시간 | 5분 | +| 최대 시도 | 5회 | + +### 5.3 SMS 실패 시 이메일 폴백 + +SMS 발송 실패 → 이메일 OTP 폴백 → 이메일도 없으면 500 에러 반환. + +--- + +## 6. 바로빌 템플릿 등록 절차 + +### 6.1 관리자 페이지 + +``` +https://www.barobill.co.kr 로그인 → 카카오톡 → 템플릿관리 +``` + +### 6.2 DEV 템플릿 등록 시 주의사항 + +1. **본문**: 운영 템플릿과 **완전히 동일** (1글자도 다르면 안 됨) +2. **버튼 URL**: 도메인만 `admin.codebridge-x.com`으로 변경 +3. **템플릿명**: 운영 이름 + `_DEV` 접미사 (예: `전자계약_서명요청_DEV`) +4. **검수 기간**: 영업일 기준 2~3일 + +### 6.3 새 템플릿 추가 시 체크리스트 + +- [ ] 바로빌에서 운영용 + 개발용 2개 등록 +- [ ] 코드에서 `resolveTemplateName('기본명')`으로 호출 +- [ ] 본문의 변수 치환 로직 추가 (str_replace) +- [ ] 버튼 URL의 `#{토큰}` 치환 확인 +- [ ] 2단계 검증 (SendKey → GetSendKakaotalk) 포함 + +--- + +## 7. 관련 파일 + +| 파일 | 역할 | +|------|------| +| `app/Http/Controllers/ESign/EsignApiController.php` | 계약 발송, `sendAlimtalk()`, `resolveTemplateName()` | +| `app/Http/Controllers/ESign/EsignPublicController.php` | OTP SMS, 완료 알림톡, `sendCompletionAlimtalk()` | +| `app/Services/Barobill/BarobillService.php` | SOAP 클라이언트 (`sendATKakaotalkEx`, `sendSMSMessage`) | +| `app/Models/ESign/EsignSigner.php` | `ROLE_CREATOR`, `ROLE_COUNTERPART` 상수 | +| `app/Mail/EsignCompletedMail.php` | 완료 이메일 (PDF 다운로드 링크) | +| `app/Services/ESign/PdfSignatureService.php` | 서명 PDF 합성 (`mergeSignatures`) | + +--- + +## 8. 트러블슈팅 + +### 8.1 환경별 템플릿 미스매치 + +**증상**: `ResultCode=4` (템플릿 데이터 일치 오류) +**원인**: 개발서버에서 운영용 템플릿(`전자계약_서명요청`)으로 발송 시 버튼 URL 도메인 불일치 +**해결**: DEV 템플릿 등록 후 `APP_ENV`가 `production`이 아닌지 확인 + +### 8.2 서명 PDF 누락 (이메일) + +**증상**: 완료 이메일의 다운로드 링크가 서명 없는 초안 PDF 반환 +**원인**: `mergeSignatures()` 실패 → `signed_file_path` 미설정 → preview PDF 폴백 +**해결**: `downloadDocument()`가 완료 상태에서 자동 재생성 시도. 로그에서 trace 확인: + +```bash +# 개발서버 로그 확인 +ssh pro@114.203.209.83 "tail -100 /home/webservice/mng/storage/logs/laravel.log | grep 'PDF 서명'" +``` + +**주요 실패 원인**: +- `storage/fonts/Pretendard-Regular.ttf` 폰트 파일 누락 +- FPDI/TCPDF 패키지 미설치 → `composer install` 필요 +- `storage/app/esign/{tenant_id}/signed/` 디렉토리 권한 문제 + +### 8.3 MNG 모델 상수 누락 + +**증상**: `Undefined constant App\Models\ESign\EsignSigner::ROLE_COUNTERPART` +**원인**: API 프로젝트와 MNG 프로젝트의 모델이 독립적 — API에만 상수 정의됨 +**해결**: MNG `EsignSigner.php`에도 동일한 상수 추가 (2026-02-26 핫픽스 완료) + +--- + +## 관련 문서 + +- [바로빌 카카오톡 연동 README](./README.md) — SOAP API 전체 연동 가이드 +- [E-Sign 기술 설계](../../projects/e-sign/technical-design.md) — 전자계약 아키텍처 +- [E-Sign API 명세](../../projects/e-sign/api-specification.md) — API 엔드포인트 +- [알림톡 연동 계획](../../plans/esign-alimtalk-integration.md) — 초기 계획 (구현 완료) + +--- + +**최종 업데이트**: 2026-02-27