TL;DR
Magento 2(Adobe Commerce) API는 개발자들이 전자상거래 상점과 프로그램적으로 통합할 수 있도록 지원합니다. OAuth 1.0a 및 토큰 기반 인증을 사용하여 REST, SOAP, GraphQL 엔드포인트를 제공하며, 제품, 주문, 고객, 재고 등에 접근할 수 있고, 속도 제한을 설정할 수 있습니다. 이 가이드에서는 인증 설정, CRUD 작업, 웹훅, 커스텀 엔드포인트 및 운영 환경 통합 전략에 대해 다룹니다.
소개
Adobe Commerce(Magento)는 연간 총 상품 가치 1,550억 달러 이상을 자랑하며 25만 개 이상의 전자상거래 상점을 운영하고 있습니다. 전자상거래 통합, ERP 커넥터 또는 모바일 앱을 구축하는 개발자에게 Magento API 통합은 선택 사항이 아니라, 이 방대한 판매자 기반에 도달하기 위한 필수 요소입니다.
현실은 다음과 같습니다. 여러 판매 채널을 관리하는 판매자들은 Magento와 다른 시스템 간의 수동 데이터 입력에 매주 20~30시간을 낭비하고 있습니다. 견고한 Magento API 통합은 제품 동기화, 주문 처리, 재고 업데이트 및 고객 데이터 관리를 자동화합니다.
이 가이드는 Magento 2 API 통합 프로세스 전체를 안내합니다. OAuth 1.0a 및 토큰 인증, REST/SOAP/GraphQL 엔드포인트, 제품 및 주문 관리, 웹훅, 맞춤형 API 개발, 그리고 운영 배포 전략에 대해 배우게 될 것입니다. 마지막에는 운영 환경에 즉시 사용 가능한 Magento 통합을 갖게 될 것입니다.
💡 팁: Apidog은 API 통합 테스트를 간소화합니다. 단일 작업 공간에서 Magento 엔드포인트를 테스트하고, 인증 흐름을 검증하고, API 응답을 검사하며, 통합 문제를 디버깅하십시오. API 사양을 가져오고, 응답을 모의하고, 테스트 시나리오를 팀과 공유하세요.
Magento 2 API란 무엇인가요?
Magento 2는 전자상거래 데이터에 접근하기 위한 세 가지 API 유형을 제공합니다:
- REST API: 웹 및 모바일 애플리케이션을 위한 JSON 기반
- SOAP API: 엔터프라이즈 통합을 위한 XML 기반
- GraphQL: 효율적인 프론트엔드 애플리케이션을 위한 쿼리 기반
API로 관리 가능한 주요 항목:
- 제품, 카테고리 및 재고
- 주문, 송장 및 배송
- 고객 및 고객 그룹
- 장바구니 및 결제
- 프로모션 및 가격 규칙
- CMS 페이지 및 블록
- 상점 구성
주요 기능
| 기능 | 설명 |
|---|---|
| 다중 프로토콜 | REST, SOAP, GraphQL |
| OAuth 1.0a | 안전한 타사 액세스 |
| 토큰 인증 | 관리자 및 통합 토큰 |
| 웹훅 | 큐를 통한 비동기 작업 |
| 속도 제한 | 설치별로 구성 가능 |
| 맞춤형 엔드포인트 | 맞춤형 API로 확장 |
| 멀티 스토어 | 단일 API, 여러 스토어 뷰 |
API 비교
| API 유형 | 프로토콜 | 사용 사례 |
|---|---|---|
| REST | JSON | 모바일 앱, 통합 |
| SOAP | XML | 엔터프라이즈 시스템 (SAP 등) |
| GraphQL | GraphQL | 스토어프론트, PWA |
Magento 버전
| 버전 | 상태 | 지원 종료 |
|---|---|---|
| Magento 2.4.x | 현재 | 활성 |
| Adobe Commerce 2.4.x | 현재 | 활성 |
| Magento 1.x | EOL | 2020년 6월 (사용 금지) |
시작하기: 인증 설정
1단계: 관리자 계정 또는 통합 생성
- Magento 관리자 패널에 로그인합니다.
- 시스템(System) > 권한(Permissions) > 모든 사용자(All Users)로 이동.
- 관리자 사용자(관리자 토큰용)를 생성하거나,
- 시스템(System) > 확장 기능(Extensions) > 통합(Integrations)으로 이동.
- 새 통합(OAuth용)을 생성합니다.
2단계: 인증 방법 선택
| 방법 | 가장 적합한 용도 | 토큰 수명 |
|---|---|---|
| 관리자 토큰 | 내부 통합 | 구성 가능 (기본: 4시간) |
| 통합 토큰 | 타사 앱 | 취소될 때까지 |
| OAuth 1.0a | 공개 마켓플레이스 앱 | 취소될 때까지 |
| 고객 토큰 | 고객 대면 앱 | 구성 가능 |
3단계: 관리자 토큰 가져오기 (가장 간단한 방법)
const MAGENTO_BASE_URL = process.env.MAGENTO_BASE_URL;
const MAGENTO_ADMIN_USERNAME = process.env.MAGENTO_ADMIN_USERNAME;
const MAGENTO_ADMIN_PASSWORD = process.env.MAGENTO_ADMIN_PASSWORD;
const getAdminToken = async () => {
const response = await fetch(`${MAGENTO_BASE_URL}/rest/V1/integration/admin/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: MAGENTO_ADMIN_USERNAME,
password: MAGENTO_ADMIN_PASSWORD
})
});
if (!response.ok) throw new Error('Invalid admin credentials');
// Response is a plain string (the token), not JSON
const token = await response.text();
return token;
};
// 사용 예시
const token = await getAdminToken();
console.log(`Admin token: ${token}`);
// 이후 API 호출에 사용
보안 참고: 토큰은 반드시 안전하게 저장해야 합니다.
# .env 파일 예시
MAGENTO_BASE_URL="https://store.example.com"
MAGENTO_ADMIN_USERNAME="api_user"
MAGENTO_ADMIN_PASSWORD="secure_password_here"
MAGENTO_ACCESS_TOKEN="obtained_via_auth"
4단계: 통합 생성 (타사에 권장)
- 관리자 패널: 시스템(System) > 확장 기능(Extensions) > 통합(Integrations)
- 새 통합 추가(Add New Integration) 클릭
- 세부 정보 입력:
- 이름: "내 통합"
- 이메일: your-email@example.com
- 콜백 URL, ID 링크 URL (OAuth용)
- API 권한(API Permissions): 필요한 리소스만 선택 (제품, 주문, 고객, 재고 등)
- 저장(Save) 후, 새 통합에서 활성화(Activate) 클릭
- 액세스 토큰(Access Token) 및 토큰 시크릿(Token Secret) 복사
5단계: 고객 토큰 가져오기
const getCustomerToken = async (email, password) => {
const response = await fetch(`${MAGENTO_BASE_URL}/rest/V1/integration/customer/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: email, password: password })
});
if (!response.ok) throw new Error('Invalid customer credentials');
const token = await response.text();
return token;
};
// 사용 예시
const customerToken = await getCustomerToken('customer@example.com', 'password123');
6단계: 인증된 API 호출 수행
재사용 가능한 API 클라이언트 생성:
const magentoRequest = async (endpoint, options = {}) => {
const token = await getAdminToken(); // 또는 저장된 토큰 사용
const response = await fetch(`${MAGENTO_BASE_URL}/rest${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Magento API Error: ${error.message}`);
}
return response.json();
};
// 사용 예시
const products = await magentoRequest('/V1/products');
console.log(`Found ${products.items.length} products`);
제품 관리
제품 가져오기 (필터 적용)
const getProducts = async (filters = {}) => {
const params = new URLSearchParams();
if (filters.search) {
params.append('searchCriteria[filterGroups][0][filters][0][field]', 'sku');
params.append('searchCriteria[filterGroups][0][filters][0][value]', `%${filters.search}%`);
params.append('searchCriteria[filterGroups][0][filters][0][conditionType]', 'like');
}
if (filters.priceFrom) {
params.append('searchCriteria[filterGroups][1][filters][0][field]', 'price');
params.append('searchCriteria[filterGroups][1][filters][0][value]', filters.priceFrom);
params.append('searchCriteria[filterGroups][1][filters][0][conditionType]', 'gteq');
}
params.append('searchCriteria[pageSize]', filters.limit || 20);
params.append('searchCriteria[currentPage]', filters.page || 1);
const response = await magentoRequest(`/V1/products?${params.toString()}`);
return response;
};
// 사용 예시
const products = await getProducts({ search: 'shirt', priceFrom: 20, limit: 50 });
products.items.forEach(product => {
console.log(`${product.sku}: ${product.name} - $${product.price}`);
});
단일 제품 가져오기
const getProduct = async (sku) => {
const response = await magentoRequest(`/V1/products/${sku}`);
return response;
};
// 사용 예시
const product = await getProduct('TSHIRT-001');
console.log(`Name: ${product.name}`);
console.log(`Price: $${product.price}`);
console.log(`Stock: ${product.extension_attributes?.stock_item?.qty}`);
제품 생성
const createProduct = async (productData) => {
const product = {
product: {
sku: productData.sku,
name: productData.name,
attribute_set_id: productData.attributeSetId || 4, // 기본 세트
type_id: 'simple',
price: productData.price,
status: productData.status || 1,
visibility: productData.visibility || 4,
weight: productData.weight || 1,
extension_attributes: {
stock_item: {
qty: productData.qty || 0,
is_in_stock: productData.qty > 0 ? true : false
}
},
custom_attributes: [
{ attribute_code: 'description', value: productData.description },
{ attribute_code: 'short_description', value: productData.shortDescription },
{ attribute_code: 'color', value: productData.color },
{ attribute_code: 'size', value: productData.size }
]
}
};
const response = await magentoRequest('/V1/products', {
method: 'POST',
body: JSON.stringify(product)
});
return response;
};
// 사용 예시
const newProduct = await createProduct({
sku: 'TSHIRT-NEW-001',
name: 'Premium Cotton T-Shirt',
price: 29.99,
qty: 100,
description: 'High-quality cotton t-shirt',
shortDescription: 'Premium cotton tee',
color: 'Blue',
size: 'M'
});
console.log(`Product created: ${newProduct.id}`);
제품 업데이트
const updateProduct = async (sku, updates) => {
const product = {
product: {
sku: sku,
...updates
}
};
const response = await magentoRequest(`/V1/products/${sku}`, {
method: 'PUT',
body: JSON.stringify(product)
});
return response;
};
// 사용 예시
await updateProduct('TSHIRT-001', {
price: 24.99,
extension_attributes: {
stock_item: {
qty: 150,
is_in_stock: true
}
}
});
제품 삭제
const deleteProduct = async (sku) => {
await magentoRequest(`/V1/products/${sku}`, {
method: 'DELETE'
});
console.log(`Product ${sku} deleted`);
};
제품 유형
| 유형 | 설명 | 사용 사례 |
|---|---|---|
| 단순 | 단일 SKU, 변형 없음 | 표준 제품 |
| 구성 가능 | 하위 변형을 가진 부모 | 사이즈/색상 옵션 |
| 그룹화 | 단순 제품들의 모음 | 제품 묶음 |
| 가상 | 비물리적 제품 | 서비스, 다운로드 |
| 번들 | 사용자 정의 묶음 | DIY 키트 |
| 다운로드 | 디지털 제품 | 전자책, 소프트웨어 |
주문 관리
주문 가져오기 (필터 적용)
const getOrders = async (filters = {}) => {
const params = new URLSearchParams();
if (filters.status) {
params.append('searchCriteria[filterGroups][0][filters][0][field]', 'status');
params.append('searchCriteria[filterGroups][0][filters][0][value]', filters.status);
params.append('searchCriteria[filterGroups][0][filters][0][conditionType]', 'eq');
}
if (filters.dateFrom) {
params.append('searchCriteria[filterGroups][1][filters][0][field]', 'created_at');
params.append('searchCriteria[filterGroups][1][filters][0][value]', filters.dateFrom);
params.append('searchCriteria[filterGroups][1][filters][0][conditionType]', 'gteq');
}
params.append('searchCriteria[pageSize]', filters.limit || 20);
params.append('searchCriteria[currentPage]', filters.page || 1);
const response = await magentoRequest(`/V1/orders?${params.toString()}`);
return response;
};
// 사용 예시
const orders = await getOrders({
status: 'pending',
dateFrom: '2026-03-18 00:00:00',
limit: 50
});
orders.items.forEach(order => {
console.log(`Order #${order.increment_id}: ${order.customer_email} - $${order.grand_total}`);
});
단일 주문 가져오기
const getOrder = async (orderId) => {
const response = await magentoRequest(`/V1/orders/${orderId}`);
return response;
};
// 사용 예시
const order = await getOrder(12345);
console.log(`Order #${order.increment_id}`);
console.log(`Status: ${order.status}`);
console.log(`Total: $${order.grand_total}`);
console.log(`Items:`);
order.items.forEach(item => {
console.log(` - ${item.name} x ${item.qty_ordered}`);
});
주문 상태 흐름
대기 중 (pending) → 처리 중 (processing) → 완료 (complete)
→ 취소됨 (canceled)
→ 보류 중 (on_hold)
→ 결제 검토 (payment_review)
주문 상태 업데이트
const updateOrderStatus = async (orderId, newStatus) => {
// 직접 상태 변경은 커스텀 엔드포인트 필요
// 기본 제공 워크플로 예시:
// 취소
await magentoRequest(`/V1/orders/${orderId}/cancel`, { method: 'POST' });
// 보류
await magentoRequest(`/V1/orders/${orderId}/hold`, { method: 'POST' });
// 보류 해제
await magentoRequest(`/V1/orders/${orderId}/unhold`, { method: 'POST' });
};
송장 생성
const createInvoice = async (orderId, items = [], notify = true, appendComment = false, comment = null) => {
const invoice = {
capture: true,
last: true,
items: items
};
if (comment) {
invoice.comment = comment;
invoice.notify_customer = notify ? 1 : 0;
invoice.append_comment = appendComment ? 1 : 0;
}
const response = await magentoRequest(`/V1/order/${orderId}/invoice`, {
method: 'POST',
body: JSON.stringify(invoice)
});
return response;
};
// 사용 예시
const invoiceId = await createInvoice(12345, [], true, false, 'Thank you for your order!');
console.log(`Invoice created: ${invoiceId}`);
배송 생성
const createShipment = async (orderId, items = [], notify = true, appendComment = false, comment = null, tracks = []) => {
const shipment = {
items: items,
notify: notify ? 1 : 0,
append_comment: appendComment ? 1 : 0,
comment: comment,
tracks: tracks
};
const response = await magentoRequest(`/V1/order/${orderId}/ship`, {
method: 'POST',
body: JSON.stringify(shipment)
});
return response;
};
// 사용 예시
const shipmentId = await createShipment(12345, [], true, false, 'Your order has shipped!', [
{
track_number: '1Z999AA10123456784',
title: 'Tracking Number',
carrier_code: 'ups'
}
]);
console.log(`Shipment created: ${shipmentId}`);
고객 관리
고객 가져오기
const getCustomers = async (filters = {}) => {
const params = new URLSearchParams();
if (filters.email) {
params.append('searchCriteria[filterGroups][0][filters][0][field]', 'email');
params.append('searchCriteria[filterGroups][0][filters][0][value]', filters.email);
params.append('searchCriteria[filterGroups][0][filters][0][conditionType]', 'eq');
}
params.append('searchCriteria[pageSize]', filters.limit || 20);
const response = await magentoRequest(`/V1/customers/search?${params.toString()}`);
return response;
};
// 사용 예시
const customers = await getCustomers({ email: 'customer@example.com' });
customers.items.forEach(customer => {
console.log(`${customer.firstname} ${customer.lastname} - ${customer.email}`);
});
고객 생성
const createCustomer = async (customerData) => {
const customer = {
customer: {
websiteId: customerData.websiteId || 1,
email: customerData.email,
firstname: customerData.firstname,
lastname: customerData.lastname,
middlename: customerData.middlename || '',
gender: customerData.gender || 0,
store_id: customerData.storeId || 0,
extension_attributes: {
is_subscribed: customerData.subscribed || false
}
},
password: customerData.password
};
const response = await magentoRequest('/V1/customers', {
method: 'POST',
body: JSON.stringify(customer)
});
return response;
};
// 사용 예시
const newCustomer = await createCustomer({
email: 'newcustomer@example.com',
firstname: 'John',
lastname: 'Doe',
password: 'SecurePass123!',
subscribed: true
});
console.log(`Customer created: ID ${newCustomer.id}`);
재고 관리 (MSI)
재고 상태 가져오기
const getStockStatus = async (sku) => {
const response = await magentoRequest(`/V1/products/${sku}/stockItems/1`);
return response;
};
// 사용 예시
const stock = await getStockStatus('TSHIRT-001');
console.log(`Qty: ${stock.qty}`);
console.log(`In Stock: ${stock.is_in_stock}`);
console.log(`Min Qty: ${stock.min_qty}`);
재고 업데이트
const updateStock = async (sku, qty, isInStock = null) => {
const stockItem = {
stockItem: {
qty: qty,
is_in_stock: isInStock !== null ? isInStock : qty > 0
}
};
const response = await magentoRequest(`/V1/products/${sku}/stockItems/1`, {
method: 'PUT',
body: JSON.stringify(stockItem)
});
return response;
};
// 사용 예시
await updateStock('TSHIRT-001', 100, true);
웹훅 및 비동기 작업
웹훅 설정
Magento는 네이티브 웹훅을 제공하지 않으므로 다음 방법으로 대체하세요.
// 1. 주기적으로 주문 엔드포인트 폴링
const pollNewOrders = async (lastOrderId) => {
const orders = await getOrders({
dateFrom: new Date().toISOString()
});
const newOrders = orders.items.filter(o => o.id > lastOrderId);
return newOrders;
};
// 2. Adobe I/O Events (Adobe Commerce 한정 사용)
// Adobe Developer Console에서 이벤트 구성
// 3. 커스텀 웹훅 모듈 생성
// 참고: https://devdocs.magento.com/guides/v2.4/extension-dev-guide/message-queues/message-queues.html
속도 제한
속도 제한 이해하기
- 기본값: 제한 없음 (관리자에서 구성)
- 권장: 100-1000 요청/분
설정 위치: 스토어(Stores) > 구성(Configuration) > 서비스(Services) > 웹 API(Web API) > 보안(Security)
속도 제한 처리 구현
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await magentoRequest(endpoint, options);
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
운영 배포 체크리스트
실제 운영 배포 전, 반드시 아래 항목을 점검하세요.
- [ ] 운영 환경에서는 통합 토큰 사용 (관리자 자격 증명 사용 금지)
- [ ] 토큰 안전 저장 (암호화된 DB 등)
- [ ] 속도 제한/요청 큐 구현
- [ ] 포괄적 오류 처리 추가
- [ ] API 호출 로깅 구성
- [ ] 웹훅 대안 마련 (폴링 또는 Adobe I/O)
- [ ] 운영 데이터 볼륨으로 테스트
- [ ] 실패 요청 재시도 로직 구현
실제 사용 사례
ERP 통합
- 과제: ERP와 Magento 간의 수동 재고 업데이트
- 해결책: 15분마다 양방향 API 동기화
- 결과: 실시간 재고, 과매도 없음
모바일 앱
- 과제: 네이티브 모바일 경험 필요
- 해결책: 제품 탐색은 GraphQL, 결제 등은 REST API 사용
- 결과: 모바일 전환율 40% 증가
결론
Magento 2 API는 전자상거래 자동화를 위한 포괄적인 기능을 제공합니다.
- REST, SOAP, GraphQL API로 유연한 통합
- 토큰 기반 인증
- 제품/주문/고객 CRUD 지원
- MSI 기반 고급 재고 관리
- 설치별 속도 제한
- Apidog으로 API 테스트 및 협업 자동화
자주 묻는 질문 (FAQ) 섹션
Magento API로 어떻게 인증하나요?
내부 통합에는 관리자 토큰을 사용하거나, 시스템(System) > 확장(Extensions)에서 OAuth를 위한 통합을 생성하세요. 고객 대면 앱에는 고객 토큰을 사용하세요.
Magento의 REST와 GraphQL의 차이점은 무엇인가요?
REST는 완전한 CRUD 작업을 제공합니다. GraphQL은 효율적인 데이터 가져오기를 통해 프론트엔드 쿼리에 최적화되어 있습니다.
API를 통해 제품을 어떻게 생성하나요?
제품 데이터(SKU, 이름, 가격, extension_attributes의 stock_item 포함)를 가지고 /V1/products에 POST 요청을 합니다.
새 주문에 대한 웹훅을 받을 수 있나요?
Magento에는 네이티브 웹훅이 없습니다. 폴링, Adobe I/O 이벤트(Adobe Commerce), 또는 맞춤형 모듈을 생성하는 방법을 사용하십시오.
재고 수량을 어떻게 업데이트하나요?
/V1/products/{sku}/stockItems/1에 qty 및 is_in_stock 값을 포함하여 PUT 요청을 합니다.
Top comments (0)