forked from GithubBackups/healthchecks
Remove PDF invoice generation bits - these are unlikely to ever be useful in the open source version.
This commit is contained in:
parent
34925f2cdf
commit
accdfb637b
@ -1,98 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
|
|
||||||
try:
|
|
||||||
from reportlab.lib.pagesizes import A4
|
|
||||||
from reportlab.lib.units import inch
|
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
|
||||||
|
|
||||||
W, H = A4
|
|
||||||
except ImportError:
|
|
||||||
# Don't crash if reportlab is not installed.
|
|
||||||
Canvas = object
|
|
||||||
|
|
||||||
|
|
||||||
def f(dt):
|
|
||||||
return dt.strftime("%b. %-d, %Y")
|
|
||||||
|
|
||||||
|
|
||||||
class PdfInvoice(Canvas):
|
|
||||||
def __init__(self, fileobj):
|
|
||||||
Canvas.__init__(self, fileobj, pagesize=A4, pageCompression=0)
|
|
||||||
self.head_y = H - inch * 0.5
|
|
||||||
|
|
||||||
def linefeed(self):
|
|
||||||
self.head_y -= inch / 8
|
|
||||||
|
|
||||||
def text(self, s, align="left", size=10, bold=False):
|
|
||||||
self.head_y -= inch / 24
|
|
||||||
self.linefeed()
|
|
||||||
self.setFont("Helvetica-Bold" if bold else "Helvetica", size)
|
|
||||||
|
|
||||||
if align == "left":
|
|
||||||
self.drawString(inch * 0.5, self.head_y, s)
|
|
||||||
elif align == "right":
|
|
||||||
self.drawRightString(W - inch * 0.5, self.head_y, s)
|
|
||||||
elif align == "center":
|
|
||||||
self.drawCentredString(W / 2, self.head_y, s)
|
|
||||||
|
|
||||||
self.head_y -= inch / 24
|
|
||||||
|
|
||||||
def hr(self):
|
|
||||||
self.setLineWidth(inch / 72 / 8)
|
|
||||||
self.line(inch * 0.5, self.head_y, W - inch * 0.5, self.head_y)
|
|
||||||
|
|
||||||
def row(self, items, align="left", bold=False, size=10):
|
|
||||||
self.head_y -= inch / 8
|
|
||||||
self.linefeed()
|
|
||||||
|
|
||||||
self.setFont("Helvetica-Bold" if bold else "Helvetica", size)
|
|
||||||
|
|
||||||
self.drawString(inch * 0.5, self.head_y, items[0])
|
|
||||||
self.drawString(inch * 3.5, self.head_y, items[1])
|
|
||||||
self.drawString(inch * 5.5, self.head_y, items[2])
|
|
||||||
self.drawRightString(W - inch * 0.5, self.head_y, items[3])
|
|
||||||
|
|
||||||
self.head_y -= inch / 8
|
|
||||||
|
|
||||||
def render(self, tx, bill_to):
|
|
||||||
invoice_id = "MS-HC-%s" % tx.id.upper()
|
|
||||||
self.setTitle(invoice_id)
|
|
||||||
|
|
||||||
self.text("SIA Monkey See Monkey Do", size=16)
|
|
||||||
self.linefeed()
|
|
||||||
self.text("Gaujas iela 4-2")
|
|
||||||
self.text("Valmiera, LV-4201, Latvia")
|
|
||||||
self.text("VAT: LV44103100701")
|
|
||||||
self.linefeed()
|
|
||||||
|
|
||||||
created = f(tx.created_at)
|
|
||||||
self.text("Date Issued: %s" % created, align="right")
|
|
||||||
self.text("Invoice Id: %s" % invoice_id, align="right")
|
|
||||||
self.linefeed()
|
|
||||||
|
|
||||||
self.hr()
|
|
||||||
self.row(["Description", "Start", "End", tx.currency_iso_code], bold=True)
|
|
||||||
self.hr()
|
|
||||||
start = f(tx.subscription_details.billing_period_start_date)
|
|
||||||
end = f(tx.subscription_details.billing_period_end_date)
|
|
||||||
if tx.currency_iso_code == "USD":
|
|
||||||
amount = "$%s" % tx.amount
|
|
||||||
elif tx.currency_iso_code == "EUR":
|
|
||||||
amount = "€%s" % tx.amount
|
|
||||||
else:
|
|
||||||
amount = "%s %s" % (tx.currency_iso_code, tx.amount)
|
|
||||||
|
|
||||||
self.row(["healthchecks.io paid plan", start, end, amount])
|
|
||||||
|
|
||||||
self.hr()
|
|
||||||
self.row(["", "", "", "Total: %s" % amount], bold=True)
|
|
||||||
self.linefeed()
|
|
||||||
|
|
||||||
self.text("Bill to:", bold=True)
|
|
||||||
for s in bill_to.split("\n"):
|
|
||||||
self.text(s.strip())
|
|
||||||
|
|
||||||
self.linefeed()
|
|
||||||
|
|
||||||
self.showPage()
|
|
||||||
self.save()
|
|
@ -173,13 +173,6 @@ class Subscription(models.Model):
|
|||||||
|
|
||||||
return self._address
|
return self._address
|
||||||
|
|
||||||
def flattened_address(self):
|
|
||||||
if self.address_id:
|
|
||||||
ctx = {"a": self.address, "email": self.user.email}
|
|
||||||
return render_to_string("payments/address_plain.html", ctx)
|
|
||||||
else:
|
|
||||||
return self.user.email
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def transactions(self):
|
def transactions(self):
|
||||||
if not hasattr(self, "_tx"):
|
if not hasattr(self, "_tx"):
|
||||||
|
@ -23,4 +23,4 @@ class BillingHistoryTestCase(BaseTestCase):
|
|||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get("/accounts/profile/billing/history/")
|
r = self.client.get("/accounts/profile/billing/history/")
|
||||||
self.assertContains(r, "123")
|
self.assertContains(r, "123")
|
||||||
self.assertContains(r, "def456")
|
self.assertContains(r, "456")
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
from mock import Mock, patch
|
|
||||||
from unittest import skipIf
|
|
||||||
|
|
||||||
from django.core import mail
|
|
||||||
from django.utils.timezone import now
|
|
||||||
from hc.payments.models import Subscription
|
|
||||||
from hc.test import BaseTestCase
|
|
||||||
|
|
||||||
try:
|
|
||||||
import reportlab
|
|
||||||
except ImportError:
|
|
||||||
reportlab = None
|
|
||||||
|
|
||||||
|
|
||||||
class ChargeWebhookTestCase(BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(ChargeWebhookTestCase, self).setUp()
|
|
||||||
self.sub = Subscription(user=self.alice)
|
|
||||||
self.sub.subscription_id = "test-id"
|
|
||||||
self.sub.customer_id = "test-customer-id"
|
|
||||||
self.sub.send_invoices = True
|
|
||||||
self.sub.save()
|
|
||||||
|
|
||||||
self.tx = Mock()
|
|
||||||
self.tx.id = "abc123"
|
|
||||||
self.tx.customer_details.id = "test-customer-id"
|
|
||||||
self.tx.created_at = now()
|
|
||||||
self.tx.currency_iso_code = "USD"
|
|
||||||
self.tx.amount = 5
|
|
||||||
self.tx.subscription_details.billing_period_start_date = now()
|
|
||||||
self.tx.subscription_details.billing_period_end_date = now()
|
|
||||||
|
|
||||||
@skipIf(reportlab is None, "reportlab not installed")
|
|
||||||
@patch("hc.payments.views.Subscription.objects.by_braintree_webhook")
|
|
||||||
def test_it_works(self, mock_getter):
|
|
||||||
mock_getter.return_value = self.sub, self.tx
|
|
||||||
|
|
||||||
r = self.client.post("/pricing/charge/")
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
|
|
||||||
# See if email was sent
|
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
|
||||||
msg = mail.outbox[0]
|
|
||||||
self.assertEqual(msg.subject, "Invoice from Mychecks")
|
|
||||||
self.assertEqual(msg.to, ["alice@example.org"])
|
|
||||||
self.assertEqual(msg.attachments[0][0], "MS-HC-ABC123.pdf")
|
|
||||||
|
|
||||||
@patch("hc.payments.views.Subscription.objects.by_braintree_webhook")
|
|
||||||
def test_it_obeys_send_invoices_flag(self, mock_getter):
|
|
||||||
mock_getter.return_value = self.sub, self.tx
|
|
||||||
|
|
||||||
self.sub.send_invoices = False
|
|
||||||
self.sub.save()
|
|
||||||
|
|
||||||
r = self.client.post("/pricing/charge/")
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
|
|
||||||
# It should not send the email
|
|
||||||
self.assertEqual(len(mail.outbox), 0)
|
|
||||||
|
|
||||||
@skipIf(reportlab is None, "reportlab not installed")
|
|
||||||
@patch("hc.payments.views.Subscription.objects.by_braintree_webhook")
|
|
||||||
def test_it_uses_invoice_email(self, mock_getter):
|
|
||||||
mock_getter.return_value = self.sub, self.tx
|
|
||||||
|
|
||||||
self.sub.invoice_email = "alices_accountant@example.org"
|
|
||||||
self.sub.save()
|
|
||||||
|
|
||||||
r = self.client.post("/pricing/charge/")
|
|
||||||
self.assertEqual(r.status_code, 200)
|
|
||||||
|
|
||||||
# See if the email was sent to Alice's accountant:
|
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
|
||||||
self.assertEqual(mail.outbox[0].to, ["alices_accountant@example.org"])
|
|
@ -1,65 +0,0 @@
|
|||||||
from mock import Mock, patch
|
|
||||||
from unittest import skipIf
|
|
||||||
|
|
||||||
from django.utils.timezone import now
|
|
||||||
from hc.payments.models import Subscription
|
|
||||||
from hc.test import BaseTestCase
|
|
||||||
|
|
||||||
try:
|
|
||||||
import reportlab
|
|
||||||
except ImportError:
|
|
||||||
reportlab = None
|
|
||||||
|
|
||||||
|
|
||||||
class PdfInvoiceTestCase(BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(PdfInvoiceTestCase, self).setUp()
|
|
||||||
self.sub = Subscription(user=self.alice)
|
|
||||||
self.sub.subscription_id = "test-id"
|
|
||||||
self.sub.customer_id = "test-customer-id"
|
|
||||||
self.sub.save()
|
|
||||||
|
|
||||||
self.tx = Mock()
|
|
||||||
self.tx.id = "abc123"
|
|
||||||
self.tx.customer_details.id = "test-customer-id"
|
|
||||||
self.tx.created_at = now()
|
|
||||||
self.tx.currency_iso_code = "USD"
|
|
||||||
self.tx.amount = 5
|
|
||||||
self.tx.subscription_details.billing_period_start_date = now()
|
|
||||||
self.tx.subscription_details.billing_period_end_date = now()
|
|
||||||
|
|
||||||
@skipIf(reportlab is None, "reportlab not installed")
|
|
||||||
@patch("hc.payments.models.braintree")
|
|
||||||
def test_it_works(self, mock_braintree):
|
|
||||||
mock_braintree.Transaction.find.return_value = self.tx
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
r = self.client.get("/invoice/pdf/abc123/")
|
|
||||||
self.assertTrue(b"ABC123" in r.content)
|
|
||||||
self.assertTrue(b"alice@example.org" in r.content)
|
|
||||||
|
|
||||||
@patch("hc.payments.models.braintree")
|
|
||||||
def test_it_checks_customer_id(self, mock_braintree):
|
|
||||||
|
|
||||||
tx = Mock()
|
|
||||||
tx.id = "abc123"
|
|
||||||
tx.customer_details.id = "test-another-customer-id"
|
|
||||||
tx.created_at = None
|
|
||||||
mock_braintree.Transaction.find.return_value = tx
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
r = self.client.get("/invoice/pdf/abc123/")
|
|
||||||
self.assertEqual(r.status_code, 403)
|
|
||||||
|
|
||||||
@skipIf(reportlab is None, "reportlab not installed")
|
|
||||||
@patch("hc.payments.models.braintree")
|
|
||||||
def test_it_shows_company_data(self, mock):
|
|
||||||
self.sub.address_id = "aa"
|
|
||||||
self.sub.save()
|
|
||||||
|
|
||||||
mock.Transaction.find.return_value = self.tx
|
|
||||||
mock.Address.find.return_value = {"company": "Alice and Partners"}
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
r = self.client.get("/invoice/pdf/abc123/")
|
|
||||||
self.assertTrue(b"Alice and Partners" in r.content)
|
|
@ -16,10 +16,6 @@ urlpatterns = [
|
|||||||
views.payment_method,
|
views.payment_method,
|
||||||
name="hc-payment-method",
|
name="hc-payment-method",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"invoice/pdf/<slug:transaction_id>/", views.pdf_invoice, name="hc-invoice-pdf"
|
|
||||||
),
|
|
||||||
path("pricing/update/", views.update, name="hc-update-subscription"),
|
path("pricing/update/", views.update, name="hc-update-subscription"),
|
||||||
path("pricing/token/", views.token, name="hc-get-client-token"),
|
path("pricing/token/", views.token, name="hc-get-client-token"),
|
||||||
path("pricing/charge/", views.charge_webhook),
|
|
||||||
]
|
]
|
||||||
|
@ -1,21 +1,11 @@
|
|||||||
from io import BytesIO
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import (
|
from django.http import HttpResponseBadRequest, JsonResponse
|
||||||
HttpResponseBadRequest,
|
|
||||||
HttpResponseForbidden,
|
|
||||||
JsonResponse,
|
|
||||||
HttpResponse,
|
|
||||||
)
|
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
from hc.api.models import Check
|
from hc.api.models import Check
|
||||||
from hc.lib import emails
|
|
||||||
from hc.payments.forms import InvoiceEmailingForm
|
from hc.payments.forms import InvoiceEmailingForm
|
||||||
from hc.payments.invoices import PdfInvoice
|
|
||||||
from hc.payments.models import Subscription
|
from hc.payments.models import Subscription
|
||||||
|
|
||||||
|
|
||||||
@ -184,39 +174,3 @@ def billing_history(request):
|
|||||||
|
|
||||||
ctx = {"transactions": transactions}
|
ctx = {"transactions": transactions}
|
||||||
return render(request, "payments/billing_history.html", ctx)
|
return render(request, "payments/billing_history.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def pdf_invoice(request, transaction_id):
|
|
||||||
sub, tx = Subscription.objects.by_transaction(transaction_id)
|
|
||||||
|
|
||||||
# Does this transaction belong to a customer we know about?
|
|
||||||
if sub is None or tx is None:
|
|
||||||
return HttpResponseForbidden()
|
|
||||||
|
|
||||||
# Does the transaction's customer match the currently logged in user?
|
|
||||||
if sub.user != request.user and not request.user.is_superuser:
|
|
||||||
return HttpResponseForbidden()
|
|
||||||
|
|
||||||
response = HttpResponse(content_type="application/pdf")
|
|
||||||
filename = "MS-HC-%s.pdf" % tx.id.upper()
|
|
||||||
response["Content-Disposition"] = 'attachment; filename="%s"' % filename
|
|
||||||
PdfInvoice(response).render(tx, sub.flattened_address())
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
@require_POST
|
|
||||||
def charge_webhook(request):
|
|
||||||
sub, tx = Subscription.objects.by_braintree_webhook(request)
|
|
||||||
if sub.send_invoices:
|
|
||||||
filename = "MS-HC-%s.pdf" % tx.id.upper()
|
|
||||||
|
|
||||||
sink = BytesIO()
|
|
||||||
PdfInvoice(sink).render(tx, sub.flattened_address())
|
|
||||||
ctx = {"tx": tx}
|
|
||||||
|
|
||||||
recipient = sub.invoice_email or sub.user.email
|
|
||||||
emails.invoice(recipient, ctx, filename, sink.getvalue())
|
|
||||||
|
|
||||||
return HttpResponse()
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
{% if a.company %}{{ a.company }}
|
|
||||||
{% else %}{{ email }}
|
|
||||||
{% endif %}{% if a.extended_address %}VAT: {{ a.extended_address }}
|
|
||||||
{% endif %}{% if a.street_address %}{{ a.street_address }}
|
|
||||||
{% endif %}{% if a.locality %}{{ a.locality }}
|
|
||||||
{% endif %}{% if a.region %}{{ a.region }}
|
|
||||||
{% endif %}{% if a.country_name %}{{ a.country_name }}
|
|
||||||
{% endif %}{% if a.postal_code %}{{ a.postal_code }}{% endif %}
|
|
@ -6,7 +6,6 @@
|
|||||||
<th>Amount</th>
|
<th>Amount</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% for tx in transactions %}
|
{% for tx in transactions %}
|
||||||
<tr {% if tx.type == "credit" %}class="text-muted"{% endif %}>
|
<tr {% if tx.type == "credit" %}class="text-muted"{% endif %}>
|
||||||
@ -29,12 +28,6 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{ tx.type|capfirst }}</td>
|
<td>{{ tx.type|capfirst }}</td>
|
||||||
<td><code>{{ tx.status }}</code></td>
|
<td><code>{{ tx.status }}</code></td>
|
||||||
<td>
|
|
||||||
{% if tx.type == "credit" %}
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'hc-invoice-pdf' tx.id %}">PDF Invoice</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor%}
|
{% endfor%}
|
||||||
</table>
|
</table>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user