예제 #1
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()
예제 #2
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')
예제 #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 _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)
예제 #5
0
    def apply_routing_slips_to_invoice(cls,  # pylint: disable = too-many-arguments, too-many-locals
                                       routing_slip_payment_account: PaymentAccountModel,
                                       active_cfs_account: CfsAccountModel,
                                       parent_routing_slip: RoutingSlipModel,
                                       invoice: InvoiceModel,
                                       invoice_number: str) -> bool:
        """Apply routing slips (receipts in CFS) to invoice."""
        has_errors = False
        child_routing_slips: List[RoutingSlipModel] = RoutingSlipModel.find_children(parent_routing_slip.number)
        # an invoice has to be applied to multiple receipts (incl. all linked RS); apply till the balance is zero
        for routing_slip in (parent_routing_slip, *child_routing_slips):
            try:
                # apply receipt now
                current_app.logger.debug(f'Apply receipt {routing_slip.number} on invoice {invoice_number} '
                                         f'for routing slip {routing_slip.number}')
                receipt_number = routing_slip.number
                # For linked routing slips, new receipt numbers ends with 'L'
                if routing_slip.status == RoutingSlipStatus.LINKED.value:
                    receipt_number = f'{routing_slip.number}L'

                # If balance of receipt is zero, continue to next receipt.
                receipt_balance_before_apply = float(
                    CFSService.get_receipt(active_cfs_account, receipt_number).get('unapplied_amount')
                )
                current_app.logger.debug(f'Current balance on {receipt_number} = {receipt_balance_before_apply}')
                if receipt_balance_before_apply == 0:
                    continue

                current_app.logger.debug(f'Applying receipt {receipt_number} to {invoice_number}')
                receipt_response = CFSService.apply_receipt(active_cfs_account, receipt_number, invoice_number)

                # Create receipt.
                receipt = Receipt()
                receipt.receipt_number = receipt_response.json().get('receipt_number', None)
                receipt_amount = receipt_balance_before_apply - float(receipt_response.json().get('unapplied_amount'))
                receipt.receipt_amount = receipt_amount
                receipt.invoice_id = invoice.id
                receipt.receipt_date = datetime.now()
                receipt.flush()

                invoice_from_cfs = CFSService.get_invoice(active_cfs_account, invoice_number)
                if invoice_from_cfs.get('amount_due') == 0:
                    break

            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on creating Routing Slip invoice: account id={routing_slip_payment_account.id}, '
                    f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error')
                current_app.logger.error(e)
                has_errors = True
                continue
        return has_errors
예제 #6
0
def get_bank_info(
        party_number: str,  # pylint: disable=too-many-arguments
        account_number: str,
        site_number: str):
    """Get bank details to the site."""
    current_app.logger.debug('<Updating CFS payment details ')
    site_payment_url = current_app.config.post(
        'CFS_BASE_URL'
    ) + f'/cfs/parties/{party_number}/accs/{account_number}/sites/{site_number}/payment/'
    access_token: str = CFSService.get_token().json().get('access_token')
    payment_details = CFSService.get(site_payment_url, access_token,
                                     AuthHeaderType.BEARER, ContentType.JSON)
    return payment_details.json()
예제 #7
0
    def get_receipt(self, payment_account: PaymentAccount, pay_response_url: str, invoice_reference: InvoiceReference):
        """Get receipt from paybc for the receipt number or get receipt against invoice number."""
        current_app.logger.debug('<paybc_service_Getting token')
        access_token: str = CFSService.get_token().json().get('access_token')
        current_app.logger.debug('<Getting receipt')
        receipt_url = current_app.config.get('CFS_BASE_URL') + '/cfs/parties/{}/accs/{}/sites/{}/rcpts/'.format(
            payment_account.cfs_party, payment_account.cfs_account, payment_account.cfs_site)
        parsed_url = parse_url_params(pay_response_url)
        receipt_number: str = parsed_url.get('receipt_number') if 'receipt_number' in parsed_url else None
        if not receipt_number:  # Find all receipts for the site and then match with invoice number
            receipts_response = self.get(receipt_url, access_token, AuthHeaderType.BEARER, ContentType.JSON,
                                         retry_on_failure=True).json()
            for receipt in receipts_response.get('items'):
                expanded_receipt = self.__get_receipt_by_number(access_token, receipt_url,
                                                                receipt.get('receipt_number'))
                for invoice in expanded_receipt.get('invoices'):
                    if invoice.get('invoice_number') == invoice_reference.invoice_number:
                        return receipt.get('receipt_number'), parser.parse(
                            expanded_receipt.get('receipt_date')), float(invoice.get('amount_applied'))

        if receipt_number:
            receipt_response = self.__get_receipt_by_number(access_token, receipt_url, receipt_number)
            receipt_date = parser.parse(receipt_response.get('receipt_date'))

            amount: float = 0
            for invoice in receipt_response.get('invoices'):
                if invoice.get('invoice_number') == invoice_reference.invoice_number:
                    amount += float(invoice.get('amount_applied'))

            return receipt_number, receipt_date, amount
        return None
