Finish first iteration of subscriptions

This commit is contained in:
Nathan Chapman 2022-12-30 11:07:07 -07:00
parent 467e736147
commit 14246afd19
8 changed files with 202 additions and 100 deletions

View File

@ -0,0 +1,19 @@
# Generated by Django 4.0.2 on 2022-12-30 16:04
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0026_orderline_product'),
]
operations = [
migrations.AddField(
model_name='order',
name='subscription',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='core.subscription'),
),
]

View File

@ -382,6 +382,13 @@ class Order(models.Model):
blank=True, blank=True,
null=True null=True
) )
subscription = models.ForeignKey(
'Subscription',
related_name='orders',
editable=False,
null=True,
on_delete=models.SET_NULL
)
created_at = models.DateTimeField(auto_now_add=True, editable=False) created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
@ -548,13 +555,59 @@ class Subscription(models.Model):
created_at = models.DateTimeField(auto_now_add=True, editable=False) created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
def convert_int_to_decimal(self, price):
return Decimal(str(price)[:-2] + '.' + str(price)[-2:])
def format_product(self, data):
return {
'product': Product.objects.get(pk=data['pk']),
'quantity': data['quantity']
}
def deserialize_subscription(self, data):
subscription = {}
for x in data:
if 'products_and_quantities' in x['metadata']:
subscription['unit_price'] = self.convert_int_to_decimal(x['price']['unit_amount'])
if 'Shipping' in x['description']:
subscription['shipping_cost'] = self.convert_int_to_decimal(x['amount'])
return subscription
def create_order(self, data_object): def create_order(self, data_object):
pass subscription = self.deserialize_subscription(data_object['lines']['data'])
subscription['items'] = map(self.format_product, self.metadata['products_and_quantities'])
subscription['customer_note'] = f"Grind: {self.metadata['grind']}"
order = Order.objects.create(
customer=self.customer,
status=OrderStatus.UNFULFILLED,
shipping_address=self.shipping_address,
subtotal_amount=self.convert_int_to_decimal(data_object['subtotal']),
shipping_total=subscription['shipping_cost'],
total_amount=self.convert_int_to_decimal(data_object['total']),
weight=self.total_weight,
subscription=self
)
bulk_lines = [OrderLine(
order=order,
product=item['product'],
quantity=item['quantity'],
customer_note=subscription['customer_note'],
unit_price=subscription['unit_price']
) for item in subscription['items']]
OrderLine.objects.bulk_create(bulk_lines)
def format_metadata(self): def format_metadata(self):
metadata = {} metadata = {}
for key, value in self.metadata.items(): for key, value in self.metadata.items():
if 'products_and_quantities' in key:
metadata[key] = json.dumps(value) metadata[key] = json.dumps(value)
else:
metadata[key] = value
metadata['subscription_pk'] = self.pk metadata['subscription_pk'] = self.pk
return metadata return metadata

View File

