이 글은 누구를 위한 것인가
- 고가 상품 구매 전환율을 높이기 위해 분할결제를 도입하려는 팀
- BNPL 서비스(카카오페이 나중결제, 토스페이 등)를 연동해야 하는 개발자
- 무이자 할부 프로모션 조건을 동적으로 관리하려는 개발팀
들어가며
100만원짜리 상품의 전환율과 10만원 × 10회 할부의 전환율은 다르다. 분할결제와 BNPL은 객단가를 높이면서 전환율도 유지하는 강력한 도구다.
이 글은 bluefoxdev.kr의 결제 시스템 설계 가이드 를 참고하여 작성했습니다.
1. 할부 유형 정리
[분할결제 유형]
1. 신용카드 할부
- 2~36개월
- 무이자: 카드사가 비용 부담 (or 가맹점 부담)
- 이자: 사용자 부담 (연 10~20%)
2. BNPL (Buy Now Pay Later)
- 30일 후 결제, 3개월 무이자 분할
- 대표: 카카오페이 나중결제, 토스페이 나중에 결제
- 특징: 신용카드 불필요, 즉시 승인
3. 선구매 후납 (가맹점 자체)
- 계약금 + 잔금 구조
- 예약 주문, 고가 가구, 맞춤 제작품
[무이자 할부 조건 예시]
5만원 이상: 2~3개월 무이자
10만원 이상: 2~6개월 무이자
50만원 이상: 2~12개월 무이자
특정 카드: 추가 혜택
2. 할부 조건 관리 시스템
from dataclasses import dataclass
from typing import Optional
@dataclass
class InstallmentPlan:
months: int
interest_free: bool
min_amount: int
max_amount: Optional[int]
card_codes: list[str] # 특정 카드만 (빈 리스트 = 전체)
valid_from: str
valid_until: str
# 동적 할부 조건 조회
INSTALLMENT_PLANS = [
InstallmentPlan(2, True, 50000, None, [], "2026-01-01", "2026-12-31"),
InstallmentPlan(3, True, 50000, None, [], "2026-01-01", "2026-12-31"),
InstallmentPlan(6, True, 100000, None, ["BC", "KB", "SHINHAN"], "2026-04-01", "2026-06-30"),
InstallmentPlan(12, True, 500000, None, ["HYUNDAI"], "2026-04-01", "2026-04-30"),
]
def get_available_installments(amount: int, card_code: str | None = None) -> list[dict]:
"""결제 금액과 카드 기준으로 가능한 할부 옵션 반환"""
from datetime import date
today = date.today().isoformat()
available = []
for plan in INSTALLMENT_PLANS:
if amount < plan.min_amount:
continue
if plan.max_amount and amount > plan.max_amount:
continue
if plan.card_codes and card_code not in plan.card_codes:
continue
if not (plan.valid_from <= today <= plan.valid_until):
continue
monthly = amount // plan.months
available.append({
"months": plan.months,
"interest_free": plan.interest_free,
"monthly_amount": monthly,
"total_amount": amount,
"label": f"{plan.months}개월 {'무이자' if plan.interest_free else '할부'}",
})
return sorted(available, key=lambda x: x["months"])
3. BNPL 결제 상태 관리
from enum import Enum
class BNPLStatus(Enum):
PENDING = "pending" # 승인 대기
APPROVED = "approved" # 승인 완료, 배송 가능
FIRST_PAYMENT_DUE = "due" # 첫 납부일 도래
PAID = "paid" # 완납
OVERDUE = "overdue" # 연체
CANCELLED = "cancelled" # 취소
class BNPLPaymentSchedule:
def __init__(self, order_id: str, total_amount: int, months: int, start_date: str):
self.order_id = order_id
self.total = total_amount
self.monthly = total_amount // months
self.schedules = self._generate_schedules(start_date, months)
def _generate_schedules(self, start_date: str, months: int) -> list[dict]:
from datetime import date, timedelta
base = date.fromisoformat(start_date)
return [
{
"installment_no": i + 1,
"due_date": (base.replace(month=base.month + i) if base.month + i <= 12
else base.replace(year=base.year + 1, month=base.month + i - 12)).isoformat(),
"amount": self.monthly,
"status": "pending"
}
for i in range(months)
]
async def handle_bnpl_webhook(provider: str, event: dict):
"""BNPL 공급사 웹훅 처리"""
order_id = event.get("merchant_order_id")
if event["type"] == "payment.approved":
await update_bnpl_status(order_id, BNPLStatus.APPROVED)
await fulfill_order(order_id)
elif event["type"] == "payment.overdue":
await update_bnpl_status(order_id, BNPLStatus.OVERDUE)
await notify_customer_overdue(order_id)
await pause_account_benefits(order_id) # 포인트 사용 제한 등
elif event["type"] == "payment.cancelled":
await update_bnpl_status(order_id, BNPLStatus.CANCELLED)
await initiate_return(order_id)
마무리
BNPL과 분할결제는 단순한 결제 옵션 추가가 아니다. 고가 상품 카테고리의 구매 장벽을 낮추고, 신규 고객층(신용카드 없는 MZ 세대)을 유입하는 전략이다. 연체 및 취소 시 처리 프로세스를 반드시 사전에 설계해야 운영 리스크를 줄일 수 있다.