Пример #1
0
async def cb_subscription_handler(msg: nats.aio.client.Msg):
    """Use Callback to process Queue Msg objects."""
    try:
        logger.info('Received raw message seq:%s, data=  %s', msg.sequence,
                    msg.data.decode())
        payment_token = extract_payment_token(msg)
        logger.debug('Extracted payment token: %s', payment_token)
        await process_payment(payment_token, FLASK_APP)
    except OperationalError as err:
        logger.error('Queue Blocked - Database Issue: %s',
                     json.dumps(payment_token),
                     exc_info=True)
        raise err  # We don't want to handle the error, as a DB down would drain the queue
    except FilingException:
        # log to sentry and absorb the error, ie: do NOT raise it, otherwise the message would be put back on the queue
        if APP_CONFIG.ENVIRONMENT == 'prod':
            capture_message('Queue Error: cannot find filing: %s' %
                            json.dumps(payment_token),
                            level='error')
            logger.error('Queue Error - cannot find filing: %s',
                         json.dumps(payment_token),
                         exc_info=True)
    except (QueueException, Exception):  # pylint: disable=broad-except
        # Catch Exception so that any error is still caught and the message is removed from the queue
        capture_message('Queue Error:' + json.dumps(payment_token),
                        level='error')
        logger.error('Queue Error: %s',
                     json.dumps(payment_token),
                     exc_info=True)
Пример #2
0
 async def close(self):
     """Close the stream and nats connections."""
     try:
         await self.sc.close()
         await self.nc.close()
     except Exception as err:  # pylint: disable=broad-except; catch all errors to log out when closing the service.
         logger.debug('error when closing the streams: %s', err, stack_info=True)
Пример #3
0
def process(email_msg: dict) -> dict:
    """Build the email for mras notification."""
    logger.debug('mras_notification: %s', email_msg)
    # get template and fill in parts
    template = Path(f'{current_app.config.get("TEMPLATE_PATH")}/BC-MRAS.html').read_text()
    filled_template = substitute_template_parts(template)
    # get template info from filing
    filing, business, leg_tmz_filing_date, leg_tmz_effective_date = get_filing_info(email_msg['filingId'])

    # render template with vars
    jnja_template = Template(filled_template, autoescape=True)
    html_out = jnja_template.render(
        business=business,
        incorporationApplication=(filing.json)['filing']['incorporationApplication'],
        header=(filing.json)['filing']['header'],
        filing_date_time=leg_tmz_filing_date,
        effective_date_time=leg_tmz_effective_date
    )

    # get recipients
    recipients = get_recipients(email_msg['option'], filing.filing_json)

    return {
        'recipients': recipients,
        'requestBy': '*****@*****.**',
        'content': {
            'subject': 'BC Business Registry Partner Information',
            'body': f'{html_out}',
            'attachments': []
        }
    }
Пример #4
0
def process(email_msg: dict, token: str) -> dict:
    """Build the email for PAD Confirmation notification."""
    logger.debug('email_msg notification: %s', email_msg)
    # fill in template

    username = email_msg.get('padTosAcceptedBy')
    pad_tos_file_name = current_app.config['PAD_TOS_FILE']
    admin_emails, admin_name = _get_admin_emails(username)
    pdf_attachment = _get_pad_confirmation_report_pdf(email_msg, token)
    tos_attachment = _get_pdf(pad_tos_file_name)
    html_body = _get_pad_confirmation_email_body(email_msg, admin_name)
    return {
        'recipients': admin_emails,
        'content': {
            'subject':
            'Confirmation of Pre-Authorized Debit (PAD) Sign-up',
            'body':
            f'{html_body}',
            'attachments': [{
                'fileName': 'PAD_Confirmation_Letter.pdf',
                'fileBytes': pdf_attachment.decode('utf-8'),
                'fileUrl': '',
                'attachOrder': '1'
            }, {
                'fileName': pad_tos_file_name,
                'fileBytes': tos_attachment.decode('utf-8'),
                'fileUrl': '',
                'attachOrder': '2'
            }]
        }
    }
