async def test_update_payment_record( app, session, test_name, action_code, start_request_state, end_request_state, start_priority, end_priority, start_datetime, days_after_start_datetime, start_payment_state, end_payment_state, start_payment_date, end_payment_has_value, error): """Assert that the update_payment_record works as expected.""" from namex.models import Request, State, Payment from namex_pay.worker import update_payment_record print(test_name) now = datetime.utcnow() with freeze_time(now): # setup PAYMENT_TOKEN = 'dog' NR_NUMBER = 'NR B000001' name_request = Request() name_request.nrNum = NR_NUMBER name_request.stateCd = start_request_state name_request._source = 'NRO' name_request.expirationDate = start_datetime name_request.priorityCd = start_priority name_request.save_to_db() payment = Payment() payment.nrId = name_request.id payment._payment_token = PAYMENT_TOKEN payment._payment_status_code = start_payment_state payment.payment_action = action_code payment.furnished = False payment._payment_completion_date = start_payment_date payment.save_to_db() # run test if error: # expecting it to raise an error with pytest.raises(error): await update_payment_record(payment) else: # else it was processable if not (payment_final := await update_payment_record(payment)): payment_final = payment nr_final = Request.find_by_nr(NR_NUMBER) assert nr_final.stateCd == end_request_state assert nr_final.priorityCd == end_priority assert nr_final.expirationDate == ( start_datetime or now) + timedelta(days=days_after_start_datetime) assert eval( f'payment_final.payment_completion_date {end_payment_has_value} None' ) assert payment_final.payment_status_code == end_payment_state
async def test_process_payment( app, session, mocker, test_name, action_code, start_request_state, start_priority, start_datetime, start_payment_state, start_payment_date, ): from namex.models import Request, State, Payment from namex_pay.worker import process_payment, FLASK_APP nest_asyncio.apply() # setup PAYMENT_TOKEN = 'dog' NR_NUMBER = 'NR B000001' name_request = Request() name_request.nrNum = NR_NUMBER name_request.stateCd = start_request_state name_request._source = 'NRO' name_request.expirationDate = start_datetime name_request.priorityCd = start_priority name_request.save_to_db() payment = Payment() payment.nrId = name_request.id payment._payment_token = PAYMENT_TOKEN payment._payment_status_code = start_payment_state payment.payment_action = action_code payment.furnished = False payment._payment_completion_date = start_payment_date payment.save_to_db() # setup mock and patch msg = None def catch(qsm, cloud_event_msg): nonlocal msg msg = cloud_event_msg mocker.patch('namex_pay.worker.publish_email_message', side_effect=catch) # Test pay_msg = { "paymentToken": { "id": PAYMENT_TOKEN, "statusCode": "COMPLETED", "filingIdentifier": None } } await process_payment(pay_msg, FLASK_APP) print(msg) # Verify message that would be sent to the email server assert msg['type'] == 'bc.registry.names.request' assert msg['source'] == '/requests/NR B000001' assert msg['datacontenttype'] == 'application/json' assert msg['identifier'] == 'NR B000001' assert msg['data']['request']['header']['nrNum'] == NR_NUMBER assert msg['data']['request']['paymentToken'] == PAYMENT_TOKEN assert msg['data']['request']['statusCode'] == 'DRAFT'
payment_response = create_payment(req.as_dict(), 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?)
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')) if payment_response.statusCode in [PaymentStatusCode.CREATED.value, PaymentStatusCode.COMPLETED.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.createdOn payment.payment_status_code = PaymentState.CREATED.value payment.payment_action = payment_action 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, '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 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)
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)