Merge branch 'feature/usps-shipping' into develop
This commit is contained in:
commit
1e49f36f7a
@ -71,3 +71,27 @@ class ShippingMethodType:
|
||||
(PRICE_BASED, "Price based shipping"),
|
||||
(WEIGHT_BASED, "Weight based shipping"),
|
||||
]
|
||||
|
||||
class ShippingService:
|
||||
FIRST_CLASS = "FIRST CLASS"
|
||||
PRIORITY = "PRIORITY"
|
||||
PRIORITY_COMMERCIAL = "PRIORITY COMMERCIAL"
|
||||
|
||||
CHOICES = [
|
||||
(FIRST_CLASS, "First Class"),
|
||||
(PRIORITY, "Priority"),
|
||||
(PRIORITY_COMMERCIAL, "Priority Commercial")
|
||||
]
|
||||
|
||||
class ShippingContainer:
|
||||
LG_FLAT_RATE_BOX = "LG FLAT RATE BOX"
|
||||
REGIONAL_RATE_BOX_A = "REGIONALRATEBOXA"
|
||||
REGIONAL_RATE_BOX_B = "REGIONALRATEBOXB"
|
||||
VARIABLE = "VARIABLE"
|
||||
|
||||
CHOICES = [
|
||||
(LG_FLAT_RATE_BOX, "Flate Rate Box - Large"),
|
||||
(REGIONAL_RATE_BOX_A, "Regional Rate Box A"),
|
||||
(REGIONAL_RATE_BOX_B, "Regional Rate Box B"),
|
||||
(VARIABLE, "Variable")
|
||||
]
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.0.2 on 2022-04-24 16:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0005_alter_product_options_product_sorting'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='order',
|
||||
options={'ordering': ('-created_at',)},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='shipping_total',
|
||||
field=models.DecimalField(decimal_places=2, default=0, max_digits=5),
|
||||
),
|
||||
]
|
||||
@ -9,6 +9,8 @@ from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.urls import reverse
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
from django_measurement.models import MeasurementField
|
||||
|
||||
@ -25,6 +27,10 @@ from .weight import WeightUnits, zero_weight
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProductEncoder(DjangoJSONEncoder):
|
||||
def default(self, obj):
|
||||
logger.info(f"\n{obj}\n")
|
||||
return super().default(obj)
|
||||
|
||||
class ProductManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
@ -69,7 +75,7 @@ class Product(models.Model):
|
||||
try:
|
||||
return self.productphoto_set.all()[1]
|
||||
except IndexError:
|
||||
pass
|
||||
return 'No image'
|
||||
|
||||
class Meta:
|
||||
ordering = ['sorting', 'name']
|
||||
@ -200,6 +206,7 @@ class Order(models.Model):
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
|
||||
coupon = models.ForeignKey(
|
||||
Coupon,
|
||||
related_name='orders',
|
||||
@ -207,6 +214,12 @@ class Order(models.Model):
|
||||
null=True
|
||||
)
|
||||
|
||||
shipping_total = models.DecimalField(
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
default=0
|
||||
)
|
||||
|
||||
total_net_amount = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
@ -237,7 +250,7 @@ class Order(models.Model):
|
||||
return Decimal('0')
|
||||
|
||||
def get_total_price_after_discount(self):
|
||||
return round(self.total_net_amount - self.get_discount(), 2)
|
||||
return round((self.total_net_amount - self.get_discount()) + self.shipping_total, 2)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dashboard:order-detail', kwargs={'pk': self.pk})
|
||||
|
||||
38
src/core/usps.py
Normal file
38
src/core/usps.py
Normal file
@ -0,0 +1,38 @@
|
||||
import json
|
||||
import requests
|
||||
import xmltodict
|
||||
|
||||
from lxml import etree
|
||||
from usps import USPSApi
|
||||
|
||||
class USPSApiWithRate(USPSApi):
|
||||
urls = {
|
||||
'tracking': 'TrackV2{test}&XML={xml}',
|
||||
'label': 'eVS{test}&XML={xml}',
|
||||
'validate': 'Verify&XML={xml}',
|
||||
'rate': 'RateV4&XML={xml}',
|
||||
}
|
||||
|
||||
def get_rate(self, *args, **kwargs):
|
||||
return Rate(self, *args, **kwargs)
|
||||
|
||||
|
||||
class Rate:
|
||||
|
||||
def __init__(self, usps, request, **kwargs):
|
||||
xml = etree.Element('RateV4Request', {'USERID': usps.api_user_id})
|
||||
etree.SubElement(xml, 'Revision').text = '2'
|
||||
package = etree.SubElement(xml, 'Package', {'ID': '0'})
|
||||
etree.SubElement(package, 'Service').text = request['service']
|
||||
etree.SubElement(package, 'ZipOrigination').text = request['zip_origination']
|
||||
etree.SubElement(package, 'ZipDestination').text = request['zip_destination']
|
||||
etree.SubElement(package, 'Pounds').text = request['pounds']
|
||||
etree.SubElement(package, 'Ounces').text = request['ounces']
|
||||
etree.SubElement(package, 'Container').text = request['container']
|
||||
etree.SubElement(package, 'Width').text = request['width']
|
||||
etree.SubElement(package, 'Length').text = request['length']
|
||||
etree.SubElement(package, 'Height').text = request['height']
|
||||
etree.SubElement(package, 'Girth').text = request['girth']
|
||||
etree.SubElement(package, 'Machinable').text = request['machinable']
|
||||
|
||||
self.result = usps.send_request('rate', xml)
|
||||
@ -103,11 +103,12 @@
|
||||
</div>
|
||||
<div class="panel__item">
|
||||
<p>
|
||||
<span>Subtotal: {{order.total_net_amount}}</span><br>
|
||||
<span>Subtotal: ${{order.total_net_amount}}</span><br>
|
||||
{% if order.coupon %}
|
||||
<span>Discount: {{order.coupon.discount_value}} {{order.coupon.get_discount_value_type_display}}</span><br>
|
||||
{% endif %}
|
||||
<span>Total: {{order.get_total_price_after_discount}}</span>
|
||||
<span>Shipping: ${{order.shipping_total}}</span><br>
|
||||
<span>Total: ${{order.get_total_price_after_discount}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
<span class="order__status--display">
|
||||
<div class="status__dot order__status--{{order.status}}"></div>
|
||||
{{order.get_status_display}}</span>
|
||||
<span>${{order.total_net_amount}}</span>
|
||||
<span>${{order.get_total_price_after_discount}}</span>
|
||||
</a>
|
||||
{% empty %}
|
||||
<span class="object__item">No orders</span>
|
||||
|
||||
@ -20,6 +20,8 @@ CACHE_CONFIG = {
|
||||
|
||||
PAYPAL_CLIENT_ID = os.environ.get('PAYPAL_CLIENT_ID', '')
|
||||
PAYPAL_SECRET_ID = os.environ.get('PAYPAL_SECRET_ID', '')
|
||||
USPS_USER_ID = os.environ.get('USPS_USER_ID', '639NATHA3105')
|
||||
DEFAULT_ZIP_ORIGINATION = os.environ.get('DEFAULT_ZIP_ORIGINATION', '98368')
|
||||
|
||||
ANYMAIL_CONFIG = {
|
||||
'MAILGUN_API_KEY': os.environ.get('MAILGUN_API_KEY', ''),
|
||||
|
||||
@ -93,6 +93,7 @@ DATABASES = {
|
||||
CACHES = {'default': CACHE_CONFIG}
|
||||
|
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
|
||||
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||
|
||||
BIN
src/static/images/site_banner.jpg
Normal file
BIN
src/static/images/site_banner.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 MiB |
@ -1,12 +1,14 @@
|
||||
:root {
|
||||
--fg-color: #34201a;
|
||||
--fg-alt-color: #663a2d;
|
||||
--bg-color: #f5f5f5;
|
||||
--bg-alt-color: #c8a783;
|
||||
--bg-color: #fffbf8;
|
||||
--bg-alt-color: #b07952;
|
||||
--gray-color: #9d9d9d;
|
||||
--yellow-color: #f8a911;
|
||||
--yellow-alt-color: #ffce6f;
|
||||
--yellow-dark-color: #b27606;
|
||||
--red-color: #d43131;
|
||||
--green-color: #3ea165;
|
||||
|
||||
--default-border: 2px solid var(--gray-color);
|
||||
}
|
||||
@ -48,6 +50,7 @@ h1, h2, h3, h4, h5 {
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
font-family: 'Vollkorn', serif;
|
||||
font-size: 2.488rem;
|
||||
}
|
||||
|
||||
@ -443,6 +446,53 @@ section:not(:last-child) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Site Banner
|
||||
========================================================================== */
|
||||
.site__banner {
|
||||
background-color: rgba(0, 0, 0, 0.44);
|
||||
background-blend-mode: multiply;
|
||||
background-image: url("/static/images/site_banner.jpg");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 8rem 1rem;
|
||||
font-family: 'Vollkorn', serif;
|
||||
}
|
||||
.site__banner h1 {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
|
||||
.site__banner p {
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
/* Messages
|
||||
========================================================================== */
|
||||
.messages {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.messages p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.messages .success {
|
||||
background-color: var(--green-color);
|
||||
}
|
||||
|
||||
.messages .warning {
|
||||
background-color: var(--yellow-color);
|
||||
}
|
||||
|
||||
.messages .error {
|
||||
background-color: var(--red-color);
|
||||
}
|
||||
|
||||
/* Site Cart
|
||||
========================================================================== */
|
||||
.site__cart {
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
import logging
|
||||
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
from core.models import Product, OrderLine, Coupon
|
||||
from .payments import CreateOrder
|
||||
from django.contrib import messages
|
||||
|
||||
from core.models import Product, OrderLine, Coupon
|
||||
from core.usps import USPSApiWithRate
|
||||
from core import (
|
||||
DiscountValueType,
|
||||
VoucherType,
|
||||
TransactionStatus,
|
||||
OrderStatus,
|
||||
ShippingMethodType
|
||||
ShippingMethodType,
|
||||
ShippingService,
|
||||
ShippingContainer
|
||||
)
|
||||
|
||||
from .payments import CreateOrder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Cart:
|
||||
@ -23,7 +29,7 @@ class Cart:
|
||||
cart = self.session[settings.CART_SESSION_ID] = {}
|
||||
self.cart = cart
|
||||
|
||||
def add(self, product, quantity=1, grind='', update_quantity=False):
|
||||
def add(self, request, product, quantity=1, grind='', update_quantity=False):
|
||||
product_id = str(product.id)
|
||||
if product_id not in self.cart:
|
||||
self.cart[product_id] = {
|
||||
@ -36,7 +42,10 @@ class Cart:
|
||||
self.cart[product_id]['quantity'] = quantity
|
||||
else:
|
||||
self.cart[product_id]['quantity'] += quantity
|
||||
self.save()
|
||||
if len(self) <= 20:
|
||||
self.save()
|
||||
else:
|
||||
messages.warning(request, "Cart is full: 20 items or less.")
|
||||
|
||||
def save(self):
|
||||
self.session[settings.CART_SESSION_ID] = self.cart
|
||||
@ -63,6 +72,26 @@ class Cart:
|
||||
def __len__(self):
|
||||
return sum(item['quantity'] for item in self.cart.values())
|
||||
|
||||
def get_total_weight(self):
|
||||
return sum([item['product'].weight.value * item['quantity'] for item in self])
|
||||
|
||||
def get_shipping_box(self):
|
||||
logger.info(len(self))
|
||||
if len(self) > 6 and len(self) <= 10:
|
||||
return ShippingContainer.LG_FLAT_RATE_BOX
|
||||
elif len(self) > 2 and len(self) <= 6:
|
||||
return ShippingContainer.REGIONAL_RATE_BOX_B
|
||||
elif len(self) <= 2:
|
||||
return ShippingContainer.REGIONAL_RATE_BOX_A
|
||||
else:
|
||||
return ShippingContainer.VARIABLE
|
||||
|
||||
def get_shipping_cost(self):
|
||||
usps_rate_request = self.build_usps_rate_request()
|
||||
usps = USPSApiWithRate(settings.USPS_USER_ID, test=True)
|
||||
validation = usps.get_rate(usps_rate_request)
|
||||
return Decimal(validation.result['RateV4Response']['Package']['Postage']['CommercialRate'])
|
||||
|
||||
def get_total_price(self):
|
||||
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
|
||||
|
||||
@ -74,6 +103,22 @@ class Cart:
|
||||
pass
|
||||
self.session.modified = True
|
||||
|
||||
def build_usps_rate_request(self):
|
||||
return \
|
||||
{
|
||||
'service': ShippingService.PRIORITY_COMMERCIAL,
|
||||
'zip_origination': settings.DEFAULT_ZIP_ORIGINATION,
|
||||
'zip_destination': f'{self.session.get("shipping_address")["postal_code"]}',
|
||||
'pounds': '0',
|
||||
'ounces': f'{self.get_total_weight()}',
|
||||
'container': f'{self.get_shipping_box()}',
|
||||
'width': '',
|
||||
'length': '',
|
||||
'height': '',
|
||||
'girth': '',
|
||||
'machinable': 'TRUE'
|
||||
}
|
||||
|
||||
def build_order_params(self):
|
||||
return \
|
||||
{
|
||||
@ -81,7 +126,7 @@ class Cart:
|
||||
'total_price': f'{self.get_total_price_after_discount()}',
|
||||
'item_total': f'{self.get_total_price()}',
|
||||
'discount': f'{self.get_discount()}',
|
||||
'shipping_price': '0',
|
||||
'shipping_price': f'{self.get_shipping_cost()}',
|
||||
'tax_total': '0',
|
||||
'shipping_method': 'US POSTAL SERVICE',
|
||||
'shipping_address': self.build_shipping_address(self.session.get('shipping_address')),
|
||||
@ -130,5 +175,8 @@ class Cart:
|
||||
return round((self.coupon.discount_value / Decimal('100')) * self.get_total_price(), 2)
|
||||
return Decimal('0')
|
||||
|
||||
def get_total_price_after_discount(self):
|
||||
def get_subtotal_price_after_discount(self):
|
||||
return round(self.get_total_price() - self.get_discount(), 2)
|
||||
|
||||
def get_total_price_after_discount(self):
|
||||
return round(self.get_total_price() - self.get_discount() + self.get_shipping_cost(), 2)
|
||||
|
||||
@ -96,9 +96,11 @@ class OrderCreateForm(forms.ModelForm):
|
||||
model = Order
|
||||
fields = (
|
||||
'total_net_amount',
|
||||
'shipping_total',
|
||||
)
|
||||
widgets = {
|
||||
'total_net_amount': forms.HiddenInput()
|
||||
'total_net_amount': forms.HiddenInput(),
|
||||
'shipping_total': forms.HiddenInput()
|
||||
}
|
||||
|
||||
class CouponApplyForm(forms.Form):
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
<table class="cart__totals">
|
||||
<tr>
|
||||
<td>Subtotal</td>
|
||||
<td>${{cart.get_total_price|floatformat:"2"}}</td>
|
||||
<td>${{ cart.get_total_price|floatformat:"2" }}</td>
|
||||
</tr>
|
||||
{% if cart.coupon %}
|
||||
<tr>
|
||||
@ -61,7 +61,7 @@
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td><strong>${{cart.get_total_price_after_discount|floatformat:"2"}}</strong></td>
|
||||
<td><strong>${{cart.get_subtotal_price_after_discount|floatformat:"2"}}</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -5,13 +5,9 @@
|
||||
<header>
|
||||
<h1>Contact us</h1>
|
||||
<h4>Problem with your online order or have a question?</h4>
|
||||
<p>
|
||||
Please contact us, we’re happy to help you over the phone<br>
|
||||
<a href="tel:+13603855856">(360) 385-5856</a> between 8:00 am and 10:00 pm Pacific Time.
|
||||
</p>
|
||||
<p>Please contact us, we’re happy to help you.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p>Or send us a message using the form below and we'll email you back as soon as we can.</p>
|
||||
<form action="{% url 'storefront:contact' %}" method="post" class="contact-form">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
|
||||
@ -62,6 +62,10 @@
|
||||
<td>{{cart.coupon.discount_value}} {{cart.coupon.get_discount_value_type_display}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>Shipping</td>
|
||||
<td>${{ cart.get_shipping_cost }}</small></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td><strong>${{cart.get_total_price_after_discount|floatformat:"2"}}</strong></td>
|
||||
|
||||
@ -6,6 +6,10 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="site__banner">
|
||||
<h1><em>Better</em>, not <em>Bitter</em></h1>
|
||||
<p>ORGANIC COFFEE, SLOW ROASTED, ITALIAN STYLE</p>
|
||||
</div>
|
||||
<article>
|
||||
<section class="product__list">
|
||||
{% for product in product_list %}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h2>Reviews</h2>
|
||||
<h1>Reviews</h1>
|
||||
</header>
|
||||
<section>
|
||||
<figure>
|
||||
|
||||
@ -44,6 +44,7 @@ class CartTest(TestCase):
|
||||
request = response.wsgi_request
|
||||
cart = Cart(request)
|
||||
cart.add(
|
||||
request=request,
|
||||
product=self.product,
|
||||
quantity=1,
|
||||
update_quantity=False
|
||||
|
||||
@ -17,6 +17,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
|
||||
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
|
||||
@ -26,6 +27,7 @@ from accounts.utils import get_or_create_customer
|
||||
from accounts.forms import AddressForm as AccountAddressForm, CustomerUpdateForm
|
||||
from core.models import Product, Order, Transaction, OrderLine, Coupon
|
||||
from core.forms import ShippingMethodForm
|
||||
from core import OrderStatus
|
||||
|
||||
from .forms import AddToCartForm, UpdateCartItemForm, OrderCreateForm, AddressForm, CouponApplyForm, ContactForm
|
||||
from .cart import Cart
|
||||
@ -61,6 +63,7 @@ class CartAddProductView(SingleObjectMixin, FormView):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
cart.add(
|
||||
request=request,
|
||||
product=self.get_object(),
|
||||
grind=form.cleaned_data['grind'],
|
||||
quantity=form.cleaned_data['quantity']
|
||||
@ -85,6 +88,7 @@ class CartUpdateProductView(SingleObjectMixin, FormView):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
cart.add(
|
||||
request=request,
|
||||
product=self.get_object(),
|
||||
quantity=form.cleaned_data['quantity'],
|
||||
update_quantity=form.cleaned_data['update']
|
||||
@ -178,7 +182,8 @@ class OrderCreateView(CreateView):
|
||||
def get_initial(self):
|
||||
cart = Cart(self.request)
|
||||
initial = {
|
||||
'total_net_amount': cart.get_total_price()
|
||||
'total_net_amount': cart.get_total_price(),
|
||||
'shipping_total': cart.get_shipping_cost()
|
||||
}
|
||||
|
||||
if self.request.user.is_authenticated:
|
||||
@ -210,13 +215,14 @@ class OrderCreateView(CreateView):
|
||||
cart = Cart(self.request)
|
||||
shipping_address = self.request.session.get('shipping_address')
|
||||
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address)
|
||||
form.instance.status = OrderStatus.DRAFT
|
||||
self.object = form.save()
|
||||
bulk_list = cart.build_bulk_list(self.object)
|
||||
objs = OrderLine.objects.bulk_create(bulk_list)
|
||||
|
||||
response = cart.create_order()
|
||||
data = response.result.__dict__['_dict']
|
||||
cart.clear()
|
||||
|
||||
self.request.session['order_id'] = self.object.pk
|
||||
|
||||
return JsonResponse(data)
|
||||
@ -224,8 +230,12 @@ class OrderCreateView(CreateView):
|
||||
def paypal_order_transaction_capture(request, transaction_id):
|
||||
if request.method =="POST":
|
||||
data = CaptureOrder().capture_order(transaction_id)
|
||||
|
||||
transaction = Transaction.objects.get(order__pk=request.session.get('order_id'))
|
||||
cart = Cart(request)
|
||||
cart.clear()
|
||||
order = Order.objects.get(pk=request.session.get('order_id'))
|
||||
order.status = OrderStatus.UNFULFILLED
|
||||
order.save()
|
||||
transaction = Transaction.objects.get(order=order)
|
||||
transaction.paypal_id = data['purchase_units'][0]['payments']['captures'][0]['id']
|
||||
transaction.status = data['status']
|
||||
transaction.save()
|
||||
|
||||
@ -73,6 +73,13 @@
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
{% if messages %}
|
||||
<section class="messages">
|
||||
{% for message in messages %}
|
||||
<p {% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</main>
|
||||
@ -81,8 +88,7 @@
|
||||
<p><button class="show-modal">Subscribe to our newsletter</button></p>
|
||||
<p>
|
||||
<strong>Problem with your online order or have a question?</strong><br>
|
||||
Please <a href="{% url 'storefront:contact' %}">contact us</a>, we’re happy to help you over the phone<br>
|
||||
<a href="tel:+13603855856">(360) 385-5856</a> between 8:00 am and 10:00 pm Pacific Time.<br>
|
||||
Please <a href="{% url 'storefront:contact' %}">contact us</a>, we’re happy to help you.<br>
|
||||
<address>854 East Park Ave. Suite 1, Port Townsend, WA 98368</address>
|
||||
</p>
|
||||
<p>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
{% block plain %}
|
||||
Great news! Your recent order #{{order_id}} has shipped
|
||||
|
||||
{{tracking_id}}
|
||||
Your USPS tracking ID: {{tracking_id}}
|
||||
|
||||
Thanks,
|
||||
Port Townsend Coffee
|
||||
@ -11,7 +11,7 @@
|
||||
{% block html %}
|
||||
<p>Great news! Your recent order #{{order_id}} has shipped</p>
|
||||
|
||||
<p>{{tracking_id}}</p>
|
||||
<p>Your USPS tracking ID: {{tracking_id}}</p>
|
||||
|
||||
<p>Thanks,<br>
|
||||
Port Townsend Coffee</p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user