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.
Files changed (217) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/LICENSE +21 -0
  9. package/README.md +1102 -1
  10. package/dist/cli/index.cjs +2026 -2168
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +2026 -2168
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/index.cjs +595 -616
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +114 -52
  17. package/dist/index.d.ts +114 -52
  18. package/dist/index.js +595 -616
  19. package/dist/index.js.map +1 -1
  20. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  21. package/docs/DATABASE_MULTI_ORM.md +399 -0
  22. package/docs/PHASE1_BREAKDOWN.md +346 -0
  23. package/docs/PROGRESS.md +550 -0
  24. package/docs/modules/ANALYTICS.md +226 -0
  25. package/docs/modules/API-VERSIONING.md +252 -0
  26. package/docs/modules/AUDIT.md +192 -0
  27. package/docs/modules/AUTH.md +431 -0
  28. package/docs/modules/CACHE.md +346 -0
  29. package/docs/modules/EMAIL.md +254 -0
  30. package/docs/modules/FEATURE-FLAG.md +291 -0
  31. package/docs/modules/I18N.md +294 -0
  32. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  33. package/docs/modules/MFA.md +266 -0
  34. package/docs/modules/NOTIFICATION.md +311 -0
  35. package/docs/modules/OAUTH.md +237 -0
  36. package/docs/modules/PAYMENT.md +804 -0
  37. package/docs/modules/QUEUE.md +540 -0
  38. package/docs/modules/RATE-LIMIT.md +339 -0
  39. package/docs/modules/SEARCH.md +288 -0
  40. package/docs/modules/SECURITY.md +327 -0
  41. package/docs/modules/SESSION.md +382 -0
  42. package/docs/modules/SWAGGER.md +305 -0
  43. package/docs/modules/UPLOAD.md +296 -0
  44. package/docs/modules/USER.md +505 -0
  45. package/docs/modules/VALIDATION.md +294 -0
  46. package/docs/modules/WEBHOOK.md +270 -0
  47. package/docs/modules/WEBSOCKET.md +691 -0
  48. package/package.json +53 -38
  49. package/prisma/schema.prisma +395 -1
  50. package/src/cli/commands/add-module.ts +520 -87
  51. package/src/cli/commands/db.ts +3 -4
  52. package/src/cli/commands/docs.ts +256 -6
  53. package/src/cli/commands/generate.ts +12 -19
  54. package/src/cli/commands/init.ts +384 -214
  55. package/src/cli/index.ts +0 -4
  56. package/src/cli/templates/repository.ts +6 -1
  57. package/src/cli/templates/routes.ts +6 -21
  58. package/src/cli/utils/docs-generator.ts +6 -7
  59. package/src/cli/utils/env-manager.ts +717 -0
  60. package/src/cli/utils/field-parser.ts +16 -7
  61. package/src/cli/utils/interactive-prompt.ts +223 -0
  62. package/src/cli/utils/template-manager.ts +346 -0
  63. package/src/config/database.config.ts +183 -0
  64. package/src/config/env.ts +0 -10
  65. package/src/config/index.ts +0 -14
  66. package/src/core/server.ts +1 -1
  67. package/src/database/adapters/mongoose.adapter.ts +132 -0
  68. package/src/database/adapters/prisma.adapter.ts +118 -0
  69. package/src/database/connection.ts +190 -0
  70. package/src/database/interfaces/database.interface.ts +85 -0
  71. package/src/database/interfaces/index.ts +7 -0
  72. package/src/database/interfaces/repository.interface.ts +129 -0
  73. package/src/database/models/mongoose/index.ts +7 -0
  74. package/src/database/models/mongoose/payment.schema.ts +347 -0
  75. package/src/database/models/mongoose/user.schema.ts +154 -0
  76. package/src/database/prisma.ts +1 -4
  77. package/src/database/redis.ts +101 -0
  78. package/src/database/repositories/mongoose/index.ts +7 -0
  79. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  80. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  81. package/src/database/seed.ts +6 -1
  82. package/src/index.ts +9 -20
  83. package/src/middleware/security.ts +2 -6
  84. package/src/modules/analytics/analytics.routes.ts +80 -0
  85. package/src/modules/analytics/analytics.service.ts +364 -0
  86. package/src/modules/analytics/index.ts +18 -0
  87. package/src/modules/analytics/types.ts +180 -0
  88. package/src/modules/api-versioning/index.ts +15 -0
  89. package/src/modules/api-versioning/types.ts +86 -0
  90. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  91. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  92. package/src/modules/api-versioning/versioning.service.ts +189 -0
  93. package/src/modules/audit/audit.repository.ts +206 -0
  94. package/src/modules/audit/audit.service.ts +27 -59
  95. package/src/modules/auth/auth.controller.ts +2 -2
  96. package/src/modules/auth/auth.middleware.ts +3 -9
  97. package/src/modules/auth/auth.routes.ts +10 -107
  98. package/src/modules/auth/auth.service.ts +126 -23
  99. package/src/modules/auth/index.ts +3 -4
  100. package/src/modules/cache/cache.service.ts +367 -0
  101. package/src/modules/cache/index.ts +10 -0
  102. package/src/modules/cache/types.ts +44 -0
  103. package/src/modules/email/email.service.ts +3 -10
  104. package/src/modules/email/templates.ts +2 -8
  105. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  106. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  107. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  108. package/src/modules/feature-flag/index.ts +20 -0
  109. package/src/modules/feature-flag/types.ts +192 -0
  110. package/src/modules/i18n/i18n.middleware.ts +186 -0
  111. package/src/modules/i18n/i18n.routes.ts +191 -0
  112. package/src/modules/i18n/i18n.service.ts +456 -0
  113. package/src/modules/i18n/index.ts +18 -0
  114. package/src/modules/i18n/types.ts +118 -0
  115. package/src/modules/media-processing/index.ts +17 -0
  116. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  117. package/src/modules/media-processing/media-processing.service.ts +245 -0
  118. package/src/modules/media-processing/types.ts +156 -0
  119. package/src/modules/mfa/index.ts +20 -0
  120. package/src/modules/mfa/mfa.repository.ts +206 -0
  121. package/src/modules/mfa/mfa.routes.ts +595 -0
  122. package/src/modules/mfa/mfa.service.ts +572 -0
  123. package/src/modules/mfa/totp.ts +150 -0
  124. package/src/modules/mfa/types.ts +57 -0
  125. package/src/modules/notification/index.ts +20 -0
  126. package/src/modules/notification/notification.repository.ts +356 -0
  127. package/src/modules/notification/notification.service.ts +483 -0
  128. package/src/modules/notification/types.ts +119 -0
  129. package/src/modules/oauth/index.ts +20 -0
  130. package/src/modules/oauth/oauth.repository.ts +219 -0
  131. package/src/modules/oauth/oauth.routes.ts +446 -0
  132. package/src/modules/oauth/oauth.service.ts +293 -0
  133. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  134. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  135. package/src/modules/oauth/providers/github.provider.ts +248 -0
  136. package/src/modules/oauth/providers/google.provider.ts +189 -0
  137. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  138. package/src/modules/oauth/types.ts +94 -0
  139. package/src/modules/payment/index.ts +19 -0
  140. package/src/modules/payment/payment.repository.ts +733 -0
  141. package/src/modules/payment/payment.routes.ts +390 -0
  142. package/src/modules/payment/payment.service.ts +354 -0
  143. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  144. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  145. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  146. package/src/modules/payment/types.ts +140 -0
  147. package/src/modules/queue/cron.ts +438 -0
  148. package/src/modules/queue/index.ts +87 -0
  149. package/src/modules/queue/queue.routes.ts +600 -0
  150. package/src/modules/queue/queue.service.ts +842 -0
  151. package/src/modules/queue/types.ts +222 -0
  152. package/src/modules/queue/workers.ts +366 -0
  153. package/src/modules/rate-limit/index.ts +59 -0
  154. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  155. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  156. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  157. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  158. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  159. package/src/modules/rate-limit/types.ts +153 -0
  160. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  161. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  162. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  163. package/src/modules/search/index.ts +21 -0
  164. package/src/modules/search/search.service.ts +234 -0
  165. package/src/modules/search/types.ts +214 -0
  166. package/src/modules/security/index.ts +40 -0
  167. package/src/modules/security/sanitize.ts +223 -0
  168. package/src/modules/security/security-audit.service.ts +388 -0
  169. package/src/modules/security/security.middleware.ts +398 -0
  170. package/src/modules/session/index.ts +3 -0
  171. package/src/modules/session/session.repository.ts +159 -0
  172. package/src/modules/session/session.service.ts +340 -0
  173. package/src/modules/session/types.ts +38 -0
  174. package/src/modules/swagger/index.ts +7 -1
  175. package/src/modules/swagger/schema-builder.ts +16 -4
  176. package/src/modules/swagger/swagger.service.ts +9 -10
  177. package/src/modules/swagger/types.ts +0 -2
  178. package/src/modules/upload/index.ts +14 -0
  179. package/src/modules/upload/types.ts +83 -0
  180. package/src/modules/upload/upload.repository.ts +199 -0
  181. package/src/modules/upload/upload.routes.ts +311 -0
  182. package/src/modules/upload/upload.service.ts +448 -0
  183. package/src/modules/user/index.ts +3 -3
  184. package/src/modules/user/user.controller.ts +15 -9
  185. package/src/modules/user/user.repository.ts +237 -113
  186. package/src/modules/user/user.routes.ts +39 -164
  187. package/src/modules/user/user.service.ts +4 -3
  188. package/src/modules/validation/validator.ts +12 -17
  189. package/src/modules/webhook/index.ts +91 -0
  190. package/src/modules/webhook/retry.ts +196 -0
  191. package/src/modules/webhook/signature.ts +135 -0
  192. package/src/modules/webhook/types.ts +181 -0
  193. package/src/modules/webhook/webhook.repository.ts +358 -0
  194. package/src/modules/webhook/webhook.routes.ts +442 -0
  195. package/src/modules/webhook/webhook.service.ts +457 -0
  196. package/src/modules/websocket/features.ts +504 -0
  197. package/src/modules/websocket/index.ts +106 -0
  198. package/src/modules/websocket/middlewares.ts +298 -0
  199. package/src/modules/websocket/types.ts +181 -0
  200. package/src/modules/websocket/websocket.service.ts +692 -0
  201. package/src/utils/errors.ts +7 -0
  202. package/src/utils/pagination.ts +4 -1
  203. package/tests/helpers/db-check.ts +79 -0
  204. package/tests/integration/auth-redis.test.ts +94 -0
  205. package/tests/integration/cache-redis.test.ts +387 -0
  206. package/tests/integration/mongoose-repositories.test.ts +410 -0
  207. package/tests/integration/payment-prisma.test.ts +637 -0
  208. package/tests/integration/queue-bullmq.test.ts +417 -0
  209. package/tests/integration/user-prisma.test.ts +441 -0
  210. package/tests/integration/websocket-socketio.test.ts +552 -0
  211. package/tests/setup.ts +11 -9
  212. package/vitest.config.ts +3 -8
  213. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  216. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  217. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -0,0 +1,190 @@
