def publish_status(transaction_dao: PaymentTransactionModel,
                       payment: Payment):
        """Publish payment/transaction status to the Queue."""
        current_app.logger.debug('<publish_status')
        if transaction_dao.status_code == Status.COMPLETED.value:
            if payment.payment_status_code == Status.COMPLETED.value:
                status_code = Status.COMPLETED.value
            else:
                current_app.logger.info(
                    f'Status {payment.payment_status_code} received for payment {payment.id}'
                )
                return
        else:
            status_code = 'TRANSACTION_FAILED'

        payload = {
            'paymentToken': {
                'id': payment.id,
                'statusCode': status_code
            }
        }

        try:
            publish_response(payload=payload)
        except Exception as e:  # pylint: disable=broad-except
            current_app.logger.error(e)
            current_app.logger.warn(
                f'Notification to Queue failed, marking the transaction : {transaction_dao.id} as EVENT_FAILED',
                e)
            transaction_dao.status_code = Status.EVENT_FAILED.value
        current_app.logger.debug('>publish_status')
    def publish_status(transaction_dao: PaymentTransactionModel,
                       invoice: Invoice):
        """Publish payment/transaction status to the Queue."""
        current_app.logger.debug('<publish_status')
        if transaction_dao.status_code == TransactionStatus.COMPLETED.value:
            if invoice.invoice_status_code == InvoiceStatus.PAID.value:
                status_code = TransactionStatus.COMPLETED.value
            else:
                current_app.logger.info(
                    f'Status {invoice.invoice_status_code} received for invoice {invoice.id}'
                )
                return
        else:
            status_code = 'TRANSACTION_FAILED'

        payload = PaymentTransaction.create_event_payload(invoice, status_code)

        try:
            publish_response(payload=payload,
                             subject=get_pay_subject_name(
                                 invoice.corp_type_code))
        except Exception as e:  # NOQA pylint: disable=broad-except
            current_app.logger.error(e)
            current_app.logger.warning(
                f'Notification to Queue failed, marking the transaction : {transaction_dao.id} as EVENT_FAILED',
                e)
            transaction_dao.status_code = TransactionStatus.EVENT_FAILED.value
        current_app.logger.debug('>publish_status')
    def _update_stale_payments(cls):
        """Update stale payment records.

        This is to handle edge cases where the user has completed payment and some error occured and payment status
        is not up-to-date.
        """
        stale_transactions = PaymentTransactionModel.find_stale_records(
            minutes=30)
        if len(stale_transactions) == 0:
            current_app.logger.info(
                f'Stale Transaction Job Ran at {datetime.datetime.now()}.But No records found!'
            )
        for transaction in stale_transactions:
            try:
                current_app.logger.info(
                    'Stale Transaction Job found records.Payment Id: {}, Transaction Id : {}'
                    .format(transaction.payment_id, transaction.id))
                TransactionService.update_transaction(transaction.id,
                                                      pay_response_url=None)
                current_app.logger.info(
                    'Stale Transaction Job Updated records.Payment Id: {}, Transaction Id : {}'
                    .format(transaction.payment_id, transaction.id))
            except BusinessException as err:  # just catch and continue .Don't stop
                current_app.logger.error(
                    'Stale Transaction Error on update_transaction')
                current_app.logger.error(err)
