No description
  • TypeScript 100%
Find a file
NillPoe 08ae0a4241
feat: 현금영수증 문자 전송 제거
PaySync API에서 POST /v2/cash-receipts/{id}/send-sms가 제거되어
문자 전송 라우트, 서비스 메서드, 요청 모델, 테스트, README 항목을 삭제합니다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:00:52 +09:00
src feat: 현금영수증 문자 전송 제거 2026-10-04 19:00:52 +09:00
test feat: 현금영수증 문자 전송 제거 2026-10-04 19:00:52 +09:00
.env.example feat: PaySync v2 API로 이전 2026-10-04 06:02:12 +09:00
.gitattributes feat: PaySync TypeScript + Express 샘플 백엔드 추가 2026-09-29 13:34:12 +09:00
.gitignore feat: PaySync TypeScript + Express 샘플 백엔드 추가 2026-09-29 13:34:12 +09:00
package-lock.json feat: PaySync TypeScript + Express 샘플 백엔드 추가 2026-09-29 13:34:12 +09:00
package.json feat: PaySync TypeScript + Express 샘플 백엔드 추가 2026-09-29 13:34:12 +09:00
README.md feat: 현금영수증 문자 전송 제거 2026-10-04 19:00:52 +09:00
tsconfig.build.json feat: PaySync TypeScript + Express 샘플 백엔드 추가 2026-09-29 13:34:12 +09:00
tsconfig.json feat: PaySync TypeScript + Express 샘플 백엔드 추가 2026-09-29 13:34:12 +09:00

PaySync 연동 예제 (TypeScript + Express)

PaySync v2 REST API를 호출하고 PaySync v2 웹훅을 받는 가맹점 백엔드 예제입니다. 주문, 현금영수증, 입금 내역 API를 그대로 중계하고, 웹훅은 Standard Webhooks 서명을 검증한 뒤 처리합니다. API 명세는 PaySync API 문서를 참고합니다.

요구사항

  • Node.js 24 이상
  • npm
  • PaySync API 키와 웹훅 서명 키 (PaySync 대시보드에서 발급)

설정

.env.example을 .env로 복사한 뒤 값을 채웁니다. .env는 Git에 올리지 않습니다.

cp .env.example .env
환경 변수 필수 기본값 설명
PAYSYNC_BASE_URL https://api.paysync.kr PaySync API 주소
PAYSYNC_API_KEY O API 키. Authorization: Bearer <키>로 전송합니다.
PAYSYNC_WEBHOOK_SIGNING_SECRET O 웹훅 서명 키 (whsec_...)
PORT 8080 서버 포트

실행

npm install
npm run dev        # 개발 모드 (파일 변경 시 자동 재시작)
npm run build      # dist/로 컴파일
npm start          # 컴파일된 서버 실행
npm test           # 테스트 (PaySync API는 가짜 응답으로 대체)
npm run typecheck  # 타입 검사

엔드포인트

메서드 경로 PaySync API 응답
POST /api/invoices POST /v2/invoices 201 주문
GET /api/invoices GET /v2/invoices 200 { items, totalItems }
GET /api/invoices/{id} GET /v2/invoices/{id} 200 주문
PATCH /api/invoices/{id} PATCH /v2/invoices/{id} 200 주문
POST /api/invoices/{id}/mark-paid POST /v2/invoices/{id}/mark-paid 200 주문
POST /api/invoices/{id}/cancel POST /v2/invoices/{id}/cancel 200 주문
POST /api/cash-receipts POST /v2/cash-receipts 201 현금영수증
GET /api/cash-receipts GET /v2/cash-receipts 200 { items, totalItems }
GET /api/cash-receipts/{id} GET /v2/cash-receipts/{id} 200 현금영수증
POST /api/cash-receipts/{id}/cancel POST /v2/cash-receipts/{id}/cancel 200 취소 영수증
POST /api/cash-receipts/{id}/reissue POST /v2/cash-receipts/{id}/reissue 201 새 현금영수증
GET /api/cash-receipts/{id}/urls GET /v2/cash-receipts/{id}/urls 200 { view, print, pdf }
POST /api/cash-receipts/{id}/send-email POST /v2/cash-receipts/{id}/send-email 204
GET /api/transactions GET /v2/transactions 200 { items, totalItems }
GET /api/transactions/{id} GET /v2/transactions/{id} 200 입금 내역
POST /api/webhooks (PaySync가 호출) 200 / 검증 실패 시 400

PaySync는 주문 생성에도 HTTP 200과 code: "10001"을 돌려주지만, 이 예제는 생성 API에 201로 응답합니다. 응답의 시각은 Unix 초 정수입니다.

목록 조회 쿼리 파라미터 (offset 기본값 0, limit 기본값 20, 나머지는 선택이며 보낸 값만 전달합니다):

  • /api/invoices: offset, limit, search, status, due, source, createdBy, dateFrom, dateTo
  • /api/cash-receipts: offset, limit, search, status, type, method, kind, invoiceId, dateFrom, dateTo
  • /api/transactions: offset, limit, search, bankAccountId, status, matchMethod, dateFrom, dateTo

