1. 왜 ACP가 필요한가 — 그리고 왜 ‘또 하나의 REST API’가 아닌가
냉소적으로 보면, ACP는 평범한 HTTP 엔드포인트와 JSON 구조의 묶음처럼 보입니다: 어떤 /checkout_sessions, 몇 가지 웹훅, 몇 가지 토큰들. “오케이, 또 하나의 플랫폼 전용 커스텀 API네”라고 생각하기 쉽죠. 하지만 ACP의 아이디어는 더 깊습니다.
ACP는 세 참여자 간 상호작용을 위한 오픈 프로토콜로 설계되었습니다: AI 플랫폼(예: ChatGPT), 여러분의 커머스 백엔드, 그리고 결제 서비스 제공자(PSP). 목표는 제품과 가격을 어떻게 서술할지, AI가 사용자의 구매 의사를 어떻게 선언할지, checkout 세션은 어떻게 생성되는지, 결제는 어떻게 수행되는지, 그리고 모든 참여자가 최종 상태를 어떻게 알게 되는지를 표준화하는 것입니다.
핵심 아이디어: ACP를 구현한 동일한 머천트 백엔드는 잠재적으로 ChatGPT뿐 아니라 이 표준을 지원하는 다른 LLM 플랫폼에서도 동작할 수 있습니다. 즉, 여러분은 “ChatGPT 전용 API”를 만드는 것이 아니라, 차세대 커머스 통합 프로토콜을 구현하는 것입니다.
ChatGPT의 Instant Checkout은 ACP 표준의 첫 대규모 구현입니다. ChatGPT는 이 프로토콜을 준수하면서 여러분의 ACP 엔드포인트를 호출하고 사용자에게 멋진 UI를 보여주지만, 게임의 규칙 자체는 ACP 사양에 기술되어 있으며, “GPT의 마법”으로 블랙박스 안에 숨겨져 있는 것이 아닙니다.
2. ACP의 세 가지 축: Product Feed, Agentic Checkout, Delegated Payment
ACP에는 우리가 계속 언급할 세 가지 핵심 사양이 있습니다:
| 사양 | 역할 | GiftGenius에서의 위치 |
|---|---|---|
| Product Feed Spec | 상품 피드의 형식과 필드(SKU, 가격, 재고, 링크, 플래그). | OpenAI가 인덱싱하는 선물용 JSON/CSV 피드. |
| Agentic Checkout | checkout_session을 위한 REST 계약: 생성, 업데이트, 완료. | 우리의 ACP 백엔드: /checkout_sessions 엔드포인트와 웹훅. |
| Delegated Payment | 결제 데이터를 위임된 토큰 형태로 머천트에 전달하는 방식. | 결제 완료 시 Stripe Shared Payment Token 사용. |
Product Feed는 이전 강의에서 이미 다뤘습니다. 이제 남은 두 블록, 즉 Agentic Checkout과 Delegated Payment에 집중해보겠습니다.
다음의 세 레벨을 구분하는 것이 중요합니다:
- 표준(SPEC). 공식 문서는 어떤 필드와 엔드포인트가 필요한지, 어떤 상태가 합법인지, 그리고 어떤 보장을 제공해야 하는지를 설명합니다.
- 아키텍처 패턴(ARCH). 예를 들어, SKU와 주문을 별도 테이블에 저장하는 결정, ACP를 감싸는 서비스 래퍼를 두는 것, 웹훅에 큐를 사용하는 것 등. 좋은 실무 관행이지만 표준의 일부는 아닙니다.
- 구체 구현(GiftGenius 예시). 우리의 학습용 프로젝트에 대한 내용입니다: 테이블 구조, TypeScript 타입의 정확한 이름, 주문 로깅 방식 등. 모두 예시일 뿐 규범 문서는 아닙니다.
우리는 SPEC이 끝나고 여러분의 아키텍처가 시작되는 지점을 계속 강조할 것입니다 — 그래야 “강의에서 persona_tags 필드를 봤으니 이것도 공식 스펙의 일부겠지” 같은 오해가 없을 것입니다.
3. Checkout session 내부: 구조와 상태
Agentic Checkout Spec의 중심 객체는 여러분의 백엔드에 있는 checkout_session입니다. 논리적으로 이는 구매의 상태를 나타냅니다: 어떤 상품을, 얼마에, 어떤 배송 옵션으로, 현재 결제 시도는 어떤 상태인지.
스펙은 checkout_session의 필수 필드를 대략 다음과 같이 설명합니다(원문 대비 단순화/요약됨):
- id — 여러분이 생성하여 반환하는 문자열 세션 식별자. ChatGPT는 이후 모든 호출에서 이를 사용합니다.
- buyer — 구매자 정보: 이름, 이메일, 전화번호, 경우에 따라 주소. 실제 스펙에서는 PSP와 여러분의 시스템이 신뢰성 있게 사용할 수 있도록 이 객체가 구조화되어 있습니다.
- status — 현재 구매 상태를 나타내는 문자열 enum. 기본 상태:
- not_ready_for_payment — 아직 결제할 수 없음(예: 배송 옵션 미선택, 세금 미계산).
- ready_for_payment — 모든 준비 완료, 결제 토큰을 요청해 금액을 청구할 수 있음.
- completed — 결제가 성공적으로 완료되어 주문이 생성됨.
- canceled — 구매가 취소됨(사용자 주도 또는 오류).
- currency — 소문자 ISO 4217 통화 코드("usd", "eur" 등).
- line_items — 장바구니의 품목 목록. 각 품목은 SKU, 수량, 계산된 금액을 가집니다.
- fulfillment_address — 배송 주소(해당되는 경우).
- fulfillment_options 및 fulfillment_option_id — 가능한 배송(또는 이행) 옵션과 현재 선택된 옵션.
- totals — 합산 금액: 상품 금액, 세금, 배송비, 최종 합계.
- order — 세션이 성공적으로 완료된 후 생성될 주문을 설명하는 객체.
- messages — ChatGPT가 구매자에게 보여줄 수 있는 사용자 메시지 목록(예: 경고나 오류).
- links — 환불 정책, Privacy Policy, Terms of Service 등에 대한 링크 목록.
데모에서 모든 필드를 구현할 필요는 없지만 핵심 개념은 이해해야 합니다: checkout_session은 “단일 구매 시도의 히스토리와 현재 상태”이며, ChatGPT는 올바른 UX를 위해 필요한 모든 정보를 그 안에서 보기 기대합니다.
이해를 돕기 위해, 학습용 코드에 단순화된 타입을 도입해 봅시다:
// GiftGenius용 단순화된 checkout_session 모델 (완전한 SPEC 아님)
type GGCheckoutStatus = 'not_ready_for_payment' | 'ready_for_payment' | 'completed' | 'canceled';
type GGLineItem = { skuId: string; quantity: number; total: number };
type GGCheckoutSession = {
id: string;
status: GGCheckoutStatus;
currency: 'usd';
lineItems: GGLineItem[];
grandTotal: number;
};
이 모델은 의도적으로 공식 스펙보다 단순하지만, 실습에는 제격입니다: 수많은 필드에 파묻히지 않고 상태와 전이를 머릿속에 붙잡는 연습이 가능합니다.
4. checkout_session의 라이프사이클
Agentic Checkout 사양은 checkout_session에 대한 여러 작업을 설명합니다. 단순화하면 라이프사이클은 다음과 같습니다:
- 세션 생성: POST /checkout_sessions.
- 세션 업데이트: POST /checkout_sessions/{id}.
- 세션 완료(complete): POST /checkout_sessions/{id}/complete.
- (가끔) 취소: 별도의 cancel 엔드포인트 또는 업데이트로 canceled 전이.
상태 관점에서 보면 다음과 같은 다이어그램을 그릴 수 있습니다:
stateDiagram-v2
[*] --> not_ready_for_payment
not_ready_for_payment --> ready_for_payment: 배송/세금 계산
옵션 선택
ready_for_payment --> completed: 성공적인 POST /complete
ready_for_payment --> canceled: 사용자 취소 또는 오류
not_ready_for_payment --> canceled: 오류, 호환되지 않는 데이터
생성된 checkout_session은 보통 not_ready_for_payment 상태에서 시작하거나, 결제에 필요한 것이 모두 알려져 있다면(예: 배송/세금이 없는 디지털 상품) 곧바로 ready_for_payment일 수 있습니다. 업데이트는 데이터를 추가(주소, 프로모션 코드, 배송 옵션)하고 합계를 재계산할 때 사용됩니다. 완료는 Delegated Payment가 개입해 실제로 돈이 청구되는 순간입니다.
여기서 역할 분담을 이해하는 것이 중요합니다:
- ChatGPT는 사용자 대화를 바탕으로 세션 생성, 업데이트, 완료를 트리거합니다.
- 여러분의 백엔드(머천트)는 SKU 검증, 가용성 확인, 가격/세금 계산, 상태 전환, 주문 생성 등 올바른 비즈니스 로직을 책임집니다.
- PSP(Stripe 등)는 실제 결제를 처리하고 Shared Payment Token을 발급합니다. 머천트는 이를 사용해 금액을 청구합니다.
조금 뒤에 이 상태 다이어그램을 구체적인 HTTP 요청과 간단한 코드 예시에 겹쳐 보겠습니다.
5. checkout_session 생성: ChatGPT가 기대하는 것
ChatGPT(또는 에이전트)가 사용자가 실제로 무언가를 구매하려 한다고 판단하면, Product Feed를 바탕으로 line items를 구성합니다: SKU 목록, 수량, 예상 통화, 그리고 경우에 따라 배송에 대한 추가 요구사항. 그런 다음 여러분의 엔드포인트 POST /checkout_sessions를 호출합니다.
머천트 측에서 이 시점에 해야 할 일은 다음과 같습니다:
- 입력 데이터 유효성 검사: 모든 SKU가 존재하고 판매 가능하며, 정책을 위반하지 않는지(예: 미성년자에게 주류 금지) 확인합니다.
- 고유 규칙에 따라 가격과 세금을 계산합니다.
- 상품이 물리적인 경우 배송 옵션(fulfillment options)을 준비합니다.
- 상태와 합계를 포함한 올바른 checkout_session을 반환합니다.
GiftGenius를 위한 가장 간단한 Express 핸들러는 다음처럼 보일 수 있습니다:
// 단순화된 checkout_session 생성 예시(의사 코드)
app.post('/checkout_sessions', async (req, res) => {
const items = req.body.lineItems as GGLineItem[]; // skuId + quantity
const pricedItems = await priceItems(items); // 각 SKU의 total 계산
const grandTotal = sum(pricedItems.map(i => i.total));
const session: GGCheckoutSession = {
id: generateId(),
status: 'ready_for_payment', // 디지털 선물의 경우 바로 결제 준비로 설정 가능
currency: 'usd',
lineItems: pricedItems,
grandTotal,
};
res.status(201).json(session);
});
여기서 우리는 몇 가지를 합니다:
- 클라이언트(ChatGPT)에서 온 입력 가격을 신뢰하지 않고 자체 데이터로 재계산합니다 — 커머스 보안을 위해 매우 중요합니다.
- 고유한 세션 id를 생성합니다(예: gg_chk_... 접두사).
- 추가 단계가 없다면(배송 없음, 자동 세금, 단순 모델) 상태를 ready_for_payment로 반환합니다.
현실의 ACP 호환 백엔드라면 messages, links, 복합 totals 객체를 추가로 반환하고, 스펙에 따라 order도(최소한 초안 형태로) 채워 넣게 됩니다.
6. checkout_session 업데이트와 멱등성
세션이 생성된 뒤 ChatGPT는 사용자에게 추가 정보를 요청할 수 있습니다: 배송 주소, 쿠폰 적용, 이행 옵션 변경 등. 이 데이터가 생기면 플랫폼은 POST /checkout_sessions/{id} 호출을 통해 여러분이 재계산하도록 합니다.
코드 관점에서는 생성과 매우 비슷하지만, 새 세션을 만들지 않고 다음을 수행합니다:
- id로 기존 세션을 찾습니다.
- 변경사항을 적용합니다(예: fulfillment_option_id 변경 또는 할인 추가).
- 합계를 재계산합니다.
- 업데이트된 checkout_session을 반환합니다.
스펙은 네트워크 오류나 ChatGPT 측 재시도로 인해 중복 호출이 발생할 수 있음을 전제합니다. 따라서 이전 모듈에서 도구/웹훅의 멱등성에 대해 이야기했듯이, 여기서도 요청 헤더의 Idempotency-Key를 사용하고, 중복을 신중히 처리하는 것이 권장됩니다.
가상의 업데이트 핸들러는 다음과 같을 수 있습니다:
app.post('/checkout_sessions/:id', async (req, res) => {
const id = req.params.id;
const key = req.header('Idempotency-Key'); // 같은 key => 같은 효과
const existing = await loadSessionWithIdempotency(id, key, req.body);
// applyUpdates 내부에서 가격, 배송 등을 재계산할 수 있음
const updated = await applyUpdates(existing, req.body);
await saveSession(updated, key);
res.json(updated);
});
여기서는 SPEC의 정확한 구조를 따르기보다는 아이디어를 보여줍니다: 입력 — 변경사항과 멱등 키, 출력 — 일관된 checkout_session 상태. 같은 키로 동일한 요청이 다시 온다면, 불필요한 주문 생성이나 로그 중복 없이 동일한 결과를 반환해야 합니다.
7. checkout_session 완료와 Delegated Payment: Shared Payment Token이 동작하는 방식
가장 흥미롭고 긴장되는 순간은 checkout_session의 완료, 즉 실제로 돈이 청구되는 때입니다. 여기서 두 번째 사양인 Delegated Payment가 등장합니다.
Delegated Payment의 아이디어
사용자는 ChatGPT 인터페이스에서 결제 정보를 입력하거나 선택합니다(카드, 지갑, 저장된 결제 수단). 플랫폼은 이 데이터를 여러분에게 직접 보내지 않습니다 — 대신 PSP(예: Stripe)에 Shared Payment Token(SPT)이라는 특별한 토큰을 요청합니다. 이 토큰은:
- 머천트와 특정 세션에 명확히 연동되어 있으며,
- 금액과 수명으로 제한되고,
- 여러분에게 실제 카드 번호를 노출하지 않습니다.
결과적으로 다음과 같은 그림이 됩니다:
| 주체 | 카드 결제 정보 접근 | Shared Payment Token 접근 | 주문 상세(SKU, 금액) 접근 |
|---|---|---|---|
| 사용자 | 예(UI에서 입력) | 아니오(불필요) | 부분적으로(무엇을 얼마에 사는지) |
| ChatGPT/OpenAI | 예(결제 처리 중) | 예 | 예 |
| PSP (Stripe) | 예 | 예 | 결제 범위 내 |
| 머천트 | 아니오 | 예 | 예 |
이 설계는 머천트가 결제 정보를 보관하지 않고 주문 비즈니스 로직에 집중하게 하며, 컴플라이언스 문제는 PSP와 플랫폼에 맡길 수 있게 해줍니다.
Insight
Shared Payment Token의 요지는 여러분의 백엔드에서는 카드 데이터를 보지 않지만, 결제는 여러분이 수행한다는 데 있습니다. 이를 조금 다르게 볼 수도 있습니다.
상점이나 호텔이 먼저 카드에 금액을 홀드(hold)해 두었다가 나중에 청구하는 상황을 경험해 보셨을 겁니다. Shared Payment Token을 홀드 토큰으로 생각해 보세요. ChatGPT가 사용자 계정에서 금액을 홀드했으며, 아직 청구하지는 않았습니다. 이 홀드 토큰이 여러분에게 전달되었고, 이제 여러분은 이를 Stripe로 보내 실제 청구를 진행할 수 있습니다.
여기에는 두 가지 중요한 뉘앙스가 있습니다:
- 홀드 금액과 청구 금액이 크게 달라지면 안 되며, 가능하면 완전히 일치하는 것이 좋습니다.
- ChatGPT를 통해 첫 달 구독을 $1에 판매한 뒤, 이후 매달 $49.99를 청구할 수도 있습니다.
요청 POST /checkout_sessions/{id}/complete
Instant Checkout에서 사용자가 결제 확인 버튼을 누르면, ChatGPT는:
- PSP에서 SPT를 요청합니다(예: Stripe ACP API).
- 이 토큰을 구매자 정보와 함께 POST /checkout_sessions/{id}/complete로 여러분의 백엔드에 보냅니다.
스펙은 요청 본문을 대략 다음과 같이 설명합니다(아래는 공식 문서를 바탕으로 한 적응/요약 예시):
POST /checkout_sessions/checkout_session_123/complete
{
"buyer": {
"first_name": "John",
"last_name": "Smith",
"email": "johnsmith@mail.com"
},
"payment_data": {
"token": "spt_123",
"provider": "stripe"
}
}
여러분의 백엔드는 여기에 대해 다음을 수행해야 합니다:
- checkout_session_123 id의 checkout_session을 찾습니다.
- 상태가 완료를 허용하는지 확인합니다(일반적으로 ready_for_payment).
- PSP에서 spt_123 토큰을 사용해 결제를 생성합니다(PSP에 따라 방법이 다름. Stripe의 경우 특정 엔드포인트와 payment method 타입 사용).
- 결제 승인 결과를 기다립니다.
- checkout_session을 completed로 업데이트하고, 주문을 생성/저장하여 세션 구조의 order 필드를 채웁니다.
- 최신 checkout_session을 응답으로 반환합니다.
아주 단순화한 TypeScript 의사 코드는 다음과 같을 수 있습니다:
app.post('/checkout_sessions/:id/complete', async (req, res) => {
const { id } = req.params;
const { buyer, payment_data } = req.body;
const session = await loadSession(id);
await chargeWithSharedToken(payment_data.token, session.grandTotal);
const completed = await markSessionCompleted(session, buyer);
res.json(completed);
});
현실 세계에서는 이 몇 줄 사이에 오류 처리, 재시도, 로깅, 여러분의 주문 모델과의 통합이 숨어 있게 됩니다.
무언가 잘못되면(예: 결제 거절), checkout_session을 not_ready_for_payment 또는 canceled 상태로 반환하고, messages를 채워 ChatGPT가 사용자에게 상황을 올바르게 설명할 수 있도록 해야 합니다.
8. ChatGPT의 Instant Checkout: 전체 플로우
이제 “의도에서 결제까지”가 ChatGPT에서 어떻게 하나의 시나리오로 이어지는지 정리해 봅시다. 이 강의는 위젯의 “구매” 버튼 뒤에 숨은 것을 “해독”하는 과정으로 볼 수 있습니다.
단순화된 시나리오:
- 사용자가 “친구에게 줄 디지털 선물을 $50 이하로 추천하고 지금 바로 구매까지 진행해줘”라고 입력합니다.
- 에이전트(또는 ChatGPT App)가 Product Feed를 사용해 예산 범위 내의 적절한 SKU를 찾습니다.
- ChatGPT는 채팅에서 여러분의 GiftGenius 위젯을 통해 몇 개의 선물 카드를 보여주고 하나를 고르게 합니다.
- 선택 후 ChatGPT는 line items를 구성하여 여러분의 ACP 백엔드에 POST /checkout_sessions를 호출하고, 합계와 상태가 포함된 checkout_session을 받습니다.
- Instant Checkout UI에서 사용자는 최종 금액, 상품명, 환불 정책, 확인 버튼을 확인합니다.
- 확인 시 ChatGPT는 PSP에서 Shared Payment Token을 받아 POST /checkout_sessions/{id}/complete를 호출합니다(앞서 논의한 대로).
- 여러분의 백엔드는 결제를 처리하고, 주문을 생성하여 checkout_session을 completed 상태로 반환합니다.
- ChatGPT는 사용자에게 확인을 보여주며, 여러분의 백엔드는(Agentic Checkout Spec에 따른 웹훅을 통해) OpenAI로 이벤트를 다시 전송하여 플랫폼이 주문 결과를 알 수 있게 할 수 있습니다.
시퀀스 다이어그램으로 표현하면 다음과 같습니다:
sequenceDiagram
actor U as 사용자
participant GPT as ChatGPT
participant GG as GiftGenius ACP 백엔드
participant PSP as Stripe (PSP)
U->>GPT: 나는 $50 이하의 선물을 원하고 여기서 바로 구매하고 싶어
GPT->>GG: POST /checkout_sessions (line_items)
GG-->>GPT: checkout_session (ready_for_payment)
GPT->>U: Instant Checkout 표시(상품, 가격, ToS)
U->>GPT: ‘결제 확인’ 클릭
GPT->>PSP: 금액과 머천트에 대한 SPT 요청
PSP-->>GPT: Shared Payment Token (spt_xxx)
GPT->>GG: POST /checkout_sessions/{id}/complete (token + buyer)
GG->>PSP: SPT로 결제 요청
PSP-->>GG: 결제 성공
GG-->>GPT: checkout_session (completed + order)
GPT-->>U: 구매 확인 표시
이 시나리오 어디에서도 임의의 DB 호출이나 이상한 내부 엔드포인트는 등장하지 않습니다. 모든 것은 ACP가 엄격히 정의한 계약 안에서 이뤄지며, 각 참여자는 자신의 역할을 알고 있습니다.
9. 미니 실습: GiftGenius용 단순화된 ACP 백엔드
이 강의가 순수 이론으로 남지 않도록, 학습용 프로젝트를 위한 ACP 레이어 구현을 머릿속으로 “돌려” 보는 것이 중요합니다.
GiftGenius에 이미 다음이 있다고 가정해 봅시다:
- Product Feed를 만들기 위한 SKU/가격 데이터베이스(지난 강의에서 모델링).
- 간단한 주문 모델: orders 테이블과 id, userId, skuId, amount, currency, status, createdAt 필드.
- 선물을 추천할 줄 아는 ChatGPT App UI와 MCP 레이어(이전 모듈에서 구축).
이제 여러분의 과제는 그 위에 작은 서비스 gg-acp를 하나 더 추가하는 것입니다:
- 엔드포인트 POST /checkout_sessions:
- SKU 목록과 수량을 입력으로 받습니다.
- 여러분의 DB를 기반으로 합계를 재계산합니다.
- 초안 주문(예: 상태 pending)과 checkout_session(상태 ready_for_payment)을 생성합니다.
- checkout_session을 반환합니다.
- 엔드포인트 POST /checkout_sessions/{id}:
- 세션과 주문을 찾습니다.
- 변경사항을 적용합니다(예: 최종 금액을 줄이는 프로모션 코드 지원).
- 업데이트된 checkout_session을 반환합니다.
- 엔드포인트 POST /checkout_sessions/{id}/complete:
- SPT, 금액, 구매자 데이터를 받습니다.
- 데모 버전에서는 실제 PSP 호출 없이 주문을 “결제 완료”로만 표시할 수 있습니다(또는 Stripe를 시뮬레이션할 수 있습니다).
- checkout_session을 completed 상태로 업데이트하고, order_id를 연결합니다.
이 모든 서비스를 작은 Node/Express 애플리케이션 하나로 구현하거나 Next.js App Router의 엔드포인트로 구현할 수 있습니다. 핵심은 결제를 에뮬레이션하더라도 형식과 상태에 대한 계약을 준수하는 것입니다.
TypeScript의 간단한 주문 모델은 다음과 같을 수 있습니다:
// GiftGenius의 단순화된 주문 모델
type GGOrderStatus = 'pending' | 'paid' | 'canceled';
type GGOrder = {
id: string;
userId: string;
skuId: string;
amount: number;
currency: 'usd';
status: GGOrderStatus;
};
프로덕션에서는 여기에 여러분의 Auth/Identity(채팅 사용자가 누구인지 식별), OpenAI로의 웹훅, 더 복잡한 환불 시나리오가 추가됩니다. 그러나 이 강의의 학습 단계에서는 세션 생성 → 업데이트 → 완료의 사이클을 안정적으로 돌리는 연습만으로 충분합니다. 그 과정에서 돈과 이성을 동시에 잃지 않는 것이 중요합니다.
10. ACP / Instant Checkout 설계 시 자주 하는 실수
실수 №1: 역할 혼동(“ChatGPT가 내 상점이다”).
개발자들이 ChatGPT를 “중앙 원장 시스템”으로 상상하고, 주문의 비즈니스 상태를 플랫폼 쪽에 저장하려는 경우가 종종 있습니다: “거기 checkout_session이 있으니 주문 이력도 OpenAI에서 읽으면 되겠지.” 이건 잘못된 길입니다. checkout_session은 프로토콜 객체이지, 주문의 단일 진실 소스가 아닙니다. 진실의 원천은 여러분의 커머스 백엔드입니다. 바로 그곳에 주문, 상태, 환불, 리포트가 존재해야 합니다. ChatGPT는 이 구조에서 신뢰할 수 있는 “채팅 프런트엔드”에 불과합니다.
실수 №2: ChatGPT에서 온 입력 가격을 신뢰.
“에이전트가 이미 SKU를 고르고 합계까지 계산했으니 그 금액을 받아 바로 청구하자”라고 생각하기 쉽습니다. 그러면 안 됩니다. ChatGPT의 입력(line items, 예상 가격)은 지시가 아닌 “제안”으로 봐야 합니다. 여러분의 백엔드는 Product Feed와 자체 DB를 기준으로 SKU, 가격, 재고, 할인 적용 가능성 등을 반드시 직접 검증해야 합니다. 그렇지 않으면 “모델이 반올림해서 사용자가 $0.01에 상품을 샀다” 같은 재미있는 버그들이 생깁니다.
실수 №3: 상태와 상태 머신 무시.
초기 프로토타입에서 종종 “구멍 난” 구현을 합니다: 세션 상태를 항상 completed 또는 그냥 ok로 두고, 실제 결제 상태와의 불일치를 내부에서 감추는 식이죠. 그 결과 ChatGPT는 사용자가 상황을 올바르게 이해하도록 표시할 수 없습니다: 결제가 진행 중인지, 이미 완료되었는지, 취소되었는지. not_ready_for_payment → ready_for_payment → completed/canceled 상태 머신을 정직하게 구현하고, 백엔드에서 실제 상태를 반환하는 편이 훨씬 신뢰할 수 있습니다. 즉석에서 만든 임시 필드로 얼버무리지 마세요.
실수 №4: Shared Payment Token을 “다회용 카드”처럼 사용.
SPT는 설계상 일회용 또는 엄격히 제한된 토큰입니다: 특정 작업, 금액, 머천트에 묶입니다. “혹시 몰라서” 캐시해 두거나 다른 구매에 재사용하려는 시도는 좋지 않습니다. 최선의 경우 PSP가 두 번째 시도를 거절할 것이고, 최악의 경우 결제와 주문의 정합성을 망치게 됩니다. 각 checkout_session.complete에는 신규 토큰이 필요하며, 결제가 실패했다면 새 토큰을 다시 요청해야 합니다.
실수 №5: /checkout_sessions와 웹훅에서 멱등성 부재.
현실의 네트워크에서는 요청이 중복될 수 있습니다: ChatGPT가 타임아웃 후 POST /checkout_sessions를 반복하거나, PSP가 일시 오류 후 웹훅을 재전송할 수 있습니다. 구현이 그때마다 새 주문과 새 DB 레코드를 만든다면, 곧 혼돈이 시작됩니다: 이중 청구, 주문 중복, 시스템 간 수상한 불일치. Idempotency-Key 사용, 중복 검사, 이전 호출 결과 저장은 “선택적 최적화”가 아니라, 신뢰할 수 있는 ACP 통합을 위한 필수 요소입니다.
실수 №6: Product Feed와의 연결을 잊음.
가끔 ACP 레이어를 “진공 상태”에서 설계합니다: SKU와 가격을 Product Feed와 일치하지 않는 내부 테이블에서 가져오는 식입니다. 그 결과 ChatGPT가 사용자에게 보여주는 것(피드 기반)과 ACP를 통한 checkout에서 처리되는 것이 완전히 달라질 수 있습니다. 이런 놀라움을 피하려면 SKU/가격 모델이 단일해야 합니다: Feed, ACP 백엔드, 내부 DB가 모두 동일한 진실의 원천을 바라보아야 합니다(그 위에 서로 다른 프로젝션과 캐시가 있더라도).
GO TO FULL VERSION