Skip to content

Conversation

@landonshumway-ia
Copy link
Collaborator

@landonshumway-ia landonshumway-ia commented Oct 16, 2025

We need to be notified immediately of any errors that may potentially occur in the history collection process, so we can address it as soon as possible. This ticket involves adding a custom metric on the transaction_processor_handler in the transaction monitoring stack with an alarm that will alert us if any log is emitted with a ERROR level. This is in conjunction with the hot fix #1150 which address the core issue we identified. This must be a follow up PR to avoid massive merge conflicts with changes we currently have in our CDK configuration. Once those are merged in, we can apply this necessary monitoring.

Closes #1151

Summary by CodeRabbit

  • New Features

    • Interactive environment selection for creating app clients.
    • Failure emails can include detailed error sections or raw messages with specific transaction IDs.
    • Unsettled-transaction tracking, reconciliation (including detection of >48‑hour unsettled items), and submitTimeUTC added to purchase responses.
    • Transaction history table wired into handlers, EventBridge bus name exposed, and error-level monitoring/alarms added.
  • Tests

    • Added tests for unsettled-transaction storage/reconciliation, 48‑hour detection, and enhanced failure-email content.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 16, 2025

Walkthrough

Adds unsettled-transaction schema and storage, records unsettled transactions at purchase time, reconciles unsettled vs settled transactions (including 48‑hour detection), surfaces unsettled IDs into batch-failure messages and emails, extends email API to accept batchFailureErrorMessage, adds submitTimeUTC to purchase responses, wires TransactionHistory table and monitoring, and moves app-client environment input into interactive flow.

Changes

Cohort / File(s) Summary
Unsettled transaction schema
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/transaction/record.py
New UnsettledTransactionRecordSchema registered as 'unsettled_transaction' with pre_dump hook to generate pk/sk from compact, transactionDate, and transactionId.
Transaction client: store & reconcile
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py
Added store_unsettled_transaction(...) to persist unsettled records and reconcile_unsettled_transactions(...) to query unsettled records, batch-delete matched ones, and return IDs older than 48 hours.
Purchase flow integration
backend/compact-connect/lambdas/python/purchases/purchase_client.py, backend/compact-connect/lambdas/python/purchases/handlers/privileges.py
process_charge_on_credit_card_for_privilege_purchase now yields submitTimeUTC; post_purchase_privileges calls transaction_client.store_unsettled_transaction after successful charge.
Transaction history processing
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py
Calls reconcile_unsettled_transactions during processing, collects old unsettled IDs, augments/creates batchFailureErrorMessage with unsettledTransactionIds and 48‑hour notice, and sets/logs BATCH_FAILURE when applicable.
Email notification changes
backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts, .../lib/email/email-notification-service.ts, .../tests/email-notification-service.test.ts
Extended sendTransactionBatchSettlementFailureEmail to accept batchFailureErrorMessage; email body attempts JSON parse to render detailed sections (message, failedTransactionIds, unsettledTransactionIds) or includes raw message; adjusted body insertion and added tests validating detailed content.
App client CLI
backend/compact-connect/app_clients/bin/create_app_client.py
Moved environment selection from CLI args into interactive get_user_input() with validation; get_user_input() now returns environment and callers use config['environment'].
Infrastructure / stacks
backend/compact-connect/stacks/api_lambda_stack/purchases.py, backend/compact-connect/stacks/event_listener_stack/__init__.py, backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py, backend/compact-connect/tests/app/test_transaction_monitoring.py
Wired TransactionHistoryTable into purchases handler and env; added EVENT_BUS_NAME and PutEvents permission for listeners; changed transaction processor grant to read/write; mapped batchFailureErrorMessage into Step Function templateVariables; added CloudWatch MetricFilter + Alarm for ERROR logs; updated test to include templateVariables.
Tests: python purchase & reconciliation
backend/compact-connect/lambdas/python/common/tests/function/test_data_model/test_transaction_client.py, backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py, backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_transaction_history.py, backend/compact-connect/lambdas/python/purchases/tests/unit/test_purchase_client.py
Added/updated tests covering storing unsettled transactions, reconciliation scenarios (matched, unmatched, old >48h), propagation of submitTimeUTC, and unit expectations updated to include submitTimeUTC.

