Envoie vers Stripe et création du ticket

This commit is contained in:
Jonas 12t 2021-10-25 15:54:55 +04:00
parent aaa81f6c7f
commit cfc22159b4
7 changed files with 279 additions and 71 deletions

View File

@ -4,7 +4,7 @@ from django.contrib.auth.models import Group
from solo.admin import SingletonModelAdmin from solo.admin import SingletonModelAdmin
from AuthBillet.models import HumanUser, SuperHumanUser, TermUser from AuthBillet.models import HumanUser, SuperHumanUser, TermUser
from BaseBillet.models import Configuration, Event, OptionGenerale, Product, Price, Reservation, LigneArticle from BaseBillet.models import Configuration, Event, OptionGenerale, Product, Price, Reservation, LigneArticle, Ticket
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from Customers.models import Client from Customers.models import Client
@ -133,6 +133,7 @@ class ConfigurationAdmin(SingletonModelAdmin):
('Billetterie', { ('Billetterie', {
'fields': ( 'fields': (
'activer_billetterie', 'activer_billetterie',
'name_required_for_ticket',
'jauge_max', 'jauge_max',
'option_generale_radio', 'option_generale_radio',
'option_generale_checkbox', 'option_generale_checkbox',
@ -175,16 +176,28 @@ class OptionGeneraleAdmin(admin.ModelAdmin):
class ReservationAdmin(admin.ModelAdmin): class ReservationAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'user_mail', 'datetime',
'total_billet', 'user_commande',
'_options_', 'event',
'total_prix', 'status',
'status'
) )
readonly_fields = list_display readonly_fields = list_display
staff_admin_site.register(Reservation, ReservationAdmin) staff_admin_site.register(Reservation, ReservationAdmin)
class TicketAdmin(admin.ModelAdmin):
list_display = [
'first_name',
'last_name',
'reservation',
'scan_status',
]
readonly_fields = list_display
staff_admin_site.register(Ticket, TicketAdmin)
class ProductAdmin(admin.ModelAdmin): class ProductAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'name', 'name',

View File

@ -1,11 +1,14 @@
from django.contrib.auth import get_user_model
from django.db import connection
from rest_framework import serializers from rest_framework import serializers
import json import json
from django.utils.translation import gettext, gettext_lazy as _ from django.utils.translation import gettext, gettext_lazy as _
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from BaseBillet.models import Event, Price, Product import PaiementStripe
from AuthBillet.models import TibilletUser, HumanUser
from BaseBillet.models import Event, Price, Product, Reservation, Configuration, LigneArticle, Ticket
from PaiementStripe.views import creation_paiement_stripe
class ProductSerializer(serializers.ModelSerializer): class ProductSerializer(serializers.ModelSerializer):
@ -27,6 +30,7 @@ class ProductSerializer(serializers.ModelSerializer):
'prices', 'prices',
] ]
class PriceSerializer(serializers.ModelSerializer): class PriceSerializer(serializers.ModelSerializer):
product = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all()) product = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all())
@ -72,7 +76,6 @@ class EventSerializer(serializers.ModelSerializer):
read_only_fields = ['uuid', 'reservations'] read_only_fields = ['uuid', 'reservations']
depth = 1 depth = 1
def validate(self, attrs): def validate(self, attrs):
products = self.initial_data.get('products') products = self.initial_data.get('products')
@ -105,3 +108,174 @@ products = [
''' '''
class ReservationSerializer(serializers.ModelSerializer):
class Meta:
model = Reservation
fields = [
'uuid',
'datetime',
'user_commande',
'event',
'status',
'options',
'tickets',
'paiements',
]
read_only_fields = [
'uuid',
'datetime',
'status',
]
depth = 1
class ReservationValidator(serializers.Serializer):
email = serializers.EmailField()
event = serializers.PrimaryKeyRelatedField(queryset=Event.objects.all())
prices = serializers.JSONField(required=True)
def validate_email(self, value):
User: TibilletUser = get_user_model()
user_paiement, created = User.objects.get_or_create(
email=value)
if created:
user_paiement: HumanUser
user_paiement.client_source = connection.tenant
user_paiement.client_achat.add(connection.tenant)
user_paiement.is_active = False
else:
user_paiement.client_achat.add(connection.tenant)
user_paiement.save()
self.user_commande = user_paiement
return user_paiement.email
def validate_prices(self, value):
print(value)
# on vérifie que chaque article existe et a sa quantité.
# et qu'il y ai au moins un billet pour la reservation.
config = Configuration.get_solo()
self.nbr_ticket = 0
self.prices_list = []
for entry in value:
try:
price = Price.objects.get(pk=entry['uuid'])
price_object = {
'price': price,
'qty': float(entry['qty']),
}
if price.product.categorie_article == Product.BILLET:
self.nbr_ticket += entry['qty']
# Si les noms sont requis pour la billetterie
if config.name_required_for_ticket and entry['qty'] > 0:
if not entry.get('customers'):
raise serializers.ValidationError(_(f'customers non trouvés'))
if len(entry.get('customers')) != entry['qty']:
raise serializers.ValidationError(_(f'nombre customers non conforme'))
price_object['customers'] = entry.get('customers')
self.prices_list.append(price_object)
except Price.DoesNotExist as e:
raise serializers.ValidationError(_(f'price non trouvé : {e}'))
except ValueError as e:
raise serializers.ValidationError(_(f'qty doit être un entier ou un flottant : {e}'))
'''
products = [
{
"uuid": "8c419d35-11a1-43b6-b500-b79db665d560",
"qty": 2,
"customers": [
{
"first_name": "Jean-Michel",
"last_name": "Amoitié"
},
{
"first_name": "Ellen",
"last_name": "Ripley"
}
]
},
{
"uuid": "c6e847d4-baaa-4d21-a4f0-a572b8319615",
"qty": 1,
"customers": [
{
"first_name": "Douglas",
"last_name": "Adams"
}
]
}
]
products = [
{
"uuid": "8c419d35-11a1-43b6-b500-b79db665d560",
"qty": 2
},
{
"uuid": "c6e847d4-baaa-4d21-a4f0-a572b8319615",
"qty": 1
}
]
'''
return value
def validate(self, attrs):
if self.nbr_ticket == 0:
raise serializers.ValidationError(_(f'pas de billet dans la reservation'))
config = Configuration.get_solo()
reservation = Reservation.objects.create(
user_commande=self.user_commande,
event=attrs.get('event'),
)
lignes_article = []
for price in self.prices_list:
ligne_article = LigneArticle.objects.create(
reservation=reservation,
price=price.get('price'),
qty=price.get('qty'),
)
lignes_article.append(ligne_article)
if config.name_required_for_ticket and price.get('customers'):
for customer in price.get('customers'):
ticket = Ticket.objects.create(
reservation=reservation,
first_name=customer.get('first_name'),
last_name=customer.get('last_name'),
)
metadata = {'reservation':f'{reservation.uuid}'}
new_paiement_stripe = creation_paiement_stripe(
email_paiement=self.user_commande.email,
liste_ligne_article=lignes_article,
metadata=metadata,
absolute_domain=self.context.get('request').build_absolute_uri().partition('/api')[0],
)
if new_paiement_stripe.is_valid():
print(new_paiement_stripe.checkout_session.stripe_id)
# return new_paiement_stripe.redirect_to_stripe()
self.checkout_session = new_paiement_stripe.checkout_session
return super().validate(attrs)
else:
raise serializers.ValidationError(_(f'checkout strip not valid'))
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['checkout_url'] = self.checkout_session.url
# import ipdb;ipdb.set_trace()
return representation

View File

@ -8,6 +8,7 @@ router = routers.DefaultRouter()
router.register(r'events', api_view.EventsViewSet, basename='event') router.register(r'events', api_view.EventsViewSet, basename='event')
router.register(r'products', api_view.ProductViewSet, basename='product') router.register(r'products', api_view.ProductViewSet, basename='product')
router.register(r'prices', api_view.TarifBilletViewSet, basename='price') router.register(r'prices', api_view.TarifBilletViewSet, basename='price')
router.register(r'reservations', api_view.ReservationViewset, basename='reservation')
urlpatterns = [ urlpatterns = [

View File

@ -4,10 +4,11 @@ from django.shortcuts import render
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from rest_framework.response import Response from rest_framework.response import Response
from ApiBillet.serializers import EventSerializer, PriceSerializer, ProductSerializer from ApiBillet.serializers import EventSerializer, PriceSerializer, ProductSerializer, ReservationSerializer, \
ReservationValidator
from AuthBillet.models import TenantAdminPermission from AuthBillet.models import TenantAdminPermission
from Customers.models import Client, Domain from Customers.models import Client, Domain
from BaseBillet.models import Event, Price, Product from BaseBillet.models import Event, Price, Product, Reservation
from rest_framework import viewsets, permissions, status from rest_framework import viewsets, permissions, status
import os import os
@ -113,3 +114,23 @@ class EventsViewSet(viewsets.ViewSet):
else: else:
permission_classes = [TenantAdminPermission] permission_classes = [TenantAdminPermission]
return [permission() for permission in permission_classes] return [permission() for permission in permission_classes]
class ReservationViewset(viewsets.ViewSet):
def list(self, request):
queryset = Reservation.objects.all().order_by('-datetime')
serializer = ReservationSerializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
def create(self, request):
print(request.data)
validator = ReservationValidator(data=request.data, context={'request': request})
if validator.is_valid():
# serializer.save()
return Response(validator.data, status=status.HTTP_201_CREATED)
return Response(validator.errors, status=status.HTTP_400_BAD_REQUEST)
def get_permissions(self):
permission_classes = [TenantAdminPermission]
return [permission() for permission in permission_classes]

View File

@ -58,6 +58,8 @@ class Configuration(SingletonModel):
adhesion_obligatoire = models.BooleanField(default=False) adhesion_obligatoire = models.BooleanField(default=False)
name_required_for_ticket = models.BooleanField(default=False,verbose_name=_("Billet nominatifs"))
carte_restaurant = StdImageField(upload_to='images/', carte_restaurant = StdImageField(upload_to='images/',
null=True, blank=True, null=True, blank=True,
validators=[MaxSizeValidator(1920, 1920)], validators=[MaxSizeValidator(1920, 1920)],
@ -118,7 +120,6 @@ class Configuration(SingletonModel):
) )
class Product(models.Model): class Product(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
@ -191,6 +192,7 @@ class Product(models.Model):
self.id_product_stripe = None self.id_product_stripe = None
self.save() self.save()
class Price(models.Model): class Price(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
@ -223,7 +225,6 @@ class Price(models.Model):
def __str__(self): def __str__(self):
return f"{self.product.name} {self.name}" return f"{self.product.name} {self.name}"
def get_id_price_stripe(self): def get_id_price_stripe(self):
configuration = Configuration.get_solo() configuration = Configuration.get_solo()
if configuration.stripe_api_key and not self.id_price_stripe: if configuration.stripe_api_key and not self.id_price_stripe:
@ -236,6 +237,7 @@ class Price(models.Model):
unit_amount=int("{0:.2f}".format(self.prix).replace('.', '')), unit_amount=int("{0:.2f}".format(self.prix).replace('.', '')),
currency="eur", currency="eur",
product=self.product.get_id_product_stripe(), product=self.product.get_id_product_stripe(),
nickname= self.name,
) )
self.id_price_stripe = price.id self.id_price_stripe = price.id
@ -306,7 +308,8 @@ class Event(models.Model):
class Reservation(models.Model): class Reservation(models.Model):
uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
datetime = models.DateTimeField(auto_now=True)
user_commande = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) user_commande = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
@ -314,46 +317,40 @@ class Reservation(models.Model):
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name="reservation") related_name="reservation")
ANNULEE, MAIL_NON_VALIDEE, NON_PAYEE, VALIDEE, PAYEE = 'NAN', 'MNV', 'NPA', 'VAL', 'PAY' CANCELED, UNPAID, PAID, VALID, = 'C', 'N', 'P', 'V'
TYPE_CHOICES = [ TYPE_CHOICES = [
(ANNULEE, _('Annulée')), (CANCELED, _('Annulée')),
(MAIL_NON_VALIDEE, _('Email non validé')), (UNPAID, _('Non payée')),
(NON_PAYEE, _('Non payée')), (PAID, _('Payée')),
(VALIDEE, _('Validée')), (VALID, _('Validée')),
(PAYEE, _('Payée')),
] ]
status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=NON_PAYEE, status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=UNPAID,
verbose_name=_("Status de la réservation")) verbose_name=_("Status de la réservation"))
options = models.ManyToManyField(OptionGenerale) options = models.ManyToManyField(OptionGenerale)
def __str__(self):
return self.user_commande.email
def user_mail(self): def user_mail(self):
return self.user_commande.email return self.user_commande.email
#
def total_billet(self): # def total_billet(self):
total = 0 # total = 0
for ligne in self.lignearticle_set.all(): # for ligne in self.paiements.all():
if ligne.billet: # if ligne.billet:
total += ligne.qty # total += ligne.qty
return total # return total
#
def total_prix(self): # def total_prix(self):
total = 0 # total = 0
for ligne in self.lignearticle_set.all(): # for ligne in self.paiements.all():
if ligne.product: # if ligne.product:
total += ligne.qty * ligne.product.prix # total += ligne.qty * ligne.product.prix
if ligne.billet: #
total += ligne.qty * ligne.billet.prix # return total
#
return total # def _options_(self):
# return " - ".join([f"{option.name}" for option in self.options.all()])
def _options_(self): #
return " - ".join([f"{option.name}" for option in self.options.all()])
@receiver(post_save, sender=Reservation) @receiver(post_save, sender=Reservation)
def verif_mail_valide(sender, instance: Reservation, created, **kwargs): def verif_mail_valide(sender, instance: Reservation, created, **kwargs):
@ -364,6 +361,25 @@ def verif_mail_valide(sender, instance: Reservation, created, **kwargs):
instance.save() instance.save()
class Ticket(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, related_name="tickets")
NOT_ACTIV, NOT_SCANNED, SCANNED = 'N', 'K', 'S'
SCAN_CHOICES = [
(NOT_ACTIV, _('Non actif')),
(NOT_SCANNED, _('Non scanné')),
(SCANNED, _('scanné')),
]
scan_status = models.CharField(max_length=1, choices=SCAN_CHOICES, default=NOT_ACTIV,
verbose_name=_("Status du scan"))
class LigneArticle(models.Model): class LigneArticle(models.Model):
uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4) uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4)
datetime = models.DateTimeField(auto_now=True) datetime = models.DateTimeField(auto_now=True)
@ -372,7 +388,7 @@ class LigneArticle(models.Model):
qty = models.SmallIntegerField() qty = models.SmallIntegerField()
reservation = models.ForeignKey(Reservation, on_delete=models.CASCADE, blank=True, null=True) 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) 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) paiement_stripe = models.ForeignKey(Paiement_stripe, on_delete=models.PROTECT, blank=True, null=True)