Пример #5
0
async def cb_subscription_handler(msg: nats.aio.client.Msg):
    """Use Callback to process Queue Msg objects."""
    try:
        logger.info('Received raw message seq:%s, data=  %s', msg.sequence,
                    msg.data.decode())
        payment_token = extract_payment_token(msg)
        logger.debug('Extracted payment token: %s', payment_token)
        await process_payment(payment_token, FLASK_APP)
    except OperationalError as err:
        logger.error('Queue Blocked - Database Issue: %s',
                     json.dumps(payment_token),
                     exc_info=True)
        raise err  # We don't want to handle the error, as a DB down would drain the queue
    except FilingException as err:
        logger.error(
            'Queue Error - cannot find filing: %s'
            '\n\nThis message has been put back on the queue for reprocessing.',
            json.dumps(payment_token),
            exc_info=True)
        raise err  # we don't want to handle the error, so that the message gets put back on the queue
    except (QueueException, Exception):  # pylint: disable=broad-except
        # Catch Exception so that any error is still caught and the message is removed from the queue
        capture_message('Queue Error:' + json.dumps(payment_token),
                        level='error')
        logger.error('Queue Error: %s',
                     json.dumps(payment_token),
                     exc_info=True)
Пример #6
0
def process(email_msg: dict) -> dict:
    """Build the email for Business Number notification."""
    logger.debug('bn notification: %s', email_msg)

    # get template and fill in parts
    template = Path(f'{current_app.config.get("TEMPLATE_PATH")}/BC-BN.html').read_text()
    filled_template = substitute_template_parts(template)

    # get filing and business json
    business = Business.find_by_identifier(email_msg['identifier'])
    filing = (Filing.get_a_businesses_most_recent_filing_of_a_type(business.id, 'incorporationApplication'))

    # render template with vars
    jnja_template = Template(filled_template, autoescape=True)
    html_out = jnja_template.render(
        business=business.json()
    )

    # get recipients
    recipients = get_recipients(email_msg['option'], filing.filing_json)
    return {
        'recipients': recipients,
        'requestBy': '*****@*****.**',
        'content': {
            'subject': f'{business.legal_name} - Business Number Information',
            'body': html_out,
            'attachments': []
        }
    }
Пример #7
0
def _process_partial_paid_invoices(inv_ref: InvoiceReferenceModel, row):
    """Process partial payments.

    Update Payment as COMPLETED.
    Update Transaction is COMPLETED.
    Update Invoice as PARTIAL.
    """
    receipt_date: datetime = datetime.strptime(
        _get_row_value(row, Column.APP_DATE), '%d-%b-%y')
    receipt_number: str = _get_row_value(row, Column.APP_ID)

    inv: InvoiceModel = InvoiceModel.find_by_id(inv_ref.invoice_id)
    _validate_account(inv, row)
    logger.debug('Partial Invoice. Invoice Reference ID : %s, invoice ID : %s',
                 inv_ref.id, inv_ref.invoice_id)
    inv.invoice_status_code = InvoiceStatus.PARTIAL.value
    inv.paid = inv.total - float(
        _get_row_value(row, Column.TARGET_TXN_OUTSTANDING))
    # Create Receipt records
    receipt: ReceiptModel = ReceiptModel()
    receipt.receipt_date = receipt_date
    receipt.receipt_amount = float(_get_row_value(row, Column.APP_AMOUNT))
    receipt.invoice_id = inv.id
    receipt.receipt_number = receipt_number
    db.session.add(receipt)
Пример #8
0
def process(org_id, recipients, template_name, subject, **kwargs) -> dict:
    """Build the email for Account notification."""
    logger.debug('account  notification: %s', org_id)

    org: OrgModel = OrgModel.find_by_id(org_id)
    # fill in template
    filled_template = generate_template(
        current_app.config.get('TEMPLATE_PATH'), template_name)
    current_time = datetime.now()
    # render template with vars from email msg
    jnja_template = Template(filled_template, autoescape=True)
    jinja_kwargs = {
        'account_name': org.name,
        'url': get_login_url(),
        'today': current_time.strftime('%m-%d-%Y'),
        **kwargs
    }
    html_out = jnja_template.render(jinja_kwargs)
    return {
        'recipients': recipients,
        'content': {
            'subject': subject,
            'body': html_out,
            'attachments': []
        }
    }
Пример #9
0
async def _process_credit_on_invoices(row):
    # Credit memo can happen for any type of accounts.
    target_txn_status = _get_row_value(row, Column.TARGET_TXN_STATUS)
    if _get_row_value(row, Column.TARGET_TXN) == TargetTransaction.INV.value:
        inv_number = _get_row_value(row, Column.TARGET_TXN_NO)
        logger.debug('Processing invoice :  %s', inv_number)

        inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \
            filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.ACTIVE.value). \
            filter(InvoiceReferenceModel.invoice_number == inv_number). \
            all()

        if target_txn_status.lower() == Status.PAID.value.lower():
            logger.debug('Fully PAID payment.')
            await _process_paid_invoices(inv_references, row)
        elif target_txn_status.lower() == Status.PARTIAL.value.lower():
            logger.info(
                'Partially PAID using credit memo. Ignoring as the credit memo payment is already captured.'
            )
        else:
            logger.error(
                'Target Transaction status is received as %s for CMAP, and cannot process.',
                target_txn_status)
            capture_message(
                f'Target Transaction status is received as {target_txn_status} for CMAP, and cannot process.',
                level='error')
