UCP Platform + AP2-like Pay 기반 Agentic Commerce 프로젝트 설계서

문서 버전: 1.1
작성일: 2026-09-02
문서 상태: 구현 기준 설계안
대상 독자: 백엔드·모바일·인프라 개발자, 기획자, 설계 검토자
프로토콜 기준: UCP 2026-08-25 REST Binding, AP2 v0.2 Human Present 흐름 참고
개발 기준: 백엔드 4인, 2026-09-07 ~ 2026-09-25


목차

  1. 문서 목적
  2. 프로젝트 한 문장 정의
  3. 가장 중요한 역할 구분
  4. 표준 적용 범위와 적합성 선언
  5. 용어 사전
  6. 참여자와 책임 매핑
  7. 전체 아키텍처
  8. 데이터 최종 소유권
  9. 변경 불가능한 설계 원칙
  10. 전체 구매 흐름
  11. 암호학적 Artifact 설계
  12. 서비스별 상세 설계
  13. UCP Profile 설계
  14. API 설계
  15. 데이터베이스 설계
  16. 상태 머신
  17. 멱등성·일관성·장애 복구
  18. 인증·인가·보안 설계
  19. 이벤트·알림·실시간 상태
  20. 오류 모델
  21. 저장소와 배포 구조
  22. 관측성과 감사
  23. 테스트 전략
  24. MVP 범위와 비범위
  25. 개발 인력과 Task 분배
  26. 2026년 9월 7일~25일 구현 계획
  27. 구현 전 확정해야 할 의사결정
  28. 기존 요구사항 및 API 초안과의 관계
  29. 요구사항 추적표
  30. Definition of Done
  31. 시연 시나리오
  32. 최종 아키텍처 요약
  33. 부록 A. 서비스 간 호출 매트릭스
  34. 부록 B. 핵심 Artifact 관계
  35. 부록 C. 권장 ADR 목록
  36. 부록 D. 참고 자료

1. 문서 목적

이 문서는 사용자가 AI Agent에게 상품 탐색과 구매 준비를 맡기고, 최종 결제는 모바일 Pay 앱에서 직접 확인·승인하는 서비스를 처음부터 끝까지 설명한다.

문서 하나만 읽어도 다음 질문에 답할 수 있도록 구성했다.

기존 요구사항의 핵심 사용자 경험은 유지한다.

  1. 사용자는 모바일 앱 계정을 만들고 배송지·카드·PIN 또는 생체인증을 등록한다.
  2. 사용자는 외부 AI Agent와 자신의 서비스 계정을 연결한다.
  3. 사용자는 Agent에게 자연어로 상품 검색과 장바구니 변경을 요청한다.
  4. Agent는 MCP를 통해 우리 서비스 기능을 호출한다.
  5. 구매 직전 외부 Merchant가 가격·재고·배송비를 최종 확정한다.
  6. 사용자는 모바일 Pay 앱에서 상품·금액·배송지·결제수단을 확인한다.
  7. 사용자 승인이 확인된 경우에만 결제 Credential이 발급된다.
  8. 외부 Merchant가 그 Credential로 Pay Service에 결제를 요청한다.
  9. 결제 성공 후 외부 Merchant가 주문을 생성한다.
  10. Agent와 앱은 같은 서버 상태를 조회한다.

2. 프로젝트 한 문장 정의

외부 AI Agent의 구매 요청을 MCP로 받아 외부 UCP Merchant와 Catalog·Cart·Checkout·Order 흐름을 진행하고, 사용자가 모바일 Pay 앱에서 명시적으로 승인한 경우에만 AP2-like Mandate와 일회성 결제 Credential을 발급하여 구매를 완료하는 Agentic Commerce 플랫폼이다.

가장 단순한 호출 흐름은 다음과 같다.

사용자 ↓ 자연어 요청 외부 AI Agent ↓ MCP Tool mcp-server ↓ 내부 API core-service ↓ UCP REST 외부 UCP Merchant ↓ 우리 Payment Handler pay-service ↓ PG 또는 PG Mock G n0->n1 자연어 요청 n1->n2 MCP Tool n2->n3 내부 API n3->n4 UCP REST n4->n5 우리 Payment Handler n5->n6 n0 사용자 n1 외부 AI Agent n2 mcp-server n3 core-service n4 외부 UCP Merchant n5 pay-service n6 PG 또는 PG Mock

결제 승인 시점에는 다음 경로가 추가된다.

core-service ↓ 승인 세션 생성 pay-service ↓ Push 모바일 Pay 앱 ↓ PIN 또는 생체인증 pay-service ↓ Mandate + Payment Credential core-service G n0->n1 승인 세션 생성 n1->n2 Push n2->n3 PIN 또는 생체인증 n3->n4 Mandate + Payment Credential n0 core-service n1 pay-service n2 모바일 Pay 앱 n3 pay-service n4 core-service

3. 가장 중요한 역할 구분

3.1 UCP는 결제회사가 아니다

UCP는 상품 탐색부터 Cart, Checkout, Order까지 Commerce 정보를 교환하는 프로토콜이다. UCP 자체가 돈을 움직이는 주체는 아니다. 외부 Business가 Merchant of Record로 남고, Platform은 Merchant가 광고한 Payment Handler에 맞춰 결제수단을 취득해 Checkout 완료 요청에 넣는다.1

UCP
=
Commerce 상태와 요청을 교환하는 규칙
Payment Handler
=
어떤 Credential을 어떻게 취득하고 처리할지 정의하는 명세
Pay Service / PSP / Processor
=
Credential을 검증하고 실제 결제를 처리하는 시스템
PG
=
카드 승인·매입 등 실제 결제 결과를 만드는 하위 결제 시스템

3.2 우리 Core는 Merchant가 아니라 UCP Platform이다

이 설계에서 core-service는 상품을 직접 파는 쇼핑몰이 아니다. 외부 Merchant와 UCP로 통신하는 Platform이다.

UCP Platform
우리 core-service
UCP Business
외부 Merchant 또는 ucp-merchant-mock

따라서 상품 가격, 재고, Checkout, 주문의 최종 원본은 외부 Merchant에 있다. Core는 그 상태를 조회하고, 사용자·Agent 관점에서 하나의 구매 흐름으로 오케스트레이션하며, 필요한 Projection과 감사 증거만 저장한다.

3.3 실제 결제는 Merchant가 시작한다

Core가 Pay에 직접 charge를 호출하지 않는다.

1. Core가 Pay에서 사용자 승인을 받고 Credential을 취득 2. Core가 Credential을 UCP Complete Checkout 요청에 포함 3. 외부 Merchant가 Complete 요청을 검증 4. 외부 Merchant가 우리 Payment Handler 규칙에 따라 Pay Service 호출 5. Pay Service가 Credential과 Mandate를 검증하고 PG 호출 6. 결제가 성공하면 외부 Merchant가 Order 생성 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n0 1. Core가 Pay에서 사용자 승인을 받고 Credential을 취득 n1 2. Core가 Credential을 UCP Complete Checkout 요청에 포함 n2 3. 외부 Merchant가 Complete 요청을 검증 n3 4. 외부 Merchant가 우리 Payment Handler 규칙에 따라 Pay Service 호출 n4 5. Pay Service가 Credential과 Mandate를 검증하고 PG 호출 n5 6. 결제가 성공하면 외부 Merchant가 Order 생성

즉 외부 Merchant가 Checkout을 완료하는 과정에서 결제를 시작한다.

3.4 AP2-like Pay의 역할

AP2는 Checkout Mandate와 Payment Mandate를 통해 Agent가 수행하는 구매와 결제에 암호학적 승인 증거를 부여한다. Human Present 흐름에서는 사용자가 최종 Checkout과 결제를 직접 보고 승인한다.2

우리 프로젝트의 Pay Service는 다음 세 역할을 결합한다.

  1. Trusted Surface Backend: 모바일 앱에 확정된 Checkout을 보여주고 사용자 인증을 받는다.
  2. Credential Provider: Payment Mandate-like를 검증하고 Checkout에 묶인 결제 Credential을 발급한다.
  3. Payment Processor: 외부 Merchant가 제출한 Credential을 검증하고 PG를 호출한다.

UCP 공식 예시에는 Token을 발급하는 Tokenizer와 최종 결제를 처리하는 Processor가 같은 주체인 패턴이 존재한다.3


4. 표준 적용 범위와 적합성 선언

4.1 UCP 적용 범위

프로젝트는 구현 중 명세 변동을 막기 위해 UCP 날짜 버전을 2026-08-25로 고정한다.

구현 대상은 다음과 같다.

UCP의 REST 서비스 엔드포인트는 Business의 /.well-known/ucp Profile에서 발견한 Base URL을 기준으로 호출한다. Platform은 모든 UCP 요청에 자신의 Profile URI를 UCP-Agent 헤더로 전달한다.4

4.2 AP2 적용 범위

프로젝트는 다음 AP2 의미를 구현한다.

4.3 MVP에서는 공식 AP2 호환을 주장하지 않는다

AP2 v0.2 및 UCP의 공식 AP2 Mandates Extension은 SD-JWT+KB, 정확한 vct 값, Key Binding, 선택적 공개, 역할별 검증 규칙 등을 요구한다.567

MVP에서 이 형식을 정확히 구현하고 상호운용성 시험까지 통과하지 않는다면 다음 표현만 사용한다.

AP2-like
AP2-inspired
AP2 Human Present 모델 참고

다음 표현은 사용하지 않는다.

AP2 compliant
AP2 certified
공식 AP2 구현

4.4 Capability 광고 원칙

MVP가 공식 AP2 Extension의 형식과 검증 규칙을 모두 구현하지 않는 경우 외부 Profile에 공식 Capability인 다음 값을 광고하지 않는다.

dev.ucp.common.payment.ap2_mandate

대신 실제로 통제하는 도메인의 역도메인 Namespace를 사용한 Custom Capability를 정의한다.

com.example.payment.ap2_like

com.example은 문서용 예시다. 실제 구현에서는 팀이 통제하는 도메인과 Schema Host에 맞게 교체해야 한다. 공식 AP2 형식까지 구현한 이후에만 공식 Capability로 전환한다.


5. 용어 사전

용어 의미
User 상품 구매와 결제 권한을 가진 실제 사용자
Shopping Agent 사용자와 대화하며 MCP Tool을 호출하는 외부 AI Agent
MCP Server Agent의 구조화된 Tool 호출을 우리 내부 API로 변환하는 Adapter
UCP Platform 외부 Business와 UCP로 Commerce 흐름을 진행하는 주체. 본 프로젝트에서는 Core Service
UCP Business 상품·가격·재고·Checkout·Order를 소유하는 외부 판매자 시스템
Merchant 실제 판매자. 이 문서에서는 UCP Business와 같은 대상을 가리킴
Merchant of Record 고객에게 판매의 법적·상업적 책임을 지는 주체. 외부 Merchant
Payment Handler 결제 Credential 취득·전달·처리 방법을 정의하는 명세. 서비스 자체가 아님
Trusted Surface Agent가 아닌 결정론적 UI에서 사용자가 결제 내용을 직접 확인하는 화면. 모바일 Pay 앱
Credential Provider Payment Mandate를 확인하고 결제 Credential을 발급하는 주체. Pay Service
Payment Processor Merchant가 제출한 Credential로 결제를 처리하는 주체. Pay Service
PG Processor 아래에서 실제 승인 결과를 만드는 결제 시스템 또는 Mock
Checkout 결제 직전 상품·수량·금액·배송·정책이 확정되는 Merchant 리소스
Order 결제가 성공하고 구매가 확정된 이후 Merchant가 만드는 주문 리소스
Merchant Authorization Merchant가 자신이 반환한 Checkout 조건에 서명한 증거
Checkout Mandate-like 사용자가 특정 Checkout의 구매 완료를 승인했다는 증거
Payment Mandate-like 사용자가 특정 Merchant·금액·통화·결제수단으로 지불을 승인했다는 증거
Payment Credential Merchant가 Pay Service에 제시할 수 있는 짧은 수명의 일회성 결제 토큰
Projection 외부 시스템 원본을 우리 DB에 조회·표시용으로 복제한 데이터
PurchaseRequest Core가 Checkout·승인·결제·Order 상태를 하나의 사용자 구매 흐름으로 묶는 내부 Aggregate

6. 참여자와 책임 매핑

표준 역할 프로젝트 구성요소 핵심 책임
User 사용자 구매 의사 표현, 앱에서 최종 승인·거부
Shopping Agent ChatGPT 등 외부 Agent 자연어 이해, MCP Tool 선택·호출
Agent Interface mcp-server Tool Schema, Scope 검사, Core 호출, 오류 변환
UCP Platform core-service Discovery, Negotiation, Catalog, Cart, Checkout, Order Orchestration
UCP Business ucp-merchant-mock 또는 실제 Merchant 상품·가격·재고·Checkout·Order 원본 소유
Trusted Surface 모바일 Pay 앱 확정 Checkout 표시, PIN·생체인증
Credential Provider pay-service 결제수단 관리, Mandate 생성·검증, Credential 발급
Payment Processor pay-service Merchant 결제 요청 검증, PG 호출, Payment Receipt
Payment Rail pg-mock 또는 실제 PG 승인 성공·실패·Timeout·조회 결과 제공
Edge Proxy Nginx/API Gateway TLS 종료, 외부 라우팅, 기본 Rate Limit, 요청 크기 제한

7. 전체 아키텍처

flowchart LR USER[사용자] AGENT[외부 AI Agent] APP[Mobile Pay App<br/>Trusted Surface] NGINX[Nginx / API Gateway] MCP[mcp-server] CORE[core-service<br/>UCP Platform] PAY[pay-service<br/>AP2-like CP + Processor] MERCHANT[ucp-merchant-mock<br/>또는 외부 UCP Business] PG[PG / PG Mock] COREDB[(Core DB)] PAYDB[(Pay DB)] MERCHANTDB[(Merchant DB)] REDIS[(Redis)] USER --> AGENT AGENT -->|MCP over HTTPS| NGINX NGINX --> MCP MCP -->|Internal HTTP| CORE CORE <-->|UCP REST| MERCHANT CORE -->|승인 세션 생성| PAY PAY -->|Push| APP USER -->|PIN / Biometrics| APP APP -->|승인·거부| NGINX NGINX --> PAY PAY -->|Mandate + Credential Reference| CORE CORE -->|UCP Complete<br/>Credential + Checkout Mandate-like| MERCHANT MERCHANT -->|Custom Payment Handler API| NGINX NGINX --> PAY PAY --> PG MERCHANT -->|Completed Checkout / Order Webhook| CORE CORE -->|통합 상태| MCP MCP --> AGENT CORE --- COREDB PAY --- PAYDB MERCHANT --- MERCHANTDB CORE --- REDIS PAY --- REDIS G USER->AGENT USER->APP PIN / Biometrics AGENT->NGINX MCP over HTTPS APP->NGINX 승인·거부 NGINX->MCP NGINX->PAY NGINX->PAY MCP->AGENT MCP->CORE Internal HTTP CORE->MCP 통합 상태 CORE->PAY 승인 세션 생성 CORE->MERCHANT UCP REST CORE->MERCHANT UCP Complete Credential + Checkout Mandate-like CORE->COREDB CORE->REDIS PAY->APP Push PAY->CORE Mandate + Credential Reference PAY->PG PAY->PAYDB PAY->REDIS MERCHANT->NGINX Custom Payment Handler API MERCHANT->CORE Completed Checkout / Order Webhook MERCHANT->MERCHANTDB USER 사용자 AGENT 외부 AI Agent APP Mobile Pay App Trusted Surface NGINX Nginx / API Gateway MCP mcp-server CORE core-service UCP Platform PAY pay-service AP2-like CP + Processor MERCHANT ucp-merchant-mock 또는 외부 UCP Business PG PG / PG Mock COREDB Core DB PAYDB Pay DB MERCHANTDB Merchant DB REDIS Redis

7.1 네트워크 경계

외부 요청은 Nginx를 거친다.

Agent
Nginx
MCP Server
Mobile App
Nginx
Core 또는 Pay
External Merchant
Nginx
Pay Merchant API
External Merchant
Nginx
Core Webhook API

내부 서비스 호출은 Nginx를 다시 거치지 않는다.

MCP Server
http://core-service:8080
Core Service
http://pay-service:8080

Core가 외부 Merchant를 호출할 때는 Merchant Profile에서 발견한 HTTPS Endpoint를 사용한다.

Core Service
https://merchant.example/ucp/v1/...

8. 데이터 최종 소유권

데이터 최종 소유자 우리 시스템에서의 처리
상품·옵션 외부 Merchant Core가 Reference와 Cache 저장
가격·할인 외부 Merchant Merchant 응답을 Projection으로 저장
재고 외부 Merchant 검색 결과는 참고, Checkout에서 최종 확인
Cart 외부 Merchant Core가 외부 Cart ID와 최신 Projection 저장
Checkout 외부 Merchant Core가 오케스트레이션하고 서명·상태를 검증
배송 가능 여부·배송비 외부 Merchant Core가 사용자 배송지 전달 후 결과 표시
사용자 계정·Agent 연동 Core Core가 원본 소유
배송지 저장본 Core Checkout 전달 전 사용자 선택·마스킹 처리
카드·결제수단 Token Pay Core·MCP·Agent에 원문 미노출
PIN·생체 Credential Pay 및 기기 보안영역 원문 미저장
사용자 결제 승인 Pay AuthorizationSession으로 원본 관리
Mandate Pay Core에는 Artifact Reference 또는 전달용 토큰만 제공
Payment Credential Pay Core는 짧게 전달만 하며 로그·평문 영속화 금지
실제 결제 상태 Pay Core가 Summary/Event로 Mirror
Order·배송 외부 Merchant Core가 Order Projection과 Webhook 상태 저장
Agent 구매 진행 상태 Core PurchaseRequest로 전체 흐름 집계

8.1 핵심 원칙

Merchant Checkout 상태가 Core DB와 다르면 Merchant가 정답이다.
Pay Payment 상태가 Core DB와 다르면 Pay가 정답이다.
Merchant Order 상태가 Core DB와 다르면 Merchant가 정답이다.
Core의 PurchaseRequest는 여러 원본 상태를 사용자 관점으로 집계한 상태다.

9. 변경 불가능한 설계 원칙

  1. Agent는 결제를 승인할 수 없다.
  2. MCP Server는 외부 Merchant나 Pay Service를 직접 호출하지 않는다.
  3. Core는 Agent가 전달한 금액을 결제 기준으로 사용하지 않는다.
  4. 최종 금액·재고·배송비는 Merchant Checkout 응답을 기준으로 한다.
  5. Pay 앱은 검증된 Merchant Checkout의 내용을 그대로 보여준다.
  6. 사용자 승인 이후 Checkout 내용이 바뀌면 기존 승인을 무효화한다.
  7. Core는 카드번호·CVC·PIN·생체정보 원문을 받지 않는다.
  8. Payment Credential은 특정 Merchant·Checkout·금액·통화에 묶인다.
  9. 실제 결제 실행은 외부 Merchant가 Payment Handler를 통해 시작한다.
  10. 결제 결과를 모르면 재결제하지 않고 조회·대사를 우선한다.
  11. Merchant Complete 결과를 모르면 새 요청을 만들지 않고 같은 Checkout을 조회한다.
  12. 외부 상태 변경 요청에는 멱등성 키를 사용한다.
  13. 서비스 간 분산 트랜잭션은 2PC가 아니라 상태 머신·Outbox·Inbox·대사로 해결한다.
  14. 외부 Merchant URL은 Registry와 Profile 검증을 통과한 경우에만 호출한다.
  15. AP2-like 구현을 공식 AP2 호환으로 과장하지 않는다.