예제 #8
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()
예제 #9
0
    def update_account(self, name: str, cfs_account: CfsAccountModel, payment_info: Dict[str, Any]) -> CfsAccountModel:
        """Update account in CFS."""
        if str(payment_info.get('bankInstitutionNumber')) != cfs_account.bank_number or \
                str(payment_info.get('bankTransitNumber')) != cfs_account.bank_branch_number or \
                str(payment_info.get('bankAccountNumber')) != cfs_account.bank_account_number:
            # This means, PAD account details have changed. So update banking details for this CFS account
            # Call cfs service to add new bank info.
            bank_details = CFSService.update_bank_details(name=cfs_account.payment_account.auth_account_name,
                                                          party_number=cfs_account.cfs_party,
                                                          account_number=cfs_account.cfs_account,
                                                          site_number=cfs_account.cfs_site,
                                                          payment_info=payment_info)

            instrument_number = bank_details.get('payment_instrument_number', None)

            # Make the current CFS Account as INACTIVE in DB
            cfs_account.status = CfsAccountStatus.INACTIVE.value
            cfs_account.flush()

            # Create new CFS Account
            updated_cfs_account = CfsAccountModel()
            updated_cfs_account.bank_account_number = payment_info.get('bankAccountNumber')
            updated_cfs_account.bank_number = payment_info.get('bankInstitutionNumber')
            updated_cfs_account.bank_branch_number = payment_info.get('bankTransitNumber')
            updated_cfs_account.cfs_site = cfs_account.cfs_site
            updated_cfs_account.cfs_party = cfs_account.cfs_party
            updated_cfs_account.cfs_account = cfs_account.cfs_account
            updated_cfs_account.payment_account = cfs_account.payment_account
            updated_cfs_account.status = CfsAccountStatus.ACTIVE.value
            updated_cfs_account.payment_instrument_number = instrument_number
            updated_cfs_account.flush()
        return cfs_account
예제 #10
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
예제 #11
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()
예제 #12
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 = PaymentAccountService.find_by_auth_account_id(
            auth_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('completedOn')
        amount = credit_request.get('paidAmount')

        receipt_response = CFSService.create_cfs_receipt(
            payment_account=pay_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.completed_on = parser.parse(receipt_date)
        payment.payment_status_code = PaymentStatus.COMPLETED.value
        payment.save()

        return payment
예제 #13
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
예제 #14
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()
예제 #15
0
    def _create_nsf_invoice(cls, cfs_account: CfsAccountModel, rs_number: str,
                            payment_account: PaymentAccountModel) -> InvoiceModel:
        """Create Invoice, line item and invoice reference records."""
        fee_schedule: FeeScheduleModel = FeeScheduleModel.find_by_filing_type_and_corp_type(corp_type_code='BCR',
                                                                                            filing_type_code='NSF')
        invoice = InvoiceModel(
            bcol_account=payment_account.bcol_account,
            payment_account_id=payment_account.id,
            cfs_account_id=cfs_account.id,
            invoice_status_code=InvoiceStatus.CREATED.value,
            total=fee_schedule.fee.amount,
            service_fees=0,
            paid=0,
            payment_method_code=PaymentMethod.INTERNAL.value,
            corp_type_code='BCR',
            created_on=datetime.now(),
            created_by='SYSTEM',
            routing_slip=rs_number
        )
        invoice = invoice.save()
        distribution: DistributionCodeModel = DistributionCodeModel.find_by_active_for_fee_schedule(
            fee_schedule.fee_schedule_id)

        line_item = PaymentLineItemModel(
            invoice_id=invoice.id,
            total=invoice.total,
            fee_schedule_id=fee_schedule.fee_schedule_id,
            description=fee_schedule.filing_type.description,
            filing_fees=invoice.total,
            gst=0,
            priority_fees=0,
            pst=0,
            future_effective_fees=0,
            line_item_status_code=LineItemStatus.ACTIVE.value,
            service_fees=0,
            fee_distribution_id=distribution.distribution_code_id)
        line_item.save()

        invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id,
                                                             line_items=invoice.payment_line_items,
                                                             cfs_account=cfs_account)

        invoice_number = invoice_response.json().get('invoice_number', None)
        current_app.logger.info(f'invoice_number  {invoice_number}  created in CFS for NSF.')

        InvoiceReferenceModel(
            invoice_id=invoice.id,
            invoice_number=invoice_number,
            reference_number=invoice_response.json().get('pbc_ref_number', None),
            status_code=InvoiceReferenceStatus.ACTIVE.value
        ).save()

        return invoice
예제 #16
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()
예제 #17
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
예제 #18
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} >>>>'
        )
