From 14246afd1961cb7662fe123814e9680eb2b52a07 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Fri, 30 Dec 2022 11:07:07 -0700 Subject: [PATCH] Finish first iteration of subscriptions --- .../migrations/0027_order_subscription.py | 19 +++++ src/core/models.py | 57 +++++++++++++- src/core/subscription_utils.py | 36 ++++----- .../templates/dashboard/order_detail.html | 76 +++++++++++++------ .../templates/storefront/customer_detail.html | 23 ++++++ .../templates/storefront/order_detail.html | 51 +++++++++---- src/storefront/urls.py | 17 ----- src/storefront/views.py | 23 ++---- 8 files changed, 202 insertions(+), 100 deletions(-) create mode 100644 src/core/migrations/0027_order_subscription.py diff --git a/src/core/migrations/0027_order_subscription.py b/src/core/migrations/0027_order_subscription.py new file mode 100644 index 0000000..0eab2a9 --- /dev/null +++ b/src/core/migrations/0027_order_subscription.py @@ -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'), + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 19504f5..92e8da8 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -382,6 +382,13 @@ class Order(models.Model): blank=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) 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) 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): - 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): metadata = {} for key, value in self.metadata.items(): - metadata[key] = json.dumps(value) + if 'products_and_quantities' in key: + metadata[key] = json.dumps(value) + else: + metadata[key] = value metadata['subscription_pk'] = self.pk return metadata diff --git a/src/core/subscription_utils.py b/src/core/subscription_utils.py index c38a9cc..9b05fb1 100644 --- a/src/core/subscription_utils.py +++ b/src/core/subscription_utils.py @@ -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): + sub_data = {} + for x in data: if 'products_and_quantities' in x['metadata']: - customer_note = f"Grind: {x['metadata']['grind']}" - unit_price = convert_int_to_decimal(x['price']['unit_amount']) - items = map(format_product, x['metadata']['products_and_quantities']) + sub_data['customer_note'] = f"Grind: {x['metadata']['grind']}" + sub_data['unit_price'] = convert_int_to_decimal(x['price']['unit_amount']) + sub_data['items'] = map(format_product, x['metadata']['products_and_quantities']) + sub_data['total_weight'] = x['metadata']['total_weight'] if x['description'] == 'Shipping': - shipping_cost = convert_int_to_decimal(x['amount']) + sub_data['shipping_cost'] = convert_int_to_decimal(x['amount']) continue + return sub_data + # shipping_cost = find_shipping_cost(data_object['lines']['data']) # items = find_products(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( customer=, @@ -76,9 +66,9 @@ order.lines.add( [OrderLine( product=item['product'], quantity=item['quantity'], - customer_note='Grind: ', - unit_price=unit_price - ) for item in items] + customer_note=sub_data['customer_note'], + unit_price=sub_data['unit_price'] + ) for item in sub_data['items']] ) order.save() diff --git a/src/dashboard/templates/dashboard/order_detail.html b/src/dashboard/templates/dashboard/order_detail.html index 3326809..bdb4a43 100644 --- a/src/dashboard/templates/dashboard/order_detail.html +++ b/src/dashboard/templates/dashboard/order_detail.html @@ -23,20 +23,37 @@ {% for item in order.lines.all %}
- {% with product=item.variant.product %} -
- {% if item.variant.image %} - {{item.variant.image.image}} - {% else %} - {{product.get_first_img.image}} - {% endif %} -
{{item.variant}}
{{item.customer_note}}
-
- {{product.sku}} - {{item.quantity}} - ${{item.unit_price}} - ${{item.get_total}} - {% endwith %} + {% if item.variant %} + {% with product=item.variant.product %} +
+ {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} + {{product.get_first_img.image}} + {% endif %} +
{{item.variant}}
{{item.customer_note}}
+
+ {{product.sku}} + {{item.quantity}} + ${{item.unit_price}} + ${{item.get_total}} + {% endwith %} + {% elif item.product %} + {% with product=item.product %} +
+ {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} + {{product.get_first_img.image}} + {% endif %} +
{{item.product}}
{{item.customer_note}}
+
+ {{product.sku}} + {{item.quantity}} + ${{item.unit_price}} + ${{item.get_total}} + {% endwith %} + {% endif %}
{% empty %}

