Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

22063 - Allow multiple payment methods, also create multiple CFS accounts to support EFT/PAD switching #1597

Merged
merged 47 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ff4ccdc
Fix update_account, so it doesn't set cfs_account to inactive
seeker25 Jul 2, 2024
804f81c
Various warning fixes, small unit test fix
seeker25 Jul 2, 2024
7145f1f
minor
seeker25 Jul 2, 2024
0f2bda8
Changes for create_invoice job
seeker25 Jul 2, 2024
f46be8b
Change logging from debug -> info
seeker25 Jul 2, 2024
b8ea822
Merge branch 'main' of https://github.com/bcgov/sbc-pay into 22063-part2
seeker25 Jul 5, 2024
f692d77
Changes for CFS_ACCOUNT.payment_method
seeker25 Jul 5, 2024
1f6bb72
Small changes for payment
seeker25 Jul 5, 2024
23094a2
More changes
seeker25 Jul 8, 2024
0a9c116
More changes
seeker25 Jul 8, 2024
d9d2478
lint fixes
seeker25 Jul 8, 2024
1f7b9ef
More spots to add in payment_method for CC plus migration
seeker25 Jul 8, 2024
09a1377
Change routing slip job to use internal for payment method not CASH o…
seeker25 Jul 8, 2024
5588809
Update refs, more lint fixes
seeker25 Jul 8, 2024
132b299
Use created instead of approved for EFT invoice creation
seeker25 Jul 8, 2024
7e960a2
Minor changes and tweaks
seeker25 Jul 8, 2024
cb9791a
small unit test fixes
seeker25 Jul 9, 2024
40a07be
Fix linting
seeker25 Jul 9, 2024
e8d7c56
Update pay-queue
seeker25 Jul 9, 2024
7552a46
more lint
seeker25 Jul 9, 2024
3bc07d6
point at new pay-api, fix lint
seeker25 Jul 9, 2024
e753057
Small tweak
seeker25 Jul 9, 2024
ef9b801
Update CfsAccount so they have payment method
seeker25 Jul 9, 2024
17dc18c
Adding in some comments and slight tweaks
seeker25 Jul 9, 2024
f55ebe1
more lint test fixes
seeker25 Jul 9, 2024
973ac26
I must be getting sleepy
seeker25 Jul 9, 2024
1f0bc30
Fixes
seeker25 Jul 9, 2024
4d9fab2
Use Ody's refactored function
seeker25 Jul 9, 2024
2f24cbe
More lint fixes
seeker25 Jul 9, 2024
9104c22
test fixes
seeker25 Jul 9, 2024
0f94bc0
test fixes
seeker25 Jul 9, 2024
9d94f65
Missing function
seeker25 Jul 9, 2024
ca072dc
Merge branch 'main' of https://github.com/bcgov/sbc-pay into 22063-part2
seeker25 Jul 10, 2024
2ff681d
Fix statement payment method
seeker25 Jul 11, 2024
200df83
linting fixes
seeker25 Jul 11, 2024
06d4d1c
comment touch up
seeker25 Jul 11, 2024
09e8b2b
put db.session.commit = flush back in
seeker25 Jul 11, 2024
55b7b7f
Fix unit tests
seeker25 Jul 11, 2024
9458d62
Lint fixes
seeker25 Jul 11, 2024
7e84628
Small change for EFT blocking invoice creation
seeker25 Jul 11, 2024
a487bce
small test fix
seeker25 Jul 11, 2024
c01c653
Unit test fix
seeker25 Jul 11, 2024
4a8b427
Migration to update cfs_account.payment_method
seeker25 Jul 12, 2024
1e2e201
Update new pay-api
seeker25 Jul 12, 2024
20ea950
Remove migration, put into job
seeker25 Jul 12, 2024
fef6cb5
more lint cleanup, setuptools add
seeker25 Jul 12, 2024
db2aee6
Remove versioning restoring, not needed.
seeker25 Jul 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
417 changes: 407 additions & 10 deletions jobs/payment-jobs/poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion jobs/payment-jobs/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
pay-api = {git = "https://github.com/seeker25/sbc-pay.git", branch = "disbursement_refund_date_fix", subdirectory = "pay-api"}
pay-api = {git = "https://github.com/seeker25/sbc-pay.git", branch = "22063-part2", subdirectory = "pay-api"}
gunicorn = "^21.2.0"
flask = "^3.0.2"
flask-sqlalchemy = "^3.1.1"
Expand All @@ -29,6 +29,7 @@ launchdarkly-server-sdk = "^8.2.1"
cx-oracle = "^8.3.0"
more-itertools = "^10.2.0"
pg8000 = "^1.30.5"
setuptools = "^70.3.0"