Пример #10
0
async def _process_paid_invoices(inv_references, row):
    """Process PAID invoices.

    Update invoices as PAID
    Update payment as COMPLETED
    Update invoice_reference as COMPLETED
    Update payment_transaction as COMPLETED.
    """
    receipt_date: datetime = datetime.strptime(
        _get_row_value(row, Column.APP_DATE), '%d-%b-%y')
    receipt_number: str = _get_row_value(row, Column.SOURCE_TXN_NO)
    for inv_ref in inv_references:
        inv_ref.status_code = InvoiceReferenceStatus.COMPLETED.value
        # Find invoice, update status
        inv: InvoiceModel = InvoiceModel.find_by_id(inv_ref.invoice_id)
        _validate_account(inv, row)
        logger.debug(
            'PAID Invoice. Invoice Reference ID : %s, invoice ID : %s',
            inv_ref.id, inv_ref.invoice_id)

        inv.invoice_status_code = InvoiceStatus.PAID.value
        inv.paid = inv.total
        # Create Receipt records
        receipt: ReceiptModel = ReceiptModel()
        receipt.receipt_date = receipt_date
        receipt.receipt_amount = inv.total
        receipt.invoice_id = inv.id
        receipt.receipt_number = receipt_number
        db.session.add(receipt)
        # Publish to the queue if it's an Online Banking payment
        if inv.payment_method_code == PaymentMethod.ONLINE_BANKING.value:
            logger.debug('Publishing payment event for OB. Invoice : %s',
                         inv.id)
            await _publish_payment_event(inv)
Пример #11
0
async def _reconcile_payments(msg: Dict[str, any]):
    """Read the file and update payment details.

    1: Parse the file and create a dict per row for easy access.
    2: If the transaction is for invoice,
    2.1 : If transaction status is PAID, update invoice and payment statuses, publish to account mailer.
        For Online Banking invoices, publish message to the payment queue.
    2.2 : If transaction status is NOT PAID, update payment status, publish to account mailer and events to handle NSF.
    2.3 : If transaction status is PARTIAL, update payment and invoice status, publish to account mailer.
    3: If the transaction is On Account for Credit, apply the credit to the account.
    """
    file_name: str = msg.get('data').get('fileName')
    minio_location: str = msg.get('data').get('location')
    file = get_object(minio_location, file_name)
    content = file.data.decode('utf-8-sig')
    # Iterate the rows and create key value pair for each row
    for row in csv.DictReader(content.splitlines()):
        # Convert lower case keys to avoid any key mismatch
        row = dict((k.lower(), v) for k, v in row.items())
        logger.debug('Processing %s', row)

        # If PAD, lookup the payment table and mark status based on the payment status
        # If BCOL, lookup the invoices and set the status:
        # Create payment record by looking the receipt_number
        # If EFT/WIRE, lookup the invoices and set the status:
        # Create payment record by looking the receipt_number
        # PS : Duplicating some code to make the code more readable.
        if (record_type := _get_row_value(row, Column.RECORD_TYPE)) \
                in (RecordType.PAD.value, RecordType.PADR.value, RecordType.PAYR.value):
            # Handle invoices
            await _process_consolidated_invoices(row)
        elif record_type in (RecordType.BOLP.value, RecordType.EFTP.value):
            # EFT, WIRE and Online Banking are one-to-one invoice. So handle them in same way.
            await _process_unconsolidated_invoices(row)
def process(business: Business, filing: Dict):
    """Render the annual_report onto the business model objects."""
    logger.debug('processing Voluntary Dissolution: %s', filing)
    dissolution_date = date.fromisoformat(
        filing['voluntaryDissolution'].get('dissolutionDate'))
    # Currently we don't use this for anything?
    # has_liabilities = filing['voluntaryDissolution'].get('hasLiabilities')
    business.dissolution_date = dissolution_date
