Skip to content

Commit 6ac1e71

Browse files
committed
feat(billing): implement billing repository and related types
- Added PaddleHttpClient module for handling HTTP requests. - Introduced types for customer, product, transaction, and charge requests. - Replaced payment module with billing module in index. - Added new mail templates for invoice reminders and overages notifications. - Created billing repository with methods for invoice and subscription management. - Implemented queries for invoice and subscription operations. - Updated project repository to handle project metadata and subscription queries.
1 parent 51237b0 commit 6ac1e71

26 files changed

+1892
-183
lines changed

package-lock.json

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"dependencies": {
3030
"@bonadocs/di": "^1.0.0",
3131
"@bonadocs/logger": "^1.0.0",
32+
"@paddle/paddle-node-sdk": "^2.7.0",
3233
"axios": "^1.7.9",
3334
"body-parser": "^1.20.3",
3435
"class-transformer": "^0.5.1",

src/app.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { rootLoggerMiddleware } from '@bonadocs/logger';
1212
import { AppErrorHandler, AuthMiddleware, healthcheckMiddleware, useCors } from './middleware';
1313
import {
1414
AuthController,
15+
BillingController,
1516
ContractController,
1617
getConfigService,
17-
PaymentController,
1818
ProjectController,
1919
SubscriptionController,
2020
TenderlyController,
@@ -83,7 +83,7 @@ useExpressServer(app, {
8383
SubscriptionController,
8484
ContractController,
8585
TenderlyController,
86-
PaymentController,
86+
BillingController,
8787
],
8888
middlewares: [AppErrorHandler, AuthMiddleware],
8989
});
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { EventName, Paddle } from '@paddle/paddle-node-sdk';
2+
import { Request } from 'express';
3+
import { Body, JsonController, Post, Req } from 'routing-controllers';
4+
import { Inject, Service } from 'typedi';
5+
6+
import { diConstants } from '@bonadocs/di';
7+
import { BonadocsLogger } from '@bonadocs/logger';
8+
9+
import { ConfigService, Paddle as PaddleConfig } from '../configuration';
10+
import { JsonResponse } from '../shared';
11+
12+
import { BillingSubscriptionResponse } from './billing.interface';
13+
import { BillingService } from './billing.service';
14+
import { GeneratePaymentLinkDto } from './dto/generate-payment-link.dto';
15+
16+
@Service()
17+
@JsonController('/billing')
18+
export class BillingController {
19+
constructor(
20+
@Inject(diConstants.logger) private readonly logger: BonadocsLogger,
21+
@Inject() private readonly billingService: BillingService,
22+
@Inject() private readonly configService: ConfigService,
23+
) {}
24+
25+
@Post('/upgrade')
26+
async create(
27+
@Body({ validate: true }) payload: GeneratePaymentLinkDto,
28+
@Req() request: Request,
29+
): Promise<JsonResponse<BillingSubscriptionResponse>> {
30+
const authData = request.auth;
31+
await this.billingService.upgradePlan(authData.projectId!, payload.planId);
32+
return {
33+
status: 'successful',
34+
message: 'Subscription plan upgraded successfully',
35+
};
36+
}
37+
38+
@Post('/webhook')
39+
async webhook(@Req() request: Request): Promise<JsonResponse<string> | void> {
40+
const paddleConfigs = this.configService.getTransformed<PaddleConfig>('paddle');
41+
const paddle = new Paddle(paddleConfigs.apiKey);
42+
const signature = request.headers['paddle-signature'] as string;
43+
const rawBody = request.body.toString();
44+
const secretKey = paddleConfigs.webhookSecretKey;
45+
try {
46+
if (signature && rawBody) {
47+
const eventData = await paddle.webhooks.unmarshal(rawBody, secretKey, signature);
48+
switch (eventData.eventType) {
49+
case EventName.TransactionPaid:
50+
this.billingService.handleTransactionPaidWebhook(eventData.data);
51+
this.logger.info(
52+
`Handle transaction paid webhook event: ${eventData.notificationId} occurred at ${eventData.occurredAt}`,
53+
);
54+
return {
55+
status: 'successful',
56+
message: 'Webhook event handled successfully',
57+
};
58+
case EventName.SubscriptionCanceled:
59+
this.billingService.handleSubscriptionCreatedWebhook(eventData.data);
60+
this.logger.info(
61+
`Handle subscription canceled webhook event: ${eventData.notificationId} occurred at ${eventData.occurredAt}`,
62+
);
63+
return {
64+
status: 'successful',
65+
message: 'Webhook event handled successfully',
66+
};
67+
default:
68+
this.logger.warn(`Unhandled event type: ${eventData.eventType}`);
69+
return {
70+
status: 'error',
71+
message: 'Webhook event could not be handled',
72+
};
73+
}
74+
}
75+
} catch (error) {
76+
this.logger.info(
77+
`An error occurred while handling webhook event: ${error} occurred at ${new Date().toISOString()}`,
78+
);
79+
}
80+
return {
81+
status: 'error',
82+
message: 'Webhook event could not be handled',
83+
};
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { SubscriptionPlan } from '../shared/types/subscriptions';
22

3-
export interface PayForSubscriptionRequest {
3+
export interface BillingSubscriptionRequest {
44
plan: SubscriptionPlan;
55
user_id: number;
66
project_id: number;
77
}
88

9-
export interface PayForSubscriptionResponse {
9+
export interface BillingSubscriptionResponse {
1010
url: string;
1111
}

0 commit comments

Comments
 (0)