Ejemplo n.º 1
0
async def publish_mailer_events(message_type: str, org_id: str):
    """Publish payment message to the mailer queue."""
    # Publish message to the Queue, saying account has been created. Using the event spec.

    queue_data = {
        'accountId': org_id,
    }
    payload = {
        'specversion': '1.x-wip',
        'type': message_type,
        'source': f'https://api.pay.bcregistry.gov.bc.ca/v1/accounts/{org_id}',
        'id': org_id,
        'time': f'{datetime.now()}',
        'datacontenttype': 'application/json',
        'data': queue_data
    }
    try:
        await publish(payload=payload,
                      client_name=APP_CONFIG.NATS_MAILER_CLIENT_NAME,
                      subject=APP_CONFIG.NATS_MAILER_SUBJECT)
    except Exception as e:  # pylint: disable=broad-except
        logger.error(e)
        logger.warning(
            'Notification to Queue failed for the Account Mailer %s - %s',
            org_id, payload)
        capture_message(
            'Notification to Queue failed for the Account Mailer {auth_account_id}, {msg}.'
            .format(auth_account_id=org_id, msg=payload),
            level='error')
Ejemplo n.º 2
0
def process(business: Business, filing: Dict):
    """Render the annual_report onto the business model objects."""
    if not (dissolution_filing := filing.get('filing',
                                             {}).get('voluntaryDissolution')):
        logger.error('Could not find Voluntary Dissolution in: %s', filing)
        raise QueueException(
            f'legal_filing:voluntaryDissolution missing from {filing}')
Ejemplo n.º 3
0
def process(business: Business, filing: Dict, filing_meta: FilingMeta):
    """Render the annual_report onto the business model objects."""
    legal_filing_name = 'annualReport'
    agm_date = filing[legal_filing_name].get('annualGeneralMeetingDate')
    ar_date = filing[legal_filing_name].get('annualReportDate')
    if ar_date:
        ar_date = datetime.date.fromisoformat(ar_date)
    else:
        # should never get here (schema validation should prevent this from making it to the filer)
        logger.error(
            'No annualReportDate given for in annual report. Filing id: %s',
            filing.id)

    # save the annual report date to the filing meta info
    filing_meta.application_date = ar_date
    filing_meta.annual_report = {
        'annualReportDate': ar_date.isoformat(),
        'annualGeneralMeetingDate': agm_date
    }
    business.last_ar_date = ar_date
    if agm_date and validations.annual_report.requires_agm(business):
        with suppress(ValueError):
            agm_date = datetime.date.fromisoformat(agm_date)
            business.last_agm_date = agm_date
            business.last_ar_date = agm_date

    business.last_ar_year = business.last_ar_year + 1 if business.last_ar_year else business.founding_date.year + 1
Ejemplo n.º 4
0
def get_recipients(option: str, filing_json: dict, token: str = None) -> str:
    """Get the recipients for the email output."""
    recipients = ''
    if filing_json['filing'].get('incorporationApplication'):
        recipients = filing_json['filing']['incorporationApplication']['contactPoint']['email']
        if option in [Filing.Status.PAID.value, 'bn'] and \
                filing_json['filing']['header']['name'] == 'incorporationApplication':
            parties = filing_json['filing']['incorporationApplication'].get('parties')
            comp_party_email = None
            for party in parties:
                for role in party['roles']:
                    if role['roleType'] == 'Completing Party':
                        comp_party_email = party['officer']['email']
                        break
            recipients = f'{recipients}, {comp_party_email}'
    else:
        headers = {
            'Accept': 'application/json',
            'Authorization': f'Bearer {token}'
        }
        identifier = filing_json['filing']['business']['identifier']
        if not identifier[:2] == 'CP':
            # only add recipients if not coop
            contact_info = requests.get(
                f'{current_app.config.get("AUTH_URL")}/entities/{identifier}',
                headers=headers
            )
            contacts = contact_info.json()['contacts']
            if not contacts:
                logger.error('Queue Error: No email in business profile to send output to.', exc_info=True)
                raise Exception

            recipients = contacts[0]['email']

    return recipients