10. 전체 구매 흐름

10.1 흐름 요약

① 사용자·Agent 계정 연동 ② Agent가 MCP로 상품 검색 ③ Core가 Merchant Profile 발견·협상 ④ Core가 Merchant Catalog 호출 ⑤ Core가 Merchant Cart 생성·수정 ⑥ Core가 Merchant Checkout 생성·업데이트 ⑦ Merchant가 가격·재고·배송·금액 확정 ⑧ Core가 Merchant Checkout 서명 검증 ⑨ Core가 Pay에 사용자 승인 세션 생성 ⑩ 사용자가 앱에서 PIN·생체인증으로 승인 ⑪ Pay가 Mandate-like와 Payment Credential 발급 ⑫ Core가 Merchant에 UCP Complete 요청 ⑬ Merchant가 Pay에 결제 요청 ⑭ Pay가 Credential 검증 후 PG 호출 ⑮ Merchant가 Order 생성 ⑯ Core가 Order Projection 저장 ⑰ Agent와 앱에 같은 결과 표시 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n7->n8 n8->n9 n9->n10 n10->n11 n11->n12 n12->n13 n13->n14 n14->n15 n15->n16 n0 ① 사용자·Agent 계정 연동 n1 ② Agent가 MCP로 상품 검색 n2 ③ Core가 Merchant Profile 발견·협상 n3 ④ Core가 Merchant Catalog 호출 n4 ⑤ Core가 Merchant Cart 생성·수정 n5 ⑥ Core가 Merchant Checkout 생성·업데이트 n6 ⑦ Merchant가 가격·재고·배송·금액 확정 n7 ⑧ Core가 Merchant Checkout 서명 검증 n8 ⑨ Core가 Pay에 사용자 승인 세션 생성 n9 ⑩ 사용자가 앱에서 PIN·생체인증으로 승인 n10 ⑪ Pay가 Mandate-like와 Payment Credential 발급 n11 ⑫ Core가 Merchant에 UCP Complete 요청 n12 ⑬ Merchant가 Pay에 결제 요청 n13 ⑭ Pay가 Credential 검증 후 PG 호출 n14 ⑮ Merchant가 Order 생성 n15 ⑯ Core가 Order Projection 저장 n16 ⑰ Agent와 앱에 같은 결과 표시

10.2 전체 시퀀스

sequenceDiagram actor U as User participant A as External Agent participant M as MCP Server participant C as Core / UCP Platform participant B as UCP Business / Merchant participant P as Pay Service participant APP as Mobile Pay App participant PG as PG U->>A: "러닝화 찾아줘" A->>M: search_products M->>C: 상품 검색 요청 C->>B: GET /.well-known/ucp B-->>C: Business Profile C->>B: POST /catalog/search B-->>C: 상품 결과 C-->>M: 정규화된 상품 목록 M-->>A: Tool 결과 U->>A: "2번 270 사이즈 담아줘" A->>M: add_cart_item M->>C: Cart 변경 C->>B: POST/PUT /carts B-->>C: Merchant Cart C-->>M: Cart Projection U->>A: "이걸 살게" A->>M: prepare_checkout M->>C: Checkout 준비 C->>B: POST /checkout-sessions B-->>C: incomplete + 필요한 정보 C->>B: PUT /checkout-sessions/{id}<br/>buyer + fulfillment B-->>C: ready_for_complete + 최종 금액 + Merchant 서명 C->>C: Profile·서명·Checkout 검증 A->>M: request_purchase_approval M->>C: 구매 승인 요청 C->>P: AuthorizationSession 생성 P-->>APP: Push U->>APP: Checkout 확인 U->>APP: PIN/생체 승인 APP->>P: approve P->>P: Mandate-like 생성<br/>Credential 발급 P-->>C: 승인 완료 + Artifact Reference C->>P: 동일 Artifact 조회 P-->>C: Checkout Mandate-like + Payment Credential C->>B: POST /checkout-sessions/{id}/complete B->>B: Checkout Mandate-like 검증 B->>P: POST /merchant/v1/payments P->>P: Credential·Payment Mandate-like 검증 P->>PG: 결제 실행 PG-->>P: SUCCESS / FAILED / UNKNOWN P-->>B: Payment 결과 + Receipt alt 결제 성공 B->>B: Merchant Order 생성 B-->>C: completed + order B-->>C: Order Webhook C-->>M: Purchase COMPLETED M-->>A: 주문 완료 A-->>U: 주문번호·금액 안내 else 결제 실패 B-->>C: Checkout 미완료 + 오류 C-->>M: Purchase FAILED M-->>A: 실패 원인·다음 행동 else 결과 불명 P->>PG: 상태 조회 / Reconcile C->>B: Checkout 상태 조회 end alt 결제 성공else 결제 실패else 결과 불명UserExternal AgentMCP ServerCore / UCP PlatformUCP Business / MerchantPay ServiceMobile Pay AppPG"러닝화 찾아줘"search_products상품 검색 요청GET /.well-known/ucpBusiness ProfilePOST /catalog/search상품 결과정규화된 상품 목록Tool 결과"2번 270 사이즈 담아줘"add_cart_itemCart 변경POST/PUT /cartsMerchant CartCart Projection"이걸 살게"prepare_checkoutCheckout 준비POST /checkout-sessionsincomplete + 필요한 정보PUT /checkout-sessions/{id}buyer + fulfillmentready_for_complete + 최종 금액 +Merchant 서명Profile·서명·Checkout 검증request_purchase_approval구매 승인 요청AuthorizationSession 생성PushCheckout 확인PIN/생체 승인approveMandate-like 생성Credential 발급승인 완료 + Artifact Reference동일 Artifact 조회Checkout Mandate-like +Payment CredentialPOST /checkout-sessions/{id}/completeCheckout Mandate-like 검증POST /merchant/v1/paymentsCredential·PaymentMandate-like 검증결제 실행SUCCESS / FAILED / UNKNOWNPayment 결과 + ReceiptMerchant Order 생성completed + orderOrder WebhookPurchase COMPLETED주문 완료주문번호·금액 안내Checkout 미완료 + 오류Purchase FAILED실패 원인·다음 행동상태 조회 / ReconcileCheckout 상태 조회

10.3 단계별 상세 설명

단계 0. 사전 준비

사용자는 최소한 모바일 앱 계정과 Agent 연동을 준비한다. 배송지와 카드는 초기 설정에서 미리 등록할 수 있지만 필수 선행 조건으로 강제하지 않는다. 누락된 정보가 구매 중 발견되면 현재 Merchant Cart·Checkout Context를 유지한 채 앱에서 등록하고 이어간다.

필수 사전 준비 회원가입 또는 로그인 → 결제 PIN 또는 생체 Credential 등록 → 외부 Agent 계정 연동 선택 사전 준비 배송지 등록 카드 등록 G cluster_0 필수 사전 준비 n0->n1 n1->n2 n0 회원가입 또는 로그인 n1 결제 PIN 또는 생체 Credential 등록 n2 외부 Agent 계정 연동 n3 선택 사전 준비 배송지 등록 카드 등록

구매 도중 정보가 부족하면 다음처럼 처리한다.

배송이 필요한데 배송지 없음 → Core가 Checkout을 incomplete 상태로 유지 → nextAction = REGISTER_ADDRESS → 앱에서 배송지 등록·선택 → 같은 Checkout Context를 Update하여 계속 사용 가능한 결제수단 없음 → Pay AuthorizationSession = PAYMENT_METHOD_REQUIRED → nextAction = REGISTER_PAYMENT_METHOD → 앱에서 카드 등록·선택 → 같은 AuthorizationSession을 PENDING으로 전환 → 최종 승인 화면으로 계속 G n0->n1 n1->n2 n2->n3 n3->n4 n5->n6 n6->n7 n7->n8 n8->n9 n9->n10 n0 배송이 필요한데 배송지 없음 n1 Core가 Checkout을 incomplete 상태로 유지 n2 nextAction = REGISTER_ADDRESS n3 앱에서 배송지 등록·선택 n4 같은 Checkout Context를 Update하여 계속 n5 사용 가능한 결제수단 없음 n6 Pay AuthorizationSession = PAYMENT_METHOD_REQUIRED n7 nextAction = REGISTER_PAYMENT_METHOD n8 앱에서 카드 등록·선택 n9 같은 AuthorizationSession을 PENDING으로 전환 n10 최종 승인 화면으로 계속

배송이 필요하지 않은 디지털 상품은 Merchant Checkout이 Fulfillment Address를 요구하지 않으며 Core가 배송지 단계를 생략한다.

Agent OAuth Token에는 필요한 Scope만 부여한다.

product:read
cart:read
cart:write
checkout:prepare
purchase:request
purchase:status

다음 권한은 Agent에게 부여하지 않는다.

payment-method:sensitive-read
payment-approval:approve
payment-approval:reject
credential:issue
payment:execute

단계 1. Agent가 MCP Tool을 호출한다

사용자가 Agent에게 “10만 원 이하 러닝화 찾아줘”라고 말하면 Agent는 search_products Tool을 호출한다.

Agent
Nginx
MCP Server

MCP Server는 다음만 수행한다.

  1. Agent Access Token 검증
  2. 계정 연동 상태 확인
  3. product:read Scope 확인
  4. Tool Input Schema 검증
  5. Core 내부 API 호출
  6. Core 응답을 MCP Tool 결과로 변환

MCP Server는 상품 DB나 Merchant Endpoint를 직접 호출하지 않는다.

단계 2. Core가 Merchant Profile을 발견한다

Core는 등록된 Merchant의 Profile을 조회한다.

GET https://merchant.example/.well-known/ucp

Profile에서 다음을 확인한다.

UCP Profile은 Business가 /.well-known/ucp에서 공개하고, Platform은 각 요청의 UCP-Agent 헤더로 자신의 Profile URI를 광고한다.4

Core는 Profile을 HTTP Cache-Control에 따라 Cache하되, 서명 검증 실패나 Key 변경이 의심되면 강제로 재조회한다.

단계 3. 버전과 Capability를 협상한다

Core는 Platform Profile과 Business Profile의 교집합을 계산한다.

MVP 필수 교집합은 다음과 같다.

dev.ucp.shopping.catalog.search
dev.ucp.shopping.catalog.lookup
dev.ucp.shopping.cart
dev.ucp.shopping.checkout
dev.ucp.shopping.order
com.example.payment.ap2_like       # Custom AP2-like Capability
com.example.pay                    # Custom Payment Handler

필수 Capability가 하나라도 없으면 구매를 진행하지 않는다.

{
  "errorCode": "UCP_CAPABILITY_INCOMPATIBLE",
  "message": "이 판매자는 현재 우리 Agent 구매 흐름을 지원하지 않습니다.",
  "currentState": "NEGOTIATION_FAILED",
  "nextAction": "CHOOSE_ANOTHER_MERCHANT"
}

단계 4. Catalog를 조회한다

Core는 Profile에서 발견한 REST Base URL을 사용한다.

POST {merchantBaseUrl}/catalog/search
UCP-Agent: profile="https://platform.example/.well-known/ucp"
Request-Id: req-...
Content-Type: application/json
{
  "query": "10만 원 이하 러닝화",
  "context": {
    "country": "KR"
  }
}

Catalog REST Binding은 /catalog/search, /catalog/lookup, /catalog/product 연산을 제공한다.8

Core는 외부 응답을 우리 MCP 표준 모델로 변환한다.

{
  "productRef": {
    "merchantId": "merchant-a",
    "externalProductId": "shoe-123"
  },
  "name": "러닝화 A",
  "price": {
    "amount": 89000,
    "currency": "KRW"
  },
  "availability": "IN_STOCK",
  "imageUrl": "https://merchant.example/images/shoe-123.jpg"
}

merchantId + externalProductId를 하나의 상품 식별자로 사용한다. 서로 다른 Merchant가 같은 Product ID를 쓸 수 있기 때문이다.

검색 가격과 재고는 최종 결제 근거가 아니다. 최종 검증은 Checkout에서 다시 수행한다.

단계 5. Merchant Cart를 생성·수정한다

사용자가 상품을 담으면 Core는 Merchant Cart를 생성하거나 수정한다.

POST /carts
GET  /carts/{id}
PUT  /carts/{id}
POST /carts/{id}/cancel

UCP Cart Update는 전체 교체 방식이므로 Core는 유지할 모든 Line Item을 포함한 전체 Cart 표현을 전송해야 한다.9

Core는 Cart 원본을 소유하지 않고 다음 Projection만 저장한다.

local_cart_projection_id
user_id
merchant_id
external_cart_id
platform_revision
raw_cart_json
normalized_summary_json
last_synced_at
expires_at

앱과 Agent의 동시 수정을 막기 위해 platformRevision을 둔다.

요청 expectedRevision == 현재 platformRevision → Merchant Update 수행 → 성공 후 platformRevision + 1 불일치 → Merchant Cart 재조회 → CART_VERSION_CONFLICT G n0->n1 n1->n2 n3->n4 n4->n5 n0 요청 expectedRevision == 현재 platformRevision n1 Merchant Update 수행 n2 성공 후 platformRevision + 1 n3 불일치 n4 Merchant Cart 재조회 n5 CART_VERSION_CONFLICT

단계 6. Checkout을 생성한다

사용자가 구매를 결정하면 Core는 Cart 또는 선택한 Line Item을 기준으로 Merchant Checkout을 생성한다.

POST {merchantBaseUrl}/checkout-sessions

Merchant는 처음에 incomplete를 반환할 수 있다.

{
  "id": "checkout-123",
  "status": "incomplete",
  "messages": [
    {
      "code": "missing",
      "path": "$.fulfillment.methods[0].selected_destination_id",
      "severity": "recoverable",
      "content": "배송지가 필요합니다."
    }
  ]
}

Merchant가 실물 배송을 요구하면 Core는 사용자의 저장 배송지와 선택한 배송 옵션을 Checkout에 넣어 전체 교체 Update를 보낸다. 저장 배송지가 없으면 Checkout ID와 Cart Context를 보존한 채 ADDRESS_REQUIRED를 반환하고, 앱 등록 후 같은 흐름을 재개한다. Merchant가 배송 불필요 상품으로 응답하면 이 단계 자체를 생략한다.

PUT {merchantBaseUrl}/checkout-sessions/checkout-123

Merchant가 수행하는 일은 다음과 같다.

가격 재계산
할인 재계산
재고 확인
옵션 유효성 확인
배송 가능 지역 확인
배송비 계산
세금·정책 계산
지원 Payment Handler 반환
Checkout 만료시각 설정

UCP Checkout의 공식 상태는 다음과 같다.1

incomplete
requires_escalation
ready_for_complete
complete_in_progress
completed
canceled

ready_for_complete는 Platform이 Complete Checkout을 호출할 준비가 됐다는 뜻이며, 아직 결제나 주문이 성공했다는 뜻은 아니다.

단계 7. Merchant Checkout의 무결성을 검증한다

AP2-like Capability가 협상된 Checkout은 Merchant의 서명을 포함한다.

{
  "id": "checkout-123",
  "status": "ready_for_complete",
  "currency": "KRW",
  "line_items": [],
  "totals": [
    {"type": "total", "amount": 87000}
  ],
  "com.example.payment.ap2_like": {
    "merchant_authorization": "<detached-jws>"
  }
}

Core는 다음을 검증한다.

  1. Profile Origin과 Merchant Registry가 일치하는가?
  2. 서명의 kid가 Merchant Profile의 공개키와 일치하는가?
  3. 서명 Algorithm이 허용 목록에 있는가?
  4. JCS Canonicalization 결과에 대한 서명이 유효한가?
  5. Checkout ID·Merchant ID·통화·총액이 유효한가?
  6. Checkout이 ready_for_complete인가?
  7. Checkout이 만료되지 않았는가?
  8. 우리 Payment Handler가 협상 결과에 포함되는가?

검증 후 VerifiedCheckoutEvidence를 생성한다.

{
  "merchantId": "merchant-a",
  "businessCheckoutId": "checkout-123",
  "ucpVersion": "2026-08-25",
  "status": "ready_for_complete",
  "checkoutDigest": "sha256:...",
  "total": {
    "amount": 87000,
    "currency": "KRW"
  },
  "signatureVerified": true,
  "verifiedAt": "2026-09-02T17:00:00+09:00",
  "expiresAt": "2026-09-02T17:05:00+09:00"
}

이 객체는 기존 설계의 OrderSnapshot을 대체한다.

기존 OrderSnapshot
=
Core가 계산하고 확정한 주문 정보
VerifiedCheckoutEvidence
=
Merchant가 확정·서명한 Checkout을 Core가 검증한 증거

단계 8. Core가 구매 승인 요청을 만든다

Agent가 request_purchase_approval을 호출하면 Core는 같은 Checkout에 활성 승인 요청이 있는지 확인한다.

Checkout ready_for_complete?
서명 검증 완료?
Checkout 만료 전?
같은 Checkout의 활성 PurchaseRequest 없음?
지원 Payment Handler 있음?

조건을 만족하면 PurchaseRequest를 만든다.

purchaseRequestId
userId
merchantId
businessCheckoutId
checkoutEvidenceId
status = AWAITING_APPROVAL
completionIdempotencyKey

그리고 Pay Service에 승인 세션 생성을 요청한다.

POST /internal/v1/authorization-sessions

단계 9. Pay가 모바일 앱에 최종 Checkout을 보여준다

Pay는 Core가 전달한 Evidence를 재검증하거나 Core의 Service Signature를 확인한다. 사용 가능한 결제수단이 있으면 승인 세션을 PENDING으로 저장한다. 결제수단이 없으면 PAYMENT_METHOD_REQUIRED로 저장하고 카드 등록 화면으로 연결한다. 카드 등록·선택이 끝나고 Checkout이 아직 유효할 때 같은 세션을 PENDING으로 전환한다. 이 과정에서 Merchant Cart를 다시 만들거나 Agent 대화를 처음부터 시작하지 않는다.

앱에는 최종 승인 직전에 다음을 표시한다.

판매자
상품명·옵션·수량
상품금액·할인·배송비·세금
최종 결제금액
배송지
선택 결제수단의 마스킹 정보
Checkout 만료시각

사용자는 Agent가 아닌 결정론적 모바일 UI에서 승인하거나 거부한다.

PENDING ├─ APPROVED ├─ REJECTED ├─ EXPIRED └─ INVALIDATED G n0->n1 n0->n2 n0->n3 n0->n4 n0 PENDING n1 APPROVED n2 REJECTED n3 EXPIRED n4 INVALIDATED

단계 10. 사용자 인증과 승인

PIN 인증:

앱 → Pay: approvalId + PIN Pay: 실패 횟수·차단 상태 확인 Pay: PIN KDF 검증 Pay: 조건부 UPDATE로 PENDING → APPROVED G n0->n1 n1->n2 n2->n3 n3->n4 n0 n1 Pay: approvalId + PIN n2 Pay: 실패 횟수·차단 상태 확인 Pay: PIN KDF 검증 n3 Pay: 조건부 UPDATE로 PENDING n4 APPROVED

생체인증:

Pay → App: nonce/challenge 앱: OS 생체인증 성공 후 기기 개인키로 challenge 서명 앱 → Pay: credentialId + signature Pay: 등록 공개키로 검증 Pay: 조건부 UPDATE로 PENDING → APPROVED G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n0 Pay n1 App: nonce/challenge n2 앱: OS 생체인증 성공 후 기기 개인키로 challenge 서명 n3 n4 Pay: credentialId + signature n5 Pay: 등록 공개키로 검증 n6 Pay: 조건부 UPDATE로 PENDING n7 APPROVED

