示例#1
0
def test_invalid_pay_system(session):
    """Test invalid data."""
    from pay_api.exceptions import BusinessException

    with pytest.raises(BusinessException) as excinfo:
        PaymentSystemFactory.create(None, None)
    assert excinfo.value.status == Error.PAY003.status
    assert excinfo.value.message == Error.PAY003.message
    assert excinfo.value.code == Error.PAY003.name

    with pytest.raises(BusinessException) as excinfo:
        PaymentSystemFactory.create('XXX', 'XXX')
    assert excinfo.value.status == Error.PAY003.status
    assert excinfo.value.message == Error.PAY003.message
    assert excinfo.value.code == Error.PAY003.name
示例#2
0
    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}
示例#3
0
def test_bcol_factory_for_public(session, public_user_mock):
    """Test payment system creation for BCOL payment instances."""
    from pay_api.factory.payment_system_factory import PaymentSystemFactory  # noqa I001; errors out the test case

    instance = PaymentSystemFactory.create(payment_method='DRAWDOWN')
    assert isinstance(instance, BcolService)
    assert isinstance(instance, PaymentSystemService)
示例#4
0
def test_pad_factory_for_system_fails(session, system_user_mock):
    """Test payment system creation for PAD payment instances."""
    from pay_api.factory.payment_system_factory import PaymentSystemFactory  # noqa I001; errors out the test case
    from pay_api.exceptions import BusinessException

    pad_account = PaymentAccountService.create(get_unlinked_pad_account_payload())
    # Try a DRAWDOWN for system user

    with pytest.raises(BusinessException) as excinfo:
        PaymentSystemFactory.create(payment_method='PAD', payment_account=pad_account)
    assert excinfo.value.code == Error.ACCOUNT_IN_PAD_CONFIRMATION_PERIOD.name

    time_delay = current_app.config['PAD_CONFIRMATION_PERIOD_IN_DAYS']
    with freeze_time(datetime.today() + timedelta(days=time_delay + 1, minutes=1)):
        instance = PaymentSystemFactory.create(payment_method='PAD', payment_account=pad_account)
        assert isinstance(instance, PadService)
示例#5
0
def test_internal_staff_factory(session, staff_user_mock):
    """Test payment system creation for staff users."""
    from pay_api.factory.payment_system_factory import PaymentSystemFactory  # noqa I001; errors out the test case

    # Test for CC and CP with staff role
    instance = PaymentSystemFactory.create(payment_method='CC')
    assert isinstance(instance, InternalPayService)
    assert isinstance(instance, PaymentSystemService)
