Passing only one product to Stripe checkout from Django - django

I've integrated Django with Stripe and seems that works ok. However i have a problem by showing all products in stripe checkout page. I need to tell stripe which products we're going to buy. If i have 2 products in cart i see only one product in stripe checkout.
views.py
def payments_stripe(request):
YOUR_DOMAIN = "http://127.0.0.1:8000/orders"
print(request.body)
body = json.loads(request.body)
cart = Cart.objects.get(cart_id=_cart_id(request))
cart_items = cartItem.objects.filter(cart=cart, is_active=True)
print(cart_items)
order = Order.objects.get(cart=cart, is_ordered=False, order_number=body['orderID'])
print(order)
for cart_item in cart_items:
quantity = cart_item.quantity
print(quantity)
name = cart_item.product.name
print(name)
try:
checkout_session = stripe.checkout.Session.create(
customer_email=order.email,
billing_address_collection='auto',
payment_method_types=['card'],
line_items=[
{
'price_data': {
'currency': 'eur',
'unit_amount': int(order.order_total * 100),
'product_data': {
'name': name,
'images': ['https://i.imgur.com/EHyR2nP.png'],
},
},
'quantity': quantity,
},
],
metadata={
'order_number': body['orderID'],
'payment_method': body['payment_method'],
'cart_id': cart,
},
mode='payment',
success_url=YOUR_DOMAIN + '/success/',
cancel_url=YOUR_DOMAIN + '/cancel/',
)
return JsonResponse({'id': checkout_session.id})
except Exception as e:
return JsonResponse(error=str(e)), 403
I iterate over cart items and Iteration gives:
1
Golden Arrow
1
Silver Heart
However in Stripe dashboard i see only one product 1 Golden Arrow.
I suppose i need to create a new list. Is that right? How can i do that in my case?
Thank you

You need to actually add each item to the line_items array, so you want to do something more like this:
line_items = []
for cart_item in cart_items:
# I presume you have something like cart_item.amount, right?
unit_amount = int(cart_item.amount * 100)
line_items.add({
'price_data': {
'currency': 'eur',
'unit_amount': unit_amount,
'product_data': {
'name': cart_item.product.name,
'images': ['https://i.imgur.com/EHyR2nP.png'],
},
},
'quantity': cart_item.quantity,
})
try:
checkout_session = stripe.checkout.Session.create(
customer_email=order.email,
billing_address_collection='auto',
payment_method_types=['card'],
line_items=line_items,
metadata={
'order_number': body['orderID'],
'payment_method': body['payment_method'],
'cart_id': cart,
},
mode='payment',
success_url=YOUR_DOMAIN + '/success/',
cancel_url=YOUR_DOMAIN + '/cancel/',
)

Related

Retrieving Stripe metadata and process it

