0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-07 06:05:34 +00:00

Invoices to email.

This commit is contained in:
Pēteris Caune 2018-01-09 16:45:38 +02:00
parent 01c3a13922
commit 39bc12e351
14 changed files with 188 additions and 30 deletions

View file

@ -58,3 +58,15 @@ def verify_email(to, ctx):
def report(to, ctx): def report(to, ctx):
send("report", to, ctx) send("report", to, ctx)
def invoice(to, ctx, filename, pdf_data):
ctx["SITE_ROOT"] = settings.SITE_ROOT
subject = render('emails/invoice-subject.html', ctx).strip()
text = render('emails/invoice-body-text.html', ctx)
html = render('emails/invoice-body-html.html', ctx)
msg = EmailMultiAlternatives(subject, text, to=(to, ))
msg.attach_alternative(html, "text/html")
msg.attach(filename, pdf_data, "application/pdf")
msg.send()

View file

@ -1,5 +0,0 @@
from django import forms
class BillToForm(forms.Form):
bill_to = forms.CharField(max_length=500, required=False)

View file

@ -93,8 +93,6 @@ class PdfInvoice(Canvas):
self.text(s.strip()) self.text(s.strip())
self.linefeed() self.linefeed()
self.text("If you have a credit card on file it will be "
"automatically charged within 24 hours.", align="center")
self.showPage() self.showPage()
self.save() self.save()

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-01-09 12:52
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payments', '0003_subscription_address_id'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='send_invoices',
field=models.BooleanField(default=True),
),
]

View file

@ -1,6 +1,7 @@
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.template.loader import render_to_string
if settings.USE_PAYMENTS: if settings.USE_PAYMENTS:
import braintree import braintree
@ -29,6 +30,7 @@ class Subscription(models.Model):
subscription_id = models.CharField(max_length=10, blank=True) subscription_id = models.CharField(max_length=10, blank=True)
plan_id = models.CharField(max_length=10, blank=True) plan_id = models.CharField(max_length=10, blank=True)
address_id = models.CharField(max_length=2, blank=True) address_id = models.CharField(max_length=2, blank=True)
send_invoices = models.BooleanField(default=True)
objects = SubscriptionManager() objects = SubscriptionManager()
@ -176,6 +178,13 @@ class Subscription(models.Model):
return self._address return self._address
def flattened_address(self):
if self.address_id:
ctx = {"a": self.address}
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"):

View file

@ -0,0 +1,16 @@
from mock import patch
from hc.payments.models import Subscription
from hc.test import BaseTestCase
class SetPlanTestCase(BaseTestCase):
@patch("hc.payments.models.braintree")
def test_it_saves_send_invoices_flag(self, mock):
self.client.login(username="alice@example.org", password="password")
form = {"save_send_invoices": True}
self.client.post("/accounts/profile/billing/", form)
sub = Subscription.objects.get()
self.assertFalse(sub.send_invoices)

View file

@ -55,11 +55,12 @@ class PdfInvoiceTestCase(BaseTestCase):
@skipIf(reportlab is None, "reportlab not installed") @skipIf(reportlab is None, "reportlab not installed")
@patch("hc.payments.models.braintree") @patch("hc.payments.models.braintree")
def test_it_shows_company_data(self, mock_braintree): def test_it_shows_company_data(self, mock):
self.profile.bill_to = "Alice and Partners" self.sub.address_id = "aa"
self.profile.save() self.sub.save()
mock_braintree.Transaction.find.return_value = self.tx 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") self.client.login(username="alice@example.org", password="password")
r = self.client.get("/invoice/pdf/abc123/") r = self.client.get("/invoice/pdf/abc123/")

View file

@ -35,4 +35,5 @@ urlpatterns = [
views.get_client_token, views.get_client_token,
name="hc-get-client-token"), name="hc-get-client-token"),
url(r'^pricing/charge/$', views.charge_webhook),
] ]

View file

