@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,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"}
|