Skip to content

Commit e312636

Browse files
committed
Merge branch 'devdev' into feature/golony/ticket
# Conflicts: # payment/urls.py # payment/views.py
2 parents 95e664b + fe6936e commit e312636

18 files changed

+389
-57
lines changed

payment/enum.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from enum import Enum
2+
3+
4+
class PaymentStatus(Enum):
5+
BEFORE_PAYMENT = 1
6+
PAYMENT_FAILED = 2
7+
PAYMENT_SUCCESS = 3
8+
REFUND_FAILED = 4
9+
REFUND_SUCCESS = 5

payment/logic.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from django.contrib.auth import get_user_model
2+
from django.db import transaction
3+
4+
from payment.enum import PaymentStatus
5+
from payment.models import Payment, PaymentHistory
6+
from ticket.models import TicketType, Ticket
7+
8+
import shortuuid
9+
10+
User = get_user_model()
11+
12+
13+
@transaction.atomic
14+
def generate_payment_key(user: User, ticket_type: TicketType):
15+
new_payment = Payment(
16+
payment_key=shortuuid.uuid(),
17+
user=user,
18+
ticket_type=ticket_type,
19+
money=ticket_type.price,
20+
status=PaymentStatus.BEFORE_PAYMENT.value
21+
)
22+
23+
new_payment.save()
24+
25+
_save_history(new_payment.payment_key, PaymentStatus.BEFORE_PAYMENT.value)
26+
return new_payment.payment_key
27+
28+
29+
@transaction.atomic
30+
def proceed_payment(payment_key: str, is_succeed: bool):
31+
status_value = PaymentStatus.PAYMENT_SUCCESS.value if is_succeed else PaymentStatus.PAYMENT_FAILED.value
32+
33+
target_payment = Payment.objects.get(payment_key=payment_key)
34+
target_payment.status = status_value
35+
target_payment.save()
36+
37+
_save_history(payment_key, status_value)
38+
39+
40+
def _save_history(payment_key: str, status: int):
41+
new_payment_history = PaymentHistory(
42+
payment_key=payment_key,
43+
status=status
44+
)
45+
46+
new_payment_history.save()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.1.5 on 2023-05-25 13:47
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("payment", "0002_rename_user_id_payment_user_payment_status_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="paymenthistory",
15+
name="is_webhook",
16+
field=models.BooleanField(default=False),
17+
),
18+
]

payment/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ class PaymentHistory(models.Model):
3333
(5, "환불 완료"),
3434
)
3535
)
36+
is_webhook = models.BooleanField(default=False)
3637
create_at = models.DateTimeField(auto_now_add=True)
3738
update_at = models.DateTimeField(auto_now=True)

payment/urls.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from django.contrib import admin
22
from django.urls import include, path
33

4-
from payment.views import get__generate_payment_key
4+
from payment.views import PortoneWebhookApi, post__generate_payment_key, PaymentSuccessApi
55

66
urlpatterns = [
7-
path("payment-key/", get__generate_payment_key, name="generate-payment-key"),
7+
path("portone/webhook/", PortoneWebhookApi.as_view(), name="portone-webhook"),
8+
path("key/", post__generate_payment_key, name="get-payment-key"),
9+
path("success/", PaymentSuccessApi.as_view(), name="payment-success"),
810
]

payment/views.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,85 @@
1+
import datetime
2+
3+
from django.db import transaction
14
from rest_framework.decorators import api_view
25
from rest_framework.response import Response
36
from rest_framework.views import APIView
47

5-
from ticket.models import TicketType
6-
from payment.utils import generate_payment_key
8+
from payment import enum
9+
from ticket.models import TicketType, Ticket
10+
from payment.logic import generate_payment_key
11+
from payment.models import Payment, PaymentHistory
12+
13+
from django.conf import settings
714

815

9-
class PortoneWebhookAPI(APIView):
16+
class PortoneWebhookApi(APIView):
17+
@transaction.atomic
1018
def post(self, request):
11-
# TODO: IP Filtering
12-
# 52.78.100.19
13-
# 52.78.48.223
14-
# 52.78.5.241 (Webhook Test Only)
19+
portone_ips = [
20+
"52.78.100.19",
21+
"52.78.48.223",
22+
"52.78.5.241" # (Webhook Test Only)
23+
]
24+
25+
if settings.DEBUG is False and request.META.get("REMOTE_ADDR") not in portone_ips:
26+
raise ValueError("Not Allowed IP")
27+
28+
if request.data["status"] != "paid":
29+
raise ValueError("결제 승인건 이외의 요청")
30+
31+
payment_key = request.data["merchant_uid"]
32+
33+
target_payment = Payment.objects.get(payment_key=payment_key)
34+
target_payment.status = enum.PaymentStatus.PAYMENT_SUCCESS.value
35+
target_payment.save()
36+
37+
payment_history = PaymentHistory(
38+
payment_key=payment_key,
39+
status=enum.PaymentStatus.PAYMENT_SUCCESS.value,
40+
is_webhook=True
41+
)
42+
payment_history.save()
43+
44+
ticket = Ticket.objects.create(
45+
ticket_type=target_payment.ticket_type,
46+
bought_at=datetime.datetime.now(),
47+
user=target_payment.user,
48+
)
49+
ticket.save()
50+
51+
dto = {
52+
"msg": "ok",
53+
"merchant_uid": request.data["merchant_uid"]
54+
}
55+
56+
return Response(dto)
57+
58+
59+
class PaymentSuccessApi(APIView):
60+
def post(self, request):
61+
if not request.is_authenticated:
62+
return Response({"msg": "not logged in user"}, status=400)
63+
64+
payment_key = request.data["merchant_uid"]
65+
66+
payment_history = PaymentHistory(
67+
payment_key=payment_key,
68+
status=enum.PaymentStatus.PAYMENT_SUCCESS.value,
69+
is_webhook=False
70+
)
71+
payment_history.save()
72+
73+
dto = {
74+
"msg": "ok",
75+
"merchant_uid": request.data["merchant_uid"]
76+
}
77+
78+
return Response(dto)
1579

