Skip to content

Commit

Permalink
18012 - Minimum changes for new PayBC partial refunds. (#1308)
Browse files Browse the repository at this point in the history
* Minimum changes for new PayBC partial refunds.

* Include required fields
  • Loading branch information
seeker25 authored Oct 26, 2023
1 parent d4283fb commit 44ede23
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 69 deletions.
2 changes: 2 additions & 0 deletions docs/docs/PayBC Mocking/paybc-1.0.0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,13 @@
revenueamount: '20'
glstatus: PAID
glerrormessage: null
refundglstatus: PAID
- linenumber: '2'
revenueaccount: 112.32562.20245.4378.3212319.000000.0000
revenueamount: '30'
glstatus: PAID
glerrormessage: null
refundglstatus: PAID
'400':
description: BadRequest
content:
Expand Down
3 changes: 1 addition & 2 deletions jobs/payment-jobs/tasks/common/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from pay_api.models import Invoice as InvoiceModel
from pay_api.models import PaymentLineItem as LineItemModel
from pay_api.utils.enums import InvoiceStatus
from tasks.common.enums import PaymentDetailsGlStatus, PaymentDetailsStatus
from tasks.common.enums import PaymentDetailsGlStatus


@dataclass
Expand All @@ -33,7 +33,6 @@ class RevenueLine(JSONWizard):
class OrderStatus(JSONWizard): # pylint:disable=too-many-instance-attributes
"""Return from order status query."""

refundstatus: Optional[PaymentDetailsStatus]
revenue: List[RevenueLine]


Expand Down
8 changes: 0 additions & 8 deletions jobs/payment-jobs/tasks/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@
from enum import Enum


class PaymentDetailsStatus(Enum):
"""Payment details status."""

REFUND_INPRG = 'REFUND_INPRG'
PAID = 'PAID'
CMPLT = 'CMPLT'


class PaymentDetailsGlStatus(Enum):
"""Payment details GL status."""

Expand Down
19 changes: 9 additions & 10 deletions jobs/payment-jobs/tasks/direct_pay_automated_refund_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from sentry_sdk import capture_message

from tasks.common.dataclasses import OrderStatus
from tasks.common.enums import PaymentDetailsGlStatus, PaymentDetailsStatus
from tasks.common.enums import PaymentDetailsGlStatus


class DirectPayAutomatedRefundTask: # pylint:disable=too-few-public-methods
Expand All @@ -54,12 +54,12 @@ def handle_non_complete_credit_card_refunds(cls):
excluding invoices with refunds that have gl_posted or gl_error.
Initial state for refunds is REFUND INPRG.
2. Get order status for CFS (refundstatus, revenue.refundglstatus)
2.1. Check for refundStatus = PAID and invoice = REFUND_REQUESTED:
2.1. Check for all revenue.refundGLstatus = PAID and invoice = REFUND_REQUESTED:
Set invoice and payment = REFUNDED
2.2. Check for refundStatus = CMPLT or None (None is for refunds done manually)
2.2. Check for all revenue.refundGLstatus = CMPLT
Set invoice and payment = REFUNDED (incase we skipped the condition above).
Set refund.gl_posted = now()
2.3. Check for refundGLstatus = RJCT
2.3. Check for any revenue.refundGLstatus = RJCT
Log the error down, contact PAYBC if this happens.
Set refund.gl_error = <error message>
"""
Expand All @@ -82,9 +82,6 @@ def handle_non_complete_credit_card_refunds(cls):
elif cls._is_status_paid_and_invoice_refund_requested(status, invoice):
cls._refund_paid(invoice)
elif cls._is_status_complete(status):
if status.refundstatus is None:
current_app.logger.info(
'Refund status was blank, setting to complete - this was an existing manual refund.')
cls._refund_complete(invoice)
else:
current_app.logger.info('No action taken for invoice.')
Expand Down Expand Up @@ -144,13 +141,15 @@ def _is_glstatus_rejected(status: OrderStatus) -> bool:
@staticmethod
def _is_status_paid_and_invoice_refund_requested(status: OrderStatus, invoice: Invoice) -> bool:
"""Check for successful refund and invoice status = REFUND_REQUESTED."""
return status.refundstatus == PaymentDetailsStatus.PAID \
return all(line.refundglstatus == PaymentDetailsGlStatus.PAID
for line in status.revenue) \
and invoice.invoice_status_code == InvoiceStatus.REFUND_REQUESTED.value

@staticmethod
def _is_status_complete(status: OrderStatus) -> bool:
"""Check for successful refund, or if the refund was done manually."""
return status.refundstatus == PaymentDetailsStatus.CMPLT or status.refundstatus is None
"""Check for successful refund."""
return all(line.refundglstatus == PaymentDetailsGlStatus.CMPLT
for line in status.revenue)

@staticmethod
def _set_invoice_and_payment_to_refunded(invoice: Invoice):
Expand Down
5 changes: 3 additions & 2 deletions jobs/payment-jobs/tasks/distribution_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def update_failed_distributions(cls): # pylint:disable=too-many-locals
continue

target_status, target_gl_status = cls.get_status_fields(gl_update_invoice.invoice_status_code)
if payment_details.get(target_status) == STATUS_PAID:
if target_status is None or payment_details.get(target_status) == STATUS_PAID:
has_gl_completed: bool = True
for revenue in payment_details.get('revenue'):
if revenue.get(target_gl_status) in STATUS_NOT_PROCESSED:
Expand All @@ -88,7 +88,8 @@ def update_failed_distributions(cls): # pylint:disable=too-many-locals
def get_status_fields(cls, invoice_status_code: str) -> tuple:
"""Get status fields for invoice status code."""
if invoice_status_code == InvoiceStatus.UPDATE_REVENUE_ACCOUNT_REFUND.value:
return 'refundstatus', 'refundglstatus'
# Refund doesn't use a top level status, as partial refunds may occur.
return None, 'refundglstatus'
return 'paymentstatus', 'glstatus'

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ def test_successful_paid_refund(session, monkeypatch):

def payment_status(cls): # pylint: disable=unused-argument; mocks of library methods
return {
'refundstatus': 'PAID',
'revenue': []
'revenue': [
{
'refundglstatus': 'PAID',
'refundglerrormessage': ''
}
]
}
target = 'tasks.direct_pay_automated_refund_task.DirectPayAutomatedRefundTask._query_order_status'
monkeypatch.setattr(target, payment_status)
Expand All @@ -63,8 +67,12 @@ def test_successful_completed_refund(session, monkeypatch):

def payment_status(cls): # pylint: disable=unused-argument; mocks of library methods
return {
'refundstatus': 'CMPLT',
'revenue': []
'revenue': [
{
'refundglstatus': 'CMPLT',
'refundglerrormessage': ''
}
]
}
target = 'tasks.direct_pay_automated_refund_task.DirectPayAutomatedRefundTask._query_order_status'
monkeypatch.setattr(target, payment_status)
Expand All @@ -86,7 +94,6 @@ def test_bad_cfs_refund(session, monkeypatch):

def payment_status(cls): # pylint: disable=unused-argument; mocks of library methods
return {
'refundstatus': 'PAID',
'revenue': [
{
'linenumber': '1',
Expand Down Expand Up @@ -115,45 +122,3 @@ def payment_status(cls): # pylint: disable=unused-argument; mocks of library me
DirectPayAutomatedRefundTask().process_cc_refunds()
assert refund.gl_error == 'BAD BAD'
assert refund.gl_posted is None


def test_manual_refund(session, monkeypatch):
"""Assert manual refunds get set to REFUNDED."""
invoice = factory_invoice(factory_create_direct_pay_account(), status_code=InvoiceStatus.REFUNDED.value)
factory_invoice_reference(invoice.id, invoice.id, InvoiceReferenceStatus.COMPLETED.value).save()
payment = factory_payment('PAYBC', invoice_number=invoice.id)
refund = factory_refund_invoice(invoice.id)

def payment_status(cls): # pylint: disable=unused-argument; mocks of library methods
return {
'refundstatus': None,
'revenue': [
{
'linenumber': '1',
'revenueaccount': '112.32041.35301.1278.3200000.000000.0000',
'revenueamount': '130',
'glstatus': 'PAID',
'glerrormessage': None,
'refundglstatus': None,
'refundglerrormessage': None
},
{
'linenumber': '2',
'revenueaccount': '112.32041.35301.1278.3200000.000000.0000',
'revenueamount': '1.5',
'glstatus': 'PAID',
'glerrormessage': None,
'refundglstatus': None,
'refundglerrormessage': None
}
]
}

target = 'tasks.direct_pay_automated_refund_task.DirectPayAutomatedRefundTask._query_order_status'
monkeypatch.setattr(target, payment_status)
with freeze_time(datetime.datetime.combine(datetime.datetime.utcnow().date(), datetime.time(6, 00))):
DirectPayAutomatedRefundTask().process_cc_refunds()
assert invoice.invoice_status_code == InvoiceStatus.REFUNDED.value
assert invoice.refund_date is not None
assert payment.payment_status_code == PaymentStatus.REFUNDED.value
assert refund.gl_posted is not None
5 changes: 5 additions & 0 deletions pay-api/src/pay_api/services/direct_pay_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ def _build_automated_refund_payload(invoice: InvoiceModel):
receipt = ReceiptModel.find_by_invoice_id_and_receipt_number(invoice_id=invoice.id)
invoice_reference = InvoiceReferenceModel.find_by_invoice_id_and_status(
invoice.id, InvoiceReferenceStatus.COMPLETED.value)
# Future: Partial refund support - This is backwards compatible
# refundRevenue: [{
# 'lineNumber': 1,
# 'refundAmount': 50.00,
# }]
return {
'orderNumber': int(receipt.receipt_number),
'pbcRefNumber': current_app.config.get('PAYBC_DIRECT_PAY_REF_NUMBER'),
Expand Down

0 comments on commit 44ede23

Please sign in to comment.