Example #1
0
def test_receipt_find_by_id(session):
    """Assert a invoice is stored.

    Start with a blank database.
    """
    payment_account = factory_payment_account()
    payment = factory_payment()
    payment_account.save()
    payment.save()
    invoice = factory_invoice(payment_id=payment.id,
                              account_id=payment_account.id)
    invoice = invoice.save()
    receipt = Receipt()
    receipt.receipt_amount = 100
    receipt.receipt_date = datetime.now()
    receipt.invoice_id = invoice.id
    receipt.receipt_number = '123451'
    receipt = receipt.save()
    receipt = receipt.find_by_id(receipt.id)
    assert receipt is not None
    receipt = receipt.find_by_invoice_id_and_receipt_number(
        invoice.id, '123451')
    assert receipt is not None
    receipt = receipt.find_by_invoice_id_and_receipt_number(invoice.id, None)
    assert receipt is not None
Example #2
0
 def _publish_to_mailer(cls, invoice):
     """Construct message and send to mailer queue."""
     receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
         invoice_id=invoice.id)
     invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
         invoice_id=invoice.id,
         status_code=InvoiceReferenceStatus.COMPLETED.value)
     payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id(
         invoice_id=invoice.id)
     q_payload = dict(
         specversion='1.x-wip',
         type='bc.registry.payment.refundRequest',
         source=
         f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}',
         id=invoice.id,
         datacontenttype='application/json',
         data=dict(identifier=invoice.business_identifier,
                   orderNumber=receipt.receipt_number,
                   transactionDateTime=get_local_formatted_date_time(
                       payment_transaction.transaction_end_time),
                   transactionAmount=receipt.receipt_amount,
                   transactionId=invoice_ref.invoice_number))
     current_app.logger.debug(
         'Publishing payment refund request to mailer ')
     current_app.logger.debug(q_payload)
     publish_response(
         payload=q_payload,
         client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'),
         subject=current_app.config.get('NATS_MAILER_SUBJECT'))
Example #3
0
    def find_by_invoice_id_and_receipt_number(invoice_id: int, receipt_number: str = None):
        """Find by the combination of invoce id and receipt number."""
        receipt_dao = ReceiptModel.find_by_invoice_id_and_receipt_number(invoice_id, receipt_number)

        receipt = Receipt()
        receipt._dao = receipt_dao  # pylint: disable=protected-access

        current_app.logger.debug('>find_by_invoice_id_and_receipt_number')
        return receipt
Example #4
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)
Example #5
0
 def _publish_refund_to_mailer(invoice: InvoiceModel):
     """Construct message and send to mailer queue."""
     from .payment_transaction import publish_response  # pylint:disable=import-outside-toplevel,cyclic-import
     receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
         invoice_id=invoice.id)
     invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
         invoice_id=invoice.id,
         status_code=InvoiceReferenceStatus.COMPLETED.value)
     payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id(
         invoice_id=invoice.id)
     message_type: str = f'bc.registry.payment.{invoice.payment_method_code.lower()}.refundRequest'
     transaction_date_time = receipt.receipt_date if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value \
         else payment_transaction.transaction_end_time
     filing_description = ''
     for line_item in invoice.payment_line_items:
         if filing_description:
             filing_description += ','
         filing_description += line_item.description
     q_payload = dict(
         specversion='1.x-wip',
         type=message_type,
         source=
         f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}',
         id=invoice.id,
         datacontenttype='application/json',
         data=dict(identifier=invoice.business_identifier,
                   orderNumber=receipt.receipt_number,
                   transactionDateTime=get_local_formatted_date_time(
                       transaction_date_time),
                   transactionAmount=receipt.receipt_amount,
                   transactionId=invoice_ref.invoice_number,
                   refundDate=get_local_formatted_date_time(
                       datetime.now(), '%Y%m%d'),
                   filingDescription=filing_description))
     if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value:
         payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
             invoice.payment_account_id)
         q_payload['data'].update(
             dict(bcolAccount=invoice.bcol_account,
                  bcolUser=payment_account.bcol_user_id))
     current_app.logger.debug(
         f'Publishing payment refund request to mailer for {invoice.id} : {q_payload}'
     )
     publish_response(
         payload=q_payload,
         client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'),
         subject=current_app.config.get('NATS_MAILER_SUBJECT'))
