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
- 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}"
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)
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)
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)
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)
Utilisation: Pour les abonnements récurrents.
- 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)}")
- 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']
- 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)