메인 콘텐츠로 건너뛰기

Stripe 연동

Stripe를 결제 수단으로 사용하여 신용카드, Apple Pay, Google Pay 및 40개 이상의 결제 수단을 전 세계적으로 지원할 수 있습니다.

사전 요구 사항

시작하기 전에 다음을 준비해주세요:
  • Stripe 계정 — 계정이 없다면 stripe.com에서 가입하세요
  • Stripe Dashboard에서 Stripe API 키 확인
  • 최소 하나 이상의 상품이 등록된 Headless Commerce 스토어
개발 중에는 테스트 모드 키(sk_test_... / pk_test_...)를 사용하세요. 실제 결제를 받을 준비가 되었을 때만 라이브 키로 전환하세요.

환경 변수

API 서버에 다음 환경 변수를 추가하세요:
# Stripe 시크릿 키 (서버 전용 — 클라이언트에 절대 노출하지 마세요)
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx

# Stripe 웹훅 서명 시크릿 (Stripe Dashboard → Webhooks에서 확인)
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx
프론트엔드 애플리케이션에는 퍼블리셔블 키를 추가하세요:
# Stripe 퍼블리셔블 키 (클라이언트에서 안전하게 사용 가능)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRET은 절대 클라이언트에 노출하지 마세요. 서버 측에서만 사용해야 합니다.

결제 흐름

Stripe 결제 흐름은 네 단계로 구성됩니다: 장바구니 생성, 체크아웃 시작, 클라이언트에서 결제 확인, 웹훅 처리.
1
장바구니 생성 및 상품 추가
2
SDK를 사용하여 장바구니를 생성하고 상품을 추가합니다:
3
TypeScript
import { createStorefrontClient } from '@headless-commerce/sdk';

const client = createStorefrontClient({
  apiKey: process.env.NEXT_PUBLIC_HC_API_KEY!,
});

// 장바구니 생성
const cart = await client.carts.create({
  session_id: 'session_abc123',
});

// 상품 추가
await client.carts.addItem(cart.id, {
  variant_id: 'var_xxx',
  quantity: 2,
});
Python
import requests

base_url = "https://api.headlesscommerce.io/v1"
headers = {
    "Authorization": "Bearer pk_test_your_key",
    "Content-Type": "application/json",
}

# 장바구니 생성
cart = requests.post(f"{base_url}/storefront/carts", headers=headers, json={
    "session_id": "session_abc123",
}).json()

# 상품 추가
requests.post(
    f"{base_url}/storefront/carts/{cart['id']}/items",
    headers=headers,
    json={"variant_id": "var_xxx", "quantity": 2},
)
cURL
# 장바구니 생성
curl -X POST https://api.headlesscommerce.io/v1/storefront/carts \
  -H "Authorization: Bearer pk_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{"session_id": "session_abc123"}'

# 상품 추가
curl -X POST https://api.headlesscommerce.io/v1/storefront/carts/{cart_id}/items \
  -H "Authorization: Bearer pk_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{"variant_id": "var_xxx", "quantity": 2}'
4
Stripe로 체크아웃 시작
5
payment_method: 'stripe'를 지정하여 체크아웃 엔드포인트를 호출합니다. API가 Stripe PaymentIntent를 생성하고 client_secret을 반환합니다:
6
TypeScript
const order = await client.carts.checkout(cart.id, {
  email: 'customer@example.com',
  shipping_address: {
    line1: '서울특별시 강남구 테헤란로 123',
    city: '서울',
    postal_code: '06234',
    country: 'KR',
  },
  payment_method: 'stripe',
});

// 응답에 Stripe client_secret이 포함됩니다
const { client_secret } = order.payment;
console.log(order.id);           // order_xxxxxxxx
console.log(client_secret);      // pi_xxx_secret_xxx
Python
order = requests.post(
    f"{base_url}/storefront/carts/{cart['id']}/checkout",
    headers=headers,
    json={
        "email": "customer@example.com",
        "shipping_address": {
            "line1": "서울특별시 강남구 테헤란로 123",
            "city": "서울",
            "postal_code": "06234",
            "country": "KR",
        },
        "payment_method": "stripe",
    },
).json()

# 응답에 Stripe client_secret이 포함됩니다
client_secret = order["payment"]["client_secret"]
print(order["id"])          # order_xxxxxxxx
print(client_secret)        # pi_xxx_secret_xxx
cURL
curl -X POST https://api.headlesscommerce.io/v1/storefront/carts/{cart_id}/checkout \
  -H "Authorization: Bearer pk_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "shipping_address": {
      "line1": "서울특별시 강남구 테헤란로 123",
      "city": "서울",
      "postal_code": "06234",
      "country": "KR"
    },
    "payment_method": "stripe"
  }'
7
클라이언트에서 결제 확인
8
프론트엔드에서 Stripe.js를 사용하여 client_secret으로 결제를 확인합니다:
9
import { loadStripe } from '@stripe/stripe-js';

const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

const { error } = await stripe!.confirmPayment({
  clientSecret: client_secret,
  confirmParams: {
    return_url: 'https://your-store.com/order/confirmation',
  },
});

