def test_account_find_by_invalid_id(session):
    """Invalid account test."""
    import pytest
    from pay_api.exceptions import BusinessException
    from pay_api.utils.errors import Error
    with pytest.raises(BusinessException) as excinfo:
        PaymentAccountService.find_by_id(999)
    assert excinfo.value.status == Error.PAY009.status
Esempio n. 2
0
def test_delete_account(session, payload):
    """Assert that delete payment account works."""
    pay_account: PaymentAccountService = PaymentAccountService.create(payload)
    PaymentAccountService.delete_account(payload.get('accountId'))

    # Try to find the account by id.
    pay_account = PaymentAccountService.find_by_id(pay_account.id)
    for cfs_account in CfsAccountModel.find_by_account_id(pay_account.id):
        assert cfs_account.status == CfsAccountStatus.INACTIVE.value if cfs_account else True
def test_account_find_by_id(session):
    """Assert that the payment is saved to the table."""
    payment_account = factory_payment_account()
    payment_account.save()

    pa = PaymentAccountService.find_by_id(payment_account.id)

    assert pa is not None
    assert pa.id is not None
    assert pa.corp_number is not None
    assert pa.corp_type_code is not None
    assert pa.payment_system_code is not None
    assert pa.asdict() is not None
    def _create_single_invoice_per_purchase(cls,
                                            payment_method: PaymentMethod):
        """Create one CFS invoice per purchase."""
        invoices: List[InvoiceModel] = InvoiceModel.query \
            .filter_by(payment_method_code=payment_method.value) \
            .filter_by(invoice_status_code=InvoiceStatus.CREATED.value) \
            .order_by(InvoiceModel.created_on.asc()).all()

        current_app.logger.info(f'Found {len(invoices)} to be created in CFS.')
        for invoice in invoices:
            # Get cfs account
            payment_account: PaymentAccountService = PaymentAccountService.find_by_id(
                invoice.payment_account_id)
            cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
                payment_account.id)

            # Check for corp type and see if online banking is allowed.
            if invoice.payment_method_code == PaymentMethod.ONLINE_BANKING.value:
                corp_type: CorpTypeModel = CorpTypeModel.find_by_code(
                    invoice.corp_type_code)
                if not corp_type.is_online_banking_allowed:
                    continue

            # Create a CFS invoice
            current_app.logger.debug(
                f'Creating cfs invoice for invoice {invoice.id}')
            try:
                invoice_response = CFSService.create_account_invoice(
                    transaction_number=invoice.id,
                    line_items=invoice.payment_line_items,
                    cfs_account=cfs_account)
            except Exception as e:  # NOQA # pylint: disable=broad-except
                capture_message(
                    f'Error on creating Online Banking invoice: account id={payment_account.id}, '
                    f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}',
                    level='error')
                current_app.logger.error(e)
                continue

            # Create invoice reference, payment record and a payment transaction
            InvoiceReference.create(
                invoice_id=invoice.id,
                invoice_number=invoice_response.json().get('invoice_number'),
                reference_number=invoice_response.json().get(
                    'pbc_ref_number', None))

            # Misc
            invoice.cfs_account_id = payment_account.cfs_account_id
            # leave the status as SETTLEMENT_SCHEDULED
            invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
            invoice.save()
    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()
