페이소프트 개발자센터
홈 로그인 가입 신청

시작하기

가맹점 조회 API

페이소프트로 정기결제(구독)를 운영하는 가맹점이 구독 계약별 월 결제 상태(완료·미납), 결제결과, 고객의 자동이체 등록 상태를 우리 회사 프로그램에서 바로 조회하는 API입니다. 우리 회사 회원번호(고객키)로 찾을 수 있습니다.

쉽게 말하면 — 페이소프트 관리자 화면에 들어가 엑셀로 내려받던 "이번 달 누가 냈고 누가 안 냈나"를, 우리 회사 서버가 주소 하나를 호출해 JSON 으로 받아 가는 것입니다. 받은 값을 우리 회원 DB 에 저장해 두면 회원 화면·미납 안내·정산에 쓸 수 있습니다.

조회만 제공합니다. 결제 요청·취소·환불, 회원 등록·변경은 이 API로 할 수 없습니다. 이 API 를 아무리 호출해도 고객에게서 돈이 빠져나가지 않습니다.

무엇을 조회할 수 있나

알고 싶은 것API한 줄이 뜻하는 것
이번 달 냈나, 안 냈나 가장 많이 씀GET /v1/billings계약 1개 × 한 달
이번 달 합계(청구·결제·미납 금액)GET /v1/billings/summary한 달 전체 합계
출금 시도 한 건씩 (재출금 포함)GET /v1/payments출금 시도 1번
날짜별 결제 건수·금액GET /v1/payments/summary출금일 하루
구독 계약 상태(정상·일시 중지·해지)GET /v1/contracts계약 1개
고객 정보·자동이체 등록 상태GET /v1/customers고객 1명
결제수단별 등록 상태GET /v1/members결제수단 1개

5분 만에 첫 호출

처음이라면 이 순서대로 따라 하십시오. 테스트 키는 가상 데이터만 돌려주므로 마음껏 호출해도 됩니다.

  1. 테스트 키 준비 — 콘솔 [API 키]에서 환경 시험으로 키를 발급합니다. 이때 호출할 컴퓨터(서버)의 공인 IP를 등록합니다. 내 PC 에서 시험한다면 내 PC 의 공인 IP 를 넣습니다. 발급 화면에 보이는 psk_test_… 값을 복사해 둡니다(다시 볼 수 없습니다).
  2. 키를 환경변수에 넣기 — 소스코드에 직접 쓰지 않고 환경변수로 둡니다.
    export PAYSOFT_API_KEY="psk_test_여기에_발급받은_키"
  3. 호출하기 — 2026년 10월 계약별 결제 상태를 조회합니다.
    curl "https://api.paysoft.co.kr/v1/billings?month=202610" \
      -H "Authorization: Bearer $PAYSOFT_API_KEY"
  4. 결과 확인 — 200 과 함께 계약 6개(1001~1006)의 상태가 옵니다. 1002 는 paid(재출금으로 결제 완료), 1005 는 unpaid(미납)입니다. 어떤 계약이 어떤 상태로 오는지는 테스트 환경에 정리돼 있습니다.
이렇게 나오면원인과 해결
401 INVALID_KEYAuthorization: Bearer 뒤에 키가 빠졌거나 잘못 복사했습니다. 앞뒤 공백·줄바꿈을 확인하십시오.
403 IP_NOT_ALLOWED키에 등록한 IP 와 실제 호출 IP 가 다릅니다. 회사망·VPN 은 공인 IP 가 다를 수 있습니다. 콘솔 [접속 기록]·[사용 현황]에서 들어온 IP 를 확인하십시오.
422 INVALID_PARAMmonth 를 2026-10 처럼 넣었습니다. 202610(YYYYMM)으로 넣으십시오.
연결이 안 됨·시간 초과회사 방화벽이 api.paysoft.co.kr 로 나가는 HTTPS(443)를 막고 있지 않은지 확인하십시오.

가입부터 운영까지

  1. 가입 신청 — 가입 신청 후 받은 메일의 링크로 이메일 인증을 마칩니다.
  2. 페이소프트 승인 — 페이소프트가 사업자 정보와 이용기관을 확인해 승인합니다. 승인 결과는 콘솔 첫 화면에 나타납니다.
  3. 테스트 키 발급 — 콘솔에서 환경을 시험으로 골라 키를 발급합니다. 호출할 서버의 공인 IP 를 함께 등록합니다.
  4. 가상 데이터로 개발 — 테스트 키는 실제 데이터 대신 가상 데이터를 돌려줍니다. 미납·재출금·확인 필요 같은 경우가 모두 들어 있어 화면과 예외 처리를 미리 만들 수 있습니다.
  5. 고객키 설정 — 우리 회원번호로 찾으려면 고객키를 지정합니다(운영 데이터에만 필요, 테스트는 미리 지정돼 있음).
  6. 운영 키로 전환 — 운영 키(psk_live_)를 발급해 서버 환경변수만 바꿉니다. 주소와 응답 형식은 같습니다.

인증 (API 키)

모든 요청의 Authorization 머리글(헤더)에 API 키를 Bearer 방식으로 넣습니다. 별도의 로그인·토큰 발급 단계는 없습니다.

요청 머리글
Authorization: Bearer psk_test_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
키 앞부분환경돌려주는 데이터
psk_test_…시험정해진 규칙의 가상 데이터. 실제 결제 데이터에 접근하지 않습니다.
psk_live_…운영연결된 이용기관의 실제 데이터.

키가 확인되면 아래 순서로 검사합니다. 앞 단계에서 걸리면 뒤 단계는 보지 않습니다.

  1. 키가 맞는가 401 INVALID_KEY
  2. 허용 IP 인가 403 IP_NOT_ALLOWED
  3. 가맹점이 이용 중인가 403 MERCHANT_INACTIVE
  4. 이 API 를 쓸 권한(범위)이 있나 403 SCOPE_DENIED
  5. 호출 한도 안인가 429 RATE_LIMITED
권한(범위)쓸 수 있는 API
payments:read 결제결과 조회/billings · /billings/summary · /payments · /payments/summary · /payments/{id}
members:read 회원 조회/contracts · /customers · /extra-fields · /members

API 키는 발급 화면에서 한 번만 보입니다. 페이소프트도 다시 보여 줄 수 없습니다. 키는 서버에만 두고 브라우저·앱·저장소에 넣지 마십시오.

키마다 등록한 허용 IP에서 온 요청만 받습니다(최대 10개, CIDR 은 /24 까지). 그래서 브라우저(자바스크립트)나 모바일 앱에서 직접 호출할 수 없습니다. 반드시 우리 회사 서버에서 호출하고, 화면에는 서버가 저장한 값을 보여 주십시오.

테스트 환경 (가상 데이터)

테스트 키(psk_test_)로 호출하면 실제 데이터 대신 정해진 규칙의 가상 데이터를 돌려줍니다. 주소·요청·응답 형식은 운영과 같습니다. 어느 달을 조회해도 같은 시나리오가 나옵니다.

계약고객 (고객키)결제수단·금액매달 일어나는 일월 결제 상태
1001C0001 (MEM-1)CMS 20,000원10일 출금 성공paid 결제 완료
1002C0002 (MEM-2)CMS 30,000원10일 잔액부족 실패 → 15일 재출금 성공paid 결제 완료
1003C0003 (MEM-3)CMS 10,000원10일 실패로 기록됐지만 응답코드는 정상 — 기록이 어긋남unknown 확인 필요
1004C0004 (MEM-4)CMS 20,000원출금 전 청구 취소canceled 청구 취소
1005C0005 (MEM-5)CMS 30,000원10일·15일 모두 잔액부족 실패unpaid 미납
1006C0001 (MEM-1)CARD 30,000원28일 출금 대기in_progress 결제 진행 중
항목가상 데이터 규칙
고객C0001 ~ C0005. 고객키는 추가 항목 1번 = MEM-1 ~ MEM-5. C0001 은 계약이 2개(1001·1006)라 "한 고객에 계약 여러 개"를 시험할 수 있습니다.
계약 상태1003 일시 중지, 1004 중도 해지, 나머지 정상
거래번호TEST<출금일 YYYYMMDD><계약번호 끝 3자리> 예: TEST20261010005 (10월 10일, 계약 1005)
추가 항목1번 "회원번호"(필수), 2번 "회원 등급"(선택값 일반·VIP)
이용기관TEST

요청·응답 규칙

모든 API 는 아래 기본 주소 뒤에 경로를 붙여 GET 으로 호출합니다. API 는 전용 도메인 api.paysoft.co.kr 에 있고, 콘솔·개발자센터(console.paysoft.co.kr)와 주소가 다릅니다. 테스트 키와 운영 키 모두 같은 주소를 씁니다.

