diff --git a/jobs/payment-jobs/services/routing_slip.py b/jobs/payment-jobs/services/routing_slip.py index d6b8826e9..2a89292ba 100644 --- a/jobs/payment-jobs/services/routing_slip.py +++ b/jobs/payment-jobs/services/routing_slip.py @@ -30,7 +30,6 @@ def create_cfs_account(cfs_account: CfsAccountModel, pay_account: PaymentAccount 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={}, @@ -41,7 +40,7 @@ def create_cfs_account(cfs_account: CfsAccountModel, pay_account: PaymentAccount 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. + # for RS , entity/business number=party name ; RS Number=site name CFSService.create_cfs_receipt(cfs_account=cfs_account, rcpt_number=routing_slip.number, rcpt_date=routing_slip.routing_slip_date.strftime('%Y-%m-%d'), diff --git a/jobs/payment-jobs/tasks/activate_pad_account_task.py b/jobs/payment-jobs/tasks/activate_pad_account_task.py index 2659006aa..e10b89d83 100644 --- a/jobs/payment-jobs/tasks/activate_pad_account_task.py +++ b/jobs/payment-jobs/tasks/activate_pad_account_task.py @@ -36,7 +36,7 @@ def activate_pad_accounts(cls): 1. Find all accounts with pending PAD account activation status. 2. Activate them. """ - pending_pad_activation_accounts: List[CfsAccountModel] = CfsAccountModel.find_all_accounts_with_status( + pending_pad_activation_accounts = CfsAccountModel.find_all_accounts_with_status( status=CfsAccountStatus.PENDING_PAD_ACTIVATION.value) current_app.logger.info( f'Found {len(pending_pad_activation_accounts)} CFS Accounts to be pending PAD activation.') @@ -54,7 +54,7 @@ def activate_pad_accounts(cls): if is_activation_period_over: pending_account.status = CfsAccountStatus.ACTIVE.value pending_account.save() - # If account was in BCOL , change it to PAD + # If account was in another payment method, update it to pad if pay_account.payment_method != PaymentMethod.PAD.value: pay_account.payment_method = PaymentMethod.PAD.value pay_account.save() diff --git a/jobs/payment-jobs/tasks/cfs_bank_name_updater.py b/jobs/payment-jobs/tasks/cfs_bank_name_updater.py index 14b58da3a..f8a49a8f4 100644 --- a/jobs/payment-jobs/tasks/cfs_bank_name_updater.py +++ b/jobs/payment-jobs/tasks/cfs_bank_name_updater.py @@ -85,7 +85,7 @@ def run_update(pay_account_id, num_records): return for payment_account in pad_accounts: - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.PAD.value) 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) diff --git a/jobs/payment-jobs/tasks/cfs_create_account_task.py b/jobs/payment-jobs/tasks/cfs_create_account_task.py index 963970baf..21e03ca38 100644 --- a/jobs/payment-jobs/tasks/cfs_create_account_task.py +++ b/jobs/payment-jobs/tasks/cfs_create_account_task.py @@ -43,7 +43,7 @@ def create_accounts(cls): # pylint: disable=too-many-locals 3. Publish a message to the queue if successful. """ # Pass payment method if offline account creation has be restricted based on payment method. - pending_accounts: List[CfsAccountModel] = CfsAccountModel.find_all_pending_accounts() + pending_accounts = CfsAccountModel.find_all_pending_accounts() current_app.logger.info(f'Found {len(pending_accounts)} CFS Accounts to be created.') if len(pending_accounts) == 0: return @@ -128,7 +128,6 @@ def _create_cfs_account(cls, pending_account: CfsAccountModel, pay_account: Paym pending_account.cfs_party = cfs_account_details.get('party_number') except Exception as e: # NOQA # pylint: disable=broad-except - # publish to mailer queue. is_user_error = False if pay_account.payment_method == PaymentMethod.PAD.value: is_user_error = CreateAccountTask._check_user_error(e.response) # pylint: disable=no-member @@ -146,8 +145,7 @@ def _create_cfs_account(cls, pending_account: CfsAccountModel, pay_account: Paym pending_account.save() return - # If the account has an activation time set , - # before that it shud be set to the PENDING_PAD_ACTIVATION status. + # If the account has an activation time set it should have PENDING_PAD_ACTIVATION status. is_account_in_pad_confirmation_period = pay_account.pad_activation_date is not None and \ pay_account.pad_activation_date > datetime.today() pending_account.status = CfsAccountStatus.PENDING_PAD_ACTIVATION.value if \ diff --git a/jobs/payment-jobs/tasks/cfs_create_invoice_task.py b/jobs/payment-jobs/tasks/cfs_create_invoice_task.py index 6a2a4eba6..89218afb2 100644 --- a/jobs/payment-jobs/tasks/cfs_create_invoice_task.py +++ b/jobs/payment-jobs/tasks/cfs_create_invoice_task.py @@ -70,7 +70,6 @@ def create_invoices(cls): cls._create_online_banking_invoices() current_app.logger.info('>> Done Online Banking Invoice Creation') - # Cancel invoice is the only non-creation of invoice in this job. current_app.logger.info('<< Starting CANCEL Routing Slip Invoices') cls._cancel_rs_invoices() current_app.logger.info('>> Done CANCEL Routing Slip Invoices') @@ -91,27 +90,24 @@ def _cancel_rs_invoices(cls): 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method( + routing_slip_payment_account.id, PaymentMethod.INTERNAL.value) # Find COMPLETED invoice reference; as unapply has to be done only if invoice is created and applied in CFS. invoice_reference = InvoiceReferenceModel. \ find_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) + # Adjust to zero: -invoice.total + invoice.total = 0 adjustment_negative_amount = -invoice.total CFSService.adjust_invoice(cfs_account=cfs_account, inv_number=invoice_reference.invoice_number, @@ -145,6 +141,7 @@ def _create_rs_invoices(cls): # pylint: disable=too-many-locals .filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \ .filter(CfsAccountModel.status.in_([CfsAccountStatus.ACTIVE.value, CfsAccountStatus.FREEZE.value])) \ + .filter(CfsAccountModel.payment_method == PaymentMethod.INTERNAL.value) \ .filter(InvoiceModel.routing_slip is not None) \ .order_by(InvoiceModel.created_on.asc()).all() @@ -162,7 +159,8 @@ def _create_rs_invoices(cls): # pylint: disable=too-many-locals routing_slip.payment_account_id) # apply invoice to the active CFS_ACCOUNT which will be the parent routing slip - active_cfs_account = CfsAccountModel.find_effective_by_account_id(routing_slip_payment_account.id) + active_cfs_account = CfsAccountModel.find_effective_by_payment_method(routing_slip_payment_account.id, + PaymentMethod.INTERNAL.value) try: invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id, @@ -231,22 +229,19 @@ def _create_rs_invoices(cls): # pylint: disable=too-many-locals @classmethod def _create_pad_invoices(cls): # pylint: disable=too-many-locals """Create PAD invoices in to CFS system.""" - # Find all accounts which have done a transaction with PAD transactions - inv_subquery = db.session.query(InvoiceModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value).subquery() - # Exclude the accounts which are in FREEZE state. pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \ + .filter(CfsAccountModel.payment_method == PaymentMethod.PAD.value) \ .filter(PaymentAccountModel.id.in_(select(inv_subquery))).all() current_app.logger.info(f'Found {len(pad_accounts)} with PAD transactions.') for account in pad_accounts: - # Find all PAD invoices for this account account_invoices = db.session.query(InvoiceModel) \ .filter(InvoiceModel.payment_account_id == account.id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ @@ -261,21 +256,13 @@ def _create_pad_invoices(cls): # pylint: disable=too-many-locals current_app.logger.debug( f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}') - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) - if cfs_account is None: - # Get the last cfs_account for it, as the account might have got upgraded from PAD to DRAWDOWN. - cfs_account: CfsAccountModel = CfsAccountModel.query.\ - filter(CfsAccountModel.account_id == payment_account.id).order_by(CfsAccountModel.id.desc()).first() - - # If the CFS Account status is not ACTIVE or INACTIVE (for above case), raise error and continue + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id, + PaymentMethod.PAD.value) if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value): current_app.logger.info(f'CFS status for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status} skipping.') continue - if not cls._verify_and_correct_receipt_method(cfs_account, payment_account, PaymentMethod.PAD.value): - continue - lines = [] invoice_total = Decimal('0') for invoice in account_invoices: @@ -348,6 +335,7 @@ def _return_eft_accounts(cls): eft_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \ + .filter(CfsAccountModel.payment_method == PaymentMethod.EFT.value) \ .filter(PaymentAccountModel.id.in_(select(invoice_subquery))).all() current_app.logger.info(f'Found {len(eft_accounts)} with EFT transactions.') @@ -398,22 +386,13 @@ def _create_eft_invoices(cls): current_app.logger.debug( f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}') - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) - - # If no CFS account then the payment method might have changed from EFT to DRAWDOWN - if not cfs_account: - cfs_account: CfsAccountModel = CfsAccountModel.query.\ - filter(CfsAccountModel.account_id == payment_account.id).order_by(CfsAccountModel.id.desc()).first() - - # If CFS account is not ACTIVE or INACTIVE (for above case), raise error and continue + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id, + PaymentMethod.EFT.value) if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value): current_app.logger.info(f'CFS status for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status} skipping.') continue - if not cls._verify_and_correct_receipt_method(cfs_account, payment_account, PaymentMethod.EFT.value): - continue - lines = [] invoice_total = Decimal('0') for invoice in account_invoices: @@ -482,16 +461,15 @@ def _create_single_invoice_per_purchase(cls, payment_method: PaymentMethod): current_app.logger.info(f'Found {len(invoices)} to be created in CFS.') for invoice in invoices: payment_account: PaymentAccountService = PaymentAccountService.find_by_id(invoice.payment_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) + # Adding this in for the future when we can switch between BCOL and ONLINE_BANKING. + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id, + PaymentMethod.ONLINE_BANKING.value) 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 - if not cls._verify_and_correct_receipt_method(cfs_account, payment_account, PaymentMethod.ONLINE_BANKING.value): - continue - current_app.logger.debug(f'Creating cfs invoice for invoice {invoice.id}') try: invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id, @@ -513,34 +491,3 @@ def _create_single_invoice_per_purchase(cls, payment_method: PaymentMethod): invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save() - @classmethod - def _verify_and_correct_receipt_method(cls, cfs_account, payment_account, payment_method: str): - """Verify and correct the receipt method site.""" - try: - current_receipt_method = CFSService.get_site(cfs_account).get('receipt_method', None) - if current_receipt_method == RECEIPT_METHOD_PAD_STOP: - current_app.logger.error(f'Skipping the account as the receipt method is {RECEIPT_METHOD_PAD_STOP},' - ' database is out of sync with CAS.') - return False - - match payment_method: - case PaymentMethod.EFT.value: - new_receipt_method = CFS_RCPT_EFT_WIRE - case PaymentMethod.ONLINE_BANKING.value: - # According to the spec it should be "BCR Online Banking Payments", but in practice we use null. - new_receipt_method = None - case PaymentMethod.PAD.value: - new_receipt_method = RECEIPT_METHOD_PAD_DAILY - case _: - current_app.logging.error(f'Site switching for {payment_method} is not implemented.') - return False - - if current_receipt_method != new_receipt_method: - current_app.logger.info('Switching site receipt_method from %s to %s', - current_receipt_method, new_receipt_method) - CFSService.update_site_receipt_method(cfs_account, receipt_method=new_receipt_method) - except Exception as e: # NO QA # pylint: disable=broad-except - capture_message(f'Error switching site for account id={cfs_account.account_id}, ' - f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') - return False - return True diff --git a/jobs/payment-jobs/tasks/electronic_funds_transfer_task.py b/jobs/payment-jobs/tasks/electronic_funds_transfer_task.py index 723d3ddfa..a0037d4c1 100644 --- a/jobs/payment-jobs/tasks/electronic_funds_transfer_task.py +++ b/jobs/payment-jobs/tasks/electronic_funds_transfer_task.py @@ -32,7 +32,8 @@ from pay_api.services.cfs_service import CFSService from pay_api.services.receipt import Receipt from pay_api.utils.enums import ( - CfsAccountStatus, EFTShortnameStatus, InvoiceReferenceStatus, InvoiceStatus, PaymentSystem, ReverseOperation) + CfsAccountStatus, EFTShortnameStatus, InvoiceReferenceStatus, InvoiceStatus, PaymentMethod, PaymentSystem, + ReverseOperation) from sentry_sdk import capture_message @@ -64,8 +65,9 @@ def link_electronic_funds_transfers(cls): current_app.logger.debug(f'Linking Electronic Funds Transfer: {eft_short_name.id}') payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id( eft_short_name.auth_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) - eft_credit: EFTCreditModel = EFTCreditModel.find_by_payment_account_id(payment_account.id) + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id, + PaymentMethod.EFT.value) + eft_credit = EFTCreditModel.find_by_payment_account_id(payment_account.id) payment = db.session.query(PaymentModel) \ .join(PaymentAccountModel, PaymentAccountModel.id == PaymentModel.payment_account_id) \ @@ -108,8 +110,9 @@ def unlink_electronic_funds_transfers(cls): current_app.logger.debug(f'Unlinking Electronic Funds Transfer: {eft_short_name.id}') payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id( eft_short_name.auth_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) - eft_credit: EFTCreditModel = EFTCreditModel.find_by_payment_account_id(payment_account.id) + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id, + PaymentMethod.EFT.value) + eft_credit = EFTCreditModel.find_by_payment_account_id(payment_account.id) payment = db.session.query(PaymentModel) \ .join(PaymentAccountModel, PaymentAccountModel.id == PaymentModel.payment_account_id) \ @@ -154,7 +157,8 @@ def _get_eft_short_names_by_status(cls, status: str) -> List[EFTShortNamesModel] .join(EFTShortnameLinksModel, EFTShortnameLinksModel.eft_short_name_id == EFTShortNamesModel.id) \ .join(PaymentAccountModel, PaymentAccountModel.auth_account_id == EFTShortnameLinksModel.auth_account_id) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ - .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value) + .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value) \ + .filter(CfsAccountModel.payment_method == PaymentMethod.EFT.value) if status == EFTShortnameStatus.LINKED.value: query = query.filter(EFTShortnameLinksModel.status_code == status) @@ -181,10 +185,8 @@ def _apply_electronic_funds_transfers_to_pending_invoices(cls, payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id( eft_short_name.auth_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) - - invoices: List[InvoiceModel] = EFTShortNamesService.get_invoices_owing( - eft_short_name.auth_account_id) + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id, PaymentMethod.EFT) + invoices = EFTShortNamesService.get_invoices_owing(eft_short_name.auth_account_id) current_app.logger.info(f'Found {len(invoices)} to apply receipt') applied_amount = 0 diff --git a/jobs/payment-jobs/tasks/routing_slip_task.py b/jobs/payment-jobs/tasks/routing_slip_task.py index 3e0d7ecab..dc9fb7dc0 100644 --- a/jobs/payment-jobs/tasks/routing_slip_task.py +++ b/jobs/payment-jobs/tasks/routing_slip_task.py @@ -32,7 +32,7 @@ from pay_api.services.receipt import Receipt from pay_api.utils.enums import ( CfsAccountStatus, CfsReceiptStatus, InvoiceReferenceStatus, InvoiceStatus, LineItemStatus, PaymentMethod, - PaymentStatus, ReverseOperation, RoutingSlipStatus) + PaymentStatus, PaymentSystem, ReverseOperation, RoutingSlipStatus) from sentry_sdk import capture_message @@ -57,8 +57,7 @@ def link_routing_slips(cls): current_app.logger.debug(f'Linking 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.INTERNAL.value) # reverse routing slip receipt if CFSService.get_receipt(cfs_account, routing_slip.number).get('status') != CfsReceiptStatus.REV.value: @@ -69,8 +68,8 @@ def link_routing_slips(cls): parent_rs: RoutingSlipModel = RoutingSlipModel.find_by_number(routing_slip.parent_number) parent_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id( parent_rs.payment_account_id) - parent_cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( - parent_payment_account.id) + parent_cfs_account = CfsAccountModel.find_effective_by_payment_method( + parent_payment_account.id, PaymentMethod.INTERNAL.value) # For linked routing slip receipts, append 'L' to the number to avoid duplicate error receipt_number = routing_slip.generate_cas_receipt_number() CFSService.create_cfs_receipt(cfs_account=parent_cfs_account, @@ -124,7 +123,7 @@ def process_correction(cls): continue current_app.logger.debug(f'Correcting Routing Slip: {rs.number}') payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(rs.payment_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.INTERNAL.value) CFSService.reverse_rs_receipt_in_cfs(cfs_account, rs.generate_cas_receipt_number(), ReverseOperation.CORRECTION.value) @@ -177,7 +176,7 @@ def process_void(cls): raise Exception('VOID - has transactions/invoices.') # pylint: disable=broad-exception-raised payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(routing_slip.payment_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.INTERNAL.value) # Reverse all child routing slips, as all linked routing slips are also considered as VOID. child_routing_slips: List[RoutingSlipModel] = RoutingSlipModel.find_children(routing_slip.number) @@ -215,7 +214,7 @@ def process_nsf(cls): try: current_app.logger.debug(f'Reverse receipt {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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.INTERNAL.value) # 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) @@ -262,7 +261,7 @@ def adjust_routing_slips(cls): # 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.INTERNAL.value) # reverse routing slip receipt # Find all child routing slip and reverse it, as all linked routing slips are also considered as NSF. @@ -292,6 +291,7 @@ def _get_routing_slip_by_status(cls, status: RoutingSlipStatus) -> List[RoutingS .join(PaymentAccountModel, PaymentAccountModel.id == RoutingSlipModel.payment_account_id) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ .filter(RoutingSlipModel.status == status) \ + .filter(CfsAccountModel.payment_method == PaymentMethod.INTERNAL.value) \ .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value).all() @classmethod @@ -375,7 +375,7 @@ def _apply_routing_slips_to_pending_invoices(cls, routing_slip: RoutingSlipModel routing_slip.payment_account_id) # apply invoice to the active CFS_ACCOUNT which will be the parent routing slip - active_cfs_account = CfsAccountModel.find_effective_by_account_id(routing_slip_payment_account.id) + active_cfs_account = CfsAccountModel.find_effective_by_payment_method(routing_slip_payment_account.id, PaymentMethod.INTERNAL.value) invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \ .filter(InvoiceModel.routing_slip == routing_slip.number, diff --git a/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py b/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py index 200f792c6..8aa7cba21 100644 --- a/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py +++ b/jobs/payment-jobs/tasks/unpaid_invoice_notify_task.py @@ -70,7 +70,8 @@ def _notify_for_ob(cls): # pylint: disable=too-many-locals pay_account: PaymentAccountModel = \ PaymentAccountModel.find_by_id(payment_account_id) - cfs_account = CfsAccountModel.find_effective_by_account_id(payment_account_id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account_id, + PaymentMethod.ONLINE_BANKING.value) # emit account mailer event addition_params_to_mailer = {'transactionAmount': float(total[0][0]), diff --git a/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py b/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py index b78322566..3394472eb 100644 --- a/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py +++ b/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py @@ -41,12 +41,12 @@ def test_activate_pad_accounts_with_time_check(session): account = factory_create_pad_account(auth_account_id='1') CreateAccountTask.create_accounts() account: PaymentAccount = PaymentAccount.find_by_id(account.id) - cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value, 'Created account has pending pad status' assert account.payment_method == PaymentMethod.PAD.value ActivatePadAccountTask.activate_pad_accounts() - cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value, \ 'Same day Job runs and shouldnt change anything.' @@ -54,7 +54,7 @@ def test_activate_pad_accounts_with_time_check(session): with freeze_time(datetime.today() + timedelta(days=time_delay, minutes=1)): ActivatePadAccountTask.activate_pad_accounts() account: PaymentAccount = PaymentAccount.find_by_id(account.id) - cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.ACTIVE.value, \ 'After the confirmation period is over , status should be active' assert account.payment_method == PaymentMethod.PAD.value @@ -66,7 +66,7 @@ def test_activate_bcol_change_to_pad(session): account = factory_create_pad_account(auth_account_id='1', payment_method=PaymentMethod.DRAWDOWN.value) CreateAccountTask.create_accounts() account = PaymentAccount.find_by_id(account.id) - cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) + cfs_account: CfsAccount = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value, 'Created account has pending pad status' assert account.payment_method == PaymentMethod.DRAWDOWN.value diff --git a/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py b/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py index 6c551f1ec..03c42dfd2 100644 --- a/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py +++ b/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py @@ -22,7 +22,7 @@ from pay_api.models import CfsAccount, PaymentAccount from pay_api.services.online_banking_service import OnlineBankingService from pay_api.services.pad_service import PadService -from pay_api.utils.enums import CfsAccountStatus +from pay_api.utils.enums import CfsAccountStatus, PaymentMethod from requests.exceptions import HTTPError from tasks.cfs_create_account_task import CreateAccountTask @@ -43,7 +43,7 @@ def test_create_pad_account(session): 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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value assert cfs_account.bank_account_number assert cfs_account.cfs_party @@ -58,7 +58,7 @@ def test_create_eft_account(session): account = factory_create_eft_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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.EFT.value) assert cfs_account.status == CfsAccountStatus.ACTIVE.value assert cfs_account.payment_instrument_number is None @@ -67,7 +67,7 @@ def test_create_pad_account_user_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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING.value mock_response = requests.models.Response() mock_response.headers['CAS-Returned-Messages'] = '[Errors = [34] Bank Account Number is Invalid]' @@ -88,7 +88,7 @@ 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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING.value mock_response = requests.models.Response() mock_response.headers['CAS-Returned-Messages'] = '[CFS Down]' @@ -111,7 +111,7 @@ def test_create_pad_account_no_confirmation_period(session): 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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.ACTIVE.value assert cfs_account.bank_account_number assert cfs_account.cfs_party @@ -126,7 +126,7 @@ def test_create_online_banking_account(session): 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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) assert cfs_account.status == CfsAccountStatus.ACTIVE.value assert not cfs_account.bank_account_number assert cfs_account.cfs_party @@ -141,11 +141,11 @@ def test_update_online_banking_account(session): 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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) # Update account, which shouldn't change any details OnlineBankingService().update_account(name='Test', cfs_account=cfs_account, payment_info=None) - updated_cfs_account = CfsAccount.find_effective_by_account_id(account.id) + updated_cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value assert cfs_account.id == updated_cfs_account.id @@ -158,7 +158,7 @@ def test_update_pad_account(session): CreateAccountTask.create_accounts() account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_effective_by_account_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.payment_instrument_number @@ -174,7 +174,7 @@ def test_update_pad_account(session): # Run the job again CreateAccountTask.create_accounts() - updated_cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(account.id) + updated_cfs_account: CfsAccount = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) 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') diff --git a/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py b/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py index 53d43a781..8340835ad 100644 --- a/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py +++ b/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py @@ -18,7 +18,7 @@ """ from pay_api.models import CfsAccount -from pay_api.utils.enums import CfsAccountStatus +from pay_api.utils.enums import CfsAccountStatus, PaymentMethod from tasks.cfs_create_account_task import CreateAccountTask from .factory import factory_routing_slip_account @@ -29,7 +29,7 @@ def test_create_rs_account(session): # 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) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.INTERNAL.value) assert cfs_account.status == CfsAccountStatus.ACTIVE.value assert cfs_account.cfs_party assert cfs_account.cfs_site diff --git a/jobs/payment-jobs/tests/jobs/test_electronic_funds_transfer_task.py b/jobs/payment-jobs/tests/jobs/test_electronic_funds_transfer_task.py index 0c4cd4942..b5392f4a5 100644 --- a/jobs/payment-jobs/tests/jobs/test_electronic_funds_transfer_task.py +++ b/jobs/payment-jobs/tests/jobs/test_electronic_funds_transfer_task.py @@ -67,8 +67,8 @@ def test_link_electronic_funds_transfers(session): payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id( eft_short_name_link.auth_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( - payment_account.id) + cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method( + payment_account.id, PaymentMethod.EFT.value) with patch('pay_api.services.CFSService.create_cfs_receipt') as mock_create_cfs: with patch.object(CFSService, 'get_receipt') as mock_get_receipt: diff --git a/jobs/payment-jobs/tests/jobs/test_routing_slip_task.py b/jobs/payment-jobs/tests/jobs/test_routing_slip_task.py index b1b92f4ba..8aa44f962 100644 --- a/jobs/payment-jobs/tests/jobs/test_routing_slip_task.py +++ b/jobs/payment-jobs/tests/jobs/test_routing_slip_task.py @@ -51,8 +51,8 @@ def test_link_rs(session): payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id( child_rs.payment_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( - payment_account.id) + cfs_account = CfsAccountModel.find_effective_by_payment_method( + payment_account.id, PaymentMethod.INTERNAL.value) 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: diff --git a/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py b/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py index ef15e6b84..d31333136 100644 --- a/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py +++ b/jobs/payment-jobs/tests/jobs/test_unpaid_invoice_notifytask.py @@ -37,7 +37,7 @@ def test_unpaid_one_invoice(session): 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) 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) @@ -78,7 +78,7 @@ def test_unpaid_multiple_invoice(session): 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) 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) @@ -112,7 +112,7 @@ def test_unpaid_invoice_pad(session): # 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) invoice = factory_invoice(payment_account=account, created_on=datetime.now(), total=10, cfs_account_id=cfs_account.id) @@ -133,7 +133,7 @@ def test_unpaid_single_invoice_total(session): 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) # invoice amount total_invoice1 = 100 total_invoice2 = 200 @@ -169,7 +169,7 @@ def test_unpaid_multiple_invoice_total(session): 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) + cfs_account = CfsAccountModel.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) # invoice amount total_invoice1 = 100 total_invoice2 = 200 diff --git a/pay-api/src/pay_api/models/cfs_account.py b/pay-api/src/pay_api/models/cfs_account.py index 2d13c78ac..fb48b5f05 100644 --- a/pay-api/src/pay_api/models/cfs_account.py +++ b/pay-api/src/pay_api/models/cfs_account.py @@ -68,6 +68,7 @@ class CfsAccount(Versioned, BaseModel): # pylint:disable=too-many-instance-attr contact_party = db.Column(db.String(50), nullable=True) bank_number = db.Column(db.String(50), nullable=True, index=True) bank_branch_number = db.Column(db.String(50), nullable=True, index=True) + payment_method = db.Column(db.String(15), ForeignKey('payment_methods.code'), nullable=True) status = db.Column(db.String(40), ForeignKey('cfs_account_status_codes.code'), nullable=True) @@ -87,11 +88,28 @@ def _get_enc_secret(): return current_app.config.get('ACCOUNT_SECRET_KEY') @classmethod - def find_effective_by_account_id(cls, account_id: str): - """Return a Account by id.""" + def find_effective_or_latest_by_payment_method(cls, account_id: str, payment_method: str) -> CfsAccount: + """Return effective cfs account by payment_method that isn't inactive or latest by payment_method. + An example of this is switching from PAD/EFT to DRAWDOWN. + """ + return cls.find_effective_by_payment_method(account_id, payment_method) or \ + cls.find_latest_by_payment_method(account_id, payment_method) + + @classmethod + def find_effective_by_payment_method(cls, account_id: str, payment_method: str) -> CfsAccount: + """Return effective cfs account by payment_method that isn't inactive.""" return CfsAccount.query.filter(CfsAccount.account_id == account_id, + CfsAccount.payment_method == payment_method, CfsAccount.status != CfsAccountStatus.INACTIVE.value).one_or_none() + @classmethod + def find_latest_by_payment_method(cls, account_id: str, payment_method: str) -> CfsAccount: + """Return latest CFS account by account_id and payment_method.""" + return CfsAccount.query.filter(CfsAccount.account_id == account_id, + CfsAccount.payment_method == payment_method) \ + .order_by(CfsAccount.id.desc()) \ + .first() + @classmethod def find_latest_account_by_account_id(cls, account_id: str): """Return a frozen account by account_id, and return the record with the highest id.""" diff --git a/pay-api/src/pay_api/services/base_payment_system.py b/pay-api/src/pay_api/services/base_payment_system.py index 0e6cd920f..7c21922f2 100644 --- a/pay-api/src/pay_api/services/base_payment_system.py +++ b/pay-api/src/pay_api/services/base_payment_system.py @@ -139,6 +139,16 @@ def complete_post_invoice(self, invoice: Invoice, # pylint: disable=unused-argu def apply_credit(self, invoice: Invoice) -> None: # pylint:disable=unused-argument """Apply credit on invoice.""" return None + + def ensure_no_payment_blockers(self, payment_account: PaymentAccount, invoice: Invoice) -> None: + """Ensure no payment blockers are present.""" + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, invoice.payment_method_code) + if cfs_account.status == CfsAccountStatus.FREEZE.value: + # Note NSF (Account Unlocking) is paid using DIRECT_PAY - CC flow, not PAD. + current_app.logger.warning(f'Account {payment_account.id} is frozen, rejecting invoice creation') + raise BusinessException(Error.PAD_CURRENTLY_NSF) + # TODO check for overdue EFT as well? + return None @staticmethod def _release_payment(invoice: Invoice): @@ -174,13 +184,8 @@ def _refund_and_create_credit_memo(invoice: InvoiceModel): invoice.id, InvoiceReferenceStatus.ACTIVE.value) is None: return InvoiceStatus.CANCELLED.value - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(invoice.payment_account_id) - if cfs_account is None: - # Get the last cfs_account for it, as the account might have been changed from PAD to DRAWDOWN. - cfs_account = CfsAccountModel.query.\ - filter(CfsAccountModel.account_id == invoice.payment_account_id).\ - order_by(CfsAccountModel.id.desc()). \ - first() + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(invoice.payment_account_id, + invoice.payment_method_code) line_items: List[PaymentLineItemModel] = [] for line_item in invoice.payment_line_items: line_items.append(PaymentLineItemModel.find_by_id(line_item.id)) @@ -301,3 +306,4 @@ def wrapper(*func_args, **func_kwargs): return function(*func_args, **func_kwargs) return wrapper + diff --git a/pay-api/src/pay_api/services/bcol_service.py b/pay-api/src/pay_api/services/bcol_service.py index 2b9ba7163..a0a8a8727 100644 --- a/pay-api/src/pay_api/services/bcol_service.py +++ b/pay-api/src/pay_api/services/bcol_service.py @@ -50,6 +50,7 @@ def get_payment_system_code(self): def create_invoice(self, payment_account: PaymentAccount, # pylint: disable=too-many-locals line_items: List[PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: """Create Invoice in PayBC.""" + self.ensure_no_payment_blockers(payment_account) current_app.logger.debug(f' EFTShortnames: short_name_model: EFTShortnameModel = EFTShortnameModel.find_by_id(short_name_id) auth_account_id = short_name_model.auth_account_id - # Find invoices to be paid - invoices: List[InvoiceModel] = EFTShortnames.get_invoices_owing(auth_account_id) + invoices = EFTShortnames.get_invoices_owing(auth_account_id) pay_service = PaymentSystemFactory.create_from_payment_method(PaymentMethod.EFT.value) for invoice in invoices: @@ -166,7 +165,7 @@ def process_owing_invoices(short_name_id: int) -> EFTShortnames: current_app.logger.debug('>process_owing_invoices') @staticmethod - def get_invoices_owing(auth_account_id: str) -> [InvoiceModel]: + def get_invoices_owing(auth_account_id: str) -> List[InvoiceModel]: """Return invoices that have not been fully paid.""" unpaid_status = (InvoiceStatus.PARTIAL.value, InvoiceStatus.CREATED.value, InvoiceStatus.OVERDUE.value) @@ -317,7 +316,8 @@ def get_search_query(cls, search_criteria: EFTShortnamesSearch, is_count: bool = .outerjoin(PaymentAccountModel, PaymentAccountModel.auth_account_id == EFTShortnameLinksModel.auth_account_id) .outerjoin(CfsAccountModel, - CfsAccountModel.account_id == PaymentAccountModel.id)) + CfsAccountModel.account_id == PaymentAccountModel.id) + .filter(CfsAccountModel.payment_method == PaymentMethod.EFT.value)) # Join payment information if this is NOT the count query if not is_count: diff --git a/pay-api/src/pay_api/services/ejv_pay_service.py b/pay-api/src/pay_api/services/ejv_pay_service.py index 094a9ab6a..cc47b855c 100644 --- a/pay-api/src/pay_api/services/ejv_pay_service.py +++ b/pay-api/src/pay_api/services/ejv_pay_service.py @@ -49,6 +49,7 @@ def get_default_invoice_status(self) -> str: def create_invoice(self, payment_account: PaymentAccount, line_items: List[PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: """Return a static invoice number.""" + self.ensure_no_payment_blockers(payment_account, invoice) invoice_reference: InvoiceReference = None # If the account is not billable, then create records, if not payment_account.billable: diff --git a/pay-api/src/pay_api/services/fas/routing_slip.py b/pay-api/src/pay_api/services/fas/routing_slip.py index aed8b6e12..326e28e86 100644 --- a/pay-api/src/pay_api/services/fas/routing_slip.py +++ b/pay-api/src/pay_api/services/fas/routing_slip.py @@ -416,7 +416,8 @@ def update(cls, rs_number: str, action: str, request_json: Dict[str, any], **kwa routing_slip.remaining_amount += correction_total CommentModel(comment=comment, routing_slip_number=rs_number).flush() # Set the routing slip status back to ACTIVE or COMPLETE, if it isn't created in CFS yet. - cfs_account = CfsAccountModel.find_effective_by_account_id(routing_slip.payment_account_id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(routing_slip.payment_account_id, + PaymentMethod.INTERNAL.value) if cfs_account and cfs_account.status == CfsAccountStatus.PENDING.value or not cfs_account: status = RoutingSlipStatus.COMPLETE.value if routing_slip.remaining_amount == 0 \ else RoutingSlipStatus.ACTIVE.value diff --git a/pay-api/src/pay_api/services/invoice.py b/pay-api/src/pay_api/services/invoice.py index c49d2fef8..19213d561 100644 --- a/pay-api/src/pay_api/services/invoice.py +++ b/pay-api/src/pay_api/services/invoice.py @@ -417,7 +417,7 @@ def create_invoice_pdf(identifier: int, **kwargs) -> Tuple: raise BusinessException(Error.INVALID_INVOICE_ID) payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(invoice_dao.payment_account_id) - cfs_account: CfsAccountModel = CfsAccountModel.find_by_id(invoice_dao.cfs_account_id) + cfs_account = CfsAccountModel.find_by_id(invoice_dao.cfs_account_id) org_response = OAuthService.get( current_app.config.get('AUTH_API_ENDPOINT') + f'orgs/{payment_account.auth_account_id}', kwargs['user'].bearer_token, AuthHeaderType.BEARER, diff --git a/pay-api/src/pay_api/services/non_sufficient_funds.py b/pay-api/src/pay_api/services/non_sufficient_funds.py index 3094cad43..9d500e762 100644 --- a/pay-api/src/pay_api/services/non_sufficient_funds.py +++ b/pay-api/src/pay_api/services/non_sufficient_funds.py @@ -28,7 +28,8 @@ from pay_api.models import PaymentLineItem as PaymentLineItemModel from pay_api.models import db from pay_api.utils.converter import Converter -from pay_api.utils.enums import AuthHeaderType, ContentType, InvoiceReferenceStatus, InvoiceStatus, ReverseOperation +from pay_api.utils.enums import (AuthHeaderType, ContentType, InvoiceReferenceStatus, InvoiceStatus, PaymentMethod, + ReverseOperation) from pay_api.utils.user_context import user_context from .oauth_service import OAuthService @@ -153,7 +154,7 @@ def create_non_sufficient_funds_statement_pdf(account_id: str, **kwargs): current_app.logger.debug(' InvoiceReference: - """Return a static invoice number for direct pay.""" + """Return a static invoice number for online banking.""" + self.ensure_no_payment_blockers(payment_account, invoice) # Do nothing here as the roll up happens later after creation of invoice. def get_receipt(self, payment_account: PaymentAccount, pay_response_url: str, invoice_reference: InvoiceReference): diff --git a/pay-api/src/pay_api/services/pad_service.py b/pay-api/src/pay_api/services/pad_service.py index 467594d4d..9bbcf1659 100644 --- a/pay-api/src/pay_api/services/pad_service.py +++ b/pay-api/src/pay_api/services/pad_service.py @@ -103,10 +103,7 @@ def update_account(self, name: str, cfs_account: CfsAccountModel, payment_info: def create_invoice(self, payment_account: PaymentAccount, line_items: List[PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: # pylint: disable=unused-argument """Return a static invoice number for direct pay.""" - if payment_account.cfs_account_status == CfsAccountStatus.FREEZE.value: - # Note NSF (Account Unlocking) is paid using DIRECT_PAY - CC flow, not PAD. - current_app.logger.info(f'Account {payment_account.id} is frozen, rejecting invoice creation') - raise BusinessException(Error.PAD_CURRENTLY_NSF) + self.ensure_no_payment_blockers(payment_account, invoice) # Do nothing here as the invoice references are created later. # If the account have credit, deduct the credit amount which will be synced when reconciliation runs. diff --git a/pay-api/tests/unit/api/test_receipt.py b/pay-api/tests/unit/api/test_receipt.py index a5ba50b64..95c8198ba 100644 --- a/pay-api/tests/unit/api/test_receipt.py +++ b/pay-api/tests/unit/api/test_receipt.py @@ -109,7 +109,8 @@ def test_create_pad_payment_receipt(session, client, jwt, app): 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: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method(payment_account.id, + PaymentMethod.PAD.value) cfs_account.status = 'ACTIVE' cfs_account.save() diff --git a/pay-api/tests/unit/services/test_payment_account.py b/pay-api/tests/unit/services/test_payment_account.py index 5ade04ca0..308f2fa09 100644 --- a/pay-api/tests/unit/services/test_payment_account.py +++ b/pay-api/tests/unit/services/test_payment_account.py @@ -229,7 +229,7 @@ def test_delete_account_failures(session): pay_account.credit = 0 pay_account.save() - cfs_account = CfsAccountModel.find_effective_by_account_id(pay_account.id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(pay_account.id, PaymentMethod.PAD.value) cfs_account.status = CfsAccountStatus.FREEZE.value cfs_account.save() @@ -239,7 +239,7 @@ def test_delete_account_failures(session): assert excinfo.value.code == Error.FROZEN_ACCOUNT.code # Now mark the status ACTIVE and create transactions within configured time. - cfs_account = CfsAccountModel.find_effective_by_account_id(pay_account.id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(pay_account.id, PaymentMethod.PAD.value) cfs_account.status = CfsAccountStatus.ACTIVE.value cfs_account.save() diff --git a/pay-api/tests/unit/services/test_payment_transaction.py b/pay-api/tests/unit/services/test_payment_transaction.py index 2f6387be3..4378d85a7 100644 --- a/pay-api/tests/unit/services/test_payment_transaction.py +++ b/pay-api/tests/unit/services/test_payment_transaction.py @@ -626,5 +626,5 @@ def get_receipt(cls, payment_account, pay_response_url: str, 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) + cfs_account = CfsAccount.find_effective_by_payment_method(payment_account.id, PaymentMethod.PAD.value) assert cfs_account.status == 'ACTIVE' diff --git a/pay-api/tests/unit/services/test_routing_slip_service.py b/pay-api/tests/unit/services/test_routing_slip_service.py index ff283b40c..6a8a4adef 100644 --- a/pay-api/tests/unit/services/test_routing_slip_service.py +++ b/pay-api/tests/unit/services/test_routing_slip_service.py @@ -74,8 +74,8 @@ def test_create_routing_slip(session, staff_user_mock): rs = RoutingSlip_service.create(routing_slip_payload) assert rs - cfs_account_model: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( - rs.get('payment_account').get('id')) + cfs_account_model: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method( + rs.get('payment_account').get('id'), PaymentMethod.INTERNAL.value) assert cfs_account_model.status == CfsAccountStatus.PENDING.value @@ -107,8 +107,8 @@ def test_create_routing_slip_usd_one_of_payments(session, staff_user_mock): rs = RoutingSlip_service.create(routing_slip_payload) assert rs assert rs.get('total_usd') == 80 - cfs_account_model: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( - rs.get('payment_account').get('id')) + cfs_account_model: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method( + rs.get('payment_account').get('id'), PaymentMethod.INTERNAL.value) assert cfs_account_model.status == CfsAccountStatus.PENDING.value @@ -141,6 +141,6 @@ def test_create_routing_slip_usd_both_payments(session, staff_user_mock): rs = RoutingSlip_service.create(routing_slip_payload) assert rs assert rs.get('total_usd') == 180 - cfs_account_model: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( - rs.get('payment_account').get('id')) + cfs_account_model: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method( + rs.get('payment_account').get('id'), PaymentMethod.INTERNAL.value) assert cfs_account_model.status == CfsAccountStatus.PENDING.value diff --git a/pay-api/tests/utilities/base_test.py b/pay-api/tests/utilities/base_test.py index adf30be69..4fb677c04 100644 --- a/pay-api/tests/utilities/base_test.py +++ b/pay-api/tests/utilities/base_test.py @@ -631,7 +631,7 @@ def activate_pad_account(auth_account_id: str): payment_account: PaymentAccount = PaymentAccount.find_by_auth_account_id(auth_account_id) payment_account.pad_activation_date = datetime.now(tz=timezone.utc) payment_account.save() - cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(payment_account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(payment_account.id, PaymentMethod.PAD.value) cfs_account.status = 'ACTIVE' cfs_account.save() diff --git a/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py b/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py index 9360b087e..69a0351d1 100644 --- a/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py +++ b/pay-queue/src/pay_queue/services/eft/eft_reconciliation.py @@ -253,8 +253,7 @@ def _process_eft_payments(shortname_balance: Dict, eft_file: EFTFileModel) -> bo # We have a mapping and can continue processing try: auth_account_id = eft_short_link['account_id'] - # Find invoices to be paid - invoices: List[InvoiceModel] = EFTShortnames.get_invoices_owing(auth_account_id) + invoices = EFTShortnames.get_invoices_owing(auth_account_id) for invoice in invoices: _pay_invoice(invoice=invoice, shortname_balance=shortname_balance[shortname], short_name_links=shortname_links['items']) diff --git a/pay-queue/src/pay_queue/services/payment_reconciliations.py b/pay-queue/src/pay_queue/services/payment_reconciliations.py index a33b8c780..946b5a44c 100644 --- a/pay-queue/src/pay_queue/services/payment_reconciliations.py +++ b/pay-queue/src/pay_queue/services/payment_reconciliations.py @@ -524,7 +524,7 @@ def _process_failed_payments(row): return False # Set CFS Account Status. - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.PAD.value) is_already_frozen = cfs_account.status == CfsAccountStatus.FREEZE.value current_app.logger.info('setting payment account id : %s status as FREEZE', payment_account.id) cfs_account.status = CfsAccountStatus.FREEZE.value @@ -591,11 +591,8 @@ def _sync_credit_records(): current_app.logger.info('Found %s credit records', len(active_credits)) account_ids: List[int] = [] for credit in active_credits: - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(credit.account_id) - if cfs_account is None: - # Get the last cfs_account for it, as the account might have got upgraded from PAD to DRAWDOWN. - cfs_account: CfsAccountModel = CfsAccountModel.query.\ - filter(CfsAccountModel.account_id == credit.account_id).order_by(CfsAccountModel.id.desc()).first() + cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(credit.account_id, + PaymentMethod.PAD.value) account_ids.append(credit.account_id) if credit.is_credit_memo: credit_memo = CFSService.get_cms(cfs_account=cfs_account, cms_number=credit.cfs_identifier) diff --git a/pay-queue/tests/integration/factory.py b/pay-queue/tests/integration/factory.py index 744cb377c..3115bd338 100644 --- a/pay-queue/tests/integration/factory.py +++ b/pay-queue/tests/integration/factory.py @@ -55,7 +55,8 @@ def factory_invoice(payment_account: PaymentAccount, status_code: str = InvoiceS created_on: datetime = datetime.now(), disbursement_status_code=None): """Return Factory.""" - cfs_account = CfsAccount.find_effective_by_account_id(payment_account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(payment_account.id, + payment_method_code or payment_account.payment_method) cfs_account_id = cfs_account.id if cfs_account else None return Invoice( invoice_status_code=status_code, diff --git a/pay-queue/tests/integration/test_payment_reconciliations.py b/pay-queue/tests/integration/test_payment_reconciliations.py index 4b147411c..0c4e963e6 100644 --- a/pay-queue/tests/integration/test_payment_reconciliations.py +++ b/pay-queue/tests/integration/test_payment_reconciliations.py @@ -477,7 +477,7 @@ def test_pad_nsf_reconciliations(session, app, client): assert payment.payment_method_code == PaymentMethod.PAD.value assert payment.invoice_number == invoice_number - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(pay_account_id) + cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_payment_method(pay_account_id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.FREEZE.value @@ -550,7 +550,7 @@ def test_pad_reversal_reconciliations(session, app, client): assert payment.payment_method_code == PaymentMethod.PAD.value assert payment.invoice_number == invoice_number - cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(pay_account_id) + cfs_account = CfsAccountModel.find_effective_by_payment_method(pay_account_id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.FREEZE.value # Receipt should be deleted