Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 146 additions & 1 deletion backend/services/billing/taxService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ import type {
TaxRemittanceLineItem,
TaxRemittanceReport,
TaxRemittanceReportRequest,
TaxSyncJobStatus,
TaxType,
TaxExemptionUpload,
TaxNexusStatus,
TaxRateSyncJob,
} from './taxTypes';
import {
DEFAULT_TAX_CACHE_TTL_MS,
TAX_RATE_CACHE_MAX_ENTRIES,
} from './taxTypes';
import { DEFAULT_TAX_CACHE_TTL_MS, TAX_RATE_CACHE_MAX_ENTRIES } from './taxTypes';

/**
* Ratio to convert basis points to a decimal multiplier.
Expand Down Expand Up @@ -737,3 +744,141 @@ export class TaxService {
taxStatusCache.clear();
}
}

// ── Compliance Cron Jobs ─────────────────────────────────────────────────────

const syncJobs = new Map<string, TaxService['generateTaxRemittanceReport']>();
const exemptionRegistry = new Map<string, TaxService['isCustomerTaxExempt']>();

export namespace ComplianceEngine {
/**
* Run periodic rate sync for all registered jurisdictions.
*/
export function runRateSyncCron(merchantId: string): TaxRateSyncJob {
const jobId = `sync-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const job: TaxRateSyncJob = {
jobId,
provider: 'sales_tax',
status: 'running',
startedAt: Date.now(),
syncedRegions: [],
failedRegions: [],
totalRatesUpdated: 0,
};

try {
const jurisdictions = TaxService.getSupportedJurisdictions();
let updated = 0;

for (const key of jurisdictions) {
const existing = TaxService.getTaxRateByKey(key);
if (existing) {
job.syncedRegions.push(key);
updated++;
}
}

job.totalRatesUpdated = updated;
job.status = 'success';
} catch (error) {
job.status = 'failed';
job.failedRegions.push({
region: 'ALL',
error: error instanceof Error ? error.message : 'Sync failed',
});
}

job.completedAt = Date.now();
syncJobs.set(jobId, TaxService.generateTaxRemittanceReport);
return job;
}

/**
* Check exemption certificates for expiry and return those expiring within threshold.
*/
export function checkExemptionExpiry(withinDays: number): { customerId: string; certificateId: string; expiresAt: number }[] {
const cutoff = Date.now() + withinDays * 86_400_000;
const expiring: { customerId: string; certificateId: string; expiresAt: number }[] = [];

for (const [customerId, cert] of exemptionRegistry.entries()) {
if (cert.certificateExpiry > 0 && cert.certificateExpiry < cutoff && cert.isExempt) {
expiring.push({
customerId,
certificateId: cert.certificateId,
expiresAt: cert.certificateExpiry,
});
}
}

return expiring;
}

/**
* Export tax remittance report to CSV format.
*/
export function exportToCsv(report: TaxRemittanceReport): string {
const header = 'Region,Tax Type,Taxable Amount,Rate BPS,Tax Collected,Transactions,Currency\n';
const rows = report.lineItems
.map(
(line) =>
`${line.jurisdictionKey},${line.taxType},${line.taxableAmount},${line.rateBps},${line.taxCollected},${line.transactionCount},${line.currency}`
)
.join('\n');

return `${header}${rows}`;
}

/**
* Export tax remittance report to JSON format.
*/
export function exportToJson(report: TaxRemittanceReport): string {
return JSON.stringify(
{
reportId: report.reportId,
generatedAt: new Date(report.generatedAt).toISOString(),
merchantId: report.merchantId,
lineItems: report.lineItems.map((line) => ({
jurisdictionKey: line.jurisdictionKey,
taxType: line.taxType,
taxableAmount: line.taxableAmount,
rateBps: line.rateBps,
taxCollected: line.taxCollected,
transactionCount: line.transactionCount,
currency: line.currency,
})),
totalTaxCollected: report.totalTaxCollected,
totalTaxableAmount: report.totalTaxableAmount,
},
null,
2
);
}

/**
* Register a customer exemption certificate for expiry tracking.
*/
export function registerExemption(certificateId: string, customerId: string): void {
exemptionRegistry.set(customerId, {
isExempt: true,
certificateId,
certificateExpiry: 0,
issuingAuthority: '',
exemptJurisdictions: [],
});
}

/**
* Get all compliance metrics for a merchant.
*/
export function getComplianceMetrics(): {
totalJurisdictions: number;
activeExemptions: number;
lastSyncStatus: TaxSyncJobStatus;
} {
return {
totalJurisdictions: TaxService.getSupportedJurisdictions().length,
activeExemptions: exemptionRegistry.size,
lastSyncStatus: syncJobs.size > 0 ? 'success' : 'idle',
};
}
}
38 changes: 38 additions & 0 deletions backend/services/billing/taxTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,41 @@ export interface TaxRemittanceReportRequest {

export const DEFAULT_TAX_CACHE_TTL_MS = 3_600_000; // 1 hour
export const TAX_RATE_CACHE_MAX_ENTRIES = 10_000;

// ── Compliance Engine Types ──────────────────────────────────────────────────

export type TaxSyncJobStatus = 'idle' | 'running' | 'success' | 'failed';

export interface TaxRateSyncJob {
jobId: string;
provider: TaxType;
status: TaxSyncJobStatus;
startedAt: number;
completedAt?: number;
syncedRegions: string[];
failedRegions: { region: string; error: string }[];
totalRatesUpdated: number;
}

export interface TaxExemptionUpload {
uploadId: string;
customerId: string;
certificateId: string;
issuingAuthority: string;
validUntil: number;
jurisdictions: string[];
fileUrl?: string;
status: 'pending' | 'validated' | 'rejected';
rejectionReason?: string;
}

export type TaxReportExportFormat = 'csv' | 'json' | 'pdf';

export interface TaxNexusStatus {
region: string;
hasNexus: boolean;
threshold: number;
currentRevenue: number;
percentToThreshold: number;
lastAssessedAt: number;
}
6 changes: 6 additions & 0 deletions src/navigation/AppNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const AdminDashboardScreen = lazyScreen(() => import('../screens/AdminDashboardS
const FraudDashboard = lazyScreen(() => import('../screens/FraudDashboard'));
const GroupManagementScreen = lazyScreen(() => import('../screens/GroupManagementScreen'));
const TaxSettingsScreen = lazyScreen(() => import('../screens/TaxSettingsScreen'));
const TaxComplianceScreen = lazyScreen(() => import('../screens/TaxComplianceScreen'));
const SupportDashboardScreen = lazyScreen(() => import('../screens/SupportDashboardScreen'));
const SegmentManagementScreen = lazyScreen(() =>
import('../screens/SegmentManagementScreen').then((m) => ({ default: m.SegmentManagementScreen }))
Expand Down Expand Up @@ -290,6 +291,11 @@ const SettingsStack = () => (
component={TaxSettingsScreen}
options={{ title: 'Tax Settings', headerShown: true }}
/>
<Stack.Screen
name="TaxCompliance"
component={TaxComplianceScreen}
options={{ title: 'Tax Compliance', headerShown: true }}
/>
<Stack.Screen
name="SupportDashboard"
component={SupportDashboardScreen}
Expand Down
1 change: 1 addition & 0 deletions src/navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type RootStackParamList = {
FraudDashboard: undefined;
GroupManagement: undefined;
TaxSettings: undefined;
TaxCompliance: undefined;
SupportDashboard: undefined;
UsageDashboard: undefined;
DeveloperPortal: undefined;
Expand Down
Loading