Files
edr-platform/apps/edr-freight-api/src/modules/payment/payment.controller.ts
Nathnael d4917bc6ec feat(auth): regate payment, booking and wagon reads
Payment summary/all moved off bookings:view onto payments:view, the
booking list now requires bookings:view for staff instead of accepting
any employee, and wagon reads require wagons:view or fleet:view rather
than bare authentication.
2026-08-07 07:41:00 +00:00

108 lines
3.9 KiB
TypeScript

import {
Controller,
Get,
HttpStatus,
Param,
ParseUUIDPipe,
Post,
Query,
Res,
} from "@nestjs/common";
import {
ApiTags,
ApiOperation,
ApiQuery,
ApiOkResponse,
ApiProduces,
} from "@nestjs/swagger";
import { Response } from "express";
import { CurrentUser, Public } from "@edr/api-common";
import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type";
import { BookingStaff, MixedAudience, PortalCustomer } from "../../common/booking-guards";
import { UserTradeAccessService } from "../user-trade-access/user-trade-access.service";
import { FREIGHT_PERMS } from "../../seed/freight-permissions.registry";
import { PaymentService } from "./payment.service";
import { IntentStatusDto } from "./payments.dto";
@ApiTags("Payment")
@Controller("payments")
export class PaymentController {
constructor(
private readonly paymentService: PaymentService,
private readonly userTradeAccessService: UserTradeAccessService,
) { }
// Customer-detail payments tab — same one-of rule as the bookings tab.
@Get("by-company/:companyId/customer-view")
@BookingStaff([FREIGHT_PERMS.customers.view, FREIGHT_PERMS.payments.view])
@ApiOperation({ summary: "List payments for a company (customer-view shape, backoffice)" })
findByCompanyCustomerView(
@Param("companyId", ParseUUIDPipe) companyId: string,
) {
return this.paymentService.findByCompanyId(companyId);
}
@Get("summary")
@BookingStaff(FREIGHT_PERMS.payments.view)
@ApiOperation({ summary: "Payment count/amount summary for dashboard cards" })
getSummary() {
return this.paymentService.getSummary();
}
@Get("all")
@BookingStaff(FREIGHT_PERMS.payments.view)
@ApiOperation({ summary: "Get all payments with filters (view-only, any staff)" })
@ApiQuery({ name: "search", required: false })
@ApiQuery({ name: "status", required: false })
@ApiQuery({ name: "method", required: false })
@ApiQuery({ name: "page", required: false })
@ApiQuery({ name: "pageSize", required: false })
async getAll(
@CurrentUser() user: TCurrentUser,
@Query("search") search?: string,
@Query("status") status?: string,
@Query("method") method?: string,
@Query("page") page?: string,
@Query("pageSize") pageSize?: string,
) {
// Per-user trade-direction scope, applied via the booking in ref_id.
const allowed =
await this.userTradeAccessService.resolveAllowedDirections(user);
return this.paymentService.getAll({
search,
status,
method,
page: page ? parseInt(page) : 1,
pageSize: pageSize ? parseInt(pageSize) : 10,
tradeDirections: allowed ?? undefined,
});
}
@Get("intents/:bookingId")
@MixedAudience(FREIGHT_PERMS.payments.view)
@ApiOperation({ summary: "Get payment intent status for a booking" })
@ApiOkResponse({ type: IntentStatusDto })
getIntent(@Param("bookingId") bookingId: string) {
return this.paymentService.getIntentByBookingId(bookingId);
}
@Post("redirect-success/:bookingId")
@PortalCustomer()
@ApiOperation({
summary:
"Success-redirect ack: mark payment processing + invoice PAYMENT_PROCESSING (webhook remains source of truth)",
})
acknowledgeSuccessRedirect(@Param("bookingId") bookingId: string) {
return this.paymentService.acknowledgeSuccessRedirect(bookingId);
}
@Get("receipt/:orderId")
@Public()
@ApiOperation({ summary: "Generate a payment receipt HTML page" })
@ApiProduces("text/html")
async receipt(@Param("orderId") orderId: string, @Res() res: Response) {
const html = await this.paymentService.genReceiptHtml(orderId);
return res.status(HttpStatus.OK).type("html").send(html);
}
}