Sequence Diagram(s)

sequenceDiagram
    participant Buyer
    participant PostPrivileges as post_purchase_privileges
    participant PurchaseClient as purchase_client
    participant TransactionClient as transaction_client
    participant DB as DynamoDB
    participant Processor as transaction_processor
    participant Email as EmailService

    Buyer->>PostPrivileges: Submit purchase
    PostPrivileges->>PurchaseClient: process_charge_on_credit_card(...)
    PurchaseClient-->>PostPrivileges: success + submitTimeUTC
    PostPrivileges->>TransactionClient: store_unsettled_transaction(compact, txId, submitTimeUTC)
    TransactionClient->>DB: Put unsettled_transaction record

    Note over Processor,DB: Later — settlement processing
    Processor->>TransactionClient: reconcile_unsettled_transactions(compact, settled_transactions)
    TransactionClient->>DB: Query unsettled records
    alt Matches found
        TransactionClient->>DB: Batch delete matched records
        TransactionClient-->>Processor: reconciled (no old IDs)
    else Old unsettled (>48h) present
        TransactionClient-->>Processor: return old_unsettled_ids
        Processor->>Email: sendTransactionBatchSettlementFailureEmail(..., batchFailureErrorMessage)
        Email-->>Recipient: Render email with detailed error info (parsed JSON or raw)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jlkravitz
  • isabeleliassen
  • jusdino
  • carlsims

Poem

🐇
I hopped through logs at break of dawn,
I filed the unsettled ones anon,
Forty‑eight hours I watch and yawn,
I stitch the mails so issues aren't gone,
Tiny paws, big fixes — hop! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title "Fix/enhance alerting for transaction settlement errors" is only partially related to the changeset. While the PR does include alerting enhancements through CloudWatch metrics and alarms (addressing issue #1151), the majority of the code changes involve substantial new functionality for unsettled transaction tracking and reconciliation (addressing issue #1150). The changes include a new UnsettledTransactionRecordSchema, methods to store and reconcile unsettled transactions, and logic to detect old unsettled transactions in the history processing workflow. The title emphasizes the alerting aspect while de-emphasizing or omitting the significant unsettled transaction handling work that represents a major portion of the changeset.
Out of Scope Changes Check ⚠️ Warning Several changes appear to be out of scope relative to the stated objectives of issues #1150 and #1151. The modifications to backend/compact-connect/app_clients/bin/create_app_client.py (refactoring environment parameter from CLI argument to interactive input) are unrelated to transaction settlement error fixes or alerting. Similarly, the changes to backend/compact-connect/stacks/event_listener_stack/__init__.py (adding EVENT_BUS_NAME environment variable to license_encumbrance listeners) do not align with either the transaction ID mapping work or the transaction processor alerting work. These changes suggest additional scope creep or unrelated work bundled into this PR.
Description Check ⚠️ Warning The PR description does not follow the required template structure. The template specifies required sections: Requirements List, Description List, and Testing List. The provided description only includes a narrative paragraph and a "Closes #1151" reference, omitting the structured sections entirely. While the narrative explains the general intent and context regarding issues #1150 and #1151, it does not provide the organized requirements, detailed description, or testing checklist that the template requires for proper PR documentation.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The PR appears to successfully implement the coding requirements for both linked issues. For #1150, the changes establish a mechanism to map provider IDs from transaction IDs by introducing unsettled transaction tracking (UnsettledTransactionRecordSchema, store_unsettled_transaction) and reconciliation logic (reconcile_unsettled_transactions) that enables privilege-to-transaction matching without relying on Authorize.net descriptions. For #1151, the PR adds CloudWatch monitoring via MetricFilter and Alarm configuration in the transaction_monitoring_stack to detect and alert on ERROR-level logs from the transaction processor handler. Both core coding objectives are addressed by the corresponding code changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@landonshumway-ia landonshumway-ia force-pushed the fix/enhance-alerting-for-transaction-settlement-errors branch 2 times, most recently from abfebd1 to 34a8c30 Compare October 20, 2025 20:32
@landonshumway-ia landonshumway-ia force-pushed the fix/enhance-alerting-for-transaction-settlement-errors branch from 5d66920 to c9f29c9 Compare October 21, 2025 21:02
@landonshumway-ia landonshumway-ia marked this pull request as ready for review October 22, 2025 02:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (1)

129-147: Guard against None when extending failedTransactionIds.

If event already has batchFailureErrorMessage and this page has no new settlement errors, failed_transactions_ids is None and .extend(None) will throw. Default to [] and extend only when non-empty.

-    failed_transactions_ids = transaction_response.get('settlementErrorTransactionIds')
-    if failed_transactions_ids or event.get('batchFailureErrorMessage'):
+    failed_transactions_ids = transaction_response.get('settlementErrorTransactionIds') or []
+    if failed_transactions_ids or event.get('batchFailureErrorMessage'):
@@
-        if event.get('batchFailureErrorMessage'):
+        if event.get('batchFailureErrorMessage'):
             batch_failure_error_message = json.loads(event.get('batchFailureErrorMessage'))
-            batch_failure_error_message['failedTransactionIds'].extend(failed_transactions_ids)
+            if failed_transactions_ids:
+                batch_failure_error_message['failedTransactionIds'].extend(failed_transactions_ids)
             response['batchFailureErrorMessage'] = json.dumps(batch_failure_error_message)
🧹 Nitpick comments (3)
backend/compact-connect/app_clients/bin/create_app_client.py (1)

128-137: Interactive environment flow: solid; two small polish items.

  • Deterministic scopes: consider sorting when deduping for stable output and reproducible diffs.
  • Optionally validate that environment keys exist in auth/api URL maps (defensive check), even though input is validated.
-    deduped_scopes = list(set(scopes))
+    deduped_scopes = sorted(set(scopes))

Also applies to: 197-203, 320-324, 346-347

backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_transaction_history.py (1)

724-819: Good coverage for unsettled paths; add time-stability to avoid flakes.

  • Tests are clear and validate both failure surfacing and deletion. To avoid edge cases around the 48h cutoff, freeze time or inject now() via dependency to keep deterministic boundaries.
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)

