basic pg app

This commit is contained in:
Sayyid Hamid Mahdavi 2025-05-13 13:48:39 +03:30
parent 19f20a8cd6
commit d5a25dfaee
9 changed files with 224 additions and 0 deletions

0
apps/pg/__init__.py Normal file
View file

3
apps/pg/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/pg/apps.py Normal file
View 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
View 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')

View file

77
apps/pg/models.py Normal file
View 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
View 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
View 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})

View 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 %}