Esempio n. 6
0
    def update_transaction(
            transaction_id: uuid,  # pylint: disable=too-many-locals
            pay_response_url: str):
        """Update transaction record.

        Does the following:
        1. Find the payment record with the id
        2. Find the invoice record using the payment identifier
        3. Call the pay system service and get the receipt details
        4. Save the receipt record
        5. Change the status of Invoice
        6. Change the status of Payment
        7. Update the transaction record
        """
        #  TODO for now assumption is this def will be called only for credit card, bcol and internal payments.
        #  When start to look into the PAD and Online Banking may need to refactor here
        transaction_dao: PaymentTransactionModel = PaymentTransactionModel.find_by_id(
            transaction_id)
        if not transaction_dao:
            raise BusinessException(Error.INVALID_TRANSACTION_ID)
        if transaction_dao.status_code == TransactionStatus.COMPLETED.value:
            raise BusinessException(Error.INVALID_TRANSACTION)

        payment: Payment = Payment.find_by_id(transaction_dao.payment_id)
        payment_account: PaymentAccount = PaymentAccount.find_by_id(
            payment.payment_account_id)

        # For transactions other than Credit Card, there could be more than one invoice per payment.
        invoices: [Invoice] = Invoice.find_invoices_for_payment(
            transaction_dao.payment_id)

        if payment.payment_status_code == PaymentStatus.COMPLETED.value:
            # if the transaction status is EVENT_FAILED then publish to queue and return, else raise error
            if transaction_dao.status_code == TransactionStatus.EVENT_FAILED.value:
                # Publish status to Queue
                for invoice in invoices:
                    PaymentTransaction.publish_status(transaction_dao, invoice)

                transaction_dao.status_code = TransactionStatus.COMPLETED.value
                return PaymentTransaction.__wrap_dao(transaction_dao.save())

            raise BusinessException(Error.COMPLETED_PAYMENT)

        pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method(
            payment_method=payment.payment_method_code)

        invoice_reference = InvoiceReference.find_any_active_reference_by_invoice_number(
            payment.invoice_number)
        try:
            receipt_details = pay_system_service.get_receipt(
                payment_account, pay_response_url, invoice_reference)
            txn_reason_code = None
        except ServiceUnavailableException as exc:
            txn_reason_code = exc.status
            transaction_dao.pay_system_reason_code = txn_reason_code
            receipt_details = None

        if receipt_details:
            PaymentTransaction._update_receipt_details(invoices, payment,
                                                       receipt_details,
                                                       transaction_dao)
        else:
            transaction_dao.status_code = TransactionStatus.FAILED.value

        # check if the pay_response_url contains any failure status
        if not txn_reason_code:
            pay_system_reason_code = pay_system_service.get_pay_system_reason_code(
                pay_response_url)
            transaction_dao.pay_system_reason_code = pay_system_reason_code

        # Save response URL
        transaction_dao.transaction_end_time = datetime.now()
        transaction_dao.pay_response_url = pay_response_url
        transaction_dao = transaction_dao.save()

        # Publish message to unlock account if account is locked.
        if payment.payment_status_code == PaymentStatus.COMPLETED.value:
            active_failed_payments = Payment.get_failed_payments(
                auth_account_id=payment_account.auth_account_id)
            current_app.logger.info('active_failed_payments %s',
                                    active_failed_payments)
            if not active_failed_payments:
                PaymentAccount.unlock_frozen_accounts(
                    payment.payment_account_id)

        transaction = PaymentTransaction.__wrap_dao(transaction_dao)

        current_app.logger.debug('>update_transaction')
        return transaction
