데모

AP2 — Stripe 정산 데모

에이전트가 데이터를 사고 Stripe Checkout(test mode)으로 정산하는 교육용 예시. 실제 결제는 발생하지 않습니다.

↓ 기술 노트 보기 (목적·동작 방식)

프리미엄 시장 인사이트

에이전트가 구매하는 목업 프리미엄 데이터 — 실제 시세 조언이 아닙니다.

$0.50

테스트 카드: 4242 4242 4242 4242, 임의의 미래 만료일 / CVC / 우편번호.

기술 노트

각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.

AP2 — Stripe 정산라이브

목적: "에이전틱 결제" 패턴의 최소 예시 — 자율 에이전트가 무언가를 구매하고 표준 법정화폐 결제 레일로 정산하는 흐름을, Stripe의 호스팅 Checkout을 정산 계층으로 삼아 보여줍니다.

동작 방식: 일반 HTML 폼이 서버 라우트로 POST 되면, 서버 전용 secret key로 Stripe SDK를 통해 Checkout Session을 생성한 뒤 Stripe의 호스팅 결제 페이지로 리다이렉트합니다. 결제 후 돌아오면 같은 서버 컴포넌트가 Stripe API로 세션의 payment_status를 다시 독립적으로 검증한 뒤에만 구매 콘텐츠를 공개합니다 — 클라이언트 리다이렉트만 믿지 않기 때문에 "결제 건너뛰고 success URL 직접 호출" 같은 공격을 막습니다.

결제 후 서버에서 재검증
관련 코드:
"""AP2 -- Stripe settlement PoC -- client "paid" claim vs server-side re-verification.
Illustrates the core mechanism: the server never trusts a client redirect alone; it
independently checks payment_status against its own mock payment record store.
"""

from dataclasses import dataclass


@dataclass
class PaymentRecord:
    session_id: str
    payment_status: str  # "paid" | "unpaid"


class MockStripe:
    """Stands in for Stripe's API: the source of truth the server re-checks against."""

    def __init__(self):
        self._sessions: dict[str, PaymentRecord] = {}

    def create_checkout_session(self, session_id: str) -> str:
        self._sessions[session_id] = PaymentRecord(session_id, "unpaid")
        return f"https://checkout.mock/{session_id}"

    def pay(self, session_id: str) -> None:
        """Simulates the buyer actually paying on Stripe's hosted page."""
        self._sessions[session_id].payment_status = "paid"

    def retrieve(self, session_id: str) -> PaymentRecord:
        return self._sessions[session_id]


class Server:
    def __init__(self, stripe: MockStripe):
        self.stripe = stripe

    def release_content(self, session_id: str, client_claims_paid: bool) -> str:
        # The point of the demo: client_claims_paid is IGNORED. Only Stripe's own
        # record, fetched server-side, decides whether content is released.
        record = self.stripe.retrieve(session_id)
        if record.payment_status == "paid":
            return "CONTENT RELEASED: here is your purchased data"
        return "DENIED: payment_status is not 'paid' per Stripe -- redirect alone proves nothing"


if __name__ == "__main__":
    stripe = MockStripe()
    server = Server(stripe)

    session_id = "cs_test_123"
    url = stripe.create_checkout_session(session_id)
    print(f"server created checkout session -> {url}")

    print("\nattacker skips payment, hits the success URL directly claiming paid=True:")
    print(" ", server.release_content(session_id, client_claims_paid=True))

    print("\nlegit buyer actually pays on Stripe's hosted page:")
    stripe.pay(session_id)
    print(" ", server.release_content(session_id, client_claims_paid=True))

docs/code/pocs/ap2.py