def publish_status(transaction_dao: PaymentTransactionModel, payment: Payment): """Publish payment/transaction status to the Queue.""" current_app.logger.debug('<publish_status') if transaction_dao.status_code == Status.COMPLETED.value: if payment.payment_status_code == Status.COMPLETED.value: status_code = Status.COMPLETED.value else: current_app.logger.info( f'Status {payment.payment_status_code} received for payment {payment.id}' ) return else: status_code = 'TRANSACTION_FAILED' payload = { 'paymentToken': { 'id': payment.id, 'statusCode': status_code } } try: publish_response(payload=payload) except Exception as e: # pylint: disable=broad-except current_app.logger.error(e) current_app.logger.warn( f'Notification to Queue failed, marking the transaction : {transaction_dao.id} as EVENT_FAILED', e) transaction_dao.status_code = Status.EVENT_FAILED.value current_app.logger.debug('>publish_status')
def publish_status(transaction_dao: PaymentTransactionModel, invoice: Invoice): """Publish payment/transaction status to the Queue.""" current_app.logger.debug('<publish_status') if transaction_dao.status_code == TransactionStatus.COMPLETED.value: if invoice.invoice_status_code == InvoiceStatus.PAID.value: status_code = TransactionStatus.COMPLETED.value else: current_app.logger.info( f'Status {invoice.invoice_status_code} received for invoice {invoice.id}' ) return else: status_code = 'TRANSACTION_FAILED' payload = PaymentTransaction.create_event_payload(invoice, status_code) try: publish_response(payload=payload, subject=get_pay_subject_name( invoice.corp_type_code)) except Exception as e: # NOQA pylint: disable=broad-except current_app.logger.error(e) current_app.logger.warning( f'Notification to Queue failed, marking the transaction : {transaction_dao.id} as EVENT_FAILED', e) transaction_dao.status_code = TransactionStatus.EVENT_FAILED.value current_app.logger.debug('>publish_status')
def _update_stale_payments(cls): """Update stale payment records. This is to handle edge cases where the user has completed payment and some error occured and payment status is not up-to-date. """ stale_transactions = PaymentTransactionModel.find_stale_records( minutes=30) if len(stale_transactions) == 0: current_app.logger.info( f'Stale Transaction Job Ran at {datetime.datetime.now()}.But No records found!' ) for transaction in stale_transactions: try: current_app.logger.info( 'Stale Transaction Job found records.Payment Id: {}, Transaction Id : {}' .format(transaction.payment_id, transaction.id)) TransactionService.update_transaction(transaction.id, pay_response_url=None) current_app.logger.info( 'Stale Transaction Job Updated records.Payment Id: {}, Transaction Id : {}' .format(transaction.payment_id, transaction.id)) except BusinessException as err: # just catch and continue .Don't stop current_app.logger.error( 'Stale Transaction Error on update_transaction') current_app.logger.error(err)
def _publish_to_mailer(cls, invoice): """Construct message and send to mailer queue.""" receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number( invoice_id=invoice.id) invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status( invoice_id=invoice.id, status_code=InvoiceReferenceStatus.COMPLETED.value) payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id( invoice_id=invoice.id) q_payload = dict( specversion='1.x-wip', type='bc.registry.payment.refundRequest', source= f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}', id=invoice.id, datacontenttype='application/json', data=dict(identifier=invoice.business_identifier, orderNumber=receipt.receipt_number, transactionDateTime=get_local_formatted_date_time( payment_transaction.transaction_end_time), transactionAmount=receipt.receipt_amount, transactionId=invoice_ref.invoice_number)) current_app.logger.debug( 'Publishing payment refund request to mailer ') current_app.logger.debug(q_payload) publish_response( payload=q_payload, client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'), subject=current_app.config.get('NATS_MAILER_SUBJECT'))
def find_by_payment_id(payment_identifier: int): """Find all transactions by payment id.""" transactions_dao = PaymentTransactionModel.find_by_payment_id(payment_identifier) data: Dict = {'items': []} if transactions_dao: for transaction_dao in transactions_dao: data['items'].append(PaymentTransaction.populate(transaction_dao).asdict()) current_app.logger.debug('>find_by_payment_id') return data
def find_by_id(transaction_id: uuid): """Find transaction by id.""" current_app.logger.debug(f'>find_by_id {transaction_id}') transaction_dao = PaymentTransactionModel.find_by_id(transaction_id) if not transaction_dao: raise BusinessException(Error.INVALID_TRANSACTION_ID) transaction = PaymentTransaction.__wrap_dao(transaction_dao) current_app.logger.debug('>find_by_id') return transaction
def find_by_id(payment_identifier: int, transaction_id: uuid): """Find transaction by id.""" transaction_dao = PaymentTransactionModel.find_by_id_and_payment_id(transaction_id, payment_identifier) if not transaction_dao: raise BusinessException(Error.INVALID_TRANSACTION_ID) transaction = PaymentTransaction() transaction._dao = transaction_dao # pylint: disable=protected-access current_app.logger.debug('>find_by_id') return transaction
def find_active_by_payment_id(payment_identifier: int): """Find active transaction by id.""" existing_transactions = PaymentTransactionModel.find_by_payment_id( payment_identifier) transaction: PaymentTransaction = None if existing_transactions: for existing_transaction in existing_transactions: if existing_transaction.status_code not in ( Status.COMPLETED.value, Status.CANCELLED.value): transaction = existing_transaction current_app.logger.debug('>find_active_by_payment_id') return transaction
def factory_payment_transaction( payment_id: str, status_code: str = 'DRAFT', redirect_url: str = 'http://google.com/', pay_system_url: str = 'http://google.com', transaction_start_time: datetime = datetime.now(), transaction_end_time: datetime = datetime.now()): """Factory.""" return PaymentTransaction(payment_id=payment_id, status_code=status_code, redirect_url=redirect_url, pay_system_url=pay_system_url, transaction_start_time=transaction_start_time, transaction_end_time=transaction_end_time)
def _publish_refund_to_mailer(invoice: InvoiceModel): """Construct message and send to mailer queue.""" from .payment_transaction import publish_response # pylint:disable=import-outside-toplevel,cyclic-import receipt: ReceiptModel = ReceiptModel.find_by_invoice_id_and_receipt_number( invoice_id=invoice.id) invoice_ref: InvoiceReferenceModel = InvoiceReferenceModel.find_reference_by_invoice_id_and_status( invoice_id=invoice.id, status_code=InvoiceReferenceStatus.COMPLETED.value) payment_transaction: PaymentTransactionModel = PaymentTransactionModel.find_recent_completed_by_invoice_id( invoice_id=invoice.id) message_type: str = f'bc.registry.payment.{invoice.payment_method_code.lower()}.refundRequest' transaction_date_time = receipt.receipt_date if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value \ else payment_transaction.transaction_end_time filing_description = '' for line_item in invoice.payment_line_items: if filing_description: filing_description += ',' filing_description += line_item.description q_payload = dict( specversion='1.x-wip', type=message_type, source= f'https://api.pay.bcregistry.gov.bc.ca/v1/invoices/{invoice.id}', id=invoice.id, datacontenttype='application/json', data=dict(identifier=invoice.business_identifier, orderNumber=receipt.receipt_number, transactionDateTime=get_local_formatted_date_time( transaction_date_time), transactionAmount=receipt.receipt_amount, transactionId=invoice_ref.invoice_number, refundDate=get_local_formatted_date_time( datetime.now(), '%Y%m%d'), filingDescription=filing_description)) if invoice.payment_method_code == PaymentMethod.DRAWDOWN.value: payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id( invoice.payment_account_id) q_payload['data'].update( dict(bcolAccount=invoice.bcol_account, bcolUser=payment_account.bcol_user_id)) current_app.logger.debug( f'Publishing payment refund request to mailer for {invoice.id} : {q_payload}' ) publish_response( payload=q_payload, client_name=current_app.config.get('NATS_MAILER_CLIENT_NAME'), subject=current_app.config.get('NATS_MAILER_SUBJECT'))
def create(payment_identifier: str, request_json: Dict): """Create transaction record.""" current_app.logger.debug('<create transaction') # Lookup payment record payment: Payment = Payment.find_by_id(payment_identifier, skip_auth_check=True) # Check if return url is valid return_url = request_json.get('clientSystemUrl') if payment.payment_system_code == PaymentSystem.PAYBC.value and not is_valid_redirect_url( return_url): raise BusinessException(Error.PAY013) if not payment.id: raise BusinessException(Error.PAY005) # Cannot start transaction on completed payment if payment.payment_status_code in (Status.COMPLETED.value, Status.DELETED.value, Status.DELETE_ACCEPTED.value): raise BusinessException(Error.PAY006) # If there are active transactions (status=CREATED), then invalidate all of them and create a new one. existing_transactions = PaymentTransactionModel.find_by_payment_id( payment.id) if existing_transactions: for existing_transaction in existing_transactions: if existing_transaction.status_code != Status.CANCELLED.value: existing_transaction.status_code = Status.CANCELLED.value existing_transaction.transaction_end_time = datetime.now() existing_transaction.save() transaction = PaymentTransaction() transaction.payment_id = payment.id transaction.client_system_url = return_url transaction.status_code = Status.CREATED.value transaction_dao = transaction.flush() transaction._dao = transaction_dao # pylint: disable=protected-access transaction.pay_system_url = transaction.build_pay_system_url( payment, transaction.id, request_json.get('payReturnUrl')) transaction_dao = transaction.save() transaction = PaymentTransaction() transaction._dao = transaction_dao # pylint: disable=protected-access current_app.logger.debug('>create transaction') return transaction
def factory_payment_transaction( payment_id: str, status_code: str = 'CREATED', client_system_url: str = 'http://google.com/', pay_system_url: str = 'http://google.com', transaction_start_time: datetime = datetime.now(), transaction_end_time: datetime = datetime.now(), ): """Return Factory.""" return PaymentTransaction( payment_id=payment_id, status_code=status_code, client_system_url=client_system_url, pay_system_url=pay_system_url, transaction_start_time=transaction_start_time, transaction_end_time=transaction_end_time, )
def _update_stale_payments(cls): """Update stale payment records. This is to handle edge cases where the user has completed payment and some error occured and payment status is not up-to-date. """ stale_transactions = PaymentTransactionModel.find_stale_records( minutes=30) # Find all payments which were failed due to service unavailable error. service_unavailable_transactions = db.session.query(PaymentTransactionModel)\ .join(PaymentModel, PaymentModel.id == PaymentTransactionModel.payment_id) \ .filter(PaymentModel.payment_status_code == PaymentStatus.CREATED.value)\ .filter(PaymentTransactionModel.pay_system_reason_code == Error.SERVICE_UNAVAILABLE.name)\ .all() if len(stale_transactions) == 0 and len( service_unavailable_transactions) == 0: current_app.logger.info( f'Stale Transaction Job Ran at {datetime.datetime.now()}.But No records found!' ) for transaction in [ *stale_transactions, *service_unavailable_transactions ]: try: current_app.logger.info( f'Stale Transaction Job found records.Payment Id: {transaction.payment_id}, ' f'Transaction Id : {transaction.id}') TransactionService.update_transaction(transaction.id, pay_response_url=None) current_app.logger.info( f'Stale Transaction Job Updated records.Payment Id: {transaction.payment_id}, ' f'Transaction Id : {transaction.id}') except BusinessException as err: # just catch and continue .Don't stop # If the error is for COMPLETED PAYMENT, then mark the transaction as CANCELLED # as there would be COMPLETED transaction in place and continue. if err.code == Error.COMPLETED_PAYMENT.name: current_app.logger.info( 'Completed payment, marking transaction as CANCELLED.') transaction.status_code = TransactionStatus.CANCELLED.value transaction.save() else: current_app.logger.info( 'Stale Transaction Error on update_transaction') current_app.logger.info(err)
def create(payment_identifier: str, redirect_uri: str, jwt: JwtManager = None, skip_auth_check: bool = False): """Create transaction record.""" current_app.logger.debug('<create transaction') # Lookup payment record payment: Payment = Payment.find_by_id(payment_identifier, jwt=jwt, one_of_roles=[EDIT_ROLE], skip_auth_check=skip_auth_check) if not payment.id: raise BusinessException(Error.PAY005) if payment.payment_status_code == Status.COMPLETED.value: # Cannot start transaction on completed payment raise BusinessException(Error.PAY006) # If there are active transactions (status=CREATED), then invalidate all of them and create a new one. existing_transactions = PaymentTransactionModel.find_by_payment_id( payment.id) if existing_transactions: for existing_transaction in existing_transactions: if existing_transaction.status_code != Status.CANCELLED.value: existing_transaction.status_code = Status.CANCELLED.value existing_transaction.transaction_end_time = datetime.now() existing_transaction.save() transaction = PaymentTransaction() transaction.payment_id = payment.id transaction.client_system_url = redirect_uri transaction.status_code = Status.CREATED.value transaction_dao = transaction.flush() transaction._dao = transaction_dao # pylint: disable=protected-access transaction.pay_system_url = transaction.build_pay_system_url( payment, transaction.id) transaction_dao = transaction.save() transaction = PaymentTransaction() transaction._dao = transaction_dao # pylint: disable=protected-access current_app.logger.debug('>create transaction') return transaction
def run(): application = create_app() application.logger.debug('Ran Batch Job--') application.app_context().push() stale_transactions = PaymentTransactionModel.find_stale_records(hours=4) if len(stale_transactions) == 0: application.logger.info( f' Job Ran at {datetime.datetime.now()}.But No records found!') for transaction in stale_transactions: try: application.logger.debug( 'Job found records.Payment Id: {}, Transaction Id : {}'.format( transaction.payment_id, transaction.id)) TransactionService.update_transaction(transaction.payment_id, transaction.id, '') application.logger.debug( 'Job Updated records.Payment Id: {}, Transaction Id : {}'. format(transaction.payment_id, transaction.id)) except BusinessException as err: # just catch and continue .Don't stop application.logger.error('Error on update_transaction') application.logger.error(err)
def _create_transaction(payment: Payment, request_json: Dict, invoice: Invoice = None): # Cannot start transaction on completed payment if payment.payment_status_code in (PaymentStatus.COMPLETED.value, PaymentStatus.DELETED.value): raise BusinessException(Error.COMPLETED_PAYMENT) pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( # todo Remove this and use payment.payment_method_code when payment methods are not created upfront payment_method=invoice.payment_method_code if invoice else payment. payment_method_code) # If there are active transactions (status=CREATED), then invalidate all of them and create a new one. existing_transaction = PaymentTransactionModel.find_active_by_payment_id( payment.id) if existing_transaction and existing_transaction.status_code != TransactionStatus.CANCELLED.value: existing_transaction.status_code = TransactionStatus.CANCELLED.value existing_transaction.transaction_end_time = datetime.now() existing_transaction.save() transaction = PaymentTransaction() transaction.payment_id = payment.id transaction.client_system_url = request_json.get('clientSystemUrl') transaction.status_code = TransactionStatus.CREATED.value transaction_dao = transaction.flush() transaction._dao = transaction_dao # pylint: disable=protected-access if invoice: transaction.pay_system_url = PaymentTransaction._build_pay_system_url_for_invoice( invoice, pay_system_service, transaction.id, request_json.get('payReturnUrl')) else: transaction.pay_system_url = PaymentTransaction._build_pay_system_url_for_payment( payment, pay_system_service, transaction.id, request_json.get('payReturnUrl')) transaction_dao = transaction.save() transaction = PaymentTransaction.__wrap_dao(transaction_dao) return transaction
def factory_payment_transaction(payment_id: int): """Return Factory.""" return PaymentTransaction(payment_id=payment_id, status_code=TransactionStatus.CREATED.value, transaction_start_time=datetime.now()).save()
def update_transaction(payment_identifier: int, transaction_id: uuid, receipt_number: str): """Update transaction record. Does the following: 1. Find the payment record with the id 2. Find the invoice record using the payment identifier 3. Call the pay system service and get the receipt details 4. Save the receipt record 5. Change the status of Invoice 6. Change the status of Payment 7. Update the transaction record """ transaction_dao: PaymentTransactionModel = PaymentTransactionModel.find_by_id_and_payment_id( transaction_id, payment_identifier) if not transaction_dao: raise BusinessException(Error.PAY008) if transaction_dao.status_code == Status.COMPLETED.value: raise BusinessException(Error.PAY006) payment: Payment = Payment.find_by_id(payment_identifier) pay_system_service: PaymentSystemService = PaymentSystemFactory.create( payment_system=payment.payment_system_code) invoice = Invoice.find_by_payment_identifier(payment_identifier) payment_account = PaymentAccount.find_by_id(invoice.account_id) receipt_details = pay_system_service.get_receipt( payment_account, receipt_number, invoice.invoice_number) if receipt_details: # Find if a receipt exists with same receipt_number for the invoice receipt: Receipt = Receipt.find_by_invoice_id_and_receipt_number( invoice.id, receipt_details[0]) if not receipt.id: receipt: Receipt = Receipt() receipt.receipt_number = receipt_details[0] receipt.receipt_date = receipt_details[1] receipt.receipt_amount = receipt_details[2] receipt.invoice_id = invoice.id else: receipt.receipt_date = receipt_details[1] receipt.receipt_amount = receipt_details[2] # Save receipt details to DB. receipt.save() invoice.paid = receipt.receipt_amount if invoice.paid == invoice.total: invoice.invoice_status_code = Status.COMPLETED.value payment.payment_status_code = Status.COMPLETED.value payment.save() elif 0 < invoice.paid < invoice.total: invoice.invoice_status_code = Status.PARTIAL.value invoice.save() transaction_dao.transaction_end_time = datetime.now() transaction_dao.status_code = Status.COMPLETED.value transaction_dao = transaction_dao.save() transaction = PaymentTransaction() transaction._dao = transaction_dao # pylint: disable=protected-access current_app.logger.debug('>update_transaction') return transaction
def update_transaction( transaction_id: uuid, # pylint: disable=too-many-locals pay_response_url: str): """Update transaction record. Does the following: 1. Find the payment record with the id 2. Find the invoice record using the payment identifier 3. Call the pay system service and get the receipt details 4. Save the receipt record 5. Change the status of Invoice 6. Change the status of Payment 7. Update the transaction record """ # TODO for now assumption is this def will be called only for credit card, bcol and internal payments. # When start to look into the PAD and Online Banking may need to refactor here transaction_dao: PaymentTransactionModel = PaymentTransactionModel.find_by_id( transaction_id) if not transaction_dao: raise BusinessException(Error.INVALID_TRANSACTION_ID) if transaction_dao.status_code == TransactionStatus.COMPLETED.value: raise BusinessException(Error.INVALID_TRANSACTION) payment: Payment = Payment.find_by_id(transaction_dao.payment_id) payment_account: PaymentAccount = PaymentAccount.find_by_id( payment.payment_account_id) # For transactions other than Credit Card, there could be more than one invoice per payment. invoices: [Invoice] = Invoice.find_invoices_for_payment( transaction_dao.payment_id) if payment.payment_status_code == PaymentStatus.COMPLETED.value: # if the transaction status is EVENT_FAILED then publish to queue and return, else raise error if transaction_dao.status_code == TransactionStatus.EVENT_FAILED.value: # Publish status to Queue for invoice in invoices: PaymentTransaction.publish_status(transaction_dao, invoice) transaction_dao.status_code = TransactionStatus.COMPLETED.value return PaymentTransaction.__wrap_dao(transaction_dao.save()) raise BusinessException(Error.COMPLETED_PAYMENT) pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( payment_method=payment.payment_method_code) invoice_reference = InvoiceReference.find_any_active_reference_by_invoice_number( payment.invoice_number) try: receipt_details = pay_system_service.get_receipt( payment_account, pay_response_url, invoice_reference) txn_reason_code = None except ServiceUnavailableException as exc: txn_reason_code = exc.status transaction_dao.pay_system_reason_code = txn_reason_code receipt_details = None if receipt_details: PaymentTransaction._update_receipt_details(invoices, payment, receipt_details, transaction_dao) else: transaction_dao.status_code = TransactionStatus.FAILED.value # check if the pay_response_url contains any failure status if not txn_reason_code: pay_system_reason_code = pay_system_service.get_pay_system_reason_code( pay_response_url) transaction_dao.pay_system_reason_code = pay_system_reason_code # Save response URL transaction_dao.transaction_end_time = datetime.now() transaction_dao.pay_response_url = pay_response_url transaction_dao = transaction_dao.save() # Publish message to unlock account if account is locked. if payment.payment_status_code == PaymentStatus.COMPLETED.value: active_failed_payments = Payment.get_failed_payments( auth_account_id=payment_account.auth_account_id) current_app.logger.info('active_failed_payments %s', active_failed_payments) if not active_failed_payments: PaymentAccount.unlock_frozen_accounts( payment.payment_account_id) transaction = PaymentTransaction.__wrap_dao(transaction_dao) current_app.logger.debug('>update_transaction') return transaction
def find_active_by_invoice_id(invoice_id: int): """Find active transaction by invoice id.""" current_app.logger.debug('>find_active_by_invoice_id') active_transaction = PaymentTransactionModel.find_active_by_invoice_id( invoice_id) return PaymentTransaction.populate(active_transaction)
def find_active_by_payment_id(payment_identifier: int): """Find active transaction by id.""" current_app.logger.debug('>find_active_by_payment_id') active_transaction = PaymentTransactionModel.find_active_by_payment_id(payment_identifier) return PaymentTransaction.populate(active_transaction)
def update_transaction(payment_identifier: int, transaction_id: uuid, # pylint: disable=too-many-locals receipt_number: str): """Update transaction record. Does the following: 1. Find the payment record with the id 2. Find the invoice record using the payment identifier 3. Call the pay system service and get the receipt details 4. Save the receipt record 5. Change the status of Invoice 6. Change the status of Payment 7. Update the transaction record """ transaction_dao: PaymentTransactionModel = PaymentTransactionModel.find_by_id_and_payment_id( transaction_id, payment_identifier ) if not transaction_dao: raise BusinessException(Error.INVALID_TRANSACTION_ID) if transaction_dao.status_code == TransactionStatus.COMPLETED.value: raise BusinessException(Error.INVALID_TRANSACTION) payment: Payment = Payment.find_by_id(payment_identifier, skip_auth_check=True) if payment.payment_status_code == PaymentStatus.COMPLETED.value: raise BusinessException(Error.COMPLETED_PAYMENT) pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_system_code( payment_system=payment.payment_system_code ) invoice = Invoice.find_by_payment_identifier(payment_identifier, skip_auth_check=True) invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(invoice.id) payment_account = PaymentAccount.find_by_pay_system_id( credit_account_id=invoice.credit_account_id, internal_account_id=invoice.internal_account_id, bcol_account_id=invoice.bcol_account_id) try: receipt_details = pay_system_service.get_receipt(payment_account, receipt_number, invoice_reference) txn_reason_code = None except ServiceUnavailableException as exc: txn_reason_code = exc.status receipt_details = None if receipt_details: # Find if a receipt exists with same receipt_number for the invoice receipt = PaymentTransaction.__save_receipt(invoice, receipt_details) invoice.paid = receipt.receipt_amount if invoice.paid == invoice.total: invoice.invoice_status_code = InvoiceStatus.PAID.value payment.payment_status_code = PaymentStatus.COMPLETED.value payment.save() invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value invoice_reference.save() invoice.save() transaction_dao.status_code = TransactionStatus.COMPLETED.value else: transaction_dao.status_code = TransactionStatus.FAILED.value transaction_dao.transaction_end_time = datetime.now() # Publish status to Queue PaymentTransaction.publish_status(transaction_dao, payment, invoice.filing_id) transaction_dao = transaction_dao.save() transaction = PaymentTransaction() transaction._dao = transaction_dao # pylint: disable=protected-access transaction.pay_system_reason_code = txn_reason_code current_app.logger.debug('>update_transaction') return transaction
def update_transaction( payment_identifier: int, transaction_id: uuid, # pylint: disable=too-many-locals receipt_number: str, jwt: JwtManager = None, skip_auth_check: bool = False): """Update transaction record. Does the following: 1. Find the payment record with the id 2. Find the invoice record using the payment identifier 3. Call the pay system service and get the receipt details 4. Save the receipt record 5. Change the status of Invoice 6. Change the status of Payment 7. Update the transaction record """ transaction_dao: PaymentTransactionModel = PaymentTransactionModel.find_by_id_and_payment_id( transaction_id, payment_identifier) if not transaction_dao: raise BusinessException(Error.PAY008) if transaction_dao.status_code == Status.COMPLETED.value: raise BusinessException(Error.PAY006) payment: Payment = Payment.find_by_id(payment_identifier, jwt=jwt, one_of_roles=[EDIT_ROLE], skip_auth_check=skip_auth_check) pay_system_service: PaymentSystemService = PaymentSystemFactory.create( payment_system=payment.payment_system_code) invoice = Invoice.find_by_payment_identifier(payment_identifier, jwt=jwt, skip_auth_check=True) payment_account = PaymentAccount.find_by_id(invoice.account_id) try: receipt_details = pay_system_service.get_receipt( payment_account, receipt_number, invoice.invoice_number) txn_reason_code = None except ServiceUnavailableException as exc: txn_reason_code = exc.status_code receipt_details = None if receipt_details: # Find if a receipt exists with same receipt_number for the invoice receipt: Receipt = Receipt.find_by_invoice_id_and_receipt_number( invoice.id, receipt_details[0]) if not receipt.id: receipt: Receipt = Receipt() receipt.receipt_number = receipt_details[0] receipt.receipt_date = receipt_details[1] receipt.receipt_amount = receipt_details[2] receipt.invoice_id = invoice.id else: receipt.receipt_date = receipt_details[1] receipt.receipt_amount = receipt_details[2] # Save receipt details to DB. receipt.save() invoice.paid = receipt.receipt_amount if invoice.paid == invoice.total: invoice.invoice_status_code = Status.COMPLETED.value payment.payment_status_code = Status.COMPLETED.value payment.save() elif 0 < invoice.paid < invoice.total: invoice.invoice_status_code = Status.PARTIAL.value invoice.save() transaction_dao.status_code = Status.COMPLETED.value else: transaction_dao.status_code = Status.FAILED.value transaction_dao.transaction_end_time = datetime.now() # Publish status to Queue PaymentTransaction.publish_status(transaction_dao, payment) transaction_dao = transaction_dao.save() transaction = PaymentTransaction() transaction._dao = transaction_dao # pylint: disable=protected-access transaction.pay_system_reason_code = txn_reason_code current_app.logger.debug('>update_transaction') return transaction