BASEhttps://api.paysoft.co.kr/v1

성공 응답은 언제나 같은 모양의 봉투(envelope)에 담겨 옵니다. 실제 내용은 data 안에 있습니다.

성공 응답의 모양
{
  "as_of": "2026-10-31T09:00:00+09:00",   // 원천을 조회한 시각
  "request_id": "3f2a9c1e-…",              // 문의할 때 알려 줄 번호
  "data": [ … ],                            // 목록이면 배열, 한 건이면 객체
  "next_cursor": null                       // 목록일 때만. null 이면 마지막 쪽
}
필드타입설명
as_ofstring원천을 조회한 시각(KST, ISO 8601). 모든 성공 응답에 들어 있습니다. 결제 상태는 시간이 지나며 바뀌므로 이 값과 함께 저장하십시오.
request_idstring요청마다 붙는 번호입니다. 응답 머리글 X-Request-Id 와 같습니다. 문의할 때 알려 주십시오.
dataarray · object목록 API 는 객체의 배열, 한 건 API 는 객체 하나입니다.
next_cursorstringnull 가능목록 API 에만 있습니다. 다음 쪽 위치입니다. 목록 넘기기를 보십시오.
규칙내용
형식응답은 UTF-8 JSON 입니다.
날짜·시각날짜는 YYYY-MM-DD, 청구월은 YYYYMM, 시각은 ISO 8601(2026-10-10T18:00:00)입니다.
금액원 단위 정수입니다. 소수점·통화 기호가 없습니다.
null값이 없으면 필드를 빼지 않고 null 로 보냅니다. 표의 null 가능 표시를 확인하십시오.
상태 이름Billing·Contract·Customer 의 상태에는 한글 이름(status_name)이 함께 옵니다. 화면에 그대로 보여 줘도 됩니다.
캐시모든 응답은 Cache-Control: no-store 입니다. 결제 상태를 캐시해 두고 판단하지 마십시오.
새 필드응답에 필드가 늘어나도 버전을 바꾸지 않습니다. 모르는 필드는 무시하도록 만드십시오.

목록 넘기기 (cursor)

목록 API 는 한 번에 최대 100건(size)을 돌려줍니다. 더 있으면 next_cursor 에 값이 들어 있습니다. 다음 요청에 같은 조건 + cursor=그 값을 넣어 다음 쪽을 받고, next_cursor 가 null 이 될 때까지 반복합니다.

  1. GET /v1/billings?month=202610 → next_cursor: "WyJUR…"
  2. GET /v1/billings?month=202610&cursor=WyJUR… → next_cursor: "WyJUR…"
  3. … → next_cursor: null 이면 끝

용어 사전

페이소프트와 이 문서에서 쓰는 말입니다. 처음 보는 용어가 있으면 여기서 찾으십시오.

용어뜻API 필드
이용기관페이소프트에서 결제를 받는 단위(사업장)입니다. 한 가맹점에 여럿 연결될 수 있습니다.account
고객코드페이소프트에 고객을 등록할 때 페이소프트가 자동으로 매기는 번호입니다. 가맹점이 정할 수 없습니다.customer_code
고객키우리 회사가 이미 쓰는 회원번호입니다. 페이소프트 고객의 추가 항목에 넣어 두면 이 값으로 찾을 수 있습니다.customer_key
추가 항목페이소프트 고객 정보에 가맹점이 자유롭게 쓰는 칸 15개입니다.extra_fields
구독 계약"매달 10일 2만 원"처럼 정기적으로 청구하는 약정 하나입니다. 한 고객이 여러 계약을 가질 수 있습니다.contract_id
청구월어느 달 몫의 청구인지입니다. 실제 출금일과 다를 수 있습니다.month, payment_month
출금일실제로 계좌·카드에서 돈을 빼려고 시도한 날입니다.payment_date
재출금출금이 실패한 뒤 같은 달 안에 다시 시도하는 것입니다. 한 달에 출금 시도가 여러 번일 수 있습니다.attempts
자동 재결제이용기관 설정에 따라 페이소프트가 실패 건을 스스로 다시 청구하는 기능입니다. 켜져 있으면 가맹점이 따로 청구하면 안 됩니다.failure.auto_retry
CMS은행 계좌 자동이체입니다. 결제 결과는 보통 출금일 다음 영업일에 확정됩니다.payment_kind
결제수단 등록자동이체·정기 카드를 결제사(효성CMS)에 등록하는 일입니다. 등록이 끝나야 청구할 수 있습니다.auto_debit
확인 필요결제사 결과와 페이소프트 기록이 어긋나 결과를 단정할 수 없는 상태입니다. 페이소프트가 확인합니다. 다시 청구하면 안 됩니다.unknown, review_required

연동 가이드

데이터 한눈에 보기

객체 다섯 개가 아래처럼 이어집니다. 위에서 아래로 갈수록 잘게 쪼개집니다.

Customer고객 1명
1 : N
Member결제수단 1개 (자동이체·카드)member_id
객체한 줄의 뜻구별하는 값언제 쓰나
Billing계약 하나의 한 달 청구. 재출금이 있어도 한 줄로 합친 최종 상태contract_id + month"이번 달 냈나?" — 대부분 이것만 쓰면 됩니다
Payment출금 시도 한 번transaction_id건별 기록·정산 대사
Contract구독 계약 하나contract_id계약 상태·금액·결제일
Customer고객 한 명 (계약·자동이체 상태 포함)customer_code회원 화면, 자동이체 등록 확인
Member결제수단 하나customer_code + member_id결제수단별 등록 상태가 따로 필요할 때

예를 들면 — 10월에 10일 출금이 잔액부족으로 실패하고 15일 재출금이 성공했다면, Payment 는 2건(실패 1, 성공 1)이고 Billing 은 1줄(paid, attempts: 2)입니다. 회원에게 보여 줄 것은 Billing 입니다.

우리 고객과 짝짓기 (고객키)

우리 회사 프로그램의 회원과 페이소프트 고객을 짝지으려면 고객키 하나만 정하면 됩니다. 고객키는 우리 회사가 회원마다 이미 쓰는 번호(회원번호·고객 ID 등)입니다.

  1. 페이소프트에 고객키 넣기 — 페이소프트 고객 정보의 추가 항목(1~15번) 중 하나에 회원번호를 넣습니다. 고객 등록·엑셀 일괄 등록 때 함께 넣거나, 이미 등록된 고객은 수정해서 넣습니다.
  2. 콘솔에서 항목 지정 — 콘솔 [고객키 설정]에서 회원번호를 넣은 항목을 고르고 저장합니다. 항목마다 값이 들어 있는 고객 수와 중복 수를 보여 주니, 추천 표시된 항목을 고르면 됩니다.
  3. API로 받기 — 이제 모든 응답에 customer_key 가 붙고, 요청에 customer_key=회원번호 를 넣어 찾을 수 있습니다.
무엇을 맞추나이 값으로 맞춥니다우리 DB 에 저장할 것
고객customer_key (우리 회원번호)이미 가지고 있음. 처음 한 번 customer_code 도 함께 저장해 두면 좋습니다
구독 계약contract_id계약을 처음 받을 때 저장. 한 고객에 계약이 여러 개일 수 있습니다
월 결제 상태contract_id + month이 두 값을 키로 상태를 덮어씁니다(같은 달을 다시 받아도 한 줄)
결제 한 건transaction_id건별 기록이 필요할 때만

고객키는 고객마다 하나뿐인 값이어야 합니다. 같은 값이 여러 고객에 들어 있으면 찾을 때 여러 명이 나옵니다. 고객키를 지정하지 않은 상태에서 customer_key 로 찾으면 422 CUSTOMER_KEY_NOT_SET 입니다.

고객키가 겹칠 때

페이소프트 추가 항목은 같은 값을 여러 고객에 넣을 수 있습니다(유일성 검사가 없음). 그래서 고객키가 겹칠 수 있고, 그때 API 는 이렇게 동작합니다.

상황API 동작가맹점이 할 일
customer_key=값 으로 찾음그 값을 가진 고객이 모두 나옵니다. /billings 는 그 고객들의 계약을 모두 돌려줍니다한 회원의 것이라고 가정하지 마십시오
응답의 각 객체같은 고객키를 다른 고객도 쓰면 customer_key_shared: truetrue 인 줄은 자동 안내·자동 처리에서 빼고 사람이 확인
앞뒤 공백만 다른 값MEM-1 과 MEM-1␠ 은 같은 고객키로 봅니다. 응답의 customer_key 는 공백을 뗀 값입니다저장할 때도 공백을 뗀 값으로
미사용 고객사용하지 않는 고객도 검색·겹침 판정에 포함됩니다(in_use 로 구분)옛 고객에 남은 값도 정리

어디가 겹치는지 보기 — 콘솔 [고객키 설정]에서 항목마다 "겹치는 값 N개 · 고객 M명"을 보여 주고, 겹치는 값 보기에서 값별 고객코드를 확인할 수 있습니다(소유자·개발자). 페이소프트 [고객] 화면에서 그 고객의 추가 항목을 고객마다 다른 값으로 고치면 됩니다.

상황별 호출 고르기

하고 싶은 일호출예제
이번 달 모든 구독의 결제 상태를 우리 DB 에 반영 (하루 1~2번)GET /v1/billings?month=202610 → next_cursor 가 없을 때까지보기
미납 고객만 뽑아 안내 문자 보내기GET /v1/billings?month=202610&status=unpaid보기
회원 한 명 화면에 계약별 결제 상태 보여 주기GET /v1/billings?month=202610&customer_key=회원번호보기
회원의 구독 계약과 자동이체 등록 상태 확인GET /v1/customers?customer_key=회원번호보기
이번 달 청구·결제·미납 금액 한눈에GET /v1/billings/summary?month=202610
날짜별 결제 건수·금액 (정산 대사)GET /v1/payments/summary?from=2026-10-01&to=2026-10-31
결제 한 건씩 자세히GET /v1/payments?month=202610&customer_key=회원번호
실패한 결제를 다시 청구해도 되는지 판단Billing·Payment 의 status · review_required · failure보기

정기 동기화 설계

이 API 는 결제 결과를 알려 주는 알림(웹훅)을 보내지 않습니다. 우리 서버가 정해진 시각에 조회해 저장하는 방식으로 연동합니다.

무엇을언제이유
이번 달 /billings 전체하루 1~2번 (예: 오전 10시·오후 3시)은행 자동이체 결과는 출금일 다음 영업일에 확정되는 경우가 많습니다
지난달 /billings 전체월초 며칠 동안 하루 1번월말 출금분 결과가 다음 달 초에 확정될 수 있습니다
회원 한 명 /billings?customer_key=회원이 화면을 열 때 (선택)최신 상태가 꼭 필요할 때만. 대개는 저장한 값을 보여 주면 됩니다

저장 테이블은 (contract_id, month) 를 기본키로 두고 받을 때마다 덮어쓰면 됩니다. 아래는 예시입니다(우리 회사 DB 에 맞게 바꾸십시오).

우리 DB 저장 테이블 예시 (SQL)
CREATE TABLE paysoft_billing (
  contract_id      BIGINT       NOT NULL,   -- Billing.contract_id
  month            CHAR(6)      NOT NULL,   -- Billing.month (YYYYMM)
  member_no        VARCHAR(200),            -- Billing.customer_key (우리 회원번호)
  customer_code    VARCHAR(50)  NOT NULL,   -- Billing.customer_code
  status           VARCHAR(20)  NOT NULL,   -- paid · unpaid · in_progress · canceled · unknown
  status_name      VARCHAR(50),             -- 화면 표시용 한글
  billed_amount    INT          NOT NULL,
  paid_amount      INT          NOT NULL,
  unpaid_amount    INT          NOT NULL,
  review_required  BOOLEAN      NOT NULL,   -- true 면 재청구 금지
  key_shared       BOOLEAN      NOT NULL,   -- Billing.customer_key_shared (true 면 사람이 확인)
  failure_category VARCHAR(20),             -- Billing.failure.category
  auto_retry       BOOLEAN,                 -- Billing.failure.auto_retry
  as_of            TIMESTAMP    NOT NULL,   -- 응답의 as_of
  PRIMARY KEY (contract_id, month)
);

contract_id 가 null 인 줄(계약 없이 청구된 건)이 있으면 (customer_code, month) 로 따로 저장하십시오. 구독 계약으로만 청구한다면 이런 줄은 나오지 않습니다.

오류 처리와 재시도

오류가 나면 HTTP 상태 코드와 함께 아래 모양의 본문이 옵니다. code 로 분기하고, message 는 로그에 남기십시오.

오류 응답
{ "error": { "code": "RANGE_TOO_LONG", "message": "조회 기간은 최대 31일입니다.", "request_id": "3f2a9c1e-…" } }
상태다시 호출?어떻게
429자동으로응답 머리글 Retry-After(초)만큼 기다린 뒤 다시. 초당 한도는 1초, 하루 한도는 3600초가 옵니다
503자동으로Retry-After(보통 30초) 뒤 다시. "결제 없음"으로 저장하지 마십시오
500몇 번만잠시 뒤 1~2번 다시. 계속되면 request_id 로 문의
401 · 403하지 않음키·IP·권한 설정 문제입니다. 반복 호출해도 같습니다. 담당자에게 알림을 보내십시오
404 · 422하지 않음요청 값을 고쳐야 합니다
연결 실패·시간 초과자동으로잠시 뒤 다시. 조회만 하므로 다시 호출해도 안전합니다

재시도를 넣은 공통 호출 함수는 예제에 있습니다.

결제 안전

재청구 전에 꼭 확인할 것

잘못 판단해 다시 청구하면 고객에게서 돈이 두 번 빠져나갑니다. 실패한 결제를 다시 청구하기 전에 위에서부터 차례로 확인하십시오. 하나라도 "하지 않음"이면 멈춥니다.

순서확인할 값이럴 때다시 청구
1HTTP 상태503 UPSTREAM_UNAVAILABLE — 원천 조회 실패하지 않음 잠시 후 다시 조회
2HTTP 상태404 NOT_FOUND — 조회 범위(정기결제)에서 찾지 못함하지 않음 페이소프트에 확인
3review_requiredtrue — 결제사 결과와 기록이 어긋났을 수 있어 페이소프트가 확인하는 중하지 않음
4statusPayment: succeeded · pending · processing · canceled · unknown
Billing: paid · in_progress · canceled · unknown
하지 않음
5failure.auto_retrytrue — 페이소프트 자동 재결제가 켜진 이용기관하지 않음 자동 재결제를 기다림
6failure.retryable (Payment)
failure.category (Billing)
true / 잔액부족·한도초과·일시장애가능 고객 안내 후
7그 밖의 실패계좌·카드 상태, 본인확인, 카드사 확인 필요 등고객 조치 후 다시 청구해도 같은 결과

503 을 '결제 없음'으로 해석하지 마십시오. 원천을 확인하지 못했다는 뜻입니다. 원천 조회에 실패했을 때 빈 목록이 오는 일은 없습니다.

이 순서를 그대로 옮긴 함수는 재청구 판단 함수 예제에 있습니다.

실패 분류표

failure.category 는 결제사 응답코드를 사람이 이해하기 쉬운 분류로 묶은 값입니다. 결제사 응답 원문은 개인정보가 섞일 수 있어 제공하지 않습니다.

category뜻다시 청구고객 안내 예
잔액부족계좌 잔액이 모자랐습니다가능"잔액을 확인해 주세요"
한도초과카드·계좌 한도를 넘었습니다가능"한도를 확인해 주세요"
일시장애은행·카드사 쪽 일시적인 장애가능안내 없이 나중에 다시
계좌상태해지·거래정지 등 계좌를 쓸 수 없음고객 조치 후"결제 계좌를 바꿔 주세요"
카드상태분실·정지·해지된 카드고객 조치 후"결제 카드를 바꿔 주세요"
카드정보오류카드번호·유효기간 등이 맞지 않음고객 조치 후"카드 정보를 다시 등록해 주세요"
본인확인예금주·본인 정보가 맞지 않음고객 조치 후"결제수단 정보를 확인해 주세요"
카드사확인카드사가 거절. 고객이 카드사에 연락해야 풀림고객 조치 후"카드사에 문의해 주세요"
가맹점설정 · 요청오류이용기관 설정이나 요청 자체의 문제하지 않음페이소프트에 문의
기타오류실패는 확정됐지만 결제사가 사유를 밝히지 않음확인 후페이소프트에 문의

결과를 단정할 수 없는 응답(결과미확정·사전에 없는 코드)은 실패로 보내지 않고 status: unknown + review_required: true 로 보냅니다. 분류는 앞으로 늘어날 수 있으니 모르는 값은 "고객 조치 후"처럼 보수적으로 다루십시오.

보안 체크리스트


API 레퍼런스 · 월별 결제 상태