Пример #13
0
def process_email(email_dict: dict, flask_app: Flask, token: str):  # pylint: disable=too-many-branches
    """Process the email contained in the message."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():
        logger.debug('Attempting to process email: %s', email_dict.get('recipients', ''))
        # get type from email
        notification_service.send_email(email_dict, token=token)
Пример #14
0
async def cb_subscription_handler(msg: nats.aio.client.Msg):
    """Use Callback to process Queue Msg objects."""
    event_message = None
    try:
        logger.info('Received raw message seq:%s, data=  %s', msg.sequence, msg.data.decode())
        event_message = json.loads(msg.data.decode('utf-8'))
        logger.debug('Event Message Received: %s', event_message)
        await process_event(event_message, FLASK_APP)
    except Exception:  # NOQA # pylint: disable=broad-except
        # Catch Exception so that any error is still caught and the message is removed from the queue
        logger.error('Queue Error: %s', json.dumps(event_message), exc_info=True)
Пример #15
0
def process(business: Business, filing: Dict, filing_meta: FilingMeta):
    """Render the annual_report onto the business model objects."""
    logger.debug('processing Change of Name: %s', filing)

    new_name = filing['changeOfName'].get('legalName')

    filing_meta.change_of_name = {
        'fromLegalName': business.legal_name,
        'toLegalName': new_name
    }

    business.legal_name = new_name
Пример #16
0
async def cb_subscription_handler(msg: nats.aio.client.Msg):
    """Use Callback to process Queue Msg objects."""
    with FLASK_APP.app_context():

        try:
            logger.info('Received raw message seq: %s, data=  %s',
                        msg.sequence, msg.data.decode())
            email_msg = json.loads(msg.data.decode('utf-8'))
            logger.debug('Extracted email msg: %s', email_msg)
            message_context_properties = tracker_util.get_message_context_properties(
                msg)
            process_message, tracker_msg = tracker_util.is_processable_message(
                message_context_properties)
            if process_message:
                tracker_msg = tracker_util.start_tracking_message(
                    message_context_properties, email_msg, tracker_msg)
                process_email(email_msg, FLASK_APP)
                tracker_util.complete_tracking_message(tracker_msg)
            else:
                # Skip processing of message due to message state - previously processed or currently being
                # processed
                logger.debug('Skipping processing of email_msg: %s', email_msg)
        except OperationalError as err:
            logger.error('Queue Blocked - Database Issue: %s',
                         json.dumps(email_msg),
                         exc_info=True)
            error_details = f'OperationalError - {str(err)}'
            tracker_util.mark_tracking_message_as_failed(
                message_context_properties, email_msg, tracker_msg,
                error_details)
            raise err  # We don't want to handle the error, as a DB down would drain the queue
        except EmailException as err:
            logger.error(
                'Queue Error - email failed to send: %s'
                '\n\nThis message has been put back on the queue for reprocessing.',
                json.dumps(email_msg),
                exc_info=True)
            error_details = f'EmailException - {str(err)}'
            tracker_util.mark_tracking_message_as_failed(
                message_context_properties, email_msg, tracker_msg,
                error_details)
            raise err  # we don't want to handle the error, so that the message gets put back on the queue
        except (QueueException, Exception) as err:  # noqa B902; pylint: disable=W0703;
            # Catch Exception so that any error is still caught and the message is removed from the queue
            capture_message('Queue Error: ' + json.dumps(email_msg),
                            level='error')
            logger.error('Queue Error: %s',
                         json.dumps(email_msg),
                         exc_info=True)
            error_details = f'QueueException, Exception - {str(err)}'
            tracker_util.mark_tracking_message_as_failed(
                message_context_properties, email_msg, tracker_msg,
                error_details)
Пример #17
0
async def _process_unconsolidated_invoices(row):
    target_txn_status = _get_row_value(row, Column.TARGET_TXN_STATUS)
    record_type = _get_row_value(row, Column.RECORD_TYPE)
    if (target_txn :=
            _get_row_value(row,
                           Column.TARGET_TXN)) == TargetTransaction.INV.value:
        inv_number = _get_row_value(row, Column.TARGET_TXN_NO)

        inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \
            filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.ACTIVE.value). \
            filter(InvoiceReferenceModel.invoice_number == inv_number). \
            all()

        if len(inv_references) != 1:
            # There could be case where same invoice can appear as PAID in 2 lines, especially when there are credits.
            # Make sure there is one invoice_reference with completed status, else raise error.
            completed_inv_references: List[InvoiceReferenceModel] = db.session.query(InvoiceReferenceModel). \
                filter(InvoiceReferenceModel.status_code == InvoiceReferenceStatus.COMPLETED.value). \
                filter(InvoiceReferenceModel.invoice_number == inv_number). \
                all()
            logger.info(
                'Found %s completed invoice references for invoice number %s',
                len(completed_inv_references), inv_number)
            if len(completed_inv_references) != 1:
                logger.error(
                    'More than one or none invoice reference received for invoice number %s for %s',
                    inv_number, record_type)
                capture_message(
                    'More than one or none invoice reference received for invoice number {inv_number} for {record_type}'
                    .format(inv_number=inv_number, record_type=record_type),
                    level='error')
        else:
            payment_account = _get_payment_account(row)
            # Handle fully PAID and Partially Paid scenarios.
            if target_txn_status.lower() == Status.PAID.value.lower():
                logger.debug('Fully PAID payment.')
                await _process_paid_invoices(inv_references, row)
                await _publish_mailer_events('OnlineBanking.PaymentSuccess',
                                             payment_account, row)
            elif target_txn_status.lower() == Status.PARTIAL.value.lower():
                logger.info('Partially PAID.')
                # As per validation above, get first and only inv ref
                _process_partial_paid_invoices(inv_references[0], row)
                await _publish_mailer_events('OnlineBanking.PartiallyPaid',
                                             payment_account, row)
            else:
                logger.error(
                    'Target Transaction Type is received as %s for %s, and cannot process.',
                    target_txn, record_type)
                capture_message(
                    'Target Transaction Type is received as {target_txn} for {record_type}, and cannot process.'
                    .format(target_txn=target_txn, record_type=record_type),
                    level='error')
Пример #18
0
    async def connect(self):
        """Connect the service worker to th3e NATS/STAN Queue.

        Also handles reconnecting when the network has dropped the connection.
        Both the NATS and the STAN clients need to be reinstantiated to work correctly.

        """
        if not self.config:
            logger.error('missing configuration object.')
            raise AttributeError('missing configuration object.')

        logger.info('Connecting...')
        if self.nc:
            try:
                logger.debug('close old NATS client')
                await self.nc.close()
            except asyncio.CancelledError as err:
                logger.debug('closing stale connection err:%s', err)
            finally:
                self.nc = None

        self.nc = NATS()
        self.sc = STAN()

        nats_connection_options = {
            **self.config.NATS_CONNECTION_OPTIONS,
            ** {'loop': self._loop,
                'error_cb': error_cb},
            **self.nats_connection_options
        }

        stan_connection_options = {
            **self.config.STAN_CONNECTION_OPTIONS,
            **{'nats': self.nc,
               'conn_lost_cb': self._stan_conn_lost_cb,
               'loop': self._loop},
            **self.stan_connection_options
        }

        subscription_options = {
            **self.config.SUBSCRIPTION_OPTIONS,
            **{'cb': self.cb_handler},
            **self.subscription_options
        }

        await self.nc.connect(**nats_connection_options)
        await self.sc.connect(**stan_connection_options)
        await self.sc.subscribe(**subscription_options)

        logger.info('Subscribe the callback: %s to the queue: %s.',
                    subscription_options.get('cb').__name__ if subscription_options.get('cb') else 'no_call_back',
                    subscription_options.get('queue'))
Пример #19
0
def process_email(email_msg: dict, flask_app: Flask):  # pylint: disable=too-many-branches
    """Process the email contained in the submission."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():
        logger.debug('Attempting to process email: %s', email_msg)
        token = AccountService.get_bearer_token()
        etype = email_msg.get('type', None)
        if etype and etype == 'bc.registry.names.request':
            option = email_msg.get('data', {}).get('request',
                                                   {}).get('option', None)
            if option and option in [
                    nr_notification.Option.BEFORE_EXPIRY.value,
                    nr_notification.Option.EXPIRED.value,
                    nr_notification.Option.RENEWAL.value,
                    nr_notification.Option.UPGRADE.value,
                    nr_notification.Option.REFUND.value
            ]:
                email = nr_notification.process(email_msg, option)
            else:
                email = name_request.process(email_msg)
            send_email(email, token)
        elif etype and etype == 'bc.registry.affiliation':
            email = affiliation_notification.process(email_msg, token)
            send_email(email, token)
        else:
            etype = email_msg['email']['type']
            option = email_msg['email']['option']
            if etype == 'businessNumber':
                email = bn_notification.process(email_msg['email'])
                send_email(email, token)
            elif etype == 'incorporationApplication' and option == 'mras':
                email = mras_notification.process(email_msg['email'])
                send_email(email, token)
            elif etype == 'annualReport' and option == 'reminder':
                email = ar_reminder_notification.process(
                    email_msg['email'], token)
                send_email(email, token)
            elif etype == 'dissolution':
                email = dissolution_notification.process(
                    email_msg['email'], token)
                send_email(email, token)
            elif etype in filing_notification.FILING_TYPE_CONVERTER.keys():
                if etype == 'annualReport' and option == Filing.Status.COMPLETED.value:
                    logger.debug('No email to send for: %s', email_msg)
                else:
                    email = filing_notification.process(
                        email_msg['email'], token)
                    if email:
                        send_email(email, token)
                    else:
                        # should only be if this was for a a coops filing
                        logger.debug('No email to send for: %s', email_msg)
            else:
                logger.debug('No email to send for: %s', email_msg)