Beispiel #4
0
 def _publish_to_mailer(cls, invoice):
     """Construct message and send to mailer queue."""
     receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
         invoice_id=invoice.id)
     invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
         invoice_id=invoice.id,
         status_code=InvoiceReferenceStatus.COMPLETED.value)
     payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id(
         invoice_id=invoice.id)
     q_payload = dict(
         specversion='1.x-wip',
         type='bc.registry.payment.refundRequest',
         source=
         f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}',
         id=invoice.id,
         datacontenttype='application/json',
         data=dict(identifier=invoice.business_identifier,
                   orderNumber=receipt.receipt_number,
                   transactionDateTime=get_local_formatted_date_time(
                       payment_transaction.transaction_end_time),
                   transactionAmount=receipt.receipt_amount,
                   transactionId=invoice_ref.invoice_number))
     current_app.logger.debug(
         'Publishing payment refund request to mailer ')
     current_app.logger.debug(q_payload)
     publish_response(
         payload=q_payload,
         client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'),
         subject=current_app.config.get('NATS_MAILER_SUBJECT'))
    def find_by_payment_id(payment_identifier: int):
        """Find all transactions by payment id."""
        transactions_dao = PaymentTransactionModel.find_by_payment_id(payment_identifier)
        data: Dict = {'items': []}
        if transactions_dao:
            for transaction_dao in transactions_dao:
                data['items'].append(PaymentTransaction.populate(transaction_dao).asdict())

        current_app.logger.debug('>find_by_payment_id')
        return data
    def find_by_id(transaction_id: uuid):
        """Find transaction by id."""
        current_app.logger.debug(f'>find_by_id {transaction_id}')
        transaction_dao = PaymentTransactionModel.find_by_id(transaction_id)
        if not transaction_dao:
            raise BusinessException(Error.INVALID_TRANSACTION_ID)

        transaction = PaymentTransaction.__wrap_dao(transaction_dao)

        current_app.logger.debug('>find_by_id')
        return transaction
    def find_by_id(payment_identifier: int, transaction_id: uuid):
        """Find transaction by id."""
        transaction_dao = PaymentTransactionModel.find_by_id_and_payment_id(transaction_id, payment_identifier)
        if not transaction_dao:
            raise BusinessException(Error.INVALID_TRANSACTION_ID)

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

        current_app.logger.debug('>find_by_id')
        return transaction
    def find_active_by_payment_id(payment_identifier: int):
        """Find active transaction by id."""
        existing_transactions = PaymentTransactionModel.find_by_payment_id(
            payment_identifier)
        transaction: PaymentTransaction = None
        if existing_transactions:
            for existing_transaction in existing_transactions:
                if existing_transaction.status_code not in (
                        Status.COMPLETED.value, Status.CANCELLED.value):
                    transaction = existing_transaction

        current_app.logger.debug('>find_active_by_payment_id')
        return transaction
def factory_payment_transaction(
    payment_id: str,
    status_code: str = 'DRAFT',
    redirect_url: str = 'http://google.com/',
    pay_system_url: str = 'http://google.com',
    transaction_start_time: datetime = datetime.now(),
    transaction_end_time: datetime = datetime.now()):
    """Factory."""
    return PaymentTransaction(payment_id=payment_id,
                              status_code=status_code,
                              redirect_url=redirect_url,
                              pay_system_url=pay_system_url,
                              transaction_start_time=transaction_start_time,
                              transaction_end_time=transaction_end_time)
Beispiel #10
0
 def _publish_refund_to_mailer(invoice: InvoiceModel):
     """Construct message and send to mailer queue."""
     from .payment_transaction import publish_response  # pylint:disable=import-outside-toplevel,cyclic-import
     receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number(
         invoice_id=invoice.id)
     invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status(
         invoice_id=invoice.id,
         status_code=InvoiceReferenceStatus.COMPLETED.value)
     payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id(
         invoice_id=invoice.id)
     message_type: str = f'bc.registry.payment.{invoice.payment_method_code.lower()}.refundRequest'
     transaction_date_time = receipt.receipt_date if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value \
         else payment_transaction.transaction_end_time
     filing_description = ''
     for line_item in invoice.payment_line_items:
         if filing_description:
             filing_description += ','
         filing_description += line_item.description
     q_payload = dict(
         specversion='1.x-wip',
         type=message_type,
         source=
         f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}',
         id=invoice.id,
         datacontenttype='application/json',
         data=dict(identifier=invoice.business_identifier,
                   orderNumber=receipt.receipt_number,
                   transactionDateTime=get_local_formatted_date_time(
                       transaction_date_time),
                   transactionAmount=receipt.receipt_amount,
                   transactionId=invoice_ref.invoice_number,
                   refundDate=get_local_formatted_date_time(
                       datetime.now(), '%Y%m%d'),
                   filingDescription=filing_description))
     if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value:
         payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
             invoice.payment_account_id)
         q_payload['data'].update(
             dict(bcolAccount=invoice.bcol_account,
                  bcolUser=payment_account.bcol_user_id))
     current_app.logger.debug(
         f'Publishing payment refund request to mailer for {invoice.id} : {q_payload}'
     )
     publish_response(
         payload=q_payload,
         client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'),
         subject=current_app.config.get('NATS_MAILER_SUBJECT'))
