def factory_create_wire_account(auth_account_id='1234', status=CfsAccountStatus.PENDING.value): """Return Factory.""" account = PaymentAccount( auth_account_id=auth_account_id, payment_method=PaymentMethod.WIRE.value, auth_account_name=f'Test {auth_account_id}').save() CfsAccount(status=status, account_id=account.id).save() return account
def test_delete_account(session, payload): """Assert that delete payment account works.""" pay_account: PaymentAccountService = PaymentAccountService.create(payload) PaymentAccountService.delete_account(payload.get('accountId')) # Try to find the account by id. pay_account = PaymentAccountService.find_by_id(pay_account.id) for cfs_account in CfsAccountModel.find_by_account_id(pay_account.id): assert cfs_account.status == CfsAccountStatus.INACTIVE.value if cfs_account else True
def _cancel_rs_invoices(cls): """Cancel routing slip invoices in CFS.""" invoices: List[InvoiceModel] = InvoiceModel.query \ .filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value) \ .filter(InvoiceModel.routing_slip is not None) \ .order_by(InvoiceModel.created_on.asc()).all() current_app.logger.info( f'Found {len(invoices)} to be cancelled in CFS.') for invoice in invoices: # call unapply rcpts # adjust invoice to zero current_app.logger.debug(f'Calling the invoice {invoice.id}') routing_slip = RoutingSlipModel.find_by_number( invoice.routing_slip) routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id( routing_slip.payment_account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( routing_slip_payment_account.id) # Find COMPLETED invoice reference; as unapply has to be done only if invoice is created and applied in CFS. invoice_reference = InvoiceReferenceModel. \ find_reference_by_invoice_id_and_status(invoice.id, status_code=InvoiceReferenceStatus.COMPLETED.value) if invoice_reference: current_app.logger.debug( f'Found invoice reference - {invoice_reference.invoice_number}' ) try: # find receipts against the invoice and unapply # apply receipt now receipts: List[ ReceiptModel] = ReceiptModel.find_all_receipts_for_invoice( invoice_id=invoice.id) for receipt in receipts: CFSService.unapply_receipt( cfs_account, receipt.receipt_number, invoice_reference.invoice_number) adjustment_negative_amount = -invoice.total CFSService.adjust_invoice( cfs_account=cfs_account, inv_number=invoice_reference.invoice_number, amount=adjustment_negative_amount) except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on canelling Routing Slip invoice: invoice id={invoice.id}, ' f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) # TODO stop execution ? what should be the invoice stats ; should we set it to error or retry? continue invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value invoice.invoice_status_code = InvoiceStatus.REFUNDED.value invoice.save()
def test_link_rs(session): """Test link routing slip.""" child_rs_number = '1234' parent_rs_number = '89799' factory_routing_slip_account(number=child_rs_number, status=CfsAccountStatus.ACTIVE.value) factory_routing_slip_account(number=parent_rs_number, status=CfsAccountStatus.ACTIVE.value) child_rs = RoutingSlipModel.find_by_number(child_rs_number) parent_rs = RoutingSlipModel.find_by_number(parent_rs_number) # Do Link child_rs.status = RoutingSlipStatus.LINKED.value child_rs.parent_number = parent_rs.number child_rs.save() payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id( child_rs.payment_account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) with patch('pay_api.services.CFSService.reverse_rs_receipt_in_cfs' ) as mock_cfs_reverse: with patch('pay_api.services.CFSService.create_cfs_receipt' ) as mock_create_cfs: RoutingSlipTask.link_routing_slips() mock_cfs_reverse.assert_called() mock_cfs_reverse.assert_called_with(cfs_account, child_rs.number) mock_create_cfs.assert_called() # child_rs = RoutingSlipModel.find_by_number(child_rs_number) # parent_rs = RoutingSlipModel.find_by_number(parent_rs_number) # PS This has changed, no longer updating child rs payment account with parent. # assert child_rs.payment_account_id == parent_rs.payment_account_id cfs_account: CfsAccountModel = CfsAccountModel.find_by_id(cfs_account.id) assert cfs_account.status == CfsAccountStatus.INACTIVE.value # make sure next invocation doesnt fetch any records with patch('pay_api.services.CFSService.reverse_rs_receipt_in_cfs' ) as mock_cfs_reverse: with patch('pay_api.services.CFSService.create_cfs_receipt' ) as mock_create_cfs: RoutingSlipTask.link_routing_slips() mock_cfs_reverse.assert_not_called() mock_create_cfs.assert_not_called()
def test_create_rs_account(session): """Test create account.""" # Create a pending account first, then call the job account = factory_routing_slip_account() CreateAccountTask.create_accounts() cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) assert cfs_account.status == CfsAccountStatus.ACTIVE.value assert cfs_account.cfs_party assert cfs_account.cfs_site assert cfs_account.cfs_account
def activate_pad_account(auth_account_id: str): """Activate the pad account.""" payment_account: PaymentAccount = PaymentAccount.find_by_auth_account_id( auth_account_id) payment_account.pad_activation_date = datetime.now() payment_account.save() cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id( payment_account.id) cfs_account.status = 'ACTIVE' cfs_account.save()
def test_unpaid_multiple_invoice_total(session): """Assert events are being sent.""" # Create an account and an invoice for the account account = factory_create_online_banking_account( auth_account_id='1', status=CfsAccountStatus.ACTIVE.value, cfs_account='1111') # Create an invoice for this account cfs_account = CfsAccountModel.find_effective_by_account_id(account.id) # invoice amount total_invoice1 = 100 total_invoice2 = 200 total_invoice3 = 300 invoice = factory_invoice( payment_account=account, created_on=datetime.now(), total=total_invoice1, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value factory_invoice(payment_account=account, created_on=datetime.now(), total=total_invoice2, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) # this is future invoice factory_invoice(payment_account=account, created_on=datetime.now() + timedelta(days=1), total=total_invoice3, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) # created two invoices ; so two events time_delay = current_app.config['NOTIFY_AFTER_DAYS'] day_after_time_delay = datetime.today() + timedelta(days=time_delay) total_amount = total_invoice1 + total_invoice2 + total_invoice3 # total amount is the same for any day invocation with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() assert mock_mailer.call_count == 1 assert mock_mailer.call_args.args[2].get( 'transactionAmount') == total_amount one_more_day_delay = datetime.today() + timedelta(days=time_delay + 1) with freeze_time(one_more_day_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() assert mock_mailer.call_count == 1 assert mock_mailer.call_args.args[2].get( 'transactionAmount') == total_amount
def factory_create_online_banking_account( auth_account_id='1234', status=CfsAccountStatus.PENDING.value, cfs_account='12356'): """Return Factory.""" account = PaymentAccount(auth_account_id=auth_account_id, payment_method=PaymentMethod.ONLINE_BANKING.value, name=f'Test {auth_account_id}').save() CfsAccount(status=status, account_id=account.id, cfs_account=cfs_account).save() return account
def test_create_pad_account_system_error(session): """Test create account.""" # Create a pending account first, then call the job account = factory_create_pad_account(auth_account_id='1') cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id( account.id) assert cfs_account.status == CfsAccountStatus.PENDING.value mock_response = requests.models.Response() mock_response.headers['CAS-Returned-Messages'] = '[CFS Down]' mock_response.status_code = 404 side_effect = HTTPError(response=mock_response) with patch.object(mailer, 'publish_mailer_events') as mock_mailer: with patch('pay_api.services.CFSService.create_cfs_account', side_effect=side_effect): CreateAccountTask.create_accounts() mock_mailer.assert_not_called() account = PaymentAccount.find_by_id(account.id) cfs_account: CfsAccount = CfsAccount.find_by_id(cfs_account.id) assert cfs_account.status == CfsAccountStatus.PENDING.value
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)
def test_create_online_banking_account(session): """Test create account.""" # Create a pending account first, then call the job account = factory_create_online_banking_account(auth_account_id='2') CreateAccountTask.create_accounts() account = PaymentAccount.find_by_id(account.id) cfs_account = CfsAccount.find_effective_by_account_id(account.id) assert cfs_account.status == CfsAccountStatus.ACTIVE.value assert not cfs_account.bank_account_number assert cfs_account.cfs_party assert cfs_account.cfs_site assert cfs_account.cfs_account assert cfs_account.payment_instrument_number is None
def test_create_pad_account(session): """Test create account.""" # Create a pending account first, then call the job account = factory_create_pad_account(auth_account_id='1') CreateAccountTask.create_accounts() account = PaymentAccount.find_by_id(account.id) cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value assert cfs_account.bank_account_number assert cfs_account.cfs_party assert cfs_account.cfs_site assert cfs_account.cfs_account assert cfs_account.payment_instrument_number
def test_create_pad_account_no_confirmation_period(session): """Test create account.Arbitrary scenario when there is no confirmation period.""" # Create a pending account first, then call the job account = factory_create_pad_account(auth_account_id='1', confirmation_period=0) CreateAccountTask.create_accounts() account = PaymentAccount.find_by_id(account.id) cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) assert cfs_account.status == CfsAccountStatus.ACTIVE.value assert cfs_account.bank_account_number assert cfs_account.cfs_party assert cfs_account.cfs_site assert cfs_account.cfs_account assert cfs_account.payment_instrument_number
def _create_single_invoice_per_purchase(cls, payment_method: PaymentMethod): """Create one CFS invoice per purchase.""" invoices: List[InvoiceModel] = InvoiceModel.query \ .filter_by(payment_method_code=payment_method.value) \ .filter_by(invoice_status_code=InvoiceStatus.CREATED.value) \ .order_by(InvoiceModel.created_on.asc()).all() current_app.logger.info(f'Found {len(invoices)} to be created in CFS.') for invoice in invoices: # Get cfs account payment_account: PaymentAccountService = PaymentAccountService.find_by_id( invoice.payment_account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) # Check for corp type and see if online banking is allowed. if invoice.payment_method_code == PaymentMethod.ONLINE_BANKING.value: corp_type: CorpTypeModel = CorpTypeModel.find_by_code( invoice.corp_type_code) if not corp_type.is_online_banking_allowed: continue # Create a CFS invoice current_app.logger.debug( f'Creating cfs invoice for invoice {invoice.id}') try: invoice_response = CFSService.create_account_invoice( transaction_number=invoice.id, line_items=invoice.payment_line_items, cfs_account=cfs_account) except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on creating Online Banking invoice: account id={payment_account.id}, ' f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue # Create invoice reference, payment record and a payment transaction InvoiceReference.create( invoice_id=invoice.id, invoice_number=invoice_response.json().get('invoice_number'), reference_number=invoice_response.json().get( 'pbc_ref_number', None)) # Misc invoice.cfs_account_id = payment_account.cfs_account_id # leave the status as SETTLEMENT_SCHEDULED invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save()
def test_update_pad_account(session): """Test update account.""" # Create a pending account first, then call the job account = factory_create_pad_account(auth_account_id='2') CreateAccountTask.create_accounts() account = PaymentAccount.find_by_id(account.id) cfs_account = CfsAccount.find_effective_by_account_id(account.id) assert cfs_account.payment_instrument_number # Now update the account. new_payment_details = { 'bankInstitutionNumber': '111', 'bankTransitNumber': '222', 'bankAccountNumber': '3333333333' } PadService().update_account(name='Test', cfs_account=cfs_account, payment_info=new_payment_details) cfs_account = CfsAccount.find_by_id(cfs_account.id) # Run the job again CreateAccountTask.create_accounts() updated_cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id( account.id) assert updated_cfs_account.id != cfs_account.id assert updated_cfs_account.bank_account_number == new_payment_details.get( 'bankAccountNumber') assert updated_cfs_account.bank_branch_number == new_payment_details.get( 'bankTransitNumber') assert updated_cfs_account.bank_number == new_payment_details.get( 'bankInstitutionNumber') assert cfs_account.status == CfsAccountStatus.INACTIVE.value assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value assert updated_cfs_account.payment_instrument_number
def test_create_pad_payment_receipt(session, client, jwt, app): """Assert payment request works for PAD accounts.""" token = jwt.create_jwt(get_claims(role=Role.SYSTEM.value), token_header) headers = { 'Authorization': f'Bearer {token}', 'content-type': 'application/json' } # Create account first rv = client.post('/api/v1/accounts', data=json.dumps( get_unlinked_pad_account_payload(account_id=1234)), headers=headers) auth_account_id = rv.json.get('accountId') # Update the payment account as ACTIVE payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id( auth_account_id) payment_account.pad_activation_date = datetime.now() payment_account.save() cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) cfs_account.status = 'ACTIVE' cfs_account.save() token = jwt.create_jwt(get_claims(), token_header) headers = { 'Authorization': f'Bearer {token}', 'content-type': 'application/json', 'Account-Id': auth_account_id } rv = client.post('/api/v1/payment-requests', data=json.dumps( get_payment_request_with_no_contact_info( corp_type='BEN', filing_type_code='BCINC', payment_method=PaymentMethod.PAD.value)), headers=headers) assert rv.json.get('paymentMethod') == PaymentMethod.PAD.value inv_id = rv.json.get('id') filing_data = { 'corpName': 'CP0001234', 'filingDateTime': 'June 27, 2019', 'fileName': 'director-change' } rv = client.post(f'/api/v1/payment-requests/{inv_id}/receipts', data=json.dumps(filing_data), headers=headers) assert rv.status_code == 201
def _validate_account(inv: InvoiceModel, row: Dict[str, str]): """Validate any mismatch in account number.""" # This should never happen, just in case cfs_account: CfsAccountModel = CfsAccountModel.find_by_id( inv.cfs_account_id) if (account_number := _get_row_value( row, Column.CUSTOMER_ACC)) != cfs_account.cfs_account: logger.error('Customer Account received as %s, but expected %s.', account_number, cfs_account.cfs_account) capture_message( f'Customer Account received as {account_number}, but expected {cfs_account.cfs_account}.', level='error') raise Exception('Invalid Account Number')
def test_patch_transaction_for_nsf_payment(session, monkeypatch): """Assert that the payment is saved to the table.""" # Create a FAILED payment (NSF), then clone the payment to create another one for CC payment # Create a transaction and assert it's success. # Patch transaction and check the status of records inv_number_1 = 'REG00001' payment_account = factory_payment_account( cfs_account_status=CfsAccountStatus.FREEZE.value, payment_method_code='PAD').save() invoice_1 = factory_invoice(payment_account, total=100) invoice_1.save() factory_payment_line_item(invoice_id=invoice_1.id, fee_schedule_id=1).save() factory_invoice_reference(invoice_1.id, invoice_number=inv_number_1).save() payment_1 = factory_payment(payment_status_code='FAILED', payment_account_id=payment_account.id, invoice_number=inv_number_1, invoice_amount=100, payment_method_code=PaymentMethod.PAD.value) payment_1.save() # Create payment for NSF payment. payment_2 = factory_payment(payment_status_code='CREATED', payment_account_id=payment_account.id, invoice_number=inv_number_1, invoice_amount=100, payment_method_code=PaymentMethod.CC.value) payment_2.save() def get_receipt(cls, payment_account, pay_response_url: str, invoice_reference): # pylint: disable=unused-argument; mocks of library methods return '1234567890', datetime.now(), 100.00 monkeypatch.setattr( 'pay_api.services.paybc_service.PaybcService.get_receipt', get_receipt) txn = PaymentTransactionService.create_transaction_for_payment( payment_2.id, get_paybc_transaction_request()) txn = PaymentTransactionService.update_transaction( txn.id, pay_response_url='receipt_number=123451') assert txn.status_code == 'COMPLETED' payment_2 = Payment.find_by_id(payment_2.id) assert payment_2.payment_status_code == 'COMPLETED' invoice_1: Invoice = Invoice.find_by_id(invoice_1.id) assert invoice_1.invoice_status_code == 'PAID' cfs_account = CfsAccount.find_effective_by_account_id(payment_account.id) assert cfs_account.status == 'ACTIVE'
def _update_pad_activation_date(cls, cfs_account: CfsAccountModel, is_sandbox: bool, payment_account: PaymentAccountModel): """Update PAD activation date.""" is_pad = payment_account.payment_method == PaymentMethod.PAD.value # If the account is created for sandbox env, then set the status to ACTIVE and set pad activation time to now if is_pad and is_sandbox: cfs_account.status = CfsAccountStatus.ACTIVE.value payment_account.pad_activation_date = datetime.now() # override payment method for since pad has 3 days wait period elif is_pad: effective_pay_method, activation_date = PaymentAccount._get_payment_based_on_pad_activation( payment_account) payment_account.pad_activation_date = activation_date payment_account.payment_method = effective_pay_method
def test_unpaid_one_invoice(session): """Assert events are being sent.""" # Create an account and an invoice for the account account = factory_create_online_banking_account( auth_account_id='1', status=CfsAccountStatus.ACTIVE.value, cfs_account='1111') # Create an invoice for this account cfs_account = CfsAccountModel.find_effective_by_account_id(account.id) invoice = factory_invoice( payment_account=account, created_on=datetime.now(), total=10, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value # invoke today ;no mail with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() mock_mailer.assert_not_called() time_delay = current_app.config['NOTIFY_AFTER_DAYS'] # invoke one day before the time delay ;shud be no mail day_after_time_delay = datetime.today() + timedelta(days=(time_delay - 1)) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() mock_mailer.assert_not_called() # exact day , mail shud be invoked day_after_time_delay = datetime.today() + timedelta(days=time_delay) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() mock_mailer.assert_called() # after the time delay day ;shud not get sent day_after_time_delay = datetime.today() + timedelta(days=time_delay + 1) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() mock_mailer.assert_not_called()
def test_unpaid_multiple_invoice(session): """Assert events are being sent.""" # Create an account and an invoice for the account account = factory_create_online_banking_account( auth_account_id='1', status=CfsAccountStatus.ACTIVE.value, cfs_account='1111') # Create an invoice for this account cfs_account = CfsAccountModel.find_effective_by_account_id(account.id) invoice = factory_invoice( payment_account=account, created_on=datetime.now(), total=10, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value factory_invoice(payment_account=account, created_on=datetime.now(), total=200, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) previous_day = datetime.now() - timedelta(days=1) factory_invoice(payment_account=account, created_on=previous_day, total=2000, payment_method_code=PaymentMethod.ONLINE_BANKING.value, cfs_account_id=cfs_account.id) # created two invoices ; so two events time_delay = current_app.config['NOTIFY_AFTER_DAYS'] day_after_time_delay = datetime.today() + timedelta(days=time_delay) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() assert mock_mailer.call_count == 1 # created one invoice yesterday ; so assert one day_after_time_delay = datetime.today() + timedelta(days=time_delay - 1) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() assert mock_mailer.call_count == 1
def run_update(pay_account_id, num_records): """Update bank info.""" current_app.logger.info( f'<<<< Running Update for account id from :{pay_account_id} and total:{num_records} >>>>' ) pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel).filter( PaymentAccountModel.payment_method == PaymentMethod.PAD.value) \ .filter(PaymentAccountModel.id >= pay_account_id) \ .order_by(PaymentAccountModel.id.asc()) \ .limit(num_records) \ .all() access_token: str = CFSService.get_token().json().get('access_token') current_app.logger.info( f'<<<< Total number of records founds: {len(pad_accounts)}') current_app.logger.info( f'<<<< records founds: {[accnt.id for accnt in pad_accounts]}') if len(pad_accounts) == 0: return for payment_account in pad_accounts: cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) current_app.logger.info( f'<<<< Running Update for account id :{payment_account.id} and cfs_account:{cfs_account.id} >>>>' ) # payment_details = get_bank_info(cfs_account.cfs_party, cfs_account.cfs_account, cfs_account.cfs_site) # current_app.logger.info(payment_details) name = re.sub(r'[^a-zA-Z0-9]+', ' ', payment_account.name) payment_info: Dict[str, any] = { 'bankInstitutionNumber': cfs_account.bank_number, 'bankTransitNumber': cfs_account.bank_branch_number, 'bankAccountNumber': cfs_account.bank_account_number, 'bankAccountName': name } save_bank_details(access_token, cfs_account.cfs_party, cfs_account.cfs_account, cfs_account.cfs_site, payment_info) current_app.logger.info( f'<<<< Successfully Updated for account id :{payment_account.id} and cfs_account:{cfs_account.id} >>>>' )
def factory_create_pad_account(auth_account_id='1234', bank_number='001', bank_branch='004', bank_account='1234567890', status=CfsAccountStatus.PENDING.value, payment_method=PaymentMethod.PAD.value, confirmation_period: int = 3): """Return Factory.""" date_after_wait_period = datetime.today() + timedelta(confirmation_period) account = PaymentAccount(auth_account_id=auth_account_id, payment_method=payment_method, pad_activation_date=date_after_wait_period, name=f'Test {auth_account_id}').save() CfsAccount(status=status, account_id=account.id, bank_number=bank_number, bank_branch_number=bank_branch, bank_account_number=bank_account).save() return account
def adjust_routing_slips(cls): """Adjust routing slips. Steps: 1. Adjust routing slip receipts for any Write off routing slips. 2. Adjust routing slip receipts for any Refund approved routing slips. """ current_app.logger.info('<<adjust_routing_slips') adjust_statuses = [RoutingSlipStatus.REFUND_AUTHORIZED.value, RoutingSlipStatus.WRITE_OFF_AUTHORIZED.value] # For any pending refund/write off balance should be more than $0 routing_slips = db.session.query(RoutingSlipModel) \ .filter(RoutingSlipModel.status.in_(adjust_statuses), RoutingSlipModel.remaining_amount > 0).all() current_app.logger.info(f'Found {len(routing_slips)} to write off or refund authorized.') for routing_slip in routing_slips: try: # 1.Adjust the routing slip and it's child routing slips for the remaining balance. current_app.logger.debug(f'Adjusting routing slip {routing_slip.number}') payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(routing_slip.payment_account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) # reverse routing slip receipt # Find all child routing slip and reverse it, as all linked routing slips are also considered as NSF. child_routing_slips: List[RoutingSlipModel] = RoutingSlipModel.find_children(routing_slip.number) for rs in (routing_slip, *child_routing_slips): receipt_number = rs.number is_refund = routing_slip.status == RoutingSlipStatus.REFUND_AUTHORIZED.value if rs.parent_number: receipt_number = f'{receipt_number}L' # Adjust the receipt to zero in CFS CFSService.adjust_receipt_to_zero(cfs_account, receipt_number, is_refund) routing_slip.refund_amount = routing_slip.remaining_amount routing_slip.remaining_amount = 0 routing_slip.save() except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on Adjusting Routing Slip for :={routing_slip.number}, ' f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue
def _sync_credit_records(): """Sync credit records with CFS.""" # 1. Get all credit records with balance > 0 # 2. If it's on account receipt call receipt endpoint and calculate balance. # 3. If it's credit memo, call credit memo endpoint and calculate balance. # 4. Roll up the credits to credit field in payment_account. active_credits: List[CreditModel] = db.session.query(CreditModel).filter( CreditModel.remaining_amount > 0).all() logger.info('Found %s credit records', len(active_credits)) account_ids: List[int] = [] for credit in active_credits: account_ids.append(credit.account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( credit.account_id) if credit.is_credit_memo: credit_memo = CFSService.get_cms(cfs_account=cfs_account, cms_number=credit.cfs_identifier) credit.remaining_amount = abs(float(credit_memo.get('amount_due'))) else: receipt = CFSService.get_receipt( cfs_account=cfs_account, receipt_number=credit.cfs_identifier) receipt_amount = float(receipt.get('receipt_amount')) applied_amount: float = 0 for invoice in receipt.get('invoices', []): applied_amount += float(invoice.get('amount_applied')) credit.remaining_amount = receipt_amount - applied_amount credit.save() # Roll up the credits and add up to credit in payment_account. for account_id in set(account_ids): account_credits: List[CreditModel] = db.session.query( CreditModel).filter(CreditModel.remaining_amount > 0).filter( CreditModel.account_id == account_id).all() credit_total: float = 0 for account_credit in account_credits: credit_total += account_credit.remaining_amount pay_account: PaymentAccountModel = PaymentAccountModel.find_by_id( account_id) pay_account.credit = credit_total pay_account.save()
def _handle_payment_details(cls, account_request, is_sandbox, pay_system, payment_account, payment_info): # pylint: disable=too-many-arguments cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) \ if payment_account.id else None if pay_system.get_payment_system_code() == PaymentSystem.PAYBC.value: if cfs_account is None: cfs_account = pay_system.create_account( # pylint:disable=assignment-from-none identifier=payment_account.auth_account_id, contact_info=account_request.get('contactInfo'), payment_info=account_request.get('paymentInfo')) if cfs_account: cfs_account.payment_account = payment_account cfs_account.flush() # If the account is PAD and bank details changed, then update bank details else: # Update details in CFS pay_system.update_account(name=payment_account.name, cfs_account=cfs_account, payment_info=payment_info) cls._update_pad_activation_date(cfs_account, is_sandbox, payment_account) elif pay_system.get_payment_system_code() == PaymentSystem.CGI.value: # if distribution code exists, put an end date as previous day and create new. dist_code_svc: DistributionCode = DistributionCode.find_active_by_account_id( payment_account.id) if dist_code_svc and dist_code_svc.distribution_code_id: end_date: datetime = datetime.now() - timedelta(days=1) dist_code_svc.end_date = end_date.date() dist_code_svc.save() # Create distribution code details. if revenue_account := payment_info.get('revenueAccount'): revenue_account.update( dict( accountId=payment_account.id, name=payment_account.name, )) DistributionCode.save_or_update(revenue_account)
def factory_create_pad_account(auth_account_id='1234', bank_number='001', bank_branch='004', bank_account='1234567890', status=CfsAccountStatus.PENDING.value, account_number='4101'): """Return Factory.""" account = PaymentAccount(auth_account_id=auth_account_id, payment_method=PaymentMethod.PAD.value, name=f'Test {auth_account_id}').save() CfsAccount( status=status, account_id=account.id, bank_number=bank_number, bank_branch_number=bank_branch, bank_account_number=bank_account, cfs_party='11111', cfs_account=account_number, cfs_site='29921', ).save() return account
def test_unpaid_invoice_pad(session): """Assert events are being sent.""" # Create an account and an invoice for the account account = factory_create_pad_account(auth_account_id='1', status=CfsAccountStatus.ACTIVE.value) # Create an invoice for this account cfs_account = CfsAccountModel.find_effective_by_account_id(account.id) invoice = factory_invoice(payment_account=account, created_on=datetime.now(), total=10, cfs_account_id=cfs_account.id) assert invoice.invoice_status_code == InvoiceStatus.CREATED.value # invoke today ;no mail time_delay = current_app.config['NOTIFY_AFTER_DAYS'] day_after_time_delay = datetime.today() + timedelta(days=time_delay) with freeze_time(day_after_time_delay): with patch.object(mailer, 'publish_mailer_events') as mock_mailer: UnpaidInvoiceNotifyTask.notify_unpaid_invoices() mock_mailer.assert_not_called()
def factory_payment_account(payment_system_code: str = 'PAYBC', payment_method_code: str = 'CC', account_number='4101', bcol_user_id='test', auth_account_id: str = '1234'): """Return Factory.""" # Create a payment account account = PaymentAccount( auth_account_id=auth_account_id, bcol_user_id=bcol_user_id, bcol_account='TEST' ).save() CfsAccount(cfs_party='11111', cfs_account=account_number, cfs_site='29921', payment_account=account).save() if payment_system_code == PaymentSystem.BCOL.value: account.payment_method = PaymentMethod.DRAWDOWN.value elif payment_system_code == PaymentSystem.PAYBC.value: account.payment_method = payment_method_code return account
def create_cfs_account(cfs_account: CfsAccountModel, pay_account: PaymentAccountModel): """Create CFS account for routing slip.""" routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_payment_account_id( pay_account.id) try: # TODO add status check so that LINKED etc can be skipped. # for RS , entity/business number=party name ; RS Number=site name cfs_account_details: Dict[str, any] = CFSService.create_cfs_account( identifier=pay_account.name, contact_info={}, site_name=routing_slip.number, is_fas=True) cfs_account.cfs_account = cfs_account_details.get('account_number') cfs_account.cfs_party = cfs_account_details.get('party_number') cfs_account.cfs_site = cfs_account_details.get('site_number') cfs_account.status = CfsAccountStatus.ACTIVE.value # Create receipt in CFS for the payment. CFSService.create_cfs_receipt( cfs_account=cfs_account, rcpt_number=routing_slip.number, rcpt_date=routing_slip.routing_slip_date.strftime('%Y-%m-%d'), amount=routing_slip.total, payment_method=pay_account.payment_method, access_token=CFSService.get_fas_token().json().get('access_token')) cfs_account.commit() return except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on creating Routing Slip CFS Account: account id={pay_account.id}, ' f'auth account : {pay_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) cfs_account.rollback() return