def process(email_info: dict, token: str) -> dict:  # pylint: disable=too-many-locals, , too-many-branches
    """Build the email for Affiliation notification."""
    logger.debug('filing_notification: %s', email_info)

    # get template vars from filing
    filing, business, leg_tmz_filing_date, leg_tmz_effective_date = \
        get_filing_info(email_info['data']['filing']['header']['filingId'])
    filing_type = filing.filing_type
    status = filing.status
    filing_name = filing.filing_type[0].upper() + ' '.join(re.findall('[a-zA-Z][^A-Z]*', filing.filing_type[1:]))

    template = Path(f'{current_app.config.get("TEMPLATE_PATH")}/BC-ALT-DRAFT.html').read_text()
    filled_template = substitute_template_parts(template)
    # render template with vars
    jnja_template = Template(filled_template, autoescape=True)
    filing_data = (filing.json)['filing'][f'{filing_type}']
    html_out = jnja_template.render(
        business=business,
        filing=filing_data,
        header=(filing.json)['filing']['header'],
        filing_date_time=leg_tmz_filing_date,
        effective_date_time=leg_tmz_effective_date,
        entity_dashboard_url=current_app.config.get('DASHBOARD_URL') +
        (filing.json)['filing']['business'].get('identifier', ''),
        email_header=filing_name.upper(),
        filing_type=filing_type
    )

    # get recipients
    recipients = get_recipients(status, filing.filing_json, token)
    if not recipients:
        return {}

    # assign subject
    legal_name = business.get('legalName', None)
    subject = f'{legal_name} - How to use BCRegistry.ca'

    return {
        'recipients': recipients,
        'requestBy': '*****@*****.**',
        'content': {
            'subject': subject,
            'body': f'{html_out}'
        }
    }
