예제 #1
0
파일: factory.py 프로젝트: lmullane/sbc-pay
def factory_create_wire_account(auth_account_id='1234',
                                status=CfsAccountStatus.PENDING.value):
    """Return Factory."""
    account = PaymentAccount(
        auth_account_id=auth_account_id,
        payment_method=PaymentMethod.WIRE.value,
        auth_account_name=f'Test {auth_account_id}').save()
    CfsAccount(status=status, account_id=account.id).save()
    return account
예제 #2
0
def test_delete_account(session, payload):
    """Assert that delete payment account works."""
    pay_account: PaymentAccountService = PaymentAccountService.create(payload)
    PaymentAccountService.delete_account(payload.get('accountId'))

    # Try to find the account by id.
    pay_account = PaymentAccountService.find_by_id(pay_account.id)
    for cfs_account in CfsAccountModel.find_by_account_id(pay_account.id):
        assert cfs_account.status == CfsAccountStatus.INACTIVE.value if cfs_account else True
예제 #3
0
    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()
예제 #4
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()
예제 #5
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
예제 #6
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()
예제 #7
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
예제 #8
0
def factory_create_online_banking_account(
        auth_account_id='1234',
        status=CfsAccountStatus.PENDING.value,
        cfs_account='12356'):
    """Return Factory."""
    account = PaymentAccount(auth_account_id=auth_account_id,
                             payment_method=PaymentMethod.ONLINE_BANKING.value,
                             name=f'Test {auth_account_id}').save()
    CfsAccount(status=status, account_id=account.id,
               cfs_account=cfs_account).save()
    return account
예제 #9
0
def test_create_pad_account_system_error(session):
    """Test create account."""
    # Create a pending account first, then call the job
    account = factory_create_pad_account(auth_account_id='1')
    cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(
        account.id)
    assert cfs_account.status == CfsAccountStatus.PENDING.value
    mock_response = requests.models.Response()
    mock_response.headers['CAS-Returned-Messages'] = '[CFS Down]'
    mock_response.status_code = 404

    side_effect = HTTPError(response=mock_response)
    with patch.object(mailer, 'publish_mailer_events') as mock_mailer:
        with patch('pay_api.services.CFSService.create_cfs_account',
                   side_effect=side_effect):
            CreateAccountTask.create_accounts()
            mock_mailer.assert_not_called()

    account = PaymentAccount.find_by_id(account.id)
    cfs_account: CfsAccount = CfsAccount.find_by_id(cfs_account.id)
    assert cfs_account.status == CfsAccountStatus.PENDING.value
예제 #10
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)
예제 #11
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 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
예제 #14
0
    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()
예제 #15
0
def test_update_pad_account(session):
    """Test update account."""
    # Create a pending account first, then call the job
    account = factory_create_pad_account(auth_account_id='2')
    CreateAccountTask.create_accounts()

    account = PaymentAccount.find_by_id(account.id)
    cfs_account = CfsAccount.find_effective_by_account_id(account.id)

    assert cfs_account.payment_instrument_number

    # Now update the account.
    new_payment_details = {
        'bankInstitutionNumber': '111',
        'bankTransitNumber': '222',
        'bankAccountNumber': '3333333333'
    }
    PadService().update_account(name='Test',
                                cfs_account=cfs_account,
                                payment_info=new_payment_details)
    cfs_account = CfsAccount.find_by_id(cfs_account.id)

    # Run the job again
    CreateAccountTask.create_accounts()

    updated_cfs_account: CfsAccount = CfsAccount.find_effective_by_account_id(
        account.id)
    assert updated_cfs_account.id != cfs_account.id
    assert updated_cfs_account.bank_account_number == new_payment_details.get(
        'bankAccountNumber')
    assert updated_cfs_account.bank_branch_number == new_payment_details.get(
        'bankTransitNumber')
    assert updated_cfs_account.bank_number == new_payment_details.get(
        'bankInstitutionNumber')

    assert cfs_account.status == CfsAccountStatus.INACTIVE.value
    assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value
    assert updated_cfs_account.payment_instrument_number
