def test_create_pad_invoice_multiple_transactions(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) previous_day = datetime.now() - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, payment_method_code=None) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type( 'CP', 'OTANN') line = factory_payment_line_item( invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() # Create another invoice for this account invoice2 = factory_invoice(payment_account=account, created_on=previous_day, total=10, payment_method_code=None) fee_schedule2 = FeeScheduleModel.find_by_filing_type_and_corp_type( 'CP', 'OTADD') line2 = factory_payment_line_item( invoice2.id, fee_schedule_id=fee_schedule2.fee_schedule_id) line2.save() CreateInvoiceTask.create_invoices() invoice2 = InvoiceModel.find_by_id(invoice2.id) invoice = InvoiceModel.find_by_id(invoice.id) assert invoice2.invoice_status_code == invoice.invoice_status_code == InvoiceStatus.SETTLEMENT_SCHEDULED.value
def factory_invoice(payment_account: PaymentAccount, status_code: str = InvoiceStatus.CREATED.value, corp_type_code='CP', business_identifier: str = 'CP0001234', service_fees: float = 0.0, total=0, payment_method_code: str = PaymentMethod.DIRECT_PAY.value, created_on: datetime = datetime.now(), cfs_account_id: int = 0, routing_slip=None): """Return Factory.""" status_code = InvoiceStatus.APPROVED.value if payment_method_code == PaymentMethod.PAD.value else status_code invoice = Invoice(invoice_status_code=status_code, payment_account_id=payment_account.id, total=total, created_by='test', created_on=created_on, business_identifier=business_identifier, corp_type_code=corp_type_code, folio_number='1234567890', service_fees=service_fees, bcol_account=payment_account.bcol_account, payment_method_code=payment_method_code or payment_account.payment_method, routing_slip=routing_slip) if cfs_account_id != 0: invoice.cfs_account_id = cfs_account_id invoice.save() return invoice
def save_or_update(distribution_details: Dict, dist_id: int = None): """Save distribution.""" current_app.logger.debug('<save_or_update') dist_code_svc = DistributionCode() if dist_id is not None: dist_code_dao = DistributionCodeModel.find_by_id(dist_id) dist_code_svc._dao = dist_code_dao # pylint: disable=protected-access if distribution_details.get('endDate', None): dist_code_svc.end_date = parser.parse( distribution_details.get('endDate')) if distribution_details.get('startDate', None): dist_code_svc.start_date = parser.parse( distribution_details.get('startDate')) else: dist_code_svc.start_date = date.today() _has_code_changes: bool = dist_code_svc.client != distribution_details.get('client', None) \ or dist_code_svc.responsibility_centre != distribution_details.get('responsibilityCentre', None) \ or dist_code_svc.service_line != distribution_details.get('serviceLine', None) \ or dist_code_svc.project_code != distribution_details.get('projectCode', None) \ or dist_code_svc.service_fee_distribution_code_id != \ distribution_details.get('serviceFeeDistributionCodeId', None) dist_code_svc.client = distribution_details.get('client', None) dist_code_svc.name = distribution_details.get('name', None) dist_code_svc.responsibility_centre = distribution_details.get( 'responsibilityCentre', None) dist_code_svc.service_line = distribution_details.get( 'serviceLine', None) dist_code_svc.stob = distribution_details.get('stob', None) dist_code_svc.project_code = distribution_details.get( 'projectCode', None) dist_code_svc.service_fee_distribution_code_id = distribution_details.get( 'serviceFeeDistributionCodeId', None) dist_code_svc.disbursement_distribution_code_id = distribution_details.get( 'disbursementDistributionCodeId', None) dist_code_svc.account_id = distribution_details.get('accountId', None) if _has_code_changes and dist_id is not None: # Update all invoices which used this distribution for updating revenue account details # If this is a service fee distribution, then find all distribution which uses this and update them. InvoiceModel.update_invoices_for_revenue_updates(dist_id) for dist in DistributionCodeModel.find_by_service_fee_distribution_id( dist_id): InvoiceModel.update_invoices_for_revenue_updates( dist.distribution_code_id) # Reset stop jv for every dave. dist_code_svc.stop_ejv = False dist_code_dao = dist_code_svc.save() distribution_code_schema = DistributionCodeSchema() current_app.logger.debug('>save_or_update') return distribution_code_schema.dump(dist_code_dao, many=False)
def _create_nsf_invoice(cls, cfs_account: CfsAccountModel, rs_number: str, payment_account: PaymentAccountModel) -> InvoiceModel: """Create Invoice, line item and invoice reference records.""" fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(corp_type_code='BCR', filing_type_code='NSF') invoice = InvoiceModel( bcol_account=payment_account.bcol_account, payment_account_id=payment_account.id, cfs_account_id=cfs_account.id, invoice_status_code=InvoiceStatus.CREATED.value, total=fee_schedule.fee.amount, service_fees=0, paid=0, payment_method_code=PaymentMethod.INTERNAL.value, corp_type_code='BCR', created_on=datetime.now(), created_by='SYSTEM', routing_slip=rs_number ) invoice = invoice.save() distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule( fee_schedule.fee_schedule_id) line_item = PaymentLineItemModel( invoice_id=invoice.id, total=invoice.total, fee_schedule_id=fee_schedule.fee_schedule_id, description=fee_schedule.filing_type.description, filing_fees=invoice.total, gst=0, priority_fees=0, pst=0, future_effective_fees=0, line_item_status_code=LineItemStatus.ACTIVE.value, service_fees=0, fee_distribution_id=distribution.distribution_code_id) line_item.save() invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id, line_items=invoice.payment_line_items, cfs_account=cfs_account) invoice_number = invoice_response.json().get('invoice_number', None) current_app.logger.info(f'invoice_number {invoice_number} created in CFS for NSF.') InvoiceReferenceModel( invoice_id=invoice.id, invoice_number=invoice_number, reference_number=invoice_response.json().get('pbc_ref_number', None), status_code=InvoiceReferenceStatus.ACTIVE.value ).save() return invoice
def test_update_distribution(session, public_user_mock, stan_server, monkeypatch): """Assert that the invoice status is updated when the distribution is updated.""" # 1. Create a distribution code # 2. Attach a fee schedule to the distribution # 3. Create and complete payment # 4. Update the distribution and assert the invoice status is changed. distribution_code_svc = services.DistributionCode() fee_schedule = FeeSchedule.find_by_filing_type_and_corp_type('CP', 'OTANN') # Create a direct pay current_app.config['DIRECT_PAY_ENABLED'] = True payment_account = factory_payment_account( payment_method_code=PaymentMethod.DIRECT_PAY.value) payment_account.save() invoice = factory_invoice(payment_account, total=30) invoice.save() invoice_reference = factory_invoice_reference(invoice.id).save() line = factory_payment_line_item( invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() factory_payment(invoice_number=invoice_reference.invoice_number, payment_method_code=PaymentMethod.DIRECT_PAY.value, invoice_amount=30).save() distribution_id = line.fee_distribution_id distribution_code = distribution_code_svc.find_by_id(distribution_id) transaction = PaymentTransactionService.create_transaction_for_invoice( invoice.id, get_paybc_transaction_request()) def get_receipt(cls, payment_account, pay_response_url: str, invoice_reference): # pylint: disable=unused-argument; mocks of library methods return '1234567890', datetime.now(), 30.00 monkeypatch.setattr( 'pay_api.services.direct_pay_service.DirectPayService.get_receipt', get_receipt) # Update transaction without response url, which should update the receipt PaymentTransactionService.update_transaction(transaction.id, pay_response_url=None) invoice = InvoiceModel.find_by_id(invoice.id) assert invoice.invoice_status_code == InvoiceStatus.PAID.value # Update distribution code distribution_code_svc.save_or_update(distribution_code, distribution_id) invoice = InvoiceModel.find_by_id(invoice.id) assert invoice.invoice_status_code == InvoiceStatus.UPDATE_REVENUE_ACCOUNT.value
def find_by_id(identifier: int, pay_id: int = None): """Find invoice by id.""" invoice_dao = InvoiceModel.find_by_id( identifier ) if not pay_id else InvoiceModel.find_by_id_and_payment_id( identifier, pay_id) if not invoice_dao: raise BusinessException(Error.PAY012) invoice = Invoice() invoice._dao = invoice_dao # pylint: disable=protected-access current_app.logger.debug('>find_by_id') return invoice
def find_by_id(identifier: int, pay_id: int = None, skip_auth_check: bool = False): """Find invoice by id.""" invoice_dao = InvoiceModel.find_by_id(identifier) if not pay_id else InvoiceModel.find_by_id_and_payment_id( identifier, pay_id) if not invoice_dao: raise BusinessException(Error.INVALID_INVOICE_ID) if not skip_auth_check: Invoice._check_for_auth(invoice_dao) invoice = Invoice() invoice._dao = invoice_dao # pylint: disable=protected-access current_app.logger.debug('>find_by_id') return invoice
def save_or_update(distribution_details: Dict, dist_id: int = None): """Save distribution.""" current_app.logger.debug('<save_or_update') dist_code_svc = DistributionCode() if dist_id is not None: dist_code_dao = DistributionCodeModel.find_by_id(dist_id) dist_code_svc._dao = dist_code_dao # pylint: disable=protected-access if distribution_details.get('endDate', None): dist_code_svc.end_date = parser.parse( distribution_details.get('endDate')) if distribution_details.get('startDate', None): dist_code_svc.start_date = parser.parse( distribution_details.get('startDate')) else: dist_code_svc.start_date = date.today() dist_code_svc.client = distribution_details.get('client', None) dist_code_svc.responsibility_centre = distribution_details.get( 'responsibilityCentre', None) dist_code_svc.service_line = distribution_details.get( 'serviceLine', None) dist_code_svc.stob = distribution_details.get('stob', None) dist_code_svc.project_code = distribution_details.get( 'projectCode', None) dist_code_svc.service_fee_client = distribution_details.get( 'serviceFeeClient', None) dist_code_svc.service_fee_responsibility_centre = distribution_details.get( 'serviceFeeResponsibilityCentre', None) dist_code_svc.service_fee_line = distribution_details.get( 'serviceFeeLine', None) dist_code_svc.service_fee_stob = distribution_details.get( 'serviceFeeStob', None) dist_code_svc.service_fee_project_code = distribution_details.get( 'serviceFeeProjectCode', None) if dist_id is not None: # Update all invoices which used this distribution for updating revenue account details InvoiceModel.update_invoices_for_revenue_updates(dist_id) dist_code_dao = dist_code_svc.save() distribution_code_schema = DistributionCodeSchema() current_app.logger.debug('>save_or_update') return distribution_code_schema.dump(dist_code_dao, many=False)
def test_create_refund_for_paid_invoice(session, monkeypatch, payment_method, invoice_status, pay_status, has_reference, expected_inv_status): """Assert that the create refund succeeds for paid invoices.""" expected = REFUND_SUCCESS_MESSAGES[f'{payment_method}.{invoice_status}'] payment_account = factory_payment_account() payment_account.save() i = factory_invoice(payment_account=payment_account, payment_method_code=payment_method) i.save() if has_reference: inv_ref = factory_invoice_reference(i.id) inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value inv_ref.save() payment = factory_payment(invoice_number=inv_ref.invoice_number, payment_status_code=pay_status).save() factory_payment_transaction(payment_id=payment.id, status_code=TransactionStatus.COMPLETED.value).save() i.invoice_status_code = invoice_status i.save() factory_receipt(invoice_id=i.id).save() monkeypatch.setattr('pay_api.services.payment_transaction.publish_response', lambda *args, **kwargs: None) message = RefundService.create_refund(invoice_id=i.id, request={'reason': 'Test'}) i = InvoiceModel.find_by_id(i.id) assert i.invoice_status_code == expected_inv_status assert message['message'] == expected
def factory_invoice(payment: Payment, payment_account: str, status_code: str = InvoiceStatus.CREATED.value, corp_type_code='CP', business_identifier: str = 'CP0001234', service_fees: float = 0.0): """Return Factory.""" bcol_account_id = None credit_account_id = None internal_account_id = None if isinstance(payment_account, BcolPaymentAccount): bcol_account_id = payment_account.id elif isinstance(payment_account, InternalPaymentAccount): internal_account_id = payment_account.id if isinstance(payment_account, CreditPaymentAccount): credit_account_id = payment_account.id return Invoice(payment_id=payment.id, invoice_status_code=status_code, bcol_account_id=bcol_account_id, credit_account_id=credit_account_id, internal_account_id=internal_account_id, total=0, created_by='test', created_on=datetime.now(), business_identifier=business_identifier, corp_type_code=corp_type_code, folio_number='1234567890', service_fees=service_fees)
def _process_partial_paid_invoices(inv_ref: InvoiceReferenceModel, row): """Process partial payments. Update Payment as COMPLETED. Update Transaction is COMPLETED. Update Invoice as PARTIAL. """ receipt_date: datetime = datetime.strptime( _get_row_value(row, Column.APP_DATE), '%d-%b-%y') receipt_number: str = _get_row_value(row, Column.APP_ID) inv: InvoiceModel = InvoiceModel.find_by_id(inv_ref.invoice_id) _validate_account(inv, row) logger.debug('Partial Invoice. Invoice Reference ID : %s, invoice ID : %s', inv_ref.id, inv_ref.invoice_id) inv.invoice_status_code = InvoiceStatus.PARTIAL.value inv.paid = inv.total - float( _get_row_value(row, Column.TARGET_TXN_OUTSTANDING)) # Create Receipt records receipt: ReceiptModel = ReceiptModel() receipt.receipt_date = receipt_date receipt.receipt_amount = float(_get_row_value(row, Column.APP_AMOUNT)) receipt.invoice_id = inv.id receipt.receipt_number = receipt_number db.session.add(receipt)
async def _process_paid_invoices(inv_references, row): """Process PAID invoices. Update invoices as PAID Update payment as COMPLETED Update invoice_reference as COMPLETED Update payment_transaction as COMPLETED. """ receipt_date: datetime = datetime.strptime( _get_row_value(row, Column.APP_DATE), '%d-%b-%y') receipt_number: str = _get_row_value(row, Column.SOURCE_TXN_NO) for inv_ref in inv_references: inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value # Find invoice, update status inv: InvoiceModel = InvoiceModel.find_by_id(inv_ref.invoice_id) _validate_account(inv, row) logger.debug( 'PAID Invoice. Invoice Reference ID : %s, invoice ID : %s', inv_ref.id, inv_ref.invoice_id) inv.invoice_status_code = InvoiceStatus.PAID.value inv.paid = inv.total # Create Receipt records receipt: ReceiptModel = ReceiptModel() receipt.receipt_date = receipt_date receipt.receipt_amount = inv.total receipt.invoice_id = inv.id receipt.receipt_number = receipt_number db.session.add(receipt) # Publish to the queue if it's an Online Banking payment if inv.payment_method_code == PaymentMethod.ONLINE_BANKING.value: logger.debug('Publishing payment event for OB. Invoice : %s', inv.id) await _publish_payment_event(inv)
def test_create_duplicate_refund_for_paid_invoice(session, monkeypatch): """Assert that the create duplicate refund fails for paid invoices.""" payment_account = factory_payment_account() payment_account.save() i = factory_invoice(payment_account=payment_account) i.save() inv_ref = factory_invoice_reference(i.id) inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value inv_ref.save() payment = factory_payment(invoice_number=inv_ref.invoice_number).save() factory_payment_transaction(payment_id=payment.id, status_code=TransactionStatus.COMPLETED.value).save() i.invoice_status_code = InvoiceStatus.PAID.value i.save() factory_receipt(invoice_id=i.id).save() monkeypatch.setattr('pay_api.services.payment_transaction.publish_response', lambda *args, **kwargs: None) RefundService.create_refund(invoice_id=i.id, request={'reason': 'Test'}) i = InvoiceModel.find_by_id(i.id) payment: PaymentModel = PaymentModel.find_by_id(payment.id) assert i.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value assert payment.payment_status_code == PaymentStatus.REFUNDED.value with pytest.raises(Exception) as excinfo: RefundService.create_refund(invoice_id=i.id, request={'reason': 'Test'}) assert excinfo.type == BusinessException
def create_refund(cls, invoice_id: int, request: Dict[str, str], **kwargs) -> None: """Create refund.""" current_app.logger.debug('<create refund') # Do validation by looking up the invoice invoice: InvoiceModel = InvoiceModel.find_by_id(invoice_id) # Allow refund only for direct pay payments, and only if the status of invoice is PAID/UPDATE_REVENUE_ACCOUNT paid_statuses = (InvoiceStatus.PAID.value, InvoiceStatus.APPROVED.value, InvoiceStatus.UPDATE_REVENUE_ACCOUNT.value) if invoice.invoice_status_code not in paid_statuses: raise BusinessException(Error.INVALID_REQUEST) refund: RefundService = RefundService() refund.invoice_id = invoice_id refund.reason = get_str_by_path(request, 'reason') refund.requested_by = kwargs['user'].user_name refund.requested_date = datetime.now() refund.flush() cls._process_cfs_refund(invoice) # set invoice status invoice.invoice_status_code = InvoiceStatus.REFUND_REQUESTED.value invoice.refund = invoice.total # no partial refund invoice.save()
def test_line_saved_from_new(session): """Assert that the payment is saved to the table.""" payment_account = factory_payment_account() payment = factory_payment() payment_account.save() payment.save() invoice = factory_invoice(payment.id, payment_account.id) invoice.save() factory_invoice_reference(invoice.id).save() fee_schedule = FeeSchedule.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id, status='DELETED') line.save() p = PaymentLineService.find_by_id(line.id) assert p is not None assert p.id is not None assert p.invoice_id is not None assert p.filing_fees is not None assert p.fee_schedule_id is not None assert p.gst is None assert p.pst is None assert p.line_item_status_code is not None assert p.priority_fees is None assert p.future_effective_fees is None invoice = Invoice.find_by_id(invoice.id) schema = InvoiceSchema() d = schema.dump(invoice) assert d.get('id') == invoice.id assert len(d.get('line_items')) == 1
def test_create_pad_invoice_single_transaction_run_again(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) previous_day = datetime.now() - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, status_code=InvoiceStatus.APPROVED.value, payment_method_code=None) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item(invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() invoice_response = {'invoice_number': '10021', 'pbc_ref_number': '10005', 'party_number': '11111', 'party_name': 'invoice'} assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value the_response = Response() the_response._content = json.dumps(invoice_response).encode('utf-8') with patch.object(CFSService, 'create_account_invoice', return_value=the_response) as mock_cfs: CreateInvoiceTask.create_invoices() mock_cfs.assert_called() updated_invoice: InvoiceModel = InvoiceModel.find_by_id(invoice.id) inv_ref: InvoiceReferenceModel = InvoiceReferenceModel. \ find_reference_by_invoice_id_and_status(invoice.id, InvoiceReferenceStatus.ACTIVE.value) assert inv_ref assert updated_invoice.invoice_status_code == InvoiceStatus.APPROVED.value with patch.object(CFSService, 'create_account_invoice', return_value=the_response) as mock_cfs: CreateInvoiceTask.create_invoices() mock_cfs.assert_not_called()
def test_create_pad_invoice_for_frozen_accounts(session): """Assert PAD invoices are created.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.FREEZE.value) previous_day = datetime.now() - timedelta(days=1) # Create an invoice for this account invoice = factory_invoice(payment_account=account, created_on=previous_day, total=10, payment_method_code=None) fee_schedule = FeeScheduleModel.find_by_filing_type_and_corp_type( 'CP', 'OTANN') line = factory_payment_line_item( invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() assert invoice.invoice_status_code == InvoiceStatus.CREATED.value CreateInvoiceTask.create_invoices() updated_invoice: InvoiceModel = InvoiceModel.find_by_id(invoice.id) inv_ref: InvoiceReferenceModel = InvoiceReferenceModel. \ find_reference_by_invoice_id_and_status(invoice.id, InvoiceReferenceStatus.ACTIVE.value) assert inv_ref is None assert updated_invoice.invoice_status_code == InvoiceStatus.CREATED.value
def delete_account(cls, auth_account_id: str) -> PaymentAccount: """Delete the payment account.""" current_app.logger.debug('<delete_account') pay_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id( auth_account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( pay_account.id) # 1 - Check if account have any credits # 2 - Check if account have any PAD transactions done in last N (10) days. if pay_account.credit and pay_account.credit > 0: raise BusinessException(Error.OUTSTANDING_CREDIT) # Check if account is frozen. cfs_status: str = cfs_account.status if cfs_account else None if cfs_status == CfsAccountStatus.FREEZE.value: raise BusinessException(Error.FROZEN_ACCOUNT) if InvoiceModel.find_outstanding_invoices_for_account( pay_account.id, get_outstanding_txns_from_date()): # Check if there is any recent PAD transactions in N days. raise BusinessException(Error.TRANSACTIONS_IN_PROGRESS) # If CFS Account present, mark it as INACTIVE. if cfs_status and cfs_status != CfsAccountStatus.INACTIVE.value: cfs_account.status = CfsAccountStatus.INACTIVE.value # If account is active or pending pad activation stop PAD payments. if pay_account.payment_method == PaymentMethod.PAD.value \ and cfs_status in [CfsAccountStatus.ACTIVE.value, CfsAccountStatus.PENDING_PAD_ACTIVATION.value]: CFSService.suspend_cfs_account(cfs_account) cfs_account.save() if pay_account.statement_notification_enabled: pay_account.statement_notification_enabled = False pay_account.save()
def test_delete_payment(session, auth_mock, public_user_mock): """Assert that the payment records are soft deleted.""" payment_account = factory_payment_account() # payment = factory_payment() payment_account.save() # payment.save() invoice = factory_invoice(payment_account, total=10) invoice.save() invoice_reference = factory_invoice_reference(invoice.id).save() # Create a payment for this reference payment = factory_payment(invoice_number=invoice_reference.invoice_number, invoice_amount=10).save() fee_schedule = FeeSchedule.find_by_filing_type_and_corp_type('CP', 'OTANN') line = factory_payment_line_item( invoice.id, fee_schedule_id=fee_schedule.fee_schedule_id) line.save() transaction = factory_payment_transaction(payment.id) transaction.save() PaymentService.delete_invoice(invoice.id) invoice = Invoice.find_by_id(invoice.id) payment = Payment.find_by_id(payment.id) assert invoice.invoice_status_code == InvoiceStatus.DELETED.value assert payment.payment_status_code == PaymentStatus.DELETED.value
def create_refund(cls, invoice_id: int, request: Dict[str, str], **kwargs) -> Dict[str, str]: """Create refund.""" current_app.logger.debug(f'Starting refund : {invoice_id}') # Do validation by looking up the invoice invoice: InvoiceModel = InvoiceModel.find_by_id(invoice_id) paid_statuses = (InvoiceStatus.PAID.value, InvoiceStatus.APPROVED.value, InvoiceStatus.UPDATE_REVENUE_ACCOUNT.value) if invoice.invoice_status_code not in paid_statuses: current_app.logger.info( f'Cannot process refund as status of {invoice_id} is {invoice.invoice_status_code}' ) raise BusinessException(Error.INVALID_REQUEST) refund: RefundService = RefundService() refund.invoice_id = invoice_id refund.reason = get_str_by_path(request, 'reason') refund.requested_by = kwargs['user'].user_name refund.requested_date = datetime.now() refund.flush() pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( payment_method=invoice.payment_method_code) invoice_status = pay_system_service.process_cfs_refund(invoice) message = REFUND_SUCCESS_MESSAGES.get( f'{invoice.payment_method_code}.{invoice.invoice_status_code}') # set invoice status invoice.invoice_status_code = invoice_status or InvoiceStatus.REFUND_REQUESTED.value invoice.refund = invoice.total # no partial refund invoice.save() current_app.logger.debug(f'Completed refund : {invoice_id}') return {'message': message}
def factory_invoice(payment: Payment, payment_account: str, status_code: str = Status.DRAFT.value, corp_type_code='CP', business_identifier: str = 'CP0001234'): """Factory.""" bcol_account_id = None credit_account_id = None internal_account_id = None if isinstance(payment_account, BcolPaymentAccount): bcol_account_id = payment_account.id elif isinstance(payment_account, InternalPaymentAccount): internal_account_id = payment_account.id if isinstance(payment_account, CreditPaymentAccount): credit_account_id = payment_account.id return Invoice(payment_id=payment.id, invoice_status_code=status_code, bcol_account_id=bcol_account_id, credit_account_id=credit_account_id, internal_account_id=internal_account_id, total=0, created_by='test', created_on=datetime.now(), business_identifier=business_identifier, corp_type_code=corp_type_code)
def test_internal_rs_back_active(session, public_user_mock): """12033 - Scenario 2. Routing slip is complete and a transaction is cancelled the balance is restored - Should move back to Active """ payment_response = PaymentService.create_invoice( get_routing_slip_payment_request(), get_auth_staff()) account_model = PaymentAccount.find_by_auth_account_id(get_auth_staff().get('account').get('id')) account_id = account_model.id assert account_id is not None assert payment_response.get('id') is not None rs_number = '123456789' rs = factory_routing_slip(number=rs_number, payment_account_id=account_id, remaining_amount=50.00) rs.save() # Create another invoice with a routing slip. invoice = PaymentService.create_invoice(get_routing_slip_payment_request(), get_auth_staff()) account_model = PaymentAccount.find_by_auth_account_id(get_auth_staff().get('account').get('id')) assert account_id == account_model.id rs = RoutingSlipModel.find_by_number(rs_number) assert rs.remaining_amount == 0.0 assert rs.status == RoutingSlipStatus.COMPLETE.name invoice = Invoice.find_by_id(invoice['id']) InternalPayService().process_cfs_refund(invoice) assert rs.status == RoutingSlipStatus.ACTIVE.name
def factory_invoice(payment_id: str, account_id: str): """Factory.""" return Invoice(payment_id=payment_id, invoice_status_code='DRAFT', account_id=account_id, total=0, created_by='test', created_on=datetime.now())
def _create_nsf_invoice(cfs_account: CfsAccountModel, inv_number: str, payment_account: PaymentAccountModel) -> InvoiceModel: """Create Invoice, line item and invoice referwnce records.""" fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type( corp_type_code='BCR', filing_type_code='NSF') invoice = InvoiceModel(bcol_account=payment_account.bcol_account, payment_account_id=payment_account.id, cfs_account_id=cfs_account.id, invoice_status_code=InvoiceStatus.CREATED.value, total=fee_schedule.fee.amount, service_fees=0, paid=0, payment_method_code=PaymentMethod.CC.value, corp_type_code='BCR', created_on=datetime.now(), created_by='SYSTEM') invoice = invoice.save() distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule( fee_schedule.fee_schedule_id) line_item = PaymentLineItemModel( invoice_id=invoice.id, total=invoice.total, fee_schedule_id=fee_schedule.fee_schedule_id, description=fee_schedule.filing_type.description, filing_fees=invoice.total, gst=0, priority_fees=0, pst=0, future_effective_fees=0, line_item_status_code=LineItemStatus.ACTIVE.value, service_fees=0, fee_distribution_id=distribution.distribution_code_id if distribution else 1) # TODO line_item.save() inv_ref: InvoiceReferenceModel = InvoiceReferenceModel( invoice_id=invoice.id, invoice_number=inv_number, reference_number=InvoiceReferenceModel. find_any_active_reference_by_invoice_number( invoice_number=inv_number).reference_number, status_code=InvoiceReferenceStatus.ACTIVE.value) inv_ref.save() return invoice
def find_by_payment_identifier(identifier: int): """Find invoice by payment identifier.""" invoice_dao = InvoiceModel.find_by_payment_id(identifier) invoice = Invoice() invoice._dao = invoice_dao # pylint: disable=protected-access current_app.logger.debug('>find_by_id') return invoice
def create_account_payment(auth_account_id: str, is_retry_payment: bool) -> Payment: """Create a payment record for the account.""" payment: Payment = None if is_retry_payment: # If there are multiple failed payments, consolidate them # Else clone failed payment # Find all failed payments. payments = Payment.get_failed_payments(auth_account_id) can_consolidate_invoice: bool = True if len(payments) == 1: can_consolidate_invoice = False failed_payment = payments[0] else: # Here iterate the payments and see if there is a failed PARTIAL payment. for payment in payments: paid_amount = payment.paid_amount or 0 if payment.payment_status_code == PaymentStatus.FAILED.value and paid_amount > 0: failed_payment = payment can_consolidate_invoice = False break if not can_consolidate_invoice: # Find if there is a payment for the same invoice number, with status CREATED. # If yes, use that record # Else create new one. stale_payments = PaymentModel.find_payment_by_invoice_number_and_status( inv_number=failed_payment.invoice_number, payment_status=PaymentStatus.CREATED.value) # pick the first one. Ideally only one will be there, but a race condition can cause multiple. if len(stale_payments) > 0: payment = Payment._populate(stale_payments[0]) # For consolidated payment status will be CREATED, if so don't create another payment record. elif failed_payment.payment_status_code == PaymentStatus.FAILED.value: invoice_total = 0 for inv in InvoiceModel.find_invoices_for_payment( payment_id=failed_payment.id): invoice_total += inv.total payment = Payment.create( payment_method=PaymentMethod.CC.value, payment_system=PaymentSystem.PAYBC.value, invoice_number=failed_payment.invoice_number, invoice_amount=invoice_total - float(failed_payment.paid_amount or 0), payment_account_id=failed_payment.payment_account_id) else: payment = Payment._populate(failed_payment) else: # Consolidate invoices into a single payment. payment = Payment._consolidate_payments( auth_account_id, payments) current_app.logger.debug('>create_account_payment') return payment
def factory_invoice(payment_id: str, account_id: str, status_code: str = Status.DRAFT.value): """Factory.""" return Invoice( payment_id=payment_id, invoice_status_code=status_code, account_id=account_id, total=0, created_by='test', created_on=datetime.now(), )
def test_create_refund_with_existing_routing_slip(session, client, jwt, app, stan_server): """Assert that the endpoint returns 202.""" claims = get_claims(roles=[ Role.FAS_CREATE.value, Role.FAS_SEARCH.value, Role.FAS_REFUND.value, Role.STAFF.value, 'make_payment' ]) token = jwt.create_jwt(claims, token_header) headers = { 'Authorization': f'Bearer {token}', 'content-type': 'application/json' } payload = get_routing_slip_request() routingslip_amount = payload.get('payments')[0].get('paidAmount') rv = client.post('/api/v1/fas/routing-slips', data=json.dumps(payload), headers=headers) rs_number = rv.json.get('number') headers = { 'Authorization': f'Bearer {token}', 'content-type': 'application/json' } data = get_payment_request() data['accountInfo'] = {'routingSlip': rs_number} rv = client.post('/api/v1/payment-requests', data=json.dumps(data), headers=headers) inv_id = rv.json.get('id') total = rv.json.get('total') rv = client.post('/api/v1/fas/routing-slips/queries', data=json.dumps({'routingSlipNumber': rs_number}), headers=headers) items = rv.json.get('items') inv: InvoiceModel = InvoiceModel.find_by_id(inv_id) inv.invoice_status_code = InvoiceStatus.PAID.value inv.save() assert items[0].get('remainingAmount') == payload.get('payments')[0].get( 'paidAmount') - total rv = client.post(f'/api/v1/payment-requests/{inv_id}/refunds', data=json.dumps({'reason': 'Test'}), headers=headers) assert rv.status_code == 202 assert rv.json.get( 'message') == REFUND_SUCCESS_MESSAGES['INTERNAL.REFUND_REQUESTED'] rv = client.post('/api/v1/fas/routing-slips/queries', data=json.dumps({'routingSlipNumber': rs_number}), headers=headers) # asssert refund amount goes to routing slip back assert rv.json.get('items')[0].get('remainingAmount') == routingslip_amount
def find_by_payment_identifier(identifier: int, skip_auth_check: bool = False): """Find invoice by payment identifier.""" invoice_dao = InvoiceModel.find_by_payment_id(identifier) if not skip_auth_check: Invoice._check_for_auth(invoice_dao) invoice = Invoice() invoice._dao = invoice_dao # pylint: disable=protected-access current_app.logger.debug('>find_by_id') return invoice
def _process_failed_payments(row): """Handle failed payments.""" # 1. Set the cfs_account status as FREEZE. # 2. Call cfs api to Stop further PAD on this account. # 3. Reverse the invoice_reference status to ACTIVE, invoice status to SETTLEMENT_SCHED, and delete receipt. # 4. Create an NSF invoice for this account. # 5. Create invoice reference for the newly created NSF invoice. # 6. Adjust invoice in CFS to include NSF fees. inv_number = _get_row_value(row, Column.TARGET_TXN_NO) # If there is a FAILED payment record for this; it means it's a duplicate event. Ignore it. payment: PaymentModel = PaymentModel.find_payment_by_invoice_number_and_status( inv_number, PaymentStatus.FAILED.value) if payment: logger.info('Ignoring duplicate NSF message for invoice : %s ', inv_number) return # Set CFS Account Status. payment_account: PaymentAccountModel = _get_payment_account(row) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) logger.info('setting payment account id : %s status as FREEZE', payment_account.id) cfs_account.status = CfsAccountStatus.FREEZE.value # Call CFS to stop any further PAD transactions on this account. CFSService.suspend_cfs_account(cfs_account) # Find the invoice_reference for this invoice and mark it as ACTIVE. inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \ filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value). \ filter(InvoiceReferenceModel.invoice_number == inv_number). \ all() # Update status to ACTIVE, if it was marked COMPLETED for inv_reference in inv_references: inv_reference.status_code = InvoiceReferenceStatus.ACTIVE.value # Find receipt and delete it. receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number( invoice_id=inv_reference.invoice_id) if receipt: db.session.delete(receipt) # Find invoice and update the status to SETTLEMENT_SCHED invoice: InvoiceModel = InvoiceModel.find_by_id( identifier=inv_reference.invoice_id) invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.paid = 0 # Create an invoice for NSF for this account invoice = _create_nsf_invoice(cfs_account, inv_number, payment_account) # Adjust CFS invoice CFSService.add_nsf_adjustment(cfs_account=cfs_account, inv_number=inv_number, amount=invoice.total)