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 _update_receipt_details(invoices, payment, receipt_details,
                                transaction_dao):
        """Update receipt details to invoice."""
        payment.paid_amount = receipt_details[2]
        payment.completed_on = datetime.now()
        transaction_dao.status_code = TransactionStatus.COMPLETED.value

        if float(payment.paid_amount) < float(payment.invoice_amount):
            current_app.logger.critical(
                'ALERT : Paid Amount is less than owed amount.  Paid : %s, Owed- %s',
                payment.paid_amount, payment.invoice_amount)
            capture_message(
                f'ALERT : Paid Amount is less than owed amount.  Paid : {payment.paid_amount}, '
                f'Owed: {payment.invoice_amount}',
                level='error')
        else:
            payment.payment_status_code = PaymentStatus.COMPLETED.value

            for invoice in invoices:
                # Save receipt details for each invoice
                PaymentTransaction.__save_receipt(invoice, receipt_details)
                invoice.paid = invoice.total  # set the paid amount as total
                invoice.invoice_status_code = InvoiceStatus.PAID.value
                invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(
                    invoice.id)
                invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value
                # TODO If it's not PAD, publish message. Refactor and move to pay system service later.
                if invoice.payment_method_code != PaymentMethod.PAD.value:
                    PaymentTransaction.publish_status(transaction_dao, invoice)
Exemple #3
0
    def create_invoice(
            self,
            payment_account: PaymentAccount,  # pylint: disable=too-many-locals
            line_items: [PaymentLineItem],
            invoice: Invoice,
            **kwargs) -> InvoiceReference:
        """Create Invoice in PayBC."""
        current_app.logger.debug('<create_invoice')
        # Build line item model array, as that's needed for CFS Service
        line_item_models: List[PaymentLineItemModel] = []
        for line_item in line_items:
            line_item_models.append(
                PaymentLineItemModel.find_by_id(line_item.id))

        invoice_response = self.create_account_invoice(invoice.id,
                                                       line_item_models,
                                                       payment_account)

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

        current_app.logger.debug('>create_invoice')
        return invoice_reference
Exemple #4
0
 def create_invoice(self, payment_account: PaymentAccount,
                    line_items: [PaymentLineItem], invoice: Invoice,
                    **kwargs) -> InvoiceReference:
     """Return a static invoice number for direct pay."""
     invoice_reference: InvoiceReference = InvoiceReference.create(
         invoice.id, generate_transaction_number(invoice.id), None)
     return invoice_reference
Exemple #5
0
def test_active_reference_by_invoice_id(session):
    """Assert that the invoice reference lookup is working."""
    payment_account = factory_payment_account()
    payment = factory_payment()
    payment_account.save()
    payment.save()
    i = factory_invoice(payment=payment, payment_account=payment_account)
    i.save()

    InvoiceReference.create(i.id, 'TEST_INV_NUMBER', 'TEST_REF_NUMBER')

    # Do a look up
    invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(i.id)

    assert invoice_reference is not None
    assert invoice_reference.id is not None
    assert invoice_reference.invoice_id == i.id
Exemple #6
0
    def _build_pay_system_url_for_payment(payment: Payment, pay_system_service: PaymentSystemService,
                                          transaction_id: uuid, pay_return_url: str):
        """Build pay system url which will be used to redirect to the payment system."""
        current_app.logger.debug('<build_pay_system_url')
        invoice_reference = InvoiceReference.find_any_active_reference_by_invoice_number(payment.invoice_number)
        return_url = f'{pay_return_url}/{payment.id}/transaction/{transaction_id}'

        current_app.logger.debug('>build_pay_system_url')
        return pay_system_service.get_payment_system_url_for_payment(payment, invoice_reference, return_url)
    def create_invoice(self, payment_account: PaymentAccount, line_items: [PaymentLineItem], invoice: Invoice,
                       **kwargs) -> InvoiceReference:
        """Return a static invoice number."""
        current_app.logger.debug('<create_invoice')

        invoice_reference: InvoiceReference = InvoiceReference.create(invoice.id,
                                                                      generate_transaction_number(invoice.id), None)

        current_app.logger.debug('>create_invoice')
        return invoice_reference