Пример #21
0
def process(email_msg: dict, token: str) -> dict:  # pylint: disable=too-many-locals
    """Build the email for Business Number notification."""
    logger.debug('incorp_notification: %s', email_msg)
    # get template and fill in parts
    template = Path(f'{current_app.config.get("TEMPLATE_PATH")}/BC-{email_msg["option"]}-success.html').read_text()
    filled_template = substitute_template_parts(template)

    # get template vars from filing
    filing, business, leg_tmz_filing_date, leg_tmz_effective_date = get_filing_info(email_msg['filingId'])

    # render template with vars
    jnja_template = Template(filled_template, autoescape=True)
    html_out = jnja_template.render(
        business=business,
        incorporationApplication=(filing.json)['filing']['incorporationApplication'],
        header=(filing.json)['filing']['header'],
        filing_date_time=leg_tmz_filing_date,
        effective_date_time=leg_tmz_effective_date,
        entity_dashboard_url=current_app.config.get('DASHBOARD_URL') +
        (filing.json)['filing']['business'].get('identifier', '')
    )

    # get attachments
    pdfs = _get_pdfs(email_msg['option'], token, business, filing, leg_tmz_filing_date)

    # get recipients
    recipients = get_recipients(email_msg['option'], filing.filing_json)

    # assign subject
    if email_msg['option'] == 'filed':
        subject = 'Confirmation of Filing from the Business Registry'
    elif email_msg['option'] == 'registered':
        subject = 'Incorporation Documents from the Business Registry'
    else:  # fallback case - should never happen
        subject = 'Notification from the BC Business Registry'

    return {
        'recipients': recipients,
        'requestBy': '*****@*****.**',
        'content': {
            'subject': subject,
            'body': f'{html_out}',
            'attachments': pdfs
        }
    }
Пример #22
0
async def _process_consolidated_invoices(row):
    target_txn_status = _get_row_value(row, Column.TARGET_TXN_STATUS)
    if (target_txn :=
            _get_row_value(row,
                           Column.TARGET_TXN)) == TargetTransaction.INV.value:
        inv_number = _get_row_value(row, Column.TARGET_TXN_NO)
        record_type = _get_row_value(row, Column.RECORD_TYPE)
        logger.debug('Processing invoice :  %s', inv_number)

        inv_references = _find_invoice_reference_by_number_and_status(
            inv_number, InvoiceReferenceStatus.ACTIVE.value)

        payment_account: PaymentAccountModel = _get_payment_account(row)

        if target_txn_status.lower() == Status.PAID.value.lower():
            logger.debug('Fully PAID payment.')
            # if no inv reference is found, and if there are no COMPLETED inv ref, raise alert
            completed_inv_references = _find_invoice_reference_by_number_and_status(
                inv_number, InvoiceReferenceStatus.COMPLETED.value)

            if not inv_references and not completed_inv_references:
                logger.error(
                    'No invoice found for %s in the system, and cannot process %s.',
                    inv_number, row)
                capture_message(
                    f'No invoice found for {inv_number} in the system, and cannot process {row}.',
                    level='error')
                return
            await _process_paid_invoices(inv_references, row)
            await _publish_mailer_events('PAD.PaymentSuccess', payment_account,
                                         row)
        elif target_txn_status.lower() == Status.NOT_PAID.value.lower() \
                or record_type in (RecordType.PADR.value, RecordType.PAYR.value):
            logger.info('NOT PAID. NSF identified.')
            # NSF Condition. Publish to account events for NSF.
            _process_failed_payments(row)
            # Send mailer and account events to update status and send email notification
            await _publish_account_events('lockAccount', payment_account, row)
        else:
            logger.error(
                'Target Transaction Type is received as %s for PAD, and cannot process %s.',
                target_txn, row)
            capture_message(
                f'Target Transaction Type is received as {target_txn} for PAD, and cannot process.',
                level='error')
