servcraft 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
import { prisma } from '../../database/prisma.js';
|
|
2
|
+
import type { PaginatedResult, PaginationParams } from '../../types/index.js';
|
|
3
|
+
import { createPaginatedResult, getSkip } from '../../utils/pagination.js';
|
|
4
|
+
import type { Payment, Subscription, Plan } from './types.js';
|
|
5
|
+
import {
|
|
6
|
+
PaymentProvider,
|
|
7
|
+
PaymentStatus,
|
|
8
|
+
PaymentMethod,
|
|
9
|
+
SubscriptionStatus,
|
|
10
|
+
PlanInterval,
|
|
11
|
+
} from '@prisma/client';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Payment Repository - Prisma Implementation
|
|
15
|
+
* Manages payment, subscription, and plan persistence
|
|
16
|
+
*/
|
|
17
|
+
export class PaymentRepository {
|
|
18
|
+
// ==========================================
|
|
19
|
+
// PAYMENT METHODS
|
|
20
|
+
// ==========================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a new payment
|
|
24
|
+
*/
|
|
25
|
+
async createPayment(data: {
|
|
26
|
+
userId: string;
|
|
27
|
+
provider: Payment['provider'];
|
|
28
|
+
method: Payment['method'];
|
|
29
|
+
amount: number;
|
|
30
|
+
currency: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
metadata?: Record<string, unknown>;
|
|
33
|
+
providerPaymentId?: string;
|
|
34
|
+
providerCustomerId?: string;
|
|
35
|
+
}): Promise<Payment> {
|
|
36
|
+
const payment = await prisma.payment.create({
|
|
37
|
+
data: {
|
|
38
|
+
userId: data.userId,
|
|
39
|
+
provider: this.mapProviderToEnum(data.provider),
|
|
40
|
+
method: this.mapMethodToEnum(data.method),
|
|
41
|
+
status: PaymentStatus.PENDING,
|
|
42
|
+
amount: data.amount,
|
|
43
|
+
currency: data.currency,
|
|
44
|
+
description: data.description,
|
|
45
|
+
metadata: data.metadata as object | undefined,
|
|
46
|
+
providerPaymentId: data.providerPaymentId,
|
|
47
|
+
providerCustomerId: data.providerCustomerId,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return this.mapPrismaPaymentToPayment(payment);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find payment by ID
|
|
56
|
+
*/
|
|
57
|
+
async findPaymentById(id: string): Promise<Payment | null> {
|
|
58
|
+
const payment = await prisma.payment.findUnique({
|
|
59
|
+
where: { id },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!payment) return null;
|
|
63
|
+
return this.mapPrismaPaymentToPayment(payment);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Find payment by provider payment ID
|
|
68
|
+
*/
|
|
69
|
+
async findPaymentByProviderPaymentId(providerPaymentId: string): Promise<Payment | null> {
|
|
70
|
+
const payment = await prisma.payment.findUnique({
|
|
71
|
+
where: { providerPaymentId },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!payment) return null;
|
|
75
|
+
return this.mapPrismaPaymentToPayment(payment);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find user payments with pagination
|
|
80
|
+
*/
|
|
81
|
+
async findUserPayments(
|
|
82
|
+
userId: string,
|
|
83
|
+
params: PaginationParams
|
|
84
|
+
): Promise<PaginatedResult<Payment>> {
|
|
85
|
+
const where = { userId };
|
|
86
|
+
const orderBy = { createdAt: 'desc' as const };
|
|
87
|
+
|
|
88
|
+
const [data, total] = await Promise.all([
|
|
89
|
+
prisma.payment.findMany({
|
|
90
|
+
where,
|
|
91
|
+
orderBy,
|
|
92
|
+
skip: getSkip(params),
|
|
93
|
+
take: params.limit,
|
|
94
|
+
}),
|
|
95
|
+
prisma.payment.count({ where }),
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const mappedPayments = data.map((payment) => this.mapPrismaPaymentToPayment(payment));
|
|
99
|
+
|
|
100
|
+
return createPaginatedResult(mappedPayments, total, params);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Find payments with filters and pagination
|
|
105
|
+
*/
|
|
106
|
+
async findPayments(
|
|
107
|
+
params: PaginationParams,
|
|
108
|
+
filters?: {
|
|
109
|
+
userId?: string;
|
|
110
|
+
provider?: Payment['provider'];
|
|
111
|
+
status?: Payment['status'];
|
|
112
|
+
startDate?: Date;
|
|
113
|
+
endDate?: Date;
|
|
114
|
+
}
|
|
115
|
+
): Promise<PaginatedResult<Payment>> {
|
|
116
|
+
const where: Record<string, unknown> = {};
|
|
117
|
+
|
|
118
|
+
if (filters?.userId) where.userId = filters.userId;
|
|
119
|
+
if (filters?.provider) where.provider = this.mapProviderToEnum(filters.provider);
|
|
120
|
+
if (filters?.status) where.status = this.mapStatusToEnum(filters.status);
|
|
121
|
+
if (filters?.startDate || filters?.endDate) {
|
|
122
|
+
where.createdAt = {
|
|
123
|
+
...(filters.startDate && { gte: filters.startDate }),
|
|
124
|
+
...(filters.endDate && { lte: filters.endDate }),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const orderBy = { createdAt: 'desc' as const };
|
|
129
|
+
|
|
130
|
+
const [data, total] = await Promise.all([
|
|
131
|
+
prisma.payment.findMany({
|
|
132
|
+
where,
|
|
133
|
+
orderBy,
|
|
134
|
+
skip: getSkip(params),
|
|
135
|
+
take: params.limit,
|
|
136
|
+
}),
|
|
137
|
+
prisma.payment.count({ where }),
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
const mappedPayments = data.map((payment) => this.mapPrismaPaymentToPayment(payment));
|
|
141
|
+
|
|
142
|
+
return createPaginatedResult(mappedPayments, total, params);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Update payment status
|
|
147
|
+
*/
|
|
148
|
+
async updatePaymentStatus(
|
|
149
|
+
id: string,
|
|
150
|
+
status: Payment['status'],
|
|
151
|
+
data?: {
|
|
152
|
+
paidAt?: Date;
|
|
153
|
+
failureReason?: string;
|
|
154
|
+
refundedAmount?: number;
|
|
155
|
+
}
|
|
156
|
+
): Promise<Payment | null> {
|
|
157
|
+
try {
|
|
158
|
+
const payment = await prisma.payment.update({
|
|
159
|
+
where: { id },
|
|
160
|
+
data: {
|
|
161
|
+
status: this.mapStatusToEnum(status),
|
|
162
|
+
...(data?.paidAt && { paidAt: data.paidAt }),
|
|
163
|
+
...(data?.failureReason && { failureReason: data.failureReason }),
|
|
164
|
+
...(data?.refundedAmount !== undefined && { refundedAmount: data.refundedAmount }),
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return this.mapPrismaPaymentToPayment(payment);
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Delete payment by ID
|
|
176
|
+
*/
|
|
177
|
+
async deletePayment(id: string): Promise<boolean> {
|
|
178
|
+
try {
|
|
179
|
+
await prisma.payment.delete({
|
|
180
|
+
where: { id },
|
|
181
|
+
});
|
|
182
|
+
return true;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Count payments with filters
|
|
190
|
+
*/
|
|
191
|
+
async countPayments(filters?: {
|
|
192
|
+
userId?: string;
|
|
193
|
+
provider?: Payment['provider'];
|
|
194
|
+
status?: Payment['status'];
|
|
195
|
+
}): Promise<number> {
|
|
196
|
+
const where: Record<string, unknown> = {};
|
|
197
|
+
|
|
198
|
+
if (filters?.userId) where.userId = filters.userId;
|
|
199
|
+
if (filters?.provider) where.provider = this.mapProviderToEnum(filters.provider);
|
|
200
|
+
if (filters?.status) where.status = this.mapStatusToEnum(filters.status);
|
|
201
|
+
|
|
202
|
+
return prisma.payment.count({ where });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ==========================================
|
|
206
|
+
// SUBSCRIPTION METHODS
|
|
207
|
+
// ==========================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Create a new subscription
|
|
211
|
+
*/
|
|
212
|
+
async createSubscription(data: {
|
|
213
|
+
userId: string;
|
|
214
|
+
planId: string;
|
|
215
|
+
provider: Subscription['provider'];
|
|
216
|
+
currentPeriodStart: Date;
|
|
217
|
+
currentPeriodEnd: Date;
|
|
218
|
+
providerSubscriptionId?: string;
|
|
219
|
+
}): Promise<Subscription> {
|
|
220
|
+
const subscription = await prisma.subscription.create({
|
|
221
|
+
data: {
|
|
222
|
+
userId: data.userId,
|
|
223
|
+
planId: data.planId,
|
|
224
|
+
provider: this.mapProviderToEnum(data.provider),
|
|
225
|
+
status: SubscriptionStatus.ACTIVE,
|
|
226
|
+
currentPeriodStart: data.currentPeriodStart,
|
|
227
|
+
currentPeriodEnd: data.currentPeriodEnd,
|
|
228
|
+
providerSubscriptionId: data.providerSubscriptionId,
|
|
229
|
+
cancelAtPeriodEnd: false,
|
|
230
|
+
},
|
|
231
|
+
include: {
|
|
232
|
+
plan: true,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return this.mapPrismaSubscriptionToSubscription(subscription);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Find subscription by ID
|
|
241
|
+
*/
|
|
242
|
+
async findSubscriptionById(id: string): Promise<Subscription | null> {
|
|
243
|
+
const subscription = await prisma.subscription.findUnique({
|
|
244
|
+
where: { id },
|
|
245
|
+
include: {
|
|
246
|
+
plan: true,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!subscription) return null;
|
|
251
|
+
return this.mapPrismaSubscriptionToSubscription(subscription);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Find user subscriptions
|
|
256
|
+
*/
|
|
257
|
+
async findUserSubscriptions(userId: string): Promise<Subscription[]> {
|
|
258
|
+
const subscriptions = await prisma.subscription.findMany({
|
|
259
|
+
where: { userId },
|
|
260
|
+
include: {
|
|
261
|
+
plan: true,
|
|
262
|
+
},
|
|
263
|
+
orderBy: { createdAt: 'desc' },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return subscriptions.map((sub) => this.mapPrismaSubscriptionToSubscription(sub));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Update subscription status
|
|
271
|
+
*/
|
|
272
|
+
async updateSubscriptionStatus(
|
|
273
|
+
id: string,
|
|
274
|
+
status: Subscription['status']
|
|
275
|
+
): Promise<Subscription | null> {
|
|
276
|
+
try {
|
|
277
|
+
const subscription = await prisma.subscription.update({
|
|
278
|
+
where: { id },
|
|
279
|
+
data: {
|
|
280
|
+
status: this.mapSubscriptionStatusToEnum(status),
|
|
281
|
+
},
|
|
282
|
+
include: {
|
|
283
|
+
plan: true,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return this.mapPrismaSubscriptionToSubscription(subscription);
|
|
288
|
+
} catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Cancel subscription
|
|
295
|
+
*/
|
|
296
|
+
async cancelSubscription(id: string): Promise<Subscription | null> {
|
|
297
|
+
try {
|
|
298
|
+
const subscription = await prisma.subscription.update({
|
|
299
|
+
where: { id },
|
|
300
|
+
data: {
|
|
301
|
+
cancelAtPeriodEnd: true,
|
|
302
|
+
},
|
|
303
|
+
include: {
|
|
304
|
+
plan: true,
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
return this.mapPrismaSubscriptionToSubscription(subscription);
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Delete subscription by ID
|
|
316
|
+
*/
|
|
317
|
+
async deleteSubscription(id: string): Promise<boolean> {
|
|
318
|
+
try {
|
|
319
|
+
await prisma.subscription.delete({
|
|
320
|
+
where: { id },
|
|
321
|
+
});
|
|
322
|
+
return true;
|
|
323
|
+
} catch {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ==========================================
|
|
329
|
+
// PLAN METHODS
|
|
330
|
+
// ==========================================
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Create a new plan
|
|
334
|
+
*/
|
|
335
|
+
async createPlan(data: {
|
|
336
|
+
name: string;
|
|
337
|
+
description?: string;
|
|
338
|
+
amount: number;
|
|
339
|
+
currency: string;
|
|
340
|
+
interval: Plan['interval'];
|
|
341
|
+
intervalCount: number;
|
|
342
|
+
trialDays?: number;
|
|
343
|
+
features?: string[];
|
|
344
|
+
metadata?: Record<string, unknown>;
|
|
345
|
+
active?: boolean;
|
|
346
|
+
}): Promise<Plan> {
|
|
347
|
+
const plan = await prisma.plan.create({
|
|
348
|
+
data: {
|
|
349
|
+
name: data.name,
|
|
350
|
+
description: data.description,
|
|
351
|
+
amount: data.amount,
|
|
352
|
+
currency: data.currency,
|
|
353
|
+
interval: this.mapIntervalToEnum(data.interval),
|
|
354
|
+
intervalCount: data.intervalCount,
|
|
355
|
+
trialDays: data.trialDays,
|
|
356
|
+
features: data.features as object | undefined,
|
|
357
|
+
metadata: data.metadata as object | undefined,
|
|
358
|
+
active: data.active ?? true,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
return this.mapPrismaPlanToPlan(plan);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Find plan by ID
|
|
367
|
+
*/
|
|
368
|
+
async findPlanById(id: string): Promise<Plan | null> {
|
|
369
|
+
const plan = await prisma.plan.findUnique({
|
|
370
|
+
where: { id },
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (!plan) return null;
|
|
374
|
+
return this.mapPrismaPlanToPlan(plan);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Find all active plans
|
|
379
|
+
*/
|
|
380
|
+
async findActivePlans(): Promise<Plan[]> {
|
|
381
|
+
const plans = await prisma.plan.findMany({
|
|
382
|
+
where: { active: true },
|
|
383
|
+
orderBy: { amount: 'asc' },
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
return plans.map((plan) => this.mapPrismaPlanToPlan(plan));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Update plan
|
|
391
|
+
*/
|
|
392
|
+
async updatePlan(
|
|
393
|
+
id: string,
|
|
394
|
+
data: {
|
|
395
|
+
name?: string;
|
|
396
|
+
description?: string;
|
|
397
|
+
amount?: number;
|
|
398
|
+
currency?: string;
|
|
399
|
+
active?: boolean;
|
|
400
|
+
features?: string[];
|
|
401
|
+
metadata?: Record<string, unknown>;
|
|
402
|
+
}
|
|
403
|
+
): Promise<Plan | null> {
|
|
404
|
+
try {
|
|
405
|
+
const plan = await prisma.plan.update({
|
|
406
|
+
where: { id },
|
|
407
|
+
data: {
|
|
408
|
+
...(data.name && { name: data.name }),
|
|
409
|
+
...(data.description !== undefined && { description: data.description }),
|
|
410
|
+
...(data.amount !== undefined && { amount: data.amount }),
|
|
411
|
+
...(data.currency && { currency: data.currency }),
|
|
412
|
+
...(data.active !== undefined && { active: data.active }),
|
|
413
|
+
...(data.features && { features: data.features as object }),
|
|
414
|
+
...(data.metadata && { metadata: data.metadata as object }),
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
return this.mapPrismaPlanToPlan(plan);
|
|
419
|
+
} catch {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Delete plan by ID
|
|
426
|
+
*/
|
|
427
|
+
async deletePlan(id: string): Promise<boolean> {
|
|
428
|
+
try {
|
|
429
|
+
await prisma.plan.delete({
|
|
430
|
+
where: { id },
|
|
431
|
+
});
|
|
432
|
+
return true;
|
|
433
|
+
} catch {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ==========================================
|
|
439
|
+
// WEBHOOK METHODS
|
|
440
|
+
// ==========================================
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Store webhook event
|
|
444
|
+
*/
|
|
445
|
+
async storeWebhookEvent(data: {
|
|
446
|
+
provider: Payment['provider'];
|
|
447
|
+
type: string;
|
|
448
|
+
data: Record<string, unknown>;
|
|
449
|
+
}): Promise<{ id: string }> {
|
|
450
|
+
const webhook = await prisma.paymentWebhook.create({
|
|
451
|
+
data: {
|
|
452
|
+
provider: this.mapProviderToEnum(data.provider),
|
|
453
|
+
type: data.type,
|
|
454
|
+
data: data.data as object,
|
|
455
|
+
processed: false,
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return { id: webhook.id };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Mark webhook as processed
|
|
464
|
+
*/
|
|
465
|
+
async markWebhookProcessed(id: string, error?: string): Promise<void> {
|
|
466
|
+
await prisma.paymentWebhook.update({
|
|
467
|
+
where: { id },
|
|
468
|
+
data: {
|
|
469
|
+
processed: true,
|
|
470
|
+
...(error && { error }),
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ==========================================
|
|
476
|
+
// TESTING UTILITIES
|
|
477
|
+
// ==========================================
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Clear all payments (for testing only)
|
|
481
|
+
*/
|
|
482
|
+
async clearPayments(): Promise<void> {
|
|
483
|
+
await prisma.payment.deleteMany();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Clear all subscriptions (for testing only)
|
|
488
|
+
*/
|
|
489
|
+
async clearSubscriptions(): Promise<void> {
|
|
490
|
+
await prisma.subscription.deleteMany();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Clear all plans (for testing only)
|
|
495
|
+
*/
|
|
496
|
+
async clearPlans(): Promise<void> {
|
|
497
|
+
await prisma.plan.deleteMany();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Clear all webhooks (for testing only)
|
|
502
|
+
*/
|
|
503
|
+
async clearWebhooks(): Promise<void> {
|
|
504
|
+
await prisma.paymentWebhook.deleteMany();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ==========================================
|
|
508
|
+
// PRIVATE MAPPING METHODS
|
|
509
|
+
// ==========================================
|
|
510
|
+
|
|
511
|
+
private mapPrismaPaymentToPayment(prismaPayment: {
|
|
512
|
+
id: string;
|
|
513
|
+
userId: string;
|
|
514
|
+
provider: PaymentProvider;
|
|
515
|
+
method: PaymentMethod;
|
|
516
|
+
status: PaymentStatus;
|
|
517
|
+
amount: number;
|
|
518
|
+
currency: string;
|
|
519
|
+
description: string | null;
|
|
520
|
+
metadata: unknown;
|
|
521
|
+
providerPaymentId: string | null;
|
|
522
|
+
providerCustomerId: string | null;
|
|
523
|
+
refundedAmount: number | null;
|
|
524
|
+
failureReason: string | null;
|
|
525
|
+
paidAt: Date | null;
|
|
526
|
+
createdAt: Date;
|
|
527
|
+
updatedAt: Date;
|
|
528
|
+
}): Payment {
|
|
529
|
+
return {
|
|
530
|
+
id: prismaPayment.id,
|
|
531
|
+
userId: prismaPayment.userId,
|
|
532
|
+
provider: this.mapEnumToProvider(prismaPayment.provider),
|
|
533
|
+
method: this.mapEnumToMethod(prismaPayment.method),
|
|
534
|
+
status: this.mapEnumToStatus(prismaPayment.status),
|
|
535
|
+
amount: prismaPayment.amount,
|
|
536
|
+
currency: prismaPayment.currency,
|
|
537
|
+
description: prismaPayment.description ?? undefined,
|
|
538
|
+
metadata: prismaPayment.metadata as Record<string, unknown> | undefined,
|
|
539
|
+
providerPaymentId: prismaPayment.providerPaymentId ?? undefined,
|
|
540
|
+
providerCustomerId: prismaPayment.providerCustomerId ?? undefined,
|
|
541
|
+
refundedAmount: prismaPayment.refundedAmount ?? undefined,
|
|
542
|
+
failureReason: prismaPayment.failureReason ?? undefined,
|
|
543
|
+
paidAt: prismaPayment.paidAt ?? undefined,
|
|
544
|
+
createdAt: prismaPayment.createdAt,
|
|
545
|
+
updatedAt: prismaPayment.updatedAt,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private mapPrismaSubscriptionToSubscription(prismaSubscription: {
|
|
550
|
+
id: string;
|
|
551
|
+
userId: string;
|
|
552
|
+
planId: string;
|
|
553
|
+
provider: PaymentProvider;
|
|
554
|
+
providerSubscriptionId: string | null;
|
|
555
|
+
status: SubscriptionStatus;
|
|
556
|
+
currentPeriodStart: Date;
|
|
557
|
+
currentPeriodEnd: Date;
|
|
558
|
+
cancelAtPeriodEnd: boolean;
|
|
559
|
+
createdAt: Date;
|
|
560
|
+
updatedAt: Date;
|
|
561
|
+
plan?: {
|
|
562
|
+
id: string;
|
|
563
|
+
name: string;
|
|
564
|
+
amount: number;
|
|
565
|
+
currency: string;
|
|
566
|
+
interval: PlanInterval;
|
|
567
|
+
intervalCount: number;
|
|
568
|
+
};
|
|
569
|
+
}): Subscription {
|
|
570
|
+
return {
|
|
571
|
+
id: prismaSubscription.id,
|
|
572
|
+
userId: prismaSubscription.userId,
|
|
573
|
+
planId: prismaSubscription.planId,
|
|
574
|
+
provider: this.mapEnumToProvider(prismaSubscription.provider),
|
|
575
|
+
providerSubscriptionId: prismaSubscription.providerSubscriptionId ?? undefined,
|
|
576
|
+
status: this.mapEnumToSubscriptionStatus(prismaSubscription.status),
|
|
577
|
+
currentPeriodStart: prismaSubscription.currentPeriodStart,
|
|
578
|
+
currentPeriodEnd: prismaSubscription.currentPeriodEnd,
|
|
579
|
+
cancelAtPeriodEnd: prismaSubscription.cancelAtPeriodEnd,
|
|
580
|
+
createdAt: prismaSubscription.createdAt,
|
|
581
|
+
updatedAt: prismaSubscription.updatedAt,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private mapPrismaPlanToPlan(prismaPlan: {
|
|
586
|
+
id: string;
|
|
587
|
+
name: string;
|
|
588
|
+
description: string | null;
|
|
589
|
+
amount: number;
|
|
590
|
+
currency: string;
|
|
591
|
+
interval: PlanInterval;
|
|
592
|
+
intervalCount: number;
|
|
593
|
+
trialDays: number | null;
|
|
594
|
+
features: unknown;
|
|
595
|
+
metadata: unknown;
|
|
596
|
+
active: boolean;
|
|
597
|
+
createdAt: Date;
|
|
598
|
+
updatedAt: Date;
|
|
599
|
+
}): Plan {
|
|
600
|
+
return {
|
|
601
|
+
id: prismaPlan.id,
|
|
602
|
+
name: prismaPlan.name,
|
|
603
|
+
description: prismaPlan.description ?? undefined,
|
|
604
|
+
amount: prismaPlan.amount,
|
|
605
|
+
currency: prismaPlan.currency,
|
|
606
|
+
interval: this.mapEnumToInterval(prismaPlan.interval),
|
|
607
|
+
intervalCount: prismaPlan.intervalCount,
|
|
608
|
+
trialDays: prismaPlan.trialDays ?? undefined,
|
|
609
|
+
features: prismaPlan.features as string[] | undefined,
|
|
610
|
+
metadata: prismaPlan.metadata as Record<string, unknown> | undefined,
|
|
611
|
+
active: prismaPlan.active,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Provider mapping
|
|
616
|
+
private mapProviderToEnum(provider: string): PaymentProvider {
|
|
617
|
+
const providerMap: Record<string, PaymentProvider> = {
|
|
618
|
+
stripe: PaymentProvider.STRIPE,
|
|
619
|
+
paypal: PaymentProvider.PAYPAL,
|
|
620
|
+
mobile_money: PaymentProvider.MOBILE_MONEY,
|
|
621
|
+
manual: PaymentProvider.MANUAL,
|
|
622
|
+
};
|
|
623
|
+
return providerMap[provider] || PaymentProvider.MANUAL;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private mapEnumToProvider(provider: PaymentProvider): Payment['provider'] {
|
|
627
|
+
const providerMap: Record<PaymentProvider, Payment['provider']> = {
|
|
628
|
+
[PaymentProvider.STRIPE]: 'stripe',
|
|
629
|
+
[PaymentProvider.PAYPAL]: 'paypal',
|
|
630
|
+
[PaymentProvider.MOBILE_MONEY]: 'mobile_money',
|
|
631
|
+
[PaymentProvider.MANUAL]: 'manual',
|
|
632
|
+
};
|
|
633
|
+
return providerMap[provider];
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Status mapping
|
|
637
|
+
private mapStatusToEnum(status: string): PaymentStatus {
|
|
638
|
+
const statusMap: Record<string, PaymentStatus> = {
|
|
639
|
+
pending: PaymentStatus.PENDING,
|
|
640
|
+
processing: PaymentStatus.PROCESSING,
|
|
641
|
+
completed: PaymentStatus.COMPLETED,
|
|
642
|
+
failed: PaymentStatus.FAILED,
|
|
643
|
+
refunded: PaymentStatus.REFUNDED,
|
|
644
|
+
cancelled: PaymentStatus.CANCELLED,
|
|
645
|
+
};
|
|
646
|
+
return statusMap[status] || PaymentStatus.PENDING;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private mapEnumToStatus(status: PaymentStatus): Payment['status'] {
|
|
650
|
+
const statusMap: Record<PaymentStatus, Payment['status']> = {
|
|
651
|
+
[PaymentStatus.PENDING]: 'pending',
|
|
652
|
+
[PaymentStatus.PROCESSING]: 'processing',
|
|
653
|
+
[PaymentStatus.COMPLETED]: 'completed',
|
|
654
|
+
[PaymentStatus.FAILED]: 'failed',
|
|
655
|
+
[PaymentStatus.REFUNDED]: 'refunded',
|
|
656
|
+
[PaymentStatus.CANCELLED]: 'cancelled',
|
|
657
|
+
};
|
|
658
|
+
return statusMap[status];
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Method mapping
|
|
662
|
+
private mapMethodToEnum(method: string): PaymentMethod {
|
|
663
|
+
const methodMap: Record<string, PaymentMethod> = {
|
|
664
|
+
card: PaymentMethod.CARD,
|
|
665
|
+
bank_transfer: PaymentMethod.BANK_TRANSFER,
|
|
666
|
+
mobile_money: PaymentMethod.MOBILE_MONEY,
|
|
667
|
+
paypal: PaymentMethod.PAYPAL,
|
|
668
|
+
crypto: PaymentMethod.CRYPTO,
|
|
669
|
+
cash: PaymentMethod.CASH,
|
|
670
|
+
};
|
|
671
|
+
return methodMap[method] || PaymentMethod.CARD;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private mapEnumToMethod(method: PaymentMethod): Payment['method'] {
|
|
675
|
+
const methodMap: Record<PaymentMethod, Payment['method']> = {
|
|
676
|
+
[PaymentMethod.CARD]: 'card',
|
|
677
|
+
[PaymentMethod.BANK_TRANSFER]: 'bank_transfer',
|
|
678
|
+
[PaymentMethod.MOBILE_MONEY]: 'mobile_money',
|
|
679
|
+
[PaymentMethod.PAYPAL]: 'paypal',
|
|
680
|
+
[PaymentMethod.CRYPTO]: 'crypto',
|
|
681
|
+
[PaymentMethod.CASH]: 'cash',
|
|
682
|
+
};
|
|
683
|
+
return methodMap[method];
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Subscription status mapping
|
|
687
|
+
private mapSubscriptionStatusToEnum(status: string): SubscriptionStatus {
|
|
688
|
+
const statusMap: Record<string, SubscriptionStatus> = {
|
|
689
|
+
active: SubscriptionStatus.ACTIVE,
|
|
690
|
+
cancelled: SubscriptionStatus.CANCELLED,
|
|
691
|
+
past_due: SubscriptionStatus.PAST_DUE,
|
|
692
|
+
trialing: SubscriptionStatus.TRIALING,
|
|
693
|
+
paused: SubscriptionStatus.PAUSED,
|
|
694
|
+
};
|
|
695
|
+
return statusMap[status] || SubscriptionStatus.ACTIVE;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private mapEnumToSubscriptionStatus(status: SubscriptionStatus): Subscription['status'] {
|
|
699
|
+
const statusMap: Record<SubscriptionStatus, Subscription['status']> = {
|
|
700
|
+
[SubscriptionStatus.ACTIVE]: 'active',
|
|
701
|
+
[SubscriptionStatus.CANCELLED]: 'cancelled',
|
|
702
|
+
[SubscriptionStatus.PAST_DUE]: 'past_due',
|
|
703
|
+
[SubscriptionStatus.TRIALING]: 'trialing',
|
|
704
|
+
[SubscriptionStatus.PAUSED]: 'paused',
|
|
705
|
+
};
|
|
706
|
+
return statusMap[status];
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Interval mapping
|
|
710
|
+
private mapIntervalToEnum(interval: string): PlanInterval {
|
|
711
|
+
const intervalMap: Record<string, PlanInterval> = {
|
|
712
|
+
day: PlanInterval.DAY,
|
|
713
|
+
week: PlanInterval.WEEK,
|
|
714
|
+
month: PlanInterval.MONTH,
|
|
715
|
+
year: PlanInterval.YEAR,
|
|
716
|
+
};
|
|
717
|
+
return intervalMap[interval] || PlanInterval.MONTH;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
private mapEnumToInterval(interval: PlanInterval): Plan['interval'] {
|
|
721
|
+
const intervalMap: Record<PlanInterval, Plan['interval']> = {
|
|
722
|
+
[PlanInterval.DAY]: 'day',
|
|
723
|
+
[PlanInterval.WEEK]: 'week',
|
|
724
|
+
[PlanInterval.MONTH]: 'month',
|
|
725
|
+
[PlanInterval.YEAR]: 'year',
|
|
726
|
+
};
|
|
727
|
+
return intervalMap[interval];
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export function createPaymentRepository(): PaymentRepository {
|
|
732
|
+
return new PaymentRepository();
|
|
733
|
+
}
|