TL;DR
iPay API를 통해 개발자는 결제 처리, 인보이스 발행, 금융 거래를 프로그래밍 방식으로 통합할 수 있습니다. 이 API는 OAuth 2.0 및 API 키 인증 방식을 사용하며, 결제, 환불, 거래, 정산을 위한 RESTful 엔드포인트를 제공하고, PCI DSS 규정 준수 요구사항 및 업계 표준 속도 제한을 따릅니다. 이 가이드에서는 인증 설정, 결제 처리, 웹훅 통합, 보안 규정 준수, 그리고 프로덕션 배포 전략을 다룹니다.
소개
디지털 결제 처리는 매년 전 세계적으로 8조 달러 이상을 처리합니다. 전자상거래 플랫폼, SaaS 애플리케이션 또는 마켓플레이스 솔루션을 구축하는 개발자에게 결제 API 통합은 선택 사항이 아니라, 고객 결제를 안전하고 규정을 준수하며 수락하기 위한 필수 요소입니다.
현실은 이렇습니다: 기업들은 실패한 결제, 수동 정산, 결제 사기로 인해 매출의 5-10%를 손실합니다. 견고한 결제 API 통합은 결제 처리를 자동화하고, 스마트한 재시도 로직으로 실패율을 줄이며, 자동 정산을 가능하게 하고, 사기 탐지를 구현합니다.
이 가이드에서는 전체 결제 API 통합 프로세스를 안내합니다. 인증 설정, 결제 처리, 환불 관리, 웹훅 처리, PCI DSS 규정 준수, 보안 모범 사례, 그리고 프로덕션 배포 전략을 배우게 됩니다. 마지막에는 프로덕션 준비가 완료된 결제 통합을 갖추게 될 것입니다.
참고: 이 가이드는 iPay 및 유사한 결제 처리기에 적용될 수 있는 일반적인 결제 API 통합 패턴을 다룹니다. 특정 엔드포인트 URL 및 인증 세부 정보는 다를 수 있습니다. 항상 iPay 공식 문서를 참조하여 구현 세부 정보를 확인하세요.
iPay API란 무엇인가요?
iPay와 같은 결제 API는 금융 거래를 처리하기 위한 RESTful 인터페이스를 제공합니다. 이 API는 다음을 처리합니다:
- 결제 승인 및 캡처
- 환불 및 차지백
- 거래 내역 및 보고
- 고객 토큰화 (금고)
- 구독 및 반복 청구
- 인보이스 생성 및 관리
- 정산 및 결제
- 사기 탐지 및 예방
주요 기능
| 기능 | 설명 |
|---|---|
| RESTful API | JSON 기반 엔드포인트 |
| OAuth 2.0 + API 키 | 보안 인증 |
| 웹훅 | 실시간 결제 알림 |
| 토큰화 | 보안 카드 저장 |
| 3D Secure | SCA 규정 준수 |
| PCI DSS | 레벨 1 규정 준수 필수 |
| 다중 통화 | 100개 이상의 통화 지원 |
| 사기 방지 도구 | 위험 점수, 속도 확인 |
결제 흐름 개요
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 고객 │───▶│ 판매자 │───▶│ 결제 │
│ (브라우저) │ │ (서버) │ │ 게이트웨이 │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ 1. 카드 입력 │ │
│───────────────────▶│ │
│ │ │
│ 2. 토큰화 │ │
│───────────────────▶│ 3. 결제 의도 생성 │
│ │───────────────────▶│
│ │ │
│ │ 4. 결제 확인 │
│ │───────────────────▶│
│ │ │
│ │ 5. 결과 │
│ │◀───────────────────│
│ │ │
│ 6. 영수증 │ │
│◀───────────────────│ │
API 환경
| 환경 | URL | 사용 사례 |
|---|---|---|
| 샌드박스 | https://sandbox.ipay.com/api |
개발, 테스트 |
| 프로덕션 | https://api.ipay.com/api |
실제 거래 |
시작하기: 인증 설정
1단계: iPay 계정 생성
API에 접근하기 전에:
- iPay 판매자 등록 페이지 방문
- 사업자 인증(KYB) 완료
- 필요한 서류 제출:
- 사업자 등록증
- 은행 계좌 정보
- 정부 발행 신분증
- 승인 대기 (1-3 영업일)
2단계: API 자격 증명 받기
API 자격 증명 생성:
- iPay 판매자 대시보드 로그인
- 설정 > API 키로 이동
- 새 API 키 생성
- 자격 증명을 안전하게 복사
# .env 파일 (절대 git에 커밋하지 마세요)
IPAY_API_KEY="live_xxxxxxxxxxxxxxxxxxxx"
IPAY_API_SECRET="secret_xxxxxxxxxxxxxxxxxxxx"
IPAY_WEBHOOK_SECRET="whsec_xxxxxxxxxxxxxxxxxxxx"
보안 참고: 샌드박스와 프로덕션 환경에 각각 다른 키를 사용하세요.
3단계: 인증 방법 이해
iPay는 여러 인증 방법을 지원합니다:
| 방법 | 가장 적합한 용도 | 보안 수준 |
|---|---|---|
| 기본 인증 (Basic Auth) | 서버-서버 통신 | 높음 |
| OAuth 2.0 | 다중 테넌트 앱 | 더 높음 |
| JWT | 마이크로서비스 | 높음 |
4단계: 인증된 API 호출 수행
재사용 가능한 API 클라이언트 생성:
const IPAY_BASE_URL = process.env.IPAY_SANDBOX
? 'https://sandbox.ipay.com/api'
: 'https://api.ipay.com/api';
const ipayRequest = async (endpoint, options = {}) => {
const apiKey = process.env.IPAY_API_KEY;
const apiSecret = process.env.IPAY_API_SECRET;
// 기본 인증 (Base64 인코딩)
const authHeader = Buffer.from(`${apiKey}:${apiSecret}`).toString('base64');
const response = await fetch(`${IPAY_BASE_URL}${endpoint}`, {
...options,
headers: {
'Authorization': `Basic ${authHeader}`,
'Content-Type': 'application/json',
'Idempotency-Key': options.idempotencyKey || generateIdempotencyKey(),
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`iPay API Error: ${error.message}`);
}
return response.json();
};
function generateIdempotencyKey() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 사용 예시
const account = await ipayRequest('/account');
console.log(`판매자: ${account.business_name}`);
결제 처리
결제 의도 생성
결제 초기화:
const createPayment = async (paymentData) => {
const payment = {
amount: paymentData.amount, // 가장 작은 통화 단위 (센트)
currency: paymentData.currency || 'USD',
customer: paymentData.customerId,
payment_method: paymentData.paymentMethodId,
confirm: true,
description: paymentData.description,
metadata: {
orderId: paymentData.orderId,
customerId: paymentData.customerId
},
capture_method: paymentData.captureMethod || 'automatic', // 'automatic' 또는 'manual'
statement_descriptor: paymentData.statementDescriptor || 'MYCOMPANY'
};
const response = await ipayRequest('/payments', {
method: 'POST',
body: JSON.stringify(payment),
idempotencyKey: paymentData.idempotencyKey
});
return response;
};
// 사용 예시
const payment = await createPayment({
amount: 2999, // $29.99
currency: 'USD',
customerId: 'cus_12345',
paymentMethodId: 'pm_67890',
description: '주문 #ORD-001',
orderId: 'ORD-001',
statementDescriptor: 'MYCOMPANY INC'
});
console.log(`결제 상태: ${payment.status}`);
console.log(`결제 ID: ${payment.id}`);
결제 상태 흐름
requires_payment_method → requires_confirmation → requires_action
→ processing → requires_capture → succeeded
→ failed
→ canceled
결제 수단
| 방법 | 유형 | 사용 사례 |
|---|---|---|
card |
신용/직불카드 | 표준 결제 |
bank_transfer |
ACH, SEPA | 저렴한 수수료 이체 |
digital_wallet |
Apple Pay, Google Pay | 모바일 결제 |
buy_now_pay_later |
Klarna, Afterpay | 할부 결제 |
카드 정보 토큰화
향후 사용을 위해 카드를 안전하게 저장:
const tokenizeCard = async (cardData) => {
// 절대로 원시 카드 데이터를 서버로 보내지 마세요
// 대신 클라이언트 측 토큰화를 사용하세요
// 클라이언트 측 (브라우저/모바일)
const response = await fetch(`${IPAY_BASE_URL}/tokens`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CLIENT_PUBLISHABLE_KEY}`
},
body: JSON.stringify({
card: {
number: cardData.number,
exp_month: cardData.expMonth,
exp_year: cardData.expYear,
cvc: cardData.cvc
}
})
});
const token = await response.json();
return token; // token.id를 서버로 보냅니다
};
// 서버 측: 토큰을 사용하여 결제 수단 생성
const createPaymentMethod = async (tokenId, customerId) => {
const response = await ipayRequest('/payment_methods', {
method: 'POST',
body: JSON.stringify({
type: 'card',
token: tokenId,
customer: customerId
})
});
return response;
};
3D Secure 인증
SCA 규정 준수 구현:
const createPaymentWith3DS = async (paymentData) => {
const payment = await createPayment({
...paymentData,
confirmation_token: true // 3DS를 위한 클라이언트 시크릿 반환
});
if (payment.status === 'requires_action') {
// 클라이언트는 3DS 챌린지를 완료해야 합니다
return {
requiresAction: true,
clientSecret: payment.client_secret,
nextAction: payment.next_action
};
}
return { success: true, payment };
};
// 클라이언트 측: 3DS 챌린지 처리
// iPay.js 또는 모바일 SDK를 사용하여 인증 챌린지 표시
환불 관리
전액 환불 처리
전체 결제 환불:
const refundPayment = async (paymentId, reason = null) => {
const refund = {
payment: paymentId,
reason: reason || 'requested_by_customer'
};
const response = await ipayRequest('/refunds', {
method: 'POST',
body: JSON.stringify(refund),
idempotencyKey: `refund_${paymentId}_${Date.now()}`
});
return response;
};
// 사용 예시
const refund = await refundPayment('pay_12345', 'duplicate');
console.log(`환불 상태: ${refund.status}`);
console.log(`환불 ID: ${refund.id}`);
부분 환불 처리
결제 금액 일부 환불:
const partialRefund = async (paymentId, amount, reason = null) => {
const refund = {
payment: paymentId,
amount: amount, // 가장 작은 통화 단위
reason: reason || 'requested_by_customer'
};
const response = await ipayRequest('/refunds', {
method: 'POST',
body: JSON.stringify(refund),
idempotencyKey: `refund_${paymentId}_${amount}_${Date.now()}`
});
return response;
};
// 사용 예시 - $29.99 결제 중 $15.00 환불
const refund = await partialRefund('pay_12345', 1500, 'partial_ship');
console.log(`환불 금액: $${refund.amount / 100}`);
환불 사유
| 사유 코드 | 설명 |
|---|---|
duplicate |
중복 청구 |
fraudulent |
사기성 거래 |
requested_by_customer |
고객 요청 |
order_canceled |
주문 취소 |
product_not_received |
상품 미수령 |
product_not_as_described |
설명과 다른 상품 |
고객 관리
고객 생성
반복 결제를 위해 고객 저장:
const createCustomer = async (customerData) => {
const customer = {
email: customerData.email,
name: customerData.name,
phone: customerData.phone,
metadata: {
internalId: customerData.internalId,
tier: customerData.tier
}
};
const response = await ipayRequest('/customers', {
method: 'POST',
body: JSON.stringify(customer)
});
return response;
};
// 사용 예시
const customer = await createCustomer({
email: 'customer@example.com',
name: 'John Doe',
phone: '+1-555-0123',
internalId: 'USR-12345',
tier: 'premium'
});
console.log(`고객 생성: ${customer.id}`);
고객에게 결제 수단 연결
향후 사용을 위해 카드 저장:
const attachPaymentMethod = async (paymentMethodId, customerId) => {
const response = await ipayRequest(`/payment_methods/${paymentMethodId}/attach`, {
method: 'POST',
body: JSON.stringify({
customer: customerId
})
});
return response;
};
// 사용 예시
await attachPaymentMethod('pm_67890', 'cus_12345');
고객 결제 수단 목록 가져오기
저장된 카드 가져오기:
const getCustomerPaymentMethods = async (customerId) => {
const response = await ipayRequest(`/customers/${customerId}/payment_methods`);
return response;
};
// 사용 예시
const methods = await getCustomerPaymentMethods('cus_12345');
methods.data.forEach(method => {
console.log(`${method.card.brand} 마지막 4자리: ${method.card.last4}`);
console.log(`만료일: ${method.card.exp_month}/${method.card.exp_year}`);
});
웹훅
웹훅 구성
웹훅 엔드포인트 설정:
- iPay 대시보드에 로그인
- 개발자 > 웹훅으로 이동
- 엔드포인트 추가 클릭
- HTTPS URL 입력
- 구독할 이벤트 선택
웹훅 이벤트
| 이벤트 | 트리거 |
|---|---|
payment.succeeded |
결제 완료 |
payment.failed |
결제 거부 |
payment.refunded |
환불 처리됨 |
payment.disputed |
차지백 접수됨 |
customer.created |
새 고객 생성 |
customer.subscription.updated |
구독 변경됨 |
웹훅 처리
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/ipay', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['ipay-signature'];
const payload = req.body;
// 웹훅 서명 검증
const isValid = verifyWebhookSignature(payload, signature, process.env.IPAY_WEBHOOK_SECRET);
if (!isValid) {
console.error('유효하지 않은 웹훅 서명');
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(payload.toString());
// 적절한 핸들러로 라우팅
switch (event.type) {
case 'payment.succeeded':
await handlePaymentSucceeded(event.data);
break;
case 'payment.failed':
await handlePaymentFailed(event.data);
break;
case 'payment.refunded':
await handlePaymentRefunded(event.data);
break;
case 'payment.disputed':
await handlePaymentDisputed(event.data);
break;
default:
console.log('처리되지 않은 이벤트 유형:', event.type);
}
// 수신 확인
res.status(200).send('OK');
});
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
async function handlePaymentSucceeded(data) {
console.log(`결제 성공: ${data.id}`);
// 주문 상태 업데이트
await db.orders.update(data.metadata.orderId, {
status: 'paid',
paymentId: data.id,
paidAt: new Date()
});
// 확인 이메일 전송
await sendOrderConfirmation(data.metadata.orderId);
}
async function handlePaymentFailed(data) {
console.log(`결제 실패: ${data.id} - ${data.failure_code}`);
// 고객에게 알림
await sendPaymentFailedEmail(data.customer, data.failure_message);
// 재시도 로직 또는 주문을 실패로 표시
await db.orders.update(data.metadata.orderId, {
status: 'payment_failed',
failureReason: data.failure_message
});
}
보안 및 규정 준수
PCI DSS 요구사항
결제 통합은 PCI DSS를 준수해야 합니다:
| 요구사항 | 구현 |
|---|---|
| 보안 네트워크 | HTTPS, 방화벽, 보안 구성 사용 |
| 카드 소유자 데이터 보호 | 절대 CVV를 저장하지 않고, PAN 암호화 |
| 취약점 관리 | 정기적인 보안 업데이트, 안티바이러스 |
| 접근 제어 | 최소 권한, MFA, 고유 ID |
| 모니터링 | 로깅, 침입 탐지 |
| 보안 정책 | 문서화된 정책, 정기 교육 |
보안 모범 사례
// 1. 토큰화 사용 - 절대 원시 카드 데이터를 다루지 마세요
const token = await tokenizeCard(cardData); // 클라이언트 측
// 2. 모든 결제 작업에 대해 멱등성(idempotency) 구현
const idempotencyKey = `pay_${orderId}_${Date.now()}`;
// 3. 서버 측에서 금액 유효성 검사
if (req.body.amount !== calculatedAmount) {
throw new Error('금액 불일치 - 변조 가능성 있음');
}
// 4. 모든 결제 작업 로깅 (민감한 데이터 없이)
logger.info('결제 시도', {
orderId,
amount,
currency,
customerId,
timestamp: new Date().toISOString()
// 절대로 로깅하지 마세요: 카드 번호, CVV, 전체 결제 수단 정보
});
// 5. 시크릿(secret)을 위해 환경 변수 사용
const apiKey = process.env.IPAY_API_KEY; // 하드코딩하지 않음
// 6. 결제 엔드포인트에 속도 제한 구현
const paymentLimiter = rateLimit({
windowMs: 60000,
max: 10 // 분당 10회 결제 시도 제한
});
프로덕션 배포 체크리스트
실제 결제를 처리하기 전에:
- [ ] PCI DSS 자체 평가 설문지 완료
- [ ] 모든 엔드포인트에 HTTPS 사용
- [ ] API 키를 안전한 시크릿 관리 시스템에 저장
- [ ] 웹훅 서명 검증 구현
- [ ] 모든 결제 작업에 멱등성(idempotency) 추가
- [ ] 포괄적인 로깅 설정 (민감한 데이터 없음)
- [ ] 사기 탐지 규칙 구성
- [ ] 환불 및 분쟁 흐름 테스트
- [ ] 결제 실패를 위한 런북 생성
- [ ] 모니터링 및 알림 설정
- [ ] 백업 결제 처리기 구현
실제 사용 사례
전자상거래 결제
온라인 소매업체가 결제를 통합합니다:
- 과제: 수동 결제 처리, 높은 이탈률
- 해결책: 토큰화된 카드를 이용한 원 페이지 결제
- 결과: 전환율 35% 증가, 즉각적인 결제
SaaS 구독 청구
소프트웨어 회사가 청구를 자동화합니다:
- 과제: 수동 인보이스 생성 및 수금
- 해결책: 자동 재시도 기능을 갖춘 반복 결제
- 결과: 95% 정시 결제, 관리 시간 80% 절감
마켓플레이스 에스크로
플랫폼이 다자간 결제를 처리합니다:
- 과제: 판매자 간의 복잡한 분할 결제
- 해결책: 송금 일정이 포함된 결제 의도
- 결과: 자동화된 판매자 지급, 사기 감소
결론
결제 API 통합은 보안, 규정 준수 및 오류 처리에 대한 세심한 주의가 필요합니다. 주요 요점:
- 원시 카드 데이터를 절대 다루지 마세요—토큰화를 사용하세요
- 모든 결제 작업에 멱등성(idempotency)을 구현하세요
- 사기를 방지하기 위해 웹훅 서명을 확인하세요
- PCI DSS 요구사항을 준수하세요
- 프로덕션 환경 전에 샌드박스에서 철저히 테스트하세요
- Apidog는 API 테스트 및 팀 협업을 간소화합니다
자주 묻는 질문 (FAQ)
iPay API로 어떻게 인증하나요?
API 키와 시크릿을 사용하는 기본 인증(Basic authentication) 또는 다중 테넌트 애플리케이션을 위한 OAuth 2.0을 사용하세요.
고객 카드 정보를 저장할 수 있나요?
네, 하지만 PCI DSS를 준수해야 합니다. iPay의 금고에 카드를 안전하게 저장하기 위해 토큰화를 사용하세요.
실패한 결제는 어떻게 처리하나요?
지수 백오프(exponential backoff)를 사용하여 재시도 로직을 구현하고, 고객에게 알림을 보내며, 대체 결제 수단을 제공하세요.
멱등성(Idempotency)은 무엇이며 왜 중요한가요?
멱등성은 동일한 키를 가진 중복 요청이 동일한 결과를 생성하도록 보장하여 중복 청구를 방지합니다.
카드를 청구하지 않고 결제를 테스트하려면 어떻게 해야 하나요?
iPay 문서에 제공된 테스트 카드 번호를 사용하여 샌드박스 모드를 사용하세요.
웹훅 서명이란 무엇인가요?
웹훅이 악의적인 행위자가 아닌 iPay에서 전송되었음을 확인하는 암호화 서명입니다.
