gestion améliorée du retour de paiement.

Ajout de la source du paiement stripe.
This commit is contained in:
Jonas 12t 2021-10-26 16:32:51 +04:00
parent 9f76969c5c
commit 290ca513ca
12 changed files with 271 additions and 87 deletions

View File

@ -214,10 +214,6 @@ staff_admin_site.register(Product, ProductAdmin)
staff_admin_site.register(LigneArticle, admin.ModelAdmin)
staff_admin_site.register(OptionGenerale, OptionGeneraleAdmin)
staff_admin_site.register(Price, admin.ModelAdmin)
@ -230,6 +226,7 @@ class PaiementStripeAdmin(admin.ModelAdmin):
'total',
'order_date',
'status',
'source',
)
ordering = ('-order_date',)
@ -237,4 +234,16 @@ class PaiementStripeAdmin(admin.ModelAdmin):
staff_admin_site.register(Paiement_stripe, PaiementStripeAdmin)
class LigneArticleAdmin(admin.ModelAdmin):
list_display = (
'datetime',
'price',
'qty',
'carte',
'status',
'paiement_stripe',
'status_stripe'
)
ordering = ('-datetime',)
staff_admin_site.register(LigneArticle, LigneArticleAdmin)

View File

@ -241,7 +241,6 @@ class ReservationValidator(serializers.Serializer):
lignes_article = []
for price in self.prices_list:
ligne_article = LigneArticle.objects.create(
reservation=reservation,
price=price.get('price'),
qty=price.get('qty'),
)
@ -264,6 +263,8 @@ class ReservationValidator(serializers.Serializer):
)
if new_paiement_stripe.is_valid():
reservation.paiement = new_paiement_stripe.paiement_stripe_db
reservation.save()
print(new_paiement_stripe.checkout_session.stripe_id)
# return new_paiement_stripe.redirect_to_stripe()
self.checkout_session = new_paiement_stripe.checkout_session

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2 on 2021-10-26 10:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('BaseBillet', '0010_auto_20211025_1002'),
]
operations = [
migrations.RemoveField(
model_name='lignearticle',
name='reservation',
),
migrations.AlterField(
model_name='configuration',
name='name_required_for_ticket',
field=models.BooleanField(default=False, verbose_name='Billet nominatifs'),
),
migrations.AlterField(
model_name='ticket',
name='scan_status',
field=models.CharField(choices=[('N', 'Non actif'), ('K', 'Non scanné'), ('S', 'scanné')], default='N', max_length=1, verbose_name='Status du scan'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2 on 2021-10-26 11:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('PaiementStripe', '0001_initial'),
('BaseBillet', '0011_auto_20211026_1459'),
]
operations = [
migrations.AddField(
model_name='reservation',
name='paiement',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reservation', to='PaiementStripe.Paiement_stripe'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2 on 2021-10-26 11:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('BaseBillet', '0012_reservation_paiement'),
]
operations = [
migrations.AddField(
model_name='lignearticle',
name='status',
field=models.CharField(choices=[('C', 'Annulée'), ('N', 'Non payée'), ('P', 'Payée'), ('V', 'Validée par serveur cashless')], default='N', max_length=3, verbose_name='Status de ligne article'),
),
]

View File

@ -1,11 +1,14 @@
import uuid
import requests
from django.contrib.auth import get_user_model
from django.db import models
# Create your models here.
from django.db.models.signals import post_save
from django.db.models import Q
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.utils import timezone
from solo.models import SingletonModel
from django.utils.translation import ugettext_lazy as _
from stdimage import StdImageField
@ -328,11 +331,14 @@ class Reservation(models.Model):
status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=UNPAID,
verbose_name=_("Status de la réservation"))
paiement = models.OneToOneField(Paiement_stripe, on_delete=models.PROTECT, blank=True, null=True, related_name='reservation')
options = models.ManyToManyField(OptionGenerale)
def user_mail(self):
return self.user_commande.email
#
# def total_billet(self):
# total = 0
# for ligne in self.paiements.all():
@ -388,7 +394,81 @@ class LigneArticle(models.Model):
qty = models.SmallIntegerField()
reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, blank=True, null=True, related_name='paiements')
carte = models.ForeignKey(CarteCashless, on_delete=models.PROTECT, blank=True, null=True)
paiement_stripe = models.ForeignKey(Paiement_stripe, on_delete=models.PROTECT, blank=True, null=True)
CANCELED, UNPAID, PAID, VALID, = 'C', 'N', 'P', 'V'
TYPE_CHOICES = [
(CANCELED, _('Annulée')),
(UNPAID, _('Non payée')),
(PAID, _('Payée')),
(VALID, _('Validée par serveur cashless')),
]
status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=UNPAID,
verbose_name=_("Status de ligne article"))
def status_stripe(self):
if self.paiement_stripe:
return self.paiement_stripe.status
else:
return _('no stripe send')
@receiver(post_save, sender=LigneArticle)
def check_status_stripe(sender, instance: LigneArticle, created, **kwargs):
if instance.paiement_stripe :
lignes_dans_paiement_stripe = instance.paiement_stripe.lignearticle_set.all()
if len(lignes_dans_paiement_stripe) == len(lignes_dans_paiement_stripe.filter(status=LigneArticle.VALID)):
# toute les lignes d'article sont VALID
# on passe le status du paiement stripe en VALID
instance.paiement_stripe.status = Paiement_stripe.VALID
instance.paiement_stripe.save()
@receiver(pre_save, sender=Paiement_stripe)
def send_to_cashless(sender, instance: Paiement_stripe, update_fields=None, **kwargs):
# si l'instance vient d'être créé, ne rien faire :
if instance.pk is None:
pass
else:
paiementStripe = instance
if paiementStripe.status == Paiement_stripe.PAID:
data_pour_serveur_cashless = {'uuid_commande': paiementStripe.uuid}
for ligne_article in paiementStripe.lignearticle_set.all():
if ligne_article.carte:
data_pour_serveur_cashless['uuid'] = ligne_article.carte.uuid
if ligne_article.price.product.categorie_article == Product.RECHARGE_CASHLESS:
data_pour_serveur_cashless['recharge_qty'] = ligne_article.price.prix
if ligne_article.price.product.categorie_article == Product.ADHESION:
data_pour_serveur_cashless['tarif_adhesion'] = ligne_article.price.prix
# si il y a autre chose que uuid_commande :
if len(data_pour_serveur_cashless) > 1:
sess = requests.Session()
configuration = Configuration.get_solo()
r = sess.post(
f'{configuration.server_cashless}/api/billetterie_endpoint',
headers={
'Authorization': f'Api-Key {configuration.key_cashless}'
},
data=data_pour_serveur_cashless,
)
sess.close()
print(
f"{timezone.now()} demande au serveur cashless pour un rechargement. réponse : {r.status_code} ")
if r.status_code == 200:
# la commande a été envoyé au serveur cashless et validé.
for ligne_article in paiementStripe.lignearticle_set.filter(
Q(price__product__categorie_article=Product.RECHARGE_CASHLESS) |
Q(price__product__categorie_article=Product.ADHESION)):
ligne_article.status = LigneArticle.VALID
ligne_article.save()

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2 on 2021-10-26 12:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('PaiementStripe', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='paiement_stripe',
name='source',
field=models.CharField(choices=[('Q', 'Depuis scan QR-Code'), ('B', 'Depuis billetterie')], default='N', max_length=1, verbose_name='Statut de la commande'),
),
migrations.AlterField(
model_name='paiement_stripe',
name='status',
field=models.CharField(choices=[('N', 'Lien de paiement non créé'), ('O', 'Envoyée a Stripe'), ('W', 'En attente de paiement'), ('E', 'Expiré'), ('P', 'Payée'), ('V', 'Payée et validée'), ('C', 'Annulée')], default='N', max_length=1, verbose_name='Statut de la commande'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2 on 2021-10-26 12:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('PaiementStripe', '0002_auto_20211026_1617'),
]
operations = [
migrations.AlterField(
model_name='paiement_stripe',
name='source',
field=models.CharField(choices=[('Q', 'Depuis scan QR-Code'), ('B', 'Depuis billetterie')], default='B', max_length=1, verbose_name='Statut de la commande'),
),
]

View File

@ -1,6 +1,8 @@
from django.db import models
from django.contrib.postgres.fields import JSONField
import uuid
from django.utils.translation import gettext, gettext_lazy as _
# Create your models here.
from TiBillet import settings
@ -20,7 +22,7 @@ class Paiement_stripe(models.Model):
NON, OPEN, PENDING, EXPIRE, PAID, VALID, CANCELED = 'N', 'O', 'W', 'E', 'P', 'V', 'C'
STATUT_CHOICES = (
(NON, 'Lien de paiement non crée'),
(NON, 'Lien de paiement non créé'),
(OPEN, 'Envoyée a Stripe'),
(PENDING, 'En attente de paiement'),
(EXPIRE, 'Expiré'),
@ -31,8 +33,14 @@ class Paiement_stripe(models.Model):
status = models.CharField(max_length=1, choices=STATUT_CHOICES, default=NON, verbose_name="Statut de la commande")
total = models.FloatField(default=0)
QRCODE, BILLETTERIE = 'Q', 'B'
SOURCE_CHOICES = (
(QRCODE, _('Depuis scan QR-Code')),
(BILLETTERIE, _('Depuis billetterie')),
)
source = models.CharField(max_length=1, choices=SOURCE_CHOICES, default=BILLETTERIE, verbose_name="Source de la commande")
total = models.FloatField(default=0)
def uuid_8(self):
return f"{self.uuid}".partition('-')[0]
@ -41,8 +49,8 @@ class Paiement_stripe(models.Model):
return self.uuid_8()
def articles(self):
return " - ".join([ f"{ligne.product.name} {ligne.qty * ligne.product.prix }" for ligne in self.lignearticle_set.all()])
return " - ".join(
[f"{ligne.product.name} {ligne.qty * ligne.product.prix}" for ligne in self.lignearticle_set.all()])
''' RECEIVER PRESAVE DANS LE VIEW QRCODECASHELESS

View File

@ -26,6 +26,7 @@ class creation_paiement_stripe():
email_paiement: str,
liste_ligne_article: list,
metadata: dict,
source: str,
absolute_domain: str
) -> None:
@ -33,6 +34,7 @@ class creation_paiement_stripe():
self.liste_ligne_article = liste_ligne_article
self.email_paiement = email_paiement
self.metadata = metadata
self.source = source
self.configuration = Configuration.get_solo()
self.user = self._user_paiement()
@ -73,6 +75,7 @@ class creation_paiement_stripe():
user=self.user,
total=self.total,
metadata_stripe=self.metadata_json,
source=self.source,
)
for ligne_article in self.liste_ligne_article:
@ -133,7 +136,6 @@ class creation_paiement_stripe():
return HttpResponseRedirect(self.checkout_session.url)
# On vérifie que les métatada soient les meme dans la DB et chez Stripe.
def metatadata_valid(paiement_stripe_db: Paiement_stripe, checkout_session):
metadata_stripe_json = checkout_session.metadata
@ -154,6 +156,7 @@ def metatadata_valid(paiement_stripe_db: Paiement_stripe, checkout_session):
f"metadata ne correspondent pas : {metadata_stripe} {metadata_db}")
return False
class retour_stripe(View):
def get(self, request, uuid_stripe):
@ -170,7 +173,7 @@ class retour_stripe(View):
checkout_session = stripe.checkout.Session.retrieve(paiement_stripe.id_stripe)
# on vérfie que les metatada soient cohérente. #NTUI !
# on vérfie que les metatada soient cohérentes. #NTUI !
if metatadata_valid(paiement_stripe, checkout_session):
if checkout_session.payment_status == "unpaid":
@ -181,7 +184,14 @@ class retour_stripe(View):
elif checkout_session.payment_status == "paid":
paiement_stripe.status = Paiement_stripe.PAID
# le .save() lance le process pre_save dans le view QrcodeCashless qui peut modifier son status
# le .save() lance le process pre_save BaseBillet.models.send_to_cashless
# qui modifie le status de chaque ligne
# et envoie les informations au serveur cashless.
# si validé par le serveur cashless, alors la ligne sera VALID.
# Si toute les lignes sont VALID, le paiement_stripe sera aussi VALID
# grace au post_save BaseBillet.models.check_status_stripe
paiement_stripe.save()
else:
@ -192,19 +202,28 @@ class retour_stripe(View):
# on vérifie que le status n'ai pas changé
paiement_stripe.refresh_from_db()
# si c'est depuis le qrcode, on renvoie vers la vue mobile :
if paiement_stripe.source == Paiement_stripe.QRCODE :
if paiement_stripe.status == Paiement_stripe.VALID :
# on boucle ici pour récuperer l'uuid de la carte.
for ligne_article in paiement_stripe.lignearticle_set.all():
if ligne_article.carte:
messages.success(request, f"Paiement validé. Merci !")
return HttpResponseRedirect(f"/qr/{ligne_article.carte.uuid}#success")
else :
# on boucle ici pour récuperer l'uuid de la carte.
for ligne_article in paiement_stripe.lignearticle_set.all():
if ligne_article.carte:
messages.error(request, f"Un problème de validation de paiement a été detecté. Merci de vérifier votre moyen de paiement ou contactez un responsable.")
messages.error(request,
f"Un problème de validation de paiement a été detecté. "
f"Merci de vérifier votre moyen de paiement ou contactez un responsable.")
return HttpResponseRedirect(f"/qr/{ligne_article.carte.uuid}#error")
return HttpResponse('Un problème de validation de paiement a été detecté. Merci de vérifier votre moyen de paiement ou contactez un responsable.')
else:
return HttpResponse(
'Un problème de validation de paiement a été detecté. Merci de vérifier votre moyen de paiement ou contactez un responsable.')
# return HttpResponseRedirect("/")

View File

@ -34,7 +34,7 @@
<li><a href="#recharger">Recharger</a></li>
{% if history %}
<li><a href="#historique">Historique</a></li>
<li><a href="#historique">Solde</a></li>
{% endif %}
{% if carte_resto %}
@ -130,7 +130,7 @@
</tfoot>
</table>
</div>
<h2 class="major">Historique</h2>
<h2 class="major">Historique 24h</h2>
<div class="table-wrapper">

View File

@ -3,6 +3,7 @@ from datetime import datetime
import requests, json
from django.contrib import messages
from django.db import connection
from django.db.models import Q
from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.shortcuts import render
from django.utils import timezone
@ -201,6 +202,7 @@ class index_scan(View):
email_paiement=data.get('email'),
liste_ligne_article=ligne_articles,
metadata=metadata,
source=Paiement_stripe.QRCODE,
absolute_domain=request.build_absolute_uri().partition('/qr')[0],
)
@ -256,44 +258,3 @@ class index_scan(View):
else:
messages.error(request, f'Erreur {r.status_code} {r.text}')
return HttpResponseRedirect(f'#erreur')
@receiver(pre_save, sender=Paiement_stripe)
def changement_paid_to_valid(sender, instance: Paiement_stripe, update_fields=None, **kwargs):
# si l'instance vient d'être créé, ne rien faire :
if instance.pk is None:
pass
else:
paiementStripe = instance
if paiementStripe.status == Paiement_stripe.PAID:
data_pour_serveur_cashless = {'uuid_commande': paiementStripe.uuid}
for ligne_article in paiementStripe.lignearticle_set.all():
if ligne_article.carte:
data_pour_serveur_cashless['uuid'] = ligne_article.carte.uuid
if ligne_article.price.product.categorie_article == Product.RECHARGE_CASHLESS:
data_pour_serveur_cashless['recharge_qty'] = ligne_article.price.prix
if ligne_article.price.product.categorie_article == Product.ADHESION:
data_pour_serveur_cashless['tarif_adhesion'] = ligne_article.price.prix
# si il y a autre chose que uuid_commande :
if len(data_pour_serveur_cashless) > 1:
sess = requests.Session()
configuration = Configuration.get_solo()
r = sess.post(
f'{configuration.server_cashless}/api/billetterie_endpoint',
headers={
'Authorization': f'Api-Key {configuration.key_cashless}'
},
data=data_pour_serveur_cashless,
)
sess.close()
print(
f"{timezone.now()} demande au serveur cashless pour un rechargement. réponse : {r.status_code} ")
if r.status_code == 200:
# la commande a été envoyé au serveur cashless et validé.
paiementStripe.status = Paiement_stripe.VALID