From df2825c91a5fbc5c1928babc6238b21a53da5077 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Tue, 10 Oct 2023 16:42:52 -0700 Subject: [PATCH] Add discounts to the dashboard The discounts come in the form of Stripe coupons. The description can be edited in Stripe. Currently, we have just a 10% and 15% coupon based on the total campaign size. They appear in: - The create/edit flight screens (for staff). The discounts are not automatically applied yet. When a flight is renewed, the discount is renewed by default. - They appear in the memo section of invoices - They are displayed in the flight metadata to advertisers --- adserver/admin.py | 28 ++++++------- adserver/forms.py | 12 +++++- adserver/migrations/0088_linked_discounts.py | 39 +++++++++++++++++++ adserver/models.py | 7 ++++ .../adserver/includes/flight-metadata.html | 4 ++ 5 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 adserver/migrations/0088_linked_discounts.py diff --git a/adserver/admin.py b/adserver/admin.py index 56e9e35e..a03410f4 100644 --- a/adserver/admin.py +++ b/adserver/admin.py @@ -630,7 +630,10 @@ def action_create_draft_invoice(self, request, queryset): ) return + # Any flight with the discount will result in the discount being in the memo + invoice_discount = None total_cost = 0 # In US cents + for flight in flights: message_components = ["Advertising", flight.name] unit_amount = 0 @@ -641,16 +644,13 @@ def action_create_draft_invoice(self, request, queryset): unit_amount = flight.cpc * 100 # Convert to US cents quantity = flight.sold_clicks elif flight.cpm: - priced_by_view = bool(flight.sold_impressions % 1000) - if priced_by_view: - print(unit_amount, quantity) - unit_amount = flight.cpm / 10 # Convert to US cents - message_components.append("${:.2f} CPM".format(flight.cpm)) - quantity = flight.sold_impressions - else: - unit_amount = flight.cpm * 100 # Convert to US cents - message_components.append("per 1k impressions") - quantity = flight.sold_impressions // 1000 + # Convert CPM to US cents (eg. $4.25 CPM -> 0.425 cents) + unit_amount = flight.cpm / 10 + message_components.append("${:.2f} CPM".format(flight.cpm)) + quantity = flight.sold_impressions + + if flight.discount: + invoice_discount = flight.discount total_cost += unit_amount * quantity @@ -670,14 +670,14 @@ def action_create_draft_invoice(self, request, queryset): ) # https://stripe.com/docs/api/invoices/create + description = "Thanks for your business!" + if invoice_discount: + description = f"Includes {invoice_discount} discount. " + description inv = stripe.Invoice.create( customer=advertiser.djstripe_customer.id, auto_advance=False, # Draft invoice collection_method="send_invoice", - # Check just under 3k just in case there's a rounding issue - description="Includes 10% volume discount" - if total_cost >= 290_000 - else "Thanks for your business!", + description=description, custom_fields=[ {"name": "Advertiser", "value": advertiser.slug[:30]}, { diff --git a/adserver/forms.py b/adserver/forms.py index 878edd48..e91c4454 100644 --- a/adserver/forms.py +++ b/adserver/forms.py @@ -103,6 +103,7 @@ class Meta: "targeting_parameters", "traffic_fill", "traffic_cap", + "discount", ) @@ -238,7 +239,8 @@ def __init__(self, *args, **kwargs): ), css_class="my-3", ), - # NOTE: remove this when this form is made for non-staff users + # NOTE: remove these when this form is made for non-staff users + Field("discount"), Field("priority_multiplier"), Fieldset( _("Flight targeting"), @@ -345,6 +347,7 @@ class Meta: "sold_clicks", "cpm", "sold_impressions", + "discount", "priority_multiplier", ) widgets = { @@ -536,7 +539,12 @@ def save(self, commit=True): instance = super().save(commit) # Copy flight fields that aren't part of the form - for field in ("targeting_parameters", "priority_multiplier", "traffic_cap"): + for field in ( + "targeting_parameters", + "priority_multiplier", + "traffic_cap", + "discount", + ): setattr(instance, field, getattr(self.old_flight, field)) instance.save() diff --git a/adserver/migrations/0088_linked_discounts.py b/adserver/migrations/0088_linked_discounts.py new file mode 100644 index 00000000..16f432f2 --- /dev/null +++ b/adserver/migrations/0088_linked_discounts.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.4 on 2023-10-10 22:08 +import django.db.models.deletion +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + dependencies = [ + ("djstripe", "0012_2_8"), + ("adserver", "0087_publisher_allow_multiple_placements"), + ] + + operations = [ + migrations.AddField( + model_name="flight", + name="discount", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="djstripe.coupon", + ), + ), + migrations.AddField( + model_name="historicalflight", + name="discount", + field=models.ForeignKey( + blank=True, + db_constraint=False, + default=None, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="djstripe.coupon", + ), + ), + ] diff --git a/adserver/models.py b/adserver/models.py index 8efcb442..b84fbabb 100644 --- a/adserver/models.py +++ b/adserver/models.py @@ -859,6 +859,13 @@ class Flight(TimeStampedModel, IndestructibleModel): verbose_name=_("Stripe invoices"), blank=True, ) + discount = models.ForeignKey( + djstripe_models.Coupon, + on_delete=models.SET_NULL, + blank=True, + null=True, + default=None, + ) history = HistoricalRecords() diff --git a/adserver/templates/adserver/includes/flight-metadata.html b/adserver/templates/adserver/includes/flight-metadata.html index ce52b41d..1a6c1f5f 100644 --- a/adserver/templates/adserver/includes/flight-metadata.html +++ b/adserver/templates/adserver/includes/flight-metadata.html @@ -38,6 +38,10 @@
{% trans 'Cost per 1,000 impressions (CPM)' %}
${{ flight.cpm|floatformat:2 }}
{% endif %} + {% if flight.discount %} +
{% trans 'Discount' %}
+
{{ flight.discount }}
+ {% endif %} {% if flight.start_date %}
{% trans 'Estimated start date' %}
{{ flight.start_date }}