Billing 객체 구독 계약별 월 결제 상태

계약 하나의 한 달 청구를 한 줄로 보여 줍니다. 그 달에 실패 후 재출금이 있었더라도 한 줄로 합쳐 최종 상태를 알려 줍니다. 구독 결제 상태를 관리할 때 가장 먼저 쓰는 객체입니다.

필드타입설명예시
monthstring청구월 YYYYMM."202610"
contract_idnumbernull 가능구독 계약번호. 계약 없이 청구된 건이면 null(이때는 고객 단위로 묶임).1005
contract_nostringnull 가능페이소프트 화면에 보이는 계약(주문)번호."TEST-ORD-1005"
contract_statusstringnull 가능계약 상태 active 정상 · paused 일시 중지 · terminated 해지."active"
contract_status_namestringnull 가능계약 상태 한글 이름."정상"
accountstring이용기관 코드."TEST"
customer_codestring페이소프트 고객코드(페이소프트가 자동으로 매김)."C0005"
customer_keystringnull 가능우리 회사 고객키(고객키 설정을 한 경우)."MEM-5"
customer_key_sharedboolean같은 고객키를 다른 고객도 쓰면 true. 한 회원으로 합쳐 저장하거나 안내를 보내기 전에 사람이 확인하십시오. 고객키가 겹칠 때 참고.false
statusstringenum이 달의 결제 상태. 값 목록 참고."unpaid"
status_namestring상태 한글 이름. 화면에 그대로 써도 됩니다."미납"
review_requiredbooleantrue 이면 다시 청구하지 마십시오(status 가 unknown).false
billed_amountnumber청구 금액(원). 청구 취소면 0.30000
paid_amountnumber결제된 금액(원). paid 가 아니면 0.0
unpaid_amountnumber미납 금액(원). unpaid 일 때만 청구 금액과 같고, 그 밖에는 0.30000
attemptsnumber그 달 출금 시도 횟수(재출금 포함).2
paid_datestringnull 가능결제된 날 YYYY-MM-DD. paid 일 때만 값이 있습니다.null
last_attempt_datestringnull 가능마지막 출금 시도일."2026-10-15"
failureobjectnull 가능마지막 실패 정보. 미납·확인 필요·진행 중이면서 실패한 시도가 있을 때만 있습니다.
failure.codestringnull 가능결제사 응답코드."Q301"
failure.categorystring실패 분류. 실패 분류표 참고."잔액부족"
failure.auto_retrybooleantrue 면 페이소프트 자동 재결제가 켜진 이용기관. 직접 재청구하지 마십시오.false

Billing 의 failure 에는 retryable 이 없습니다. 다시 청구할지는 category 로 판단하십시오(예제).

status 값

statusstatus_name뜻가맹점이 할 일
paid결제 완료그 달 청구가 결제됐습니다(재출금으로 성공한 경우 포함).없음
unpaid미납그 달 시도가 모두 실패했습니다. 실패 사유는 failure.사유에 맞춰 고객 안내
in_progress결제 진행 중출금 대기 또는 결제사 처리 중입니다.결과를 기다림
canceled청구 취소그 달 청구가 취소됐습니다.없음
unknown확인 필요 — 재청구하지 마십시오결과를 확정할 수 없어 페이소프트가 확인합니다.다시 청구하지 말 것

계약별 월 결제 상태 조회

GET/v1/billingspayments:read

청구월 하나의 Billing 목록을 돌려줍니다. 고객키·고객코드·계약번호·상태로 좁힐 수 있습니다.

파라미터타입설명
month필수string청구월 YYYYMM. 예: 202610
statusstringpaid · unpaid · in_progress · canceled · unknown 중 하나로 거릅니다.
customer_keystring우리 회사 고객키로 거릅니다(최대 200자).
customer_codestring페이소프트 고객코드로 거릅니다.
contract_idnumber계약 하나만 봅니다.
sizenumber한 쪽 건수 1~100, 기본 100.
cursorstring앞 응답의 next_cursor. 첫 쪽은 비웁니다.
curl "https://api.paysoft.co.kr/v1/billings?month=202610&status=unpaid" \
  -H "Authorization: Bearer $PAYSOFT_API_KEY"
응답 (테스트 키, 2건만 표시)
{
  "as_of": "2026-10-31T09:00:00+09:00",
  "request_id": "3f2a9c1e-…",
  "data": [
    {
      "month": "202610", "contract_id": 1002, "contract_no": "TEST-ORD-1002",
      "contract_status": "active", "contract_status_name": "정상",
      "account": "TEST", "customer_code": "C0002", "customer_key": "MEM-2",
      "status": "paid", "status_name": "결제 완료", "review_required": false,
      "billed_amount": 30000, "paid_amount": 30000, "unpaid_amount": 0,
      "attempts": 2, "paid_date": "2026-10-15", "last_attempt_date": "2026-10-15",
      "failure": null
    },
    {
      "month": "202610", "contract_id": 1005, "contract_no": "TEST-ORD-1005",
      "contract_status": "active", "contract_status_name": "정상",
      "account": "TEST", "customer_code": "C0005", "customer_key": "MEM-5",
      "status": "unpaid", "status_name": "미납", "review_required": false,
      "billed_amount": 30000, "paid_amount": 0, "unpaid_amount": 30000,
      "attempts": 2, "paid_date": null, "last_attempt_date": "2026-10-15",
      "failure": { "code": "Q301", "category": "잔액부족", "auto_retry": false }
    }
  ],
  "next_cursor": null
}

청구월 집계

GET/v1/billings/summarypayments:read

한 달 청구 전체를 상태별 계약 수와 금액으로 돌려줍니다. 대시보드나 월말 점검에 씁니다.

파라미터타입설명
month필수string청구월 YYYYMM.
customer_key · customer_code · contract_id좁혀서 집계할 때(선택).
data 필드타입설명
monthstring청구월.
totalobject전체 count(계약 수) · billed_amount · paid_amount · unpaid_amount.
by_statusarray상태별 status · status_name · count · 금액 3종. paid·unpaid·in_progress·canceled·unknown 순서.
review_requirednumber확인 필요 계약 수.
응답의 data
{
  "month": "202610",
  "total": { "count": 6, "billed_amount": 140000, "paid_amount": 50000, "unpaid_amount": 30000 },
  "by_status": [
    { "status": "paid", "status_name": "결제 완료", "count": 2, "billed_amount": 50000, "paid_amount": 50000, "unpaid_amount": 0 },
    { "status": "unpaid", "status_name": "미납", "count": 1, "billed_amount": 30000, "paid_amount": 0, "unpaid_amount": 30000 },
    …
  ],
  "review_required": 1
}

API 레퍼런스 · 결제결과

Payment 객체 출금 시도 한 건

결제 한 건의 결과입니다. 정기결제 원장의 거래 한 건이 Payment 객체 하나입니다. 재출금이 있으면 같은 달·같은 계약에 Payment 가 여러 건입니다.

필드타입설명예시
transaction_idstring거래번호. 결제 한 건을 식별합니다. 같은 거래는 언제나 이 값 하나로만 나타납니다."TEST20261010005"
accountstring이용기관 코드. 가맹점에 이용기관이 여럿 연결돼 있으면 어느 이용기관의 거래인지 알려 줍니다."TEST"
customer_codestring페이소프트 고객코드. 페이소프트가 자동으로 매기는 번호라 가맹점이 정할 수 없습니다. 우리 회원번호로 찾으려면 고객키를 쓰십시오."C0005"
customer_keystringnull 가능우리 회사 고객키(고객키 설정을 한 경우)."MEM-5"
customer_key_sharedboolean같은 고객키를 다른 고객도 쓰면 true. 한 회원으로 합쳐 저장하거나 안내를 보내기 전에 사람이 확인하십시오. 고객키가 겹칠 때 참고.false
member_idstringnull 가능결제사(효성CMS) 회원 ID."M0005"
payment_kindstringnull 가능결제수단. CMS(자동이체), CARD(카드) 등."CMS"
contract_idnumbernull 가능이 결제가 속한 구독 계약번호. 계약 없이 청구된 건은 null.1005
payment_monthstringnull 가능청구월 YYYYMM."202610"
payment_countnumbernull 가능계약의 몇 번째 청구(회차)인지.10
payment_datestringnull 가능출금일(결제일) YYYY-MM-DD."2026-10-10"
amountnumbernull 가능청구 금액(원).30000
paid_amountnumbernull 가능실제로 받은 금액(원). status 가 succeeded 일 때만 값이 있습니다.null
statusstringenum결제 상태. 값 목록 참고."failed"
status_codestring페이소프트 내부 상태 코드. 문의할 때 참고용이며 분기에 쓰지 마십시오."500"
review_requiredbooleantrue 이면 결제사 결과와 기록이 어긋났을 수 있어 사람이 확인하는 건입니다. 다시 청구하지 마십시오.false
failureobjectnull 가능실패 정보. 실패한 건(또는 실패 코드가 있는 확인 필요 건)에만 있습니다.
failure.codestringnull 가능결제사 응답코드."Q301"
failure.categorystring실패 분류. 실패 분류표 참고."잔액부족"
failure.auto_retrybooleantrue 이면 이 이용기관은 페이소프트 자동 재결제가 켜져 있습니다. 따로 다시 청구하면 이중 출금이 될 수 있습니다.false
failure.retryableboolean다시 청구해서 성공할 수 있는 실패인지. review_required 나 auto_retry 가 true 이면 언제나 false.true
cancel_kindstringnull 가능취소 종류. canceled 일 때만 값이 있습니다. 값 목록 참고.null
updated_atstringnull 가능원장에서 마지막으로 바뀐 시각."2026-10-10T18:00:00"