@ -4,9 +4,11 @@ from django.http import (HttpResponseBadRequest, HttpResponseForbidden,
JsonResponse, HttpResponse) JsonResponse, HttpResponse)
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
import six
from hc.api.models import Check from hc.api.models import Check
from hc.lib import emails
from hc.payments.invoices import PdfInvoice from hc.payments.invoices import PdfInvoice
from hc.payments.models import Subscription from hc.payments.models import Subscription
@ -37,15 +39,37 @@ def billing(request):
# subscription object is not created just by viewing a page. # subscription object is not created just by viewing a page.
sub = Subscription.objects.filter(user_id=request.user.id).first() sub = Subscription.objects.filter(user_id=request.user.id).first()
send_invoices_status = "default"
if request.method == "POST":
if "save_send_invoices" in request.POST:
sub = Subscription.objects.for_user(request.user)
sub.send_invoices = "send_invoices" in request.POST
sub.save()
send_invoices_status = "success"
ctx = { ctx = {
"page": "billing", "page": "billing",
"profile": request.profile, "profile": request.profile,
"sub": sub, "sub": sub,
"num_checks": Check.objects.filter(user=request.user).count(), "num_checks": Check.objects.filter(user=request.user).count(),
"team_size": request.profile.member_set.count() + 1, "team_size": request.profile.member_set.count() + 1,
"team_max": request.profile.team_limit + 1 "team_max": request.profile.team_limit + 1,
"send_invoices_status": send_invoices_status,
"set_plan_status": "default",
"address_status": "default",
"payment_method_status": "default"
} }
if "set_plan_status" in request.session:
ctx["set_plan_status"] = request.session.pop("set_plan_status")
if "address_status" in request.session:
ctx["address_status"] = request.session.pop("address_status")
if "payment_method_status" in request.session:
ctx["payment_method_status"] = \
request.session.pop("payment_method_status")
return render(request, "accounts/billing.html", ctx) return render(request, "accounts/billing.html", ctx)
@ -105,7 +129,7 @@ def set_plan(request):
profile.sms_sent = 0 profile.sms_sent = 0
profile.save() profile.save()
request.session["first_charge"] = True request.session["set_plan_status"] = "success"
return redirect("hc-billing") return redirect("hc-billing")
@ -117,6 +141,7 @@ def address(request):
if error: if error:
return log_and_bail(request, error) return log_and_bail(request, error)
request.session["address_status"] = "success"
return redirect("hc-billing") return redirect("hc-billing")
ctx = {"a": sub.address} ctx = {"a": sub.address}
@ -136,6 +161,7 @@ def payment_method(request):
if error: if error:
return log_and_bail(request, error) return log_and_bail(request, error)
request.session["payment_method_status"] = "success"
return redirect("hc-billing") return redirect("hc-billing")
ctx = { ctx = {
@ -167,15 +193,29 @@ def pdf_invoice(request, transaction_id):
response = HttpResponse(content_type='application/pdf') response = HttpResponse(content_type='application/pdf')
filename = "MS-HC-%s.pdf" % transaction.id.upper() filename = "MS-HC-%s.pdf" % transaction.id.upper()
response['Content-Disposition'] = 'attachment; filename="%s"' % filename response['Content-Disposition'] = 'attachment; filename="%s"' % filename
PdfInvoice(response).render(transaction, sub.flattened_address())
bill_to = []
if sub.address_id:
ctx = {"a": sub.address}
bill_to = render_to_string("payments/address_plain.html", ctx)
elif request.user.profile.bill_to:
bill_to = request.user.profile.bill_to
else:
bill_to = request.user.email
PdfInvoice(response).render(transaction, bill_to)
return response return response
@csrf_exempt
@require_POST
def charge_webhook(request):
sig = str(request.POST["bt_signature"])
payload = str(request.POST["bt_payload"])
import braintree
doc = braintree.WebhookNotification.parse(sig, payload)
if doc.kind != "subscription_charged_successfully":
return HttpResponseBadRequest()
sub = Subscription.objects.get(subscription_id=doc.subscription.id)
if sub.send_invoices:
transaction = doc.subscription.transactions[0]
filename = "MS-HC-%s.pdf" % transaction.id.upper()
sink = six.BytesIO()
PdfInvoice(sink).render(transaction, sub.flattened_address())
ctx = {"tx": transaction}
emails.invoice(sub.user.email, ctx, filename, sink.getvalue())
return HttpResponse()

View file

@ -35,7 +35,7 @@
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
<div class="panel panel-default"> <div class="panel panel-{{ set_plan_status }}">
<div class="panel-body settings-block"> <div class="panel-body settings-block">
<h2>Billing Plan</h2> <h2>Billing Plan</h2>
@ -96,9 +96,14 @@
Change Billing Plan Change Billing Plan
</button> </button>
</div> </div>
{% if set_plan_status == "success" %}
<div class="panel-footer">
Your billing plan has been updated!
</div>
{% endif %}
</div> </div>
<div class="panel panel-default"> <div class="panel panel-{{ payment_method_status }}">
<div class="panel-body settings-block"> <div class="panel-body settings-block">
<h2>Payment Method</h2> <h2>Payment Method</h2>
{% if sub.payment_method_token %} {% if sub.payment_method_token %}
@ -113,10 +118,15 @@
class="btn btn-default pull-right"> class="btn btn-default pull-right">
Change Payment Method</button> Change Payment Method</button>
</div> </div>
{% if payment_method_status == "success" %}
<div class="panel-footer">
Your payment method has been updated!
</div>
{% endif %}
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="panel panel-default"> <div class="panel panel-{{ address_status }}">
<div class="panel-body settings-block"> <div class="panel-body settings-block">
<h2>Billing Address</h2> <h2>Billing Address</h2>
@ -137,7 +147,7 @@
Change Billing Address Change Billing Address
</button> </button>
</div> </div>
{% if status == "info" %} {% if address_status == "success" %}
<div class="panel-footer"> <div class="panel-footer">
Your billing address has been updated! Your billing address has been updated!
</div> </div>
@ -146,6 +156,37 @@
</div> </div>
</div> </div>
<div class="panel panel-{{ send_invoices_status }}">
<div class="panel-body settings-block">
<form method="post">
{% csrf_token %}
<h2>Invoices to Email</h2>
<label class="checkbox-container">
<input
name="send_invoices"
type="checkbox"
{% if sub.send_invoices %}checked{% endif %}>
<span class="checkmark"></span>
Send the invoice to {{ request.user.email }}
each time my payment method is successfully charged.
</label>
<button
type="submit"
name="save_send_invoices"
class="btn btn-default pull-right">
Save Changes
</button>
</form>
</div>
{% if send_invoices_status == "success" %}
<div class="panel-footer">
Your preferences have been updated!
</div>
{% endif %}
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-body settings-block"> <div class="panel-body settings-block">
<h2>Billing History</h2> <h2>Billing History</h2>

View file

@ -57,7 +57,7 @@
</div> </div>
</div> </div>
<div class="panel panel-{{ api_status }}"> <div class="panel panel-{{ send_invoices_status }}">
<div class="panel-body settings-block"> <div class="panel-body settings-block">
<h2>API Access</h2> <h2>API Access</h2>
{% if profile.api_key %} {% if profile.api_key %}

View file

@ -0,0 +1,15 @@
{% extends "emails/base.html" %}
{% load hc_extras %}
{% block content %}
Hello,<br />
Here's your invoice from {% site_name %} for
{{ tx.subscription_details.billing_period_start_date }} - {{ tx.subscription_details.billing_period_end_date }}
.</p>
{% endblock %}
{% block content_more %}
Thanks,<br />
The {% escaped_site_name %} Team
{% endblock %}

View file

@ -0,0 +1,8 @@
{% load hc_extras %}
Hello,
Here's your invoice from {% site_name %}.
--
Regards,
{% site_name %}

View file

@ -0,0 +1,2 @@
{% load hc_extras %}
Invoice from {% site_name %}