마카롱 페이먼츠 API

Production Ready

간편하고 안전한 결제 시스템 통합

마카롱 페이먼츠 API를 사용하여 전자상거래 사이트에 결제 기능을 쉽게 통합하세요.
실시간 매칭 시스템과 웹훅 알림을 제공합니다.

📚 목차

📋 개요

마카롱 페이먼츠는 전자상거래 사이트를 위한 안전하고 간편한 결제 시스템입니다.

주요 특징

  • • 실시간 입금/출금 매칭
  • 부분 매칭 지원 (여러 건 매칭 가능)
  • 10분 검증 타임아웃
  • • 2분 지연 웹훅 알림 시스템
  • • RESTful API 설계
  • • 토큰 기반 인증

지원 기능

  • • 입금 요청 생성 및 관리
  • • 출금 요청 생성 및 관리
  • • 실시간 상태 확인 (매칭/잔여 금액)
  • • 웹훅 기반 알림
  • • OCR 스크린샷 검증
  • 사이트별 매칭 격리

매칭 시스템

  • 부분 매칭: 2000원 출금이 1000원 입금 2건과 매칭 가능
  • 정확한 금액 우선: 같은 금액끼리 먼저 매칭
  • 검증 타임아웃: 10분 내 미검증 시 자동 취소 및 재매칭
  • 사이트 격리: 동일 사이트 내에서만 매칭하여 보안 강화

🔐 인증

토큰 발급

POST /v1/site-auth/token

요청 예제

{
  "siteId": "your-site-id",
  "siteSecret": "your-site-secret"
}

🔌 API 엔드포인트

입금 요청

POST /v1/deposit-requests

필수 파라미터

  • siteUserId: 사이트 사용자 ID
  • amount: 입금 금액
  • depositRequestId: 고유 요청 ID

출금 요청

POST /v1/withdrawal-requests

필수 파라미터

  • siteUserId: 사이트 사용자 ID
  • amount: 출금 금액
  • kakaoPayUrl: 카카오페이 송금 URL
  • kakaoPayName: 카카오페이 등록 이름 (OCR 검증용)
  • withdrawalRequestId: 고유 요청 ID

📄 응답 형식

입금/출금 요청 응답

{
  "success": true,
  "data": {
    "depositRequestId": "dep_123456789",
    "siteId": "site_abc",
    "siteUserId": "user_123",
    "depositAmount": 10000,
    "matchedAmount": 5000,      // 매칭된 금액
    "remainingAmount": 5000,    // 잔여 금액
    "status": "MATCHED",
    "webhookUrl": "https://example.com/webhook",
    "MatchLog": [
      {
        "matchLogId": "match_xxx",
        "amount": 5000,
        "status": "VERIFICATION_PENDING"
      }
    ],
    "createdAt": "2025-01-22T10:00:00Z",
    "updatedAt": "2025-01-22T10:05:00Z"
  }
}

💡 부분 매칭: 하나의 입출금 요청이 여러 건과 매칭될 수 있습니다.matchedAmount는 현재까지 매칭된 총 금액,remainingAmount는 아직 매칭되지 않은 잔여 금액입니다.

오류 응답

{
  "success": false,
  "error": {
    "code": "INVALID_AMOUNT",
    "message": "입금 금액이 유효하지 않습니다.",
    "details": "최소 1,000원 이상이어야 합니다."
  }
}

⚠️ 오류 처리

주요 오류 코드

INVALID_TOKEN토큰이 유효하지 않음
INVALID_AMOUNT금액이 유효하지 않음
DUPLICATE_REQUEST중복된 요청 ID
RATE_LIMITED요청 횟수 제한 초과

권장 처리 방법

  • • HTTP 상태 코드와 함께 오류 코드를 확인하세요
  • • 토큰 만료 시 자동으로 재발급하세요
  • • 일시적 오류는 지수 백오프로 재시도하세요
  • • 사용자에게 명확한 오류 메시지를 제공하세요

💻 코드 예제

// 토큰 발급
async function getSiteToken(siteId, siteSecret) {
  const response = await fetch('/v1/site-auth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ siteId, siteSecret })
  });
  return (await response.json()).data.accessToken;
}

// 입금 요청 생성
async function createDepositRequest(token, requestData) {
  const response = await fetch('/v1/deposit-requests', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify(requestData)
  });
  return await response.json();
}

🎯 매칭 규칙

자동 매칭 알고리즘

입금/출금 요청 생성 시 2분 후 첫 매칭을 시도하며, 실패 시 30초마다 재시도합니다 (최대 5분).

2분 → 2분30초 → 3분 → 3분30초 → 4분 → 4분30초 → 5분 (총 7회 시도)

1순위: 동일 금액 매칭