Exemple #8
0
def test_find_completed_reference_by_invoice_id(session):
    """Assert that the completed invoice reference lookup is working."""
    payment_account = factory_payment_account()
    payment = factory_payment()
    payment_account.save()
    payment.save()
    i = factory_invoice(payment=payment, payment_account=payment_account)
    i.save()

    invoice_reference = InvoiceReference.create(i.id, 'TEST_INV_NUMBER', 'TEST_REF_NUMBER')
    invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value
    invoice_reference.save()

    # Do a look up
    invoice_reference = InvoiceReference.find_completed_reference_by_invoice_id(i.id)

    assert invoice_reference is not None
    assert invoice_reference.id is not None
    assert invoice_reference.invoice_id == i.id
Exemple #9
0
 def wrapper(*func_args, **func_kwargs):
     """Complete any post invoice activities if needed."""
     user: UserContext = func_kwargs['user']
     if user.is_sandbox():
         current_app.logger.info(
             'Skipping invoice creation as sandbox token is detected.')
         invoice: Invoice = func_args[
             3]  # 3 is invoice from the create_invoice signature
         return InvoiceReference.create(invoice.id, f'SANDBOX-{invoice.id}',
                                        f'REF-{invoice.id}')
     return function(*func_args, **func_kwargs)
    def build_pay_system_url(payment: Payment, transaction_id: uuid, pay_return_url: str):
        """Build pay system url which will be used to redirect to the payment system."""
        current_app.logger.debug('<build_pay_system_url')
        pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_system_code(
            payment_system=payment.payment_system_code
        )
        invoice = InvoiceModel.find_by_payment_id(payment.id)
        invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(invoice.id)
        return_url = f'{pay_return_url}/{payment.id}/transaction/{transaction_id}'

        current_app.logger.debug('>build_pay_system_url')
        return pay_system_service.get_payment_system_url(Invoice.populate(invoice), invoice_reference, return_url)
Exemple #11
0
 def create_invoice(self, payment_account: PaymentAccount,
                    line_items: [PaymentLineItem], invoice: Invoice,
                    **kwargs) -> InvoiceReference:
     """Return a static invoice number."""
     invoice_reference: InvoiceReference = None
     # If the account is not billable, then create records,
     if not payment_account.billable:
         current_app.logger.debug(
             f'Non billable invoice {invoice.id}, '
             f'Auth Account : {payment_account.auth_account_id}')
         invoice_reference = InvoiceReference.create(
             invoice.id, generate_transaction_number(invoice.id), None)
     # else Do nothing here as the invoice references are created later.
     return invoice_reference
Exemple #12
0
def test_invoice_saved_from_new(session):
    """Assert that the invoice reference is saved to the table."""
    payment_account = factory_payment_account()
    payment = factory_payment()
    payment_account.save()
    payment.save()
    i = factory_invoice(payment=payment, payment_account=payment_account)
    i.save()

    invoice_reference = InvoiceReference.create(i.id, 'TEST_INV_NUMBER', 'TEST_REF_NUMBER')

    assert invoice_reference is not None
    assert invoice_reference.id is not None
    assert invoice_reference.invoice_id == i.id
    assert invoice_reference.status_code == InvoiceReferenceStatus.ACTIVE.value