서버는 지문·얼굴 원본을 받거나 저장하지 않는다.

단계 11. Pay가 Mandate-like와 Credential을 발급한다

승인에 성공하면 Pay는 다음을 생성한다.

Checkout Mandate-like
Payment Mandate-like
Payment Credential Token
Authorization Receipt

각 Artifact는 같은 checkoutDigest를 포함해야 한다.

Merchant Checkout Digest
=
Checkout Mandate-like.checkoutDigest
=
Payment Mandate-like.transactionId
=
Payment Credential.checkoutDigest

Credential은 다음에 제한된다.

특정 Merchant
특정 Checkout
특정 금액
특정 통화
특정 결제수단 Reference
짧은 만료시간
한 번의 논리적 결제

단계 12. Core가 UCP Complete Checkout을 호출한다

Core는 Pay에서 동일 AuthorizationSession의 Artifact를 조회한다.

GET /internal/v1/authorization-sessions/{id}/artifact

Core는 Credential 원문을 로그나 DB 평문에 저장하지 않고 즉시 Merchant Complete 요청에 넣는다.

POST {merchantBaseUrl}/checkout-sessions/{checkoutId}/complete
UCP-Agent: profile="https://platform.example/.well-known/ucp"
Idempotency-Key: checkout-complete-{purchaseRequestId}

축약 Body:

{
  "payment": {
    "instruments": [
      {
        "id": "instrument-1",
        "handler_id": "example_pay_1",
        "type": "card",
        "selected": true,
        "display": {
          "description": "VISA •••• 1234"
        },
        "credential": {
          "type": "COM_EXAMPLE_PAY_TOKEN",
          "token": "<one-time-payment-credential>"
        }
      }
    ]
  },
  "com.example.payment.ap2_like": {
    "checkout_mandate": "<signed-checkout-mandate-like>"
  }
}

단계 13. Merchant가 Checkout Mandate-like를 검증한다

Merchant는 다음을 확인한다.

AP2-like Capability가 이 세션에서 협상됐는가?
Checkout Mandate-like가 존재하는가?
서명자가 신뢰하는 Platform Key인가?
Mandate가 만료되지 않았는가?
Mandate가 자신의 Checkout ID를 가리키는가?
checkoutDigest가 현재 Checkout과 같은가?
금액·통화·Line Item이 현재 Checkout과 같은가?
자신의 Merchant Authorization이 Mandate 내부에 포함돼 있는가?

하나라도 실패하면 결제를 호출하지 않고 Checkout 완료를 거절한다.

단계 14. Merchant가 Pay Service에 결제를 요청한다

Merchant는 Custom Payment Handler 명세에 따라 Pay를 호출한다.

POST https://pay.example/merchant/v1/payments
Authorization: Bearer <merchant-client-token>
Idempotency-Key: merchant-checkout-checkout-123
Content-Type: application/json
{
  "merchantId": "merchant-a",
  "businessCheckoutId": "checkout-123",
  "amount": 87000,
  "currency": "KRW",
  "credentialToken": "<one-time-payment-credential>"
}

이 API는 UCP Core가 정한 표준 엔드포인트가 아니다. com.example.pay Payment Handler 명세가 정의하는 프로젝트 전용 Processor API다.

단계 15. Pay가 Credential과 Payment Mandate-like를 검증한다

Pay는 다음을 모두 확인한다.

호출 Merchant 인증 성공?
요청 merchantId == Credential audience?
요청 Checkout ID == Credential binding?
요청 금액·통화 == Credential 금액·통화?
Payment Mandate-like 서명 정상?
Payment Mandate-like checkoutDigest 일치?
사용자 승인 상태가 여전히 APPROVED?
Credential 만료 전?
Credential 미폐기?
Credential이 다른 논리적 결제에 사용되지 않았는가?
Idempotency-Key 재시도 규칙을 만족하는가?

검증 후에만 PG를 호출한다.

단계 16. PG 결과를 처리한다

READY ↓ PROCESSING ├─ SUCCESS ├─ FAILED └─ UNKNOWN ├─ SUCCESS └─ FAILED G n0->n1 n1->n2 n1->n3 n1->n4 n4->n5 n4->n6 n0 READY n1 PROCESSING n2 SUCCESS n3 FAILED n4 UNKNOWN n5 SUCCESS n6 FAILED

PG Timeout 또는 응답 유실이면 UNKNOWN으로 저장한다.

UNKNOWN에서 금지
- 새 Payment 생성
- 다른 Idempotency-Key로 자동 재결제
- 사용자에게 단순 실패로 표시

UNKNOWN에서 허용
- PG 거래 상태 조회
- 같은 Payment ID로 Reconcile
- 운영자 확인

단계 17. Merchant가 Order를 생성한다

Pay가 SUCCESS를 반환하면 Merchant가 자신의 DB에 Order를 생성한다.

Merchant Checkout
+
Payment SUCCESS
Merchant Order

Merchant는 Complete 응답에 Order를 포함한다.

{
  "id": "checkout-123",
  "status": "completed",
  "order": {
    "id": "merchant-order-999",
    "permalink_url": "https://merchant.example/orders/merchant-order-999"
  }
}

UCP Checkout Complete 성공 응답은 completed 상태와 Order Confirmation을 포함할 수 있다.10

단계 18. Core가 Order를 동기화한다

Core는 로컬 Order 원본을 새로 만드는 대신 OrderProjection을 저장한다.

localProjectionId
userId
merchantId
externalOrderId
businessCheckoutId
purchaseRequestId
currentStatus
rawOrderJson
lastVerifiedAt

Merchant는 이후 배송·환불·취소 등의 Order 변경을 서명된 Webhook으로 전달한다. UCP Order Webhook은 Business가 서명하고 Platform이 검증해야 한다.11

단계 19. Agent와 앱에 같은 결과를 보여준다

Agent는 다음 Tool을 호출한다.

get_purchase_status(purchaseRequestId)

Core는 다음 원본 상태를 종합한다.

Core PurchaseRequest
Merchant Checkout
Pay AuthorizationSession
Pay Payment Summary
Merchant Order Projection

응답 예시:

{
  "purchaseRequestId": "purchase-123",
  "status": "COMPLETED",
  "checkoutStatus": "completed",
  "approvalStatus": "APPROVED",
  "paymentStatus": "SUCCESS",
  "order": {
    "merchantId": "merchant-a",
    "externalOrderId": "merchant-order-999"
  },
  "amount": {
    "value": 87000,
    "currency": "KRW"
  },
  "nextAction": "VIEW_ORDER"
}

11. 암호학적 Artifact 설계

이 절의 JSON은 프로젝트 내부 계약을 설명하기 위한 축약 예시다. 공식 AP2 SD-JWT+KB Schema와 동일하다고 간주하면 안 된다.

11.1 Merchant Authorization

Merchant는 자신이 반환한 Checkout 조건에 서명한다.

서명 대상:

JCS_Canonicalize(checkoutWithoutSecurityExtension)

권장 방식:

Algorithm: ES256
Format: Detached JWS
Key Discovery: Merchant UCP Profile의 keys[]

축약 Checkout:

{
  "id": "checkout-123",
  "status": "ready_for_complete",
  "currency": "KRW",
  "line_items": [
    {
      "id": "line-1",
      "item": {
        "id": "shoe-123",
        "title": "러닝화 A",
        "price": 89000
      },
      "quantity": 1
    }
  ],
  "totals": [
    {"type": "subtotal", "amount": 89000},
    {"type": "discount", "amount": -5000},
    {"type": "fulfillment", "amount": 3000},
    {"type": "total", "amount": 87000}
  ],
  "expires_at": "2026-09-02T17:05:00+09:00"
}

Core는 서명 검증 후 전체 Checkout의 Canonical Hash를 계산한다.

checkoutDigest = base64url(SHA-256(JCS_Canonicalize(fullCheckout)))

11.2 Checkout Mandate-like

목적은 다음 문장을 증명하는 것이다.

사용자가 Merchant가 제시한 특정 Checkout의 구매 완료를 명시적으로 승인했다.

축약 Claims:

{
  "typ": "com.example.mandate.checkout.v1",
  "iss": "https://platform.example/.well-known/ucp",
  "aud": "merchant-a",
  "sub": "user-opaque-ref",
  "purchase_request_id": "purchase-123",
  "business_checkout_id": "checkout-123",
  "checkout_digest": "sha256:...",
  "checkout": {
    "id": "checkout-123",
    "merchant_authorization": "<detached-jws>",
    "total": 87000,
    "currency": "KRW"
  },
  "iat": 1788336060,
  "exp": 1788336300,
  "jti": "checkout-mandate-123"
}

검증자는 다음을 확인한다.

11.3 Payment Mandate-like

목적은 다음 문장을 증명하는 것이다.

사용자가 특정 Merchant에게 특정 Checkout 대금으로 특정 금액과 통화를 지불하는 것을 승인했다.

축약 Claims:

{
  "typ": "com.example.mandate.payment.v1",
  "iss": "https://platform.example/.well-known/ucp",
  "aud": "https://pay.example",
  "transaction_id": "sha256:...",
  "payee": {
    "id": "merchant-a",
    "name": "ABC Shop"
  },
  "payment_amount": {
    "currency": "KRW",
    "amount": 87000
  },
  "payment_instrument": {
    "type": "card",
    "reference": "payment-method-ref-123",
    "display": "VISA •••• 1234"
  },
  "purchase_request_id": "purchase-123",
  "business_checkout_id": "checkout-123",
  "iat": 1788336060,
  "exp": 1788336300,
  "jti": "payment-mandate-123"
}

AP2의 closed Payment Mandate는 Checkout Hash에 해당하는 transaction_id, Payee, 최종 결제 금액, 결제수단을 포함한다.6

11.4 Payment Credential Token

Payment Credential은 Merchant가 Pay Processor에 제출하는 일회성 토큰이다. Token 안에 Payment Mandate-like를 포함하거나, Token이 Pay DB의 Mandate Reference를 안전하게 가리키게 한다.

축약 Claims:

{
  "iss": "https://pay.example",
  "aud": "merchant-a",
  "jti": "credential-123",
  "purchase_request_id": "purchase-123",
  "business_checkout_id": "checkout-123",
  "checkout_digest": "sha256:...",
  "amount": 87000,
  "currency": "KRW",
  "payment_mandate": "<signed-payment-mandate-like>",
  "nbf": 1788336060,
  "exp": 1788336300
}

Credential에는 다음을 넣지 않는다.

카드번호
CVC
PIN
생체정보
상세 주소
PG 장기 Token 원문

11.5 Receipt

Authorization Receipt

Pay가 사용자의 승인·거부 결과를 기록한다.

{
  "receiptId": "authorization-receipt-123",
  "authorizationSessionId": "auth-123",
  "checkoutDigest": "sha256:...",
  "status": "APPROVED",
  "authorizedAt": "2026-09-02T17:01:00+09:00",
  "authMethod": "BIOMETRIC",
  "signature": "<pay-signature>"
}

Payment Receipt-like

{
  "receiptId": "payment-receipt-123",
  "paymentId": "payment-123",
  "paymentMandateId": "payment-mandate-123",
  "merchantId": "merchant-a",
  "businessCheckoutId": "checkout-123",
  "amount": 87000,
  "currency": "KRW",
  "status": "SUCCESS",
  "processorTransactionId": "pg-777",
  "createdAt": "2026-09-02T17:01:05+09:00",
  "signature": "<pay-signature>"
}

AP2 Payment Receipt는 상태, 발급자, 발급시각, 연결된 closed Mandate Hash를 포함하는 증거 구조를 정의한다.12


12. 서비스별 상세 설계

12.1 Nginx / API Gateway

책임

외부 라우팅