정확히 같은 금액끼리 우선 매칭합니다.
예: 10,000원 입금 ↔ 10,000원 출금

2순위: 큰 매칭 가능 금액

부분 매칭 시 더 많이 매칭 가능한 것을 우선합니다.
예: 15,000원 입금 시 → 12,000원 출금 > 8,000원 출금

3순위: 잔여 금액이 작은 순서

완전 소진 가능성이 높은 것을 우선 매칭합니다.
예: 잔여 3,000원 > 잔여 5,000원

4순위: 생성 시간 순서

먼저 생성된 요청을 우선 처리합니다 (FIFO).

부분 매칭 예시

시나리오: 20,000원 출금 요청
• 10,000원 입금 A와 매칭 → 잔여 10,000원
• 7,000원 입금 B와 매칭 → 잔여 3,000원
• 3,000원 입금 C와 매칭 → 완료 ✅
결과: 총 3건의 입금과 매칭되어 출금 완료

사이트 격리 (Security)

🔒 동일 사이트 내에서만 매칭됩니다.
사이트 A의 입금과 사이트 B의 출금은 절대 매칭되지 않아 보안이 강화됩니다.

⏱️ 타임아웃 로직

1. 자동 수동처리 (5분)

조건:

입금/출금 요청이 5분간 매칭되지 않음

동작:

상태가 MANUAL_PENDING으로 자동 변경되어 관리자가 수동으로 처리합니다.

💡 2분 후 첫 매칭 시도 + 30초마다 재시도 (최대 5분) → 5분 후에도 매칭 실패 시 수동처리

2. 검증 타임아웃 (10분)

조건:

매칭 완료 후 10분 내에 스크린샷 검증을 하지 않음

동작:
  • • 매칭이 자동으로 취소됩니다
  • • 입금자: CANCELLED 상태로 변경
  • • 출금자: PENDING 또는 MATCHED 상태로 복원 (재매칭 가능)
  • • 취소 웹훅 (match_cancelled) 즉시 발송

전체 타임라인

00:00 - 입금/출금 요청 생성
02:00 - 첫 매칭 시도
02:30 - 재시도 (매칭 실패 시)
03:00 - 재시도
03:30 - 재시도
04:00 - 재시도
04:30 - 재시도
05:00 - 마지막 재시도 → 실패 시 MANUAL_PENDING
매칭 성공 시:
+2분 - 매칭 웹훅 발송 (match_notification)
+10분 - 검증 안 하면 자동 취소 + 취소 웹훅 발송

특수 케이스: 200만원 초과

💰 200만원 초과 금액은 자동 매칭 없이 즉시 MANUAL_PENDING 상태로 생성되어 관리자가 수동으로 처리합니다.

📡 웹훅 가이드

매칭 완료 후 2분 지연하여 웹훅이 전송됩니다. 웹훅 수신 주소를 구현한 후 관리자에게 등록을 요청하세요.

웹훅 이벤트 타입

1️⃣ MATCH_NOTIFICATION (매칭 완료)

입금/출금이 매칭된 후 2분 후에 발송됩니다. 입금자와 출금자 모두에게 발송됩니다.

{
  "type": "MATCH_NOTIFICATION",
  "matchId": "match_xxx",
  "amount": 10000,
  "siteName": "사이트명",
  "depositRequest": {
    "depositRequestId": "dep_xxx",
    "amount": 10000,
    "siteUserId": "user123"
  },
  "withdrawalRequest": {
    "withdrawalRequestId": "with_xxx",
    "amount": 10000,
    "kakaoPayUrl": "https://...",
    "siteUserId": "user456"
  },
  "matchedAt": "2025-01-22T10:00:00Z"
}

2️⃣ VERIFICATION_COMPLETED (검증 완료)

OCR 스크린샷 검증 성공 시 즉시 발송됩니다. 입금자와 출금자 모두에게 발송됩니다.

{
  "type": "VERIFICATION_COMPLETED",
  "matchId": "match_xxx",
  "amount": 10000,
  "ocrExtractedAmount": 10000,
  "screenshotUrl": "https://...",
  "verifiedAt": "2025-01-22T10:05:00Z",
  "siteName": "사이트명",
  "depositRequest": { ... },
  "withdrawalRequest": { ... }
}

3️⃣ MATCH_CANCELLED_BY_TIMEOUT (타임아웃 취소)

검증 타임아웃(10분) 시 즉시 발송됩니다. 입금자에게만 발송됩니다. (출금자는 재매칭 대기)

{
  "type": "MATCH_CANCELLED_BY_TIMEOUT",
  "matchId": "match_xxx",
  "amount": 10000,
  "reason": "검증 타임아웃 (10분 경과) - 입금 미완료",
  "cancelledBy": "timeout",
  "cancelledAt": "2025-01-22T10:10:00Z",
  "depositRequest": { ... },
  "withdrawalRequest": { ... }
}