dateFrom, dateTo는 YYYY-MM-DD 형식의 한국 시간 날짜이며 두 날짜 모두 포함합니다. status, source, bankAccountId는 쉼표로 여러 값을 보낼 수 있습니다 (예: status=PENDING,EXPIRED).

오류 응답은 { "code": "...", "message": "...", "data": ... } 형식입니다. data는 PaySync가 함께 준 경우에만 있습니다.

  • 요청 값이 올바르지 않으면 400 BAD_REQUEST
  • PaySync가 4xx와 code를 돌려주면 같은 상태와 code, data (예: 404 "20014", 409 "220010"과 기존 주문의 data.invoiceId)
  • 그 밖의 PaySync 오류(5xx, JSON이 아닌 응답, 10001이 아닌 200 응답)나 연결 실패는 502 (code는 PaySync가 준 값 또는 UPSTREAM_ERROR)

코드 목록은 에러 코드를 참고합니다.

요청 예시:

curl -X POST http://localhost:8080/api/invoices \
  -H 'Content-Type: application/json' \
  -d '{"customer":{"name":"홍길동"},"amount":50000,"expireAfterDays":1,"metadata":{"orderId":"ORDER-0001"}}'

주문 수정은 보낸 필드만 바뀝니다. null을 보내면 그 값을 지우므로 본문을 그대로 PaySync에 전달합니다.

curl -X PATCH http://localhost:8080/api/invoices/ivc_xxx \
  -H 'Content-Type: application/json' \
  -d '{"extendDays":3,"customer":{"email":null}}'

웹훅 로컬 테스트

PaySync는 localhost로 웹훅을 보낼 수 없으므로 ngrok 같은 터널로 로컬 서버를 외부에 엽니다.

npm run dev
ngrok http 8080

PaySync 대시보드의 웹훅 페이지에서 웹훅 추가를 누르고, 출력된 주소 뒤에 경로를 붙여(예: https://abcd-1234.ngrok-free.app/api/webhooks) 받을 URL로 입력합니다. API 버전은 v2를 고릅니다. 웹훅 목록의 보기에서 서명 키(whsec_...)를 확인해 PAYSYNC_WEBHOOK_SIGNING_SECRET에 넣은 뒤 서버를 다시 시작합니다. 주문을 만들면 invoice.created 웹훅이 오고, 서버 로그에 정상 웹훅이 수신되었습니다: evt_... (invoice.created)가 찍힙니다.

터널 없이 직접 서명한 요청을 보내 볼 수도 있습니다. .env의 서명 키로 서명해서 전송합니다.

node --env-file=.env --input-type=module -e '
import { Webhook } from "standardwebhooks";
const body = JSON.stringify({ id: "evt_local", type: "invoice.paid", timestamp: "2026-04-28T03:20:12Z", data: { id: "ivc_test", paidTrigger: "API_CALL" } });
const id = "whm_local", timestamp = new Date();
const signature = new Webhook(process.env.PAYSYNC_WEBHOOK_SIGNING_SECRET).sign(id, timestamp, body);
const res = await fetch("http://localhost:8080/api/webhooks", {
  method: "POST",
  headers: { "webhook-id": id, "webhook-timestamp": String(Math.floor(timestamp.getTime() / 1000)), "webhook-signature": signature },
  body,
});
console.log(res.status);
'

200이 출력되면 성공입니다. PaySync는 200~299 응답을 성공으로 기록하며, 이 예제는 200을 돌려줍니다. 서명이 틀리면 400을 돌려주고 웹훅 검증에 실패했습니다 경고를 남깁니다.

v2 웹훅 본문은 { "id": "evt_...", "type": "...", "timestamp": "<ISO 8601>", "data": { ... } } 형식입니다. invoice.* 이벤트의 data는 주문이고, invoice.paid의 결제 경로는 data.paidTrigger에 옵니다. transaction.created의 data는 입금입니다. 웹훅 페이로드의 시각은 ISO 8601 문자열입니다. 재시도와 재전송 때문에 같은 이벤트가 여러 번 올 수 있으므로 id(evt_...)로 중복을 걸러야 하며, 이벤트 순서는 보장되지 않습니다. 이벤트별 처리는 src/webhook/service.ts의 dispatch 함수에 추가합니다. 모르는 이벤트는 무시하고 200으로 응답합니다.

구조

src/
├── config.ts             환경 변수 읽기
├── app.ts                Express 앱 구성 (포트를 열지 않음)
├── server.ts             서버 시작
├── error-handler.ts      오류 → HTTP 응답 변환
├── paysync/client.ts     PaySync API 클라이언트 (인증, 응답 해석, PaySyncApiError)
├── paysync/query.ts      다중 값 쿼리 파라미터 처리
├── invoice/              주문: model, service, routes
├── cash-receipt/         현금영수증: model, service, routes
├── transaction/          입금 내역: model, service, routes
├── bank-account/         계좌 모델
└── webhook/              웹훅 서명 검증, 페이로드 모델, 이벤트 처리
test/                     node:test + supertest 테스트