Skip to Content

Python SDK

payri는 Python 3.10 이상을 지원하고 런타임 의존성은 httpx  하나뿐이에요.
동기(Payri)·비동기(AsyncPayri) 클라이언트를 같은 표면으로 제공해요.

pip install payri

소스는 sdk-python 저장소 에 있어요.

시작하기

서버에서 Payri 클라이언트에 secret 키를 넣어 사용해요.
클라이언트는 context manager라서 with 블록을 벗어나면 연결이 정리돼요.

import os from payri import Payri with Payri(api_key=os.environ["PAYRI_API_KEY"]) as client: page = client.products.list(page=1, limit=20) result = client.licenses.verify(key="XXXX-YYYY-ZZZZ") # 체크아웃 세션 — 반환된 url로 구매자를 리다이렉트해요. session = client.checkout.create(link_id="link_abc") redirect_url = session["url"]

API 키는 서버 전용 비밀키예요.
클라이언트 배포물·저장소에 포함하지 말고 환경변수나 시크릿 매니저로 관리하세요.
구매자 쪽에서 필요한 건 키를 받지 않는 PayriPublic뿐이에요.

비동기 환경에서는 AsyncPayri를 써요.

from payri import AsyncPayri async with AsyncPayri(api_key=os.environ["PAYRI_API_KEY"]) as client: page = await client.products.list(limit=10)

with 없이 길게 쓰는 경우 close()(비동기는 aclose())로 직접 정리해요.
단, http_client 옵션으로 직접 주입한 httpx 클라이언트는 SDK가 닫지 않아요.

생성자 옵션

옵션기본값설명
api_key(필수)서버 전용 secret 키 (key_live_* / key_test_*)
base_urlhttps://api.payri.krAPI base URL. 스테이징 등 다른 환경을 겨눌 때만 변경해요
timeouthttpx 기본값요청 타임아웃(초)
http_client자동 생성재사용할 httpx.Client/httpx.AsyncClient 직접 주입

리소스

메서드 인자는 keyword-only예요 (client.checkout.create(link_id=...)).

리소스메서드REST 문서
client.productslist() · get(product_id)Products
client.orderslist() · get(order_id) · refund(order_id, ...)Orders · Refunds
client.subscriptionslist() · get(subscription_id) · cancel(subscription_id, reason=...)Subscriptions
client.checkoutcreate(link_id=..., ...) · get(session_id)Checkout
client.licensesverify(key=...) · activate(key=...) · deactivate(key=..., activation_id=...)Licenses

키 없는 공개 클라이언트: PayriPublic

PayriPublic은 API 키를 받지 않는 공개 클라이언트예요.
구매자가 이름·이메일 본인확인으로 자기 주문의 라이선스를 조회하는 용도예요.

from payri import PayriPublic license = PayriPublic().lookup_license( "ord_123", name="홍길동", email="hong@example.com" ) if license is None: ... # 주문이 없거나 이름·이메일 불일치 — 존재 여부는 알려주지 않아요

비동기 버전 AsyncPayriPublic도 같은 표면으로 제공해요.

웹훅 검증

Webhooks.construct_eventStandard Webhooks  서명(webhook-id·webhook-timestamp·webhook-signature 헤더)을 검증하고 { "type": ..., "data": ... } 이벤트를 반환해요.
검증에는 원본(raw) 본문이 필요해요 — JSON으로 파싱한 dict를 다시 직렬화해 넘기면 서명이 깨져요.
검증 실패 시 PayriWebhookError를 던지고, reason으로 사유를 확인할 수 있어요.

import os from payri import Webhooks, PayriWebhookError try: event = Webhooks.construct_event( payload=raw_body, # 원본 바디 (bytes 또는 str) 그대로 headers=headers, # 요청 헤더 매핑 그대로 secret=os.environ["PAYRI_WEBHOOK_SECRET"], ) except PayriWebhookError as err: ... # err.reason 확인 후 400 응답 if event["type"] == "order.paid": ...

같은 이벤트가 중복 도착할 수 있으니 webhook-id 헤더를 멱등 키로 쓰는 걸 권장해요. (재전송과 멱등성)

FastAPI

import os from fastapi import FastAPI, Request, Response from payri import Webhooks, PayriWebhookError app = FastAPI() @app.post("/webhooks/payri") async def payri_webhook(request: Request): payload = await request.body() # 원본 바디 — request.json() 금지 try: event = Webhooks.construct_event( payload=payload, headers=request.headers, secret=os.environ["PAYRI_WEBHOOK_SECRET"], ) except PayriWebhookError: return Response(status_code=400) if event["type"] == "order.paid": ... # webhook-id로 멱등 확인 후 처리 return {"received": True}

Flask

import os from flask import Flask, request from payri import Webhooks, PayriWebhookError app = Flask(__name__) @app.post("/webhooks/payri") def payri_webhook(): try: event = Webhooks.construct_event( payload=request.get_data(), # 원본 바디 — request.json 금지 headers=request.headers, secret=os.environ["PAYRI_WEBHOOK_SECRET"], ) except PayriWebhookError: return "", 400 if event["type"] == "order.paid": ... # webhook-id로 멱등 확인 후 처리 return "", 200

Django

외부(페이리)에서 오는 요청이라 CSRF 토큰이 없으므로 해당 뷰만 csrf_exempt로 제외해요.

import os from django.http import HttpResponse, JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from payri import Webhooks, PayriWebhookError @csrf_exempt @require_POST def payri_webhook(request): try: event = Webhooks.construct_event( payload=request.body, # 원본 바디 headers=request.headers, secret=os.environ["PAYRI_WEBHOOK_SECRET"], ) except PayriWebhookError: return HttpResponse(status=400) if event["type"] == "order.paid": ... # webhook-id로 멱등 확인 후 처리 return JsonResponse({"received": True})

이벤트 종류와 페이로드 형식은 이벤트 종류에서 확인해요.

에러 처리

모든 에러는 PayriError를 상속해요. (에러 계층)
JS SDK와 달리 API 에러 클래스 표기가 PayriAPIError(대문자 API)인 점에 주의하세요.

import os from payri import Payri, PayriAPIError, PayriAuthError, PayriConnectionError try: with Payri(api_key=os.environ["PAYRI_API_KEY"]) as client: order = client.orders.get("ord_123") except PayriAuthError: ... # 401/403 — 키 누락·무효, 스코프 부족, IP 제한 except PayriAPIError as err: ... # 그 외 4xx/5xx — err.status, err.code, err.body 확인 except PayriConnectionError: ... # 네트워크 실패·타임아웃 — 응답을 받지 못했어요

PayriAuthErrorPayriAPIError를 상속하므로 먼저 잡아야 해요.
에러 코드 목록은 에러 코드에서 확인해요.

다음 단계

웹훅 이벤트 종류

구독할 수 있는 이벤트와 페이로드 형식을 확인해요.

재전송과 멱등성

중복 이벤트를 안전하게 처리해요.

라이선스 연동

키 발급 방식을 내 시스템에 맞게 연결해요.

Last updated on