status · cancel_kind 값

status뜻가맹점이 할 일
succeeded출금·승인이 성공했습니다.없음
failed출금·승인이 실패로 확정됐습니다. 사유는 failure.재청구 판단
pending출금일을 기다리고 있습니다.기다림
processing결제사가 처리 중입니다. 자동이체는 보통 출금일 다음 영업일에 결과가 확정됩니다.기다림
canceled취소됐습니다. 종류는 cancel_kind.없음
unknown결과를 확정할 수 없습니다. review_required 가 true.다시 청구하지 말 것
cancel_kind뜻
before_withdrawal출금 전 취소 — 돈이 빠져나가지 않았습니다.
approval_canceled카드 승인 취소.
purchase_canceled카드 매입 취소.

결제결과 목록

GET/v1/paymentspayments:read

결제 시도 한 건씩 돌려줍니다. 출금일 기간(최대 31일) 또는 청구월 중 하나로 조회합니다. 한 달 계약별 최종 상태만 필요하면 /v1/billings 가 더 간단합니다.

파라미터타입설명
from조건부string조회 시작 출금일 YYYY-MM-DD.
to조건부string조회 끝 출금일. from 부터 최대 31일.
month조건부string청구월 YYYYMM. from·to 대신 씁니다(함께 쓰면 오류). 둘 중 하나는 필수입니다.
statusstringsucceeded · failed · pending · processing · canceled 중 하나로 거릅니다.
customer_keystring우리 회사 고객키로 거릅니다.
customer_codestring고객코드로 거릅니다.
contract_idnumber계약 하나의 결제만 봅니다.
sizenumber한 쪽 건수 1~100, 기본 100.
cursorstring앞 응답의 next_cursor.
curl "https://api.paysoft.co.kr/v1/payments?from=2026-10-01&to=2026-10-31&status=failed" \
  -H "Authorization: Bearer $PAYSOFT_API_KEY"
응답 (테스트 키)
{
  "as_of": "2026-10-31T09:00:00+09:00",
  "request_id": "3f2a9c1e-…",
  "data": [
    {
      "transaction_id": "TEST20261010005",
      "account": "TEST",
      "customer_code": "C0005",
      "customer_key": "MEM-5",
      "member_id": "M0005",
      "payment_kind": "CMS",
      "contract_id": 1005,
      "payment_month": "202610",
      "payment_count": 10,
      "payment_date": "2026-10-10",
      "amount": 30000,
      "paid_amount": null,
      "status": "failed",
      "status_code": "500",
      "review_required": false,
      "failure": { "code": "Q301", "category": "잔액부족", "auto_retry": false, "retryable": true },
      "cancel_kind": null,
      "updated_at": "2026-10-10T18:00:00"
    }
  ],
  "next_cursor": null
}

결제일별 집계

GET/v1/payments/summarypayments:read

출금일마다 건수·청구 금액·결제 금액을 상태별로 돌려줍니다(최대 31일). 정산 대사나 일별 그래프에 씁니다.

파라미터타입설명
from필수string출금일 시작 YYYY-MM-DD.
to필수string출금일 끝 (최대 31일).
customer_key · customer_code · contract_id좁혀서 집계할 때(선택).
응답의 data
{
  "from": "2026-10-01", "to": "2026-10-31",
  "total": { "count": 8, "amount": 190000, "paid_amount": 50000, "review_required": 1 },
  "days": [
    { "date": "2026-10-10", "count": 5, "amount": 110000, "paid_amount": 20000, "review_required": 1,
      "by_status": {
        "succeeded": { "status_name": "결제 성공", "count": 1, "amount": 20000, "paid_amount": 20000 },
        "failed":    { "status_name": "결제 실패", "count": 2, "amount": 60000, "paid_amount": 0 },
        …
      } }
  ]
}

amount 는 시도한 청구 금액의 합입니다. 재출금이 있으면 같은 청구가 여러 번 더해지므로, 월 미납 금액은 청구월 집계로 보십시오.

결제결과 한 건

GET/v1/payments/{transaction_id}payments:read

거래번호로 결제결과 한 건을 돌려줍니다. 응답의 data 는 Payment 객체 하나입니다.

curl "https://api.paysoft.co.kr/v1/payments/TEST20261010005" \
  -H "Authorization: Bearer $PAYSOFT_API_KEY"

404 는 이 API 의 조회 범위(정기결제)에서 찾지 못했다는 뜻입니다. 거래가 없다고 단정해 다시 청구하지 마십시오.

API 레퍼런스 · 구독 계약

Contract 객체 구독 계약

구독 계약 하나입니다. 한 고객에 계약이 여러 개일 수 있습니다. 삭제된 계약은 나오지 않습니다.

필드타입설명예시
contract_idnumber계약번호. 결제결과·월 결제 상태의 contract_id 와 같습니다.1001
contract_nostringnull 가능페이소프트 화면의 계약(주문)번호."TEST-ORD-1001"
accountstring이용기관 코드."TEST"
customer_codestring고객코드."C0001"
customer_keystringnull 가능우리 회사 고객키."MEM-1"
customer_key_sharedboolean같은 고객키를 다른 고객도 쓰면 true. 한 회원으로 합쳐 저장하거나 안내를 보내기 전에 사람이 확인하십시오. 고객키가 겹칠 때 참고.false
statusstringenumactive 정상 · paused 일시 중지 · terminated 해지 · 알 수 없는 코드면 unknown."active"
status_namestring정상 · 일시 중지 · 중도 해지 · 잔액 완납 해지 · 만기 해지 · 계약 해지. 해지 종류는 여기서 구분합니다."정상"
status_codestring페이소프트 내부 상태 코드(문의용)."01"
payment_typestringnull 가능CMS 은행 자동이체 · CARD 카드 정기결제 등."CMS"
payment_type_namestringnull 가능결제 방식 한글 이름."은행 자동이체"
payment_daynumbernull 가능매달 결제 약정일(1~31).10
monthly_amountnumbernull 가능계약 금액(원).20000
contract_startstringnull 가능약정 시작일."2026-01-01"
contract_endstringnull 가능약정 종료일."2027-12-31"
contract_monthsnumbernull 가능약정 기간(개월).24
billing_startstringnull 가능청구 시작일."2026-01-10"
billing_endstringnull 가능청구 종료일."2027-12-10"
billing_monthsnumbernull 가능청구 횟수.24
updated_atstringnull 가능마지막으로 바뀐 시각."2026-10-01T09:00:00"

계약 목록

GET/v1/contractsmembers:read
파라미터타입설명
statusstringactive · paused · terminated 중 하나로 거릅니다.
customer_keystring우리 회사 고객키로 거릅니다.
customer_codestring고객코드로 거릅니다.
size · cursornumber · string목록 넘기기.
curl "https://api.paysoft.co.kr/v1/contracts?customer_key=MEM-1" \
  -H "Authorization: Bearer $PAYSOFT_API_KEY"

계약 한 건

GET/v1/contracts/{contract_id}members:read

응답의 data 는 Contract 객체 하나입니다. 없으면 404 NOT_FOUND.

API 레퍼런스 · 고객

Customer 객체 고객

고객 한 명의 고객키, 추가 항목, 자동이체 등록 상태, 구독 계약 목록을 한 번에 돌려줍니다. 이름·연락처·주소·계좌·카드번호는 제공하지 않습니다.

