stripe-integration

Par wshobson · agents

Implémentez le traitement des paiements Stripe pour des flux de paiement robustes et conformes PCI, incluant le checkout, les abonnements et les webhooks. À utiliser lors de l'intégration de paiements Stripe, de la création de systèmes d'abonnement ou de l'implémentation de flux de checkout sécurisés.

npx skills add https://github.com/wshobson/agents --skill stripe-integration

Intégration Stripe

Maîtrisez l'intégration du traitement des paiements Stripe pour des flux de paiement robustes et conformes aux normes PCI, incluant le checkout, les abonnements, les webhooks et les remboursements.

Quand utiliser cette compétence

  • Implémenter le traitement des paiements dans les applications web/mobile
  • Configurer des systèmes de facturation par abonnement
  • Gérer les paiements uniques et les débits récurrents
  • Traiter les remboursements et les litiges
  • Gérer les méthodes de paiement des clients
  • Implémenter l'authentification forte des clients (SCA) pour les paiements européens
  • Créer des flux de paiement marketplace avec Stripe Connect

Concepts fondamentaux

1. Flux de paiement

Sessions de checkout

  • Recommandé pour la plupart des intégrations
  • Supporte tous les chemins UI :
    • Page de checkout hébergée par Stripe
    • Formulaire de checkout intégré
    • Interface personnalisée avec Elements (Payment Element, Express Checkout Element) en utilisant ui_mode='custom'
  • Fournit des capacités de checkout intégrées (articles, remises, taxes, expédition, collecte d'adresses, méthodes de paiement enregistrées et événements du cycle de vie du checkout)
  • Charge d'intégration et de maintenance inférieure aux Payment Intents

Payment Intents (Contrôle sur mesure)

  • Vous calculez le montant final avec taxes, remises, abonnements et conversion de devises vous-même.
  • Implémentation plus complexe et charge de maintenance à long terme
  • Nécessite Stripe.js pour la conformité PCI

Setup Intents (Enregistrer les méthodes de paiement)

  • Collecter une méthode de paiement sans facturer
  • Utilisé pour les abonnements et les paiements futurs
  • Nécessite la confirmation du client

2. Webhooks

Événements critiques :

  • payment_intent.succeeded : Paiement complété
  • payment_intent.payment_failed : Paiement échoué
  • customer.subscription.updated : Abonnement modifié
  • customer.subscription.deleted : Abonnement annulé
  • charge.refunded : Remboursement traité
  • invoice.payment_succeeded : Paiement d'abonnement réussi

3. Abonnements

Composants :

  • Product : Ce que vous vendez
  • Price : Le montant et la fréquence
  • Subscription : Le paiement récurrent du client
  • Invoice : Générée pour chaque cycle de facturation

4. Gestion des clients

  • Créer et gérer les enregistrements de clients
  • Stocker plusieurs méthodes de paiement
  • Suivre les métadonnées des clients
  • Gérer les détails de facturation

Démarrage rapide

import stripe

stripe.api_key = "sk_test_..."

# Créer une session de checkout
session = stripe.checkout.Session.create(
    line_items=[{
        'price_data': {
            'currency': 'usd',
            'product_data': {
                'name': 'Premium Subscription',
            },
            'unit_amount': 2000,  # $20.00
            'recurring': {
                'interval': 'month',
            },
        },
        'quantity': 1,
    }],
    mode='subscription',
    success_url='https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}',
    cancel_url='https://yourdomain.com/cancel'
)

# Rediriger l'utilisateur vers session.url
print(session.url)

Modèles d'implémentation de paiement

Modèle 1 : Paiement unique (Checkout hébergé)

def create_checkout_session(amount, currency='usd'):
    """Créer une session de checkout pour un paiement unique."""
    try:
        session = stripe.checkout.Session.create(
            line_items=[{
                'price_data': {
                    'currency': currency,
                    'product_data': {
                        'name': 'Blue T-shirt',
                        'images': ['https://example.com/product.jpg'],
                    },
                    'unit_amount': amount,  # Montant en centimes
                },
                'quantity': 1,
            }],
            mode='payment',
            success_url='https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}',
            cancel_url='https://yourdomain.com/cancel',
            metadata={
                'order_id': 'order_123',
                'user_id': 'user_456'
            }
        )
        return session
    except stripe.error.StripeError as e:
        # Gérer l'erreur
        print(f"Stripe error: {e.user_message}")
        raise

Modèle 2 : Elements avec sessions de checkout

def create_checkout_session_for_elements(amount, currency='usd'):
    """Créer une session de checkout configurée pour Payment Element."""
    session = stripe.checkout.Session.create(
        mode='payment',
        ui_mode='custom',
        line_items=[{
            'price_data': {
                'currency': currency,
                'product_data': {'name': 'Blue T-shirt'},
                'unit_amount': amount,
            },
            'quantity': 1,
        }],
        return_url='https://yourdomain.com/complete?session_id={CHECKOUT_SESSION_ID}'
    )
    return session.client_secret  # Envoyer au frontend
const stripe = Stripe("pk_test_...");
const appearance = { theme: "stripe" };

const checkout = stripe.initCheckout({
  clientSecret,
  elementsOptions: { appearance },
});
const loadActionsResult = await checkout.loadActions();

if (loadActionsResult.type === "success") {
  const { actions } = loadActionsResult;
  const session = actions.getSession();

  const button = document.getElementById("pay-button");
  const checkoutContainer = document.getElementById("checkout-container");
  const emailInput = document.getElementById("email");
  const emailErrors = document.getElementById("email-errors");
  const errors = document.getElementById("confirm-errors");

  // Afficher une chaîne formatée représentant le montant total
  checkoutContainer.append(`Total: ${session.total.total.amount}`);

  // Monter Payment Element
  const paymentElement = checkout.createPaymentElement();
  paymentElement.mount("#payment-element");

  // Stocker l'email pour la soumission
  emailInput.addEventListener("blur", () => {
    actions.updateEmail(emailInput.value).then((result) => {
      if (result.error) emailErrors.textContent = result.error.message;
    });
  });

  // Gérer la soumission du formulaire
  button.addEventListener("click", () => {
    actions.confirm().then((result) => {
      if (result.type === "error") errors.textContent = result.error.message;
    });
  });
}

Modèle 3 : Elements avec Payment Intents

Le Modèle 2 (Elements avec sessions de checkout) est l'approche recommandée par Stripe, mais vous pouvez aussi utiliser Payment Intents comme alternative.

def create_payment_intent(amount, currency='usd', customer_id=None):
    """Créer un payment intent pour une interface de checkout personnalisée avec Payment Element."""
    intent = stripe.PaymentIntent.create(
        amount=amount,
        currency=currency,
        customer=customer_id,
        automatic_payment_methods={
            'enabled': True,
        },
        metadata={
            'integration_check': 'accept_a_payment'
        }
    )
    return intent.client_secret  # Envoyer au frontend
// Monter Payment Element et confirmer via Payment Intents
const stripe = Stripe("pk_test_...");
const appearance = { theme: "stripe" };
const elements = stripe.elements({ appearance, clientSecret });

const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");

document.getElementById("pay-button").addEventListener("click", async () => {
  const { error } = await stripe.confirmPayment({
    elements,
    confirmParams: {
      return_url: "https://yourdomain.com/complete",
    },
  });

  if (error) {
    document.getElementById("errors").textContent = error.message;
  }
});

Modèle 4 : Création d'abonnement

def create_subscription(customer_id, price_id):
    """Créer un abonnement pour un client."""
    try:
        subscription = stripe.Subscription.create(
            customer=customer_id,
            items=[{'price': price_id}],
            payment_behavior='default_incomplete',
            payment_settings={'save_default_payment_method': 'on_subscription'},
            expand=['latest_invoice.payment_intent'],
        )

        return {
            'subscription_id': subscription.id,
            'client_secret': subscription.latest_invoice.payment_intent.client_secret
        }
    except stripe.error.StripeError as e:
        print(f"Subscription creation failed: {e}")
        raise

Modèle 5 : Portail client

def create_customer_portal_session(customer_id):
    """Créer une session portail pour que les clients gèrent les abonnements."""
    session = stripe.billing_portal.Session.create(
        customer=customer_id,
        return_url='https://yourdomain.com/account',
    )
    return session.url  # Rediriger le client ici

Gestion des webhooks

Point de terminaison de webhook sécurisé

from flask import Flask, request
import stripe

app = Flask(__name__)

endpoint_secret = 'whsec_...'

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.data
    sig_header = request.headers.get('Stripe-Signature')

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError:
        # Charge utile invalide
        return 'Invalid payload', 400
    except stripe.error.SignatureVerificationError:
        # Signature invalide
        return 'Invalid signature', 400

    # Gérer l'événement
    if event['type'] == 'payment_intent.succeeded':
        payment_intent = event['data']['object']
        handle_successful_payment(payment_intent)
    elif event['type'] == 'payment_intent.payment_failed':
        payment_intent = event['data']['object']
        handle_failed_payment(payment_intent)
    elif event['type'] == 'customer.subscription.deleted':
        subscription = event['data']['object']
        handle_subscription_canceled(subscription)

    return 'Success', 200

def handle_successful_payment(payment_intent):
    """Traiter un paiement réussi."""
    customer_id = payment_intent.get('customer')
    amount = payment_intent['amount']
    metadata = payment_intent.get('metadata', {})

    # Mettre à jour votre base de données
    # Envoyer un email de confirmation
    # Traiter la commande
    print(f"Payment succeeded: {payment_intent['id']}")

def handle_failed_payment(payment_intent):
    """Gérer un paiement échoué."""
    error = payment_intent.get('last_payment_error', {})
    print(f"Payment failed: {error.get('message')}")
    # Notifier le client
    # Mettre à jour le statut de la commande

def handle_subscription_canceled(subscription):
    """Gérer l'annulation d'un abonnement."""
    customer_id = subscription['customer']
    # Mettre à jour l'accès utilisateur
    # Envoyer un email d'annulation
    print(f"Subscription canceled: {subscription['id']}")

Bonnes pratiques des webhooks

import hashlib
import hmac

def verify_webhook_signature(payload, signature, secret):
    """Vérifier manuellement la signature du webhook."""
    expected_sig = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected_sig)