[tool.poetry.group.dev.dependencies]
Expand Down
6 changes: 3 additions & 3 deletions jobs/payment-jobs/services/routing_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from pay_api.models import PaymentAccount as PaymentAccountModel
from pay_api.models import RoutingSlip as RoutingSlipModel
from pay_api.services.cfs_service import CFSService
from pay_api.utils.enums import CfsAccountStatus
from pay_api.utils.enums import CfsAccountStatus, PaymentMethod, PaymentSystem
from sentry_sdk import capture_message


Expand All @@ -40,13 +40,13 @@ def create_cfs_account(cfs_account: CfsAccountModel, pay_account: PaymentAccount
cfs_account.cfs_party = cfs_account_details.get('party_number')
cfs_account.cfs_site = cfs_account_details.get('site_number')
cfs_account.status = CfsAccountStatus.ACTIVE.value
# Create receipt in CFS for the payment.
cfs_account.payment_method = PaymentMethod.INTERNAL.value
CFSService.create_cfs_receipt(cfs_account=cfs_account,
rcpt_number=routing_slip.number,
rcpt_date=routing_slip.routing_slip_date.strftime('%Y-%m-%d'),
amount=routing_slip.total,
payment_method=pay_account.payment_method,
access_token=CFSService.get_fas_token().json().get('access_token'))
access_token=CFSService.get_token(PaymentSystem.FAS).json().get('access_token'))
cfs_account.commit()
return

Expand Down
13 changes: 7 additions & 6 deletions jobs/payment-jobs/tasks/activate_pad_account_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
"""Task to activate accounts with pending activation.Mostly for PAD with 3 day activation period."""

from datetime import datetime, timedelta
from typing import List

from flask import current_app
from pay_api.models import CfsAccount as CfsAccountModel
from pay_api.models import PaymentAccount as PaymentAccountModel
from pay_api.services.flags import flags
from pay_api.utils.enums import CfsAccountStatus, PaymentMethod
from sbc_common_components.utils.enums import QueueMessageTypes

Expand All @@ -36,7 +36,7 @@ def activate_pad_accounts(cls):
1. Find all accounts with pending PAD account activation status.
2. Activate them.
"""
pending_pad_activation_accounts: List[CfsAccountModel] = CfsAccountModel.find_all_accounts_with_status(
pending_pad_activation_accounts = CfsAccountModel.find_all_accounts_with_status(
status=CfsAccountStatus.PENDING_PAD_ACTIVATION.value)
current_app.logger.info(
f'Found {len(pending_pad_activation_accounts)} CFS Accounts to be pending PAD activation.')
Expand All @@ -54,8 +54,9 @@ def activate_pad_accounts(cls):
if is_activation_period_over:
pending_account.status = CfsAccountStatus.ACTIVE.value
pending_account.save()
# If account was in BCOL , change it to PAD
if pay_account.payment_method != PaymentMethod.PAD.value:
pay_account.payment_method = PaymentMethod.PAD.value
pay_account.save()
if flags.is_on('multiple-payment-methods', default=False) is False:
# If account was in another payment method, update it to pad
if pay_account.payment_method != PaymentMethod.PAD.value:
pay_account.payment_method = PaymentMethod.PAD.value
pay_account.save()
mailer.publish_mailer_events(QueueMessageTypes.CONFIRMATION_PERIOD_OVER.value, pay_account)
2 changes: 1 addition & 1 deletion jobs/payment-jobs/tasks/cfs_bank_name_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def run_update(pay_account_id, num_records):
return

for payment_account in pad_accounts:
cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)
cfs_account = CfsAccountModel.find_effective_by_payment_method(payment_account.id, PaymentMethod.PAD.value)
current_app.logger.info(
f'<<<< Running Update for account id :{payment_account.id} and cfs_account:{cfs_account.id} >>>>')
# payment_details = get_bank_info(cfs_account.cfs_party, cfs_account.cfs_account, cfs_account.cfs_site)
Expand Down
12 changes: 7 additions & 5 deletions jobs/payment-jobs/tasks/cfs_create_account_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""Task to create CFS account offline."""
import re
from datetime import datetime
from typing import Dict, List
from typing import Dict

