.claude/skills/payment-validator/SKILL.md
Use this skill when modifying payment states, implementing verification flows, or calculating late statuses in the Aegis platform.
npx skillsauth add muhammadcaeed/aegis payment-validatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
UNPAID ──submit──> SUBMITTED ──verify──> VERIFIED (terminal)
│
└──reject──> REJECTED ──resubmit──> SUBMITTED
| From | To | Trigger | |------|----|---------| | UNPAID | SUBMITTED | Resident submits proof | | SUBMITTED | VERIFIED | Moderator verifies | | SUBMITTED | REJECTED | Moderator rejects | | REJECTED | SUBMITTED | Resident resubmits |
UNPAID → VERIFIED (must go through SUBMITTED)VERIFIED → * (VERIFIED is terminal, no changes allowed)REJECTED → VERIFIED (must resubmit first)// WRONG - Never allow this
async updatePayment(id: string, data: UpdatePaymentDto) {
const payment = await this.findById(id);
if (payment.state === PaymentState.VERIFIED) {
throw new ForbiddenException('Verified payments cannot be modified');
}
}
expectedAmount is frozen at payment creationNever store late/overdue status in database. Always derive.
// CORRECT - Calculate dynamically
function getLateStatus(payment: Payment): 'ON_TIME' | 'LATE' | 'PENDING' {
if (payment.state !== PaymentState.VERIFIED) {
return 'PENDING'; // Not yet verified
}
const verifiedAt = payment.verification.verifiedAt;
const billingPeriodEnd = payment.billingPeriodEnd; // 22nd of month
return verifiedAt <= billingPeriodEnd ? 'ON_TIME' : 'LATE';
}
// WRONG - Don't store this
payment.lateStatus = 'LATE'; // Never do this
// For UNPAID/SUBMITTED payments, show "overdue" if past billing period
function isOverdue(payment: Payment): boolean {
if (payment.state === PaymentState.VERIFIED) return false;
return new Date() > payment.billingPeriodEnd;
}
// Each submission is a new record, not an update
async createSubmission(data: CreateSubmissionDto): Promise<PaymentSubmission> {
// Mark any existing active submission as superseded
await this.markPreviousSubmissionsSuperseded(data.paymentId);
// Create new submission with incremented version
const latestVersion = await this.getLatestVersion(data.paymentId);
return this.create({
...data,
version: latestVersion + 1,
state: SubmissionState.ACTIVE,
});
}
| Payment State | Can Edit Submission? | |---------------|---------------------| | SUBMITTED | Yes (creates new version) | | REJECTED | Yes (creates new version) | | VERIFIED | No (submission is locked) |
// Moderators submitting for their OWN household auto-verify
if (user.role === UserRole.MODERATOR && payment.householdId === user.householdId) {
// Auto-verify the payment
await this.verifyPayment(payment.id, user.id);
}
// Moderators cannot verify payments from their own household (unless auto-verified)
if (verifier.householdId === payment.householdId) {
throw new ForbiddenException('Cannot verify your own household payment');
}
// Always include communityId (multi-tenancy)
async findByHouseholdAndStates(
householdId: string,
communityId: string,
states: PaymentState[],
): Promise<Payment[]> {
return this.model.find({
householdId: new Types.ObjectId(householdId),
communityId: new Types.ObjectId(communityId),
state: { $in: states },
});
}
// Explicit transition methods, not generic update
async markAsSubmitted(paymentId: string, communityId: string): Promise<Payment> {
return this.model.findOneAndUpdate(
{
_id: new Types.ObjectId(paymentId),
communityId: new Types.ObjectId(communityId),
state: PaymentState.UNPAID, // Enforce valid source state
},
{ $set: { state: PaymentState.SUBMITTED } },
{ new: true },
);
}
development
Use this skill when writing MongoDB queries, repository methods, or service logic in Aegis. It enforces strict multi-tenancy by ensuring communityId is always included.
development
Mandatory backend module architecture and implementation rules for the Aegis system
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------