@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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/admin/api/payment-methods-client.d.ts +65 -0
  4. package/dist/admin/api/payment-methods-client.d.ts.map +1 -0
  5. package/dist/admin/api/payment-methods-client.js +39 -0
  6. package/dist/admin/api/payment-methods-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +35 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +37 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/PaymentMethodsPage.d.ts +9 -0
  12. package/dist/admin/pages/PaymentMethodsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/PaymentMethodsPage.js +228 -0
  14. package/dist/admin/pages/PaymentMethodsPage.js.map +1 -0
  15. package/dist/admin/renderers/registry.d.ts +15 -0
  16. package/dist/admin/renderers/registry.d.ts.map +1 -0
  17. package/dist/admin/renderers/registry.js +18 -0
  18. package/dist/admin/renderers/registry.js.map +1 -0
  19. package/dist/backend/commands/payment-method.commands.d.ts +86 -0
  20. package/dist/backend/commands/payment-method.commands.d.ts.map +1 -0
  21. package/dist/backend/commands/payment-method.commands.js +168 -0
  22. package/dist/backend/commands/payment-method.commands.js.map +1 -0
  23. package/dist/backend/demo/reset.d.ts +13 -0
  24. package/dist/backend/demo/reset.d.ts.map +1 -0
  25. package/dist/backend/demo/reset.js +13 -0
  26. package/dist/backend/demo/reset.js.map +1 -0
  27. package/dist/backend/demo/rows.d.ts +31 -0
  28. package/dist/backend/demo/rows.d.ts.map +1 -0
  29. package/dist/backend/demo/rows.js +50 -0
  30. package/dist/backend/demo/rows.js.map +1 -0
  31. package/dist/backend/demo/seed.d.ts +17 -0
  32. package/dist/backend/demo/seed.d.ts.map +1 -0
  33. package/dist/backend/demo/seed.js +30 -0
  34. package/dist/backend/demo/seed.js.map +1 -0
  35. package/dist/backend/entities/payment-method.entity.d.ts +28 -0
  36. package/dist/backend/entities/payment-method.entity.d.ts.map +1 -0
  37. package/dist/backend/entities/payment-method.entity.js +93 -0
  38. package/dist/backend/entities/payment-method.entity.js.map +1 -0
  39. package/dist/backend/index.d.ts +103 -0
  40. package/dist/backend/index.d.ts.map +1 -0
  41. package/dist/backend/index.js +151 -0
  42. package/dist/backend/index.js.map +1 -0
  43. package/dist/backend/routes.d.ts +55 -0
  44. package/dist/backend/routes.d.ts.map +1 -0
  45. package/dist/backend/routes.js +226 -0
  46. package/dist/backend/routes.js.map +1 -0
  47. package/dist/backend/services/name-resolver.d.ts +12 -0
  48. package/dist/backend/services/name-resolver.d.ts.map +1 -0
  49. package/dist/backend/services/name-resolver.js +19 -0
  50. package/dist/backend/services/name-resolver.js.map +1 -0
  51. package/dist/backend/services/order-status-registry.port.d.ts +57 -0
  52. package/dist/backend/services/order-status-registry.port.d.ts.map +1 -0
  53. package/dist/backend/services/order-status-registry.port.js +53 -0
  54. package/dist/backend/services/order-status-registry.port.js.map +1 -0
  55. package/dist/backend/services/payment-adapter-registry.d.ts +79 -0
  56. package/dist/backend/services/payment-adapter-registry.d.ts.map +1 -0
  57. package/dist/backend/services/payment-adapter-registry.js +87 -0
  58. package/dist/backend/services/payment-adapter-registry.js.map +1 -0
  59. package/dist/backend/services/payment-method-eligibility.d.ts +24 -0
  60. package/dist/backend/services/payment-method-eligibility.d.ts.map +1 -0
  61. package/dist/backend/services/payment-method-eligibility.js +48 -0
  62. package/dist/backend/services/payment-method-eligibility.js.map +1 -0
  63. package/dist/backend/services/payment-method-read-port.d.ts +25 -0
  64. package/dist/backend/services/payment-method-read-port.d.ts.map +1 -0
  65. package/dist/backend/services/payment-method-read-port.js +57 -0
  66. package/dist/backend/services/payment-method-read-port.js.map +1 -0
  67. package/dist/backend/services/payment-method-reconciler.d.ts +97 -0
  68. package/dist/backend/services/payment-method-reconciler.d.ts.map +1 -0
  69. package/dist/backend/services/payment-method-reconciler.js +164 -0
  70. package/dist/backend/services/payment-method-reconciler.js.map +1 -0
  71. package/dist/backend/services/registry-singleton.d.ts +16 -0
  72. package/dist/backend/services/registry-singleton.d.ts.map +1 -0
  73. package/dist/backend/services/registry-singleton.js +17 -0
  74. package/dist/backend/services/registry-singleton.js.map +1 -0
  75. package/dist/install/index.d.ts +57 -0
  76. package/dist/install/index.d.ts.map +1 -0
  77. package/dist/install/index.js +57 -0
  78. package/dist/install/index.js.map +1 -0
  79. package/dist/manifest.d.ts +169 -0
  80. package/dist/manifest.d.ts.map +1 -0
  81. package/dist/manifest.js +145 -0
  82. package/dist/manifest.js.map +1 -0
  83. package/dist/migrations/20260611T140353_payment_methods_adapter.d.ts +32 -0
  84. package/dist/migrations/20260611T140353_payment_methods_adapter.d.ts.map +1 -0
  85. package/dist/migrations/20260611T140353_payment_methods_adapter.js +76 -0
  86. package/dist/migrations/20260611T140353_payment_methods_adapter.js.map +1 -0
  87. package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.d.ts +46 -0
  88. package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.d.ts.map +1 -0
  89. package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.js +54 -0
  90. package/dist/migrations/20260821T084920_payment_methods_failure_status_on_hold.js.map +1 -0
  91. package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.d.ts +27 -0
  92. package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.d.ts.map +1 -0
  93. package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.js +44 -0
  94. package/dist/migrations/20260912T094631_payment_methods_sales_channel_payment_methods.js.map +1 -0
  95. package/dist/migrations/index.d.ts +32 -0
  96. package/dist/migrations/index.d.ts.map +1 -0
  97. package/dist/migrations/index.js +36 -0
  98. package/dist/migrations/index.js.map +1 -0
  99. package/dist/ports/index.d.ts +157 -0
  100. package/dist/ports/index.d.ts.map +1 -0
  101. package/dist/ports/index.js +2 -0
  102. package/dist/ports/index.js.map +1 -0
  103. package/docs/payment_methods.md +146 -0
  104. package/i18n/en.json +5 -0
  105. package/i18n/pl.json +5 -0
  106. package/package.json +105 -0
  107. 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
@@ -0,0 +1,5 @@
1
+ {
2
+ "actions.openPaymentMethods.label": "Payment methods",
3
+ "actions.openPaymentMethods.description": "Define payment methods and choose which ones are offered to buyers",
4
+ "nav.paymentMethods.label": "Payment methods"
5
+ }
package/i18n/pl.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "actions.openPaymentMethods.label": "Metody płatności",
3
+ "actions.openPaymentMethods.description": "Zdefiniuj metody płatności i wybierz, które są oferowane kupującym",
4
+ "nav.paymentMethods.label": "Metody płatności"
5
+ }
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";