No items in order yet.

@@ -114,15 +131,26 @@ -
-
-

Transaction

-
-
-

PayPal transaction ID: {{order.transaction.paypal_id}}
- Status: {{order.transaction.get_status_display}} -

-
-
+ {% if order.subscription %} +
+
+

Subscription

+
+
+

{{ order.subscription.stripe_id }}

+
+
+ {% else %} +
+
+

Transaction

+
+
+

PayPal transaction ID: {{order.transaction.paypal_id}}
+ Status: {{order.transaction.get_status_display}} +

+
+
+ {% endif %} {% endblock content %} diff --git a/src/storefront/templates/storefront/customer_detail.html b/src/storefront/templates/storefront/customer_detail.html index 213666d..f1f7b0f 100644 --- a/src/storefront/templates/storefront/customer_detail.html +++ b/src/storefront/templates/storefront/customer_detail.html @@ -57,6 +57,29 @@ {% endfor %} + {% if customer.subscriptions.count > 0 %} +
+

Your subscriptions

+ + + + + + + + + {% for subscription in subscriptions %} + + + + + + {% endfor %} + +
Subscription #Status
{{ subscription.id }}{{ subscription.status }}manage subscription ↗
+ +
+ {% endif %}

Your orders

diff --git a/src/storefront/templates/storefront/order_detail.html b/src/storefront/templates/storefront/order_detail.html index cdfe16d..7881c5f 100644 --- a/src/storefront/templates/storefront/order_detail.html +++ b/src/storefront/templates/storefront/order_detail.html @@ -21,22 +21,41 @@ {% for item in order.lines.all %} - {% with product=item.variant.product %} - - - - - - {% endwith %} + {% if item.variant %} + {% with product=item.variant.product %} + + + + + + {% endwith %} + {% elif item.product %} + {% with product=item.product %} + + + + + + {% endwith %} + {% endif %} {% empty %} diff --git a/src/storefront/urls.py b/src/storefront/urls.py index 5443db6..5508bcd 100644 --- a/src/storefront/urls.py +++ b/src/storefront/urls.py @@ -127,22 +127,5 @@ urlpatterns = [ views.SubscriptionDoneView.as_view(), name='subscription-done' ), - path('/', 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' - ), - ])), ])), ] diff --git a/src/storefront/views.py b/src/storefront/views.py index 070943d..d854ae3 100644 --- a/src/storefront/views.py +++ b/src/storefront/views.py @@ -437,6 +437,10 @@ class CustomerDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView): context['order_list'] = Order.objects.without_drafts().filter( customer=self.object ).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 def test_func(self): @@ -720,7 +724,7 @@ class SubscriptionCreateView(SuccessMessageMixin, CreateView): session = stripe.checkout.Session.create( customer=self.object.customer.get_or_create_stripe_id(), 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}', cancel_url='http://' + Site.objects.get_current().domain + reverse( 'storefront:subscription-create'), @@ -743,27 +747,10 @@ class SubscriptionCreateView(SuccessMessageMixin, CreateView): return super().form_valid(form) -class SubscriptionDetailView(DetailView): - model = Subscription - template_name = 'storefront/subscription/detail.html' - - class SubscriptionDoneView(TemplateView): 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 @csrf_exempt @require_POST
- {% if item.variant.image %} - {{item.variant.image.image}} - {% else %} - {{product.get_first_img.image}} - {% endif %} - - {{ item.variant }}
- {{item.customer_note}} -
{{item.quantity}}${{item.unit_price}}${{item.get_total}} + {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} + {{product.get_first_img.image}} + {% endif %} + + {{ item.variant }}
+ {{item.customer_note}} +
{{item.quantity}}${{item.unit_price}}${{item.get_total}} + {% if item.variant.image %} + {{item.variant.image.image}} + {% else %} + {{product.get_first_img.image}} + {% endif %} + + {{ item.product }}
+ {{item.customer_note}} +
{{item.quantity}}${{item.unit_price}}${{item.get_total}}