예제 #16
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
예제 #17
0
def _validate_account(inv: InvoiceModel, row: Dict[str, str]):
    """Validate any mismatch in account number."""
    # This should never happen, just in case
    cfs_account: CfsAccountModel = CfsAccountModel.find_by_id(
        inv.cfs_account_id)
    if (account_number := _get_row_value(
            row, Column.CUSTOMER_ACC)) != cfs_account.cfs_account:
        logger.error('Customer Account received as %s, but expected %s.',
                     account_number, cfs_account.cfs_account)
        capture_message(
            f'Customer Account received as {account_number}, but expected {cfs_account.cfs_account}.',
            level='error')

        raise Exception('Invalid Account Number')
예제 #18
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'
예제 #19
0
 def _update_pad_activation_date(cls, cfs_account: CfsAccountModel,
                                 is_sandbox: bool,
                                 payment_account: PaymentAccountModel):
     """Update PAD activation date."""
     is_pad = payment_account.payment_method == PaymentMethod.PAD.value
     # If the account is created for sandbox env, then set the status to ACTIVE and set pad activation time to now
     if is_pad and is_sandbox:
         cfs_account.status = CfsAccountStatus.ACTIVE.value
         payment_account.pad_activation_date = datetime.now()
     # override payment method for since pad has 3 days wait period
     elif is_pad:
         effective_pay_method, activation_date = PaymentAccount._get_payment_based_on_pad_activation(
             payment_account)
         payment_account.pad_activation_date = activation_date
         payment_account.payment_method = effective_pay_method
예제 #20
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()
예제 #21
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
예제 #22
0
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} >>>>'
        )
예제 #23
0
def factory_create_pad_account(auth_account_id='1234',
                               bank_number='001',
                               bank_branch='004',
                               bank_account='1234567890',
                               status=CfsAccountStatus.PENDING.value,
                               payment_method=PaymentMethod.PAD.value,
                               confirmation_period: int = 3):
    """Return Factory."""
    date_after_wait_period = datetime.today() + timedelta(confirmation_period)
    account = PaymentAccount(auth_account_id=auth_account_id,
                             payment_method=payment_method,
                             pad_activation_date=date_after_wait_period,
                             name=f'Test {auth_account_id}').save()
    CfsAccount(status=status,
               account_id=account.id,
               bank_number=bank_number,
               bank_branch_number=bank_branch,
               bank_account_number=bank_account).save()
    return account
예제 #24
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
예제 #25
0
def _sync_credit_records():
    """Sync credit records with CFS."""
    # 1. Get all credit records with balance > 0
    # 2. If it's on account receipt call receipt endpoint and calculate balance.
    # 3. If it's credit memo, call credit memo endpoint and calculate balance.
    # 4. Roll up the credits to credit field in payment_account.
    active_credits: List[CreditModel] = db.session.query(CreditModel).filter(
        CreditModel.remaining_amount > 0).all()
    logger.info('Found %s credit records', len(active_credits))
    account_ids: List[int] = []
    for credit in active_credits:
        account_ids.append(credit.account_id)
        cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
            credit.account_id)
        if credit.is_credit_memo:
            credit_memo = CFSService.get_cms(cfs_account=cfs_account,
                                             cms_number=credit.cfs_identifier)
            credit.remaining_amount = abs(float(credit_memo.get('amount_due')))
        else:
            receipt = CFSService.get_receipt(
                cfs_account=cfs_account, receipt_number=credit.cfs_identifier)
            receipt_amount = float(receipt.get('receipt_amount'))
            applied_amount: float = 0
            for invoice in receipt.get('invoices', []):
                applied_amount += float(invoice.get('amount_applied'))
            credit.remaining_amount = receipt_amount - applied_amount

        credit.save()

    # Roll up the credits and add up to credit in payment_account.
    for account_id in set(account_ids):
        account_credits: List[CreditModel] = db.session.query(
            CreditModel).filter(CreditModel.remaining_amount > 0).filter(
                CreditModel.account_id == account_id).all()
        credit_total: float = 0
        for account_credit in account_credits:
            credit_total += account_credit.remaining_amount
        pay_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
            account_id)
        pay_account.credit = credit_total
        pay_account.save()