I am sending cart data from the cookies to Stripe and retrieving it, but I am unable to find a solution to process it correctly.
Please help!
I am learning Django and wanted to save the cart items of non-logged users into the cookies and send it to Stripe, as Metadata.
From there retrieve it and if the checkout is completed to process the order, but I am unsuccessful to process the data that is retrieved to be able to save the order.
Stripe Checkout Session:
#csrf_exempt
def create_checkout_session(request):
stripe.api_key = settings.STRIPE_SECRET_KEY
domain_url = 'http://localhost:8000/checkout/'
if request.user.is_authenticated:
customer = request.user.customer
else:
data = json.loads(request.body)
total = data['form']['total'].replace('.', '')
email = data['form']['email']
first_name = data['form']['first_name']
last_name = data['form']['last_name']
customer, created = Customer.objects.get_or_create(
email=email
)
customer.first_name = first_name
customer.last_name = last_name
customer.save()
cart_info = cart_details(request)
cart_items = cart_info['cart_items']
order = cart_info['order']
items = cart_info['items']
print(items)
if request.method == 'GET':
checkout_session = stripe.checkout.Session.create(
shipping_address_collection={
"allowed_countries":
["US", "CA", "NL", "GB"]
},
client_reference_id=request.user.id,
customer_email=request.user.email,
success_url=domain_url + 'success?session_id={CHECKOUT_SESSION_ID}',
cancel_url=domain_url + 'cancelled/',
payment_method_types=['card'],
mode='payment',
line_items=[
{
'name': 'Kenpachi Katana Store',
'quantity': 1,
'currency': 'usd',
'amount': int(order.get_cart_total*100),
}
]
)
return JsonResponse({'sessionId': checkout_session['id']})
else:
checkout_session = stripe.checkout.Session.create(
shipping_address_collection={
"allowed_countries":
["US", "CA", "NL"]
},
**metadata=[items]**,
client_reference_id=customer.id,
customer_email=email,
success_url=domain_url + 'success?session_id={CHECKOUT_SESSION_ID}',
cancel_url=domain_url + 'cancelled/',
payment_method_types=['card'],
mode='payment',
line_items=[
{
'name': 'Kenpachi Katana Store',
'quantity': 1,
'currency': 'usd',
'amount': total,
}
]
)
return JsonResponse({'sessionId': checkout_session['id']})
Terminal Output with the Cookie Cart variable:
print(type(items))
<class 'list'>
And:
[{'product':
`{'id': 1,
'name': 'Sasuke Katana',
'price': Decimal('270.00'),
'imageURL': '/media/1_ccbb983f-a35d-40f8-8efb-dc55db02ad8f_700x.webp'},
'quantity': 1,
'get_total': Decimal('270.00')},
`
{'product':
{'id': 3,
'name': 'Zoro Katana',
'price': Decimal('260.00'),
'imageURL': '/media/1_466b0afb-d483-4a32-b0bb-89388aeccaa4_700x.webp'},
'quantity': 1,
'get_total': Decimal('260.00')
}]
And it easy to loop through it
for item in items:
print(item)
Output:
enter image description here
After the the order is completed, I retrieve the Stripe Session to fulfill the order
#csrf_exempt
def stripe_webhook(request):
stripe.api_key = settings.STRIPE_SECRET_KEY
endpoint_secret = settings.STRIPE_ENDPOINT_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:
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
return HttpResponse(status=400)
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
session = stripe.checkout.Session.retrieve(
event['data']['object']['id'],
expand=['line_items'],
)
stripe_metadata = session['metadata'].setdefault('0')
print(stripe_metadata)
print(type(stripe_metadata))
# Fulfill the purchase...
# TODO: drill down on the metadata from stripe
transaction_id = datetime.datetime.now().timestamp()
total = session['amount_total']
customer_id = session['client_reference_id']
customer = Customer.objects.get(pk=customer_id)
order, created = Order.objects.get_or_create(
customer=customer,
complete=False
)
order.transaction_id = transaction_id
if (total / 100) == int(order.get_cart_total):
order.complete = True
ShippingAddress.objects.create(
customer=customer,
order=order,
address=session['shipping']['address']['line1'],
city=session['shipping']['address']['city'],
state=session['shipping']['address']['state'],
zipcode=session['shipping']['address']['postal_code'],
country=session['shipping']['address']['country'],
)
order.save()
print('Order was added to the database')
return HttpResponse(status=200)
Terminal Output:
enter image description here
What would be the best option to retrieve it in the same format, to be able to iterate through the products of the cart.
Any help would be deeply appreciated.
GitHub repo with the cookie cart function:
https://github.com/GeorgianF/Kenpachi-P5-CI/blob/main/cart/utils.py
Thank you!
Maybe you can try
stripe_metadata = json.loads(str(stripe_metadata))
to convert string to JSON format

How to do pagination for a serializer field?

I have a task where I need to get stats and feedback by moderator ID. The 'stats' field is general, the 'feedback' field is a list of feedbacks. Can I make pagination for 'feedback' field? Of course I can make different endpoints for stats and feedback, but I'm not allowed to do this.
// GET /api/moderators/:id/feedback
{
"stats": [
{
"name": "123",
"value": -10
}
],
"feedback": [
{
"id": 1,
"createdBy": "FN LN",
"createdAt": "DT",
"comment": "",
"score": 5,
"categories": [
{
"name": "123",
"status": "POSITIVE/NEGETIVE/UNSET"
}
],
"webinarID": 123456
},
{
...
}
]
}
views.py
class TeacherFeedbackViewSet(ViewSet):
permission_classes = [IsAuthenticated, TeacherFeedbackPerm]
renderer_classes = [CamelCaseJSONRenderer]
#base_view
def list(self, request, pk):
moderator = get_object_or_404(Moderator, pk=pk)
serializer = ModeratorFeedback(moderator)
return Response(serializer.data)
serializers.py
class TeacherFeedbackSerializerDetail(ModelSerializer):
created_at = DateTimeField(source='datetime_filled')
created_by = SerializerMethodField(method_name='get_created_by')
categories = SerializerMethodField(method_name='get_categories')
webinar_id = IntegerField(source='webinar.id')
moderator_id = IntegerField(source='moderator.id')
class Meta:
model = TeacherFeedback
fields = ['id', 'created_by', 'created_at', 'categories', 'score', 'comment', 'moderator_id', 'webinar_id']
def get_categories(self, feedback: TeacherFeedback):
data = []
category_names = list(FeedbackCategory.objects.all().values_list('name', flat=True))
for category in feedback.category.all():
z = TeacherFeedbackCategory.objects.get(category=category, feedback=feedback)
data.append({"name": z.category.name, "status": z.status})
unset = list(map(lambda x: {"name": x, "status": "unset"},
list(set(category_names) - set([i["name"] for i in data]))))
return sorted(data + unset, key=lambda x: x["status"])
def get_created_by(self, feedback: TeacherFeedback):
return str(feedback.created_by.teacher)
class ModeratorFeedback(serializers.ModelSerializer):
stats = serializers.SerializerMethodField(method_name='get_stats_list')
feedback = TeacherFeedbackSerializerDetail(many=True, source='actual_feedbacks')
class Meta:
model = Moderator
fields = ['stats', 'feedback']
def get_stats_list(self, moderator: Moderator):
data = {}
for feedback in moderator.actual_feedbacks:
for category in feedback.category.all():
category_detail = TeacherFeedbackCategory.objects.get(feedback=feedback, category=category)
if category.name not in data:
data[category.name] = [category_detail.status]
else:
data[category.name].append(category_detail.status)
stats = []
for k, statuses in data.items():
weight = 100/len(statuses)
current_value = 0
for status in statuses:
if status == 'positive':
current_value += weight
else:
current_value -= weight
stats.append({"name": k, "value": float("{0:.2f}".format(current_value))})
return stats
In order to realize the pagination here, you need to make serializer for stats and feedback data respectively.
First you can define the ModeratorStats serializer.
class ModeratorStats(serializers.ModelSerializer):
stats = serializers.SerializerMethodField(method_name='get_stats_list')
class Meta:
model = Moderator
fields = ['stats']
def get_stats_list(self, moderator: Moderator):
...
And TeacherFeedbackSerializerDetail serializer is for the feedback.
Now in view,
from django.core.paginator import Paginator
from rest_framework.response import Response
class TeacherFeedbackViewSet(ViewSet):
...
#base_view
def list(self, request, pk):
moderator = get_object_or_404(Moderator, pk=pk)
# first get page and size param
page = int(request.GET.get('page', "1"))
size = int(request.GET.get('size', "10"))
# here I assumed the foreign key field name is `moderator`
query_set = TeacherFeedback.objects.filter(moderator__id = pk)
paginator = Paginator(query_set.order_by('id'), size)
feedbacks = paginator.page(page)
stats_data = ModeratorStats(moderator).data
feedbacks = TeacherFeedbackSerializerDetail(feedbacks, many=True).data
return Response({"stats": stats_data, "feedback": feedbacks})
And in frontend, you need to upload pagination params like ...?page=1&size=10.

How to JSON serialize APIField of StreamField using a ListBlocks of PageChooserBlocks

hi guys my head is spinning trying to figure this out. I cant get a response from a streamfield api using blocks.PageChooserBlock. I need to deserialize the JSON but am out of juice.
I get this error Object of type 'TimelineEvent' is not JSON serializable
This is the Page
class Timeline(Page):
content = StreamField([
('title', blocks.CharBlock(classname="full title")),
('time_period', TimePeriodBlock())
])
content_panels = Page.content_panels + [
StreamFieldPanel('content')
]
api_fields = [
APIField("title"),
APIField("content")
]
I'm able to get data from TimePeriodBlock and it looks like this
class TimePeriodBlock(blocks.StructBlock):
def get_api_representation(self, value, context=None):
dict_list = []
for item in value:
temp_dict = {
'period_name': value.get("period_name"),
'description': value.get("description"),
'duration': value.get("duration"),
'events': value.get('events')
}
print(value.get('events'))
dict_list.append(temp_dict)
return dict_list
period_name = blocks.CharBlock()
description = blocks.CharBlock()
duration = blocks.CharBlock()
events = blocks.ListBlock(EventBlock())
BUT can't get anywhere near EventBlock
class EventBlock(blocks.StructBlock):
def get_api_representation(self, value, context=None):
dict_list = []
print('here')
print(value)
for item in value:
temp_dict = {
# 'event': item.get("event"),
'short_desc': item.get("short_desc"),
}
dict_list.append(temp_dict)
print(dict_list)
return dict_list
event = blocks.PageChooserBlock(page_type=TimelineEvent)
short_desc = blocks.CharBlock(max_length=250)
Event looks like this
[StructValue([('event', <TimelineEvent: Dance 1700 fandango and jota >), ('short_desc', 'first event')]), StructValue([('event', <TimelineEvent: Dance 1700 contredanse>), ('short_desc', '2nd event')])]
[StructValue([('event', <TimelineEvent: Dance 1937 Trudl Dubsky>), ('short_desc', '3rd')])]
value.get(...) will just give you the native value of the child block, not the API representation. To get that, you need to call get_api_representation on the child block:
temp_dict = {
'period_name': value.get("period_name"),
'description': value.get("description"),
'duration': value.get("duration"),
'events': self.child_blocks['events'].get_api_representation(value.get("events"))
}

Using multiple models for django ecommerce cart

I'm making a cart for an ecommerce project that I am working on, at the moment the cart does work. However it only works for one model and I'm not sure how to make it work with more than one.
This is my contexts.py file in my cart folder:
from django.shortcuts import get_object_or_404
from courses.models import Course
from food_order.models import Food_order
def cart_contents(request):
"""
Ensures that the cart contents are available when rendering
every page
"""
cart = request.session.get('cart', {})
cart_items = []
total = 0
course_count = 0
for id, quantity in cart.items():
course = get_object_or_404(Course, pk=id)
total += quantity * course.price
course_count += quantity
cart_items.append({'id': id, 'quantity': quantity, 'course': course})
return {'cart_items': cart_items, 'total': total, 'course_count': course_count}
Here is my views.py
from django.shortcuts import render, redirect, reverse
# Create your views here.
def view_cart(request):
"""A View that renders the cart contents page"""
return render(request, "cart.html")
def add_to_cart(request, id):
"""Add a quantity of the specified product to the cart"""
quantity = int(request.POST.get('quantity'))
cart = request.session.get('cart', {})
if id in cart:
cart[id] = int(cart[id]) + quantity
else:
cart[id] = cart.get(id, quantity)
request.session['cart'] = cart
return redirect(reverse('index'))
def adjust_cart(request, id):
"""
Adjust the quantity of the specified product to the specified
amount
"""
quantity = int(request.POST.get('quantity'))
cart = request.session.get('cart', {})
if quantity > 0:
cart[id] = quantity
else:
cart.pop(id)
request.session['cart'] = cart
return redirect(reverse('view_cart'))
So I have 2 different classes I would like to be able to add to the cart from 2 different places on the ecommerce site. I am just uncertain how to acheive this.
If I do something along the lines of:
from django.shortcuts import get_object_or_404
from courses.models import Course
from food_order.models import Food_order
def cart_contents(request):
"""
Ensures that the cart contents are available when rendering
every page
"""
cart = request.session.get('cart', {})
cart_items = []
total_food_order = 0
total_course = 0
total = 0
product_count = 0
for id, quantity in cart.items():
course = get_object_or_404(Course, pk=id)
food_order = get_object_or_404(Food_order, pk=id)
total_course += quantity * course.price
total_food_order += quantity * food_order.price
product_count += quantity
cart_items.append({'id': id, 'quantity': quantity, 'course': course, 'food_order':food_order})
total = total_course + total_food_order
return {'cart_items': cart_items, 'total': total, 'product_count': product_count}
Then the resulting cart_items will be:
[{'id': '1', 'quantity': 1, 'course': <Course: Basic Order Taking>, 'food_order': <Food_order: Onions>}, {'id': '2', 'quantity': 3, 'course': <Course: Advanced Order Taking>, 'food_order': <Food_order: Peppers>}]
I understand why it's doing this, but I cannot think of a way to adjust my code to get the desired result. Any pointers would be very appreciated.
Thank you for your time.
I would suggest making an base model for all your models that need to be added to the cart, and use multi-table inheritence.
class BaseCartItem(models.Model):
price = models.DecimalField()
item_type = models.CharField(max_length=20, editable_false)
....
def save(*args, **kwargs):
self.item_type = self._get_item_type()
return super().save(*args, **kwargs)
def _get_item_type(self):
raise NotImplementedError
class Course(BaseCartItem):
....
def _get_item_type(self):
return "Course"
class FoodOrder(BaseCartItem):
....
def _get_item_type(self):
return "Food Order"
# if you want to add to the db
class Cart(models.Model)
item = models.ForeignKey(BaseCartItem, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
....
And in the views everything remains the same. You just use the ids of the base model rather than the child models.
The cart_contents is simplified a bit
def cart_contents(request):
"""
Ensures that the cart contents are available when rendering
every page
"""
cart = request.session.get('cart', {})
cart_items = []
item_groups = defaultdict(int)
total = 0
product_count = 0
items_map = {
item.id: item
for item in BaseCartItem.objects.filter(id__in=cart.keys()
}
for id, quantity in cart.items():
item = items_map[id]
item_group[item.item_type] += quantity * item.price
product_count += quantity
cart_items.append({'id': id, 'quantity': quantity, 'course': item, 'food_order':item})
total = sum(item_groups.values())
return {'cart_items': cart_items, 'total': total, 'product_count': product_count}

Return empty JSON object with Flask-Restful Nested field object for SQLAlchemy association if association is None

The summary might be very confusing but I don't know how to formulate it more concise.
The models I have:
class Movie(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(
imdb_data = db.relationship('IMDBData', uselist=False)
class IMDBData(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255))
rating = db.Column(db.Float)
movie_id = db.Column(db.Integer, db.ForeignKey('movie.id'))
Using Flask-Restful fields I am marshaling the response like this:
imdb_data_fields = {
'id': fields.Integer,
'title': fields.String,
'rating': fields.Float
}
movie_fields = {
'id': fields.Integer,
'title': fields.String
}
class MovieListAPI(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
super(MovieListAPI, self).__init__()
def get(self):
self.parser.add_argument('imdb_data', type=str, location='args')
args = self.parser.parse_args()
m_fields = copy.copy(movie_fields)
# TODO: Return empty object if movie.imdb_data = None
if args['imdb_data']:
m_fields['imdb_data'] = fields.Nested(imdb_data_fields)
movies = Movie.query.all()
return {'movies': marshal(movies, m_fields)}
Now if it happens that a movie does not have a corresponding imdb_data record, i.e. Movie.query.filter_by(id=123).first().imdb_data = None that movie's object is marshaled like this:
{
"id": 1302,
"imdb_data": {
"id": 0,
"rating": null,
"title": null
},
"title": "F 63 9 Love Sickness"
}
Instead I want the response to look like this:
{
"id": 1302,
"imdb_data": {},
"title": "F 63 9 Love Sickness"
}
I know how to hack this when I return one movie (by id):
if args['imdb_data']:
if movie.imdb_data:
m_fields['imdb_data'] = fields.Nested(imdb_data_fields)
else:
m_fields['imdb_data'] = fields.Nested({})
But how do I do that for the list of movies? Probably I could go through the array myself and change it by hand but there must be a more efficient way.
This can be achieved by creating a custom field, like this:
class NestedWithEmpty(Nested):
"""
Allows returning an empty dictionary if marshaled value is None
"""
def __init__(self, nested, allow_empty=False, **kwargs):
self.allow_empty = allow_empty
super(NestedWithEmpty, self).__init__(nested, **kwargs)
def output(self, key, obj):
value = get_value(key if self.attribute is None else self.attribute, obj)
if value is None:
if self.allow_null:
return None
elif self.allow_empty:
return {}
return marshal(value, self.nested)
and then using it to marshal objects passing allow_empty=True:
m_fields['imdb_data'] = NestedWithEmpty(imdb_data_fields, allow_empty=True)
I even created a pull request with this feature: https://github.com/twilio/flask-restful/pull/328
After reading the PR #328 (thanks #Andriy), and following it, my fix was to add the default arg
foo['bar'] = fields.Nested(nested_fields, default={})
Wasn't obvious in the docs.
Starting from 0.11.0, you can use the option skip_none=True to return an empty object instead of nulls.
Example with #marshal_with:
from flask_restplus import Model, fields, marshal_with
model = Model('Model', {
'name': fields.String,
'address_1': fields.String,
'address_2': fields.String
})
#marshal_with(model, skip_none=True)
def get():
return {'name': 'John', 'address_1': None}
Specifying on nested field:
from flask_restplus import Model, fields
model = Model('Model', {
'name': fields.String,
'location': fields.Nested(location_model, skip_none=True)
})
source: https://flask-restplus.readthedocs.io/en/0.11.0/marshalling.html#skip-fields-which-value-is-none