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)
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)
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': [] } }
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' }] } }
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)
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': [] } }
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)
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': [] } }
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')
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)
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
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)
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)
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
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)
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')
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'))
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}' } }
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 } }
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')
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 } }
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')
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': [] } }
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()
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)
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)
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': [] } }
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)