Esempio n. 7
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()
    def update_transaction(
            payment_identifier: int,
            transaction_id: uuid,  # pylint: disable=too-many-locals
            receipt_number: str,
            jwt: JwtManager = None,
            skip_auth_check: bool = False):
        """Update transaction record.

        Does the following:
        1. Find the payment record with the id
        2. Find the invoice record using the payment identifier
        3. Call the pay system service and get the receipt details
        4. Save the receipt record
        5. Change the status of Invoice
        6. Change the status of Payment
        7. Update the transaction record
        """
        transaction_dao: PaymentTransactionModel = PaymentTransactionModel.find_by_id_and_payment_id(
            transaction_id, payment_identifier)
        if not transaction_dao:
            raise BusinessException(Error.PAY008)
        if transaction_dao.status_code == Status.COMPLETED.value:
            raise BusinessException(Error.PAY006)

        payment: Payment = Payment.find_by_id(payment_identifier,
                                              jwt=jwt,
                                              one_of_roles=[EDIT_ROLE],
                                              skip_auth_check=skip_auth_check)

        pay_system_service: PaymentSystemService = PaymentSystemFactory.create(
            payment_system=payment.payment_system_code)

        invoice = Invoice.find_by_payment_identifier(payment_identifier,
                                                     jwt=jwt,
                                                     skip_auth_check=True)

        payment_account = PaymentAccount.find_by_id(invoice.account_id)

        try:
            receipt_details = pay_system_service.get_receipt(
                payment_account, receipt_number, invoice.invoice_number)
            txn_reason_code = None
        except ServiceUnavailableException as exc:
            txn_reason_code = exc.status_code
            receipt_details = None

        if receipt_details:
            # Find if a receipt exists with same receipt_number for the invoice
            receipt: Receipt = Receipt.find_by_invoice_id_and_receipt_number(
                invoice.id, receipt_details[0])
            if not receipt.id:
                receipt: Receipt = Receipt()
                receipt.receipt_number = receipt_details[0]
                receipt.receipt_date = receipt_details[1]
                receipt.receipt_amount = receipt_details[2]
                receipt.invoice_id = invoice.id
            else:
                receipt.receipt_date = receipt_details[1]
                receipt.receipt_amount = receipt_details[2]
            # Save receipt details to DB.
            receipt.save()

            invoice.paid = receipt.receipt_amount
            if invoice.paid == invoice.total:
                invoice.invoice_status_code = Status.COMPLETED.value
                payment.payment_status_code = Status.COMPLETED.value
                payment.save()
            elif 0 < invoice.paid < invoice.total:
                invoice.invoice_status_code = Status.PARTIAL.value
            invoice.save()
            transaction_dao.status_code = Status.COMPLETED.value
        else:
            transaction_dao.status_code = Status.FAILED.value

        transaction_dao.transaction_end_time = datetime.now()

        # Publish status to Queue
        PaymentTransaction.publish_status(transaction_dao, payment)

        transaction_dao = transaction_dao.save()

        transaction = PaymentTransaction()
        transaction._dao = transaction_dao  # pylint: disable=protected-access
        transaction.pay_system_reason_code = txn_reason_code

        current_app.logger.debug('>update_transaction')
        return transaction
Esempio n. 9
0
    def update_transaction(payment_identifier: int, transaction_id: uuid,
                           receipt_number: str):
        """Update transaction record.

        Does the following:
        1. Find the payment record with the id
        2. Find the invoice record using the payment identifier
        3. Call the pay system service and get the receipt details
        4. Save the receipt record
        5. Change the status of Invoice
        6. Change the status of Payment
        7. Update the transaction record
        """
        transaction_dao: PaymentTransactionModel = PaymentTransactionModel.find_by_id_and_payment_id(
            transaction_id, payment_identifier)
        if not transaction_dao:
            raise BusinessException(Error.PAY008)
        if transaction_dao.status_code == Status.COMPLETED.value:
            raise BusinessException(Error.PAY006)

        payment: Payment = Payment.find_by_id(payment_identifier)

        pay_system_service: PaymentSystemService = PaymentSystemFactory.create(
            payment_system=payment.payment_system_code)

        invoice = Invoice.find_by_payment_identifier(payment_identifier)

        payment_account = PaymentAccount.find_by_id(invoice.account_id)

        receipt_details = pay_system_service.get_receipt(
            payment_account, receipt_number, invoice.invoice_number)
        if receipt_details:
            # Find if a receipt exists with same receipt_number for the invoice
            receipt: Receipt = Receipt.find_by_invoice_id_and_receipt_number(
                invoice.id, receipt_details[0])
            if not receipt.id:
                receipt: Receipt = Receipt()
                receipt.receipt_number = receipt_details[0]
                receipt.receipt_date = receipt_details[1]
                receipt.receipt_amount = receipt_details[2]
                receipt.invoice_id = invoice.id
            else:
                receipt.receipt_date = receipt_details[1]
                receipt.receipt_amount = receipt_details[2]
            # Save receipt details to DB.
            receipt.save()

            invoice.paid = receipt.receipt_amount
            if invoice.paid == invoice.total:
                invoice.invoice_status_code = Status.COMPLETED.value
                payment.payment_status_code = Status.COMPLETED.value
                payment.save()
            elif 0 < invoice.paid < invoice.total:
                invoice.invoice_status_code = Status.PARTIAL.value
            invoice.save()

        transaction_dao.transaction_end_time = datetime.now()
        transaction_dao.status_code = Status.COMPLETED.value
        transaction_dao = transaction_dao.save()

        transaction = PaymentTransaction()
        transaction._dao = transaction_dao  # pylint: disable=protected-access

        current_app.logger.debug('>update_transaction')
        return transaction