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