Django Session Variables Don't Work In Stripe Webhook? - django

I am trying to use data saved in django session variables to run a function once the webhook has confirmed that 'checkout.session.completed' but I always get a key error. I am 100% sure the keys exist in the session variables.
Here is my webhook:
#csrf_exempt
def stripe_webhook(request):
# You can find your endpoint's secret in your webhook settings
endpoint_secret = 'secret'
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
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)
# Handle the checkout.session.completed event
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
fulfull_order(session)
return HttpResponse(status=200)
Here is my fulfill order function:
def fulfull_order(session):
generator = PlanMaker(goal=request.session['goal'], gender=request.session['gender'])
/// send email code.
This line generator = PlanMaker(goal=request.session['goal'], gender=request.session['gender'])
Always gives a key error on request.session['goal'] The key definitely exists, it just seems it is inaccessible from the webhook view.
How to solve?

You should save the information you want to the metadata field when creating the checkout.Session.
def checkout(request):
session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items=[{
'price': 'price_key',
'quantity': 1,
}],
mode='payment',
success_url=request.build_absolute_uri(reverse('success_url')) + '?session_id={CHECKOUT_SESSION_ID}',
cancel_url=request.build_absolute_uri(reverse('cancel_url')),
metadata={'someKeyHere': 'your session variable data'}
)
return JsonResponse({
'session_id' : session.id,
'stripe_public_key' : settings.STRIPE_PUBLISHABLE_KEY
})
then you can access the information like session['metadata']['someKeyHere']

The webhook event is a separate request coming directly from Stripe that would not be related to any Django session and so this lack of session data would seem expected. As #Anthony suggests you can store this information in the Checkout Session metadata when you create the session. The metadata will be included in the webhook object.

Related

Stripe with Django - Retrieve product / price from Session

I'm integrating Stripe Checkout with my django website. I have 2 products and everytime a PaymentIntent is successful, I want to fetch the Price related (the product that was bought).
I have 2 checkouts, one for each product, and a webhook to listen.
views.py - Create a purchase session (exist twice for product 1 & 2)
#csrf_exempt
def create_checkout_session_product1(request):
if request.method == 'GET':
domain_url = 'example.com'
checkout_session = stripe.checkout.Session.create(
success_url=domain_url + 'paiement_ok/',
cancel_url=domain_url + 'paiement_ko/',
payment_method_types=['card'],
line_items=[
{
"price" : "price_1...", <----- What I want to fetch
"quantity": 1,
},
],
mode='payment',
customer_email=request.user.get_username(),
)
return JsonResponse({'sessionId': checkout_session['id']})
views.py - Webhook to trigger a process after a purchase
#csrf_exempt
def webhook(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
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)
# Handle the checkout.session.completed event
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
#then I'd want to do something like :
line_items = stripe.checkout.Session.list_line_items(session.id)
price = line_items.data.price.id
if price == product1:
activate_process_product1(session.customer_email)
elif price == product2:
activate_process_product2(session.customer_email)
# Passed signature verification
return HttpResponse(status=200)
So the webhook works since I get the money.
But I don't get how to fetch the product once the purchase is complete ?
If you have a Payment Intent that was created from a Checkout Session, you need to go back and retrieve the associated Checkout Session to get price/product information.
Get the ID of the Payment Intent.
Make a request to list Checkout Sessions [0] while setting payment_intent and having data.line_items expanded [1]. The call in python should look like this: sessions = stripe.checkout.Session.list(payment_intent='pi_xxx', expand=['data.line_items'])
If the call was successful and returned a non-empty list of Checkout Sessions, take the first one and check line_items for information on the price and product.
[0] https://stripe.com/docs/api/checkout/sessions/list#list_checkout_sessions-payment_intent
[1] https://stripe.com/docs/expand#lists
One way I've found to get it from the Session was to use the stripe.checkout.Session.list_line_items method.
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
line_items = stripe.checkout.Session.list_line_items(session.id)
price_id = line_items.data[0]['price']['id']
got me the price id.
Source : Stripe doc

POST request uploading to Database but not getting custom response

