Developers
제휴사 연동

구매내역 API

쏠브가 user_key 를 보내면 회원이 지금 이용할 수 있는 상품코드 목록을 돌려주는, 제휴사가 구현하는 API 입니다.

쏠브가 user_key 를 보내면, 그 회원이 지금 이용할 수 있는 상품의 item_code 목록을 돌려주는 server-to-server API 입니다. 쏠브는 이 목록을 보고 책장에 책을 넣고 뺍니다.

핵심 규칙 하나

응답에는 현재 유효한 구매만 담아 주세요. 목록에서 빠진 상품은 쏠브 책장에서도 빠집니다(목록 전체가 빈 경우는 예외, 아래 표 참고). 구매 이력 전체가 아니라 "지금 이 순간 이용 가능한 것"을 돌려주는 API 입니다.

한눈에 보기

항목내용
호출하는 쪽쏠브 서버 → 제휴사 서버
메서드·URLPOST, URL 은 제휴사가 정합니다
형식Content-Type: application/json 요청, JSON 응답
인증제휴사가 발급한 시크릿을 쏠브가 헤더에 담습니다. 헤더 이름과 방식은 제휴사가 정합니다.
타임아웃호출당 10초
재시도5xx·타임아웃이면 최대 2번 더 호출합니다. 4xx 는 재시도하지 않습니다.
호출 시점연동 직후, 회원의 책장 진입, "전체 동기화" 버튼 (개념과 용어)
페이지네이션사용하지 않습니다. 유효한 항목을 한 번에 모두 돌려주세요.

요청

쏠브가 보내는 요청 (curl 로 재현)
curl -X POST https://api.partner.example.com/solve/purchases \
  -H 'Content-Type: application/json' \
  -H 'X-Solve-Secret: <제휴사가 발급한 시크릿>' \
  -d '{"user_key": "100023"}'
필드타입필수설명
user_keystring필수연동할 때 받은 회원 식별자

응답

성공 (200)

{
  "item_list": [
    { "item_code": "BOOK_TOEIC_2026", "item_name": "토익 실전 2026" },
    { "item_code": "PKG_TOEIC_FULL" }
  ]
}
필드타입필수설명
item_listarray필수현재 유효한 구매 항목. 없으면 빈 배열 []
item_list[].item_codestring필수제휴사 상품코드. 파트너 어드민에 입력한 연동코드와 같은 값이어야 합니다. 책이 아니라 상품 단위이며, 한 코드에 여러 권이 들어갈 수 있습니다.
item_list[].item_namestring선택표시·문의 대응용 상품명
item_list[].pricenumber 또는 string선택결제 금액(원). 아래 "선택 필드" 참고
item_list[].paid_atstring선택결제 시각. 아래 "선택 필드" 참고
item_list[].valid_fromstring선택이용 시작. 아래 "선택 필드" 참고
item_list[].valid_untilstring선택이용 종료. 아래 "선택 필드" 참고

위 표에 없는 다른 필드를 더 담아도 쏠브는 무시하므로 괜찮습니다.

응답 본문은 1MB(1,048,576바이트) 이하여야 합니다. 쏠브는 1MB 를 넘는 응답을 받지 않습니다. 넘으면 그 호출은 실패로 처리하고 재시도하지 않으며, 기존 책장은 유지합니다. 압축해서 보내는 경우는 푼 뒤의 크기로 셉니다. item_list 에는 필요한 필드만 담아 주세요.

선택 필드 (v1.1)

항목마다 아래 네 필드를 선택으로 더 담을 수 있습니다. 쏠브는 정산과 문의 대응에 사용합니다.

필드형식
price0 이상 정수(숫자) 또는 숫자만으로 된 문자열(1~12자리). 원 단위입니다.
paid_atISO 8601 날짜·시각. Z·+09:00 같은 오프셋이 있으면 그대로 읽고, 오프셋이 없으면 한국 시간(+09:00) 으로 읽습니다. 날짜만 보내면 그날 00:00 한국 시간입니다.
valid_from, valid_until1~32자 문자열. 쏠브는 해석하지 않고 그대로 기록합니다.
  • 네 필드 모두 없어도 동작합니다. v1.0 대로 구현한 응답은 수정 없이 유효합니다.
  • 형식이 틀리면 그 필드만 무시하고, 책 지급에는 영향이 없습니다.
  • price 를 안 보내거나 0 이면 파트너 어드민에 등록한 가격으로 정산합니다.
  • valid_until 이 지나도 책은 자동으로 빠지지 않습니다. 책을 회수하려면 지금처럼 목록에서 빼 주세요.
