Browse Source

Invoices to email.

pull/149/head
Pēteris Caune 7 years ago
parent
commit
39bc12e351
14 changed files with 187 additions and 29 deletions
  1. +12
    -0
      hc/lib/emails.py
  2. +0
    -5
      hc/payments/forms.py
  3. +0
    -2
      hc/payments/invoices.py
  4. +20
    -0
      hc/payments/migrations/0004_subscription_send_invoices.py
  5. +9
    -0
      hc/payments/models.py
  6. +16
    -0
      hc/payments/tests/test_billing.py
  7. +5
    -4
      hc/payments/tests/test_pdf_invoice.py
  8. +1
    -0
      hc/payments/urls.py
  9. +53
    -13
      hc/payments/views.py
  10. +45
    -4
      templates/accounts/billing.html
  11. +1
    -1
      templates/accounts/profile.html
  12. +15
    -0
      templates/emails/invoice-body-html.html
  13. +8
    -0
      templates/emails/invoice-body-text.html
  14. +2
    -0
      templates/emails/invoice-subject.html

+ 12
- 0
hc/lib/emails.py View File

@ -58,3 +58,15 @@ def verify_email(to, ctx):
def 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()

+ 0
- 5
hc/payments/forms.py View File

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

+ 0
- 2
hc/payments/invoices.py View File

@ -93,8 +93,6 @@ class PdfInvoice(Canvas):
self.text(s.strip())
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.save()

+ 20
- 0
hc/payments/migrations/0004_subscription_send_invoices.py 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),
),
]

+ 9
- 0
hc/payments/models.py View File

@ -1,6 +1,7 @@
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.template.loader import render_to_string
if settings.USE_PAYMENTS:
import braintree
@ -29,6 +30,7 @@ class Subscription(models.Model):
subscription_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)
send_invoices = models.BooleanField(default=True)
objects = SubscriptionManager()
@ -176,6 +178,13 @@ class Subscription(models.Model):
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
def transactions(self):
if not hasattr(self, "_tx"):


+ 16
- 0
hc/payments/tests/test_billing.py 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="[email protected]", password="password")
form = {"save_send_invoices": True}
self.client.post("/accounts/profile/billing/", form)
sub = Subscription.objects.get()
self.assertFalse(sub.send_invoices)

+ 5
- 4
hc/payments/tests/test_pdf_invoice.py View File

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


+ 1
- 0
hc/payments/urls.py View File

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

+ 53
- 13
hc/payments/views.py View File

@ -4,9 +4,11 @@ from django.http import (HttpResponseBadRequest, HttpResponseForbidden,
JsonResponse, HttpResponse)
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
import six
from hc.api.models import Check
from hc.lib import emails
from hc.payments.invoices import PdfInvoice
from hc.payments.models import Subscription
@ -37,15 +39,37 @@ def billing(request):
# subscription object is not created just by viewing a page.
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 = {
"page": "billing",
"profile": request.profile,
"sub": sub,
"num_checks": Check.objects.filter(user=request.user).count(),
"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)
@ -105,7 +129,7 @@ def set_plan(request):
profile.sms_sent = 0
profile.save()
request.session["first_charge"] = True
request.session["set_plan_status"] = "success"
return redirect("hc-billing")
@ -117,6 +141,7 @@ def address(request):
if error:
return log_and_bail(request, error)
request.session["address_status"] = "success"
return redirect("hc-billing")
ctx = {"a": sub.address}
@ -136,6 +161,7 @@ def payment_method(request):
if error:
return log_and_bail(request, error)
request.session["payment_method_status"] = "success"
return redirect("hc-billing")
ctx = {
@ -167,15 +193,29 @@ def pdf_invoice(request, transaction_id):
response = HttpResponse(content_type='application/pdf')
filename = "MS-HC-%s.pdf" % transaction.id.upper()
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
PdfInvoice(response).render(transaction, sub.flattened_address())
return response
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
@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()

+ 45
- 4
templates/accounts/billing.html View File

@ -35,7 +35,7 @@
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel panel-{{ set_plan_status }}">
<div class="panel-body settings-block">
<h2>Billing Plan</h2>
@ -96,9 +96,14 @@
Change Billing Plan
</button>
</div>
{% if set_plan_status == "success" %}
<div class="panel-footer">
Your billing plan has been updated!
</div>
{% endif %}
</div>
<div class="panel panel-default">
<div class="panel panel-{{ payment_method_status }}">
<div class="panel-body settings-block">
<h2>Payment Method</h2>
{% if sub.payment_method_token %}
@ -113,10 +118,15 @@
class="btn btn-default pull-right">
Change Payment Method</button>
</div>
{% if payment_method_status == "success" %}
<div class="panel-footer">
Your payment method has been updated!
</div>
{% endif %}
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel panel-{{ address_status }}">
<div class="panel-body settings-block">
<h2>Billing Address</h2>
@ -137,7 +147,7 @@
Change Billing Address
</button>
</div>
{% if status == "info" %}
{% if address_status == "success" %}
<div class="panel-footer">
Your billing address has been updated!
</div>
@ -146,6 +156,37 @@
</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-body settings-block">
<h2>Billing History</h2>


+ 1
- 1
templates/accounts/profile.html View File

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


+ 15
- 0
templates/emails/invoice-body-html.html 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 %}

+ 8
- 0
templates/emails/invoice-body-text.html View File

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

+ 2
- 0
templates/emails/invoice-subject.html View File

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

Loading…
Cancel
Save