Skip to content

Commit 554dbbc

Browse files
authored
Merge pull request #1426 from KeNgKawthar/No-API-client
No api client
2 parents c26457b + e31e759 commit 554dbbc

5 files changed

Lines changed: 87 additions & 2 deletions

File tree

backend/src/payments/payments.controller.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
Body,
33
Controller,
4+
Get,
45
Param,
56
ParseUUIDPipe,
67
Post,
@@ -10,6 +11,7 @@ import {
1011
ApiBearerAuth,
1112
ApiConflictResponse,
1213
ApiForbiddenResponse,
14+
ApiOkResponse,
1315
ApiOperation,
1416
ApiTags,
1517
ApiUnprocessableEntityResponse,
@@ -20,18 +22,36 @@ import { RolesGuard } from '../auth/guards/roles.guard';
2022
import { UserRole } from '../common/enums/role.enum';
2123
import { User } from '../users/entities/user.entity';
2224
import { PaymentsService } from './payments.service';
25+
import { Payment } from './entities/payment.entity';
2326
import { SubmitPaymentDto } from './dto/submit-payment.dto';
2427
import { TestSignAndSubmitPaymentDto } from './dto/test-sign-and-submit-payment.dto';
2528

2629
@ApiTags('payments')
2730
@ApiBearerAuth()
2831
@Controller('shipments/:shipmentId/payment')
29-
@UseGuards(RolesGuard)
30-
@Roles(UserRole.SHIPPER, UserRole.ADMIN)
3132
export class PaymentsController {
3233
constructor(private readonly paymentsService: PaymentsService) {}
3334

35+
@Get()
36+
@ApiOperation({
37+
summary: 'Get payment/escrow status for a shipment',
38+
description:
39+
'Returns the escrow payment record if one exists for this shipment. Accessible to the shipper, carrier, and admin.',
40+
})
41+
@ApiOkResponse({ description: 'Payment record found', type: Payment })
42+
@ApiOperation({ summary: 'Get escrow status for a shipment' })
43+
@UseGuards(RolesGuard)
44+
@Roles(UserRole.SHIPPER, UserRole.CARRIER, UserRole.ADMIN)
45+
getStatus(
46+
@Param('shipmentId', ParseUUIDPipe) shipmentId: string,
47+
@CurrentUser() user: User,
48+
) {
49+
return this.paymentsService.findByShipmentIdForUser(shipmentId, user);
50+
}
51+
3452
@Post()
53+
@UseGuards(RolesGuard)
54+
@Roles(UserRole.SHIPPER, UserRole.ADMIN)
3555
@ApiOperation({
3656
summary:
3757
'Build an unsigned funding transaction for an accepted shipment (shipper only)',

backend/src/payments/payments.service.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export interface SubmitFundingResult {
4141
stellarTxHash: string | null;
4242
}
4343

44+
export interface PaymentResponse {
45+
payment: Payment | null;
46+
}
47+
4448
// A payment beyond this point is either funded or otherwise terminal for
4549
// this issue's purposes — funding it again (or funding a still-in-flight
4650
// one) is always a 4xx, never a fresh chain call.
@@ -65,6 +69,26 @@ export class PaymentsService {
6569
private readonly config: ConfigService,
6670
) {}
6771

72+
async findByShipmentIdForUser(
73+
shipmentId: string,
74+
user: User,
75+
): Promise<PaymentResponse> {
76+
const shipment = await this.getShipment(shipmentId);
77+
78+
const isShipper = shipment.shipperId === user.id;
79+
const isCarrier = shipment.carrierId === user.id;
80+
const isAdmin = user.role === 'admin';
81+
82+
if (!isShipper && !isCarrier && !isAdmin) {
83+
throw new ForbiddenException(
84+
'You do not have access to this shipment payment',
85+
);
86+
}
87+
88+
const payment = await this.findByShipmentId(shipmentId);
89+
return { payment };
90+
}
91+
6892
async getOrCreatePayment(
6993
shipmentId: string,
7094
amount: number,

frontend/app/(dashboard)/shipments/[id]/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { useCallback, useEffect, useState } from 'react';
44
import { useParams, useRouter } from 'next/navigation';
55
import { toast } from 'sonner';
66
import { Shipment, ShipmentStatus, ShipmentStatusHistory } from '../../../../types/shipment.types';
7+
import { Payment, PaymentStatus } from '../../../../types/payment.types';
78
import { shipmentApi } from '../../../../lib/api/shipment.api';
9+
import { paymentApi } from '../../../../lib/api/payments.api';
810
import { useAuthStore } from '../../../../stores/auth.store';
911
import { StatusBadge } from '../../../../components/shipment/status-badge';
1012
import { StatusTimeline } from '../../../../components/shipment/status-timeline';
@@ -18,7 +20,9 @@ export default function ShipmentDetailPage() {
1820

1921
const [shipment, setShipment] = useState<Shipment | null>(null);
2022
const [history, setHistory] = useState<ShipmentStatusHistory[]>([]);
23+
const [payment, setPayment] = useState<Payment | null>(null);
2124
const [loading, setLoading] = useState(true);
25+
const [paymentLoading, setPaymentLoading] = useState(false);
2226
const [actionLoading, setActionLoading] = useState(false);
2327

2428
const reload = useCallback(async () => {

frontend/lib/api/payments.api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { apiClient } from './client';
2+
import { Payment, PaymentStatus } from '../../types/payment.types';
3+
4+
export const paymentApi = {
5+
getByShipmentId(shipmentId: string): Promise<Payment | null> {
6+
return apiClient<{ payment: Payment | null }>(
7+
`/shipments/${shipmentId}/payment`,
8+
).then((data) => data.payment);
9+
},
10+
};

frontend/types/payment.types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export enum PaymentStatus {
2+
PENDING = 'pending',
3+
FUNDING = 'funding',
4+
FUNDED = 'funded',
5+
RELEASED = 'released',
6+
REFUNDED = 'refunded',
7+
DISPUTED = 'disputed',
8+
CANCELLED = 'cancelled',
9+
}
10+
11+
export interface Payment {
12+
id: string;
13+
shipmentId: string;
14+
onChainShipmentId: number;
15+
status: PaymentStatus;
16+
amount: number;
17+
assetCode: string;
18+
tokenContractAddress: string | null;
19+
shipperWalletAddress: string | null;
20+
carrierWalletAddress: string | null;
21+
fundedAt: string | null;
22+
settledAt: string | null;
23+
stellarTxHash: string | null;
24+
failureReason: string | null;
25+
createdAt: string;
26+
updatedAt: string;
27+
}

0 commit comments

Comments
 (0)