def test_account_find_by_invalid_id(session): """Invalid account test.""" import pytest from pay_api.exceptions import BusinessException from pay_api.utils.errors import Error with pytest.raises(BusinessException) as excinfo: PaymentAccountService.find_by_id(999) assert excinfo.value.status == Error.PAY009.status
def test_delete_account(session, payload): """Assert that delete payment account works.""" pay_account: PaymentAccountService = PaymentAccountService.create(payload) PaymentAccountService.delete_account(payload.get('accountId')) # Try to find the account by id. pay_account = PaymentAccountService.find_by_id(pay_account.id) for cfs_account in CfsAccountModel.find_by_account_id(pay_account.id): assert cfs_account.status == CfsAccountStatus.INACTIVE.value if cfs_account else True
def test_account_find_by_id(session): """Assert that the payment is saved to the table.""" payment_account = factory_payment_account() payment_account.save() pa = PaymentAccountService.find_by_id(payment_account.id) assert pa is not None assert pa.id is not None assert pa.corp_number is not None assert pa.corp_type_code is not None assert pa.payment_system_code is not None assert pa.asdict() is not None
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 _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 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, 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
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