from flask import current_app
from pay_api.models import CfsAccount as CfsAccountModel
Expand Down Expand Up @@ -43,7 +43,7 @@ def create_accounts(cls): # pylint: disable=too-many-locals
3. Publish a message to the queue if successful.
"""
# Pass payment method if offline account creation has be restricted based on payment method.
pending_accounts: List[CfsAccountModel] = CfsAccountModel.find_all_pending_accounts()
pending_accounts = CfsAccountModel.find_all_pending_accounts()
current_app.logger.info(f'Found {len(pending_accounts)} CFS Accounts to be created.')
if len(pending_accounts) == 0:
return
Expand Down Expand Up @@ -108,6 +108,7 @@ def _create_cfs_account(cls, pending_account: CfsAccountModel, pay_account: Paym
site_number=pending_account.cfs_site,
payment_info=payment_info)
pending_account.payment_instrument_number = bank_details.get('payment_instrument_number', None)
pending_account.payment_method = PaymentMethod.PAD.value
else: # It's a new account, now create
# If the account have banking information, then create a PAD account else a regular account.
if pending_account.bank_number and pending_account.bank_branch_number \
Expand All @@ -116,6 +117,7 @@ def _create_cfs_account(cls, pending_account: CfsAccountModel, pay_account: Paym
contact_info=contact_info,
payment_info=payment_info,
receipt_method=RECEIPT_METHOD_PAD_DAILY)
pending_account.payment_method = PaymentMethod.PAD.value
else:
cfs_account_details = CFSService.create_cfs_account(identifier=pay_account.auth_account_id,
contact_info=contact_info,
Expand All @@ -126,9 +128,10 @@ def _create_cfs_account(cls, pending_account: CfsAccountModel, pay_account: Paym
pending_account.cfs_account = cfs_account_details.get('account_number')
pending_account.cfs_site = cfs_account_details.get('site_number')
pending_account.cfs_party = cfs_account_details.get('party_number')
if not pending_account.payment_method:
pending_account.payment_method = pay_account.payment_method

except Exception as e: # NOQA # pylint: disable=broad-except
# publish to mailer queue.
is_user_error = False
if pay_account.payment_method == PaymentMethod.PAD.value:
is_user_error = CreateAccountTask._check_user_error(e.response) # pylint: disable=no-member
Expand All @@ -146,8 +149,7 @@ def _create_cfs_account(cls, pending_account: CfsAccountModel, pay_account: Paym
pending_account.save()
return

# If the account has an activation time set ,
# before that it shud be set to the PENDING_PAD_ACTIVATION status.
# If the account has an activation time set it should have PENDING_PAD_ACTIVATION status.
is_account_in_pad_confirmation_period = pay_account.pad_activation_date is not None and \
pay_account.pad_activation_date > datetime.today()
pending_account.status = CfsAccountStatus.PENDING_PAD_ACTIVATION.value if \
Expand Down
51 changes: 15 additions & 36 deletions jobs/payment-jobs/tasks/cfs_create_invoice_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def create_invoices(cls):
cls._create_online_banking_invoices()
current_app.logger.info('>> Done Online Banking Invoice Creation')

# Cancel invoice is the only non-creation of invoice in this job.
current_app.logger.info('<< Starting CANCEL Routing Slip Invoices')
cls._cancel_rs_invoices()
current_app.logger.info('>> Done CANCEL Routing Slip Invoices')
Expand All @@ -90,27 +89,24 @@ def _cancel_rs_invoices(cls):

current_app.logger.info(f'Found {len(invoices)} to be cancelled in CFS.')
for invoice in invoices:
# call unapply rcpts
# adjust invoice to zero
current_app.logger.debug(f'Calling the invoice {invoice.id}')
routing_slip = RoutingSlipModel.find_by_number(invoice.routing_slip)
routing_slip_payment_account: PaymentAccountModel = PaymentAccountModel.find_by_id(
routing_slip.payment_account_id)
cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(
routing_slip_payment_account.id)
cfs_account = CfsAccountModel.find_effective_by_payment_method(
routing_slip_payment_account.id, PaymentMethod.INTERNAL.value)
# Find COMPLETED invoice reference; as unapply has to be done only if invoice is created and applied in CFS.
invoice_reference = InvoiceReferenceModel. \
find_by_invoice_id_and_status(invoice.id, status_code=InvoiceReferenceStatus.COMPLETED.value)
if invoice_reference:
current_app.logger.debug(f'Found invoice reference - {invoice_reference.invoice_number}')
try:
# find receipts against the invoice and unapply
# apply receipt now
receipts: List[ReceiptModel] = ReceiptModel.find_all_receipts_for_invoice(invoice_id=invoice.id)
for receipt in receipts:
CFSService.unapply_receipt(cfs_account, receipt.receipt_number,
invoice_reference.invoice_number)

# Adjust to zero: -invoice.total + invoice.total = 0
adjustment_negative_amount = -invoice.total
CFSService.adjust_invoice(cfs_account=cfs_account,
inv_number=invoice_reference.invoice_number,
Expand Down Expand Up @@ -144,6 +140,7 @@ def _create_rs_invoices(cls): # pylint: disable=too-many-locals
.filter(InvoiceModel.payment_method_code == PaymentMethod.INTERNAL.value) \
.filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \
.filter(CfsAccountModel.status.in_([CfsAccountStatus.ACTIVE.value, CfsAccountStatus.FREEZE.value])) \
.filter(CfsAccountModel.payment_method == PaymentMethod.INTERNAL.value) \
.filter(InvoiceModel.routing_slip is not None) \
.order_by(InvoiceModel.created_on.asc()).all()

Expand All @@ -161,7 +158,8 @@ def _create_rs_invoices(cls): # pylint: disable=too-many-locals
routing_slip.payment_account_id)

# apply invoice to the active CFS_ACCOUNT which will be the parent routing slip
active_cfs_account = CfsAccountModel.find_effective_by_account_id(routing_slip_payment_account.id)
active_cfs_account = CfsAccountModel.find_effective_by_payment_method(routing_slip_payment_account.id,
PaymentMethod.INTERNAL.value)

try:
invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id,
Expand Down Expand Up @@ -230,50 +228,40 @@ def _create_rs_invoices(cls): # pylint: disable=too-many-locals
@classmethod
def _create_pad_invoices(cls): # pylint: disable=too-many-locals
"""Create PAD invoices in to CFS system."""
# Find all accounts which have done a transaction with PAD transactions

inv_subquery = db.session.query(InvoiceModel.payment_account_id) \
.filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \
.filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value).subquery()

# Exclude the accounts which are in FREEZE state.
pad_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \
.join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
.filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \
.filter(CfsAccountModel.payment_method == PaymentMethod.PAD.value) \
.filter(PaymentAccountModel.id.in_(select(inv_subquery))).all()

current_app.logger.info(f'Found {len(pad_accounts)} with PAD transactions.')

for account in pad_accounts:
# Find all PAD invoices for this account
account_invoices = db.session.query(InvoiceModel) \
.filter(InvoiceModel.payment_account_id == account.id) \
.filter(InvoiceModel.payment_method_code == PaymentMethod.PAD.value) \
.filter(InvoiceModel.invoice_status_code == InvoiceStatus.APPROVED.value) \
.filter(InvoiceModel.id.notin_(cls._active_invoice_reference_subquery())) \
.order_by(InvoiceModel.created_on.desc()).all()

# Get cfs account
payment_account: PaymentAccountService = PaymentAccountService.find_by_id(account.id)

if len(account_invoices) == 0:
continue
current_app.logger.debug(
f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}')

cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)
if cfs_account is None:
# Get the last cfs_account for it, as the account might have got upgraded from PAD to DRAWDOWN.
cfs_account: CfsAccountModel = CfsAccountModel.query.\
filter(CfsAccountModel.account_id == payment_account.id).order_by(CfsAccountModel.id.desc()).first()

# If the CFS Account status is not ACTIVE or INACTIVE (for above case), raise error and continue
cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id,
PaymentMethod.PAD.value)
if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value):
current_app.logger.info(f'CFS status for account {payment_account.auth_account_id} '
f'is {payment_account.cfs_account_status} skipping.')
continue

# Add all lines together
lines = []
invoice_total = Decimal('0')
for invoice in account_invoices:
Expand Down Expand Up @@ -346,6 +334,7 @@ def _return_eft_accounts(cls):
eft_accounts: List[PaymentAccountModel] = db.session.query(PaymentAccountModel) \
.join(CfsAccountModel, CfsAccountModel.account_id == PaymentAccountModel.id) \
.filter(CfsAccountModel.status != CfsAccountStatus.FREEZE.value) \
.filter(CfsAccountModel.payment_method == PaymentMethod.EFT.value) \
.filter(PaymentAccountModel.id.in_(select(invoice_subquery))).all()

current_app.logger.info(f'Found {len(eft_accounts)} with EFT transactions.')
Expand Down Expand Up @@ -396,20 +385,13 @@ def _create_eft_invoices(cls):
current_app.logger.debug(
f'Found {len(account_invoices)} invoices for account {payment_account.auth_account_id}')

cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)

# If no CFS account then the payment method might have changed from EFT to DRAWDOWN
if not cfs_account:
cfs_account: CfsAccountModel = CfsAccountModel.query.\
filter(CfsAccountModel.account_id == payment_account.id).order_by(CfsAccountModel.id.desc()).first()

# If CFS account is not ACTIVE or INACTIVE (for above case), raise error and continue
cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id,
PaymentMethod.EFT.value)
if cfs_account.status not in (CfsAccountStatus.ACTIVE.value, CfsAccountStatus.INACTIVE.value):
current_app.logger.info(f'CFS status for account {payment_account.auth_account_id} '
f'is {payment_account.cfs_account_status} skipping.')
continue

# Add all payment line items together
lines = []
invoice_total = Decimal('0')
for invoice in account_invoices:
Expand Down Expand Up @@ -477,17 +459,16 @@ def _create_single_invoice_per_purchase(cls, payment_method: PaymentMethod):

current_app.logger.info(f'Found {len(invoices)} to be created in CFS.')
for invoice in invoices:
# Get cfs account
payment_account: PaymentAccountService = PaymentAccountService.find_by_id(invoice.payment_account_id)
cfs_account: CfsAccountModel = CfsAccountModel.find_effective_by_account_id(payment_account.id)
# Adding this in for the future when we can switch between BCOL and ONLINE_BANKING.
cfs_account = CfsAccountModel.find_effective_or_latest_by_payment_method(payment_account.id,
PaymentMethod.ONLINE_BANKING.value)

# Check for corp type and see if online banking is allowed.
if invoice.payment_method_code == PaymentMethod.ONLINE_BANKING.value:
corp_type: CorpTypeModel = CorpTypeModel.find_by_code(invoice.corp_type_code)
if not corp_type.is_online_banking_allowed:
continue

# Create a CFS invoice
current_app.logger.debug(f'Creating cfs invoice for invoice {invoice.id}')
try:
invoice_response = CFSService.create_account_invoice(transaction_number=invoice.id,
Expand All @@ -505,8 +486,6 @@ def _create_single_invoice_per_purchase(cls, payment_method: PaymentMethod):
invoice_number=invoice_response.get('invoice_number'),
reference_number=invoice_response.get('pbc_ref_number', None))

# Misc
invoice.cfs_account_id = payment_account.cfs_account_id
# leave the status as SETTLEMENT_SCHEDULED
invoice.invoice_status_code = InvoiceStatus.SETTLEMENT_SCHEDULED.value
invoice.save()
Loading
Loading