4️⃣ DEPOSIT_REQUEST_CANCELLED (입금신청 취소)

관리자가 입금신청을 취소할 때 즉시 발송됩니다. 입금자에게만 발송됩니다. (출금자는 재매칭 대기)

{
  "type": "DEPOSIT_REQUEST_CANCELLED",
  "matchId": "match_xxx",
  "amount": 10000,
  "reason": "입금신청 취소: 사용자 요청",
  "cancelledBy": "deposit_cancelled",
  "cancelledAt": "2025-01-22T10:15:00Z",
  "depositRequest": { ... },
  "withdrawalRequest": { ... }
}

5️⃣ WITHDRAWAL_REQUEST_CANCELLED (출금신청 취소)

관리자가 출금신청을 취소할 때 즉시 발송됩니다. 출금자에게만 발송됩니다. (입금자는 재매칭 대기)

{
  "type": "WITHDRAWAL_REQUEST_CANCELLED",
  "matchId": "match_xxx",
  "amount": 10000,
  "reason": "출금신청 취소: 관리자 판단",
  "cancelledBy": "withdrawal_cancelled",
  "cancelledAt": "2025-01-22T10:20:00Z",
  "depositRequest": { ... },
  "withdrawalRequest": { ... }
}

6️⃣ MATCH_CANCELLED_BY_ADMIN (관리자 직접 취소)

관리자가 매칭을 직접 취소할 때 즉시 발송됩니다. 입금자와 출금자 모두에게 발송됩니다.

{
  "type": "MATCH_CANCELLED_BY_ADMIN",
  "matchId": "match_xxx",
  "amount": 10000,
  "reason": "매칭 취소: 관리자 판단",
  "cancelledBy": "admin",
  "cancelledAt": "2025-01-22T10:25:00Z",
  "depositRequest": { ... },
  "withdrawalRequest": { ... }
}

웹훅 수신 예제

app.post('/webhook/macaron-payments', (req, res) => {
  const payload = req.body;
  
  console.log('웹훅 수신:', payload);
  
  switch (payload.type) {
    case 'MATCH_NOTIFICATION':
      // 매칭 완료 (2분 지연, 양쪽 발송)
      console.log(`매칭 완료: ${payload.matchId}, 금액: ${payload.amount}`);
      // 사용자에게 매칭 완료 알림 발송
      break;
    
    case 'VERIFICATION_COMPLETED':
      // 검증 완료 (즉시, 양쪽 발송)
      console.log(`검증 완료: ${payload.matchId}`);
      console.log(`OCR 금액: ${payload.ocrExtractedAmount}`);
      // 최종 완료 처리 및 사용자 알림
      break;
    
    case 'MATCH_CANCELLED_BY_TIMEOUT':
      // 타임아웃 취소 (즉시, 입금자만)
      console.log(`타임아웃 취소: ${payload.matchId}`);
      console.log(`사유: ${payload.reason}`);
      // 입금자에게 취소 알림 발송
      break;
    
    case 'DEPOSIT_REQUEST_CANCELLED':
      // 입금신청 취소 (즉시, 입금자만)
      console.log(`입금신청 취소: ${payload.matchId}`);
      console.log(`사유: ${payload.reason}`);
      // 입금자에게 취소 알림 발송
      break;
    
    case 'WITHDRAWAL_REQUEST_CANCELLED':
      // 출금신청 취소 (즉시, 출금자만)
      console.log(`출금신청 취소: ${payload.matchId}`);
      console.log(`사유: ${payload.reason}`);
      // 출금자에게 취소 알림 발송
      break;
    
    case 'MATCH_CANCELLED_BY_ADMIN':
      // 관리자 직접 취소 (즉시, 양쪽 발송)
      console.log(`관리자 취소: ${payload.matchId}`);
      console.log(`사유: ${payload.reason}`);
      // 양쪽 사용자에게 취소 알림 발송
      break;
    
    default:
      console.log('알 수 없는 이벤트:', payload.type);
  }
  
  res.status(200).json({ success: true });
});

📸 OCR 스크린샷 검증

매칭 완료 후 사용자가 업로드한 송금 완료 스크린샷을 OCR로 자동 검증합니다. OCR 기술을 사용하여 금액과 완료 메시지를 확인합니다.

검증 플로우

1️⃣ 입금/출금 요청 매칭 완료 (MATCHED 또는 VERIFICATION_PENDING)
2️⃣ 2분 후 MATCH_NOTIFICATION 웹훅 발송
3️⃣ 사용자가 송금 완료 스크린샷 업로드 (10분 내)
4️⃣ OCR로 금액 + 완료 메시지 검증
5️⃣ 검증 성공 시 VERIFICATION_COMPLETED로 상태 변경
6️⃣ VERIFICATION_COMPLETED 웹훅 즉시 발송