Пример #23
0
def process(email_info: dict) -> dict:
    """Build the email for Name Request notification."""
    logger.debug('NR_notification: %s', email_info)
    nr_number = email_info['identifier']
    payment_token = email_info.get('data', {}).get('request', {}).get('paymentToken', '')
    template = Path(f'{current_app.config.get("TEMPLATE_PATH")}/NR-PAID.html').read_text()
    filled_template = substitute_template_parts(template)
    # render template with vars
    mail_template = Template(filled_template, autoescape=True)
    html_out = mail_template.render(
        identifier=nr_number
    )

    # get nr data
    nr_response = NameXService.query_nr_number(nr_number)
    if nr_response.status_code != HTTPStatus.OK:
        logger.error('Failed to get nr info for name request: %s', nr_number)
        capture_message(f'Email Queue: nr_id={nr_number}, error=receipt generation', level='error')
        return {}
    nr_data = nr_response.json()

    # get attachments
    pdfs = _get_pdfs(nr_data['id'], payment_token)
    if not pdfs:
        return {}

    # get recipients
    recipients = nr_data['applicants']['emailAddress']
    if not recipients:
        return {}

    subject = f'{nr_number} - Receipt from Corporate Registry'

    return {
        'recipients': recipients,
        'requestBy': '*****@*****.**',
        'content': {
            'subject': subject,
            'body': f'{html_out}',
            'attachments': pdfs
        }
    }
Пример #24
0
async def process_event(event_message, flask_app):
    """Insert into Activity log table."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():
        data = event_message.get('data')
        logger.debug('message_type received %s', data)

        activity_model: ActivityLogModel = ActivityLogModel(
            actor_id=data.get('actor'),
            action=data.get('action'),
            item_type=data.get('itemType'),
            item_name=data.get('itemName'),
            item_id=data.get('itemId'),
            remote_addr=data.get('remoteAddr'),
            created=data.get('createdAt'),
            org_id=data.get('orgId'))
        activity_model.save()
        logger.debug('activity log saved')
Пример #25
0
def process(email_msg: dict) -> dict:
    """Build the email for Payment Completed notification."""
    logger.debug('refund_request notification: %s', email_msg)

    # fill in template
    filled_template = generate_template(
        current_app.config.get('TEMPLATE_PATH'), 'refund_request_email')

    # render template with vars from email msg
    jnja_template = Template(filled_template, autoescape=True)
    html_out = jnja_template.render(refund_data=email_msg)
    return {
        'recipients':
        current_app.config.get('REFUND_REQUEST').get('recipients'),
        'content': {
            'subject': f'Refund Request for {email_msg.get("identifier")}',
            'body': html_out,
            'attachments': []
        }
    }
Пример #26
0
async def process_event(event_message, flask_app):
    """Render the payment status."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():
        if event_message.get('type', None) == INCORPORATION_TYPE \
                and 'tempidentifier' in event_message \
                and event_message.get('tempidentifier', None) is not None:

            old_identifier = event_message.get('tempidentifier')
            new_identifier = event_message.get('identifier')
            logger.debug('Received message to update %s to %s', old_identifier,
                         new_identifier)

            # Find all invoice records which have the old corp number
            invoices = Invoice.find_by_business_identifier(old_identifier)
            for inv in invoices:
                inv.business_identifier = new_identifier
                inv.flush()

            db.session.commit()
