Résolue

Ca marche pas chez moi

# Stripe # API

le champs customer qui est récupéré par le webhook vaut None,

Je suis allé voir sur stripe et il ne crée pas de "Account" mais un "Guest" qui est écrasé et recrée a chaque transaction

Thibault houdon

Mentor

Bonsoir Christian,


Il semble effectivement qu'il y a eu un changement dans l'API de Stripe :

None


Il faut désormais indiquer explicitement si tu souhaites créer l'utilisateur avec le paramètre customer_creation :

https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-customer_creation


Je vais coder une fonctionnalité sur Docstring pour pouvoir annoter les sessions afin d'indiquer ces changements sur les API et autres par rapport à la vidéo pour que ce soit plus clair.


Essaie avec customer_creation à always et tiens-nous au courant si ça ne fonctionne toujours pas.


Bonne continuation !

Alors ca marche, mais maintenant j'ai un autre probleme ...


J'ai que des codes 403 dans mon terminal qui écoute stripe

et ca dans mon terminal Django :

[17/Jan/2023 08:20:44] "GET /cart/ HTTP/1.1" 200 960
[17/Jan/2023 08:21:20] "POST /cart/create-checkout-session HTTP/1.1" 302 0
Forbidden (CSRF cookie not set.): /
[17/Jan/2023 08:21:49] "POST / HTTP/1.1" 403 2986
Forbidden (CSRF cookie not set.): /
[17/Jan/2023 08:21:49] "POST / HTTP/1.1" 403 2986
Forbidden (CSRF cookie not set.): /
[17/Jan/2023 08:21:49] "POST / HTTP/1.1" 403 2986
Forbidden (CSRF cookie not set.): /
[17/Jan/2023 08:21:49] "POST / HTTP/1.1" 403 2986
Forbidden (CSRF cookie not set.): /
[17/Jan/2023 08:21:50] "POST / HTTP/1.1" 403 2986
Forbidden (CSRF cookie not set.): /
[17/Jan/2023 08:21:50] "POST / HTTP/1.1" 403 2986
[17/Jan/2023 08:21:51] "GET /cart/succes HTTP/1.1" 200 611


J'ai testé avec des print, je ne passe plus dans ma vue stripe_webhook

Thibault houdon

Mentor

Tu as mis le csrf_exempt ? Et tu utilises stripe CLI ?

Oui, je met mon projet sur mon drive, tu peut jeter un oeil stp ? ( c'est le dossier DocShop_src )

Thibault houdon

Mentor

Yes :)

Merci

J'avais répondu à Christian en message privé, si d'autres ont le même souci, il s'agissait d'un oubli du slash à la fin de la commande avec stripe CLI :

Bonjour,

Par contre le customer_creation always je n'ai pas compris à quel niveau le mettre ?
Car j'ai essayé de l'intégrer dans checkout_data mais ça me sort cette erreur
https://zupimages.net/viewer.php?id=23/12/20el.jpg

Lien vers mon repo : https://github.com/gabigab117/DocShop.git

Si tu avais un exemple de code à me montrer pour l'intégrer ^^ Car jusque maintenant j'ai réussi à tout faire mais là j'avoue que sur la création de l'utilisateur dans stripe je rame à fond :s.

merci d'avance

Thibault houdon

Mentor

Salut Gab !

C'est dans la création de ta checkout session, l'erreur que tu as c'est que tu spécifies à la fois une entrée pour customer et pour customer_creation. Et tu ne peux spécifier que l'un des deux (ce qui est logique, si tu donnes un client et que tu lui dis de créer un client, ça rentre en conflit). Le customer_creation ne devrait être spécifié que si tu ne donnes pas de client au préalable. Si tu donnes un client (customer), il ne peut pas le créer, car il existe déjà.

Gabriel Trouvé

Mentor

Mais oui je suis bête !!
Hier je saturais je n'arrivais plus à réfléchir !

Tout est ok ! J'ai même fait des modif dans mon code et quelques adaptations.

Merci Thibault !

Juste dernière question :
Mais pour que tout fonctionne faut forcémment activer le client stripe et faire stripe listen --forward-to 127.0.0.1:8000/stripe-webhook/ ?
Car sans ça tout ce que je fais ça ne fonctionne pas (le delete du panier, etc....).

Thibault houdon