def handle_webhook_idempotently(event_id, handler):
    """Assurer que le webhook est traité exactement une fois."""
    # Vérifier si l'événement a déjà été traité
    if is_event_processed(event_id):
        return

    # Traiter l'événement
    try:
        handler()
        mark_event_processed(event_id)
    except Exception as e:
        log_error(e)
        # Stripe réessayera les webhooks échoués
        raise

Gestion des clients

def create_customer(email, name, payment_method_id=None):
    """Créer un client Stripe."""
    customer = stripe.Customer.create(
        email=email,
        name=name,
        payment_method=payment_method_id,
        invoice_settings={
            'default_payment_method': payment_method_id
        } if payment_method_id else None,
        metadata={
            'user_id': '12345'
        }
    )
    return customer

def attach_payment_method(customer_id, payment_method_id):
    """Joindre une méthode de paiement à un client."""
    stripe.PaymentMethod.attach(
        payment_method_id,
        customer=customer_id
    )

    # Définir comme méthode par défaut
    stripe.Customer.modify(
        customer_id,
        invoice_settings={
            'default_payment_method': payment_method_id
        }
    )

def list_customer_payment_methods(customer_id):
    """Lister toutes les méthodes de paiement d'un client."""
    payment_methods = stripe.PaymentMethod.list(
        customer=customer_id,
        type='card'
    )
    return payment_methods.data

