def test_filters_is_overdue(self): today = datetime.now(pytz.utc).date() yesterday = today - timedelta(days=1) tomorrow = today + timedelta(days=1) customer = CustomerFactory() payment_unpaid_overdue = PaymentFactory.create( customer=customer, due_date=yesterday, status=Payment.Status.Unpaid) payment_unpaied_not_due = PaymentFactory.create( customer=customer, due_date=tomorrow, status=Payment.Status.Unpaid) payment_paid_not_due = PaymentFactory.create( customer=payment_unpaid_overdue.customer, due_date=tomorrow, status=Payment.Status.Paid) payment_canceled = PaymentFactory.create( customer=customer, due_date=yesterday, status=Payment.Status.Canceled) url = reverse('payment-list', kwargs={'customer_pk': customer.pk}) overdue_url = url + '?is_overdue=True' not_overdue_url = url + '?is_overdue=False' self.assert_get_data(overdue_url, [payment_unpaid_overdue]) self.assert_get_data( not_overdue_url, [payment_unpaied_not_due, payment_paid_not_due, payment_canceled])
def test_create_negative_document(self): """ Confirm that an invoice can be issued with a negative value. """ # 0 for easy asserting. customer = CustomerFactory(sales_tax_percent=0, currency='USD') entry = DocumentEntryFactory(quantity=1, unit_price=-150) invoice = InvoiceFactory.create(invoice_entries=[entry], customer=customer) invoice.issue() customer = invoice.customer payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False) transaction = TransactionFactory.create( invoice=invoice, payment_method=payment_method, amount=invoice.total_in_transaction_currency, state=Transaction.States.Initial) assert invoice.transactions.count() == 1 assert invoice.total_in_transaction_currency == -150 assert transaction.amount == -150
def test_filters_min_max_amount(self): customer = CustomerFactory() payment_below_min = PaymentFactory.create(customer=customer, amount=9.99) payment_min = PaymentFactory.create(customer=customer, amount=10.00) payment_above_min = PaymentFactory.create(customer=customer, amount=10.01) payment_center = PaymentFactory.create(customer=customer, amount=60) payment_below_max = PaymentFactory.create(customer=customer, amount=99.99) payment_max = PaymentFactory.create(customer=customer, amount=100.00) payment_above_max = PaymentFactory.create(customer=customer, amount=100.01) url = reverse('payment-list', kwargs={'customer_pk': customer.pk}) url_range_slice = url + '?min_amount=10&max_amount=100' url_range_all_entries = url + '?min_amount=9.99&max_amount=100.01' url_exact_amount = url + '?min_amount=60&max_amount=60' url_no_intersection = url + '?min_amount=100&max_amount=0' self.assert_get_data(url_range_slice, [ payment_min, payment_above_min, payment_center, payment_below_max, payment_max ]) self.assert_get_data(url_range_all_entries, [ payment_below_min, payment_min, payment_above_min, payment_center, payment_below_max, payment_max, payment_above_max ]) self.assert_get_data(url_exact_amount, [payment_center]) self.assert_get_data(url_no_intersection, [])
def test_filter_provider(self): customer = CustomerFactory() payment = PaymentFactory.create(customer=customer, provider__name='Gigel') url = reverse('payment-list', kwargs={'customer_pk': customer.pk}) iexact_url = url + '?provider=gigel' exact_url = url + '?provider=Gigel' url_no_output = url + '?provider=RANDOM' self.assert_get_data(iexact_url, [payment]) self.assert_get_data(exact_url, [payment]) self.assert_get_data(url_no_output, [])
def test_filter_visible(self): customer = CustomerFactory() payment_visible = PaymentFactory.create(customer=customer, visible=True) payment_not_visible = PaymentFactory.create(customer=customer, visible=False) url = reverse('payment-list', kwargs={'customer_pk': customer.pk}) visible_url = url + '?visible=True' not_visible_url = url + '?visible=False' self.assert_get_data(visible_url, [payment_visible]) self.assert_get_data(not_visible_url, [payment_not_visible])
def test_filters_currency(self): customer = CustomerFactory() payment_usd = PaymentFactory.create(customer=customer, currency='USD') payment_std = PaymentFactory.create(customer=customer, currency='STD') url = reverse('payment-list', kwargs={'customer_pk': customer.pk}) url_usd_iexact = url + '?currency=UsD' url_usd_exact = url + '?currency=USD' url_std = url + '?currency=STD' url_no_output = url + '?currency=RANDOM' self.assert_get_data(url_usd_iexact, [payment_usd]) self.assert_get_data(url_usd_exact, [payment_usd]) self.assert_get_data(url_std, [payment_std]) self.assert_get_data(url_no_output, [])
def test_filter_flow(self): customer = CustomerFactory() payment_flow_invoice = PaymentFactory.create( customer=customer, provider__flow=Provider.FLOWS.INVOICE) payment_flow_proforma = PaymentFactory.create( customer=customer, provider__flow=Provider.FLOWS.PROFORMA) url = reverse('payment-list', kwargs={'customer_pk': customer.pk}) invoice_url = url + '?flow=' + Provider.FLOWS.INVOICE proforma_url = url + '?flow=' + Provider.FLOWS.PROFORMA url_no_output = url + '?flow=RANDOM' self.assert_get_data(invoice_url, [payment_flow_invoice]) self.assert_get_data(proforma_url, [payment_flow_proforma]) self.assert_get_data(url_no_output, [])
def test_filter_status(self): customer = CustomerFactory() payment_paid_status = PaymentFactory.create(customer=customer, status=Payment.Status.Paid) payment_canceled_status = PaymentFactory.create( customer=customer, status=Payment.Status.Canceled) url = reverse('payment-list', kwargs={'customer_pk': customer.pk}) url_paid_only = url + '?status=' + Payment.Status.Paid url_canceled_only = url + '?status=' + Payment.Status.Canceled url_no_output = url + '?status=RANDOM' self.assert_get_data(url_paid_only, [payment_paid_status]) self.assert_get_data(url_canceled_only, [payment_canceled_status]) self.assert_get_data(url_no_output, [])
def _create_default_payment_method(self): # 0 for easy asserting. customer = CustomerFactory(sales_tax_percent=0, currency='USD', first_name="Captain", last_name="Hook") # Create customer payment method payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False, verified=True, data={ # Wait until payment day X to begin retry attempts 'attempt_retries_after': 2, # Stop attempts on day X 'stop_retry_attempts': 5, }) return customer, payment_method
def test_balance_on_date(self): import pytz start_date = dt.datetime(2019, 1, 1, 0, 0, 0, 0, tzinfo=pytz.UTC) invoice_date = dt.datetime(2019, 1, 15, 0, 0, 0, 0, tzinfo=pytz.UTC) payment_date = dt.datetime(2019, 1, 17, 0, 0, 0, 0, tzinfo=pytz.UTC) mid_date = dt.datetime(2019, 1, 20, 0, 0, 0, 0, tzinfo=pytz.UTC) from silver.overpayment_checker import OverpaymentChecker # 0 for easy asserting. customer = CustomerFactory(sales_tax_percent=0, currency='USD', first_name="Bob", last_name="Smith") customer.save() assert customer.balance_on_date(date=start_date) == Decimal(0) ## Now we create an invoice situation after the start date... # Create customer payment method payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False) payment_method.save() # Create a simple invoice entry = DocumentEntryFactory(quantity=1, unit_price=150) entry.save() invoice = InvoiceFactory.create(invoice_entries=[entry], due_date=invoice_date, customer=customer, transaction_currency='USD') invoice.issue() invoice.save() # Customer overpays by 2x transaction = TransactionFactory.create( invoice=invoice, payment_method=payment_method, created_at=payment_date, updated_at=payment_date, overpayment=True, amount=300, state=Transaction.States.Initial) transaction.settle() transaction.save() invoice.pay() assert invoice.state == Invoice.STATES.PAID assert customer.balance_on_date(date=start_date) == Decimal(0) assert customer.balance_on_date(date=payment_date) == Decimal(0) # This balance should be 150, but it's not yet. assert customer.balance_on_date(date=mid_date) == Decimal(150) return # Grab the overpayment defaults op = OverpaymentChecker() # Run the overpayment process call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) # An invoice has been issued a for the correct amount. repayment = Invoice.objects.filter( provider=op.default_provider).first() assert Invoice.objects.filter( provider=op.default_provider).count() == 1 assert repayment.total_in_transaction_currency == -150 assert repayment.state == Invoice.STATES.ISSUED # Customer balance is still the same; payment has not occurred # yet. assert customer.balance == Decimal(150) # Run the overpayment process a couple more times, does it duplicate # things? call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) assert Invoice.objects.filter( provider=op.default_provider).count() == 1 assert repayment.total_in_transaction_currency == -150 assert repayment.state == Invoice.STATES.ISSUED # Customer balance is still the same; payment has not occurred # yet. assert customer.balance == Decimal(150) # Create the repayment transaction, this is supposed to happen # somewhere automatically. amt = repayment.total_in_transaction_currency repayment_tx = Transaction.objects.create( invoice=repayment, amount=amt, overpayment=True, state=Transaction.States.Initial, payment_method=payment_method) repayment_tx.settle() repayment_tx.save() # There's one transaction issued for this doc, and it has set # the state of the invoice to PAID. The customer's balance is # now 0. assert repayment.state == Invoice.STATES.PAID assert repayment.transactions.count() == 1 assert repayment.amount_paid_in_transaction_currency == -150 assert repayment.customer.balance == Decimal(0)
def test_customer_balance_gte_zero(self): """ Create a zero and positive balance, and rerun the overpayment process: no invoices should be issued. """ from silver.overpayment_checker import OverpaymentChecker # 0 for easy asserting. customer = CustomerFactory(sales_tax_percent=0, currency='USD', first_name="Bob", last_name="Smith") customer.save() # Create customer payment method payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False) payment_method.save() # Create a simple invoice entry = DocumentEntryFactory(quantity=1, unit_price=150) entry.save() invoice = InvoiceFactory.create(invoice_entries=[entry], customer=customer, transaction_currency='USD') invoice.issue() invoice.save() # Customer pays an accurate amount. transaction = TransactionFactory.create( invoice=invoice, payment_method=payment_method, amount=150, state=Transaction.States.Initial) transaction.settle() transaction.save() assert invoice.state == Invoice.STATES.PAID assert Invoice.objects.filter(customer=customer).count() == 1 assert customer.balance == Decimal(0) # Run the overpayment process call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) provider = OverpaymentChecker().default_provider assert Invoice.objects.filter(provider=provider).count() == 0 # Run the overpayment process a couple more times, does it duplicate # things? call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) assert Invoice.objects.filter(provider=provider).count() == 0
def test_overpayment_checker_process(self): from silver.overpayment_checker import OverpaymentChecker customer = CustomerFactory( sales_tax_percent=0, # 0 for easy asserting. currency='USD', first_name="Bob", last_name="Smith") customer.save() # Create customer payment method # payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False) payment_method.save() # Create a simple invoice. # entry = DocumentEntryFactory(quantity=1, unit_price=150) entry.save() invoice = InvoiceFactory.create(invoice_entries=[entry], customer=customer, transaction_currency='USD') invoice.issue() invoice.save() # Customer overpays by 2x # transaction = TransactionFactory.create( invoice=invoice, payment_method=payment_method, overpayment=True, amount=300, state=Transaction.States.Initial) transaction.settle() transaction.save() invoice.pay() assert invoice.state == Invoice.STATES.PAID assert customer.balance == Decimal(150) # Grab the overpayment defaults op = OverpaymentChecker() # Run the overpayment process call_command('check_overpayments', billing_date=timezone.now().date(), stdout=self.output) # An invoice has been issued a for the correct amount. repayment = Invoice.objects.filter( provider=op.default_provider).first() assert Invoice.objects.filter( provider=op.default_provider).count() == 1 assert repayment.total_in_transaction_currency == -150 assert repayment.state == Invoice.STATES.ISSUED # Customer balance is still the same; payment has not occurred # yet. # assert customer.balance == Decimal(150) # Create the repayment transaction, this is supposed to happen # somewhere automatically. # repayment_tx = Transaction.objects.create( invoice=repayment, amount=repayment.total_in_transaction_currency, overpayment=True, state=Transaction.States.Initial, payment_method=payment_method) repayment_tx.settle() repayment_tx.save() # There's one transaction issued for this doc, and it has set # the state of the invoice to PAID. The customer's balance is # now 0. # assert repayment.state == Invoice.STATES.PAID assert repayment.transactions.count() == 1 assert repayment.amount_paid_in_transaction_currency == -150 assert repayment.customer.balance == Decimal(0)
def test_correct_overpayment(self): """ An invoice is issued, and it is overpaid. A correction is issued """ # 0 for easy asserting. customer = CustomerFactory(sales_tax_percent=0, currency='USD', first_name="Bob", last_name="Smith") customer.save() payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False) # Create a simple invoice entry = DocumentEntryFactory(quantity=1, unit_price=150) invoice = InvoiceFactory.create(invoice_entries=[entry], customer=customer, transaction_currency='USD') invoice.issue() invoice.save() assert PaymentMethod.objects.count() == 1 assert Invoice.objects.count() == 1 assert BillingDocumentBase.objects.count() == 1 # Customer overpays by 2x transaction = TransactionFactory.create( invoice=invoice, payment_method=payment_method, overpayment=True, amount=300, state=Transaction.States.Initial) transaction.settle() transaction.save() invoice.pay() assert invoice.state == Invoice.STATES.PAID assert PaymentMethod.objects.count() == 1 assert Invoice.objects.count() == 1 # Proforma issued as well assert BillingDocumentBase.objects.count() == 2 assert customer.balance == Decimal(150) # # Create a repayment invoice entry = DocumentEntryFactory(quantity=1, unit_price=-150) invoice = InvoiceFactory.create(invoice_entries=[entry], customer=customer, state=Invoice.STATES.DRAFT, transaction_currency='USD') invoice.save() assert customer.balance == Decimal(150) invoice.issue() invoice.save() assert customer.balance == Decimal(150) # This is the transaction to correct the balance. We're using # .settle() here, but we will need another method (forthcoming). transaction = TransactionFactory.create( invoice=invoice, payment_method=payment_method, overpayment=True, amount=-150, state=Transaction.States.Initial) transaction.settle() transaction.save() assert invoice.state == Invoice.STATES.PAID assert customer.balance == Decimal(0)
def test_customer_balance_calculation_with_overpayments(self): """ An invoice is issued, and it is paid in two transactions: one for half the amount, and another for well over the amount. """ # 0 for easy asserting. customer = CustomerFactory(sales_tax_percent=0, currency='USD', first_name="Bob", last_name="Smith") customer.save() payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False) # Create a simple invoice entry = DocumentEntryFactory(quantity=1, unit_price=250) invoice = InvoiceFactory.create(invoice_entries=[entry], customer=customer) invoice.issue() # Customer underpays by half transaction = TransactionFactory.create( invoice=invoice, payment_method=payment_method, amount=125.00, state=Transaction.States.Initial) transaction.settle() transaction.save() assert invoice.state != Invoice.STATES.PAID # Customer overpays by double transaction_over = TransactionFactory.create( invoice=invoice, payment_method=payment_method, amount=500, # NB: overpayment=True, state=Transaction.States.Initial) transaction_over.settle() transaction_over.save() assert invoice.state != Invoice.STATES.PAID invoice.pay() assert invoice.state == Invoice.STATES.PAID # Payment calculation works even with overpayment. assert invoice.amount_paid_in_transaction_currency == \ ((invoice.total_in_transaction_currency / 2) + \ (invoice.total_in_transaction_currency * 2)) # Customer paid 625 total, for an invoice of 250 # Current balance: 375.00 overpayment = abs( invoice.total_in_transaction_currency - \ ((invoice.total_in_transaction_currency / 2) + \ (invoice.total_in_transaction_currency * 2)) ) assert invoice.total_in_transaction_currency == 250.00 assert overpayment == Decimal(375.00) assert customer.balance == overpayment