16-
pass
1780

18-
@api_view(["GET"])
19-
def get__generate_payment_key(request):
81+
@api_view(["POST"])
82+
def post__generate_payment_key(request):
2083

2184
request_ticket_type = TicketType.objects.get(id=request.data["ticket_type"])
2285

@@ -27,7 +90,8 @@ def get__generate_payment_key(request):
2790

2891
response_data = {
2992
"msg": "ok",
30-
"payment_key": payment_key
93+
"payment_key": payment_key,
94+
"price": request_ticket_type.price
3195
}
3296

33-
return Response()
97+
return Response(response_data)

program/migrations/0003_program.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 4.1.5 on 2023-05-24 13:39
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('program', '0002_alter_proposal_options_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='Program',
15+
fields=[
16+
('id', models.UUIDField(primary_key=True, serialize=False)),
17+
('host', models.CharField(max_length=100)),
18+
('title', models.CharField(max_length=100)),
19+
('short_desc', models.CharField(max_length=1000)),
20+
('desc', models.CharField(max_length=4000)),
21+
('room', models.CharField(blank=True, choices=[('101', '101'), ('102', '102'), ('103', '103'), ('104', '104'), ('105', '105'), ('201', '201'), ('202', '202'), ('203', '203')], max_length=15, null=True)),
22+
('capacity', models.IntegerField(blank=True, help_text='최대 참가 가능 인원 수', null=True)),
23+
('start_at', models.DateTimeField(blank=True, null=True)),
24+
('end_at', models.DateTimeField(blank=True, null=True)),
25+
('program_type', models.CharField(choices=[('CONFERENCE', '컨퍼런스'), ('TUTORIAL', '튜토리얼'), ('SPRINT', '스프린트')], max_length=30)),
26+
],
27+
),
28+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.1.5 on 2023-05-25 13:57
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('program', '0003_program'),
10+
]
11+
12+
operations = [
13+
migrations.RenameField(
14+
model_name='program',
15+
old_name='capacity',
16+
new_name='slot',
17+
),
18+
]

program/models.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,45 @@ class Meta:
9595

9696
def __str__(self):
9797
return self.title
98+
99+
100+
CONFERENCE = "CONFERENCE"
101+
TUTORIAL = "TUTORIAL"
102+
SPRINT = "SPRINT"
103+
104+
105+
class Program(models.Model):
106+
id = models.UUIDField(primary_key=True)
107+
host = models.CharField(max_length=100) # TODO User로?
108+
title = models.CharField(max_length=100)
109+
short_desc = models.CharField(max_length=1000)
110+
desc = models.CharField(max_length=4000)
111+
room = models.CharField(
112+
max_length=15,
113+
null=True,
114+
blank=True,
115+
choices=(
116+
("101", "101"),
117+
("102", "102"),
118+
("103", "103"),
119+
("104", "104"),
120+
("105", "105"),
121+
("201", "201"),
122+
("202", "202"),
123+
("203", "203"), # TODO 2층 호실 추가 필요
124+
),
125+
)
126+
slot = models.IntegerField(null=True, blank=True, help_text="최대 참가 가능 인원 수")
127+
start_at = models.DateTimeField(null=True, blank=True)
128+
end_at = models.DateTimeField(null=True, blank=True)
129+
program_type = models.CharField(
130+
max_length=30,
131+
choices=(
132+
(CONFERENCE, "컨퍼런스"),
133+
(TUTORIAL, "튜토리얼"),
134+
(SPRINT, "스프린트"),
135+
),
136+
)
137+
138+
def __str__(self):
139+
return self.title

pyconkr/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@
207207
# YOUR SETTINGS
208208
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
209209
"DEFAULT_AUTHENTICATION_CLASSES": (
210+
'rest_framework.authentication.BasicAuthentication',
211+
# 'rest_framework.authentication.SessionAuthentication',
210212
"rest_framework_simplejwt.authentication.JWTAuthentication",
211213
"dj_rest_auth.jwt_auth.JWTCookieAuthentication",
212214
),
@@ -243,6 +245,10 @@
243245
)
244246
CORS_ALLOW_CREDENTIALS = True
245247

248+
# CSRF WHITE LIST
249+
CSRF_TRUSTED_ORIGINS = tuple(CORS_ORIGIN_WHITELIST)
250+
251+
# OAUTH
246252
OAUTH_GITHUB_CALLBACK_URL = "http://localhost:8000/accounts/github/login/callback/"
247253
OAUTH_GOOGLE_CALLBACK_URL = "http://localhost:8000/accounts/google/login/callback/"
248254

0 commit comments

Comments
 (0)