필드타입설명예시
accountstring이용기관 코드."TEST"
customer_codestring페이소프트 고객코드(자동 부여)."C0001"
customer_keystringnull 가능우리 회사 고객키."MEM-1"
customer_key_sharedboolean같은 고객키를 다른 고객도 쓰면 true. 한 회원으로 합쳐 저장하거나 안내를 보내기 전에 사람이 확인하십시오. 고객키가 겹칠 때 참고.false
in_useboolean페이소프트에서 사용 중인 고객인지.true
extra_fieldsarray추가 항목 1~15 중 이름이 있거나 값이 들어 있는 항목.
extra_fields[].nonumber추가 항목 번호 1~15.1
extra_fields[].namestringnull 가능페이소프트에서 정한 항목 이름."회원번호"
extra_fields[].valuestringnull 가능값."MEM-1"
auto_debitobject자동이체(결제수단) 등록 상태 요약.
auto_debit.statusstringenum고객 단위 등록 상태. 결제수단이 여럿이면 하나라도 등록 완료면 registered. 값 목록 참고."registered"
auto_debit.status_namestring한글 이름."등록 완료"
auto_debit.methodsarray결제수단별 payment_kind · member_id · status · status_name · status_code · recurring · updated_at.
contractsarray이 고객의 구독 계약 요약. contract_id · contract_no · status · status_name · payment_type · monthly_amount · billing_start · billing_end.
created_atstringnull 가능페이소프트에 등록된 시각.
updated_atstringnull 가능마지막으로 바뀐 시각.

auto_debit.status 값

statusstatus_name뜻
registered등록 완료청구할 수 있습니다.
pending등록 진행 중결제사에 등록을 신청했고 결과를 기다립니다.
failed등록 실패고객 정보 확인이 필요합니다.
deleted해지결제사에서 해지됐습니다.
none등록된 결제수단 없음결제수단을 아직 등록하지 않았습니다.
unknown확인 필요그 밖의 상태입니다.

고객 찾기·목록

GET/v1/customersmembers:read
파라미터타입설명
customer_keystring우리 회원번호로 찾습니다. 가장 많이 쓰는 방법입니다.
customer_codestring고객코드로 찾습니다.
size · cursornumber · string목록 넘기기.
curl "https://api.paysoft.co.kr/v1/customers?customer_key=MEM-1" \
  -H "Authorization: Bearer $PAYSOFT_API_KEY"
응답의 data[0] (테스트 키)
{
  "account": "TEST", "customer_code": "C0001", "customer_key": "MEM-1", "in_use": true,
  "extra_fields": [
    { "no": 1, "name": "회원번호", "value": "MEM-1" },
    { "no": 2, "name": "회원 등급", "value": "VIP" }
  ],
  "auto_debit": {
    "status": "registered", "status_name": "등록 완료",
    "methods": [ { "payment_kind": "CMS", "member_id": "M0001", "status": "registered",
                   "status_name": "등록 완료", "status_code": "900", "recurring": true, "updated_at": "…" } ]
  },
  "contracts": [
    { "contract_id": 1001, "contract_no": "TEST-ORD-1001", "status": "active", "status_name": "정상",
      "payment_type": "CMS", "monthly_amount": 20000, "billing_start": "2026-01-10", "billing_end": "2027-12-10" },
    { "contract_id": 1006, "contract_no": "TEST-ORD-1006", "status": "active", "status_name": "정상",
      "payment_type": "CARD", "monthly_amount": 30000, "billing_start": "2026-01-10", "billing_end": "2027-12-10" }
  ],
  "created_at": "…", "updated_at": "…"
}

추가 항목 값은 가맹점이 직접 넣은 값이라 그대로 돌려줍니다. 개인정보를 넣었다면 우리 회사 서버에서도 개인정보로 다루십시오.

고객 한 명

GET/v1/customers/{customer_code}members:read

고객코드로 한 명을 봅니다. data 는 Customer 객체 하나이고, 여러 이용기관에 같은 고객코드가 있으면 배열입니다. 우리 회원번호로 찾으려면 GET /v1/customers?customer_key= 를 쓰십시오.

추가 항목 이름표

GET/v1/extra-fieldsmembers:read

이용기관별로 사용 중인 추가 항목의 번호·이름·필수 여부·선택값과, 지금 고객키로 지정된 항목 번호(customer_key_field)를 돌려줍니다. 고객키 설정이 제대로 됐는지 프로그램에서 확인할 때 씁니다.

data[] 필드타입설명
accountstring이용기관 코드.
customer_key_fieldnumbernull 가능고객키로 지정한 추가 항목 번호. 미지정이면 null.
fieldsarray항목별 no · name · required(필수 여부) · options(선택형 항목의 선택값, 없으면 null).
응답의 data
[ { "account": "TEST", "customer_key_field": 1,
    "fields": [ { "no": 1, "name": "회원번호", "required": true, "options": null },
                { "no": 2, "name": "회원 등급", "required": false, "options": ["일반", "VIP"] } ] } ]

API 레퍼런스 · 결제수단

Member 객체 결제수단

회원의 결제수단 하나와 그 등록 상태입니다. 한 회원이 결제수단을 여럿 가질 수 있어, 목록은 결제수단 단위로 나옵니다. 고객 단위로 한 번에 보려면 Customer 객체의 auto_debit 이 더 간단합니다.

필드타입설명예시
accountstring이용기관 코드."TEST"
customer_codestring고객코드."C0001"
member_idstringnull 가능결제사(효성CMS) 회원 ID."M0001"
payment_kindstringnull 가능결제수단."CMS"
recurringboolean정기결제가 켜져 있는지.true
registration_statusstringenum결제사 등록 상태. 아래 값 목록 참고."registered"
registration_status_codestring페이소프트 내부 상태 코드(문의용)."900"
updated_atstringnull 가능마지막으로 바뀐 시각."2026-10-01T09:00:00"
registration_status뜻
registered등록이 끝나 청구할 수 있습니다.
pending등록 신청 중입니다.
failed등록에 실패했습니다. 고객 정보 확인이 필요합니다.
deleted결제사에서 삭제된 회원입니다.
unknown그 밖의 상태입니다.

결제수단 목록

GET/v1/membersmembers:read
파라미터타입설명
customer_codestring고객코드로 거릅니다.
sizenumber1~100, 기본 100.
cursorstring앞 응답의 next_cursor.
curl "https://api.paysoft.co.kr/v1/members?size=100" \
  -H "Authorization: Bearer $PAYSOFT_API_KEY"
응답
{
  "as_of": "2026-10-31T09:00:00+09:00",
  "request_id": "9b1c…",
  "data": [
    {
      "account": "TEST", "customer_code": "C0001", "member_id": "M0001",
      "payment_kind": "CMS", "recurring": true,
      "registration_status": "registered", "registration_status_code": "900",
      "updated_at": "2026-10-01T09:00:00"
    }
  ],
  "next_cursor": null
}

고객 한 명의 결제수단

GET/v1/members/{customer_code}members:read

고객코드 하나의 결제수단 목록을 돌려줍니다. payment_methods 의 각 항목은 Member 와 같은 필드(customer_code 제외)입니다.

응답의 data
{
  "customer_code": "C0001",
  "accounts": ["TEST"],
  "payment_methods": [
    { "account": "TEST", "member_id": "M0001", "payment_kind": "CMS", "recurring": true,
      "registration_status": "registered", "registration_status_code": "900", "updated_at": "…" }
  ]
}

예제 코드

공통 호출 함수 (재시도 포함)

모든 예제가 쓰는 기본 함수입니다. 키를 머리글에 넣고, 429·503 은 Retry-After 만큼 기다렸다 다시, 그 밖의 오류는 예외로 올립니다. 목록은 next_cursor 가 null 이 될 때까지 넘깁니다. 그대로 복사해 쓰셔도 됩니다.

# paysoft_client.py — pip install requests
import os
import time

import requests

BASE = "https://api.paysoft.co.kr/v1"
_session = requests.Session()
_session.headers["Authorization"] = f"Bearer {os.environ['PAYSOFT_API_KEY']}"


class PaysoftError(Exception):
    def __init__(self, status, code, message, request_id):
        super().__init__(f"{status} {code}: {message} (request_id={request_id})")
        self.status, self.code, self.request_id = status, code, request_id


def call(path, params=None, max_tries=4):
    """GET 한 번. 429·503·연결 오류는 기다렸다 다시 시도한다."""
    for attempt in range(1, max_tries + 1):
        try:
            r = _session.get(BASE + path, params=params, timeout=30)
        except requests.RequestException:
            if attempt == max_tries:
                raise
            time.sleep(2 ** attempt)
            continue
        if r.status_code == 200:
            return r.json()
        wait = int(r.headers.get("Retry-After", "0") or 0)
        if r.status_code in (429, 503) and attempt < max_tries and wait <= 60:
            time.sleep(wait or 5)
            continue
        err = r.json().get("error", {}) if "json" in r.headers.get("Content-Type", "") else {}
        raise PaysoftError(r.status_code, err.get("code"), err.get("message"), err.get("request_id"))


