basic pg app
This commit is contained in:
parent
19f20a8cd6
commit
d5a25dfaee
9 changed files with 224 additions and 0 deletions
0
apps/pg/__init__.py
Normal file
0
apps/pg/__init__.py
Normal file
3
apps/pg/admin.py
Normal file
3
apps/pg/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
apps/pg/apps.py
Normal file
6
apps/pg/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PgConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'pg'
|
||||
17
apps/pg/constans.py
Normal file
17
apps/pg/constans.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from django.db.models import IntegerChoices
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class StateChoices(IntegerChoices):
|
||||
CREATED = 1 , _('created')
|
||||
DELAYED = 2 , _('delayed') # delayed as user wish
|
||||
PENDING = 3 , _('pending') # wait for external service response (or action)
|
||||
INCOMPLETE = 4 , _('incomplete') # started and wait for internal progress to complete
|
||||
SUCCESS = 5 , _('success')
|
||||
FAILED = 6 , _('failed')
|
||||
EXPECTED_FAILURE = 7 , _('expected_failure') # no exact data available but guessed to be failed
|
||||
|
||||
|
||||
class TypeChoices(IntegerChoices):
|
||||
USER = 1 , _('user')
|
||||
APPLICATION = 2 , _('application')
|
||||
0
apps/pg/migrations/__init__.py
Normal file
0
apps/pg/migrations/__init__.py
Normal file
77
apps/pg/models.py
Normal file
77
apps/pg/models.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from oauth2_provider.settings import oauth2_settings
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
from apps.pg.constans import StateChoices
|
||||
import uuid
|
||||
|
||||
from utils.wallet_client import deposit_to_user_wallet_submit, withdraw_from_user_wallet_verify, \
|
||||
deposit_to_user_wallet_verify
|
||||
|
||||
|
||||
class ChargeRequest(BaseModel):
|
||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||
related_name='+', null=True)
|
||||
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
amount = models.PositiveBigIntegerField(null=True, blank=True)
|
||||
callback_url = models.URLField(null=True, blank=True)
|
||||
details = models.JSONField(null=True, blank=True, default=dict)
|
||||
|
||||
# TODO: complete and use this
|
||||
def change_state(self, from_state, to_state, error_message=None, **data):
|
||||
if self.state == from_state:
|
||||
updated = ChargeRequest.objects.filter(pk=self.pk, state=from_state).update(
|
||||
state=to_state,
|
||||
**data
|
||||
)
|
||||
if updated: # double check in db level
|
||||
self.state = to_state
|
||||
else:
|
||||
raise Exception(error_message)
|
||||
else:
|
||||
raise Exception(error_message)
|
||||
|
||||
def charge_wallet_balance(self):
|
||||
if self.state in [StateChoices.CREATED]:
|
||||
to_pending = ChargeRequest.objects.filter(
|
||||
pk=self.pk,
|
||||
state=StateChoices.CREATED.value,
|
||||
).update(
|
||||
state=StateChoices.PENDING.value)
|
||||
if not to_pending:
|
||||
raise Exception('Cannot charge balance because state')
|
||||
|
||||
data = {
|
||||
"uuid": str(self.pk),
|
||||
"payer_id": str(self.user.uuid),
|
||||
"payer_type": 1,
|
||||
"amount": self.amount,
|
||||
"details": {
|
||||
'description': "ad balance charge",
|
||||
'reference_id': str(self.pk),
|
||||
'application_details_url': ''
|
||||
},
|
||||
}
|
||||
submit_response = deposit_to_user_wallet_submit(data)
|
||||
|
||||
if not submit_response.uuid:
|
||||
raise Exception('Failed to submit payment in wallet')
|
||||
|
||||
verify_response = deposit_to_user_wallet_verify(self.pk)
|
||||
|
||||
if not verify_response.uuid or verify_response.state != 5:
|
||||
raise Exception('Failed to verify payment in wallet')
|
||||
|
||||
to_pay_success = ChargeRequest.objects.filter(
|
||||
pk=self.pk,
|
||||
state=StateChoices.PENDING.value
|
||||
).update(
|
||||
publication_state=StateChoices.SUCCESS.value,
|
||||
)
|
||||
|
||||
if not to_pay_success:
|
||||
raise Exception('Failed to update state payment')
|
||||
|
||||
return bool(to_pay_success)
|
||||
21
apps/pg/serializers.py
Normal file
21
apps/pg/serializers.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from .models import ChargeRequest
|
||||
|
||||
|
||||
class ChargeRequestSerializer(serializers.ModelSerializer):
|
||||
# uuid = serializers.UUIDField(required=True)
|
||||
|
||||
class Meta:
|
||||
model = ChargeRequest
|
||||
fields = (
|
||||
'uuid',
|
||||
'user',
|
||||
"created_at",
|
||||
"state",
|
||||
'amount',
|
||||
'updated_at',
|
||||
)
|
||||
|
||||
read_only_fields = ['uuid', 'user','updated_at', 'state', 'created_at']
|
||||
|
||||
68
apps/pg/views.py
Normal file
68
apps/pg/views.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import UpdateView
|
||||
from oauth2_provider.contrib.rest_framework import TokenHasScope, IsAuthenticatedOrTokenHasScope
|
||||
from rest_framework import generics
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .forms import ChargeForm
|
||||
from .models import ChargeRequest
|
||||
from .serializers import ChargeRequestSerializer
|
||||
from .constans import StateChoices
|
||||
from ..gooyal_oauth2.utils import get_application
|
||||
|
||||
|
||||
class ChargeRequestApiView(generics.CreateAPIView):
|
||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||
serializer_class = ChargeRequestSerializer
|
||||
required_scopes = ['wallet.wallet:get_balance'] # TODO: is it good scope?
|
||||
model = ChargeRequest
|
||||
|
||||
def perform_create(self, serializer):
|
||||
user = self.request.user
|
||||
application = get_application(self.request)
|
||||
charge_request: ChargeRequest = serializer.save(user=user, application=application)
|
||||
|
||||
# TODO: web view
|
||||
# charge_request.charge_wallet_balance()
|
||||
|
||||
|
||||
class ChargeView(UpdateView):
|
||||
template_name = 'pg/charge-request.html'
|
||||
form_class = ChargeForm
|
||||
|
||||
def get_queryset(self):
|
||||
return ChargeRequest.objects.all()
|
||||
# return Charge.objects.filter(state__in=[StateChoices.CREATED.value, StateChoices.SUCCESS.value])
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
charge_request: ChargeRequest = self.get_object()
|
||||
try:
|
||||
charge_request.charge_wallet_balance()
|
||||
# transaction = Transaction.objects.create(
|
||||
# uuid=charge.uuid,
|
||||
# payee_id=charge.user.pk,
|
||||
# payee_type=1,
|
||||
# payer_id=payer_pk,
|
||||
# payer_type=2,
|
||||
# amount=amount,
|
||||
# details={'description': 'شارژ مجازی کیف پول'}
|
||||
# )
|
||||
# transaction.submit()
|
||||
# transaction.verify()
|
||||
# charge.change_state(from_state=StateChoices.PENDING.value, to_state=StateChoices.SUCCESS.value)
|
||||
messages.success(self.request, 'شارژ انجام شد')
|
||||
|
||||
except:
|
||||
messages.error(self.request, 'خطایی رخ داده است')
|
||||
charge_request.change_state(from_state=StateChoices.PENDING.value, to_state=StateChoices.EXPECTED_FAILURE.value)
|
||||
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('core:charge', kwargs={'pk': self.object.pk})
|
||||
32
templates/pg/charge-request.html
Normal file
32
templates/pg/charge-request.html
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{% extends "base_charge.html" %}
|
||||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load jalali_tags %}
|
||||
|
||||
{% block title %}افزایش موجودی{% endblock %}
|
||||
{% block page_title %}افزایش موجودی{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% if object.state in '1' %}
|
||||
<form action="{{ request.get_full_path }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12 col-lg-12" id="formset">
|
||||
{# <div class="card">#}
|
||||
{# <div class="card-body">#}
|
||||
{{ form|crispy }}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<input class="w-100 btn btn-primary btn-lg" type="submit" value="ثبت" name="save"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
{{ object.amount }} in state {{ object.get_state_display }}<br>
|
||||
back to application
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
Loading…
Add table
Reference in a new issue