Package Exports
- @openmkt/payment-sdk
Readme
@openmkt/payment-sdk
PortOne V2 기반 서버사이드 결제 SDK
HMAC-SHA256 / ECDSA P-256 서명 · 멱등성 · Replay 방지 · 본인인증
설치
npm install @openmkt/payment-sdk변경 이력
v1.1.0
- ECDSA P-256 서명 지원 (
ecdsaPrivateKey옵션) PaymentSDK.generateEcdsaKeyPair()키 쌍 생성 유틸리티 추가- 보안 강화: Nonce TTL 10분 → 24시간, Webhook 이벤트 TTL 72시간
v1.0.0
- 최초 릴리스: 결제·취소·조회·빌링키·본인인증
초기화
기본 (HMAC-SHA256)
import { PaymentSDK } from '@openmkt/payment-sdk';
const sdk = new PaymentSDK({
apiKey: process.env.SDK_API_KEY!,
signingSecret: process.env.SDK_SIGNING_SECRET!,
baseUrl: process.env.PAYMENT_SERVER_URL!,
});고보안 (ECDSA P-256) — v1.1.0+
import { PaymentSDK } from '@openmkt/payment-sdk';
// 키 쌍 생성 (최초 1회 — 개인키는 안전하게 보관)
const { privateKey, publicKey } = PaymentSDK.generateEcdsaKeyPair();
// publicKey → 서버 Tenant.ecdsaPublicKey 에 등록
const sdk = new PaymentSDK({
apiKey: process.env.SDK_API_KEY!,
signingSecret: process.env.SDK_SIGNING_SECRET!,
baseUrl: process.env.PAYMENT_SERVER_URL!,
ecdsaPrivateKey: privateKey, // ECDSA 모드 활성화
});
// 이후 모든 요청이 ECDSA P-256으로 서명됨
// 개인키가 서버에 없으므로 시크릿 유출 시에도 위조 불가API 레퍼런스
결제 생성 (checkout)
const result = await sdk.checkout({
provider: 'portone',
pgProvider: 'KAKAOPAY', // SMARTRO_V2 | INICIS | KAKAOPAY | KCP | TOSSPAYMENTS
orderId: 'order001', // 영문+숫자만 (특수문자 불가)
amount: 15000, // 원 단위 정수
currency: 'KRW',
customerEmail: 'buyer@example.com',
returnUrl: 'https://yourshop.com/success',
});
// result.merchantOrderRef → PortOne 프론트 SDK의 paymentId로 전달
// result.channelKey → PortOne 프론트 SDK의 channelKey로 전달결제 검증 (verify)
const result = await sdk.verify({
provider: 'portone',
orderId: 'order001',
providerPaymentId: result.merchantOrderRef, // checkout 응답값
amount: 15000,
currency: 'KRW',
});
if (result.status === 'PAID') {
// 결제 완료 처리
}결제 취소 (cancel)
// 전액 취소
await sdk.cancel({ provider: 'portone', paymentId: 'payment_abc123', reason: '고객 요청' });
// 부분 취소
await sdk.cancel({ provider: 'portone', paymentId: 'payment_abc123', reason: '부분 환불', amount: 5000 });결제 조회 (getPayment)
const payment = await sdk.getPayment({ provider: 'portone', paymentId: 'payment_abc123' });빌링키 발급 (정기결제 카드 등록)
const { billingKey } = await sdk.issueBillingKey({
provider: 'portone',
storeId: 'store-xxxx',
channelKey: 'channel-key-xxxx',
customer: { email: 'user@example.com' },
});빌링키 결제
await sdk.payWithBillingKey({
provider: 'portone',
paymentId: `pay${Date.now()}`,
billingKey: 'billing-key-xxxx',
orderName: '월정액 구독',
amount: 9900,
currency: 'KRW',
});본인인증
// 방법 A: 프론트 PortOne 브라우저 SDK 완료 후 서버 조회
const result = await sdk.getIdentityVerification({ identityVerificationId: 'iv001' });
// 방법 B: 서버 주도 OTP
await sdk.sendIdentityVerification({
identityVerificationId: 'iv001',
customer: { name: '홍길동', phoneNumber: '01012345678' },
});
const verified = await sdk.confirmIdentityVerification({ identityVerificationId: 'iv001', otp: '123456' });
// verified.status === 'VERIFIED'
// verified.name, verified.phone, verified.birthdate, verified.gender, verified.ci에러 처리
import { SdkError } from '@openmkt/payment-sdk';
try {
await sdk.verify({ ... });
} catch (err) {
if (err instanceof SdkError) {
switch (err.code) {
case 'AMOUNT_EXCEEDS_LIMIT': // 1회 한도 초과
case 'DAILY_LIMIT_EXCEEDED': // 일일 한도 초과
case 'VELOCITY_EXCEEDED': // 단시간 과다 요청
case 'VERIFY_AMOUNT_MISMATCH':// 결제 금액 위변조
case 'PROVIDER_ERROR': // PortOne API 오류
case 'REPLAY_DETECTED': // Nonce 재사용 공격
case 'UNAUTHORIZED': // 인증 실패
case 'API_KEY_EXPIRED': // 만료된 API 키
console.error(err.code, err.status, err.requestId);
}
}
}지원 PG사
| pgProvider | PG사 | 비고 |
|---|---|---|
INICIS |
KG이니시스 | 기본값 |
SMARTRO_V2 |
스마트로 | |
KAKAOPAY |
카카오페이 | |
TOSSPAYMENTS |
토스페이먼츠 | |
KCP |
NHN KCP |
주의: 스마트로는 orderId에 영문+숫자만 허용 (특수문자 불가)
보안 기능
| 기능 | 설명 |
|---|---|
| HMAC-SHA256 | 기본 요청 서명 방식 |
| ECDSA P-256 | 고보안 비대칭 서명 (v1.1.0+) |
| Nonce 재사용 차단 | 24시간 TTL로 Replay 공격 방지 |
| 타임스탬프 검증 | ±5분 스큐 초과 요청 거부 |
| 자동 IP 차단 | 반복 공격 시 테넌트 자동 블록 |
| 거래 한도 | 1회/일일/월간 한도 설정 가능 |
| 속도 감지 | N분 내 과다 요청 자동 차단 |
| API 키 로테이션 | 유예 기간 병행 인증 지원 |
빌드
npm run build # dist/ 생성 (CJS + ESM + .d.ts)
npm run typecheck # TypeScript 타입 검사