def iter_all(path, params):
    """목록 API 의 모든 쪽을 차례로 돌려준다."""
    params = dict(params)
    while True:
        body = call(path, params)
        yield from body["data"]
        if not body["next_cursor"]:
            return
        params["cursor"] = body["next_cursor"]

월 결제 상태 동기화

이번 달 모든 구독의 결제 상태를 받아 우리 DB 에 저장합니다. 하루 1~2번 예약 작업(cron 등)으로 돌리면 됩니다. 저장 테이블은 정기 동기화 설계의 예시를 따릅니다.

from datetime import date

from paysoft_client import PaysoftError, call, iter_all


def sync_month(month: str, db) -> int:
    """month 예: "202610". 받은 줄 수를 돌려준다."""
    as_of = None
    n = 0
    for b in iter_all("/billings", {"month": month}):
        f = b["failure"] or {}
        db.execute(
            """INSERT INTO paysoft_billing
                 (contract_id, month, member_no, customer_code, status, status_name,
                  billed_amount, paid_amount, unpaid_amount, review_required, key_shared,
                  failure_category, auto_retry, as_of)
               VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, NOW())
               ON DUPLICATE KEY UPDATE                       -- MySQL 기준. PostgreSQL 은 ON CONFLICT
                 status=VALUES(status), status_name=VALUES(status_name),
                 paid_amount=VALUES(paid_amount), unpaid_amount=VALUES(unpaid_amount),
                 review_required=VALUES(review_required), key_shared=VALUES(key_shared),
                 failure_category=VALUES(failure_category), auto_retry=VALUES(auto_retry), as_of=NOW()""",
            (b["contract_id"], b["month"], b["customer_key"], b["customer_code"], b["status"],
             b["status_name"], b["billed_amount"], b["paid_amount"], b["unpaid_amount"],
             b["review_required"], b["customer_key_shared"], f.get("category"), f.get("auto_retry")))
        n += 1
    db.commit()
    return n


if __name__ == "__main__":
    try:
        print(sync_month(date.today().strftime("%Y%m"), db=...))   # 우리 회사 DB 연결
    except PaysoftError as e:
        # 503 이 4번 이어져도 여기로 온다 — 저장된 상태를 지우지 말고 다음 실행을 기다린다
        print("동기화 실패:", e)

호출이 실패하면 저장된 상태를 지우거나 "미납"으로 바꾸지 마십시오. 다음 실행에서 다시 받으면 됩니다.

미납 안내 대상 뽑기

이번 달 미납 계약을 뽑아 실패 사유에 맞는 안내 문구를 고릅니다. 확인 필요(review_required), 자동 재결제 대상, 고객키가 겹치는 줄(customer_key_shared)은 안내에서 뺍니다.

from paysoft_client import iter_all

GUIDE = {
    "잔액부족": "결제 계좌의 잔액이 부족해 이번 달 이용료가 출금되지 않았습니다.",
    "한도초과": "결제 한도를 넘어 이번 달 이용료가 결제되지 않았습니다.",
    "계좌상태": "결제 계좌를 사용할 수 없습니다. 결제 계좌를 바꿔 주세요.",
    "카드상태": "결제 카드를 사용할 수 없습니다. 결제 카드를 바꿔 주세요.",
    "카드사확인": "카드사에서 결제를 거절했습니다. 카드사에 문의해 주세요.",
}


def unpaid_targets(month):
    for b in iter_all("/billings", {"month": month, "status": "unpaid"}):
        f = b["failure"] or {}
        if b["review_required"] or f.get("auto_retry"):
            continue                       # 페이소프트 확인·자동 재결제 대기 — 안내하지 않음
        if b["customer_key_shared"]:
            continue                       # 고객키가 다른 고객과 겹침 — 사람이 확인 후 안내
        yield {
            "member_no": b["customer_key"],          # 우리 회원번호 → 우리 DB 에서 연락처를 찾는다
            "contract_id": b["contract_id"],
            "amount": b["unpaid_amount"],
            "message": GUIDE.get(f.get("category"), "이번 달 이용료가 결제되지 않았습니다. 고객센터로 문의해 주세요."),
        }

재청구 판단 함수

재청구 전에 꼭 확인할 것의 순서를 그대로 옮겼습니다. Billing 과 Payment 를 모두 받습니다. 결과가 False 이면 절대 다시 청구하지 마십시오.

RETRYABLE_CATEGORIES = {"잔액부족", "한도초과", "일시장애"}
FAILED = {"unpaid", "failed"}            # Billing.status / Payment.status 의 실패 값


def can_recharge(item: dict) -> tuple[bool, str]:
    """item: Billing 또는 Payment 객체. (다시 청구해도 되는가, 이유)"""
    if item.get("review_required"):
        return False, "확인 필요 — 페이소프트 확인을 기다립니다"
    if item.get("status") not in FAILED:
        return False, f"실패 상태가 아님({item.get('status')})"
    f = item.get("failure") or {}
    if f.get("auto_retry"):
        return False, "자동 재결제 대상 — 페이소프트가 다시 청구합니다"
    if "retryable" in f:                  # Payment 에만 있음
        ok = f["retryable"]
    else:                                 # Billing 은 분류로 판단
        ok = f.get("category") in RETRYABLE_CATEGORIES
    return (True, "고객 안내 후 다시 청구 가능") if ok else (False, f"고객 조치 필요({f.get('category')})")

회원 화면에 결제 상태 표시

회원 한 명의 계약과 이번 달 결제 상태를 함께 보여 줍니다. 보통은 동기화해 둔 우리 DB 값을 보여 주고, "새로 고침"을 눌렀을 때만 API 를 부르면 호출 한도를 아낄 수 있습니다.

from paysoft_client import call


def member_billing_view(member_no: str, month: str) -> dict:
    customers = call("/customers", {"customer_key": member_no})["data"]
    if not customers:
        return {"registered": False}
    c = customers[0]
    billings = {b["contract_id"]: b for b in call("/billings", {"month": month, "customer_key": member_no})["data"]}
    return {
        "registered": True,
        "auto_debit": c["auto_debit"]["status_name"],              # 예: 등록 완료
        "contracts": [
            {
                "contract_id": k["contract_id"],
                "contract_status": k["status_name"],                 # 예: 정상
                "monthly_amount": k["monthly_amount"],
                "this_month": billings.get(k["contract_id"], {}).get("status_name", "청구 없음"),
            }
            for k in c["contracts"]
        ],
    }

AI 도구로 연동

AI 코딩 도구에 문서 주기

ChatGPT·Claude·Copilot·Cursor 같은 AI 코딩 도구에 이 문서를 주면 우리 회사 언어와 구조에 맞는 연동 코드를 빠르게 만들 수 있습니다. AI 가 읽기 쉽도록 같은 내용을 Markdown 한 파일로도 제공합니다.

AI용 문서 (Markdown)객체·파라미터·상태값·오류 코드·안전 규칙 전체
열기

