Browse Source

Users can specify a separate email address that will receive invoices.

py2
Pēteris Caune 7 years ago
parent
commit
9fb7ca7103
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
9 changed files with 247 additions and 44 deletions
  1. +16
    -0
      hc/payments/forms.py
  2. +20
    -0
      hc/payments/migrations/0006_subscription_invoice_email.py
  3. +11
    -0
      hc/payments/models.py
  4. +23
    -6
      hc/payments/tests/test_billing.py
  5. +75
    -0
      hc/payments/tests/test_charge_webhook.py
  6. +13
    -17
      hc/payments/views.py
  7. +8
    -0
      static/css/billing.css
  8. +80
    -21
      templates/accounts/billing.html
  9. +1
    -0
      templates/base.html

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

@ -0,0 +1,16 @@
from django import forms
from hc.accounts.forms import LowercaseEmailField
class InvoiceEmailingForm(forms.Form):
send_invoices = forms.IntegerField(min_value=0, max_value=2)
invoice_email = LowercaseEmailField(required=False)
def update_subscription(self, sub):
sub.send_invoices = self.cleaned_data["send_invoices"] > 0
if self.cleaned_data["send_invoices"] == 2:
sub.invoice_email = self.cleaned_data["invoice_email"]
else:
sub.invoice_email = ""
sub.save()

+ 20
- 0
hc/payments/migrations/0006_subscription_invoice_email.py View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-04-21 15:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('payments', '0005_subscription_plan_name'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='invoice_email',
field=models.EmailField(blank=True, max_length=254),
),
]

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

@ -35,6 +35,16 @@ class SubscriptionManager(models.Manager):
return sub, tx
def by_braintree_webhook(self, request):
sig = str(request.POST["bt_signature"])
payload = str(request.POST["bt_payload"])
doc = braintree.WebhookNotification.parse(sig, payload)
assert doc.kind == "subscription_charged_successfully"
sub = self.get(subscription_id=doc.subscription.id)
return sub, doc.subscription.transactions[0]
class Subscription(models.Model):
user = models.OneToOneField(User, models.CASCADE, blank=True, null=True)
@ -45,6 +55,7 @@ class Subscription(models.Model):
plan_name = models.CharField(max_length=50, blank=True)
address_id = models.CharField(max_length=2, blank=True)
send_invoices = models.BooleanField(default=True)
invoice_email = models.EmailField(blank=True)
objects = SubscriptionManager()


+ 23
- 6
hc/payments/tests/test_billing.py View File

@ -1,16 +1,33 @@
from mock import patch
from hc.payments.models import Subscription
from hc.test import BaseTestCase
class SetPlanTestCase(BaseTestCase):
class BillingCase(BaseTestCase):
@patch("hc.payments.models.braintree")
def test_it_saves_send_invoices_flag(self, mock):
def test_it_disables_invoice_emailing(self):
self.client.login(username="[email protected]", password="password")
form = {"save_send_invoices": True}
form = {"send_invoices": "0"}
self.client.post("/accounts/profile/billing/", form)
sub = Subscription.objects.get()
self.assertFalse(sub.send_invoices)
self.assertEqual(sub.invoice_email, "")
def test_it_enables_invoice_emailing(self):
self.client.login(username="[email protected]", password="password")
form = {"send_invoices": "1"}
self.client.post("/accounts/profile/billing/", form)
sub = Subscription.objects.get()
self.assertTrue(sub.send_invoices)
self.assertEqual(sub.invoice_email, "")
def test_it_saves_invoice_email(self):
self.client.login(username="[email protected]", password="password")
form = {"send_invoices": "2", "invoice_email": "[email protected]"}
self.client.post("/accounts/profile/billing/", form)
sub = Subscription.objects.get()
self.assertTrue(sub.send_invoices)
self.assertEqual(sub.invoice_email, "[email protected]")

+ 75
- 0
hc/payments/tests/test_charge_webhook.py View File

