NestPay PG 연동 가이드

QR·앱 결제 게이트웨이 — 매장(쇼핑몰) 서버 개발자용 연동 문서

외부 공개 문서 · 내부 API(Swagger)와 별개
목차
1. 개요·흐름 2. 온보딩(신청→라이브) 3. 인증(HMAC·화이트IP) 4. API 엔드포인트 5. 웹훅 6. 시나리오별 처리 7. 상태·오류코드

1. 개요 · 결제 흐름

NestPay PG 는 QR/앱 기반 결제 게이트웨이입니다(위챗페이 방식). 매장 서버가 결제 대상(상품·금액·부가세)을 등록하면 NestPay 가 결제용 QR·딥링크를 돌려주고, 사용자가 NestPay 앱으로 결제하면 웹훅조회 API로 결과를 알려 줍니다. 유효시간이 지나면 결제 실패로 처리됩니다.

1
매장 서버 → POST /pg/payments (주문 생성) → paymentId·qrData·deeplink·만료시각 수신
2
매장 → QR 표시(PC 웹) 또는 딥링크로 앱 열기(모바일 웹)
3
사용자 → NestPay 앱으로 스캔·결제
4
NestPay → 매장 서버로 웹훅(PAYMENT_COMPLETED) 발송 + 매장은 GET /pg/payments/{id}로도 확인
5
미결제로 유효시간 경과 → 웹훅(PAYMENT_EXPIRED) + 조회 시 EXPIRED
기준 주소(Base URL): https://pg.nestpay.co.kr · 모든 경로는 /pg 로 시작합니다(예: https://pg.nestpay.co.kr/pg/payments). PG 연동은 이 전용 호스트만 사용합니다(내부 API 와 분리). 유효시간은 NestPay 관리자 설정값을 따릅니다(기본 5분).

2. 온보딩 — 신청 → 승인 → 테스트 → 라이브

연동은 4단계로 진행되며, 샌드박스에서 3종 테스트(주문 생성·조회·웹훅 수신)를 모두 통과해야 라이브로 전환됩니다.

단계내용
① 신청매장앱에서 PG 연동 신청 → client_id·api_key(1회 표시) 발급, 웹훅 주소 등록(서명키 1회 표시), 서버 IP 화이트리스트 신청
② 승인NestPay 관리자가 신청·화이트IP 승인 → 샌드박스 모드로 API 사용 가능
③ 테스트샌드박스에서 주문 생성·조회·웹훅 수신을 각각 1회 이상 성공(자동 기록)
④ 라이브3종 테스트 통과 확인 후 관리자가 라이브 전환 → 실제 결제 시작
모드 구분: 인증 응답·조회 결과의 sandbox 값으로 현재 모드를 확인하세요. 샌드박스와 라이브는 같은 엔드포인트를 쓰며, 승인된 모드에 따라 서버가 구분합니다.

3. 인증 — HMAC 서명 + 화이트IP

모든 /pg 요청에는 아래 4개 헤더가 필요합니다. 승인된 IP 에서만 호출할 수 있습니다.

헤더설명
X-Client-Id발급받은 매장코드
X-Api-Key발급받은 비밀키
X-Timestamp요청 시각(Unix epoch ). 서버 시각과 오차가 크면 거부(재전송 공격 방지)
X-Signature아래 규칙의 HMAC-SHA256 서명(소문자 16진수)

서명 만드는 법

서명 대상 문자열(canonical)은 다음 4줄을 \n(줄바꿈)으로 이어 붙입니다. 경로는 쿼리스트링을 제외하고, 본문이 없으면 빈 문자열입니다.

canonical = HTTP메서드 + "\n" + 경로(쿼리 제외) + "\n" + X-Timestamp + "\n" + 본문(raw)
signature = HMAC_SHA256(api_key, canonical) 를 소문자 16진수로
서명은 보낸 본문 바이트 그대로에 대해 계산해야 합니다(공백·키 순서까지 동일). 서버는 받은 본문 원문으로 다시 서명해 대조합니다.
# 예: 주문 생성 요청 서명 (Python)
import hmac, hashlib, time, json, requests
CLIENT_ID = "mc_xxxxxxxxxxxx"; API_KEY = "발급받은_비밀키"
body = json.dumps({"orderNo":"SHOP-1001","itemName":"주문 #1001","amount":11000,"vatIncluded":True})
ts = str(int(time.time()))
canonical = f"POST\n/pg/payments\n{ts}\n{body}"
sig = hmac.new(API_KEY.encode(), canonical.encode(), hashlib.sha256).hexdigest()
r = requests.post("https://pg.nestpay.co.kr/pg/payments", data=body, headers={
    "Content-Type": "application/json",
    "X-Client-Id": CLIENT_ID, "X-Api-Key": API_KEY, "X-Timestamp": ts, "X-Signature": sig,
})

4. API 엔드포인트

공통 응답 포장: { "success": true, "data": { ... }, "error": null }. 실패 시 success:false, error에 사유.

POST /pg/payments — 결제 주문 생성

요청 필드필수설명
orderNoY매장 주문번호(매장별 유일). 같은 값 재요청 시 기존 주문을 그대로 반환(멱등)
itemNameY결제 대상 이름
amountY청구 총액(원, 0 초과)
vatIncludedYtrue=금액에 부가세 포함(서버가 공급가/부가세 10% 분리) · false=면세(부가세 0)
// 응답 data
{
  "paymentId": 1024, "orderNo": "SHOP-1001", "status": "PENDING",
  "amount": 11000, "supplyAmount": 10000, "vatAmount": 1000, "itemName": "주문 #1001",
  "qrData": "NPQR1.xxxx",                 // QR 로 그려 사용자에게 표시
  "deeplink": "nestpay://pay?token=NPQR1.xxxx", // 모바일 웹은 이 링크로 앱 열기
  "expiresAt": "2026-07-26T12:34:56", "expiresInSeconds": 300, "sandbox": true
}