예제 #19
0
    def _refund_and_create_credit_memo(invoice: InvoiceModel):
        # Create credit memo in CFS if the invoice status is PAID.
        # Don't do anything is the status is APPROVED.
        current_app.logger.info(
            f'Creating credit memo for invoice : {invoice.id}, {invoice.invoice_status_code}'
        )
        if invoice.invoice_status_code == InvoiceStatus.APPROVED.value \
                and InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                    invoice.id, InvoiceReferenceStatus.ACTIVE.value) is None:
            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).flush()

        # Add up the credit amount and update payment account table.
        payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
            invoice.payment_account_id)
        payment_account.credit = (payment_account.credit or 0) + invoice.total
        current_app.logger.info(
            f'Updating credit amount to  {payment_account.credit} for account {payment_account.auth_account_id}'
        )
        payment_account.flush()
예제 #20
0
    def _create_pad_invoices(cls):  # pylint: disable=too-many-locals
        """Create PAD invoices in to CFS system."""
        # Find all accounts which have done a transaction with PAD transactions

        inv_subquery = db.session.query(InvoiceModel.payment_account_id) \
            .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \
            .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value).subquery()

        # Exclude the accounts which are in FREEZE state.
        pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \
            .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
            .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \
            .filter(PaymentAccountModel.id.in_(inv_subquery)).all()

        current_app.logger.info(
            f'Found {len(pad_accounts)} with PAD transactions.')

        invoice_ref_subquery = db.session.query(InvoiceReferenceModel.invoice_id). \
            filter(InvoiceReferenceModel.status_code.in_((InvoiceReferenceStatus.ACTIVE.value,)))

        for account in pad_accounts:
            # Find all PAD invoices for this account
            account_invoices = db.session.query(InvoiceModel) \
                .filter(InvoiceModel.payment_account_id == account.id) \
                .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \
                .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \
                .filter(InvoiceModel.id.notin_(invoice_ref_subquery)) \
                .order_by(InvoiceModel.created_on.desc()).all()

            # Get cfs account
            payment_account: PaymentAccountService = PaymentAccountService.find_by_id(
                account.id)

            current_app.logger.debug(
                f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}'
            )
            if len(account_invoices) == 0:
                continue

            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                payment_account.id)
            if cfs_account is None:
                # Get the last cfs_account for it, as the account might have got upgraded from PAD to DRAWDOWN.
                cfs_account: CfsAccountModel = CfsAccountModel.query.\
                    filter(CfsAccountModel.account_id == payment_account.id).order_by(CfsAccountModel.id.desc()).first()

            # If the CFS Account status is not ACTIVE or INACTIVE (for above case), raise error and continue
            if cfs_account.status not in (CfsAccountStatus.ACTIVE.value,
                                          CfsAccountStatus.INACTIVE.value):
                capture_message(
                    f'CFS Account status is not ACTIVE. for account {payment_account.auth_account_id} '
                    f'is {payment_account.cfs_account_status}',
                    level='error')
                current_app.logger.error(
                    f'CFS status for account {payment_account.auth_account_id} '
                    f'is {payment_account.cfs_account_status}')
                continue

            # Add all lines together
            lines = []
            invoice_total: float = 0
            for invoice in account_invoices:
                lines.extend(invoice.payment_line_items)
                invoice_total += invoice.total
            invoice_number = account_invoices[-1].id
            try:
                # Get the first invoice id as the trx number for CFS
                invoice_response = CFSService.create_account_invoice(
                    transaction_number=invoice_number,
                    line_items=lines,
                    cfs_account=cfs_account)
            except Exception as e:  # NOQA # pylint: disable=broad-except
                # There is a chance that the error is a timeout from CAS side,
                # so to make sure we are not missing any data, make a GET call for the invoice we tried to create
                # and use it if it got created.
                current_app.logger.info(
                    e
                )  # INFO is intentional as sentry alerted only after the following try/catch
                has_invoice_created: bool = False
                try:
                    # add a 60 seconds delay here as safe bet, as CFS takes time to create the invoice and
                    # since this is a job, delay doesn't cause any performance issue
                    time.sleep(60)
                    invoice_number = generate_transaction_number(
                        str(invoice_number))
                    invoice_response = CFSService.get_invoice(
                        cfs_account=cfs_account, inv_number=invoice_number)
                    has_invoice_created = invoice_response.json().get(
                        'invoice_number', None) == invoice_number
                except Exception as exc:  # NOQA # pylint: disable=broad-except,unused-variable
                    # Ignore this error, as it is irrelevant and error on outer level is relevant.
                    pass
                # If no invoice is created raise an error for sentry
                if not has_invoice_created:
                    capture_message(
                        f'Error on creating PAD 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

            # emit account mailer event
            mailer.publish_mailer_events('pad.invoiceCreated', payment_account,
                                         {'invoice_total': invoice_total})
            # Iterate invoice and create invoice reference records
            for invoice in account_invoices:
                # 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
                # no longer set to settlement sceduled
                # invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
                invoice.save()
예제 #21
0
    def _create_rs_invoices(cls):  # pylint: disable=too-many-locals
        """Create RS invoices in to CFS system."""
        # Find all pending routing slips.

        # find all routing slip invoices [cash or cheque]
        # create invoices in csf
        # do the receipt apply
        invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
            .join(RoutingSlipModel, RoutingSlipModel.number == InvoiceModel.routing_slip) \
            .join(CfsAccountModel, CfsAccountModel.account_id == RoutingSlipModel.payment_account_id) \
            .filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \
            .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \
            .filter(CfsAccountModel.status.in_([CfsAccountStatus.ACTIVE.value, CfsAccountStatus.FREEZE.value])) \
            .filter(InvoiceModel.routing_slip is not None) \
            .order_by(InvoiceModel.created_on.asc()).all()

        current_app.logger.info(f'Found {len(invoices)} to be created in CFS.')

        for invoice in invoices:
            # Create a CFS invoice
            current_app.logger.debug(
                f'Creating cfs invoice for invoice {invoice.id}')
            routing_slip = RoutingSlipModel.find_by_number(
                invoice.routing_slip)
            # If routing slip is not found in Pay-DB, assume legacy RS and move on to next one.
            if not routing_slip:
                continue

            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)

            invoice_response = CFSService.create_account_invoice(
                transaction_number=invoice.id,
                line_items=invoice.payment_line_items,
                cfs_account=active_cfs_account)
            invoice_number = invoice_response.json().get(
                'invoice_number', None)

            current_app.logger.info(
                f'invoice_number  {invoice_number}  created in CFS.')

            has_error_in_apply_receipt = RoutingSlipTask.apply_routing_slips_to_invoice(
                routing_slip_payment_account, active_cfs_account, routing_slip,
                invoice, invoice_number)

            if has_error_in_apply_receipt:
                # move on to next invoice
                continue

            invoice_reference: InvoiceReference = InvoiceReference.create(
                invoice.id, invoice_number,
                invoice_response.json().get('pbc_ref_number', None))

            current_app.logger.debug('>create_invoice')
            # leave the status as PAID
            invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value
            invoice.invoice_status_code = InvoiceStatus.PAID.value
            invoice.paid = invoice.total

            Payment.create(payment_method=PaymentMethod.INTERNAL.value,
                           payment_system=PaymentSystem.INTERNAL.value,
                           payment_status=PaymentStatus.COMPLETED.value,
                           invoice_number=invoice_reference.invoice_number,
                           invoice_amount=invoice.total,
                           payment_account_id=invoice.payment_account_id)
            invoice.save()
