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_list_transactions(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create(customer=customer) transaction_1 = TransactionFactory.create(payment_method=payment_method) invoice_1 = transaction_1.invoice proforma_1 = transaction_1.proforma provider_1 = invoice_1.provider expected_t1 = OrderedDict([ ('id', unicode(transaction_1.uuid)), ('url', reverse('transaction-detail', kwargs={'customer_pk': customer.id, 'transaction_uuid': transaction_1.uuid})), ('customer', reverse('customer-detail', args=[customer.pk])), ('provider', reverse('provider-detail', args=[provider_1.pk])), ('amount', unicode(Decimal('0.00') + transaction_1.amount)), ('currency', unicode(transaction_1.currency)), ('currency_rate_date', None), ('state', unicode(transaction_1.state)), ('proforma', reverse('proforma-detail', args=[proforma_1.pk])), ('invoice', reverse('invoice-detail', args=[invoice_1.pk])), ('can_be_consumed', transaction_1.can_be_consumed), ('payment_method', reverse('payment-method-detail', kwargs={'customer_pk': customer.id, 'payment_method_id': payment_method.id})), ('pay_url', reverse('pay-transaction', kwargs={'transaction_uuid': transaction_1.uuid})), ('valid_until', None) ]) transaction_2 = TransactionFactory.create(payment_method=payment_method) invoice_2 = transaction_2.invoice proforma_2 = transaction_2.proforma provider_2 = invoice_2.provider expected_t2 = OrderedDict([ ('id', unicode(transaction_2.uuid)), ('url', reverse('transaction-detail', kwargs={'customer_pk': customer.id, 'transaction_uuid': transaction_2.uuid})), ('customer', reverse('customer-detail', args=[customer.pk])), ('provider', reverse('provider-detail', args=[provider_2.pk])), ('amount', unicode(Decimal('0.00') + transaction_2.amount)), ('currency', unicode(transaction_2.currency)), ('currency_rate_date', None), ('state', unicode(transaction_2.state)), ('proforma', reverse('proforma-detail', args=[proforma_2.pk])), ('invoice', reverse('invoice-detail', args=[invoice_2.pk])), ('can_be_consumed', transaction_2.can_be_consumed), ('payment_method', reverse('payment-method-detail', kwargs={'customer_pk': customer.id, 'payment_method_id': payment_method.id})), ('pay_url', reverse('pay-transaction', kwargs={'transaction_uuid': transaction_2.uuid})), ('valid_until', None) ]) url = reverse('transaction-list', kwargs={'customer_pk': customer.pk}) response = self.client.get(url, format='json') self.assertEqual(response.data[0], expected_t1) self.assertEqual(response.data[1], expected_t2)
def test_skip_transaction_with_unverified_payment_method(self): payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, verified=False ) TransactionFactory.create(payment_method=payment_method) mock_execute = MagicMock() with patch.multiple(TriggeredProcessor, execute_transaction=mock_execute): call_command('execute_transactions') self.assertEqual(mock_execute.call_count, 0)
def test_patch_transaction_documents(self): payment_method = PaymentMethodFactory.create( payment_processor='someprocessor' ) transaction = TransactionFactory.create(payment_method=payment_method) proforma = ProformaFactory.create() invoice = InvoiceFactory.create(proforma=proforma) proforma.invoice = invoice proforma.save() invoice_url = reverse('invoice-detail', args=[invoice.pk]) proforma_url = reverse('proforma-detail', args=[proforma.pk]) url = reverse('transaction-detail', args=[transaction.customer.pk, transaction.uuid]) data = { 'proforma': proforma_url, 'invoice': invoice_url } response = self.client.patch(url, format='json', data=data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data, { 'proforma': [u'This field may not be modified.'], 'invoice': [u'This field may not be modified.'] })
def test_patch_transaction_with_initial_status(self): payment_method = PaymentMethodFactory.create( payment_processor='someprocessor' ) transaction = TransactionFactory.create(payment_method=payment_method) url = reverse('transaction-detail', args=[transaction.customer.pk, transaction.uuid]) valid_until = timezone.now() currency_rate_date = timezone.now().date() data = { 'valid_until': valid_until, 'currency': 'RON', 'currency_rate_date': currency_rate_date, 'amount': 200 } response = self.client.patch(url, format='json', data=data) self.assertEqual(response.status_code, status.HTTP_200_OK) transaction.refresh_from_db() self.assertEqual(transaction.valid_until, valid_until) self.assertEqual(transaction.currency, 'RON') self.assertEqual(transaction.currency_rate_date, currency_rate_date) self.assertEqual(transaction.amount, 200)
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_pay_transaction_view_invalid_state(self): transaction = TransactionFactory.create(state=Transaction.States.Settled) response = self.client.get(get_payment_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(force_text(response.content), render_to_string('transactions/complete_payment.html', { 'transaction': transaction, 'document': transaction.document, }))
def test_transaction_update_status_exception_logging(self, mock_logger): payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor ) TransactionFactory.create(payment_method=payment_method, state=Transaction.States.Pending) mock_fetch_status = MagicMock() mock_fetch_status.side_effect = Exception('This happened.') with patch.multiple(TriggeredProcessor, fetch_transaction_status=mock_fetch_status): call_command('fetch_transactions_status') expected_call = call( 'Encountered exception while updating transaction with id=%s.', 1, exc_info=True ) self.assertEqual(expected_call, mock_logger.call_args)
def test_pay_transaction_view_not_consumable_transaction(self): last_year = timezone.now() - timedelta(days=365) transaction = TransactionFactory.create(state=Transaction.States.Initial, valid_until=last_year) response = self.client.get(get_payment_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(force_text(response.content), render_to_string('transactions/expired_payment.html', { 'document': transaction.document, }))
def test_cancel_action_failed_void(self): payment_method = self.create_payment_method( customer=self.customer, payment_processor=failing_void_processor ) transaction_initial = TransactionFactory.create(payment_method=payment_method) transaction_pending = TransactionFactory.create(payment_method=payment_method, state='pending') url = reverse('payment-method-action', kwargs={ 'customer_pk': self.customer.pk, 'payment_method_id': payment_method.pk, 'requested_action': 'cancel', }) response = self.client.post(url) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) expected_error = "Transaction {} couldn't be voided".format(transaction_pending.uuid) self.assertEqual(response.data, {'errors': [expected_error]})
def test_exception_logging(self, mock_logger): payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, verified=True ) TransactionFactory.create(payment_method=payment_method) mock_execute = MagicMock() mock_execute.side_effect = Exception('This happened.') with patch.multiple(TriggeredProcessor, execute_transaction=mock_execute): call_command('execute_transactions') expected_call = call( 'Encountered exception while executing transaction with id=%s.', 1, exc_info=True ) self.assertEqual(expected_call, mock_logger.call_args)
def test_create_transaction_with_not_allowed_currency(self): invoice = InvoiceFactory.create(transaction_currency='EUR', transaction_xe_rate=Decimal('1.0'), state=Invoice.STATES.ISSUED) payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor, customer=invoice.customer, canceled=False, verified=False ) expected_exception = ValidationError expected_message = 'Currency EUR is not allowed by ' \ 'the payment method. Allowed currencies are ' \ '[\'RON\', \'USD\'].' try: TransactionFactory.create(payment_method=payment_method, invoice=invoice) self.fail('{} not raised.'.format(str(expected_exception))) except expected_exception as e: self.assertTrue(expected_message in str(e))
def test_complete_payment_view_with_return_url(self): transaction = TransactionFactory.create(state=Transaction.States.Settled) return_url = 'http://home.com' complete_url = "{}?return_url={}".format(get_payment_complete_url(transaction, None), return_url) expected_url = "{}?transaction_uuid={}".format(return_url, transaction.uuid) response = self.client.get(complete_url, follow=False) self.assertRedirects(response, expected_url, fetch_redirect_response=False)
def test_pay_documents_on_transaction_settle(self): transaction = TransactionFactory.create( state=Transaction.States.Pending ) transaction.settle() transaction.save() proforma = transaction.proforma invoice = transaction.invoice self.assertEqual(proforma.state, proforma.STATES.PAID) self.assertEqual(invoice.state, invoice.STATES.PAID)
def test_pay_transaction_view_expired(self): transaction = TransactionFactory.create() with patch('silver.utils.payments.datetime') as mocked_datetime: mocked_datetime.utcnow.return_value = datetime.utcnow() - timedelta(days=365) url = get_payment_url(transaction, None) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(force_text(response.content), render_to_string('transactions/expired_payment.html', { 'document': transaction.document, }))
def test_cancel_action(self): payment_method = self.create_payment_method(customer=self.customer, payment_processor='triggered') transaction_initial = TransactionFactory.create(payment_method=payment_method) transaction_pending = TransactionFactory.create(payment_method=payment_method, state='pending') url = reverse('payment-method-action', kwargs={ 'customer_pk': self.customer.pk, 'payment_method_id': payment_method.pk, 'requested_action': 'cancel', }) response = self.client.post(url) self.assertEqual(response.status_code, status.HTTP_200_OK) payment_method.refresh_from_db() transaction_initial.refresh_from_db() transaction_pending.refresh_from_db() self.assertTrue(payment_method.canceled) self.assertEqual(transaction_initial.state, Transaction.States.Canceled) self.assertEqual(transaction_pending.state, Transaction.States.Canceled)
def test_transaction_settle_with_already_paid_invoice(self): transaction = TransactionFactory.create( state=Transaction.States.Pending, ) transaction.invoice.pay() transaction.settle() transaction.save() transaction.invoice.refresh_from_db() transaction.refresh_from_db() assert transaction.state == Transaction.States.Settled assert transaction.invoice.state == Invoice.STATES.PAID
def test_filter_payment_method(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create( payment_processor='someprocessor', customer=customer) transaction1 = TransactionFactory.create( payment_method=payment_method ) transaction_data_1 = self._transaction_data(transaction1) transaction2 = TransactionFactory.create( payment_method=payment_method ) transaction_data_2 = self._transaction_data(transaction2) 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_method_someprocessor = url + '?payment_method=someprocessor' url_no_output = url + '?payment_method=Random' response = self.client.get(url_method_someprocessor, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0], transaction_data_1) self.assertEqual(response.data[1], transaction_data_2) response = self.client.get(url_no_output, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, [])
def test_not_allowed_methods(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create(customer=customer) transaction_1 = TransactionFactory.create(payment_method=payment_method) valid_until = datetime.now() url = reverse('transaction-detail', kwargs={'customer_pk': customer.id, 'transaction_uuid': transaction_1.uuid}) data = { 'valid_until': valid_until } response = self.client.put(url, format='json', data=data) self.assertEqual(response.data['detail'], 'Method "PUT" not allowed.') response = self.client.post(url, format='json', data=data) self.assertEqual(response.data['detail'], 'Method "POST" not allowed.')
def test_pay_transaction_not_implemented_get_call(self): last_year = timezone.now() - timedelta(days=365) transaction = TransactionFactory.create(state=Transaction.States.Initial, valid_until=last_year) def get_view(processor, transaction, request): return not_implemented_view with patch('silver.tests.fixtures.ManualProcessor.get_view', new=get_view): response = self.client.get(get_payment_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(force_text(response.content), render_to_string('transactions/expired_payment.html', { 'document': transaction.document, }))
def test_pay_documents_on_transaction_settle(self): proforma = ProformaFactory.create() proforma.issue() proforma.save() invoice = proforma.create_invoice() transaction = TransactionFactory.create( state=Transaction.States.Pending, invoice=invoice, proforma=proforma ) transaction.settle() transaction.save() proforma.refresh_from_db() invoice.refresh_from_db() self.assertEqual(proforma.state, proforma.STATES.PAID) self.assertEqual(invoice.state, invoice.STATES.PAID)
def test_create_one_without_required_fields(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create(customer=customer) transaction = TransactionFactory.create(payment_method=payment_method) valid_until = datetime.now() data = { 'valid_until': valid_until } url = reverse('payment-method-transaction-list', kwargs={'customer_pk': customer.id, 'payment_method_id': payment_method.id}) response = self.client.post(url, format='json', data=data) self.assertEqual(response.data['payment_method'], ['This field is required.'])
def test_patch_after_initial_state(self): transaction = TransactionFactory.create(state=Transaction.States.Pending) data = { 'valid_until': timezone.now() } url = reverse('transaction-detail', args=[transaction.customer.pk, transaction.uuid]) response = self.client.patch(url, format='json', data=data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.data, { u'non_field_errors': [ u'The transaction cannot be modified once it is in pending state.' ] })
def test_patch_after_initial_state(self): transaction = TransactionFactory.create( state=Transaction.States.Pending) data = {'valid_until': timezone.now()} url = reverse('transaction-detail', args=[transaction.customer.pk, transaction.uuid]) response = self.client.patch(url, format='json', data=data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.data, { u'non_field_errors': [ u'The transaction cannot be modified once it is in pending state.' ] })
def test_transaction_invoice_on_transaction_settle(self): transaction = TransactionFactory.create( state=Transaction.States.Pending, invoice=None) # here transaction.proforma is an old version of itself # the actual proforma that is saved in db has a related invoice # so a refresh_from_db is needed # test_no_transaction_settle_with_only_related_proforma would be enough # if the transition callbacks would be handled in post_save transaction.settle() transaction.save() transaction.refresh_from_db() proforma = transaction.proforma invoice = transaction.invoice self.assertEqual(proforma.related_document, invoice)
def test_filter_min_max_amount(self): customer = CustomerFactory.create() payment = PaymentFactory.create(customer=customer, amount=100) payment_method_ok = PaymentMethodFactory.create( payment_processor='someprocessor', customer=customer) transaction = TransactionFactory.create( payment_method=payment_method_ok, payment=payment) transaction_data = self._transaction_data(customer, payment, payment_method_ok, transaction) urls = [ reverse('payment-method-transaction-list', kwargs={ 'customer_pk': customer.pk, 'payment_method_id': payment_method_ok.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' response = self.client.get(url_with_filterable_data, format='json') 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') 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_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_filter_min_max_amount(self): customer = CustomerFactory.create() payment_method_ok = PaymentMethodFactory.create( payment_processor='someprocessor', customer=customer) transaction = TransactionFactory.create( payment_method=payment_method_ok, amount=100 ) transaction_data = self._transaction_data(transaction) urls = [ reverse( 'payment-method-transaction-list', kwargs={ 'customer_pk': customer.pk, 'payment_method_id': payment_method_ok.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' response = self.client.get(url_with_filterable_data, format='json') 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') 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_transaction_invoice_on_transaction_settle(self): transaction = TransactionFactory.create( state=Transaction.States.Pending, invoice=None ) # here transaction.proforma is an old version of itself # the actual proforma that is saved in db has a related invoice # so a refresh_from_db is needed # test_no_transaction_settle_with_only_related_proforma would be enough # if the transition callbacks would be handled in post_save transaction.settle() transaction.save() transaction.refresh_from_db() proforma = transaction.proforma invoice = transaction.invoice self.assertEqual(proforma.related_document, invoice)
def test_get_transaction_details(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create(customer=customer) payment = PaymentFactory.create(customer=customer) transaction_1 = TransactionFactory.create( payment_method=payment_method, payment=payment) expected_t1 = OrderedDict([ ('url', reverse('transaction-detail', kwargs={ 'customer_pk': customer.pk, 'transaction_uuid': transaction_1.uuid })), ('payment_method', reverse('payment-method-detail', kwargs={ 'customer_pk': customer.pk, 'payment_method_id': payment_method.id })), ('payment', reverse('payment-detail', kwargs={ 'customer_pk': customer.pk, 'payment_pk': transaction_1.payment.pk })), ('is_usable', True), ('pay_url', reverse('pay-transaction', kwargs={'transaction_uuid': transaction_1.uuid})), ('valid_until', None), ]) url = reverse('transaction-detail', kwargs={ 'customer_pk': customer.pk, 'transaction_uuid': transaction_1.uuid }) response = self.client.get(url, format='json') self.assertEqual(response.data, dict(expected_t1))
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_patch_transaction_with_initial_status(self): payment_method = PaymentMethodFactory.create( payment_processor=triggered_processor) transaction = TransactionFactory.create(payment_method=payment_method) url = reverse('transaction-detail', args=[transaction.customer.pk, transaction.uuid]) valid_until = timezone.now().replace(microsecond=0) data = { 'valid_until': valid_until, } response = self.client.patch(url, format='json', data=data) self.assertEqual( response.status_code, status.HTTP_200_OK, "status %s, data %s" % (response.status_code, response.data)) transaction.refresh_from_db() self.assertEqual(transaction.valid_until, valid_until)
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_get_invoice(self, mocked_settings): InvoiceFactory.reset_sequence(1) TransactionFactory.reset_sequence(1) customer = CustomerFactory.create() invoice = InvoiceFactory.create(customer=customer, state=Invoice.STATES.ISSUED) invoice.generate_pdf() with mute_signals(pre_save): transactions = [ TransactionFactory.create( state=state, invoice=invoice, payment_method=PaymentMethodFactory(customer=customer) ) for state in Transaction.States.as_list() if state not in [Transaction.States.Canceled, Transaction.States.Refunded, Transaction.States.Failed] ] expected_transactions = [{ "id": str(transaction.uuid), "url": build_absolute_test_url(reverse('transaction-detail', [transaction.customer.pk, transaction.uuid])), "customer": build_absolute_test_url(reverse('customer-detail', [transaction.customer.id])), "provider": build_absolute_test_url(reverse('provider-detail', [transaction.provider.id])), "amount": "%.2f" % transaction.amount, "currency": "RON", "state": transaction.state, "proforma": build_absolute_test_url(reverse('proforma-detail', [transaction.proforma.id])), "invoice": build_absolute_test_url(reverse('invoice-detail', [transaction.invoice.id])), "can_be_consumed": transaction.can_be_consumed, "payment_processor": transaction.payment_processor, "payment_method": build_absolute_test_url(reverse('payment-method-detail', [transaction.customer.pk, transaction.payment_method.pk])), "pay_url": (build_absolute_test_url(reverse('payment', ['token'])) if transaction.state == Transaction.States.Initial else None), } for transaction in transactions] with patch('silver.utils.payments._get_jwt_token') as mocked_token: mocked_token.return_value = 'token' url = reverse('invoice-detail', kwargs={'pk': invoice.pk}) for show_pdf_storage_url, pdf_url in [ (True, build_absolute_test_url(invoice.pdf.url)), (False, build_absolute_test_url(reverse('pdf', args=[invoice.pdf.pk]))) ]: mocked_settings.SILVER_SHOW_PDF_STORAGE_URL = show_pdf_storage_url response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) expected_response = { "id": invoice.pk, "series": "InvoiceSeries", "number": 1, "provider": build_absolute_test_url(reverse('provider-detail', [invoice.provider.pk])), "customer": build_absolute_test_url(reverse('customer-detail', [invoice.customer.pk])), "archived_provider": '{}', "archived_customer": '{}', "due_date": None, "issue_date": invoice.issue_date.strftime('%Y-%m-%d'), "paid_date": None, "cancel_date": None, "sales_tax_name": "VAT", "sales_tax_percent": '1.00', "currency": "RON", "transaction_currency": invoice.transaction_currency, "transaction_xe_rate": ("%.4f" % invoice.transaction_xe_rate if invoice.transaction_xe_rate else None), "transaction_xe_date": invoice.transaction_xe_date, "state": "issued", "proforma": build_absolute_test_url(reverse('proforma-detail', [invoice.related_document.pk])), "invoice_entries": [], "pdf_url": pdf_url, "total": 0 } for field in expected_response: self.assertEqual(expected_response[field], response.data[field], msg=("Expected %s, actual %s for field %s" % ( expected_response[field], response.data[field], field))) self.assertEqual(len(response.data["transactions"]), len(expected_transactions)) for actual_transaction in response.data["transactions"]: expected_transaction = [ transaction for transaction in expected_transactions if transaction["id"] == actual_transaction["id"] ] self.assertTrue(expected_transaction) expected_transaction = expected_transaction[0] for field in expected_transaction: self.assertEqual( expected_transaction[field], actual_transaction[field], msg=("Expected %s, actual %s for field %s" % ( expected_transaction[field], actual_transaction[field], field) ) )
def test_get_invoice(self): InvoiceFactory.reset_sequence(1) TransactionFactory.reset_sequence(1) customer = CustomerFactory.create() invoice = InvoiceFactory.create(customer=customer, state=Invoice.STATES.ISSUED) with mute_signals(pre_save): transactions = [ TransactionFactory.create( state=state, invoice=invoice, payment_method=PaymentMethodFactory(customer=customer)) for state in Transaction.States.as_list() if state not in [ Transaction.States.Canceled, Transaction.States.Refunded, Transaction.States.Failed ] ] expected_transactions = [{ "id": str(transaction.uuid), "url": "http://testserver/customers/%s/transactions/%s/" % (invoice.customer.pk, transaction.uuid), "customer": "http://testserver/customers/%s/" % invoice.customer.pk, "provider": "http://testserver/providers/%s/" % invoice.provider.pk, "amount": "%s.00" % str(transaction.amount), "currency": "USD", "state": transaction.state, "proforma": "http://testserver/proformas/%s/" % transaction.proforma.pk, "invoice": "http://testserver/invoices/%s/" % transaction.invoice.pk, "can_be_consumed": transaction.can_be_consumed, "payment_processor": transaction.payment_processor, "payment_method": "http://testserver/customers/%s/payment_methods/%s/" % (invoice.customer.pk, transaction.payment_method.pk), "pay_url": "http://testserver/pay/token/" if transaction.state == Transaction.States.Initial else None, } for transaction in transactions] with patch('silver.utils.payments._get_jwt_token') as mocked_token: mocked_token.return_value = 'token' url = reverse('invoice-detail', kwargs={'pk': invoice.pk}) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) expected_response = { "id": invoice.pk, "series": "InvoiceSeries", "number": 1, "provider": "http://testserver/providers/%s/" % invoice.provider.pk, "customer": "http://testserver/customers/%s/" % invoice.customer.pk, "archived_provider": '{}', "archived_customer": '{}', "due_date": None, "issue_date": str(invoice.issue_date), "paid_date": None, "cancel_date": None, "sales_tax_name": "VAT", "sales_tax_percent": '1.00', "currency": "RON", "transaction_currency": invoice.transaction_currency, "transaction_xe_rate": ("%.4f" % invoice.transaction_xe_rate if invoice.transaction_xe_rate else None), "transaction_xe_date": invoice.transaction_xe_date, "state": "issued", "proforma": "http://testserver/proformas/%s/" % invoice.proforma.pk, "invoice_entries": [], "pdf_url": invoice.pdf.url, "total": 0 } for field in expected_response: self.assertEqual(expected_response[field], response.data[field], msg=("Expected %s, actual %s for field %s" % (expected_response[field], response.data[field], field))) self.assertEqual(len(response.data["transactions"]), len(expected_transactions)) for actual_transaction in response.data["transactions"]: expected_transaction = [ transaction for transaction in expected_transactions if transaction["id"] == actual_transaction["id"] ] self.assertTrue(expected_transaction) expected_transaction = expected_transaction[0] self.assertEqual(expected_transaction[field], actual_transaction[field], msg=("Expected %s, actual %s for field %s" % (expected_response[field], response.data[field], field)))
def test_get_invoice(self, mocked_settings): InvoiceFactory.reset_sequence(1) TransactionFactory.reset_sequence(1) customer = CustomerFactory.create() invoice = InvoiceFactory.create(customer=customer, state=Invoice.STATES.ISSUED) invoice.generate_pdf() with mute_signals(pre_save): transactions = [ TransactionFactory.create( state=state, invoice=invoice, payment_method=PaymentMethodFactory(customer=customer)) for state in Transaction.States.as_list() if state not in [ Transaction.States.Canceled, Transaction.States.Refunded, Transaction.States.Failed ] ] expected_transactions = [{ "id": str(transaction.uuid), "url": build_absolute_test_url( reverse('transaction-detail', [transaction.customer.pk, transaction.uuid])), "customer": build_absolute_test_url( reverse('customer-detail', [transaction.customer.id])), "provider": build_absolute_test_url( reverse('provider-detail', [transaction.provider.id])), "amount": "%.2f" % transaction.amount, "currency": "RON", "state": transaction.state, "proforma": build_absolute_test_url( reverse('proforma-detail', [transaction.proforma.id])), "invoice": build_absolute_test_url( reverse('invoice-detail', [transaction.invoice.id])), "can_be_consumed": transaction.can_be_consumed, "payment_processor": transaction.payment_processor, "payment_method": build_absolute_test_url( reverse( 'payment-method-detail', [transaction.customer.pk, transaction.payment_method.pk])), "pay_url": (build_absolute_test_url(reverse('payment', ['token'])) if transaction.state == Transaction.States.Initial else None), } for transaction in transactions] with patch('silver.utils.payments._get_jwt_token') as mocked_token: mocked_token.return_value = 'token' url = reverse('invoice-detail', kwargs={'pk': invoice.pk}) for show_pdf_storage_url, pdf_url in [ (True, build_absolute_test_url(invoice.pdf.url)), (False, build_absolute_test_url(reverse('pdf', args=[invoice.pdf.pk]))) ]: mocked_settings.SILVER_SHOW_PDF_STORAGE_URL = show_pdf_storage_url response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) expected_response = { "id": invoice.pk, "series": "InvoiceSeries", "number": 1, "provider": build_absolute_test_url( reverse('provider-detail', [invoice.provider.pk])), "customer": build_absolute_test_url( reverse('customer-detail', [invoice.customer.pk])), "archived_provider": '{}', "archived_customer": '{}', "due_date": None, "issue_date": invoice.issue_date.strftime('%Y-%m-%d'), "paid_date": None, "cancel_date": None, "sales_tax_name": "VAT", "sales_tax_percent": '1.00', "currency": "RON", "transaction_currency": invoice.transaction_currency, "transaction_xe_rate": ("%.4f" % invoice.transaction_xe_rate if invoice.transaction_xe_rate else None), "transaction_xe_date": invoice.transaction_xe_date, "state": "issued", "proforma": build_absolute_test_url( reverse('proforma-detail', [invoice.related_document.pk])), "invoice_entries": [], "pdf_url": pdf_url, "total": 0 } for field in expected_response: self.assertEqual( expected_response[field], response.data[field], msg=("Expected %s, actual %s for field %s" % (expected_response[field], response.data[field], field))) self.assertEqual(len(response.data["transactions"]), len(expected_transactions)) for actual_transaction in response.data["transactions"]: expected_transaction = [ transaction for transaction in expected_transactions if transaction["id"] == actual_transaction["id"] ] self.assertTrue(expected_transaction) expected_transaction = expected_transaction[0] for field in expected_transaction: self.assertEqual( expected_transaction[field], actual_transaction[field], msg=("Expected %s, actual %s for field %s" % (expected_transaction[field], actual_transaction[field], field)))
def test_list_transactions(self): customer = CustomerFactory.create() payment_method = PaymentMethodFactory.create(customer=customer) transaction_1 = TransactionFactory.create( payment_method=payment_method) invoice_1 = transaction_1.invoice proforma_1 = transaction_1.proforma provider_1 = invoice_1.provider expected_t1 = OrderedDict([ ('id', unicode(transaction_1.uuid)), ('url', reverse('transaction-detail', kwargs={ 'customer_pk': customer.id, 'transaction_uuid': transaction_1.uuid })), ('customer', reverse('customer-detail', args=[customer.pk])), ('provider', reverse('provider-detail', args=[provider_1.pk])), ('amount', unicode(Decimal('0.00') + transaction_1.amount)), ('currency', unicode(transaction_1.currency)), ('currency_rate_date', None), ('state', unicode(transaction_1.state)), ('proforma', reverse('proforma-detail', args=[proforma_1.pk])), ('invoice', reverse('invoice-detail', args=[invoice_1.pk])), ('can_be_consumed', transaction_1.can_be_consumed), ('payment_method', reverse('payment-method-detail', kwargs={ 'customer_pk': customer.id, 'payment_method_id': payment_method.id })), ('pay_url', reverse('pay-transaction', kwargs={'transaction_uuid': transaction_1.uuid})), ('valid_until', None) ]) transaction_2 = TransactionFactory.create( payment_method=payment_method) invoice_2 = transaction_2.invoice proforma_2 = transaction_2.proforma provider_2 = invoice_2.provider expected_t2 = OrderedDict([ ('id', unicode(transaction_2.uuid)), ('url', reverse('transaction-detail', kwargs={ 'customer_pk': customer.id, 'transaction_uuid': transaction_2.uuid })), ('customer', reverse('customer-detail', args=[customer.pk])), ('provider', reverse('provider-detail', args=[provider_2.pk])), ('amount', unicode(Decimal('0.00') + transaction_2.amount)), ('currency', unicode(transaction_2.currency)), ('currency_rate_date', None), ('state', unicode(transaction_2.state)), ('proforma', reverse('proforma-detail', args=[proforma_2.pk])), ('invoice', reverse('invoice-detail', args=[invoice_2.pk])), ('can_be_consumed', transaction_2.can_be_consumed), ('payment_method', reverse('payment-method-detail', kwargs={ 'customer_pk': customer.id, 'payment_method_id': payment_method.id })), ('pay_url', reverse('pay-transaction', kwargs={'transaction_uuid': transaction_2.uuid})), ('valid_until', None) ]) url = reverse('transaction-list', kwargs={'customer_pk': customer.pk}) response = self.client.get(url, format='json') self.assertEqual(response.data[0], expected_t1) self.assertEqual(response.data[1], expected_t2)
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_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
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)