if (error) {
  // 고객에게 오류 표시 (예: 잔액 부족, 카드 거절)
  console.error(error.message);
} else {
  // 결제 처리 중 — Stripe가 return_url로 리다이렉트합니다
}
10
Stripe Elements를 사용한 커스텀 결제 폼의 경우:
11
import { useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js';

function CheckoutForm({ clientSecret }: { clientSecret: string }) {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        return_url: 'https://your-store.com/order/confirmation',
      },
    });

    if (error) {
      console.error(error.message);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button type="submit" disabled={!stripe}>
        결제하기
      </button>
    </form>
  );
}
12
웹훅이 자동으로 결제를 확인합니다
13
고객이 결제를 완료하면 Stripe가 서버로 웹훅 이벤트를 전송합니다. Headless Commerce가 이를 자동으로 처리하여 주문 상태를 confirmed로 업데이트합니다.
14
별도의 추가 코드가 필요하지 않습니다 — 플랫폼이 웹훅 검증과 주문 업데이트를 내부적으로 처리합니다.

웹훅 설정

Headless Commerce는 Stripe 웹훅 이벤트를 수신하여 주문 및 결제 상태를 동기화합니다. Stripe Dashboard에서 웹훅을 설정하세요:
  1. Stripe Dashboard에서 DevelopersWebhooks로 이동
  2. Add endpoint 클릭
  3. 웹훅 URL 입력: https://api.headlesscommerce.io/v1/webhooks/stripe
  4. 다음 이벤트를 선택:
    • payment_intent.succeeded
    • payment_intent.payment_failed
    • charge.refunded
    • charge.dispute.created
  5. Signing secret을 복사하여 STRIPE_WEBHOOK_SECRET으로 설정

이벤트 처리

Stripe 이벤트Headless Commerce 동작
payment_intent.succeeded주문 상태 confirmed로 변경, 결제 completed 처리
payment_intent.payment_failed결제 failed 처리, payment.failed 웹훅 전송
charge.refunded환불 기록, payment.refunded 웹훅 전송
charge.dispute.created주문에 검토 플래그 설정

환불

Admin API를 통해 전액 또는 부분 환불을 처리할 수 있습니다:
import { createAdminClient } from '@headless-commerce/sdk';

const admin = createAdminClient({
  apiKey: process.env.HC_ADMIN_API_KEY!,
});

// 전액 환불
const refund = await admin.orders.refund('order_xxxxxxxx', {
  reason: 'customer_request',
});

// 부분 환불
const partialRefund = await admin.orders.refund('order_xxxxxxxx', {
  amount: 1500,
  reason: '배송 중 상품 파손',
});
환불은 Stripe를 통해 자동으로 처리되며 주문 상태가 그에 따라 업데이트됩니다.

테스트

테스트 모드 키

개발 중에는 항상 테스트 모드 API 키를 사용하세요. 테스트 키로 생성된 거래는 실제 결제 네트워크를 사용하지 않습니다.
키 유형접두사예시
퍼블리셔블 키pk_test_pk_test_51ABC...
시크릿 키sk_test_sk_test_51ABC...

테스트 카드 번호

테스트 모드에서 다음 카드 번호를 사용할 수 있습니다:
카드 번호시나리오
4242 4242 4242 4242결제 성공
4000 0000 0000 32203D Secure 인증 필요
4000 0000 0000 9995결제 거절 (잔액 부족)
4000 0000 0000 0002일반 카드 거절
만료일은 미래 날짜, CVC는 아무 3자리 숫자, 우편번호는 아무 값이나 사용할 수 있습니다.

로컬에서 웹훅 테스트

Stripe CLI를 사용하여 로컬 개발 서버로 웹훅 이벤트를 전달할 수 있습니다:
# Stripe CLI 설치
brew install stripe/stripe-cli/stripe

# Stripe 계정 로그인
stripe login

# 로컬 엔드포인트로 이벤트 전달
stripe listen --forward-to localhost:3000/v1/webhooks/stripe

# 다른 터미널에서 테스트 이벤트 트리거
stripe trigger payment_intent.succeeded

오류 처리

프론트엔드에서 결제 오류를 적절히 처리하세요:
try {
  const order = await client.carts.checkout(cart.id, {
    email: 'customer@example.com',
    shipping_address: { /* ... */ },
    payment_method: 'stripe',
  });
} catch (error: unknown) {
  if (error instanceof Error && 'code' in error) {
    const apiError = error as { code: string; message: string };
    switch (apiError.code) {
      case 'cart_empty':
        // 장바구니가 비어 있음
        break;
      case 'insufficient_stock':
        // 하나 이상의 상품 재고가 부족함
        break;
      case 'payment_provider_error':
        // Stripe에서 PaymentIntent 생성 중 오류 반환
        break;
      default:
        console.error(apiError.message);
    }
  }
}
confirmPayment() 이후 발생할 수 있는 주요 Stripe 오류:
오류 코드설명권장 조치
card_declined카드가 거절됨다른 카드 사용 요청
expired_card카드가 만료됨카드 정보 업데이트 요청
insufficient_funds잔액 부족다른 카드 사용 요청
processing_errorStripe 처리 오류결제 재시도
incorrect_cvcCVC 불일치CVC 재입력 요청

다음 단계

웹훅

모든 웹훅 이벤트와 페이로드 형식을 확인하세요.

TossPayments

한국 결제 수단을 위한 TossPayments를 설정하세요.

API 레퍼런스

모든 체크아웃 및 결제 엔드포인트를 살펴보세요.

SDK

전체 SDK 레퍼런스와 모든 리소스 및 메서드를 확인하세요.