So as the title suggest, I am able to send a POST request, and see in my postgresql database (through the pgAdmin client) that the data is being submitted and stored, however I run into a number of errors in the response output. It may have to do with the Authorization, as I have never worked with authorizing before. Both errors produce responses with 500 errors, contrary to what I would like.
The POST request is as follows:
def create():
"""
Create Student Function
"""
req_data = request.get_json()
try:
data = student_schema.load(req_data)
except ValidationError as err:
error = err.messages
return custom_response(error, 400)
#check if student exists in db
student_in_db = Student.get_user_by_email(data.get('email'))
if student_in_db:
message = {'error': 'Student already exists, please supply another email address'}
return custom_response(message, 400)
student = Student(data)
student.save()
ser_data = student_schema.dump(student).data
token = Auth.generate_token(ser_data.get('id'))
return custom_response({'jwt_token': token}, 201)
The custom_response is as so:
def custom_response(res, status_code):
"""
Custom Response Function
"""
return Response(
mimetype="application/json",
response=json.dumps(res),
status=status_code
)
Error 1
The new entry is stored, however the response to the server is still a 500 error. The error output is an attribute error pointing towards
ser_data = student_schema.dump(student).data
which is apparently a dict object, thus has no attribute data.
Error 2
Emails are declared unique in my database, thus when trying to create another user with the same email, I get a unique constraint failure, which leads to a 500 response, even though I have the built in function of checking if the student is already in the database.
With flask_sqlalchemy, I expect to see
db.session.add(student)
db.session.commit()
unless you're doing that in
student.save()

Django - can not access session

Using Django REST Framework in development, I have the following (previously working) code example, where in one view I set session data, and in another view I use that data.
And like I said, this code used to work, but now for some reason the stored session data can not be accessed anymore.
View for setting session data
#api_view(["POST"])
#permission_classes((AllowAny, ))
def set_session_data(request):
session_data_dict = loads(request.body.decode('utf-8'))
if not isinstance(session_data_dict, dict):
return Response({"message": "Expected a JSON object with key-val pairs to be sent. Key-val pairs to be set to session. Received something else.", status: status.HTTP_400_BAD_REQUEST})
try:
for key, value in session_data_dict.items():
request.session[key] = value
response_data = {"status": rest_status.HTTP_200_OK}
except:
response_data = {"status": rest_status.HTTP_500_INTERNAL_SERVER_ERROR}
return Response(response_data)
View that accesses stored session data:
#api_view(["GET"])
#permission_classes((AllowAny, ))
def check_user_logged_in(request):
try:
data = {"login_token": request.session["login_token"]}
except KeyError:
data = {"login_token": ""}
return Response(data, status=rest_status.HTTP_200_OK)
I have tested a little, and I can access the data in the session in the set_session_data view, after it has been added, like so:
request.session['login_token']
But when I try the same in the check_user_logged_in view, I get a KeyError.
So I tried checking if the sessions for both views are the same session, by checking the value of request.COOKIES[settings.SESSION_COOKIE_NAME] in each view. But in both views that results in the following error:
KeyError: 'sessionid'
Now, I have not touched the session settings, so they are the default settings from django-admin startproject. ('django.contrib.sessions' in INSTALLED_APPS, 'django.contrib.sessions.middleware.SessionMiddleware' in MIDDLEWARE, and nothing added.)
Can anyone explain why this is happening?

Not getting notified on return/return_cancel using Django paypal