View File

@ -12,7 +12,6 @@ class ReservationValidator(serializers.Serializer):
radio_generale = serializers.PrimaryKeyRelatedField(queryset=OptionGenerale.objects.all(), many=True) radio_generale = serializers.PrimaryKeyRelatedField(queryset=OptionGenerale.objects.all(), many=True)
option_checkbox = serializers.PrimaryKeyRelatedField(queryset=OptionGenerale.objects.all(), many=True) option_checkbox = serializers.PrimaryKeyRelatedField(queryset=OptionGenerale.objects.all(), many=True)
articles = serializers.ListField() articles = serializers.ListField()
billets = serializers.ListField()
def validate_articles(self, value): def validate_articles(self, value):
value_dict = {} value_dict = {}
@ -22,17 +21,3 @@ class ReservationValidator(serializers.Serializer):
value_dict[pk] = qty value_dict[pk] = qty
return value_dict return value_dict
def validate_billets(self, value):
value_dict = {}
billet_obj = Price.objects.all()
for couple in value:
pk, qty = billet_obj.get(pk=couple.split(',')[0]), int(couple.split(',')[1])
value_dict[pk] = qty
return value_dict
#
# # if value <= configuration.max_per_user :
# return value
# else :
# raise serializers.ValidationError(_(f"Pas plus de {configuration.max_per_user} places en même temps."))

View File

@ -103,8 +103,6 @@ class creation_paiement_stripe():
return line_items return line_items
def _checkout_session(self): def _checkout_session(self):
checkout_session = stripe.checkout.Session.create( checkout_session = stripe.checkout.Session.create(
success_url=f'{self.absolute_domain}/stripe/return/{self.paiement_stripe_db.uuid}', success_url=f'{self.absolute_domain}/stripe/return/{self.paiement_stripe_db.uuid}',
cancel_url=f'{self.absolute_domain}/stripe/return/{self.paiement_stripe_db.uuid}', cancel_url=f'{self.absolute_domain}/stripe/return/{self.paiement_stripe_db.uuid}',