@ -28,38 +28,28 @@ def format_product(data, unit_price):
} }
def find_products(data):
for x in data:
if 'products_and_quantities' in x['metadata']:
return map(format_product, x['metadata']['products_and_quantities'])
break
else:
continue
shipping_cost = None
unit_price = None
items = None
customer_note = ''
def deserialize_subscription(data): def deserialize_subscription(data):
sub_data = {}
for x in data: for x in data:
if 'products_and_quantities' in x['metadata']: if 'products_and_quantities' in x['metadata']:
customer_note = f"Grind: {x['metadata']['grind']}" sub_data['customer_note'] = f"Grind: {x['metadata']['grind']}"
unit_price = convert_int_to_decimal(x['price']['unit_amount']) sub_data['unit_price'] = convert_int_to_decimal(x['price']['unit_amount'])
items = map(format_product, x['metadata']['products_and_quantities']) sub_data['items'] = map(format_product, x['metadata']['products_and_quantities'])
sub_data['total_weight'] = x['metadata']['total_weight']
if x['description'] == 'Shipping': if x['description'] == 'Shipping':
shipping_cost = convert_int_to_decimal(x['amount']) sub_data['shipping_cost'] = convert_int_to_decimal(x['amount'])
continue continue
return sub_data
# shipping_cost = find_shipping_cost(data_object['lines']['data']) # shipping_cost = find_shipping_cost(data_object['lines']['data'])
# items = find_products(data_object['lines']['data']) # items = find_products(data_object['lines']['data'])
# unit_price = find_unit_price(data_object['lines']['data']) # unit_price = find_unit_price(data_object['lines']['data'])
deserialize_subscription(data_object['lines']['data']) sub_data = deserialize_subscription(data_object['lines']['data'])
order = Order.objects.create( order = Order.objects.create(
customer=, customer=,
@ -76,9 +66,9 @@ order.lines.add(
[OrderLine( [OrderLine(
product=item['product'], product=item['product'],
quantity=item['quantity'], quantity=item['quantity'],
customer_note='Grind: ', customer_note=sub_data['customer_note'],
unit_price=unit_price unit_price=sub_data['unit_price']
) for item in items] ) for item in sub_data['items']]
) )
order.save() order.save()

View File

@ -23,6 +23,7 @@
</div> </div>
{% for item in order.lines.all %} {% for item in order.lines.all %}
<div class="object__item object__item--col5"> <div class="object__item object__item--col5">
{% if item.variant %}
{% with product=item.variant.product %} {% with product=item.variant.product %}
<figure class="item__figure"> <figure class="item__figure">
{% if item.variant.image %} {% if item.variant.image %}
@ -37,6 +38,22 @@
<span>${{item.unit_price}}</span> <span>${{item.unit_price}}</span>
<span>${{item.get_total}}</span> <span>${{item.get_total}}</span>
{% endwith %} {% endwith %}
{% elif item.product %}
{% with product=item.product %}
<figure class="item__figure">
{% if item.variant.image %}
<img class="item__image" src="{{item.variant.image.image.url}}" alt="{{item.variant.image.image}}">
{% else %}
<img class="item__image" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
{% endif %}
<figcaption><strong>{{item.product}}</strong><br>{{item.customer_note}}</figcaption>
</figure>
<span>{{product.sku}}</span>
<span>{{item.quantity}}</span>
<span>${{item.unit_price}}</span>
<span>${{item.get_total}}</span>
{% endwith %}
{% endif %}
</div> </div>
{% empty %} {% empty %}
<p>No items in order yet.</p> <p>No items in order yet.</p>
@ -114,6 +131,16 @@
</div> </div>
</section> </section>
{% if order.subscription %}
<section class="object__panel">
<div class="object__item panel__header">
<h4>Subscription</h4>
</div>
<div class="panel__item">
<p>{{ order.subscription.stripe_id }}</p>
</div>
</section>
{% else %}
<section class="object__panel"> <section class="object__panel">
<div class="object__item panel__header"> <div class="object__item panel__header">
<h4>Transaction</h4> <h4>Transaction</h4>
@ -124,5 +151,6 @@
</p> </p>
</div> </div>
</section> </section>
{% endif %}
</article> </article>
{% endblock content %} {% endblock content %}

View File

@ -57,6 +57,29 @@
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
{% if customer.subscriptions.count > 0 %}
<section>
<h3>Your subscriptions</h3>
<table>
<thead>
<tr>
<th>Subscription #</th>
<th colspan="2">Status</th>
</tr>
</thead>
<tbody>
{% for subscription in subscriptions %}
<tr>
<td>{{ subscription.id }}</td>
<td>{{ subscription.status }}</td>
<td><a href="https://dashboard.stripe.com/test/subscriptions/{{ subscription.id }}">manage subscription &nearr;</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
{% endif %}
<section> <section>
<h3>Your orders</h3> <h3>Your orders</h3>
<table> <table>

View File

@ -21,6 +21,7 @@
<tbody> <tbody>
{% for item in order.lines.all %} {% for item in order.lines.all %}
<tr> <tr>
{% if item.variant %}
{% with product=item.variant.product %} {% with product=item.variant.product %}
<td> <td>
{% if item.variant.image %} {% if item.variant.image %}
@ -37,6 +38,24 @@
<td>${{item.unit_price}}</td> <td>${{item.unit_price}}</td>
<td>${{item.get_total}}</td> <td>${{item.get_total}}</td>
{% endwith %} {% endwith %}
{% elif item.product %}
{% with product=item.product %}
<td>
{% if item.variant.image %}
<img class="line__image" src="{{item.variant.image.image.url}}" alt="{{item.variant.image.image}}">
{% else %}
<img class="line__image" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
{% endif %}
</td>
<td>
<strong>{{ item.product }}</strong><br>
{{item.customer_note}}
</td>
<td>{{item.quantity}}</td>
<td>${{item.unit_price}}</td>
<td>${{item.get_total}}</td>
{% endwith %}
{% endif %}
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>

View File

@ -127,22 +127,5 @@ urlpatterns = [
views.SubscriptionDoneView.as_view(), views.SubscriptionDoneView.as_view(),
name='subscription-done' name='subscription-done'
), ),
path('<int:pk>/', include([
path(
'',
views.SubscriptionDetailView.as_view(),
name='subscription-detail'
),
path(
'update/',
views.SubscriptionUpdateView.as_view(),
name='subscription-update'
),
path(
'delete/',
views.SubscriptionDeleteView.as_view(),
name='subscription-delete'
),
])),
])), ])),
] ]