예제 #22
0
    def _create_pad_invoices(cls):  # pylint: disable=too-many-locals
        """Create PAD invoices in to CFS system."""
        # Find all accounts which have done a transaction with PAD transactions

        inv_subquery = db.session.query(InvoiceModel.payment_account_id) \
            .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \
            .filter(InvoiceModel.invoice_status_code == PaymentStatus.CREATED.value).subquery()

        # Exclude the accounts which are in FREEZE state.
        pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \
            .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
            .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \
            .filter(PaymentAccountModel.id.in_(inv_subquery)).all()

        current_app.logger.info(
            f'Found {len(pad_accounts)} with PAD transactions.')

        for account in pad_accounts:
            # Find all PAD invoices for this account
            account_invoices = db.session.query(InvoiceModel) \
                .filter(InvoiceModel.payment_account_id == account.id) \
                .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \
                .filter(InvoiceModel.invoice_status_code == InvoiceStatus.CREATED.value) \
                .order_by(InvoiceModel.created_on.desc()).all()

            # Get cfs account
            payment_account: PaymentAccountService = PaymentAccountService.find_by_id(
                account.id)

            current_app.logger.debug(
                f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}'
            )
            if len(account_invoices) == 0:
                continue

            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                payment_account.id)
            if cfs_account is None:
                # Get the last invoice and look up cfs_account for it, as the account might have got upgraded.
                cfs_account = CfsAccountModel.find_by_id(
                    account_invoices[0].cfs_account_id)

            # If the CFS Account status is not ACTIVE, raise error and continue
            if cfs_account.status not in (CfsAccountStatus.ACTIVE.value,
                                          CfsAccountStatus.INACTIVE.value):
                capture_message(
                    f'CFS Account status is not ACTIVE. for account {payment_account.auth_account_id} '
                    f'is {payment_account.cfs_account_status}',
                    level='error')
                current_app.logger.error(
                    f'CFS status for account {payment_account.auth_account_id} '
                    f'is {payment_account.cfs_account_status}')
                continue

            # Add all lines together
            lines = []
            invoice_total: float = 0
            for invoice in account_invoices:
                lines.append(*invoice.payment_line_items)
                invoice_total += invoice.total

            try:
                # Get the first invoice id as the trx number for CFS
                invoice_response = CFSService.create_account_invoice(
                    transaction_number=account_invoices[0].id,
                    line_items=lines,
                    payment_account=cfs_account)
            except Exception as e:  # pylint: disable=broad-except
                capture_message(
                    f'Error on creating PAD 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
            # emit account mailer event
            mailer.publish_mailer_events('pad.invoiceCreated', payment_account,
                                         {'invoice_total': invoice_total})
            # Iterate invoice and create invoice reference records
            for invoice in account_invoices:
                # 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
                invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
                invoice.save()
예제 #23
0
    def link_routing_slips(cls):
        """Create invoice in CFS.

        Steps:
        1. Find all pending rs with pending status.
        1. Notify mailer
        """
        routing_slips = db.session.query(RoutingSlipModel) \
            .join(PaymentAccountModel, PaymentAccountModel.id == RoutingSlipModel.payment_account_id) \
            .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
            .filter(RoutingSlipModel.status == RoutingSlipStatus.LINKED.value) \
            .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value).all()

        for routing_slip in routing_slips:
            # 1.reverse the child routing slip
            # 2.create receipt to the parent
            # 3.change the payment account of child to parent
            # 4. change the status

            try:
                current_app.logger.debug(f'Linking Routing Slip: {routing_slip.number}')
                payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
                    routing_slip.payment_account_id)
                cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                    payment_account.id)

                # reverse routing slip receipt
                CFSService.reverse_rs_receipt_in_cfs(cfs_account, routing_slip.number)
                cfs_account.status = CfsAccountStatus.INACTIVE.value

                # apply receipt to parent cfs account
                parent_rs: RoutingSlipModel = RoutingSlipModel.find_by_number(routing_slip.parent_number)
                parent_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
                    parent_rs.payment_account_id)
                parent_cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                    parent_payment_account.id)
                # For linked routing slip receipts, append 'L' to the number to avoid duplicate error
                receipt_number = f'{routing_slip.number}L'
                CFSService.create_cfs_receipt(cfs_account=parent_cfs_account,
                                              rcpt_number=receipt_number,
                                              rcpt_date=routing_slip.routing_slip_date.strftime('%Y-%m-%d'),
                                              amount=routing_slip.total,
                                              payment_method=parent_payment_account.payment_method,
                                              access_token=CFSService.get_fas_token().json().get('access_token'))

                # Add to the list if parent is NSF, to apply the receipts.
                if parent_rs.status == RoutingSlipStatus.NSF.value:
                    total_invoice_amount = cls._apply_routing_slips_to_pending_invoices(parent_rs)
                    current_app.logger.debug(f'Total Invoice Amount : {total_invoice_amount}')
                    # Update the parent routing slip status to ACTIVE
                    parent_rs.status = RoutingSlipStatus.ACTIVE.value
                    # linking routing slip balance is transferred ,so use the total
                    parent_rs.remaining_amount = float(routing_slip.total) - total_invoice_amount

                routing_slip.save()

            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on Linking Routing Slip number:={routing_slip.number}, '
                    f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error')
                current_app.logger.error(e)
                continue
