[Django]Iamport 연동하기 - 2

Edit

[Django]Iamport 연동하기 - 2

기본적으로 결제 모델을 만들 때 가장 중요한 것인 order_idtransaction_id이다. order_id는 서버 내에서 자동으로 생성하는 주문 번호이고 transaction_id는 아임포트에서 생성해주는 고유 번호이다. 서버에서 결제가 정상적으로 이루어졌는지 확인하기 위해서 transaction_id로 통해 조회하게 된다.

이제 아임포트와 통신할 수 있는 모듈을 만들어 보자. billing 앱 안에 iamport.py을 만들어보자.

#billing/iamport.py
import requests
import json

from django.conf import settings


def get_access_token():
access_data = {
'imp_key': settings.IAMPORT_KEY,
'imp_secret': settings.IAMPORT_SECRET
}

url = "https://api.iamport.kr/users/getToken"
req = requests.post(url, data=access_data)
access_res = req.json()

if access_res['code'] is 0:
return access_res['response']['access_token']
else:
return None


def validation_prepare(merchant_id, amount, *args, **kwargs):
access_token = get_access_token()

if access_token:
access_data = {
'merchant_uid': merchant_id,
'amount': amount
}

url = "https://api.iamport.kr/payments/prepare"

headers = {
'Authorization': access_token
}

req = requests.post(url, data=access_data, headers=headers)
res = req.json()

if res['code'] is not 0:
raise ValueError("API 연결에 문제가 생겼습니다.")
else:
raise ValueError("인증 토큰이 없습니다.")


def get_transaction(merchant_id, *args, **kwargs):
access_token = get_access_token()

if access_token:
url = "https://api.iamport.kr/payments/find/" + merchant_id

headers = {
'Authorization': access_token
}

req = requests.post(url, headers=headers)
res = req.json()

if res['code'] is 0:
context = {
'imp_id': res['response']['imp_uid'],
'merchant_id': res['response']['merchant_uid'],
'amount': res['response']['amount'],
'status': res['response']['status'],
'type': res['response']['pay_method'],
'receipt_url': res['response']['receipt_url']
}
return context
else:
return None
else:
raise ValueError("인증 토큰이 없습니다.")

아임포트와 통신 할 때는 크게 3가지 과정을 나누어 진행된다. get_access_token은 아임포트 서버에 접근할 수 있는 토큰을 발급 받는 과정으로 발급 받은 토큰으로 유저가 결제한 정보를 가져오게 된다. validation_prepare은 결제를 검증하는 단계로 유저가 요청한 금액과 아임포트에 있는 결제 금액이 일치하는지 검증하는 단계이다. get_transaction은 결제가 끝나고 나서 결제에 대한 정보를 가져오는 단계이다.

이제 포인트에 대한 Manager를 만들어보자. model 부분을 수정하자.

# billing/models.py
from django.db import models
# User 모델은 알아서 가져오기
from users.models import MyUser
from billing.iamport import validation_prepare, get_transaction

class Point(models.Model):
user = models.OneToOneField(MyUser)
point = models.PositiveIntegerField(default=0)
created = models.DateTimeField(auto_now_add=True, auto_now=False)
timestamp = models.DateTimeField(auto_now_add=False, auto_now=True)

def __str__(self):
return str(self.point)


class PointTransactionManager(models.Manager):
# 새로운 트랜젝션 생성
def create_new(self, user, amount, type, success=None, transaction_status=None):
if not user:
raise ValueError("유저가 확인되지 않습니다.")
short_hash = hashlib.sha1(str(random.random())).hexdigest()[:2]
time_hash = hashlib.sha1(str(int(time.time()))).hexdigest()[-3:]
base = str(user.email).split("@")[0]
key = hashlib.sha1(short_hash + time_hash + base).hexdigest()[:10]
new_order_id = "%s" % (key)

# 아임포트 결제 사전 검증 단계
validation_prepare(new_order_id, amount)

# 트랜젝션 저장
new_trans = self.model(
user=user,
order_id=new_order_id,
amount=amount,
type=type
)