1
+ import { logger } from '../../../core/logger.js';
2
+ import type { PayPalConfig, PayPalOrderData, PaymentIntent, Payment } from '../types.js';
3
+
4
+ /**
5
+ * PayPal Payment Provider
6
+ * Uses PayPal REST API v2
7
+ */
8
+ export class PayPalProvider {
9
+ private config: PayPalConfig;
10
+ private accessToken: string | null = null;
11
+ private tokenExpiry: number = 0;
12
+
13
+ constructor(config: PayPalConfig) {
14
+ this.config = config;
15
+ logger.info({ sandbox: config.sandbox }, 'PayPal provider initialized');
16
+ }
17
+
18
+ private get baseUrl(): string {
19
+ return this.config.sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
20
+ }
21
+
22
+ private async getAccessToken(): Promise<string> {
23
+ if (this.accessToken && Date.now() < this.tokenExpiry) {
24
+ return this.accessToken;
25
+ }
26
+
27
+ const auth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString(
28
+ 'base64'
29
+ );
30
+
31
+ const response = await fetch(`${this.baseUrl}/v1/oauth2/token`, {
32
+ method: 'POST',
33
+ headers: {
34
+ Authorization: `Basic ${auth}`,
35
+ 'Content-Type': 'application/x-www-form-urlencoded',
36
+ },
37
+ body: 'grant_type=client_credentials',
38
+ });
39
+
40
+ if (!response.ok) {
41
+ throw new Error('Failed to get PayPal access token');
42
+ }
43
+
44
+ const data = (await response.json()) as { access_token: string; expires_in: number };
45
+ this.accessToken = data.access_token;
46
+ this.tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;
47
+
48
+ return this.accessToken;
49
+ }
50
+
51
+ async createOrder(data: PayPalOrderData): Promise<PaymentIntent> {
52
+ const accessToken = await this.getAccessToken();
53
+
54
+ const response = await fetch(`${this.baseUrl}/v2/checkout/orders`, {
55
+ method: 'POST',
56
+ headers: {
57
+ Authorization: `Bearer ${accessToken}`,
58
+ 'Content-Type': 'application/json',
59
+ },
60
+ body: JSON.stringify({
61
+ intent: 'CAPTURE',
62
+ purchase_units: [
63
+ {
64
+ amount: {
65
+ currency_code: data.currency.toUpperCase(),
66
+ value: data.amount.toFixed(2),
67
+ },
68
+ description: data.description,
69
+ },
70
+ ],
71
+ application_context: {
72
+ return_url: data.returnUrl,
73
+ cancel_url: data.cancelUrl,
74
+ brand_name: 'Servcraft',
75
+ landing_page: 'LOGIN',
76
+ user_action: 'PAY_NOW',
77
+ },
78
+ }),
79
+ });
80
+
81
+ if (!response.ok) {
82
+ const error = await response.text();
83
+ logger.error({ error }, 'PayPal order creation failed');
84
+ throw new Error('Failed to create PayPal order');
85
+ }
86
+
87
+ const order = (await response.json()) as {
88
+ id: string;
89
+ status: string;
90
+ links: Array<{ rel: string; href: string }>;
91
+ };
92
+ const approveLink = order.links.find((l) => l.rel === 'approve');
93
+
94
+ return {
95
+ id: order.id,
96
+ paymentUrl: approveLink?.href,
97
+ status: this.mapPayPalStatus(order.status),
98
+ provider: 'paypal',
99
+ };
100
+ }
101
+
102
+ async captureOrder(orderId: string): Promise<PaymentIntent> {
103
+ const accessToken = await this.getAccessToken();
104
+
105
+ const response = await fetch(`${this.baseUrl}/v2/checkout/orders/${orderId}/capture`, {
106
+ method: 'POST',
107
+ headers: {
108
+ Authorization: `Bearer ${accessToken}`,
109
+ 'Content-Type': 'application/json',
110
+ },
111
+ });
112
+
113
+ if (!response.ok) {
114
+ throw new Error('Failed to capture PayPal order');
115
+ }
116
+
117
+ const result = (await response.json()) as { id: string; status: string };
118
+
119
+ return {
120
+ id: result.id,
121
+ status: this.mapPayPalStatus(result.status),
122
+ provider: 'paypal',
123
+ };
124
+ }
125
+
126
+ async refundPayment(captureId: string, amount?: number, currency?: string): Promise<boolean> {
127
+ const accessToken = await this.getAccessToken();
128
+
129
+ const body: Record<string, unknown> = {};
130
+ if (amount && currency) {
131
+ body.amount = {
132
+ value: amount.toFixed(2),
133
+ currency_code: currency.toUpperCase(),
134
+ };
135
+ }
136
+
137
+ const response = await fetch(`${this.baseUrl}/v2/payments/captures/${captureId}/refund`, {
138
+ method: 'POST',
139
+ headers: {
140
+ Authorization: `Bearer ${accessToken}`,
141
+ 'Content-Type': 'application/json',
142
+ },
143
+ body: JSON.stringify(body),
144
+ });
145
+
146
+ return response.ok;
147
+ }
148
+
149
+ async getOrderDetails(orderId: string): Promise<Record<string, unknown>> {
150
+ const accessToken = await this.getAccessToken();
151
+
152
+ const response = await fetch(`${this.baseUrl}/v2/checkout/orders/${orderId}`, {
153
+ headers: {
154
+ Authorization: `Bearer ${accessToken}`,
155
+ },
156
+ });
157
+
158
+ if (!response.ok) {
159
+ throw new Error('Failed to get PayPal order details');
160
+ }
161
+
162
+ return response.json() as Promise<Record<string, unknown>>;
163
+ }
164
+
165
+ verifyWebhookSignature(
166
+ _headers: Record<string, string>,
167
+ _body: string,
168
+ _webhookId: string
169
+ ): Promise<boolean> {
170
+ // PayPal webhook verification requires calling their API
171
+ // This is a simplified version
172
+ return Promise.resolve(true);
173
+ }
174
+
175
+ private mapPayPalStatus(status: string): Payment['status'] {
176
+ const statusMap: Record<string, Payment['status']> = {
177
+ CREATED: 'pending',
178
+ SAVED: 'pending',
179
+ APPROVED: 'processing',
180
+ VOIDED: 'cancelled',
181
+ COMPLETED: 'completed',
182
+ PAYER_ACTION_REQUIRED: 'pending',
183
+ };
184
+ return statusMap[status] || 'pending';
185
+ }
186
+ }
187
+
188
+ export function createPayPalProvider(config: PayPalConfig): PayPalProvider {
189
+ return new PayPalProvider(config);
190
+ }
@@ -0,0 +1,215 @@
1
+ import { logger } from '../../../core/logger.js';
2
+ import type { StripeConfig, StripePaymentData, PaymentIntent, Payment } from '../types.js';
3
+
4
+ /**
5
+ * Stripe Payment Provider
6
+ * Note: Requires 'stripe' package to be installed
7
+ * npm install stripe
8
+ */
9
+ export class StripeProvider {
10
+ private stripe: unknown;
11
+ private config: StripeConfig;
12
+
13
+ constructor(config: StripeConfig) {
14
+ this.config = config;
15
+ this.initStripe();
16
+ }
17
+
18
+ private async initStripe(): Promise<void> {
19
+ try {
20
+ // Dynamic import to avoid requiring stripe if not used
21
+ const Stripe = await import('stripe');
22
+ this.stripe = new Stripe.default(this.config.secretKey, {
23
+ apiVersion: '2025-02-24.acacia',
24
+ });
25
+ logger.info('Stripe provider initialized');
26
+ } catch {
27
+ logger.warn('Stripe package not installed. Run: npm install stripe');
28
+ }
29
+ }
30
+
31
+ async createPaymentIntent(data: StripePaymentData): Promise<PaymentIntent> {
32
+ if (!this.stripe) {
33
+ throw new Error('Stripe not initialized');
34
+ }
35
+
36
+ const stripe = this.stripe as {
37
+ paymentIntents: {
38
+ create: (params: Record<string, unknown>) => Promise<{
39
+ id: string;
40
+ client_secret: string;
41
+ status: string;
42
+ }>;
43
+ };
44
+ };
45
+
46
+ const paymentIntent = await stripe.paymentIntents.create({
47
+ amount: Math.round(data.amount * 100), // Convert to cents
48
+ currency: data.currency.toLowerCase(),
49
+ customer: data.customerId,
50
+ payment_method: data.paymentMethodId,
51
+ description: data.description,
52
+ metadata: data.metadata,
53
+ automatic_payment_methods: {
54
+ enabled: true,
55
+ },
56
+ });
57
+
58
+ return {
59
+ id: paymentIntent.id,
60
+ clientSecret: paymentIntent.client_secret,
61
+ status: this.mapStripeStatus(paymentIntent.status),
62
+ provider: 'stripe',
63
+ };
64
+ }
65
+
66
+ async confirmPayment(paymentIntentId: string): Promise<PaymentIntent> {
67
+ if (!this.stripe) {
68
+ throw new Error('Stripe not initialized');
69
+ }
70
+
71
+ const stripe = this.stripe as {
72
+ paymentIntents: {
73
+ confirm: (id: string) => Promise<{
74
+ id: string;
75
+ client_secret: string;
76
+ status: string;
77
+ }>;
78
+ };
79
+ };
80
+
81
+ const paymentIntent = await stripe.paymentIntents.confirm(paymentIntentId);
82
+
83
+ return {
84
+ id: paymentIntent.id,
85
+ clientSecret: paymentIntent.client_secret,
86
+ status: this.mapStripeStatus(paymentIntent.status),
87
+ provider: 'stripe',
88
+ };
89
+ }
90
+
91
+ async refundPayment(paymentIntentId: string, amount?: number): Promise<boolean> {
92
+ if (!this.stripe) {
93
+ throw new Error('Stripe not initialized');
94
+ }
95
+
96
+ const stripe = this.stripe as {
97
+ refunds: {
98
+ create: (params: Record<string, unknown>) => Promise<{ id: string }>;
99
+ };
100
+ };
101
+
102
+ const refund = await stripe.refunds.create({
103
+ payment_intent: paymentIntentId,
104
+ ...(amount ? { amount: Math.round(amount * 100) } : {}),
105
+ });
106
+
107
+ return !!refund.id;
108
+ }
109
+
110
+ async createCustomer(email: string, name?: string): Promise<string> {
111
+ if (!this.stripe) {
112
+ throw new Error('Stripe not initialized');
113
+ }
114
+
115
+ const stripe = this.stripe as {
116
+ customers: {
117
+ create: (params: Record<string, unknown>) => Promise<{ id: string }>;
118
+ };
119
+ };
120
+
121
+ const customer = await stripe.customers.create({
122
+ email,
123
+ name,
124
+ });
125
+
126
+ return customer.id;
127
+ }
128
+
129
+ async createSubscription(
130
+ customerId: string,
131
+ priceId: string,
132
+ trialDays?: number
133
+ ): Promise<{ subscriptionId: string; clientSecret?: string }> {
134
+ if (!this.stripe) {
135
+ throw new Error('Stripe not initialized');
136
+ }
137
+
138
+ const stripe = this.stripe as {
139
+ subscriptions: {
140
+ create: (params: Record<string, unknown>) => Promise<{
141
+ id: string;
142
+ latest_invoice: { payment_intent: { client_secret: string } };
143
+ }>;
144
+ };
145
+ };
146
+
147
+ const subscription = await stripe.subscriptions.create({
148
+ customer: customerId,
149
+ items: [{ price: priceId }],
150
+ payment_behavior: 'default_incomplete',
151
+ expand: ['latest_invoice.payment_intent'],
152
+ ...(trialDays ? { trial_period_days: trialDays } : {}),
153
+ });
154
+
155
+ return {
156
+ subscriptionId: subscription.id,
157
+ clientSecret: subscription.latest_invoice?.payment_intent?.client_secret,
158
+ };
159
+ }
160
+
161
+ async cancelSubscription(subscriptionId: string): Promise<boolean> {
162
+ if (!this.stripe) {
163
+ throw new Error('Stripe not initialized');
164
+ }
165
+
166
+ const stripe = this.stripe as {
167
+ subscriptions: {
168
+ cancel: (id: string) => Promise<{ id: string }>;
169
+ };
170
+ };
171
+
172
+ const result = await stripe.subscriptions.cancel(subscriptionId);
173
+ return !!result.id;
174
+ }
175
+
176
+ verifyWebhookSignature(payload: string, signature: string): Record<string, unknown> | null {
177
+ if (!this.stripe || !this.config.webhookSecret) {
178
+ return null;
179
+ }
180
+
181
+ try {
182
+ const stripe = this.stripe as {
183
+ webhooks: {
184
+ constructEvent: (
185
+ payload: string,
186
+ signature: string,
187
+ secret: string
188
+ ) => Record<string, unknown>;
189
+ };
190
+ };
191
+
192
+ return stripe.webhooks.constructEvent(payload, signature, this.config.webhookSecret);
193
+ } catch {
194
+ logger.error('Stripe webhook signature verification failed');
195
+ return null;
196
+ }
197
+ }
198
+
199
+ private mapStripeStatus(status: string): Payment['status'] {
200
+ const statusMap: Record<string, Payment['status']> = {
201
+ requires_payment_method: 'pending',
202
+ requires_confirmation: 'pending',
203
+ requires_action: 'processing',
204
+ processing: 'processing',
205
+ requires_capture: 'processing',
206
+ canceled: 'cancelled',
207
+ succeeded: 'completed',
208
+ };
209
+ return statusMap[status] || 'pending';
210
+ }
211
+ }
212
+
213
+ export function createStripeProvider(config: StripeConfig): StripeProvider {
214
+ return new StripeProvider(config);
215
+ }
@@ -0,0 +1,140 @@
1
+ export type PaymentProvider = 'stripe' | 'paypal' | 'mobile_money' | 'manual';
2
+ export type PaymentStatus =
3
+ | 'pending'
4
+ | 'processing'
5
+ | 'completed'
6
+ | 'failed'
7
+ | 'refunded'
8
+ | 'cancelled';
9
+ export type PaymentMethod =
10
+ | 'card'
11
+ | 'bank_transfer'
12
+ | 'mobile_money'
13
+ | 'paypal'
14
+ | 'crypto'
15
+ | 'cash';
16
+
17
+ export interface Payment {
18
+ id: string;
19
+ userId: string;
20
+ provider: PaymentProvider;
21
+ method: PaymentMethod;
22
+ status: PaymentStatus;
23
+ amount: number;
24
+ currency: string;
25
+ description?: string;
26
+ metadata?: Record<string, unknown>;
27
+ providerPaymentId?: string;
28
+ providerCustomerId?: string;
29
+ refundedAmount?: number;
30
+ failureReason?: string;
31
+ paidAt?: Date;
32
+ createdAt: Date;
33
+ updatedAt: Date;
34
+ }
35
+
36
+ export interface CreatePaymentData {
37
+ userId: string;
38
+ amount: number;
39
+ currency: string;
40
+ provider: PaymentProvider;
41
+ method?: PaymentMethod;
42
+ description?: string;
43
+ metadata?: Record<string, unknown>;
44
+ returnUrl?: string;
45
+ cancelUrl?: string;
46
+ }
47
+
48
+ export interface PaymentIntent {
49
+ id: string;
50
+ clientSecret?: string;
51
+ paymentUrl?: string;
52
+ status: PaymentStatus;
53
+ provider: PaymentProvider;
54
+ }
55
+
56
+ // Stripe specific
57
+ export interface StripeConfig {
58
+ secretKey: string;
59
+ publishableKey: string;
60
+ webhookSecret?: string;
61
+ }
62
+
63
+ export interface StripePaymentData {
64
+ amount: number;
65
+ currency: string;
66
+ customerId?: string;
67
+ paymentMethodId?: string;
68
+ description?: string;
69
+ metadata?: Record<string, string>;
70
+ }
71
+
72
+ // PayPal specific
73
+ export interface PayPalConfig {
74
+ clientId: string;
75
+ clientSecret: string;
76
+ sandbox: boolean;
77
+ }
78
+
79
+ export interface PayPalOrderData {
80
+ amount: number;
81
+ currency: string;
82
+ description?: string;
83
+ returnUrl: string;
84
+ cancelUrl: string;
85
+ }
86
+
87
+ // Mobile Money (MTN, Orange, Wave, etc.)
88
+ export interface MobileMoneyConfig {
89
+ provider: 'mtn' | 'orange' | 'wave' | 'mpesa';
90
+ apiKey: string;
91
+ apiSecret: string;
92
+ environment: 'sandbox' | 'production';
93
+ callbackUrl: string;
94
+ }
95
+
96
+ export interface MobileMoneyPaymentData {
97
+ amount: number;
98
+ currency: string;
99
+ phoneNumber: string;
100
+ description?: string;
101
+ reference?: string;
102
+ }
103
+
104
+ // Subscription
105
+ export interface Subscription {
106
+ id: string;
107
+ userId: string;
108
+ planId: string;
109
+ provider: PaymentProvider;
110
+ providerSubscriptionId?: string;
111
+ status: 'active' | 'cancelled' | 'past_due' | 'trialing' | 'paused';
112
+ currentPeriodStart: Date;
113
+ currentPeriodEnd: Date;
114
+ cancelAtPeriodEnd: boolean;
115
+ createdAt: Date;
116
+ updatedAt: Date;
117
+ }
118
+
119
+ export interface Plan {
120
+ id: string;
121
+ name: string;
122
+ description?: string;
123
+ amount: number;
124
+ currency: string;
125
+ interval: 'day' | 'week' | 'month' | 'year';
126
+ intervalCount: number;
127
+ trialDays?: number;
128
+ features?: string[];
129
+ metadata?: Record<string, unknown>;
130
+ active: boolean;
131
+ }
132
+
133
+ // Webhook events
134
+ export interface PaymentWebhookEvent {
135
+ id: string;
136
+ type: string;
137
+ provider: PaymentProvider;
138
+ data: Record<string, unknown>;
139
+ createdAt: Date;
140
+ }