diff --git a/src/escrow/escrow.controller.ts b/src/escrow/escrow.controller.ts index 8387138..16683bb 100644 --- a/src/escrow/escrow.controller.ts +++ b/src/escrow/escrow.controller.ts @@ -5,6 +5,7 @@ import { FundEscrowDto } from './dto/fund-escrow.dto'; import { ReleaseEscrowDto } from './dto/release-escrow.dto'; import { SplitReleaseDto } from './dto/split-release.dto'; import { toPublicEscrow } from './escrow-response.mapper'; +import { toPublicPayments } from './payment-response.mapper'; import { Idempotent } from '../common/idempotency/idempotent.decorator'; @ApiTags('escrow') @@ -37,8 +38,10 @@ export class EscrowController { @Idempotent('escrow.splitRelease') @Post(':id/split-release') - splitRelease(@Param('id') id: string, @Body() dto: SplitReleaseDto) { - return this.escrowService.splitRelease(id, dto.recipients); + async splitRelease(@Param('id') id: string, @Body() dto: SplitReleaseDto) { + return toPublicPayments( + await this.escrowService.splitRelease(id, dto.recipients), + ); } @Idempotent('escrow.refund') diff --git a/src/escrow/payment-response.mapper.spec.ts b/src/escrow/payment-response.mapper.spec.ts new file mode 100644 index 0000000..74d6a9b --- /dev/null +++ b/src/escrow/payment-response.mapper.spec.ts @@ -0,0 +1,52 @@ +import { Payment } from '../common/entities'; +import { AssetType, PaymentStatus } from '../common/enums'; +import { toPublicPayment, toPublicPayments } from './payment-response.mapper'; + +function makePayment(overrides: Partial = {}): Payment { + return { + id: 'pay_1', + escrow: null as unknown as Payment['escrow'], + escrowId: 'esc_1', + recipient: null, + recipientId: 'user_1', + recipientAddress: 'GRECIPIENT', + amount: '50.0000000', + asset: AssetType.USDC, + splitPercentage: '50.00', + status: PaymentStatus.PENDING, + txHash: null, + createdAt: new Date(), + updatedAt: new Date(), + ...overrides, + }; +} + +describe('toPublicPayment', () => { + it('preserves every field unchanged', () => { + const payment = makePayment(); + + const publicPayment = toPublicPayment(payment); + + expect(publicPayment).toMatchObject({ + id: 'pay_1', + escrowId: 'esc_1', + recipientId: 'user_1', + amount: '50.0000000', + status: PaymentStatus.PENDING, + }); + }); +}); + +describe('toPublicPayments', () => { + it('maps every payment in the array', () => { + const payments = [ + makePayment({ id: 'pay_1' }), + makePayment({ id: 'pay_2' }), + ]; + + const publicPayments = toPublicPayments(payments); + + expect(publicPayments).toHaveLength(2); + expect(publicPayments.map((p) => p.id)).toEqual(['pay_1', 'pay_2']); + }); +}); diff --git a/src/escrow/payment-response.mapper.ts b/src/escrow/payment-response.mapper.ts new file mode 100644 index 0000000..76e39cf --- /dev/null +++ b/src/escrow/payment-response.mapper.ts @@ -0,0 +1,18 @@ +import { Payment } from '../common/entities'; + +export type PublicPayment = Payment; + +/** + * Thin passthrough today — Payment doesn't carry anything as sensitive as + * Escrow.metadata yet. Exists so splitRelease follows the same + * controller-boundary mapping pattern as every other escrow endpoint + * (see toPublicEscrow), so a future internal-only field added to Payment + * has somewhere to be stripped instead of leaking by default. + */ +export function toPublicPayment(payment: Payment): PublicPayment { + return payment; +} + +export function toPublicPayments(payments: Payment[]): PublicPayment[] { + return payments.map(toPublicPayment); +}