235-271: Unsettled storage: OK; normalize/validate input timestamp.

store_unsettled_transaction assumes ISO strings that datetime.fromisoformat can parse later. If callers pass Z-suffixed UTC (e.g., 2024-01-01T12:00:00.000Z), later parsing will fail. Normalize or parse Z explicitly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7ba4f6 and 14573d3.

📒 Files selected for processing (16)
  • backend/compact-connect/app_clients/bin/create_app_client.py (4 hunks)
  • backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts (1 hunks)
  • backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts (2 hunks)
  • backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/transaction/record.py (1 hunks)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/common/tests/function/test_data_model/test_transaction_client.py (2 hunks)
  • backend/compact-connect/lambdas/python/purchases/handlers/privileges.py (1 hunks)
  • backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (2 hunks)
  • backend/compact-connect/lambdas/python/purchases/purchase_client.py (1 hunks)
  • backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py (4 hunks)
  • backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_transaction_history.py (2 hunks)
  • backend/compact-connect/stacks/api_lambda_stack/purchases.py (5 hunks)
  • backend/compact-connect/stacks/event_listener_stack/__init__.py (3 hunks)
  • backend/compact-connect/stacks/transaction_monitoring_stack/transaction_history_processing_workflow.py (4 hunks)
  • backend/compact-connect/tests/app/test_transaction_monitoring.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts (1)
backend/compact-connect/lambdas/nodejs/lib/models/email-notification-service-event.ts (1)
  • EmailNotificationEvent (10-19)
backend/compact-connect/stacks/api_lambda_stack/purchases.py (2)
backend/compact-connect/stacks/persistent_stack/transaction_history_table.py (1)
  • TransactionHistoryTable (19-70)
backend/compact-connect/lambdas/python/common/cc_common/config.py (2)
  • transaction_history_table (274-275)
  • compact_configuration_table (60-61)
backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_transaction_history.py (3)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (1)
  • process_settled_transactions (16-193)
backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_transaction_reporting.py (2)
  • _add_compact_configuration_data (228-245)
  • _generate_mock_transaction (52-151)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)
  • store_unsettled_transaction (235-270)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • transaction_history_table (274-275)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/transaction/record.py (2)
  • TransactionRecordSchema (34-73)
  • UnsettledTransactionRecordSchema (77-102)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • transaction_client (260-263)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)
  • reconcile_unsettled_transactions (272-339)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/transaction/record.py (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/base_record.py (2)
  • BaseRecordSchema (28-82)
  • register_schema (68-75)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/fields.py (1)
  • Compact (48-50)
backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_purchase_privileges.py (3)
backend/compact-connect/lambdas/python/purchases/handlers/privileges.py (1)
  • post_purchase_privileges (176-464)
backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_privilege_options.py (1)
  • _when_testing_provider_user_event_with_custom_claims (17-36)
backend/compact-connect/lambdas/python/purchases/tests/function/test_handlers/test_credentials.py (1)
  • _generate_test_request_body (17-24)
backend/compact-connect/stacks/event_listener_stack/__init__.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (2)
  • event_bus_name (84-85)
  • provider_table (88-89)
backend/common-cdk/common_constructs/stack.py (1)
  • common_env_vars (81-89)
backend/compact-connect/lambdas/python/common/tests/function/test_data_model/test_transaction_client.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • transaction_client (260-263)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (3)
  • TransactionClient (11-339)
  • store_unsettled_transaction (235-270)
  • reconcile_unsettled_transactions (272-339)
backend/compact-connect/lambdas/python/purchases/purchase_client.py (1)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • current_standard_datetime (253-257)
backend/compact-connect/lambdas/python/purchases/handlers/privileges.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • transaction_client (260-263)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)
  • store_unsettled_transaction (235-270)
