def test_activate_bcol_change_to_pad(session):
    """Test Activate account."""
    # Create a pending account first, then call the job
    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)
    assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value, 'Created account has pending pad status'
    assert account.payment_method == PaymentMethod.DRAWDOWN.value

    ActivatePadAccountTask.activate_pad_accounts()
    cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(
        account.id)
    assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value, \
        'Same day Job runs and shouldnt change anything.'
    account = PaymentAccount.find_by_id(account.id)
    assert account.payment_method == PaymentMethod.DRAWDOWN.value

    time_delay = current_app.config['PAD_CONFIRMATION_PERIOD_IN_DAYS']
    with freeze_time(datetime.today() + timedelta(days=time_delay, minutes=1)):
        ActivatePadAccountTask.activate_pad_accounts()
        assert cfs_account.status == CfsAccountStatus.ACTIVE.value, \
            'After the confirmation period is over , status should be active'
        account = PaymentAccount.find_by_id(account.id)
        assert account.payment_method == PaymentMethod.PAD.value
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.PENDING_PAD_ACTIVATION.value
    assert updated_cfs_account.payment_instrument_number
def test_update_online_banking_account(session):
    """Test update 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)

    # 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)

    assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value
    assert cfs_account.id == updated_cfs_account.id
Example #4
0
def test_create_routing_slip_usd_one_of_payments(session, staff_user_mock):
    """Create a routing slip."""
    routing_slip_payload: Dict[str, any] = {
        'number':
        '206380834',
        'routingSlipDate':
        datetime.now().strftime(DT_SHORT_FORMAT),
        'paymentAccount': {
            'accountName': 'TEST'
        },
        'payments': [{
            'paymentMethod': PaymentMethod.CHEQUE.value,
            'paymentDate': datetime.now().strftime(DT_SHORT_FORMAT),
            'chequeReceiptNumber': '123',
            'paidAmount': 100
        }, {
            'paymentMethod': PaymentMethod.CHEQUE.value,
            'paymentDate': datetime.now().strftime(DT_SHORT_FORMAT),
            'chequeReceiptNumber': '123',
            'paidAmount': 100,
            'paidUsdAmount': 80
        }]
    }

    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'))
    assert cfs_account_model.status == CfsAccountStatus.PENDING.value
Example #5
0
    def create_payment_receipt(auth_account_id: str, credit_request: Dict[str,
                                                                          str],
                               **kwargs) -> Payment:
        """Create a payment record for the account."""
        pay_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id(
            auth_account_id)
        cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
            pay_account.id)
        # Create a payment record
        # Create a receipt in CFS for the  amount.
        payment = Payment.create(
            payment_method=credit_request.get('paymentMethod'),
            payment_system=PaymentSystem.PAYBC.value,
            payment_account_id=pay_account.id)
        receipt_number: str = generate_receipt_number(payment.id)
        receipt_date = credit_request.get('paymentDate')
        amount = credit_request.get('paidAmount')

        receipt_response = CFSService.create_cfs_receipt(
            cfs_account=cfs_account,
            rcpt_number=receipt_number,
            rcpt_date=receipt_date,
            amount=amount,
            payment_method=credit_request.get('paymentMethod'))

        payment.receipt_number = receipt_response.get('receipt_number',
                                                      receipt_number)
        payment.paid_amount = amount
        payment.created_by = kwargs['user'].user_name
        payment.payment_date = parser.parse(receipt_date)
        payment.payment_status_code = PaymentStatus.COMPLETED.value
        payment.save()

        return payment
Example #6
0
    def unlock_frozen_accounts(account_id: int):
        """Unlock frozen accounts."""
        from pay_api.services.cfs_service import CFSService  # pylint: disable=import-outside-toplevel,cyclic-import
        pay_account: PaymentAccount = PaymentAccount.find_by_id(account_id)
        if pay_account.cfs_account_status == CfsAccountStatus.FREEZE.value:
            # update CSF
            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                pay_account.id)
            CFSService.unsuspend_cfs_account(cfs_account=cfs_account)

            cfs_account.status = CfsAccountStatus.ACTIVE.value
            cfs_account.save()

            payload = pay_account._create_account_event_payload(  # pylint:disable=protected-access
                'bc.registry.payment.unlockAccount')

            try:
                publish_response(
                    payload=payload,
                    client_name=current_app.config['NATS_ACCOUNT_CLIENT_NAME'],
                    subject=current_app.config['NATS_ACCOUNT_SUBJECT'])
            except Exception as e:  # pylint: disable=broad-except
                current_app.logger.error(e)
                current_app.logger.error(
                    'Notification to Queue failed for the Unlock Account %s - %s',
                    pay_account.auth_account_id, pay_account.auth_account_name)
                capture_message(
                    'Notification to Queue failed for the Unlock Account : {msg}.'
                    .format(msg=payload),
                    level='error')
Example #7
0
    def delete_account(cls, auth_account_id: str) -> PaymentAccount:
        """Delete the payment account."""
        current_app.logger.debug('<delete_account')
        pay_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id(
            auth_account_id)
        cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
            pay_account.id)
        # 1 - Check if account have any credits
        # 2 - Check if account have any PAD transactions done in last N (10) days.
        if pay_account.credit and pay_account.credit > 0:
            raise BusinessException(Error.OUTSTANDING_CREDIT)
        # Check if account is frozen.
        cfs_status: str = cfs_account.status if cfs_account else None
        if cfs_status == CfsAccountStatus.FREEZE.value:
            raise BusinessException(Error.FROZEN_ACCOUNT)
        if InvoiceModel.find_outstanding_invoices_for_account(
                pay_account.id, get_outstanding_txns_from_date()):
            # Check if there is any recent PAD transactions in N days.
            raise BusinessException(Error.TRANSACTIONS_IN_PROGRESS)

        # If CFS Account present, mark it as INACTIVE.
        if cfs_status and cfs_status != CfsAccountStatus.INACTIVE.value:
            cfs_account.status = CfsAccountStatus.INACTIVE.value
            # If account is active or pending pad activation stop PAD payments.
            if pay_account.payment_method == PaymentMethod.PAD.value \
                    and cfs_status in [CfsAccountStatus.ACTIVE.value, CfsAccountStatus.PENDING_PAD_ACTIVATION.value]:
                CFSService.suspend_cfs_account(cfs_account)
            cfs_account.save()

        if pay_account.statement_notification_enabled:
            pay_account.statement_notification_enabled = False
            pay_account.save()
Example #8
0
    def _dao(self, value: PaymentAccountModel):
        self.__dao = value
        self.id: int = self._dao.id
        self.auth_account_id: str = self._dao.auth_account_id
        self.auth_account_name: str = self._dao.auth_account_name
        self.payment_method: str = self._dao.payment_method
        self.bcol_user_id: str = self._dao.bcol_user_id
        self.bcol_account: str = self._dao.bcol_account
        self.pad_activation_date: datetime = self._dao.pad_activation_date
        self.pad_tos_accepted_by: str = self._dao.pad_tos_accepted_by

        cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
            self.id)
        if cfs_account:
            self.cfs_account: str = cfs_account.cfs_account
            self.cfs_party: str = cfs_account.cfs_party
            self.cfs_site: str = cfs_account.cfs_site

            self.bank_name: str = cfs_account.bank_name
            self.bank_number: str = cfs_account.bank_number
            self.bank_branch: str = cfs_account.bank_branch
            self.bank_branch_number: str = cfs_account.bank_branch_number
            self.bank_account_number: str = cfs_account.bank_account_number
            self.cfs_account_id: int = cfs_account.id
            self.cfs_account_status: str = cfs_account.status
Example #9
0
    def _apply_routing_slips_to_pending_invoices(cls, routing_slip: RoutingSlipModel) -> float:
        """Apply the routing slips again, when routing slip is linked to an NSF parent."""
        current_app.logger.info(f'Starting NSF recovery process for {routing_slip.number}')
        routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
            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)

        invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
            .filter(InvoiceModel.routing_slip == routing_slip.number,
                    InvoiceModel.invoice_status_code.in_([InvoiceStatus.CREATED.value, InvoiceStatus.APPROVED.value])) \
            .all()
        current_app.logger.info(f'Found {len(invoices)} to apply receipt')
        applied_amount = 0
        for inv in invoices:
            inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                inv.id, InvoiceReferenceStatus.ACTIVE.value
            )
            cls.apply_routing_slips_to_invoice(
                routing_slip_payment_account, active_cfs_account, routing_slip, inv, inv_ref.invoice_number
            )

            # IF invoice balance is zero, then update records.
            if CFSService.get_invoice(cfs_account=active_cfs_account, inv_number=inv_ref.invoice_number) \
                    .get('amount_due') == 0:
                applied_amount += inv.total
                inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value
                inv.invoice_status_code = InvoiceStatus.PAID.value

        return applied_amount
Example #10
0
    def _process_cfs_refund(cls, invoice: InvoiceModel):
        """Process refund in CFS."""
        if invoice.payment_method_code in ([
                PaymentMethod.DIRECT_PAY.value, PaymentMethod.DRAWDOWN.value
        ]):
            cls._publish_to_mailer(invoice)
            payment: PaymentModel = PaymentModel.find_payment_for_invoice(
                invoice.id)
            payment.payment_status_code = PaymentStatus.REFUNDED.value
            payment.flush()
        else:
            # Create credit memo in CFS if the invoice status is PAID.
            # Don't do anything is the status is APPROVED.
            if invoice.invoice_status_code == InvoiceStatus.APPROVED.value:
                return
            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                invoice.payment_account_id)
            line_items: List[PaymentLineItemModel] = []
            for line_item in invoice.payment_line_items:
                line_items.append(PaymentLineItemModel.find_by_id(
                    line_item.id))

            cms_response = CFSService.create_cms(line_items=line_items,
                                                 cfs_account=cfs_account)
            # TODO Create a payment record for this to show up on transactions, when the ticket comes.
            # Create a credit with CM identifier as CMs are not reported in payment interface file
            # until invoice is applied.
            CreditModel(cfs_identifier=cms_response.get('credit_memo_number'),
                        is_credit_memo=True,
                        amount=invoice.total,
                        remaining_amount=invoice.total,
                        account_id=invoice.payment_account_id).save()
Example #11
0
    def _process_cfs_refund(cls, invoice: InvoiceModel):
        """Process refund in CFS."""
        if invoice.payment_method_code == PaymentMethod.DIRECT_PAY.value:
            cls._publish_to_mailer(invoice)
            payment: PaymentModel = PaymentModel.find_payment_for_invoice(
                invoice.id)
            payment.payment_status_code = PaymentStatus.REFUNDED.value
            payment.flush()
        else:
            # Create credit memo in CFS.
            # TODO Refactor this when actual task is done. This is just a quick fix for CFS UAT - Dec 2020
            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                invoice.payment_account_id)
            line_items: List[PaymentLineItemModel] = []
            for line_item in invoice.payment_line_items:
                line_items.append(PaymentLineItemModel.find_by_id(
                    line_item.id))

            cms_response = CFSService.create_cms(line_items=line_items,
                                                 cfs_account=cfs_account)
            # TODO Create a payment record for this to show up on transactions, when the ticket comes.
            # Create a credit with CM identifier as CMs are not reported in payment interface file
            # until invoice is applied.
            CreditModel(cfs_identifier=cms_response.get('credit_memo_number'),
                        is_credit_memo=True,
                        amount=invoice.total,
                        remaining_amount=invoice.total,
                        account_id=invoice.payment_account_id).save()
Example #12
0
def test_delete_account_failures(session):
    """Assert that delete payment account works."""
    # Create a PAD Account.
    # Add credit and assert account cannot be deleted.
    # Remove the credit.
    # Add a PAD transaction for within N days and mark as PAID.
    # Assert account cannot be deleted.
    # Mark the account as NSF and assert account cannot be deleted.
    payload = get_pad_account_payload()
    pay_account: PaymentAccountService = PaymentAccountService.create(payload)
    pay_account.credit = 100
    pay_account.save()

    with pytest.raises(BusinessException) as excinfo:
        PaymentAccountService.delete_account(payload.get('accountId'))

    assert excinfo.value.code == Error.OUTSTANDING_CREDIT.code

    # Now mark the credit as zero and mark teh CFS account status as FREEZE.
    pay_account.credit = 0
    pay_account.save()

    cfs_account = CfsAccountModel.find_effective_by_account_id(pay_account.id)
    cfs_account.status = CfsAccountStatus.FREEZE.value
    cfs_account.save()

    with pytest.raises(BusinessException) as excinfo:
        PaymentAccountService.delete_account(payload.get('accountId'))

    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.status = CfsAccountStatus.ACTIVE.value
    cfs_account.save()

    created_on: datetime = get_outstanding_txns_from_date() + timedelta(
        minutes=1)
    factory_invoice(pay_account,
                    payment_method_code=PaymentMethod.PAD.value,
                    created_on=created_on,
                    status_code=InvoiceStatus.PAID.value).save()

    with pytest.raises(BusinessException) as excinfo:
        PaymentAccountService.delete_account(payload.get('accountId'))

    assert excinfo.value.code == Error.TRANSACTIONS_IN_PROGRESS.code
    def _notify_for_ob(cls):  # pylint: disable=too-many-locals
        """Notify for online banking.

        1) Find the accounts with pending invoices
        2) get total remaining for that account

        """
        unpaid_status = (InvoiceStatus.SETTLEMENT_SCHEDULED.value,
                         InvoiceStatus.PARTIAL.value,
                         InvoiceStatus.CREATED.value)
        notification_date = datetime.today() - timedelta(
            days=current_app.config.get('NOTIFY_AFTER_DAYS'))
        # Get distinct accounts with pending invoices for that exact day
        notification_pending_accounts = db.session.query(
            InvoiceModel.payment_account_id
        ).distinct().filter(
            and_(
                InvoiceModel.invoice_status_code.in_(unpaid_status),
                InvoiceModel.payment_method_code ==
                PaymentMethod.ONLINE_BANKING.value,
                # cast is used to get the exact match stripping the timestamp from date
                cast(InvoiceModel.created_on,
                     Date) == notification_date.date())).all()
        current_app.logger.debug(
            f'Found {len(notification_pending_accounts)} invoices to notify admins.'
        )
        for payment_account in notification_pending_accounts:
            try:
                payment_account_id = payment_account[0]
                total = db.session.query(
                    func.sum(InvoiceModel.total).label('total')
                ).filter(
                    and_(
                        InvoiceModel.invoice_status_code.in_(unpaid_status),
                        InvoiceModel.payment_account_id == payment_account_id,
                        InvoiceModel.payment_method_code ==
                        PaymentMethod.ONLINE_BANKING.value)).group_by(
                            InvoiceModel.payment_account_id).all()
                pay_account: PaymentAccountModel = \
                    PaymentAccountModel.find_by_id(payment_account_id)

                cfs_account = CfsAccountModel.find_effective_by_account_id(
                    payment_account_id)

                # emit account mailer event
                addition_params_to_mailer = {
                    'transactionAmount': total[0][0],
                    'cfsAccountId': cfs_account.cfs_account,
                    'authAccountId': pay_account.auth_account_id,
                }
                mailer.publish_mailer_events('ob.outstandingInvoice',
                                             pay_account,
                                             addition_params_to_mailer)
            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on notifying mailer  OB Pending invoice: account id={pay_account.id}, '
                    f'auth account : {pay_account.auth_account_id}, ERROR : {str(e)}',
                    level='error')
                current_app.logger.error(e)
Example #14
0
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 _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()
Example #16
0
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
Example #17
0
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
Example #18
0
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_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
Example #21
0
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 _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()
Example #23
0
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
Example #24
0
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'
Example #25
0
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()
Example #26
0
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
Example #27
0
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 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} >>>>'
        )
Example #29
0
    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
Example #30
0
    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)