UCP Platform + AP2-like Pay 기반 Agentic Commerce 프로젝트 설계서
문서 버전: 1.1
작성일: 2026-09-02
문서 상태: 구현 기준 설계안
대상 독자: 백엔드·모바일·인프라 개발자, 기획자, 설계 검토자
프로토콜 기준: UCP2026-08-25REST Binding, AP2v0.2Human Present 흐름 참고
개발 기준: 백엔드 4인, 2026-09-07 ~ 2026-09-25
목차
- 문서 목적
- 프로젝트 한 문장 정의
- 가장 중요한 역할 구분
- 표준 적용 범위와 적합성 선언
- 용어 사전
- 참여자와 책임 매핑
- 전체 아키텍처
- 데이터 최종 소유권
- 변경 불가능한 설계 원칙
- 전체 구매 흐름
- 암호학적 Artifact 설계
- 서비스별 상세 설계
- UCP Profile 설계
- API 설계
- 데이터베이스 설계
- 상태 머신
- 멱등성·일관성·장애 복구
- 인증·인가·보안 설계
- 이벤트·알림·실시간 상태
- 오류 모델
- 저장소와 배포 구조
- 관측성과 감사
- 테스트 전략
- MVP 범위와 비범위
- 개발 인력과 Task 분배
- 2026년 9월 7일~25일 구현 계획
- 구현 전 확정해야 할 의사결정
- 기존 요구사항 및 API 초안과의 관계
- 요구사항 추적표
- Definition of Done
- 시연 시나리오
- 최종 아키텍처 요약
- 부록 A. 서비스 간 호출 매트릭스
- 부록 B. 핵심 Artifact 관계
- 부록 C. 권장 ADR 목록
- 부록 D. 참고 자료
1. 문서 목적
이 문서는 사용자가 AI Agent에게 상품 탐색과 구매 준비를 맡기고, 최종 결제는 모바일 Pay 앱에서 직접 확인·승인하는 서비스를 처음부터 끝까지 설명한다.
문서 하나만 읽어도 다음 질문에 답할 수 있도록 구성했다.
- 우리 서비스는 UCP에서 어떤 역할을 맡는가?
mcp-server,core-service,pay-service는 각각 무엇을 하는가?- Core는 외부 UCP Merchant와 어떤 순서로 통신하는가?
- 사용자가 앱에서 승인한 뒤 실제 결제는 누가 시작하고 누가 처리하는가?
- Checkout Mandate-like, Payment Mandate-like, Payment Credential은 각각 무엇인가?
- 상품·장바구니·Checkout·결제·주문의 최종 상태는 어느 시스템이 소유하는가?
- 중복 요청, 응답 유실, PG Timeout, Credential 재사용을 어떻게 막는가?
- 실제 구현을 어떤 모듈·API·DB·상태 머신으로 나눌 것인가?
기존 요구사항의 핵심 사용자 경험은 유지한다.
- 사용자는 모바일 앱 계정을 만들고 배송지·카드·PIN 또는 생체인증을 등록한다.
- 사용자는 외부 AI Agent와 자신의 서비스 계정을 연결한다.
- 사용자는 Agent에게 자연어로 상품 검색과 장바구니 변경을 요청한다.
- Agent는 MCP를 통해 우리 서비스 기능을 호출한다.
- 구매 직전 외부 Merchant가 가격·재고·배송비를 최종 확정한다.
- 사용자는 모바일 Pay 앱에서 상품·금액·배송지·결제수단을 확인한다.
- 사용자 승인이 확인된 경우에만 결제 Credential이 발급된다.
- 외부 Merchant가 그 Credential로 Pay Service에 결제를 요청한다.
- 결제 성공 후 외부 Merchant가 주문을 생성한다.
- Agent와 앱은 같은 서버 상태를 조회한다.
2. 프로젝트 한 문장 정의
외부 AI Agent의 구매 요청을 MCP로 받아 외부 UCP Merchant와 Catalog·Cart·Checkout·Order 흐름을 진행하고, 사용자가 모바일 Pay 앱에서 명시적으로 승인한 경우에만 AP2-like Mandate와 일회성 결제 Credential을 발급하여 구매를 완료하는 Agentic Commerce 플랫폼이다.
가장 단순한 호출 흐름은 다음과 같다.
결제 승인 시점에는 다음 경로가 추가된다.
3. 가장 중요한 역할 구분
3.1 UCP는 결제회사가 아니다
UCP는 상품 탐색부터 Cart, Checkout, Order까지 Commerce 정보를 교환하는 프로토콜이다. UCP 자체가 돈을 움직이는 주체는 아니다. 외부 Business가 Merchant of Record로 남고, Platform은 Merchant가 광고한 Payment Handler에 맞춰 결제수단을 취득해 Checkout 완료 요청에 넣는다.1
3.2 우리 Core는 Merchant가 아니라 UCP Platform이다
이 설계에서 core-service는 상품을 직접 파는 쇼핑몰이 아니다. 외부 Merchant와 UCP로 통신하는 Platform이다.
따라서 상품 가격, 재고, Checkout, 주문의 최종 원본은 외부 Merchant에 있다. Core는 그 상태를 조회하고, 사용자·Agent 관점에서 하나의 구매 흐름으로 오케스트레이션하며, 필요한 Projection과 감사 증거만 저장한다.
3.3 실제 결제는 Merchant가 시작한다
Core가 Pay에 직접 charge를 호출하지 않는다.
즉 외부 Merchant가 Checkout을 완료하는 과정에서 결제를 시작한다.
3.4 AP2-like Pay의 역할
AP2는 Checkout Mandate와 Payment Mandate를 통해 Agent가 수행하는 구매와 결제에 암호학적 승인 증거를 부여한다. Human Present 흐름에서는 사용자가 최종 Checkout과 결제를 직접 보고 승인한다.2
우리 프로젝트의 Pay Service는 다음 세 역할을 결합한다.
- Trusted Surface Backend: 모바일 앱에 확정된 Checkout을 보여주고 사용자 인증을 받는다.
- Credential Provider: Payment Mandate-like를 검증하고 Checkout에 묶인 결제 Credential을 발급한다.
- Payment Processor: 외부 Merchant가 제출한 Credential을 검증하고 PG를 호출한다.
UCP 공식 예시에는 Token을 발급하는 Tokenizer와 최종 결제를 처리하는 Processor가 같은 주체인 패턴이 존재한다.3
4. 표준 적용 범위와 적합성 선언
4.1 UCP 적용 범위
프로젝트는 구현 중 명세 변동을 막기 위해 UCP 날짜 버전을 2026-08-25로 고정한다.
구현 대상은 다음과 같다.
- REST Transport
- Business Profile Discovery
- Platform Profile Advertisement
- Protocol Version 및 Capability Negotiation
- Catalog Search·Lookup
- Cart Create·Get·Update·Cancel
- Checkout Create·Get·Update·Complete·Cancel
- Order Get 및 Order Webhook
- Custom Payment Handler
UCP의 REST 서비스 엔드포인트는 Business의 /.well-known/ucp Profile에서 발견한 Base URL을 기준으로 호출한다. Platform은 모든 UCP 요청에 자신의 Profile URI를 UCP-Agent 헤더로 전달한다.4
4.2 AP2 적용 범위
프로젝트는 다음 AP2 의미를 구현한다.
- Human Present만 지원
- Merchant가 확정 Checkout에 서명
- 사용자가 Trusted Surface에서 최종 Checkout과 결제를 직접 확인
- Checkout Mandate-like와 Payment Mandate-like를 분리
- 두 Mandate를 같은 Merchant Checkout에 암호학적으로 결합
- Credential을 Merchant·Checkout·금액·통화에 제한
- Mandate 및 결제 결과에 대한 Receipt/Audit Evidence 저장
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. 전체 아키텍처
7.1 네트워크 경계
외부 요청은 Nginx를 거친다.
내부 서비스 호출은 Nginx를 다시 거치지 않는다.
Core가 외부 Merchant를 호출할 때는 Merchant Profile에서 발견한 HTTPS Endpoint를 사용한다.
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. 변경 불가능한 설계 원칙
- Agent는 결제를 승인할 수 없다.
- MCP Server는 외부 Merchant나 Pay Service를 직접 호출하지 않는다.
- Core는 Agent가 전달한 금액을 결제 기준으로 사용하지 않는다.
- 최종 금액·재고·배송비는 Merchant Checkout 응답을 기준으로 한다.
- Pay 앱은 검증된 Merchant Checkout의 내용을 그대로 보여준다.
- 사용자 승인 이후 Checkout 내용이 바뀌면 기존 승인을 무효화한다.
- Core는 카드번호·CVC·PIN·생체정보 원문을 받지 않는다.
- Payment Credential은 특정 Merchant·Checkout·금액·통화에 묶인다.
- 실제 결제 실행은 외부 Merchant가 Payment Handler를 통해 시작한다.
- 결제 결과를 모르면 재결제하지 않고 조회·대사를 우선한다.
- Merchant Complete 결과를 모르면 새 요청을 만들지 않고 같은 Checkout을 조회한다.
- 외부 상태 변경 요청에는 멱등성 키를 사용한다.
- 서비스 간 분산 트랜잭션은 2PC가 아니라 상태 머신·Outbox·Inbox·대사로 해결한다.
- 외부 Merchant URL은 Registry와 Profile 검증을 통과한 경우에만 호출한다.
- AP2-like 구현을 공식 AP2 호환으로 과장하지 않는다.
10. 전체 구매 흐름
10.1 흐름 요약
10.2 전체 시퀀스
10.3 단계별 상세 설명
단계 0. 사전 준비
사용자는 최소한 모바일 앱 계정과 Agent 연동을 준비한다. 배송지와 카드는 초기 설정에서 미리 등록할 수 있지만 필수 선행 조건으로 강제하지 않는다. 누락된 정보가 구매 중 발견되면 현재 Merchant Cart·Checkout Context를 유지한 채 앱에서 등록하고 이어간다.
구매 도중 정보가 부족하면 다음처럼 처리한다.
배송이 필요하지 않은 디지털 상품은 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을 호출한다.
MCP Server는 다음만 수행한다.
- Agent Access Token 검증
- 계정 연동 상태 확인
product:readScope 확인- Tool Input Schema 검증
- Core 내부 API 호출
- Core 응답을 MCP Tool 결과로 변환
MCP Server는 상품 DB나 Merchant Endpoint를 직접 호출하지 않는다.
단계 2. Core가 Merchant Profile을 발견한다
Core는 등록된 Merchant의 Profile을 조회한다.
GET https://merchant.example/.well-known/ucp
Profile에서 다음을 확인한다.
- UCP Version
- REST Service Endpoint
- Catalog·Cart·Checkout·Order Capability
- 지원 Payment Handler
- Merchant 공개키
- 이전 지원 버전 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을 둔다.
단계 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는 다음을 검증한다.
- Profile Origin과 Merchant Registry가 일치하는가?
- 서명의
kid가 Merchant Profile의 공개키와 일치하는가? - 서명 Algorithm이 허용 목록에 있는가?
- JCS Canonicalization 결과에 대한 서명이 유효한가?
- Checkout ID·Merchant ID·통화·총액이 유효한가?
- Checkout이
ready_for_complete인가? - Checkout이 만료되지 않았는가?
- 우리 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을 대체한다.
단계 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에서 승인하거나 거부한다.
단계 10. 사용자 인증과 승인
PIN 인증:
생체인증:
서버는 지문·얼굴 원본을 받거나 저장하지 않는다.
단계 11. Pay가 Mandate-like와 Credential을 발급한다
승인에 성공하면 Pay는 다음을 생성한다.
Checkout Mandate-like
Payment Mandate-like
Payment Credential Token
Authorization Receipt
각 Artifact는 같은 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 결과를 처리한다
PG Timeout 또는 응답 유실이면 UNKNOWN으로 저장한다.
UNKNOWN에서 금지
- 새 Payment 생성
- 다른 Idempotency-Key로 자동 재결제
- 사용자에게 단순 실패로 표시
UNKNOWN에서 허용
- PG 거래 상태 조회
- 같은 Payment ID로 Reconcile
- 운영자 확인
단계 17. Merchant가 Order를 생성한다
Pay가 SUCCESS를 반환하면 Merchant가 자신의 DB에 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"
}
검증자는 다음을 확인한다.
- Platform 서명
- Audience가 자신인지
- 만료시간
- Checkout ID
- Checkout Digest
- Embedded Merchant Authorization
- 현재 Merchant Checkout과의 일치
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
책임
- 외부 HTTPS 진입점
- TLS 종료
- Host 및 Path 기반 라우팅
- 기본 Rate Limit
- Request Body 크기 제한
- 표준 보안 헤더
- 접근 로그와 Trace ID 전달
- 외부에 공개하면 안 되는
/internal/**차단
외부 라우팅
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다.
책임
- MCP Transport 제공
- Agent OAuth Token 검증
- Scope 기반 Tool 권한 검사
- Tool Input·Output JSON Schema
- Core Internal API Client
- Core 오류를 Agent 친화적 결과로 변환
requestId,traceId,idempotencyKey전달- Tool 실행 감사 로그
하지 않는 일
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를 둔다.
Tool 오류 형식
{
"errorCode": "CHECKOUT_PRICE_CHANGED",
"message": "판매자가 최종 가격을 변경했습니다. 새 금액을 확인해 주세요.",
"currentState": "RECONFIRMATION_REQUIRED",
"nextAction": "REVIEW_CHECKOUT",
"retryable": false,
"traceId": "trace-123"
}
12.3 core-service
Core는 UCP Platform이자 전체 구매 오케스트레이터다.
책임
- 사용자 계정과 Agent 연동
- 사용자 배송지
- Platform UCP Profile
- Merchant Registry
- Merchant Profile Discovery·Cache·검증
- UCP Version·Capability·Payment Handler Negotiation
- Catalog·Cart·Checkout·Order Client
- Merchant Authorization 검증
- Verified Checkout Evidence 생성
- PurchaseRequest 상태 머신
- Pay AuthorizationSession 연동
- UCP Complete Checkout
- Merchant Order Webhook 수신
- Checkout·Order 대사
- 앱·Agent에 통합 상태 제공
- SSE 및 사용자 이벤트
하지 않는 일
상품·재고 원본 소유
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
Core는 여러 외부 상태를 직접 하나로 합치지 않고 각각의 원본 상태와 Reference를 유지한 뒤 사용자용 통합 상태를 계산한다.
12.4 pay-service
Pay Service는 사용자 승인과 결제 신뢰 경계를 소유한다.
책임
- 카드 Token 및 마스킹 정보
- 기본 결제수단
- PIN Credential
- 생체 Device Credential과 Challenge
- Merchant Account와 Payment Handler 설정
- AuthorizationSession
- Trusted Surface용 승인 화면 데이터
- Checkout Mandate-like
- Payment Mandate-like
- Payment Credential 발급·폐기·소비
- Merchant Payment API
- Credential·Mandate 검증
- Payment 상태 머신
- PG Adapter
- Payment Reconciliation
- Payment Receipt
- 결제 감사 로그
하지 않는 일
상품 검색
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이 아니라 전체 구매 흐름과 실패 복구를 증명하는 시연 구성요소다.
책임
- Business UCP Profile
- Catalog Search·Lookup
- 상품·옵션·재고
- Cart 상태
- Checkout Create·Get·Update·Complete·Cancel
- 가격·할인·배송비 계산
- Merchant Authorization 서명
- Custom AP2-like Capability 광고
- Custom Payment Handler 광고
- Checkout Mandate-like 검증
- Pay Merchant API 호출
- 결제 성공 후 Order 생성
- Order Get
- 서명된 Order Webhook
- Idempotency와 Replay Protection
패키지 구조
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
책임
- 결제 성공·실패·Timeout 재현
- 같은 Processor Idempotency Key에 같은 결과 반환
- 거래 상태 조회
- Pay Reconciliation 지원
시나리오
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 상태
UCP Checkout 상태는 Merchant가 결정한다. Core는 자체 추측으로 변경하지 않는다.
16.2 Core PurchaseRequest
상태 의미
| 상태 | 의미 |
|---|---|
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
PAYMENT_METHOD_REQUIRED는 아직 결제 승인을 받은 상태가 아니다. 카드가 등록·선택된 뒤 확정 Checkout이 그대로 유효할 때만 PENDING으로 이동한다. 승인 상태의 Terminal 전이는 단방향이며 APPROVED, REJECTED, EXPIRED, INVALIDATED를 다시 PENDING으로 되돌리지 않는다.
16.4 Payment Credential
CONSUMED는 Credential이 다시 조회되지 않는다는 뜻이 아니다. 같은 Merchant·Idempotency-Key·Request Hash의 재시도에는 연결된 기존 Payment 결과를 반환할 수 있다.
16.5 Pay Payment
16.6 상태 구분 원칙
각 상태를 별도 저장해야 다음 장애를 구분할 수 있다.
사용자는 승인했지만 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를 도입하지 않고 다음 조합을 사용한다.
17.2 MCP → Core 멱등성
구매 승인 요청에는 Agent가 생성한 idempotencyKey를 받는다.
UNIQUE(agent_connection_id, idempotency_key)
동작:
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)
동작:
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와 다르면:
17.9 Core의 Complete 응답 유실
상황:
Core 처리:
PurchaseRequest = COMPLETION_UNKNOWN
금지:
새 PurchaseRequest 생성
새 승인 세션 생성
새 Credential 발급
새 Idempotency-Key로 Complete 호출
복구:
17.10 Pay의 PG 응답 유실
상황:
Pay 처리:
Payment = UNKNOWN
Reconciliation Job 생성
복구:
결과 확인 전 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 권장 조합:
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 호출 전:
개발 환경의 Local Merchant Mock만 별도 Profile로 허용하고 운영 정책과 분리한다.
18.5 Key 관리
운영 Key 정책:
kid 필수
활성 Key와 검증 전용 이전 Key 동시 공개
Private Key를 애플리케이션 설정 파일에 저장 금지
정기 Rotation
폐기 시점 전 발급 Artifact 검증 기간 고려
18.6 PIN
- 원문 저장 금지
- Argon2id 또는 조직 표준 Password KDF 사용
- 사용자별 Salt
- 실패 횟수 및 잠금 시각 서버 관리
- 로그·APM·Exception에 PIN DTO 미노출
- 변경 시 기존 PIN 또는 강한 재인증 요구
18.7 생체인증
서버가 생체 원본을 받지 않는다.
Challenge 요구사항:
짧은 TTL
1회 사용
authorizationSessionId와 결합
userId·deviceId와 결합
재사용 시 거절
18.8 Payment Credential 보안
- Audience를 Merchant ID로 제한
- Checkout ID·Digest·금액·통화 포함
- 짧은 TTL
jtiUnique- 로그 마스킹
- DB에는 Hash와 암호문만 저장
- 동일 Credential의 새로운 논리적 소비 차단
- 승인 무효화 시 Token 폐기
- Core가 Artifact를 조회할 때 Service Authorization과 Purchase Ownership 검증
18.9 Trusted Surface 무결성
앱 표시 정보는 별도로 조합한 비신뢰 DTO가 아니라 검증된 Checkout Evidence에서 생성한다.
승인 화면과 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가 담당한다.
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
- Business Profile Schema
- Platform Profile Schema
UCP-Agent헤더- Version Negotiation
- Capability Intersection
- Catalog Search·Lookup
- Cart 전체 교체 Update
- Checkout 상태 전이
- Complete·Cancel
- Order Get
- Signed Webhook
- 금액은 Minor Unit Integer
- RFC 3339 시간
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 필수 테스트
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 정상 시나리오
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 완료 기준
- 정상 구매 흐름이 한 번의 사용자 승인으로 끝난다.
- Agent는 카드·PIN·생체정보에 접근하지 못한다.
- 같은 구매 요청 반복에도 결제는 한 번만 실행된다.
- 가격·금액·Checkout 변조가 결제 전에 차단된다.
- PG 결과 불명 상태에서 자동 재결제가 발생하지 않는다.
- Complete 응답 유실 후 중복 주문 없이 복구된다.
- Agent와 앱이 같은 구매·결제·주문 상태를 표시한다.
24. MVP 범위와 비범위
이 프로젝트는 UCP와 AP2의 모든 기능을 구현하는 범용 상용 플랫폼이 아니라, Agent → UCP Checkout → 모바일 사용자 승인 → Merchant 주도 결제 → Order 생성이라는 핵심 수직 흐름을 안전하게 증명하는 MVP다.
24.1 MVP 구현 범위
사용자와 Agent 연동
- Google 로그인 또는 프로젝트가 채택한 단일 사용자 로그인 방식
- Access Token과 Refresh Token
- Agent OAuth Authorization Code 흐름
- Agent Connection 조회·해제
- Scope 기반 MCP Tool 권한 제어
Commerce
- UCP Business Profile Discovery
- UCP Platform Profile 공개
- 등록 Merchant 1곳 이상
- Catalog Search·Lookup
- 단일 Merchant Cart
- Cart Create·Get·Update·Cancel
- Checkout Create·Get·Update·Complete·Cancel
- Merchant가 배송을 요구하는 실물 상품의 배송지 전달
- 배송 불필요 상품의 Fulfillment 단계 생략
- 배송지 미등록 시 기존 Checkout Context를 유지한 등록 후 이어가기
- 가격·재고·배송비 변경 처리
- Merchant Order 조회
- 서명된 Order Webhook 수신
결제
- 카드 Token Mock 또는 PG가 제공한 테스트 Token
- 기본 결제수단 지정
- 카드 미등록 시 기존 Authorization Context를 유지한 등록 후 이어가기
- PIN 승인
- 기기 생체인증 기반 Challenge 서명 검증
- 결제 승인 대기 목록
- Merchant Authorization 검증
- Checkout Mandate-like
- Payment Mandate-like
- Merchant·Checkout·금액·통화에 묶인 일회성 Payment Credential
- Merchant 전용 Payment Processor API
- PG Mock 성공·실패·Timeout·결과 불명
- Payment Reconciliation
- Payment Receipt-like와 감사 증거
사용자 경험
- Agent에서 상품 검색·선택·Cart 변경
- Agent에서 구매 승인 요청
- 모바일 앱 Push 또는 승인 대기 목록 진입
- 앱에서 확정 Checkout 확인
- PIN 또는 생체인증 승인·거부
- Agent와 앱에서 동일한 구매·결제·주문 결과 조회
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 이후 기능은 다음 순서로 확장하는 것이 안전하다.
- 실제 PG Tokenization과 결제 조회 연동
- 복수 UCP Merchant 등록과 Capability별 호환성 시험
- 취소·환불 및 Order Adjustment
- 공식 AP2 SD-JWT+KB Artifact 구현
- 다중 결제수단과 3DS
- 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를 둔다.
A와 B는 Core ↔ 외부 Merchant와 Agent ↔ 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가 결정권을 갖는 영역
- 외부 Merchant 응답을 어떤 내부 Projection으로 정규화할지
- Merchant Cart/Checkout Revision을 어떻게 비교할지
- UCP 오류를 어떤 구매 상태와 사용자 행동으로 매핑할지
- Complete Timeout 후 어떤 순서로 상태를 복구할지
- Merchant Mock가 어떤 UCP Commerce 상태를 재현할지
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가 결정권을 갖는 영역
- Tool 입력에서 어떤 정보를 허용하고 어떤 값은 Core에서만 결정할지
- Agent에게 노출할 오류·다음 행동 표현
- OAuth Scope와 Tool 매핑
- MCP 결과에 포함할 최소 상태와 민감정보 마스킹
- Agent 재시도와 멱등키 전달 방식
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가 결정권을 갖는 영역
- 승인 화면에 표시할 불변 Checkout 모델
- PIN·생체 Challenge와 사용자 인증 정책
- Mandate Claims와 Checkout Digest 결합 규칙
- Credential의 Audience·Binding·TTL·발급 조건
- 승인 만료·거부·무효화 시 Artifact 폐기 정책
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가 결정권을 갖는 영역
- Merchant 인증과 Processor API 보안 계약
- Credential 소비 원자성과 결제 멱등성 구현
- PG Timeout을
FAILED와UNKNOWN중 어디에 매핑할지 - Reconciliation 주기·횟수·운영 이관 정책
- Outbox/Inbox 전달과 추적 ID 규칙
- 로컬·CI 환경의 공통 실행 기준
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다.
공통 파일이나 패키지 루트를 직접 공유하기보다, 작은 계약 인터페이스와 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 수직 흐름
공동
- ADR-001: 우리 서비스는 UCP Platform이라는 역할 확정
- ADR-002: Merchant가 Catalog·Cart·Checkout·Order의 Source of Truth
- ADR-003: Core는 Pay를 직접 Charge하지 않음
- ADR-004: Pay는 AP2-like Human Present만 구현
- UCP 버전
2026-08-25고정 - Payment Handler와 Custom Capability Namespace 확정
- MCP Tool, Core Internal API, Core→Pay, Merchant→Pay 계약 초안 확정
- PurchaseRequest·AuthorizationSession·Payment 상태 enum과 전이 조건 확정
- 모든 외부 ID와 통합 오류 Envelope 확정
A
- Core와 Merchant Mock Commerce Skeleton
- Business/Platform Profile
- Merchant Registry와 Profile Discovery
- Version/Capability Negotiation
- Catalog Search/Lookup
- Cart Create/Get/Update/Cancel
- Checkout Create/Update/Get
- 가격 변경·재고 부족·배송지 필요 상태
- Checkout Projection과 Evidence 저장 Skeleton
B
- MCP Server Skeleton과
/mcp - OAuth/Token Skeleton
- Tool 9종 Schema
- Core Internal Client Stub
search_products,get_product- Cart Tool 4종
prepare_checkout- User/Address API Skeleton
C
- Pay Service Authorization 영역 Skeleton
- 결제수단 Token Mock
- PIN 등록·검증 Skeleton
- AuthorizationSession 상태 모델과 승인 대기 API
- Checkout Evidence 표시 DTO
- Mandate·Credential Claims 초안과 Key Skeleton
D
- Pay Service Processor 영역과 PG Mock Skeleton
- Merchant Account와 Processor API Skeleton
- Nginx 외부 라우팅
- Docker Compose로 전체 서비스 기동
- DB Migration과 Health Check
- CI 기본 파이프라인
- 공통 Trace ID와 구조화 로그
1주차 통합 완료 조건
추가로 Core가 Pay에 승인 세션을 생성하는 Stub 호출까지 성공해야 한다.
26.3 2주차 — 9월 14일~18일: 승인, Artifact, Complete, 정상 결제
A
- Merchant Authorization 검증
- Verified Checkout Evidence 불변 저장
- PurchaseRequest 상태 머신
- Core→Pay Authorization API 연동
- Checkout 변경 시 Approval 무효화
- UCP Complete 요청 조립
- Complete 멱등키
- Merchant Mock의 Checkout Mandate 검증
- Merchant Order 생성·조회
- Order Webhook과 Core Order Projection
B
- Agent OAuth 완료
request_purchase_approvalget_purchase_status- Tool 오류 Mapping
- Push/SSE 기본 흐름
- 앱·Agent 통합 상태 모델
- 주문·결제 결과 Public API
C
- AuthorizationSession 전체 상태 전이
- 승인 상세 Trusted Surface View
- PIN 승인·거부·만료
- 생체 Challenge와 서명 검증
- Checkout Mandate-like
- Payment Mandate-like
- Payment Credential Token
- Core Artifact 조회 API
D
- Merchant 인증과 요청 서명 검증
- Merchant Processor API
- Credential Audience/Binding/금액·통화 검증
- Token 1회 소비
- Payment 상태 머신
- PG 성공·실패 Adapter
- Merchant Mock Payment Handler
- Payment Receipt-like와 결제 이벤트
클라이언트 연동 전제
- 모바일 담당자는 Pay의 승인 목록·상세·PIN/생체 API를 연결한다.
- Agent UI 담당자는 MCP Tool 결과와
nextAction을 연결한다. - 백엔드 4인은 OpenAPI와 Mock 응답을 먼저 제공해 클라이언트 작업이 서버 완성까지 대기하지 않게 한다.
2주차 통합 완료 조건
9월 18일까지 위 정상 흐름을 실제 서비스 프로세스 사이에서 한 번 이상 통과시킨다.
26.4 3주차 — 9월 21일~25일: 장애 복구, 보안, 관측성, 시연 안정화
A
- Cart Revision 충돌 복구
- 가격·재고 변경 후 기존 승인 무효화
- Complete 응답 유실과 Checkout 재조회
- 같은 Idempotency-Key로 Complete 재시도
- Order Webhook 중복·순서 뒤바뀜
- Merchant 오류와 사용자
nextActionMapping
B
- OAuth Refresh·Revoke·연결 해제
- Agent의 승인 API 접근 차단 검증
- SSE
Last-Event-ID재연결 - Push 실패 대체 경로
- Agent 오류·재시도 UX용 상태 정리
- MCP/Core E2E 회귀 테스트
C
- PIN 실패 누적과 잠금
- 생체 Challenge Replay 차단
- Authorization 만료·거부·무효화 경합
- Checkout Digest·Merchant Authorization 변조 차단
- Mandate Claims 변조와 서명 실패
- 승인 이후 Checkout 변경 시 Credential 폐기
D
- PG
TIMEOUT_BEFORE_PROCESS - PG
TIMEOUT_AFTER_PROCESS - Payment
UNKNOWNReconciliation - Credential Replay와 이중 소비 차단
- 금액·통화·Merchant Audience 변조 차단
- Outbox/Inbox 재전송과 중복 소비
- Metrics·Trace Dashboard
- 부하·응답시간 측정과 병목 수정
- CI 전체 회귀·배포 스크립트 안정화
날짜별 마감점
| 날짜 | 마감점 |
|---|---|
| 9월 21일 | 정상 E2E 회귀 자동화, 실패 시나리오별 재현 스위치 확정 |
| 9월 22일 | Payment UNKNOWN과 Complete 응답 유실 복구 통과 |
| 9월 23일 | Replay·Digest·Audience·권한 우회 차단 통과 |
| 9월 24일 | 전체 회귀 테스트, 관측성, 문서·시연 환경 동결 |
| 9월 25일 | 최종 버그 수정, 배포 검증, 시연 시나리오 확정 |
3주차 통합 완료 조건
- 정상 구매 E2E가 반복 실행되어도 중복 Order와 중복 Payment가 발생하지 않는다.
- PG 응답이 유실되면 새 결제를 만들지 않고 기존 Payment를 조회해 최종 상태를 확정한다.
- UCP Complete 응답이 유실되면 같은 Checkout을 조회해 Order 존재 여부를 복구한다.
- 사용자가 승인한 Checkout의 Digest·금액·통화·Merchant 중 하나라도 달라지면 결제가 실행되지 않는다.
- Credential을 다른 Idempotency-Key나 다른 Merchant에서 재사용해도 PG 호출 횟수가 증가하지 않는다.
- Agent와 모바일 앱이 같은 PurchaseRequest 통합 상태를 표시한다.
- 모든 주요 요청을
traceId,purchaseRequestId,authorizationId,paymentId,externalCheckoutId,externalOrderId로 추적할 수 있다.
26.5 일정 운영 원칙
- 매일 서비스별 작업량이 아니라 수직 흐름을 막고 있는 계약과 장애물을 우선 점검한다.
- API가 늦어도 상대 담당자가 기다리지 않도록 Contract와 Stub을 먼저 제공한다.
- 공통 계약 변경은 작은 별도 PR로 만들고 관련 서비스 Contract Test를 모두 실행한다.
- 9월 11일까지 상태 enum과 외부 계약을 고정하고, 이후 변경은 ADR과 네 명의 리뷰를 요구한다.
- 9월 18일까지 정상 E2E를 완성하고, 마지막 주에는 새 기능을 추가하지 않는다.
- Timeout·중복·변조 시나리오는 2주차부터 병렬로 작성하며 마지막 주에 처음 시작하지 않는다.
- 9월 24일에 시연 환경과 문서를 동결하고, 9월 25일은 회귀 실패와 배포 문제만 수정한다.
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 사용을 검토한다.
- 공식 Schema 버전 고정
- SD-JWT VC 생성·검증
- Key Binding JWT
vct,cnf,sd_hash등 필수 Claims- 선택적 공개 규칙
- Merchant, Credential Provider, Processor별 Verification Rule
- Receipt 형식
- UCP AP2 Extension Complete Payload 상호운용 시험
- 적대적 Test Vector 통과
이 조건이 충족되지 않으면 Custom AP2-like Extension을 유지한다.
28. 기존 요구사항 및 API 초안과의 관계
초기 서비스 요구사항과 API 초안은 우리 서버가 상품·Cart·Order를 직접 소유하는 자체 Commerce Backend 형태를 일부 전제했다. 이번 설계는 외부 UCP Merchant가 Commerce 원본을 소유하는 Platform 모델로 재해석한다.
28.1 유지되는 요구사항
다음 사용자 가치와 안전 요구사항은 그대로 유지한다.
- 앱 로그인과 자동 로그인
- PIN·생체인증 등록
- 카드와 배송지 관리
- Agent 계정 연동과 해제
- 자연어 상품 검색
- 상품 상세·옵션·수량 선택
- Agent와 앱의 Cart 상태 일치
- 구매 직전 가격·재고·배송 가능 여부 재확인
- 앱의 명시적 결제 승인·거부
- 승인 대기 목록
- Push 실패 대체 경로
- 단일 승인 요청과 결제 멱등성
- 카드·CVC·PIN·생체정보 비노출
- 결제 결과 불명 시 재결제보다 상태 조회 우선
- 앱과 Agent의 결제·주문 상태 일치
- 주문·결제·배송 이력 조회
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 보정
초기 흐름의 다음 구간은:
재설계 후 다음처럼 상세화된다.
사용자 관점의 화면 수는 크게 늘지 않지만, 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 기능 완료
- 사용자가 앱에 로그인하고 배송지·결제수단·PIN 또는 생체 Credential을 등록할 수 있다.
- 사용자가 Agent와 계정을 OAuth로 연결하고 Scope를 확인·해제할 수 있다.
- Agent가 MCP로 상품 검색·상세 조회·Cart 조작을 수행할 수 있다.
- Core가 등록 Merchant의 UCP Profile을 Discovery하고 Capability를 협상한다.
- Merchant Checkout을
ready_for_complete까지 진행한다. - Merchant Authorization과 Checkout Digest를 검증한다.
- 사용자가 앱에서 최종 상품·금액·배송지·결제수단을 확인한다.
- 승인 후에만 Mandate와 Payment Credential이 발급된다.
- Core가 Credential을 UCP Complete에 포함해 Merchant에 전달한다.
- Merchant가 Pay Processor를 호출하고 Pay가 PG를 실행한다.
- 결제 성공 후 Merchant Order가 생성된다.
- Core가 Order Projection을 저장하고 Agent와 앱에 같은 결과를 보여준다.
30.2 보안 완료
- Agent와 MCP는 카드번호·CVC·PIN·생체 원본에 접근할 수 없다.
- Core는 PG Token과 Payment Credential 원문을 장기 저장하지 않는다.
- Merchant URL은 Registry Allowlist를 통과해야 한다.
- Merchant Checkout 서명 변조가 차단된다.
- Checkout Digest·Merchant·금액·통화 중 하나라도 다르면 결제가 차단된다.
- Payment Credential은 만료·Audience·Binding·1회 사용을 검증한다.
- Agent Token으로 결제 승인 API를 호출하면 차단된다.
- PIN 실패 제한과 생체 Challenge Replay 방지가 동작한다.
- Private Key는 Repository·일반 환경변수·로그에 노출되지 않는다.
30.3 신뢰성 완료
- 같은 MCP 구매 요청을 반복해도 PurchaseRequest가 중복 생성되지 않는다.
- 같은 UCP Complete 요청을 반복해도 Order와 Payment가 중복되지 않는다.
- 같은 Payment Credential을 재사용해 새 결제를 만들 수 없다.
- PG Timeout 시 Payment가
UNKNOWN으로 남고 자동 재결제되지 않는다. - Complete 응답 유실 시 Merchant Checkout 조회로 결과를 복구한다.
- Webhook 중복 수신 시 Order Projection이 한 번만 갱신된다.
- Checkout 변경 시 기존 Approval·Mandate·Credential이 무효화된다.
30.4 관측성 완료
다음 식별자로 전체 경로를 추적할 수 있다.
traceId
requestId
userId
agentConnectionId
merchantId
externalCartId
businessCheckoutId
purchaseRequestId
authorizationSessionId
checkoutMandateId
paymentMandateId
paymentId
externalOrderId
webhookId
- 주요 상태 전이는 감사 이벤트로 남는다.
- 민감정보는 로그에서 마스킹하거나 제외한다.
- UCP, Pay, PG 지연·오류·UNKNOWN 메트릭을 볼 수 있다.
- 시연 중 하나의 PurchaseRequest를 기준으로 전 구간 상태를 확인할 수 있다.
30.5 문서 완료
- Platform Profile과 Business Profile 예시
- MCP Tool Schema
- Core·Pay·Merchant OpenAPI
- 상태 전이표
- ERD
- 정상·실패 Sequence Diagram
- Key 및 Trust Model
- 위협 모델
- 로컬 실행 가이드
- 테스트 시나리오와 결과
- 표준 적용 범위와 AP2-like 고지
31. 시연 시나리오
31.1 정상 구매
사용자가 Agent에게 말한다.
10만 원 이하 러닝화를 찾아서 270 사이즈 하나 장바구니에 담아줘.
시연 흐름:
31.2 결제 결과 불명
시연 중 반드시 강조할 점:
"Timeout이 났지만 다시 결제하지 않았고,
같은 Payment와 Checkout을 조회해 성공을 확정했다."
31.3 Checkout 변조 차단
31.4 Credential Replay 차단
32. 최종 아키텍처 요약
32.1 각 구성요소를 한 문장으로 정의
32.2 누가 무엇의 최종 상태를 소유하는가
32.3 최종 결제 경로
32.4 절대 바꾸지 말아야 할 세 가지
- Core는 Agent가 준 가격을 신뢰하지 않고 Merchant Checkout을 기준으로 한다.
- Pay는 사용자 승인 없이 Credential을 발급하지 않는다.
- 결제 결과가 불명확할 때 새 결제를 실행하지 않고 기존 상태를 조회한다.
이 세 원칙을 지키면 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 관계
관계 제약:
그리고:
하나라도 일치하지 않으면 결제를 실행하지 않는다.
부록 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 모델에 맞춰 재설계했다.
서비스_요구사항_명세서.md: 사용자 흐름, 기능·비기능 요구사항, 보안·신뢰성 원칙User-Flow-Diagram.txt: 앱 초기 설정, Agent 연동, 상품 탐색, 결제 승인 흐름API_명세서.md: Public API, OAuth, MCP Tool, Payment 상태 및 초기 데이터 정책
내부 자료에서 확정되지 않았던 프로토콜 상세, 외부 Merchant 소유권, Merchant→Pay 결제 경로, 암호 Artifact 형식은 본 문서에서 설계 결정 또는 권장 기본값으로 명시했다. 따라서 구현 전 27절의 의사결정을 팀 ADR로 확정해야 한다.
D.2 외부 공식 문서
문서 종료
구현 중 역할 또는 호출 경로가 헷갈릴 때는 3절, 8절, 10절, 16절, 17절, 32절을 우선 기준으로 삼는다.
Universal Commerce Protocol, “Checkout,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/checkout/Agent Payments Protocol, “Agentic Payment Protocol,” version
v0.2. https://ap2-protocol.org/ap2/specification/Universal Commerce Protocol, “Processor-Tokenizer Payment Handler,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/payment/examples/processor-tokenizer-payment-handler/Universal Commerce Protocol, “Overview,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/overview/Agent Payments Protocol, “Checkout Mandate.” https://ap2-protocol.org/ap2/checkout_mandate/
Agent Payments Protocol, “Payment Mandate.” https://ap2-protocol.org/ap2/payment_mandate/
Universal Commerce Protocol, “AP2 Mandates Extension,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/payment/extensions/ap2-mandates/Universal Commerce Protocol, “Catalog REST Binding,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/catalog/rest/Universal Commerce Protocol, “Cart REST Binding,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/cart/rest/Universal Commerce Protocol, “Checkout REST Binding,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/checkout/rest/Universal Commerce Protocol, “Order,” version
2026-08-25. https://ucp.dev/2026-08-25/specification/shopping/order/Agent Payments Protocol, “Flows” and receipt behavior. https://ap2-protocol.org/ap2/flows/
Agent Payments Protocol, “Agent Authorization Framework.” https://ap2-protocol.org/ap2/agent_authorization/