Finish first iteration of subscriptions
This commit is contained in:
parent
467e736147
commit
14246afd19
19
src/core/migrations/0027_order_subscription.py
Normal file
19
src/core/migrations/0027_order_subscription.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -23,20 +23,37 @@
|
||||
</div>
|
||||
{% for item in order.lines.all %}
|
||||
<div class="object__item object__item--col5">
|
||||
{% with product=item.variant.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.variant}}</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 %}
|
||||
{% if item.variant %}
|
||||
{% with product=item.variant.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.variant}}</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 %}
|
||||
{% 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>
|
||||
{% empty %}
|
||||
<p>No items in order yet.</p>
|
||||
@ -114,15 +131,26 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="object__panel">
|
||||
<div class="object__item panel__header">
|
||||
<h4>Transaction</h4>
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<p>PayPal transaction ID: <strong>{{order.transaction.paypal_id}}</strong><br>
|
||||
Status: <strong>{{order.transaction.get_status_display}}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</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">
|
||||
<div class="object__item panel__header">
|
||||
<h4>Transaction</h4>
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<p>PayPal transaction ID: <strong>{{order.transaction.paypal_id}}</strong><br>
|
||||
Status: <strong>{{order.transaction.get_status_display}}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock content %}
|
||||
|
||||
@ -57,6 +57,29 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</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 ↗</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</section>
|
||||
{% endif %}
|
||||
<section>
|
||||
<h3>Your orders</h3>
|
||||
<table>
|
||||
|
||||
@ -21,22 +21,41 @@
|
||||
<tbody>
|
||||
{% for item in order.lines.all %}
|
||||
<tr>
|
||||
{% with product=item.variant.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.variant }}</strong><br>
|
||||
{{item.customer_note}}
|
||||
</td>
|
||||
<td>{{item.quantity}}</td>
|
||||
<td>${{item.unit_price}}</td>
|
||||
<td>${{item.get_total}}</td>
|
||||
{% endwith %}
|
||||
{% if item.variant %}
|
||||
{% with product=item.variant.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.variant }}</strong><br>
|
||||
{{item.customer_note}}
|
||||
</td>
|
||||
<td>{{item.quantity}}</td>
|
||||
<td>${{item.unit_price}}</td>
|
||||
<td>${{item.get_total}}</td>
|
||||
{% 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>
|
||||
{% empty %}
|
||||
<tr>
|
||||
|
||||
@ -127,22 +127,5 @@ urlpatterns = [
|
||||
views.SubscriptionDoneView.as_view(),
|
||||
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'
|
||||
),
|
||||
])),
|
||||
])),
|
||||
]
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user