mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
146 lines
4.7 KiB
TypeScript
146 lines
4.7 KiB
TypeScript
import {
|
|
All,
|
|
Body,
|
|
Controller,
|
|
Headers,
|
|
HttpCode,
|
|
HttpStatus,
|
|
Logger,
|
|
Post,
|
|
Req,
|
|
} from "@nestjs/common";
|
|
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
|
import {
|
|
CardWebhookPayload,
|
|
CbeBirrWebhookPayload,
|
|
DMoneyWebhookPayload,
|
|
EBirrWebhookPayload,
|
|
TelebirrWebhookPayload,
|
|
WaafiWebhookHeaders,
|
|
WaafiWebhookPayload,
|
|
} from "@edr/payment-providers";
|
|
import { TelebirrWebhookService } from "./handlers/telebirr-webhook.service";
|
|
import { CbeBirrWebhookService } from "./handlers/cbe-birr-webhook.service";
|
|
import { EBirrWebhookService } from "./handlers/ebirr-webhook.service";
|
|
import { CardWebhookService } from "./handlers/card-webhook.service";
|
|
import { WaafiWebhookService } from "./handlers/waafi-webhook.service";
|
|
import { DMoneyWebhookService } from "./handlers/dmoney-webhook.service";
|
|
|
|
/**
|
|
* The ONLY public surface of the payment service — the single registered webhook URL per
|
|
* provider for the whole platform. No service auth here (provider-facing); trust comes from
|
|
* signature verification inside each handler. Every route acks 2xx fast and never rethrows:
|
|
* Waafi times out at 5s and does NOT retry.
|
|
*/
|
|
@ApiTags("Provider Webhooks")
|
|
@Controller("webhooks")
|
|
export class WebhooksController {
|
|
private readonly logger = new Logger(WebhooksController.name);
|
|
|
|
constructor(
|
|
private readonly telebirr: TelebirrWebhookService,
|
|
private readonly cbeBirr: CbeBirrWebhookService,
|
|
private readonly eBirr: EBirrWebhookService,
|
|
private readonly card: CardWebhookService,
|
|
private readonly waafi: WaafiWebhookService,
|
|
private readonly dMoney: DMoneyWebhookService,
|
|
) {}
|
|
|
|
@All("telebirr")
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: "Telebirr payment notification callback (Ethiopia)",
|
|
})
|
|
async receiveTelebirr(@Body() payload: TelebirrWebhookPayload) {
|
|
this.logger.log("Telebirr webhook called");
|
|
try {
|
|
await this.telebirr.handle(payload);
|
|
} catch (err) {
|
|
this.logger.error(`Telebirr webhook handler threw: ${this.message(err)}`);
|
|
}
|
|
return { code: "0", message: "OK" };
|
|
}
|
|
|
|
@Post("cbe-birr")
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: "CBE Birr payment notification callback (Ethiopia)",
|
|
})
|
|
async receiveCbeBirr(@Body() payload: CbeBirrWebhookPayload) {
|
|
try {
|
|
await this.cbeBirr.handle(payload);
|
|
} catch (err) {
|
|
this.logger.error(`CBE Birr webhook handler threw: ${this.message(err)}`);
|
|
}
|
|
return { success: true };
|
|
}
|
|
|
|
@Post("ebirr")
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: "eBirr payment notification callback (Ethiopia)" })
|
|
async receiveEBirr(@Body() payload: EBirrWebhookPayload) {
|
|
try {
|
|
await this.eBirr.handle(payload);
|
|
} catch (err) {
|
|
this.logger.error(`eBirr webhook handler threw: ${this.message(err)}`);
|
|
}
|
|
return { code: "0000", message: "success" };
|
|
}
|
|
|
|
@Post("card")
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({
|
|
summary: "Card payment notification callback (International)",
|
|
})
|
|
async receiveCard(
|
|
@Body() payload: CardWebhookPayload,
|
|
@Headers("stripe-signature") signature: string,
|
|
) {
|
|
try {
|
|
await this.card.handle(payload, signature);
|
|
} catch (err) {
|
|
this.logger.error(`Card webhook handler threw: ${this.message(err)}`);
|
|
}
|
|
return { received: true };
|
|
}
|
|
|
|
@Post("waafi")
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: "Waafi payment notification callback (Djibouti)" })
|
|
async receiveWaafi(
|
|
@Body() payload: WaafiWebhookPayload,
|
|
@Headers() headers: WaafiWebhookHeaders,
|
|
@Req() req: { rawBody?: Buffer },
|
|
) {
|
|
|
|
this.logger.log("\n\n\n\nWaafi payment notification callback (Djibouti)\n\n\n\n");
|
|
this.logger.log(
|
|
`Waafi webhook hit: event=${payload?.event ?? "unknown"} eventId=${headers["x-webhook-event-id"] ?? "n/a"}`,
|
|
);
|
|
try {
|
|
// HMAC verification must sign over the exact raw bytes Waafi sent, not re-serialized JSON.
|
|
const rawBody = req.rawBody?.toString("utf8") ?? "";
|
|
await this.waafi.handle(payload, rawBody, headers);
|
|
} catch (err) {
|
|
this.logger.error(`Waafi webhook handler threw: ${this.message(err)}`);
|
|
}
|
|
return { responseCode: "2001", responseMsg: "Success" };
|
|
}
|
|
|
|
@Post("dmoney")
|
|
@HttpCode(HttpStatus.OK)
|
|
@ApiOperation({ summary: "D-Money payment notification callback (Djibouti)" })
|
|
async receiveDMoney(@Body() payload: DMoneyWebhookPayload) {
|
|
try {
|
|
await this.dMoney.handle(payload);
|
|
} catch (err) {
|
|
this.logger.error(`D-Money webhook handler threw: ${this.message(err)}`);
|
|
}
|
|
return { code: "0", msg: "Success", result: "SUCCESS" };
|
|
}
|
|
|
|
private message(err: unknown): string {
|
|
return err instanceof Error ? err.message : String(err);
|
|
}
|
|
}
|