토스페이먼츠 완전 연동 가이드: 결제부터 환불 자동화까지 - 코드픽 블로그
토스페이먼츠 완전 연동 가이드: 결제부터 환불 자동화까지
기술 가이드

토스페이먼츠 완전 연동 가이드: 결제부터 환불 자동화까지

2026년 3월 2일 33 views by 코드벤터

토스페이먼츠 완전 연동 가이드: 결제부터 환불 자동화까지

토스페이먼츠는 국내 핀테크 시장에서 가장 널리 쓰이는 결제 솔루션 중 하나입니다. 개발자 친화적인 API 문서와 SDK를 제공하며, 카드·계좌이체·간편결제 등 다양한 결제 수단을 지원합니다. 이 글에서는 토스페이먼츠를 처음 연동하는 개발자를 위해 결제창 띄우기부터 결제 승인, 그리고 웹훅을 이용한 환불 자동화까지 전 과정을 실전 코드와 함께 안내합니다.


1. 사전 준비

토스페이먼츠 개발자센터(developers.tosspayments.com)에 가입하면 테스트용 클라이언트 키와 시크릿 키를 발급받을 수 있습니다. 실제 결제는 라이브 키로 전환해야 하며, 테스트 환경에서는 실제 출금이 발생하지 않습니다.

구분클라이언트 키시크릿 키
테스트test_ck_...test_sk_...
라이브live_ck_...live_sk_...
용도프론트엔드 SDK 초기화서버 API 인증 (Base64 인코딩)
주의공개 가능절대 노출 금지

2. 프론트엔드: 결제창 띄우기 (SDK v2)

토스페이먼츠는 SDK v2 사용을 권장합니다. CDN 방식으로 간단하게 로드할 수 있습니다.

html
<!-- HTML head에 추가 -->
<script src="https://js.tosspayments.com/v2/standard"></script>

결제 위젯을 초기화하고 결제창을 호출하는 JavaScript 코드입니다.

javascript
const clientKey = "test_ck_D5GePWvyJnrK0W0k6q8gLzN97E0R";
const tossPayments = TossPayments(clientKey);

const widgets = tossPayments.widgets({ customerKey: TossPayments.ANONYMOUS });

async function renderPaymentWidget() {
  await widgets.setAmount({ currency: "KRW", value: 15000 });

  await Promise.all([
    widgets.renderPaymentMethods({ selector: "#payment-method", variantKey: "DEFAULT" }),
    widgets.renderAgreement({ selector: "#agreement", variantKey: "AGREEMENT" }),
  ]);
}

async function requestPayment() {
  await widgets.requestPayment({
    orderId: "order_" + Date.now(),
    orderName: "코드픽 프리미엄 구독",
    successUrl: window.location.origin + "/payment/success",
    failUrl: window.location.origin + "/payment/fail",
    customerEmail: "user@example.com",
    customerName: "홍길동",
  });
}

3. 백엔드: 결제 승인 처리 (FastAPI)

결제창에서 결제가 완료되면 successUrl로 리다이렉트되며, 쿼리 파라미터로 paymentKey, orderId, amount가 전달됩니다. 이를 받아 토스페이먼츠 서버에 승인 요청을 보내야 실제 결제가 확정됩니다.

python
import httpx
import base64
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

router = APIRouter()

SECRET_KEY = "test_sk_zXLkKEypNArWmo50nX3lmeaxYG5R"

def get_auth_header() -> str:
    encoded = base64.b64encode(f"{SECRET_KEY}:".encode()).decode()
    return f"Basic {encoded}"

class PaymentConfirmRequest(BaseModel):
    paymentKey: str
    orderId: str
    amount: int

@router.post("/payment/confirm")
async def confirm_payment(body: PaymentConfirmRequest):
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://api.tosspayments.com/v1/payments/confirm",
            headers={
                "Authorization": get_auth_header(),
                "Content-Type": "application/json",
            },
            json={
                "paymentKey": body.paymentKey,
                "orderId": body.orderId,
                "amount": body.amount,
            },
        )
    if response.status_code != 200:
        raise HTTPException(status_code=400, detail=response.json())
    return response.json()

4. 환불(결제 취소) API 연동

환불은 POST /v1/payments/{paymentKey}/cancel 엔드포인트를 사용합니다. 전액 환불 또는 부분 환불 모두 지원합니다.

python
@router.post("/payment/cancel")
async def cancel_payment(payment_key: str, cancel_reason: str, cancel_amount: int = None):
    body = {"cancelReason": cancel_reason}
    if cancel_amount:
        body["cancelAmount"] = cancel_amount

    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"https://api.tosspayments.com/v1/payments/{payment_key}/cancel",
            headers={
                "Authorization": get_auth_header(),
                "Content-Type": "application/json",
            },
            json=body,
        )
    if response.status_code != 200:
        raise HTTPException(status_code=400, detail=response.json())
    return response.json()