예제 #26
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)
예제 #27
0
def factory_create_pad_account(auth_account_id='1234',
                               bank_number='001',
                               bank_branch='004',
                               bank_account='1234567890',
                               status=CfsAccountStatus.PENDING.value,
                               account_number='4101'):
    """Return Factory."""
    account = PaymentAccount(auth_account_id=auth_account_id,
                             payment_method=PaymentMethod.PAD.value,
                             name=f'Test {auth_account_id}').save()
    CfsAccount(
        status=status,
        account_id=account.id,
        bank_number=bank_number,
        bank_branch_number=bank_branch,
        bank_account_number=bank_account,
        cfs_party='11111',
        cfs_account=account_number,
        cfs_site='29921',
    ).save()
    return account
예제 #28
0
def test_unpaid_invoice_pad(session):
    """Assert events are being sent."""
    # Create an account and an invoice for the account
    account = factory_create_pad_account(auth_account_id='1',
                                         status=CfsAccountStatus.ACTIVE.value)
    # Create an invoice for this account
    cfs_account = CfsAccountModel.find_effective_by_account_id(account.id)

    invoice = factory_invoice(payment_account=account,
                              created_on=datetime.now(),
                              total=10,
                              cfs_account_id=cfs_account.id)
    assert invoice.invoice_status_code == InvoiceStatus.CREATED.value

    # invoke today ;no mail
    time_delay = current_app.config['NOTIFY_AFTER_DAYS']
    day_after_time_delay = datetime.today() + timedelta(days=time_delay)
    with freeze_time(day_after_time_delay):
        with patch.object(mailer, 'publish_mailer_events') as mock_mailer:
            UnpaidInvoiceNotifyTask.notify_unpaid_invoices()
            mock_mailer.assert_not_called()
예제 #29
0
def factory_payment_account(payment_system_code: str = 'PAYBC', payment_method_code: str = 'CC', account_number='4101',
                            bcol_user_id='test',
                            auth_account_id: str = '1234'):
    """Return Factory."""
    # Create a payment account
    account = PaymentAccount(
        auth_account_id=auth_account_id,
        bcol_user_id=bcol_user_id,
        bcol_account='TEST'
    ).save()

    CfsAccount(cfs_party='11111',
               cfs_account=account_number,
               cfs_site='29921', payment_account=account).save()

    if payment_system_code == PaymentSystem.BCOL.value:
        account.payment_method = PaymentMethod.DRAWDOWN.value
    elif payment_system_code == PaymentSystem.PAYBC.value:
        account.payment_method = payment_method_code

    return account
예제 #30
0
def create_cfs_account(cfs_account: CfsAccountModel,
                       pay_account: PaymentAccountModel):
    """Create CFS account for routing slip."""
    routing_slip: RoutingSlipModel = RoutingSlipModel.find_by_payment_account_id(
        pay_account.id)
    try:
        # TODO add status check so that LINKED etc can be skipped.
        # for RS , entity/business number=party name ; RS Number=site name
        cfs_account_details: Dict[str, any] = CFSService.create_cfs_account(
            identifier=pay_account.name,
            contact_info={},
            site_name=routing_slip.number,
            is_fas=True)
        cfs_account.cfs_account = cfs_account_details.get('account_number')
        cfs_account.cfs_party = cfs_account_details.get('party_number')
        cfs_account.cfs_site = cfs_account_details.get('site_number')
        cfs_account.status = CfsAccountStatus.ACTIVE.value
        # Create receipt in CFS for the payment.
        CFSService.create_cfs_receipt(
            cfs_account=cfs_account,
            rcpt_number=routing_slip.number,
            rcpt_date=routing_slip.routing_slip_date.strftime('%Y-%m-%d'),
            amount=routing_slip.total,
            payment_method=pay_account.payment_method,
            access_token=CFSService.get_fas_token().json().get('access_token'))
        cfs_account.commit()
        return

    except Exception as e:  # NOQA # pylint: disable=broad-except

        capture_message(
            f'Error on creating Routing Slip CFS Account: account id={pay_account.id}, '
            f'auth account : {pay_account.auth_account_id}, ERROR : {str(e)}',
            level='error')
        current_app.logger.error(e)
        cfs_account.rollback()
        return