示例#6
0
def test_paybc_system_factory(session, public_user_mock):
    """Assert a paybc service is returned."""
    from pay_api.factory.payment_system_factory import PaymentSystemFactory  # noqa I001; errors out the test case

    # Test for CC and CP
    instance = PaymentSystemFactory.create(payment_method='CC', corp_type='CP')
    assert isinstance(instance, PaybcService)
    assert isinstance(instance, PaymentSystemService)

    # Test for CC and CP
    instance = PaymentSystemFactory.create(payment_method=PaymentMethod.DIRECT_PAY.value, corp_type='CP')
    assert isinstance(instance, DirectPayService)
    assert isinstance(instance, PaymentSystemService)

    # Test for CC and CP with zero fees
    instance = PaymentSystemFactory.create(fees=0, payment_method='CC', corp_type='CP')
    assert isinstance(instance, InternalPayService)
    assert isinstance(instance, PaymentSystemService)

    # Test for PAYBC Service
    instance = PaymentSystemFactory.create_from_payment_method(PaymentMethod.CC.value)
    assert isinstance(instance, PaybcService)
    assert isinstance(instance, PaymentSystemService)

    # Test for Direct Pay Service
    instance = PaymentSystemFactory.create_from_payment_method(PaymentMethod.DIRECT_PAY.value)
    assert isinstance(instance, DirectPayService)
    assert isinstance(instance, PaymentSystemService)

    # Test for Internal Service
    instance = PaymentSystemFactory.create_from_payment_method(PaymentMethod.INTERNAL.value)
    assert isinstance(instance, InternalPayService)
    assert isinstance(instance, PaymentSystemService)

    # Test for BCOL Service
    instance = PaymentSystemFactory.create_from_payment_method(PaymentMethod.DRAWDOWN.value)
    assert isinstance(instance, BcolService)
    assert isinstance(instance, PaymentSystemService)

    # Test for EFT Service
    instance = PaymentSystemFactory.create_from_payment_method(PaymentMethod.EFT.value)
    assert isinstance(instance, EftService)
    assert isinstance(instance, DepositService)
    assert isinstance(instance, PaymentSystemService)

    # Test for WIRE Service
    instance = PaymentSystemFactory.create_from_payment_method(PaymentMethod.WIRE.value)
    assert isinstance(instance, WireService)
    assert isinstance(instance, DepositService)
    assert isinstance(instance, PaymentSystemService)
    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 build_pay_system_url(payment: Payment, transaction_id: uuid):
        """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(
            payment_system=payment.payment_system_code)
        invoice = InvoiceModel.find_by_payment_id(payment.id)
        pay_web_transaction_url = current_app.config.get(
            'AUTH_WEB_PAY_TRANSACTION_URL')
        return_url = f'{pay_web_transaction_url}/returnpayment/{payment.id}/transaction/{transaction_id}'

        current_app.logger.debug('>build_pay_system_url')
        return pay_system_service.get_payment_system_url(
            Invoice.populate(invoice), return_url)
示例#9
0
    def delete_invoice(cls, invoice_id: int):  # pylint: disable=too-many-locals,too-many-statements
        """Delete invoice related records.

        Does the following;
        1. Check if payment is eligible to be deleted.
        2. Mark the payment and invoices records as deleted.
        3. Publish message to queue
        """
        # update transaction function will update the status from PayBC
        _update_active_transactions(invoice_id)

        invoice: Invoice = Invoice.find_by_id(invoice_id, skip_auth_check=True)
        current_app.logger.debug(
            f'<Delete Invoice {invoice_id}, {invoice.invoice_status_code}')

        # Create the payment system implementation
        pay_service: PaymentSystemService = PaymentSystemFactory.create_from_payment_method(
            invoice.payment_method_code)

        # set payment status as deleted
        payment = Payment.find_payment_for_invoice(invoice_id)
        _check_if_invoice_can_be_deleted(invoice, payment)

        if payment:
            payment.payment_status_code = PaymentStatus.DELETED.value
            payment.flush()

        # Cancel invoice
        invoice_reference = InvoiceReference.find_active_reference_by_invoice_id(
            invoice.id)
        payment_account = PaymentAccount.find_by_id(invoice.payment_account_id)

        if invoice_reference:
            pay_service.cancel_invoice(
                payment_account=payment_account,
                inv_number=invoice_reference.invoice_number)
        invoice.invoice_status_code = InvoiceStatus.DELETED.value

        for line in invoice.payment_line_items:
            line.line_item_status_code = LineItemStatus.CANCELLED.value

        if invoice_reference:
            invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value
            invoice_reference.flush()

        invoice.save()

        current_app.logger.debug('>delete_invoice')
示例#10
0
def _save_payment(
        payment_date,
        inv_number,
        invoice_amount,  # pylint: disable=too-many-arguments
        paid_amount,
        row,
        status,
        payment_method,
        receipt_number):
    # pylint: disable=import-outside-toplevel
    from pay_api.factory.payment_system_factory import PaymentSystemFactory

    payment_account = _get_payment_account(row)
    pay_service = PaymentSystemFactory.create_from_payment_method(
        payment_method)
    # If status is failed, which means NSF. We already have a COMPLETED payment record, find and update iit.
    payment: PaymentModel = None
    if status == PaymentStatus.FAILED.value:
        payment = _get_payment_by_inv_number_and_status(
            inv_number, PaymentStatus.COMPLETED.value)
        # Just to handle duplicate rows in settlement file,
        # pull out failed payment record if it exists and no COMPLETED payments are present.
        if not payment:
            payment = _get_payment_by_inv_number_and_status(
                inv_number, PaymentStatus.FAILED.value)
    elif status == PaymentStatus.COMPLETED.value:
        # if the payment status is COMPLETED, then make sure there are
        # no other COMPLETED payment for same invoice_number.If found, return. This is to avoid duplicate entries.
        payment = _get_payment_by_inv_number_and_status(
            inv_number, PaymentStatus.COMPLETED.value)
        if payment:
            return

    if not payment:
        payment = PaymentModel()
    payment.payment_method_code = pay_service.get_payment_method_code()
    payment.payment_status_code = status
    payment.payment_system_code = pay_service.get_payment_system_code()
    payment.invoice_number = inv_number
    payment.invoice_amount = invoice_amount
    payment.payment_account_id = payment_account.id
    payment.payment_date = payment_date
    payment.paid_amount = paid_amount
    payment.receipt_number = receipt_number
    db.session.add(payment)
示例#11
0
    def delete_payment(cls, payment_id: int):  # pylint: disable=too-many-locals,too-many-statements
        """Delete payment related records.

        Does the following;
        1. Check if payment is eligible to be deleted.
        2. Mark the payment and invoices records as deleted.
        3. Publish message to queue
        """
        current_app.logger.debug('<delete_payment')

        # update transaction function will update the status from PayBC
        _update_active_transactions(payment_id)

        payment: Payment = Payment.find_by_id(payment_id, skip_auth_check=True)
        _check_if_payment_is_completed(payment)

        # Create the payment system implementation
        pay_service: PaymentSystemService = PaymentSystemFactory.create_from_system_code(
            payment.payment_system_code)

        # Cancel all invoices
        for invoice in payment.invoices:
            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)
            pay_service.cancel_invoice(
                payment_account=payment_account,
                inv_number=invoice_reference.invoice_number)
            invoice.invoice_status_code = InvoiceStatus.DELETED.value
            for line in invoice.payment_line_items:
                line.line_item_status_code = LineItemStatus.CANCELLED.value
            invoice.save()
            invoice_reference.status_code = InvoiceReferenceStatus.CANCELLED.value
            invoice_reference.save()

        payment.payment_status_code = PaymentStatus.DELETED.value
        payment.save()

        current_app.logger.debug('>delete_payment')
示例#12
0
    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
示例#13
0
def test_invalid_pay_system(session, public_user_mock):
    """Test invalid data."""
    from pay_api.factory.payment_system_factory import PaymentSystemFactory  # noqa I001; errors out the test case

    from pay_api.exceptions import BusinessException

    with pytest.raises(BusinessException) as excinfo:
        PaymentSystemFactory.create(payment_method=None, corp_type=None)
    assert excinfo.value.code == Error.INVALID_CORP_OR_FILING_TYPE.name

    with pytest.raises(BusinessException) as excinfo:
        PaymentSystemFactory.create(payment_method='XXX', corp_type='XXX')
    assert excinfo.value.code == Error.INVALID_CORP_OR_FILING_TYPE.name

    with pytest.raises(BusinessException) as excinfo:
        PaymentSystemFactory.create_from_system_code('XXX', 'XXXX')
    assert excinfo.value.code == Error.INVALID_CORP_OR_FILING_TYPE.name
示例#14
0
    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()
示例#15
0
    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
示例#16
0
    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()
示例#17
0
    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()
示例#18
0
    def update_payment(cls, payment_id: int, payment_request: Tuple[Dict[str,
                                                                         Any]],
                       authorization: Tuple[Dict[str, Any]]):
        # pylint: disable=too-many-locals,too-many-statements
        """Update payment related records.

        Does the following;
        1. Calculate the fees based on the filing types received.
        2. Check if the payment account exists,
            3.1 If yes, use the one from database.
            3.2 Else create one in payment system and update database.
        3. Check PayBC invoice status
            1.1 If payment completed, do not update the payment,
            1.2 Else continue the process.
        4. Get invoice record in database.
        5. Invalidate old payment line items and create new payment line items in database and flush.
        6. Update 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
        7. Update payment record in database and flush.
        """
        current_app.logger.debug('<update_payment')
        business_info = payment_request.get('businessInfo')
        filing_info = payment_request.get('filingInfo')

        corp_type = business_info.get('corpType', None)
        payment_method = _get_payment_method(payment_request, authorization)

        current_app.logger.debug('Calculate the fees')
        # Calculate the fees
        fees = _calculate_fees(business_info.get('corpType'), filing_info)

        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))

        current_app.logger.debug('Check if payment account exists')

        payment: Payment = None

        try:
            # update transaction function will update the status from PayBC
            _update_active_transactions(payment_id)

            payment: Payment = Payment.find_by_id(payment_id,
                                                  skip_auth_check=True)
            _check_if_payment_is_completed(payment)

            current_app.logger.debug(
                'Updating Invoice record for payment {}'.format(payment.id))
            invoices = payment.invoices
            for invoice in invoices:
                if invoice.invoice_status_code == InvoiceStatus.CREATED.value:
                    payment_line_items = invoice.payment_line_items

                    # Invalidate active payment line items
                    for payment_line_item in payment_line_items:
                        if payment_line_item.line_item_status_code != LineItemStatus.CANCELLED.value:
                            payment_line_item.line_item_status_code = LineItemStatus.CANCELLED.value
                            payment_line_item.save()

                    # add new payment line item(s)
                    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 update invoice')

                    # Mark the current active invoice reference as CANCELLED
                    inv_number: str = None
                    for reference in invoice.references:
                        if reference.status_code == InvoiceReferenceStatus.ACTIVE.value:
                            inv_number = reference.invoice_number
                            reference.status_code = InvoiceReferenceStatus.CANCELLED.value
                            reference.flush()

                    # update invoice
                    payment_account: PaymentAccount = 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)

                    pay_system_invoice = pay_service.update_invoice(
                        payment_account,
                        line_items,
                        invoice.id,
                        inv_number,
                        len(invoice.references),
                        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.total = sum(fee.total for fee in fees)
                    invoice.save()

                    InvoiceReference.create(
                        invoice.id,
                        pay_system_invoice.get('invoice_number', None),
                        pay_system_invoice.get('reference_number', None))

            payment.save()
            payment.commit()
            _complete_post_payment(pay_service, payment)
            # return payment with updated contents
            payment = Payment.find_by_id(payment.id, skip_auth_check=True)
        except Exception as e:
            current_app.logger.error('Rolling back as error occurred!')
            current_app.logger.error(e)
            if payment:
                payment.rollback()
            raise

        current_app.logger.debug('>update_payment')

        return payment.asdict()
示例#19
0
    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)
示例#20
0
    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()
示例#21
0
        if 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

        if payment_method:
            pay_system = PaymentSystemFactory.create_from_payment_method(
                payment_method=payment_method)
            cls._handle_payment_details(account_request, is_sandbox,
                                        pay_system, payment_account,
                                        payment_info)
        payment_account.save()

    @classmethod
    def _handle_payment_details(cls, account_request, is_sandbox, pay_system,
                                payment_account, payment_info):
        # pylint: disable=too-many-arguments
        cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id) \
            if payment_account.id else None
        if pay_system.get_payment_system_code() == PaymentSystem.PAYBC.value:
            if cfs_account is None:
                cfs_account = pay_system.create_account(  # pylint:disable=assignment-from-none
                    identifier=payment_account.auth_account_id,
    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,  # 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
示例#24
0
def test_paybc_system_factory(session):
    """Assert a paybc service is returned."""
    instance = PaymentSystemFactory.create('CC', 'CP')
    assert isinstance(instance, PaybcService)
    assert isinstance(instance, PaymentSystemService)
示例#25
0
    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
示例#26
0
    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
示例#27
0
    def update_payment(cls, payment_id: int, payment_request: Tuple[Dict[str, Any]], current_user: str = None):
        # pylint: disable=too-many-locals,too-many-statements
        """Update payment related records.

        Does the following;
        1. Calculate the fees based on the filing types received.
        2. Check if the payment account exists,
            3.1 If yes, use the one from database.
            3.2 Else create one in payment system and update database.
        3. Check PayBC invoice status
            1.1 If payment completed, do not update the payment,
            1.2 Else continue the process.
        4. Get invoice record in database.
        5. Invalidate old payment line items and create new payment line items in database and flush.
        6. Update 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
        7. Update payment record in database and flush.
        """
        current_app.logger.debug('<update_payment')
        payment_info = payment_request.get('paymentInfo')
        business_info = payment_request.get('businessInfo')
        filing_info = payment_request.get('filingInfo')

        current_app.logger.debug('Creating PaymentSystemService impl')
        pay_service: PaymentSystemService = PaymentSystemFactory.create(
            payment_info.get('methodOfPayment', None), business_info.get('corpType', None)
        )

        current_app.logger.debug('Calculate the fees')
        # Calculate the fees
        fees = []
        for filing_type_info in filing_info.get('filingTypes'):
            current_app.logger.debug('Getting fees for {} '.format(filing_type_info.get('filingTypeCode')))
            fee: FeeSchedule = FeeSchedule.find_by_corp_type_and_filing_type(
                corp_type=business_info.get('corpType', None),
                filing_type_code=filing_type_info.get('filingTypeCode', None),
                valid_date=filing_info.get('date', None),
                jurisdiction=None,
                priority=filing_info.get('priority', None),
            )
            if filing_type_info.get('filingDescription'):
                fee.description = filing_type_info.get('filingDescription')

            fees.append(fee)

        current_app.logger.debug('Check if payment account exists')

        payment: Payment = None

        try:
            # get existing payment transaction
            transaction: PaymentTransaction = PaymentTransaction.find_active_by_payment_id(payment_id)
            current_app.logger.debug(transaction)
            if transaction:
                # check existing payment status in PayBC;
                PaymentTransaction.update_transaction(payment_id, transaction.id, None)

            # update transaction function will update the status from PayBC
            payment: Payment = Payment.find_by_id(payment_id)
            current_app.logger.debug(payment)
            if payment.payment_status_code == Status.COMPLETED.value:
                raise BusinessException(Error.PAY010)

            if payment.payment_status_code == Status.CANCELLED.value:
                raise BusinessException(Error.PAY011)

            current_app.logger.debug('Updating Invoice record for payment {}'.format(payment.id))
            invoices = payment.invoices
            for invoice in invoices:
                if invoice.invoice_status_code in (Status.DRAFT.value, Status.CREATED.value, Status.PARTIAL.value):
                    payment_line_items = invoice.payment_line_items

                    # Invalidate active payment line items
                    for payment_line_item in payment_line_items:
                        if payment_line_item.line_item_status_code != Status.CANCELLED.value:
                            payment_line_item.line_item_status_code = Status.CANCELLED.value
                            payment_line_item.save()

                    # add new payment line item(s)
                    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 update invoice')

                    payment_account: PaymentAccount = PaymentAccount.find_by_id(invoice.account_id)

                    # update invoice
                    pay_service.update_invoice(
                        (payment_account.party_number, payment_account.account_number, payment_account.site_number),
                        invoice.invoice_number,
                    )
                    current_app.logger.debug('Updating invoice record')
                    invoice = Invoice.find_by_id(invoice.id)
                    invoice.updated_on = datetime.now()
                    invoice.updated_by = current_user
                    invoice.total = sum(fee.total for fee in fees)
                    invoice.save()

            payment.updated_on = datetime.now()
            payment.updated_by = current_user
            payment.save()
            payment.commit()

            # return payment with updated contents
            payment = Payment.find_by_id(payment.id)
        except Exception as e:
            current_app.logger.error('Rolling back as error occurred!')
            current_app.logger.error(e)
            if payment:
                payment.rollback()
            raise

        current_app.logger.debug('>update_payment')

        return payment.asdict()
示例#28
0
    def create_payment(cls, payment_request: Tuple[Dict[str, Any]], current_user: str = None):
        # 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')
        payment_info = payment_request.get('paymentInfo')
        business_info = payment_request.get('businessInfo')
        contact_info = business_info.get('contactInfo')
        filing_info = payment_request.get('filingInfo')

        current_app.logger.debug('Creating PaymentSystemService impl')
        pay_service: PaymentSystemService = PaymentSystemFactory.create(
            payment_info.get('methodOfPayment', None), business_info.get('corpType', None)
        )

        current_app.logger.debug('Calculate the fees')
        # Calculate the fees
        fees = []
        for filing_type_info in filing_info.get('filingTypes'):
            current_app.logger.debug('Getting fees for {} '.format(filing_type_info.get('filingTypeCode')))
            fee: FeeSchedule = FeeSchedule.find_by_corp_type_and_filing_type(
                corp_type=business_info.get('corpType', None),
                filing_type_code=filing_type_info.get('filingTypeCode', None),
                valid_date=filing_info.get('date', None),
                jurisdiction=None,
                priority=filing_info.get('priority', None),
            )
            if filing_type_info.get('filingDescription'):
                fee.description = filing_type_info.get('filingDescription')

            fees.append(fee)

        current_app.logger.debug('Check if payment account exists')
        payment_account: PaymentAccount = PaymentAccount.find_account(
            business_info.get('businessIdentifier', None),
            business_info.get('corpType', None),
            pay_service.get_payment_system_code(),
        )
        if not payment_account.id:
            current_app.logger.debug('No payment account, creating new')
            party_number, account_number, site_number = pay_service.create_account(
                business_info.get('businessName'), contact_info
            )
            payment_account = PaymentAccount.create(
                business_info, (account_number, party_number, site_number), pay_service.get_payment_system_code()
            )

        current_app.logger.debug('Creating payment record for account : {}'.format(payment_account.id))

        payment: Payment = None
        pay_system_invoice: Dict[str, any] = None

        try:
            payment: Payment = Payment.create(payment_info, current_user, pay_service.get_payment_system_code())
            current_app.logger.debug(payment)

            current_app.logger.debug('Creating Invoice record for payment {}'.format(payment.id))
            invoice = Invoice.create(payment_account, payment.id, fees, current_user)

            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.id)
            current_app.logger.debug('Updating invoice record')
            invoice = Invoice.find_by_id(invoice.id)
            invoice.invoice_status_code = Status.CREATED.value
            invoice.reference_number = pay_system_invoice.get('reference_number', None)
            invoice.invoice_number = pay_system_invoice.get('invoice_number', None)
            invoice.save()
            payment.commit()
            payment = Payment.find_by_id(payment.id)
        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.party_number, payment_account.account_number, payment_account.site_number),
                    pay_system_invoice.get('invoice_number'),
                )
            raise

        current_app.logger.debug('>create_payment')

        return payment.asdict()
示例#29
0
    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)