Agent-Ready Commerce, 파트 6: 결제(Checkout)는 양식이 아니라 상태 머신(State Machine)이다
요약
에이전트 중심의 커머스 환경에서 결제(Checkout) 프로세스를 단순한 양식이 아닌 상태 머신(State Machine)으로 설계해야 함을 강조합니다. 에이전트가 권한을 위임받아 상업적 상태를 변이시킬 때 필요한 백엔드 경계와 아키텍처 모델을 다룹니다.
핵심 포인트
- 결제는 단순 요청 핸들러가 아닌 상태 머신으로 모델링되어야 함
- 에이전트의 행동을 위해 장바구니, 재고, 가격 등의 상태 전이 관리 필요
- 사실, 자격, 권한, 상태 전이, 증거, 감사로 이어지는 아키텍처 모델 제시
- 에이전트가 상업적 상태를 안전하게 변이할 수 있는 백엔드 구조 설계 중요
결제(Checkout)는 Agent-Ready Commerce가 대부분 읽기 전용(read-only) 상태를 벗어나는 지점입니다.
탐색(Discovery)은 투영(projection)일 수 있습니다. 비교(Comparison)는 쿼리(query)일 수 있습니다. 정책 견적(Policy quotation)은 출처 증거(source evidence)로 뒷받침되는 통제된 답변일 수 있습니다. 하지만 결제는 다릅니다. 결제는 상업적 상태(commercial state)를 변이(mutate)시킵니다.
장바구니(cart)가 변경됩니다. 가격이 선택되거나 고정될 수 있습니다. 재고(Inventory)가 재검증되거나 예약될 수 있습니다. 배송 옵션(Shipping options)이 계산될 수 있습니다. 정책 적용 범위(Policy coverage)가 전제 조건이 될 수 있습니다. 결제 스냅샷(checkout snapshot)이 생성될 수 있습니다. 결제 권한(Payment authority)이 관련될 수 있습니다. 주문(order)이 최종적으로 확정(committed)될 수 있습니다.
이것이 바로 Agent-Ready 플랫폼에서 결제를 단순한 양식 엔드포인트(form endpoint)로 모델링해서는 안 되는 이유입니다.
결제는 상태 머신(state machine)으로 모델링되어야 합니다.
문제는 사용자 인터페이스(user interface) 스타일이 아닙니다. 인간을 대상으로 하는 스토어프론트(storefront)는 여전히 결제를 주소, 배송, 검토, 결제, 확인과 같은 익숙한 화면 시퀀스로 제시할 수 있습니다. 문제는 백엔드 경계(backend boundary)입니다. 에이전트(agents)가 아이템을 추가하고, 결제를 준비하며, 작업을 재시도하고, 구매자 권한(buyer authority) 하에 행동하거나, 위임된 결제(delegated payment)에 접근할 수 있게 되면, 결제는 더 이상 느슨한 요청 핸들러(request handlers)의 집합으로 취급될 수 없습니다.
플랫폼은 장바구니나 결제가 어떤 상태(state)에 있는지, 어떤 명령(command)이 요청되고 있는지, 어떤 사실(facts)이 평가되었는지, 어떤 행위자(actor)가 권한을 부여받았는지, 어떤 전이(transition)가 발생했는지, 이를 뒷받침하는 증거(evidence)는 무엇인지, 그리고 어떤 감사 기록(audit record)이 생성되었는지를 알아야 합니다.
이 글은 Agent-Ready Commerce 시리즈의 여섯 번째 기사입니다.
파트 1에서는 더 넓은 아키텍처 모델을 소개했습니다:
사실(Facts) → 자격(Eligibility) → 권한(Authority) → 상태 전이(State transition) → 증거(Evidence) → 감사(Audit)
파트 2는 상업적 진실(commercial truth)에 초점을 맞췄습니다. 카탈로그 데이터만으로는 충분하지 않다고 주장했습니다. 에이전트나 다른 시스템이 제품 정보에 안전하게 의존하기 전에, 플랫폼은 출처가 뒷받침되고 최신성을 인지하는(freshness-aware) 제품 사실(product facts)을 필요로 합니다.
파트 3은 실행 가능성(action eligibility)에 초점을 맞췄습니다. 여기서는 “사용 가능(available)”이라는 표현이 너무 광범위하다고 주장했습니다. 제품이 검색(discoverable)은 가능할 수 있지만 결제 준비(checkout-ready)가 되지 않았을 수 있고, 비교(comparable)는 가능할 수 있지만 정책 인용(policy-quotable)은 불가능할 수 있으며, 인간의 흐름(human flow)에는 결제 준비가 되어 있어도 위임된 결제(delegated payment)에는 적합하지 않을 수 있습니다.
파트 4는 정책 구조(policy structure)에 초점을 맞췄습니다. 에이전트가 자유 형식의 텍스트(free-text)로 된 정책 페이지를 실행 가능한 규칙(executable rules)으로 해석해서는 안 된다고 주장했습니다. 정책에는 적용 가능성(applicability), 증거(evidence), 생명주기(lifecycle), 충돌(conflicts), 그리고 인용 가능성(quoteability)을 갖춘 구조화된 사실(structured facts)이 필요합니다.
파트 5는 프로토콜 어댑터(protocol adapters)에 초점을 맞췄습니다. ACP, MCP, AP2, 피드(feeds), 도구(tools), 그리고 미래의 인터페이스들은 상업적 의미의 별도 소스가 되기보다는 도메인 결정(domain decisions)을 번역해야 한다고 주장했습니다.
이 글은 결제 상태(checkout state)에 초점을 맞춥니다.
핵심 논점은 에이전트 준비형 결제(agent-ready checkout)가 제어된 변이 경계(controlled mutation boundary)로 취급되어야 한다는 것입니다. 플랫폼은 장바구니 변이(cart mutation), 검증(validation), 결제 준비(checkout preparation), 결제 권한(payment authority), 재시도(retries), 만료(expiry), 주문 확정(order commitment), 증거(evidence), 그리고 감사(audit)를 위한 명시적인 상태(states)와 명령(commands)을 필요로 합니다.
결제는 변이 경계이다
이 시리즈의 이전 파트들은 여러 관심사(concerns)를 분리했습니다:
상업적 진실(Commercial truth)은 알려진 내용을 말합니다.
정책 사실(Policy facts)은 어떤 조건이 적용되는지 말합니다.
적격성(Eligibility)은 어떤 행동이 유효한지 말합니다.
...
결제는 이러한 관심사들이 지속적인 결과(durable consequences)를 생성하기 시작하는 지점입니다.
제품이 검색 가능하다는 사실은 제품을 변경하지 않습니다. 비교 결과는 재고를 예약하지 않습니다. 정책 인용은 결제 의무를 생성하지 않습니다. 하지만 결제는 할 수 있습니다.
결제의 초기 단계 작업조차 상업적으로 중요할 수 있습니다. 장바구니에 항목을 추가하는 것은 총액에 영향을 줄 수 있습니다. 지역을 선택하는 것은 배송 옵션을 변경할 수 있습니다. 결제를 준비하는 것은 스냅샷(snapshot)을 생성할 수 있습니다. 결제를 요청하는 것은 위임(mandate)에 따라 달라질 수 있습니다. 주문을 확정하는 것은 재고를 할당하고 이행(fulfillment), 지원(support), 회계(accounting), 그리고 감사(audit)를 위한 의무를 생성할 수 있습니다.
이것이 바로 결제가 읽기 전용(read-only) 작업보다 더 강력한 제어를 필요로 하는 이유입니다.
유용한 사고 모델(mental model)은 다음과 같습니다:
읽기 전용 작업 (Read-only actions)
↓
장바구니 변이 (Cart mutation)
...
각 단계가 진행될수록 오류가 발생했을 때의 비용이 증가합니다.
비교 응답 (comparison response)이 불완전하다면 플랫폼이 정답을 수정할 수 있습니다. 하지만 결제 상태 전이 (checkout state transition)가 잘못된다면, 시스템은 장바구니를 되돌리거나(unwind), 재고를 해제하거나, 결제 시도를 취소하거나, 환불을 처리하거나, 혹은 에이전트가 왜 오래된 상업적 상태 (stale commercial state)를 바탕으로 행동했는지 설명해야 할 수도 있습니다.
쿼리 (query)에서 변이 (mutation)로 넘어가는 경계야말로 아키텍처가 더욱 명시적 (explicit)이 되어야 하는 지점입니다.
Travel Backpack 예시
파트 2~5에서 진행 중이던 예시를 이어가겠습니다:
Product: Travel Backpack
SKU: BAG-TRAVEL-42
Price: €129
...
현재 상업적 진실 계층 (commercial truth layer)은 다음과 같이 말합니다:
Price: fresh
Inventory: stale
Return policy: missing for Travel Bags
...
파트 3에서 액션 매트릭스 (action matrix)는 다음과 같았습니다:
| Action | Result |
|---|---|
discover | allowed |
| ... |
사람을 위한 상점 (human storefront)의 경우, Travel Backpack은 여전히 제품 페이지에 나타날 수 있습니다. 사용자는 제목, 이미지, 가격, 카테고리를 볼 수 있습니다. 플랫폼의 위험 허용 범위 (risk tolerance)에 따라, UI는 여전히 사용자가 "장바구니에 담기"를 클릭하는 것을 허용하고 더 엄격한 검사는 나중으로 미룰 수 있습니다.
에이전트 대응 플랫폼 (agent-facing platform)은 더 정밀한 동작이 필요합니다.
에이전트는 다음과 같이 요청할 수 있습니다:
BAG-TRAVEL-42를 구매자의 장바구니에 추가해줘.
EU 배송을 위한 결제 (checkout)를 준비해줘.
총액이 €150 미만으로 유지된다면 위임된 결제 (delegated payment)를 사용해줘.
이것은 단일 작업이 아닙니다.
여기에는 적어도 세 가지의 서로 다른 상업적 단계가 포함되어 있습니다:
Cart mutation:
구매자 컨텍스트 (buyer context)에 제품을 추가합니다.
...
각 단계는 서로 다른 전제 조건 (preconditions)을 가집니다.
아이템을 추가하는 데는 제품 식별 (product identity), 현재 가격 처리 (current price handling), 그리고 재고 재검증 규칙 (inventory revalidation rules)이 필요할 수 있습니다.
결제를 준비하는 데는 최신 재고 (fresh inventory), 정책 적용 범위 (policy coverage), 배송 컨텍스트 (shipping context), 구매자 유형 (buyer type), 생성된 클레임 제약 조건 (generated-claim constraints), 그리고 안정적인 장바구니 스냅샷 (stable cart snapshot)이 필요할 수 있습니다.
위임된 결제 (delegated payment)에는 결제 유효성 (checkout validity), 행위자 권한 (actor authority), 위임 범위 (mandate scope), 금액 제한 (amount limits), 통화 일치 (currency match), 가맹점 결합 (merchant binding), 만료 (expiry), 취소 확인 (revocation checks), 그리고 어쩌면 인간의 확인 (human confirmation)이 필요할 수 있습니다.
단일 checkout() 함수는 내부에 상태 머신 (State Machine)을 숨기지 않는 한, 이러한 경계들을 안전하게 표현할 수 없습니다. 상태 머신이 존재한다면, 이는 명시적으로 모델링되어야 합니다.
폼 엔드포인트는 결정 경로를 숨깁니다
전형적인 결제 (Checkout) 구현은 쉽게 엔드포인트 중심 (endpoint-driven)이 될 수 있습니다.
단순화된 핸들러는 다음과 같은 모습일 수 있습니다:
async function submitCheckout(req: Request) {
const cart = await carts.get(req.cartId);
...
이 예시는 의도적으로 단순화되었지만, 경계 문제 (boundary problem)를 보여줍니다. 여러 상업적 전환 (commercial transitions)이 하나의 요청 핸들러 (request handler) 안에 압축되어 있습니다.
이 핸들러는 기본적인 인간용 결제 폼에는 작동할 수 있습니다. 하지만 에이전트 대응 뮤테이션 경계 (agent-facing mutation boundary)로서는 취약합니다. 왜냐하면 시스템이 다음과 같은 질문에 쉽게 답할 수 없기 때문입니다:
어떤 장바구니 스냅샷 (cart snapshot)이 평가되었는가?
결제가 준비되었을 때 재고 (inventory)가 최신 상태였는가?
어떤 정책 사실 (policy facts)이 요구되었는가?
...
이러한 질문들은 중요합니다. 왜냐하면 에이전트 (agents)와 프로토콜 어댑터 (protocol adapters)가 결제 작업을 직접 호출할 수 있기 때문입니다. 이들은 재시도 (retry)를 할 수도 있고, 비동기적 (asynchronously)으로 작동할 수도 있습니다. 또한 인간이 통상적으로 결제 화면에 도달하기 전에 결제 준비 상태를 물어볼 수도 있습니다. 플랫폼 외부로부터 결제 권한 아티팩트 (payment authority artifacts)를 가져올 수도 있습니다.
결제 폼은 사용자로부터 상태를 숨길 수 있습니다.
하지만 백엔드는 자기 자신으로부터 상태를 숨겨서는 안 됩니다.
상태는 단순한 상태 필드가 아닙니다
흔히 타협하는 구현 방식은 상태 필드 (status field)를 추가하는 것입니다:
type CheckoutStatus = "active" | "completed" | "failed";
그것만으로는 충분하지 않습니다.
결제는 활성 (active) 상태이지만 유효 (valid)하지 않을 수 있습니다. 유효하지만 준비 (prepared)되지 않았을 수 있습니다. 준비되었지만 결제 권한 (payment authority)을 기다리고 있을 수 있습니다. 권한은 있지만 스냅샷 매칭 (snapshot matching)에 실패할 수 있습니다. 정책 커버리지 (policy coverage)가 누락되어 차단될 수 있습니다. 만료될 수도 있고, 취소될 수도 있으며, 재검증 (revalidation)을 기다리고 있을 수도 있습니다.
이것들은 실질적으로 서로 다른 상태들입니다.
더 유용한 모델은 다음과 같이 상업적 경계 (commercial boundaries)를 명명합니다:
type CheckoutState =
| "draft_cart"
| "cart_requires_revalidation"
...
정확한 명칭 자체보다는 이들이 만들어내는 구별(distinction)이 더 중요합니다.
draft_cart는 아이템 변경을 수락할 수 있습니다.
cart_requires_revalidation은 어떤 사실이나 변이(mutation)로 인해 이전의 준비 상태가 무효화되었음을 플랫폼에 알립니다.
cart_blocked는 시스템에 결제 준비를 방해하는 차단 요소(blockers)가 있음을 의미합니다.
checkout_prepared 상태는 플랫폼이 안정적인 결제 컨텍스트(checkout context)를 생성했음을 의미합니다.
payment_authority_required 상태는 결제가 상업적으로는 유효할 수 있으나, 행위자(actor)의 권한(authority)이 결제를 수행하기에 아직 충분하지 않음을 의미합니다.
order_committed 상태는 상태 전이(transition)가 주문 생성 단계로 넘어갔음을 의미합니다.
이러한 상태들은 화면(screens)을 설명하는 것이 아닙니다. 플랫폼이 다음에 무엇을 안전하게 수행할 수 있다고 믿는지(believes)를 설명합니다.
명령(Commands)이 전이를 유도해야 합니다
결제 상태 머신(checkout state machine)은 임의의 업데이트에 의해 변이(mutated)되어서는 안 됩니다. 대신 명령(commands)을 수락해야 합니다.
명령은 요청된 상태 변경을 나타냅니다:
type CheckoutCommand =
| "add_item"
| "remove_item"
...
각 명령에는 전제 조건(preconditions)이 있습니다.
| 명령 | 예시 전제 조건 |
|---|---|
add_item | 제품 식별 정보 인지됨, 행위자가 장바구니를 변이할 수 있음, 장바구니가 변이 가능함 |
| ... |
명령 결과는 전이 결과(transition outcome)를 보고해야 합니다:
type CheckoutCommandRequest = {
command: CheckoutCommand;
checkoutId: string;
...
이는 단순히 HTTP 성공 또는 실패를 반환하는 것과는 다릅니다.
플랫폼이 명령을 처리했기 때문에 HTTP 요청은 성공할 수 있습니다. 하지만 전이(transition)가 유효하지 않다면 결제 명령은 여전히 거부될 수 있습니다.
이러한 구별은 에이전트(agents)에게 매우 중요합니다. 차단된 전이(blocked transition)는 인프라 오류가 아닙니다. 그것은 도메인 차원의 답변(domain answer)입니다.
자격(Eligibility), 권한(authority), 전이(transition)는 별개의 체크 항목입니다
결제 전이는 자격, 권한, 그리고 상태를 하나로 통합해서는 안 됩니다.
이들은 서로 다른 질문에 답합니다.
자격(Eligibility)은 다음과 같이 묻습니다:
이 동작이 이 제품, 장바구니, 구매자, 지역, 채널 및 현재의 사실(facts)에 대해 유효한가?
권한(Authority)은 다음과 같이 묻습니다:
이 행위자가 이 동작을 요청할 권한이 있는가?
상태 전이 (State transition)는 다음과 같이 묻습니다:
현재 결제 (checkout) 상태를 고려할 때, 이 명령이 결제를 새로운 상태로 이동시킬 수 있는가?
이 세 가지 요소는 각각 독립적으로 실패할 수 있습니다.
제품은 결제 (checkout) 대상이 될 수 있지만, 결제 세션 (checkout session)이 만료되었을 수 있습니다.
행위자 (actor)가 결제 준비를 요청할 권한 (authority)을 가지고 있더라도, 정책 적용 (policy coverage)이 불완전하여 장바구니 (cart)가 차단될 수 있습니다.
결제가 준비되었을 수 있지만, 위임된 결제 권한 (delegated payment authority)이 누락되었을 수 있습니다.
결제 권한 (payment authority)이 존재하더라도, 장바구니 스냅샷 (cart snapshot)이 더 이상 일치하지 않을 수 있습니다.
유용한 실행 경로는 다음과 같습니다:
명령 수신 (Command received)
↓
현재 결제 상태 로드 (Current checkout state loaded)
...
이 지점에서 파트 1의 모델이 작동하기 시작합니다.
사실 (Facts) → 적격성 (Eligibility) → 권한 (Authority) → 상태 전이 (State transition) → 증거 (Evidence) → 감사 (Audit)
결제 (Checkout)는 이 전체 체인이 불가피해지는 시리즈의 첫 번째 항목입니다.
장바구니 변이 (Cart mutation)는 무해하지 않다
장바구니 변이 (Cart mutation)는 구매자가 아직 결제하지 않았기 때문에 때때로 저위험으로 취급됩니다.
에이전트 대응 커머스 (agent-facing commerce)에서 그 가정은 취약합니다.
장바구니는 결제 준비 (checkout preparation), 결제 권한 (payment authority), 가격 표시 (price display), 정책 견적 (policy quotation), 세금 계산 (tax calculation), 프로모션 적격성 (promotion eligibility) 또는 감사 기록 (audit records)의 근거가 될 수 있습니다. 일단 장바구니가 위임된 결제 (delegated payment)의 입력값으로 사용되면, 그것은 더 이상 단순한 임시 UI 편의 기능이 아닙니다.
플랫폼은 변이 가능한 장바구니 (mutable carts)와 검증된 스냅샷 (validated snapshots)을 구분해야 합니다.
단순한 장바구니 상태 모델은 다음과 같을 수 있습니다:
type CartState =
| "mutable"
| "requires_revalidation"
...
mutable 상태에서는 항목을 추가하거나 제거할 수 있습니다.
requires_revalidation 상태에서는 장바구니의 변경이나 오래된 사실 (stale fact)로 인해 이전의 준비 상태를 더 이상 신뢰할 수 없음을 의미합니다.
validated 상태에서는 플랫폼이 현재 컨텍스트에 대한 관련 사실들을 확인했습니다.
locked_for_checkout 상태에서는 장바구니 스냅샷이 결제 준비 또는 결제 권한을 위해 사용되고 있습니다.
expired 상태에서는 재검증 (revalidation) 없이는 장바구니가 더 이상 결제나 주문 확약을 지원할 수 없습니다.
Travel Backpack의 경우:
add_item:
재고가 오래되어 (stale) 재검증 (revalidation)이 필요함
...
플랫폼은 아이템 추가 단계에서 결제 준비 단계로 조용히(silently) 넘어가서는 안 됩니다. 그 전환은 명시적(explicit)이어야 합니다.
스냅샷(Snapshots)은 계약의 경계(contract boundary)이다
결제(Checkout)에는 스냅샷이 필요합니다. 왜냐하면 가변적인(mutable) 장바구니는 결제 권한(payment authority) 부여나 주문 확정(order commitment)을 수행하기에 충분히 안정적이지 않기 때문입니다.
장바구니 ID(cart ID)는 컨테이너를 식별할 뿐입니다. 장바구니의 내용물, 가격, 통화(currency), 배송 지역, 세금, 정책 또는 총액이 동일하게 유지된다는 것을 증명하지는 않습니다.
에이전트 지원 커머스(agent-ready commerce)의 경우, 이는 매우 중요합니다. 위임된 결제(delegated payment)는 대개 구매자가 제한된 범위의 동작(bounded action)을 승인하는 것에 의존하기 때문입니다.
예를 들어:
이 판매자로부터 최대 150유로(EUR)까지의 Travel Backpack 1개를 구매해줘.
해당 권한은 동일한 장바구니 ID의 미래의 어떤 내용물에도 적용되는 것이 아니라, 특정 상거래 스냅샷(commercial snapshot)에 적용되어야 합니다.
장바구니 스냅샷(Cart snapshot)에는 다음과 같은 내용이 포함될 수 있습니다:
type CartSnapshot = {
snapshotId: string;
cartId: string;
...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기