Пример #27
0
async def process_event(event_message: dict, flask_app):
    """Process the incoming queue event message."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():
        message_type = event_message.get('type', None)
        email_msg = None
        email_dict = None
        token = RestService.get_service_account_token()
        if message_type == 'account.mailer':
            email_msg = json.loads(event_message.get('data'))
            email_dict = payment_completed.process(email_msg)
        elif message_type == 'bc.registry.payment.refundRequest':
            email_msg = event_message.get('data')
            email_dict = refund_requested.process(email_msg)
        elif event_message.get('type',
                               None) == 'bc.registry.payment.padAccountCreate':
            email_msg = event_message.get('data')
            email_dict = pad_confirmation.process(email_msg, token)

        logger.debug('Extracted email msg: %s', email_dict)
        process_email(email_dict, FLASK_APP, token)
Пример #28
0
def process_email(email_msg: dict, flask_app: Flask):  # pylint: disable=too-many-branches
    """Process the email contained in the submission."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():
        logger.debug('Attempting to process email: %s', email_msg)
        token = AccountService.get_bearer_token()
        etype = email_msg.get('type', None)
        if etype and etype == 'bc.registry.names.request':
            email = name_request.process(email_msg)
            send_email(email, token)
        else:
            etype = email_msg['email']['type']
            option = email_msg['email']['option']
            if etype == 'businessNumber':
                email = bn_notification.process(email_msg['email'])
                send_email(email, token)
            elif etype == 'incorporationApplication' and option == 'mras':
                email = mras_notification.process(email_msg['email'])
                send_email(email, token)
            elif etype in filing_notification.FILING_TYPE_CONVERTER.keys():
                if etype == 'annualReport' and option == Filing.Status.COMPLETED.value:
                    logger.debug('No email to send for: %s', email_msg)
                # Remove this when self serve alteration is implemented.
                elif etype == 'alteration' and option == Filing.Status.PAID.value:
                    logger.debug('No email to send for: %s', email_msg)
                else:
                    email = filing_notification.process(
                        email_msg['email'], token)
                    if email:
                        send_email(email, token)
                    else:
                        # should only be if this was for a a coops filing
                        logger.debug('No email to send for: %s', email_msg)
            else:
                logger.debug('No email to send for: %s', email_msg)
Пример #29
0
def process(event_message: dict) -> dict:
    """Build the email for Payment Completed notification."""
    logger.debug('refund_request notification: %s', event_message)
    email_msg = event_message.get('data')
    message_type = event_message.get('type')
    template_name = None
    recepients = None
    subject = None
    if message_type == MessageType.REFUND_DIRECT_PAY_REQUEST.value:
        template_name = 'creditcard_refund_request_email'
        recepients = current_app.config.get('REFUND_REQUEST').get(
            'creditcard').get('recipients')
        subject = f'Refund Request for {email_msg.get("identifier")}'
    elif message_type == MessageType.REFUND_DRAWDOWN_REQUEST.value:
        template_name = 'bcol_refund_request_email'
        recepients = current_app.config.get('REFUND_REQUEST').get('bcol').get(
            'recipients')
        refund_date = datetime.strptime(email_msg.get('refundDate'),
                                        '%Y%m%d').strftime('%Y-%m-%d')
        subject = f'BC Registries and Online Services Refunds for {refund_date}'

    # fill in template
    filled_template = generate_template(
        current_app.config.get('TEMPLATE_PATH'), template_name)

    # render template with vars from email msg
    jnja_template = Template(filled_template, autoescape=True)
    html_out = jnja_template.render(refund_data=email_msg,
                                    logo_url=email_msg.get('logo_url'))
    return {
        'recipients': recepients,
        'content': {
            'subject': subject,
            'body': html_out,
            'attachments': []
        }
    }
Пример #30
0
async def cb_subscription_handler(msg: nats.aio.client.Msg):
    """Use Callback to process Queue Msg objects."""
    try:
        logger.info('Received raw message seq: %s, data=  %s', msg.sequence,
                    msg.data.decode())
        email_msg = json.loads(msg.data.decode('utf-8'))
        logger.debug('Extracted email msg: %s', email_msg)
        process_email(email_msg, FLASK_APP)
    except OperationalError as err:
        logger.error('Queue Blocked - Database Issue: %s',
                     json.dumps(email_msg),
                     exc_info=True)
        raise err  # We don't want to handle the error, as a DB down would drain the queue
    except EmailException as err:
        logger.error(
            'Queue Error - email failed to send: %s'
            '\n\nThis message has been put back on the queue for reprocessing.',
            json.dumps(email_msg),
            exc_info=True)
        raise err  # we don't want to handle the error, so that the message gets put back on the queue
    except (QueueException, Exception):  # pylint: disable=broad-except
        # Catch Exception so that any error is still caught and the message is removed from the queue
        capture_message('Queue Error: ' + json.dumps(email_msg), level='error')
        logger.error('Queue Error: %s', json.dumps(email_msg), exc_info=True)