alepha 0.19.3 → 0.19.4
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/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/dist/api/audits/index.d.ts +8 -8
- package/dist/api/invitations/index.d.ts +790 -0
- package/dist/api/invitations/index.d.ts.map +1 -0
- package/dist/api/invitations/index.js +665 -0
- package/dist/api/invitations/index.js.map +1 -0
- package/dist/api/jobs/index.browser.js +8 -9
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +99 -43
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +257 -40
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +5 -5
- package/dist/api/notifications/index.browser.js +0 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +0 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +112 -1
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +90 -3
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +79 -12
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/{billing → api/payments}/index.d.ts +67 -49
- package/dist/api/payments/index.d.ts.map +1 -0
- package/dist/{billing → api/payments}/index.js +108 -74
- package/dist/api/payments/index.js.map +1 -0
- package/dist/api/subscriptions/index.d.ts +1692 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1870 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +18 -2
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +167 -34
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +13 -13
- package/dist/api/workflows/index.browser.js +246 -0
- package/dist/api/workflows/index.browser.js.map +1 -0
- package/dist/api/workflows/index.d.ts +1618 -0
- package/dist/api/workflows/index.d.ts.map +1 -0
- package/dist/api/workflows/index.js +1504 -0
- package/dist/api/workflows/index.js.map +1 -0
- package/dist/cli/core/index.d.ts +44 -28
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +16 -61
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +31 -8
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +79 -24
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/core/index.browser.js +21 -2
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +33 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +21 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +21 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +21 -2
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.js +24 -8
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +0 -18
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +0 -17
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +1 -13
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +0 -17
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +3 -3
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +3 -3
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/router/index.browser.js +25 -3
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +16 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +25 -3
- package/dist/react/router/index.js.map +1 -1
- package/dist/security/index.d.ts +28 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +28 -0
- package/dist/security/index.js.map +1 -1
- package/package.json +37 -20
- package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
- package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
- package/src/api/invitations/controllers/InvitationController.ts +84 -0
- package/src/api/invitations/entities/invitations.ts +33 -0
- package/src/api/invitations/index.ts +65 -0
- package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
- package/src/api/invitations/providers/InvitationProvider.ts +45 -0
- package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
- package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
- package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
- package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
- package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
- package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
- package/src/api/invitations/services/InvitationService.ts +556 -0
- package/src/api/jobs/__tests__/$job.spec.ts +876 -0
- package/src/api/jobs/controllers/AdminJobController.ts +44 -0
- package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
- package/src/api/jobs/index.ts +0 -3
- package/src/api/jobs/primitives/$job.ts +22 -11
- package/src/api/jobs/providers/JobProvider.ts +229 -19
- package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
- package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
- package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
- package/src/api/jobs/services/JobService.ts +51 -12
- package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
- package/src/api/parameters/index.browser.ts +12 -0
- package/src/api/parameters/primitives/$parameter.ts +20 -3
- package/src/api/parameters/services/ParameterProvider.ts +48 -7
- package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
- package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
- package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
- package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
- package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
- package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
- package/src/{billing → api/payments}/index.ts +31 -25
- package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
- package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
- package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
- package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +144 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
- package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
- package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
- package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
- package/src/api/users/__tests__/SessionService.spec.ts +142 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
- package/src/api/users/controllers/UserController.ts +3 -8
- package/src/api/users/notifications/UserNotifications.ts +23 -0
- package/src/api/users/schemas/loginSchema.ts +1 -1
- package/src/api/users/services/CredentialService.ts +51 -4
- package/src/api/users/services/RegistrationService.ts +38 -9
- package/src/api/users/services/SessionService.ts +62 -9
- package/src/api/users/services/UserService.ts +21 -12
- package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
- package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
- package/src/api/workflows/entities/workflowExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
- package/src/api/workflows/index.browser.ts +22 -0
- package/src/api/workflows/index.ts +124 -0
- package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
- package/src/api/workflows/primitives/$workflow.ts +202 -0
- package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
- package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
- package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
- package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
- package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
- package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
- package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
- package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
- package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
- package/src/api/workflows/services/WorkflowService.ts +382 -0
- package/src/cli/core/templates/webAppRouterTs.ts +5 -58
- package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
- package/src/cli/vendor/services/VendorService.ts +126 -27
- package/src/core/__tests__/TypeProvider.spec.ts +4 -2
- package/src/core/providers/SchemaValidator.ts +1 -1
- package/src/core/providers/TypeProvider.ts +46 -3
- package/src/orm/__tests__/enums.spec.ts +22 -29
- package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
- package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
- package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
- package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
- package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
- package/src/security/primitives/$secure.ts +28 -0
- package/dist/billing/index.d.ts.map +0 -1
- package/dist/billing/index.js.map +0 -1
- package/src/billing/__tests__/BillingService.spec.ts +0 -136
- /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
- /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
- /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
|
@@ -6,7 +6,7 @@ import { $job } from "alepha/api/jobs";
|
|
|
6
6
|
import { DateTimeProvider } from "alepha/datetime";
|
|
7
7
|
import { $logger } from "alepha/logger";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
|
-
//#region ../../src/
|
|
9
|
+
//#region ../../src/api/payments/entities/paymentIntents.ts
|
|
10
10
|
const paymentIntents = $entity({
|
|
11
11
|
name: "payment_intents",
|
|
12
12
|
schema: t.object({
|
|
@@ -22,6 +22,7 @@ const paymentIntents = $entity({
|
|
|
22
22
|
"processing",
|
|
23
23
|
"authorized",
|
|
24
24
|
"captured",
|
|
25
|
+
"partially_refunded",
|
|
25
26
|
"voided",
|
|
26
27
|
"failed",
|
|
27
28
|
"cancelled",
|
|
@@ -42,7 +43,7 @@ const paymentIntents = $entity({
|
|
|
42
43
|
]
|
|
43
44
|
});
|
|
44
45
|
//#endregion
|
|
45
|
-
//#region ../../src/
|
|
46
|
+
//#region ../../src/api/payments/schemas/intentSchemas.ts
|
|
46
47
|
const createIntentSchema = t.object({
|
|
47
48
|
amount: t.integer({ minimum: 1 }),
|
|
48
49
|
currency: t.text({ size: "short" }),
|
|
@@ -74,7 +75,7 @@ const intentQuerySchema = t.extend(pageQuerySchema, {
|
|
|
74
75
|
});
|
|
75
76
|
const intentResourceSchema = paymentIntents.schema;
|
|
76
77
|
//#endregion
|
|
77
|
-
//#region ../../src/
|
|
78
|
+
//#region ../../src/api/payments/entities/refunds.ts
|
|
78
79
|
const refunds = $entity({
|
|
79
80
|
name: "refunds",
|
|
80
81
|
schema: t.object({
|
|
@@ -102,23 +103,23 @@ const refunds = $entity({
|
|
|
102
103
|
]
|
|
103
104
|
});
|
|
104
105
|
//#endregion
|
|
105
|
-
//#region ../../src/
|
|
106
|
+
//#region ../../src/api/payments/schemas/refundSchemas.ts
|
|
106
107
|
const refundResourceSchema = refunds.schema;
|
|
107
108
|
//#endregion
|
|
108
|
-
//#region ../../src/
|
|
109
|
-
var
|
|
109
|
+
//#region ../../src/api/payments/errors/PaymentError.ts
|
|
110
|
+
var PaymentError = class extends AlephaError {
|
|
110
111
|
status = 400;
|
|
111
112
|
};
|
|
112
113
|
//#endregion
|
|
113
|
-
//#region ../../src/
|
|
114
|
-
var
|
|
114
|
+
//#region ../../src/api/payments/providers/PaymentProvider.ts
|
|
115
|
+
var PaymentProvider = class {};
|
|
115
116
|
//#endregion
|
|
116
|
-
//#region ../../src/
|
|
117
|
-
var
|
|
117
|
+
//#region ../../src/api/payments/services/PaymentService.ts
|
|
118
|
+
var PaymentService = class PaymentService {
|
|
118
119
|
alepha = $inject(Alepha);
|
|
119
120
|
log = $logger();
|
|
120
121
|
dateTime = $inject(DateTimeProvider);
|
|
121
|
-
provider = $inject(
|
|
122
|
+
provider = $inject(PaymentProvider);
|
|
122
123
|
intentRepo = $repository(paymentIntents);
|
|
123
124
|
refundRepo = $repository(refunds);
|
|
124
125
|
/**
|
|
@@ -150,7 +151,7 @@ var BillingService = class {
|
|
|
150
151
|
async createIntent(amount, currency, metadata, options) {
|
|
151
152
|
return await this.intentRepo.create({
|
|
152
153
|
amount,
|
|
153
|
-
currency,
|
|
154
|
+
currency: currency.toLowerCase(),
|
|
154
155
|
status: "created",
|
|
155
156
|
metadata,
|
|
156
157
|
paymentMethodId: options?.paymentMethodId,
|
|
@@ -161,9 +162,11 @@ var BillingService = class {
|
|
|
161
162
|
* Create a checkout session with the payment provider and
|
|
162
163
|
* transition the intent to "processing".
|
|
163
164
|
*/
|
|
164
|
-
async createSession(intentId, returnUrl, authorize) {
|
|
165
|
+
async createSession(intentId, returnUrl, authorize, userId) {
|
|
165
166
|
const intent = await this.getIntent(intentId);
|
|
166
167
|
this.assertStatus(intent, "created", "createSession");
|
|
168
|
+
if (userId && intent.userId && intent.userId !== userId) throw new PaymentError("Payment intent does not belong to this user");
|
|
169
|
+
if (userId && !intent.userId) await this.intentRepo.updateById(intent.id, { userId });
|
|
167
170
|
const result = await this.provider.createSession(intent, {
|
|
168
171
|
returnUrl,
|
|
169
172
|
authorize
|
|
@@ -195,20 +198,36 @@ var BillingService = class {
|
|
|
195
198
|
}
|
|
196
199
|
/**
|
|
197
200
|
* Process a webhook event by updating the intent status and emitting
|
|
198
|
-
* the corresponding
|
|
201
|
+
* the corresponding payment event.
|
|
202
|
+
*/
|
|
203
|
+
/**
|
|
204
|
+
* Valid status transitions from webhook events.
|
|
205
|
+
* Only these transitions are allowed — all others are silently ignored.
|
|
199
206
|
*/
|
|
207
|
+
static VALID_WEBHOOK_TRANSITIONS = {
|
|
208
|
+
processing: [
|
|
209
|
+
"authorized",
|
|
210
|
+
"captured",
|
|
211
|
+
"failed"
|
|
212
|
+
],
|
|
213
|
+
authorized: ["captured", "failed"]
|
|
214
|
+
};
|
|
200
215
|
async handleWebhookEvent(intentId, status, raw) {
|
|
201
216
|
const intent = await this.getIntent(intentId);
|
|
202
217
|
const eventMap = {
|
|
203
|
-
authorized: "
|
|
204
|
-
captured: "
|
|
205
|
-
failed: "
|
|
218
|
+
authorized: "payments:authorized",
|
|
219
|
+
captured: "payments:captured",
|
|
220
|
+
failed: "payments:failed"
|
|
206
221
|
};
|
|
207
222
|
if (!(status in eventMap)) {
|
|
208
223
|
this.log.warn(`Unknown webhook status: ${status}`);
|
|
209
224
|
return;
|
|
210
225
|
}
|
|
211
226
|
const webhookStatus = status;
|
|
227
|
+
if (!PaymentService.VALID_WEBHOOK_TRANSITIONS[intent.status]?.includes(webhookStatus)) {
|
|
228
|
+
this.log.warn(`Ignoring webhook: cannot transition ${intent.status} → ${webhookStatus}`, { intentId: intent.id });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
212
231
|
await this.intentRepo.updateById(intent.id, {
|
|
213
232
|
status: webhookStatus,
|
|
214
233
|
providerRaw: raw
|
|
@@ -228,12 +247,13 @@ var BillingService = class {
|
|
|
228
247
|
const intent = await this.getIntent(intentId);
|
|
229
248
|
this.assertStatus(intent, "authorized", "capture");
|
|
230
249
|
const amount = finalAmount ?? intent.amount;
|
|
250
|
+
if (amount > intent.amount) throw new PaymentError(`Capture amount ${amount} exceeds authorized amount ${intent.amount}`);
|
|
231
251
|
if (intent.providerRef) await this.provider.capturePayment(intent.providerRef, amount);
|
|
232
252
|
const updated = await this.intentRepo.updateById(intent.id, {
|
|
233
253
|
status: "captured",
|
|
234
254
|
amount
|
|
235
255
|
});
|
|
236
|
-
await this.alepha.events.emit("
|
|
256
|
+
await this.alepha.events.emit("payments:captured", {
|
|
237
257
|
intentId: intent.id,
|
|
238
258
|
amount,
|
|
239
259
|
currency: intent.currency,
|
|
@@ -249,7 +269,7 @@ var BillingService = class {
|
|
|
249
269
|
this.assertStatus(intent, "authorized", "void");
|
|
250
270
|
if (intent.providerRef) await this.provider.voidPayment(intent.providerRef);
|
|
251
271
|
const updated = await this.intentRepo.updateById(intent.id, { status: "voided" });
|
|
252
|
-
await this.alepha.events.emit("
|
|
272
|
+
await this.alepha.events.emit("payments:voided", {
|
|
253
273
|
intentId: intent.id,
|
|
254
274
|
amount: intent.amount,
|
|
255
275
|
currency: intent.currency,
|
|
@@ -262,7 +282,10 @@ var BillingService = class {
|
|
|
262
282
|
*/
|
|
263
283
|
async refund(intentId, amount, reason) {
|
|
264
284
|
const intent = await this.getIntent(intentId);
|
|
265
|
-
|
|
285
|
+
if (intent.status !== "captured" && intent.status !== "partially_refunded") throw new PaymentError(`Cannot refund: intent ${intent.id} is '${intent.status}', expected 'captured' or 'partially_refunded'`);
|
|
286
|
+
const totalRefunded = (await this.refundRepo.findMany({ where: { intentId: { eq: intent.id } } })).reduce((sum, r) => sum + r.amount, 0);
|
|
287
|
+
const remaining = intent.amount - totalRefunded;
|
|
288
|
+
if (amount > remaining) throw new PaymentError(`Refund amount ${amount} exceeds remaining refundable amount ${remaining}`);
|
|
266
289
|
let refundProviderRef;
|
|
267
290
|
if (intent.providerRef) refundProviderRef = (await this.provider.refundPayment(intent.providerRef, amount)).providerRef;
|
|
268
291
|
const refund = await this.refundRepo.create({
|
|
@@ -274,8 +297,9 @@ var BillingService = class {
|
|
|
274
297
|
reason,
|
|
275
298
|
providerRef: refundProviderRef
|
|
276
299
|
});
|
|
277
|
-
|
|
278
|
-
await this.
|
|
300
|
+
const newStatus = totalRefunded + amount >= intent.amount ? "refunded" : "partially_refunded";
|
|
301
|
+
await this.intentRepo.updateById(intent.id, { status: newStatus });
|
|
302
|
+
await this.alepha.events.emit("payments:refunded", {
|
|
279
303
|
intentId: intent.id,
|
|
280
304
|
refundId: refund.id,
|
|
281
305
|
amount,
|
|
@@ -291,11 +315,11 @@ var BillingService = class {
|
|
|
291
315
|
async recordCashPayment(amount, currency, metadata) {
|
|
292
316
|
const intent = await this.intentRepo.create({
|
|
293
317
|
amount,
|
|
294
|
-
currency,
|
|
318
|
+
currency: currency.toLowerCase(),
|
|
295
319
|
status: "captured",
|
|
296
320
|
metadata
|
|
297
321
|
});
|
|
298
|
-
await this.alepha.events.emit("
|
|
322
|
+
await this.alepha.events.emit("payments:captured", {
|
|
299
323
|
intentId: intent.id,
|
|
300
324
|
amount,
|
|
301
325
|
currency,
|
|
@@ -309,7 +333,14 @@ var BillingService = class {
|
|
|
309
333
|
async cancel(intentId) {
|
|
310
334
|
const intent = await this.getIntent(intentId);
|
|
311
335
|
this.assertStatus(intent, "created", "cancel");
|
|
312
|
-
|
|
336
|
+
const cancelled = await this.intentRepo.updateById(intent.id, { status: "cancelled" });
|
|
337
|
+
await this.alepha.events.emit("payments:cancelled", {
|
|
338
|
+
intentId: intent.id,
|
|
339
|
+
amount: intent.amount,
|
|
340
|
+
currency: intent.currency,
|
|
341
|
+
metadata: intent.metadata
|
|
342
|
+
});
|
|
343
|
+
return cancelled;
|
|
313
344
|
}
|
|
314
345
|
/**
|
|
315
346
|
* Get a payment intent by ID. Throws NotFoundError if not found.
|
|
@@ -327,28 +358,28 @@ var BillingService = class {
|
|
|
327
358
|
return await this.intentRepo.paginate(query, { where }, { count: true });
|
|
328
359
|
}
|
|
329
360
|
assertStatus(intent, expected, operation) {
|
|
330
|
-
if (intent.status !== expected) throw new
|
|
361
|
+
if (intent.status !== expected) throw new PaymentError(`Cannot ${operation}: intent ${intent.id} is '${intent.status}', expected '${expected}'`);
|
|
331
362
|
}
|
|
332
363
|
};
|
|
333
364
|
//#endregion
|
|
334
|
-
//#region ../../src/
|
|
335
|
-
var
|
|
336
|
-
url = "/admin/
|
|
337
|
-
group = "admin:
|
|
338
|
-
|
|
365
|
+
//#region ../../src/api/payments/controllers/AdminPaymentController.ts
|
|
366
|
+
var AdminPaymentController = class {
|
|
367
|
+
url = "/admin/payments";
|
|
368
|
+
group = "admin:payments";
|
|
369
|
+
payments = $inject(PaymentService);
|
|
339
370
|
/**
|
|
340
371
|
* List payment intents with pagination and filtering.
|
|
341
372
|
*/
|
|
342
373
|
listIntents = $action({
|
|
343
374
|
path: `${this.url}/intents`,
|
|
344
375
|
group: this.group,
|
|
345
|
-
use: [$secure({ permissions: ["
|
|
376
|
+
use: [$secure({ permissions: ["payments:read"] })],
|
|
346
377
|
description: "List payment intents",
|
|
347
378
|
schema: {
|
|
348
379
|
query: intentQuerySchema,
|
|
349
380
|
response: t.page(intentResourceSchema)
|
|
350
381
|
},
|
|
351
|
-
handler: ({ query }) => this.
|
|
382
|
+
handler: ({ query }) => this.payments.findIntents(query)
|
|
352
383
|
});
|
|
353
384
|
/**
|
|
354
385
|
* Get a payment intent by ID.
|
|
@@ -356,13 +387,13 @@ var AdminBillingController = class {
|
|
|
356
387
|
getIntent = $action({
|
|
357
388
|
path: `${this.url}/intents/:id`,
|
|
358
389
|
group: this.group,
|
|
359
|
-
use: [$secure({ permissions: ["
|
|
390
|
+
use: [$secure({ permissions: ["payments:read"] })],
|
|
360
391
|
description: "Get payment intent details",
|
|
361
392
|
schema: {
|
|
362
393
|
params: t.object({ id: t.uuid() }),
|
|
363
394
|
response: intentResourceSchema
|
|
364
395
|
},
|
|
365
|
-
handler: ({ params }) => this.
|
|
396
|
+
handler: ({ params }) => this.payments.getIntent(params.id)
|
|
366
397
|
});
|
|
367
398
|
/**
|
|
368
399
|
* Capture an authorized intent.
|
|
@@ -371,14 +402,14 @@ var AdminBillingController = class {
|
|
|
371
402
|
method: "POST",
|
|
372
403
|
path: `${this.url}/intents/:id/capture`,
|
|
373
404
|
group: this.group,
|
|
374
|
-
use: [$secure({ permissions: ["
|
|
405
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
375
406
|
description: "Capture an authorized payment intent",
|
|
376
407
|
schema: {
|
|
377
408
|
params: t.object({ id: t.uuid() }),
|
|
378
409
|
body: captureIntentSchema,
|
|
379
410
|
response: intentResourceSchema
|
|
380
411
|
},
|
|
381
|
-
handler: ({ params, body }) => this.
|
|
412
|
+
handler: ({ params, body }) => this.payments.capture(params.id, body.amount)
|
|
382
413
|
});
|
|
383
414
|
/**
|
|
384
415
|
* Void an authorized intent.
|
|
@@ -387,13 +418,13 @@ var AdminBillingController = class {
|
|
|
387
418
|
method: "POST",
|
|
388
419
|
path: `${this.url}/intents/:id/void`,
|
|
389
420
|
group: this.group,
|
|
390
|
-
use: [$secure({ permissions: ["
|
|
421
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
391
422
|
description: "Void an authorized payment intent",
|
|
392
423
|
schema: {
|
|
393
424
|
params: t.object({ id: t.uuid() }),
|
|
394
425
|
response: intentResourceSchema
|
|
395
426
|
},
|
|
396
|
-
handler: ({ params }) => this.
|
|
427
|
+
handler: ({ params }) => this.payments.void(params.id)
|
|
397
428
|
});
|
|
398
429
|
/**
|
|
399
430
|
* Refund a captured intent.
|
|
@@ -402,14 +433,14 @@ var AdminBillingController = class {
|
|
|
402
433
|
method: "POST",
|
|
403
434
|
path: `${this.url}/intents/:id/refund`,
|
|
404
435
|
group: this.group,
|
|
405
|
-
use: [$secure({ permissions: ["
|
|
436
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
406
437
|
description: "Issue partial or full refund",
|
|
407
438
|
schema: {
|
|
408
439
|
params: t.object({ id: t.uuid() }),
|
|
409
440
|
body: refundIntentSchema,
|
|
410
441
|
response: refundResourceSchema
|
|
411
442
|
},
|
|
412
|
-
handler: ({ params, body }) => this.
|
|
443
|
+
handler: ({ params, body }) => this.payments.refund(params.id, body.amount, body.reason)
|
|
413
444
|
});
|
|
414
445
|
/**
|
|
415
446
|
* Cancel a created intent.
|
|
@@ -418,13 +449,13 @@ var AdminBillingController = class {
|
|
|
418
449
|
method: "POST",
|
|
419
450
|
path: `${this.url}/intents/:id/cancel`,
|
|
420
451
|
group: this.group,
|
|
421
|
-
use: [$secure({ permissions: ["
|
|
452
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
422
453
|
description: "Cancel a created payment intent",
|
|
423
454
|
schema: {
|
|
424
455
|
params: t.object({ id: t.uuid() }),
|
|
425
456
|
response: intentResourceSchema
|
|
426
457
|
},
|
|
427
|
-
handler: ({ params }) => this.
|
|
458
|
+
handler: ({ params }) => this.payments.cancel(params.id)
|
|
428
459
|
});
|
|
429
460
|
/**
|
|
430
461
|
* Record a cash payment.
|
|
@@ -433,31 +464,31 @@ var AdminBillingController = class {
|
|
|
433
464
|
method: "POST",
|
|
434
465
|
path: `${this.url}/cash`,
|
|
435
466
|
group: this.group,
|
|
436
|
-
use: [$secure({ permissions: ["
|
|
467
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
437
468
|
description: "Record a cash payment",
|
|
438
469
|
schema: {
|
|
439
470
|
body: recordCashSchema,
|
|
440
471
|
response: intentResourceSchema
|
|
441
472
|
},
|
|
442
|
-
handler: ({ body }) => this.
|
|
473
|
+
handler: ({ body }) => this.payments.recordCashPayment(body.amount, body.currency, body.metadata)
|
|
443
474
|
});
|
|
444
475
|
/**
|
|
445
476
|
* PSP webhook endpoint (not under /admin, no auth — verified by provider).
|
|
446
477
|
*/
|
|
447
478
|
webhook = $action({
|
|
448
479
|
method: "POST",
|
|
449
|
-
path: "/
|
|
480
|
+
path: "/payments/webhook",
|
|
450
481
|
group: this.group,
|
|
451
482
|
description: "PSP webhook endpoint",
|
|
452
483
|
schema: { response: okSchema },
|
|
453
484
|
handler: async (request) => {
|
|
454
|
-
await this.
|
|
485
|
+
await this.payments.handleWebhook(request.raw.web.req);
|
|
455
486
|
return { ok: true };
|
|
456
487
|
}
|
|
457
488
|
});
|
|
458
489
|
};
|
|
459
490
|
//#endregion
|
|
460
|
-
//#region ../../src/
|
|
491
|
+
//#region ../../src/api/payments/entities/paymentMethods.ts
|
|
461
492
|
const paymentMethods = $entity({
|
|
462
493
|
name: "payment_methods",
|
|
463
494
|
schema: t.object({
|
|
@@ -478,14 +509,14 @@ const paymentMethods = $entity({
|
|
|
478
509
|
indexes: ["userId", "organizationId"]
|
|
479
510
|
});
|
|
480
511
|
//#endregion
|
|
481
|
-
//#region ../../src/
|
|
512
|
+
//#region ../../src/api/payments/schemas/paymentMethodSchemas.ts
|
|
482
513
|
const addPaymentMethodSchema = t.object({ token: t.text() });
|
|
483
514
|
const paymentMethodResourceSchema = paymentMethods.schema;
|
|
484
515
|
//#endregion
|
|
485
|
-
//#region ../../src/
|
|
516
|
+
//#region ../../src/api/payments/services/PaymentMethodService.ts
|
|
486
517
|
var PaymentMethodService = class {
|
|
487
518
|
log = $logger();
|
|
488
|
-
provider = $inject(
|
|
519
|
+
provider = $inject(PaymentProvider);
|
|
489
520
|
methodRepo = $repository(paymentMethods);
|
|
490
521
|
async addPaymentMethod(userId, organizationId, token) {
|
|
491
522
|
const result = await this.provider.createPaymentMethod(userId, token);
|
|
@@ -507,24 +538,24 @@ var PaymentMethodService = class {
|
|
|
507
538
|
}
|
|
508
539
|
async removePaymentMethod(methodId, userId) {
|
|
509
540
|
const method = await this.methodRepo.getById(methodId);
|
|
510
|
-
if (method.userId !== userId) throw new
|
|
541
|
+
if (method.userId !== userId) throw new PaymentError("Cannot remove another user's payment method");
|
|
511
542
|
await this.provider.deletePaymentMethod(method.providerRef);
|
|
512
543
|
await this.methodRepo.deleteById(method.id);
|
|
513
544
|
}
|
|
514
545
|
async setDefault(methodId, userId) {
|
|
515
546
|
const method = await this.methodRepo.getById(methodId);
|
|
516
|
-
if (method.userId !== userId) throw new
|
|
547
|
+
if (method.userId !== userId) throw new PaymentError("Cannot modify another user's payment method");
|
|
517
548
|
const userMethods = await this.methodRepo.findMany({ where: { userId: { eq: userId } } });
|
|
518
549
|
for (const m of userMethods) if (m.isDefault) await this.methodRepo.updateById(m.id, { isDefault: false });
|
|
519
550
|
return await this.methodRepo.updateById(method.id, { isDefault: true });
|
|
520
551
|
}
|
|
521
552
|
};
|
|
522
553
|
//#endregion
|
|
523
|
-
//#region ../../src/
|
|
524
|
-
var
|
|
525
|
-
url = "/
|
|
526
|
-
group = "
|
|
527
|
-
|
|
554
|
+
//#region ../../src/api/payments/controllers/PaymentController.ts
|
|
555
|
+
var PaymentController = class {
|
|
556
|
+
url = "/payments";
|
|
557
|
+
group = "payments";
|
|
558
|
+
payments = $inject(PaymentService);
|
|
528
559
|
paymentMethods = $inject(PaymentMethodService);
|
|
529
560
|
/**
|
|
530
561
|
* List the current user's saved payment methods.
|
|
@@ -550,7 +581,10 @@ var BillingController = class {
|
|
|
550
581
|
body: addPaymentMethodSchema,
|
|
551
582
|
response: paymentMethodResourceSchema
|
|
552
583
|
},
|
|
553
|
-
handler: ({ body, user }) =>
|
|
584
|
+
handler: ({ body, user }) => {
|
|
585
|
+
if (!user.organization) throw new PaymentError("Organization is required to add a payment method");
|
|
586
|
+
return this.paymentMethods.addPaymentMethod(user.id, user.organization, body.token);
|
|
587
|
+
}
|
|
554
588
|
});
|
|
555
589
|
/**
|
|
556
590
|
* Remove a payment method.
|
|
@@ -601,12 +635,12 @@ var BillingController = class {
|
|
|
601
635
|
body: createCheckoutSchema,
|
|
602
636
|
response: checkoutResponseSchema
|
|
603
637
|
},
|
|
604
|
-
handler: ({ body }) => this.
|
|
638
|
+
handler: ({ body, user }) => this.payments.createSession(body.intentId, body.returnUrl, body.authorize, user.id)
|
|
605
639
|
});
|
|
606
640
|
};
|
|
607
641
|
//#endregion
|
|
608
|
-
//#region ../../src/
|
|
609
|
-
var
|
|
642
|
+
//#region ../../src/api/payments/providers/MemoryPaymentProvider.ts
|
|
643
|
+
var MemoryPaymentProvider = class {
|
|
610
644
|
charges = /* @__PURE__ */ new Map();
|
|
611
645
|
refundRecords = /* @__PURE__ */ new Map();
|
|
612
646
|
methods = /* @__PURE__ */ new Map();
|
|
@@ -620,7 +654,7 @@ var MemoryBillingProvider = class {
|
|
|
620
654
|
status
|
|
621
655
|
});
|
|
622
656
|
return {
|
|
623
|
-
url: `/
|
|
657
|
+
url: `/payments/mock-checkout/${intent.id}`,
|
|
624
658
|
providerRef
|
|
625
659
|
};
|
|
626
660
|
}
|
|
@@ -688,26 +722,26 @@ var MemoryBillingProvider = class {
|
|
|
688
722
|
}
|
|
689
723
|
};
|
|
690
724
|
//#endregion
|
|
691
|
-
//#region ../../src/
|
|
692
|
-
const
|
|
693
|
-
name: "alepha.
|
|
725
|
+
//#region ../../src/api/payments/index.ts
|
|
726
|
+
const AlephaApiPayments = $module({
|
|
727
|
+
name: "alepha.api.payments",
|
|
694
728
|
services: [
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
729
|
+
AdminPaymentController,
|
|
730
|
+
PaymentController,
|
|
731
|
+
PaymentProvider,
|
|
732
|
+
MemoryPaymentProvider,
|
|
733
|
+
PaymentService,
|
|
700
734
|
PaymentMethodService
|
|
701
735
|
],
|
|
702
736
|
register: (alepha) => {
|
|
703
737
|
alepha.with({
|
|
704
738
|
optional: true,
|
|
705
|
-
provide:
|
|
706
|
-
use:
|
|
739
|
+
provide: PaymentProvider,
|
|
740
|
+
use: MemoryPaymentProvider
|
|
707
741
|
});
|
|
708
742
|
}
|
|
709
743
|
});
|
|
710
744
|
//#endregion
|
|
711
|
-
export {
|
|
745
|
+
export { AdminPaymentController, AlephaApiPayments, MemoryPaymentProvider, PaymentController, PaymentError, PaymentMethodService, PaymentProvider, PaymentService, addPaymentMethodSchema, captureIntentSchema, checkoutResponseSchema, createCheckoutSchema, createIntentSchema, intentQuerySchema, intentResourceSchema, paymentIntents, paymentMethodResourceSchema, paymentMethods, recordCashSchema, refundIntentSchema, refundResourceSchema, refunds };
|
|
712
746
|
|
|
713
747
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 15 minutes.\n */\n protected readonly expireStaleIntents = $job({\n cron: \"*/15 * * * *\",\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 MemoryPaymentProvider,\n PaymentService,\n PaymentMethodService,\n ],\n register: (alepha) => {\n alepha.with({\n optional: true,\n provide: PaymentProvider,\n use: MemoryPaymentProvider,\n });\n },\n});\n"],"mappings":"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"}
|