def handle(self, *args, **options): proforma = ProformaFactory.create(proforma_entries=[DocumentEntryFactory.create()], state=Proforma.STATES.ISSUED) proforma.create_invoice() invoice = InvoiceFactory.create(invoice_entries=[DocumentEntryFactory.create()], state=Invoice.STATES.ISSUED, related_document=None) TransactionFactory.create(state=Transaction.States.Settled, invoice=invoice, payment_method__customer=invoice.customer, proforma=None) InvoiceFactory.create_batch(size=3, invoice_entries=[DocumentEntryFactory.create()], state=Invoice.STATES.PAID, related_document=None) InvoiceFactory.create_batch(size=3, invoice_entries=[DocumentEntryFactory.create()], state=Invoice.STATES.DRAFT, related_document=None) InvoiceFactory.create_batch(size=3, invoice_entries=[DocumentEntryFactory.create()], state=Invoice.STATES.CANCELED, related_document=None)
def test_proforma_total_before_tax_decimal_places(self): proforma_entries = DocumentEntryFactory.create_batch(3) proforma = ProformaFactory.create(proforma_entries=proforma_entries) proforma.sales_tax_percent = Decimal('20.00') assert self._get_decimal_places(proforma.total_before_tax) == 2
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_documents_list_case_1(self): """ One proforma, one invoice, without related documents """ proforma = ProformaFactory.create() invoice_entries = DocumentEntryFactory.create_batch(3) invoice = InvoiceFactory.create(invoice_entries=invoice_entries) invoice.issue() payment_method = PaymentMethodFactory.create(customer=invoice.customer) transaction = TransactionFactory.create(payment_method=payment_method, invoice=invoice) url = reverse('document-list') with patch('silver.utils.payments._get_jwt_token', new=self._jwt_token): response = self.client.get(url) # ^ there's a bug where specifying format='json' doesn't work response_data = response.data self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response_data), 2) self.assertIn(self._get_expected_data(invoice, [transaction]), response_data) self.assertIn(self._get_expected_data(proforma), response_data)
def test_proforma_total_with_tax_integrity(self): proforma_entries = DocumentEntryFactory.create_batch(5) proforma = ProformaFactory.create(proforma_entries=proforma_entries) proforma.sales_tax_percent = Decimal('20.00') assert proforma.total == proforma.total_before_tax + proforma.tax_value
def test_invoice_total_with_tax_integrity(self): invoice_entries = DocumentEntryFactory.create_batch(5) invoice = InvoiceFactory.create(invoice_entries=invoice_entries) invoice.sales_tax_percent = Decimal('20.00') assert invoice.total == invoice.total_before_tax + invoice.tax_value
def test_invoice_tax_value_decimal_places(self): invoice_entries = DocumentEntryFactory.create_batch(3) invoice = InvoiceFactory.create(invoice_entries=invoice_entries) invoice.sales_tax_percent = Decimal('20.00') assert self._get_decimal_places(invoice.tax_value) == 2
def test_proforma_tax_value_decimal_places(self): proforma_entries = DocumentEntryFactory.create_batch(3) proforma = ProformaFactory.create(proforma_entries=proforma_entries) proforma.sales_tax_percent = Decimal('20.00') assert self._get_decimal_places(proforma.tax_value) == 2
def test_invoice_total_with_tax_integrity(self): invoice_entries = DocumentEntryFactory.create_batch(5) invoice = InvoiceFactory.create(invoice_entries=invoice_entries) invoice.sales_tax_percent = Decimal('20.00') self.assertEqual(invoice.total, invoice.total_before_tax + invoice.tax_value)
def test_filter_min_max_amount(self): payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, ) customer = payment_method.customer entry = DocumentEntryFactory(quantity=1, unit_price=100) invoice = InvoiceFactory.create(invoice_entries=[entry], customer=customer) invoice.issue() transaction = TransactionFactory.create(payment_method=payment_method, invoice=invoice) transaction_data = self._transaction_data(transaction) urls = [ reverse('payment-method-transaction-list', kwargs={ 'customer_pk': customer.pk, 'payment_method_id': payment_method.pk }), reverse('transaction-list', kwargs={'customer_pk': customer.pk}) ] for url in urls: url_with_filterable_data = url + '?min_amount=10' url_no_output = url + '?min_amount=150' with patch('silver.utils.payments._get_jwt_token') as mocked_token: mocked_token.return_value = 'token' response = self.client.get(url_with_filterable_data, format='json') transaction.refresh_from_db() transaction_data['updated_at'] = response.data[0]['updated_at'] self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0], transaction_data) response = self.client.get(url_no_output, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, []) url_with_filterable_data = url + '?max_amount=1050' url_no_output = url + '?max_amount=10' response = self.client.get(url_with_filterable_data, format='json') transaction.refresh_from_db() transaction_data['updated_at'] = response.data[0]['updated_at'] self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0], transaction_data) response = self.client.get(url_no_output, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, [])
def test_add_transaction_without_currency_and_amount(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create(customer=customer) entries = DocumentEntryFactory.create_batch(2) proforma = ProformaFactory.create(customer=customer, state=Proforma.STATES.ISSUED, issue_date=timezone.now().date(), currency='USD', transaction_currency='RON', transaction_xe_rate=Decimal('0.25'), proforma_entries=entries) proforma.create_invoice() invoice = proforma.invoice valid_until = datetime.now().replace(microsecond=0) + timedelta( minutes=30) url = reverse('payment-method-transaction-list', kwargs={ 'customer_pk': customer.pk, 'payment_method_id': payment_method.pk }) payment_method_url = reverse('payment-method-detail', kwargs={ 'customer_pk': customer.pk, 'payment_method_id': payment_method.id }) invoice_url = reverse('invoice-detail', args=[invoice.pk]) proforma_url = reverse('proforma-detail', args=[proforma.pk]) data = { 'payment_method': reverse('payment-method-detail', kwargs={ 'customer_pk': customer.pk, 'payment_method_id': payment_method.id }), 'valid_until': valid_until, 'invoice': invoice_url, 'proforma': proforma_url } response = self.client.post(url, format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['payment_method'], payment_method_url) self.assertEqual(response.data['valid_until'][:-1], valid_until.isoformat()) self.assertEqual(response.data['can_be_consumed'], True) self.assertEqual(response.data['amount'], unicode(Decimal('0.00') + invoice.transaction_total)) self.assertEqual(response.data['invoice'], invoice_url) self.assertEqual(response.data['proforma'], proforma_url) self.assertEqual(response.data['currency'], invoice.transaction_currency)
def test_invoice_create_storno_from_paid_state(self): invoice = InvoiceFactory.create( invoice_entries=[DocumentEntryFactory.create()]) invoice.issue() invoice.pay() storno = invoice.create_storno() assert -invoice.total == storno.total != 0 assert storno.related_document == invoice assert invoice.customer == storno.customer assert invoice.provider == storno.provider
def test_add_transaction(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create(customer=customer) entry = DocumentEntryFactory(quantity=1, unit_price=200) proforma = ProformaFactory.create(customer=customer, proforma_entries=[entry]) proforma.issue() proforma.create_invoice() proforma.refresh_from_db() invoice = proforma.related_document payment_method_url = reverse('payment-method-detail', kwargs={'customer_pk': customer.pk, 'payment_method_id': payment_method.id}) invoice_url = reverse('invoice-detail', args=[invoice.pk]) proforma_url = reverse('proforma-detail', args=[proforma.pk]) url = reverse('payment-method-transaction-list', kwargs={'customer_pk': customer.pk, 'payment_method_id': payment_method.pk}) valid_until = datetime.now().replace(microsecond=0) + timedelta(minutes=30) currency = invoice.transaction_currency data = { 'payment_method': reverse('payment-method-detail', kwargs={'customer_pk': customer.pk, 'payment_method_id': payment_method.id}), 'amount': invoice.total_in_transaction_currency, 'invoice': invoice_url, 'proforma': proforma_url, 'valid_until': valid_until, 'currency': currency, } response = self.client.post(url, format='json', data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['payment_method'], payment_method_url) self.assertEqual(response.data['valid_until'][:-1], valid_until.isoformat()) self.assertEqual(response.data['can_be_consumed'], True) self.assertEqual(response.data['amount'], force_text(invoice.total_in_transaction_currency)) self.assertEqual(response.data['invoice'], invoice_url) self.assertEqual(response.data['proforma'], proforma_url) self.assertEqual(response.data['currency'], currency) self.assertTrue(Transaction.objects.filter(uuid=response.data['id']))
def test_no_transaction_creation_for_issued_documents_case3(self): """ There are 1 pending and 1 settled transactions that together cover the document amount. """ entry = DocumentEntryFactory(quantity=1, unit_price=100) invoice = InvoiceFactory.create(invoice_entries=[entry]) invoice.issue() customer = invoice.customer payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=customer, canceled=False, verified=False, ) TransactionFactory.create( invoice=invoice, payment_method=payment_method, amount=invoice.total_in_transaction_currency / 2, state=Transaction.States.Settled) TransactionFactory.create( invoice=invoice, payment_method=payment_method, amount=invoice.total_in_transaction_currency / 2, state=Transaction.States.Pending) mock_execute = MagicMock() with patch.multiple(TriggeredProcessor, execute_transaction=mock_execute): expected_exception = ValidationError expected_message = "Amount is greater than the amount that should be " \ "charged in order to pay the billing document." try: TransactionFactory.create(invoice=invoice, payment_method=payment_method, amount=1) self.fail('{} not raised.'.format(str(expected_exception))) except expected_exception as e: self.assertTrue(expected_message in str(e)) transactions = Transaction.objects.filter( payment_method=payment_method, invoice=invoice, ) self.assertEqual(len(transactions), 2) self.assertEqual(mock_execute.call_count, 0)
def test_create_invoice_overpayment_transaction(self): """ An invoice is issued, and it is paid in two transactions: one for half the amount, and another for well over the amount. """ # Create a simple invoice entry = DocumentEntryFactory(quantity=1, unit_price=250) invoice = InvoiceFactory.create(invoice_entries=[entry]) 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 / 2, state=Transaction.States.Initial) transaction.settle() transaction.save() assert invoice.state != Invoice.STATES.PAID transaction_over = TransactionFactory.create( invoice=invoice, payment_method=payment_method, amount=invoice.total_in_transaction_currency * 2, # 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 assert invoice.total_in_transaction_currency != \ invoice.amount_paid_in_transaction_currency # 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))
def test_clone_proforma_into_draft(self): proforma = ProformaFactory.create() proforma.issue() proforma.pay() entries = DocumentEntryFactory.create_batch(3) proforma.proforma_entries.add(*entries) clone = proforma.clone_into_draft() assert clone.state == Proforma.STATES.DRAFT assert clone.paid_date is None assert clone.issue_date is None assert clone.related_document is None assert (clone.series != proforma.series or clone.number != proforma.number) assert clone.sales_tax_percent == proforma.sales_tax_percent assert clone.sales_tax_name == proforma.sales_tax_name assert not clone.archived_customer assert not clone.archived_provider assert clone.customer == proforma.customer assert clone.provider == proforma.provider assert clone.currency == proforma.currency assert clone._last_state == clone.state assert clone.pk != proforma.pk assert clone.id != proforma.id assert not clone.pdf assert clone.proforma_entries.count() == 3 assert proforma.proforma_entries.count() == 3 entry_fields = [ entry.name for entry in DocumentEntry._meta.get_fields() ] for clone_entry, original_entry in zip( clone.proforma_entries.all(), proforma.proforma_entries.all()): for entry in entry_fields: if entry not in ('id', 'proforma', 'invoice'): assert getattr(clone_entry, entry) == \ getattr(original_entry, entry) assert proforma.state == Proforma.STATES.PAID
def test_clone_proforma_into_draft(self): proforma = ProformaFactory.create() proforma.issue() proforma.pay() proforma.save() entries = DocumentEntryFactory.create_batch(3) proforma.proforma_entries.add(*entries) clone = proforma.clone_into_draft() assert clone.state == Proforma.STATES.DRAFT assert clone.paid_date is None assert clone.issue_date is None assert clone.invoice is None assert (clone.series != proforma.series or clone.number != proforma.number) assert clone.sales_tax_percent == proforma.sales_tax_percent assert clone.sales_tax_name == proforma.sales_tax_name assert not clone.archived_customer assert not clone.archived_provider assert clone.customer == proforma.customer assert clone.provider == proforma.provider assert clone.currency == proforma.currency assert clone._last_state == clone.state assert clone.pk != proforma.pk assert clone.id != proforma.id assert not clone.pdf assert clone.proforma_entries.count() == 3 assert proforma.proforma_entries.count() == 3 entry_fields = [entry.name for entry in DocumentEntry._meta.get_fields()] for clone_entry, original_entry in zip(clone.proforma_entries.all(), proforma.proforma_entries.all()): for entry in entry_fields: if entry not in ('id', 'proforma', 'invoice'): assert getattr(clone_entry, entry) == \ getattr(original_entry, entry) assert proforma.state == Proforma.STATES.PAID
def test_proforma_total_decimal_points(self): proforma_entries = DocumentEntryFactory.create_batch(3) proforma = ProformaFactory.create(proforma_entries=proforma_entries) assert self._get_decimal_places(proforma.total) == 2
def test_invoice_total_decimal_points(self): invoice_entries = DocumentEntryFactory.create_batch(3) invoice = InvoiceFactory.create(invoice_entries=invoice_entries) assert self._get_decimal_places(invoice.total) == 2
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