if success is not None:
new_trans.success = success
new_trans.transaction_status = transaction_status

new_trans.save(using=self._db)
return new_trans.order_id

# 생선된 트랜잭션 검증
def validation_trans(self, merchant_id):
result = get_transaction(merchant_id)

if result['status'] is not 'paid':
return result
else:
return None

def all_for_user(self, user):
return super(PointTransactionManager, self).filter(user=user)

def get_recent_user(self, user, num):
return super(PointTransactionManager, self).filter(user=user)[:num]


class PointTransaction(models.Model):
user = models.ForeignKey(MyUser)
transaction_id = models.CharField(max_length=120, null=True, blank=True)
order_id = models.CharField(max_length=120, unique=True)
amount = models.PositiveIntegerField(default=0)
success = models.BooleanField(default=False)
transaction_status = models.CharField(max_length=220, null=True, blank=True)
type = models.CharField(max_length=120)
created = models.DateTimeField(auto_now_add=True, auto_now=False)

objects = PointTransactionManager()

def __str__(self):
return self.order_id

class Meta:
ordering = ['-created']

그 다음으로는 Transaction이 일어나서 나서 검증하는 post_save를 만들어보자. 같은 model 안에서 작성하면 된다.

# billing/models.py
import time
import random
import hashlib
from django.db.models.signals import post_save

def new_point_trans_validation(sender, instance, created, *args, **kwargs):
if instance.transaction_id:
# 거래 후 아임포트에서 넘긴 결과
v_trans = PointTransaction.objects.validation_trans(
merchant_id=instance.order_id
)

res_merchant_id = v_trans['merchant_id']
res_imp_id = v_trans['imp_id']
res_amount = v_trans['amount']

# 데이터베이스에 실제 결제된 정보가 있는지 체크
r_trans = PointTransaction.objects.filter(
order_id=res_merchant_id,
transaction_id=res_imp_id,
amount=res_amount
).exists()

if not v_trans or not r_trans:
raise ValueError('비정상적인 거래입니다.')


post_save.connect(new_point_trans_validation, sender=PointTransaction)

결제를 위한 model 부분은 거의 다 끝났다. 다음으로 진행 할 것은 view와 template 단으로 실제 유저가 결제를 할 수 있도록 만들어보자.