GET /pg/payments/{paymentId} — 결제 조회

웹훅을 못 받았거나 확인이 필요할 때 언제든 폴링할 수 있습니다. status로 결과를 판단하세요.

// 응답 data
{ "paymentId":1024, "orderNo":"SHOP-1001", "status":"PAID", "amount":11000,
  "txnId":55231, "paidAt":"2026-07-26T12:31:10", "sandbox":true }

POST /pg/payments/{paymentId}/cancel — 취소 / 환불

하나의 엔드포인트가 상태에 따라 동작합니다. 부분취소·부분환불은 제공하지 않습니다(전액만).

주문 상태동작
PENDING(미결제)취소 — QR 소각(이후 결제 시도 불가) + 상태 CANCELED + 취소 웹훅
PAID(결제완료)전액 환불 — 사용자에게 전액 환불(원장 역분개) + 상태 CANCELED + 환불 웹훅
EXPIRED취소 불가(이미 만료). 별도 조치 불필요
CANCELED멱등 — 그대로 CANCELED 반환

GET /pg/payments?status=&page=&limit= — 결제 내역

매장 본인 결제 내역(상태 필터·페이징). 응답: { items:[…], total, page, limit }.

GET /pg/balance — 정산 예정 잔액

{ "settlementBalance": 452000, "paidCount": 61, "paidSum": 690000, "refundedCount": 3 }

5. 웹훅(콜백)

결제 결과가 확정되면 등록한 주소로 POST합니다. 본문은 JSON, 헤더로 이벤트와 서명이 옵니다.

헤더설명
X-Nestpay-Event이벤트 종류(아래)
X-Nestpay-SignatureHMAC_SHA256(webhook_secret, 본문) 소문자 16진수 — 반드시 검증
이벤트의미
PAYMENT_COMPLETED결제 완료(status=PAID)
PAYMENT_EXPIRED유효시간 경과로 결제 실패(status=EXPIRED)
PAYMENT_CANCELED미결제 주문 취소(status=CANCELED)
PAYMENT_REFUNDED결제완료건 전액 환불(status=REFUNDED)
// 웹훅 본문 예
{ "event":"PAYMENT_COMPLETED", "paymentId":1024, "orderNo":"SHOP-1001", "amount":11000, "status":"PAID", "txnId":55231 }

// 서명 검증(Python)
import hmac, hashlib
def valid(body_bytes, header_sig, webhook_secret):
    calc = hmac.new(webhook_secret.encode(), body_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(calc, header_sig)
재시도: 2xx 응답이 아니면 지수 백오프로 최대 10회 재시도합니다(24시간 내). 그래도 실패하면 발송을 멈춥니다 → 이 경우 매장은 조회 API 로 결과를 확인해야 합니다. 응답은 빠르게 200을 돌려주고 처리는 비동기로 하세요.
멱등 처리 필수: 같은 이벤트가 두 번 이상 올 수 있습니다(재시도·네트워크). paymentId+event 기준으로 이미 처리한 건이면 무시하도록 구현하세요.

6. 시나리오별 처리

상황NestPay 동작매장(쇼핑몰) 처리
정상 결제결제요청 → 결제 → PAYMENT_COMPLETED 웹훅/조회 PAID주문 결제완료 처리
결제 후, 이미 쇼핑몰 주문이 취소됨환불요청(/cancel) → PAYMENT_REFUNDED환불요청 호출 후 환불 확정 처리
미결제로 유효시간 경과자동 만료 → PAYMENT_EXPIRED, 조회 EXPIRED주문 미결제 처리(장바구니 복원 등)
미결제 상태에서 쇼핑몰이 주문 취소취소요청(/cancel) → QR 소각 → PAYMENT_CANCELED취소 확정 처리
취소 후 사용자가 뒤늦게 결제 시도QR 이 소각돼 결제 거부(에러)추가 처리 불필요(이미 취소됨)
웹훅 유실(재시도 소진)발송 중단조회 API 폴링으로 최종 상태 확정(웹훅과 조회는 상호 보완)
웹훅 중복 수신재시도로 같은 이벤트 재발송 가능멱등 처리(paymentId+event 기준 1회만)
주문 생성 재요청(동일 orderNo)기존 주문 그대로 반환(중복 생성 방지)동일 결제창 재사용
만료와 결제가 거의 동시QR·주문 만료 시각이 동일 → 만료 후 결제는 거부. 결제가 먼저면 PAID 고정조회 API 최종 상태를 신뢰

7. 상태 · 오류

주문 상태(status): PENDING(대기) · PAID(완료) · EXPIRED(만료) · CANCELED(취소/환불).

HTTP의미 / 대응
200정상. 단 successfalseerror 확인
400필수값 누락·형식 오류·취소 불가 상태 등 요청 문제
401서명 불일치·헤더 누락·시각 오차(HMAC 재확인)
403미승인 화이트IP·본인 매장 아님
404주문 없음
429호출 과다(이용제한) — 잠시 후 재시도
© NestPay · (주)페이네스트 — PG 연동 개발자 문서 · 문의는 매장 담당자를 통해 주세요.