Exemple #13
0
    def process_cfs_refund(self, invoice: InvoiceModel) -> str:  # pylint:disable=unused-argument
        """Do nothing to process refund; as the refund is handled by CRON job.

        Return the status after checking invoice status.
            1. If invoice status is APPROVED:
            1.1 return REFUND_REQUESTED if there is an ACTIVE invoice_reference
            1.2 else return REFUNDED (as no refund process is needed for this as JV hasn't started yet)
            2. If invoice status is PAID
            2.1 Return REFUND_REQUESTED
        """
        current_app.logger.info(
            f'Received JV refund for invoice {invoice.id}, {invoice.invoice_status_code}'
        )
        if invoice.invoice_status_code == InvoiceStatus.APPROVED.value:
            if InvoiceReference.find_active_reference_by_invoice_id(
                    invoice.id):
                return InvoiceStatus.REFUND_REQUESTED.value
            return InvoiceStatus.REFUNDED.value
        return InvoiceStatus.REFUND_REQUESTED.value
    def create_transaction_for_invoice(
            invoice_id: int, request_json: Dict) -> PaymentTransaction:
        """Create transaction record for invoice, by creating a payment record if doesn't exist."""
        current_app.logger.debug('<create transaction')
        # Lookup invoice record
        invoice: Invoice = Invoice.find_by_id(invoice_id, skip_auth_check=True)
        if not invoice.id:
            raise BusinessException(Error.INVALID_INVOICE_ID)
        if invoice.payment_method_code == PaymentMethod.PAD.value:  # No transaction needed for PAD invoices.
            raise BusinessException(Error.INVALID_TRANSACTION)

        pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method(
            payment_method=invoice.payment_method_code)
        # Check if return url is valid
        PaymentTransaction._validate_redirect_url_and_throw_error(
            invoice.payment_method_code, request_json.get('clientSystemUrl'))

        # Check if there is a payment created. If not, create a payment record with status CREATED
        payment: Payment = Payment.find_payment_for_invoice(invoice_id)
        if not payment:
            # Transaction is against payment, so create a payment if not present.
            invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(
                invoice.id)

            # Create a payment record
            payment = Payment.create(
                payment_method=pay_system_service.get_payment_method_code(),
                payment_system=pay_system_service.get_payment_system_code(),
                payment_status=pay_system_service.get_default_payment_status(),
                invoice_number=invoice_reference.invoice_number,
                invoice_amount=invoice.total,
                payment_account_id=invoice.payment_account_id)

        transaction = PaymentTransaction._create_transaction(payment,
                                                             request_json,
                                                             invoice=invoice)
        current_app.logger.debug('>create transaction')

        return transaction
class InternalPayService(PaymentSystemService, OAuthService):
    """Service to manage internal payment."""
    def get_payment_system_code(self):
        """Return INTERNAL as the system code."""
        return PaymentSystem.INTERNAL.value

    def create_invoice(self, payment_account: PaymentAccount,
                       line_items: [PaymentLineItem], invoice: Invoice,
                       **kwargs) -> InvoiceReference:
        """Return a static invoice number."""
        routing_slip = None
        is_zero_dollar_invoice = get_quantized(invoice.total) == 0
        invoice_reference: InvoiceReference = None
        if routing_slip_number := invoice.routing_slip:
            current_app.logger.info(
                f'Routing slip number {routing_slip_number}, for invoice {invoice.id}'
            )
            routing_slip = RoutingSlipModel.find_by_number(routing_slip_number)
            InternalPayService._validate_routing_slip(routing_slip, invoice)
        if not is_zero_dollar_invoice and routing_slip is not None:
            # creating invoice in cfs is done in job
            current_app.logger.info(
                f'FAS Routing slip found with remaining amount : {routing_slip.remaining_amount}'
            )
            routing_slip.remaining_amount = routing_slip.remaining_amount - \
                get_quantized(invoice.total)
            if routing_slip.status == RoutingSlipStatus.ACTIVE.value and routing_slip.remaining_amount < 0.01:
                routing_slip.status = RoutingSlipStatus.COMPLETE.value
            routing_slip.flush()
        else:
            invoice_reference = InvoiceReference.create(
                invoice.id, generate_transaction_number(invoice.id), None)
            invoice.invoice_status_code = InvoiceStatus.CREATED.value
            invoice.save()

        return invoice_reference
Exemple #16
0
def test_invoice_invalid_lookup(session):
    """Test invalid lookup."""
    inv_reference = InvoiceReference.find_active_reference_by_invoice_id(999)
    assert inv_reference is None
    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()
    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()
    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
Exemple #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 == 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):
        """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