Beispiel #11
0
    def create(payment_identifier: str, request_json: Dict):
        """Create transaction record."""
        current_app.logger.debug('<create transaction')
        # Lookup payment record
        payment: Payment = Payment.find_by_id(payment_identifier,
                                              skip_auth_check=True)

        # Check if return url is valid
        return_url = request_json.get('clientSystemUrl')
        if payment.payment_system_code == PaymentSystem.PAYBC.value and not is_valid_redirect_url(
                return_url):
            raise BusinessException(Error.PAY013)

        if not payment.id:
            raise BusinessException(Error.PAY005)
        # Cannot start transaction on completed payment
        if payment.payment_status_code in (Status.COMPLETED.value,
                                           Status.DELETED.value,
                                           Status.DELETE_ACCEPTED.value):
            raise BusinessException(Error.PAY006)

        # If there are active transactions (status=CREATED), then invalidate all of them and create a new one.
        existing_transactions = PaymentTransactionModel.find_by_payment_id(
            payment.id)
        if existing_transactions:
            for existing_transaction in existing_transactions:
                if existing_transaction.status_code != Status.CANCELLED.value:
                    existing_transaction.status_code = Status.CANCELLED.value
                    existing_transaction.transaction_end_time = datetime.now()
                    existing_transaction.save()

        transaction = PaymentTransaction()
        transaction.payment_id = payment.id
        transaction.client_system_url = return_url
        transaction.status_code = Status.CREATED.value
        transaction_dao = transaction.flush()
        transaction._dao = transaction_dao  # pylint: disable=protected-access
        transaction.pay_system_url = transaction.build_pay_system_url(
            payment, transaction.id, request_json.get('payReturnUrl'))
        transaction_dao = transaction.save()

        transaction = PaymentTransaction()
        transaction._dao = transaction_dao  # pylint: disable=protected-access
        current_app.logger.debug('>create transaction')

        return transaction