%23%23%23%5BDjango%5DIamport%20%uC5F0%uB3D9%uD558%uAE30%20-%202%0A@%28Django%29%5Bdjango%2C%20iamport%5D%0A%0A%0A%20%uAE30%uBCF8%uC801%uC73C%uB85C%20%uACB0%uC81C%20%uBAA8%uB378%uC744%20%uB9CC%uB4E4%20%uB54C%20%uAC00%uC7A5%20%uC911%uC694%uD55C%20%uAC83%uC778%20%60order_id%60%uC640%20%60transaction_id%60%uC774%uB2E4.%20%60order_id%60%uB294%20%uC11C%uBC84%20%uB0B4%uC5D0%uC11C%20%uC790%uB3D9%uC73C%uB85C%20%uC0DD%uC131%uD558%uB294%20%uC8FC%uBB38%20%uBC88%uD638%uC774%uACE0%20%60transaction_id%60%uB294%20%uC544%uC784%uD3EC%uD2B8%uC5D0%uC11C%20%uC0DD%uC131%uD574%uC8FC%uB294%20%uACE0%uC720%20%uBC88%uD638%uC774%uB2E4.%20%uC11C%uBC84%uC5D0%uC11C%20%uACB0%uC81C%uAC00%20%uC815%uC0C1%uC801%uC73C%uB85C%20%uC774%uB8E8%uC5B4%uC84C%uB294%uC9C0%20%uD655%uC778%uD558%uAE30%20%uC704%uD574%uC11C%20%60transaction_id%60%uB85C%20%uD1B5%uD574%20%uC870%uD68C%uD558%uAC8C%20%uB41C%uB2E4.%0A%0A%uC774%uC81C%20%uC544%uC784%uD3EC%uD2B8%uC640%20%uD1B5%uC2E0%uD560%20%uC218%20%uC788%uB294%20%uBAA8%uB4C8%uC744%20%uB9CC%uB4E4%uC5B4%20%uBCF4%uC790.%20billing%20%uC571%20%uC548%uC5D0%20%60iamport.py%60%uC744%20%uB9CC%uB4E4%uC5B4%uBCF4%uC790.%0A%0A%60%60%60python%0A%23billing/iamport.py%0Aimport%20requests%0Aimport%20json%0A%0Afrom%20django.conf%20import%20settings%0A%0A%0Adef%20get_access_token%28%29%3A%0A%20%20%20%20access_data%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%27imp_key%27%3A%20settings.IAMPORT_KEY%2C%0A%20%20%20%20%20%20%20%20%27imp_secret%27%3A%20settings.IAMPORT_SECRET%0A%20%20%20%20%7D%0A%0A%20%20%20%20url%20%3D%20%22https%3A//api.iamport.kr/users/getToken%22%0A%20%20%20%20req%20%3D%20requests.post%28url%2C%20data%3Daccess_data%29%0A%20%20%20%20access_res%20%3D%20req.json%28%29%0A%0A%20%20%20%20if%20access_res%5B%27code%27%5D%20is%200%3A%0A%20%20%20%20%20%20%20%20return%20access_res%5B%27response%27%5D%5B%27access_token%27%5D%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%0Adef%20validation_prepare%28merchant_id%2C%20amount%2C%20*args%2C%20**kwargs%29%3A%0A%20%20%20%20access_token%20%3D%20get_access_token%28%29%0A%0A%20%20%20%20if%20access_token%3A%0A%20%20%20%20%20%20%20%20access_data%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%27merchant_uid%27%3A%20merchant_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%27amount%27%3A%20amount%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20url%20%3D%20%22https%3A//api.iamport.kr/payments/prepare%22%0A%0A%20%20%20%20%20%20%20%20headers%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%27Authorization%27%3A%20access_token%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20req%20%3D%20requests.post%28url%2C%20data%3Daccess_data%2C%20headers%3Dheaders%29%0A%20%20%20%20%20%20%20%20res%20%3D%20req.json%28%29%0A%0A%20%20%20%20%20%20%20%20if%20res%5B%27code%27%5D%20is%20not%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%22API%20%uC5F0%uACB0%uC5D0%20%uBB38%uC81C%uAC00%20%uC0DD%uACBC%uC2B5%uB2C8%uB2E4.%22%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22%uC778%uC99D%20%uD1A0%uD070%uC774%20%uC5C6%uC2B5%uB2C8%uB2E4.%22%29%0A%0A%0Adef%20get_transaction%28merchant_id%2C%20*args%2C%20**kwargs%29%3A%0A%20%20%20%20access_token%20%3D%20get_access_token%28%29%0A%0A%20%20%20%20if%20access_token%3A%0A%20%20%20%20%20%20%20%20url%20%3D%20%22https%3A//api.iamport.kr/payments/find/%22%20+%20merchant_id%0A%0A%20%20%20%20%20%20%20%20headers%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%27Authorization%27%3A%20access_token%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20req%20%3D%20requests.post%28url%2C%20headers%3Dheaders%29%0A%20%20%20%20%20%20%20%20res%20%3D%20req.json%28%29%0A%0A%20%20%20%20%20%20%20%20if%20res%5B%27code%27%5D%20is%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20context%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27imp_id%27%3A%20res%5B%27response%27%5D%5B%27imp_uid%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27merchant_id%27%3A%20res%5B%27response%27%5D%5B%27merchant_uid%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27amount%27%3A%20res%5B%27response%27%5D%5B%27amount%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27status%27%3A%20res%5B%27response%27%5D%5B%27status%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27type%27%3A%20res%5B%27response%27%5D%5B%27pay_method%27%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%27receipt_url%27%3A%20res%5B%27response%27%5D%5B%27receipt_url%27%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20context%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22%uC778%uC99D%20%uD1A0%uD070%uC774%20%uC5C6%uC2B5%uB2C8%uB2E4.%22%29%0A%0A%60%60%60%0A%0A%uC544%uC784%uD3EC%uD2B8%uC640%20%uD1B5%uC2E0%20%uD560%20%uB54C%uB294%20%uD06C%uAC8C%203%uAC00%uC9C0%20%uACFC%uC815%uC744%20%uB098%uB204%uC5B4%20%uC9C4%uD589%uB41C%uB2E4.%20%60get_access_token%60%uC740%20%uC544%uC784%uD3EC%uD2B8%20%uC11C%uBC84%uC5D0%20%uC811%uADFC%uD560%20%uC218%20%uC788%uB294%20%uD1A0%uD070%uC744%20%uBC1C%uAE09%20%uBC1B%uB294%20%uACFC%uC815%uC73C%uB85C%20%uBC1C%uAE09%20%uBC1B%uC740%20%uD1A0%uD070%uC73C%uB85C%20%uC720%uC800%uAC00%20%uACB0%uC81C%uD55C%20%uC815%uBCF4%uB97C%20%uAC00%uC838%uC624%uAC8C%20%uB41C%uB2E4.%20%60validation_prepare%60%uC740%20%uACB0%uC81C%uB97C%20%uAC80%uC99D%uD558%uB294%20%uB2E8%uACC4%uB85C%20%uC720%uC800%uAC00%20%uC694%uCCAD%uD55C%20%uAE08%uC561%uACFC%20%uC544%uC784%uD3EC%uD2B8%uC5D0%20%uC788%uB294%20%uACB0%uC81C%20%uAE08%uC561%uC774%20%uC77C%uCE58%uD558%uB294%uC9C0%20%uAC80%uC99D%uD558%uB294%20%uB2E8%uACC4%uC774%uB2E4.%20%60get_transaction%60%uC740%20%uACB0%uC81C%uAC00%20%uB05D%uB098%uACE0%20%uB098%uC11C%20%uACB0%uC81C%uC5D0%20%uB300%uD55C%20%uC815%uBCF4%uB97C%20%uAC00%uC838%uC624%uB294%20%uB2E8%uACC4%uC774%uB2E4.%20%0A%0A%uC774%uC81C%20%uD3EC%uC778%uD2B8%uC5D0%20%uB300%uD55C%20Manager%uB97C%20%uB9CC%uB4E4%uC5B4%uBCF4%uC790.%20model%20%uBD80%uBD84%uC744%20%uC218%uC815%uD558%uC790.%0A%0A%60%60%60python%0A%23%20billing/models.py%0Afrom%20django.db%20import%20models%0A%23%20User%20%uBAA8%uB378%uC740%20%uC54C%uC544%uC11C%20%uAC00%uC838%uC624%uAE30%0Afrom%20users.models%20import%20MyUser%0Afrom%20billing.iamport%20import%20validation_prepare%2C%20get_transaction%0A%0Aclass%20Point%28models.Model%29%3A%0A%20%20%20%20user%20%3D%20models.OneToOneField%28MyUser%29%0A%20%20%20%20point%20%3D%20models.PositiveIntegerField%28default%3D0%29%0A%20%20%20%20created%20%3D%20models.DateTimeField%28auto_now_add%3DTrue%2C%20auto_now%3DFalse%29%0A%20%20%20%20timestamp%20%3D%20models.DateTimeField%28auto_now_add%3DFalse%2C%20auto_now%3DTrue%29%0A%0A%20%20%20%20def%20__str__%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20str%28self.point%29%0A%0A%0Aclass%20PointTransactionManager%28models.Manager%29%3A%0A%20%20%20%20%23%20%uC0C8%uB85C%uC6B4%20%uD2B8%uB79C%uC81D%uC158%20%uC0DD%uC131%0A%20%20%20%20def%20create_new%28self%2C%20user%2C%20amount%2C%20type%2C%20success%3DNone%2C%20transaction_status%3DNone%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20user%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%22%uC720%uC800%uAC00%20%uD655%uC778%uB418%uC9C0%20%uC54A%uC2B5%uB2C8%uB2E4.%22%29%0A%20%20%20%20%20%20%20%20short_hash%20%3D%20hashlib.sha1%28str%28random.random%28%29%29%29.hexdigest%28%29%5B%3A2%5D%0A%20%20%20%20%20%20%20%20time_hash%20%3D%20hashlib.sha1%28str%28int%28time.time%28%29%29%29%29.hexdigest%28%29%5B-3%3A%5D%0A%20%20%20%20%20%20%20%20base%20%3D%20str%28user.email%29.split%28%22@%22%29%5B0%5D%0A%20%20%20%20%20%20%20%20key%20%3D%20hashlib.sha1%28short_hash%20+%20time_hash%20+%20base%29.hexdigest%28%29%5B%3A10%5D%0A%20%20%20%20%20%20%20%20new_order_id%20%3D%20%22%25s%22%20%25%20%28key%29%0A%0A%20%20%20%20%20%20%20%20%23%20%uC544%uC784%uD3EC%uD2B8%20%uACB0%uC81C%20%uC0AC%uC804%20%uAC80%uC99D%20%uB2E8%uACC4%0A%20%20%20%20%20%20%20%20validation_prepare%28new_order_id%2C%20amount%29%0A%0A%20%20%20%20%20%20%20%20%23%20%uD2B8%uB79C%uC81D%uC158%20%uC800%uC7A5%0A%20%20%20%20%20%20%20%20new_trans%20%3D%20self.model%28%0A%20%20%20%20%20%20%20%20%20%20%20%20user%3Duser%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20order_id%3Dnew_order_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20amount%3Damount%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20type%3Dtype%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%20%20%20%20if%20success%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20new_trans.success%20%3D%20success%0A%20%20%20%20%20%20%20%20%20%20%20%20new_trans.transaction_status%20%3D%20transaction_status%0A%0A%20%20%20%20%20%20%20%20new_trans.save%28using%3Dself._db%29%0A%20%20%20%20%20%20%20%20return%20new_trans.order_id%0A%0A%20%20%20%20%23%20%uC0DD%uC120%uB41C%20%uD2B8%uB79C%uC7AD%uC158%20%uAC80%uC99D%0A%20%20%20%20def%20validation_trans%28self%2C%20merchant_id%29%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20get_transaction%28merchant_id%29%0A%0A%20%20%20%20%20%20%20%20if%20result%5B%27status%27%5D%20is%20not%20%27paid%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20result%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20all_for_user%28self%2C%20user%29%3A%0A%20%20%20%20%20%20%20%20return%20super%28PointTransactionManager%2C%20self%29.filter%28user%3Duser%29%0A%0A%20%20%20%20def%20get_recent_user%28self%2C%20user%2C%20num%29%3A%0A%20%20%20%20%20%20%20%20return%20super%28PointTransactionManager%2C%20self%29.filter%28user%3Duser%29%5B%3Anum%5D%0A%0A%0Aclass%20PointTransaction%28models.Model%29%3A%0A%20%20%20%20user%20%3D%20models.ForeignKey%28MyUser%29%0A%20%20%20%20transaction_id%20%3D%20models.CharField%28max_length%3D120%2C%20null%3DTrue%2C%20blank%3DTrue%29%0A%20%20%20%20order_id%20%3D%20models.CharField%28max_length%3D120%2C%20unique%3DTrue%29%0A%20%20%20%20amount%20%3D%20models.PositiveIntegerField%28default%3D0%29%0A%20%20%20%20success%20%3D%20models.BooleanField%28default%3DFalse%29%0A%20%20%20%20transaction_status%20%3D%20models.CharField%28max_length%3D220%2C%20null%3DTrue%2C%20blank%3DTrue%29%0A%20%20%20%20type%20%3D%20models.CharField%28max_length%3D120%29%0A%20%20%20%20created%20%3D%20models.DateTimeField%28auto_now_add%3DTrue%2C%20auto_now%3DFalse%29%0A%0A%09objects%20%3D%20PointTransactionManager%28%29%0A%0A%20%20%20%20def%20__str__%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20self.order_id%0A%0A%20%20%20%20class%20Meta%3A%0A%20%20%20%20%20%20%20%20ordering%20%3D%20%5B%27-created%27%5D%0A%60%60%60%0A%0A%uADF8%20%uB2E4%uC74C%uC73C%uB85C%uB294%20Transaction%uC774%20%uC77C%uC5B4%uB098%uC11C%20%uB098%uC11C%20%uAC80%uC99D%uD558%uB294%20post_save%uB97C%20%uB9CC%uB4E4%uC5B4%uBCF4%uC790.%20%uAC19%uC740%20model%20%uC548%uC5D0%uC11C%20%uC791%uC131%uD558%uBA74%20%uB41C%uB2E4.%20%0A%0A%60%60%60python%0A%23%20billing/models.py%0Aimport%20time%0Aimport%20random%0Aimport%20hashlib%0Afrom%20django.db.models.signals%20import%20post_save%0A%0Adef%20new_point_trans_validation%28sender%2C%20instance%2C%20created%2C%20*args%2C%20**kwargs%29%3A%0A%20%20%20%20if%20instance.transaction_id%3A%0A%20%20%20%20%20%20%20%20%23%20%uAC70%uB798%20%uD6C4%20%uC544%uC784%uD3EC%uD2B8%uC5D0%uC11C%20%uB118%uAE34%20%uACB0%uACFC%0A%20%20%20%20%20%20%20%20v_trans%20%3D%20PointTransaction.objects.validation_trans%28%0A%20%20%20%20%20%20%20%20%20%20%20%20merchant_id%3Dinstance.order_id%0A%20%20%20%20%20%20%20%20%29%0A%0A%20%20%20%20%20%20%20%20res_merchant_id%20%3D%20v_trans%5B%27merchant_id%27%5D%0A%20%20%20%20%20%20%20%20res_imp_id%20%3D%20v_trans%5B%27imp_id%27%5D%0A%20%20%20%20%20%20%20%20res_amount%20%3D%20v_trans%5B%27amount%27%5D%0A%0A%20%20%20%20%20%20%20%20%23%20%uB370%uC774%uD130%uBCA0%uC774%uC2A4%uC5D0%20%uC2E4%uC81C%20%uACB0%uC81C%uB41C%20%uC815%uBCF4%uAC00%20%uC788%uB294%uC9C0%20%uCCB4%uD06C%0A%20%20%20%20%20%20%20%20r_trans%20%3D%20PointTransaction.objects.filter%28%0A%20%20%20%20%20%20%20%20%20%20%20%20order_id%3Dres_merchant_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20transaction_id%3Dres_imp_id%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20amount%3Dres_amount%0A%20%20%20%20%20%20%20%20%29.exists%28%29%0A%0A%20%20%20%20%20%20%20%20if%20not%20v_trans%20or%20not%20r_trans%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%27%uBE44%uC815%uC0C1%uC801%uC778%20%uAC70%uB798%uC785%uB2C8%uB2E4.%27%29%0A%0A%0Apost_save.connect%28new_point_trans_validation%2C%20sender%3DPointTransaction%29%0A%0A%60%60%60%0A%0A%uACB0%uC81C%uB97C%20%uC704%uD55C%20model%20%uBD80%uBD84%uC740%20%uAC70%uC758%20%uB2E4%20%uB05D%uB0AC%uB2E4.%20%uB2E4%uC74C%uC73C%uB85C%20%uC9C4%uD589%20%uD560%20%uAC83%uC740%20view%uC640%20template%20%uB2E8%uC73C%uB85C%20%uC2E4%uC81C%20%uC720%uC800%uAC00%20%uACB0%uC81C%uB97C%20%uD560%20%uC218%20%uC788%uB3C4%uB85D%20%uB9CC%uB4E4%uC5B4%uBCF4%uC790.