Skip to content
Snippets Groups Projects
Commit e59c58f3 authored by Pelle Koster's avatar Pelle Koster
Browse files

fix styling, force invoice creation payment method

parent 625eabe0
Branches
Tags
No related merge requests found
...@@ -11,6 +11,9 @@ body { ...@@ -11,6 +11,9 @@ body {
color: var(--dark); color: var(--dark);
font-family: sans-serif; font-family: sans-serif;
font-size: 18px; font-size: 18px;
}
.tnc-background {
background-color: var(--secondary); background-color: var(--secondary);
background-image: url("/static/background.svg"); background-image: url("/static/background.svg");
background-size: 150%; background-size: 150%;
...@@ -79,6 +82,10 @@ body { ...@@ -79,6 +82,10 @@ body {
font-weight: bold; font-weight: bold;
} }
.checkout-summary .total:not(.last) {
padding-bottom: 16px;
}
.card { .card {
padding: 2rem; padding: 2rem;
border-radius: 2rem; border-radius: 2rem;
...@@ -147,7 +154,7 @@ body { ...@@ -147,7 +154,7 @@ body {
} }
@media only screen and (min-width: 1440px) { @media only screen and (min-width: 1440px) {
body { .tnc-background {
background-position: calc(50% + 500px) -100px; background-position: calc(50% + 500px) -100px;
background-size: min(200%, 2500px); background-size: min(200%, 2500px);
} }
......
...@@ -9,7 +9,9 @@ from django.conf import settings ...@@ -9,7 +9,9 @@ from django.conf import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
NICOLE_CUSTOMER_ID = "cus_RISxOE5iM40CkO" INVOICE_TEMPLATE_ID = "inrtem_1QPs0nDSpyjzuj5pfs50CWSy"
TAX_RATE_ID = "txr_1QeddlDSpyjzuj5pPwUcMwTd"
TAX_RATE = 20 # %
def get_or_create_customer(visitor) -> Optional[str]: def get_or_create_customer(visitor) -> Optional[str]:
...@@ -144,20 +146,41 @@ def create_invoice(shopping_cart, customer_id, purchase_order=None, vat_number=N ...@@ -144,20 +146,41 @@ def create_invoice(shopping_cart, customer_id, purchase_order=None, vat_number=N
collection_method="send_invoice", collection_method="send_invoice",
days_until_due=30, days_until_due=30,
custom_fields=custom_fields, custom_fields=custom_fields,
automatic_tax={ rendering={"template": INVOICE_TEMPLATE_ID},
"enabled": True,
},
) )
for item in shopping_cart: for item in shopping_cart:
stripe.InvoiceItem.create( stripe.InvoiceItem.create(
customer=customer_id, customer=customer_id,
price=item.stripe_id, price=item.stripe_id,
invoice=invoice["id"], invoice=invoice["id"],
tax_rates=[TAX_RATE_ID],
) )
stripe.Invoice.send_invoice(invoice["id"]) stripe.Invoice.send_invoice(invoice["id"])
return invoice return invoice
def get_product_prices():
stripe.api_key = settings.STRIPE_API_KEY
products = _list_all_items(stripe.Product)
prices = _list_all_items(stripe.Price)
price_by_id = {p.id: p for p in prices}
return {product.id: price_by_id.get(product.default_price) for product in products}
def _list_all_items(stripe_cls):
data = []
starting_after = None
while True:
result = stripe_cls.list(limit=100, starting_after=starting_after)
data.extend(result.data)
if not result.data or not result.has_more:
break
starting_after = result.data[-1].id
return data
def read_event(payload, sig_header): def read_event(payload, sig_header):
# cf. https://docs.stripe.com/webhooks#verify-official-libraries # cf. https://docs.stripe.com/webhooks#verify-official-libraries
try: try:
......
...@@ -10,10 +10,12 @@ ...@@ -10,10 +10,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% static 'main.css' %}"> <link rel="stylesheet" href="{% static 'main.css' %}">
<link rel="icon" href="{% static 'icon-150.png' %}" sizes="32x32"> <link rel="icon" href="{% static 'icon-150.png' %}" sizes="32x32">
<link rel="shortcut icon" href="{% static 'icon-300.png' %}" sizes="192x192"> <link rel="shortcut icon"
href="{% static 'icon-300.png' %}"
sizes="192x192">
{% endblock head %} {% endblock head %}
</head> </head>
<body> <body class="{{ body_classes|default:'' }}">
{% block body %} {% block body %}
{% endblock body %} {% endblock body %}
</body> </body>
......
...@@ -20,16 +20,23 @@ ...@@ -20,16 +20,23 @@
<div class="price">€{{ item.price }}</div> <div class="price">€{{ item.price }}</div>
{% endfor %} {% endfor %}
<div class="total item">Total (excl. VAT)</div> <div class="total item">Total (excl. VAT)</div>
<div class="total price">€{{ total }}</div> <div class="total price">€{{ total_excl }}</div>
<div class="item">VAT (20%)</div>
<div class="price">€{{ vat }}</div>
<div class="total item last">Total (incl. VAT)</div>
<div class="total price last">€{{ total_incl }}</div>
</div> </div>
</div> </div>
<form id="checkout-form" class="flex column" method="post"> <form id="checkout-form" class="flex column" method="post">
{% for field in form %} {% for field in form %}
<label class="label">{{ field.label }}</label> {% if field.name != "payment_method" %}
{% for error in field.errors %}<div class="error">{{ error }}</div>{% endfor %} <label class="label">{{ field.label }}</label>
{{ field }} {% for error in field.errors %}<div class="error">{{ error }}</div>{% endfor %}
{{ field }}
{% endif %}
{% endfor %} {% endfor %}
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="payment_method" value="invoice">
<div class="confirm flex center"> <div class="confirm flex center">
<input type="submit" class="button bg-primary text-primary" value="confirm"> <input type="submit" class="button bg-primary text-primary" value="confirm">
</div> </div>
......
...@@ -6,9 +6,10 @@ ...@@ -6,9 +6,10 @@
<div class="checkout flex center"> <div class="checkout flex center">
<div class="card flex center column"> <div class="card flex center column">
<p> <p>
You should receive an email with an invoice for your registration soon with payment Thank you for registering for TNC 2025! You should soon receive an email with an
instructions. After you have successfully paid the invoice, your registration will invoice for your registration as well as payment instructions. If not, please check
be completed. your junk mail. After you have successfully paid the invoice, your registration
will be completed.
</p> </p>
</div> </div>
</div> </div>
......
...@@ -57,3 +57,11 @@ def get_answers(visitor, question_id) -> set[str]: ...@@ -57,3 +57,11 @@ def get_answers(visitor, question_id) -> set[str]:
def read_shopping_cart(visitor, question_id=None) -> set[str]: def read_shopping_cart(visitor, question_id=None) -> set[str]:
question_id = question_id or settings.VISIT["shopping-cart-question-id"] question_id = question_id or settings.VISIT["shopping-cart-question-id"]
return get_answers(visitor, question_id) return get_answers(visitor, question_id)
def compose_shopping_cart(visitor: dict):
# - Read out the various visitor status and questions
# - compose all the paid items that they need to have
# - compare with our internal orders
# - bill them for whatever is missing
pass
...@@ -9,14 +9,13 @@ from django.views.decorators.csrf import csrf_exempt ...@@ -9,14 +9,13 @@ from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse from django.urls import reverse
import requests import requests
from stripe_checkout.stripe_checkout.models import Event, Order, PricedItem from .models import Event, Order, PricedItem
from . import stripe from . import stripe
from stripe_checkout.stripe_checkout.visit import VisitorAPI, read_shopping_cart from .visit import VisitorAPI, read_shopping_cart
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
PAYMENT_OPTIONS = { PAYMENT_OPTIONS = {
"checkout": {"display_name": "Pay Now"},
"invoice": {"display_name": "Request invoice"}, "invoice": {"display_name": "Request invoice"},
} }
...@@ -56,7 +55,13 @@ def checkout(request, visitor_id): ...@@ -56,7 +55,13 @@ def checkout(request, visitor_id):
response.status_code = 303 response.status_code = 303
elif data["payment_method"] == "invoice": elif data["payment_method"] == "invoice":
obj = create_invoice(visitor, data) obj = create_invoice(visitor, data)
response = render(request, "invoice-created.html") response = render(
request,
"invoice-created.html",
context={
"body_classes": "tnc-background",
},
)
Order.create_from_shopping_cart(visitor, shopping_cart, stripe_obj=obj) Order.create_from_shopping_cart(visitor, shopping_cart, stripe_obj=obj)
empty_shopping_cart(visitor) empty_shopping_cart(visitor)
...@@ -65,14 +70,20 @@ def checkout(request, visitor_id): ...@@ -65,14 +70,20 @@ def checkout(request, visitor_id):
# form is invalid # form is invalid
status_code = 400 status_code = 400
total_excl = sum(item.price for item in shopping_cart)
vat = int(sum(item.price * stripe.TAX_RATE / 100 for item in shopping_cart))
total_incl = total_excl + vat
return render( return render(
request, request,
"checkout.html", "checkout.html",
context={ context={
"body_classes": "tnc-background",
"visitor_id": visitor_id, "visitor_id": visitor_id,
"shopping_cart": shopping_cart, "shopping_cart": shopping_cart,
"total": sum(item.price for item in shopping_cart), "total_excl": total_excl,
"vat": vat,
"total_incl": total_incl,
"form": form or PaymentDetailsForm(), "form": form or PaymentDetailsForm(),
}, },
status=status_code, status=status_code,
...@@ -117,7 +128,12 @@ def create_banktransfer(visitor, data): ...@@ -117,7 +128,12 @@ def create_banktransfer(visitor, data):
def create_invoice(visitor, data): def create_invoice(visitor, data):
shopping_cart = get_shopping_cart(visitor) shopping_cart = get_shopping_cart(visitor)
customer = stripe.get_or_create_customer(visitor) customer = stripe.get_or_create_customer(visitor)
return stripe.create_invoice(shopping_cart, customer) return stripe.create_invoice(
shopping_cart,
customer,
purchase_order=data["purchase_order"],
vat_number=data["vat_number"],
)
@require_GET @require_GET
...@@ -130,6 +146,7 @@ def checkout_success(request, visitor_id): ...@@ -130,6 +146,7 @@ def checkout_success(request, visitor_id):
"checkout-success.html", "checkout-success.html",
context={ context={
"visitor_id": visitor_id, "visitor_id": visitor_id,
"body_classes": "tnc-background",
}, },
) )
...@@ -142,6 +159,7 @@ def checkout_cancel(request, visitor_id): ...@@ -142,6 +159,7 @@ def checkout_cancel(request, visitor_id):
"checkout-cancel.html", "checkout-cancel.html",
context={ context={
"visitor_id": visitor_id, "visitor_id": visitor_id,
"body_classes": "tnc-background",
}, },
) )
......
...@@ -29,24 +29,6 @@ def test_update_visitor(client, user, visitor_id): ...@@ -29,24 +29,6 @@ def test_update_visitor(client, user, visitor_id):
assert rv.status_code == 302 assert rv.status_code == 302
@responses.activate
@pytest.mark.django_db
def test_create_checkout_session(client, visitor_id):
assert not Order.objects.count()
with patch(
"stripe.checkout.Session.create",
return_value={"id": "stripe-checkout-id", "url": "http://bogus.url"},
):
rv = client.post(
f"/checkout/{visitor_id}/", data={"payment_method": "checkout"}
)
assert rv.status_code == 303
assert rv.headers["Location"] == "http://bogus.url"
assert Order.objects.count() == 1
@responses.activate @responses.activate
@pytest.mark.django_db @pytest.mark.django_db
def test_create_invoice(client, visitor_id): def test_create_invoice(client, visitor_id):
......
...@@ -8,7 +8,6 @@ extend-ignore = E203,E701,W605 ...@@ -8,7 +8,6 @@ extend-ignore = E203,E701,W605
[pytest] [pytest]
DJANGO_SETTINGS_MODULE = stripe_checkout.settings.dev DJANGO_SETTINGS_MODULE = stripe_checkout.settings.dev
FAIL_INVALID_TEMPLATE_VARS = True
[testenv] [testenv]
deps = deps =
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment