Toss Payments — KRW 정산 데모
에이전트가 데이터를 사고 Toss Payments(test mode)로 정산하는 교육용 예시 — AP2/Stripe의 국내 버전. 실제 결제는 발생하지 않습니다.
프리미엄 시장 인사이트
에이전트가 구매하는 목업 프리미엄 데이터 — 실제 시세 조언이 아닙니다.
1,000원
테스트 카드: 4330 1234 5678 5678, 임의의 유효기간, 비밀번호 앞 2자리 00, 생년월일/사업자번호 임의.
기술 노트
각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.
토스페이먼츠라이브
목적: Stripe 예시의 KRW 버전 — 같은 에이전틱 결제 패턴을, 국제 결제망이 아니라 한국의 대표적인 로컬 결제 레일로 보여줍니다. Stripe가 아직 한국에 등록된 가맹점 계정을 지원하지 않기 때문에 나온 대안입니다.
동작 방식: 클라이언트가 script 태그로 Toss의 호스팅 결제 SDK를 로드하고 requestPayment를 호출해 Toss 결제 페이지로 리다이렉트합니다. 결제 성공 후 돌아오면 쿼리스트링에 paymentKey/orderId/amount가 담겨 있고, 서버가 서버 전용 secret key로 Toss의 결제승인 API에 이를 독립적으로 다시 제출한 뒤에만 구매 콘텐츠를 공개합니다. Toss의 confirm 엔드포인트가 제출된 금액을 실제 승인된 금액과 대조하기 때문에, 리다이렉트 URL을 조작(예: 금액을 낮춤)해도 서버 측 승인에서 걸러지고 그대로 신뢰되지 않습니다.
관련 코드:
# Toss Payments — client presents paymentKey/orderId/amount from a redirect, but the
# server independently re-confirms against its own authorized record before releasing
# content. A tampered amount in the redirect must fail, never be trusted as-is.
from dataclasses import dataclass
@dataclass
class AuthorizedPayment:
payment_key: str
order_id: str
authorized_amount: int # what Toss actually authorized, held server-side
# Simulates Toss's own record of what was actually approved during the payment window.
TOSS_AUTHORIZED_RECORDS = {
"pk_abc123": AuthorizedPayment("pk_abc123", "order_001", authorized_amount=15000),
}
def client_redirect(payment_key: str, order_id: str, claimed_amount: int) -> dict:
"""What the browser's redirect query string carries — untrusted input."""
return {"paymentKey": payment_key, "orderId": order_id, "amount": claimed_amount}
def server_confirm(redirect_payload: dict) -> bool:
"""Server-side confirm step: re-check the claimed amount against Toss's own
authorized record using a server-only secret key (mocked as a dict lookup)."""
record = TOSS_AUTHORIZED_RECORDS.get(redirect_payload["paymentKey"])
if record is None:
return False
if record.order_id != redirect_payload["orderId"]:
return False
return record.authorized_amount == redirect_payload["amount"]
def release_content_if_confirmed(redirect_payload: dict) -> str:
if server_confirm(redirect_payload):
return "content released"
return "confirmation rejected — content withheld"
if __name__ == "__main__":
print("Toss Payments — client presents amount, server independently re-checks\n")
print("Case 1: honest redirect, amount matches Toss's authorized record")
honest = client_redirect("pk_abc123", "order_001", claimed_amount=15000)
print(f" redirect payload: {honest}")
print(f" -> {release_content_if_confirmed(honest)}\n")
print("Case 2: tampered redirect, amount lowered by the client")
tampered = client_redirect("pk_abc123", "order_001", claimed_amount=100)
print(f" redirect payload: {tampered}")
print(f" -> {release_content_if_confirmed(tampered)}")