@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,57 @@
1
+ import { type OrderStatusOption, type OrderStatusRegistry } from '@endora-commerce/contracts';
2
+ /**
3
+ * OrderStatusRegistry port (feature 034 — research.md R2).
4
+ *
5
+ * `statusOnPending/Success/Failure` on a payment method reference *Order
6
+ * Statuses*. Those will eventually be an admin-configurable registry owned by
7
+ * the Orders module; until then this port is backed by the current hard-coded
8
+ * order `status` enum. Consumers (admin upsert validation, order-service,
9
+ * receive-payment handler) depend only on this interface, so the future
10
+ * configurable registry drops in with no change to `payment_methods`.
11
+ *
12
+ * The port intentionally does NOT touch the Order entity — applying a status
13
+ * to an order is done by the caller, which already owns the Order. This keeps
14
+ * the module isolated (constitution Principle I).
15
+ *
16
+ * The interface moved to `@endora-commerce/contracts` in feature 075's Phase P — `orders`
17
+ * and `payments` both read it. Re-exported here for the length of Phase P,
18
+ * which cuts no consumer; the implementation below now `implements` the
19
+ * published type, which is what keeps the two from drifting.
20
+ */
21
+ export type { OrderStatusRegistry };
22
+ export declare class OrderStatusRegistryError extends Error {
23
+ readonly code: string;
24
+ constructor(code: string);
25
+ }
26
+ /**
27
+ * Default enum-backed implementation. Seed set = the order `status` enum.
28
+ *
29
+ * **The absent-owner policy this registry states (issue #129): honour — and the
30
+ * reason is that there is nothing to skip.** It is named a registry, and the
31
+ * ledger classifies it as a contribution seam because `payments` reads it across
32
+ * a module boundary, but no module contributes to it: the option set is
33
+ * `orderStatusSchema`, fixed at compile time, so it holds no per-contributor
34
+ * state that an operator's flip could invalidate and no entry that could outlive
35
+ * the module that pushed it.
36
+ *
37
+ * Filtering would therefore not withdraw a stale answer, it would withdraw the
38
+ * only answer — and the reads are guards rather than surfaces. `payments` asks
39
+ * `has` before moving an order into the status a settled payment names, so a
40
+ * skip would silently leave a paid order in its old status; a throw would make a
41
+ * PSP webhook retry forever. Every status here is one live orders are already
42
+ * in, and a status an order is in has to stay nameable while the module holding
43
+ * this table is off. The buyer-facing consequence of that module being off is
44
+ * carried where it belongs: `payment_methods` closes its own catalogue at its
45
+ * own seam, and `orders` declares the sentence an operator is shown.
46
+ *
47
+ * The policy is structural rather than promised: the class takes no presence
48
+ * input, so no read of it can be made to drop a status without changing the
49
+ * policy first.
50
+ */
51
+ export declare class EnumOrderStatusRegistry implements OrderStatusRegistry {
52
+ private readonly codes;
53
+ list(): OrderStatusOption[];
54
+ has(code: string): boolean;
55
+ assertValid(code: string): void;
56
+ }
57
+ //# sourceMappingURL=order-status-registry.port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-status-registry.port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/order-status-registry.port.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;;GAkBG;AACH,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,qBAAa,wBAAyB,SAAQ,KAAK;aACrB,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIzC;AAQD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgD;IAEtE,IAAI,IAAI,iBAAiB,EAAE;IAI3B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAKhC"}
@@ -0,0 +1,53 @@
1
+ import { orderStatusSchema, } from '@endora-commerce/contracts';
2
+ export class OrderStatusRegistryError extends Error {
3
+ code;
4
+ constructor(code) {
5
+ super(`Unknown order status reference: "${code}".`);
6
+ this.code = code;
7
+ this.name = 'OrderStatusRegistryError';
8
+ }
9
+ }
10
+ const humanize = (code) => code
11
+ .split('_')
12
+ .map((part) => (part.length > 0 ? part[0].toUpperCase() + part.slice(1) : part))
13
+ .join(' ');
14
+ /**
15
+ * Default enum-backed implementation. Seed set = the order `status` enum.
16
+ *
17
+ * **The absent-owner policy this registry states (issue #129): honour — and the
18
+ * reason is that there is nothing to skip.** It is named a registry, and the
19
+ * ledger classifies it as a contribution seam because `payments` reads it across
20
+ * a module boundary, but no module contributes to it: the option set is
21
+ * `orderStatusSchema`, fixed at compile time, so it holds no per-contributor
22
+ * state that an operator's flip could invalidate and no entry that could outlive
23
+ * the module that pushed it.
24
+ *
25
+ * Filtering would therefore not withdraw a stale answer, it would withdraw the
26
+ * only answer — and the reads are guards rather than surfaces. `payments` asks
27
+ * `has` before moving an order into the status a settled payment names, so a
28
+ * skip would silently leave a paid order in its old status; a throw would make a
29
+ * PSP webhook retry forever. Every status here is one live orders are already
30
+ * in, and a status an order is in has to stay nameable while the module holding
31
+ * this table is off. The buyer-facing consequence of that module being off is
32
+ * carried where it belongs: `payment_methods` closes its own catalogue at its
33
+ * own seam, and `orders` declares the sentence an operator is shown.
34
+ *
35
+ * The policy is structural rather than promised: the class takes no presence
36
+ * input, so no read of it can be made to drop a status without changing the
37
+ * policy first.
38
+ */
39
+ export class EnumOrderStatusRegistry {
40
+ codes = orderStatusSchema.options;
41
+ list() {
42
+ return this.codes.map((code) => ({ code, label: humanize(code) }));
43
+ }
44
+ has(code) {
45
+ return this.codes.includes(code);
46
+ }
47
+ assertValid(code) {
48
+ if (!this.has(code)) {
49
+ throw new OrderStatusRegistryError(code);
50
+ }
51
+ }
52
+ }
53
+ //# sourceMappingURL=order-status-registry.port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-status-registry.port.js","sourceRoot":"","sources":["../../../src/backend/services/order-status-registry.port.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAGlB,MAAM,4BAA4B,CAAC;AAuBpC,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACrB;IAA5B,YAA4B,IAAY;QACtC,KAAK,CAAC,oCAAoC,IAAI,IAAI,CAAC,CAAC;QAD1B,SAAI,GAAJ,IAAI,CAAQ;QAEtC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE,CACxC,IAAI;KACD,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;KAChF,IAAI,CAAC,GAAG,CAAC,CAAC;AAEf;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,OAAO,uBAAuB;IACjB,KAAK,GAAsB,iBAAiB,CAAC,OAAO,CAAC;IAEtE,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,79 @@
1
+ import type { PaymentAdapter, PaymentAdapterRegistryPort } from '@endora-commerce/contracts';
2
+ /**
3
+ * PaymentAdapterRegistry (feature 034) — in-memory map of adapter key →
4
+ * implementation. A module is recognised as a payment-method adapter iff it
5
+ * registers a `PaymentAdapter` here (FR-001). `payments` contributes the four
6
+ * built-in adapters from its boot hook; each gateway module contributes its own.
7
+ *
8
+ * **Every entry names the module that contributed it (issue #96).** This is a
9
+ * contribution registry in D-39's sense: the push is ungated — a boot hook runs
10
+ * whatever the module's effective state is, and gating the push would make a
11
+ * deactivation survive as a permanently missing entry — so the presence
12
+ * question is answered *here*, at enumeration, keyed on the owner recorded with
13
+ * the entry.
14
+ *
15
+ * The policy this registry states, per D-39's requirement that each host state
16
+ * one: an adapter whose owner is not effectively present is **skipped**. A
17
+ * payment method is a surface-like contribution — the product ruling of
18
+ * 2026-08-15 is that a buyer never sees a method that cannot take their money —
19
+ * so `get`, `resolve` and `list` answer as if the adapter were not registered.
20
+ * `entry`, `ownerOf`, `isRegistered` and `listAll` deliberately do not filter:
21
+ * they are what the admin surface reads to keep showing the method *and* the
22
+ * reason it is unavailable, and switching a module off is not uninstalling it.
23
+ *
24
+ * Collision policy mirrors the CMS PageBuilderRegistry: last-writer-wins with
25
+ * a warning, so a re-registration during a hot reload or re-enable does not
26
+ * throw. Re-registration by the *same* owner is a re-composition rather than a
27
+ * collision (the test suite performs several hundred against this one process
28
+ * singleton) and is silent.
29
+ */
30
+ export interface RegistryLogger {
31
+ warn(message: string): void;
32
+ }
33
+ /** One contributed adapter, with the module that contributed it. */
34
+ export interface PaymentAdapterEntry {
35
+ readonly adapter: PaymentAdapter;
36
+ readonly module: string;
37
+ }
38
+ /**
39
+ * `implements` the shape feature 075's Phase P published, which is what stops
40
+ * the registry and its contract drifting: the four gateway modules will read
41
+ * the published type, and `tsc` refuses the day a method here stops matching.
42
+ */
43
+ export declare class PaymentAdapterRegistry implements PaymentAdapterRegistryPort {
44
+ private readonly log;
45
+ private readonly isModulePresent;
46
+ private readonly entries;
47
+ /**
48
+ * @param log collision warnings.
49
+ * @param isModulePresent the effective-state probe. Defaults to
50
+ * always-present so a registry built for a unit test or a bare harness
51
+ * behaves exactly as it did before this filter existed; the process
52
+ * singleton wires it to the kernel's effective state.
53
+ */
54
+ constructor(log?: RegistryLogger, isModulePresent?: (moduleId: string) => boolean);
55
+ register(adapter: PaymentAdapter, moduleId: string): void;
56
+ unregister(adapterKey: string): void;
57
+ /** Whether any module has contributed this key — presence-blind. */
58
+ isRegistered(adapterKey: string): boolean;
59
+ /** The contributed entry, presence-blind. Admin and diagnostics read this. */
60
+ entry(adapterKey: string): PaymentAdapterEntry | undefined;
61
+ /** The module that contributed `adapterKey`, or `null` when nobody did. */
62
+ ownerOf(adapterKey: string): string | null;
63
+ /** Registered AND its owner effectively present. */
64
+ isAvailable(adapterKey: string): boolean;
65
+ /** The adapter, or `undefined` when unregistered or its owner is absent. */
66
+ get(adapterKey: string): PaymentAdapter | undefined;
67
+ /**
68
+ * The adapter, or a throw. An unregistered key is a programming error; a
69
+ * registered one whose owner is absent is the ordinary 503 envelope, so a
70
+ * caller reached through a service boundary learns the capability is off
71
+ * rather than that the platform is broken.
72
+ */
73
+ resolve(adapterKey: string): PaymentAdapter;
74
+ /** Adapter keys whose owner is present (stable insertion order). */
75
+ list(): string[];
76
+ /** Every registered adapter key, presence-blind. */
77
+ listAll(): string[];
78
+ }
79
+ //# sourceMappingURL=payment-adapter-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-adapter-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-adapter-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAG7F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAMD,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,0BAA0B;IAWrE,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAXlC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;IAElE;;;;;;OAMG;gBAEgB,GAAG,GAAE,cAA8B,EACnC,eAAe,GAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAoB;IAG9E,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWzD,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIpC,oEAAoE;IACpE,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIzC,8EAA8E;IAC9E,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAI1D,2EAA2E;IAC3E,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAI1C,oDAAoD;IACpD,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAKxC,4EAA4E;IAC5E,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAMnD;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc;IAW3C,oEAAoE;IACpE,IAAI,IAAI,MAAM,EAAE;IAMhB,oDAAoD;IACpD,OAAO,IAAI,MAAM,EAAE;CAGpB"}
@@ -0,0 +1,87 @@
1
+ import { ModuleDisabledError } from '@endora-commerce/platform/kernel';
2
+ const consoleLogger = {
3
+ warn: (message) => console.warn(message),
4
+ };
5
+ /**
6
+ * `implements` the shape feature 075's Phase P published, which is what stops
7
+ * the registry and its contract drifting: the four gateway modules will read
8
+ * the published type, and `tsc` refuses the day a method here stops matching.
9
+ */
10
+ export class PaymentAdapterRegistry {
11
+ log;
12
+ isModulePresent;
13
+ entries = new Map();
14
+ /**
15
+ * @param log collision warnings.
16
+ * @param isModulePresent the effective-state probe. Defaults to
17
+ * always-present so a registry built for a unit test or a bare harness
18
+ * behaves exactly as it did before this filter existed; the process
19
+ * singleton wires it to the kernel's effective state.
20
+ */
21
+ constructor(log = consoleLogger, isModulePresent = () => true) {
22
+ this.log = log;
23
+ this.isModulePresent = isModulePresent;
24
+ }
25
+ register(adapter, moduleId) {
26
+ const existing = this.entries.get(adapter.adapterKey);
27
+ if (existing && existing.module !== moduleId) {
28
+ this.log.warn(`PaymentAdapterRegistry: adapter "${adapter.adapterKey}" re-registered by ` +
29
+ `module "${moduleId}" (was "${existing.module}"); overwriting previous registration.`);
30
+ }
31
+ this.entries.set(adapter.adapterKey, { adapter, module: moduleId });
32
+ }
33
+ unregister(adapterKey) {
34
+ this.entries.delete(adapterKey);
35
+ }
36
+ /** Whether any module has contributed this key — presence-blind. */
37
+ isRegistered(adapterKey) {
38
+ return this.entries.has(adapterKey);
39
+ }
40
+ /** The contributed entry, presence-blind. Admin and diagnostics read this. */
41
+ entry(adapterKey) {
42
+ return this.entries.get(adapterKey);
43
+ }
44
+ /** The module that contributed `adapterKey`, or `null` when nobody did. */
45
+ ownerOf(adapterKey) {
46
+ return this.entries.get(adapterKey)?.module ?? null;
47
+ }
48
+ /** Registered AND its owner effectively present. */
49
+ isAvailable(adapterKey) {
50
+ const entry = this.entries.get(adapterKey);
51
+ return entry !== undefined && this.isModulePresent(entry.module);
52
+ }
53
+ /** The adapter, or `undefined` when unregistered or its owner is absent. */
54
+ get(adapterKey) {
55
+ const entry = this.entries.get(adapterKey);
56
+ if (!entry || !this.isModulePresent(entry.module))
57
+ return undefined;
58
+ return entry.adapter;
59
+ }
60
+ /**
61
+ * The adapter, or a throw. An unregistered key is a programming error; a
62
+ * registered one whose owner is absent is the ordinary 503 envelope, so a
63
+ * caller reached through a service boundary learns the capability is off
64
+ * rather than that the platform is broken.
65
+ */
66
+ resolve(adapterKey) {
67
+ const entry = this.entries.get(adapterKey);
68
+ if (!entry) {
69
+ throw new Error(`PaymentAdapterRegistry: no adapter registered for key "${adapterKey}".`);
70
+ }
71
+ if (!this.isModulePresent(entry.module)) {
72
+ throw new ModuleDisabledError(entry.module);
73
+ }
74
+ return entry.adapter;
75
+ }
76
+ /** Adapter keys whose owner is present (stable insertion order). */
77
+ list() {
78
+ return [...this.entries.entries()]
79
+ .filter(([, entry]) => this.isModulePresent(entry.module))
80
+ .map(([key]) => key);
81
+ }
82
+ /** Every registered adapter key, presence-blind. */
83
+ listAll() {
84
+ return [...this.entries.keys()];
85
+ }
86
+ }
87
+ //# sourceMappingURL=payment-adapter-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-adapter-registry.js","sourceRoot":"","sources":["../../../src/backend/services/payment-adapter-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { PaymentSurface } from '@endora-commerce/contracts';
2
+ import type { PaymentMethod } from '../entities/payment-method.entity.js';
3
+ import type { PaymentAdapterRegistry } from './payment-adapter-registry.js';
4
+ /**
5
+ * PaymentMethodEligibilityService (feature 034, FR-011/FR-012/FR-013/FR-014).
6
+ *
7
+ * Given the active methods (already filtered by status and the per-Organization
8
+ * allow-list at the route), keeps only those whose adapter is currently
9
+ * registered (FR-003 — a disabled adapter drops out) and whose surface
10
+ * validator returns true. Sales-channel-assignment filtering is applied by the
11
+ * caller via the membership bridge.
12
+ */
13
+ export interface EligibilityContext {
14
+ salesChannelId: string | null;
15
+ organizationId: string | null;
16
+ customerAccountId: string | null;
17
+ surface: PaymentSurface;
18
+ }
19
+ export declare class PaymentMethodEligibilityService {
20
+ private readonly registry;
21
+ constructor(registry: PaymentAdapterRegistry);
22
+ filter(methods: PaymentMethod[], ctx: EligibilityContext): Promise<PaymentMethod[]>;
23
+ }
24
+ //# sourceMappingURL=payment-method-eligibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-method-eligibility.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-method-eligibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,+BAA+B;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,sBAAsB;IAEvD,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CAyC1F"}
@@ -0,0 +1,48 @@
1
+ export class PaymentMethodEligibilityService {
2
+ registry;
3
+ constructor(registry) {
4
+ this.registry = registry;
5
+ }
6
+ async filter(methods, ctx) {
7
+ const eligible = [];
8
+ for (const m of methods) {
9
+ if (m.status !== 'active')
10
+ continue;
11
+ const adapter = this.registry.get(m.adapter);
12
+ if (!adapter)
13
+ continue; // unregistered adapter ⇒ not offered (FR-003)
14
+ const eligCtx = {
15
+ paymentMethod: {
16
+ id: m.id,
17
+ code: m.code,
18
+ adapter: m.adapter,
19
+ kind: m.kind,
20
+ name: m.name,
21
+ status: m.status,
22
+ additionalPrice: Number(m.additionalPrice),
23
+ statusOnPending: m.statusOnPending,
24
+ statusOnSuccess: m.statusOnSuccess,
25
+ statusOnFailure: m.statusOnFailure,
26
+ salesChannelIds: [],
27
+ },
28
+ // Issue #103 — passed through, `null` included. It used to be
29
+ // `?? ''`, and an empty string is not a channel id: an adapter that
30
+ // reads its own per-channel configuration hit the settings seam guard
31
+ // and dropped every method. `null` is the platform-wide tier.
32
+ salesChannelId: ctx.salesChannelId,
33
+ organizationId: ctx.organizationId,
34
+ customerAccountId: ctx.customerAccountId,
35
+ surface: ctx.surface,
36
+ };
37
+ const ok = ctx.surface === 'admin'
38
+ ? await adapter.validateUseOnAdmin(eligCtx)
39
+ : ctx.surface === 'api'
40
+ ? await adapter.validateUseInApi(eligCtx)
41
+ : await adapter.validateUseOnStorefront(eligCtx);
42
+ if (ok)
43
+ eligible.push(m);
44
+ }
45
+ return eligible;
46
+ }
47
+ }
48
+ //# sourceMappingURL=payment-method-eligibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-method-eligibility.js","sourceRoot":"","sources":["../../../src/backend/services/payment-method-eligibility.ts"],"names":[],"mappings":"AAoBA,MAAM,OAAO,+BAA+B;IACb;IAA7B,YAA6B,QAAgC;QAAhC,aAAQ,GAAR,QAAQ,CAAwB;IAAG,CAAC;IAEjE,KAAK,CAAC,MAAM,CAAC,OAAwB,EAAE,GAAuB;QAC5D,MAAM,QAAQ,GAAoB,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ;gBAAE,SAAS;YACpC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC7C,IAAI,CAAC,OAAO;gBAAE,SAAS,CAAC,8CAA8C;YAEtE,MAAM,OAAO,GAAG;gBACd,aAAa,EAAE;oBACb,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC;oBAC1C,eAAe,EAAE,CAAC,CAAC,eAAe;oBAClC,eAAe,EAAE,CAAC,CAAC,eAAe;oBAClC,eAAe,EAAE,CAAC,CAAC,eAAe;oBAClC,eAAe,EAAE,EAAc;iBAChC;gBACD,8DAA8D;gBAC9D,oEAAoE;gBACpE,sEAAsE;gBACtE,8DAA8D;gBAC9D,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;gBACxC,OAAO,EAAE,GAAG,CAAC,OAAO;aACrB,CAAC;YAEF,MAAM,EAAE,GACN,GAAG,CAAC,OAAO,KAAK,OAAO;gBACrB,CAAC,CAAC,MAAM,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC;gBAC3C,CAAC,CAAC,GAAG,CAAC,OAAO,KAAK,KAAK;oBACrB,CAAC,CAAC,MAAM,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC;oBACzC,CAAC,CAAC,MAAM,OAAO,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,EAAE;gBAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { PaymentMethodReadPort, PaymentMethodRecord } from '@endora-commerce/contracts';
3
+ import { PaymentMethod } from '../entities/payment-method.entity.js';
4
+ /**
5
+ * The row-level read model `payment_methods` publishes (feature 075, Phase P).
6
+ *
7
+ * Nineteen of this module's 33 inbound import sites are a direct read of the
8
+ * `PaymentMethod` entity: four gateways resolving the method behind a payment,
9
+ * `orders` resolving it at placement, `quick_order` resolving a buyer's
10
+ * default, `payments` mapping an outcome onto an order status.
11
+ *
12
+ * `additionalPrice` stays a string across the boundary: it is `decimal(14,2)`
13
+ * and lands verbatim in the order's `paymentMethodSnapshot`.
14
+ */
15
+ export declare class PaymentMethodReadService implements PaymentMethodReadPort {
16
+ private readonly emFactory;
17
+ constructor(emFactory: () => EntityManager);
18
+ findById(id: string): Promise<PaymentMethodRecord | null>;
19
+ findByIds(ids: readonly string[]): Promise<PaymentMethodRecord[]>;
20
+ findByCode(code: string): Promise<PaymentMethodRecord | null>;
21
+ listAll(): Promise<PaymentMethodRecord[]>;
22
+ listActive(): Promise<PaymentMethodRecord[]>;
23
+ }
24
+ export declare function toPaymentMethodRecord(method: PaymentMethod): PaymentMethodRecord;
25
+ //# sourceMappingURL=payment-method-read-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-method-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-method-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAErE;;;;;;;;;;GAUG;AACH,qBAAa,wBAAyB,YAAW,qBAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAKzD,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAMjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAK7D,OAAO,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAKzC,UAAU,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;CAQnD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,mBAAmB,CAehF"}
@@ -0,0 +1,57 @@
1
+ import { PaymentMethod } from '../entities/payment-method.entity.js';
2
+ /**
3
+ * The row-level read model `payment_methods` publishes (feature 075, Phase P).
4
+ *
5
+ * Nineteen of this module's 33 inbound import sites are a direct read of the
6
+ * `PaymentMethod` entity: four gateways resolving the method behind a payment,
7
+ * `orders` resolving it at placement, `quick_order` resolving a buyer's
8
+ * default, `payments` mapping an outcome onto an order status.
9
+ *
10
+ * `additionalPrice` stays a string across the boundary: it is `decimal(14,2)`
11
+ * and lands verbatim in the order's `paymentMethodSnapshot`.
12
+ */
13
+ export class PaymentMethodReadService {
14
+ emFactory;
15
+ constructor(emFactory) {
16
+ this.emFactory = emFactory;
17
+ }
18
+ async findById(id) {
19
+ const method = await this.emFactory().findOne(PaymentMethod, { id });
20
+ return method ? toPaymentMethodRecord(method) : null;
21
+ }
22
+ async findByIds(ids) {
23
+ if (ids.length === 0)
24
+ return [];
25
+ const methods = await this.emFactory().find(PaymentMethod, { id: { $in: [...ids] } });
26
+ return methods.map(toPaymentMethodRecord);
27
+ }
28
+ async findByCode(code) {
29
+ const method = await this.emFactory().findOne(PaymentMethod, { code });
30
+ return method ? toPaymentMethodRecord(method) : null;
31
+ }
32
+ async listAll() {
33
+ const methods = await this.emFactory().find(PaymentMethod, {}, { orderBy: { code: 'asc' } });
34
+ return methods.map(toPaymentMethodRecord);
35
+ }
36
+ async listActive() {
37
+ const methods = await this.emFactory().find(PaymentMethod, { status: 'active' }, { orderBy: { code: 'asc' } });
38
+ return methods.map(toPaymentMethodRecord);
39
+ }
40
+ }
41
+ export function toPaymentMethodRecord(method) {
42
+ return {
43
+ id: method.id,
44
+ code: method.code,
45
+ name: method.name,
46
+ kind: method.kind,
47
+ adapter: method.adapter,
48
+ status: method.status,
49
+ additionalPrice: method.additionalPrice,
50
+ statusOnPending: method.statusOnPending,
51
+ statusOnSuccess: method.statusOnSuccess,
52
+ statusOnFailure: method.statusOnFailure,
53
+ createdAt: method.createdAt,
54
+ updatedAt: method.updatedAt,
55
+ };
56
+ }
57
+ //# sourceMappingURL=payment-method-read-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-method-read-port.js","sourceRoot":"","sources":["../../../src/backend/services/payment-method-read-port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAErE;;;;;;;;;;GAUG;AACH,MAAM,OAAO,wBAAwB;IACN;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAsB;QACpC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QACtF,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7F,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACzC,aAAa,EACb,EAAE,MAAM,EAAE,QAAQ,EAAE,EACpB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAC7B,CAAC;QACF,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAqB;IACzD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,97 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { PaymentMethodSeedApi, PaymentMethodSeedDefaults, PaymentMethodSeedOutcome } from '../../ports/index.js';
3
+ /**
4
+ * PaymentMethodReconciler (feature 034, FR-002) — the implementation behind
5
+ * `PaymentMethodSeedApi`, this module's published install surface
6
+ * (`../../ports/index.ts`, `createPaymentMethodSeeder` below).
7
+ *
8
+ * When a module contributes a payment adapter, it calls `ensureMethodForAdapter`
9
+ * from its **install hook** to create a configurable `payment_methods` row bound
10
+ * to that adapter — so installing a payment-method module surfaces an entry at
11
+ * `/payment-methods` with no core change. Idempotent and prune-safe: an existing
12
+ * row (matched by `code`) keeps its admin-edited configuration; the reconciler
13
+ * only fills a missing `adapter` link.
14
+ *
15
+ * The row and the adapter are contributed at **different moments, on purpose**.
16
+ * This row is durable state and is written once, at install; the adapter is an
17
+ * in-memory entry in a per-process table and is pushed from the contributing
18
+ * module's boot hook, on every composition (see `payment-adapter-registry.ts`).
19
+ * Neither waits for the other: a row whose `adapter` key nothing has contributed
20
+ * is simply not offered, and the registry is not read until a request reads it.
21
+ *
22
+ * **The `EntityManager` is a parameter of every method and is never held**
23
+ * (feature 134, FR-064; D-169). It used to be a constructor `emFactory`, which is
24
+ * the shape of a service resolved from a container — and the caller this class
25
+ * was written for has no container: `ModuleLifecycleContext` is
26
+ * `{ em, redis, log, module }` and `module:install` composes nothing (D-46). The
27
+ * hook's own `ctx.em` is therefore what every statement here runs on, handed in
28
+ * at the call, so the write lands in the transaction the orchestrator will
29
+ * commit or revert rather than on a fork of it.
30
+ *
31
+ * **It no longer touches sales-channel membership unconditionally (issue #96).**
32
+ * It used to call `bindToDefaultIfEmpty` for every row it created, and the four
33
+ * gateway modules called it from their plugin body on every composition — so a
34
+ * method an operator had deliberately unbound from every channel came back bound
35
+ * to Default at the next boot, and nothing said so. Binding belongs to the two
36
+ * seams that own the decision: the admin create path (a method an operator just
37
+ * created has to land somewhere) and the module's own seed (once, for the rows it
38
+ * creates). `bindToDefaultChannel` below is that second seam, and the caller
39
+ * guards it on `created === true`. "Unbound" is a state an operator is entitled
40
+ * to reach and to keep.
41
+ */
42
+ export declare class PaymentMethodReconciler implements PaymentMethodSeedApi {
43
+ ensureMethodForAdapter(em: EntityManager, adapterKey: string, defaults: PaymentMethodSeedDefaults): Promise<PaymentMethodSeedOutcome>;
44
+ /**
45
+ * The seed's channel binding — one membership row in the system-default
46
+ * channel, written against **this module's own** bridge table.
47
+ *
48
+ * Not through `SalesChannelMembershipService`, and the reason is structural
49
+ * rather than a preference: that service needs the `EventBus`, the audit port
50
+ * and the channel-bridge registry, and the registry is contributed from a boot
51
+ * hook. An install composes nothing, so at this seam it holds no registration
52
+ * for `'payment-method'` and `bridges.require` would refuse (FR-017) before the
53
+ * database was touched. `sales_channel_payment_methods` is this module's since
54
+ * `specs/120-migration-closure-bridge-ownership/` Phase 2 (D-226), so the
55
+ * statement is the owner's own and crosses no boundary — which is what FR-064
56
+ * buys by putting the writer here instead of in the seeding module.
57
+ *
58
+ * The channel itself is read through the kernel's own `SalesChannel` entity
59
+ * rather than in SQL — `api_keys` reads it the same way — because a raw
60
+ * `select … from "sales_channels"` from a module is a `check:module-boundary`
61
+ * finding against a kernel-owned table, and correctly so: the table is not
62
+ * this module's and the entity is the platform's published name for it.
63
+ *
64
+ * `em.execute` for the insert rather than `em.getConnection().execute`, so the
65
+ * statement runs inside the caller's transaction (issue #200). The bridge has
66
+ * no entity class — the kernel's membership service writes it in SQL too.
67
+ *
68
+ * **No system-default channel answers `false` rather than raising**, exactly as
69
+ * the delivery twin does and for the same measured reason: the default channel
70
+ * is created by `DefaultChannelReconciler` at **boot**, from `composeApp`, and
71
+ * `module:install` composes nothing (D-46), so a database that has been migrated
72
+ * and never booted has none. The seed migration this replaced degraded the same
73
+ * way, silently — its `cross join "sales_channels" where "system_default"`
74
+ * produced no rows and inserted no membership — so answering `false` is what
75
+ * keeps a fresh install and an upgraded one at the same row state in that state
76
+ * too. Raising instead **aborts the install**, and the hook is not inside a
77
+ * database transaction.
78
+ */
79
+ bindToDefaultChannel(em: EntityManager, paymentMethodId: string): Promise<boolean>;
80
+ /**
81
+ * The hard-uninstall half. Channel memberships go with the row, and so does
82
+ * every vendor-owned row keyed on it — each gateway's
83
+ * `*_payment_method_rules` and `*_payment_method_org_disables` declare
84
+ * `references "payment_methods" ("id") on delete cascade`.
85
+ */
86
+ removeMethodForAdapter(em: EntityManager, code: string): Promise<boolean>;
87
+ }
88
+ /**
89
+ * The runtime half of this module's install surface (feature 134, FR-064).
90
+ *
91
+ * A seeding module's `installHook` writes `createPaymentMethodSeeder()` and hands
92
+ * `ctx.em` to each call. The factory takes no arguments because the `em` belongs
93
+ * to the call and not to the seeder: one `em`, named at every statement, with no
94
+ * second source of truth for which transaction the write lands in.
95
+ */
96
+ export declare function createPaymentMethodSeeder(): PaymentMethodSeedApi;
97
+ //# sourceMappingURL=payment-method-reconciler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-method-reconciler.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-method-reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EACV,oBAAoB,EACpB,yBAAyB,EACzB,wBAAwB,EAEzB,MAAM,sBAAsB,CAAC;AAG9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,uBAAwB,YAAW,oBAAoB;IAC5D,sBAAsB,CAC1B,EAAE,EAAE,aAAa,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAAC,wBAAwB,CAAC;IAkCpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACG,oBAAoB,CAAC,EAAE,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAexF;;;;;OAKG;IACG,sBAAsB,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAOhF;AAqBD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,IAAI,oBAAoB,CAEhE"}