backend/compact-connect/app_clients/bin/create_app_client.py (1)
webroot/src/plugins/EnvConfig/envConfig.plugin.ts (1)
  • config (60-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (8)
backend/compact-connect/stacks/event_listener_stack/__init__.py (4)

63-63: Clarify alignment between PR objectives and changes.

The PR objectives focus on transaction settlement error alerting (issue #1151), but the changes in this file configure license encumbrance event listeners. Could you clarify how these event bus configuration changes relate to the transaction settlement monitoring objectives?

Also applies to: 72-72, 113-113, 121-121


63-63: LGTM: Event bus name propagation.

The EVENT_BUS_NAME environment variable is correctly propagated to both Lambda handlers, enabling them to publish events back to the data event bus.

Also applies to: 113-113


72-72: LGTM: EventBridge permissions granted.

The grant_put_events_to calls correctly authorize both Lambda handlers to publish events to the data event bus. The existing NagSuppressions already cover the IAM wildcard requirements for these permissions.

Also applies to: 121-121


148-192: ****

The exclusion of EVENT_BUS_NAME and grant_put_events_to for the license deactivation listener is intentional and correct. Unlike the encumbrance handlers which publish privilege events to the event bus, the deactivation handler only modifies data by calling deactivate_license_privileges() without emitting events. The handler imports do not include EventBusClient or EventBatchWriter, confirming it has no event publishing responsibility. Adding unnecessary event bus permissions would violate the principle of least privilege.

Likely an incorrect or invalid review comment.

backend/compact-connect/stacks/api_lambda_stack/purchases.py (1)

34-42: Wiring and permissions look correct.

  • Env var TRANSACTION_HISTORY_TABLE_NAME, handler param, and grant_read_write_data are all consistent with cc_common.config.transaction_history_table usage. LGTM.

Also applies to: 50-55, 73-74, 107-109

backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (1)

95-99: Right place for reconciliation.

Reconciling before enrichment/storage is sensible. LGTM.

backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts (1)

74-103: Nice job surfacing actionable error context.

Parsing the optional payload and gracefully falling back to the raw message makes the alert far more informative without risking runtime failures, and threading the insertBody alignment preserves readability for the ops folks.

backend/compact-connect/lambdas/python/common/tests/function/test_data_model/test_transaction_client.py (1)

111-267: Great coverage around unsettled reconciliation edge cases.

These scenarios (bad input, empty queues, matched cleanup, and >48h stragglers) map directly to the client’s branching and should keep regressions from sneaking back in.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (1)

124-141: Fix None handling and ensure list before extend when merging failedTransactionIds.

If there’s a prior batchFailureErrorMessage but no new settlement errors this run, failed_transactions_ids is None and .extend(None) will raise. Also assumes existing dict has failedTransactionIds. Coalesce to [] and merge safely.

-    failed_transactions_ids = transaction_response.get('settlementErrorTransactionIds')
+    failed_transactions_ids = transaction_response.get('settlementErrorTransactionIds') or []
     if failed_transactions_ids or event.get('batchFailureErrorMessage'):
         # error message should be a json object we can load
         if event.get('batchFailureErrorMessage'):
             batch_failure_error_message = json.loads(event.get('batchFailureErrorMessage'))
-            batch_failure_error_message['failedTransactionIds'].extend(failed_transactions_ids)
+            existing = batch_failure_error_message.get('failedTransactionIds', []) or []
+            # dedupe while preserving type
+            merged_failed = list({*existing, *failed_transactions_ids})
+            batch_failure_error_message['failedTransactionIds'] = merged_failed
             response['batchFailureErrorMessage'] = json.dumps(batch_failure_error_message)
         else:
             # if there was no previous error message, we'll create a new one
             response['batchFailureErrorMessage'] = json.dumps(
                 {
                     'message': 'Settlement errors detected in one or more transactions.',
-                    'failedTransactionIds': failed_transactions_ids,
+                    'failedTransactionIds': failed_transactions_ids,
                 }
             )
♻️ Duplicate comments (2)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (2)

157-174: Merge and dedupe unsettledTransactionIds; avoid leading/trailing spaces and duplicate message suffix.

Currently overwrites any existing unsettledTransactionIds and can prepend a leading space if no existing message. Merge, dedupe, and avoid duplicate suffix.

-    # Parse existing error message if it exists
-    existing_error = {}
-    if response.get('batchFailureErrorMessage'):
-        existing_error = json.loads(response.get('batchFailureErrorMessage'))
-
-    existing_error.update(
-        {
-            'message': existing_error.get('message', '')
-            + ' One or more transactions have not settled in over 48 hours.',
-            'unsettledTransactionIds': old_unsettled_transaction_ids,
-        }
-    )
+    # Parse existing error message if it exists
+    existing_error = json.loads(response.get('batchFailureErrorMessage')) if response.get('batchFailureErrorMessage') else {}
+
+    suffix = 'One or more transactions have not settled in over 48 hours.'
+    prev_msg = (existing_error.get('message') or '').strip()
+    if not prev_msg:
+        new_msg = suffix
+    elif suffix not in prev_msg:
+        new_msg = f'{prev_msg} {suffix}'
+    else:
+        new_msg = prev_msg
+    existing_error['message'] = new_msg
+
+    prev_unsettled = set(existing_error.get('unsettledTransactionIds', []))
+    merged_unsettled = list(prev_unsettled.union(old_unsettled_transaction_ids))
+    existing_error['unsettledTransactionIds'] = merged_unsettled

111-112: Use safe access for processedBatchIds to avoid KeyError.

Mirror earlier defensive get on event; fall back to previous processed_batch_ids if the client omits it.

-        'processedBatchIds': transaction_response['processedBatchIds'],
+        'processedBatchIds': transaction_response.get('processedBatchIds', processed_batch_ids),
🧹 Nitpick comments (5)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (5)

145-147: Log structured fields (and compact) instead of a large JSON string.

Helps CloudWatch metric filters and reduces log volume; include compact for correlation.

-            logger.error(
-                'Batch settlement error detected', batchFailureErrorMessage=response['batchFailureErrorMessage']
-            )
+            _err = json.loads(response['batchFailureErrorMessage'])
+            logger.error(
+                'Batch settlement error detected',
+                compact=compact,
+                message=_err.get('message'),
+                failedTransactionIds=_err.get('failedTransactionIds', []),
+                unsettledTransactionIds=_err.get('unsettledTransactionIds', []),
+            )

150-155: Consider isolating reconcile failures to avoid masking primary processing.

If DynamoDB or reconcile throws, decide whether to continue or explicitly fail this iteration. Optional guard shown; still emits ERROR and BATCH_FAILURE as per alerting objectives.

-    old_unsettled_transaction_ids = config.transaction_client.reconcile_unsettled_transactions(
-        compact=compact, settled_transactions=transaction_response['transactions']
-    )
+    try:
+        old_unsettled_transaction_ids = config.transaction_client.reconcile_unsettled_transactions(
+            compact=compact, settled_transactions=transaction_response.get('transactions', [])
+        )
+    except Exception as e:
+        logger.error('Unsettled reconciliation failed', compact=compact, error=str(e))
+        response['status'] = 'BATCH_FAILURE'
+        return response

175-179: Include compact and counts in ERROR log; avoid large arrays in logs.

Emit counts to keep logs lean; arrays remain in batchFailureErrorMessage if needed.

-        logger.error(
-            'Unsettled transactions older than 48 hours detected',
-            unsettledTransactionIds=old_unsettled_transaction_ids,
-        )
+        logger.error(
+            'Unsettled transactions older than 48 hours detected',
+            compact=compact,
+            unsettledTransactionIdsCount=len(old_unsettled_transaction_ids),
+        )

68-69: Make scheduledTime parsing robust to ISO8601 'Z' and ensure UTC normalization.

datetime.fromisoformat doesn’t parse 'Z' on some runtimes. Normalize to UTC and then set the hour.

-fromiso = datetime.fromisoformat
-end_time = datetime.fromisoformat(scheduled_time).replace(hour=1, minute=0, second=0, microsecond=0)
+from datetime import timezone
+_ts = scheduled_time.replace('Z', '+00:00')
+end_time = datetime.fromisoformat(_ts).astimezone(timezone.utc).replace(hour=1, minute=0, second=0, microsecond=0)

Please confirm the actual format of scheduledTime from EventBridge (e.g., "2025-10-16T22:12:50Z"). If it’s always 'Z', the above change prevents runtime ValueError.


96-105: Minor: guard access to transactions key.

Use .get to avoid KeyError if the client ever omits transactions on error. No behavior change when present.

-    if transaction_response['transactions']:
+    if transaction_response.get('transactions'):
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b1e402 and 721c44a.

📒 Files selected for processing (1)
  • backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • transaction_client (260-263)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)
  • reconcile_unsettled_transactions (272-339)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (1)

165-171: Consider trimming whitespace in message concatenation.

If existing_error.get('message', '') returns an empty string, line 168 produces a message with a leading space. Consider adding .strip() or checking for non-empty message before concatenation.

Apply this diff to handle spacing more gracefully:

-        existing_error.update(
-            {
-                'message': existing_error.get('message', '')
-                + ' One or more transactions have not settled in over 48 hours.',
-                'unsettledTransactionIds': old_unsettled_transaction_ids,
-            }
-        )
+        message_parts = [existing_error.get('message', ''), 'One or more transactions have not settled in over 48 hours.']
+        existing_error.update(
+            {
+                'message': ' '.join(filter(None, message_parts)),
+                'unsettledTransactionIds': old_unsettled_transaction_ids,
+            }
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 721c44a and 42993cf.

📒 Files selected for processing (1)
  • backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • transaction_client (260-263)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)
  • reconcile_unsettled_transactions (272-339)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (3)
backend/compact-connect/lambdas/python/purchases/handlers/transaction_history.py (3)

126-148: LGTM! Settlement error handling is well-implemented.

The defensive use of .get('settlementErrorTransactionIds', []) on line 126, proper aggregation of failed transaction IDs across iterations, and the elevation of the log level to error when all transactions are processed (line 145) align perfectly with the PR objectives of enhancing alerting for transaction settlement errors.


150-155: LGTM! Reconciliation approach is sound.

Running reconcile_unsettled_transactions on every iteration ensures timely cleanup of matched transactions and reliable detection of stale unsettled records. The implementation correctly delegates to the transaction client and captures the 48-hour threshold violations for reporting.


157-179: Solid implementation of 48-hour unsettled transaction detection.

The logic correctly defers reporting until all transactions are processed, properly merges with existing error messages, and sets the appropriate BATCH_FAILURE status with error-level logging. This aligns well with the PR's monitoring objectives.

Copy link
Collaborator

@jusdino jusdino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done. No notes.

@landonshumway-ia
Copy link
Collaborator Author

@jlkravitz This is ready for your review. Thanks

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)

