Skip to content

Commit db5bc1f

Browse files
committed
Emit payment and limit domain events through webhooks
1 parent 1ba8134 commit db5bc1f

6 files changed

Lines changed: 95 additions & 6 deletions

File tree

src/limits/limits.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
22
import { LimitsService } from './limits.service';
33
import { LimitsController } from './limits.controller';
44
import { PrismaModule } from '../prisma/prisma.module';
5+
import { WebhookModule } from '../webhooks/webhook.module';
56

67
@Module({
7-
imports: [PrismaModule],
8+
imports: [PrismaModule, WebhookModule],
89
controllers: [LimitsController],
910
providers: [LimitsService],
1011
exports: [LimitsService],

src/limits/limits.service.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
HttpStatus,
66
} from '@nestjs/common';
77
import { PrismaService } from '../prisma/prisma.service';
8+
import { WebhookEventEmitterService } from '../webhooks/webhook-event-emitter.service';
89
import { CreateLimitDto, LimitPeriod } from './dto/create-limit.dto';
910
import { UpdateLimitDto } from './dto/update-limit.dto';
1011

@@ -27,14 +28,25 @@ export class LimitExceededException extends HttpException {
2728

2829
@Injectable()
2930
export class LimitsService {
30-
constructor(private readonly prisma: PrismaService) {}
31+
constructor(
32+
private readonly prisma: PrismaService,
33+
private readonly webhookEventEmitter: WebhookEventEmitterService,
34+
) {}
3135

3236
async setLimits(walletId: string, daily: number, perTx: number) {
33-
return this.prisma.walletLimit.upsert({
37+
const limits = await this.prisma.walletLimit.upsert({
3438
where: { walletId },
3539
update: { dailyLimit: daily, perTransactionLimit: perTx },
3640
create: { walletId, dailyLimit: daily, perTransactionLimit: perTx },
3741
});
42+
43+
await this.webhookEventEmitter.emitLimitsUpdated({
44+
walletId,
45+
dailyLimit: limits.dailyLimit,
46+
perTransactionLimit: limits.perTransactionLimit,
47+
});
48+
49+
return limits;
3850
}
3951

4052
async getLimits(walletId: string) {

src/payments/payments.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { PaymentsService } from './payments.service';
33
import { PaymentsController } from './payments.controller';
44
import { LimitsModule } from '../limits/limits.module';
55
import { WalletsModule } from '../wallets/wallets.module';
6+
import { WebhookModule } from '../webhooks/webhook.module';
67

78
@Module({
8-
imports: [LimitsModule, WalletsModule],
9+
imports: [LimitsModule, WalletsModule, WebhookModule],
910
controllers: [PaymentsController],
1011
providers: [PaymentsService],
1112
})

src/payments/payments.service.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { WalletStatus } from '../wallets/domain/wallet.model';
1212
import { PaymentStatus } from './entities/payment.entity';
1313
import { PaginationDto, PaginatedResponse } from '../common/dto/pagination.dto';
1414
import { PaymentsFilterDto } from './dto/payments-filter.dto';
15+
import { WebhookEventEmitterService } from '../webhooks/webhook-event-emitter.service';
1516

1617
// Only PENDING payments can be transitioned; terminal states are immutable.
1718
const ALLOWED_TRANSITIONS: Record<string, PaymentStatus[]> = {
@@ -26,6 +27,7 @@ export class PaymentsService {
2627
private readonly prisma: PrismaService,
2728
private readonly limitsService: LimitsService,
2829
private readonly walletsService: WalletsService,
30+
private readonly webhookEventEmitter: WebhookEventEmitterService,
2931
) {}
3032

3133
async create(createPaymentDto: CreatePaymentDto) {
@@ -49,7 +51,7 @@ export class PaymentsService {
4951
await this.walletsService.findWalletById(receiverWalletId);
5052
await this.limitsService.checkLimits(walletId, amount);
5153

52-
return this.prisma.payment.create({
54+
const payment = await this.prisma.payment.create({
5355
data: {
5456
fromId,
5557
toId,
@@ -60,6 +62,17 @@ export class PaymentsService {
6062
status: PaymentStatus.PENDING,
6163
},
6264
});
65+
66+
await this.webhookEventEmitter.emitPaymentCreated({
67+
paymentId: payment.id,
68+
walletId,
69+
receiverWalletId,
70+
amount,
71+
currency,
72+
status: payment.status,
73+
});
74+
75+
return payment;
6376
}
6477

6578
async findAll(
@@ -114,10 +127,21 @@ export class PaymentsService {
114127
}
115128
}
116129

117-
return this.prisma.payment.update({
130+
const updatedPayment = await this.prisma.payment.update({
118131
where: { id: paymentId },
119132
data: updatePaymentDto,
120133
});
134+
135+
if (updatePaymentDto.status !== undefined) {
136+
await this.webhookEventEmitter.emitPaymentStatusUpdated({
137+
paymentId: updatedPayment.id,
138+
walletId: String(updatedPayment.fromId),
139+
previousStatus: payment.status,
140+
status: updatedPayment.status,
141+
});
142+
}
143+
144+
return updatedPayment;
121145
}
122146

123147
remove(id: string) {

src/webhooks/domain/webhook-events.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ export enum WebhookEventType {
2323
// User events
2424
USER_CREATED = 'user.created',
2525
USER_UPDATED = 'user.updated',
26+
27+
// Payment events
28+
PAYMENT_CREATED = 'payment.created',
29+
PAYMENT_STATUS_UPDATED = 'payment.status.updated',
30+
31+
// Limit events
32+
LIMITS_UPDATED = 'limits.updated',
2633
}
2734

2835
export interface WebhookEvent {

src/webhooks/webhook-event-emitter.service.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,50 @@ export class WebhookEventEmitterService {
177177
await this.webhookDispatcher.dispatchEvent({ event });
178178
}
179179

180+
/**
181+
* Emits a payment.created event
182+
*/
183+
async emitPaymentCreated(data: {
184+
paymentId: string | number;
185+
walletId: string;
186+
receiverWalletId: string;
187+
amount: string | number;
188+
currency: string;
189+
status: string;
190+
}): Promise<void> {
191+
const event = this.createEvent(WebhookEventType.PAYMENT_CREATED, data);
192+
await this.webhookDispatcher.dispatchEvent({ event });
193+
}
194+
195+
/**
196+
* Emits a payment.status.updated event
197+
*/
198+
async emitPaymentStatusUpdated(data: {
199+
paymentId: string | number;
200+
walletId: string;
201+
previousStatus: string;
202+
status: string;
203+
reason?: string;
204+
}): Promise<void> {
205+
const event = this.createEvent(
206+
WebhookEventType.PAYMENT_STATUS_UPDATED,
207+
data,
208+
);
209+
await this.webhookDispatcher.dispatchEvent({ event });
210+
}
211+
212+
/**
213+
* Emits a limits.updated event
214+
*/
215+
async emitLimitsUpdated(data: {
216+
walletId: string;
217+
dailyLimit: number;
218+
perTransactionLimit: number;
219+
}): Promise<void> {
220+
const event = this.createEvent(WebhookEventType.LIMITS_UPDATED, data);
221+
await this.webhookDispatcher.dispatchEvent({ event });
222+
}
223+
180224
/**
181225
* Creates a webhook event with standard structure
182226
*/

0 commit comments

Comments
 (0)