Beispiel #12
0
def factory_payment_transaction(
        payment_id: str,
        status_code: str = 'CREATED',
        client_system_url: str = 'http://google.com/',
        pay_system_url: str = 'http://google.com',
        transaction_start_time: datetime = datetime.now(),
        transaction_end_time: datetime = datetime.now(),
):
    """Return Factory."""
    return PaymentTransaction(
        payment_id=payment_id,
        status_code=status_code,
        client_system_url=client_system_url,
        pay_system_url=pay_system_url,
        transaction_start_time=transaction_start_time,
        transaction_end_time=transaction_end_time,
    )
    def _update_stale_payments(cls):
        """Update stale payment records.

        This is to handle edge cases where the user has completed payment and some error occured and payment status
        is not up-to-date.
        """
        stale_transactions = PaymentTransactionModel.find_stale_records(
            minutes=30)
        # Find all payments which were failed due to service unavailable error.
        service_unavailable_transactions = db.session.query(PaymentTransactionModel)\
            .join(PaymentModel, PaymentModel.id == PaymentTransactionModel.payment_id) \
            .filter(PaymentModel.payment_status_code == PaymentStatus.CREATED.value)\
            .filter(PaymentTransactionModel.pay_system_reason_code == Error.SERVICE_UNAVAILABLE.name)\
            .all()

        if len(stale_transactions) == 0 and len(
                service_unavailable_transactions) == 0:
            current_app.logger.info(
                f'Stale Transaction Job Ran at {datetime.datetime.now()}.But No records found!'
            )
        for transaction in [
                *stale_transactions, *service_unavailable_transactions
        ]:
            try:
                current_app.logger.info(
                    f'Stale Transaction Job found records.Payment Id: {transaction.payment_id}, '
                    f'Transaction Id : {transaction.id}')
                TransactionService.update_transaction(transaction.id,
                                                      pay_response_url=None)
                current_app.logger.info(
                    f'Stale Transaction Job Updated records.Payment Id: {transaction.payment_id}, '
                    f'Transaction Id : {transaction.id}')
            except BusinessException as err:  # just catch and continue .Don't stop
                # If the error is for COMPLETED PAYMENT, then mark the transaction as CANCELLED
                # as there would be COMPLETED transaction in place and continue.
                if err.code == Error.COMPLETED_PAYMENT.name:
                    current_app.logger.info(
                        'Completed payment, marking transaction as CANCELLED.')
                    transaction.status_code = TransactionStatus.CANCELLED.value
                    transaction.save()
                else:
                    current_app.logger.info(
                        'Stale Transaction Error on update_transaction')
                    current_app.logger.info(err)
    def create(payment_identifier: str,
               redirect_uri: str,
               jwt: JwtManager = None,
               skip_auth_check: bool = False):
        """Create transaction record."""
        current_app.logger.debug('<create transaction')
        # Lookup payment record
        payment: Payment = Payment.find_by_id(payment_identifier,
                                              jwt=jwt,
                                              one_of_roles=[EDIT_ROLE],
                                              skip_auth_check=skip_auth_check)

        if not payment.id:
            raise BusinessException(Error.PAY005)
        if payment.payment_status_code == Status.COMPLETED.value:  # Cannot start transaction on completed payment
            raise BusinessException(Error.PAY006)

        # If there are active transactions (status=CREATED), then invalidate all of them and create a new one.
        existing_transactions = PaymentTransactionModel.find_by_payment_id(
            payment.id)
        if existing_transactions:
            for existing_transaction in existing_transactions:
                if existing_transaction.status_code != Status.CANCELLED.value:
                    existing_transaction.status_code = Status.CANCELLED.value
                    existing_transaction.transaction_end_time = datetime.now()
                    existing_transaction.save()

        transaction = PaymentTransaction()
        transaction.payment_id = payment.id
        transaction.client_system_url = redirect_uri
        transaction.status_code = Status.CREATED.value
        transaction_dao = transaction.flush()
        transaction._dao = transaction_dao  # pylint: disable=protected-access
        transaction.pay_system_url = transaction.build_pay_system_url(
            payment, transaction.id)
        transaction_dao = transaction.save()

        transaction = PaymentTransaction()
        transaction._dao = transaction_dao  # pylint: disable=protected-access
        current_app.logger.debug('>create transaction')

        return transaction
def run():
    application = create_app()
    application.logger.debug('Ran Batch Job--')

    application.app_context().push()
    stale_transactions = PaymentTransactionModel.find_stale_records(hours=4)
    if len(stale_transactions) == 0:
        application.logger.info(
            f' Job Ran at {datetime.datetime.now()}.But No records found!')
    for transaction in stale_transactions:
        try:
            application.logger.debug(
                'Job found records.Payment Id: {}, Transaction Id : {}'.format(
                    transaction.payment_id, transaction.id))
            TransactionService.update_transaction(transaction.payment_id,
                                                  transaction.id, '')
            application.logger.debug(
                'Job Updated records.Payment Id: {}, Transaction Id : {}'.
                format(transaction.payment_id, transaction.id))
        except BusinessException as err:  # just catch and continue .Don't stop
            application.logger.error('Error on update_transaction')
            application.logger.error(err)
    def _create_transaction(payment: Payment,
                            request_json: Dict,
                            invoice: Invoice = None):
        # Cannot start transaction on completed payment
        if payment.payment_status_code in (PaymentStatus.COMPLETED.value,
                                           PaymentStatus.DELETED.value):
            raise BusinessException(Error.COMPLETED_PAYMENT)

        pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method(
            # todo Remove this and use payment.payment_method_code when payment methods are not created upfront
            payment_method=invoice.payment_method_code if invoice else payment.
            payment_method_code)

        # If there are active transactions (status=CREATED), then invalidate all of them and create a new one.
        existing_transaction = PaymentTransactionModel.find_active_by_payment_id(
            payment.id)
        if existing_transaction and existing_transaction.status_code != TransactionStatus.CANCELLED.value:
            existing_transaction.status_code = TransactionStatus.CANCELLED.value
            existing_transaction.transaction_end_time = datetime.now()
            existing_transaction.save()
        transaction = PaymentTransaction()
        transaction.payment_id = payment.id
        transaction.client_system_url = request_json.get('clientSystemUrl')
        transaction.status_code = TransactionStatus.CREATED.value
        transaction_dao = transaction.flush()
        transaction._dao = transaction_dao  # pylint: disable=protected-access
        if invoice:
            transaction.pay_system_url = PaymentTransaction._build_pay_system_url_for_invoice(
                invoice, pay_system_service, transaction.id,
                request_json.get('payReturnUrl'))
        else:
            transaction.pay_system_url = PaymentTransaction._build_pay_system_url_for_payment(
                payment, pay_system_service, transaction.id,
                request_json.get('payReturnUrl'))
        transaction_dao = transaction.save()
        transaction = PaymentTransaction.__wrap_dao(transaction_dao)
        return transaction