Example #6
0
async def _process_jv_details_feedback(ejv_file, has_errors, line,
                                       receipt_number):  # pylint:disable=too-many-locals
    journal_name: str = line[7:17]  # {ministry}{ejv_header_model.id:0>8}
    ejv_header_model_id = int(journal_name[2:])
    invoice_id = int(line[205:315])
    invoice: InvoiceModel = InvoiceModel.find_by_id(invoice_id)
    invoice_link: EjvInvoiceLinkModel = db.session.query(
        EjvInvoiceLinkModel).filter(
            EjvInvoiceLinkModel.ejv_header_id == ejv_header_model_id).filter(
                EjvInvoiceLinkModel.invoice_id == invoice_id).one_or_none()
    invoice_return_code = line[315:319]
    invoice_return_message = line[319:469]
    # If the JV process failed, then mark the GL code against the invoice to be stopped
    # for further JV process for the credit GL.
    if line[104:105] == 'C' and ejv_file.is_distribution:
        invoice_link.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)
        invoice_link.message = invoice_return_message
        invoice.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)

        if invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value:
            has_errors = True

        line_items: List[PaymentLineItemModel] = invoice.payment_line_items
        for line_item in line_items:
            # Line debit distribution
            debit_distribution: DistributionCodeModel = DistributionCodeModel \
                .find_by_id(line_item.fee_distribution_id)
            credit_distribution: DistributionCodeModel = DistributionCodeModel \
                .find_by_id(debit_distribution.disbursement_distribution_code_id)
            credit_distribution.stop_ejv = True
    elif line[104:105] == 'D' and not ejv_file.is_distribution:
        # This is for gov account payment JV.
        invoice_link.disbursement_status_code = _get_disbursement_status(
            invoice_return_code)
        invoice_link.message = invoice_return_message
        inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
            invoice_id, InvoiceReferenceStatus.ACTIVE.value)

        if invoice_link.disbursement_status_code == DisbursementStatus.ERRORED.value:
            has_errors = True
            # Cancel the invoice reference.
            inv_ref.status_code = InvoiceReferenceStatus.CANCELLED.value
            # Find the distribution code and set the stop_ejv flag to TRUE
            dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_account(
                invoice.payment_account_id)
            dist_code.stop_ejv = True
        elif invoice_link.disbursement_status_code == DisbursementStatus.COMPLETED.value:
            # Set the invoice status as PAID. Mark the invoice reference as COMPLETED, create a receipt
            invoice.invoice_status_code = InvoiceStatus.PAID.value
            if inv_ref:
                inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value
            # Find receipt and add total to it, as single invoice can be multiple rows in the file
            receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(
                invoice_id=invoice_id, receipt_number=receipt_number)
            if receipt:
                receipt.receipt_amount += float(line[89:104])
            else:
                ReceiptModel(invoice_id=invoice_id,
                             receipt_number=receipt_number,
                             receipt_date=datetime.now(),
                             receipt_amount=float(line[89:104])).flush()
    return has_errors
Example #7
0
async def test_pad_reconciliations_with_credit_memo(session, app, stan_server,
                                                    event_loop, client_id,
                                                    events_stan, future,
                                                    mock_publish):
    """Test Reconciliations worker."""
    # Call back for the subscription
    from reconciliations.worker import cb_subscription_handler

    # Create a Credit Card Payment
    # register the handler to test it
    await subscribe_to_queue(
        events_stan,
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('subject'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('queue'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('durable_name'),
        cb_subscription_handler)

    # 1. Create payment account
    # 2. Create invoices and related records
    # 3. Create CFS Invoice records
    # 4. Mimic some credits on the account
    # 4. Create a CFS settlement file, and verify the records
    cfs_account_number = '1234'
    pay_account: PaymentAccountModel = factory_create_pad_account(
        status=CfsAccountStatus.ACTIVE.value,
        account_number=cfs_account_number)
    invoice1: InvoiceModel = factory_invoice(
        payment_account=pay_account,
        total=100,
        service_fees=10.0,
        payment_method_code=PaymentMethod.PAD.value)
    factory_payment_line_item(invoice_id=invoice1.id,
                              filing_fees=90.0,
                              service_fees=10.0,
                              total=90.0)

    invoice2: InvoiceModel = factory_invoice(
        payment_account=pay_account,
        total=200,
        service_fees=10.0,
        payment_method_code=PaymentMethod.PAD.value)
    factory_payment_line_item(invoice_id=invoice2.id,
                              filing_fees=190.0,
                              service_fees=10.0,
                              total=190.0)

    invoice_number = '1234567890'

    factory_invoice_reference(invoice_id=invoice1.id,
                              invoice_number=invoice_number)
    factory_invoice_reference(invoice_id=invoice2.id,
                              invoice_number=invoice_number)

    invoice1.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
    invoice2.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
    invoice1.save()
    invoice2.save()
    invoice1_id = invoice1.id
    invoice2_id = invoice2.id
    total = invoice1.total + invoice2.total

    # Create a settlement file and publish.
    file_name: str = 'cas_settlement_file.csv'
    # Settlement row
    receipt_number = '1234567890'
    credit_memo_number = 'CM123'
    date = datetime.now().strftime('%d-%b-%y')
    credit_amount = 25

    credit_row = [
        RecordType.CMAP.value, SourceTransaction.CREDIT_MEMO.value,
        credit_memo_number, 100002, date, credit_amount, cfs_account_number,
        'INV', invoice_number, total, 0, Status.PAID.value
    ]
    pad_row = [
        RecordType.PAD.value, SourceTransaction.PAD.value, receipt_number,
        100001, date, total - credit_amount, cfs_account_number, 'INV',
        invoice_number, total, 0, Status.PAID.value
    ]
    create_and_upload_settlement_file(file_name, [credit_row, pad_row])
    await helper_add_event_to_queue(events_stan, file_name=file_name)

    # The invoice should be in PAID status and Payment should be completed
    updated_invoice1 = InvoiceModel.find_by_id(invoice1_id)
    assert updated_invoice1.invoice_status_code == InvoiceStatus.PAID.value
    updated_invoice2 = InvoiceModel.find_by_id(invoice2_id)
    assert updated_invoice2.invoice_status_code == InvoiceStatus.PAID.value

    payment: PaymentModel = PaymentModel.find_payment_by_receipt_number(
        receipt_number)
    assert payment.payment_status_code == PaymentStatus.COMPLETED.value
    assert payment.paid_amount == total - credit_amount
    assert payment.receipt_number == receipt_number
    assert payment.payment_method_code == PaymentMethod.PAD.value
    assert payment.invoice_number == invoice_number

    rcpt1: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
        invoice1_id, receipt_number)
    rcpt2: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
        invoice2_id, receipt_number)
    assert rcpt1
    assert rcpt2
    assert rcpt1.receipt_date == rcpt2.receipt_date