Am using django paypal for my e-commerce site and payments are all working correctly,but once payment is done it is not redirected back to my site.Am using paypal IPN in my localhost.is it because am running in my local machine,Following is the code for sending data to paypal.
def checkout(request,name):
product=Products.objects.get(name=name)
print "producttttttttttttttttttttt",product
# What you want the button to do.
paypal_dict = {
"business": settings.PAYPAL_RECEIVER_EMAIL,
"amount": product.price,
"item_name": product.name,
"invoice": "unique-invoice-id",
"notify_url": "192.168.5.108:8000" + reverse('paypalipn'),
"return_url": "192.168.5.108:8000/payment-complete/",
"cancel_return": "192.168.5.108:8000",
}
form = PayPalPaymentsForm(initial=paypal_dict)
context = {"form": form}
return render_to_response("payment.html", context)
Following view is for getting data from paypal ipn:
def paypalipn(request,item_check_callable=None):
'''
django paypal view to store the IPN . the notify url excecutes this view.
'''
print "haaaaaaaaaaaaaaaaaaaaaaaaaaiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
"""
PayPal IPN endpoint (notify_url).
Used by both PayPal Payments Pro and Payments Standard to confirm transactions.
https://www.paypal.com/it/home
PayPal IPN Simulator:
https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session
"""
#TODO: Clean up code so that we don't need to set None here and have a lot
# of if checks just to determine if flag is set.
flag = None
ipn_obj = None
# Clean up the data as PayPal sends some weird values such as "N/A"
# Also, need to cope with custom encoding, which is stored in the body (!).
# Assuming the tolerate parsing of QueryDict and an ASCII-like encoding,
# such as windows-1252, latin1 or UTF8, the following will work:
encoding = request.POST.get('charset', None)
if encoding is None:
flag = "Invalid form - no charset passed, can't decode"
data = None
else:
try:
data = QueryDict(request.body, encoding=encoding)
except LookupError:
data = None
flag = "Invalid form - invalid charset"
if data is not None:
date_fields = ('time_created', 'payment_date', 'next_payment_date',
'subscr_date', 'subscr_effective')
for date_field in date_fields:
if data.get(date_field) == 'N/A':
del data[date_field]
form = PayPalIPNForm(data)
if form.is_valid():
try:
#When commit = False, object is returned without saving to DB.
ipn_obj = form.save(commit=False)
except Exception, e:
flag = "Exception while processing. (%s)" % e
else:
flag = "Invalid form. (%s)" % form.errors
if ipn_obj is None:
ipn_obj = PayPalIPN()
#Set query params and sender's IP address
ipn_obj.initialize(request)
if flag is not None:
#We save errors in the flag field
ipn_obj.set_flag(flag)
else:
# Secrets should only be used over SSL.
if request.is_secure() and 'secret' in request.GET:
ipn_obj.verify_secret(form, request.GET['secret'])
else:
ipn_obj.verify(item_check_callable)
ipn_obj.save()
return HttpResponse("OKAY")
Plese Help???
The issue happened because i was working on localhost,when i moved to development server it worked for me.The page was redirected back to my site.
I will quote this directly from django-paypal documentation:
If you are attempting to test this in development, using the PayPal sandbox, and your machine is behind a firewall/router and therefore is not publicly accessible on the internet (this will be the case for most developer machines), PayPal will not be able to post back to your view. You will need to use a tool like https://ngrok.com/ to make your machine publicly accessible, and ensure that you are sending PayPal your public URL, not localhost, in the notify_url, return and cancel_return fields.

How to Test Stripe Webhooks with Mock Data

I'm trying to write a unit test that posts a mock event to my stripe webhook.
I went and pulled an event from my logs and tried sending that with the test mode enabled, but I (somewhat predictably) got an error:
a similar object exists in live mode, but a test mode key was used to make this request.
Fair enough. So how do I create a mock event that I can actually send to my webhook and get it processed correctly?
Here's my current test:
class StripeTest(TestCase):
def setUp(self):
self.client = Client()
def test_receiving_a_callback(self):
with open('donate/test_assets/stripe_event.json', 'r') as f:
stripe_event = simplejson.load(f)
self.client.post('/donate/callbacks/stripe/',
data=simplejson.dumps(stripe_event),
content_type='application/json')
The solution is to create your own mock data. In the code below we create a test payment by creating a stripe token, then submitting it via the front end (at the /donate/ endpoint).
Once the front end has worked properly, you can get the event from stripe and then send it to your development machine's webhook endpoint.
This is more work than I expected, and I don't love that my tests are hitting the network, but it seems to be a decent solution. I feel a lot more confident about my payments than before.
def test_making_a_donation_and_getting_the_callback(self):
"""These two tests must live together because they need to be done sequentially.
First, we place a donation using the client. Then we send a mock callback to our
webhook, to make sure it accepts it properly.
"""
stripe.api_key = settings.STRIPE_SECRET_KEY
# Create a stripe token (this would normally be done via javascript in the front
# end when the submit button was pressed)
token = stripe.Token.create(
card={
'number': '4242424242424242',
'exp_month': '6',
'exp_year': str(datetime.today().year + 1),
'cvc': '123',
}
)
# Place a donation as an anonymous (not logged in) person using the
# token we just got
r = self.client.post('/donate/', data={
'amount': '25',
'payment_provider': 'cc',
'first_name': 'Barack',
'last_name': 'Obama',
'address1': '1600 Pennsylvania Ave.',
'address2': 'The Whitehouse',
'city': 'DC',
'state': 'DC',
'zip_code': '20500',
'email': 'barack#freelawproject.org',
'referrer': 'footer',
'stripeToken': token.id,
})
self.assertEqual(r.status_code, 302) # 302 because we redirect after a post.
# Get the stripe event so we can post it to the webhook
# We don't know the event ID, so we have to get the latest ones, then filter...
events = stripe.Event.all()
event = None
for obj in events.data:
if obj.data.object.card.fingerprint == token.card.fingerprint:
event = obj
break
self.assertIsNotNone(event, msg="Unable to find correct event for token: %s" % token.card.fingerprint)
# Finally, we can test the webhook!
r = self.client.post('/donate/callbacks/stripe/',
data=simplejson.dumps(event),
content_type='application/json')
# Does it return properly?
self.assertEqual(r.status_code, 200)