@ -0,0 +1,75 @@
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, ["[email protected]"])
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 = "[email protected]"
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, ["[email protected]"])

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

@ -8,6 +8,7 @@ from django.views.decorators.http import require_POST
import six
from hc.api.models import Check
from hc.lib import emails
from hc.payments.forms import InvoiceEmailingForm
from hc.payments.invoices import PdfInvoice
from hc.payments.models import Subscription
@ -41,13 +42,15 @@ def billing(request):
# Don't use Subscription.objects.for_user method here, so a
# subscription object is not created just by viewing a page.
sub = Subscription.objects.filter(user_id=request.user.id).first()
if sub is None:
sub = Subscription(user=request.user)
send_invoices_status = "default"
if request.method == "POST":
if "save_send_invoices" in request.POST:
form = InvoiceEmailingForm(request.POST)
if form.is_valid():
sub = Subscription.objects.for_user(request.user)
sub.send_invoices = "send_invoices" in request.POST
sub.save()
form.update_subscription(sub)
send_invoices_status = "success"
ctx = {
@ -208,22 +211,15 @@ def pdf_invoice(request, transaction_id):
@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)
sub, tx = Subscription.objects.by_braintree_webhook(request)
if sub.send_invoices:
transaction = doc.subscription.transactions[0]
filename = "MS-HC-%s.pdf" % transaction.id.upper()
filename = "MS-HC-%s.pdf" % tx.id.upper()
sink = six.BytesIO()
PdfInvoice(sink).render(transaction, sub.flattened_address())
ctx = {"tx": transaction}
emails.invoice(sub.user.email, ctx, filename, sink.getvalue())
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()

+ 8
- 0
static/css/billing.css View File

@ -0,0 +1,8 @@
#invoice-email {
margin-left: 50px;
width: 300px;
}
#invoice-emailing-status {
margin-right: 150px;
}

+ 80
- 21
templates/accounts/billing.html View File

@ -148,27 +148,24 @@
<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>
<h2>Invoices to Email</h2>
<p id="invoice-emailing-status">
{% if sub.send_invoices %}
Send the invoice to
{{ sub.invoice_email|default:request.user.email }}
each time my payment method is successfully charged.
{% else %}
Do not email invoices to me.
{% endif %}
</p>
<button
data-toggle="modal"
data-target="#invoice-emailing-modal"
class="btn btn-default pull-right">
Change Preference
</button>
</div>
{% if send_invoices_status == "success" %}
<div class="panel-footer">
@ -447,6 +444,68 @@
</form>
</div>
</div>
<div id="invoice-emailing-modal" class="modal pm-modal">
<div class="modal-dialog">
<form method="post">
{% csrf_token %}
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4>Invoices to Email</h4>
</div>
<div class="modal-body">
<label class="radio-container">
<input
type="radio"
name="send_invoices"
value="0"
{% if not sub.send_invoices %} checked {% endif %}>
<span class="radiomark"></span>
Do not email invoices to me
</label>
<label class="radio-container">
<input
type="radio"
name="send_invoices"
value="1"
{% if sub.send_invoices and not sub.invoice_email %} checked {% endif %}>
<span class="radiomark"></span>
Send invoices to {{ profile.user.email }}
</label>
<label class="radio-container">
<input
type="radio"
name="send_invoices"
value="2"
{% if sub.send_invoices and sub.invoice_email %} checked {% endif %}>
<span class="radiomark"></span>
Send invoices to this email address:
</label>
<input
id="invoice-email"
name="invoice_email"
placeholder="[email protected]"
value="{{ sub.invoice_email }}"
type="email"
class="form-control" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Cancel
</button>
<button type="submit" class="btn btn-primary">
Save Changes
</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}


+ 1
- 0
templates/base.html View File

@ -40,6 +40,7 @@
<link rel="stylesheet" href="{% static 'css/profile.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/checkbox.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css">
{% endcompress %}
</head>
<body class="page-{{ page }}">


Loading…
Cancel
Save