본문으로 건너뛰기

서버 연동

SDK의 onCreateInvoice 콜백은 가맹점 서버를 호출하고, 서버는 HMAC 서명 후 OozooPay API로 요청을 전달합니다. 서버는 받은 ID(pay()invoiceId, transfer()withdrawalId)를 SDK에 돌려줍니다.

HMAC 서명 명세는 인증 문서를 참고하세요.

Secret Key 보안

OOZOO_SECRET_KEY (sk_xxx)는 절대 브라우저로 노출되면 안 됩니다. 환경변수로 서버에만 보관하고, 프론트엔드 코드나 git 커밋에 포함하지 마세요.

엔드포인트

SDK 호출HTTPPath필수 body응답
pay()POST/api/invoicesprice, chainId, tokenAddress, senderdata.invoiceId
transfer()POST/api/invoices/withdrawalsprice, chainId, tokenAddress, receiverdata.withdrawalId

HMAC 서명 방식은 동일하며, path / 주소 필드(sender vs receiver) / 응답 키만 다릅니다.

인보이스 생성 (Pay) 엔드포인트

// app/api/create-invoice/route.ts
import * as crypto from 'crypto';

export async function POST(req: Request) {
const { price, unit, chainId, tokenAddress, sender } = await req.json();

const clientKey = process.env.OOZOO_CLIENT_KEY!;
const secretKey = process.env.OOZOO_SECRET_KEY!;

const hmacKey = crypto.createHash('sha256').update(secretKey).digest('hex');
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'POST';
const path = '/api/invoices';
const body = JSON.stringify({ price, unit, chainId, tokenAddress, sender });

const message = `${timestamp}.${method}.${path}.${body}`;
const signature = crypto.createHmac('sha256', hmacKey).update(message).digest('hex');

const res = await fetch('https://api.oozoopay.com/api/invoices', {
method,
headers: {
'Content-Type': 'application/json',
'X-Client-Key': clientKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});

const result = await res.json();
return Response.json({ invoiceId: result.data.invoiceId });
}

출금 생성 (Transfer) 엔드포인트

transfer()의 콜백은 sender 대신 receiver를 받고, 서버는 출금 엔드포인트를 호출하여 withdrawalId를 반환합니다.

이 엔드포인트는 REQUESTED 상태의 출금 요청만 생성합니다 — 즉시 온체인으로 자금이 이동하지 않습니다. 가맹점 관리자가 대시보드에서 2FA로 승인해야 실제 트랜잭션이 실행됩니다.

// app/api/create-withdrawal/route.ts
import * as crypto from 'crypto';

export async function POST(req: Request) {
const { price, unit, chainId, tokenAddress, receiver } = await req.json();

const clientKey = process.env.OOZOO_CLIENT_KEY!;
const secretKey = process.env.OOZOO_SECRET_KEY!;

const hmacKey = crypto.createHash('sha256').update(secretKey).digest('hex');
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'POST';
const path = '/api/invoices/withdrawals';
const body = JSON.stringify({ price, unit, chainId, tokenAddress, receiver });

const message = `${timestamp}.${method}.${path}.${body}`;
const signature = crypto.createHmac('sha256', hmacKey).update(message).digest('hex');

const res = await fetch(`https://api.oozoopay.com${path}`, {
method,
headers: {
'Content-Type': 'application/json',
'X-Client-Key': clientKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});

const result = await res.json();
return Response.json({ withdrawalId: result.data.withdrawalId });
}

자주 발생하는 실수

  • 요청 본문은 바이트 단위로 일치해야 합니다: HMAC 서명에 쓴 문자열과 실제 전송된 본문이 정확히 같아야 합니다. 서명 후 다시 직렬화하면(키 순서 변경, 공백 차이 등) 서명 검증 실패.
  • 타임스탬프 오차: 서버 시간이 OozooPay 시간과 ±5분 이내여야 합니다. 서명 오류가 자주 나면 NTP 동기화 확인.
  • 응답 필드 잘못 반환: OozooPay API는 { success: true, data: { invoiceId | withdrawalId, ... } } 형태로 응답합니다. pay는 data.invoiceId, transfer는 data.withdrawalId 를 반환해야 합니다.