v1.1 예시 — 선택 필드를 모두 채운 항목과 item_code 만 있는 항목
{
  "item_list": [
    {
      "item_code": "BOOK_TOEIC_2026",
      "item_name": "토익 실전 2026",
      "price": 25000,
      "paid_at": "2026-09-01T14:30:00+09:00",
      "valid_from": "2026-09-01",
      "valid_until": "2027-08-31"
    },
    { "item_code": "PKG_TOEIC_FULL" }
  ]
}

상황별 응답

빈 목록과 오류는 결과가 정반대입니다. 이 표대로 응답해 주세요.

상황응답쏠브의 처리
구매한 상품이 있음200 + 유효한 항목목록에 있는 책을 책장에 넣습니다
일부 상품이 환불·만료됨해당 항목을 목록에서 뺌다음 동기화 때 그 책을 책장에서 뺍니다
구매한 상품이 없음 (전부 환불 포함)200 + {"item_list": []}새로 넣을 책이 없습니다. 이미 있는 책은 빼지 않습니다 (아래 참고)
회원이 없음 (탈퇴 등)200 + {"item_list": []}위와 같습니다
시크릿 불일치401 또는 403인증 실패로 기록하고 기존 책장은 유지합니다
요청 형식 오류400오류로 기록하고 기존 책장은 유지합니다
서버 오류, DB 장애, 점검500·503 등 5xx재시도하고, 실패해도 기존 책장은 유지합니다

쏠브는 401 과 403 을 구분하지 않고 같은 인증 실패로 처리합니다. 둘 다 재시도하지 않습니다.

빈 목록일 때 책을 빼지 않는 이유: 장애 중인 API 가 실수로 빈 목록을 돌려주면 회원 전원의 책이 사라질 수 있어, 쏠브는 목록이 통째로 비어 있으면 회수를 건너뜁니다. 그래서 회원의 모든 상품이 환불되거나 회원이 탈퇴한 경우에는 책이 책장에 남습니다. 이런 경우의 회수가 필요하면 쏠브 담당자와 처리 방법을 협의해 주세요.

조회가 실패하면 200 이 아니라 5xx 로 응답하세요

예외를 삼키고 200 을 보내면 쏠브는 정상 응답으로 처리합니다. 특히 여러 저장소 중 일부만 조회된 불완전한 목록을 보내면, 빠진 상품이 회원 책장에서 회수됩니다. 조회가 하나라도 실패하면 5xx 로 응답해 주세요.

구현 예시

시크릿 확인 → 회원의 현재 유효한 구매 조회 → item_list 응답 순서입니다. 조회 함수는 제휴사 시스템에 맞게 바꾸세요.

import express from 'express';
import crypto from 'node:crypto';

const app = express();
app.use(express.json());

const SOLVE_SECRET = Buffer.from(process.env.SOLVE_SECRET ?? '');
// 시크릿이 비어 있으면 빈 헤더 요청이 통과하므로 서버를 띄우지 않는다
if (SOLVE_SECRET.length === 0) throw new Error('SOLVE_SECRET is not set');

function isFromSolve(req) {
  const given = Buffer.from(req.get('X-Solve-Secret') ?? '');
  return given.length === SOLVE_SECRET.length && crypto.timingSafeEqual(given, SOLVE_SECRET);
}

app.post('/solve/purchases', async (req, res) => {
  if (!isFromSolve(req)) return res.status(403).json({ error: 'forbidden' });

  const userKey = req.body?.user_key;
  if (typeof userKey !== 'string' || userKey === '') {
    return res.status(400).json({ error: 'invalid_request' });
  }

  try {
    // 환불·만료를 뺀, 지금 이용 가능한 구매만 조회. 회원이 없으면 빈 배열.
    const purchases = await findActivePurchases(userKey);
    return res.json({
      item_list: purchases.map((p) => ({ item_code: p.productCode, item_name: p.productName })),
    });
  } catch (err) {
    // 조회 실패를 200 으로 보내면 불완전한 목록 기준으로 책이 회수될 수 있다 → 반드시 5xx
    console.error('solve purchases failed', err);
    return res.status(500).json({ error: 'internal_error' });
  }
});

흐름

구현 체크리스트

  • 환불·만료된 상품을 목록에서 뺀다
  • 구매가 없거나 회원이 없으면 200 + 빈 배열로 응답한다
  • 조회 실패·장애는 5xx 로 응답한다 (빈 배열·불완전한 목록 금지)
  • item_code 가 파트너 어드민에 입력한 연동코드와 같다 (쏠브는 앞뒤 공백을 떼고 비교하고, 그 밖은 대소문자까지 글자 그대로 같아야 한다)
  • 10초 안에 응답한다
  • 구매 항목을 페이지 나누지 않고 한 번에 돌려준다
  • 응답 본문을 1MB(1,048,576바이트) 이하로 만든다

관련 문서

이 페이지 목차