Real World Example

Audit Trail for Refunds

Payment and banking apps (Paytm, PhonePe, net banking) must record who did what and when for every money movement. Instead of adding audit code to every method, ShopEase marks sensitive methods with @Audited and one aspect writes the audit row.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited { String action(); }

@Aspect
@Component
@RequiredArgsConstructor
public class AuditAspect {
    private final AuditRepository auditRepository;

    @AfterReturning(pointcut = "@annotation(audited)", returning = "result")
    public void audit(JoinPoint jp, Audited audited, Object result) {
        String user = SecurityContextHolder.getContext().getAuthentication().getName();
        auditRepository.save(new AuditEntry(user, audited.action(),
                Arrays.toString(jp.getArgs()), String.valueOf(result), Instant.now()));
    }
}

@Service
public class RefundService {
    @Audited(action = "REFUND")
    @Transactional
    public RefundResponse refund(Long orderId, BigDecimal amount) { ... }
}

Example

curl -X POST localhost:8080/api/admin/orders/1001/refund -H "Authorization: Bearer eyJ..." -H "Content-Type: application/json" -d '{"amount":499.00}'

Example output

HTTP/1.1 200 OK
{"orderId":1001,"refunded":499.00,"status":"REFUND_INITIATED"}

-- audit_entry table --
| user          | action | args           | result           | at                   |
| [email protected] | REFUND | [1001, 499.00] | REFUND_INITIATED | 2026-03-14T10:22:05Z |