Gestion des remboursements

def create_refund(payment_intent_id, amount=None, reason=None):
    """Créer un remboursement."""
    refund_params = {
        'payment_intent': payment_intent_id
    }

    if amount:
        refund_params['amount'] = amount  # Remboursement partiel

    if reason:
        refund_params['reason'] = reason  # 'duplicate', 'fraudulent', 'requested_by_customer'

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

def handle_dispute(charge_id, evidence):
    """Mettre à jour un litige avec des preuves."""
    stripe.Dispute.modify(
        charge_id,
        evidence={
            'customer_name': evidence.get('customer_name'),
            'customer_email_address': evidence.get('customer_email'),
            'shipping_documentation': evidence.get('shipping_proof'),
            'customer_communication': evidence.get('communication'),
        }
    )

Tests

# Utiliser les clés du mode test
stripe.api_key = "sk_test_..."

# Numéros de cartes de test
TEST_CARDS = {
    'success': '4242424242424242',
    'declined': '4000000000000002',
    '3d_secure': '4000002500003155',
    'insufficient_funds': '4000000000009995'
}

def test_payment_flow():
    """Tester le flux de paiement complet."""
    # Créer un client de test
    customer = stripe.Customer.create(
        email="test@example.com"
    )

    # Créer un payment intent
    intent = stripe.PaymentIntent.create(
        amount=1000,
        automatic_payment_methods={
            'enabled': True
        },
        currency='usd',
        customer=customer.id
    )

    # Confirmer avec une carte de test
    confirmed = stripe.PaymentIntent.confirm(
        intent.id,
        payment_method='pm_card_visa'  # Méthode de paiement de test
    )

    assert confirmed.status == 'succeeded'

Skills similaires