288-291: Add pagination to prevent silently missing unsettled transactions.

The query lacks pagination and may exceed DynamoDB's 1 MB response limit if there are many unsettled transactions. This was flagged in the previous review and remains unresolved. Without pagination, some unsettled transactions will be silently missed, preventing proper reconciliation and 48-hour detection.

Apply this diff to add pagination following the existing pattern in _query_transactions_for_month:

-        response = self.config.transaction_history_table.query(
-            KeyConditionExpression=Key('pk').eq(pk),
-        )
-
-        unsettled_transactions = response.get('Items', [])
+        unsettled_transactions = []
+        last_evaluated_key = None
+        while True:
+            query_kwargs = {'KeyConditionExpression': Key('pk').eq(pk)}
+            if last_evaluated_key:
+                query_kwargs['ExclusiveStartKey'] = last_evaluated_key
+            
+            response = self.config.transaction_history_table.query(**query_kwargs)
+            unsettled_transactions.extend(response.get('Items', []))
+            
+            last_evaluated_key = response.get('LastEvaluatedKey')
+            if not last_evaluated_key:
+                break
🧹 Nitpick comments (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (1)

299-340: LGTM! Reconciliation and 48-hour detection logic is correct.

The logic properly matches unsettled transactions with settled ones, batch deletes matched records, and identifies transactions older than 48 hours. The datetime.fromisoformat usage at line 329 is safe since transactionDate is stored in normalized '+00:00' format (as per your earlier clarification).

Optional: Line 318 logs the entire settled_transaction_ids set, which could be verbose if there are many transactions. Consider logging just the count for cleaner logs:

-                settled_transaction_ids=settled_transaction_ids
+                settled_count=len(settled_transaction_ids)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42993cf and b846bd2.

📒 Files selected for processing (1)
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-25T22:31:44.837Z
Learnt from: jusdino
PR: csg-org/CompactConnect#1005
File: backend/compact-connect/lambdas/python/common/cc_common/dsa_auth.py:212-228
Timestamp: 2025-08-25T22:31:44.837Z
Learning: The datetime.fromisoformat() method in the CompactConnect DSA authentication module already supports both 'Z' and '+00:00' ISO 8601 timestamp formats, as verified by existing tests. The timezone-naive timestamp handling should be added as an additional safety measure.

Applied to files:

  • backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py
🧬 Code graph analysis (1)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (2)
backend/compact-connect/lambdas/python/common/cc_common/config.py (1)
  • transaction_history_table (274-275)
backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/transaction/record.py (2)
  • TransactionRecordSchema (34-73)
  • UnsettledTransactionRecordSchema (77-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: TestApp
🔇 Additional comments (2)
backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py (2)

1-1: LGTM! Imports support the new unsettled transaction functionality.

The added imports (UTC, timedelta, and UnsettledTransactionRecordSchema) are all used appropriately in the new methods.

Also applies to: 6-6


235-271: LGTM! Appropriate error handling for non-critical monitoring feature.

The broad exception handling is justified since this is a monitoring feature that shouldn't disrupt the main transaction flow. The error logging ensures visibility while preventing cascading failures.

Copy link
Collaborator

@jlkravitz jlkravitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! @isabeleliassen good to merge.

@isabeleliassen isabeleliassen merged commit 3d4e062 into csg-org:development Oct 24, 2025
4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Alert on all error logs from the transaction history collector Map provider ids directly from transaction id rather than Authorize.net

4 participants