alepha 0.20.4 → 0.20.6
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/dist/api/audits/index.d.ts +391 -359
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +23 -1
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +18 -0
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +51 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +33 -14
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +452 -155
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +474 -159
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +32 -4
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +53 -0
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +29 -1
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +55 -13
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/organizations/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +15 -0
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +37 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.d.ts +150 -9
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +237 -28
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +3 -3
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bin/index.js +0 -0
- package/dist/bucket/index.d.ts +18 -0
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +47 -0
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +24 -0
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +20 -3
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +155 -0
- package/dist/cache/database/index.d.ts.map +1 -0
- package/dist/cache/database/index.js +266 -0
- package/dist/cache/database/index.js.map +1 -0
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +35 -5
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +85 -6
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.js +1 -1
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.browser.js.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/brevo/index.js.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/queue/core/index.js.map +1 -1
- package/dist/queue/core/index.workerd.js.map +1 -1
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.js +2 -0
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/react/websocket/index.js.map +1 -1
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +22 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +12 -0
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +12 -0
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/etag/index.js.map +1 -1
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/websocket/index.browser.js +4 -0
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js +10 -0
- package/dist/websocket/index.js.map +1 -1
- package/package.json +282 -272
- package/src/api/audits/controllers/AdminAuditController.ts +29 -0
- package/src/api/files/controllers/FileController.ts +24 -0
- package/src/api/files/services/FileService.ts +41 -0
- package/src/api/jobs/__tests__/$job.spec.ts +427 -2
- package/src/api/jobs/entities/jobExecutionEntity.ts +3 -3
- package/src/api/jobs/index.ts +47 -10
- package/src/api/jobs/primitives/$job.ts +22 -9
- package/src/api/jobs/providers/DirectJobDispatcher.ts +71 -0
- package/src/api/jobs/providers/JobDispatcher.ts +49 -0
- package/src/api/jobs/providers/JobProvider.ts +365 -142
- package/src/api/jobs/providers/JobQueueProvider.ts +43 -18
- package/src/api/jobs/schemas/jobConfigAtom.ts +4 -3
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +11 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +4 -2
- package/src/api/jobs/services/JobService.ts +21 -11
- package/src/api/keys/controllers/AdminApiKeyController.ts +23 -0
- package/src/api/keys/services/ApiKeyService.ts +42 -0
- package/src/api/notifications/__tests__/AlephaApiNotifications.spec.ts +63 -0
- package/src/api/notifications/controllers/AdminNotificationController.ts +48 -1
- package/src/api/notifications/index.ts +13 -3
- package/src/api/notifications/jobs/NotificationJobs.ts +0 -6
- package/src/api/parameters/controllers/AdminParameterController.ts +26 -0
- package/src/api/parameters/services/ParameterProvider.ts +18 -0
- package/src/api/users/__tests__/Registration-emailMode.spec.ts +203 -0
- package/src/api/users/__tests__/UsernameSlugger.spec.ts +138 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +41 -3
- package/src/api/users/controllers/AdminSessionController.ts +29 -0
- package/src/api/users/controllers/AdminUserController.ts +32 -0
- package/src/api/users/index.ts +3 -0
- package/src/api/users/services/CredentialService.ts +5 -0
- package/src/api/users/services/RegistrationService.ts +49 -1
- package/src/api/users/services/SessionCrudService.ts +16 -0
- package/src/api/users/services/SessionService.ts +17 -59
- package/src/api/users/services/UsernameSlugger.ts +195 -0
- package/src/bucket/primitives/$bucket.ts +21 -0
- package/src/bucket/providers/CloudflareR2Provider.ts +15 -0
- package/src/bucket/providers/FileStorageProvider.ts +9 -0
- package/src/bucket/providers/LocalFileStorageProvider.ts +14 -0
- package/src/bucket/providers/MemoryFileStorageProvider.ts +9 -0
- package/src/bucket/providers/NodeS3BucketProvider.ts +35 -0
- package/src/cache/core/primitives/$cache.ts +20 -3
- package/src/cache/database/__tests__/DatabaseCacheProvider.behavior.spec.ts +203 -0
- package/src/cache/database/__tests__/DatabaseCacheProvider.spec.ts +110 -0
- package/src/cache/database/entities/cacheEntries.ts +55 -0
- package/src/cache/database/index.ts +36 -0
- package/src/cache/database/providers/DatabaseCacheProvider.ts +348 -0
- package/src/cli/core/services/ProjectScaffolder.ts +0 -2
- package/src/cli/core/tasks/BuildCloudflareTask.ts +17 -3
- package/src/cli/core/tasks/BuildSitemapTask.ts +7 -0
- package/src/cli/core/tasks/BuildVercelTask.ts +82 -3
- package/src/cli/platform/__tests__/detectResources.spec.ts +96 -0
- package/src/cli/platform/commands/platform.ts +7 -1
- package/src/scheduler/index.ts +14 -0
- package/src/scheduler/providers/CronProvider.ts +13 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/payments/entities/paymentIntents.ts","../../../src/api/payments/schemas/intentSchemas.ts","../../../src/api/payments/entities/refunds.ts","../../../src/api/payments/schemas/refundSchemas.ts","../../../src/api/payments/errors/PaymentError.ts","../../../src/api/payments/providers/PaymentProvider.ts","../../../src/api/payments/services/PaymentService.ts","../../../src/api/payments/controllers/AdminPaymentController.ts","../../../src/api/payments/entities/paymentMethods.ts","../../../src/api/payments/schemas/paymentMethodSchemas.ts","../../../src/api/payments/services/PaymentMethodService.ts","../../../src/api/payments/controllers/PaymentController.ts","../../../src/api/payments/providers/MemoryPaymentProvider.ts","../../../src/api/payments/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const paymentIntents = $entity({\n name: \"payment_intents\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n organizationId: db.organization(),\n amount: t.integer(),\n currency: t.text({ size: \"short\" }),\n status: t.enum([\n \"created\",\n \"processing\",\n \"authorized\",\n \"captured\",\n \"partially_refunded\",\n \"voided\",\n \"failed\",\n \"cancelled\",\n \"refunded\",\n \"expired\",\n ]),\n providerRef: t.optional(t.text()),\n providerRaw: t.optional(t.json()),\n metadata: t.optional(t.json()),\n paymentMethodId: t.optional(t.uuid()),\n userId: t.optional(t.uuid()),\n }),\n indexes: [\"status\", \"organizationId\", \"userId\", \"createdAt\"],\n});\n\nexport type PaymentIntentEntity = Static<typeof paymentIntents.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\nimport { paymentIntents } from \"../entities/paymentIntents.ts\";\n\nexport const createIntentSchema = t.object({\n amount: t.integer({ minimum: 1 }),\n currency: t.text({ size: \"short\" }),\n metadata: t.optional(t.json()),\n paymentMethodId: t.optional(t.uuid()),\n});\n\nexport type CreateIntent = Static<typeof createIntentSchema>;\n\nexport const createCheckoutSchema = t.object({\n intentId: t.uuid(),\n returnUrl: t.text(),\n authorize: t.optional(t.boolean()),\n});\n\nexport type CreateCheckout = Static<typeof createCheckoutSchema>;\n\nexport const checkoutResponseSchema = t.object({\n url: t.text(),\n intentId: t.text(),\n});\n\nexport type CheckoutResponse = Static<typeof checkoutResponseSchema>;\n\nexport const captureIntentSchema = t.object({\n amount: t.optional(t.integer({ minimum: 1 })),\n});\n\nexport type CaptureIntent = Static<typeof captureIntentSchema>;\n\nexport const refundIntentSchema = t.object({\n amount: t.integer({ minimum: 1 }),\n reason: t.optional(t.text()),\n});\n\nexport type RefundIntent = Static<typeof refundIntentSchema>;\n\nexport const recordCashSchema = t.object({\n amount: t.integer({ minimum: 1 }),\n currency: t.text({ size: \"short\" }),\n metadata: t.optional(t.json()),\n});\n\nexport type RecordCash = Static<typeof recordCashSchema>;\n\nexport const intentQuerySchema = t.extend(pageQuerySchema, {\n status: t.optional(t.text({ description: \"Filter by status\" })),\n userId: t.optional(t.uuid({ description: \"Filter by user ID\" })),\n});\n\nexport type IntentQuery = Static<typeof intentQuerySchema>;\n\nexport const intentResourceSchema = paymentIntents.schema;\n\nexport type IntentResource = Static<typeof intentResourceSchema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const refunds = $entity({\n name: \"refunds\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n organizationId: db.organization(),\n intentId: t.uuid(),\n amount: t.integer(),\n currency: t.text({ size: \"short\" }),\n status: t.enum([\"pending\", \"processing\", \"completed\", \"failed\"]),\n reason: t.optional(t.text()),\n providerRef: t.optional(t.text()),\n }),\n indexes: [\"intentId\", \"organizationId\", \"status\"],\n});\n\nexport type RefundEntity = Static<typeof refunds.schema>;\n","import type { Static } from \"alepha\";\nimport { refunds } from \"../entities/refunds.ts\";\n\nexport const refundResourceSchema = refunds.schema;\n\nexport type RefundResource = Static<typeof refundResourceSchema>;\n","import { AlephaError } from \"alepha\";\n\nexport class PaymentError extends AlephaError {\n public readonly status = 400;\n}\n","import type { PaymentIntentEntity } from \"../entities/paymentIntents.ts\";\n\nexport interface CreateSessionResult {\n url: string;\n providerRef: string;\n}\n\nexport interface RefundResult {\n providerRef: string;\n}\n\nexport interface WebhookEvent {\n providerRef: string;\n status: string;\n raw: unknown;\n}\n\nexport interface CreatePaymentMethodResult {\n providerRef: string;\n type: string;\n brand?: string;\n last4?: string;\n expMonth?: number;\n expYear?: number;\n}\n\nexport abstract class PaymentProvider {\n /**\n * Create a checkout session with the PSP.\n * Returns a URL to redirect the user to, and the PSP's reference ID.\n */\n abstract createSession(\n intent: PaymentIntentEntity,\n options: { returnUrl: string; authorize?: boolean },\n ): Promise<CreateSessionResult>;\n\n /**\n * Capture a previously authorized payment.\n * Amount can differ from the original authorization (partial capture).\n */\n abstract capturePayment(providerRef: string, amount: number): Promise<void>;\n\n /**\n * Void/cancel a previously authorized payment before capture.\n */\n abstract voidPayment(providerRef: string): Promise<void>;\n\n /**\n * Refund a captured payment (partial or full).\n */\n abstract refundPayment(\n providerRef: string,\n amount: number,\n ): Promise<RefundResult>;\n\n /**\n * Parse and verify an incoming PSP webhook request.\n *\n * Implementations MUST verify the webhook signature before returning.\n * Throw an error if the signature is invalid or missing — this is the\n * only authentication on the webhook endpoint (no $secure middleware).\n *\n * Failure to verify signatures allows attackers to forge payment\n * confirmations by POSTing fake webhook events.\n */\n abstract parseWebhook(request: Request): Promise<WebhookEvent>;\n\n /**\n * Store a payment method token with the PSP.\n */\n abstract createPaymentMethod(\n userId: string,\n token: string,\n ): Promise<CreatePaymentMethodResult>;\n\n /**\n * Delete a stored payment method from the PSP.\n */\n abstract deletePaymentMethod(providerRef: string): Promise<void>;\n\n /**\n * Expire/cancel a checkout session on the PSP side.\n * Called during stale session cleanup.\n */\n abstract expireSession(providerRef: string): Promise<void>;\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $job } from \"alepha/api/jobs\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport {\n type PaymentIntentEntity,\n paymentIntents,\n} from \"../entities/paymentIntents.ts\";\nimport { type RefundEntity, refunds } from \"../entities/refunds.ts\";\nimport { PaymentError } from \"../errors/PaymentError.ts\";\nimport { PaymentProvider } from \"../providers/PaymentProvider.ts\";\n\nexport class PaymentService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly provider = $inject(PaymentProvider);\n protected readonly intentRepo = $repository(paymentIntents);\n protected readonly refundRepo = $repository(refunds);\n\n /**\n * Expires stale payment intents that have been in \"processing\" status\n * for more than 30 minutes. Runs every 5 minutes — shares the CF wrangler\n * trigger with the jobs sweep so no extra binding is consumed.\n */\n protected readonly expireStaleIntents = $job({\n name: \"api:payments:expireStaleIntents\",\n cron: \"*/5 * * * *\",\n handler: async () => {\n const cutoff = this.dateTime.now().subtract(30, \"minutes\").toISOString();\n\n const stale = await this.intentRepo.findMany({\n where: { status: { eq: \"processing\" }, createdAt: { lt: cutoff } },\n });\n\n for (const intent of stale) {\n if (intent.providerRef) {\n try {\n await this.provider.expireSession(intent.providerRef);\n } catch (error) {\n this.log.warn(\n `Failed to expire session for intent ${intent.id}`,\n error,\n );\n }\n }\n await this.intentRepo.updateById(intent.id, { status: \"expired\" });\n this.log.info(`Expired stale intent ${intent.id}`);\n }\n },\n });\n\n /**\n * Create a new payment intent in \"created\" status.\n */\n public async createIntent(\n amount: number,\n currency: string,\n metadata?: unknown,\n options?: { paymentMethodId?: string; userId?: string },\n ): Promise<PaymentIntentEntity> {\n return await this.intentRepo.create({\n amount,\n currency: currency.toLowerCase(),\n status: \"created\",\n metadata: metadata as any,\n paymentMethodId: options?.paymentMethodId,\n userId: options?.userId,\n });\n }\n\n /**\n * Create a checkout session with the payment provider and\n * transition the intent to \"processing\".\n */\n public async createSession(\n intentId: string,\n returnUrl: string,\n authorize?: boolean,\n userId?: string,\n ): Promise<{ url: string; intentId: string }> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"created\", \"createSession\");\n\n // Verify intent ownership if userId is provided\n if (userId && intent.userId && intent.userId !== userId) {\n throw new PaymentError(\"Payment intent does not belong to this user\");\n }\n\n // Associate intent with user if not already set\n if (userId && !intent.userId) {\n await this.intentRepo.updateById(intent.id, { userId });\n }\n\n const result = await this.provider.createSession(intent, {\n returnUrl,\n authorize,\n });\n\n await this.intentRepo.updateById(intent.id, {\n status: \"processing\",\n providerRef: result.providerRef,\n });\n\n return { url: result.url, intentId: intent.id };\n }\n\n /**\n * Handle an incoming webhook from the payment provider.\n */\n public async handleWebhook(request: Request): Promise<void> {\n const event = await this.provider.parseWebhook(request);\n const intents = await this.intentRepo.findMany({\n where: { providerRef: { eq: event.providerRef } },\n limit: 1,\n });\n\n if (intents.length === 0) {\n this.log.warn(`Webhook for unknown providerRef: ${event.providerRef}`);\n return;\n }\n\n const intent = intents[0];\n await this.handleWebhookEvent(intent.id, event.status, event.raw);\n }\n\n /**\n * Process a webhook event by updating the intent status and emitting\n * the corresponding payment event.\n */\n /**\n * Valid status transitions from webhook events.\n * Only these transitions are allowed — all others are silently ignored.\n */\n protected static readonly VALID_WEBHOOK_TRANSITIONS: Record<\n string,\n string[]\n > = {\n processing: [\"authorized\", \"captured\", \"failed\"],\n authorized: [\"captured\", \"failed\"],\n };\n\n public async handleWebhookEvent(\n intentId: string,\n status: string,\n raw?: unknown,\n ): Promise<void> {\n const intent = await this.getIntent(intentId);\n\n const eventMap = {\n authorized: \"payments:authorized\",\n captured: \"payments:captured\",\n failed: \"payments:failed\",\n } as const;\n\n type WebhookStatus = keyof typeof eventMap;\n if (!(status in eventMap)) {\n this.log.warn(`Unknown webhook status: ${status}`);\n return;\n }\n\n const webhookStatus = status as WebhookStatus;\n\n // Validate status transition\n const allowed = PaymentService.VALID_WEBHOOK_TRANSITIONS[intent.status];\n if (!allowed?.includes(webhookStatus)) {\n this.log.warn(\n `Ignoring webhook: cannot transition ${intent.status} → ${webhookStatus}`,\n { intentId: intent.id },\n );\n return;\n }\n\n await this.intentRepo.updateById(intent.id, {\n status: webhookStatus,\n providerRaw: raw as any,\n });\n\n await this.alepha.events.emit(eventMap[webhookStatus], {\n intentId: intent.id,\n amount: intent.amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n }\n\n /**\n * Capture a previously authorized payment. Optionally specify a different\n * amount for partial capture.\n */\n public async capture(\n intentId: string,\n finalAmount?: number,\n ): Promise<PaymentIntentEntity> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"authorized\", \"capture\");\n\n const amount = finalAmount ?? intent.amount;\n if (amount > intent.amount) {\n throw new PaymentError(\n `Capture amount ${amount} exceeds authorized amount ${intent.amount}`,\n );\n }\n\n if (intent.providerRef) {\n await this.provider.capturePayment(intent.providerRef, amount);\n }\n\n const updated = await this.intentRepo.updateById(intent.id, {\n status: \"captured\",\n amount,\n });\n\n await this.alepha.events.emit(\"payments:captured\", {\n intentId: intent.id,\n amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return updated;\n }\n\n /**\n * Void a previously authorized payment before capture.\n */\n public async void(intentId: string): Promise<PaymentIntentEntity> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"authorized\", \"void\");\n\n if (intent.providerRef) {\n await this.provider.voidPayment(intent.providerRef);\n }\n\n const updated = await this.intentRepo.updateById(intent.id, {\n status: \"voided\",\n });\n\n await this.alepha.events.emit(\"payments:voided\", {\n intentId: intent.id,\n amount: intent.amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return updated;\n }\n\n /**\n * Refund a captured payment (partial or full).\n */\n public async refund(\n intentId: string,\n amount: number,\n reason?: string,\n ): Promise<RefundEntity> {\n const intent = await this.getIntent(intentId);\n\n // Allow refunds from both \"captured\" and \"partially_refunded\" states\n if (\n intent.status !== \"captured\" &&\n intent.status !== \"partially_refunded\"\n ) {\n throw new PaymentError(\n `Cannot refund: intent ${intent.id} is '${intent.status}', expected 'captured' or 'partially_refunded'`,\n );\n }\n\n // Validate refund amount against remaining refundable amount\n const existingRefunds = await this.refundRepo.findMany({\n where: { intentId: { eq: intent.id } },\n });\n const totalRefunded = existingRefunds.reduce((sum, r) => sum + r.amount, 0);\n const remaining = intent.amount - totalRefunded;\n\n if (amount > remaining) {\n throw new PaymentError(\n `Refund amount ${amount} exceeds remaining refundable amount ${remaining}`,\n );\n }\n\n let refundProviderRef: string | undefined;\n if (intent.providerRef) {\n const result = await this.provider.refundPayment(\n intent.providerRef,\n amount,\n );\n refundProviderRef = result.providerRef;\n }\n\n const refund = await this.refundRepo.create({\n intentId: intent.id,\n organizationId: intent.organizationId,\n amount,\n currency: intent.currency,\n status: \"completed\",\n reason,\n providerRef: refundProviderRef,\n });\n\n // Set status based on whether fully or partially refunded\n const newTotalRefunded = totalRefunded + amount;\n const newStatus =\n newTotalRefunded >= intent.amount ? \"refunded\" : \"partially_refunded\";\n await this.intentRepo.updateById(intent.id, {\n status: newStatus,\n });\n\n await this.alepha.events.emit(\"payments:refunded\", {\n intentId: intent.id,\n refundId: refund.id,\n amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return refund;\n }\n\n /**\n * Record a cash or offline payment directly as captured,\n * bypassing the checkout flow.\n */\n public async recordCashPayment(\n amount: number,\n currency: string,\n metadata?: unknown,\n ): Promise<PaymentIntentEntity> {\n const intent = await this.intentRepo.create({\n amount,\n currency: currency.toLowerCase(),\n status: \"captured\",\n metadata: metadata as any,\n });\n\n await this.alepha.events.emit(\"payments:captured\", {\n intentId: intent.id,\n amount,\n currency,\n metadata,\n });\n\n return intent;\n }\n\n /**\n * Cancel a payment intent that has not yet entered processing.\n */\n public async cancel(intentId: string): Promise<PaymentIntentEntity> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"created\", \"cancel\");\n\n const cancelled = await this.intentRepo.updateById(intent.id, {\n status: \"cancelled\",\n });\n\n await this.alepha.events.emit(\"payments:cancelled\", {\n intentId: intent.id,\n amount: intent.amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return cancelled;\n }\n\n /**\n * Get a payment intent by ID. Throws NotFoundError if not found.\n */\n public async getIntent(intentId: string): Promise<PaymentIntentEntity> {\n return await this.intentRepo.getById(intentId);\n }\n\n /**\n * Find payment intents with optional filters and pagination.\n */\n public async findIntents(query: {\n status?: string;\n userId?: string;\n sort?: string;\n size?: number;\n page?: number;\n }) {\n const where = this.intentRepo.createQueryWhere();\n if (query.status)\n where.status = { eq: query.status as PaymentIntentEntity[\"status\"] };\n if (query.userId) where.userId = { eq: query.userId };\n return await this.intentRepo.paginate(query, { where }, { count: true });\n }\n\n protected assertStatus(\n intent: PaymentIntentEntity,\n expected: PaymentIntentEntity[\"status\"],\n operation: string,\n ): void {\n if (intent.status !== expected) {\n throw new PaymentError(\n `Cannot ${operation}: intent ${intent.id} is '${intent.status}', expected '${expected}'`,\n );\n }\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport {\n captureIntentSchema,\n intentQuerySchema,\n intentResourceSchema,\n recordCashSchema,\n refundIntentSchema,\n} from \"../schemas/intentSchemas.ts\";\nimport { refundResourceSchema } from \"../schemas/refundSchemas.ts\";\nimport { PaymentService } from \"../services/PaymentService.ts\";\n\nexport class AdminPaymentController {\n protected readonly url = \"/admin/payments\";\n protected readonly group = \"admin:payments\";\n protected readonly payments = $inject(PaymentService);\n\n /**\n * List payment intents with pagination and filtering.\n */\n public readonly listIntents = $action({\n path: `${this.url}/intents`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:read\"] })],\n description: \"List payment intents\",\n schema: {\n query: intentQuerySchema,\n response: t.page(intentResourceSchema),\n },\n handler: ({ query }) => this.payments.findIntents(query),\n });\n\n /**\n * Get a payment intent by ID.\n */\n public readonly getIntent = $action({\n path: `${this.url}/intents/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:read\"] })],\n description: \"Get payment intent details\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: intentResourceSchema,\n },\n handler: ({ params }) => this.payments.getIntent(params.id),\n });\n\n /**\n * Capture an authorized intent.\n */\n public readonly captureIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/capture`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Capture an authorized payment intent\",\n schema: {\n params: t.object({ id: t.uuid() }),\n body: captureIntentSchema,\n response: intentResourceSchema,\n },\n handler: ({ params, body }) =>\n this.payments.capture(params.id, body.amount),\n });\n\n /**\n * Void an authorized intent.\n */\n public readonly voidIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/void`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Void an authorized payment intent\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: intentResourceSchema,\n },\n handler: ({ params }) => this.payments.void(params.id),\n });\n\n /**\n * Refund a captured intent.\n */\n public readonly refundIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/refund`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Issue partial or full refund\",\n schema: {\n params: t.object({ id: t.uuid() }),\n body: refundIntentSchema,\n response: refundResourceSchema,\n },\n handler: ({ params, body }) =>\n this.payments.refund(params.id, body.amount, body.reason),\n });\n\n /**\n * Cancel a created intent.\n */\n public readonly cancelIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Cancel a created payment intent\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: intentResourceSchema,\n },\n handler: ({ params }) => this.payments.cancel(params.id),\n });\n\n /**\n * Record a cash payment.\n */\n public readonly recordCash = $action({\n method: \"POST\",\n path: `${this.url}/cash`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Record a cash payment\",\n schema: {\n body: recordCashSchema,\n response: intentResourceSchema,\n },\n handler: ({ body }) =>\n this.payments.recordCashPayment(\n body.amount,\n body.currency,\n body.metadata,\n ),\n });\n\n /**\n * PSP webhook endpoint (not under /admin, no auth — verified by provider).\n */\n public readonly webhook = $action({\n method: \"POST\",\n path: \"/payments/webhook\",\n group: this.group,\n description: \"PSP webhook endpoint\",\n schema: {\n response: okSchema,\n },\n handler: async (request) => {\n await this.payments.handleWebhook(request.raw.web!.req);\n return { ok: true };\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const paymentMethods = $entity({\n name: \"payment_methods\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n organizationId: db.organization(),\n userId: t.uuid(),\n type: t.text({ size: \"short\" }),\n brand: t.optional(t.text({ size: \"short\" })),\n last4: t.optional(t.text({ size: \"short\" })),\n expMonth: t.optional(t.integer()),\n expYear: t.optional(t.integer()),\n isDefault: t.boolean(),\n providerRef: t.text(),\n }),\n indexes: [\"userId\", \"organizationId\"],\n});\n\nexport type PaymentMethodEntity = Static<typeof paymentMethods.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { paymentMethods } from \"../entities/paymentMethods.ts\";\n\nexport const addPaymentMethodSchema = t.object({\n token: t.text(),\n});\n\nexport type AddPaymentMethod = Static<typeof addPaymentMethodSchema>;\n\nexport const paymentMethodResourceSchema = paymentMethods.schema;\n\nexport type PaymentMethodResource = Static<typeof paymentMethodResourceSchema>;\n","import { $inject } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport {\n type PaymentMethodEntity,\n paymentMethods,\n} from \"../entities/paymentMethods.ts\";\nimport { PaymentError } from \"../errors/PaymentError.ts\";\nimport { PaymentProvider } from \"../providers/PaymentProvider.ts\";\n\nexport class PaymentMethodService {\n protected readonly log = $logger();\n protected readonly provider = $inject(PaymentProvider);\n protected readonly methodRepo = $repository(paymentMethods);\n\n public async addPaymentMethod(\n userId: string,\n organizationId: string,\n token: string,\n ): Promise<PaymentMethodEntity> {\n const result = await this.provider.createPaymentMethod(userId, token);\n\n const existing = await this.methodRepo.findMany({\n where: { userId: { eq: userId } },\n });\n\n return await this.methodRepo.create({\n userId,\n organizationId,\n type: result.type,\n brand: result.brand,\n last4: result.last4,\n expMonth: result.expMonth,\n expYear: result.expYear,\n isDefault: existing.length === 0,\n providerRef: result.providerRef,\n });\n }\n\n public async listPaymentMethods(\n userId: string,\n ): Promise<PaymentMethodEntity[]> {\n return await this.methodRepo.findMany({\n where: { userId: { eq: userId } },\n });\n }\n\n public async removePaymentMethod(\n methodId: string,\n userId: string,\n ): Promise<void> {\n const method = await this.methodRepo.getById(methodId);\n if (method.userId !== userId) {\n throw new PaymentError(\"Cannot remove another user's payment method\");\n }\n\n await this.provider.deletePaymentMethod(method.providerRef);\n await this.methodRepo.deleteById(method.id);\n }\n\n public async setDefault(\n methodId: string,\n userId: string,\n ): Promise<PaymentMethodEntity> {\n const method = await this.methodRepo.getById(methodId);\n if (method.userId !== userId) {\n throw new PaymentError(\"Cannot modify another user's payment method\");\n }\n\n const userMethods = await this.methodRepo.findMany({\n where: { userId: { eq: userId } },\n });\n\n for (const m of userMethods) {\n if (m.isDefault) {\n await this.methodRepo.updateById(m.id, { isDefault: false });\n }\n }\n\n return await this.methodRepo.updateById(method.id, { isDefault: true });\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { PaymentError } from \"../errors/PaymentError.ts\";\nimport {\n checkoutResponseSchema,\n createCheckoutSchema,\n} from \"../schemas/intentSchemas.ts\";\nimport {\n addPaymentMethodSchema,\n paymentMethodResourceSchema,\n} from \"../schemas/paymentMethodSchemas.ts\";\nimport { PaymentMethodService } from \"../services/PaymentMethodService.ts\";\nimport { PaymentService } from \"../services/PaymentService.ts\";\n\nexport class PaymentController {\n protected readonly url = \"/payments\";\n protected readonly group = \"payments\";\n protected readonly payments = $inject(PaymentService);\n protected readonly paymentMethods = $inject(PaymentMethodService);\n\n /**\n * List the current user's saved payment methods.\n */\n public readonly listPaymentMethods = $action({\n path: `${this.url}/payment-methods`,\n group: this.group,\n use: [$secure()],\n description: \"List current user's saved payment methods\",\n schema: {\n response: t.array(paymentMethodResourceSchema),\n },\n handler: ({ user }) => this.paymentMethods.listPaymentMethods(user.id),\n });\n\n /**\n * Add a new payment method.\n */\n public readonly addPaymentMethod = $action({\n method: \"POST\",\n path: `${this.url}/payment-methods`,\n group: this.group,\n use: [$secure()],\n description: \"Tokenize and store a new payment method\",\n schema: {\n body: addPaymentMethodSchema,\n response: paymentMethodResourceSchema,\n },\n handler: ({ body, user }) => {\n if (!user.organization) {\n throw new PaymentError(\n \"Organization is required to add a payment method\",\n );\n }\n return this.paymentMethods.addPaymentMethod(\n user.id,\n user.organization,\n body.token,\n );\n },\n });\n\n /**\n * Remove a payment method.\n */\n public readonly removePaymentMethod = $action({\n method: \"DELETE\",\n path: `${this.url}/payment-methods/:id`,\n group: this.group,\n use: [$secure()],\n description: \"Remove own payment method\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: async ({ params, user }) => {\n await this.paymentMethods.removePaymentMethod(params.id, user.id);\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Set a payment method as default.\n */\n public readonly setDefaultPaymentMethod = $action({\n method: \"PATCH\",\n path: `${this.url}/payment-methods/:id/default`,\n group: this.group,\n use: [$secure()],\n description: \"Set as default payment method\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: paymentMethodResourceSchema,\n },\n handler: ({ params, user }) =>\n this.paymentMethods.setDefault(params.id, user.id),\n });\n\n /**\n * Create a checkout session.\n */\n public readonly createCheckout = $action({\n method: \"POST\",\n path: `${this.url}/checkout`,\n group: this.group,\n use: [$secure()],\n description: \"Create checkout session and return URL\",\n schema: {\n body: createCheckoutSchema,\n response: checkoutResponseSchema,\n },\n handler: ({ body, user }) =>\n this.payments.createSession(\n body.intentId,\n body.returnUrl,\n body.authorize,\n user.id,\n ),\n });\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { PaymentIntentEntity } from \"../entities/paymentIntents.ts\";\nimport type {\n CreatePaymentMethodResult,\n CreateSessionResult,\n PaymentProvider,\n RefundResult,\n WebhookEvent,\n} from \"./PaymentProvider.ts\";\n\ninterface MemoryCharge {\n providerRef: string;\n amount: number;\n status: string;\n}\n\ninterface MemoryRefund {\n providerRef: string;\n chargeRef: string;\n amount: number;\n}\n\nexport class MemoryPaymentProvider implements PaymentProvider {\n protected readonly charges: Map<string, MemoryCharge> = new Map();\n protected readonly refundRecords: Map<string, MemoryRefund> = new Map();\n protected readonly methods: Map<string, CreatePaymentMethodResult> =\n new Map();\n protected readonly expiredSessions: Set<string> = new Set();\n\n public async createSession(\n intent: PaymentIntentEntity,\n options: { returnUrl: string; authorize?: boolean },\n ): Promise<CreateSessionResult> {\n const providerRef = `mem_session_${randomUUID()}`;\n const status = options.authorize ? \"authorized\" : \"captured\";\n this.charges.set(providerRef, {\n providerRef,\n amount: intent.amount,\n status,\n });\n return {\n url: `/payments/mock-checkout/${intent.id}`,\n providerRef,\n };\n }\n\n public async capturePayment(\n providerRef: string,\n amount: number,\n ): Promise<void> {\n const charge = this.charges.get(providerRef);\n if (charge) {\n charge.status = \"captured\";\n charge.amount = amount;\n }\n }\n\n public async voidPayment(providerRef: string): Promise<void> {\n const charge = this.charges.get(providerRef);\n if (charge) {\n charge.status = \"voided\";\n }\n }\n\n public async refundPayment(\n providerRef: string,\n amount: number,\n ): Promise<RefundResult> {\n const refundRef = `mem_refund_${randomUUID()}`;\n this.refundRecords.set(refundRef, {\n providerRef: refundRef,\n chargeRef: providerRef,\n amount,\n });\n return { providerRef: refundRef };\n }\n\n public async parseWebhook(request: Request): Promise<WebhookEvent> {\n const body = (await request.json()) as {\n providerRef: string;\n status: string;\n };\n return {\n providerRef: body.providerRef,\n status: body.status,\n raw: body,\n };\n }\n\n public async createPaymentMethod(\n userId: string,\n token: string,\n ): Promise<CreatePaymentMethodResult> {\n const providerRef = `mem_pm_${randomUUID()}`;\n const result: CreatePaymentMethodResult = {\n providerRef,\n type: \"card\",\n brand: \"visa\",\n last4: \"4242\",\n expMonth: 12,\n expYear: 2030,\n };\n this.methods.set(providerRef, result);\n return result;\n }\n\n public async deletePaymentMethod(providerRef: string): Promise<void> {\n this.methods.delete(providerRef);\n }\n\n public async expireSession(providerRef: string): Promise<void> {\n this.expiredSessions.add(providerRef);\n }\n\n // --- Test assertion helpers ---\n\n public wasCharged(providerRef: string): boolean {\n const charge = this.charges.get(providerRef);\n return charge?.status === \"captured\";\n }\n\n public wasRefunded(providerRef: string): boolean {\n return Array.from(this.refundRecords.values()).some(\n (r) => r.chargeRef === providerRef,\n );\n }\n\n public wasExpired(providerRef: string): boolean {\n return this.expiredSessions.has(providerRef);\n }\n\n public getCharges(): MemoryCharge[] {\n return Array.from(this.charges.values());\n }\n\n public getRefunds(): MemoryRefund[] {\n return Array.from(this.refundRecords.values());\n }\n}\n","import { $module } from \"alepha\";\nimport { AdminPaymentController } from \"./controllers/AdminPaymentController.ts\";\nimport { PaymentController } from \"./controllers/PaymentController.ts\";\nimport { MemoryPaymentProvider } from \"./providers/MemoryPaymentProvider.ts\";\nimport { PaymentProvider } from \"./providers/PaymentProvider.ts\";\nimport { PaymentMethodService } from \"./services/PaymentMethodService.ts\";\nimport { PaymentService } from \"./services/PaymentService.ts\";\n\nexport * from \"./controllers/AdminPaymentController.ts\";\nexport * from \"./controllers/PaymentController.ts\";\nexport * from \"./entities/paymentIntents.ts\";\nexport * from \"./entities/paymentMethods.ts\";\nexport * from \"./entities/refunds.ts\";\nexport * from \"./errors/PaymentError.ts\";\nexport * from \"./providers/MemoryPaymentProvider.ts\";\nexport * from \"./providers/PaymentProvider.ts\";\nexport * from \"./schemas/intentSchemas.ts\";\nexport * from \"./schemas/paymentMethodSchemas.ts\";\nexport * from \"./schemas/refundSchemas.ts\";\nexport * from \"./services/PaymentMethodService.ts\";\nexport * from \"./services/PaymentService.ts\";\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"payments:authorized\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:captured\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:failed\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:voided\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:refunded\": {\n intentId: string;\n refundId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:cancelled\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n }\n}\n\nexport const AlephaApiPayments = $module({\n name: \"alepha.api.payments\",\n services: [\n AdminPaymentController,\n PaymentController,\n PaymentProvider,\n PaymentService,\n PaymentMethodService,\n ],\n variants: [MemoryPaymentProvider],\n register: (alepha) => {\n alepha.with({\n optional: true,\n provide: PaymentProvider,\n use: MemoryPaymentProvider,\n });\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/api/payments/entities/paymentIntents.ts","../../../src/api/payments/schemas/intentSchemas.ts","../../../src/api/payments/entities/refunds.ts","../../../src/api/payments/schemas/refundSchemas.ts","../../../src/api/payments/errors/PaymentError.ts","../../../src/api/payments/providers/PaymentProvider.ts","../../../src/api/payments/services/PaymentService.ts","../../../src/api/payments/controllers/AdminPaymentController.ts","../../../src/api/payments/entities/paymentMethods.ts","../../../src/api/payments/schemas/paymentMethodSchemas.ts","../../../src/api/payments/services/PaymentMethodService.ts","../../../src/api/payments/controllers/PaymentController.ts","../../../src/api/payments/providers/MemoryPaymentProvider.ts","../../../src/api/payments/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const paymentIntents = $entity({\n name: \"payment_intents\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n organizationId: db.organization(),\n amount: t.integer(),\n currency: t.text({ size: \"short\" }),\n status: t.enum([\n \"created\",\n \"processing\",\n \"authorized\",\n \"captured\",\n \"partially_refunded\",\n \"voided\",\n \"failed\",\n \"cancelled\",\n \"refunded\",\n \"expired\",\n ]),\n providerRef: t.optional(t.text()),\n providerRaw: t.optional(t.json()),\n metadata: t.optional(t.json()),\n paymentMethodId: t.optional(t.uuid()),\n userId: t.optional(t.uuid()),\n }),\n indexes: [\"status\", \"organizationId\", \"userId\", \"createdAt\"],\n});\n\nexport type PaymentIntentEntity = Static<typeof paymentIntents.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\nimport { paymentIntents } from \"../entities/paymentIntents.ts\";\n\nexport const createIntentSchema = t.object({\n amount: t.integer({ minimum: 1 }),\n currency: t.text({ size: \"short\" }),\n metadata: t.optional(t.json()),\n paymentMethodId: t.optional(t.uuid()),\n});\n\nexport type CreateIntent = Static<typeof createIntentSchema>;\n\nexport const createCheckoutSchema = t.object({\n intentId: t.uuid(),\n returnUrl: t.text(),\n authorize: t.optional(t.boolean()),\n});\n\nexport type CreateCheckout = Static<typeof createCheckoutSchema>;\n\nexport const checkoutResponseSchema = t.object({\n url: t.text(),\n intentId: t.text(),\n});\n\nexport type CheckoutResponse = Static<typeof checkoutResponseSchema>;\n\nexport const captureIntentSchema = t.object({\n amount: t.optional(t.integer({ minimum: 1 })),\n});\n\nexport type CaptureIntent = Static<typeof captureIntentSchema>;\n\nexport const refundIntentSchema = t.object({\n amount: t.integer({ minimum: 1 }),\n reason: t.optional(t.text()),\n});\n\nexport type RefundIntent = Static<typeof refundIntentSchema>;\n\nexport const recordCashSchema = t.object({\n amount: t.integer({ minimum: 1 }),\n currency: t.text({ size: \"short\" }),\n metadata: t.optional(t.json()),\n});\n\nexport type RecordCash = Static<typeof recordCashSchema>;\n\nexport const intentQuerySchema = t.extend(pageQuerySchema, {\n status: t.optional(t.text({ description: \"Filter by status\" })),\n userId: t.optional(t.uuid({ description: \"Filter by user ID\" })),\n});\n\nexport type IntentQuery = Static<typeof intentQuerySchema>;\n\nexport const intentResourceSchema = paymentIntents.schema;\n\nexport type IntentResource = Static<typeof intentResourceSchema>;\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const refunds = $entity({\n name: \"refunds\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n organizationId: db.organization(),\n intentId: t.uuid(),\n amount: t.integer(),\n currency: t.text({ size: \"short\" }),\n status: t.enum([\"pending\", \"processing\", \"completed\", \"failed\"]),\n reason: t.optional(t.text()),\n providerRef: t.optional(t.text()),\n }),\n indexes: [\"intentId\", \"organizationId\", \"status\"],\n});\n\nexport type RefundEntity = Static<typeof refunds.schema>;\n","import type { Static } from \"alepha\";\nimport { refunds } from \"../entities/refunds.ts\";\n\nexport const refundResourceSchema = refunds.schema;\n\nexport type RefundResource = Static<typeof refundResourceSchema>;\n","import { AlephaError } from \"alepha\";\n\nexport class PaymentError extends AlephaError {\n public readonly status = 400;\n}\n","import type { PaymentIntentEntity } from \"../entities/paymentIntents.ts\";\n\nexport interface CreateSessionResult {\n url: string;\n providerRef: string;\n}\n\nexport interface RefundResult {\n providerRef: string;\n}\n\nexport interface WebhookEvent {\n providerRef: string;\n status: string;\n raw: unknown;\n}\n\nexport interface CreatePaymentMethodResult {\n providerRef: string;\n type: string;\n brand?: string;\n last4?: string;\n expMonth?: number;\n expYear?: number;\n}\n\nexport abstract class PaymentProvider {\n /**\n * Create a checkout session with the PSP.\n * Returns a URL to redirect the user to, and the PSP's reference ID.\n */\n abstract createSession(\n intent: PaymentIntentEntity,\n options: { returnUrl: string; authorize?: boolean },\n ): Promise<CreateSessionResult>;\n\n /**\n * Capture a previously authorized payment.\n * Amount can differ from the original authorization (partial capture).\n */\n abstract capturePayment(providerRef: string, amount: number): Promise<void>;\n\n /**\n * Void/cancel a previously authorized payment before capture.\n */\n abstract voidPayment(providerRef: string): Promise<void>;\n\n /**\n * Refund a captured payment (partial or full).\n */\n abstract refundPayment(\n providerRef: string,\n amount: number,\n ): Promise<RefundResult>;\n\n /**\n * Parse and verify an incoming PSP webhook request.\n *\n * Implementations MUST verify the webhook signature before returning.\n * Throw an error if the signature is invalid or missing — this is the\n * only authentication on the webhook endpoint (no $secure middleware).\n *\n * Failure to verify signatures allows attackers to forge payment\n * confirmations by POSTing fake webhook events.\n */\n abstract parseWebhook(request: Request): Promise<WebhookEvent>;\n\n /**\n * Store a payment method token with the PSP.\n */\n abstract createPaymentMethod(\n userId: string,\n token: string,\n ): Promise<CreatePaymentMethodResult>;\n\n /**\n * Delete a stored payment method from the PSP.\n */\n abstract deletePaymentMethod(providerRef: string): Promise<void>;\n\n /**\n * Expire/cancel a checkout session on the PSP side.\n * Called during stale session cleanup.\n */\n abstract expireSession(providerRef: string): Promise<void>;\n}\n","import { $inject, Alepha } from \"alepha\";\nimport { $job } from \"alepha/api/jobs\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport {\n type PaymentIntentEntity,\n paymentIntents,\n} from \"../entities/paymentIntents.ts\";\nimport { type RefundEntity, refunds } from \"../entities/refunds.ts\";\nimport { PaymentError } from \"../errors/PaymentError.ts\";\nimport { PaymentProvider } from \"../providers/PaymentProvider.ts\";\n\nexport class PaymentService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly provider = $inject(PaymentProvider);\n protected readonly intentRepo = $repository(paymentIntents);\n protected readonly refundRepo = $repository(refunds);\n\n /**\n * Expires stale payment intents that have been in \"processing\" status\n * for more than 30 minutes. Runs every 5 minutes — shares the CF wrangler\n * trigger with the jobs sweep so no extra binding is consumed.\n */\n protected readonly expireStaleIntents = $job({\n name: \"api:payments:expireStaleIntents\",\n cron: \"*/5 * * * *\",\n handler: async () => {\n const cutoff = this.dateTime.now().subtract(30, \"minutes\").toISOString();\n\n const stale = await this.intentRepo.findMany({\n where: { status: { eq: \"processing\" }, createdAt: { lt: cutoff } },\n });\n\n for (const intent of stale) {\n if (intent.providerRef) {\n try {\n await this.provider.expireSession(intent.providerRef);\n } catch (error) {\n this.log.warn(\n `Failed to expire session for intent ${intent.id}`,\n error,\n );\n }\n }\n await this.intentRepo.updateById(intent.id, { status: \"expired\" });\n this.log.info(`Expired stale intent ${intent.id}`);\n }\n },\n });\n\n /**\n * Create a new payment intent in \"created\" status.\n */\n public async createIntent(\n amount: number,\n currency: string,\n metadata?: unknown,\n options?: { paymentMethodId?: string; userId?: string },\n ): Promise<PaymentIntentEntity> {\n return await this.intentRepo.create({\n amount,\n currency: currency.toLowerCase(),\n status: \"created\",\n metadata: metadata as any,\n paymentMethodId: options?.paymentMethodId,\n userId: options?.userId,\n });\n }\n\n /**\n * Create a checkout session with the payment provider and\n * transition the intent to \"processing\".\n */\n public async createSession(\n intentId: string,\n returnUrl: string,\n authorize?: boolean,\n userId?: string,\n ): Promise<{ url: string; intentId: string }> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"created\", \"createSession\");\n\n // Verify intent ownership if userId is provided\n if (userId && intent.userId && intent.userId !== userId) {\n throw new PaymentError(\"Payment intent does not belong to this user\");\n }\n\n // Associate intent with user if not already set\n if (userId && !intent.userId) {\n await this.intentRepo.updateById(intent.id, { userId });\n }\n\n const result = await this.provider.createSession(intent, {\n returnUrl,\n authorize,\n });\n\n await this.intentRepo.updateById(intent.id, {\n status: \"processing\",\n providerRef: result.providerRef,\n });\n\n return { url: result.url, intentId: intent.id };\n }\n\n /**\n * Handle an incoming webhook from the payment provider.\n */\n public async handleWebhook(request: Request): Promise<void> {\n const event = await this.provider.parseWebhook(request);\n const intents = await this.intentRepo.findMany({\n where: { providerRef: { eq: event.providerRef } },\n limit: 1,\n });\n\n if (intents.length === 0) {\n this.log.warn(`Webhook for unknown providerRef: ${event.providerRef}`);\n return;\n }\n\n const intent = intents[0];\n await this.handleWebhookEvent(intent.id, event.status, event.raw);\n }\n\n /**\n * Process a webhook event by updating the intent status and emitting\n * the corresponding payment event.\n */\n /**\n * Valid status transitions from webhook events.\n * Only these transitions are allowed — all others are silently ignored.\n */\n protected static readonly VALID_WEBHOOK_TRANSITIONS: Record<\n string,\n string[]\n > = {\n processing: [\"authorized\", \"captured\", \"failed\"],\n authorized: [\"captured\", \"failed\"],\n };\n\n public async handleWebhookEvent(\n intentId: string,\n status: string,\n raw?: unknown,\n ): Promise<void> {\n const intent = await this.getIntent(intentId);\n\n const eventMap = {\n authorized: \"payments:authorized\",\n captured: \"payments:captured\",\n failed: \"payments:failed\",\n } as const;\n\n type WebhookStatus = keyof typeof eventMap;\n if (!(status in eventMap)) {\n this.log.warn(`Unknown webhook status: ${status}`);\n return;\n }\n\n const webhookStatus = status as WebhookStatus;\n\n // Validate status transition\n const allowed = PaymentService.VALID_WEBHOOK_TRANSITIONS[intent.status];\n if (!allowed?.includes(webhookStatus)) {\n this.log.warn(\n `Ignoring webhook: cannot transition ${intent.status} → ${webhookStatus}`,\n { intentId: intent.id },\n );\n return;\n }\n\n await this.intentRepo.updateById(intent.id, {\n status: webhookStatus,\n providerRaw: raw as any,\n });\n\n await this.alepha.events.emit(eventMap[webhookStatus], {\n intentId: intent.id,\n amount: intent.amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n }\n\n /**\n * Capture a previously authorized payment. Optionally specify a different\n * amount for partial capture.\n */\n public async capture(\n intentId: string,\n finalAmount?: number,\n ): Promise<PaymentIntentEntity> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"authorized\", \"capture\");\n\n const amount = finalAmount ?? intent.amount;\n if (amount > intent.amount) {\n throw new PaymentError(\n `Capture amount ${amount} exceeds authorized amount ${intent.amount}`,\n );\n }\n\n if (intent.providerRef) {\n await this.provider.capturePayment(intent.providerRef, amount);\n }\n\n const updated = await this.intentRepo.updateById(intent.id, {\n status: \"captured\",\n amount,\n });\n\n await this.alepha.events.emit(\"payments:captured\", {\n intentId: intent.id,\n amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return updated;\n }\n\n /**\n * Void a previously authorized payment before capture.\n */\n public async void(intentId: string): Promise<PaymentIntentEntity> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"authorized\", \"void\");\n\n if (intent.providerRef) {\n await this.provider.voidPayment(intent.providerRef);\n }\n\n const updated = await this.intentRepo.updateById(intent.id, {\n status: \"voided\",\n });\n\n await this.alepha.events.emit(\"payments:voided\", {\n intentId: intent.id,\n amount: intent.amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return updated;\n }\n\n /**\n * Refund a captured payment (partial or full).\n */\n public async refund(\n intentId: string,\n amount: number,\n reason?: string,\n ): Promise<RefundEntity> {\n const intent = await this.getIntent(intentId);\n\n // Allow refunds from both \"captured\" and \"partially_refunded\" states\n if (\n intent.status !== \"captured\" &&\n intent.status !== \"partially_refunded\"\n ) {\n throw new PaymentError(\n `Cannot refund: intent ${intent.id} is '${intent.status}', expected 'captured' or 'partially_refunded'`,\n );\n }\n\n // Validate refund amount against remaining refundable amount\n const existingRefunds = await this.refundRepo.findMany({\n where: { intentId: { eq: intent.id } },\n });\n const totalRefunded = existingRefunds.reduce((sum, r) => sum + r.amount, 0);\n const remaining = intent.amount - totalRefunded;\n\n if (amount > remaining) {\n throw new PaymentError(\n `Refund amount ${amount} exceeds remaining refundable amount ${remaining}`,\n );\n }\n\n let refundProviderRef: string | undefined;\n if (intent.providerRef) {\n const result = await this.provider.refundPayment(\n intent.providerRef,\n amount,\n );\n refundProviderRef = result.providerRef;\n }\n\n const refund = await this.refundRepo.create({\n intentId: intent.id,\n organizationId: intent.organizationId,\n amount,\n currency: intent.currency,\n status: \"completed\",\n reason,\n providerRef: refundProviderRef,\n });\n\n // Set status based on whether fully or partially refunded\n const newTotalRefunded = totalRefunded + amount;\n const newStatus =\n newTotalRefunded >= intent.amount ? \"refunded\" : \"partially_refunded\";\n await this.intentRepo.updateById(intent.id, {\n status: newStatus,\n });\n\n await this.alepha.events.emit(\"payments:refunded\", {\n intentId: intent.id,\n refundId: refund.id,\n amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return refund;\n }\n\n /**\n * Record a cash or offline payment directly as captured,\n * bypassing the checkout flow.\n */\n public async recordCashPayment(\n amount: number,\n currency: string,\n metadata?: unknown,\n ): Promise<PaymentIntentEntity> {\n const intent = await this.intentRepo.create({\n amount,\n currency: currency.toLowerCase(),\n status: \"captured\",\n metadata: metadata as any,\n });\n\n await this.alepha.events.emit(\"payments:captured\", {\n intentId: intent.id,\n amount,\n currency,\n metadata,\n });\n\n return intent;\n }\n\n /**\n * Cancel a payment intent that has not yet entered processing.\n */\n public async cancel(intentId: string): Promise<PaymentIntentEntity> {\n const intent = await this.getIntent(intentId);\n this.assertStatus(intent, \"created\", \"cancel\");\n\n const cancelled = await this.intentRepo.updateById(intent.id, {\n status: \"cancelled\",\n });\n\n await this.alepha.events.emit(\"payments:cancelled\", {\n intentId: intent.id,\n amount: intent.amount,\n currency: intent.currency,\n metadata: intent.metadata,\n });\n\n return cancelled;\n }\n\n /**\n * Get a payment intent by ID. Throws NotFoundError if not found.\n */\n public async getIntent(intentId: string): Promise<PaymentIntentEntity> {\n return await this.intentRepo.getById(intentId);\n }\n\n /**\n * Find payment intents with optional filters and pagination.\n */\n public async findIntents(query: {\n status?: string;\n userId?: string;\n sort?: string;\n size?: number;\n page?: number;\n }) {\n const where = this.intentRepo.createQueryWhere();\n if (query.status)\n where.status = { eq: query.status as PaymentIntentEntity[\"status\"] };\n if (query.userId) where.userId = { eq: query.userId };\n return await this.intentRepo.paginate(query, { where }, { count: true });\n }\n\n protected assertStatus(\n intent: PaymentIntentEntity,\n expected: PaymentIntentEntity[\"status\"],\n operation: string,\n ): void {\n if (intent.status !== expected) {\n throw new PaymentError(\n `Cannot ${operation}: intent ${intent.id} is '${intent.status}', expected '${expected}'`,\n );\n }\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport {\n captureIntentSchema,\n intentQuerySchema,\n intentResourceSchema,\n recordCashSchema,\n refundIntentSchema,\n} from \"../schemas/intentSchemas.ts\";\nimport { refundResourceSchema } from \"../schemas/refundSchemas.ts\";\nimport { PaymentService } from \"../services/PaymentService.ts\";\n\nexport class AdminPaymentController {\n protected readonly url = \"/admin/payments\";\n protected readonly group = \"admin:payments\";\n protected readonly payments = $inject(PaymentService);\n\n /**\n * List payment intents with pagination and filtering.\n */\n public readonly listIntents = $action({\n path: `${this.url}/intents`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:read\"] })],\n description: \"List payment intents\",\n schema: {\n query: intentQuerySchema,\n response: t.page(intentResourceSchema),\n },\n handler: ({ query }) => this.payments.findIntents(query),\n });\n\n /**\n * Get a payment intent by ID.\n */\n public readonly getIntent = $action({\n path: `${this.url}/intents/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:read\"] })],\n description: \"Get payment intent details\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: intentResourceSchema,\n },\n handler: ({ params }) => this.payments.getIntent(params.id),\n });\n\n /**\n * Capture an authorized intent.\n */\n public readonly captureIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/capture`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Capture an authorized payment intent\",\n schema: {\n params: t.object({ id: t.uuid() }),\n body: captureIntentSchema,\n response: intentResourceSchema,\n },\n handler: ({ params, body }) =>\n this.payments.capture(params.id, body.amount),\n });\n\n /**\n * Void an authorized intent.\n */\n public readonly voidIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/void`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Void an authorized payment intent\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: intentResourceSchema,\n },\n handler: ({ params }) => this.payments.void(params.id),\n });\n\n /**\n * Refund a captured intent.\n */\n public readonly refundIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/refund`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Issue partial or full refund\",\n schema: {\n params: t.object({ id: t.uuid() }),\n body: refundIntentSchema,\n response: refundResourceSchema,\n },\n handler: ({ params, body }) =>\n this.payments.refund(params.id, body.amount, body.reason),\n });\n\n /**\n * Cancel a created intent.\n */\n public readonly cancelIntent = $action({\n method: \"POST\",\n path: `${this.url}/intents/:id/cancel`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Cancel a created payment intent\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: intentResourceSchema,\n },\n handler: ({ params }) => this.payments.cancel(params.id),\n });\n\n /**\n * Record a cash payment.\n */\n public readonly recordCash = $action({\n method: \"POST\",\n path: `${this.url}/cash`,\n group: this.group,\n use: [$secure({ permissions: [\"payments:write\"] })],\n description: \"Record a cash payment\",\n schema: {\n body: recordCashSchema,\n response: intentResourceSchema,\n },\n handler: ({ body }) =>\n this.payments.recordCashPayment(\n body.amount,\n body.currency,\n body.metadata,\n ),\n });\n\n /**\n * PSP webhook endpoint (not under /admin, no auth — verified by provider).\n */\n public readonly webhook = $action({\n method: \"POST\",\n path: \"/payments/webhook\",\n group: this.group,\n description: \"PSP webhook endpoint\",\n schema: {\n response: okSchema,\n },\n handler: async (request) => {\n await this.payments.handleWebhook(request.raw.web!.req);\n return { ok: true };\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const paymentMethods = $entity({\n name: \"payment_methods\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n organizationId: db.organization(),\n userId: t.uuid(),\n type: t.text({ size: \"short\" }),\n brand: t.optional(t.text({ size: \"short\" })),\n last4: t.optional(t.text({ size: \"short\" })),\n expMonth: t.optional(t.integer()),\n expYear: t.optional(t.integer()),\n isDefault: t.boolean(),\n providerRef: t.text(),\n }),\n indexes: [\"userId\", \"organizationId\"],\n});\n\nexport type PaymentMethodEntity = Static<typeof paymentMethods.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { paymentMethods } from \"../entities/paymentMethods.ts\";\n\nexport const addPaymentMethodSchema = t.object({\n token: t.text(),\n});\n\nexport type AddPaymentMethod = Static<typeof addPaymentMethodSchema>;\n\nexport const paymentMethodResourceSchema = paymentMethods.schema;\n\nexport type PaymentMethodResource = Static<typeof paymentMethodResourceSchema>;\n","import { $inject } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport {\n type PaymentMethodEntity,\n paymentMethods,\n} from \"../entities/paymentMethods.ts\";\nimport { PaymentError } from \"../errors/PaymentError.ts\";\nimport { PaymentProvider } from \"../providers/PaymentProvider.ts\";\n\nexport class PaymentMethodService {\n protected readonly log = $logger();\n protected readonly provider = $inject(PaymentProvider);\n protected readonly methodRepo = $repository(paymentMethods);\n\n public async addPaymentMethod(\n userId: string,\n organizationId: string,\n token: string,\n ): Promise<PaymentMethodEntity> {\n const result = await this.provider.createPaymentMethod(userId, token);\n\n const existing = await this.methodRepo.findMany({\n where: { userId: { eq: userId } },\n });\n\n return await this.methodRepo.create({\n userId,\n organizationId,\n type: result.type,\n brand: result.brand,\n last4: result.last4,\n expMonth: result.expMonth,\n expYear: result.expYear,\n isDefault: existing.length === 0,\n providerRef: result.providerRef,\n });\n }\n\n public async listPaymentMethods(\n userId: string,\n ): Promise<PaymentMethodEntity[]> {\n return await this.methodRepo.findMany({\n where: { userId: { eq: userId } },\n });\n }\n\n public async removePaymentMethod(\n methodId: string,\n userId: string,\n ): Promise<void> {\n const method = await this.methodRepo.getById(methodId);\n if (method.userId !== userId) {\n throw new PaymentError(\"Cannot remove another user's payment method\");\n }\n\n await this.provider.deletePaymentMethod(method.providerRef);\n await this.methodRepo.deleteById(method.id);\n }\n\n public async setDefault(\n methodId: string,\n userId: string,\n ): Promise<PaymentMethodEntity> {\n const method = await this.methodRepo.getById(methodId);\n if (method.userId !== userId) {\n throw new PaymentError(\"Cannot modify another user's payment method\");\n }\n\n const userMethods = await this.methodRepo.findMany({\n where: { userId: { eq: userId } },\n });\n\n for (const m of userMethods) {\n if (m.isDefault) {\n await this.methodRepo.updateById(m.id, { isDefault: false });\n }\n }\n\n return await this.methodRepo.updateById(method.id, { isDefault: true });\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $secure } from \"alepha/security\";\nimport { $action, okSchema } from \"alepha/server\";\nimport { PaymentError } from \"../errors/PaymentError.ts\";\nimport {\n checkoutResponseSchema,\n createCheckoutSchema,\n} from \"../schemas/intentSchemas.ts\";\nimport {\n addPaymentMethodSchema,\n paymentMethodResourceSchema,\n} from \"../schemas/paymentMethodSchemas.ts\";\nimport { PaymentMethodService } from \"../services/PaymentMethodService.ts\";\nimport { PaymentService } from \"../services/PaymentService.ts\";\n\nexport class PaymentController {\n protected readonly url = \"/payments\";\n protected readonly group = \"payments\";\n protected readonly payments = $inject(PaymentService);\n protected readonly paymentMethods = $inject(PaymentMethodService);\n\n /**\n * List the current user's saved payment methods.\n */\n public readonly listPaymentMethods = $action({\n path: `${this.url}/payment-methods`,\n group: this.group,\n use: [$secure()],\n description: \"List current user's saved payment methods\",\n schema: {\n response: t.array(paymentMethodResourceSchema),\n },\n handler: ({ user }) => this.paymentMethods.listPaymentMethods(user.id),\n });\n\n /**\n * Add a new payment method.\n */\n public readonly addPaymentMethod = $action({\n method: \"POST\",\n path: `${this.url}/payment-methods`,\n group: this.group,\n use: [$secure()],\n description: \"Tokenize and store a new payment method\",\n schema: {\n body: addPaymentMethodSchema,\n response: paymentMethodResourceSchema,\n },\n handler: ({ body, user }) => {\n if (!user.organization) {\n throw new PaymentError(\n \"Organization is required to add a payment method\",\n );\n }\n return this.paymentMethods.addPaymentMethod(\n user.id,\n user.organization,\n body.token,\n );\n },\n });\n\n /**\n * Remove a payment method.\n */\n public readonly removePaymentMethod = $action({\n method: \"DELETE\",\n path: `${this.url}/payment-methods/:id`,\n group: this.group,\n use: [$secure()],\n description: \"Remove own payment method\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: okSchema,\n },\n handler: async ({ params, user }) => {\n await this.paymentMethods.removePaymentMethod(params.id, user.id);\n return { ok: true, id: params.id };\n },\n });\n\n /**\n * Set a payment method as default.\n */\n public readonly setDefaultPaymentMethod = $action({\n method: \"PATCH\",\n path: `${this.url}/payment-methods/:id/default`,\n group: this.group,\n use: [$secure()],\n description: \"Set as default payment method\",\n schema: {\n params: t.object({ id: t.uuid() }),\n response: paymentMethodResourceSchema,\n },\n handler: ({ params, user }) =>\n this.paymentMethods.setDefault(params.id, user.id),\n });\n\n /**\n * Create a checkout session.\n */\n public readonly createCheckout = $action({\n method: \"POST\",\n path: `${this.url}/checkout`,\n group: this.group,\n use: [$secure()],\n description: \"Create checkout session and return URL\",\n schema: {\n body: createCheckoutSchema,\n response: checkoutResponseSchema,\n },\n handler: ({ body, user }) =>\n this.payments.createSession(\n body.intentId,\n body.returnUrl,\n body.authorize,\n user.id,\n ),\n });\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { PaymentIntentEntity } from \"../entities/paymentIntents.ts\";\nimport type {\n CreatePaymentMethodResult,\n CreateSessionResult,\n PaymentProvider,\n RefundResult,\n WebhookEvent,\n} from \"./PaymentProvider.ts\";\n\ninterface MemoryCharge {\n providerRef: string;\n amount: number;\n status: string;\n}\n\ninterface MemoryRefund {\n providerRef: string;\n chargeRef: string;\n amount: number;\n}\n\nexport class MemoryPaymentProvider implements PaymentProvider {\n protected readonly charges: Map<string, MemoryCharge> = new Map();\n protected readonly refundRecords: Map<string, MemoryRefund> = new Map();\n protected readonly methods: Map<string, CreatePaymentMethodResult> =\n new Map();\n protected readonly expiredSessions: Set<string> = new Set();\n\n public async createSession(\n intent: PaymentIntentEntity,\n options: { returnUrl: string; authorize?: boolean },\n ): Promise<CreateSessionResult> {\n const providerRef = `mem_session_${randomUUID()}`;\n const status = options.authorize ? \"authorized\" : \"captured\";\n this.charges.set(providerRef, {\n providerRef,\n amount: intent.amount,\n status,\n });\n return {\n url: `/payments/mock-checkout/${intent.id}`,\n providerRef,\n };\n }\n\n public async capturePayment(\n providerRef: string,\n amount: number,\n ): Promise<void> {\n const charge = this.charges.get(providerRef);\n if (charge) {\n charge.status = \"captured\";\n charge.amount = amount;\n }\n }\n\n public async voidPayment(providerRef: string): Promise<void> {\n const charge = this.charges.get(providerRef);\n if (charge) {\n charge.status = \"voided\";\n }\n }\n\n public async refundPayment(\n providerRef: string,\n amount: number,\n ): Promise<RefundResult> {\n const refundRef = `mem_refund_${randomUUID()}`;\n this.refundRecords.set(refundRef, {\n providerRef: refundRef,\n chargeRef: providerRef,\n amount,\n });\n return { providerRef: refundRef };\n }\n\n public async parseWebhook(request: Request): Promise<WebhookEvent> {\n const body = (await request.json()) as {\n providerRef: string;\n status: string;\n };\n return {\n providerRef: body.providerRef,\n status: body.status,\n raw: body,\n };\n }\n\n public async createPaymentMethod(\n userId: string,\n token: string,\n ): Promise<CreatePaymentMethodResult> {\n const providerRef = `mem_pm_${randomUUID()}`;\n const result: CreatePaymentMethodResult = {\n providerRef,\n type: \"card\",\n brand: \"visa\",\n last4: \"4242\",\n expMonth: 12,\n expYear: 2030,\n };\n this.methods.set(providerRef, result);\n return result;\n }\n\n public async deletePaymentMethod(providerRef: string): Promise<void> {\n this.methods.delete(providerRef);\n }\n\n public async expireSession(providerRef: string): Promise<void> {\n this.expiredSessions.add(providerRef);\n }\n\n // --- Test assertion helpers ---\n\n public wasCharged(providerRef: string): boolean {\n const charge = this.charges.get(providerRef);\n return charge?.status === \"captured\";\n }\n\n public wasRefunded(providerRef: string): boolean {\n return Array.from(this.refundRecords.values()).some(\n (r) => r.chargeRef === providerRef,\n );\n }\n\n public wasExpired(providerRef: string): boolean {\n return this.expiredSessions.has(providerRef);\n }\n\n public getCharges(): MemoryCharge[] {\n return Array.from(this.charges.values());\n }\n\n public getRefunds(): MemoryRefund[] {\n return Array.from(this.refundRecords.values());\n }\n}\n","import { $module } from \"alepha\";\nimport { AdminPaymentController } from \"./controllers/AdminPaymentController.ts\";\nimport { PaymentController } from \"./controllers/PaymentController.ts\";\nimport { MemoryPaymentProvider } from \"./providers/MemoryPaymentProvider.ts\";\nimport { PaymentProvider } from \"./providers/PaymentProvider.ts\";\nimport { PaymentMethodService } from \"./services/PaymentMethodService.ts\";\nimport { PaymentService } from \"./services/PaymentService.ts\";\n\nexport * from \"./controllers/AdminPaymentController.ts\";\nexport * from \"./controllers/PaymentController.ts\";\nexport * from \"./entities/paymentIntents.ts\";\nexport * from \"./entities/paymentMethods.ts\";\nexport * from \"./entities/refunds.ts\";\nexport * from \"./errors/PaymentError.ts\";\nexport * from \"./providers/MemoryPaymentProvider.ts\";\nexport * from \"./providers/PaymentProvider.ts\";\nexport * from \"./schemas/intentSchemas.ts\";\nexport * from \"./schemas/paymentMethodSchemas.ts\";\nexport * from \"./schemas/refundSchemas.ts\";\nexport * from \"./services/PaymentMethodService.ts\";\nexport * from \"./services/PaymentService.ts\";\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"payments:authorized\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:captured\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:failed\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:voided\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:refunded\": {\n intentId: string;\n refundId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n \"payments:cancelled\": {\n intentId: string;\n amount: number;\n currency: string;\n metadata?: unknown;\n };\n }\n}\n\nexport const AlephaApiPayments = $module({\n name: \"alepha.api.payments\",\n services: [\n AdminPaymentController,\n PaymentController,\n PaymentProvider,\n PaymentService,\n PaymentMethodService,\n ],\n variants: [MemoryPaymentProvider],\n register: (alepha) => {\n alepha.with({\n optional: true,\n provide: PaymentProvider,\n use: MemoryPaymentProvider,\n });\n },\n});\n"],"mappings":"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"}
|
|
@@ -41,14 +41,26 @@ import { FileController } from "alepha/api/files";
|
|
|
41
41
|
* - `"required"`: Field is shown and required.
|
|
42
42
|
*/
|
|
43
43
|
type FieldRequirement = "none" | "optional" | "required";
|
|
44
|
+
/**
|
|
45
|
+
* Username-specific field requirement, extending {@link FieldRequirement}
|
|
46
|
+
* with an additional auto-derivation mode.
|
|
47
|
+
*
|
|
48
|
+
* - `"none"` / `"optional"` / `"required"`: same as {@link FieldRequirement}.
|
|
49
|
+
* - `"email"`: Field is hidden in the registration UI and the value is
|
|
50
|
+
* auto-derived from the user's email at signup. Same handling on
|
|
51
|
+
* credentials and OAuth flows. See `UsernameSlugger` for the rule and
|
|
52
|
+
* collision behavior.
|
|
53
|
+
*/
|
|
54
|
+
type UsernameFieldRequirement = FieldRequirement | "email";
|
|
44
55
|
declare const realmAuthSettingsAtom: _$alepha.Atom<_$alepha.TObject<{
|
|
45
56
|
displayName: _$alepha.TOptional<_$alepha.TString>;
|
|
46
57
|
description: _$alepha.TOptional<_$alepha.TString>;
|
|
47
58
|
logoUrl: _$alepha.TOptional<_$alepha.TString>;
|
|
48
59
|
registrationAllowed: _$alepha.TBoolean;
|
|
49
60
|
email: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
50
|
-
username: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
61
|
+
username: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">, _$typebox.TLiteral<"email">]>;
|
|
51
62
|
usernameRegExp: _$alepha.TString;
|
|
63
|
+
usernameBlocklist: _$alepha.TArray<_$alepha.TString>;
|
|
52
64
|
phoneNumber: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
53
65
|
verifyEmailRequired: _$alepha.TBoolean;
|
|
54
66
|
verifyPhoneRequired: _$alepha.TBoolean;
|
|
@@ -7136,6 +7148,10 @@ declare class SessionCrudService {
|
|
|
7136
7148
|
* Delete a session by ID.
|
|
7137
7149
|
*/
|
|
7138
7150
|
deleteSession(id: string, userRealmName?: string): Promise<void>;
|
|
7151
|
+
/**
|
|
7152
|
+
* Delete many sessions by ID in one repository call.
|
|
7153
|
+
*/
|
|
7154
|
+
deleteSessions(ids: string[], userRealmName?: string): Promise<string[]>;
|
|
7139
7155
|
}
|
|
7140
7156
|
//#endregion
|
|
7141
7157
|
//#region ../../src/api/users/controllers/AdminSessionController.d.ts
|
|
@@ -7212,6 +7228,20 @@ declare class AdminSessionController {
|
|
|
7212
7228
|
count: _$alepha.TOptional<_$alepha.TNumber>;
|
|
7213
7229
|
}>;
|
|
7214
7230
|
}>;
|
|
7231
|
+
/**
|
|
7232
|
+
* Delete many sessions in one repository call.
|
|
7233
|
+
*/
|
|
7234
|
+
readonly deleteSessions: _$alepha_server0.ActionPrimitiveFn<{
|
|
7235
|
+
query: _$alepha.TObject<{
|
|
7236
|
+
userRealmName: _$alepha.TOptional<_$alepha.TString>;
|
|
7237
|
+
}>;
|
|
7238
|
+
body: _$alepha.TObject<{
|
|
7239
|
+
ids: _$alepha.TArray<_$alepha.TString>;
|
|
7240
|
+
}>;
|
|
7241
|
+
response: _$alepha.TObject<{
|
|
7242
|
+
deleted: _$alepha.TArray<_$alepha.TString>;
|
|
7243
|
+
}>;
|
|
7244
|
+
}>;
|
|
7215
7245
|
}
|
|
7216
7246
|
//#endregion
|
|
7217
7247
|
//#region ../../src/api/users/notifications/UserNotifications.d.ts
|
|
@@ -7517,6 +7547,22 @@ declare class AdminUserController {
|
|
|
7517
7547
|
count: _$alepha.TOptional<_$alepha.TNumber>;
|
|
7518
7548
|
}>;
|
|
7519
7549
|
}>;
|
|
7550
|
+
/**
|
|
7551
|
+
* Delete many users in one request. Each id is processed sequentially so
|
|
7552
|
+
* cascades and side-effects run as if called one-by-one. Errors on a single
|
|
7553
|
+
* id surface with that id in the response.
|
|
7554
|
+
*/
|
|
7555
|
+
readonly deleteUsers: _$alepha_server0.ActionPrimitiveFn<{
|
|
7556
|
+
query: _$alepha.TObject<{
|
|
7557
|
+
userRealmName: _$alepha.TOptional<_$alepha.TString>;
|
|
7558
|
+
}>;
|
|
7559
|
+
body: _$alepha.TObject<{
|
|
7560
|
+
ids: _$alepha.TArray<_$alepha.TString>;
|
|
7561
|
+
}>;
|
|
7562
|
+
response: _$alepha.TObject<{
|
|
7563
|
+
deleted: _$alepha.TArray<_$alepha.TString>;
|
|
7564
|
+
}>;
|
|
7565
|
+
}>;
|
|
7520
7566
|
}
|
|
7521
7567
|
//#endregion
|
|
7522
7568
|
//#region ../../src/api/users/controllers/RealmController.d.ts
|
|
@@ -7545,8 +7591,9 @@ declare class RealmController {
|
|
|
7545
7591
|
logoUrl: _$alepha.TOptional<_$alepha.TString>;
|
|
7546
7592
|
registrationAllowed: _$alepha.TBoolean;
|
|
7547
7593
|
email: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
7548
|
-
username: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
7594
|
+
username: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">, _$typebox.TLiteral<"email">]>;
|
|
7549
7595
|
usernameRegExp: _$alepha.TString;
|
|
7596
|
+
usernameBlocklist: _$alepha.TArray<_$alepha.TString>;
|
|
7550
7597
|
phoneNumber: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
7551
7598
|
verifyEmailRequired: _$alepha.TBoolean;
|
|
7552
7599
|
verifyPhoneRequired: _$alepha.TBoolean;
|
|
@@ -7761,6 +7808,96 @@ declare const registrationIntentResponseSchema: _$alepha.TObject<{
|
|
|
7761
7808
|
}>;
|
|
7762
7809
|
type RegistrationIntentResponse = Static<typeof registrationIntentResponseSchema>;
|
|
7763
7810
|
//#endregion
|
|
7811
|
+
//#region ../../src/api/users/services/UsernameSlugger.d.ts
|
|
7812
|
+
/**
|
|
7813
|
+
* Derive stable, URL-safe usernames from email addresses.
|
|
7814
|
+
*
|
|
7815
|
+
* Used by the registration flow when `realm.settings.username === "email"`,
|
|
7816
|
+
* and reusable from any custom user-creation site (e.g. an OAuth-only flow
|
|
7817
|
+
* that wants the same handle convention).
|
|
7818
|
+
*
|
|
7819
|
+
* **Slug rule** — the local-part of the email is kept as-is (gmail
|
|
7820
|
+
* `+suffix` retained for predictability). Everything outside `[a-z0-9]` is
|
|
7821
|
+
* replaced with `-`, runs of `-` are collapsed, leading/trailing `-` are
|
|
7822
|
+
* trimmed, lowercased. If the result is shorter than {@link MIN_LENGTH} it
|
|
7823
|
+
* is padded with random alphanumerics. Result is clamped to
|
|
7824
|
+
* {@link MAX_LENGTH}.
|
|
7825
|
+
*
|
|
7826
|
+
* **Collision retry** — `pickAvailable()` checks the realm's `users` table
|
|
7827
|
+
* and the realm's `usernameBlocklist`. On hit, it appends `-<4 random>` and
|
|
7828
|
+
* retries up to {@link MAX_RETRIES} times. Best-effort against concurrent
|
|
7829
|
+
* registrations: the unique index on `(realm, lower(username))` is the
|
|
7830
|
+
* authoritative race guard, callers that hit a unique-violation should
|
|
7831
|
+
* call `pickAvailable` again with a fresh suffix.
|
|
7832
|
+
*
|
|
7833
|
+
* @see RegistrationService.createRegistrationIntent
|
|
7834
|
+
*/
|
|
7835
|
+
declare class UsernameSlugger {
|
|
7836
|
+
protected readonly realmProvider: RealmProvider;
|
|
7837
|
+
protected readonly log: _$alepha_logger0.Logger;
|
|
7838
|
+
/**
|
|
7839
|
+
* Floor for derived usernames. Shorter slugs are padded with random
|
|
7840
|
+
* alphanumerics. Matches the lower bound of the default `usernameRegExp`.
|
|
7841
|
+
*/
|
|
7842
|
+
static readonly MIN_LENGTH = 3;
|
|
7843
|
+
/**
|
|
7844
|
+
* Ceiling for derived usernames. Matches the upper bound of the default
|
|
7845
|
+
* `usernameRegExp` and gives enough headroom for the random suffix added
|
|
7846
|
+
* on collisions.
|
|
7847
|
+
*/
|
|
7848
|
+
static readonly MAX_LENGTH = 30;
|
|
7849
|
+
/**
|
|
7850
|
+
* Length of the random suffix appended on collision (e.g. `-3dp6`).
|
|
7851
|
+
* 36⁴ ≈ 1.6M variants per base — plenty for a tiny number of retries.
|
|
7852
|
+
*/
|
|
7853
|
+
static readonly SUFFIX_LENGTH = 4;
|
|
7854
|
+
/**
|
|
7855
|
+
* How many times `pickAvailable` retries before giving up.
|
|
7856
|
+
*/
|
|
7857
|
+
static readonly MAX_RETRIES = 5;
|
|
7858
|
+
/**
|
|
7859
|
+
* Alphabet used for the random suffix and for padding short slugs.
|
|
7860
|
+
*/
|
|
7861
|
+
static readonly ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
7862
|
+
/**
|
|
7863
|
+
* Default replacement when the email's local-part contains no
|
|
7864
|
+
* `[a-z0-9]` characters at all (rare but possible: `é@example.com`).
|
|
7865
|
+
*/
|
|
7866
|
+
static readonly EMPTY_LOCAL_FALLBACK = "user";
|
|
7867
|
+
/**
|
|
7868
|
+
* Sanitize an email into a base username slug.
|
|
7869
|
+
*
|
|
7870
|
+
* Pure function — no DB access. Always returns a string that satisfies
|
|
7871
|
+
* `^[a-z0-9-]{MIN_LENGTH,MAX_LENGTH}$`.
|
|
7872
|
+
*
|
|
7873
|
+
* @example
|
|
7874
|
+
* slug("ni.foures+testkv@gmail.com") // "ni-foures-testkv"
|
|
7875
|
+
* slug("john.doe@example.com") // "john-doe"
|
|
7876
|
+
* slug("é@example.com") // "user-XXX" (padded)
|
|
7877
|
+
*/
|
|
7878
|
+
slug(email: string | null | undefined): string;
|
|
7879
|
+
/**
|
|
7880
|
+
* Find an available username for the realm, starting from `base`.
|
|
7881
|
+
*
|
|
7882
|
+
* Returns `base` when nothing collides. On a hit (existing row OR
|
|
7883
|
+
* blocklisted name) appends `-<4 random>` and tries again, up to
|
|
7884
|
+
* {@link MAX_RETRIES} times.
|
|
7885
|
+
*
|
|
7886
|
+
* The check is best-effort: a concurrent registration may still claim
|
|
7887
|
+
* the same value before the caller's INSERT runs, in which case the DB
|
|
7888
|
+
* unique index throws and the caller should retry.
|
|
7889
|
+
*/
|
|
7890
|
+
pickAvailable(realmName: string | undefined, base: string): Promise<string>;
|
|
7891
|
+
/**
|
|
7892
|
+
* Check a name against the realm's `usernameBlocklist`. Case-insensitive.
|
|
7893
|
+
*/
|
|
7894
|
+
isBlocked(realmName: string | undefined, name: string): Promise<boolean>;
|
|
7895
|
+
protected getBlocklist(realmName: string | undefined): Promise<Set<string>>;
|
|
7896
|
+
protected isBlockedAgainst(name: string, blocklist: Set<string>): boolean;
|
|
7897
|
+
protected clamp(s: string): string;
|
|
7898
|
+
protected randomSuffix(length: number): string;
|
|
7899
|
+
}
|
|
7900
|
+
//#endregion
|
|
7764
7901
|
//#region ../../src/api/users/services/RegistrationService.d.ts
|
|
7765
7902
|
/**
|
|
7766
7903
|
* Intent stored in cache during the registration flow.
|
|
@@ -7792,6 +7929,7 @@ declare class RegistrationService {
|
|
|
7792
7929
|
protected readonly realmProvider: RealmProvider;
|
|
7793
7930
|
protected readonly credentialService: CredentialService;
|
|
7794
7931
|
protected readonly captchaProvider: CaptchaProvider;
|
|
7932
|
+
protected readonly usernameSlugger: UsernameSlugger;
|
|
7795
7933
|
protected readonly intentCache: _$alepha_cache0.CacheMiddlewareFn<RegistrationIntent>;
|
|
7796
7934
|
protected readonly rateLimitCache: _$alepha_cache0.CacheMiddlewareFn<number>;
|
|
7797
7935
|
protected userAudits(realmName?: string): UserAudits | undefined;
|
|
@@ -8098,8 +8236,9 @@ declare const realmConfigSchema: _$alepha.TObject<{
|
|
|
8098
8236
|
logoUrl: _$alepha.TOptional<_$alepha.TString>;
|
|
8099
8237
|
registrationAllowed: _$alepha.TBoolean;
|
|
8100
8238
|
email: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
8101
|
-
username: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
8239
|
+
username: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">, _$typebox.TLiteral<"email">]>;
|
|
8102
8240
|
usernameRegExp: _$alepha.TString;
|
|
8241
|
+
usernameBlocklist: _$alepha.TArray<_$alepha.TString>;
|
|
8103
8242
|
phoneNumber: _$alepha.TUnion<[_$typebox.TLiteral<"none">, _$typebox.TLiteral<"optional">, _$typebox.TLiteral<"required">]>;
|
|
8104
8243
|
verifyEmailRequired: _$alepha.TBoolean;
|
|
8105
8244
|
verifyPhoneRequired: _$alepha.TBoolean;
|
|
@@ -8206,6 +8345,7 @@ declare class SessionService {
|
|
|
8206
8345
|
protected readonly realmProvider: RealmProvider;
|
|
8207
8346
|
protected readonly fileController: _$alepha_server_links0.HttpVirtualClient<FileController>;
|
|
8208
8347
|
protected readonly cacheProvider: CacheProvider;
|
|
8348
|
+
protected readonly usernameSlugger: UsernameSlugger;
|
|
8209
8349
|
protected userAudits(realmName?: string): UserAudits | undefined;
|
|
8210
8350
|
protected userNotifications(realmName?: string): UserNotifications | undefined;
|
|
8211
8351
|
users(userRealmName?: string): Repository$1<_$alepha.TObject<{
|
|
@@ -8265,12 +8405,13 @@ declare class SessionService {
|
|
|
8265
8405
|
/**
|
|
8266
8406
|
* Generate a unique username from an OAuth profile.
|
|
8267
8407
|
*
|
|
8268
|
-
*
|
|
8269
|
-
*
|
|
8270
|
-
*
|
|
8271
|
-
*
|
|
8408
|
+
* Routes through {@link UsernameSlugger}, which is the same code path as
|
|
8409
|
+
* `username: "email"` registration. The OAuth profile's email is the
|
|
8410
|
+
* primary signal; if absent (rare — most IDPs return one), we fall back
|
|
8411
|
+
* to `profile.name`, then to a random handle. The slugger applies the
|
|
8412
|
+
* realm's `usernameBlocklist` and retries on collision.
|
|
8272
8413
|
*/
|
|
8273
|
-
protected generateUniqueUsername(profile: OAuth2Profile,
|
|
8414
|
+
protected generateUniqueUsername(profile: OAuth2Profile, _realmSettings: any, _users: any, realmName?: string): Promise<string>;
|
|
8274
8415
|
/**
|
|
8275
8416
|
* Random delay to prevent timing attacks (50-200ms)
|
|
8276
8417
|
* Uses cryptographically secure random number generation
|
|
@@ -8401,5 +8542,5 @@ declare class SessionService {
|
|
|
8401
8542
|
*/
|
|
8402
8543
|
declare const AlephaApiUsers: _$alepha.Service<_$alepha.Module>;
|
|
8403
8544
|
//#endregion
|
|
8404
|
-
export { $realm, AdminIdentityController, AdminSessionController, AdminUserController, AlephaApiUsers, CompletePasswordResetRequest, CompleteRegistrationRequest, CreateUser, CredentialService, DEFAULT_USER_REALM_NAME, FieldRequirement, IdentityEntity, IdentityQuery, IdentityResource, IdentityService, LoginInput, PasswordResetIntentResponse, Realm, RealmAuthSettings, RealmConfig, RealmController, RealmFeatures, RealmOptions, RealmPrimitive, RealmProvider, RealmRepositories, RegisterInput, RegistrationIntentResponse, RegistrationService, ResetPasswordInput, ResetPasswordRequest, SessionCrudService, SessionEntity, SessionQuery, SessionResource, SessionService, UpdateUser, UserAudits, UserBuckets, UserController, UserEntity, UserJobs, UserNotifications, UserQuery, UserResource, UserService, completePasswordResetRequestSchema, completeRegistrationRequestSchema, createUserSchema, identities, identityQuerySchema, identityResourceSchema, loginSchema, passwordResetIntentResponseSchema, realmAuthSettingsAtom, realmConfigSchema, registerSchema, registrationIntentResponseSchema, resetPasswordRequestSchema, resetPasswordSchema, sessionQuerySchema, sessionResourceSchema, sessions, updateUserSchema, userQuerySchema, userResourceSchema, users };
|
|
8545
|
+
export { $realm, AdminIdentityController, AdminSessionController, AdminUserController, AlephaApiUsers, CompletePasswordResetRequest, CompleteRegistrationRequest, CreateUser, CredentialService, DEFAULT_USER_REALM_NAME, FieldRequirement, IdentityEntity, IdentityQuery, IdentityResource, IdentityService, LoginInput, PasswordResetIntentResponse, Realm, RealmAuthSettings, RealmConfig, RealmController, RealmFeatures, RealmOptions, RealmPrimitive, RealmProvider, RealmRepositories, RegisterInput, RegistrationIntentResponse, RegistrationService, ResetPasswordInput, ResetPasswordRequest, SessionCrudService, SessionEntity, SessionQuery, SessionResource, SessionService, UpdateUser, UserAudits, UserBuckets, UserController, UserEntity, UserJobs, UserNotifications, UserQuery, UserResource, UserService, UsernameFieldRequirement, UsernameSlugger, completePasswordResetRequestSchema, completeRegistrationRequestSchema, createUserSchema, identities, identityQuerySchema, identityResourceSchema, loginSchema, passwordResetIntentResponseSchema, realmAuthSettingsAtom, realmConfigSchema, registerSchema, registrationIntentResponseSchema, resetPasswordRequestSchema, resetPasswordSchema, sessionQuerySchema, sessionResourceSchema, sessions, updateUserSchema, userQuerySchema, userResourceSchema, users };
|
|
8405
8546
|
//# sourceMappingURL=index.d.ts.map
|