From 290ca513ca8fd7c2d31fbc1a5d6488b78116266a Mon Sep 17 00:00:00 2001 From: Jonas 12t Date: Tue, 26 Oct 2021 16:32:51 +0400 Subject: [PATCH] =?UTF-8?q?gestion=20am=C3=A9lior=C3=A9e=20du=20retour=20d?= =?UTF-8?q?e=20paiement.=20Ajout=20de=20la=20source=20du=20paiement=20stri?= =?UTF-8?q?pe.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DjangoFiles/Administration/admin_tenant.py | 17 +++- DjangoFiles/ApiBillet/serializers.py | 3 +- .../migrations/0011_auto_20211026_1459.py | 27 ++++++ .../migrations/0012_reservation_paiement.py | 20 +++++ .../migrations/0013_lignearticle_status.py | 18 ++++ DjangoFiles/BaseBillet/models.py | 86 ++++++++++++++++++- .../migrations/0002_auto_20211026_1617.py | 23 +++++ .../migrations/0003_auto_20211026_1619.py | 18 ++++ DjangoFiles/PaiementStripe/models.py | 16 +++- DjangoFiles/PaiementStripe/views.py | 83 +++++++++++------- .../templates/html5up-dimension/index.html | 4 +- DjangoFiles/QrcodeCashless/views.py | 43 +--------- 12 files changed, 271 insertions(+), 87 deletions(-) create mode 100644 DjangoFiles/BaseBillet/migrations/0011_auto_20211026_1459.py create mode 100644 DjangoFiles/BaseBillet/migrations/0012_reservation_paiement.py create mode 100644 DjangoFiles/BaseBillet/migrations/0013_lignearticle_status.py create mode 100644 DjangoFiles/PaiementStripe/migrations/0002_auto_20211026_1617.py create mode 100644 DjangoFiles/PaiementStripe/migrations/0003_auto_20211026_1619.py diff --git a/DjangoFiles/Administration/admin_tenant.py b/DjangoFiles/Administration/admin_tenant.py index fd145d1..226df74 100644 --- a/DjangoFiles/Administration/admin_tenant.py +++ b/DjangoFiles/Administration/admin_tenant.py @@ -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) diff --git a/DjangoFiles/ApiBillet/serializers.py b/DjangoFiles/ApiBillet/serializers.py index c2d81af..2ea0936 100644 --- a/DjangoFiles/ApiBillet/serializers.py +++ b/DjangoFiles/ApiBillet/serializers.py @@ -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 diff --git a/DjangoFiles/BaseBillet/migrations/0011_auto_20211026_1459.py b/DjangoFiles/BaseBillet/migrations/0011_auto_20211026_1459.py new file mode 100644 index 0000000..1f6b60e --- /dev/null +++ b/DjangoFiles/BaseBillet/migrations/0011_auto_20211026_1459.py @@ -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'), + ), + ] diff --git a/DjangoFiles/BaseBillet/migrations/0012_reservation_paiement.py b/DjangoFiles/BaseBillet/migrations/0012_reservation_paiement.py new file mode 100644 index 0000000..482cd39 --- /dev/null +++ b/DjangoFiles/BaseBillet/migrations/0012_reservation_paiement.py @@ -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'), + ), + ] diff --git a/DjangoFiles/BaseBillet/migrations/0013_lignearticle_status.py b/DjangoFiles/BaseBillet/migrations/0013_lignearticle_status.py new file mode 100644 index 0000000..c40afcb --- /dev/null +++ b/DjangoFiles/BaseBillet/migrations/0013_lignearticle_status.py @@ -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'), + ), + ] diff --git a/DjangoFiles/BaseBillet/models.py b/DjangoFiles/BaseBillet/models.py index 30f5854..1df54f0 100644 --- a/DjangoFiles/BaseBillet/models.py +++ b/DjangoFiles/BaseBillet/models.py @@ -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() \ No newline at end of file diff --git a/DjangoFiles/PaiementStripe/migrations/0002_auto_20211026_1617.py b/DjangoFiles/PaiementStripe/migrations/0002_auto_20211026_1617.py new file mode 100644 index 0000000..cf0a2de --- /dev/null +++ b/DjangoFiles/PaiementStripe/migrations/0002_auto_20211026_1617.py @@ -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'), + ), + ] diff --git a/DjangoFiles/PaiementStripe/migrations/0003_auto_20211026_1619.py b/DjangoFiles/PaiementStripe/migrations/0003_auto_20211026_1619.py new file mode 100644 index 0000000..2b82470 --- /dev/null +++ b/DjangoFiles/PaiementStripe/migrations/0003_auto_20211026_1619.py @@ -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'), + ), + ] diff --git a/DjangoFiles/PaiementStripe/models.py b/DjangoFiles/PaiementStripe/models.py index 0535df2..3c2443c 100644 --- a/DjangoFiles/PaiementStripe/models.py +++ b/DjangoFiles/PaiementStripe/models.py @@ -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 diff --git a/DjangoFiles/PaiementStripe/views.py b/DjangoFiles/PaiementStripe/views.py index 901adbc..2cfd4ff 100644 --- a/DjangoFiles/PaiementStripe/views.py +++ b/DjangoFiles/PaiementStripe/views.py @@ -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,9 +75,10 @@ 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 : + for ligne_article in self.liste_ligne_article: ligne_article: LigneArticle ligne_article.paiement_stripe = paiementStripeDb ligne_article.save() @@ -123,7 +126,7 @@ class creation_paiement_stripe(): def is_valid(self): if self.checkout_session.id and \ - self.checkout_session.url : + self.checkout_session.url: return True else: @@ -133,26 +136,26 @@ 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 - metadata_stripe = json.loads(str(metadata_stripe_json)) + metadata_stripe_json = checkout_session.metadata + metadata_stripe = json.loads(str(metadata_stripe_json)) - metadata_db_json = paiement_stripe_db.metadata_stripe - metadata_db = json.loads(metadata_db_json) + metadata_db_json = paiement_stripe_db.metadata_stripe + metadata_db = json.loads(metadata_db_json) + + try: + assert metadata_stripe == metadata_db + assert set(metadata_db.keys()) == set(metadata_stripe.keys()) + for key in set(metadata_stripe.keys()): + assert metadata_db[key] == metadata_stripe[key] + return True + except: + logger.error(f"{timezone.now()} " + f"retour_stripe {paiement_stripe_db.uuid} : " + f"metadata ne correspondent pas : {metadata_stripe} {metadata_db}") + return False - try: - assert metadata_stripe == metadata_db - assert set(metadata_db.keys()) == set(metadata_stripe.keys()) - for key in set(metadata_stripe.keys()): - assert metadata_db[key] == metadata_stripe[key] - return True - except: - logger.error(f"{timezone.now()} " - f"retour_stripe {paiement_stripe_db.uuid} : " - f"metadata ne correspondent pas : {metadata_stripe} {metadata_db}") - return False class retour_stripe(View): @@ -170,8 +173,8 @@ class retour_stripe(View): checkout_session = stripe.checkout.Session.retrieve(paiement_stripe.id_stripe) - # on vérfie que les metatada soient cohérente. #NTUI ! - if metatadata_valid(paiement_stripe , checkout_session): + # on vérfie que les metatada soient cohérentes. #NTUI ! + if metatadata_valid(paiement_stripe, checkout_session): if checkout_session.payment_status == "unpaid": paiement_stripe.status = Paiement_stripe.PENDING @@ -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() - if paiement_stripe.status == Paiement_stripe.VALID: - 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 : - 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.") - return HttpResponseRedirect(f"/qr/{ligne_article.carte.uuid}#error") + # 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") - 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 : + # 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é. " + f"Merci de vérifier votre moyen de paiement ou contactez un responsable.") + return HttpResponseRedirect(f"/qr/{ligne_article.carte.uuid}#error") + + 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("/") diff --git a/DjangoFiles/QrcodeCashless/templates/html5up-dimension/index.html b/DjangoFiles/QrcodeCashless/templates/html5up-dimension/index.html index fb0b65a..6186873 100644 --- a/DjangoFiles/QrcodeCashless/templates/html5up-dimension/index.html +++ b/DjangoFiles/QrcodeCashless/templates/html5up-dimension/index.html @@ -34,7 +34,7 @@
  • Recharger
  • {% if history %} -
  • Historique
  • +
  • Solde
  • {% endif %} {% if carte_resto %} @@ -130,7 +130,7 @@ -

    Historique

    +

    Historique 24h

    diff --git a/DjangoFiles/QrcodeCashless/views.py b/DjangoFiles/QrcodeCashless/views.py index a389dfc..404a770 100644 --- a/DjangoFiles/QrcodeCashless/views.py +++ b/DjangoFiles/QrcodeCashless/views.py @@ -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