def _create_single_invoice_per_purchase(cls, payment_method: PaymentMethod): """Create one CFS invoice per purchase.""" invoices: List[InvoiceModel] = InvoiceModel.query \ .filter_by(payment_method_code=payment_method.value) \ .filter_by(invoice_status_code=InvoiceStatus.CREATED.value) \ .order_by(InvoiceModel.created_on.asc()).all() current_app.logger.info(f'Found {len(invoices)} to be created in CFS.') for invoice in invoices: # Get cfs account payment_account: PaymentAccountService = PaymentAccountService.find_by_id( invoice.payment_account_id) cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) # Check for corp type and see if online banking is allowed. if invoice.payment_method_code == PaymentMethod.ONLINE_BANKING.value: corp_type: CorpTypeModel = CorpTypeModel.find_by_code( invoice.corp_type_code) if not corp_type.is_online_banking_allowed: continue # Create a CFS invoice current_app.logger.debug( f'Creating cfs invoice for invoice {invoice.id}') try: invoice_response = CFSService.create_account_invoice( transaction_number=invoice.id, line_items=invoice.payment_line_items, cfs_account=cfs_account) except Exception as e: # NOQA # pylint: disable=broad-except capture_message( f'Error on creating Online Banking invoice: account id={payment_account.id}, ' f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue # Create invoice reference, payment record and a payment transaction InvoiceReference.create( invoice_id=invoice.id, invoice_number=invoice_response.json().get('invoice_number'), reference_number=invoice_response.json().get( 'pbc_ref_number', None)) # Misc invoice.cfs_account_id = payment_account.cfs_account_id # leave the status as SETTLEMENT_SCHEDULED invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save()
def _update_receipt_details(invoices, payment, receipt_details, transaction_dao): """Update receipt details to invoice.""" payment.paid_amount = receipt_details[2] payment.completed_on = datetime.now() transaction_dao.status_code = TransactionStatus.COMPLETED.value if float(payment.paid_amount) < float(payment.invoice_amount): current_app.logger.critical( 'ALERT : Paid Amount is less than owed amount. Paid : %s, Owed- %s', payment.paid_amount, payment.invoice_amount) capture_message( f'ALERT : Paid Amount is less than owed amount. Paid : {payment.paid_amount}, ' f'Owed: {payment.invoice_amount}', level='error') else: payment.payment_status_code = PaymentStatus.COMPLETED.value for invoice in invoices: # Save receipt details for each invoice PaymentTransaction.__save_receipt(invoice, receipt_details) invoice.paid = invoice.total # set the paid amount as total invoice.invoice_status_code = InvoiceStatus.PAID.value invoice_reference = InvoiceReference.find_active_reference_by_invoice_id( invoice.id) invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value # TODO If it's not PAD, publish message. Refactor and move to pay system service later. if invoice.payment_method_code != PaymentMethod.PAD.value: PaymentTransaction.publish_status(transaction_dao, invoice)
def create_invoice( self, payment_account: PaymentAccount, # pylint: disable=too-many-locals line_items: [PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: """Create Invoice in PayBC.""" current_app.logger.debug('<create_invoice') # Build line item model array, as that's needed for CFS Service line_item_models: List[PaymentLineItemModel] = [] for line_item in line_items: line_item_models.append( PaymentLineItemModel.find_by_id(line_item.id)) invoice_response = self.create_account_invoice(invoice.id, line_item_models, payment_account) invoice_reference: InvoiceReference = InvoiceReference.create( invoice.id, invoice_response.json().get('invoice_number', None), invoice_response.json().get('pbc_ref_number', None)) current_app.logger.debug('>create_invoice') return invoice_reference
def create_invoice(self, payment_account: PaymentAccount, line_items: [PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: """Return a static invoice number for direct pay.""" invoice_reference: InvoiceReference = InvoiceReference.create( invoice.id, generate_transaction_number(invoice.id), None) return invoice_reference
def test_active_reference_by_invoice_id(session): """Assert that the invoice reference lookup is working.""" payment_account = factory_payment_account() payment = factory_payment() payment_account.save() payment.save() i = factory_invoice(payment=payment, payment_account=payment_account) i.save() InvoiceReference.create(i.id, 'TEST_INV_NUMBER', 'TEST_REF_NUMBER') # Do a look up invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(i.id) assert invoice_reference is not None assert invoice_reference.id is not None assert invoice_reference.invoice_id == i.id
def _build_pay_system_url_for_payment(payment: Payment, pay_system_service: PaymentSystemService, transaction_id: uuid, pay_return_url: str): """Build pay system url which will be used to redirect to the payment system.""" current_app.logger.debug('<build_pay_system_url') invoice_reference = InvoiceReference.find_any_active_reference_by_invoice_number(payment.invoice_number) return_url = f'{pay_return_url}/{payment.id}/transaction/{transaction_id}' current_app.logger.debug('>build_pay_system_url') return pay_system_service.get_payment_system_url_for_payment(payment, invoice_reference, return_url)
def create_invoice(self, payment_account: PaymentAccount, line_items: [PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: """Return a static invoice number.""" current_app.logger.debug('<create_invoice') invoice_reference: InvoiceReference = InvoiceReference.create(invoice.id, generate_transaction_number(invoice.id), None) current_app.logger.debug('>create_invoice') return invoice_reference
def test_find_completed_reference_by_invoice_id(session): """Assert that the completed invoice reference lookup is working.""" payment_account = factory_payment_account() payment = factory_payment() payment_account.save() payment.save() i = factory_invoice(payment=payment, payment_account=payment_account) i.save() invoice_reference = InvoiceReference.create(i.id, 'TEST_INV_NUMBER', 'TEST_REF_NUMBER') invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value invoice_reference.save() # Do a look up invoice_reference = InvoiceReference.find_completed_reference_by_invoice_id(i.id) assert invoice_reference is not None assert invoice_reference.id is not None assert invoice_reference.invoice_id == i.id
def wrapper(*func_args, **func_kwargs): """Complete any post invoice activities if needed.""" user: UserContext = func_kwargs['user'] if user.is_sandbox(): current_app.logger.info( 'Skipping invoice creation as sandbox token is detected.') invoice: Invoice = func_args[ 3] # 3 is invoice from the create_invoice signature return InvoiceReference.create(invoice.id, f'SANDBOX-{invoice.id}', f'REF-{invoice.id}') return function(*func_args, **func_kwargs)
def build_pay_system_url(payment: Payment, transaction_id: uuid, pay_return_url: str): """Build pay system url which will be used to redirect to the payment system.""" current_app.logger.debug('<build_pay_system_url') pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_system_code( payment_system=payment.payment_system_code ) invoice = InvoiceModel.find_by_payment_id(payment.id) invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(invoice.id) return_url = f'{pay_return_url}/{payment.id}/transaction/{transaction_id}' current_app.logger.debug('>build_pay_system_url') return pay_system_service.get_payment_system_url(Invoice.populate(invoice), invoice_reference, return_url)
def create_invoice(self, payment_account: PaymentAccount, line_items: [PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: """Return a static invoice number.""" invoice_reference: InvoiceReference = None # If the account is not billable, then create records, if not payment_account.billable: current_app.logger.debug( f'Non billable invoice {invoice.id}, ' f'Auth Account : {payment_account.auth_account_id}') invoice_reference = InvoiceReference.create( invoice.id, generate_transaction_number(invoice.id), None) # else Do nothing here as the invoice references are created later. return invoice_reference
def test_invoice_saved_from_new(session): """Assert that the invoice reference is saved to the table.""" payment_account = factory_payment_account() payment = factory_payment() payment_account.save() payment.save() i = factory_invoice(payment=payment, payment_account=payment_account) i.save() invoice_reference = InvoiceReference.create(i.id, 'TEST_INV_NUMBER', 'TEST_REF_NUMBER') assert invoice_reference is not None assert invoice_reference.id is not None assert invoice_reference.invoice_id == i.id assert invoice_reference.status_code == InvoiceReferenceStatus.ACTIVE.value
def process_cfs_refund(self, invoice: InvoiceModel) -> str: # pylint:disable=unused-argument """Do nothing to process refund; as the refund is handled by CRON job. Return the status after checking invoice status. 1. If invoice status is APPROVED: 1.1 return REFUND_REQUESTED if there is an ACTIVE invoice_reference 1.2 else return REFUNDED (as no refund process is needed for this as JV hasn't started yet) 2. If invoice status is PAID 2.1 Return REFUND_REQUESTED """ current_app.logger.info( f'Received JV refund for invoice {invoice.id}, {invoice.invoice_status_code}' ) if invoice.invoice_status_code == InvoiceStatus.APPROVED.value: if InvoiceReference.find_active_reference_by_invoice_id( invoice.id): return InvoiceStatus.REFUND_REQUESTED.value return InvoiceStatus.REFUNDED.value return InvoiceStatus.REFUND_REQUESTED.value
def create_transaction_for_invoice( invoice_id: int, request_json: Dict) -> PaymentTransaction: """Create transaction record for invoice, by creating a payment record if doesn't exist.""" current_app.logger.debug('<create transaction') # Lookup invoice record invoice: Invoice = Invoice.find_by_id(invoice_id, skip_auth_check=True) if not invoice.id: raise BusinessException(Error.INVALID_INVOICE_ID) if invoice.payment_method_code == PaymentMethod.PAD.value: # No transaction needed for PAD invoices. raise BusinessException(Error.INVALID_TRANSACTION) pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( payment_method=invoice.payment_method_code) # Check if return url is valid PaymentTransaction._validate_redirect_url_and_throw_error( invoice.payment_method_code, request_json.get('clientSystemUrl')) # Check if there is a payment created. If not, create a payment record with status CREATED payment: Payment = Payment.find_payment_for_invoice(invoice_id) if not payment: # Transaction is against payment, so create a payment if not present. invoice_reference = InvoiceReference.find_active_reference_by_invoice_id( invoice.id) # Create a payment record payment = Payment.create( payment_method=pay_system_service.get_payment_method_code(), payment_system=pay_system_service.get_payment_system_code(), payment_status=pay_system_service.get_default_payment_status(), invoice_number=invoice_reference.invoice_number, invoice_amount=invoice.total, payment_account_id=invoice.payment_account_id) transaction = PaymentTransaction._create_transaction(payment, request_json, invoice=invoice) current_app.logger.debug('>create transaction') return transaction
class InternalPayService(PaymentSystemService, OAuthService): """Service to manage internal payment.""" def get_payment_system_code(self): """Return INTERNAL as the system code.""" return PaymentSystem.INTERNAL.value def create_invoice(self, payment_account: PaymentAccount, line_items: [PaymentLineItem], invoice: Invoice, **kwargs) -> InvoiceReference: """Return a static invoice number.""" routing_slip = None is_zero_dollar_invoice = get_quantized(invoice.total) == 0 invoice_reference: InvoiceReference = None if routing_slip_number := invoice.routing_slip: current_app.logger.info( f'Routing slip number {routing_slip_number}, for invoice {invoice.id}' ) routing_slip = RoutingSlipModel.find_by_number(routing_slip_number) InternalPayService._validate_routing_slip(routing_slip, invoice) if not is_zero_dollar_invoice and routing_slip is not None: # creating invoice in cfs is done in job current_app.logger.info( f'FAS Routing slip found with remaining amount : {routing_slip.remaining_amount}' ) routing_slip.remaining_amount = routing_slip.remaining_amount - \ get_quantized(invoice.total) if routing_slip.status == RoutingSlipStatus.ACTIVE.value and routing_slip.remaining_amount < 0.01: routing_slip.status = RoutingSlipStatus.COMPLETE.value routing_slip.flush() else: invoice_reference = InvoiceReference.create( invoice.id, generate_transaction_number(invoice.id), None) invoice.invoice_status_code = InvoiceStatus.CREATED.value invoice.save() return invoice_reference
def test_invoice_invalid_lookup(session): """Test invalid lookup.""" inv_reference = InvoiceReference.find_active_reference_by_invoice_id(999) assert inv_reference is None
def _create_pad_invoices(cls): # pylint: disable=too-many-locals """Create PAD invoices in to CFS system.""" # Find all accounts which have done a transaction with PAD transactions inv_subquery = db.session.query(InvoiceModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value).subquery() # Exclude the accounts which are in FREEZE state. pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \ .filter(PaymentAccountModel.id.in_(inv_subquery)).all() current_app.logger.info( f'Found {len(pad_accounts)} with PAD transactions.') invoice_ref_subquery = db.session.query(InvoiceReferenceModel.invoice_id). \ filter(InvoiceReferenceModel.status_code.in_((InvoiceReferenceStatus.ACTIVE.value,))) for account in pad_accounts: # Find all PAD invoices for this account account_invoices = db.session.query(InvoiceModel) \ .filter(InvoiceModel.payment_account_id == account.id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \ .filter(InvoiceModel.id.notin_(invoice_ref_subquery)) \ .order_by(InvoiceModel.created_on.desc()).all() # Get cfs account payment_account: PaymentAccountService = PaymentAccountService.find_by_id( account.id) current_app.logger.debug( f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}' ) if len(account_invoices) == 0: continue cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) if cfs_account is None: # Get the last cfs_account for it, as the account might have got upgraded from PAD to DRAWDOWN. cfs_account: CfsAccountModel = CfsAccountModel.query.\ filter(CfsAccountModel.account_id == payment_account.id).order_by(CfsAccountModel.id.desc()).first() # If the CFS Account status is not ACTIVE or INACTIVE (for above case), raise error and continue if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value): capture_message( f'CFS Account status is not ACTIVE. for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}', level='error') current_app.logger.error( f'CFS status for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}') continue # Add all lines together lines = [] invoice_total: float = 0 for invoice in account_invoices: lines.extend(invoice.payment_line_items) invoice_total += invoice.total invoice_number = account_invoices[-1].id try: # Get the first invoice id as the trx number for CFS invoice_response = CFSService.create_account_invoice( transaction_number=invoice_number, line_items=lines, cfs_account=cfs_account) except Exception as e: # NOQA # pylint: disable=broad-except # There is a chance that the error is a timeout from CAS side, # so to make sure we are not missing any data, make a GET call for the invoice we tried to create # and use it if it got created. current_app.logger.info( e ) # INFO is intentional as sentry alerted only after the following try/catch has_invoice_created: bool = False try: # add a 60 seconds delay here as safe bet, as CFS takes time to create the invoice and # since this is a job, delay doesn't cause any performance issue time.sleep(60) invoice_number = generate_transaction_number( str(invoice_number)) invoice_response = CFSService.get_invoice( cfs_account=cfs_account, inv_number=invoice_number) has_invoice_created = invoice_response.json().get( 'invoice_number', None) == invoice_number except Exception as exc: # NOQA # pylint: disable=broad-except,unused-variable # Ignore this error, as it is irrelevant and error on outer level is relevant. pass # If no invoice is created raise an error for sentry if not has_invoice_created: capture_message( f'Error on creating PAD invoice: account id={payment_account.id}, ' f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue # emit account mailer event mailer.publish_mailer_events('pad.invoiceCreated', payment_account, {'invoice_total': invoice_total}) # Iterate invoice and create invoice reference records for invoice in account_invoices: # Create invoice reference, payment record and a payment transaction InvoiceReference.create( invoice_id=invoice.id, invoice_number=invoice_response.json().get( 'invoice_number'), reference_number=invoice_response.json().get( 'pbc_ref_number', None)) # Misc invoice.cfs_account_id = payment_account.cfs_account_id # no longer set to settlement sceduled # invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save()
def _create_rs_invoices(cls): # pylint: disable=too-many-locals """Create RS invoices in to CFS system.""" # Find all pending routing slips. # find all routing slip invoices [cash or cheque] # create invoices in csf # do the receipt apply invoices: List[InvoiceModel] = db.session.query(InvoiceModel) \ .join(RoutingSlipModel, RoutingSlipModel.number == InvoiceModel.routing_slip) \ .join(CfsAccountModel, CfsAccountModel.account_id == RoutingSlipModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \ .filter(CfsAccountModel.status.in_([CfsAccountStatus.ACTIVE.value, CfsAccountStatus.FREEZE.value])) \ .filter(InvoiceModel.routing_slip is not None) \ .order_by(InvoiceModel.created_on.asc()).all() current_app.logger.info(f'Found {len(invoices)} to be created in CFS.') for invoice in invoices: # Create a CFS invoice current_app.logger.debug( f'Creating cfs invoice for invoice {invoice.id}') routing_slip = RoutingSlipModel.find_by_number( invoice.routing_slip) # If routing slip is not found in Pay-DB, assume legacy RS and move on to next one. if not routing_slip: continue routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id( routing_slip.payment_account_id) # apply invoice to the active CFS_ACCOUNT which will be the parent routing slip active_cfs_account = CfsAccountModel.find_effective_by_account_id( routing_slip_payment_account.id) invoice_response = CFSService.create_account_invoice( transaction_number=invoice.id, line_items=invoice.payment_line_items, cfs_account=active_cfs_account) invoice_number = invoice_response.json().get( 'invoice_number', None) current_app.logger.info( f'invoice_number {invoice_number} created in CFS.') has_error_in_apply_receipt = RoutingSlipTask.apply_routing_slips_to_invoice( routing_slip_payment_account, active_cfs_account, routing_slip, invoice, invoice_number) if has_error_in_apply_receipt: # move on to next invoice continue invoice_reference: InvoiceReference = InvoiceReference.create( invoice.id, invoice_number, invoice_response.json().get('pbc_ref_number', None)) current_app.logger.debug('>create_invoice') # leave the status as PAID invoice_reference.status_code = InvoiceReferenceStatus.COMPLETED.value invoice.invoice_status_code = InvoiceStatus.PAID.value invoice.paid = invoice.total Payment.create(payment_method=PaymentMethod.INTERNAL.value, payment_system=PaymentSystem.INTERNAL.value, payment_status=PaymentStatus.COMPLETED.value, invoice_number=invoice_reference.invoice_number, invoice_amount=invoice.total, payment_account_id=invoice.payment_account_id) invoice.save()
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 _create_pad_invoices(cls): # pylint: disable=too-many-locals """Create PAD invoices in to CFS system.""" # Find all accounts which have done a transaction with PAD transactions inv_subquery = db.session.query(InvoiceModel.payment_account_id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == PaymentStatus.CREATED.value).subquery() # Exclude the accounts which are in FREEZE state. pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \ .join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \ .filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \ .filter(PaymentAccountModel.id.in_(inv_subquery)).all() current_app.logger.info( f'Found {len(pad_accounts)} with PAD transactions.') for account in pad_accounts: # Find all PAD invoices for this account account_invoices = db.session.query(InvoiceModel) \ .filter(InvoiceModel.payment_account_id == account.id) \ .filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \ .filter(InvoiceModel.invoice_status_code == InvoiceStatus.CREATED.value) \ .order_by(InvoiceModel.created_on.desc()).all() # Get cfs account payment_account: PaymentAccountService = PaymentAccountService.find_by_id( account.id) current_app.logger.debug( f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}' ) if len(account_invoices) == 0: continue cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id( payment_account.id) if cfs_account is None: # Get the last invoice and look up cfs_account for it, as the account might have got upgraded. cfs_account = CfsAccountModel.find_by_id( account_invoices[0].cfs_account_id) # If the CFS Account status is not ACTIVE, raise error and continue if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value): capture_message( f'CFS Account status is not ACTIVE. for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}', level='error') current_app.logger.error( f'CFS status for account {payment_account.auth_account_id} ' f'is {payment_account.cfs_account_status}') continue # Add all lines together lines = [] invoice_total: float = 0 for invoice in account_invoices: lines.append(*invoice.payment_line_items) invoice_total += invoice.total try: # Get the first invoice id as the trx number for CFS invoice_response = CFSService.create_account_invoice( transaction_number=account_invoices[0].id, line_items=lines, payment_account=cfs_account) except Exception as e: # pylint: disable=broad-except capture_message( f'Error on creating PAD invoice: account id={payment_account.id}, ' f'auth account : {payment_account.auth_account_id}, ERROR : {str(e)}', level='error') current_app.logger.error(e) continue # emit account mailer event mailer.publish_mailer_events('pad.invoiceCreated', payment_account, {'invoice_total': invoice_total}) # Iterate invoice and create invoice reference records for invoice in account_invoices: # Create invoice reference, payment record and a payment transaction InvoiceReference.create( invoice_id=invoice.id, invoice_number=invoice_response.json().get( 'invoice_number'), reference_number=invoice_response.json().get( 'pbc_ref_number', None)) # Misc invoice.cfs_account_id = payment_account.cfs_account_id invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value invoice.save()
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