# -*- encoding: utf-8 -*-
import pytest
from datetime import date
from dateutil.relativedelta import relativedelta
from decimal import Decimal
from unittest import mock
from django.db import transaction
from checkout.models import (
CheckoutError,
CheckoutState,
Customer,
ObjectPaymentPlan,
)
from checkout.tests.factories import (
CustomerFactory,
ObjectPaymentPlanFactory,
ObjectPaymentPlanInstalmentFactory,
PaymentPlanFactory,
)
from mail.models import Message
from mail.tests.factories import MailTemplateFactory
from .factories import SalesLedgerFactory
[docs]@pytest.mark.django_db
def test_factory():
ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
[docs]@pytest.mark.django_db
def test_str():
str(ObjectPaymentPlanFactory(content_object=SalesLedgerFactory()))
[docs]@pytest.mark.django_db
def test_create_object_payment_plan():
sl = SalesLedgerFactory()
payment_plan = PaymentPlanFactory(deposit=20, count=2, interval=1)
# create the contact plan with the deposit
with transaction.atomic():
# this must be run within a transaction
ObjectPaymentPlan.objects.create_object_payment_plan(
sl, payment_plan, Decimal("100")
)
object_payment_plan = ObjectPaymentPlan.objects.for_content_object(sl)
# check deposit - count should be '1' and the 'due' date ``today``
result = [(p.count, p.amount, p.due) for p in object_payment_plan.payments]
assert [(1, Decimal("20"), date.today())] == result
# create the instalments
with transaction.atomic():
# this must be run within a transaction
object_payment_plan.create_instalments()
result = [(p.count, p.amount, p.due) for p in object_payment_plan.payments]
offset = 0
# instalments start a month later if after the 15th of the month
if date.today().day > 15:
offset = 1
assert [
(1, Decimal("20"), date.today()),
(
2,
Decimal("40"),
date.today() + relativedelta(months=+(1 + offset), day=1),
),
(
3,
Decimal("40"),
date.today() + relativedelta(months=+(2 + offset), day=1),
),
] == result
[docs]@pytest.mark.django_db
def test_create_instalments_once_only():
sl = SalesLedgerFactory()
payment_plan = PaymentPlanFactory(deposit=20, count=2, interval=1)
# create the contact plan with the deposit
with transaction.atomic():
# this must be run within a transaction
pp = ObjectPaymentPlan.objects.create_object_payment_plan(
sl, payment_plan, Decimal("100")
)
# create the instalments
with transaction.atomic():
# this must be run within a transaction
pp.create_instalments()
with pytest.raises(CheckoutError) as e:
pp.create_instalments()
assert "instalments already created" in str(e.value)
[docs]@pytest.mark.django_db
def test_create_instalments_no_deposit():
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
with pytest.raises(CheckoutError) as e:
obj.create_instalments()
assert "no deposit/instalment record" in str(e.value)
[docs]@pytest.mark.django_db
def test_create_instalments_corrupt():
object_payment_plan = ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
)
obj = ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan, count=2
)
with pytest.raises(CheckoutError) as e:
obj.object_payment_plan.create_instalments()
assert "no deposit record" in str(e.value)
[docs]@pytest.mark.django_db
def test_delete():
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
assert obj.deleted is False
obj.delete()
obj.refresh_from_db()
assert obj.deleted is True
[docs]@pytest.mark.django_db
def test_fail_or_request():
"""Payment plans which have an instalment in the fail or request state."""
p1 = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
i1 = ObjectPaymentPlanInstalmentFactory(
object_payment_plan=p1,
due=date.today() + relativedelta(months=-2),
state=CheckoutState.objects.fail,
)
p2 = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
i2 = ObjectPaymentPlanInstalmentFactory(
object_payment_plan=p2,
due=date.today() + relativedelta(months=-1),
state=CheckoutState.objects.request,
)
p3 = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=p3,
due=date.today() + relativedelta(months=-1),
state=CheckoutState.objects.pending,
)
result = ObjectPaymentPlan.objects.fail_or_request
assert [i1.pk, i2.pk] == [obj.pk for obj in result]
[docs]@pytest.mark.django_db
def test_fail_or_request_duplicate():
"""Payment plans which have an instalment in the fail or request state."""
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj,
due=date.today() + relativedelta(months=-2),
state=CheckoutState.objects.fail,
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj,
due=date.today() + relativedelta(months=-1),
state=CheckoutState.objects.request,
)
result = ObjectPaymentPlan.objects.fail_or_request
assert [obj.pk] == [obj.pk for obj in result]
[docs]@pytest.mark.django_db
def test_instalment_count():
"""See ``payment_count``."""
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
today = date.today()
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj,
deposit=True,
due=today + relativedelta(days=1),
count=1,
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj, due=today + relativedelta(days=2), count=2
)
assert 1 == obj.instalment_count
[docs]@pytest.mark.parametrize(
"state", [CheckoutState.FAIL, CheckoutState.PENDING, CheckoutState.REQUEST]
)
@pytest.mark.django_db
def test_instalments_due(state):
today = date.today()
checkout_state = CheckoutState.objects.get(slug=state)
object_payment_plan = ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan,
deposit=True,
due=today + relativedelta(days=-30),
count=1,
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan,
due=today + relativedelta(days=-15),
state=checkout_state,
count=2,
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan, due=today, count=3
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan,
due=today + relativedelta(days=+30),
count=4,
)
assert [1, 2] == [x.count for x in object_payment_plan.instalments_due()]
[docs]@pytest.mark.parametrize(
"state", [CheckoutState.FAIL, CheckoutState.PENDING, CheckoutState.REQUEST]
)
@pytest.mark.django_db
def test_instalments_due_exclude_by_due_date(state):
today = date.today()
checkout_state = CheckoutState.objects.get(slug=state)
object_payment_plan = ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan,
deposit=True,
due=today + relativedelta(days=-30),
count=1,
)
obj = ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan,
due=today + relativedelta(days=-15),
state=checkout_state,
count=2,
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan, due=today, count=3
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=object_payment_plan,
due=today + relativedelta(days=+30),
count=4,
)
qs = object_payment_plan.instalments_due(obj.due)
assert [1] == [x.count for x in qs]
[docs]@pytest.mark.django_db
def test_outstanding_payment_plans():
assert 0 == ObjectPaymentPlan.objects.outstanding_payment_plans.count()
[docs]@pytest.mark.django_db
def test_outstanding_payment_plans_exclude_deleted():
obj = ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory(), deleted=True
)
ObjectPaymentPlanInstalmentFactory(object_payment_plan=obj)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
)
)
assert 1 == ObjectPaymentPlan.objects.outstanding_payment_plans.count()
[docs]@pytest.mark.django_db
def test_outstanding_payment_plans_exclude_success():
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
)
)
ObjectPaymentPlanInstalmentFactory(
state=CheckoutState.objects.success,
object_payment_plan=ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
),
)
assert 1 == ObjectPaymentPlan.objects.outstanding_payment_plans.count()
[docs]@pytest.mark.django_db
def test_outstanding_payment_plans_filter_two():
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
ObjectPaymentPlanInstalmentFactory(object_payment_plan=obj)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj, due=date.today() + relativedelta(months=+1)
)
assert 1 == ObjectPaymentPlan.objects.outstanding_payment_plans.count()
[docs]@pytest.mark.django_db
def test_payment_count():
"""See ``instalment_count``."""
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj,
deposit=True,
due=date.today() + relativedelta(days=1),
count=1,
)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj,
due=date.today() + relativedelta(days=2),
count=2,
)
assert 2 == obj.payment_count
[docs]@pytest.mark.django_db
def test_refresh_card_expiry_dates():
MailTemplateFactory(slug=Customer.MAIL_TEMPLATE_CARD_EXPIRY)
with mock.patch("stripe.Customer.retrieve") as mock_retrieve:
mock_retrieve.return_value = {
"default_source": "1234",
"sources": {
"data": [{"id": "1234", "exp_month": "8", "exp_year": "1986"}]
},
}
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
ObjectPaymentPlanInstalmentFactory(object_payment_plan=obj)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj, due=date.today() + relativedelta(months=+1)
)
customer = CustomerFactory(email=obj.content_object.checkout_email)
ObjectPaymentPlan.objects.refresh_card_expiry_dates()
customer.refresh_from_db()
assert customer.refresh is True
# check email template context
assert 1 == Message.objects.count()
message = Message.objects.first()
assert 1 == message.mail_set.count()
mail = message.mail_set.first()
assert 1 == mail.mailfield_set.count()
assert {"name": customer.name} == {
f.key: f.value for f in mail.mailfield_set.all()
}
[docs]@pytest.mark.django_db
def test_refresh_card_expiry_dates_future():
with mock.patch("stripe.Customer.retrieve") as mock_retrieve:
mock_retrieve.return_value = {
"default_source": "1234",
"sources": {
"data": [{"id": "1234", "exp_month": "8", "exp_year": "2050"}]
},
}
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
ObjectPaymentPlanInstalmentFactory(object_payment_plan=obj)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj, due=date.today() + relativedelta(months=+1)
)
customer = CustomerFactory(email=obj.content_object.checkout_email)
ObjectPaymentPlan.objects.refresh_card_expiry_dates()
customer.refresh_from_db()
assert customer.refresh is False
# check we didn't send a notification email
assert 0 == Message.objects.count()
[docs]@pytest.mark.django_db
def test_refresh_card_expiry_dates_refreshed():
"""Customer card already marked for 'refresh', so don't send an email."""
with mock.patch("stripe.Customer.retrieve") as mock_retrieve:
mock_retrieve.return_value = {
"default_source": "1234",
"sources": {
"data": [{"id": "1234", "exp_month": "8", "exp_year": "1986"}]
},
}
obj = ObjectPaymentPlanFactory(content_object=SalesLedgerFactory())
ObjectPaymentPlanInstalmentFactory(object_payment_plan=obj)
ObjectPaymentPlanInstalmentFactory(
object_payment_plan=obj, due=date.today() + relativedelta(months=+1)
)
customer = CustomerFactory(
email=obj.content_object.checkout_email, refresh=True
)
ObjectPaymentPlan.objects.refresh_card_expiry_dates()
customer.refresh_from_db()
assert customer.refresh is True
# check we didn't send a notification email
assert 0 == Message.objects.count()
[docs]@pytest.mark.django_db
def test_report_card_expiry_dates():
object_payment_plan = ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
)
ObjectPaymentPlanInstalmentFactory(object_payment_plan=object_payment_plan)
obj = ObjectPaymentPlanInstalmentFactory(
object_payment_plan=ObjectPaymentPlanFactory(
content_object=SalesLedgerFactory()
)
)
CustomerFactory(email=obj.object_payment_plan.content_object.checkout_email)
ObjectPaymentPlan.objects.report_card_expiry_dates