import { Body,Controller,Get,Post,Req,UseGuards } from '@nestjs/common';
import { AuthGuard } from '../../../common/auth/auth.guard';
import { OrganizationContextGuard,type TenantScopedRequest } from '../../../common/auth/organization-context.guard';
import { PermissionGuard } from '../../../common/auth/permission.guard';
import { RequirePermissions } from '../../../common/auth/permissions.decorator';
import { CommercialService } from '../application/commercial.service';
import { BillingWebhookService } from '../webhooks/billing-webhook.service';
import { SubscriptionLifecycleWorker } from '../workers/subscription-lifecycle.worker';
import { SubscriptionCommandService } from '../application/subscription-command.service';
import { ZodValidationPipe } from '../../../common/validation/zod-validation.pipe';
import { commercialCancelSchema,commercialCheckoutSchema,commercialPlanChangeSchema,commercialPortalSchema } from '../../../common/validation/critical-write.schemas';
import { BillingHistoryService } from '../application/billing-history.service';
import { BillingReconciliationWorker } from '../workers/billing-reconciliation.worker';
import { ScheduledPlanChangeWorker } from '../workers/scheduled-plan-change.worker';
import type { z } from 'zod';
@Controller('/commercial')
@UseGuards(AuthGuard,OrganizationContextGuard,PermissionGuard)
export class CommercialController {
constructor(
private readonly commercial:CommercialService,
private readonly webhooks:BillingWebhookService,
private readonly lifecycle:SubscriptionLifecycleWorker,
private readonly commands:SubscriptionCommandService,
private readonly history:BillingHistoryService,
private readonly reconciliation:BillingReconciliationWorker,
private readonly scheduled:ScheduledPlanChangeWorker,
) {}
@Get('/subscription')
@RequirePermissions('commercial.subscription.read')
view(@Req() req:TenantScopedRequest){return this.commercial.view(req.organizationId!);}
@Get('/entitlements')
@RequirePermissions('commercial.entitlements.read')
entitlements(@Req() req:TenantScopedRequest){
return this.commercial.view(req.organizationId!).then(x=>x.entitlements);
}
@Get('/usage')
@RequirePermissions('commercial.entitlements.read')
usage(@Req() req:TenantScopedRequest){
return this.commercial.view(req.organizationId!).then(x=>x.usage);
}
@Get('/billing-history')
@RequirePermissions('commercial.billing.read')
historyList(@Req() req:TenantScopedRequest){return this.history.list(req.organizationId!);}
@Post('/checkout')
@RequirePermissions('commercial.subscription.manage')
checkout(@Req() req:TenantScopedRequest,@Body(new ZodValidationPipe(commercialCheckoutSchema)) body:z.infer<typeof commercialCheckoutSchema>){
return this.commercial.checkout({
organizationId:req.organizationId!,planVersionId:body.planVersionId,
successUrl:body.successUrl,cancelUrl:body.cancelUrl,
billingEmail:body.billingEmail,companyName:body.companyName,userId:req.auth?.userId,
});
}
@Post('/billing-portal')
@RequirePermissions('commercial.subscription.manage')
portal(@Req() req:TenantScopedRequest,@Body(new ZodValidationPipe(commercialPortalSchema)) body:z.infer<typeof commercialPortalSchema>){
return this.commercial.portal({organizationId:req.organizationId!,returnUrl:body.returnUrl});
}
@Post('/change-plan/preview')
@RequirePermissions('commercial.subscription.manage')
preview(@Req() req:TenantScopedRequest,@Body(new ZodValidationPipe(commercialPlanChangeSchema)) body:z.infer<typeof commercialPlanChangeSchema>){
return this.commercial.previewPlanChange({
organizationId:req.organizationId!,toPlanVersionId:body.toPlanVersionId,
});
}
@Post('/change-plan')
@RequirePermissions('commercial.subscription.manage')
change(@Req() req:TenantScopedRequest,@Body(new ZodValidationPipe(commercialPlanChangeSchema)) body:z.infer<typeof commercialPlanChangeSchema>){
return this.commands.changePlan({
organizationId:req.organizationId!,toPlanVersionId:body.toPlanVersionId,userId:req.auth?.userId,
});
}
@Post('/cancel')
@RequirePermissions('commercial.subscription.manage')
cancel(@Req() req:TenantScopedRequest,@Body(new ZodValidationPipe(commercialCancelSchema)) body:z.infer<typeof commercialCancelSchema>){
return this.commands.cancel({
organizationId:req.organizationId!,atPeriodEnd:body.atPeriodEnd!==false,userId:req.auth?.userId,
});
}
@Post('/refresh')
@RequirePermissions('commercial.entitlements.read')
refresh(@Req() req:TenantScopedRequest){
return this.commercial.refreshUsageAndEntitlements(req.organizationId!);
}
@Post('/reconcile')
@RequirePermissions('commercial.subscription.manage')
reconcile(@Req() req:TenantScopedRequest){
return this.reconciliation.reconcileOrganization(req.organizationId!);
}
@Post('/workers/billing-events')
@RequirePermissions('commercial.subscription.manage')
billingEvents(){return this.webhooks.processBatch();}
@Post('/workers/lifecycle')
@RequirePermissions('commercial.subscription.manage')
lifecycleRun(){return this.lifecycle.run();}
@Post('/workers/scheduled-plan-changes')
@RequirePermissions('commercial.subscription.manage')
scheduledRun(){return this.scheduled.runBatch();}
}
