mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
feat: ( payment ) create payment microservice
This commit is contained in:
143
apps/edr-payment-api/src/modules/webhooks/webhooks.controller.ts
Normal file
143
apps/edr-payment-api/src/modules/webhooks/webhooks.controller.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
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(
|
||||
`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 { success: true };
|
||||
}
|
||||
|
||||
private message(err: unknown): string {
|
||||
return err instanceof Error ? err.message : String(err);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user