Advanced 30 min read

Secure Account Data

By the end of this lesson, you'll be able to:

  • Apply Module 28's WITH SECURITY_ENFORCED and CRUD/FLS checks to the banking objects
  • Prevent a withdrawal from making Balance__c negative
  • Prevent transactions against a Frozen or Closed account

Prerequisites: "Schedule Nightly Reconciliation (Batch + Scheduled)"

Field-level security on Balance__c

public with sharing class BankAccountService {
    public static List<Bank_Account__c> getAccountsFor(Id userId) {
        return [
            SELECT Id, Balance__c, Status__c
            FROM Bank_Account__c
            WHERE OwnerId = :userId
            WITH SECURITY_ENFORCED
        ];
    }
}

with sharing and WITH SECURITY_ENFORCED are Module 28's exact security mechanisms — for a banking system, enforcing that a user can only see accounts and balances they're actually permitted to see isn't optional polish, it's the whole point of the security lesson finally mattering in a domain where the stakes are obvious.

Rejecting an overdraft in a before-insert validation trigger

trigger TransactionValidation on Transaction__c (before insert) {
    Set<Id> accountIds = new Set<Id>();
    for (Transaction__c t : Trigger.new) accountIds.add(t.Bank_Account__c);

    Map<Id, Bank_Account__c> accounts = new Map<Id, Bank_Account__c>(
        [SELECT Id, Balance__c, Status__c FROM Bank_Account__c WHERE Id IN :accountIds]
    );

    for (Transaction__c t : Trigger.new) {
        Bank_Account__c acc = accounts.get(t.Bank_Account__c);
        if (acc.Status__c != 'Active') {
            t.addError('This account is not active and cannot accept transactions.');
        } else if (t.Type__c == 'Withdrawal' && t.Amount__c > acc.Balance__c) {
            t.addError('Insufficient funds for this withdrawal.');
        }
    }
}

This is a separate before insert trigger, deliberately kept apart from Lesson 2's balance-update after insert trigger — Module 24's separation-of-concerns principle, applied here so that validation logic and balance-update logic can each be tested (Lesson 7) and reasoned about independently.

Why validation happens before, not after, the balance update

before insert runs, and can call addError() to block the record entirely, before Lesson 2's after insert balance-update trigger ever executes. This ordering guarantee means an invalid overdraft transaction never reaches the balance-update logic at all — it's rejected outright, exactly the fail-safe design a banking system requires.

Exercise

As a comment, explain why the overdraft check belongs in a before-insert trigger rather than the after-insert trigger from Lesson 2.

Show hint

Think about what addError() does and when a record needs to be rejected relative to the balance update.

APEX

Secure Account Data Quiz

1. Why is the overdraft/status validation kept in a separate before-insert trigger, rather than combined into Lesson 2's after-insert balance-update trigger?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

This lesson layers Module 28's security discipline and genuine business-rule validation onto the banking objects — enforcing field-level security on sensitive financial data, and rejecting transactions that would violate the account's own rules.