fix: [barobill] 알림톡 발송 파라미터 구조 전면 수정

- SenderID: 바로빌 계정 ID (barobill_id) 사용
- YellowId: 카카오톡 채널 ID 필수 전달 (기존에 빈값)
- SmsReply: 'N' (대체문자 미발송) 또는 'S' (발송) 명시
- 템플릿 본문: API에서 조회 후 변수 치환 (정확한 포맷 유지)
- getKakaotalkSenderId → getKakaotalkChannelId 이름 변경
- EsignPublicController: 잘못된 필드명(corp_num, is_test_mode) 수정
This commit is contained in:
김보곤
2026-02-24 10:04:54 +09:00
parent 2ae07d40af
commit e12d0d1607
4 changed files with 213 additions and 88 deletions

View File

@@ -7,7 +7,6 @@
use App\Services\Barobill\BarobillService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class BarobillKakaotalkController extends Controller
{
@@ -23,7 +22,7 @@ private function resolveMember(): BarobillMember|JsonResponse
$tenantId = session('selected_tenant_id', 1);
$member = BarobillMember::where('tenant_id', $tenantId)->first();
if (!$member) {
if (! $member) {
return response()->json([
'success' => false,
'message' => '바로빌 회원사가 등록되어 있지 않습니다.',
@@ -45,11 +44,13 @@ private function resolveMember(): BarobillMember|JsonResponse
public function getChannels(): JsonResponse
{
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->getKakaotalkChannels($member->biz_no);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -62,14 +63,16 @@ public function getChannels(): JsonResponse
public function getChannelManagementUrl(): JsonResponse
{
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->getKakaotalkChannelManagementUrl(
$member->biz_no,
$member->barobill_id
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -90,14 +93,16 @@ public function getTemplates(Request $request): JsonResponse
]);
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->getKakaotalkTemplates(
$member->biz_no,
$request->input('channel_id')
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -110,14 +115,16 @@ public function getTemplates(Request $request): JsonResponse
public function getTemplateManagementUrl(): JsonResponse
{
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->getKakaotalkTemplateManagementUrl(
$member->biz_no,
$member->barobill_id
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -151,41 +158,48 @@ public function sendAlimtalk(Request $request): JsonResponse
]);
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$buttons = $validated['buttons'] ?? [];
// sender_id 폼 필드는 채널 ID (예: @codebridge-x)
// SenderID는 바로빌 계정 ID, YellowId는 카카오톡 채널 ID
$yellowId = $validated['sender_id'];
// 버튼이 있으면 Ex 버전 사용
if (!empty($buttons)) {
if (! empty($buttons)) {
$result = $this->barobillService->sendATKakaotalkEx(
$member->biz_no,
$validated['sender_id'],
$validated['template_name'],
$validated['receiver_name'],
$validated['receiver_num'],
$validated['title'] ?? '',
$validated['message'],
$buttons,
$validated['sms_message'] ?? '',
$validated['sms_subject'] ?? '',
$validated['reserve_dt'] ?? ''
corpNum: $member->biz_no,
senderId: $member->barobill_id,
yellowId: $yellowId,
templateName: $validated['template_name'],
receiverName: $validated['receiver_name'],
receiverNum: $validated['receiver_num'],
title: $validated['title'] ?? '',
message: $validated['message'],
buttons: $buttons,
smsMessage: $validated['sms_message'] ?? '',
smsSubject: $validated['sms_subject'] ?? '',
reserveDT: $validated['reserve_dt'] ?? ''
);
} else {
$result = $this->barobillService->sendATKakaotalk(
$member->biz_no,
$validated['sender_id'],
$validated['template_name'],
$validated['receiver_name'],
$validated['receiver_num'],
$validated['title'] ?? '',
$validated['message'],
$validated['sms_message'] ?? '',
$validated['sms_subject'] ?? '',
$validated['reserve_dt'] ?? ''
corpNum: $member->biz_no,
senderId: $member->barobill_id,
yellowId: $yellowId,
templateName: $validated['template_name'],
receiverName: $validated['receiver_name'],
receiverNum: $validated['receiver_num'],
title: $validated['title'] ?? '',
message: $validated['message'],
smsMessage: $validated['sms_message'] ?? '',
smsSubject: $validated['sms_subject'] ?? '',
reserveDT: $validated['reserve_dt'] ?? ''
);
}
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -211,17 +225,20 @@ public function sendAlimtalkBulk(Request $request): JsonResponse
]);
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->sendATKakaotalks(
$member->biz_no,
$validated['sender_id'],
$validated['template_name'],
$validated['messages'],
$validated['reserve_dt'] ?? ''
corpNum: $member->biz_no,
senderId: $member->barobill_id,
yellowId: $validated['sender_id'],
templateName: $validated['template_name'],
messages: $validated['messages'],
reserveDT: $validated['reserve_dt'] ?? ''
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -255,7 +272,9 @@ public function sendFriendtalk(Request $request): JsonResponse
]);
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->sendFTKakaotalk(
$member->biz_no,
@@ -271,7 +290,7 @@ public function sendFriendtalk(Request $request): JsonResponse
$validated['reserve_dt'] ?? ''
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -298,7 +317,9 @@ public function sendFriendtalkImage(Request $request): JsonResponse
]);
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->sendFIKakaotalk(
$member->biz_no,
@@ -315,7 +336,7 @@ public function sendFriendtalkImage(Request $request): JsonResponse
$validated['reserve_dt'] ?? ''
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -342,7 +363,9 @@ public function sendFriendtalkWide(Request $request): JsonResponse
]);
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->sendFWKakaotalk(
$member->biz_no,
@@ -359,7 +382,7 @@ public function sendFriendtalkWide(Request $request): JsonResponse
$validated['reserve_dt'] ?? ''
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -376,11 +399,13 @@ public function sendFriendtalkWide(Request $request): JsonResponse
public function getSendResult(string $sendKey): JsonResponse
{
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->getSendKakaotalk($member->biz_no, $sendKey);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -398,14 +423,16 @@ public function getSendResults(Request $request): JsonResponse
]);
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->getSendKakaotalks(
$member->biz_no,
$validated['send_keys']
);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}
@@ -422,11 +449,13 @@ public function getSendResults(Request $request): JsonResponse
public function cancelReserved(string $sendKey): JsonResponse
{
$member = $this->resolveMember();
if ($member instanceof JsonResponse) return $member;
if ($member instanceof JsonResponse) {
return $member;
}
$result = $this->barobillService->cancelReservedKakaotalk($member->biz_no, $sendKey);
if (!$result['success']) {
if (! $result['success']) {
return response()->json($result, 422);
}

View File

@@ -970,9 +970,9 @@ private function sendAlimtalk(
$barobill = app(BarobillService::class);
$barobill->setServerMode($member->server_mode ?? 'production');
// 카카오톡 채널 목록에서 발신프로필 키(SenderID) 조회
$senderId = $this->getKakaotalkSenderId($barobill, $member->biz_no);
if (! $senderId) {
// 카카오톡 채널 ID 조회 (YellowId로 사용)
$channelId = $this->getKakaotalkChannelId($barobill, $member->biz_no);
if (! $channelId) {
return ['success' => false, 'channel' => 'alimtalk', 'error' => '등록된 카카오톡 채널이 없습니다'];
}
@@ -981,10 +981,21 @@ private function sendAlimtalk(
$templateName = $isReminder ? '전자계약_리마인드' : '전자계약_서명요청';
// 등록된 템플릿 본문과 정확히 일치해야 함 (변수 치환)
$message = $isReminder
? "안녕하세요, {$signer->name}님.\n아직 서명이 완료되지 않은 전자계약이 있습니다.\n\n ■ 계약명: {$contract->title}\n ■ 서명 기한: {$expires}\n\n 기한 내에 서명을 완료해 주세요."
: " 안녕하세요, {$signer->name}님. \n 전자계약 서명 요청이 도착했습니다.\n\n ■ 계약명: {$contract->title}\n ■ 서명 기한: {$expires}\n\n 아래 버튼을 눌러 계약서를 확인하고 서명해 주세요.";
// 등록된 템플릿 본문을 가져와서 변수 치환 (정확한 포맷 유지)
$templateContent = $this->getTemplateContent($barobill, $member->biz_no, $channelId, $templateName);
if ($templateContent) {
$message = str_replace(
['#{이름}', '#{계약명}', '#{기한}'],
[$signer->name, $contract->title, $expires],
$templateContent
);
} else {
// 템플릿 조회 실패 시 하드코딩 폴백
$message = $isReminder
? "안녕하세요, {$signer->name}님.\n아직 서명이 완료되지 않은 전자계약이 있습니다.\n\n ■ 계약명: {$contract->title}\n ■ 서명 기한: {$expires}\n\n 기한 내에 서명을 완료해 주세요."
: " 안녕하세요, {$signer->name}님. \n 전자계약 서명 요청이 도착했습니다.\n\n ■ 계약명: {$contract->title}\n ■ 서명 기한: {$expires}\n\n 아래 버튼을 눌러 계약서를 확인하고 서명해 주세요.";
}
$smsMessage = $smsFallback
? "[SAM] {$signer->name}님, 전자계약 서명 요청이 도착했습니다. {$signUrl}"
@@ -992,15 +1003,16 @@ private function sendAlimtalk(
$result = $barobill->sendATKakaotalkEx(
corpNum: $member->biz_no,
senderId: $senderId,
senderId: $member->barobill_id,
yellowId: $channelId,
templateName: $templateName,
receiverName: $signer->name,
receiverNum: preg_replace('/[^0-9]/', '', $signer->phone),
title: $isReminder ? '전자계약 리마인드' : '전자계약 서명 요청',
title: '',
message: $message,
buttons: [
[
'Name' => $isReminder ? '서명하기' : '계약서 확인하기',
'Name' => '계약서 확인하기',
'ButtonType' => 'WL',
'Url1' => $signUrl,
'Url2' => $signUrl,
@@ -1032,9 +1044,9 @@ private function sendAlimtalk(
}
/**
* 바로빌 카카오톡 채널 목록에서 발신프로필 키(ChannelId) 조회
* 바로빌 카카오톡 채널 ID 조회 (YellowId로 사용)
*/
private function getKakaotalkSenderId(BarobillService $barobill, string $bizNo): ?string
private function getKakaotalkChannelId(BarobillService $barobill, string $bizNo): ?string
{
$result = $barobill->getKakaotalkChannels($bizNo);
@@ -1044,10 +1056,7 @@ private function getKakaotalkSenderId(BarobillService $barobill, string $bizNo):
$data = $result['data'];
// 바로빌 응답 형식에 따라 채널 목록 파싱
if (is_array($data) && isset($data[0])) {
$channels = $data;
} elseif (is_object($data) && isset($data->KakaotalkChannel)) {
if (is_object($data) && isset($data->KakaotalkChannel)) {
$channels = is_array($data->KakaotalkChannel)
? $data->KakaotalkChannel
: [$data->KakaotalkChannel];
@@ -1056,7 +1065,7 @@ private function getKakaotalkSenderId(BarobillService $barobill, string $bizNo):
? $data['KakaotalkChannel']
: [$data['KakaotalkChannel']];
} else {
$channels = [$data];
$channels = is_array($data) ? $data : [$data];
}
$channel = $channels[0] ?? null;
@@ -1070,6 +1079,35 @@ private function getKakaotalkSenderId(BarobillService $barobill, string $bizNo):
: ($channel->ChannelId ?? null);
}
/**
* 바로빌 등록 템플릿의 본문 내용 조회
*/
private function getTemplateContent(BarobillService $barobill, string $bizNo, string $channelId, string $templateName): ?string
{
$result = $barobill->getKakaotalkTemplates($bizNo, $channelId);
if (! ($result['success'] ?? false) || empty($result['data'])) {
return null;
}
$data = $result['data'];
$items = [];
if (is_object($data) && isset($data->KakaotalkTemplate)) {
$items = is_array($data->KakaotalkTemplate)
? $data->KakaotalkTemplate
: [$data->KakaotalkTemplate];
}
foreach ($items as $tpl) {
if (($tpl->TemplateName ?? '') === $templateName) {
return $tpl->TemplateContent ?? null;
}
}
return null;
}
/**
* PDF 다운로드
*/

View File

@@ -387,17 +387,51 @@ public function submitSignature(Request $request, string $token): JsonResponse
$member = BarobillMember::where('tenant_id', $contract->tenant_id)->first();
if ($member) {
$barobill = app(BarobillService::class);
$barobill->setServerMode($member->is_test_mode ? 'test' : 'production');
$barobill->setServerMode($member->server_mode ?? 'production');
$nextSignUrl = config('app.url').'/esign/sign/'.$nextSigner->access_token;
$nextExpires = $contract->expires_at?->format('Y-m-d H:i') ?? '없음';
// 채널 ID 조회
$channelResult = $barobill->getKakaotalkChannels($member->biz_no);
$yellowId = '';
if ($channelResult['success'] ?? false) {
$chData = $channelResult['data'];
if (is_object($chData) && isset($chData->KakaotalkChannel)) {
$ch = is_array($chData->KakaotalkChannel) ? $chData->KakaotalkChannel[0] : $chData->KakaotalkChannel;
$yellowId = $ch->ChannelId ?? '';
}
}
// 템플릿 본문 조회하여 변수 치환
$tplResult = $barobill->getKakaotalkTemplates($member->biz_no, $yellowId);
$tplMessage = null;
if ($tplResult['success'] ?? false) {
$tplData = $tplResult['data'];
$tplItems = [];
if (is_object($tplData) && isset($tplData->KakaotalkTemplate)) {
$tplItems = is_array($tplData->KakaotalkTemplate) ? $tplData->KakaotalkTemplate : [$tplData->KakaotalkTemplate];
}
foreach ($tplItems as $t) {
if (($t->TemplateName ?? '') === '전자계약_서명요청') {
$tplMessage = str_replace(
['#{이름}', '#{계약명}', '#{기한}'],
[$nextSigner->name, $contract->title, $nextExpires],
$t->TemplateContent
);
break;
}
}
}
$atResult = $barobill->sendATKakaotalkEx(
corpNum: $member->corp_num,
senderId: $member->kakaotalk_sender_id ?? '',
corpNum: $member->biz_no,
senderId: $member->barobill_id,
yellowId: $yellowId,
templateName: '전자계약_서명요청',
receiverName: $nextSigner->name,
receiverNum: preg_replace('/[^0-9]/', '', $nextSigner->phone),
title: '전자계약 서명 요청',
message: "안녕하세요, {$nextSigner->name}님.\n전자계약 서명 요청이 도착했습니다.\n\n■ 계약명: {$contract->title}\n■ 서명 기한: {$nextExpires}\n\n아래 버튼을 눌러 계약서를 확인하고 서명해 주세요.",
title: '',
message: $tplMessage ?? "안녕하세요, {$nextSigner->name}님.\n전자계약 서명 요청이 도착했습니다.\n\n■ 계약명: {$contract->title}\n■ 서명 기한: {$nextExpires}\n\n아래 버튼을 눌러 계약서를 확인하고 서명해 주세요.",
buttons: [['Name' => '계약서 확인하기', 'ButtonType' => 'WL', 'Url1' => $nextSignUrl, 'Url2' => $nextSignUrl]],
smsMessage: $nextSmsFallback ? "[SAM] {$nextSigner->name}님, 전자계약 서명 요청이 도착했습니다. {$nextSignUrl}" : '',
);
@@ -536,18 +570,30 @@ private function sendCompletionAlimtalk(EsignContract $contract, EsignSigner $si
}
$barobill = app(BarobillService::class);
$barobill->setServerMode($member->is_test_mode ? 'test' : 'production');
$barobill->setServerMode($member->server_mode ?? 'production');
// 채널 ID 조회
$channelResult = $barobill->getKakaotalkChannels($member->biz_no);
$yellowId = '';
if ($channelResult['success'] ?? false) {
$chData = $channelResult['data'];
if (is_object($chData) && isset($chData->KakaotalkChannel)) {
$ch = is_array($chData->KakaotalkChannel) ? $chData->KakaotalkChannel[0] : $chData->KakaotalkChannel;
$yellowId = $ch->ChannelId ?? '';
}
}
$signUrl = config('app.url').'/esign/sign/'.$signer->access_token;
$completedAt = $contract->completed_at?->format('Y-m-d H:i') ?? now()->format('Y-m-d H:i');
$result = $barobill->sendATKakaotalkEx(
corpNum: $member->corp_num,
senderId: $member->kakaotalk_sender_id ?? '',
corpNum: $member->biz_no,
senderId: $member->barobill_id,
yellowId: $yellowId,
templateName: '전자계약_완료',
receiverName: $signer->name,
receiverNum: preg_replace('/[^0-9]/', '', $signer->phone),
title: '전자계약 완료',
title: '',
message: "안녕하세요, {$signer->name}님.\n전자계약이 모든 서명자의 서명 완료로 확정되었습니다.\n\n■ 계약명: {$contract->title}\n■ 완료일: {$completedAt}\n\n아래 버튼에서 서명 완료된 계약서를 확인할 수 있습니다.",
buttons: [
[