Post

AInfo 프로젝트 결제 기능 구현

AInfo 프로젝트에서 유료 기능을 위한 결제 기능을 Django 기반으로 어떻게 구현했는지 소개합니다.

AInfo 프로젝트 결제 기능 구현

결제 기능 도입 이유

AInfo 프로젝트는 기본적으로 무료 서비스지만, 추가적인 프리미엄 기능 제공을 위해 크레딧 기반 결제 시스템을 도입했습니다. 사용자는 결제를 통해 크레딧을 구매하고, 이 크레딧은 특정 프리미엄 챗봇 기능을 사용할 때 차감됩니다.

전체 흐름

  1. 사용자가 결제 페이지에서 상품(크레딧)을 선택
  2. PortOne 결제창에서 결제 수행
  3. PortOne이 Webhook으로 결제 성공 여부를 백엔드에 전달
  4. 백엔드에서 결제 정보 저장 및 상태 업데이트
  5. 사용자가 결제 ID로 최종 검증 요청
  6. 상태가 ‘Paid’면 해당 사용자에게 크레딧 +100 지급

모델 정의 (payments/models.py)

1
2
3
4
5
6
7
class Payment(models.Model):
    product_name = models.CharField(max_length=255, default="100 크레딧")
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
    status = models.CharField(max_length=20, null=True, blank=True)  # 결제 상태
    payment_id = models.CharField(max_length=100, unique=True)  # 고유 결제번호
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
  • 결제 상태(status)는 Paid, Ready, Failed 등 다양하게 들어올 수 있습니다.
  • 최초 Webhook에서는 user가 비어 있으며, 결제 검증 이후에 채워집니다.

Webhook 처리 (payment_webhook)

1
2
3
4
5
6
7
8
9
10
11
@csrf_exempt
def payment_webhook(request):
    if request.method == "POST":
        data = json.loads(request.body)
        payment_status = data.get("status")
        payment_id = data.get("payment_id")

        Payment.objects.update_or_create(
            payment_id=payment_id,
            defaults={"status": payment_status},
        )
  • PortOne에서 전송하는 Webhook을 받아서 DB에 결제 상태를 저장합니다.
  • Webhook 요청은 외부에서 오므로 @csrf_exempt 처리합니다.

결제 검증 (PayVerify)

1
2
3
4
5
6
7
8
9
10
11
class PayVerify(APIView):
    def post(self, request):
        user = request.user
        payment_id = request.data.get("payment_id")
        payment = Payment.objects.filter(payment_id=payment_id).first()

        if payment.status == "Paid":
            payment.user = user
            payment.save()
            user.credit += 100
            user.save()
  • 사용자가 결제 후 전달받은 payment_id를 백엔드로 보내 검증합니다.
  • status가 “Paid”인 경우에만 실제로 유저에게 크레딧이 적립됩니다.

URL 구조

1
2
3
4
urlpatterns = [
    path("pay-verify/", PayVerify.as_view(), name="pay_verify"),
    path("webhook/kg/", payment_webhook),
]
  • 프론트에서는 결제 완료 후 /pay-verify/로 POST 요청을 보내 검증을 수행합니다.
  • 웹훅은 PortOne 설정에서 /webhook/kg/ 경로로 전달되도록 설정했습니다.

마무리

이 구조를 통해 결제 성공 여부를 안정적으로 수신하고, 그에 따라 사용자에게 크레딧을 부여할 수 있었습니다. 또한 Webhook을 통해 결제 상태를 먼저 받고, 사용자 요청으로 검증하는 이중 구조 덕분에 보안성과 유연성을 모두 확보할 수 있었습니다.

This post is licensed under CC BY 4.0 by the author.