Files
edr-platform/apps/edr-passenger-api/src/modules/payments/payments.dto.ts
2026-08-07 13:48:58 +03:00

223 lines
7.4 KiB
TypeScript

import {
IsString,
IsEnum,
IsOptional,
IsIn,
IsBoolean,
IsInt,
} from "class-validator";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { PaymentIntentStatus } from "@prisma/client";
export enum PaymentRegionEnum {
ETHIOPIA = "ETHIOPIA",
DJIBOUTI = "DJIBOUTI",
INTERNATIONAL = "INTERNATIONAL",
GLOBAL = "GLOBAL",
}
export enum PaymentMethodTypeEnum {
TELEBIRR = "TELEBIRR", // Ethiopia
CBE_BIRR = "CBE_BIRR", // Ethiopia
EBIRR = "EBIRR", // Ethiopia
WAAFI = "WAAFI",
DMONEY= "DMONEY",// Djibouti
CAC_BANK = "CAC_BANK", // Djibouti (OTP debit)
CARD = "CARD", // International
WALLET = "WALLET", // Internal
CBE_BILL = "CBE_BILL", // Ethiopia (pay at any CBE channel by bill number)
}
/** Mirrors `PaymentPlatform` in @edr/types — see there for what each surface means. */
export type PaymentPlatformDto = "web" | "mobile" | "inapp";
export class InitiatePaymentDto {
@ApiProperty({ example: "booking-uuid" }) @IsString() bookingId: string;
@ApiProperty({
enum: PaymentMethodTypeEnum,
description:
"Payment method: TELEBIRR/CBE_BIRR/EBIRR (Ethiopia), WAAFI (Djibouti), CARD (International), WALLET (Internal)",
example: "TELEBIRR",
})
@IsEnum(PaymentMethodTypeEnum)
method: PaymentMethodTypeEnum;
@ApiPropertyOptional({ description: "Saved payment method ID (optional)" })
@IsOptional()
@IsString()
paymentMethodId?: string;
@ApiPropertyOptional({
enum: ["web", "mobile", "inapp"],
default: "web",
description:
"Payer surface. `inapp` = the portal is running inside a SuperApp mini-app WebView " +
"(Telebirr), which cannot follow redirect flows and gets a bridge payload instead.",
})
@IsOptional()
@IsIn(["web", "mobile", "inapp"])
platform?: PaymentPlatformDto;
@ApiPropertyOptional({
description:
"Payer account / mobile number. Required for OTP-debit methods (CAC_BANK) — " +
"the bank sends the OTP to this number.",
example: "77112233",
})
@IsOptional()
@IsString()
payerAccount?: string;
}
export class ConfirmOtpDto {
@ApiProperty({
description: "One-time password the payer received by SMS (e.g. CAC Bank).",
example: "4530",
})
@IsString()
otp: string;
}
export class RefundDto {
@ApiProperty() @IsString() bookingId: string;
@ApiPropertyOptional() @IsOptional() @IsString() reason?: string;
}
export class AddPaymentMethodDto {
@ApiProperty({ enum: PaymentMethodTypeEnum })
@IsEnum(PaymentMethodTypeEnum)
type: PaymentMethodTypeEnum;
@ApiProperty() @IsString() displayName: string;
@ApiProperty({ enum: PaymentRegionEnum })
@IsEnum(PaymentRegionEnum)
region: PaymentRegionEnum;
@ApiPropertyOptional({ example: "ETB" })
@IsOptional()
@IsString()
currency?: string;
@ApiPropertyOptional() @IsOptional() @IsString() providerId?: string;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
enabled?: boolean;
@ApiPropertyOptional({ default: 0 })
@IsOptional()
@IsInt()
sortOrder?: number;
}
export class SupportedPaymentMethodDto {
@ApiProperty({ enum: PaymentMethodTypeEnum }) type: PaymentMethodTypeEnum;
@ApiProperty({ example: "Telebirr" }) displayName: string;
@ApiProperty({ enum: PaymentRegionEnum }) region: PaymentRegionEnum;
@ApiProperty({
example: "ETB",
description: "Settlement currency for this method",
})
currency: string;
@ApiProperty({
description: "Whether the platform currently accepts this method",
})
enabled: boolean;
}
export class ClientActionDto {
@ApiProperty({
enum: [
"REDIRECT",
"LAUNCH_APP",
"INVOKE_BRIDGE",
"COLLECT_OTP",
"SHOW_BILL_REFERENCE",
],
})
type:
| "REDIRECT"
| "LAUNCH_APP"
| "INVOKE_BRIDGE"
| "COLLECT_OTP"
| "SHOW_BILL_REFERENCE";
@ApiPropertyOptional({ description: "Set when type=REDIRECT (web flow)" })
url?: string;
@ApiPropertyOptional({
description: "Set when type=LAUNCH_APP (mobile flow)",
})
prepayId?: string;
@ApiPropertyOptional({
description: "Set when type=LAUNCH_APP (mobile flow)",
})
receiveCode?: string;
@ApiPropertyOptional({
description: "Set when type=LAUNCH_APP (mobile flow)",
})
shortCode?: string;
@ApiPropertyOptional({
description:
"Set when type=INVOKE_BRIDGE (telebirr mini app) — which SuperApp host bridge to call",
enum: ["TELEBIRR"],
})
bridge?: "TELEBIRR";
@ApiPropertyOptional({
description:
"Set when type=INVOKE_BRIDGE (telebirr mini app). Signed query string handed verbatim " +
"to the host bridge (js_fun_start_pay). NOT a URL — never navigate to it.",
})
rawRequest?: string;
@ApiPropertyOptional({ description: "Set when type=COLLECT_OTP (e.g. CAC Bank)" })
providerOrderId?: string;
@ApiPropertyOptional({ description: "Set when type=COLLECT_OTP" })
message?: string;
@ApiPropertyOptional({
description: "Set when type=SHOW_BILL_REFERENCE (CBE bill payment)",
})
billReference?: string;
@ApiPropertyOptional({ description: "Set when type=SHOW_BILL_REFERENCE" })
instructions?: string;
@ApiPropertyOptional({ description: "Set when type=SHOW_BILL_REFERENCE" })
expiresAt?: string;
}
export class InitiateResponseDto {
@ApiProperty() intentId: string;
@ApiProperty({ enum: PaymentIntentStatus }) status: PaymentIntentStatus;
@ApiPropertyOptional({ type: ClientActionDto })
clientAction?: ClientActionDto;
@ApiPropertyOptional() merchantOrderId?: string;
/** When this payment session stops being offered — PAYMENT_SESSION_MINUTES from initiation, capped at paymentDeadline. Drives the client-side countdown. */
@ApiPropertyOptional() sessionExpiresAt?: string;
/** The booking's payment deadline: after it, the booking is auto-cancelled. */
@ApiPropertyOptional() paymentDeadline?: string;
}
export class IntentStatusDto {
@ApiProperty() intentId: string;
@ApiProperty({ enum: PaymentIntentStatus }) status: PaymentIntentStatus;
@ApiPropertyOptional({ type: ClientActionDto })
clientAction?: ClientActionDto;
@ApiPropertyOptional() merchantOrderId?: string;
@ApiPropertyOptional() paidAt?: string;
@ApiPropertyOptional() failureCode?: string;
@ApiPropertyOptional() failureMessage?: string;
@ApiPropertyOptional({
type: "object",
additionalProperties: true,
description:
"Raw provider payload (initiation response merged with the latest status query) for inspection/debugging. Provider-specific shape; never trusted for state.",
})
providerResponse?: Record<string, unknown>;
}
export class BookingAmountResponseDto {
@ApiProperty({ example: 'booking-uuid' }) booking_id: string;
@ApiProperty({ example: 'DJF', description: 'Currency of the returned amount' }) currency: string;
@ApiProperty({ example: 162.5, description: 'Booking total converted to the requested currency (major units)' }) amount: number;
}
export class ForceConfirmDto {
@ApiPropertyOptional({ description: 'External payment reference / transaction ID from the vendor', example: 'TXN-123456' })
@IsOptional() @IsString() paymentReference?: string;
@ApiPropertyOptional({ enum: PaymentMethodTypeEnum, description: 'Payment method used externally', example: 'TELEBIRR' })
@IsOptional() @IsEnum(PaymentMethodTypeEnum) paymentMethod?: PaymentMethodTypeEnum;
@ApiPropertyOptional({ description: 'Internal notes about why this was force-confirmed', example: 'Vendor confirmed via phone' })
@IsOptional() @IsString() notes?: string;
}