5. 웹훅(Webhook)으로 환불 자동화하기

웹훅을 활용하면 결제 상태 변경을 실시간으로 감지해 DB 업데이트, 이메일 발송 등의 후속 작업을 자동화할 수 있습니다. 토스페이먼츠는 결제 취소 시 PAYMENT_STATUS_CHANGED 이벤트를 상점 서버로 전송합니다.

python
from fastapi import Request
import logging

logger = logging.getLogger(__name__)

@router.post("/webhook/tosspayments")
async def toss_webhook(request: Request):
    payload = await request.json()
    event_type = payload.get("eventType")
    data = payload.get("data", {})

    if event_type == "PAYMENT_STATUS_CHANGED":
        status = data.get("status")
        order_id = data.get("orderId")
        payment_key = data.get("paymentKey")

        if status in ("CANCELED", "PARTIAL_CANCELED"):
            await update_order_status(order_id, status)
            await send_refund_email(order_id)
            logger.info(f"환불 처리 완료: {order_id} ({status})")

    # 반드시 200 반환 (미반환 시 최대 7회 재전송)
    return {"ok": True}

웹훅 URL은 토스페이먼츠 개발자센터 > 웹훅 메뉴에서 등록하며, 로컬 개발 환경에서는 ngrok을 사용해 외부 URL을 임시로 생성할 수 있습니다.

웹훅 요청 본문 예시:

json
{
  "eventType": "PAYMENT_STATUS_CHANGED",
  "createdAt": "2026-03-02T12:00:00.000",
  "data": {
    "paymentKey": "B3EvL1cKz9p-kO6XPNpfF",
    "status": "CANCELED",
    "orderId": "YOWWcpZSDCZ8WJC5x7mkl"
  }
}

6. 주요 에러 코드 및 대응 방법

에러 코드설명대응 방법
ALREADY_PROCESSED_PAYMENT이미 처리된 결제중복 승인 방지 로직 추가
PAYMENT_TOKEN_EXPIRED결제 토큰 만료결제 재시도 유도
EXCEED_MAX_DAILY_PAYMENT_COUNT일일 한도 초과고객 안내 메시지 출력
INVALID_REFUND_ACCOUNT_INFO환불 계좌 정보 오류계좌 재입력 요청
EXCEED_MAX_REFUND_DUE환불 기한 초과고객센터 연결 안내

7. 보안 체크리스트

  • 시크릿 키는 서버 환경변수로만 관리하고 절대 클라이언트에 노출하지 마세요.
  • 웹훅 엔드포인트는 토스페이먼츠 IP 대역에서의 요청만 허용하도록 방화벽 설정을 검토하세요.
  • 결제 승인 전 amount 값을 서버에서 반드시 재검증하세요 (클라이언트 조작 방지).
  • 멱등성(Idempotency) 키를 활용해 중복 환불 요청을 방지하세요.
  • HTTPS 통신만 허용하고, 웹훅 URL은 외부에 노출되지 않도록 관리하세요.

마무리

토스페이먼츠는 공식 문서가 잘 정리되어 있고 테스트 환경이 충실해 국내 결제 연동 중 진입 장벽이 낮은 편입니다. SDK v2와 웹훅을 조합하면 결제부터 환불까지 완전 자동화된 시스템을 구축할 수 있습니다.

코드벤터는 스타트업과 1인 개발자가 핵심 비즈니스에 집중할 수 있도록 결제 연동, API 설계, 인프라 구성 등 기술적인 부분을 함께 해결해 나가고 있습니다. 토스페이먼츠 연동에 어려움이 있거나 결제 시스템 설계가 필요하다면 언제든지 코드픽(codepick.kr)을 통해 검증된 개발 전문가와 연결해보세요.

개발 의뢰 상담

AI 서비스나 플랫폼 개발을
고민 중이신가요?

CodePick에서는
기획 → 개발 → 운영까지 함께합니다.
아이디어만 있어도 상담 가능합니다.

CodeVenter 개발팀이 직접 담당 · 1~2 영업일 내 회신

✓ 스타트업 MVP 개발✓ AI 서비스 개발✓ 웹 플랫폼 개발✓ 기업 시스템 구축✓ 모바일 앱 개발

AI Development Studio

코드픽 by 코드벤터

  • 대표: 윤승환 · 사업자등록번호: 121-57-64983
  • 대구광역시 중구 국채보상로 586, 16층 · info@codeventer.com

© 2025 코드벤터. All rights reserved.