Class Name: UpdateChargentOrderTransactionsBatch¶
Last Updated: 2025-10-22 Source Code: https://github.com/AANP-IT/I2C.Salesforce.Metadata/blob/STAGING/force-app/main/default/classes/UpdateChargentOrderTransactionsBatch.cls
API Name: UpdateChargentOrderTransactionsBatch Type: Batch (Database.Batchable) Test Coverage: Test class needed
Business Purpose¶
The UpdateChargentOrderTransactionsBatch class is the second phase of GDPR/privacy anonymization, processing ChargentOrders__Transaction__c records associated with anonymized Chargent Orders. This ensures:
- Payment transaction details are anonymized for deleted/anonymized accounts
- PCI DSS compliance by removing sensitive payment data from transaction history
- Complete anonymization chain (Orders → Transactions)
- Compliance with data retention and privacy policies
This batch is automatically chained from UpdateChargentOrdersBatch.
Class Overview¶
- Author: Not specified
- Created: Unknown
- Test Class: Needs identification
- Scope/Sharing:
with sharing- Respects record-level security - Implements:
Database.Batchable<SObject>,Database.Stateful- Stateful batch processing - Key Responsibilities:
- Query transactions for anonymized Chargent Orders
- Anonymize payment method, card, and billing information
- Set amounts to $0
- Mark transactions as anonymized
Batch Methods¶
start¶
Purpose: Queries ChargentOrders__Transaction__c records for anonymized Chargent Orders.
Business Logic:
- Query Anonymized Accounts (lines 4-10):
- Same logic as UpdateChargentOrdersBatch
-
Limits to 50,000 accounts
-
Query Anonymized Chargent Orders (lines 18-29):
- Filters for already anonymized orders (Anonymized__c = true)
-
These are orders processed by UpdateChargentOrdersBatch
-
Return Transaction QueryLocator (lines 34-48):
return Database.getQueryLocator([ SELECT Id, Anonymized__c, ChargentOrders__Gateway_ID__c, ChargentOrders__Order__c, ChargentOrders__Payment_Method__c, ChargentOrders__Amount__c, ChargentOrders__Tokenization__c, ... FROM ChargentOrders__Transaction__c WHERE Anonymized__c = false AND ChargentOrders__Order__c IN :orderIds ]); - Queries transactions for anonymized orders
-
Only non-anonymized transactions (Anonymized__c = false)
-
Empty QueryLocator (line 53):
- Returns safe empty result if no records found
Issues/Concerns: - ⚠️ Three-Query Approach (lines 4, 18, 34): Queries Accounts → Orders → Transactions - Could be optimized with relationship queries - Consumes multiple SOQL queries - ⚠️ 50,000 Account Limit (line 9): Same limitation as UpdateChargentOrdersBatch - ⚠️ Stateful Not Used: Implements Database.Stateful but doesn't use instance variables - ✅ Filtered Queries: Properly filters by Anonymized__c flags - ✅ Empty Handling: Returns safe empty QueryLocator
execute¶
Purpose: Anonymizes transaction payment and billing information.
Business Logic:
- Query Related Chargent Orders (lines 58-67):
Set<Id> charOrderIds = new Set<Id>(); for (ChargentOrders__Transaction__c chargentOrderTransaction : scope) { charOrderIds.add(chargentOrderTransaction.ChargentOrders__Order__c); } Map<Id, ChargentOrders__ChargentOrder__c> chargentOrderMap = new Map<Id, ChargentOrders__ChargentOrder__c>([ SELECT Id, ChargentOrders__Billing_First_Name__c FROM ChargentOrders__ChargentOrder__c WHERE Id IN :charOrderIds ]); - Queries parent Chargent Order for Billing_First_Name__c
-
Uses this value for anonymization replacement
-
Anonymize Transaction Fields (lines 71-96):
for (ChargentOrders__Transaction__c charOrderTransaction : scope) { ChargentOrders__ChargentOrder__c relatedChargentOrder = chargentOrderMap.get(charOrderTransaction.ChargentOrders__Order__c); if (relatedChargentOrder != null) { charOrderTransaction.ChargentOrders__Gateway_ID__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Payment_Method__c = null; charOrderTransaction.ChargentOrders__Amount__c = 0; charOrderTransaction.ChargentOrders__Tokenization__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Payment_Method_Applied__c = null; charOrderTransaction.ChargentOrders__Authorization__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Credit_Card_Name__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Card_Last_4__c = '0000'; charOrderTransaction.ChargentOrders__Billing_Address__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Billing_Address_Line_2__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Billing_City__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Billing_State__c = null; charOrderTransaction.ChargentOrders__Billing_Province__c = null; charOrderTransaction.ChargentOrders__Billing_Postal_Code__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Billing_Country__c = null; charOrderTransaction.ChargentOrders__Billing_First__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Billing_Last__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.ChargentOrders__Gateway_Response__c = relatedChargentOrder.ChargentOrders__Billing_First_Name__c; charOrderTransaction.Anonymized__c = true; addressesToUpdate.add(charOrderTransaction); } }
Anonymization Strategy: - Gateway ID: Replaced with Billing_First_Name__c - Payment Method: Nullified - Amount: Set to $0 - Tokenization: Replaced with Billing_First_Name__c - Authorization: Replaced with Billing_First_Name__c - Card Last 4: Set to '0000' - Billing Address: All fields replaced with Billing_First_Name__c or null - Gateway Response: Replaced with Billing_First_Name__c - Anonymized Flag: Marked as true
- Update Records (lines 97-99):
- Uses partial success (false parameter)
- No error handling for failures
Issues/Concerns: - ⚠️ Re-Query Chargent Orders (line 63): Already queried in start() - Could include ChargentOrders__Billing_First_Name__c in main query via relationship - Extra SOQL query consumption - ⚠️ No Error Logging: Partial success update (line 98) doesn't log failures - Failed updates are silent - Should check SaveResult for errors - ⚠️ Billing_First_Name for All Fields: Uses same value for multiple fields - Loses distinction between fields - Consider using 'REDACTED' or field-specific values - ⚠️ Card Last 4 = '0000': Uses '0000' instead of null - May be required by Chargent package validation - ✅ Partial Success DML: Allows batch to continue on individual failures
finish¶
Purpose: Batch completion callback.
Business Logic:
Issues/Concerns: - ✅ Simple Completion: Just logs message - ⚠️ No Summary: Doesn't log how many records processed/failed - ⚠️ No Notification: No admin notification on completion - ✅ No Chaining: Appropriately ends the anonymization chain
Dependencies¶
Salesforce Objects¶
- Account (Standard Object)
- Fields:
Id,LastName,PersonMailingPostalCode,PersonEmail,Anonymized__c,PersonContactId -
Access: Read
-
User (Standard Object)
- Fields:
ContactId,isActive -
Access: Read (for subquery)
-
ChargentOrders__ChargentOrder__c (Chargent Package Object)
- Fields:
Id,ChargentOrders__Billing_First_Name__c,Anonymized__c -
Access: Read
-
ChargentOrders__Transaction__c (Chargent Package Object)
- Many fields for payment transaction details
- Custom field:
Anonymized__c - Access: Read, Update
Other Classes¶
- UpdateChargentOrdersBatch: Previous batch in anonymization chain (launches this batch)
Design Patterns¶
- Chained Batch Pattern: Launched by UpdateChargentOrdersBatch.finish()
- Anonymization Pattern: Replaces sensitive data with generic values
- Partial Success DML: Continues processing despite individual failures
- Stateful Batch: Implements Database.Stateful (though not actively used)
Governor Limits Considerations¶
Per Batch Invocation¶
- SOQL Queries: 2 per execute (Account + Orders queries in start, ChargentOrder query in execute)
- DML Statements: 1 (Transaction updates)
- DML Rows: Up to batch size (default 200)
Scalability¶
- ✅ Batch Processing: Handles large volumes
- ⚠️ 50,000 Limit: Inherited from UpdateChargentOrdersBatch
- ⚠️ Multiple Queries: Three queries in start() method
- ✅ Partial Success: Individual failures don't stop batch
Security Considerations¶
Data Privacy¶
- Purpose: GDPR/privacy compliance (transaction history)
- PCI DSS: Removes card numbers, tokenization, gateway IDs
- Amount Handling: Sets to $0 for privacy
Sharing Model¶
- WITH SHARING: Respects record-level security
- Consideration: Same concern as UpdateChargentOrdersBatch
Error Handling¶
Exception Types¶
- None: No try-catch blocks
Error Handling Gaps¶
- No DML Error Checking (line 98): Partial success but no SaveResult validation
- No Exception Handling: Batch failure stops processing
- No Logging: Failures only in batch job logs
Recommendations¶
public void execute(Database.BatchableContext bc, List<ChargentOrders__Transaction__c> scope) {
// ... anonymization logic ...
if (!addressesToUpdate.isEmpty()) {
Database.SaveResult[] results = Database.update(addressesToUpdate, false);
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
System.debug(LoggingLevel.ERROR,
'Failed to anonymize Chargent Transaction: ' + addressesToUpdate[i].Id +
' - ' + results[i].getErrors()[0].getMessage());
}
}
}
}
Test Class Requirements¶
@IsTest
public class UpdateChargentOrderTransactionsBatchTest {
@TestSetup
static void setup() {
// Create anonymized account
Account acc = TestDataFactory.getAccountRecord(true);
acc.Anonymized__c = true;
update acc;
// Create and anonymize Chargent Order
ChargentOrders__ChargentOrder__c order = new ChargentOrders__ChargentOrder__c();
order.ChargentOrders__Account__c = acc.Id;
order.ChargentOrders__Billing_First_Name__c = 'Anonymized';
order.Anonymized__c = true;
insert order;
// Create transaction
ChargentOrders__Transaction__c txn = new ChargentOrders__Transaction__c();
txn.ChargentOrders__Order__c = order.Id;
txn.ChargentOrders__Amount__c = 100;
txn.ChargentOrders__Card_Last_4__c = '1234';
txn.Anonymized__c = false;
insert txn;
}
@IsTest
static void testBatchAnonymization() {
Test.startTest();
UpdateChargentOrderTransactionsBatch batch = new UpdateChargentOrderTransactionsBatch();
Database.executeBatch(batch);
Test.stopTest();
List<ChargentOrders__Transaction__c> txns = [
SELECT Id, Anonymized__c, ChargentOrders__Amount__c,
ChargentOrders__Card_Last_4__c
FROM ChargentOrders__Transaction__c
];
Assert.areEqual(1, txns.size(), 'Should have one transaction');
Assert.isTrue(txns[0].Anonymized__c, 'Transaction should be anonymized');
Assert.areEqual(0, txns[0].ChargentOrders__Amount__c, 'Amount should be 0');
Assert.areEqual('0000', txns[0].ChargentOrders__Card_Last_4__c, 'Card should be 0000');
}
}
Pre-Go-Live Concerns¶
🚨 CRITICAL¶
- No Error Logging (line 98): Partial success but no SaveResult checking
- Add error logging for failed anonymizations
- Critical for compliance audit trail
HIGH¶
- Multiple Queries in start() (lines 4, 18): Could be optimized
- Combine into single query with relationships
- Improve performance and reduce SOQL usage
- 50,000 Account Limit (line 9): May not process all accounts
- Same limitation as UpdateChargentOrdersBatch
MEDIUM¶
- Re-Query Chargent Orders (line 63): Unnecessary SOQL query
- Include Billing_First_Name__c in main query via relationship
- No Summary Logging: Doesn't log processing results
- Add summary to finish() method
LOW¶
- Billing_First_Name for All Fields (lines 75-92): Generic anonymization
- Consider field-specific placeholders
Changes & History¶
| Date | Author | Description |
|---|---|---|
| Unknown | Original Developer | Initial implementation for transaction anonymization |
Documentation Status: ✅ Complete Code Review Status: 🚨 CRITICAL - Add error logging for compliance Test Coverage: Test class needed Chained From: UpdateChargentOrdersBatch Compliance: GDPR "Right to be Forgotten", PCI DSS