Beispiel #17
0
def factory_payment_transaction(payment_id: int):
    """Return Factory."""
    return PaymentTransaction(payment_id=payment_id,
                              status_code=TransactionStatus.CREATED.value,
                              transaction_start_time=datetime.now()).save()
Beispiel #18
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
    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
 def find_active_by_invoice_id(invoice_id: int):
     """Find active transaction by invoice id."""
     current_app.logger.debug('>find_active_by_invoice_id')
     active_transaction = PaymentTransactionModel.find_active_by_invoice_id(
         invoice_id)
     return PaymentTransaction.populate(active_transaction)
 def find_active_by_payment_id(payment_identifier: int):
     """Find active transaction by id."""
     current_app.logger.debug('>find_active_by_payment_id')
     active_transaction = PaymentTransactionModel.find_active_by_payment_id(payment_identifier)
     return PaymentTransaction.populate(active_transaction)
    def update_transaction(payment_identifier: int, transaction_id: uuid,  # pylint: disable=too-many-locals
                           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.INVALID_TRANSACTION_ID)
        if transaction_dao.status_code == TransactionStatus.COMPLETED.value:
            raise BusinessException(Error.INVALID_TRANSACTION)

        payment: Payment = Payment.find_by_id(payment_identifier, skip_auth_check=True)

        if payment.payment_status_code == PaymentStatus.COMPLETED.value:
            raise BusinessException(Error.COMPLETED_PAYMENT)

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

        invoice = Invoice.find_by_payment_identifier(payment_identifier, skip_auth_check=True)
        invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(invoice.id)
        payment_account = PaymentAccount.find_by_pay_system_id(
            credit_account_id=invoice.credit_account_id,
            internal_account_id=invoice.internal_account_id,
            bcol_account_id=invoice.bcol_account_id)

        try:
            receipt_details = pay_system_service.get_receipt(payment_account, receipt_number, invoice_reference)
            txn_reason_code = None
        except ServiceUnavailableException as exc:
            txn_reason_code = exc.status
            receipt_details = None

        if receipt_details:
            # Find if a receipt exists with same receipt_number for the invoice
            receipt = PaymentTransaction.__save_receipt(invoice, receipt_details)

            invoice.paid = receipt.receipt_amount

            if invoice.paid == invoice.total:
                invoice.invoice_status_code = InvoiceStatus.PAID.value
                payment.payment_status_code = PaymentStatus.COMPLETED.value
                payment.save()

                invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value
                invoice_reference.save()

            invoice.save()

            transaction_dao.status_code = TransactionStatus.COMPLETED.value
        else:
            transaction_dao.status_code = TransactionStatus.FAILED.value

        transaction_dao.transaction_end_time = datetime.now()

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

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