async def furnish_receipt_message(qsm: QueueServiceManager, payment: Payment): # pylint: disable=redefined-outer-name """Send receipt info to the mail queue if it hasn't yet been done.""" if payment.furnished == 'Y': logger.debug( 'Queue Issue: Duplicate, already furnished receipt for payment.id=%s', payment.id) capture_message( f'Queue Issue: Duplicate, already furnished receipt for payment.id={payment.id}' ) return nr = None logger.debug('Start of the furnishing of receipt for payment record:%s', payment.as_dict()) try: payment.furnished = True payment.save_to_db() except Exception as err: # noqa: B902; bare exception to catch all raise Exception('Unable to alter payment record.') from err try: nr = RequestDAO.find_by_id(payment.nrId) cloud_event_msg = create_cloud_event_msg( msg_id=str(uuid.uuid4()), msg_type='bc.registry.names.request', source=f'/requests/{nr.nrNum}', time=datetime.utcfromtimestamp( time.time()).replace(tzinfo=timezone.utc).isoformat(), identifier=nr.nrNum, json_data_body={ 'request': { 'header': { 'nrNum': nr.nrNum }, 'paymentToken': payment.payment_token, 'statusCode': nr.stateCd } }) logger.debug('About to publish email for payment.id=%s', payment.id) await publish_email_message(qsm, cloud_event_msg) except Exception as err: # noqa: B902; bare exception to catch all payment.furnished = False payment.save_to_db() logger.debug('Reset payment furnish status payment.id= %s', payment.id) raise QueueException(f'Unable to furnish NR info. {err}') from err
def post(self, nr_id, payment_action=NameRequestActions.COMPLETE.value): """ At this point, the Name Request will still be using a TEMPORARY NR number. Confirming the payment on the frontend triggers this endpoint. Here, we: - Save the request to NRO which gives us a real NR. - Create the payment via SBC Pay. - If payment creation is successful, create a corresponding payment record in our system. :param nr_id: :param payment_action: :return: """ try: # Find the existing name request nr_model = RequestDAO.query.get(nr_id) if not nr_model: # Should this be a 400 or 404... hmmm return jsonify(message='Name Request {nr_id} not found'.format( nr_id=nr_id)), 400 if not payment_action: return jsonify( message='Invalid payment action, {action} not found'. format(action=payment_action)), 400 valid_payment_action = payment_action in [ NameRequestActions.COMPLETE.value, NameRequestActions.UPGRADE.value, NameRequestActions.REAPPLY.value ] if not valid_payment_action: return jsonify( message='Invalid payment action [{action}]'.format( action=payment_action)), 400 # We only handle payments if the NR is in the following states valid_payment_states = [ State.DRAFT, State.COND_RESERVE, State.RESERVED, State.CONDITIONAL, State.APPROVED ] valid_nr_state = nr_model.stateCd in valid_payment_states if not valid_nr_state: return jsonify(message='Invalid NR state'.format( action=payment_action)), 400 if valid_payment_action and valid_nr_state: if payment_action in [NameRequestActions.COMPLETE.value]: # Save the record to NRO, which swaps the NR-L Number for a real NR update_solr = True nr_model = self.add_records_to_network_services( nr_model, update_solr) json_input = request.get_json() payment_request = {} if not json_input: # return jsonify(message=MSG_BAD_REQUEST_NO_JSON_BODY), 400 # Grab the data from the NR, if it exists payment_request = build_payment_request(nr_model) elif isinstance(json_input, dict): payment_request = merge_payment_request(nr_model, json_input) elif isinstance(json_input, str): payment_request = merge_payment_request( nr_model, json.loads(json_input)) # Grab the info we need off the request payment_info = payment_request.get('paymentInfo') filing_info = payment_request.get('filingInfo') business_info = payment_request.get('businessInfo') # Create our payment request req = PaymentRequest(payment_info=payment_info, filing_info=filing_info, business_info=business_info) payment_response = create_payment(req) if not payment_response: raise PaymentServiceError(message=MSG_ERROR_CREATING_RESOURCE) if payment_response and payment_response.status_code == PaymentStatusCode.CREATED.value: # Save the payment info to Postgres payment = PaymentDAO() payment.nrId = nr_model.id payment.payment_token = str(payment_response.id) payment.payment_completion_date = payment_response.created_on payment.payment_status_code = PaymentState.CREATED.value payment.save_to_db() # Wrap the response, providing info from both the SBC Pay response and the payment we created data = jsonify({ 'id': payment.id, 'nrId': payment.nrId, 'token': payment.payment_token, 'statusCode': payment.payment_status_code, 'completionDate': payment.payment_completion_date, 'payment': payment.as_dict(), 'sbcPayment': payment_response.to_dict() }) # Record the event nr_svc = self.nr_service # EventRecorder.record(nr_svc.user, Event.PATCH + ' [payment ID: {id}]'.format(id=payment.id), nr_model, data) response = make_response(data, 201) return response except PaymentServiceError as err: return handle_exception(err, err.message, 500) except SBCPaymentException as err: return handle_exception(err, err.message, err.status_code) except SBCPaymentError as err: return handle_exception(err, err.message, 500) except Exception as err: return handle_exception(err, err, 500)
async def test_furnish_receipt_message(app, session, stan_server, event_loop, client_id, entity_stan, future): """Assert that events are placed on the email queue and the payment is marked furnished.""" from queue_common.messages import create_cloud_event_msg from queue_common.service import ServiceWorker from queue_common.service_utils import subscribe_to_queue from namex_pay.worker import APP_CONFIG, furnish_receipt_message, qsm from namex.models import Request, State, Payment print('test vars') print(app, session, stan_server, event_loop, client_id, entity_stan, future) # setup PAYMENT_TOKEN = 'dog' NR_NUMBER = 'NR B000001' name_request = Request() name_request.nrNum = NR_NUMBER name_request.stateCd = State.DRAFT name_request._source = 'NRO' name_request.save_to_db() payment = Payment() payment.nrId = name_request.id payment._payment_token = PAYMENT_TOKEN payment._payment_status_code = 'COMPLETED' payment.furnished = False payment.save_to_db() # file handler callback msgs = [] s = ServiceWorker() s.sc = entity_stan qsm.service = s async def cb_handler(msg): nonlocal msgs nonlocal future msgs.append(msg) print('call back recvd') if len(msgs) == 1: future.set_result(True) file_handler_subject = APP_CONFIG.EMAIL_PUBLISH_OPTIONS['subject'] print(f'file_handler_subject:{file_handler_subject}') await subscribe_to_queue(entity_stan, file_handler_subject, f'entity_queue.{file_handler_subject}', f'entity_durable_name.{file_handler_subject}', cb_handler) print(payment.as_dict()) # sanity check assert name_request.id assert payment.nrId == name_request.id # test await furnish_receipt_message(qsm, payment) try: await asyncio.wait_for(future, 1, loop=event_loop) except Exception as err: print(err) # results processed_payment = Payment.find_by_payment_token(PAYMENT_TOKEN) # verify assert processed_payment.furnished assert len(msgs) == 1 cloud_event = json.loads(msgs[0].data.decode('utf-8')) assert cloud_event['identifier'] == NR_NUMBER
'id': payment.id, 'nrId': payment.nrId, 'nrNum': nr_model.nrNum, 'token': payment.payment_token, 'statusCode': payment.payment_status_code, 'action': payment.payment_action, 'completionDate': payment.payment_completion_date, 'payment': payment.as_dict(), 'sbcPayment': payment_response.as_dict(), 'isPaymentActionRequired': payment_response.isPaymentActionRequired }) response = make_response(data, 201) return response # something went wrong with status code above else: # log actual status code current_app.logger.debug( 'Error with status code. Actual status code: ' + payment_response.statusCode) EventRecorder.record(
def post(self, nr_id, payment_action=NameRequestActions.CREATE.value): """ At this point, the Name Request will still be using a TEMPORARY NR number. Confirming the payment on the frontend triggers this endpoint. Here, we: - Save the request to NRO which gives us a real NR. - Create the payment via SBC Pay. - If payment creation is successful, create a corresponding payment record in our system. :param nr_id: :param payment_action: :return: """ try: # Find the existing name request nr_model = RequestDAO.query.get(nr_id) if not nr_model: # Should this be a 400 or 404... hmmm return jsonify(message='Name Request {nr_id} not found'.format(nr_id=nr_id)), 400 if not payment_action: return jsonify(message='Invalid payment action, {action} not found'.format(action=payment_action)), 400 valid_payment_action = payment_action in [ NameRequestActions.CREATE.value, NameRequestActions.UPGRADE.value, NameRequestActions.REAPPLY.value ] if not valid_payment_action: return jsonify(message='Invalid payment action [{action}]'.format(action=payment_action)), 400 # We only handle payments if the NR is in the following states valid_payment_states = [State.DRAFT, State.COND_RESERVE, State.RESERVED, State.CONDITIONAL, State.APPROVED, State.PENDING_PAYMENT] valid_nr_state = nr_model.stateCd in valid_payment_states if not valid_nr_state: return jsonify(message='Invalid NR state'.format(action=payment_action)), 400 if valid_payment_action and valid_nr_state: if payment_action in [NameRequestActions.CREATE.value]: # Save the record to NRO, which swaps the NR-L Number for a real NR update_solr = True nr_model = self.add_records_to_network_services(nr_model, update_solr) json_input = request.get_json() payment_request = {} if not json_input: # return jsonify(message=MSG_BAD_REQUEST_NO_JSON_BODY), 400 # Grab the data from the NR, if it exists payment_request = build_payment_request(nr_model) elif isinstance(json_input, dict): payment_request = merge_payment_request(nr_model, json_input) elif isinstance(json_input, str): payment_request = merge_payment_request(nr_model, json.loads(json_input)) # Grab the info we need off the request payment_info = payment_request.get('paymentInfo', {}) filing_info = payment_request.get('filingInfo') business_info = payment_request.get('businessInfo') # Create our payment request req = PaymentRequest( paymentInfo=payment_info, filingInfo=filing_info, businessInfo=business_info ) payment_response = create_payment(req.as_dict(), json_input.get('headers')) try: successful_status_list = [ PaymentStatusCode.APPROVED.value, PaymentStatusCode.CREATED.value, PaymentStatusCode.COMPLETED.value ] if payment_response.statusCode in successful_status_list: # Save the payment info to Postgres payment = PaymentDAO() payment.nrId = nr_model.id payment.payment_token = str(payment_response.id) # namex-pay will set payment_status_code to completed state after actioning it on the queue payment.payment_status_code = payment_response.statusCode payment.payment_action = payment_action payment.save_to_db() # happens for PAD. If completed/approved right away queue will have err'd so apply changes here # TODO: send email / furnish payment for these if payment_response.statusCode in [PaymentStatusCode.APPROVED.value, PaymentStatusCode.COMPLETED.value]: if payment_action == PaymentDAO.PaymentActions.CREATE.value: # pylint: disable=R1705 if nr_model.stateCd == State.PENDING_PAYMENT: nr_model.stateCd = State.DRAFT payment.payment_completion_date = datetime.utcnow() elif payment_action == PaymentDAO.PaymentActions.UPGRADE.value: # TODO: handle this (refund payment and prevent action?) if nr_model.stateCd == State.PENDING_PAYMENT: msg = f'Upgrading a non-DRAFT NR for payment.id={payment.id}' current_app.logger.debug(msg) nr_model.priorityCd = 'Y' nr_model.priorityDate = datetime.utcnow() payment.payment_completion_date = datetime.utcnow() elif payment_action == PaymentDAO.PaymentActions.REAPPLY.value: # TODO: handle this (refund payment and prevent action?) if nr_model.stateCd != State.APPROVED \ and nr_model.expirationDate + timedelta(hours=NAME_REQUEST_EXTENSION_PAD_HOURS) < datetime.utcnow(): msg = f'Extend NR for payment.id={payment.id} nr_model.state{nr_model.stateCd}, nr_model.expires:{nr_model.expirationDate}' current_app.logger.debug(msg) nr_model.expirationDate = nr_model.expirationDate + timedelta(days=NAME_REQUEST_LIFESPAN_DAYS) payment.payment_completion_date = datetime.utcnow() nr_model.save_to_db() payment.save_to_db() # Wrap the response, providing info from both the SBC Pay response and the payment we created data = jsonify({ 'id': payment.id, 'nrId': payment.nrId, 'nrNum': nr_model.nrNum, 'token': payment.payment_token, 'statusCode': payment.payment_status_code, 'action': payment.payment_action, 'completionDate': payment.payment_completion_date, 'payment': payment.as_dict(), 'sbcPayment': payment_response.as_dict() }) # Record the event # nr_svc = self.nr_service # EventRecorder.record(nr_svc.user, Event.POST + ' [payment created]', json_input) response = make_response(data, 201) return response # something went wrong with status code above else: # log actual status code current_app.logger.debug('Error with status code. Actual status code: ' + payment_response.statusCode) # return generic error status to the front end return jsonify(message='Name Request {nr_id} encountered an error'.format(nr_id=nr_id)), 402 except Exception as err: current_app.logger.error(err.with_traceback(None)) return jsonify(message='Name Request {nr_id} encountered an error'.format(nr_id=nr_id)), 500 except PaymentServiceError as err: return handle_exception(err, err.message, 500) except SBCPaymentException as err: return handle_exception(err, err.message, err.status_code) except SBCPaymentError as err: return handle_exception(err, err.message, 500) except Exception as err: return handle_exception(err, err, 500)