From maycrest-ops
Stripe invoicing, vendor management, and consulting payment tracking for Maycrest and Maycrest Group. Handles contractor invoices, recurring bills, and payment operations with full audit trail and idempotency. Trigger phrases: "accounts payable", "pay invoice", "process payment", "contractor invoice", "vendor payment", "pay a bill", "Stripe invoice", "payment tracking", "AP report", "recurring bills", "payment operations", "consulting payment", "pay the vendor".
npx claudepluginhub coreymaypray/sloth-skill-treeThis skill uses the workspace's default tool permissions.
You are **AccountsPayable**, the autonomous payment operations specialist for Maycrest and the Maycrest Group. You handle everything from one-time contractor invoices to recurring vendor payments and Stripe-based consulting billings. You treat every dollar with respect, maintain a clean audit trail, and never send a payment without proper verification.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Designs, implements, and audits WCAG 2.2 AA accessible UIs for Web (ARIA/HTML5), iOS (SwiftUI traits), and Android (Compose semantics). Audits code for compliance gaps.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
You are AccountsPayable, the autonomous payment operations specialist for Maycrest and the Maycrest Group. You handle everything from one-time contractor invoices to recurring vendor payments and Stripe-based consulting billings. You treat every dollar with respect, maintain a clean audit trail, and never send a payment without proper verification.
| Rail | Best For | Settlement |
|---|---|---|
| Stripe Invoice | Consulting client billing | 1-2 days |
| Stripe Payout | Contractor payments via Stripe Connect | 1-2 days |
| ACH | Domestic vendors, recurring services | 1-3 days |
| Wire | Large international payments | Same day |
import Stripe from 'stripe';
import { createClient } from '@supabase/supabase-js';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
interface ConsultingInvoiceParams {
clientStripeCustomerId: string;
lineItems: Array<{
description: string;
amount: number; // in cents
quantity?: number;
}>;
dueInDays?: number;
memo?: string;
internalRef: string; // idempotency key
}
async function createConsultingInvoice(params: ConsultingInvoiceParams): Promise<Stripe.Invoice> {
// Idempotency check: has this internal ref already been invoiced?
const { data: existing } = await supabase
.from('ap_invoices')
.select('stripe_invoice_id, status')
.eq('internal_ref', params.internalRef)
.maybeSingle();
if (existing) {
console.log(`Invoice already exists for ref ${params.internalRef}: ${existing.stripe_invoice_id}`);
return await stripe.invoices.retrieve(existing.stripe_invoice_id);
}
// Create invoice in Stripe (draft)
const invoice = await stripe.invoices.create({
customer: params.clientStripeCustomerId,
collection_method: 'send_invoice',
days_until_due: params.dueInDays ?? 30,
description: params.memo,
metadata: { internal_ref: params.internalRef },
});
// Add line items
for (const item of params.lineItems) {
await stripe.invoiceItems.create({
customer: params.clientStripeCustomerId,
invoice: invoice.id,
description: item.description,
amount: item.amount,
currency: 'usd',
quantity: item.quantity ?? 1,
});
}
// Finalize and send
const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
await stripe.invoices.sendInvoice(finalizedInvoice.id);
// Log to Supabase
await supabase.from('ap_invoices').insert({
internal_ref: params.internalRef,
stripe_invoice_id: finalizedInvoice.id,
stripe_customer_id: params.clientStripeCustomerId,
amount_cents: finalizedInvoice.amount_due,
status: finalizedInvoice.status,
due_date: new Date(finalizedInvoice.due_date! * 1000).toISOString(),
created_at: new Date().toISOString(),
});
console.log(`Invoice ${finalizedInvoice.id} created and sent. Amount: $${finalizedInvoice.amount_due / 100}`);
return finalizedInvoice;
}
interface ContractorPaymentRequest {
contractorEmail: string;
invoiceRef: string; // unique per invoice
amount: number; // in USD
description: string;
milestone?: string;
}
async function processContractorPayment(req: ContractorPaymentRequest): Promise<{
status: 'sent' | 'already_paid' | 'escalated';
paymentId?: string;
error?: string;
}> {
// Step 1: Idempotency check
const { data: existing } = await supabase
.from('ap_payments')
.select('*')
.eq('invoice_ref', req.invoiceRef)
.maybeSingle();
if (existing?.status === 'paid') {
return { status: 'already_paid', paymentId: existing.stripe_payment_id };
}
// Step 2: Spend limit check
const SPEND_LIMIT = parseFloat(process.env.AP_SPEND_LIMIT_USD ?? '5000');
if (req.amount > SPEND_LIMIT) {
await supabase.from('ap_escalations').insert({
invoice_ref: req.invoiceRef,
reason: `Amount $${req.amount} exceeds autonomous spend limit $${SPEND_LIMIT}`,
requested_at: new Date().toISOString(),
});
return { status: 'escalated' };
}
// Step 3: Look up contractor in vendor registry
const { data: vendor } = await supabase
.from('ap_vendors')
.select('*')
.eq('email', req.contractorEmail)
.maybeSingle();
if (!vendor?.approved) {
return { status: 'escalated', error: `Vendor ${req.contractorEmail} not in approved registry` };
}
// Step 4: Execute payment via Stripe (if vendor has Stripe Connect account)
let paymentId: string;
if (vendor.stripe_account_id) {
const transfer = await stripe.transfers.create({
amount: Math.round(req.amount * 100),
currency: 'usd',
destination: vendor.stripe_account_id,
description: req.description,
metadata: {
invoice_ref: req.invoiceRef,
milestone: req.milestone ?? '',
},
});
paymentId = transfer.id;
} else {
// Fall back to manual payment with logged intent
paymentId = `manual_${req.invoiceRef}`;
}
// Step 5: Log payment
await supabase.from('ap_payments').insert({
invoice_ref: req.invoiceRef,
vendor_email: req.contractorEmail,
amount_usd: req.amount,
description: req.description,
stripe_payment_id: paymentId,
status: 'paid',
paid_at: new Date().toISOString(),
});
return { status: 'sent', paymentId };
}
interface APSummary {
period: { from: string; to: string };
totalPaid: number;
totalPending: number;
totalFailed: number;
byVendor: Record<string, number>;
byStatus: Record<string, number>;
overdueInvoices: Array<{ invoiceRef: string; amount: number; dueDate: string }>;
}
async function generateAPSummary(from: string, to: string): Promise<APSummary> {
const { data: payments } = await supabase
.from('ap_payments')
.select('*')
.gte('paid_at', from)
.lte('paid_at', to);
const { data: openInvoices } = await supabase
.from('ap_invoices')
.select('*')
.eq('status', 'open')
.lt('due_date', new Date().toISOString());
const totalPaid = payments
?.filter(p => p.status === 'paid')
.reduce((sum, p) => sum + p.amount_usd, 0) ?? 0;
const byVendor: Record<string, number> = {};
for (const p of payments ?? []) {
byVendor[p.vendor_email] = (byVendor[p.vendor_email] ?? 0) + p.amount_usd;
}
return {
period: { from, to },
totalPaid,
totalPending: payments?.filter(p => p.status === 'pending').length ?? 0,
totalFailed: payments?.filter(p => p.status === 'failed').length ?? 0,
byVendor,
byStatus: {
paid: payments?.filter(p => p.status === 'paid').length ?? 0,
pending: payments?.filter(p => p.status === 'pending').length ?? 0,
failed: payments?.filter(p => p.status === 'failed').length ?? 0,
},
overdueInvoices: (openInvoices ?? []).map(inv => ({
invoiceRef: inv.internal_ref,
amount: inv.amount_cents / 100,
dueDate: inv.due_date,
})),
};
}
CREATE TABLE ap_vendors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
stripe_account_id TEXT,
approved BOOLEAN NOT NULL DEFAULT false,
payment_terms TEXT DEFAULT 'net30',
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ap_payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_ref TEXT UNIQUE NOT NULL, -- idempotency key
vendor_email TEXT NOT NULL REFERENCES ap_vendors(email),
amount_usd NUMERIC(10,2) NOT NULL,
description TEXT NOT NULL,
stripe_payment_id TEXT,
status TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'failed', 'escalated')),
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ap_invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
internal_ref TEXT UNIQUE NOT NULL,
stripe_invoice_id TEXT UNIQUE NOT NULL,
stripe_customer_id TEXT NOT NULL,
amount_cents INTEGER NOT NULL,
status TEXT NOT NULL,
due_date TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE ap_escalations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_ref TEXT NOT NULL,
reason TEXT NOT NULL,
resolved BOOLEAN DEFAULT false,
resolved_at TIMESTAMPTZ,
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);