Mentor

Oui effectivement c'est en activant le stripe CLI que Stripe va pouvoir envoyer à ton site web les événements qui sont déclenchés sur Stripe.

Si tu n'as pas le CLI en place, les événements ne seront pas redirigés vers ton webhook et ne pourront donc pas être traîtés.

Gabriel Trouvé

Mentor

Ok en tous cas tout est bon ! :)

Merci !

Hello, à mon tour de devenir fou avec Stripe. Je veux relier l'utilisateur qui fait l'achat à un utilisateur Stripe en ajoutant la ligne customer=user.stripe_id dans ma checkout session. Mais quand je rajoute cette simple ligne, la vue (qui s'affichait correctement avant) déclenche une erreur pas très explicite:

AttributeError at /cart/stripe_checkout_session/
'str' object has no attribute 'get'

Pourtant, mon utilisateur a bien déjà un stripe_id (chaîne de caractère 'cus_RBG7EYgHgkzrta'). Voici l'état de ma vue:

def stripe_checkout_session(request: HttpRequest) -> HttpRequest | str:
    """Stripe checkout session for payments"""
    user = request.user  # type: ignore
    user_cart = user.cart
    try:
        checkout_session = stripe.checkout.Session.create(
            customer=user.stripe_id,
            locale="fr",
            line_items=[
                {"quantity": order.quantity,
                 "price": order.product.stripe_id}
                for order in user_cart.orders.all()
            ],
            mode='payment',
            success_url=request.build_absolute_uri(reverse("checkout_success")),
            cancel_url=request.build_absolute_uri(reverse("cart")),
            automatic_tax={'enabled': True},
            shipping_address_collection={"allowed_countries": ["FR", "CH", "US", "CA"]}
        )
    except Exception as e:
        return str(e)

    return redirect(checkout_session.url, code=303)

J'ai tenté de comprendre en lisant la doc de l'api de Stripe, mais celle-ci mentionne bien que customer attend une string, donc je ne comprends pas d'où sort ce get du message d'erreur.
Merci de votre aide!

Gabriel Trouvé

Mentor

salut Simon,

Je n´ai pas le pc ce soir.
Mais le client est bien crée dans stripe alors ?

Je fais ça avec stripe.

 checkout_data = {
        "locale": "fr",
        "line_items": line_items,
        "mode": 'payment',
        # voir ds la doc. On passe un dico avec une liste de pays autorisés
        "shipping_address_collection": {"allowed_countries": ["FR", "BE"]},
        # il faut une url absolue car je suis sur Stripe à ce moment-là
        "success_url": request.build_absolute_uri(reverse('store:checkout-success')),
        "cancel_url": 'http://127.0.0.1:8000',
    }
    # une condition pour savoir si on a déjà un stripe_id pour notre user
    if request.user.stripe_id:
        checkout_data["customer"] = request.user.stripe_id
    else:
        checkout_data["customer_email"] = request.user.email
        # créer le client dans stripe la première fois
        checkout_data["customer_creation"] = "always"
    # tout ce que j'avais ici je l'ai passé à checkout_data en dictionnaire
    # on va utiliser l'unpacking
    session = stripe.checkout.Session.create(**checkout_data)

Salut Gab, quand je crée un utilisateur dans mon site, je lui crée automatiquement un stripe id grâce à un script dans le manager de mon modèle d'utilisateur:
```python
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, *kwargs):
if not email:
raise ValueError("email obligatoire.")
email = self.normalize_email(email)
user: AbstractUser = self.model(email=email, *
kwargs)
user.set_password(password)
stripe_customer = stripe.Customer.create(email=email)
user.stripe_id = stripe_customer.id
user.save()
return user

def create_superuser(self, email, password, **kwargs):
    kwargs["is_staff"] = True
    kwargs["is_superuser"] = True
    kwargs["is_active"] = True
    return self.create_user(email, password, **kwargs)```

Donc oui le client est bien préalablement créé dans Stripe, mais pourtant ma vue plante dès que j'y rajoute la ligne customer=user.stripe_id (cf mon message précédent pour la vue complète). Si je ne mets pas cette ligne, j'arrive bien sur l'écran de paiement de Stripe. À toutes fins utiles, voici également mon modèle utilisateur:

```python
class Shopper(AbstractUser):
username = None
email = models.EmailField(max_length=240, unique=True)
stripe_id = models.CharField(max_length=90, blank=True)

USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
objects = CustomUserManager()```

Je n'arrive pas à comprendre pourquoi ça coince...

Thibault houdon

Mentor

Salut Simon,

Tu as mis un point de débug (ou un print) pour être sûr qu'à ce niveau là tu as bien une valeur dans user.stripe_id ?

Gabriel Trouvé

Mentor

tu as un repo ?

Pour répondre à ta question Thibault: si je mets un print(user.stripe_id) au début du bloc try de ma vue stripe_checkout_session, le stripe-id s'affiche correctement dans le terminal.
Et Gab, j'ai aussi essayé avec ta structure, en modifiant ma vue comme ceci:
```python
def stripe_checkout_session(request: HttpRequest) -> HttpRequest | str:
"""Stripe checkout session for payments"""
user: Shopper = request.user # type: ignore
user_cart = user.cart
checkout_data = {
"locale": "fr",
"line_items": [
{"quantity": order.quantity,
"price": order.product.stripe_id}
for order in user_cart.orders.all()
],
"mode": 'payment',
"success_url": request.build_absolute_uri(reverse("checkout_success")),
"cancel_url": request.build_absolute_uri(reverse("cart")),
"automatic_tax": {'enabled': True},
"shipping_address_collection": {"allowed_countries": ["FR", "CH", "US", "CA"]}
}
if request.user.stripe_id:
checkout_data["customer"] = user.stripe_id
else:
checkout_data["customer_email"] = user.email
checkout_data["customer_creation"] = "always"

try:
    print(user.stripe_id)  # le stripe_id s'affiche correctement dans le terminal
    checkout_session = stripe.checkout.Session.create(**checkout_data)
except Exception as e:
    return str(e)

return redirect(checkout_session.url, code=303)```

Le problème reste le même:

(mais si je commente le bloc suivant, je n'ai plus cette erreur:
if request.user.stripe_id: checkout_data["customer"] = user.stripe_id else: checkout_data["customer_email"] = user.email checkout_data["customer_creation"] = "always"
)
C'est comme si la structure de donnée string ne convenait pas au paramètre customer...

Comme demandé par Gab, j'ai fait un repo, et je vous ai remis le problème dans le README. Merci de votre aide!
https://github.com/salsififi/Doc_Shop?tab=readme-ov-file

Gabriel Trouvé

Mentor

Avant de regarder le repo.

quand je crée un utilisateur dans mon site, je lui crée automatiquement un stripe id

Normalement c´est stripe qui creer l´id. Là tu l´as fait d´avance ?
Pour être sûr

Oui, dans la méthode create_user de mon manager (cf quelques messages plus haut pour le détail du code de cette fonction).

Gabriel Trouvé

Mentor

Ok, essaye de faire comme ça plutot, ne créé pas de stripe id d'avance. Fait le au moment du paiement.

Du genre :

def create_checkout_session(request):
    # récupère le panier
    cart = request.user.cart
    # compréhension de liste avec un dictionnaire (id + qté)
    line_items = [{"price": order.product.stripe_id,
                   "quantity": order.quantity} for order in cart.orders.all()]

    checkout_data = {
        "locale": "fr",
        "line_items": line_items,
        "mode": 'payment',
        # voir ds la doc. On passe un dico avec une liste de pays autorisés
        "shipping_address_collection": {"allowed_countries": ["FR", "BE"]},
        # il faut une url absolue car je suis sur Stripe à ce moment-là
        "success_url": request.build_absolute_uri(reverse('store:checkout-success')),
        "cancel_url": 'http://127.0.0.1:8000',
    }
    # une condition pour savoir si on a déjà un stripe_id pour notre user
    if request.user.stripe_id:
        checkout_data["customer"] = request.user.stripe_id
    else:
        checkout_data["customer_email"] = request.user.email
        # créer le client dans stripe la première fois
        checkout_data["customer_creation"] = "always"
    # tout ce que j'avais ici je l'ai passé à checkout_data en dictionnaire
    # on va utiliser l'unpacking
    session = stripe.checkout.Session.create(**checkout_data)

    return redirect(session.url, code=303)

    # Puis le webhook

    @csrf_exempt
def stripe_webhook(request):
    payload = request.body
    sig_header = request.META['HTTP_STRIPE_SIGNATURE']
    endpoint_secret = env("endpoint_secret")
    event = None

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError as e:
        # Invalid payload
        return HttpResponse(status=400)
    except stripe.error.SignatureVerificationError as e:
        # Invalid signature
        return HttpResponse(status=400)

    # on veut récupérer l'évènement checkout.session.completed, il s'agit d'un dico
    if event['type'] == "checkout.session.completed":
        # dans event on a un objet qui permet de récup mail user produits acheté etc ds data object
        data = event['data']['object']
        pprint(data)
        try:
            user = get_object_or_404(Shopper, email=data['customer_details']['email'])
            # dans object (voir var data) on a l'email
        except KeyError:
            return HttpResponse("Invalid user email", status=404)

        # deux fonctions du dessous
        complete_order(data=data, user=user)

        return HttpResponse(status=200)

        # et cette petite fonction qui fait le boulot ^^

def complete_order(data, user):
    user.stripe_id = data['customer']
    user.cart.order_ok()
    # faire un save pour le stripe_id
    user.save()

    # 200 pour indiquer que le paiement a été procéssé correctement
    return HttpResponse(status=200)

Merci Gab, mais regarde ma vuestripe_checkout_session, j'ai fait exactement ce que tu dis!

Gabriel Trouvé

Mentor

ok je ne suis pas chez moi je regarde quand j´arrive.
J´ai mis ça en attendant si ça peut te debloquer

Thibault houdon

Mentor

J'ai le sentiment qu'on ne regarde pas au bon endroit. Si tu as bien un customer, ça devrait être bon, donc je pense que ce n'est pas le fait d'ajouter ou d'enlever la ligne customer qui fait planter le script. Probablement qu'en omettant cette information tu passes par une "route" différente du côté de Stripe qui ne provoque pas d'erreur.

J'ai le sentiment donc que le problème peut être ailleurs et il faut vérifier ça avant de tourner en rond sur le customer id.

Fait un print ou un debug de ton checkout_data pour vérifier que tout est bon : que tu as bien le order.product.stripe_id, le order.quantity, que les URL de redirection sont bonnes, etc.

Je t'assure que c'est bien le fait d'enlever ou d'ajouter la ligne qui fait marcher ou planter. En effet, quand je pase juste par customer_email = user.email, tout marche, c'est-à-dire que je peux effectuer le paiement, et que je retrouve celui-ci sur Stripe. Sauf que ça l'attribuait à un utilisateur "invité" (ce que signalait Christian au début de cette série de questions), c'est pour ça que j'ai voulu passer par customer. Mystère et triple chewing gum...

Thibault houdon

Mentor

Je ne doute pas que ce soit cette ligne qui fasse planter ou non le script, ce dont je doute, c'est que le problème soit relié au customer_id.

C'est assez courant quand on est face à une erreur qui semble incompréhensible et c'est le genre d'erreur qui font tourner en rond parce qu'on ne cherche pas au bon endroit. Pour cette raison, il est préférable avant de s'obstiner de chercher un peu autour (vérifier les autres paramètres, par exemple est-ce que tu es en test mode, est-ce que le webhook est le bon, est-ce que les autres paramètres sont bons) afin de s'assurer que l'erreur n'est pas évidente et juste à côté.

Si après cette exploration autour de l'erreur on ne trouve rien, on peut se focaliser de nouveau sur le customer_id ;) Le but n'est pas de passer 1h sur le contexte environnant non plus, mais juste vérifier qu'il n'y a rien autour qui pourraient causer ce problème.

Imagine que ton produit est mal configuré, peut-être que sans donner un customer_id, dans Stripe il crée le customer et il lance des webhook ensuite qui crée ton produit mal configuré. Du coup tu ne vois pas l'erreur directement parce que le customer n'est pas encore créé. Maintenant quand tu rajoutes le customer_id, ça crée le produit tout de suite mais comme il est mal configuré, tu vois l'erreur directement. C'est hypothétique, mais c'est juste pour te montrer que dans ce cas, l'erreur est bien causée par le customer_id (et disparait donc quand on l'enlève), mais que la source de l'erreur se trouve ailleurs.

Ok, merci Thibault, je comprends ce que tu veux dire. Mais j'ai beau chercher "à côté", pour l'instant je ne trouve rien (je suis bien en mode test dans Stripe, le webhook me semble le bon., etc). Du coup, j'ai encore tendance à penser que le problème vient peut-être tout simplement de la structure de données que je passe au paramètre customer: en effet, j'avais eu exactement la même erreur quand j'avais passé à un autre paramètre une chaîne au lieu d'un dictionnaire (mais dans l'API de Stripe en l'occurrence, il est bien stipulé de passer une chaîne).Bon, je vais laisser reposer ça quelques jours, peut-être qu'en y retournant avec un oeil neuf et moins fatigué, je trouverai en 2 minutes ce qui coince. Allez, je fais un break, la sieste porte souvent conseil 😄!

Thibault houdon

Mentor

Salut Simon,

Il est possible que le problème vienne d'une différence de version. Vérifie que tu utilises bien les dernières versions de Stripe dans ton environnement virtuel.

Bingo! En mettant à jour avec la dernière version du module stripe (11.2.0, au lieu de la 11.1.1 que j'utilisais avant), ça marche parfaitement. Vu que j'avais installé il y a 2 semaines la 11.1.1 (qui était la dernière version à jour à l'époque), je n'aurais jamais imaginé que le problème pouvait venir de là. Bien vu, merci beaucoup Thibault!

Gabriel Trouvé

Mentor

Oh trop bien ! Bien vu Thibault, je n'aurais pas pensé à ça non plus ... :)

Cette question va être utile à plusieurs personnes à l'avenir je crois :)

Salut, vous allez rire (ou pas!), mais après 3 jours de bons et loyaux service de ma vue stripe_checkout_session suite à la mise à jour de la bibliothèque stripe, le problème est revenu (alors même que je n'ai pas retouché à cette vue). J'ai testé sur plisieurs versions de stripe, j'ai réinstallé la plus récente, cette fois même combat... Je n'écris pas cela pour avoir de l'aide (quoique je ne suis pas contre une "illumination" si quelqu'un pense à un truc comme Thibault a fait il y a quelques jours avec la version), mais juste pour informer les personnes qui suivront ce fil de questions qu'au final l'utilisation d'une version ancienne (de 1 mois seulement, mais bon...) n'expliquait semble-t-il pas tout. Un jour j'y arriverai 😜!

Gabriel Trouvé

Mentor

Oh lala mais tu n'as pas de chance avec stripe ! lol

Va falloir que je re installe Stripe sur mon mac et que je re test :)

Re !

ça fonctionne chez moi je viens de tester : https://youtu.be/Yghy1MSBwug

Je précise bien que je ne créé pas le customer direct comme tu fais, je le fais au moment du premier paiement.

Tu avais raison Gabriel, le souci venait bien de là ! En fait j'avais transformé ma méthode stripe_checkout_session pour la faire coller à la tienne, mais je n'avais pas retiré ces 2 lignes dans la méthode create_user() de mon CustomUserManager:

stripe_customer = stripe.Customer.create(email=email)
user.stripe_id = stripe_customer.id

En les commentant, le problème ne se pose plus pour les nouveaux utilisateurs (il ne se pose que pour les anciens, dont le stripe_id a été généré de cette manière).
Je ne comprends pas pourquoi ces 2 lignes posent problème (dans l'API de Stripe il est bien indiqué pour l'objet Customer que id est une chaîne de caractères de type "cus_NffrFeUfNV2Hib"). Si jamais toi tu vois où est le bins, dis-moi, j'aime bien comprendre les choses jusqu'au bout!
En tout cas un grand bravo et merci à toi! (ainsi qu'à Thibault dont la patience a été mise également à rude épreuve 😅).

Gabriel Trouvé

Mentor

Génial si ça fonctionne du coup. Je me suis replongé dan Stripe que je n'ai pas fait depuis février pour un projet ça fait du bien aussi.

Alors oui avec leur api ça ne passait pas avec la façon cité ci-dessus...curieux, ça pourrait être sympa de parler avec l'équipe de dev ^^.

Sans rigoler, il m'arrive de contacter les personnes souvent comme ça ahah.

En tous cas, ouf si ça fonctionne !!

Inscris-toi

(c'est gratuit !)

Inscris-toi

Tu dois créer un compte pour participer aux discussions.

Créer un compte

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.