311 lines
15 KiB
PHP
311 lines
15 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'SMS 발송 테스트')
|
|
|
|
@section('content')
|
|
<div class="flex flex-col h-full">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6 flex-shrink-0">
|
|
<div>
|
|
<div class="flex items-center gap-2 text-sm text-gray-500 mb-1">
|
|
<span>바로빌</span>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
|
<span class="text-gray-700">SMS 발송 테스트</span>
|
|
</div>
|
|
<h1 class="text-2xl font-bold text-gray-800">SMS 발송 테스트</h1>
|
|
<p class="text-sm text-gray-500 mt-1">바로빌 SMS 서비스 연동 테스트 (단문 16.5원/건)</p>
|
|
</div>
|
|
</div>
|
|
|
|
@if(!$barobillMember)
|
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-6 text-center">
|
|
<h3 class="text-lg font-semibold text-yellow-800 mb-1">바로빌 회원사 연동 필요</h3>
|
|
<p class="text-sm text-yellow-600">SMS 발송을 위해 먼저 바로빌 회원사를 등록해주세요.</p>
|
|
</div>
|
|
@else
|
|
<div class="space-y-6">
|
|
<!-- 발신번호 상태 카드 -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="font-semibold text-gray-800">등록된 발신번호</h3>
|
|
<button type="button" onclick="loadFromNumbers()" class="text-xs px-3 py-1 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 transition">
|
|
새로고침
|
|
</button>
|
|
</div>
|
|
<div id="from-numbers-area">
|
|
<div class="flex items-center gap-2 text-sm text-gray-500">
|
|
<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
|
|
발신번호 목록 로딩 중...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SMS 발송 폼 -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<div class="flex items-center gap-2 mb-4">
|
|
<h3 class="font-semibold text-gray-800">SMS 발송</h3>
|
|
<button type="button" onclick="fillTestData()" title="테스트 데이터 자동 입력" class="p-1 text-yellow-500 hover:text-yellow-600 hover:bg-yellow-50 rounded transition">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
|
</button>
|
|
</div>
|
|
<form id="sms-form" class="space-y-4">
|
|
<!-- 발신번호 (고정) -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-600 mb-1">발신번호</label>
|
|
<input type="hidden" name="from_number" value="0263470005">
|
|
<div class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm bg-gray-50 text-gray-800">
|
|
02-6347-0005 <span class="text-gray-500">((주)코드브릿지엑스)</span>
|
|
</div>
|
|
</div>
|
|
<!-- 수신자 -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-600 mb-1">수신자명</label>
|
|
<input type="text" name="to_name" class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm" placeholder="홍길동">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-600 mb-1">수신번호</label>
|
|
<input type="tel" name="to_number" class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm" placeholder="01012345678">
|
|
</div>
|
|
</div>
|
|
<!-- 메시지 내용 -->
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label class="block text-sm font-medium text-gray-600">메시지 내용</label>
|
|
<span id="byte-counter" class="text-xs text-gray-400">0 / 90 바이트</span>
|
|
</div>
|
|
<textarea name="contents" id="sms-contents" rows="4" class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm" placeholder="SMS 메시지를 입력하세요 (90바이트 이내)" oninput="updateByteCounter()"></textarea>
|
|
<p id="lms-warning" class="text-xs text-orange-500 mt-1 hidden">90바이트 초과 시 LMS로 발송되며 요금이 다릅니다.</p>
|
|
</div>
|
|
<!-- 예약 발송 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-600 mb-1">예약 발송</label>
|
|
<input type="datetime-local" name="send_dt" class="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm">
|
|
<p class="text-xs text-gray-500 mt-1">비워두면 즉시 발송됩니다.</p>
|
|
</div>
|
|
<div class="flex justify-end">
|
|
<button type="submit" id="sms-submit-btn" class="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition font-medium flex items-center gap-2">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /></svg>
|
|
SMS 발송
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 발송 결과 -->
|
|
<div id="send-result-area" class="hidden">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h3 class="font-semibold text-gray-800 mb-4">발송 결과</h3>
|
|
<div id="send-result-content"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
const SMS_API = {
|
|
send: '{{ route('api.admin.barobill.sms.send') }}',
|
|
fromNumbers: '{{ route('api.admin.barobill.sms.from-numbers') }}',
|
|
sendState: '{{ route('api.admin.barobill.sms.send-state', ['sendKey' => '__KEY__']) }}',
|
|
};
|
|
function fillTestData() {
|
|
document.querySelector('input[name="to_name"]').value = '김보곤';
|
|
document.querySelector('input[name="to_number"]').value = '01051238210';
|
|
document.getElementById('sms-contents').value = '테스트 메시지 전송합니다. 코드브릿지엑스';
|
|
updateByteCounter();
|
|
}
|
|
|
|
function getByteLength(str) {
|
|
let byteLen = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
const charCode = str.charCodeAt(i);
|
|
byteLen += (charCode > 127 || charCode === 0xFFFD) ? 2 : 1;
|
|
}
|
|
return byteLen;
|
|
}
|
|
|
|
function updateByteCounter() {
|
|
const textarea = document.getElementById('sms-contents');
|
|
const counter = document.getElementById('byte-counter');
|
|
const warning = document.getElementById('lms-warning');
|
|
const bytes = getByteLength(textarea.value);
|
|
|
|
counter.textContent = bytes + ' / 90 바이트';
|
|
|
|
if (bytes > 90) {
|
|
counter.classList.remove('text-gray-400');
|
|
counter.classList.add('text-orange-500');
|
|
warning.classList.remove('hidden');
|
|
} else {
|
|
counter.classList.remove('text-orange-500');
|
|
counter.classList.add('text-gray-400');
|
|
warning.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
function loadFromNumbers() {
|
|
const area = document.getElementById('from-numbers-area');
|
|
|
|
area.innerHTML = '<div class="flex items-center gap-2 text-sm text-gray-500"><svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg> 발신번호 목록 로딩 중...</div>';
|
|
|
|
fetch(SMS_API.fromNumbers, {
|
|
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (!data.success) {
|
|
area.innerHTML = '<div class="text-sm text-red-500">발신번호 조회 실패: ' + (data.error || data.message || '알 수 없는 오류') + '</div>';
|
|
return;
|
|
}
|
|
|
|
const raw = data.data;
|
|
let numbers = [];
|
|
if (Array.isArray(raw)) {
|
|
numbers = raw;
|
|
} else if (raw && raw.SMSFromNumber) {
|
|
numbers = Array.isArray(raw.SMSFromNumber) ? raw.SMSFromNumber : [raw.SMSFromNumber];
|
|
} else if (raw && typeof raw === 'string') {
|
|
numbers = [{ FromNumber: raw }];
|
|
}
|
|
|
|
if (numbers.length === 0) {
|
|
area.innerHTML = '<div class="text-sm text-gray-500">바로빌에 등록된 발신번호가 없습니다.</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="flex flex-wrap gap-2">';
|
|
numbers.forEach(num => {
|
|
const number = num.FromNumber || num;
|
|
html += '<span class="inline-flex items-center gap-1 px-3 py-1 bg-blue-50 text-blue-700 rounded-full text-sm">';
|
|
html += '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /></svg>';
|
|
html += number;
|
|
html += '</span>';
|
|
});
|
|
html += '</div>';
|
|
area.innerHTML = html;
|
|
})
|
|
.catch(err => {
|
|
area.innerHTML = '<div class="text-sm text-red-500">API 오류: ' + err.message + '</div>';
|
|
});
|
|
}
|
|
|
|
function formatReserveDT(datetimeLocal) {
|
|
if (!datetimeLocal) return '';
|
|
return datetimeLocal.replace(/[-T:]/g, '').substring(0, 14);
|
|
}
|
|
|
|
function showSendResult(data) {
|
|
const area = document.getElementById('send-result-area');
|
|
const content = document.getElementById('send-result-content');
|
|
area.classList.remove('hidden');
|
|
|
|
if (data.success) {
|
|
const sendKey = data.data || '';
|
|
content.innerHTML = `
|
|
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
<span class="font-semibold text-green-800">발송 성공</span>
|
|
</div>
|
|
<p class="text-sm text-green-700 mb-3">전송키: <code class="bg-green-100 px-2 py-0.5 rounded">${sendKey}</code></p>
|
|
<button type="button" onclick="checkSendState('${sendKey}')" class="text-xs px-3 py-1.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">
|
|
전송 상태 조회
|
|
</button>
|
|
<div id="send-state-result" class="mt-3"></div>
|
|
</div>`;
|
|
} else {
|
|
content.innerHTML = `
|
|
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
<span class="font-semibold text-red-800">발송 실패</span>
|
|
</div>
|
|
<p class="text-sm text-red-700">${data.error || data.message || '알 수 없는 오류'}</p>
|
|
</div>`;
|
|
}
|
|
}
|
|
|
|
function checkSendState(sendKey) {
|
|
const stateArea = document.getElementById('send-state-result');
|
|
stateArea.innerHTML = '<div class="text-sm text-gray-500">상태 조회 중...</div>';
|
|
|
|
fetch(SMS_API.sendState.replace('__KEY__', encodeURIComponent(sendKey)), {
|
|
headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const state = data.data;
|
|
let stateHtml = '<div class="bg-white border rounded-lg p-3 text-sm">';
|
|
stateHtml += '<p class="font-medium text-gray-700 mb-1">전송 상태 상세:</p>';
|
|
stateHtml += '<pre class="bg-gray-50 p-2 rounded text-xs overflow-auto">' + JSON.stringify(state, null, 2) + '</pre>';
|
|
stateHtml += '</div>';
|
|
stateArea.innerHTML = stateHtml;
|
|
} else {
|
|
stateArea.innerHTML = '<div class="text-sm text-red-500">상태 조회 실패: ' + (data.error || data.message) + '</div>';
|
|
}
|
|
})
|
|
.catch(err => {
|
|
stateArea.innerHTML = '<div class="text-sm text-red-500">API 오류: ' + err.message + '</div>';
|
|
});
|
|
}
|
|
|
|
// SMS 발송 폼 제출
|
|
document.getElementById('sms-form')?.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const fd = new FormData(this);
|
|
const btn = document.getElementById('sms-submit-btn');
|
|
|
|
const body = {
|
|
from_number: fd.get('from_number'),
|
|
to_name: fd.get('to_name'),
|
|
to_number: fd.get('to_number'),
|
|
contents: fd.get('contents'),
|
|
send_dt: formatReserveDT(fd.get('send_dt')),
|
|
};
|
|
|
|
if (!body.from_number) { alert('발신번호를 선택해주세요.'); return; }
|
|
if (!body.to_name) { alert('수신자명을 입력해주세요.'); return; }
|
|
if (!body.to_number) { alert('수신번호를 입력해주세요.'); return; }
|
|
if (!body.contents) { alert('메시지 내용을 입력해주세요.'); return; }
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = '발송 중...';
|
|
|
|
fetch(SMS_API.send, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content
|
|
},
|
|
body: JSON.stringify(body),
|
|
})
|
|
.then(async r => {
|
|
const data = await r.json();
|
|
if (!r.ok && data.errors) {
|
|
data.success = false;
|
|
data.error = Object.values(data.errors).flat().join('\n');
|
|
}
|
|
return data;
|
|
})
|
|
.then(data => {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /></svg> SMS 발송';
|
|
showSendResult(data);
|
|
})
|
|
.catch(err => {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /></svg> SMS 발송';
|
|
showSendResult({ success: false, error: err.message });
|
|
});
|
|
});
|
|
|
|
document.addEventListener('DOMContentLoaded', loadFromNumbers);
|
|
</script>
|
|
@endpush
|