View File

@ -437,6 +437,10 @@ class CustomerDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView):
context['order_list'] = Order.objects.without_drafts().filter( context['order_list'] = Order.objects.without_drafts().filter(
customer=self.object customer=self.object
).prefetch_related('lines') ).prefetch_related('lines')
subscriptions = []
if self.object.stripe_id is not None:
subscriptions = stripe.Subscription.list(customer=self.object.stripe_id)['data']
context['subscriptions'] = subscriptions
return context return context
def test_func(self): def test_func(self):
@ -720,7 +724,7 @@ class SubscriptionCreateView(SuccessMessageMixin, CreateView):
session = stripe.checkout.Session.create( session = stripe.checkout.Session.create(
customer=self.object.customer.get_or_create_stripe_id(), customer=self.object.customer.get_or_create_stripe_id(),
success_url='http://' + Site.objects.get_current().domain + reverse( success_url='http://' + Site.objects.get_current().domain + reverse(
'storefront:subscription-detail', kwargs={'pk': self.object.pk} 'storefront:subscription-done'
) + '?session_id={CHECKOUT_SESSION_ID}', ) + '?session_id={CHECKOUT_SESSION_ID}',
cancel_url='http://' + Site.objects.get_current().domain + reverse( cancel_url='http://' + Site.objects.get_current().domain + reverse(
'storefront:subscription-create'), 'storefront:subscription-create'),
@ -743,27 +747,10 @@ class SubscriptionCreateView(SuccessMessageMixin, CreateView):
return super().form_valid(form) return super().form_valid(form)
class SubscriptionDetailView(DetailView):
model = Subscription
template_name = 'storefront/subscription/detail.html'
class SubscriptionDoneView(TemplateView): class SubscriptionDoneView(TemplateView):
template_name = 'storefront/subscription/done.html' template_name = 'storefront/subscription/done.html'
class SubscriptionUpdateView(SuccessMessageMixin, UpdateView):
model = Subscription
success_message = 'Subscription saved.'
fields = '__all__'
class SubscriptionDeleteView(SuccessMessageMixin, DeleteView):
model = Subscription
success_message = 'Subscription deleted.'
success_url = reverse_lazy('subscription-list')
# stripe listen --forward-to localhost:8000/stripe-webhook # stripe listen --forward-to localhost:8000/stripe-webhook
@csrf_exempt @csrf_exempt
@require_POST @require_POST