def post(): """Create the payment records.""" current_app.logger.info('<Payment.post') request_json = request.get_json() current_app.logger.debug(request_json) # Validate the input request valid_format, errors = schema_utils.validate(request_json, 'payment_request') if not valid_format: return error_to_response( Error.INVALID_REQUEST, invalid_params=schema_utils.serialize(errors)) # Check if user is authorized to perform this action business_identifier = get_str_by_path( request_json, 'businessInfo/businessIdentifier') corp_type_code = get_str_by_path(request_json, 'businessInfo/corpType') authorization = check_auth(business_identifier=business_identifier, corp_type_code=corp_type_code, contains_role=EDIT_ROLE) try: response, status = PaymentService.create_payment( request_json, authorization), HTTPStatus.CREATED except (BusinessException, ServiceUnavailableException) as exception: return exception.response() current_app.logger.debug('>Payment.post') return jsonify(response), status
def _get_payment_method(payment_request: Dict, authorization: Dict): payment_method = get_str_by_path(payment_request, 'paymentInfo/methodOfPayment') if not payment_method: payment_method = get_str_by_path( authorization, 'account/paymentPreference/methodOfPayment') if not payment_method: payment_method = 'CC' return payment_method
def create_account(self, name: str, account_info: Dict[str, Any], authorization: Dict[str, Any]): """Create account.""" current_app.logger.debug('<create_account') bcol_user_id: str = get_str_by_path( authorization, 'account/paymentPreference/bcOnlineUserId') bcol_account_id: str = get_str_by_path( authorization, 'account/paymentPreference/bcOnlineAccountId') return { 'bcol_account_id': bcol_account_id, 'bcol_user_id': bcol_user_id }
def _get_payment_method(payment_request: Dict, authorization: Dict): payment_method = get_str_by_path(payment_request, 'paymentInfo/methodOfPayment') if not payment_method: payment_method = get_str_by_path( authorization, 'account/paymentPreference/methodOfPayment') if not payment_method: is_direct_pay_enabled = current_app.config.get('DIRECT_PAY_ENABLED') if is_direct_pay_enabled: payment_method = PaymentMethod.DIRECT_PAY.value else: payment_method = PaymentMethod.CC.value return payment_method
def _find_payment_account(cls, authorization): # find payment account payment_account: PaymentAccount = PaymentAccount.find_account( authorization) # If there is no payment_account it must be a request with no account (NR, Staff payment etc.) # and invoked using a service account or a staff token if not payment_account: payment_method = get_str_by_path( authorization, 'account/paymentInfo/methodOfPayment' ) or _get_default_payment() payment_account = PaymentAccount.create( dict(accountId=get_str_by_path(authorization, 'account/id'), paymentInfo=dict(methodOfPayment=payment_method))) return payment_account
def create_account(self, name: str, contact_info: Dict[str, Any], authorization: Dict[str, Any], **kwargs): """Create account.""" current_app.logger.debug('<create_account') user: UserContext = kwargs['user'] account_info: Dict[str, Any] = kwargs['account_info'] if user.is_staff() or user.is_system(): bcol_user_id: str = None bcol_account_id: str = get_str_by_path(account_info, 'bcolAccountNumber') else: bcol_user_id: str = get_str_by_path(authorization, 'account/paymentPreference/bcOnlineUserId') bcol_account_id: str = get_str_by_path(authorization, 'account/paymentPreference/bcOnlineAccountId') return { 'bcol_account_id': bcol_account_id, 'bcol_user_id': bcol_user_id }
def create_refund(cls, invoice_id: int, request: Dict[str, str], **kwargs) -> Dict[str, str]: """Create refund.""" current_app.logger.debug(f'Starting refund : {invoice_id}') # Do validation by looking up the invoice invoice: InvoiceModel = InvoiceModel.find_by_id(invoice_id) paid_statuses = (InvoiceStatus.PAID.value, InvoiceStatus.APPROVED.value, InvoiceStatus.UPDATE_REVENUE_ACCOUNT.value) if invoice.invoice_status_code not in paid_statuses: current_app.logger.info( f'Cannot process refund as status of {invoice_id} is {invoice.invoice_status_code}' ) raise BusinessException(Error.INVALID_REQUEST) refund: RefundService = RefundService() refund.invoice_id = invoice_id refund.reason = get_str_by_path(request, 'reason') refund.requested_by = kwargs['user'].user_name refund.requested_date = datetime.now() refund.flush() pay_system_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( payment_method=invoice.payment_method_code) invoice_status = pay_system_service.process_cfs_refund(invoice) message = REFUND_SUCCESS_MESSAGES.get( f'{invoice.payment_method_code}.{invoice.invoice_status_code}') # set invoice status invoice.invoice_status_code = invoice_status or InvoiceStatus.REFUND_REQUESTED.value invoice.refund = invoice.total # no partial refund invoice.save() current_app.logger.debug(f'Completed refund : {invoice_id}') return {'message': message}
def _create_account(pay_service, business_info, contact_info, account_info, authorization): """Create account in pay system and save it in pay db.""" current_app.logger.debug('Check if payment account exists') payment_account: PaymentAccount = PaymentAccount.find_account( business_info, authorization, pay_service.get_payment_system_code(), ) if not payment_account.id: current_app.logger.debug('No payment account, creating new') name = business_info.get( 'businessName', get_str_by_path(authorization, 'account/name')) pay_system_account = pay_service.create_account( name=name, contact_info=contact_info, account_info=account_info, authorization=authorization) current_app.logger.debug( 'Creating payment record for account : {}'.format( payment_account.id)) payment_account = PaymentAccount.create( business_info=business_info, account_details=pay_system_account, payment_system=pay_service.get_payment_system_code(), authorization=authorization) return payment_account
def create_refund(cls, invoice_id: int, request: Dict[str, str], **kwargs) -> None: """Create refund.""" current_app.logger.debug('<create refund') # Do validation by looking up the invoice invoice: InvoiceModel = InvoiceModel.find_by_id(invoice_id) # Allow refund only for direct pay payments, and only if the status of invoice is PAID/UPDATE_REVENUE_ACCOUNT paid_statuses = (InvoiceStatus.PAID.value, InvoiceStatus.APPROVED.value, InvoiceStatus.UPDATE_REVENUE_ACCOUNT.value) if invoice.invoice_status_code not in paid_statuses: raise BusinessException(Error.INVALID_REQUEST) refund: RefundService = RefundService() refund.invoice_id = invoice_id refund.reason = get_str_by_path(request, 'reason') refund.requested_by = kwargs['user'].user_name refund.requested_date = datetime.now() refund.flush() cls._process_cfs_refund(invoice) # set invoice status invoice.invoice_status_code = InvoiceStatus.REFUND_REQUESTED.value invoice.refund = invoice.total # no partial refund invoice.save()
def _save_account(cls, account_request: Dict[str, any], payment_account: PaymentAccountModel, is_sandbox: bool = False): """Update and save payment account and CFS account model.""" # pylint:disable=cyclic-import, import-outside-toplevel from pay_api.factory.payment_system_factory import PaymentSystemFactory payment_account.auth_account_id = account_request.get('accountId') # If the payment method is CC, set the payment_method as DIRECT_PAY if payment_method := get_str_by_path(account_request, 'paymentInfo/methodOfPayment'): payment_account.payment_method = payment_method payment_account.bcol_account = account_request.get( 'bcolAccountNumber', None) payment_account.bcol_user_id = account_request.get( 'bcolUserId', None)
def create_routing_slip_refund(cls, routing_slip_number: str, request: Dict[str, str], **kwargs) -> Dict[str, str]: """Create Routing slip refund.""" current_app.logger.debug('<create Routing slip refund') # # check if routing slip exists # validate user role -> update status of routing slip # check refunds table # if Yes ; update the data [only with whatever is in payload] # if not ; create new entry # call cfs rs_model = RoutingSlipModel.find_by_number(routing_slip_number) if not rs_model: raise BusinessException(Error.RS_DOESNT_EXIST) reason = get_str_by_path(request, 'reason') if (refund_status := get_str_by_path(request, 'status')) is None: raise BusinessException(Error.INVALID_REQUEST)
def find_account(cls, business_info: Dict[str, Any], authorization: Dict[str, Any], payment_system: str, **kwargs): """Find payment account by corp number, corp type and payment system code.""" current_app.logger.debug('<find_payment_account') user: UserContext = kwargs['user'] auth_account_id: str = get_str_by_path(authorization, 'account/id') bcol_user_id: str = get_str_by_path(authorization, 'account/paymentPreference/bcOnlineUserId') bcol_account_id: str = get_str_by_path(authorization, 'account/paymentPreference/bcOnlineAccountId') corp_number: str = business_info.get('businessIdentifier') corp_type: str = business_info.get('corpType') account_dao = None if payment_system == PaymentSystem.BCOL.value: # If BCOL payment and if the payment is inititated by customer check if bcol_user_id is present # Staff and system can make payments for users with no bcol account if not user.is_system() and not user.is_staff() and bcol_user_id is None: raise BusinessException(Error.INCOMPLETE_ACCOUNT_SETUP) account_dao: BcolPaymentAccount = BcolPaymentAccount.find_by_bcol_user_id_and_account( auth_account_id=auth_account_id, bcol_user_id=bcol_user_id, bcol_account_id=bcol_account_id ) elif payment_system == PaymentSystem.INTERNAL.value: account_dao: InternalPaymentAccount = InternalPaymentAccount. \ find_by_corp_number_and_corp_type_and_account_id(corp_number=corp_number, corp_type=corp_type, account_id=auth_account_id ) elif payment_system == PaymentSystem.PAYBC.value: if not corp_number and not corp_type: raise BusinessException(Error.INVALID_CORP_OR_FILING_TYPE) account_dao = CreditPaymentAccount.find_by_corp_number_and_corp_type_and_auth_account_id( corp_number=corp_number, corp_type=corp_type, auth_account_id=auth_account_id ) payment_account = PaymentAccount() payment_account.populate(account_dao) # pylint: disable=protected-access current_app.logger.debug('>find_payment_account') return payment_account
def _get_payment_method(payment_request: Dict, payment_account: PaymentAccount): # If no methodOfPayment is provided, use the one against the payment account table. payment_method = get_str_by_path(payment_request, 'paymentInfo/methodOfPayment') if not payment_method: payment_method = payment_account.payment_method if not payment_method: payment_method = _get_default_payment() return payment_method
def find_account(cls, business_info: Dict[str, Any], authorization: Dict[str, Any], payment_system: str): """Find payment account by corp number, corp type and payment system code.""" current_app.logger.debug('<find_payment_account') auth_account_id: str = get_str_by_path(authorization, 'account/id') bcol_user_id: str = get_str_by_path(authorization, 'account/paymentPreference/bcOnlineUserId') bcol_account_id: str = get_str_by_path(authorization, 'account/paymentPreference/bcOnlineAccountId') corp_number: str = business_info.get('businessIdentifier') corp_type: str = business_info.get('corpType') account_dao = None if payment_system == PaymentSystem.BCOL.value: if not bcol_user_id: raise BusinessException(Error.PAY015) account_dao: BcolPaymentAccount = BcolPaymentAccount.find_by_bcol_user_id_and_account( auth_account_id=auth_account_id, bcol_user_id=bcol_user_id, bcol_account_id=bcol_account_id ) elif payment_system == PaymentSystem.INTERNAL.value: account_dao: InternalPaymentAccount = InternalPaymentAccount. \ find_by_corp_number_and_corp_type_and_account_id(corp_number=corp_number, corp_type=corp_type, account_id=auth_account_id ) elif payment_system == PaymentSystem.PAYBC.value: if not corp_number and not corp_type: raise BusinessException(Error.PAY004) account_dao = CreditPaymentAccount.find_by_corp_number_and_corp_type_and_auth_account_id( corp_number=corp_number, corp_type=corp_type, auth_account_id=auth_account_id ) payment_account = PaymentAccount() payment_account.populate(account_dao) # pylint: disable=protected-access current_app.logger.debug('>find_payment_account') return payment_account
def create(business_info: Dict[str, Any], account_details: Dict[str, str], payment_system: str, authorization: Dict[str, Any]): """Create Payment account record.""" current_app.logger.debug('<create') auth_account_id = get_str_by_path(authorization, 'account/id') payment_account: PaymentAccountModel = PaymentAccountModel.find_by_auth_account_id(auth_account_id) new_payment_account: bool = False is_premium_payment: bool = False if not payment_account: payment_account = PaymentAccountModel() payment_account.auth_account_id = auth_account_id payment_account = payment_account.save() new_payment_account = True dao = None if payment_system == PaymentSystem.BCOL.value: dao = BcolPaymentAccount() dao.account_id = payment_account.id dao.bcol_account_id = account_details.get('bcol_account_id', None) dao.bcol_user_id = account_details.get('bcol_user_id', None) is_premium_payment = True elif payment_system == PaymentSystem.INTERNAL.value: dao = InternalPaymentAccount() dao.corp_number = business_info.get('businessIdentifier', None) dao.corp_type_code = business_info.get('corpType', None) dao.account_id = payment_account.id elif payment_system == PaymentSystem.PAYBC.value: dao = CreditPaymentAccount() dao.corp_number = business_info.get('businessIdentifier', None) dao.corp_type_code = business_info.get('corpType', None) dao.paybc_account = account_details.get('account_number', None) dao.paybc_party = account_details.get('party_number', None) dao.paybc_site = account_details.get('site_number', None) dao.account_id = payment_account.id dao = dao.save() if new_payment_account and is_premium_payment: PaymentAccount._persist_default_statement_frequency(payment_account.id) p = PaymentAccount() p.populate(dao) # pylint: disable=protected-access current_app.logger.debug('>create') return p
def update_invoice(cls, invoice_id: int, payment_request: Tuple[Dict[str, Any]]): """Update invoice related records.""" current_app.logger.debug('<update_invoice') invoice: Invoice = Invoice.find_by_id(invoice_id, skip_auth_check=False) payment_method = get_str_by_path(payment_request, 'paymentInfo/methodOfPayment') is_not_currently_on_ob = invoice.payment_method_code != PaymentMethod.ONLINE_BANKING.value is_not_changing_to_cc = payment_method not in ( PaymentMethod.CC.value, PaymentMethod.DIRECT_PAY.value) # can patch only if the current payment method is OB if is_not_currently_on_ob or is_not_changing_to_cc: raise BusinessException(Error.INVALID_REQUEST) # check if it has any invoice references already created # if there is any invoice ref , send them to the invoiced credit card flow invoice_reference = InvoiceReference.find_active_reference_by_invoice_id( invoice.id) if invoice_reference: invoice.payment_method_code = PaymentMethod.CC.value else: pay_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( PaymentMethod.DIRECT_PAY.value) payment_account = PaymentAccount.find_by_id( invoice.payment_account_id) pay_service.create_invoice(payment_account, invoice.payment_line_items, invoice, corp_type_code=invoice.corp_type_code) invoice.payment_method_code = PaymentMethod.DIRECT_PAY.value invoice.save() current_app.logger.debug('>update_invoice') return invoice.asdict()
def search_purchase_history(cls, # pylint:disable=too-many-arguments, too-many-locals, too-many-branches auth_account_id: str, search_filter: Dict, page: int, limit: int, return_all: bool, max_no_records: int = 0): """Search for purchase history.""" # Payment Account Sub Query payment_account_sub_query = db.session.query(PaymentAccount).filter( PaymentAccount.auth_account_id == auth_account_id).subquery('pay_accnt') query = db.session.query(Payment, Invoice) \ .join(Invoice) \ .outerjoin(CreditPaymentAccount) \ .outerjoin(BcolPaymentAccount) \ .outerjoin(InternalPaymentAccount) \ .filter(or_(InternalPaymentAccount.account_id == payment_account_sub_query.c.id, BcolPaymentAccount.account_id == payment_account_sub_query.c.id, CreditPaymentAccount.account_id == payment_account_sub_query.c.id)) if search_filter.get('status', None): query = query.filter(Payment.payment_status_code == search_filter.get('status')) if search_filter.get('folioNumber', None): query = query.filter(Invoice.folio_number == search_filter.get('folioNumber')) if search_filter.get('businessIdentifier', None): query = query.filter(Invoice.business_identifier == search_filter.get('businessIdentifier')) if search_filter.get('createdBy', None): # pylint: disable=no-member query = query.filter( Payment.created_name.like('%' + search_filter.get('createdBy') + '%')) # pylint: disable=no-member # Find start and end dates created_from: datetime = None created_to: datetime = None if get_str_by_path(search_filter, 'dateFilter/startDate'): created_from = datetime.strptime(get_str_by_path(search_filter, 'dateFilter/startDate'), '%m/%d/%Y') if get_str_by_path(search_filter, 'dateFilter/endDate'): created_to = datetime.strptime(get_str_by_path(search_filter, 'dateFilter/endDate'), '%m/%d/%Y') if get_str_by_path(search_filter, 'weekFilter/index'): created_from, created_to = get_week_start_and_end_date( int(get_str_by_path(search_filter, 'weekFilter/index'))) if get_str_by_path(search_filter, 'monthFilter/month') and get_str_by_path(search_filter, 'monthFilter/year'): month = int(get_str_by_path(search_filter, 'monthFilter/month')) year = int(get_str_by_path(search_filter, 'monthFilter/year')) created_from, created_to = get_first_and_last_dates_of_month(month=month, year=year) if created_from and created_to: # Truncate time for from date and add max time for to date tz_name = current_app.config['LEGISLATIVE_TIMEZONE'] tz_local = pytz.timezone(tz_name) created_from = created_from.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(tz_local) created_to = created_to.replace(hour=23, minute=59, second=59, microsecond=999999).astimezone(tz_local) query = query.filter( func.timezone(tz_name, func.timezone('UTC', Payment.created_on)).between(created_from, created_to)) # Add ordering query = query.order_by(Payment.created_on.desc()) if not return_all: # Add pagination pagination = query.paginate(per_page=limit, page=page) result, count = pagination.items, pagination.total # If maximum number of records is provided, return it as total if max_no_records > 0: count = max_no_records if max_no_records < count else count else: # If maximum number of records is provided, set the page with that number if max_no_records > 0: pagination = query.paginate(per_page=max_no_records, page=1) result, count = pagination.items, max_no_records else: result = query.all() count = len(result) return result, count
def find_account(cls, authorization: Dict[str, Any]) -> PaymentAccount: """Find payment account by corp number, corp type and payment system code.""" current_app.logger.debug('<find_payment_account') auth_account_id: str = get_str_by_path(authorization, 'account/id') return PaymentAccount.find_by_auth_account_id(auth_account_id)
def search_purchase_history( cls, # pylint:disable=too-many-arguments, too-many-locals, too-many-branches auth_account_id: str, search_filter: Dict, page: int, limit: int, return_all: bool, max_no_records: int = 0, **kwargs): """Search for purchase history.""" user: UserContext = kwargs['user'] product_code = user.product_code query = db.session.query(Invoice) \ .outerjoin(PaymentAccount, Invoice.payment_account_id == PaymentAccount.id) \ .filter(PaymentAccount.auth_account_id == auth_account_id) # If a product code is present in token (service account), then filter only that product's invoices. if product_code: query = query.join(CorpType, CorpType.code == Invoice.corp_type_code)\ .filter(CorpType.product == product_code) if search_filter.get('status', None): query = query.filter( Invoice.invoice_status_code == search_filter.get('status')) if search_filter.get('folioNumber', None): query = query.filter( Invoice.folio_number == search_filter.get('folioNumber')) if search_filter.get('businessIdentifier', None): query = query.filter(Invoice.business_identifier == search_filter.get('businessIdentifier')) if search_filter.get('createdBy', None): # pylint: disable=no-member query = query.filter( Invoice.created_name.ilike('%' + search_filter.get('createdBy') + '%')) # pylint: disable=no-member # Find start and end dates created_from: datetime = None created_to: datetime = None if get_str_by_path(search_filter, 'dateFilter/startDate'): created_from = datetime.strptime( get_str_by_path(search_filter, 'dateFilter/startDate'), DT_SHORT_FORMAT) if get_str_by_path(search_filter, 'dateFilter/endDate'): created_to = datetime.strptime( get_str_by_path(search_filter, 'dateFilter/endDate'), DT_SHORT_FORMAT) if get_str_by_path(search_filter, 'weekFilter/index'): created_from, created_to = get_week_start_and_end_date( int(get_str_by_path(search_filter, 'weekFilter/index'))) if get_str_by_path(search_filter, 'monthFilter/month') and get_str_by_path( search_filter, 'monthFilter/year'): month = int(get_str_by_path(search_filter, 'monthFilter/month')) year = int(get_str_by_path(search_filter, 'monthFilter/year')) created_from, created_to = get_first_and_last_dates_of_month( month=month, year=year) if created_from and created_to: # Truncate time for from date and add max time for to date tz_name = current_app.config['LEGISLATIVE_TIMEZONE'] tz_local = pytz.timezone(tz_name) created_from = created_from.replace( hour=0, minute=0, second=0, microsecond=0).astimezone(tz_local) created_to = created_to.replace( hour=23, minute=59, second=59, microsecond=999999).astimezone(tz_local) query = query.filter( func.timezone(tz_name, func.timezone('UTC', Invoice.created_on)).between( created_from, created_to)) # Add ordering query = query.order_by(Invoice.created_on.desc()) if not return_all: # Add pagination pagination = query.paginate(per_page=limit, page=page) result, count = pagination.items, pagination.total # If maximum number of records is provided, return it as total if max_no_records > 0: count = max_no_records if max_no_records < count else count else: # If maximum number of records is provided, set the page with that number if max_no_records > 0: pagination = query.paginate(per_page=max_no_records, page=1) result, count = pagination.items, max_no_records else: result = query.all() count = len(result) return result, count
def _save_account(cls, account_request: Dict[str, any], payment_account: PaymentAccountModel): """Update and save payment account and CFS account model.""" # pylint:disable=cyclic-import, import-outside-toplevel from pay_api.factory.payment_system_factory import PaymentSystemFactory # If the payment method is CC, set the payment_method as DIRECT_PAY payment_method: str = get_str_by_path(account_request, 'paymentInfo/methodOfPayment') if not payment_method or payment_method == PaymentMethod.CC.value: payment_method = PaymentMethod.DIRECT_PAY.value payment_account.payment_method = payment_method payment_account.auth_account_id = account_request.get('accountId') payment_account.auth_account_name = account_request.get( 'accountName', None) payment_account.bcol_account = account_request.get( 'bcolAccountNumber', None) payment_account.bcol_user_id = account_request.get('bcolUserId', None) payment_account.pad_tos_accepted_by = account_request.get( 'padTosAcceptedBy', None) payment_account.pad_tos_accepted_date = datetime.now() payment_info = account_request.get('paymentInfo') billable = payment_info.get('billable', True) payment_account.billable = billable # Steps to decide on creating CFS Account or updating CFS bank account. # Updating CFS account apart from bank details not in scope now. # Create CFS Account IF: # 1. New payment account # 2. Existing payment account: # - If the account was on DIRECT_PAY and switching to Online Banking, and active CFS account is not present. # - If the account was on DRAWDOWN and switching to PAD, and active CFS account is not present cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) \ if payment_account.id else None pay_system = PaymentSystemFactory.create_from_payment_method( payment_method=payment_method) if pay_system.get_payment_system_code() == PaymentSystem.PAYBC.value: if cfs_account is None: cfs_account = pay_system.create_account( name=payment_account.auth_account_name, contact_info=account_request.get('contactInfo'), payment_info=account_request.get('paymentInfo')) if cfs_account: cfs_account.payment_account = payment_account cfs_account.flush() # If the account is PAD and bank details changed, then update bank details else: # Update details in CFS pay_system.update_account( name=payment_account.auth_account_name, cfs_account=cfs_account, payment_info=payment_info) elif cfs_account is not None: # if its not PAYBC ,it means switching to either drawdown or internal ,deactivate the cfs account cfs_account.status = CfsAccountStatus.INACTIVE.value cfs_account.flush() is_pad = payment_method == PaymentMethod.PAD.value if is_pad: # override payment method for since pad has 3 days wait period effective_pay_method, activation_date = PaymentAccount._get_payment_based_on_pad_activation( payment_account) payment_account.pad_activation_date = activation_date payment_account.payment_method = effective_pay_method payment_account.save()
def _save_account(cls, account_request: Dict[str, any], payment_account: PaymentAccountModel): """Update and save payment account and CFS account model.""" # pylint:disable=cyclic-import, import-outside-toplevel from pay_api.factory.payment_system_factory import PaymentSystemFactory # If the payment method is CC, set the payment_method as DIRECT_PAY payment_method: str = get_str_by_path(account_request, 'paymentInfo/methodOfPayment') if not payment_method or payment_method == PaymentMethod.CC.value: payment_method = PaymentMethod.DIRECT_PAY.value payment_account.payment_method = payment_method payment_account.auth_account_id = account_request.get('accountId') payment_account.auth_account_name = account_request.get( 'accountName', None) payment_account.bcol_account = account_request.get( 'bcolAccountNumber', None) payment_account.bcol_user_id = account_request.get('bcolUserId', None) payment_account.pad_tos_accepted_by = account_request.get( 'padTosAcceptedBy', None) if payment_account.pad_tos_accepted_by is not None: payment_account.pad_tos_accepted_date = datetime.now() payment_info = account_request.get('paymentInfo') billable = payment_info.get('billable', True) payment_account.billable = billable payment_account.flush() # Steps to decide on creating CFS Account or updating CFS bank account. # Updating CFS account apart from bank details not in scope now. # Create CFS Account IF: # 1. New payment account # 2. Existing payment account: # - If the account was on DIRECT_PAY and switching to Online Banking, and active CFS account is not present. # - If the account was on DRAWDOWN and switching to PAD, and active CFS account is not present cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) \ if payment_account.id else None pay_system = PaymentSystemFactory.create_from_payment_method( payment_method=payment_method) if pay_system.get_payment_system_code() == PaymentSystem.PAYBC.value: if cfs_account is None: cfs_account = pay_system.create_account( name=payment_account.auth_account_name, contact_info=account_request.get('contactInfo'), payment_info=account_request.get('paymentInfo')) if cfs_account: cfs_account.payment_account = payment_account cfs_account.flush() # If the account is PAD and bank details changed, then update bank details else: # Update details in CFS pay_system.update_account( name=payment_account.auth_account_name, cfs_account=cfs_account, payment_info=payment_info) is_pad = payment_method == PaymentMethod.PAD.value if is_pad: # override payment method for since pad has 3 days wait period effective_pay_method, activation_date = PaymentAccount._get_payment_based_on_pad_activation( payment_account) payment_account.pad_activation_date = activation_date payment_account.payment_method = effective_pay_method elif pay_system.get_payment_system_code() == PaymentSystem.CGI.value: # if distribution code exists, put an end date as previous day and create new. dist_code_svc: DistributionCode = DistributionCode.find_active_by_account_id( payment_account.id) if dist_code_svc and dist_code_svc.distribution_code_id: end_date: datetime = datetime.now() - timedelta(days=1) dist_code_svc.end_date = end_date.date() dist_code_svc.save() # Create distribution code details. if revenue_account := payment_info.get('revenueAccount'): revenue_account.update( dict( accountId=payment_account.id, name=payment_account.auth_account_name, )) DistributionCode.save_or_update(revenue_account)
def create_payment(cls, payment_request: Tuple[Dict[str, Any]], authorization: Tuple[Dict[str, Any]]): # pylint: disable=too-many-locals, too-many-statements """Create payment related records. Does the following; 1. Calculate the fees based on the filing types received. 2. Check if the payment account exists, 2.1 If yes, use the one from database. 2.2 Else create one in payment system and update database. 3. Create payment record in database and flush. 4. Create invoice record in database and flush. 5. Create payment line items in database and flush. 6. Create invoice in payment system; 6.1 If successful update the invoice table with references from payment system. 6.1.1 If failed adjust the invoice to zero and roll back the transaction. 6.2 If fails rollback the transaction """ current_app.logger.debug('<create_payment') business_info = payment_request.get('businessInfo') contact_info = business_info.get('contactInfo') filing_info = payment_request.get('filingInfo') account_info = payment_request.get('accountInfo', None) filing_id = filing_info.get('filingIdentifier', None) folio_number = filing_info.get( 'folioNumber', get_str_by_path(authorization, 'business/folioNumber')) corp_type = business_info.get('corpType', None) payment_method = _get_payment_method(payment_request, authorization) # Calculate the fees current_app.logger.debug('Calculate the fees') fees = _calculate_fees(corp_type, filing_info) # Create payment system instance from factory current_app.logger.debug('Creating PaymentSystemService impl') pay_service: PaymentSystemService = PaymentSystemFactory.create( payment_method=payment_method, corp_type=corp_type, fees=sum(fee.total for fee in fees), account_info=account_info) # Create payment account payment_account = _create_account(pay_service, business_info, contact_info, account_info, authorization) payment: Payment = None pay_system_invoice: Dict[str, any] = None try: payment: Payment = Payment.create( payment_method, pay_service.get_payment_system_code()) current_app.logger.debug( 'Creating Invoice record for payment {}'.format(payment.id)) invoice = Invoice.create( payment_account, payment.id, fees, corp_type, routing_slip=get_str_by_path(account_info, 'routingSlip'), dat_number=get_str_by_path(account_info, 'datNumber'), filing_id=filing_id, folio_number=folio_number, business_identifier=business_info.get('businessIdentifier')) line_items = [] for fee in fees: current_app.logger.debug('Creating line items') line_items.append(PaymentLineItem.create(invoice.id, fee)) current_app.logger.debug( 'Handing off to payment system to create invoice') pay_system_invoice = pay_service.create_invoice( payment_account, line_items, invoice, corp_type_code=invoice.corp_type_code) current_app.logger.debug('Updating invoice record') invoice = Invoice.find_by_id(invoice.id, skip_auth_check=True) invoice.invoice_status_code = InvoiceStatus.CREATED.value invoice.save() InvoiceReference.create( invoice.id, pay_system_invoice.get('invoice_number', None), pay_system_invoice.get('reference_number', None)) payment.commit() _complete_post_payment(pay_service, payment) payment = Payment.find_by_id(payment.id, skip_auth_check=True) except Exception as e: current_app.logger.error('Rolling back as error occured!') current_app.logger.error(e) if payment: payment.rollback() if pay_system_invoice: pay_service.cancel_invoice( payment_account, pay_system_invoice.get('invoice_number'), ) raise current_app.logger.debug('>create_payment') return payment.asdict()
def update_invoice(cls, invoice_id: int, payment_request: Tuple[Dict[str, Any]], is_apply_credit: bool = False): """Update invoice related records.""" current_app.logger.debug('<update_invoice') invoice: Invoice = Invoice.find_by_id(invoice_id, skip_auth_check=False) # If the call is to apply credit, apply credit and release records. if is_apply_credit: credit_balance: float = 0 payment_account: PaymentAccount = PaymentAccount.find_by_id( invoice.payment_account_id) invoice_balance = invoice.total - (invoice.paid or 0) if (payment_account.credit or 0) >= invoice_balance: pay_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( invoice.payment_method_code) # Only release records, as the actual status change should happen during reconciliation. pay_service.apply_credit(invoice) credit_balance = payment_account.credit - invoice_balance invoice.paid = invoice.total invoice.save() elif (payment_account.credit or 0) <= invoice_balance: invoice.paid = (invoice.paid or 0) + payment_account.credit invoice.save() payment_account.credit = credit_balance payment_account.save() else: payment_method = get_str_by_path(payment_request, 'paymentInfo/methodOfPayment') is_not_currently_on_ob = invoice.payment_method_code != PaymentMethod.ONLINE_BANKING.value is_not_changing_to_cc = payment_method not in ( PaymentMethod.CC.value, PaymentMethod.DIRECT_PAY.value) # can patch only if the current payment method is OB if is_not_currently_on_ob or is_not_changing_to_cc: raise BusinessException(Error.INVALID_REQUEST) # check if it has any invoice references already created # if there is any invoice ref , send them to the invoiced credit card flow invoice_reference = InvoiceReference.find_active_reference_by_invoice_id( invoice.id) if invoice_reference: invoice.payment_method_code = PaymentMethod.CC.value else: pay_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method( PaymentMethod.DIRECT_PAY.value) payment_account = PaymentAccount.find_by_id( invoice.payment_account_id) pay_service.create_invoice( payment_account, invoice.payment_line_items, invoice, corp_type_code=invoice.corp_type_code) invoice.payment_method_code = PaymentMethod.DIRECT_PAY.value invoice.save() current_app.logger.debug('>update_invoice') return invoice.asdict()
def create_invoice(cls, payment_request: Tuple[Dict[str, Any]], authorization: Tuple[Dict[str, Any]]) -> Dict: # pylint: disable=too-many-locals, too-many-statements """Create payment related records. Does the following; 1. Calculate the fees based on the filing types received. 2. Check if the payment account exists, 2.1 If yes, use the one from database. 2.2 Else create one in payment system and update database. 3. Create payment record in database and flush. 4. Create invoice record in database and flush. 5. Create payment line items in database and flush. 6. Create invoice in payment system; 6.1 If successful update the invoice table with references from payment system. 6.1.1 If failed adjust the invoice to zero and roll back the transaction. 6.2 If fails rollback the transaction """ current_app.logger.debug('<create_invoice', payment_request) business_info = payment_request.get('businessInfo') filing_info = payment_request.get('filingInfo') account_info = payment_request.get('accountInfo', None) filing_id = filing_info.get('filingIdentifier', None) folio_number = filing_info.get( 'folioNumber', get_str_by_path(authorization, 'business/folioNumber')) corp_type = business_info.get('corpType', None) payment_account = cls._find_payment_account(authorization) payment_method = _get_payment_method(payment_request, payment_account) bcol_account = cls._get_bcol_account(account_info, payment_account) # Calculate the fees current_app.logger.debug('Calculate the fees') fees = _calculate_fees(corp_type, filing_info) # Create payment system instance from factory current_app.logger.debug('Creating PaymentSystemService impl') pay_service: PaymentSystemService = PaymentSystemFactory.create( payment_method=payment_method, corp_type=corp_type, fees=sum(fee.total for fee in fees), account_info=account_info, payment_account=payment_account) pay_system_invoice: Dict[str, any] = None invoice: Invoice = None try: current_app.logger.debug('Creating Invoice record') invoice = Invoice() invoice.bcol_account = bcol_account invoice.payment_account_id = payment_account.id invoice.cfs_account_id = payment_account.cfs_account_id invoice.invoice_status_code = pay_service.get_default_invoice_status( ) # TODO Change based on decision, whether to apply service fees for each line or not. # For now add up the service fee on each fee schedule invoice.service_fees = sum(fee.service_fees for fee in fees) if fees else 0 invoice.total = sum(fee.total for fee in fees) if fees else 0 invoice.paid = 0 invoice.refund = 0 invoice.routing_slip = get_str_by_path(account_info, 'routingSlip') invoice.filing_id = filing_id invoice.dat_number = get_str_by_path(account_info, 'datNumber') invoice.folio_number = folio_number invoice.business_identifier = business_info.get( 'businessIdentifier') invoice.payment_method_code = pay_service.get_payment_method_code() invoice.corp_type_code = corp_type invoice = invoice.flush() line_items = [] for fee in fees: current_app.logger.debug('Creating line items') line_items.append(PaymentLineItem.create(invoice.id, fee)) current_app.logger.debug( 'Handing off to payment system to create invoice') invoice_reference = pay_service.create_invoice( payment_account, line_items, invoice, corp_type_code=invoice.corp_type_code) current_app.logger.debug('Updating invoice record') invoice.commit() pay_service.complete_post_invoice(invoice, invoice_reference) invoice = Invoice.find_by_id(invoice.id, skip_auth_check=True) except Exception as e: # NOQA pylint: disable=broad-except current_app.logger.error('Rolling back as error occured!') current_app.logger.error(e) if invoice: invoice.rollback() if pay_system_invoice: pay_service.cancel_invoice( payment_account, pay_system_invoice.get('invoice_number'), ) raise current_app.logger.debug('>create_invoice') return invoice.asdict(include_dynamic_fields=True)