TL;DR
iPay API를 통해 개발자는 결제 처리, 인보이스 발행, 금융 거래를 프로그래밍 방식으로 통합할 수 있습니다. 이 API는 OAuth 2.0 및 API 키 인증 방식을 사용하며, 결제, 환불, 거래, 정산을 위한 RESTful 엔드포인트를 제공합니다. PCI DSS 규정 준수 및 업계 표준 속도 제한을 준수합니다. 본 가이드에서는 인증 설정, 결제 처리, 웹훅 통합, 보안 규정 준수, 프로덕션 배포 전략에 대해 실무적으로 설명합니다.
소개
디지털 결제 처리는 전 세계적으로 매년 8조 달러 이상을 처리합니다. 전자상거래, SaaS, 마켓플레이스 등에서 결제 API 통합은 필수입니다. 실패한 결제, 수동 정산, 결제 사기로 인해 매출의 5~10%가 손실됩니다. 견고한 결제 API 통합은 결제를 자동화하고, 실패율을 줄이며, 자동 정산 및 사기 탐지를 구현할 수 있습니다.
이 가이드에서는 인증, 결제 처리, 환불 관리, 웹훅 처리, PCI DSS 준수, 보안, 프로덕션 배포 전략까지 단계별로 실무 적용법을 다룹니다.
💡 Apidog는 결제 API 테스트를 간소화합니다. 샌드박스 모드에서 결제 엔드포인트를 테스트하고, 웹훅 서명을 검증하며, 거래 응답을 검사하고, 통합 문제를 한 곳에서 디버깅할 수 있습니다. API 사양을 가져오고, 응답을 모의하며, 테스트 시나리오를 팀과 공유하세요.
참고: 이 가이드는 iPay 및 유사 결제 처리기의 일반적인 API 통합 패턴을 다룹니다. 엔드포인트 URL·인증 세부 정보는 공식 문서를 참고하세요.
iPay API란 무엇인가요?
iPay와 같은 결제 API는 금융 거래 처리를 위한 RESTful 인터페이스를 제공하며, 아래 기능을 지원합니다:
- 결제 승인/캡처
- 환불/차지백
- 거래 내역/보고
- 고객 토큰화(금고)
- 구독 및 반복 청구
- 인보이스 생성/관리
- 정산/결제
- 사기 탐지 및 예방
주요 기능
| 기능 | 설명 |
|---|---|
| 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 계정 생성
- iPay 판매자 등록 페이지에서 회원가입
- 사업자 인증(KYB) 제출
- 필요한 서류 제출 (사업자 등록증, 은행 계좌 정보, 신분증)
- 승인 대기 (1~3 영업일)
2단계: API 자격 증명 받기
- iPay 대시보드 로그인
- 설정 → API 키 이동
- 새 API 키 생성
- 키/시크릿 안전하게 복사 및 보관
# .env 예시 (git에 커밋 금지)
IPAY_API_KEY="live_xxxxxxxxxxxxxxxxxxxx"
IPAY_API_SECRET="secret_xxxxxxxxxxxxxxxxxxxx"
IPAY_WEBHOOK_SECRET="whsec_xxxxxxxxxxxxxxxxxxxx"
보안 팁: 샌드박스와 프로덕션 환경에서 각각 별도의 키를 사용하세요.
3단계: 인증 방법 이해
| 방법 | 적합 용도 | 보안 수준 |
|---|---|---|
| 기본 인증 | 서버-서버 통신 | 높음 |
| OAuth 2.0 | 다중 테넌트 앱 | 매우 높음 |
| JWT | 마이크로서비스 | 높음 |
4단계: 인증된 API 호출 수행
재사용 가능한 API 클라이언트 예제 (Node.js):
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;
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',
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/Google Pay | 모바일 결제 |
buy_now_pay_later |
Klarna 등 | 할부 결제 |
카드 정보 토큰화
클라이언트 측에서 카드 토큰화 → 서버에 토큰 전달:
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 인증
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 };
};
// 클라이언트: iPay.js나 모바일 SDK로 3DS 인증 처리
환불 관리
전액 환불 처리
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 |
구독 변경 |
웹훅 처리 예시 (Express.js)
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 요구사항
| 요구사항 | 구현 예시 |
|---|---|
| 보안 네트워크 | 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. 환경 변수로 시크릿 관리 (하드코딩 금지)
const apiKey = process.env.IPAY_API_KEY;
// 6. 결제 엔드포인트에 속도 제한 적용
const paymentLimiter = rateLimit({
windowMs: 60000,
max: 10 // 분당 10회 시도 제한
});
프로덕션 배포 체크리스트
실거래 전 아래 체크리스트 점검:
- [ ] PCI DSS 자체 평가 설문지 제출
- [ ] 모든 엔드포인트 HTTPS 적용
- [ ] API 키를 시크릿 관리 시스템에 저장
- [ ] 웹훅 서명 검증 구현
- [ ] 모든 결제 작업에 멱등성 적용
- [ ] 민감하지 않은 포괄적 로깅
- [ ] 사기 탐지 규칙 적용
- [ ] 환불/분쟁 흐름 테스트
- [ ] 결제 실패 대응 런북 준비
- [ ] 모니터링 및 알림 구성
- [ ] 백업 결제 처리기 준비
실제 사용 사례
전자상거래 결제
- 과제: 수동 결제 처리, 이탈률 높음
- 해결책: 토큰화 카드로 원페이지 결제 구현
- 결과: 전환율 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에서 보낸 웹훅임을 암호화 서명으로 검증하는 방식입니다.
Top comments (0)