콘솔에서 바로 만들기 — 가입한 가맹점은 콘솔 [연동 코드 만들기]에서 언어(Python·Java·PHP·Node.js)·DB·기능만 고르면 이 문서를 바탕으로 AI 가 연동 코드를 만들어 드립니다(하루 5회).

  1. 문서 주기 — 위 전체 복사를 눌러 AI 대화창에 붙여 넣거나, 주소(https://console.paysoft.co.kr/portal/docs.md)를 읽을 수 있는 도구라면 주소를 알려 줍니다.
  2. 요청하기 — 아래 요청문을 우리 회사 상황에 맞게 고쳐 붙여 넣습니다.
  3. 테스트 키로 확인 — 만들어진 코드를 테스트 키로 돌려 테스트 환경 표와 같은 결과가 나오는지 봅니다.

실제 API 키는 AI 대화창에 붙여 넣지 마십시오. 코드에는 PAYSOFT_API_KEY 환경변수 이름만 쓰게 하십시오. AI 가 만든 코드는 반드시 사람이 검토하고, 특히 재청구 판단 부분은 이 문서의 규칙과 같은지 확인하십시오.

바로 쓰는 요청문

AI 에게 보낼 요청문 (괄호 안을 바꾸십시오)
아래 "페이소프트 가맹점 조회 API" 문서를 읽고 연동 코드를 만들어 줘.

[우리 환경]
- 언어/프레임워크: (예: Python 3.12 + Django / Java 17 + Spring Boot / PHP 8 + Laravel / Node.js 20)
- DB: (예: MySQL 8 / PostgreSQL 16 / Oracle)
- 우리 회원번호 컬럼: (예: members.member_no) — 페이소프트 고객키(customer_key)와 같은 값
- 실행 방식: (예: 매일 10시·15시 cron / Spring @Scheduled)

[만들 것]
1. 공통 호출 모듈: Authorization: Bearer 키(환경변수 PAYSOFT_API_KEY), 429·503 은 Retry-After 만큼 기다렸다 재시도, next_cursor 로 모든 쪽 넘기기
2. 이번 달 /v1/billings 를 받아 (contract_id, month) 기준으로 우리 DB 에 덮어쓰는 동기화 작업
3. 미납(status=unpaid) 회원 목록 조회 함수
4. 테스트 코드 (테스트 키의 가상 데이터 기준: 1001 paid, 1002 paid(재출금), 1003 unknown, 1004 canceled, 1005 unpaid, 1006 in_progress)

[반드시 지킬 것]
- 이 API 는 조회만 한다. 결제·취소 호출을 만들지 마.
- status 가 unknown 이거나 review_required 가 true 면 재청구 대상에서 빼.
- failure.auto_retry 가 true 면 재청구 대상에서 빼.
- customer_key_shared 가 true 면 다른 고객과 고객키가 겹친 것이니 자동 안내·자동 처리에서 빼고 따로 표시해.
- 503 을 '결제 없음'으로 저장하지 말고, 호출이 실패하면 기존 저장값을 지우지 마.
- 문서에 없는 필드나 엔드포인트를 지어내지 마. 모르는 응답 필드는 무시해.
- API 키를 코드에 쓰지 마.

[문서]
(여기에 /portal/docs.md 내용을 붙여 넣기)

참고

오류 코드

오류 응답
{ "error": { "code": "RANGE_TOO_LONG", "message": "조회 기간은 최대 31일입니다.", "request_id": "…" } }
HTTPcode뜻과 조치다시 호출
401INVALID_KEY키가 없거나 틀렸거나 폐기·만료됐습니다. 콘솔에서 키 상태를 확인하십시오.아니오
403IP_NOT_ALLOWED키에 등록하지 않은 IP 에서 호출했습니다.아니오
403SCOPE_DENIED이 키로 쓸 수 없는 API 입니다(승인된 범위 밖). 권한 표 참고.아니오
403MERCHANT_INACTIVE이용이 정지됐거나 승인 전이거나, 연결된 이용기관이 없습니다.아니오
404NOT_FOUND조회 범위에서 찾지 못했습니다. 다른 가맹점의 데이터도 같은 응답입니다.아니오
422INVALID_PARAM요청 값 형식이 맞지 않습니다. message 에 문제 된 파라미터가 있습니다.아니오
422RANGE_TOO_LONG조회 기간이 31일을 넘었습니다.아니오
422CUSTOMER_KEY_NOT_SET고객키 항목을 지정하지 않고 customer_key 로 찾았습니다. 콘솔 [고객키 설정]에서 지정하십시오.아니오
422RESULT_TOO_LARGE한 번에 집계하기에 건수가 너무 많습니다. 페이소프트에 문의하십시오.아니오
429RATE_LIMITED호출 한도를 넘었습니다. Retry-After 초 뒤에 다시 호출하십시오.기다린 뒤
503UPSTREAM_UNAVAILABLE원천 조회에 실패했습니다. 빈 결과로 해석하지 말고 Retry-After 뒤 다시 조회하십시오.기다린 뒤
500INTERNAL처리 중 오류입니다. request_id 를 알려 주십시오.몇 번만

호출 한도

가맹점마다 초당·하루 호출 한도가 있습니다. 한도는 승인할 때 정하며 콘솔 첫 화면에서 확인할 수 있습니다(기본 초당 2회, 하루 10,000회). 초당 한도는 가맹점 단위로 세므로 키를 여러 개 써도 늘어나지 않습니다.

넘었을 때응답Retry-After
초당 한도429 RATE_LIMITED "초당 호출 한도를 넘었습니다."1초
하루 한도429 RATE_LIMITED "하루 호출 한도를 넘었습니다."3600초

한도를 아끼는 법 — 회원마다 따로 부르지 말고 /billings?month= 로 한 달 전체를 받아(100건씩) 저장하십시오. 회원 1만 명이면 하루 2번 동기화에 약 200번이면 충분합니다.

자주 묻는 질문

결제 요청이나 취소도 이 API 로 할 수 있나요?

아니요. 이 API 는 조회만 제공합니다. 결제·취소·환불·회원 등록은 페이소프트 관리자 화면에서 처리합니다.

결제가 끝나면 알림(웹훅)을 받을 수 있나요?

지금은 제공하지 않습니다. 하루 1~2번 /billings 를 조회해 저장하는 방식을 권합니다(정기 동기화 설계).

브라우저(자바스크립트)나 모바일 앱에서 바로 호출해도 되나요?

안 됩니다. 키가 노출되고, 허용 IP 에서만 호출할 수 있어 동작하지도 않습니다. 우리 회사 서버에서 호출하고 화면에는 서버가 저장한 값을 보여 주십시오.

고객코드(customer_code)를 우리 회원번호로 정할 수 있나요?

고객코드는 페이소프트가 자동으로 매깁니다. 대신 추가 항목에 회원번호를 넣고 고객키로 지정하면 우리 회원번호로 찾을 수 있습니다.

같은 회원번호가 여러 고객에 들어가 있으면 어떻게 되나요?

페이소프트 추가 항목은 같은 값을 막지 않습니다. 그 값으로 찾으면 고객이 모두 나오고 응답의 customer_key_shared 가 true 입니다. 콘솔 [고객키 설정] → 겹치는 값 보기에서 고객코드를 확인해 페이소프트에서 고쳐 주십시오(자세히).

Billing 과 Payment 중 무엇을 써야 하나요?

"이번 달 냈나?"는 Billing 입니다. 재출금까지 합친 최종 상태 한 줄이라 판단이 쉽습니다. 출금 시도마다 기록이 필요하거나 날짜별 정산 대사를 할 때만 Payment 를 쓰십시오.

status 가 unknown 인 건은 어떻게 하나요?

결제사 결과와 기록이 어긋나 결과를 확정하지 못한 건입니다. 페이소프트가 확인하니 다시 청구하지 말고 기다리십시오. 오래 남아 있으면 request_id 와 함께 문의하십시오.

테스트 키로 개발한 코드를 운영에서 그대로 써도 되나요?

네. 주소와 응답 형식이 같으니 환경변수의 키만 psk_live_ 로 바꾸면 됩니다. 운영 데이터에서 customer_key 를 쓰려면 고객키 설정을 먼저 하십시오.

응답에 모르는 필드가 생겼어요.

필드가 늘어나는 변경은 버전을 바꾸지 않고 반영합니다. 모르는 필드는 무시하도록 만드십시오. 바뀐 내용은 변경 이력에 적습니다.

변경 이력

날짜내용
2026-10-12customer_key_shared 추가(Billing·Payment·Contract·Customer) — 같은 고객키를 다른 고객도 쓰면 true. 고객키 검색·응답은 앞뒤 공백을 무시합니다
2026-10-12API 주소 확정 — https://api.paysoft.co.kr/v1 (API 전용 도메인). 콘솔 도메인의 /partner/ 경로는 쓰지 않습니다
2026-10-12문서 개편(API 변경 없음) — 객체·파라미터 표, 상태값 표, 실패 분류표, 용어 사전, 언어별 예제(Python·Node.js·Java·PHP), 문서 검색, AI 도구용 Markdown판(/portal/docs.md) 추가
2026-10-09구독 계약(/contracts), 계약별 월 결제 상태(/billings, /billings/summary), 결제일별 집계(/payments/summary), 고객(/customers — 추가 항목 15개·자동이체 등록 상태), 추가 항목 이름표(/extra-fields) 추가. 고객키(customer_key) 매칭, 결제결과에 contract_id · payment_count · month 조회 추가. 테스트 환경 가상 데이터를 계약별 월 시나리오로 변경
2026-10-09v1 공개 — 결제결과·회원 조회, 테스트 환경. failure.auto_retry 추가

응답에 필드가 새로 늘어나는 변경은 버전을 바꾸지 않고 반영합니다. 모르는 필드는 무시하도록 연동해 주십시오.