Ejemplo n.º 5
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')
Ejemplo n.º 6
0
async def process_event(event_message, flask_app):
    """Render the org status."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():
        message_type = event_message.get('type', None)
        data = event_message.get('data')
        org_id = data.get('accountId')
        org: OrgModel = OrgModel.find_by_org_id(org_id)
        if org is None:
            logger.error('Unknown org for orgid %s', org_id)
            return

        if message_type == LOCK_ACCOUNT_MESSAGE_TYPE:
            org.status_code = OrgStatus.NSF_SUSPENDED.value
            org.suspended_on = datetime.now()
            await publish_mailer_events(LOCK_ACCOUNT_MESSAGE_TYPE, org_id)
        elif message_type == UNLOCK_ACCOUNT_MESSAGE_TYPE:
            org.status_code = OrgStatus.ACTIVE.value
            await publish_mailer_events(UNLOCK_ACCOUNT_MESSAGE_TYPE, org_id)
        else:
            logger.error('Unknown Message Type : %s', message_type)
            return

        org.flush()
        db.session.commit()
Ejemplo n.º 7
0
def _get_pad_confirmation_report_pdf(email_msg, token):
    current_time = datetime.datetime.now()
    mailing_address = _get_address(email_msg.get('accountId'))
    template_vars = {
        **email_msg, 'generatedDate': current_time.strftime('%m-%d-%Y'),
        'accountAddress': mailing_address
    }
    filled_template = generate_template(
        current_app.config.get('PDF_TEMPLATE_PATH'), 'pad_confirmation')
    template_b64 = "'" + base64.b64encode(bytes(filled_template,
                                                'utf-8')).decode() + "'"

    pdf_payload = {
        'reportName': 'PAD_Confirmation_Letter',
        'template': template_b64,
        'templateVars': template_vars,
        'populatePageNumber': True,
    }

    report_response = RestService.post(
        endpoint=current_app.config.get('REPORT_API_BASE_URL'),
        token=token,
        auth_header_type=AuthHeaderType.BEARER,
        content_type=ContentType.JSON,
        data=pdf_payload,
        raise_for_status=True,
        additional_headers={'Accept': 'application/pdf'})
    pdf_attachment = None
    if report_response.status_code != 200:
        logger.error('Failed to get pdf')
    else:
        pdf_attachment = base64.b64encode(report_response.content)

    return pdf_attachment
Ejemplo n.º 8
0
async def _publish_online_banking_mailer_events(rows: List[Dict[str, str]],
                                                paid_amount: float):
    """Publish payment message to the mailer queue."""
    # Publish message to the Queue, saying account has been created. Using the event spec.
    pay_account = _get_payment_account(
        rows[0])  # All rows are for same account.
    # Check for credit, or fully paid or under paid payment
    credit_rows = list(
        filter(
            lambda r: (_get_row_value(r, Column.TARGET_TXN) ==
                       TargetTransaction.RECEIPT.value), rows))
    under_pay_rows = list(
        filter(
            lambda r: (_get_row_value(r, Column.TARGET_TXN_STATUS).lower() ==
                       Status.PARTIAL.value.lower()), rows))

    credit_amount: float = 0
    if credit_rows:
        message_type = 'bc.registry.payment.OverPaid'
        for row in credit_rows:
            credit_amount += float(_get_row_value(row, Column.APP_AMOUNT))
    elif under_pay_rows:
        message_type = 'bc.registry.payment.UnderPaid'
    else:
        message_type = 'bc.registry.payment.Payment'

    queue_data = {
        'accountId': pay_account.auth_account_id,
        'paymentMethod': PaymentMethod.ONLINE_BANKING.value,
        'amount': '{:.2f}'.format(
            paid_amount),  # pylint: disable = consider-using-f-string
        'creditAmount': '{:.2f}'.format(
            credit_amount)  # pylint: disable = consider-using-f-string
    }

    payload = {
        'specversion': '1.x-wip',
        'type': message_type,
        'source':
        f'https://api.pay.bcregistry.gov.bc.ca/v1/accounts/{pay_account.auth_account_id}',
        'id': f'{pay_account.auth_account_id}',
        'time': f'{datetime.now()}',
        'datacontenttype': 'application/json',
        'data': queue_data
    }

    try:
        await publish(payload=payload,
                      client_name=APP_CONFIG.NATS_MAILER_CLIENT_NAME,
                      subject=APP_CONFIG.NATS_MAILER_SUBJECT)
    except Exception as e:  # NOQA pylint: disable=broad-except
        logger.error(e)
        logger.warning(
            'Notification to Queue failed for the Account Mailer %s - %s',
            pay_account.auth_account_id, payload)
        capture_message(
            'Notification to Queue failed for the Account Mailer {auth_account_id}, {msg}.'
            .format(auth_account_id=pay_account.auth_account_id, msg=payload),
            level='error')
Ejemplo n.º 9
0
async def process_payment(payment_token, flask_app):
    """Render the payment status."""
    if not flask_app:
        raise QueueException('Flask App not available.')

    with flask_app.app_context():

        # try to find the filing 5 times before putting back on the queue - in case payment token ends up on the queue
        # before it is assigned to filing.
        counter = 1
        filing_submission = None
        while not filing_submission and counter <= 5:
            filing_submission = get_filing_by_payment_id(payment_token['paymentToken'].get('id'))
            counter += 1
            if not filing_submission:
                await asyncio.sleep(0.2)
        if not filing_submission:
            raise FilingException

        if filing_submission.status == Filing.Status.COMPLETED.value:
            # log and skip this
            # it shouldn't be an error, but there could be something to investigate if
            # multiple retries are happening on something that should have been completed.
            logger.warning('Queue: Attempting to reprocess business.id=%s, filing.id=%s payment=%s',
                           filing_submission.business_id, filing_submission.id, payment_token)
            capture_message(f'Queue Issue: Attempting to reprocess business.id={filing_submission.business_id},'
                            'filing.id={filing_submission.id} payment={payment_token}')
            return

        if payment_token['paymentToken'].get('statusCode') == 'TRANSACTION_FAILED':
            # TODO: The customer has cancelled out of paying, so we could note this better
            # technically the filing is still pending payment/processing
            return

        if payment_token['paymentToken'].get('statusCode') == Filing.Status.COMPLETED.value:
            filing_submission.payment_completion_date = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
            db.session.add(filing_submission)
            db.session.commit()

            if not filing_submission.effective_date or \
                    filing_submission.effective_date <= \
                    datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc):
                # if we're not a future effective date, then submit for processing
                try:
                    await publish_filing(filing_submission)
                except Exception as err:  # pylint: disable=broad-except, unused-variable # noqa F841;
                    # mark any failure for human review
                    capture_message(
                        'Queue Error: Failied to place filing:{filing_submission.id} on Queue with error:{err}',
                        level='error')

            return

        # if we're here and haven't been able to action it,
        # then we've received an unknown status and should throw an error
        logger.error('Unknown payment status given: %s', payment_token['paymentToken'].get('statusCode'))
        raise QueueException
Ejemplo n.º 10
0
async def run(loop, email_info):  # pylint: disable=too-many-locals
    """Run the main application loop for the service.

    This runs the main top level service functions for working with the Queue.
    """
    # NATS client connections
    nc = NATS()
    sc = STAN()

    async def close():
        """Close the stream and nats connections."""
        await sc.close()
        await nc.close()

    # Connection and Queue configuration.
    def nats_connection_options():
        return {
            'servers': os.getenv('NATS_SERVERS', 'nats://127.0.0.1:4222').split(','),
            'io_loop': loop,
            'error_cb': error_cb,
            'name': os.getenv('NATS_CLIENT_NAME', 'entity.filing.tester')
        }

    def stan_connection_options():
        return {
            'cluster_id': os.getenv('NATS_CLUSTER_ID', 'test-cluster'),
            'client_id': str(random.SystemRandom().getrandbits(0x58)),
            'nats': nc
        }

    def subscription_options():
        return {
            'subject': os.getenv('NATS_EMAILER_SUBJECT', 'error'),
            'queue': os.getenv('NATS_QUEUE', 'error'),
            'durable_name': os.getenv('NATS_QUEUE', 'error') + '_durable'
        }

    try:
        # Connect to the NATS server, and then use that for the streaming connection.
        await nc.connect(**nats_connection_options())
        await sc.connect(**stan_connection_options())

        # register the signal handler
        for sig in ('SIGINT', 'SIGTERM'):
            loop.add_signal_handler(getattr(signal, sig),
                                    functools.partial(signal_handler, sig_loop=loop, sig_nc=nc, task=close)
                                    )

        payload = {'email': email_info}
        print('publishing:', payload)
        await sc.publish(subject=subscription_options().get('subject'),
                         payload=json.dumps(payload).encode('utf-8'))

    except Exception as e:  # pylint: disable=broad-except
        # TODO tighten this error and decide when to bail on the infinite reconnect
        logger.error(e)
Ejemplo n.º 11
0
 async def conn_lost_cb(error):
     logger.info('Connection lost:%s', error)
     for i in range(0, 100):
         try:
             logger.info('Reconnecting, attempt=%i...', i)
             await self.connect()
         except Exception as e:  # pylint: disable=broad-except; catch all errors from client framework
             logger.error('Error %s', e.with_traceback(), stack_info=True)
             continue
         break
Ejemplo n.º 12
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)
Ejemplo n.º 13
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')
Ejemplo n.º 14
0
async def publish_event(payload: dict):
    """Publish the email message onto the NATS event subject."""
    try:
        subject = APP_CONFIG.ENTITY_EVENT_PUBLISH_OPTIONS['subject']
        await qsm.service.publish(subject, payload)
    except Exception as err:  # pylint: disable=broad-except; we don't want to fail out the email, so ignore all.
        capture_message(
            f'Queue Publish Event Error: email msg={payload}, error={err}',
            level='error')
        logger.error('Queue Publish Event Error: email msg=%s',
                     payload,
                     exc_info=True)
Ejemplo n.º 15
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'))
Ejemplo n.º 16
0
def _validate_account(inv: InvoiceModel, row: Dict[str, str]):
    """Validate any mismatch in account number."""
    # This should never happen, just in case
    cfs_account: CfsAccountModel = CfsAccountModel.find_by_id(
        inv.cfs_account_id)
    if (account_number := _get_row_value(
            row, Column.CUSTOMER_ACC)) != cfs_account.cfs_account:
        logger.error('Customer Account received as %s, but expected %s.',
                     account_number, cfs_account.cfs_account)
        capture_message(
            f'Customer Account received as {account_number}, but expected {cfs_account.cfs_account}.',
            level='error')

        raise Exception('Invalid Account Number')
Ejemplo n.º 17
0
def process(business: Business, filing: Dict, app: Flask = None):  # pylint: disable=too-many-locals; 1 extra
    """Process the incoming incorporation filing."""
    # Extract the filing information for incorporation
    incorp_filing = filing['incorporationApplication']

    if incorp_filing:  # pylint: disable=too-many-nested-blocks; 1 extra and code is still very clear
        # Extract the office, business, addresses, directors etc.
        # these will have to be inserted into the db.
        offices = incorp_filing.get('offices', None)
        parties = incorp_filing.get('parties', None)
        business_info = incorp_filing['nameRequest']
        share_classes = incorp_filing['shareClasses']

        # Sanity check
        if business and business.identifier == business_info['nrNumber']:
            # Reserve the Corp Numper for this entity
            corp_num = get_next_corp_num(business_info['legalType'], app)

            # Initial insert of the business record
            business = update_business_info(corp_num, business, business_info)

            if business:
                for office_type, addresses in offices.items():
                    office = create_office(business, office_type, addresses)
                    business.offices.append(office)

                if parties:
                    for party_info in parties:
                        party = create_party(party_info=party_info)
                        for role_type in party_info.get('roles'):
                            role = {
                                'roleType':
                                role_type.get('roleType'),
                                'appointmentDate':
                                role_type.get('appointmentDate', None),
                                'cessationDate':
                                role_type.get('cessationDate', None)
                            }
                            party_role = create_role(party=party,
                                                     role_info=role)
                            business.party_roles.append(party_role)

                if share_classes:
                    for share_class_info in share_classes:
                        share_class = create_share_class(share_class_info)
                        business.share_classes.append(share_class)
        else:
            logger.error('No business exists for NR number: %s',
                         business_info['nrNUmber'])
Ejemplo n.º 18
0
def process(business: Business, filing: Dict):
    """Render the annual_report onto the business model objects."""
    agm_date = filing['annualReport'].get('annualGeneralMeetingDate')
    ar_date = filing['annualReport'].get('annualReportDate')
    if agm_date and validations.annual_report.requires_agm(business):
        agm_date = datetime.date.fromisoformat(agm_date)
    if ar_date:
        ar_date = datetime.date.fromisoformat(ar_date)
    else:
        # should never get here (schema validation should prevent this from making it to the filer)
        logger.error(
            'No annualReportDate given for in annual report. Filing id: %s',
            filing.id)
    business.last_agm_date = agm_date
    business.last_ar_date = ar_date
Ejemplo n.º 19
0
async def publish_event(business: Business, filing: Filing):
    """Publish the filing message onto the NATS filing subject."""
    try:
        payload = {
            'specversion':
            '1.x-wip',
            'type':
            'bc.registry.business.' + filing.filing_type,
            'source':
            ''.join([
                APP_CONFIG.LEGAL_API_URL, '/business/', business.identifier,
                '/filing/',
                str(filing.id)
            ]),
            'id':
            str(uuid.uuid4()),
            'time':
            datetime.utcnow().isoformat(),
            'datacontenttype':
            'application/json',
            'identifier':
            business.identifier,
            'data': {
                'filing': {
                    'header': {
                        'filingId': filing.id,
                        'effectiveDate': filing.effective_date.isoformat()
                    },
                    'business': {
                        'identifier': business.identifier
                    },
                    'legalFilings': get_filing_types(filing.filing_json)
                }
            }
        }
        if filing.temp_reg:
            payload['tempidentifier'] = filing.temp_reg
        subject = APP_CONFIG.ENTITY_EVENT_PUBLISH_OPTIONS['subject']
        await qsm.service.publish(subject, payload)
    except Exception as err:  # pylint: disable=broad-except; we don't want to fail out the filing, so ignore all.
        capture_message('Queue Publish Event Error: filing.id=' +
                        str(filing.id) + str(err),
                        level='error')
        logger.error('Queue Publish Event Error: filing.id=%s',
                     filing.id,
                     exc_info=True)
Ejemplo n.º 20
0
async def publish_event(business: Business, filing: Filing):
    """Publish the filing message onto the NATS filing subject."""
    try:
        payload = {'filing':
                   {
                       'identifier': business.identifier,
                       'legalName': business.legal_name,
                       'filingId': filing.id,
                       'effectiveDate': filing.effective_date.isoformat(),
                       'legalFilings': get_filing_types(filing.filing_json)
                   }
                   }
        subject = APP_CONFIG.ENTITY_EVENT_PUBLISH_OPTIONS['subject']
        await qsm.service.publish(subject, payload)
    except Exception as err:  # pylint: disable=broad-except; we don't want to fail out the filing, so ignore all.
        capture_message('Queue Publish Event Error: filing.id=' + str(filing.id) + str(err), level='error')
        logger.error('Queue Publish Event Error: filing.id=%s', filing.id, exc_info=True)
Ejemplo n.º 21
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')
Ejemplo n.º 22
0
def get_nr_bearer_token():
    """Get a valid Bearer token for the Name Request Service."""
    token_url = current_app.config.get('NAMEX_AUTH_SVC_URL')
    client_id = current_app.config.get('NAMEX_SERVICE_CLIENT_USERNAME')
    client_secret = current_app.config.get('NAMEX_SERVICE_CLIENT_SECRET')

    data = 'grant_type=client_credentials'
    # get service account token
    res = requests.post(url=token_url,
                        data=data,
                        headers={'content-type': 'application/x-www-form-urlencoded'},
                        auth=(client_id, client_secret))
    try:
        return res.json().get('access_token')
    except Exception:  # noqa B902; pylint: disable=W0703;
        logger.error('Failed to get nr token')
        capture_message('Failed to get nr token', level='error')
        return None
Ejemplo n.º 23
0
def _get_pdfs(nr_data: dict) -> list:
    """Get the receipt for the name request application."""
    pdfs = []
    token = get_nr_bearer_token()
    if token:
        nr_id = nr_data['id']
        payment_info = requests.get(
            f'{current_app.config.get("NAMEX_SVC_URL")}payments/{nr_id}',
            headers={
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + token
            })
        if payment_info.status_code != HTTPStatus.OK:
            logger.error('Failed to get payment info for name request: %s',
                         nr_id)
            capture_message(
                f'Email Queue: nr_id={nr_id}, error=receipt generation',
                level='error')
        else:
            payment_info = payment_info.json()
            payment_id = payment_info[0]['payment']['id']
            receipt = requests.post(
                f'{current_app.config.get("NAMEX_SVC_URL")}payments/{payment_id}/receipt',
                json={},
                headers={
                    'Accept': 'application/pdf',
                    'Authorization': f'Bearer {token}'
                })
            if receipt.status_code != HTTPStatus.OK:
                logger.error('Failed to get receipt pdf for name request: %s',
                             nr_id)
                capture_message(
                    f'Email Queue: nr_id={nr_id}, error=receipt generation',
                    level='error')
            else:
                receipt_encoded = base64.b64encode(receipt.content)
                pdfs.append({
                    'fileName': 'Receipt.pdf',
                    'fileBytes': receipt_encoded.decode('utf-8'),
                    'fileUrl': '',
                    'attachOrder': '1'
                })
    return pdfs
Ejemplo n.º 24
0
async def _publish_payment_event(inv: InvoiceModel):
    """Publish payment message to the queue."""
    payment_event_payload = PaymentTransactionService.create_event_payload(
        invoice=inv, status_code=PaymentStatus.COMPLETED.value)
    try:

        await publish(payload=payment_event_payload,
                      client_name=APP_CONFIG.NATS_PAYMENT_CLIENT_NAME,
                      subject=get_pay_subject_name(
                          inv.corp_type_code,
                          subject_format=APP_CONFIG.NATS_PAYMENT_SUBJECT))
    except Exception as e:  # NOQA pylint: disable=broad-except
        logger.error(e)
        logger.warning(
            'Notification to Queue failed for the Payment Event - %s',
            payment_event_payload)
        capture_message(
            f'Notification to Queue failed for the Payment Event {payment_event_payload}.',
            level='error')
Ejemplo n.º 25
0
async def _publish_account_events(message_type: str,
                                  pay_account: PaymentAccountModel,
                                  row: Dict[str, str]):
    """Publish payment message to the mailer queue."""
    # Publish message to the Queue, saying account has been created. Using the event spec.
    payload = _create_event_payload(message_type, pay_account, row)

    try:
        await publish(payload=payload,
                      client_name=APP_CONFIG.NATS_ACCOUNT_CLIENT_NAME,
                      subject=APP_CONFIG.NATS_ACCOUNT_SUBJECT)
    except Exception as e:  # NOQA pylint: disable=broad-except
        logger.error(e)
        logger.warning('Notification to Queue failed for the Account %s - %s',
                       pay_account.auth_account_id, pay_account.name)
        capture_message(
            'Notification to Queue failed for the Account {auth_account_id}, {msg}.'
            .format(auth_account_id=pay_account.auth_account_id, msg=payload),
            level='error')
Ejemplo n.º 26
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
        }
    }
def process(business: Business, filing: Dict):
    """Render the change_of_directors onto the business model objects."""
    new_directors = filing['changeOfDirectors'].get('directors')

    for new_director in new_directors:
        if 'appointed' in new_director['actions']:
            # create address
            address = create_address(new_director['deliveryAddress'], Address.DELIVERY)

            director_to_add = Director(first_name=new_director['officer'].get('firstName', '').upper(),
                                       middle_initial=new_director['officer'].get('middleInitial', '').upper(),
                                       last_name=new_director['officer'].get('lastName', '').upper(),
                                       title=new_director.get('title', '').upper(),
                                       appointment_date=new_director.get('appointmentDate'),
                                       cessation_date=new_director.get('cessationDate'),
                                       delivery_address=address)

            if 'mailingAddress' in new_director.keys():
                mailing_address = create_address(new_director['mailingAddress'], Address.MAILING)
                director_to_add.mailing_address = mailing_address

            # add new director to the list
            business.directors.append(director_to_add)

        if any([action != 'appointed' for action in new_director['actions']]):
            # get name of director in json for comparison *
            new_director_name = \
                new_director['officer'].get('firstName') + new_director['officer'].get('middleInitial', '') + \
                new_director['officer'].get('lastName') \
                if 'nameChanged' not in new_director['actions'] \
                else new_director['officer'].get('prevFirstName') + \
                new_director['officer'].get('prevMiddleInitial') + new_director['officer'].get('prevLastName')
            if not new_director_name:
                logger.error('Could not resolve director name from json %s.', new_director)
                raise QueueException

            for director in business.directors:
                # get name of director in database for comparison *
                director_name = director.first_name + director.middle_initial + director.last_name
                if director_name.upper() == new_director_name.upper():
                    update_director(director, new_director)
                    break
Ejemplo n.º 28
0
def get_recipient_from_auth(identifier: str, token: str) -> str:
    """Get the recipients for the email output from auth."""
    headers = {
        'Accept': 'application/json',
        'Authorization': f'Bearer {token}'
    }

    contact_info = requests.get(
        f'{current_app.config.get("AUTH_URL")}/entities/{identifier}',
        headers=headers)
    contacts = contact_info.json()['contacts']

    if not contacts:
        logger.error(
            'Queue Error: No email in business (%s) profile to send output to.',
            identifier,
            exc_info=True)
        raise Exception

    return contacts[0]['email']
Ejemplo n.º 29
0
    async def run(self, loop, config, callback):  # pylint: disable=too-many-locals
        """Run the main application loop for the service.

        This runs the main top level service functions for working with the Queue.
        """
        self.service = ServiceWorker(loop=loop, cb_handler=callback, config=config)
        self.probe = Probes(components=[self.service], loop=loop)

        try:
            await self.probe.start()
            await self.service.connect()

            # register the signal handler
            for sig in ('SIGINT', 'SIGTERM'):
                loop.add_signal_handler(getattr(signal, sig),
                                        functools.partial(signal_handler, sig_loop=loop, task=self.close)
                                        )

        except Exception as e:  # pylint: disable=broad-except
            # TODO tighten this error and decide when to bail on the infinite reconnect
            logger.error(e)
Ejemplo n.º 30
0
async def _publish_mailer_events(file_name: str, minio_location: str):
    """Publish payment message to the mailer queue."""
    # Publish message to the Queue, saying account has been created. Using the event spec.
    queue_data = {'fileName': file_name, 'minioLocation': minio_location}
    payload = {
        'specversion': '1.x-wip',
        'type': 'bc.registry.payment.ejvFailed',
        'source': 'https://api.pay.bcregistry.gov.bc.ca/v1/accounts/',
        'id': file_name,
        'time': f'{datetime.now()}',
        'datacontenttype': 'application/json',
        'data': queue_data
    }

    try:
        await publish(payload=payload,
                      client_name=APP_CONFIG.NATS_MAILER_CLIENT_NAME,
                      subject=APP_CONFIG.NATS_MAILER_SUBJECT)
    except Exception as e:  # NOQA pylint: disable=broad-except
        logger.error(e)
        capture_message('EJV Failed message error', level='error')