@endora-commerce/mod-payment-methods 0.100.0
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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/admin/api/payment-methods-client.d.ts +65 -0
- package/dist/admin/api/payment-methods-client.d.ts.map +1 -0
- package/dist/admin/api/payment-methods-client.js +39 -0
- package/dist/admin/api/payment-methods-client.js.map +1 -0
- package/dist/admin/index.d.ts +35 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +37 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/PaymentMethodsPage.d.ts +9 -0
- package/dist/admin/pages/PaymentMethodsPage.d.ts.map +1 -0
- package/dist/admin/pages/PaymentMethodsPage.js +228 -0
- package/dist/admin/pages/PaymentMethodsPage.js.map +1 -0
- package/dist/admin/renderers/registry.d.ts +15 -0
- package/dist/admin/renderers/registry.d.ts.map +1 -0
- package/dist/admin/renderers/registry.js +18 -0
- package/dist/admin/renderers/registry.js.map +1 -0
- package/dist/backend/commands/payment-method.commands.d.ts +86 -0
- package/dist/backend/commands/payment-method.commands.d.ts.map +1 -0
- package/dist/backend/commands/payment-method.commands.js +168 -0
- package/dist/backend/commands/payment-method.commands.js.map +1 -0
- package/dist/backend/demo/reset.d.ts +13 -0
- package/dist/backend/demo/reset.d.ts.map +1 -0
- package/dist/backend/demo/reset.js +13 -0
- package/dist/backend/demo/reset.js.map +1 -0
- package/dist/backend/demo/rows.d.ts +31 -0
- package/dist/backend/demo/rows.d.ts.map +1 -0
- package/dist/backend/demo/rows.js +50 -0
- package/dist/backend/demo/rows.js.map +1 -0
- package/dist/backend/demo/seed.d.ts +17 -0
- package/dist/backend/demo/seed.d.ts.map +1 -0
- package/dist/backend/demo/seed.js +30 -0
- package/dist/backend/demo/seed.js.map +1 -0
- package/dist/backend/entities/payment-method.entity.d.ts +28 -0
- package/dist/backend/entities/payment-method.entity.d.ts.map +1 -0
- package/dist/backend/entities/payment-method.entity.js +93 -0
- package/dist/backend/entities/payment-method.entity.js.map +1 -0
- package/dist/backend/index.d.ts +103 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +151 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.d.ts +55 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +226 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/name-resolver.d.ts +12 -0
- package/dist/backend/services/name-resolver.d.ts.map +1 -0
- package/dist/backend/services/name-resolver.js +19 -0
- package/dist/backend/services/name-resolver.js.map +1 -0
- package/dist/backend/services/order-status-registry.port.d.ts +57 -0
- package/dist/backend/services/order-status-registry.port.d.ts.map +1 -0
- package/dist/backend/services/order-status-registry.port.js +53 -0
- package/dist/backend/services/order-status-registry.port.js.map +1 -0
- package/dist/backend/services/payment-adapter-registry.d.ts +79 -0
- package/dist/backend/services/payment-adapter-registry.d.ts.map +1 -0
- package/dist/backend/services/payment-adapter-registry.js +87 -0
- package/dist/backend/services/payment-adapter-registry.js.map +1 -0
- package/dist/backend/services/payment-method-eligibility.d.ts +24 -0
- package/dist/backend/services/payment-method-eligibility.d.ts.map +1 -0
- package/dist/backend/services/payment-method-eligibility.js +48 -0
- package/dist/backend/services/payment-method-eligibility.js.map +1 -0
- package/dist/backend/services/payment-method-read-port.d.ts +25 -0
- package/dist/backend/services/payment-method-read-port.d.ts.map +1 -0
- package/dist/backend/services/payment-method-read-port.js +57 -0
- package/dist/backend/services/payment-method-read-port.js.map +1 -0
- package/dist/backend/services/payment-method-reconciler.d.ts +97 -0
- package/dist/backend/services/payment-method-reconciler.d.ts.map +1 -0
- package/dist/backend/services/payment-method-reconciler.js +164 -0
- package/dist/backend/services/payment-method-reconciler.js.map +1 -0
- package/dist/backend/services/registry-singleton.d.ts +16 -0
- package/dist/backend/services/registry-singleton.d.ts.map +1 -0
- package/dist/backend/services/registry-singleton.js +17 -0
- package/dist/backend/services/registry-singleton.js.map +1 -0
- package/dist/install/index.d.ts +57 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +57 -0
- package/dist/install/index.js.map +1 -0
- package/dist/manifest.d.ts +169 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +145 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260611T140353_payment_methods_adapter.d.ts +32 -0
- package/dist/migrations/20260611T140353_payment_methods_adapter.d.ts.map +1 -0
- package/dist/migrations/20260611T140353_payment_methods_adapter.js +76 -0
- package/dist/migrations/20260611T140353_payment_methods_adapter.js.map +1 -0
- package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.d.ts +46 -0
- package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.d.ts.map +1 -0
- package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.js +54 -0
- package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.js.map +1 -0
- package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.d.ts +27 -0
- package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.d.ts.map +1 -0
- package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.js +44 -0
- package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.js.map +1 -0
- package/dist/migrations/index.d.ts +32 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +36 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +157 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/payment_methods.md +146 -0
- package/i18n/en.json +5 -0
- package/i18n/pl.json +5 -0
- package/package.json +105 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: payment_methods
|
|
3
|
+
description: Configured payment methods
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `payment_methods`
|
|
7
|
+
|
|
8
|
+
CRUD over the configured payment methods that Customers can pick at
|
|
9
|
+
checkout. Each `PaymentMethod` row is backed by a registered **adapter**
|
|
10
|
+
(see *Adapter framework* below) and a per-Sales-Channel visibility list.
|
|
11
|
+
|
|
12
|
+
## Public surface
|
|
13
|
+
|
|
14
|
+
Admin routes are gated by `payment_methods:read` (reads) and
|
|
15
|
+
`payment_methods:write` (mutations) — the module's own codes since 2026-08-28.
|
|
16
|
+
They were `catalog:read` / `catalog:write` until then, which meant whoever could
|
|
17
|
+
edit a product could also decide how the shop takes money. A role that was
|
|
18
|
+
relying on the catalogue codes for this screen has to be granted the new ones on
|
|
19
|
+
`/admin-roles`; nothing grants them automatically, deliberately.
|
|
20
|
+
|
|
21
|
+
`GET /api/v1/admin/order-statuses` is the one exception, and it is an any-of
|
|
22
|
+
rather than a widening: the route is registered here but read by two editors —
|
|
23
|
+
this module's screen and the `delivery_methods` one — so it accepts either
|
|
24
|
+
module's read code. The second member was `catalog:read` while
|
|
25
|
+
`delivery_methods` still borrowed the catalogue's authority; it became
|
|
26
|
+
`delivery_methods:read` when that module minted its own pair, so no catalogue
|
|
27
|
+
holder reaches the shared list any more.
|
|
28
|
+
|
|
29
|
+
| Verb + Path | Audience | Gate | Purpose |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| `GET /api/v1/payment-methods` | anon | — | Eligible methods for the storefront checkout (active ∩ org allow-list ∩ registered adapter ∩ `validateUseOnStorefront`) |
|
|
32
|
+
| `GET /api/v1/admin/payment-methods` | admin | `payment_methods:read` | Full config (active + inactive) incl. `adapter`, `additionalPrice`, `statusOn*`, sales channels |
|
|
33
|
+
| `GET /api/v1/admin/payment-methods/adapters` | admin | `payment_methods:read` | Registered adapter keys, for the admin adapter picker |
|
|
34
|
+
| `GET /api/v1/admin/order-statuses` | admin | `payment_methods:read` **or** `delivery_methods:read` | Order-status options for the `statusOn*` selectors, here and on the delivery-method screen |
|
|
35
|
+
| `PUT /api/v1/admin/payment-methods/:code` | admin | `payment_methods:write` | Upsert by code; `adapter` defaults to `kind`, `statusOn*` validated against the order-status registry |
|
|
36
|
+
| `PATCH /api/v1/admin/payment-methods/:id/status` | admin | `payment_methods:write` | Availability alone — the one write the four gateway screens link to |
|
|
37
|
+
| `DELETE /api/v1/admin/payment-methods/:id` | admin | `payment_methods:write` | Delete — blocked (409) when a `Payment` references the method; set it `inactive` instead |
|
|
38
|
+
|
|
39
|
+
## Entities
|
|
40
|
+
|
|
41
|
+
`PaymentMethod` — `code`, `kind`, **`adapter`** (registry key), default +
|
|
42
|
+
per-language `name`, `status`, **`additionalPrice`** (flat surcharge in the
|
|
43
|
+
order currency), and the three Order-status references
|
|
44
|
+
**`statusOnPending` / `statusOnSuccess` / `statusOnFailure`**. Sales-channel
|
|
45
|
+
scoping via `sales_channel_payment_methods`; per-Organization availability via
|
|
46
|
+
`organization_payment_methods`.
|
|
47
|
+
|
|
48
|
+
## Adapter framework
|
|
49
|
+
|
|
50
|
+
A payment method's behaviour is supplied by a **`PaymentAdapter`** registered
|
|
51
|
+
in the `PaymentAdapterRegistry`. The platform recognises a module as a
|
|
52
|
+
payment-method provider **iff it registers an adapter** — there is no other
|
|
53
|
+
condition. The bundled `bank_transfer`, `pickup`, `credit_limit`, and
|
|
54
|
+
`gateway` adapters are the reference implementations
|
|
55
|
+
(`packages/modules/payments/src/backend/adapters/built-in-adapters.ts`).
|
|
56
|
+
|
|
57
|
+
### The contract
|
|
58
|
+
|
|
59
|
+
`PaymentAdapter` (from `@endora-commerce/contracts`) carries:
|
|
60
|
+
|
|
61
|
+
- `adapterKey` — stable id; matches `payment_methods.adapter`.
|
|
62
|
+
- `type` — one of `bank_transfer | pickup | credit_limit | gateway`.
|
|
63
|
+
- `validateUseOnStorefront` / `validateUseOnAdmin` / `validateUseInApi` —
|
|
64
|
+
extra eligibility conditions per surface; return a constant `true` when
|
|
65
|
+
there are none.
|
|
66
|
+
- `onStorefrontOrderCreated` — runs on **`storefront_order_created`**; returns
|
|
67
|
+
a `StartPaymentResult` (`awaiting_transfer | redirect | none`).
|
|
68
|
+
- `onReceivePayment` — interprets a **`receive_payment`** callback into a
|
|
69
|
+
success/failure `PaymentOutcome`.
|
|
70
|
+
- `renderers?` — optional `{ storefront?, admin?, email? }` renderer keys;
|
|
71
|
+
absent ⇒ the platform default renderer is used.
|
|
72
|
+
|
|
73
|
+
### Lifecycle
|
|
74
|
+
|
|
75
|
+
1. **`storefront_order_created`** — `order-service.placeOrder` creates the
|
|
76
|
+
Order at the method's `statusOnPending`, adds `additionalPrice` to the
|
|
77
|
+
total, opens a pending `Payment`, and invokes
|
|
78
|
+
`adapter.onStorefrontOrderCreated`.
|
|
79
|
+
2. **`receive_payment`** — `POST /api/v1/payments/receive` resolves the
|
|
80
|
+
`Payment` (by `paymentId` or `orderId + externalReference`), applies the
|
|
81
|
+
outcome, and maps the Order status through `statusOnSuccess` /
|
|
82
|
+
`statusOnFailure`. Idempotent: a re-success is a no-op; a failure after a
|
|
83
|
+
terminal `paid` is rejected. A failed payment is retried via
|
|
84
|
+
`POST /api/v1/admin/orders/:id/payments/retry`, opening a new `Payment`
|
|
85
|
+
(`attemptNo + 1`) while keeping the prior attempts.
|
|
86
|
+
|
|
87
|
+
`Payment.status` (the payment-process status: `awaiting_payment → paid |
|
|
88
|
+
failed`, plus `deferred` for credit limit) is **separate** from the
|
|
89
|
+
order-status mapping. `statusOn*` reference *Order Statuses* resolved through
|
|
90
|
+
the `OrderStatusRegistry` port — enum-backed today, swappable for the Orders
|
|
91
|
+
module's configurable registry later with no change here.
|
|
92
|
+
|
|
93
|
+
### Build your own payment-method module
|
|
94
|
+
|
|
95
|
+
1. Create a module package (`packages/modules/<your_module>/`) with a
|
|
96
|
+
`src/manifest.ts` declaring `dependencies: ['payment_methods']`, then run
|
|
97
|
+
`pnpm --filter backend run composer:generate` so the generated manifest
|
|
98
|
+
registry picks it up.
|
|
99
|
+
2. Implement `PaymentAdapter`: set `adapterKey`, `type`, the three
|
|
100
|
+
`validateUse*` (return `true` if unconstrained), `onStorefrontOrderCreated`
|
|
101
|
+
(return a `redirect` / `awaiting_transfer` / `none`), and
|
|
102
|
+
`onReceivePayment` (map your PSP callback to `success` / `failure`).
|
|
103
|
+
3. Contribute the adapter from the module's **boot hook**, naming the module as
|
|
104
|
+
its owner:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { paymentAdapterRegistry } from '.../payment_methods/services/registry-singleton.js';
|
|
108
|
+
|
|
109
|
+
ctx.onBoot(() => {
|
|
110
|
+
paymentAdapterRegistry.register(myAdapter, 'my_module');
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The owner id is not decoration: the registry skips an adapter whose module is
|
|
115
|
+
not effectively present, so a gateway an operator switches off stops being
|
|
116
|
+
offered at checkout without anything unregistering it. Boot hooks
|
|
117
|
+
run whatever the module's state is — the *enumeration* answers presence, not
|
|
118
|
+
the registration.
|
|
119
|
+
4. Ship the method rows as a **migration** owned by your module. The codes,
|
|
120
|
+
kinds and default names are compile-time constants, so they are static
|
|
121
|
+
reference data, not a per-boot reconcile: `insert … on conflict (code) do
|
|
122
|
+
nothing`, seeded `inactive` so an operator opts in. The four bundled gateways
|
|
123
|
+
do exactly this (`stripe/migrations/…_stripe_seed_payment_methods.ts`).
|
|
124
|
+
`PaymentMethodReconciler.ensureMethodForAdapter` remains available from an
|
|
125
|
+
`installHook` for a module that must create a row from code; it is idempotent,
|
|
126
|
+
never clobbers admin edits, and never touches sales-channel membership.
|
|
127
|
+
5. (Optional) register storefront / admin / email renderers under the keys the
|
|
128
|
+
adapter declares; otherwise the defaults render it.
|
|
129
|
+
6. Enable the module → a configurable Payment Method appears at
|
|
130
|
+
`/payment-methods`. No core change required.
|
|
131
|
+
|
|
132
|
+
The `paymentAdapterRegistry` is a **process-wide singleton**
|
|
133
|
+
(`registry-singleton.ts`): one table of adapters per process, however many times
|
|
134
|
+
the platform is composed, wired into the live eligibility, admin and
|
|
135
|
+
order-placement paths. Every entry records the module that contributed it, and
|
|
136
|
+
every buyer-facing read (`get`, `resolve`, `list`) skips an entry whose owner is
|
|
137
|
+
absent. The admin-facing reads (`entry`, `ownerOf`, `isRegistered`, `listAll`)
|
|
138
|
+
deliberately do not: switching a module off is not uninstalling it, so the
|
|
139
|
+
`/payment-methods` screen keeps the row and shows why it is unavailable.
|
|
140
|
+
|
|
141
|
+
## Per-Organization availability
|
|
142
|
+
|
|
143
|
+
`organization_payment_methods` is an **allow-list**: empty ⇒ all
|
|
144
|
+
active methods are offered; non-empty ⇒ only the listed methods. Use it to
|
|
145
|
+
filter the methods a given Organization may use. Managed through the
|
|
146
|
+
organizations restriction service / admin restrictions UI.
|
package/i18n/en.json
ADDED
package/i18n/pl.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endora-commerce/mod-payment-methods",
|
|
3
|
+
"version": "0.100.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Payment method definitions and per-channel availability.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"endora": {
|
|
9
|
+
"type": "module",
|
|
10
|
+
"id": "payment_methods"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/endora-commerce/endora-commerce.git",
|
|
15
|
+
"directory": "packages/modules/payment_methods"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/manifest.d.ts",
|
|
23
|
+
"default": "./dist/manifest.js"
|
|
24
|
+
},
|
|
25
|
+
"./backend": {
|
|
26
|
+
"types": "./dist/backend/index.d.ts",
|
|
27
|
+
"default": "./dist/backend/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./migrations": {
|
|
30
|
+
"types": "./dist/migrations/index.d.ts",
|
|
31
|
+
"default": "./dist/migrations/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./ports": {
|
|
34
|
+
"types": "./dist/ports/index.d.ts",
|
|
35
|
+
"default": "./dist/ports/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./install": {
|
|
38
|
+
"types": "./dist/install/index.d.ts",
|
|
39
|
+
"default": "./dist/install/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./admin": {
|
|
42
|
+
"types": "./dist/admin/index.d.ts",
|
|
43
|
+
"default": "./dist/admin/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./tailwind.css": "./tailwind.css",
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"i18n",
|
|
51
|
+
"docs",
|
|
52
|
+
"tailwind.css"
|
|
53
|
+
],
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22.18.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@mikro-orm/core": "^6",
|
|
59
|
+
"@mikro-orm/migrations": "^6",
|
|
60
|
+
"@mikro-orm/postgresql": "^6",
|
|
61
|
+
"fastify": "^5",
|
|
62
|
+
"lucide-react": "^1",
|
|
63
|
+
"react": "^19",
|
|
64
|
+
"react-router-dom": "^7",
|
|
65
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
66
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
67
|
+
"@endora-commerce/platform": "0.100.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"@endora-commerce/admin-kit": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"lucide-react": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"react": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"react-router-dom": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@mikro-orm/core": "^6.6.13",
|
|
85
|
+
"@mikro-orm/migrations": "^6.6.13",
|
|
86
|
+
"@mikro-orm/postgresql": "^6.6.13",
|
|
87
|
+
"@types/node": "^22.9.0",
|
|
88
|
+
"@types/react": "^19.2.14",
|
|
89
|
+
"fastify": "^5.12.5",
|
|
90
|
+
"lucide-react": "^1.11.0",
|
|
91
|
+
"react": "^19.2.5",
|
|
92
|
+
"react-router-dom": "^7.18.2",
|
|
93
|
+
"typescript": "^5.9.3",
|
|
94
|
+
"vitest": "^4.1.11",
|
|
95
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
96
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
97
|
+
"@endora-commerce/platform": "0.100.0"
|
|
98
|
+
},
|
|
99
|
+
"scripts": {
|
|
100
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
|
|
101
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
|
|
102
|
+
"lint": "eslint src",
|
|
103
|
+
"test": "vitest run"
|
|
104
|
+
}
|
|
105
|
+
}
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @endora-commerce/mod-payment-methods — AUTO-GENERATED by `pnpm --filter backend run manifests:generate`.
|
|
2
|
+
*
|
|
3
|
+
* The `@source` directives this package asks its host to scan
|
|
4
|
+
* (`specs/110-instance-repository/contracts/admin-stylesheet-composition.md` R1).
|
|
5
|
+
* They resolve relative to **this file**, so they hold wherever the package is
|
|
6
|
+
* installed — a workspace link here, `node_modules` in a client's instance.
|
|
7
|
+
*
|
|
8
|
+
* The `dist` line is what a published tarball ships and is what an instance
|
|
9
|
+
* scans; the `src` line is inert there and is what keeps `pnpm --filter admin
|
|
10
|
+
* run dev` reading source in this repository. Do not edit: run
|
|
11
|
+
* `pnpm --filter backend run manifests:generate`.
|
|
12
|
+
*/
|
|
13
|
+
@source "./dist/admin";
|
|
14
|
+
@source "./src/admin";
|