alepha 0.19.2 → 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 +90 -34
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +267 -44
- package/dist/api/jobs/index.js.map +1 -1
- 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 +27 -21
- 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/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/config/index.d.ts +6 -28
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +5 -10
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +11669 -208
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +60 -69
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +5 -0
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +4 -0
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +69 -64
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +6 -2
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +38 -10
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +85 -26
- 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 +25 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +25 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +25 -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/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/logger/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 +25 -73
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +10 -32
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +25 -73
- 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 +2 -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 +239 -25
- 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/config/defineConfig.ts +17 -46
- package/src/cli/core/providers/ViteDevServerProvider.ts +45 -3
- package/src/cli/core/services/PackageManagerUtils.ts +3 -1
- package/src/cli/core/services/ProjectScaffolder.ts +5 -5
- package/src/cli/core/templates/agentMd.ts +14 -5
- package/src/cli/core/templates/webAppRouterTs.ts +5 -58
- package/src/cli/devtools/index.ts +21 -1
- package/src/cli/platform/index.ts +23 -2
- package/src/cli/vendor/__tests__/VendorService.spec.ts +283 -178
- package/src/cli/vendor/index.ts +20 -3
- package/src/cli/vendor/services/VendorService.ts +126 -27
- package/src/core/Alepha.ts +10 -0
- 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/logger/index.ts +6 -1
- 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/core/providers/DrizzleKitProvider.ts +56 -105
- 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/tsconfig.base.json +0 -1
- 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
|
@@ -2,7 +2,8 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { Alepha } from "alepha";
|
|
3
3
|
import { AlephaOrmPostgres } from "alepha/orm/postgres";
|
|
4
4
|
import { describe, it } from "vitest";
|
|
5
|
-
import {
|
|
5
|
+
import { PaymentError } from "../errors/PaymentError.ts";
|
|
6
|
+
import { AlephaApiPayments } from "../index.ts";
|
|
6
7
|
import { PaymentMethodService } from "../services/PaymentMethodService.ts";
|
|
7
8
|
|
|
8
9
|
describe("PaymentMethodService", () => {
|
|
@@ -10,7 +11,9 @@ describe("PaymentMethodService", () => {
|
|
|
10
11
|
const userId2 = randomUUID();
|
|
11
12
|
const orgId = randomUUID();
|
|
12
13
|
it("should add a payment method", async ({ expect }) => {
|
|
13
|
-
const alepha = Alepha.create()
|
|
14
|
+
const alepha = Alepha.create()
|
|
15
|
+
.with(AlephaOrmPostgres)
|
|
16
|
+
.with(AlephaApiPayments);
|
|
14
17
|
const service = alepha.inject(PaymentMethodService);
|
|
15
18
|
await alepha.start();
|
|
16
19
|
|
|
@@ -21,7 +24,9 @@ describe("PaymentMethodService", () => {
|
|
|
21
24
|
});
|
|
22
25
|
|
|
23
26
|
it("should list payment methods for a user", async ({ expect }) => {
|
|
24
|
-
const alepha = Alepha.create()
|
|
27
|
+
const alepha = Alepha.create()
|
|
28
|
+
.with(AlephaOrmPostgres)
|
|
29
|
+
.with(AlephaApiPayments);
|
|
25
30
|
const service = alepha.inject(PaymentMethodService);
|
|
26
31
|
await alepha.start();
|
|
27
32
|
|
|
@@ -33,7 +38,9 @@ describe("PaymentMethodService", () => {
|
|
|
33
38
|
});
|
|
34
39
|
|
|
35
40
|
it("should remove a payment method", async ({ expect }) => {
|
|
36
|
-
const alepha = Alepha.create()
|
|
41
|
+
const alepha = Alepha.create()
|
|
42
|
+
.with(AlephaOrmPostgres)
|
|
43
|
+
.with(AlephaApiPayments);
|
|
37
44
|
const service = alepha.inject(PaymentMethodService);
|
|
38
45
|
await alepha.start();
|
|
39
46
|
|
|
@@ -45,7 +52,9 @@ describe("PaymentMethodService", () => {
|
|
|
45
52
|
});
|
|
46
53
|
|
|
47
54
|
it("should set a default payment method", async ({ expect }) => {
|
|
48
|
-
const alepha = Alepha.create()
|
|
55
|
+
const alepha = Alepha.create()
|
|
56
|
+
.with(AlephaOrmPostgres)
|
|
57
|
+
.with(AlephaApiPayments);
|
|
49
58
|
const service = alepha.inject(PaymentMethodService);
|
|
50
59
|
await alepha.start();
|
|
51
60
|
|
|
@@ -66,7 +75,9 @@ describe("PaymentMethodService", () => {
|
|
|
66
75
|
it("should reject removing another user's payment method", async ({
|
|
67
76
|
expect,
|
|
68
77
|
}) => {
|
|
69
|
-
const alepha = Alepha.create()
|
|
78
|
+
const alepha = Alepha.create()
|
|
79
|
+
.with(AlephaOrmPostgres)
|
|
80
|
+
.with(AlephaApiPayments);
|
|
70
81
|
const service = alepha.inject(PaymentMethodService);
|
|
71
82
|
await alepha.start();
|
|
72
83
|
|
|
@@ -75,4 +86,19 @@ describe("PaymentMethodService", () => {
|
|
|
75
86
|
service.removePaymentMethod(method.id, userId2),
|
|
76
87
|
).rejects.toThrowError();
|
|
77
88
|
});
|
|
89
|
+
|
|
90
|
+
it("should reject setting default for another user's payment method", async ({
|
|
91
|
+
expect,
|
|
92
|
+
}) => {
|
|
93
|
+
const alepha = Alepha.create()
|
|
94
|
+
.with(AlephaOrmPostgres)
|
|
95
|
+
.with(AlephaApiPayments);
|
|
96
|
+
const service = alepha.inject(PaymentMethodService);
|
|
97
|
+
await alepha.start();
|
|
98
|
+
|
|
99
|
+
const method = await service.addPaymentMethod(userId, orgId, "tok_visa");
|
|
100
|
+
await expect(service.setDefault(method.id, userId2)).rejects.toThrowError(
|
|
101
|
+
PaymentError,
|
|
102
|
+
);
|
|
103
|
+
});
|
|
78
104
|
});
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Alepha } from "alepha";
|
|
3
|
+
import { AlephaOrmPostgres } from "alepha/orm/postgres";
|
|
4
|
+
import { describe, it } from "vitest";
|
|
5
|
+
import { PaymentError } from "../errors/PaymentError.ts";
|
|
6
|
+
import { AlephaApiPayments } from "../index.ts";
|
|
7
|
+
import { PaymentService } from "../services/PaymentService.ts";
|
|
8
|
+
|
|
9
|
+
const setup = async () => {
|
|
10
|
+
const alepha = Alepha.create()
|
|
11
|
+
.with(AlephaOrmPostgres)
|
|
12
|
+
.with(AlephaApiPayments);
|
|
13
|
+
const payments = alepha.inject(PaymentService);
|
|
14
|
+
await alepha.start();
|
|
15
|
+
return { alepha, payments };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe("PaymentService", () => {
|
|
19
|
+
it("should create an intent in 'created' status", async ({ expect }) => {
|
|
20
|
+
const { payments } = await setup();
|
|
21
|
+
|
|
22
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
23
|
+
expect(intent.amount).toBe(1500);
|
|
24
|
+
expect(intent.currency).toBe("eur");
|
|
25
|
+
expect(intent.status).toBe("created");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should create a session and transition to 'processing'", async ({
|
|
29
|
+
expect,
|
|
30
|
+
}) => {
|
|
31
|
+
const { payments } = await setup();
|
|
32
|
+
|
|
33
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
34
|
+
const session = await payments.createSession(
|
|
35
|
+
intent.id,
|
|
36
|
+
"https://example.com/return",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(session.url).toContain("/payments/mock-checkout/");
|
|
40
|
+
expect(session.intentId).toBe(intent.id);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should capture an authorized intent", async ({ expect }) => {
|
|
44
|
+
const { payments } = await setup();
|
|
45
|
+
|
|
46
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
47
|
+
await payments.createSession(intent.id, "https://example.com", true);
|
|
48
|
+
await payments.handleWebhookEvent(intent.id, "authorized");
|
|
49
|
+
|
|
50
|
+
const captured = await payments.capture(intent.id);
|
|
51
|
+
expect(captured.status).toBe("captured");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should void an authorized intent", async ({ expect }) => {
|
|
55
|
+
const { payments } = await setup();
|
|
56
|
+
|
|
57
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
58
|
+
await payments.createSession(intent.id, "https://example.com", true);
|
|
59
|
+
await payments.handleWebhookEvent(intent.id, "authorized");
|
|
60
|
+
|
|
61
|
+
const voided = await payments.void(intent.id);
|
|
62
|
+
expect(voided.status).toBe("voided");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should refund a captured intent", async ({ expect }) => {
|
|
66
|
+
const { payments } = await setup();
|
|
67
|
+
|
|
68
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
69
|
+
await payments.createSession(intent.id, "https://example.com");
|
|
70
|
+
await payments.handleWebhookEvent(intent.id, "captured");
|
|
71
|
+
|
|
72
|
+
const refund = await payments.refund(intent.id, 500, "Customer request");
|
|
73
|
+
expect(refund.amount).toBe(500);
|
|
74
|
+
expect(refund.status).toBe("completed");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should record a cash payment directly as captured", async ({
|
|
78
|
+
expect,
|
|
79
|
+
}) => {
|
|
80
|
+
const { payments } = await setup();
|
|
81
|
+
|
|
82
|
+
const intent = await payments.recordCashPayment(1500, "eur", {
|
|
83
|
+
orderId: "order-1",
|
|
84
|
+
});
|
|
85
|
+
expect(intent.status).toBe("captured");
|
|
86
|
+
expect(intent.metadata).toEqual({ orderId: "order-1" });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should cancel a created intent", async ({ expect }) => {
|
|
90
|
+
const { payments } = await setup();
|
|
91
|
+
|
|
92
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
93
|
+
const cancelled = await payments.cancel(intent.id);
|
|
94
|
+
expect(cancelled.status).toBe("cancelled");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should reject capture from wrong status", async ({ expect }) => {
|
|
98
|
+
const { payments } = await setup();
|
|
99
|
+
|
|
100
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
101
|
+
await expect(payments.capture(intent.id)).rejects.toThrowError();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should reject refund from wrong status", async ({ expect }) => {
|
|
105
|
+
const { payments } = await setup();
|
|
106
|
+
|
|
107
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
108
|
+
await expect(payments.refund(intent.id, 500)).rejects.toThrowError();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should reject void from wrong status", async ({ expect }) => {
|
|
112
|
+
const { payments } = await setup();
|
|
113
|
+
|
|
114
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
115
|
+
await expect(payments.void(intent.id)).rejects.toThrowError();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should reject cancel from wrong status", async ({ expect }) => {
|
|
119
|
+
const { payments } = await setup();
|
|
120
|
+
|
|
121
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
122
|
+
await payments.createSession(intent.id, "https://example.com");
|
|
123
|
+
await expect(payments.cancel(intent.id)).rejects.toThrowError();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should reject capture amount exceeding authorized amount", async ({
|
|
127
|
+
expect,
|
|
128
|
+
}) => {
|
|
129
|
+
const { payments } = await setup();
|
|
130
|
+
|
|
131
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
132
|
+
await payments.createSession(intent.id, "https://example.com", true);
|
|
133
|
+
await payments.handleWebhookEvent(intent.id, "authorized");
|
|
134
|
+
|
|
135
|
+
await expect(payments.capture(intent.id, 5000)).rejects.toThrowError(
|
|
136
|
+
PaymentError,
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should reject refund exceeding captured amount", async ({ expect }) => {
|
|
141
|
+
const { payments } = await setup();
|
|
142
|
+
|
|
143
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
144
|
+
await payments.createSession(intent.id, "https://example.com");
|
|
145
|
+
await payments.handleWebhookEvent(intent.id, "captured");
|
|
146
|
+
|
|
147
|
+
await expect(payments.refund(intent.id, 5000)).rejects.toThrowError(
|
|
148
|
+
PaymentError,
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should allow multiple partial refunds up to captured amount", async ({
|
|
153
|
+
expect,
|
|
154
|
+
}) => {
|
|
155
|
+
const { payments } = await setup();
|
|
156
|
+
|
|
157
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
158
|
+
await payments.createSession(intent.id, "https://example.com");
|
|
159
|
+
await payments.handleWebhookEvent(intent.id, "captured");
|
|
160
|
+
|
|
161
|
+
await payments.refund(intent.id, 500);
|
|
162
|
+
const after1 = await payments.getIntent(intent.id);
|
|
163
|
+
expect(after1.status).toBe("partially_refunded");
|
|
164
|
+
|
|
165
|
+
await payments.refund(intent.id, 500);
|
|
166
|
+
const after2 = await payments.getIntent(intent.id);
|
|
167
|
+
expect(after2.status).toBe("partially_refunded");
|
|
168
|
+
|
|
169
|
+
await payments.refund(intent.id, 500);
|
|
170
|
+
const after3 = await payments.getIntent(intent.id);
|
|
171
|
+
expect(after3.status).toBe("refunded");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should reject refund that would exceed remaining amount", async ({
|
|
175
|
+
expect,
|
|
176
|
+
}) => {
|
|
177
|
+
const { payments } = await setup();
|
|
178
|
+
|
|
179
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
180
|
+
await payments.createSession(intent.id, "https://example.com");
|
|
181
|
+
await payments.handleWebhookEvent(intent.id, "captured");
|
|
182
|
+
|
|
183
|
+
await payments.refund(intent.id, 1000);
|
|
184
|
+
|
|
185
|
+
await expect(payments.refund(intent.id, 1000)).rejects.toThrowError(
|
|
186
|
+
PaymentError,
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should ignore webhook that would downgrade status", async ({
|
|
191
|
+
expect,
|
|
192
|
+
}) => {
|
|
193
|
+
const { payments } = await setup();
|
|
194
|
+
|
|
195
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
196
|
+
await payments.createSession(intent.id, "https://example.com");
|
|
197
|
+
await payments.handleWebhookEvent(intent.id, "captured");
|
|
198
|
+
|
|
199
|
+
await payments.handleWebhookEvent(intent.id, "authorized");
|
|
200
|
+
|
|
201
|
+
const current = await payments.getIntent(intent.id);
|
|
202
|
+
expect(current.status).toBe("captured");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should ignore duplicate webhook for same status", async ({ expect }) => {
|
|
206
|
+
const { payments } = await setup();
|
|
207
|
+
|
|
208
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
209
|
+
await payments.createSession(intent.id, "https://example.com", true);
|
|
210
|
+
await payments.handleWebhookEvent(intent.id, "authorized");
|
|
211
|
+
|
|
212
|
+
await payments.handleWebhookEvent(intent.id, "authorized");
|
|
213
|
+
|
|
214
|
+
const current = await payments.getIntent(intent.id);
|
|
215
|
+
expect(current.status).toBe("authorized");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should normalize currency to lowercase", async ({ expect }) => {
|
|
219
|
+
const { payments } = await setup();
|
|
220
|
+
|
|
221
|
+
const intent = await payments.createIntent(1500, "EUR");
|
|
222
|
+
expect(intent.currency).toBe("eur");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should emit payments:cancelled event on cancel", async ({ expect }) => {
|
|
226
|
+
const { alepha, payments } = await setup();
|
|
227
|
+
|
|
228
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
229
|
+
|
|
230
|
+
let emitted: unknown = null;
|
|
231
|
+
alepha.events.on("payments:cancelled", (payload: unknown) => {
|
|
232
|
+
emitted = payload;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
await payments.cancel(intent.id);
|
|
236
|
+
|
|
237
|
+
expect(emitted).toEqual({
|
|
238
|
+
intentId: intent.id,
|
|
239
|
+
amount: 1500,
|
|
240
|
+
currency: "eur",
|
|
241
|
+
metadata: intent.metadata,
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should reject checkout for intent belonging to another user", async ({
|
|
246
|
+
expect,
|
|
247
|
+
}) => {
|
|
248
|
+
const { payments } = await setup();
|
|
249
|
+
|
|
250
|
+
const userA = randomUUID();
|
|
251
|
+
const userB = randomUUID();
|
|
252
|
+
const intent = await payments.createIntent(1500, "eur", undefined, {
|
|
253
|
+
userId: userA,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
await expect(
|
|
257
|
+
payments.createSession(intent.id, "https://example.com", false, userB),
|
|
258
|
+
).rejects.toThrowError(PaymentError);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should set userId on intent during checkout if not already set", async ({
|
|
262
|
+
expect,
|
|
263
|
+
}) => {
|
|
264
|
+
const { payments } = await setup();
|
|
265
|
+
|
|
266
|
+
const userX = randomUUID();
|
|
267
|
+
const intent = await payments.createIntent(1500, "eur");
|
|
268
|
+
|
|
269
|
+
await payments.createSession(
|
|
270
|
+
intent.id,
|
|
271
|
+
"https://example.com",
|
|
272
|
+
false,
|
|
273
|
+
userX,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const updated = await payments.getIntent(intent.id);
|
|
277
|
+
expect(updated.userId).toBe(userX);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
refundIntentSchema,
|
|
10
10
|
} from "../schemas/intentSchemas.ts";
|
|
11
11
|
import { refundResourceSchema } from "../schemas/refundSchemas.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { PaymentService } from "../services/PaymentService.ts";
|
|
13
13
|
|
|
14
|
-
export class
|
|
15
|
-
protected readonly url = "/admin/
|
|
16
|
-
protected readonly group = "admin:
|
|
17
|
-
protected readonly
|
|
14
|
+
export class AdminPaymentController {
|
|
15
|
+
protected readonly url = "/admin/payments";
|
|
16
|
+
protected readonly group = "admin:payments";
|
|
17
|
+
protected readonly payments = $inject(PaymentService);
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* List payment intents with pagination and filtering.
|
|
@@ -22,13 +22,13 @@ export class AdminBillingController {
|
|
|
22
22
|
public readonly listIntents = $action({
|
|
23
23
|
path: `${this.url}/intents`,
|
|
24
24
|
group: this.group,
|
|
25
|
-
use: [$secure({ permissions: ["
|
|
25
|
+
use: [$secure({ permissions: ["payments:read"] })],
|
|
26
26
|
description: "List payment intents",
|
|
27
27
|
schema: {
|
|
28
28
|
query: intentQuerySchema,
|
|
29
29
|
response: t.page(intentResourceSchema),
|
|
30
30
|
},
|
|
31
|
-
handler: ({ query }) => this.
|
|
31
|
+
handler: ({ query }) => this.payments.findIntents(query),
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -37,13 +37,13 @@ export class AdminBillingController {
|
|
|
37
37
|
public readonly getIntent = $action({
|
|
38
38
|
path: `${this.url}/intents/:id`,
|
|
39
39
|
group: this.group,
|
|
40
|
-
use: [$secure({ permissions: ["
|
|
40
|
+
use: [$secure({ permissions: ["payments:read"] })],
|
|
41
41
|
description: "Get payment intent details",
|
|
42
42
|
schema: {
|
|
43
43
|
params: t.object({ id: t.uuid() }),
|
|
44
44
|
response: intentResourceSchema,
|
|
45
45
|
},
|
|
46
|
-
handler: ({ params }) => this.
|
|
46
|
+
handler: ({ params }) => this.payments.getIntent(params.id),
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -53,14 +53,15 @@ export class AdminBillingController {
|
|
|
53
53
|
method: "POST",
|
|
54
54
|
path: `${this.url}/intents/:id/capture`,
|
|
55
55
|
group: this.group,
|
|
56
|
-
use: [$secure({ permissions: ["
|
|
56
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
57
57
|
description: "Capture an authorized payment intent",
|
|
58
58
|
schema: {
|
|
59
59
|
params: t.object({ id: t.uuid() }),
|
|
60
60
|
body: captureIntentSchema,
|
|
61
61
|
response: intentResourceSchema,
|
|
62
62
|
},
|
|
63
|
-
handler: ({ params, body }) =>
|
|
63
|
+
handler: ({ params, body }) =>
|
|
64
|
+
this.payments.capture(params.id, body.amount),
|
|
64
65
|
});
|
|
65
66
|
|
|
66
67
|
/**
|
|
@@ -70,13 +71,13 @@ export class AdminBillingController {
|
|
|
70
71
|
method: "POST",
|
|
71
72
|
path: `${this.url}/intents/:id/void`,
|
|
72
73
|
group: this.group,
|
|
73
|
-
use: [$secure({ permissions: ["
|
|
74
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
74
75
|
description: "Void an authorized payment intent",
|
|
75
76
|
schema: {
|
|
76
77
|
params: t.object({ id: t.uuid() }),
|
|
77
78
|
response: intentResourceSchema,
|
|
78
79
|
},
|
|
79
|
-
handler: ({ params }) => this.
|
|
80
|
+
handler: ({ params }) => this.payments.void(params.id),
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
/**
|
|
@@ -86,7 +87,7 @@ export class AdminBillingController {
|
|
|
86
87
|
method: "POST",
|
|
87
88
|
path: `${this.url}/intents/:id/refund`,
|
|
88
89
|
group: this.group,
|
|
89
|
-
use: [$secure({ permissions: ["
|
|
90
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
90
91
|
description: "Issue partial or full refund",
|
|
91
92
|
schema: {
|
|
92
93
|
params: t.object({ id: t.uuid() }),
|
|
@@ -94,7 +95,7 @@ export class AdminBillingController {
|
|
|
94
95
|
response: refundResourceSchema,
|
|
95
96
|
},
|
|
96
97
|
handler: ({ params, body }) =>
|
|
97
|
-
this.
|
|
98
|
+
this.payments.refund(params.id, body.amount, body.reason),
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
/**
|
|
@@ -104,13 +105,13 @@ export class AdminBillingController {
|
|
|
104
105
|
method: "POST",
|
|
105
106
|
path: `${this.url}/intents/:id/cancel`,
|
|
106
107
|
group: this.group,
|
|
107
|
-
use: [$secure({ permissions: ["
|
|
108
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
108
109
|
description: "Cancel a created payment intent",
|
|
109
110
|
schema: {
|
|
110
111
|
params: t.object({ id: t.uuid() }),
|
|
111
112
|
response: intentResourceSchema,
|
|
112
113
|
},
|
|
113
|
-
handler: ({ params }) => this.
|
|
114
|
+
handler: ({ params }) => this.payments.cancel(params.id),
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
/**
|
|
@@ -120,14 +121,18 @@ export class AdminBillingController {
|
|
|
120
121
|
method: "POST",
|
|
121
122
|
path: `${this.url}/cash`,
|
|
122
123
|
group: this.group,
|
|
123
|
-
use: [$secure({ permissions: ["
|
|
124
|
+
use: [$secure({ permissions: ["payments:write"] })],
|
|
124
125
|
description: "Record a cash payment",
|
|
125
126
|
schema: {
|
|
126
127
|
body: recordCashSchema,
|
|
127
128
|
response: intentResourceSchema,
|
|
128
129
|
},
|
|
129
130
|
handler: ({ body }) =>
|
|
130
|
-
this.
|
|
131
|
+
this.payments.recordCashPayment(
|
|
132
|
+
body.amount,
|
|
133
|
+
body.currency,
|
|
134
|
+
body.metadata,
|
|
135
|
+
),
|
|
131
136
|
});
|
|
132
137
|
|
|
133
138
|
/**
|
|
@@ -135,14 +140,14 @@ export class AdminBillingController {
|
|
|
135
140
|
*/
|
|
136
141
|
public readonly webhook = $action({
|
|
137
142
|
method: "POST",
|
|
138
|
-
path: "/
|
|
143
|
+
path: "/payments/webhook",
|
|
139
144
|
group: this.group,
|
|
140
145
|
description: "PSP webhook endpoint",
|
|
141
146
|
schema: {
|
|
142
147
|
response: okSchema,
|
|
143
148
|
},
|
|
144
149
|
handler: async (request) => {
|
|
145
|
-
await this.
|
|
150
|
+
await this.payments.handleWebhook(request.raw.web!.req);
|
|
146
151
|
return { ok: true };
|
|
147
152
|
},
|
|
148
153
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { $inject, t } from "alepha";
|
|
2
2
|
import { $secure } from "alepha/security";
|
|
3
3
|
import { $action, okSchema } from "alepha/server";
|
|
4
|
+
import { PaymentError } from "../errors/PaymentError.ts";
|
|
4
5
|
import {
|
|
5
6
|
checkoutResponseSchema,
|
|
6
7
|
createCheckoutSchema,
|
|
@@ -9,13 +10,13 @@ import {
|
|
|
9
10
|
addPaymentMethodSchema,
|
|
10
11
|
paymentMethodResourceSchema,
|
|
11
12
|
} from "../schemas/paymentMethodSchemas.ts";
|
|
12
|
-
import { BillingService } from "../services/BillingService.ts";
|
|
13
13
|
import { PaymentMethodService } from "../services/PaymentMethodService.ts";
|
|
14
|
+
import { PaymentService } from "../services/PaymentService.ts";
|
|
14
15
|
|
|
15
|
-
export class
|
|
16
|
-
protected readonly url = "/
|
|
17
|
-
protected readonly group = "
|
|
18
|
-
protected readonly
|
|
16
|
+
export class PaymentController {
|
|
17
|
+
protected readonly url = "/payments";
|
|
18
|
+
protected readonly group = "payments";
|
|
19
|
+
protected readonly payments = $inject(PaymentService);
|
|
19
20
|
protected readonly paymentMethods = $inject(PaymentMethodService);
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -45,12 +46,18 @@ export class BillingController {
|
|
|
45
46
|
body: addPaymentMethodSchema,
|
|
46
47
|
response: paymentMethodResourceSchema,
|
|
47
48
|
},
|
|
48
|
-
handler: ({ body, user }) =>
|
|
49
|
-
|
|
49
|
+
handler: ({ body, user }) => {
|
|
50
|
+
if (!user.organization) {
|
|
51
|
+
throw new PaymentError(
|
|
52
|
+
"Organization is required to add a payment method",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return this.paymentMethods.addPaymentMethod(
|
|
50
56
|
user.id,
|
|
51
|
-
user.organization
|
|
57
|
+
user.organization,
|
|
52
58
|
body.token,
|
|
53
|
-
)
|
|
59
|
+
);
|
|
60
|
+
},
|
|
54
61
|
});
|
|
55
62
|
|
|
56
63
|
/**
|
|
@@ -102,7 +109,12 @@ export class BillingController {
|
|
|
102
109
|
body: createCheckoutSchema,
|
|
103
110
|
response: checkoutResponseSchema,
|
|
104
111
|
},
|
|
105
|
-
handler: ({ body }) =>
|
|
106
|
-
this.
|
|
112
|
+
handler: ({ body, user }) =>
|
|
113
|
+
this.payments.createSession(
|
|
114
|
+
body.intentId,
|
|
115
|
+
body.returnUrl,
|
|
116
|
+
body.authorize,
|
|
117
|
+
user.id,
|
|
118
|
+
),
|
|
107
119
|
});
|
|
108
120
|
}
|