diff --git a/apps/edr-freight-api/.env.example b/apps/edr-freight-api/.env.example
index 73e122a1d..73312aae3 100644
--- a/apps/edr-freight-api/.env.example
+++ b/apps/edr-freight-api/.env.example
@@ -19,6 +19,11 @@ TELEBIRR_TIMEOUT_EXPRESS=15m
TELEBIRR_PRIVATE_KEY=
TELEBIRR_PUBLIC_KEY=
TELEBIRR_INSECURE_TLS=false
+
+# Portal pages the payment provider redirects the browser to after payment.
+# Point these at the freight portal's public payment result routes.
+PAYMENT_RETURN_URL=http://localhost:5173/payment/success
+PAYMENT_FAILURE_URL=http://localhost:5173/payment/failure
# JWT (used by @tria-plc/api-common SharedAuthModule)
JWT_SECRET=
JWT_ACCESS_TOKEN_SECRET=
diff --git a/apps/edr-freight-api/src/modules/payment/payment.service.ts b/apps/edr-freight-api/src/modules/payment/payment.service.ts
index 5421a841e..177b7475b 100644
--- a/apps/edr-freight-api/src/modules/payment/payment.service.ts
+++ b/apps/edr-freight-api/src/modules/payment/payment.service.ts
@@ -157,8 +157,8 @@ export class PaymentService {
provider: dto.method as unknown as ProviderMethod,
platform: dto.platform,
payerAccount: dto.payerAccount,
- returnUrl: dto.returnUrl ?? process.env.PAYMENT_RETURN_URL,
- failureUrl: dto.failureUrl ?? process.env.PAYMENT_FAILURE_URL,
+ returnUrl:'https://edrfreight.triaplc.com/payment/success',
+ failureUrl: 'https://edrfreight.triaplc.com/payment/failure',
});
const intent = await this.syncIntentProjection(booking.id, booking, snapshot);
diff --git a/apps/edr-freight-api/src/modules/scheduling-reschedule/scheduling-reschedule.service.ts b/apps/edr-freight-api/src/modules/scheduling-reschedule/scheduling-reschedule.service.ts
index 7191a203e..dd20b100d 100644
--- a/apps/edr-freight-api/src/modules/scheduling-reschedule/scheduling-reschedule.service.ts
+++ b/apps/edr-freight-api/src/modules/scheduling-reschedule/scheduling-reschedule.service.ts
@@ -49,8 +49,17 @@ export class SchedulingRescheduleService {
if (!schedule) {
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
}
+ // A train can be rescheduled (with or without bookings) at any time UNLESS it
+ // is already on the move (DISPATCHED), has completed its run (ARRIVED), or was
+ // cancelled. Only DRAFT / SCHEDULED trains are reschedulable.
if (schedule.status === TrainScheduleStatus.Dispatched) {
- throw new BadRequestException('Cannot reschedule a dispatched train');
+ throw new BadRequestException('Cannot reschedule a train that is already dispatched');
+ }
+ if (schedule.status === TrainScheduleStatus.Arrived) {
+ throw new BadRequestException('Cannot reschedule a train that has already arrived');
+ }
+ if (schedule.status === TrainScheduleStatus.Cancelled) {
+ throw new BadRequestException('Cannot reschedule a cancelled train');
}
const currentOnSchedule = (schedule.scheduleBookings ?? [])
@@ -156,10 +165,24 @@ export class SchedulingRescheduleService {
}
}
- const assignResult = await this.trainSchedulingService.assignBookingsToSchedule(scheduleId, {
- bookingIds: dto.finalBookingIds,
- forceAssign: dto.trigger === 'GOVERNMENT_PREEMPT',
- });
+ // A train can be rescheduled even with no bookings (e.g. moved for
+ // maintenance). assignBookingsToSchedule requires at least one booking, so
+ // only call it when something is actually being (re)assigned — the new
+ // departure date above is the meaningful change for an empty train. The
+ // empty-train branch returns the same schedule-detail shape as the assign
+ // path so callers get a consistent response.
+ const assignResult = dto.finalBookingIds.length
+ ? await this.trainSchedulingService.assignBookingsToSchedule(scheduleId, {
+ bookingIds: dto.finalBookingIds,
+ forceAssign: dto.trigger === 'GOVERNMENT_PREEMPT',
+ })
+ : {
+ ...(await this.trainSchedulingService.getContainerTrainScheduleById(
+ scheduleId,
+ )),
+ warnings: [] as string[],
+ deferredBookings: [] as unknown[],
+ };
await this.schedulingRescheduleRepository.createEvent({
trainScheduleId: scheduleId,
diff --git a/apps/edr-freight-web/portal/src/App.tsx b/apps/edr-freight-web/portal/src/App.tsx
index 9938daff9..a4c2c8553 100644
--- a/apps/edr-freight-web/portal/src/App.tsx
+++ b/apps/edr-freight-web/portal/src/App.tsx
@@ -36,6 +36,8 @@ import EditBookingPage from "./pages/bookings/EditBookingPage";
import MyBookings from "./pages/bookings/MyBookings";
import NewBookingPage from "./pages/bookings/NewBookingPage";
import CheckPaymentPage from "./pages/payments/CheckPaymentPage";
+import PaymentSuccessPage from "./pages/payments/PaymentSuccessPage";
+import PaymentFailurePage from "./pages/payments/PaymentFailurePage";
import TrackingPage from "./pages/tracking/TrackingPage";
function FullScreenSpinner() {
@@ -158,6 +160,9 @@ const App = () => {
path="/booking/check-status/:orderId"
element={
+ Payment was not completed +
++ Your payment didn't go through and you haven't been charged. You can + try again from your booking using "Pay now". +
++ Payment successful +
++ Thank you — your payment has been received. Your booking will be + updated shortly and is now confirmed for scheduling. +
+