mcp.example.com/mcp → mcp-server api.example.com/api/v1/auth/** api.example.com/api/v1/addresses/** api.example.com/api/v1/purchases/** api.example.com/api/v1/orders/** api.example.com/api/v1/events → core-service api.example.com/api/v1/payment-methods/** api.example.com/api/v1/payment-approvals/** → pay-service platform.example.com/.well-known/ucp platform.example.com/webhooks/ucp/** → core-service pay.example.com/merchant/v1/payments/** → pay-service merchant.example.com/.well-known/ucp merchant.example.com/ucp/v1/** → ucp-merchant-mock G n0->n1 n2->n3 n4->n5 n6->n7 n8->n9 n10->n11 n0 mcp.example.com/mcp n1 mcp-server n2 api.example.com/api/v1/auth/** api.example.com/api/v1/addresse s/** api.example.com/api/v1/purchase s/** api.example.com/api/v1/orders/* * api.example.com/api/v1/events n3 core-service n4 api.example.com/api/v1/payment- methods/** api.example.com/api/v1/payment- approvals/** n5 pay-service n6 platform.example.com/.well-know n/ucp platform.example.com/webhooks/u cp/** n7 core-service n8 pay.example.com/merchant/v1/pay ments/** n9 pay-service n10 merchant.example.com/.well-know n/ucp merchant.example.com/ucp/v1/** n11 ucp-merchant-mock

UCP Profile Endpoint는 HTTPS로 제공하고 Redirect를 사용하지 않으며 적절한 Public Cache-Control을 제공한다.4

금지 사항

외부 /internal/** 라우팅
Nginx에서 비즈니스 상태 판단
Nginx 로그에 Authorization/Credential Body 원문 저장
Merchant가 넘긴 Host를 동적으로 proxy_pass에 사용

12.2 mcp-server

MCP Server는 Agent 전용 Adapter다.

책임

하지 않는 일

UCP Merchant 직접 호출
Pay Service 직접 호출
상품·Cart·Checkout 상태 저장
가격·재고·배송비 계산
Payment Credential 취득·보관
사용자 결제 승인
PG 호출

권장 패키지 구조

mcp-server/
└── src/main/java/com/project/mcp/
    ├── auth/
    │   ├── AgentTokenVerifier.java
    │   ├── ScopeAuthorizer.java
    │   └── AgentPrincipal.java
    ├── tool/
    │   ├── catalog/
    │   ├── cart/
    │   ├── checkout/
    │   └── purchase/
    ├── client/
    │   └── CoreInternalClient.java
    ├── mapper/
    ├── error/
    ├── observability/
    └── config/

MCP Tool 목록

Tool 입력 핵심값 Core 기능 필요한 Scope
search_products query, filters, merchantIds UCP Catalog Search product:read
get_product productRef UCP Catalog Lookup product:read
get_cart merchantId Merchant Cart 조회 cart:read
add_cart_item productRef, options, quantity, expectedRevision Cart 전체 교체 Update cart:write
update_cart_item cartItemRef, quantity, expectedRevision Cart 전체 교체 Update cart:write
remove_cart_item cartItemRef, expectedRevision Cart 전체 교체 Update cart:write
prepare_checkout cartRef, addressId, fulfillmentPreference Merchant Checkout 준비 checkout:prepare
request_purchase_approval purchaseDraftId, idempotencyKey Pay 승인 요청 생성 purchase:request
get_purchase_status purchaseRequestId 통합 구매 상태 조회 purchase:status

기존 이름과의 호환이 필요하면 Alias를 둔다.

prepare_order
prepare_checkout
request_payment
request_purchase_approval
get_payment_request_status
get_purchase_status

Tool 오류 형식

{
  "errorCode": "CHECKOUT_PRICE_CHANGED",
  "message": "판매자가 최종 가격을 변경했습니다. 새 금액을 확인해 주세요.",
  "currentState": "RECONFIRMATION_REQUIRED",
  "nextAction": "REVIEW_CHECKOUT",
  "retryable": false,
  "traceId": "trace-123"
}

12.3 core-service

Core는 UCP Platform이자 전체 구매 오케스트레이터다.

책임

하지 않는 일

상품·재고 원본 소유
Merchant 가격 임의 계산
카드·CVC·PIN·생체정보 저장
결제 승인
Payment Credential 장기 보관
PG 직접 호출
결제 성공만 보고 로컬 원본 Order 생성
Agent가 전달한 임의 URL 호출

권장 패키지 구조

core-service/
└── src/main/java/com/project/core/
    ├── identity/
    │   ├── user/
    │   ├── login/
    │   └── token/
    ├── agentauth/
    │   ├── client/
    │   ├── connection/
    │   ├── authorization/
    │   └── token/
    ├── address/
    ├── platformprofile/
    │   ├── PlatformProfileController.java
    │   ├── PlatformProfileService.java
    │   └── PlatformKeyProvider.java
    ├── merchantregistry/
    │   ├── MerchantRegistration.java
    │   ├── MerchantEndpointPolicy.java
    │   ├── MerchantAuthConfig.java
    │   └── MerchantRegistryService.java
    ├── ucp/
    │   ├── discovery/
    │   │   ├── BusinessProfileClient.java
    │   │   ├── BusinessProfileValidator.java
    │   │   └── ProfileCache.java
    │   ├── negotiation/
    │   │   ├── ProtocolVersionNegotiator.java
    │   │   ├── CapabilityNegotiator.java
    │   │   └── PaymentHandlerSelector.java
    │   ├── catalog/
    │   │   ├── UcpCatalogClient.java
    │   │   ├── ProductReference.java
    │   │   └── ProductProjectionMapper.java
    │   ├── cart/
    │   │   ├── UcpCartClient.java
    │   │   ├── CartProjection.java
    │   │   └── CartRevisionPolicy.java
    │   ├── checkout/
    │   │   ├── UcpCheckoutClient.java
    │   │   ├── CheckoutProjection.java
    │   │   ├── VerifiedCheckoutEvidence.java
    │   │   └── CheckoutStateMapper.java
    │   ├── order/
    │   │   ├── UcpOrderClient.java
    │   │   ├── OrderProjection.java
    │   │   └── OrderWebhookHandler.java
    │   └── security/
    │       ├── MerchantAuthorizationVerifier.java
    │       ├── UcpHttpSignatureVerifier.java
    │       ├── WebhookSignatureVerifier.java
    │       └── CanonicalJsonService.java
    ├── purchase/
    │   ├── domain/
    │   │   ├── PurchaseRequest.java
    │   │   ├── PurchaseStatus.java
    │   │   └── CompletionAttempt.java
    │   ├── application/
    │   │   ├── PrepareCheckoutService.java
    │   │   ├── RequestApprovalService.java
    │   │   ├── CompleteCheckoutService.java
    │   │   └── GetPurchaseStatusService.java
    │   └── scheduler/
    │       └── CompletionReconciliationJob.java
    ├── integration/pay/
    │   ├── PayAuthorizationClient.java
    │   └── PayArtifactClient.java
    ├── notification/
    ├── realtime/
    ├── outbox/
    ├── inbox/
    ├── audit/
    └── observability/

Core의 중심 Aggregate

PurchaseRequest ├── User ├── Merchant ├── Merchant Cart Reference ├── Merchant Checkout Reference ├── VerifiedCheckoutEvidence ├── Pay AuthorizationSession Reference ├── CompletionAttempt └── External Order Reference G n0->n1 n0->n2 n0->n3 n0->n4 n0->n5 n0->n6 n0->n7 n0->n8 n0 PurchaseRequest n1 User n2 Merchant n3 Merchant Cart Reference n4 Merchant Checkout Reference n5 VerifiedCheckoutEvidence n6 Pay AuthorizationSession Reference n7 CompletionAttempt n8 External Order Reference

Core는 여러 외부 상태를 직접 하나로 합치지 않고 각각의 원본 상태와 Reference를 유지한 뒤 사용자용 통합 상태를 계산한다.


12.4 pay-service

Pay Service는 사용자 승인과 결제 신뢰 경계를 소유한다.

책임

하지 않는 일

상품 검색
Merchant Cart 수정
배송비 계산
Merchant Checkout 생성·Update
Merchant Order 생성
Agent OAuth 관리
MCP Tool 제공

권장 패키지 구조

pay-service/
└── src/main/java/com/project/pay/
    ├── paymentmethod/
    │   ├── PaymentMethod.java
    │   ├── PaymentMethodToken.java
    │   └── PaymentMethodService.java
    ├── credential/
    │   ├── PinCredential.java
    │   ├── BiometricCredential.java
    │   ├── CredentialVerifier.java
    │   └── DeviceChallengeService.java
    ├── merchant/
    │   ├── MerchantAccount.java
    │   ├── MerchantAuthenticator.java
    │   └── MerchantHandlerConfiguration.java
    ├── authorization/
    │   ├── AuthorizationSession.java
    │   ├── AuthorizationStatus.java
    │   ├── AuthorizationSessionService.java
    │   └── AuthorizationExpiryJob.java
    ├── trustedsurface/
    │   ├── ApprovalViewAssembler.java
    │   └── CheckoutEvidenceVerifier.java
    ├── mandate/
    │   ├── CheckoutMandate.java
    │   ├── PaymentMandate.java
    │   ├── MandateSigner.java
    │   └── MandateVerifier.java
    ├── token/
    │   ├── PaymentCredentialToken.java
    │   ├── PaymentTokenIssuer.java
    │   ├── PaymentTokenVerifier.java
    │   └── TokenConsumptionPolicy.java
    ├── payment/
    │   ├── Payment.java
    │   ├── PaymentAttempt.java
    │   ├── PaymentService.java
    │   └── PaymentStateMachine.java
    ├── processor/
    │   ├── PaymentProcessor.java
    │   ├── PgPaymentProcessor.java
    │   └── PgMockPaymentProcessor.java
    ├── reconciliation/
    │   └── PaymentReconciliationJob.java
    ├── receipt/
    │   └── PaymentReceipt.java
    ├── idempotency/
    ├── outbox/
    ├── inbox/
    ├── audit/
    └── observability/

세 가지 API 면

1. App API
   사용자 카드·PIN·생체·승인 대기 목록·승인·거부

2. Core Internal API
   AuthorizationSession 생성·상태 조회·Artifact 조회·무효화

3. Merchant Processor API
   외부 Merchant가 Credential로 결제 요청·상태 조회

12.5 ucp-merchant-mock

Merchant Mock는 외부 UCP Business를 재현한다. 단순 고정 응답 Stub이 아니라 전체 구매 흐름과 실패 복구를 증명하는 시연 구성요소다.

책임

패키지 구조

ucp-merchant-mock/
├── profile/
├── catalog/
├── inventory/
├── cart/
├── checkout/
├── security/
├── paymenthandler/
├── order/
├── fulfillment/
├── webhook/
├── idempotency/
└── scenario/

제어 가능한 시나리오

NORMAL
PRICE_CHANGED
OUT_OF_STOCK
ADDRESS_NOT_SERVICEABLE
REQUIRES_ESCALATION
INVALID_MERCHANT_SIGNATURE
COMPLETE_TIMEOUT_BEFORE_PROCESS
COMPLETE_TIMEOUT_AFTER_ORDER
WEBHOOK_DUPLICATE
WEBHOOK_DELAYED

12.6 pg-mock

책임

시나리오

SUCCESS
FAILED
TIMEOUT_BEFORE_PROCESS
TIMEOUT_AFTER_PROCESS
UNKNOWN_THEN_SUCCESS
UNKNOWN_THEN_FAILED
DELAYED_SUCCESS

TIMEOUT_AFTER_PROCESS는 PG에서는 이미 성공했지만 Pay가 응답을 받지 못한 상황이다. 이 시나리오에서 자동 재결제를 막는 테스트가 반드시 있어야 한다.


13. UCP Profile 설계

다음 JSON은 필수 개념만 남긴 축약 예시다. 실제 구현 시 UCP 공식 Schema 검증을 적용한다.

13.1 Platform Profile

GET https://platform.example/.well-known/ucp
{
  "ucp": {
    "version": "2026-08-25",
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-08-25",
          "transport": "rest",
          "endpoint": "https://platform.example/ucp/v1",
          "spec": "https://ucp.dev/2026-08-25/specification/overview",
          "schema": "https://ucp.dev/2026-08-25/services/shopping/rest.openapi.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.catalog.lookup": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.cart": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.checkout": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.order": [
        {
          "version": "2026-08-25",
          "config": {
            "webhook_url": "https://platform.example/webhooks/ucp/orders"
          }
        }
      ],
      "com.example.payment.ap2_like": [
        {
          "version": "2026-09-02",
          "spec": "https://pay.example/spec/ap2-like",
          "schema": "https://pay.example/schemas/ap2-like.json",
          "extends": "dev.ucp.shopping.checkout"
        }
      ]
    },
    "payment_handlers": {
      "com.example.pay": [
        {
          "id": "example_pay_platform_v1",
          "version": "2026-09-02",
          "spec": "https://pay.example/spec/payment-handler",
          "schema": "https://pay.example/schemas/payment-handler.json",
          "available_instruments": [
            {"type": "card"}
          ]
        }
      ]
    }
  },
  "keys": [
    {
      "kty": "EC",
      "crv": "P-256",
      "alg": "ES256",
      "use": "sig",
      "kid": "platform-signing-2026-09",
      "x": "...",
      "y": "..."
    }
  ]
}

13.2 Business Profile

GET https://merchant.example/.well-known/ucp
{
  "ucp": {
    "version": "2026-08-25",
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-08-25",
          "transport": "rest",
          "endpoint": "https://merchant.example/ucp/v1",
          "spec": "https://ucp.dev/2026-08-25/specification/overview",
          "schema": "https://ucp.dev/2026-08-25/services/shopping/rest.openapi.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.catalog.lookup": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.cart": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.checkout": [
        {"version": "2026-08-25"}
      ],
      "dev.ucp.shopping.order": [
        {"version": "2026-08-25"}
      ],
      "com.example.payment.ap2_like": [
        {
          "version": "2026-09-02",
          "spec": "https://pay.example/spec/ap2-like",
          "schema": "https://pay.example/schemas/ap2-like.json",
          "extends": "dev.ucp.shopping.checkout"
        }
      ]
    },
    "payment_handlers": {
      "com.example.pay": [
        {
          "id": "example_pay_merchant_a",
          "version": "2026-09-02",
          "spec": "https://pay.example/spec/payment-handler",
          "schema": "https://pay.example/schemas/payment-handler.json",
          "available_instruments": [
            {"type": "card"}
          ],
          "config": {
            "merchant_id": "merchant-a",
            "processor_endpoint": "https://pay.example/merchant/v1/payments"
          }
        }
      ]
    }
  },
  "keys": [
    {
      "kty": "EC",
      "crv": "P-256",
      "alg": "ES256",
      "use": "sig",
      "kid": "merchant-a-signing-2026-09",
      "x": "...",
      "y": "..."
    }
  ]
}

13.3 Profile 검증 규칙

Core는 Profile을 사용할 때 다음을 검증한다.

HTTPS인가?
Redirect가 없는가?
Host가 Merchant Registry의 허용 Origin인가?
문서 크기가 제한 이내인가?
ucp.version이 날짜 버전인가?
REST Endpoint가 HTTPS인가?
Capability Namespace와 Schema Host의 Authority Binding이 유효한가?
Payment Handler Namespace와 Schema Host가 일치하는가?
keys[]의 kid가 중복되지 않는가?
지원하지 않는 필드는 tolerant reader 원칙으로 무시 가능한가?

14. API 설계

API는 네 종류로 분리한다.

Public App API
MCP Tool
Internal Service API
External Merchant/Protocol API

14.1 Public App API — Core

Method Endpoint 설명
POST /api/v1/auth/oauth/google 로그인·회원가입
POST /api/v1/auth/token/refresh Access Token 갱신
POST /api/v1/auth/logout 로그아웃
GET /oauth/authorize Agent OAuth 인가
POST /oauth/token Agent Token 발급
GET /api/v1/users/me/agent-connections Agent 연동 목록
DELETE /api/v1/users/me/agent-connections/{connectionId} 연동 해제
POST /api/v1/addresses 배송지 등록
GET /api/v1/addresses 배송지 목록
PUT /api/v1/addresses/{addressId} 배송지 수정
PATCH /api/v1/addresses/{addressId}/default 기본 배송지 지정
DELETE /api/v1/addresses/{addressId} 배송지 삭제
GET /api/v1/purchases/{purchaseRequestId} 통합 구매 상태
GET /api/v1/orders Order Projection 목록
GET /api/v1/orders/{orderProjectionId} Order Projection 상세
GET /api/v1/events SSE 상태 구독

14.2 Public App API — Pay

Method Endpoint 설명
POST /api/v1/users/me/payment-pin PIN 등록
PUT /api/v1/users/me/payment-pin PIN 변경
POST /api/v1/users/me/biometric-credentials 생체 Device Credential 등록
POST /api/v1/payment-methods/cards 카드 Token 등록
GET /api/v1/payment-methods/cards 카드 목록
PATCH /api/v1/payment-methods/cards/{id}/default 기본 카드 지정
DELETE /api/v1/payment-methods/cards/{id} 카드 삭제
GET /api/v1/payment-approvals?status=PENDING 승인 대기 목록
GET /api/v1/payment-approvals/{approvalId} 승인 상세
POST /api/v1/payment-approvals/{approvalId}/approve 승인
POST /api/v1/payment-approvals/{approvalId}/reject 거부
POST /api/v1/payment-approvals/{approvalId}/challenge 생체 Challenge 발급

14.3 MCP → Core Internal API

Method Endpoint 설명
POST /internal/v1/catalog/search UCP Catalog 검색
POST /internal/v1/catalog/lookup 상품 상세
GET /internal/v1/carts/current?merchantId=... 현재 Merchant Cart
POST /internal/v1/carts/items Cart Item 추가
PATCH /internal/v1/carts/items/{cartItemRef} 수량 수정
DELETE /internal/v1/carts/items/{cartItemRef} Item 삭제
POST /internal/v1/checkouts/prepare Merchant Checkout 준비
POST /internal/v1/purchase-requests 승인 요청 생성
GET /internal/v1/purchase-requests/{id} 통합 구매 상태

모든 요청은 Agent 사용자 Context를 명시적으로 전달한다.

Authorization: Bearer <service-jwt>
X-End-User-Id: user-123
X-Agent-Connection-Id: connection-123
X-Trace-Id: trace-123

X-End-User-Id만 신뢰하지 않고 Service JWT와 서명된 Context 또는 내부 Auth Middleware로 위변조를 방지한다.

14.4 Core → Pay Internal API

Method Endpoint 설명
POST /internal/v1/authorization-sessions 승인 세션 생성
GET /internal/v1/authorization-sessions/{id} 승인 상태 조회
GET /internal/v1/authorization-sessions/{id}/artifact 동일 Mandate·Credential 조회
POST /internal/v1/authorization-sessions/{id}/invalidate Checkout 변경 시 무효화
GET /internal/v1/payment-summaries/by-purchase/{purchaseRequestId} 결제 Summary

승인 세션 생성 예시:

{
  "purchaseRequestId": "purchase-123",
  "userId": "user-123",
  "merchant": {
    "merchantId": "merchant-a",
    "displayName": "ABC Shop",
    "profileUrl": "https://merchant.example/.well-known/ucp"
  },
  "checkoutEvidence": {
    "businessCheckoutId": "checkout-123",
    "checkoutDigest": "sha256:...",
    "canonicalCheckout": {},
    "merchantAuthorization": "<detached-jws>",
    "total": {
      "amount": 87000,
      "currency": "KRW"
    },
    "expiresAt": "2026-09-02T17:05:00+09:00"
  },
  "paymentHandler": {
    "name": "com.example.pay",
    "handlerId": "example_pay_merchant_a"
  }
}

14.5 Merchant → Pay Processor API

Method Endpoint 설명
POST /merchant/v1/payments Credential로 결제 실행
GET /merchant/v1/payments/{paymentId} 결제 상태 조회
POST /merchant/v1/payments/{paymentId}/reconcile PG 상태 대사 트리거

결제 요청은 Merchant 인증과 멱등성 키가 필수다.

Authorization: Bearer <merchant-client-token>
Idempotency-Key: merchant-checkout-checkout-123
X-Request-Id: req-123

14.6 Core → UCP Business

Business Profile의 REST Base URL을 기준으로 호출한다.

Capability Method 상대 Endpoint
Profile GET /.well-known/ucp
Catalog Search POST /catalog/search
Catalog Lookup POST /catalog/lookup
Product Detail POST /catalog/product
Cart Create POST /carts
Cart Get GET /carts/{id}
Cart Update PUT /carts/{id}
Cart Cancel POST /carts/{id}/cancel
Checkout Create POST /checkout-sessions
Checkout Get GET /checkout-sessions/{id}
Checkout Update PUT /checkout-sessions/{id}
Checkout Complete POST /checkout-sessions/{id}/complete
Checkout Cancel POST /checkout-sessions/{id}/cancel
Order Get GET /orders/{id}

UCP Checkout REST는 Create, Get, Update, Complete, Cancel 연산을 정의한다.10

14.7 Merchant → Core Webhook

POST /webhooks/ucp/orders
UCP-Agent: profile="https://merchant.example/.well-known/ucp"
Signature-Input: ...
Signature: ...
Content-Digest: ...
Webhook-Id: webhook-123

Core는 서명 검증 전에 Business Profile과 kid를 확인하고, Webhook-Id를 Inbox의 Unique Key로 사용한다.


15. 데이터베이스 설계

서비스별 DB를 분리한다.

core_db / core_user
pay_db / pay_user
merchant_db / merchant_user

각 DB 계정은 다른 서비스 DB에 접근할 수 없다. 서비스 간 식별자는 외래키가 아니라 UUID 또는 외부 ID 문자열 Reference다.

15.1 Core DB

users
refresh_tokens
login_failure_attempts

agent_clients
agent_connections
agent_authorization_codes
agent_refresh_tokens

addresses

merchant_registrations
merchant_auth_configs
merchant_profile_cache
negotiated_capability_snapshots

product_references
product_projection_cache

commerce_sessions
ucp_cart_projections
ucp_checkout_projections
verified_checkout_evidence

purchase_requests
completion_attempts

ucp_order_projections
ucp_order_snapshots

webhook_inbox
core_outbox_events
core_inbox_events
user_events
core_audit_events

merchant_registrations

id
merchant_id
name
display_name
profile_url
allowed_origin
status
authentication_type
auth_secret_reference
created_at
updated_at

규칙:

UNIQUE(merchant_id)
UNIQUE(profile_url)
UNIQUE(allowed_origin)

merchant_profile_cache

id
merchant_id
profile_url
ucp_version
raw_profile_json
etag
cache_control
fetched_at
expires_at
validation_status
profile_hash

negotiated_capability_snapshots

id
merchant_id
platform_profile_hash
business_profile_hash
ucp_version
service_binding_json
capability_intersection_json
payment_handler_intersection_json
negotiated_at
expires_at

Checkout이 시작된 뒤 Profile이 바뀌어도 세션에 적용된 협상 결과를 추적할 수 있도록 Snapshot을 남긴다.

product_references

id
merchant_id
external_product_id
canonical_ref
created_at
UNIQUE(merchant_id, external_product_id)

ucp_cart_projections

id
user_id
merchant_id
external_cart_id
platform_revision
ucp_status
raw_cart_json
normalized_summary_json
expires_at
last_synced_at
created_at
updated_at
UNIQUE(user_id, merchant_id, external_cart_id)

ucp_checkout_projections

id
user_id
merchant_id
external_checkout_id
external_cart_id
ucp_status
amount
currency
raw_checkout_json
merchant_authorization
expires_at
last_synced_at
created_at
updated_at

verified_checkout_evidence

id
merchant_id
external_checkout_id
ucp_version
raw_checkout_json
canonical_checkout_json
merchant_authorization
merchant_key_id
signature_algorithm
signature_verified
checkout_digest
amount
currency
verified_at
expires_at
created_at
UNIQUE(merchant_id, external_checkout_id, checkout_digest)

purchase_requests

id
user_id
merchant_id
external_checkout_id
checkout_evidence_id
authorization_session_id
status
completion_idempotency_key
external_order_id
last_error_code
last_error_payload
created_at
updated_at
version

주요 제약:

UNIQUE(merchant_id, external_checkout_id, checkout_evidence_id)
UNIQUE(completion_idempotency_key)

Checkout 내용이 바뀌어 Digest가 달라지면 기존 PurchaseRequest를 무효화하고 새 Evidence로 새 PurchaseRequest를 만들 수 있다.

completion_attempts

id
purchase_request_id
request_id
idempotency_key
request_hash
status
merchant_http_status
response_hash
started_at
completed_at
last_error

ucp_order_projections

id
user_id
merchant_id
external_order_id
external_checkout_id
purchase_request_id
status
permalink_url
amount
currency
raw_order_json
last_verified_at
created_at
updated_at
UNIQUE(merchant_id, external_order_id)
UNIQUE(purchase_request_id)

webhook_inbox

id
merchant_id
webhook_id
event_type
payload_hash
status
received_at
processed_at
last_error
UNIQUE(merchant_id, webhook_id)

15.2 Pay DB

payment_pin_credentials
pin_failure_attempts
biometric_credentials
biometric_challenges

payment_methods
payment_method_tokens

merchant_accounts
merchant_handler_configs
merchant_api_credentials

authorization_sessions
pay_verified_checkout_evidence
authorization_receipts

checkout_mandates
payment_mandates
payment_credential_tokens

payments
payment_attempts
pg_transactions
payment_receipts
payment_reconciliation_jobs

idempotency_records
pay_outbox_events
pay_inbox_events
pay_audit_events

payment_methods

id
user_id
provider
provider_token_reference
brand
last4
expiry_month
expiry_year
status
is_default
created_at
updated_at
deleted_at

카드 원문과 CVC는 저장하지 않는다.

authorization_sessions

id
purchase_request_id
user_id
merchant_id
external_checkout_id
checkout_digest
amount
currency
selected_payment_method_id
status
required_action
expires_at
approved_at
rejected_at
invalidated_at
auth_method
created_at
updated_at
version

주요 제약:

UNIQUE(purchase_request_id)
UNIQUE(merchant_id, external_checkout_id, checkout_digest)

checkout_mandates

id
authorization_session_id
jti
issuer
audience
checkout_digest
signed_artifact_encrypted
artifact_hash
issued_at
expires_at
status

payment_mandates

id
authorization_session_id
jti
transaction_id
merchant_id
external_checkout_id
amount
currency
payment_method_ref
signed_artifact_encrypted
artifact_hash
issued_at
expires_at
status

payment_credential_tokens

id
authorization_session_id
checkout_mandate_id
payment_mandate_id
token_hash
token_ciphertext
merchant_id
external_checkout_id
checkout_digest
amount
currency
status
issued_at
expires_at
consumed_at
consumed_by_payment_id

상태:

ISSUED
CONSUMED
EXPIRED
REVOKED

Token 원문을 저장해야 재시도 시 같은 Artifact를 돌려줄 수 있다면 KMS 기반 Envelope Encryption으로 암호화한다. 검색·중복 검사용으로는 token_hash를 사용한다.

payments

id
merchant_id
external_checkout_id
purchase_request_id
credential_token_id
amount
currency
status
processor_transaction_id
idempotency_key
request_hash
created_at
updated_at
version

제약:

UNIQUE(merchant_id, idempotency_key)
UNIQUE(credential_token_id)

동일 Credential의 정상적인 같은 요청 재시도는 기존 Payment를 반환하고, 다른 논리적 요청에서 재사용하면 차단한다.

payment_attempts

id
payment_id
attempt_no
operation
request_hash
pg_request_id
status
started_at
completed_at
error_code

15.3 Merchant Mock DB

products
product_options
inventory

carts
cart_items

checkout_sessions
checkout_line_items
checkout_totals
checkout_idempotency_records

orders
order_items
fulfillment_events

merchant_keys
platform_registrations
webhook_deliveries
merchant_audit_events

15.4 Redis 사용 범위

Redis는 다음 용도로만 사용한다.

Profile Cache 보조
짧은 TTL Challenge
Rate Limit Counter
SSE Connection Registry
분산 Lock이 꼭 필요한 짧은 구간

다음 상태의 최종 원본으로 사용하지 않는다.

PurchaseRequest
AuthorizationSession
Payment
Credential 소비 상태
Order Projection
Idempotency 결과

16. 상태 머신

16.1 UCP Checkout 상태

stateDiagram-v2 [*] --> incomplete incomplete --> incomplete: Update로 정보 보완 incomplete --> requires_escalation: API로 해결 불가 requires_escalation --> incomplete: 사용자 입력 후 복귀 incomplete --> ready_for_complete: 모든 조건 충족 ready_for_complete --> complete_in_progress: Complete 호출 complete_in_progress --> completed: 결제·주문 성공 complete_in_progress --> ready_for_complete: 복구 가능한 실패 incomplete --> canceled requires_escalation --> canceled ready_for_complete --> canceled completed --> [*] canceled --> [*] G START->incomplete canceled->END complete_in_progress->completed 결제·주문 성공 complete_in_progress->ready_for_complete 복구 가능한 실패 completed->END incomplete->canceled incomplete->incomplete Update로 정보 보완 incomplete->ready_for_complete 모든 조건 충족 incomplete->requires_escalation API로 해결 불가 ready_for_complete->canceled ready_for_complete->complete_in_progress Complete 호출 requires_escalation->canceled requires_escalation->incomplete 사용자 입력 후 복귀 START END canceled canceled complete_in_progress complete_in_progress completed completed incomplete incomplete ready_for_complete ready_for_complete requires_escalation requires_escalation

UCP Checkout 상태는 Merchant가 결정한다. Core는 자체 추측으로 변경하지 않는다.

16.2 Core PurchaseRequest

stateDiagram-v2 [*] --> PREPARING PREPARING --> AWAITING_APPROVAL: Checkout 검증 완료 PREPARING --> PREPARATION_FAILED AWAITING_APPROVAL --> AUTHORIZED: Pay 승인 완료 AWAITING_APPROVAL --> REJECTED AWAITING_APPROVAL --> EXPIRED AWAITING_APPROVAL --> INVALIDATED AUTHORIZED --> SUBMITTING: Complete 시작 SUBMITTING --> COMPLETED: Merchant completed + order SUBMITTING --> FAILED: 확정 실패 SUBMITTING --> COMPLETION_UNKNOWN: 응답 유실/Timeout COMPLETION_UNKNOWN --> COMPLETED: Checkout 조회로 성공 확인 COMPLETION_UNKNOWN --> FAILED: 조회로 실패 확인 COMPLETION_UNKNOWN --> SUBMITTING: 같은 멱등키로 안전 재전송 G START->PREPARING AUTHORIZED->SUBMITTING Complete 시작 AWAITING_APPROVAL->AUTHORIZED Pay 승인 완료 AWAITING_APPROVAL->EXPIRED AWAITING_APPROVAL->INVALIDATED AWAITING_APPROVAL->REJECTED COMPLETION_UNKNOWN->COMPLETED Checkout 조회로 성공 확인 COMPLETION_UNKNOWN->FAILED 조회로 실패 확인 COMPLETION_UNKNOWN->SUBMITTING 같은 멱등키로 안전 재전송 PREPARING->AWAITING_APPROVAL Checkout 검증 완료 PREPARING->PREPARATION_FAILED SUBMITTING->COMPLETED Merchant completed + order SUBMITTING->COMPLETION_UNKNOWN 응답 유실/Timeout SUBMITTING->FAILED 확정 실패 START END AUTHORIZED AUTHORIZED AWAITING_APPROVAL AWAITING_APPROVAL COMPLETED COMPLETED COMPLETION_UNKNOWN COMPLETION_UNKNOWN EXPIRED EXPIRED FAILED FAILED INVALIDATED INVALIDATED PREPARATION_FAILED PREPARATION_FAILED PREPARING PREPARING REJECTED REJECTED SUBMITTING SUBMITTING

상태 의미

상태 의미
PREPARING Merchant Checkout 생성·Update·검증 중
AWAITING_APPROVAL 모바일 앱 승인 대기
AUTHORIZED 사용자 승인과 Artifact 발급 완료
SUBMITTING UCP Complete 호출 중
COMPLETION_UNKNOWN Complete 결과를 확정할 수 없음
COMPLETED Merchant Order까지 확인
FAILED 재시도만으로 복구할 수 없는 구매 실패
REJECTED 사용자가 거부
EXPIRED 승인 또는 Checkout 만료
INVALIDATED Checkout 내용 변경 등으로 승인 무효화

16.3 Pay AuthorizationSession

stateDiagram-v2 [*] --> PAYMENT_METHOD_REQUIRED: 사용 가능한 카드 없음 [*] --> PENDING: 사용 가능한 카드 있음 PAYMENT_METHOD_REQUIRED --> PENDING: 카드 등록·선택 PAYMENT_METHOD_REQUIRED --> EXPIRED: Checkout/세션 만료 PAYMENT_METHOD_REQUIRED --> INVALIDATED: Checkout 변경/연동 해제 PENDING --> APPROVED: PIN/생체 성공 PENDING --> REJECTED: 사용자 거부 PENDING --> EXPIRED: expiresAt 경과 PENDING --> INVALIDATED: Checkout 변경/연동 해제 APPROVED --> [*] REJECTED --> [*] EXPIRED --> [*] INVALIDATED --> [*] G START->PAYMENT_METHOD_REQUIRED 사용 가능한 카드 없음 START->PENDING 사용 가능한 카드 있음 APPROVED->END EXPIRED->END INVALIDATED->END PAYMENT_METHOD_REQUIRED->EXPIRED Checkout/세션 만료 PAYMENT_METHOD_REQUIRED->INVALIDATED Checkout 변경/연동 해제 PAYMENT_METHOD_REQUIRED->PENDING 카드 등록·선택 PENDING->APPROVED PIN/생체 성공 PENDING->EXPIRED expiresAt 경과 PENDING->INVALIDATED Checkout 변경/연동 해제 PENDING->REJECTED 사용자 거부 REJECTED->END START END APPROVED APPROVED EXPIRED EXPIRED INVALIDATED INVALIDATED PAYMENT_METHOD_REQUIRED PAYMENT_METHOD_REQUIRE D PENDING PENDING REJECTED REJECTED

PAYMENT_METHOD_REQUIRED는 아직 결제 승인을 받은 상태가 아니다. 카드가 등록·선택된 뒤 확정 Checkout이 그대로 유효할 때만 PENDING으로 이동한다. 승인 상태의 Terminal 전이는 단방향이며 APPROVED, REJECTED, EXPIRED, INVALIDATED를 다시 PENDING으로 되돌리지 않는다.

16.4 Payment Credential

stateDiagram-v2 [*] --> ISSUED ISSUED --> CONSUMED: 정상 결제와 연결 ISSUED --> EXPIRED: 만료 ISSUED --> REVOKED: 승인 무효화/보안 사건 CONSUMED --> [*] EXPIRED --> [*] REVOKED --> [*] G START->ISSUED CONSUMED->END EXPIRED->END ISSUED->CONSUMED 정상 결제와 연결 ISSUED->EXPIRED 만료 ISSUED->REVOKED 승인 무효화/보안 사건 REVOKED->END START END CONSUMED CONSUMED EXPIRED EXPIRED ISSUED ISSUED REVOKED REVOKED

CONSUMED는 Credential이 다시 조회되지 않는다는 뜻이 아니다. 같은 Merchant·Idempotency-Key·Request Hash의 재시도에는 연결된 기존 Payment 결과를 반환할 수 있다.

16.5 Pay Payment

stateDiagram-v2 [*] --> READY READY --> PROCESSING PROCESSING --> SUCCESS PROCESSING --> FAILED PROCESSING --> UNKNOWN UNKNOWN --> SUCCESS: PG 조회 UNKNOWN --> FAILED: PG 조회 SUCCESS --> [*] FAILED --> [*] G START->READY FAILED->END PROCESSING->FAILED PROCESSING->SUCCESS PROCESSING->UNKNOWN READY->PROCESSING SUCCESS->END UNKNOWN->FAILED PG 조회 UNKNOWN->SUCCESS PG 조회 START END FAILED FAILED PROCESSING PROCESSING READY READY SUCCESS SUCCESS UNKNOWN UNKNOWN

16.6 상태 구분 원칙

AuthorizationSession.APPROVED
Payment.SUCCESS
UCP Checkout.completed
Merchant Order 생성 확인

각 상태를 별도 저장해야 다음 장애를 구분할 수 있다.

사용자는 승인했지만 Merchant Complete가 아직 호출되지 않음
Merchant가 Pay를 호출했지만 PG 결과가 UNKNOWN
Pay는 성공했지만 Merchant Complete 응답이 Core에 도착하지 않음
Merchant는 Order를 만들었지만 Webhook이 지연됨

16.7 사용자용 통합 상태

내부 상태 조합 사용자용 상태 nextAction
Checkout incomplete REQUIRES_CHECKOUT_INPUT UPDATE_ADDRESS_OR_OPTION
Checkout requires_escalation MERCHANT_HANDOFF_REQUIRED OPEN_CONTINUE_URL
Purchase AWAITING_APPROVAL APPROVAL_REQUIRED APPROVE_IN_APP
Authorization REJECTED REJECTED RETURN_TO_CART
Authorization EXPIRED EXPIRED REQUEST_NEW_APPROVAL
Purchase AUTHORIZED PROCESSING WAIT_FOR_COMPLETION
Payment UNKNOWN PAYMENT_CONFIRMING DO_NOT_RETRY_PAYMENT
Purchase COMPLETION_UNKNOWN ORDER_CONFIRMING CHECK_STATUS_LATER
Checkout completed + Order COMPLETED VIEW_ORDER
확정 실패 FAILED 오류별 행동

17. 멱등성·일관성·장애 복구

17.1 분산 트랜잭션 전략

다음 시스템의 DB를 하나의 ACID 트랜잭션으로 묶을 수 없다.

Core DB
Pay DB
Merchant DB
PG

2PC를 도입하지 않고 다음 조합을 사용한다.

Local Transaction
+
Idempotency
+
Transactional Outbox
+
Inbox Deduplication
+
명시적 상태 머신
+
Reconciliation

17.2 MCP → Core 멱등성

구매 승인 요청에는 Agent가 생성한 idempotencyKey를 받는다.

UNIQUE(agent_connection_id, idempotency_key)

동작:

같은 Key + 같은 Request Hash → 기존 PurchaseRequest 반환 같은 Key + 다른 Request Hash → 409 IDEMPOTENCY_KEY_CONFLICT G n0->n1 n2->n3 n0 같은 Key + 같은 Request Hash n1 기존 PurchaseRequest 반환 n2 같은 Key + 다른 Request Hash n3 409 IDEMPOTENCY_KEY_CONFLICT

17.3 Core → Pay 멱등성

Idempotency-Key: authorization-{purchaseRequestId}

Pay:

UNIQUE(purchase_request_id)
UNIQUE(service_client_id, idempotency_key)

같은 요청은 같은 AuthorizationSession을 반환한다.

17.4 Core → Merchant UCP 멱등성

권장 Key:

Cart Create      cart-create-{commerceSessionId}
Cart Update      cart-update-{externalCartId}-{platformRevision}
Checkout Create  checkout-create-{purchaseDraftId}
Checkout Update  checkout-update-{checkoutId}-{evidenceRevision}
Checkout Complete checkout-complete-{purchaseRequestId}
Checkout Cancel  checkout-cancel-{checkoutId}

Complete Timeout 후 새 Key로 다시 호출하면 중복 결제·주문 가능성이 생기므로 같은 Key를 유지한다.

17.5 Merchant → Pay 멱등성

UNIQUE(merchant_id, idempotency_key)
UNIQUE(credential_token_id)

동작:

같은 Merchant + 같은 Key + 같은 Body → 기존 Payment 결과 반환 같은 Merchant + 같은 Key + 다른 Body → IDEMPOTENCY_KEY_CONFLICT 다른 Key + 이미 다른 논리적 요청에서 소비된 Credential → CREDENTIAL_ALREADY_CONSUMED G n0->n1 n2->n3 n4->n5 n0 같은 Merchant + 같은 Key + 같은 Body n1 기존 Payment 결과 반환 n2 같은 Merchant + 같은 Key + 다른 Body n3 IDEMPOTENCY_KEY_CONFLICT n4 다른 Key + 이미 다른 논리적 요청에서 소비된 Credential n5 CREDENTIAL_ALREADY_CONSUMED

17.6 승인 동시성

승인 API는 읽은 뒤 저장하는 방식이 아니라 조건부 UPDATE를 사용한다.

UPDATE authorization_sessions
SET status = 'APPROVED',
    approved_at = NOW(),
    version = version + 1
WHERE id = :authorizationId
  AND user_id = :userId
  AND status = 'PENDING'
  AND expires_at > NOW();

영향받은 행이 1개일 때만 Mandate 발급을 진행한다.

17.7 Cart Revision 충돌

UPDATE ucp_cart_projections
SET platform_revision = platform_revision + 1,
    updated_at = NOW()
WHERE id = :cartProjectionId
  AND platform_revision = :expectedRevision;

0건이면 Merchant Cart를 다시 조회하고 CART_VERSION_CONFLICT를 반환한다.

17.8 Checkout 변경 시 승인 무효화

다음 중 하나라도 바뀌면 Digest가 달라진다.

상품
옵션
수량
가격
할인
배송비
세금
배송지
통화
Merchant

Core가 새 Checkout 응답을 받았는데 기존 Evidence Digest와 다르면:

1. 기존 PurchaseRequest를 INVALIDATED 2. Pay AuthorizationSession invalidate 호출 3. 기존 Credential을 REVOKED 4. 새 Evidence 생성 5. 사용자의 새 승인 필요 G n0->n1 n1->n2 n2->n3 n3->n4 n0 1. 기존 PurchaseRequest를 INVALIDATED n1 2. Pay AuthorizationSession invalidate 호출 n2 3. 기존 Credential을 REVOKED n3 4. 새 Evidence 생성 n4 5. 사용자의 새 승인 필요

17.9 Core의 Complete 응답 유실

상황:

Core → Merchant Complete Merchant: 결제 성공 + Order 생성 Merchant → Core 응답 유실 G n0->n1 n1->n2 n2->n3 n3->n4 n0 Core n1 Merchant Complete n2 Merchant: 결제 성공 + Order 생성 n3 Merchant n4 Core 응답 유실

Core 처리:

PurchaseRequest = COMPLETION_UNKNOWN

금지:

새 PurchaseRequest 생성
새 승인 세션 생성
새 Credential 발급
새 Idempotency-Key로 Complete 호출

복구:

1. GET /checkout-sessions/{id} 2. completed면 Order 동기화 3. complete_in_progress면 제한적 Polling 4. 안전한 재전송이 필요하면 같은 Idempotency-Key 사용 5. Order ID를 받았으면 GET /orders/{id} G n0->n1 n1->n2 n2->n3 n3->n4 n0 1. GET /checkout-sessions/{id} n1 2. completed면 Order 동기화 n2 3. complete_in_progress면 제한적 Polling n3 4. 안전한 재전송이 필요하면 같은 Idempotency-Key 사용 n4 5. Order ID를 받았으면 GET /orders/{id}

17.10 Pay의 PG 응답 유실

상황:

Pay → PG charge PG: 실제 성공 PG → Pay 응답 유실 G n0->n1 n1->n2 n2->n3 n3->n4 n0 Pay n1 PG charge n2 PG: 실제 성공 n3 PG n4 Pay 응답 유실

Pay 처리:

Payment = UNKNOWN
Reconciliation Job 생성

복구:

PG status/query API → SUCCESS 또는 FAILED 확정 G n0->n1 n0 PG status/query API n1 SUCCESS 또는 FAILED 확정

결과 확인 전 charge를 재호출하지 않는다.

17.11 Outbox와 Inbox

Pay에서 상태 변경과 이벤트 기록은 같은 DB 트랜잭션으로 처리한다.

Payment SUCCESS 저장
Payment Receipt 저장
pay_outbox_events 저장
COMMIT

Core의 이벤트 수신:

core_inbox_events에 eventId 저장
중복이면 기존 처리 결과 반환
PurchaseRequest Projection 갱신
user_events 저장
COMMIT

이벤트 전달은 Exactly Once가 아니라 At-Least-Once를 전제로 하고 Consumer가 멱등성을 보장한다.

17.12 주요 실패 시나리오

상황 저장 상태 복구 행동 자동 재실행 여부
Merchant Profile Timeout Discovery 실패 Cache 또는 제한적 재시도 조회만 가능
Cart Update Timeout Cart 상태 불명 GET Cart 같은 Key 재전송 가능
Checkout Create Timeout 생성 여부 불명 같은 Key 재전송 또는 조회 가능한 Reference 확인 같은 Key만
가격 변경 새 Digest 승인 무효화, 재확인 자동 승인 금지
재고 부족 Checkout incomplete/오류 장바구니 수정 결제 금지
승인 Push 실패 Authorization PENDING 앱 승인 대기 목록 Push만 재시도
사용자 거부 REJECTED Cart 유지 자동 재승인 금지
승인 만료 EXPIRED Checkout 재조회 후 새 승인 기존 Artifact 사용 금지
Complete Timeout COMPLETION_UNKNOWN Checkout/Order 조회 새 Key 금지
Credential 재사용 기존 Payment 또는 거절 같은 요청은 기존 결과 새 결제 금지
PG Timeout Payment UNKNOWN PG Reconcile charge 재호출 금지
Order Webhook 중복 Inbox 중복 성공 응답 후 무시 처리 중복 금지
Order Webhook 유실 Projection 지연 Order Polling 조회만

18. 인증·인가·보안 설계

18.1 구간별 인증

통신 구간 권장 인증
Agent → MCP Agent OAuth Access Token + Scope
Mobile App → Core User Access Token
Mobile App → Pay User Access Token + 승인 Challenge
MCP → Core Service JWT 또는 mTLS
Core → Pay Service JWT 또는 mTLS
Core → Merchant UCP-Agent + API Key/OAuth/mTLS/HTTP Message Signature
Merchant → Pay Merchant Client Credential + Request Signature 또는 mTLS
Merchant → Core Webhook UCP HTTP Message Signature 검증

UCP는 API Key, OAuth 2.0, mTLS, HTTP Message Signatures를 지원하며, 인증 주체와 UCP-Agent가 주장하는 Profile의 일치를 검증하도록 요구한다.4

MVP 권장 조합:

Core → Merchant API Key + UCP-Agent Identity Binding Merchant Checkout 조건 ES256 Merchant Authorization Merchant → Core Webhook RFC 9421 HTTP Message Signature Core → Pay Service JWT Merchant → Pay OAuth Client Credentials + Request Signature G n0->n1 n1->n2 n4->n5 n5->n6 n7->n8 n8->n9 n10->n11 n11->n12 n0 Core n1 Merchant n2 API Key + UCP-Agent Identity Binding n3 Merchant Checkout 조건 ES256 Merchant Authorization n4 Merchant n5 Core Webhook n6 RFC 9421 HTTP Message Signature n7 Core n8 Pay n9 Service JWT n10 Merchant n11 Pay n12 OAuth Client Credentials + Request Signature

18.2 Agent Token Claims

{
  "sub": "user-123",
  "client_id": "external-agent-1",
  "client_type": "AGENT",
  "aud": ["mcp-server"],
  "scope": [
    "product:read",
    "cart:read",
    "cart:write",
    "checkout:prepare",
    "purchase:request",
    "purchase:status"
  ],
  "connection_id": "connection-123",
  "exp": 1788340000
}

Agent Token으로 Pay 승인 API를 호출하면 Gateway와 Pay 양쪽에서 거절한다.

18.3 Mobile Token Claims

{
  "sub": "user-123",
  "client_type": "MOBILE_APP",
  "aud": ["core-api", "pay-api"],
  "scope": [
    "address:manage",
    "payment-method:manage",
    "payment-approval:read",
    "payment-approval:approve",
    "payment-approval:reject"
  ],
  "exp": 1788340000
}

18.4 Merchant Registry와 SSRF 방어

Agent나 사용자가 전달한 URL을 Core가 그대로 호출하지 않는다.

Merchant 호출 전:

merchantId로 Registry 조회 → 저장된 profile_url 사용 → HTTPS 확인 → Userinfo 금지 → IP Literal 금지 → DNS Resolve 결과의 Private/Loopback/Link-local 차단 → Redirect 금지 → 허용 Origin과 Endpoint Origin 검증 → Response 크기·Timeout 제한 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n7->n8 n0 merchantId로 Registry 조회 n1 저장된 profile_url 사용 n2 HTTPS 확인 n3 Userinfo 금지 n4 IP Literal 금지 n5 DNS Resolve 결과의 Private/Loopback/Link-local 차단 n6 Redirect 금지 n7 허용 Origin과 Endpoint Origin 검증 n8 Response 크기·Timeout 제한

개발 환경의 Local Merchant Mock만 별도 Profile로 허용하고 운영 정책과 분리한다.

18.5 Key 관리

Merchant Private Key → Merchant/KMS에만 존재 → Checkout Authorization, Webhook 서명 Platform Mandate Private Key → Pay Service/KMS에만 존재 → Checkout Mandate-like, Payment Mandate-like 서명 Pay Receipt Private Key → Pay Service/KMS에만 존재 → Payment Receipt 서명 Public Keys → 각 UCP Profile의 keys[] G n0->n1 n1->n2 n3->n4 n4->n5 n6->n7 n7->n8 n9->n10 n0 Merchant Private Key n1 Merchant/KMS에만 존재 n2 Checkout Authorization, Webhook 서명 n3 Platform Mandate Private Key n4 Pay Service/KMS에만 존재 n5 Checkout Mandate-like, Payment Mandate-like 서명 n6 Pay Receipt Private Key n7 Pay Service/KMS에만 존재 n8 Payment Receipt 서명 n9 Public Keys n10 각 UCP Profile의 keys[]

운영 Key 정책:

kid 필수
활성 Key와 검증 전용 이전 Key 동시 공개
Private Key를 애플리케이션 설정 파일에 저장 금지
정기 Rotation
폐기 시점 전 발급 Artifact 검증 기간 고려

18.6 PIN

18.7 생체인증

서버가 생체 원본을 받지 않는다.

기기 OS 생체인증 → Android Keystore/Secure Enclave 개인키 사용 → Pay Challenge 서명 → 서버가 등록 공개키로 검증 G n0->n1 n1->n2 n2->n3 n0 기기 OS 생체인증 n1 Android Keystore/Secure Enclave 개인키 사용 n2 Pay Challenge 서명 n3 서버가 등록 공개키로 검증

Challenge 요구사항:

짧은 TTL
1회 사용
authorizationSessionId와 결합
userId·deviceId와 결합
재사용 시 거절

18.8 Payment Credential 보안

18.9 Trusted Surface 무결성

앱 표시 정보는 별도로 조합한 비신뢰 DTO가 아니라 검증된 Checkout Evidence에서 생성한다.

Merchant Signed Checkout → Core 검증 → Pay Evidence 검증 → Approval View → 사용자 승인 → 같은 Digest로 Mandate 발급 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n0 Merchant Signed Checkout n1 Core 검증 n2 Pay Evidence 검증 n3 Approval View n4 사용자 승인 n5 같은 Digest로 Mandate 발급

승인 화면과 Mandate의 Digest가 다르면 발급하지 않는다.

18.10 민감정보 로그 정책

로그 금지:

카드번호
CVC
PIN
생체 서명 전체
Payment Credential 원문
Mandate 원문 전체
Refresh Token 원문
상세 배송지 전체
PG 장기 Token

허용 가능한 식별 정보:

traceId
requestId
userId 또는 해시
merchantId
externalCheckoutId
purchaseRequestId
authorizationSessionId
paymentId
externalOrderId
credential jti 또는 Hash 일부
카드 brand/last4
상태 전이
오류 코드

18.11 위협과 대응

위협 대응
Agent가 금액을 바꿈 Merchant Checkout이 금액 원본, Digest Binding
Core와 Pay 사이 Payload 변조 Service TLS/JWT + Evidence Signature
Merchant 사칭 Registry, Profile Origin, UCP Identity Binding
Credential 탈취 Audience·Checkout·금액 제한, 짧은 TTL, 1회 논리적 소비
Credential Replay jti, Idempotency, 기존 Payment 반환 또는 거절
Checkout 바뀐 뒤 기존 승인 사용 Digest 변경 감지, Approval·Token 폐기
승인 버튼 중복 클릭 조건부 UPDATE + Unique Constraint
결제 중복 실행 Payment/Token Unique + PG Idempotency
Webhook 위조 HTTP Message Signature + Content Digest
Webhook 중복 Inbox Unique Key
SSRF Merchant Registry와 URL/DNS 검증
로그를 통한 유출 민감 필드 Redaction과 Body Logging 금지

19. 이벤트·알림·실시간 상태

19.1 이벤트 소유

Pay가 발행:

authorization.created
authorization.approved
authorization.rejected
authorization.expired
authorization.invalidated
payment.processing
payment.succeeded
payment.failed
payment.unknown
payment.reconciled

Core가 발행:

checkout.prepared
checkout.reconfirmation_required
purchase.approval_required
purchase.submitting
purchase.completed
purchase.failed
purchase.completion_unknown
order.updated

Merchant가 Webhook으로 전달:

order.created
order.updated
fulfillment.updated
adjustment.created

19.2 Push

승인 요청 Push는 Pay가 담당한다.

AuthorizationSession DB Commit
+
Pay Outbox Event
Push Worker

Push 실패와 관계없이 승인 요청은 PENDING으로 유지한다. 사용자는 앱의 승인 대기 목록에서 조회할 수 있다.

19.3 SSE

Core는 사용자에게 통합 상태를 제공하므로 SSE Endpoint를 소유한다.

GET /api/v1/events
Last-Event-ID: event-123

Core는 Pay 이벤트와 Merchant Order 이벤트를 수신해 user_events에 저장한 뒤 SSE로 보낸다.

user_events
- event_id
- user_id
- event_type
- resource_type
- resource_id
- payload
- created_at
- expires_at

SSE 연결이 끊겨도 Last-Event-ID 이후 이벤트를 재전송할 수 있어야 한다.


20. 오류 모델

공통 오류 응답:

{
  "errorCode": "...",
  "message": "...",
  "currentState": "...",
  "nextAction": "...",
  "retryable": false,
  "traceId": "..."
}

주요 오류 코드

오류 코드 의미 nextAction
AGENT_CONNECTION_REQUIRED Agent 계정 미연동 LINK_ACCOUNT
AGENT_SCOPE_DENIED Scope 부족 REAUTHORIZE_AGENT
MERCHANT_NOT_REGISTERED 허용되지 않은 Merchant CHOOSE_ANOTHER_MERCHANT
UCP_PROFILE_INVALID Profile 검증 실패 CONTACT_SUPPORT
UCP_VERSION_UNSUPPORTED 버전 불일치 CHOOSE_ANOTHER_MERCHANT
UCP_CAPABILITY_INCOMPATIBLE 필수 Capability 교집합 없음 CHOOSE_ANOTHER_MERCHANT
CART_VERSION_CONFLICT 오래된 Cart Projection REFETCH_CART
CHECKOUT_INCOMPLETE Checkout 정보 부족 UPDATE_CHECKOUT_INPUT
ADDRESS_REQUIRED 배송 필요 상품이나 배송지 없음 REGISTER_ADDRESS
PAYMENT_METHOD_REQUIRED 사용 가능한 결제수단 없음 REGISTER_PAYMENT_METHOD
MERCHANT_HANDOFF_REQUIRED API로 처리 불가 OPEN_CONTINUE_URL
CHECKOUT_PRICE_CHANGED 가격 변경 REVIEW_CHECKOUT
CHECKOUT_STOCK_INSUFFICIENT 재고 부족 RETURN_TO_CART
MERCHANT_AUTHORIZATION_INVALID Merchant Checkout 서명 실패 DO_NOT_PROCEED
APPROVAL_ALREADY_EXISTS 활성 승인 중복 요청 OPEN_EXISTING_APPROVAL
APPROVAL_EXPIRED 승인 만료 REQUEST_NEW_APPROVAL
APPROVAL_REJECTED 사용자가 거부 RETURN_TO_CART
APPROVAL_INVALIDATED Checkout 변경 REVIEW_CHECKOUT
MANDATE_SCOPE_MISMATCH Mandate와 Checkout 불일치 DO_NOT_PROCEED
PAYMENT_CREDENTIAL_EXPIRED Credential 만료 REQUEST_NEW_APPROVAL
CREDENTIAL_ALREADY_CONSUMED 다른 결제에서 사용됨 CHECK_EXISTING_PAYMENT
IDEMPOTENCY_KEY_CONFLICT 같은 Key에 다른 Body USE_NEW_KEY
PAYMENT_FAILED 확정 결제 실패 SELECT_ANOTHER_PAYMENT_METHOD
PAYMENT_UNKNOWN PG 결과 불명 WAIT_FOR_RECONCILIATION
COMPLETION_UNKNOWN Merchant Complete 결과 불명 CHECK_PURCHASE_STATUS
ORDER_NOT_YET_AVAILABLE Order 동기화 지연 RETRY_STATUS_QUERY

21. 저장소와 배포 구조

backend/
├── settings.gradle
├── build.gradle
├── gradle/
│   └── libs.versions.toml
│
├── apps/
│   ├── core-service/
│   ├── mcp-server/
│   └── pay-service/
│
├── mocks/
│   ├── ucp-merchant-mock/
│   └── pg-mock/
│
├── libs/
│   ├── ucp-contract/
│   ├── payment-handler-contract/
│   ├── internal-api-contract/
│   ├── event-contract/
│   ├── security-support/
│   ├── observability/
│   └── test-support/
│
├── infra/
│   ├── nginx/
│   ├── docker/
│   ├── monitoring/
│   ├── migrations/
│   └── ci/
│
└── docs/
    ├── adr/
    ├── openapi/
    ├── mcp/
    ├── ucp-profile/
    ├── payment-handler/
    ├── event-schema/
    ├── sequence/
    └── threat-model/

21.1 공유 라이브러리에 둘 수 있는 것

생성된 API Client/DTO
UCP Schema Validation 도구
공통 오류 Envelope
Service JWT 검증
HTTP Signature 유틸리티
JCS Canonicalization 유틸리티
Trace ID·Observability
테스트 Fixture

21.2 공유하면 안 되는 것

JPA Entity
Repository
Core Purchase Domain
Pay Payment Domain
Merchant Checkout Domain
상태 전이 로직
DB 접근 코드

21.3 Docker Compose

nginx
mcp-server
core-service
pay-service
ucp-merchant-mock
pg-mock
core-db
pay-db
merchant-db
redis
prometheus
grafana

21.4 Health Check

/actuator/health/liveness
/actuator/health/readiness

Readiness는 필요한 의존성 상태를 반영한다.

Core: Core DB, Redis 선택, Pay 연결 가능 여부는 Degraded 표현
Pay: Pay DB, PG 연결 상태
Merchant Mock: Merchant DB, Pay Handler 연결 상태
MCP: Core 연결 상태

22. 관측성과 감사

22.1 공통 추적 식별자

모든 서비스 로그에 가능한 범위에서 다음을 포함한다.

traceId
requestId
userId 또는 userHash
agentConnectionId
merchantId
externalCartId
externalCheckoutId
checkoutDigest
purchaseRequestId
authorizationSessionId
paymentId
externalOrderId
webhookId
idempotencyKeyHash

22.2 필수 메트릭

MCP

mcp_tool_calls_total{tool,status}
mcp_tool_latency_seconds{tool}
mcp_scope_denied_total

Core

ucp_profile_fetch_total{merchant,status}
ucp_request_latency_seconds{merchant,operation}
ucp_capability_negotiation_failed_total
checkout_status_total{status}
merchant_authorization_failed_total
purchase_request_total{status}
completion_unknown_total
order_webhook_lag_seconds

Pay

authorization_total{status,method}
authorization_failure_total{reason}
credential_issued_total
credential_replay_blocked_total
payment_total{status,merchant}
payment_unknown_total
payment_reconciliation_latency_seconds
pg_request_latency_seconds{operation}

22.3 감사 이벤트

다음 이벤트는 상태 전후와 Actor를 기록한다.

Agent 연결·해제
Merchant Profile 변경
Capability 협상 결과
Cart 변경
Checkout 생성·Update
Merchant Authorization 검증
PurchaseRequest 생성
승인·거부·만료·무효화
Mandate·Credential 발급·폐기·소비
Payment 상태 변경
Reconciliation
Order Webhook 처리
운영자 수동 상태 확정

감사 로그에는 Artifact 원문 대신 Hash와 Key ID를 저장한다.


23. 테스트 전략

23.1 테스트 계층

Unit Test
Contract Test
Integration Test
Concurrency Test
Security Test
End-to-End Test
Failure-Recovery Test

23.2 UCP Contract Test

23.3 Core 필수 테스트

등록되지 않은 Merchant 차단
악성 Profile URL 차단
Profile Cache 만료·갱신
Version 불일치
필수 Capability 미지원
Catalog 결과 정규화
상품 ID 충돌 방지
Cart Revision 충돌
Checkout incomplete 보완
requires_escalation 처리
Merchant 서명 정상·실패
Checkout Digest 재현성
가격 변경 후 승인 무효화
같은 승인 요청 멱등 처리
Complete 응답 유실 후 조회 복구
Order Webhook 중복 제거
Order Webhook 지연 후 Polling

23.4 Pay 필수 테스트

PIN 등록·변경·실패 잠금 생체 Challenge 재사용 차단 같은 Approval 동시 승인 만료 Approval 승인 차단 거부된 Approval 재승인 차단 Checkout Digest 불일치 차단 Mandate 서명 검증 Credential Audience 불일치 Credential Checkout Binding 불일치 Credential 금액·통화 불일치 Credential 만료 Credential Replay 같은 Merchant Idempotency 재시도 같은 Key에 다른 Body 충돌 PG 성공 PG 실패 PG Timeout Before Process PG Timeout After Process UNKNOWN → SUCCESS UNKNOWN → FAILED Payment Receipt 서명 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n0 PIN 등록·변경·실패 잠금 생체 Challenge 재사용 차단 같은 Approval 동시 승인 만료 Approval 승인 차단 거부된 Approval 재승인 차단 Checkout Digest 불일치 차단 Mandate 서명 검증 Credential Audience 불일치 Credential Checkout Binding 불일치 Credential 금액·통화 불일치 Credential 만료 Credential Replay 같은 Merchant Idempotency 재시도 같은 Key에 다른 Body 충돌 PG 성공 PG 실패 PG Timeout Before Process PG Timeout After Process n1 UNKNOWN n2 SUCCESS n3 UNKNOWN n4 FAILED n5 Payment Receipt 서명

23.5 Merchant Mock 필수 테스트

Profile Discovery
Payment Handler 광고
Custom AP2-like Capability 광고
Checkout 조건 서명
잘못된 Mandate 거절
현재 Checkout과 Mandate 조건 비교
Pay 성공 전 Order 생성 금지
Pay 성공 후 Order 한 번 생성
Complete 중복 호출에 기존 Order 반환
Order Webhook 서명·중복 전송

23.6 E2E 정상 시나리오

Agent 검색 → Cart 추가 → Checkout 준비 → 앱 승인 → Mandate/Credential 발급 → UCP Complete → Merchant가 Pay 호출 → PG 성공 → Merchant Order 생성 → Core Projection → Agent와 앱 완료 표시 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n7->n8 n8->n9 n9->n10 n0 Agent 검색 n1 Cart 추가 n2 Checkout 준비 n3 앱 승인 n4 Mandate/Credential 발급 n5 UCP Complete n6 Merchant가 Pay 호출 n7 PG 성공 n8 Merchant Order 생성 n9 Core Projection n10 Agent와 앱 완료 표시

23.7 E2E 실패 시나리오

시나리오 기대 결과
가격 변경 새 Checkout 표시, 기존 승인 사용 금지
재고 부족 Checkout 완료 금지, Cart 수정 안내
배송 불가 다른 배송지 선택 안내
Merchant 서명 변조 Pay 승인 요청 생성 금지
사용자 거부 Credential 미발급, Complete 미호출
승인 만료 기존 Mandate·Credential 사용 금지
Credential 금액 변조 Pay Processor가 거절
Credential 재사용 기존 결과 반환 또는 차단, 새 결제 없음
PG Timeout After Process Payment UNKNOWN 후 조회로 성공 확정
Complete 응답 유실 Core가 Merchant Checkout 조회로 Order 확인
Webhook 중복 Projection 한 번만 갱신
Agent가 승인 API 시도 403 Scope/Client Type 차단

23.8 완료 기준


24. MVP 범위와 비범위

이 프로젝트는 UCP와 AP2의 모든 기능을 구현하는 범용 상용 플랫폼이 아니라, Agent → UCP Checkout → 모바일 사용자 승인 → Merchant 주도 결제 → Order 생성이라는 핵심 수직 흐름을 안전하게 증명하는 MVP다.

24.1 MVP 구현 범위

사용자와 Agent 연동

Commerce

결제

사용자 경험

24.2 MVP 제약

항목 MVP 결정
UCP 버전 2026-08-25 고정
AP2 참고 버전 v0.2
Transport REST만 구현
Merchant 수 시연 기준 1곳, 구조상 복수 등록 가능
Cart Merchant별 분리, 한 PurchaseRequest는 단일 Merchant
통화 KRW 단일 통화
결제수단 카드 Token 1종
승인 방식 Human Present만
결제 횟수 Checkout당 1회
주문 Merchant Order 1건
배송 Merchant가 요구할 때 실물 상품 단일 배송지, 배송 불필요 상품은 생략
메시지 브로커 필수 아님. Transactional Outbox + 내부 HTTP 가능
암호 형식 ES256/JWS 기반 AP2-like Artifact를 기본안으로 사용

24.3 명시적 비범위

다음 기능은 설계 확장점을 남기되 MVP 구현 대상에서 제외한다.

Human Not Present 자율 구매
Open Checkout Mandate
Open Payment Mandate
Agent Key 위임과 Mandate Chain
공식 SD-JWT+KB 상호운용 구현
공식 AP2 적합성 주장
다중 Merchant 합산 Cart
분할 결제와 부분 성공
다중 통화와 환율
할부
3DS 정식 연동
실제 카드번호·CVC 저장
부분취소·부분환불
반품·교환
판매자 정산
Chargeback 자동화
구독·반복 결제
프로모션 엔진 전체 구현
택배사 실연동

24.4 확장 순서

MVP 이후 기능은 다음 순서로 확장하는 것이 안전하다.

  1. 실제 PG Tokenization과 결제 조회 연동
  2. 복수 UCP Merchant 등록과 Capability별 호환성 시험
  3. 취소·환불 및 Order Adjustment
  4. 공식 AP2 SD-JWT+KB Artifact 구현
  5. 다중 결제수단과 3DS
  6. Human Not Present와 Open Mandate 검토

Human Not Present는 단순히 승인 화면을 생략하는 기능이 아니다. Agent 키 위임, Open/Closed Mandate Chain, 재사용 제약, Receipt 기반 소비 관리가 추가되므로 별도 보안 설계로 다뤄야 한다.13


25. 개발 인력과 Task 분배

이 절은 백엔드 개발자 4명을 기준으로 한다. 실제 개발 기간은 2026년 9월 7일 월요일부터 2026년 9월 25일 금요일까지다. 모바일 앱과 Agent UI는 별도 클라이언트 담당자가 병렬로 개발하며, 이 절에서는 백엔드 저장소와 외부 연동에 대한 소유권만 정의한다.

서비스 이름만 기준으로 Core 1명 / MCP 1명 / Pay 1명 / 기타 1명처럼 나누지 않는다. 대신 외부 시스템 방향의 수직 흐름과 상태 머신을 기준으로 네 명의 책임을 분리한다.

25.1 권장 역할 구성

담당 역할 주 책임
A UCP / Commerce Core Backend Core의 UCP Client·구매 오케스트레이션·Merchant Mock Commerce
B MCP / Agent Platform Backend MCP Server·Agent OAuth·사용자/배송지·실시간 통합 상태
C AP2-like Authorization / Credential Backend 결제수단·PIN/생체·승인·Mandate·Payment Credential
D Payment Processor / Reliability Backend Merchant 결제 API·PG Adapter·Reconciliation·멱등성·인프라/관측성

각 상태 머신에는 한 명의 명확한 Owner를 둔다.

UCP Checkout·PurchaseRequest 상태 머신
A
Agent OAuth·MCP Tool 계약
B
Authorization·Mandate·Credential 상태
C
Payment·Reconciliation 상태
D

A와 B는 Core ↔ 외부 MerchantAgent ↔ Platform 방향을 각각 소유한다. C와 D는 하나의 Pay Service를 함께 개발하지만, C는 사용자 승인과 권한 증거를, D는 Merchant 결제 요청과 실제 결제 결과를 소유한다.

25.2 A — UCP / Commerce Core Backend

A는 Core와 외부 UCP Merchant 사이의 전체 Commerce 수직 흐름을 소유한다.

코드 소유 영역

apps/core-service/
├── platformprofile/**
├── merchantregistry/**
├── ucp/discovery/**
├── ucp/negotiation/**
├── ucp/catalog/**
├── ucp/cart/**
├── ucp/checkout/**
├── ucp/order/**
├── ucp/security/**
├── purchase/**
└── integration/pay/**

mocks/ucp-merchant-mock/
├── profile/**
├── catalog/**
├── inventory/**
├── cart/**
├── checkout/**
├── order/**
└── webhook/**

Merchant Mock의 paymenthandler 모듈과 Pay 호출 부분은 D가 공동 소유한다.

주요 Task

ID Task 완료 산출물
UCP-01 UCP 버전·Profile 계약 고정 Platform/Business Profile JSON Schema
UCP-02 Merchant Registry 허용 Origin·Profile URI·인증정보 관리
UCP-03 Business Profile Discovery HTTPS·Redirect·Size·Cache 검증 포함
UCP-04 Version/Capability Negotiation 협상 결과 Snapshot
UCP-05 Catalog Client Search·Lookup·Projection Mapper
UCP-06 Cart Client Create·Get·Update·Cancel
UCP-07 Cart Revision 충돌 최신 Projection 재조회
UCP-08 Checkout Client Create·Get·Update·Complete·Cancel
UCP-09 Merchant Authorization 검증 JCS/Digest/JWS 검증
UCP-10 Verified Checkout Evidence 불변 Evidence 저장
UCP-11 PurchaseRequest 상태 머신 승인·제출·복구 상태
UCP-12 Pay Authorization 연동 Core→Pay 내부 계약
UCP-13 Complete 오케스트레이션 Artifact 조립·멱등키·재조회
UCP-14 Order Projection Order 조회·Webhook·중복 제거
UCP-15 Merchant Mock Commerce 정상·가격변경·재고부족·응답유실 시나리오
UCP-16 Contract/E2E 테스트 Core↔Merchant 전 흐름

A가 결정권을 갖는 영역

25.3 B — MCP / Agent Platform Backend

B는 외부 Agent가 우리 Platform을 안전하게 사용하는 전체 경로를 소유한다.

코드 소유 영역

apps/mcp-server/**

apps/core-service/
├── identity/**
├── agentauth/**
├── address/**
├── realtime/**
├── notification/**
└── publicapi/**

주요 Task

ID Task 완료 산출물
MCP-01 MCP Transport 외부 /mcp 엔드포인트
MCP-02 Tool Schema 입력·출력 JSON Schema와 설명
MCP-03 Agent Token 검증 Audience·Client Type·Scope 검사
MCP-04 Agent OAuth authorize·token·refresh·revoke
MCP-05 Agent Connection 목록·권한·연결 해제
MCP-06 search_products Core Catalog 호출과 결과 정규화
MCP-07 get_product 상품 상세 조회
MCP-08 Cart Tool 4종 조회·추가·수정·삭제
MCP-09 prepare_checkout 부족 정보와 다음 행동 반환
MCP-10 request_purchase_approval 멱등키·구매 요청 생성
MCP-11 get_purchase_status 통합 상태 반환
MCP-12 User/Address API 로그인·배송지 CRUD
MCP-13 Push/SSE 승인·결제·주문 상태 전달
MCP-14 Error Mapping currentState·nextAction 포함
MCP-15 Agent E2E Agent→MCP→Core 시나리오

B가 결정권을 갖는 영역

25.4 C — AP2-like Authorization / Credential Backend

C는 사용자에게 최종 Checkout을 보여주고, 승인 증거와 일회성 결제 Credential을 발급하는 수직 흐름을 소유한다.

코드 소유 영역

apps/pay-service/
├── paymentmethod/**
├── credential/**
├── authorization/**
├── trustedsurface/**
├── mandate/**
└── token/**

주요 Task

ID Task 완료 산출물
AUTH-01 결제수단 Token Mock·마스킹·기본 카드
AUTH-02 PIN KDF 저장·실패 제한·잠금
AUTH-03 생체 Challenge 공개키 등록·서명 검증·Replay 방지
AUTH-04 AuthorizationSession 생성·조회·승인·거부·만료·무효화
AUTH-05 Trusted Surface View 검증 Checkout의 불변 표시 모델
AUTH-06 Checkout Evidence 재검증 Digest·Merchant Authorization·만료 확인
AUTH-07 Checkout Mandate-like Claims·서명·검증
AUTH-08 Payment Mandate-like Claims·서명·검증
AUTH-09 Payment Credential Audience/Binding/금액 제한·짧은 TTL
AUTH-10 Artifact 조회 API Core가 Mandate/Credential을 안전하게 취득
AUTH-11 승인 동시성 조건부 상태 전이·중복 승인 차단
AUTH-12 승인 보안 테스트 만료·변조·PIN 잠금·생체 Replay

C가 결정권을 갖는 영역

25.5 D — Payment Processor / Reliability Backend

D는 외부 Merchant가 결제 Credential을 제출한 시점부터 PG 결과 확정과 장애 복구까지의 수직 흐름을 소유한다. 공통 실행 환경과 관측성도 함께 책임진다.

코드 소유 영역

apps/pay-service/
├── merchant/**
├── payment/**
├── processor/**
├── reconciliation/**
├── receipt/**
├── idempotency/**
├── outbox/**
├── inbox/**
└── audit/**

mocks/pg-mock/**

mocks/ucp-merchant-mock/
└── paymenthandler/**

infra/
├── nginx/**
├── docker-compose.yml
├── ci/**
└── monitoring/**

주요 Task

ID Task 완료 산출물
PROC-01 Merchant Account Merchant Client Credential·상태·Handler 설정
PROC-02 Merchant 인증 Client Token·요청 서명·Audience 검증
PROC-03 Processor API Merchant→Pay 결제·조회 API
PROC-04 Credential 소비 검증 1회 사용·금액·통화·Checkout Binding
PROC-05 Payment 상태 머신 READY→PROCESSING→SUCCESS/FAILED/UNKNOWN
PROC-06 PG Adapter 성공·실패·Timeout·상태 조회
PROC-07 PG Mock TIMEOUT_BEFORE_PROCESS, TIMEOUT_AFTER_PROCESS
PROC-08 Reconciliation UNKNOWN 지수 Backoff·조회·운영 큐
PROC-09 멱등성 Merchant+Key+Request Hash·기존 결과 반환
PROC-10 Receipt/Audit Payment Receipt-like·상태 변경 증거
PROC-11 Outbox/Inbox 이벤트 재전송·중복 소비 방지
PROC-12 Merchant Mock Payment Handler Credential로 Pay를 호출하는 완료 흐름
PROC-13 Nginx·Docker Compose·CI 모든 서비스의 공통 실행·배포 기반
PROC-14 Metrics·Trace MCP/Core/Pay/Merchant/PG 연계 관측성
PROC-15 결제 신뢰성 테스트 Replay·중복·Timeout·응답 유실·대사

D가 결정권을 갖는 영역

25.6 공동 소유 계약

다음 파일은 한 담당자의 구현 세부사항이 아니라 서비스 간 계약이므로 변경 전에 관련 담당자의 리뷰를 받아야 한다.

libs/ucp-contract/**
libs/pay-handler-contract/**
libs/event-contract/**
docs/openapi/**
docs/mcp/**
docs/ucp-profile/**
docs/adr/**

공동 계약 Owner는 다음처럼 둔다.

계약 작성 Owner 필수 Reviewer
MCP Tool ↔ Core API B A
Core ↔ UCP Merchant Commerce A B, D
Core ↔ Pay Authorization A C
Authorization ↔ Processor 내부 계약 C D
Merchant ↔ Pay Processor D A, C
Mobile ↔ Pay Approval C B, D
상태 이벤트 Schema B A, C, D
보안 Claims와 Key 정책 C A, D
멱등성·Reconciliation 정책 D A, C
Nginx·Docker Compose·CI D A, B, C

25.7 A와 B의 협업 경계

Core와 MCP는 단순히 Core 1명 / MCP 1명으로 분리하지 않는다. MCP는 Adapter 비중이 크고 Core는 UCP·Checkout·복구 책임이 크므로, 두 사람은 외부 방향을 기준으로 역할을 나눈다.

A — UCP / Commerce Core
외부 Merchant 방향의 수직 흐름 소유

B — MCP / Agent Platform
외부 Agent·사용자 방향의 수직 흐름 소유

둘 사이 경계는 다음 한 줄로 고정한다.

B는 "Agent가 무엇을 요청했는가"를 책임지고,
A는 "외부 Merchant와 그 요청을 어떻게 안전하게 완료하는가"를 책임진다.

25.8 C와 D의 협업 경계

C와 D는 같은 Pay Service를 수정하므로 도메인 경계를 코드 소유권으로 강제한다.

C — Authorization / Credential
사용자가 정확히 무엇을 승인했으며 어떤 권한 증거를 발급할 수 있는가

D — Processor / Reliability
승인된 Credential로 결제를 한 번만 실행하고 결과를 어떻게 확정하는가

둘 사이의 핵심 계약은 PaymentCredentialVerificationResult다.

C가 발급 규칙과 Claims를 소유 ↓ D가 Merchant 요청에서 Token을 검증·소비 ↓ 검증 성공 후에만 Payment 상태 머신과 PG 호출 시작 G n0->n1 n1->n2 n0 C가 발급 규칙과 Claims를 소유 n1 D가 Merchant 요청에서 Token을 검증·소비 n2 검증 성공 후에만 Payment 상태 머신과 PG 호출 시작

공통 파일이나 패키지 루트를 직접 공유하기보다, 작은 계약 인터페이스와 Contract Test로 연결한다.


26. 2026년 9월 7일~25일 구현 계획

실제 개발 기간은 2026년 9월 7일 월요일부터 2026년 9월 25일 금요일까지이며, 세 개의 주간 단위로 운영한다. 목표는 서비스별 기능 목록을 따로 완성하는 것이 아니라 매주 실행 가능한 수직 E2E 흐름을 확보하는 것이다.

26.1 기간별 통합 목표

기간 통합 목표 완료 조건
1주차 — 9월 7일~11일 계약 고정과 결제 전 UCP 흐름 Agent 검색→Cart→Checkout ready_for_complete
2주차 — 9월 14일~18일 사용자 승인부터 정상 결제·Order까지 앱 승인→Mandate/Credential→Merchant→Pay→PG→Order
3주차 — 9월 21일~25일 장애 복구·보안·관측성·시연 안정화 Timeout·Replay·변조·중복 시나리오와 전체 회귀 테스트 통과

26.2 1주차 — 9월 7일~11일: 계약 고정과 UCP Commerce 수직 흐름

공동

A

B

C

D

1주차 통합 완료 조건

Agent → Nginx → MCP Server → Core Service → Merchant Profile Discovery → Catalog Search → Merchant Cart → Checkout ready_for_complete G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n0 Agent n1 Nginx n2 MCP Server n3 Core Service n4 Merchant Profile Discovery n5 Catalog Search n6 Merchant Cart n7 Checkout ready_for_complete

추가로 Core가 Pay에 승인 세션을 생성하는 Stub 호출까지 성공해야 한다.

26.3 2주차 — 9월 14일~18일: 승인, Artifact, Complete, 정상 결제

A

B

C

D

클라이언트 연동 전제

2주차 통합 완료 조건

Checkout ready_for_complete → Core가 Pay 승인 요청 → 모바일 앱에서 동일 Checkout 표시 → 사용자 PIN 또는 생체 승인 → Mandate와 Credential 발급 → Core가 Merchant에 UCP Complete → Merchant가 Pay Processor 호출 → Pay가 PG 성공 처리 → Merchant Order 생성 → Core Order Projection → Agent와 앱에 완료 표시 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n7->n8 n8->n9 n9->n10 n0 Checkout ready_for_complete n1 Core가 Pay 승인 요청 n2 모바일 앱에서 동일 Checkout 표시 n3 사용자 PIN 또는 생체 승인 n4 Mandate와 Credential 발급 n5 Core가 Merchant에 UCP Complete n6 Merchant가 Pay Processor 호출 n7 Pay가 PG 성공 처리 n8 Merchant Order 생성 n9 Core Order Projection n10 Agent와 앱에 완료 표시

9월 18일까지 위 정상 흐름을 실제 서비스 프로세스 사이에서 한 번 이상 통과시킨다.

26.4 3주차 — 9월 21일~25일: 장애 복구, 보안, 관측성, 시연 안정화

A

B

C

D

날짜별 마감점

날짜 마감점
9월 21일 정상 E2E 회귀 자동화, 실패 시나리오별 재현 스위치 확정
9월 22일 Payment UNKNOWN과 Complete 응답 유실 복구 통과
9월 23일 Replay·Digest·Audience·권한 우회 차단 통과
9월 24일 전체 회귀 테스트, 관측성, 문서·시연 환경 동결
9월 25일 최종 버그 수정, 배포 검증, 시연 시나리오 확정

3주차 통합 완료 조건

26.5 일정 운영 원칙


27. 구현 전 확정해야 할 의사결정

아래 값은 프로젝트 요구사항이나 기존 API 초안만으로 확정되지 않은 부분이다. 구현 전에 팀이 결정해야 하며, 우측 값은 본 설계의 권장 기본값이다.

ID 항목 권장 기본값 영향
D-01 사용자 로그인 Google OAuth 단일 방식 앱 Auth API·사용자 모델
D-02 Agent 연동 OAuth 2.0 Authorization Code + PKCE Token·Connection 수명주기
D-03 Approval 유효시간 5분 앱 UX·Credential 만료
D-04 Checkout Evidence 유효시간 Merchant Checkout 만료와 동일 승인 재사용 가능 여부
D-05 UCP 버전 2026-08-25 Profile·Schema·Contract Test
D-06 Payment Handler 이름 팀 통제 도메인의 역도메인 외부 Profile·Credential Type
D-07 AP2-like Capability Custom Namespace 공식 호환 오인 방지
D-08 Artifact 서명 ES256 Key 관리·검증 라이브러리
D-09 Canonicalization JCS Digest 일관성
D-10 Core→Merchant 인증 API Key + UCP-Agent, 가능하면 요청 서명 Merchant 인증·SSRF 방어
D-11 Merchant→Pay 인증 Client Credential + 요청 서명 Processor 보안
D-12 Core→Pay 인증 Service JWT, Audience 고정 내부 API 보호
D-13 Cart 병행 정책 Merchant별 Cart, 한 PurchaseRequest는 단일 Merchant 데이터 모델·UX
D-14 가격 변경 정책 기존 승인 무효화 후 새 Checkout 재확인 결제 무결성
D-15 재고 부족 정책 자동 제거하지 않고 Agent에 선택 요청 사용자 통제
D-16 결제 거부 후 Cart Merchant Cart 유지 재구매 UX
D-17 결제 실패 후 Approval 소비 처리, 새 승인 필요 재시도 안전성
D-18 PG Timeout 조회 주기 지수 Backoff + 제한 횟수 + 운영 큐 Reconciliation
D-19 Complete Unknown 조회 Checkout GET 우선, 같은 Key 재전송은 최후 수단 중복 Order 방지
D-20 Webhook 인증 ES256 HTTP Payload Signature Order 동기화
D-21 Event 보존 최소 24시간 또는 프로젝트 시연 기간 SSE 재연결
D-22 민감 로그 보존 원문 금지, 감사 메타데이터만 보안·운영
D-23 취소·환불 MVP 제외 Order/Payment 상태 확장
D-24 실제 PG 초기 PG Mock, 이후 테스트 PG Adapter 일정·보안 범위

27.1 Namespace 예시를 그대로 사용하면 안 되는 이유

문서의 다음 값은 예시다.

com.example.pay
com.example.payment.ap2_like
https://platform.example/.well-known/ucp
https://merchant.example/.well-known/ucp

UCP의 역도메인 이름은 Schema나 Profile을 제공하는 주체가 실제로 통제하는 Host와 연결돼야 한다. 구현 전 실제 배포 도메인을 기준으로 교체한다.4

27.2 공식 AP2 Extension으로 전환하는 조건

다음 조건을 모두 만족할 때만 공식 dev.ucp.common.payment.ap2_mandate Capability 사용을 검토한다.

이 조건이 충족되지 않으면 Custom AP2-like Extension을 유지한다.


28. 기존 요구사항 및 API 초안과의 관계

초기 서비스 요구사항과 API 초안은 우리 서버가 상품·Cart·Order를 직접 소유하는 자체 Commerce Backend 형태를 일부 전제했다. 이번 설계는 외부 UCP Merchant가 Commerce 원본을 소유하는 Platform 모델로 재해석한다.

28.1 유지되는 요구사항

다음 사용자 가치와 안전 요구사항은 그대로 유지한다.

28.2 의미가 변경되는 데이터

초기 개념 재설계된 의미
Product Merchant 상품을 가리키는 ProductReference와 짧은 Cache
Cart Merchant Cart의 Platform Projection
Cart Version Merchant Revision과 Platform Projection Revision
OrderValidation UCP Checkout Create/Update와 Business 검증 결과
OrderSnapshot VerifiedCheckoutEvidence
PaymentApproval Pay의 AuthorizationSession UI Workflow
Payment Pay Processor가 소유하는 실제 결제 상태
Order Merchant Order의 OrderProjection
Shipping Merchant Order/Fulfillment Projection

28.3 의미가 변경되는 API

초기 API/Tool 재설계 이유
prepare_order prepare_checkout 이 시점에는 아직 Merchant Order가 없음
request_payment request_purchase_approval 즉시 결제가 아니라 앱 승인을 요청
get_payment_request_status get_purchase_status Checkout·승인·결제·Order를 종합
POST /api/v1/orders/validations POST /internal/v1/checkouts/prepare 또는 Public Wrapper Merchant Checkout이 검증 주체
POST /internal/v1/payments POST /merchant/v1/payments Core가 아니라 Merchant가 Processor 호출
Core의 Order 생성 Merchant Order Projection 저장 외부 Merchant가 Order Source of Truth
Core Shopping Adapter UCP Gateway/Client 선택적 Adapter가 아니라 핵심 경로

호환성 때문에 기존 MCP Tool 이름을 유지해야 한다면 Alias를 둘 수 있다.

prepare_order
  → 내부적으로 prepare_checkout 실행

request_payment
  → 내부적으로 request_purchase_approval 실행

get_payment_request_status
  → 내부적으로 get_purchase_status 실행

다만 Tool 설명에는 실제 의미를 명확히 적어 Agent가 request_payment 호출 직후 결제가 끝난 것으로 오해하지 않게 한다.

28.4 기존 User Flow 보정

초기 흐름의 다음 구간은:

결제 요청 MCP → 앱 알림 → 카드 선택 → PIN/지문 → 결제 G n0->n1 n1->n2 n2->n3 n3->n4 n0 결제 요청 MCP n1 앱 알림 n2 카드 선택 n3 PIN/지문 n4 결제

재설계 후 다음처럼 상세화된다.

MCP 구매 승인 요청 → Core가 Merchant Checkout ready_for_complete 확인 → Pay AuthorizationSession 생성 → 앱 알림 → Checkout/금액/배송지/결제수단 확인 → PIN/생체인증 → Mandate와 Payment Credential 발급 → Core가 Merchant에 UCP Complete → Merchant가 Pay Processor 호출 → Pay가 PG 호출 → Merchant가 Order 생성 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n7->n8 n8->n9 n9->n10 n0 MCP 구매 승인 요청 n1 Core가 Merchant Checkout ready_for_complete 확인 n2 Pay AuthorizationSession 생성 n3 앱 알림 n4 Checkout/금액/배송지/결제수단 확인 n5 PIN/생체인증 n6 Mandate와 Payment Credential 발급 n7 Core가 Merchant에 UCP Complete n8 Merchant가 Pay Processor 호출 n9 Pay가 PG 호출 n10 Merchant가 Order 생성

사용자 관점의 화면 수는 크게 늘지 않지만, Backend에서는 승인과 결제 사이에 무결성 검증과 Merchant 주도 Complete가 추가된다.


29. 요구사항 추적표

요구사항 영역 설계 반영 위치 핵심 검증
사용자 로그인·자동 로그인 identity, Public Core API Token 만료·회전·폐기
Agent 연동 agentauth, OAuth API Scope·연결 해제·Refresh 폐기
자연어 검색 MCP Tool, UCP Catalog Merchant 상품 Reference 유지
Cart 동기화 UCP Cart Projection, Revision 오래된 Revision 충돌
가격·재고 재검증 UCP Checkout ready_for_complete 전에는 승인 금지
승인 대상 고정 Verified Checkout Evidence Merchant 서명·Digest
앱 승인 Pay AuthorizationSession PENDING에서 한 번만 전이
PIN·생체 Pay Credential 실패 제한·Challenge Replay 차단
Agent 승인 금지 MCP Scope, Pay Client Type 승인 API 403
단일 결제 요청 PurchaseRequest·Credential Unique 같은 Checkout 중복 차단
결제 멱등성 Merchant→Pay Idempotency 같은 Key 동일 결과
결과 불명 복구 Payment UNKNOWN PG 조회 우선
Complete 결과 불명 COMPLETION_UNKNOWN Merchant Checkout 조회 우선
민감정보 비노출 서비스 경계·로그 정책 Core/MCP에 카드 원문 없음
Push 실패 대체 Approval 목록·SSE 승인 세션은 계속 유효
앱·Agent 상태 일치 Core 통합 상태 동일 PurchaseRequest 조회
주문·배송 이력 Order Projection Merchant Order 조회/Webhook
감사 가능성 Evidence·Audit·Trace ID로 전 구간 추적

30. Definition of Done

프로젝트는 단순히 API가 존재하는 것으로 완료되지 않는다. 다음 조건을 모두 만족해야 구현 완료로 본다.

30.1 기능 완료

30.2 보안 완료

30.3 신뢰성 완료

30.4 관측성 완료

다음 식별자로 전체 경로를 추적할 수 있다.

traceId
requestId
userId
agentConnectionId
merchantId
externalCartId
businessCheckoutId
purchaseRequestId
authorizationSessionId
checkoutMandateId
paymentMandateId
paymentId
externalOrderId
webhookId

30.5 문서 완료


31. 시연 시나리오

31.1 정상 구매

사용자가 Agent에게 말한다.

10만 원 이하 러닝화를 찾아서 270 사이즈 하나 장바구니에 담아줘.

시연 흐름:

1. Agent가 search_products 호출 2. MCP가 Core에 검색 요청 3. Core가 Merchant Catalog Search 호출 4. Agent가 상품 목록 표시 5. 사용자가 상품 선택 6. Agent가 add_cart_item 호출 7. Core가 Merchant Cart Update 8. 사용자가 "이걸로 살게"라고 요청 9. Agent가 prepare_checkout 호출 10. Core가 배송지를 넣고 Checkout을 ready_for_complete로 만듦 11. Merchant의 최종 금액과 서명을 Core가 검증 12. Agent가 request_purchase_approval 호출 13. 사용자 앱에 승인 알림 14. 앱에서 상품·배송지·87,000원·VISA ****1234 표시 15. 사용자가 생체인증 승인 16. Pay가 Mandate와 Payment Credential 발급 17. Core가 Merchant에 Complete Checkout 18. Merchant가 Pay Processor에 87,000원 결제 요청 19. Pay가 PG Mock 호출 20. PG 성공 21. Merchant가 Order 생성 22. Core가 Order Projection 저장 23. Agent와 앱에 같은 주문번호·금액 표시 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n7->n8 n8->n9 n9->n10 n10->n11 n11->n12 n12->n13 n13->n14 n14->n15 n15->n16 n16->n17 n17->n18 n18->n19 n19->n20 n20->n21 n21->n22 n0 1. Agent가 search_products 호출 n1 2. MCP가 Core에 검색 요청 n2 3. Core가 Merchant Catalog Search 호출 n3 4. Agent가 상품 목록 표시 n4 5. 사용자가 상품 선택 n5 6. Agent가 add_cart_item 호출 n6 7. Core가 Merchant Cart Update n7 8. 사용자가 "이걸로 살게"라고 요청 n8 9. Agent가 prepare_checkout 호출 n9 10. Core가 배송지를 넣고 Checkout을 ready_for_complete로 만듦 n10 11. Merchant의 최종 금액과 서명을 Core가 검증 n11 12. Agent가 request_purchase_approval 호출 n12 13. 사용자 앱에 승인 알림 n13 14. 앱에서 상품·배송지·87,000원·VISA ****1234 표시 n14 15. 사용자가 생체인증 승인 n15 16. Pay가 Mandate와 Payment Credential 발급 n16 17. Core가 Merchant에 Complete Checkout n17 18. Merchant가 Pay Processor에 87,000원 결제 요청 n18 19. Pay가 PG Mock 호출 n19 20. PG 성공 n20 21. Merchant가 Order 생성 n21 22. Core가 Order Projection 저장 n22 23. Agent와 앱에 같은 주문번호·금액 표시

31.2 결제 결과 불명

1. PG Mock가 결제를 처리한 뒤 응답을 유실 2. Pay는 Payment를 UNKNOWN으로 저장 3. Merchant Complete는 complete_in_progress 또는 처리 중 상태 유지 4. Pay는 결제를 다시 실행하지 않고 PG 조회 5. PG 조회 결과 SUCCESS 6. Pay가 Payment SUCCESS로 확정 7. Merchant가 Order 생성 8. Core가 Checkout/Order를 재조회해 완료 상태 반영 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n0 1. PG Mock가 결제를 처리한 뒤 응답을 유실 n1 2. Pay는 Payment를 UNKNOWN으로 저장 n2 3. Merchant Complete는 complete_in_progress 또는 처리 중 상태 유지 n3 4. Pay는 결제를 다시 실행하지 않고 PG 조회 n4 5. PG 조회 결과 SUCCESS n5 6. Pay가 Payment SUCCESS로 확정 n6 7. Merchant가 Order 생성 n7 8. Core가 Checkout/Order를 재조회해 완료 상태 반영

시연 중 반드시 강조할 점:

"Timeout이 났지만 다시 결제하지 않았고,
같은 Payment와 Checkout을 조회해 성공을 확정했다."

31.3 Checkout 변조 차단

1. Merchant가 87,000원 Checkout을 서명 2. Core가 Digest를 검증하고 앱 승인 요청 생성 3. 테스트 코드가 Complete 직전 금액을 870,000원으로 변경 4. Merchant 또는 Pay가 Digest/금액 불일치를 발견 5. 결제 요청 차단 6. Audit에 CHECKOUT_BINDING_MISMATCH 기록 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n0 1. Merchant가 87,000원 Checkout을 서명 n1 2. Core가 Digest를 검증하고 앱 승인 요청 생성 n2 3. 테스트 코드가 Complete 직전 금액을 870,000원으로 변경 n3 4. Merchant 또는 Pay가 Digest/금액 불일치를 발견 n4 5. 결제 요청 차단 n5 6. Audit에 CHECKOUT_BINDING_MISMATCH 기록

31.4 Credential Replay 차단

1. 정상 결제에 Credential 사용 2. 같은 Credential을 다른 Idempotency-Key로 재제출 3. Pay가 token status=CONSUMED 확인 4. CREDENTIAL_ALREADY_CONSUMED 반환 5. PG 호출 횟수가 증가하지 않음 G n0->n1 n1->n2 n2->n3 n3->n4 n0 1. 정상 결제에 Credential 사용 n1 2. 같은 Credential을 다른 Idempotency-Key로 재제출 n2 3. Pay가 token status=CONSUMED 확인 n3 4. CREDENTIAL_ALREADY_CONSUMED 반환 n4 5. PG 호출 횟수가 증가하지 않음

32. 최종 아키텍처 요약

32.1 각 구성요소를 한 문장으로 정의

Agent
=
사용자와 대화하고 MCP Tool을 선택하는 외부 비서
MCP Server
=
Agent 요청을 인증하고 Core의 결정론적 API로 전달하는 접수창구
Core Service
=
외부 Merchant와 UCP로 Catalog·Cart·Checkout·Order를 진행하는 Platform Orchestrator
Pay Service
=
모바일 앱에서 사용자 동의를 받고 Checkout에 제한된 Credential을 발급하며 Merchant의 결제를 처리하는 AP2-like 결제 계층
UCP Merchant
=
상품·가격·재고·Checkout·Order를 소유하는 판매자이자 Merchant of Record
PG
=
실제 결제 승인 결과를 만드는 하위 결제 시스템

32.2 누가 무엇의 최종 상태를 소유하는가

상품·가격·재고
Merchant
Cart
Merchant
Checkout
Merchant
사용자/Agent 연결
Core
구매 오케스트레이션
Core
결제수단·승인
Pay
Mandate·Credential
Pay
Payment
Pay
Order·Fulfillment
Merchant
Agent/앱 통합 상태
Core Projection

32.3 최종 결제 경로

사용자 → Agent → MCP Server → Core Service → 외부 Merchant의 UCP Checkout 확정 → Core가 Pay에 사용자 승인 요청 → 앱에서 PIN/생체인증 → Pay가 Mandate와 Payment Credential 발급 → Core가 Merchant에 UCP Complete → Merchant가 Payment Credential로 Pay 호출 → Pay가 PG 호출 → Merchant가 Order 생성 → Core가 Order를 동기화 → Agent와 앱에 같은 결과 표시 G n0->n1 n1->n2 n2->n3 n3->n4 n4->n5 n5->n6 n6->n7 n7->n8 n8->n9 n9->n10 n10->n11 n11->n12 n12->n13 n0 사용자 n1 Agent n2 MCP Server n3 Core Service n4 외부 Merchant의 UCP Checkout 확정 n5 Core가 Pay에 사용자 승인 요청 n6 앱에서 PIN/생체인증 n7 Pay가 Mandate와 Payment Credential 발급 n8 Core가 Merchant에 UCP Complete n9 Merchant가 Payment Credential로 Pay 호출 n10 Pay가 PG 호출 n11 Merchant가 Order 생성 n12 Core가 Order를 동기화 n13 Agent와 앱에 같은 결과 표시

32.4 절대 바꾸지 말아야 할 세 가지

  1. Core는 Agent가 준 가격을 신뢰하지 않고 Merchant Checkout을 기준으로 한다.
  2. Pay는 사용자 승인 없이 Credential을 발급하지 않는다.
  3. 결제 결과가 불명확할 때 새 결제를 실행하지 않고 기존 상태를 조회한다.

이 세 원칙을 지키면 UCP, MCP, AP2-like Pay가 각각 분리돼 있으면서도 하나의 일관된 구매 경험으로 동작한다.


부록 A. 서비스 간 호출 매트릭스

호출자 수신자 목적 프로토콜 외부 공개 여부
Agent Nginx MCP Tool 호출 HTTPS/MCP 공개
Nginx MCP Reverse Proxy HTTP/HTTPS 내부
MCP Core Agent 기능 실행 Internal HTTP 비공개
App Nginx 사용자·승인 API HTTPS 공개
Nginx Core 사용자/배송지/상태 HTTP/HTTPS 내부
Nginx Pay 결제수단/승인 HTTP/HTTPS 내부
Core Merchant UCP Commerce HTTPS/UCP REST 상대 시스템
Core Pay 승인 세션·Artifact Internal HTTP 비공개
Pay Push Provider 승인 알림 HTTPS 외부 연동
Merchant Nginx/Pay 결제 처리 HTTPS Custom Handler Merchant 공개
Pay PG 실제 결제·조회 HTTPS Adapter 외부 연동
Merchant Core Order Webhook HTTPS Signed Webhook 제한 공개
Core MCP 구매 상태 응답 Internal HTTP 비공개

부록 B. 핵심 Artifact 관계

flowchart LR MC[Merchant Checkout] MA[Merchant Authorization] VE[Verified Checkout Evidence] AS[Authorization Session] CM[Checkout Mandate-like] PM[Payment Mandate-like] PC[Payment Credential] P[Payment] PR[Payment Receipt-like] O[Merchant Order] MC --> MA MC --> VE MA --> VE VE --> AS AS --> CM AS --> PM PM --> PC VE --> CM VE --> PM PC --> P P --> PR P --> O G MC->MA MC->VE MA->VE VE->AS VE->CM VE->PM AS->CM AS->PM PM->PC PC->P P->PR P->O MC Merchant Checkout MA Merchant Authorization VE Verified Checkout Evidence AS Authorization Session CM Checkout Mandate-like PM Payment Mandate-like PC Payment Credential P Payment PR Payment Receipt-like O Merchant Order

관계 제약:

VerifiedCheckoutEvidence.checkoutDigest
=
CheckoutMandate.checkoutDigest
=
PaymentMandate.checkoutDigest
=
PaymentCredential.checkoutDigest
=
Merchant Payment Request.checkoutDigest

그리고:

PaymentCredential.audience
=
실제 호출 Merchant
PaymentCredential.amount/currency
=
Merchant Payment Request.amount/currency
PaymentCredential.businessCheckoutId
=
현재 Merchant Checkout ID

하나라도 일치하지 않으면 결제를 실행하지 않는다.


부록 C. 권장 ADR 목록

ADR 제목
ADR-001 우리 서비스의 UCP 역할은 Platform이다
ADR-002 외부 Merchant가 Commerce Source of Truth다
ADR-003 MCP는 Core만 호출한다
ADR-004 Core는 PG를 직접 호출하지 않는다
ADR-005 Merchant가 Pay Processor를 호출한다
ADR-006 Pay는 Credential Provider와 Processor를 결합한다
ADR-007 MVP는 AP2-like Human Present만 지원한다
ADR-008 공식 AP2 Capability를 광고하지 않는다
ADR-009 UCP 버전을 2026-08-25로 고정한다
ADR-010 Checkout Evidence는 Merchant 서명과 Digest를 보존한다
ADR-011 Payment Credential은 Checkout에 묶인 일회성 Token이다
ADR-012 분산 일관성은 Outbox/Inbox와 멱등성으로 처리한다
ADR-013 UNKNOWN 상태에서 자동 재결제하지 않는다
ADR-014 Merchant Registry Allowlist로 SSRF를 방어한다
ADR-015 Order는 로컬 원본이 아니라 Merchant Projection이다

부록 D. 참고 자료

D.1 프로젝트 내부 기준 자료

이 문서는 다음 내부 초안을 통합하고, 외부 UCP Platform 모델에 맞춰 재설계했다.

내부 자료에서 확정되지 않았던 프로토콜 상세, 외부 Merchant 소유권, Merchant→Pay 결제 경로, 암호 Artifact 형식은 본 문서에서 설계 결정 또는 권장 기본값으로 명시했다. 따라서 구현 전 27절의 의사결정을 팀 ADR로 확정해야 한다.

D.2 외부 공식 문서


문서 종료
구현 중 역할 또는 호출 경로가 헷갈릴 때는 3절, 8절, 10절, 16절, 17절, 32절을 우선 기준으로 삼는다.

  1. Universal Commerce Protocol, “Checkout,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/checkout/

  2. Agent Payments Protocol, “Agentic Payment Protocol,” version v0.2. https://ap2-protocol.org/ap2/specification/

  3. Universal Commerce Protocol, “Processor-Tokenizer Payment Handler,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/payment/examples/processor-tokenizer-payment-handler/

  4. Universal Commerce Protocol, “Overview,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/overview/

  5. Agent Payments Protocol, “Checkout Mandate.” https://ap2-protocol.org/ap2/checkout_mandate/

  6. Agent Payments Protocol, “Payment Mandate.” https://ap2-protocol.org/ap2/payment_mandate/

  7. Universal Commerce Protocol, “AP2 Mandates Extension,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/payment/extensions/ap2-mandates/

  8. Universal Commerce Protocol, “Catalog REST Binding,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/catalog/rest/

  9. Universal Commerce Protocol, “Cart REST Binding,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/cart/rest/

  10. Universal Commerce Protocol, “Checkout REST Binding,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/checkout/rest/

  11. Universal Commerce Protocol, “Order,” version 2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/order/

  12. Agent Payments Protocol, “Flows” and receipt behavior. https://ap2-protocol.org/ap2/flows/

  13. Agent Payments Protocol, “Agent Authorization Framework.” https://ap2-protocol.org/ap2/agent_authorization/