DEV Community

PEMPEME MOHAMED CHAMSOUDINE
PEMPEME MOHAMED CHAMSOUDINE

Posted on

Intégration de Stripe dans Django pour la Gestion des Paiements : Un Guide Complet

Introduction
Image description

L'intégration de systèmes de paiement comme Stripe dans vos applications web est cruciale pour gérer les transactions en ligne de manière sécurisée et efficace. Dans cet article, nous allons explorer comment utiliser Django pour structurer une API de paiement avec Stripe, en se concentrant sur la gestion des clients, des méthodes de paiement, des paiements uniques, des remboursements et des abonnements.

Le code source est disponible sur github via ce lien: api-payment-stripe-in-drf

  1. Modèles Django

Voici les modèles définis dans core/models.py pour gérer les entités de paiement :

  • Customer
class Customer(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    stripe_customer_id = models.CharField(max_length=255, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Customer {self.user.email}"

Enter fullscreen mode Exit fullscreen mode

Utilisation: Chaque utilisateur a un client Stripe associé pour gérer ses informations de paiement.

  • PaymentMethod
class PaymentMethod(models.Model):
    TYPES = (
        ('card', 'Carte bancaire'),
        ('sepa_debit', 'Prélèvement SEPA'),
        ('bancontact', 'Bancontact'),
        ('giropay', 'Giropay'),
        ('ideal', 'iDEAL'),
        ('p24', 'P24'),
        ('sofort', 'Sofort'),
        ('apple_pay', 'Apple Pay'),
        ('google_pay', 'Google Pay'),
    )

    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    stripe_payment_method_id = models.CharField(max_length=255)
    type = models.CharField(max_length=20, choices=TYPES)
    last4 = models.CharField(max_length=4, null=True, blank=True)
    is_default = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
Enter fullscreen mode Exit fullscreen mode

Utilisation: Pour stocker différentes méthodes de paiement pour chaque client.

  • Payment
class Payment(models.Model):
    STATUS_CHOICES = (
        ('pending', 'En attente'),
        ('processing', 'En cours'),
        ('succeeded', 'Réussi'),
        ('failed', 'Échoué'),
        ('canceled', 'Annulé'),
        ('refunded', 'Remboursé'),
        ('partially_refunded', 'Partiellement remboursé'),
    )

    customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    currency = models.CharField(max_length=3, default='EUR')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    payment_method = models.ForeignKey(PaymentMethod, on_delete=models.SET_NULL, null=True)
    stripe_payment_intent_id = models.CharField(max_length=255, unique=True)
    description = models.TextField(blank=True)
    metadata = models.JSONField(default=dict)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
Enter fullscreen mode Exit fullscreen mode

Utilisation: Pour enregistrer chaque transaction avec son statut et détails.

  • Refund
class Refund(models.Model):
    payment = models.ForeignKey(Payment, on_delete=models.CASCADE)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    stripe_refund_id = models.CharField(max_length=255, unique=True)
    reason = models.CharField(max_length=255)
    status = models.CharField(max_length=20)
    created_at = models.DateTimeField(auto_now_add=True)
Enter fullscreen mode Exit fullscreen mode

Utilisation: Gère les remboursements liés à des paiements.

  • Subscription
class Subscription(models.Model):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    stripe_subscription_id = models.CharField(max_length=255, unique=True)
    status = models.CharField(max_length=20)
    current_period_start = models.DateTimeField()
    current_period_end = models.DateTimeField()
    cancel_at_period_end = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)


Enter fullscreen mode Exit fullscreen mode

Utilisation: Pour les abonnements récurrents.

  1. Services Stripe Dans core/services.py, nous avons des méthodes pour interagir avec l'API Stripe :
  • Création d'un Client
import stripe
from django.conf import settings
import stripe.error
from core.models import Customer,Payment,PaymentMethod,Refund,Subscription

stripe.api_key = settings.STRIPE_SECRET_KEY

class StripeService:

    @staticmethod
    def create_account(user, token=None):
        try:
            customer = stripe.Customer.create(
                email = user.email,
                source=token
            )

            return Customer.objects.create(
                user = user,
                stripe_customer_id = customer.id
            )
        except stripe.error.StripeError as e:
            raise Exception(f"Erreur lors de la création du client: {str(e)}")



    @staticmethod
    def add_payment_method(customer: Customer,payment_method_id):
        try:
            payment_method = stripe.PaymentMethod.attach(
                payment_method_id,
                customer=customer.stripe_customer_id
            )

            return PaymentMethod.objects.create(
                stripe_payment_method_id = payment_method.id,
                customer = customer,
                type = payment_method.type,
                last4 = payment_method.card.last4 if payment_method.type =="card" else None
            )
        except stripe.error.StripeError as e:
            raise Exception(f"Erreur lors de l'ajout du moyen de paiement: {str(e)}")


    @staticmethod
    def create_payment_intent(customer, amount, currency, payment_method_id=None):

        try:
            intent_params = {
                'amount': int(amount * 100),
                'currency': currency,
                'customer': customer.stripe_customer_id,
                'payment_method_types': ['card', 'sepa_debit', 'bancontact', 
                                       'giropay', 'ideal', 'p24', 'sofort'],
                'setup_future_usage': 'off_session',
            }

            if payment_method_id:
                intent_params['payment_method'] = payment_method_id

                intent = stripe.PaymentIntent.create(**intent_params)

                return Payment.objects.create(
                    customer=customer,
                    currency=currency,
                    stripe_payment_intent_id = intent.id

                ), intent.client_secret
        except stripe.error.StripeError as e:
            raise Exception(f"Erreur lors de la création du paiement: {str(e)}")


    @staticmethod
    def process_refund(payment, amount=None, reason=None):
        try:
            refund_params = {
                'payment_intent': payment.stripe_payment_intent_id,
            }

            if amount:
                refund_params['amount'] = int(amount * 100)
            if reason:
                refund_params['reason'] = reason

            refund = stripe.Refund.create(**refund_params)

            return Refund.objects.create(
                payment=payment,
                amount=amount or payment.amount,
                stripe_refund_id=refund.id,
                reason=reason or 'requested_by_customer',
                status=refund.status
            )

        except stripe.error.StripeError as e:
            raise Exception(f"Erreur lors du remboursement: {str(e)}")


    @staticmethod
    def create_subscription(customer, price_id, payment_method_id=None):
        try:
            subscription = stripe.Subscription.create(
                customer=customer.stripe_customer_id,
                items=[{'price': price_id}],
                payment_method=payment_method_id,
                expand=['latest_invoice.payment_intent']
            )

            return Subscription.objects.create(
                customer=customer,
                stripe_subscription_id=subscription.id,
                status=subscription.status,
                current_period_start=subscription.current_period_start,
                current_period_end=subscription.current_period_end
            )
        except stripe.error.StripeError as e:
            raise Exception(f"Erreur lors de la création de l'abonnement: {str(e)}")

Enter fullscreen mode Exit fullscreen mode
  1. Seralizers

from rest_framework import serializers
from core.models import Customer, PaymentMethod, Payment, Refund, Subscription

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ['id', 'stripe_customer_id', 'created_at']

class PaymentMethodSerializer(serializers.ModelSerializer):
    class Meta:
        model = PaymentMethod
        fields = ['id', 'type', 'last4', 'is_default', 'created_at']

class PaymentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Payment
        fields = ['id', 'amount', 'currency', 'status', 'description', 
                 'metadata', 'created_at', 'updated_at']

class RefundSerializer(serializers.ModelSerializer):
    class Meta:
        model = Refund
        fields = ['id', 'payment', 'amount', 'reason', 'status', 'created_at']

class SubscriptionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subscription
        fields = ['id', 'status', 'current_period_start', 
                 'current_period_end', 'cancel_at_period_end']

Enter fullscreen mode Exit fullscreen mode
  1. API Views Les ViewSets dans core/views.py facilitent l'interaction RESTful avec les modèles :

CustomerViewSet, PaymentViewSet, SubscriptionViewSet: Offrent des points d'entrée pour CRUD sur les clients, paiements, et abonnements.

Suivez le lien github pour avoir le code source complet:api-payment-stripe-in-drf.git

Conclusion
Cette structure permet de construire une API de paiement robuste avec Django et Stripe. Assurez-vous de sécuriser votre application en utilisant HTTPS, de gérer les exceptions correctement et de tester chaque composant avec des scénarios réels. Pour une sécurité accrue, utilisez des environnements de staging avant de passer en production.

Top comments (0)