본문으로

가이드

레시피: API 크레딧

API나 AI 기능용 선불 크레딧을 팔고 요청마다 하나씩 차감하세요. 잔액이 부족하면 충전 링크와 함께 402로 응답합니다.

Markdown으로 열기

구매자는 크레딧 패키지를 미리 결제하고, 요청마다 몇 개씩 씁니다. 장부는 Perly Pay가 맡습니다: 차감은 마이너스를 허용하지 않는 원자적 호출 하나이고, 구매자마다 자기 충전 페이지가 있습니다. 자동으로 청구되는 것은 없습니다 — 크레딧이 떨어지면 구매자가 충전합니다. 크레딧의 모든 것은 Sell credits에 있고, 여기는 그 둘레의 레시피입니다.

어시스턴트와 함께: 이 레시피는 Perly Pay 플러그인의 sell-credits 스킬이 내 프로젝트에서 실행합니다. 실행 가능한 예제는 플러그인 저장소의 examples/api-credits(MIT)입니다.

작동 방식

  1. 1

    크레딧 패키지

    model: "credit_pack", 센트 단위 unit_amount, credits가 있는 상품과 가격. 모드당 한 번.

  2. 2

    내 사용자가 고객

    external_id(내 사용자 ID)로 한 번 만들면, 이후 credits.*가 그 ID를 받습니다.

  3. 3

    충전

    credits.topUpUrl(userId)는 구매자 전용 페이지입니다: 패키지를 골라 결제합니다. 결제된 패키지마다 크레딧이 더해집니다.

  4. 4

    요청당 차감

    먼저 credits.consume(userId, cost, { idempotencyKey }), 그다음 작업. 같은 키로 재시도하면 첫 차감이 돌아오므로, 요청마다 키는 내 서버가 만들고 호출하는 쪽에서 절대 받지 마세요.

  5. 5

    부족하면?

    insufficient_credits(409)는 아무것도 차감하지 않습니다: 500이 아니라 충전 페이지와 함께 402로 답하세요.

Spend one request's credits
try {
  await pay.credits.consume(userId, cost, { product: PRODUCT, action: "api", idempotencyKey: `req-${userId}-${requestId}` });
} catch (e) {
  if (e.code !== "insufficient_credits") throw e;
  return reply(402, { top_up: await pay.credits.topUpUrl(userId, { product: PRODUCT }) });
}
// now do the work

예제 실행

  1. 1

    설치

    examples/api-credits에서 npm install(Node 20.12+).

  2. 2

    키

    .env.example을 .env로 복사하고 PERLY_PAY_SECRET_KEY에 테스트 키(sk_test_…)를 넣으세요.

  3. 3

    준비

    npm run setup이 내 키로 상품과 가격을 만들고 .env용 ID를 출력합니다.

  4. 4

    실행

    README에 적힌 대로 npm start(또는 npm run dev).

  5. 5

    지갑 없이 결제

    POST /v1/shout를 부르고(402), POST /top-up이 주는 페이지를 열어 패키지를 고르고, 청구서에서 Simulate payment를 누른 뒤 다시 부르세요: 200과 X-Credits-Remaining: 499.

함정

  • 멱등성 키 없는 차감: 재시도된 요청이 두 번 차감합니다.
  • 클라이언트가 보내는 것(Idempotency-Key 헤더)에서 차감 키를 가져오기: 같은 키는 첫 차감을 성공으로 돌려주므로 클라이언트가 결제 없이 작업을 또 받습니다.
  • 잔액을 확인한 뒤 차감하는 두 단계. consume이 스스로 마이너스를 막습니다. 잔액은 표시용입니다.
  • 작업 먼저, 차감 나중. 반대로: 차감 먼저.
  • 라이브 키에 테스트 ID. 상품, 가격, 웹훅은 한 모드에 속합니다: 출시 전에 sk_live_…로 설정을 다시 하세요.