예제 #24
0
    def _create_cfs_account(cls, pending_account: CfsAccountModel,
                            pay_account: PaymentAccountModel, auth_token: str):
        # If PAD Account creation in CFS is paused, then just continue
        # TODO Remove once PAD account bugs are fixed and stable on CAS side.
        if current_app.config.get('CFS_STOP_PAD_ACCOUNT_CREATION') and \
                pay_account.payment_method == PaymentMethod.PAD.value:
            current_app.logger.info(
                'Continuing to next record as CFS PAD account creation is stopped.'
            )
            return

        current_app.logger.info(
            f'Creating pay system instance for {pay_account.payment_method} for account {pay_account.id}.'
        )

        # For an existing CFS Account, call update.. This is to handle PAD update when CFS is offline
        try:
            account_contact = cls._get_account_contact(
                auth_token, pay_account.auth_account_id)

            contact_info: Dict[str, str] = {
                'city': account_contact.get('city'),
                'postalCode': account_contact.get('postalCode'),
                'province': account_contact.get('region'),
                'addressLine1': account_contact.get('street'),
                'country': account_contact.get('country')
            }

            payment_info: Dict[str, any] = {
                'bankInstitutionNumber': pending_account.bank_number,
                'bankTransitNumber': pending_account.bank_branch_number,
                'bankAccountNumber': pending_account.bank_account_number,
                'bankAccountName': pay_account.name
            }

            if pending_account.cfs_account and pending_account.cfs_party and pending_account.cfs_site:
                # This means, PAD account details have changed. So update banking details for this CFS account
                bank_details = CFSService.update_bank_details(
                    name=pay_account.auth_account_id,
                    party_number=pending_account.cfs_party,
                    account_number=pending_account.cfs_account,
                    site_number=pending_account.cfs_site,
                    payment_info=payment_info)
                pending_account.payment_instrument_number = bank_details.get(
                    'payment_instrument_number', None)
            else:  # It's a new account, now create
                # If the account have banking information, then create a PAD account else a regular account.
                if pending_account.bank_number and pending_account.bank_branch_number \
                        and pending_account.bank_account_number:
                    cfs_account_details = CFSService.create_cfs_account(
                        identifier=pay_account.auth_account_id,
                        contact_info=contact_info,
                        payment_info=payment_info,
                        receipt_method=RECEIPT_METHOD_PAD_DAILY)
                else:
                    cfs_account_details = CFSService.create_cfs_account(
                        identifier=pay_account.auth_account_id,
                        contact_info=contact_info,
                        receipt_method=None)

                pending_account.payment_instrument_number = cfs_account_details.get(
                    'payment_instrument_number', None)
                pending_account.cfs_account = cfs_account_details.get(
                    'account_number')
                pending_account.cfs_site = cfs_account_details.get(
                    'site_number')
                pending_account.cfs_party = cfs_account_details.get(
                    'party_number')

        except Exception as e:  # NOQA # pylint: disable=broad-except
            # publish to mailer queue.
            is_user_error = False
            if pay_account.payment_method == PaymentMethod.PAD.value:
                is_user_error = CreateAccountTask._check_user_error(e.response)  # pylint: disable=no-member
            capture_message(
                f'Error on creating 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)
            pending_account.rollback()

            if is_user_error:
                capture_message(
                    f'User Input needed for creating CFS Account: account id={pay_account.id}, '
                    f'auth account : {pay_account.auth_account_id}, ERROR : Invalid Bank Details',
                    level='error')
                mailer.publish_mailer_events('PadSetupFailed', pay_account)
                pending_account.status = CfsAccountStatus.INACTIVE.value
                pending_account.save()
            return

        # If the account has an activation time set ,
        # before that it shud be set to the  PENDING_PAD_ACTIVATION status.
        is_account_in_pad_confirmation_period = pay_account.pad_activation_date is not None and \
            pay_account.pad_activation_date > datetime.today()
        pending_account.status = CfsAccountStatus.PENDING_PAD_ACTIVATION.value if \
            is_account_in_pad_confirmation_period else CfsAccountStatus.ACTIVE.value
        pending_account.save()
예제 #25
0
    def create_accounts(cls):  # pylint: disable=too-many-locals
        """Find all pending accounts to be created in CFS.

        Steps:
        1. Find all pending CFS accounts.
        2. Create CFS accounts.
        3. Publish a message to the queue if successful.
        """
        # Pass payment method if offline account creation has be restricted based on payment method.
        pending_accounts: List[
            CfsAccountModel] = CfsAccountModel.find_all_pending_accounts()
        current_app.logger.info(
            f'Found {len(pending_accounts)} CFS Accounts to be created.')
        if len(pending_accounts) == 0:
            return

        auth_token = get_token()

        for pending_account in pending_accounts:
            # Find the payment account and create the pay system instance.
            pay_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
                pending_account.account_id)

            # If PAD Account creation in CFS is paused, then just continue
            # TODO Remove once PAD account bugs are fixed and stable on CAS side.
            if current_app.config.get('CFS_STOP_PAD_ACCOUNT_CREATION') and \
                    pay_account.payment_method == PaymentMethod.PAD.value:
                current_app.logger.info(
                    'Continuing to next record as CFS PAD account creation is stopped.'
                )
                continue

            current_app.logger.info(
                f'Creating pay system instance for {pay_account.payment_method} for account {pay_account.id}.'
            )

            account_contact = cls._get_account_contact(
                auth_token, pay_account.auth_account_id)

            contact_info: Dict[str, str] = {
                'city': account_contact.get('city'),
                'postalCode': account_contact.get('postalCode'),
                'province': account_contact.get('region'),
                'addressLine1': account_contact.get('street'),
                'country': account_contact.get('country')
            }

            payment_info: Dict[str, any] = {
                'bankInstitutionNumber': pending_account.bank_number,
                'bankTransitNumber': pending_account.bank_branch_number,
                'bankAccountNumber': pending_account.bank_account_number,
            }

            account_name = pay_account.auth_account_name
            # For an existing CFS Account, call update.. This is to handle PAD update when CFS is offline
            try:
                if pending_account.cfs_account and pending_account.cfs_party and pending_account.cfs_site:
                    # This means, PAD account details have changed. So update banking details for this CFS account
                    bank_details = CFSService.update_bank_details(
                        name=account_name,
                        party_number=pending_account.cfs_party,
                        account_number=pending_account.cfs_account,
                        site_number=pending_account.cfs_site,
                        payment_info=payment_info)
                    pending_account.payment_instrument_number = bank_details.get(
                        'payment_instrument_number', None)
                else:  # It's a new account, now create
                    # If the account have banking information, then create a PAD account else a regular account.
                    if pending_account.bank_number and pending_account.bank_branch_number \
                            and pending_account.bank_account_number:
                        cfs_account_details = CFSService.create_cfs_account(
                            name=account_name,
                            contact_info=contact_info,
                            payment_info=payment_info,
                            receipt_method=RECEIPT_METHOD_PAD_DAILY)
                    else:
                        cfs_account_details = CFSService.create_cfs_account(
                            name=account_name,
                            contact_info=contact_info,
                            receipt_method=None)

                    pending_account.payment_instrument_number = cfs_account_details.get(
                        'payment_instrument_number', None)
                    pending_account.cfs_account = cfs_account_details.get(
                        'account_number')
                    pending_account.cfs_site = cfs_account_details.get(
                        'site_number')
                    pending_account.cfs_party = cfs_account_details.get(
                        'party_number')

            except Exception as e:  # NOQA # pylint: disable=broad-except
                # publish to mailer queue.
                mailer.publish_mailer_events('PadSetupFailed', pay_account)
                capture_message(
                    f'Error on creating 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)
                pending_account.rollback()
                # pending_account.status = CfsAccountStatus.INACTIVE.value
                # pending_account.save()
                continue

            # If the account has an activation time set ,
            # before that it shud be set to the  PENDING_PAD_ACTIVATION status.
            is_account_in_pad_confirmation_period = pay_account.pad_activation_date is not None and \
                pay_account.pad_activation_date > datetime.today()
            pending_account.status = CfsAccountStatus.PENDING_PAD_ACTIVATION.value if \
                is_account_in_pad_confirmation_period else CfsAccountStatus.ACTIVE.value
            pending_account.save()
예제 #26
0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests to assure the CFS service layer.

Test-Suite to ensure that the CFS Service layer is working as expected.
"""
from unittest.mock import patch

from requests import ConnectTimeout

from pay_api.services.cfs_service import CFSService

cfs_service = CFSService()


def test_validate_bank_account_valid(session):
    """Test create_account."""
    input_bank_details = {
        'bankInstitutionNumber': '2001',
        'bankTransitNumber': '00720',
        'bankAccountNumber': '1234567',
    }
    with patch('pay_api.services.oauth_service.requests.post') as mock_post:
        # Configure the mock to return a response with an OK status code.
        mock_post.return_value.ok = True
        mock_post.return_value.status_code = 200
        valid_address = {
            'bank_number': '0001',
예제 #27
0
    def process_nsf(cls):
        """Process NSF routing slips.

        Steps:
        1. Find all routing slips with NSF status.
        2. Reverse the receipt for the NSF routing slips.
        3. Add an invoice for NSF fees.
        """
        routing_slips: List[RoutingSlipModel] = db.session.query(RoutingSlipModel) \
            .join(PaymentAccountModel, PaymentAccountModel.id == RoutingSlipModel.payment_account_id) \
            .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
            .filter(RoutingSlipModel.status == RoutingSlipStatus.NSF.value) \
            .filter(CfsAccountModel.status == CfsAccountStatus.ACTIVE.value).all()

        current_app.logger.info(f'Found {len(routing_slips)} to process NSF.')
        for routing_slip in routing_slips:
            # 1. Reverse the routing slip receipt.
            # 2. Reverse all the child receipts.
            # 3. Change the CFS Account status to FREEZE.
            try:
                current_app.logger.debug(f'Reverse receipt {routing_slip.number}')
                payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(routing_slip.payment_account_id)
                cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)

                # 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
                    if rs.parent_number:
                        receipt_number = f'{receipt_number}L'
                    CFSService.reverse_rs_receipt_in_cfs(cfs_account, receipt_number, is_nsf=True)

                    for payment in db.session.query(PaymentModel) \
                            .filter(PaymentModel.receipt_number == receipt_number).all():
                        payment.payment_status_code = PaymentStatus.FAILED.value

                # Update the CFS Account status to FREEZE.
                cfs_account.status = CfsAccountStatus.FREEZE.value

                # Update all invoice status to CREATED.
                invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \
                    .filter(InvoiceModel.routing_slip == routing_slip.number) \
                    .filter(InvoiceModel.invoice_status_code == InvoiceStatus.PAID.value) \
                    .all()
                for inv in invoices:
                    # Reset the statuses
                    inv.invoice_status_code = InvoiceStatus.CREATED.value
                    inv_ref = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                        inv.id, InvoiceReferenceStatus.COMPLETED.value
                    )
                    inv_ref.status_code = InvoiceReferenceStatus.ACTIVE.value
                    # Delete receipts as receipts are reversed in CFS.
                    for receipt in ReceiptModel.find_all_receipts_for_invoice(inv.id):
                        db.session.delete(receipt)

                inv = cls._create_nsf_invoice(cfs_account, routing_slip.number, payment_account)
                # Reduce the NSF fee from remaining amount.
                routing_slip.remaining_amount = float(routing_slip.remaining_amount) - inv.total
                routing_slip.save()

            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on Processing NSF for :={routing_slip.number}, '
                    f'routing slip : {routing_slip.id}, ERROR : {str(e)}', level='error')
                current_app.logger.error(e)
                continue
예제 #28
0
    def _consolidate_payments(cls, auth_account_id: str,
                              failed_payments: List[PaymentModel]) -> Payment:
        # If the payment is for consolidating failed payments,
        # 1. Cancel the invoices in CFS
        # 2. Update status of invoice_reference to CANCELLED
        # 3. Create new consolidated invoice in CFS.
        # 4. Create new invoice reference records.
        # 5. Create new payment records for the invoice as CREATED.
        pay_account = PaymentAccountService.find_by_auth_account_id(
            auth_account_id)

        consolidated_invoices: List[InvoiceModel] = []
        consolidated_line_items: List[PaymentLineItem] = []

        invoice_total: float = 0
        for failed_payment in failed_payments:
            # Reverse invoice balance
            CFSService.reverse_invoice(
                inv_number=failed_payment.invoice_number)
            # Find all invoices for this payment.
            # Add all line items to the array
            for invoice in InvoiceModel.find_invoices_for_payment(
                    payment_id=failed_payment.id):
                consolidated_invoices.append(invoice)
                invoice_total += invoice.total
                consolidated_line_items.append(*invoice.payment_line_items)

        # Create consolidated invoice
        invoice_response = CFSService.create_account_invoice(
            transaction_number=str(consolidated_invoices[-1].id) + '-C',
            line_items=consolidated_line_items,
            payment_account=pay_account)

        invoice_number: str = invoice_response.json().get('invoice_number')

        # Mark all invoice references to status CANCELLED, and create a new one for the new invoice number.
        for invoice in consolidated_invoices:
            inv_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
                invoice_id=invoice.id,
                status_code=InvoiceReferenceStatus.ACTIVE.value)
            inv_ref.status_code = InvoiceReferenceStatus.CANCELLED.value
            # print(inv_ref)

            InvoiceReferenceModel(
                invoice_id=invoice.id,
                status_code=InvoiceReferenceStatus.ACTIVE.value,
                invoice_number=invoice_number,
                reference_number=invoice_response.json().get(
                    'pbc_ref_number')).flush()

        payment = Payment.create(payment_method=PaymentMethod.CC.value,
                                 payment_system=PaymentSystem.PAYBC.value,
                                 invoice_number=invoice_number,
                                 invoice_amount=invoice_total,
                                 payment_account_id=pay_account.id)

        # Update all failed payment with consolidated invoice number.
        for failed_payment in failed_payments:
            failed_payment.cons_inv_number = invoice_number
            # If the payment status is CREATED, which means we consolidated one payment which was already consolidated.
            # Set the status as DELETED
            if failed_payment.payment_status_code == PaymentStatus.CREATED.value:
                failed_payment.payment_status_code = PaymentStatus.DELETED.value

        # commit transaction
        BaseModel.commit()
        return payment