프리랜서 3.3%, 진짜 세금 다 낸 걸까? [2편] 개발기
지난 [1편]에서는 프리랜서 세금 시뮬레이터가 뭘 보여주는지 다뤘습니다. 이번 편에서는 정규직 비교와 절세 시뮬레이션을 어떻게 구현했는지 기록합니다.
1. 정규직 비교, 함수 하나 가져와서 끝
“같은 돈이면 프리랜서가 유리한지 정규직이 유리한지” 비교하려면 결국 근로소득세 계산이 필요합니다. 이건 이미 [연봉 실수령액 계산기]에서 만들어둔 로직이 있어서, 함수를 그대로 복사해왔습니다.
function computeEmployeeNetPay(annualSalary, dependents) {
// 4대보험 계산 (기존 로직 그대로)
// 근로소득공제 → 누진세율 → 근로소득세액공제 (기존 로직 그대로)
return { netAnnual: netMonthly * 12 };
}
프리랜서 쪽 계산 함수(computeFreelancerTax)와 나란히 두고, 같은 totalIncome을 각각에 넣어서 결과를 비교하는 방식입니다. 두 세금 체계(사업소득 vs 근로소득)는 계산 흐름이 완전히 다르지만, “같은 총수입을 넣었을 때 최종 손에 남는 돈”이라는 하나의 기준으로 통일하니 비교가 단순해졌습니다.
const emp = computeEmployeeNetPay(totalIncome, dependents);
const freelancerNetCash = totalIncome - r.determinedTax - insurancePaid;
const winIsEmployee = emp.netAnnual > freelancerNetCash;
2. 여러 회사 입력창, 동적으로 늘어나게
프리랜서는 소득을 주는 곳이 몇 개일지 미리 알 수 없습니다. 그래서 입력창 개수를 고정하지 않고, 버튼을 누를 때마다 새 입력창을 동적으로 추가하는 구조로 만들었습니다.
let companyCount = 0;
function addCompanyRow(value) {
companyCount++;
const id = 'company-' + companyCount;
const div = document.createElement('div');
div.className = 'company-row';
div.id = 'row-' + id;
div.innerHTML = `
<input type="number" id="${id}" placeholder="예: 20000000" value="${value || ''}">
<button class="remove-btn" onclick="removeCompanyRow('${id}')">✕</button>
`;
document.getElementById('companyRows').appendChild(div);
}
각 입력창에 고유 ID를 부여해서, 나중에 삭제 버튼을 눌렀을 때 정확히 그 행만 제거할 수 있게 했습니다. 합계를 구할 때는 몇 개가 추가됐든 상관없이 화면에 있는 입력창을 전부 훑습니다.
function getTotalIncome() {
const inputs = document.querySelectorAll('#companyRows input');
let total = 0;
inputs.forEach(inp => total += Number(inp.value) || 0);
return total;
}
개수를 변수로 관리하지 않고 querySelectorAll로 그때그때 현재 화면 상태를 읽어오는 방식이라, 행을 추가하거나 지워도 별도로 상태를 동기화할 필요가 없어졌습니다.
3. 절세 시뮬레이션 — 계산 함수는 그대로, 파라미터만 바꿔서 비교
노란우산공제·연금저축 슬라이더를 움직일 때마다 “얼마나 절세되는지”를 보여주려면, 같은 계산을 슬라이더 값 유무로 두 번 돌려서 차이를 비교하면 됩니다.
function updateSavingLabels() {
const yellow = Number(document.getElementById('yellowUmbrella').value);
const pension = Number(document.getElementById('pensionSaving').value);
const base = computeFreelancerTax(totalIncome, expenseRate, dependents, insurancePaid, 0, 0);
const withSaving = computeFreelancerTax(totalIncome, expenseRate, dependents, insurancePaid, yellow, pension);
const saved = base.determinedTax - withSaving.determinedTax;
document.getElementById('savingResult').innerHTML =
`이렇게 넣으면 세금이 <b>${saved.toLocaleString()}원</b> 줄어들어요.`;
}
새로운 계산 로직을 따로 만들 필요 없이, 이미 있는 computeFreelancerTax 함수를 “공제 없이 한 번, 공제 적용해서 한 번” 호출하고 그 차이만 보여주는 방식입니다. 슬라이더의 oninput 이벤트에 이 함수를 연결해서, 값을 움직이는 즉시 결과가 갱신되도록 만들었습니다.
4. 검증되지 않은 숫자는 넣지 않기
업종별 정확한 단순경비율을 계산기에 미리 다 넣어두고 싶었지만, 조사 과정에서 신뢰할 만한 정확한 수치를 확인할 수 있었던 건 IT 개발자(65.9%) 하나뿐이었습니다. 디자이너, 작가, 강사 등 다른 업종의 수치는 인터넷에 떠도는 값들이 출처가 불분명하거나 서로 달랐습니다.
그래서 억지로 채워 넣는 대신, 사용자가 직접 확인해서 입력하도록 설계를 바꿨습니다. 대신 경비율 입력창 바로 아래에 홈택스에서 정확한 값을 찾는 방법을 안내하고 바로가기 링크를 넣었습니다. 계산기가 모든 걸 자동으로 알려주는 것보다, 모르는 부분은 정직하게 “확인해보세요”라고 안내하는 쪽이 결과적으로 더 신뢰할 수 있는 도구가 된다고 판단했습니다.
5. 마무리하며
이번 프로젝트는 새 계산 로직을 만드는 것보다, 이미 검증된 함수를 다른 맥락에 옮겨 쓰는 작업이 대부분이었습니다. 근로소득 계산 함수를 그대로 가져와 비교 기준으로 쓰고, 프리랜서 계산 함수를 파라미터만 바꿔 절세 시뮬레이션에 재사용하면서, 처음부터 “재사용 가능한 형태로 함수를 짜두는 습관”이 다음 도구를 얼마나 빠르게 만들 수 있는지 다시 확인했습니다.
무료로 직접 사용해보고 싶으시다면 [1편]에 링크해둔 시뮬레이터를 확인해보세요.