⏱️ 검증 타임아웃 (10분)

  • • 매칭 후 10분 내에 검증하지 않으면 자동으로 매칭이 취소됩니다
  • • 입금자는 CANCELLED 상태로 변경되며, 취소 웹훅이 발송됩니다
  • • 출금자는 PENDING 또는 MATCHED 상태로 복원되어 재매칭 가능합니다

API 엔드포인트

POST /v1/match-logs/verify-screenshot
// FormData를 사용한 multipart/form-data 전송
const formData = new FormData();
formData.append('matchLogId', 'cm5abc...');
formData.append('screenshot', imageFile); // File 객체

const response = await fetch('/v1/match-logs/verify-screenshot', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${siteToken}`
  },
  body: formData
});

검증 조건 (모두 충족 필요)

✅ 금액 검증

  • • OCR로 추출한 금액과 실제 매칭 금액 비교
  • • ±1% 오차 허용
  • • “송금액”, “보냈어요” 등 키워드 근처의 숫자 우선 인식

✅ 완료 메시지 검증

  • • “송금을 완료했어요”
  • • “보냈어요”, “보냈습니다”
  • • “송금 완료”, “이체 완료”
  • • 기타 완료 관련 메시지

사용자 스크린샷 가이드

✅ 올바른 스크린샷

  • • “송금을 완료했어요” 텍스트 포함
  • • 금액이 명확히 보임
  • • 선명한 이미지
  • • 송금 완료 후 화면

❌ 잘못된 스크린샷

  • • 송금 전 화면 (완료 메시지 없음)
  • • 흐릿하거나 각도가 있는 이미지
  • • 금액이 잘 안 보임
  • • 광고/배너만 가득한 화면

응답 예제

검증 성공

{
  "message": "OCR 검증이 완료되었습니다",
  "data": {
    "matchLogId": "cm5abc...",
    "status": "VERIFICATION_COMPLETED",
    "ocrExtractedAmount": 50000,
    "verifiedAt": "2025-10-11T09:45:00.000Z",
    "hasCompletionMessage": true
  }
}

검증 실패 (금액 불일치)

{
  "message": "금액 불일치 (추출: 45000원, 실제: 50000원)"
}

검증 실패 (완료 메시지 없음)

{
  "message": "\"송금을 완료했어요\" 등의 완료 메시지가 스크린샷에 포함되어야 합니다"
}

구현 팁

  • 사용자 가이드 제공: 올바른 스크린샷 촬영 방법을 명확히 안내하세요
  • 재업로드 옵션: 검증 실패 시 재업로드할 수 있는 UI를 제공하세요
  • 이미지 최적화: 업로드 전에 이미지 크기를 적절히 조정하세요 (권장: 1MB 이하)
  • 로딩 상태: OCR 처리에 시간이 걸릴 수 있으므로 로딩 인디케이터를 표시하세요
  • 웹훅 처리: VERIFICATION_COMPLETED 웹훅을 받아 최종 완료 처리를 하세요

⭐ 모범 사례

보안

  • 토큰 관리: 액세스 토큰을 안전하게 저장하고 정기적으로 갱신하세요
  • HTTPS 필수: 프로덕션 환경에서는 반드시 HTTPS를 사용하세요
  • 웹훅 검증: 웹훅 요청의 출처를 검증하세요
  • 민감 정보 보호: 사이트 시크릿을 소스코드에 하드코딩하지 마세요

성능

  • 중복 요청 방지: 동일한 요청 ID에 대해 중복 호출을 방지하세요
  • 적절한 타임아웃: API 호출 시 적절한 타임아웃을 설정하세요
  • 연결 풀링: HTTP 연결을 재사용하여 성능을 향상시키세요
  • 비동기 처리: 웹훅 처리를 비동기로 구현하세요

안정성

  • 재시도 로직: 일시적 오류에 대해 지수 백오프 재시도를 구현하세요
  • 로깅: 모든 API 호출과 응답을 상세히 로깅하세요
  • 모니터링: API 호출 성공률과 응답 시간을 모니터링하세요
  • 장애 대응: 서비스 중단 시 사용자에게 명확한 안내를 제공하세요

사용자 경험

  • 실시간 상태: 요청 상태를 실시간으로 사용자에게 보여주세요
  • 명확한 메시지: 오류 발생 시 사용자가 이해할 수 있는 메시지를 제공하세요
  • 진행 상태: 매칭 진행 상황을 시각적으로 표시하세요
  • 2분 지연 안내: 웹훅이 2분 지연된다는 점을 사용자에게 미리 안내하세요