Example #8
0
async def test_failed_payment_ejv_reconciliations(session, app, stan_server,
                                                  event_loop, client_id,
                                                  events_stan, future,
                                                  mock_publish):
    """Test Reconciliations worker."""
    # Call back for the subscription
    from reconciliations.worker import cb_subscription_handler

    # Create a Credit Card Payment
    # register the handler to test it
    await subscribe_to_queue(
        events_stan,
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('subject'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('queue'),
        current_app.config.get('SUBSCRIPTION_OPTIONS').get('durable_name'),
        cb_subscription_handler)

    # 1. Create EJV payment accounts
    # 2. Create invoice and related records
    # 3. Create a feedback file and assert status

    corp_type = 'BEN'
    filing_type = 'BCINC'

    # Find fee schedule which have service fees.
    fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(
        corp_type, filing_type)
    # Create a service fee distribution code
    service_fee_dist_code = factory_distribution(name='service fee',
                                                 client='112',
                                                 reps_centre='99999',
                                                 service_line='99999',
                                                 stob='9999',
                                                 project_code='9999999')
    service_fee_dist_code.save()

    dist_code: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule(
        fee_schedule.fee_schedule_id)
    # Update fee dist code to match the requirement.
    dist_code.client = '112'
    dist_code.responsibility_centre = '22222'
    dist_code.service_line = '33333'
    dist_code.stob = '4444'
    dist_code.project_code = '5555555'
    dist_code.service_fee_distribution_code_id = service_fee_dist_code.distribution_code_id
    dist_code.save()

    # GA
    jv_account_1 = factory_create_ejv_account(auth_account_id='1')
    jv_account_2 = factory_create_ejv_account(auth_account_id='2')

    # GI
    jv_account_3 = factory_create_ejv_account(auth_account_id='3',
                                              client='111')
    jv_account_4 = factory_create_ejv_account(auth_account_id='4',
                                              client='111')

    # Now create JV records.
    # Create EJV File model
    file_ref = f'INBOX.{datetime.now()}'
    ejv_file: EjvFileModel = EjvFileModel(
        file_ref=file_ref,
        disbursement_status_code=DisbursementStatus.UPLOADED.value,
        is_distribution=False).save()
    ejv_file_id = ejv_file.id

    feedback_content = f'..BG...........00000000{ejv_file_id}...\n' \
                       f'..BH...0000.................................................................................' \
                       f'.....................................................................CGI\n'

    jv_accounts = [jv_account_1, jv_account_2, jv_account_3, jv_account_4]
    inv_ids = []
    jv_account_ids = []
    inv_total_amount = 101.5
    for jv_acc in jv_accounts:
        jv_account_ids.append(jv_acc.id)
        inv = factory_invoice(payment_account=jv_acc,
                              corp_type_code=corp_type,
                              total=inv_total_amount,
                              status_code=InvoiceStatus.APPROVED.value,
                              payment_method_code=None)
        factory_invoice_reference(inv.id)
        line: PaymentLineItemModel = factory_payment_line_item(
            invoice_id=inv.id,
            fee_schedule_id=fee_schedule.fee_schedule_id,
            filing_fees=100,
            total=100,
            service_fees=1.5,
            fee_dist_id=dist_code.distribution_code_id)
        inv_ids.append(inv.id)
        ejv_header: EjvHeaderModel = EjvHeaderModel(
            disbursement_status_code=DisbursementStatus.UPLOADED.value,
            ejv_file_id=ejv_file.id,
            payment_account_id=jv_acc.id).save()

        EjvInvoiceLinkModel(
            invoice_id=inv.id,
            ejv_header_id=ejv_header.id,
            disbursement_status_code=DisbursementStatus.UPLOADED.value).save()
        inv_total = f'{inv.total:.2f}'.zfill(15)
        pay_line_amount = f'{line.total:.2f}'.zfill(15)
        service_fee_amount = f'{line.service_fees:.2f}'.zfill(15)
        jh_and_jd = f'..JH...FI0000000{ejv_header.id}.........................{inv_total}.....................' \
                    f'............................................................................................' \
                    f'............................................................................................' \
                    f'.........1111ERROR..........................................................................' \
                    f'.......................................................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00001........................................................' \
                    f'...........{pay_line_amount}D.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00002........................................................' \
                    f'...........{pay_line_amount}C.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00003...........................................................' \
                    f'........{service_fee_amount}D.................................................................' \
                    f'...................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n' \
                    f'..JD...FI0000000{ejv_header.id}00004........................................................' \
                    f'...........{service_fee_amount}C..............................................................' \
                    f'......................................{inv.id}                                             ' \
                    f'                                                                1111ERROR...................' \
                    f'............................................................................................' \
                    f'..................................CGI\n'
        feedback_content = feedback_content + jh_and_jd
    feedback_content = feedback_content + f'..BT.......FI0000000{ejv_header.id}000000000000002{inv_total}0000.......' \
                                          f'.........................................................................' \
                                          f'......................................................................CGI'
    ack_file_name = f'ACK.{file_ref}'

    with open(ack_file_name, 'a+') as jv_file:
        jv_file.write('')
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    upload_to_minio(file_name=ack_file_name, value_as_bytes=str.encode(''))

    await helper_add_ejv_event_to_queue(events_stan, file_name=ack_file_name)

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.ACKNOWLEDGED.value

    feedback_file_name = f'FEEDBACK.{file_ref}'

    with open(feedback_file_name, 'a+') as jv_file:
        jv_file.write(feedback_content)
        jv_file.close()

    # Now upload the ACK file to minio and publish message.
    with open(feedback_file_name, 'rb') as f:
        upload_to_minio(f.read(), feedback_file_name)

    await helper_add_ejv_event_to_queue(events_stan,
                                        file_name=feedback_file_name,
                                        message_type='FEEDBACKReceived')

    # Query EJV File and assert the status is changed
    ejv_file = EjvFileModel.find_by_id(ejv_file_id)
    assert ejv_file.disbursement_status_code == DisbursementStatus.COMPLETED.value
    # Assert invoice and receipt records
    for inv_id in inv_ids:
        invoice: InvoiceModel = InvoiceModel.find_by_id(inv_id)
        assert invoice.disbursement_status_code is None
        assert invoice.invoice_status_code == InvoiceStatus.APPROVED.value  # Will stay as original status
        invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
            inv_id, InvoiceReferenceStatus.COMPLETED.value)
        assert invoice_ref is None  # No completed inv ref
        receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(
            invoice_id=inv_id)
        assert receipt is None  # NO receipt

    # Assert payment records
    for jv_account_id in jv_account_ids:
        account = PaymentAccountModel.find_by_id(jv_account_id)
        payment: PaymentModel = PaymentModel.search_account_payments(
            auth_account_id=account.auth_account_id,
            payment_status=PaymentStatus.COMPLETED.value,
            page=1,
            limit=100)[0]
        assert len(payment) == 0