@endora-commerce/mod-payments 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 +59 -0
  3. package/dist/admin/index.d.ts +32 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +61 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/zones/OrderPaymentsTab.d.ts +42 -0
  8. package/dist/admin/zones/OrderPaymentsTab.d.ts.map +1 -0
  9. package/dist/admin/zones/OrderPaymentsTab.js +104 -0
  10. package/dist/admin/zones/OrderPaymentsTab.js.map +1 -0
  11. package/dist/backend/adapters/built-in-adapters.d.ts +58 -0
  12. package/dist/backend/adapters/built-in-adapters.d.ts.map +1 -0
  13. package/dist/backend/adapters/built-in-adapters.js +83 -0
  14. package/dist/backend/adapters/built-in-adapters.js.map +1 -0
  15. package/dist/backend/drivers/bank-transfer-driver.d.ts +28 -0
  16. package/dist/backend/drivers/bank-transfer-driver.d.ts.map +1 -0
  17. package/dist/backend/drivers/bank-transfer-driver.js +26 -0
  18. package/dist/backend/drivers/bank-transfer-driver.js.map +1 -0
  19. package/dist/backend/drivers/gateway-adapter-port.d.ts +71 -0
  20. package/dist/backend/drivers/gateway-adapter-port.d.ts.map +1 -0
  21. package/dist/backend/drivers/gateway-adapter-port.js +18 -0
  22. package/dist/backend/drivers/gateway-adapter-port.js.map +1 -0
  23. package/dist/backend/drivers/pickup-driver.d.ts +23 -0
  24. package/dist/backend/drivers/pickup-driver.d.ts.map +1 -0
  25. package/dist/backend/drivers/pickup-driver.js +19 -0
  26. package/dist/backend/drivers/pickup-driver.js.map +1 -0
  27. package/dist/backend/email-templates/transactional-defaults.d.ts +6 -0
  28. package/dist/backend/email-templates/transactional-defaults.d.ts.map +1 -0
  29. package/dist/backend/email-templates/transactional-defaults.js +27 -0
  30. package/dist/backend/email-templates/transactional-defaults.js.map +1 -0
  31. package/dist/backend/entities/payment.entity.d.ts +26 -0
  32. package/dist/backend/entities/payment.entity.d.ts.map +1 -0
  33. package/dist/backend/entities/payment.entity.js +100 -0
  34. package/dist/backend/entities/payment.entity.js.map +1 -0
  35. package/dist/backend/index.d.ts +88 -0
  36. package/dist/backend/index.d.ts.map +1 -0
  37. package/dist/backend/index.js +318 -0
  38. package/dist/backend/index.js.map +1 -0
  39. package/dist/backend/routes.customer.d.ts +20 -0
  40. package/dist/backend/routes.customer.d.ts.map +1 -0
  41. package/dist/backend/routes.customer.js +24 -0
  42. package/dist/backend/routes.customer.js.map +1 -0
  43. package/dist/backend/routes.d.ts +36 -0
  44. package/dist/backend/routes.d.ts.map +1 -0
  45. package/dist/backend/routes.js +44 -0
  46. package/dist/backend/routes.js.map +1 -0
  47. package/dist/backend/services/gateway-refund-registry.d.ts +122 -0
  48. package/dist/backend/services/gateway-refund-registry.d.ts.map +1 -0
  49. package/dist/backend/services/gateway-refund-registry.js +106 -0
  50. package/dist/backend/services/gateway-refund-registry.js.map +1 -0
  51. package/dist/backend/services/payment-email-notifier.d.ts +71 -0
  52. package/dist/backend/services/payment-email-notifier.d.ts.map +1 -0
  53. package/dist/backend/services/payment-email-notifier.js +73 -0
  54. package/dist/backend/services/payment-email-notifier.js.map +1 -0
  55. package/dist/backend/services/payment-email-renderer.d.ts +22 -0
  56. package/dist/backend/services/payment-email-renderer.d.ts.map +1 -0
  57. package/dist/backend/services/payment-email-renderer.js +17 -0
  58. package/dist/backend/services/payment-email-renderer.js.map +1 -0
  59. package/dist/backend/services/payment-placement-apply-port.d.ts +33 -0
  60. package/dist/backend/services/payment-placement-apply-port.d.ts.map +1 -0
  61. package/dist/backend/services/payment-placement-apply-port.js +68 -0
  62. package/dist/backend/services/payment-placement-apply-port.js.map +1 -0
  63. package/dist/backend/services/payment-read-port.d.ts +33 -0
  64. package/dist/backend/services/payment-read-port.d.ts.map +1 -0
  65. package/dist/backend/services/payment-read-port.js +64 -0
  66. package/dist/backend/services/payment-read-port.js.map +1 -0
  67. package/dist/backend/services/payment-reference-port.d.ts +29 -0
  68. package/dist/backend/services/payment-reference-port.d.ts.map +1 -0
  69. package/dist/backend/services/payment-reference-port.js +70 -0
  70. package/dist/backend/services/payment-reference-port.js.map +1 -0
  71. package/dist/backend/services/payment-refund.d.ts +55 -0
  72. package/dist/backend/services/payment-refund.d.ts.map +1 -0
  73. package/dist/backend/services/payment-refund.js +101 -0
  74. package/dist/backend/services/payment-refund.js.map +1 -0
  75. package/dist/backend/services/payment-retry-service.d.ts +45 -0
  76. package/dist/backend/services/payment-retry-service.d.ts.map +1 -0
  77. package/dist/backend/services/payment-retry-service.js +187 -0
  78. package/dist/backend/services/payment-retry-service.js.map +1 -0
  79. package/dist/backend/services/payment-service.d.ts +62 -0
  80. package/dist/backend/services/payment-service.d.ts.map +1 -0
  81. package/dist/backend/services/payment-service.js +134 -0
  82. package/dist/backend/services/payment-service.js.map +1 -0
  83. package/dist/backend/services/receive-payment-handler.d.ts +260 -0
  84. package/dist/backend/services/receive-payment-handler.d.ts.map +1 -0
  85. package/dist/backend/services/receive-payment-handler.js +356 -0
  86. package/dist/backend/services/receive-payment-handler.js.map +1 -0
  87. package/dist/backend/services/registry-singleton.d.ts +16 -0
  88. package/dist/backend/services/registry-singleton.d.ts.map +1 -0
  89. package/dist/backend/services/registry-singleton.js +17 -0
  90. package/dist/backend/services/registry-singleton.js.map +1 -0
  91. package/dist/manifest.d.ts +210 -0
  92. package/dist/manifest.d.ts.map +1 -0
  93. package/dist/manifest.js +182 -0
  94. package/dist/manifest.js.map +1 -0
  95. package/dist/migrations/20260925T115728_payments_refunded_amount.d.ts +28 -0
  96. package/dist/migrations/20260925T115728_payments_refunded_amount.d.ts.map +1 -0
  97. package/dist/migrations/20260925T115728_payments_refunded_amount.js +32 -0
  98. package/dist/migrations/20260925T115728_payments_refunded_amount.js.map +1 -0
  99. package/dist/migrations/index.d.ts +27 -0
  100. package/dist/migrations/index.d.ts.map +1 -0
  101. package/dist/migrations/index.js +29 -0
  102. package/dist/migrations/index.js.map +1 -0
  103. package/docs/payments.md +69 -0
  104. package/i18n/en.json +5 -0
  105. package/i18n/pl.json +5 -0
  106. package/package.json +96 -0
  107. package/tailwind.css +14 -0
@@ -0,0 +1,106 @@
1
+ const consoleLogger = {
2
+ warn: (message) => console.warn(message),
3
+ };
4
+ export class GatewayRefundRegistry {
5
+ log;
6
+ isModulePresent;
7
+ handlers = new Map();
8
+ /**
9
+ * @param log collision warnings.
10
+ * @param isModulePresent the effective-state probe. Defaults to
11
+ * always-present, so a registry a unit test builds for itself keeps
12
+ * answering about the handlers that test registered; the process singleton
13
+ * wires it to the kernel's effective state.
14
+ */
15
+ constructor(log = consoleLogger, isModulePresent = () => true) {
16
+ this.log = log;
17
+ this.isModulePresent = isModulePresent;
18
+ }
19
+ register(handler, moduleId) {
20
+ const existing = this.handlers.get(handler.adapterKey);
21
+ if (existing && existing.module !== moduleId) {
22
+ this.log.warn(`GatewayRefundRegistry: handler "${handler.adapterKey}" re-registered by module ` +
23
+ `"${moduleId}" (was "${existing.module}"); overwriting previous registration.`);
24
+ }
25
+ this.handlers.set(handler.adapterKey, { handler, module: moduleId });
26
+ }
27
+ unregister(adapterKey) {
28
+ this.handlers.delete(adapterKey);
29
+ }
30
+ /** The contributed entry, presence-blind. Diagnostics read this. */
31
+ entry(adapterKey) {
32
+ return this.handlers.get(adapterKey);
33
+ }
34
+ /** The module that contributed `adapterKey`, or `null` when nobody did. */
35
+ ownerOf(adapterKey) {
36
+ return this.handlers.get(adapterKey)?.module ?? null;
37
+ }
38
+ /**
39
+ * The module that would have handled this refund but is not present (D-71).
40
+ *
41
+ * The question a caller asks *after* {@link resolve} declined, because one
42
+ * empty `resolve` covers two situations that are not interchangeable: a
43
+ * gateway an operator switched off is a capability restorable in one click
44
+ * and must be **refused**, while an adapter nobody ever registered is a
45
+ * deployment with no PSP integration, which records the obligation and
46
+ * settles by hand.
47
+ *
48
+ * It mirrors `resolve`'s two arms exactly, which is why the unkeyed one is
49
+ * here at all: `resolve` answers with the sole registered handler when the
50
+ * order names no adapter, and point 3 of the docblock above says that is the
51
+ * worst arm to get wrong — the refunds with the least information behind
52
+ * them. Presence-blind in the same sense `ownerOf` is.
53
+ */
54
+ absentOwnerFor(adapterKey) {
55
+ if (adapterKey) {
56
+ const entry = this.handlers.get(adapterKey);
57
+ if (!entry || this.isModulePresent(entry.module))
58
+ return null;
59
+ return entry.module;
60
+ }
61
+ // No key: `resolve` would have answered with the sole registered handler.
62
+ // Nothing is available and exactly one thing is registered ⇒ that one is
63
+ // the handler the operator switched off.
64
+ if (this.list().length > 0)
65
+ return null;
66
+ const entries = [...this.handlers.values()];
67
+ return entries.length === 1 ? entries[0].module : null;
68
+ }
69
+ /** The handler, or `undefined` when unregistered or its owner is absent. */
70
+ get(adapterKey) {
71
+ const entry = this.handlers.get(adapterKey);
72
+ if (!entry || !this.isModulePresent(entry.module))
73
+ return undefined;
74
+ return entry.handler;
75
+ }
76
+ /**
77
+ * Resolve the handler for a gateway payment.
78
+ * - Known `adapterKey` → exact handler only (never guess another PSP).
79
+ * - Missing key → sole registered handler when exactly one exists.
80
+ *
81
+ * Both arms drop a handler whose owner is absent, and the second arm counts
82
+ * only the handlers that are available: with one gateway installed and
83
+ * switched off, "the sole registered handler" must not be it.
84
+ */
85
+ resolve(adapterKey) {
86
+ if (adapterKey) {
87
+ return this.get(adapterKey);
88
+ }
89
+ const available = this.list();
90
+ const [only, ...rest] = available;
91
+ if (only !== undefined && rest.length === 0)
92
+ return this.get(only);
93
+ return undefined;
94
+ }
95
+ /** Adapter keys whose owner is present (stable insertion order). */
96
+ list() {
97
+ return [...this.handlers.entries()]
98
+ .filter(([, entry]) => this.isModulePresent(entry.module))
99
+ .map(([key]) => key);
100
+ }
101
+ /** Every registered adapter key, presence-blind. */
102
+ listAll() {
103
+ return [...this.handlers.keys()];
104
+ }
105
+ }
106
+ //# sourceMappingURL=gateway-refund-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-refund-registry.js","sourceRoot":"","sources":["../../../src/backend/services/gateway-refund-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CustomerAccountReadPort, OrderReadPort, TransactionalEmailSender } from '@endora-commerce/contracts';
3
+ /**
4
+ * Why the payment-status e-mail did — or did not — go out (issue #78).
5
+ *
6
+ * `notify` answered `void`, and `void` covered seven situations: no sender
7
+ * wired, no order behind the id, no address on the customer, the three outcomes
8
+ * `send` reports since issue #67, and anything the bare `catch` absorbed. A
9
+ * payment was recorded and nothing anywhere said whether the customer had been
10
+ * told.
11
+ */
12
+ export type PaymentEmailNotSentReason =
13
+ /** No transactional sender is wired in this composition. */
14
+ 'no_sender'
15
+ /** The event named an order this process cannot load. */
16
+ | 'order_not_found'
17
+ /** The customer account carries no address to send to. */
18
+ | 'no_recipient'
19
+ /** The operator switched the `payment_status_changed` e-mail off. */
20
+ | 'deactivated'
21
+ /** No mailer is wired behind the sender. */
22
+ | 'no_transport'
23
+ /** No `payment_status_changed` template exists yet. */
24
+ | 'no_definition'
25
+ /** The send raised, and the payment stays recorded. */
26
+ | 'failed';
27
+ export type PaymentEmailResult = {
28
+ sent: true;
29
+ } | {
30
+ sent: false;
31
+ reason: PaymentEmailNotSentReason;
32
+ };
33
+ /**
34
+ * Where a send that did not happen is reported. Injectable so a test can read
35
+ * it; defaults to `console.warn`, which is what the rest of this layer uses.
36
+ */
37
+ export type PaymentEmailLog = (message: string, context: Record<string, unknown>) => void;
38
+ export interface PaymentEmailNotifierDeps {
39
+ emFactory: () => EntityManager;
40
+ /**
41
+ * Feature 075 Phase C — the order and its buyer come from the ports their
42
+ * owners publish, not from `Order` and `CustomerAccount`. Both fail closed
43
+ * when their owner is off, and that is the right answer for a notification:
44
+ * an e-mail addressed from data the platform will not read is worse than no
45
+ * e-mail. `notify`'s `catch` re-throws `ModuleDisabledError` first, so the
46
+ * refusal reaches the subscriber rather than being logged as `failed`.
47
+ */
48
+ orderRead: OrderReadPort;
49
+ customerAccountRead: CustomerAccountReadPort;
50
+ getTransactionalEmailSender: () => TransactionalEmailSender | undefined;
51
+ log?: PaymentEmailLog;
52
+ }
53
+ export declare class PaymentEmailNotifier {
54
+ private readonly deps;
55
+ private readonly log;
56
+ constructor(deps: PaymentEmailNotifierDeps);
57
+ /**
58
+ * Public since feature 072 (T126), and `attach(eventBus)` is gone with it.
59
+ * That method subscribed to the raw bus, which is how a payment-status
60
+ * e-mail went out while this module was switched off; the module now
61
+ * subscribes through `ctx.subscribe`, which stops with it.
62
+ *
63
+ * **Best-effort, and now audible.** The subscriber that calls this has no
64
+ * result to inspect, so every non-sent path is written to the log as well as
65
+ * named in the return value; a payment must not be un-recorded because the
66
+ * notification failed.
67
+ */
68
+ notify(orderId: string, status: 'paid' | 'failed', failureReason: string | null): Promise<PaymentEmailResult>;
69
+ private notSent;
70
+ }
71
+ //# sourceMappingURL=payment-email-notifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-email-notifier.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-email-notifier.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,uBAAuB,EACvB,aAAa,EACb,wBAAwB,EACzB,MAAM,4BAA4B,CAAC;AAIpC;;;;;;;;GAQG;AACH,MAAM,MAAM,yBAAyB;AACnC,4DAA4D;AAC1D,WAAW;AACb,yDAAyD;GACvD,iBAAiB;AACnB,0DAA0D;GACxD,cAAc;AAChB,qEAAqE;GACnE,aAAa;AACf,4CAA4C;GAC1C,cAAc;AAChB,uDAAuD;GACrD,eAAe;AACjB,uDAAuD;GACrD,QAAQ,CAAC;AAEb,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAErG;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAE1F,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B;;;;;;;OAOG;IACH,SAAS,EAAE,aAAa,CAAC;IACzB,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,2BAA2B,EAAE,MAAM,wBAAwB,GAAG,SAAS,CAAC;IACxE,GAAG,CAAC,EAAE,eAAe,CAAC;CACvB;AAED,qBAAa,oBAAoB;IAGnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;gBAET,IAAI,EAAE,wBAAwB;IAI3D;;;;;;;;;;OAUG;IACG,MAAM,CACV,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,QAAQ,EACzB,aAAa,EAAE,MAAM,GAAG,IAAI,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IAqC9B,OAAO,CAAC,OAAO;CAYhB"}
@@ -0,0 +1,73 @@
1
+ // Feature 047 — payment_status_changed transactional email. A net-new email
2
+ // (no legacy builder): subscribes to payment.received/failed and sends the
3
+ // admin-editable template to the order's customer. Best-effort; event-bus
4
+ // dispatch isolates handler errors.
5
+ import { rethrowIfModuleDisabled } from '@endora-commerce/platform/kernel';
6
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
7
+ export class PaymentEmailNotifier {
8
+ deps;
9
+ log;
10
+ constructor(deps) {
11
+ this.deps = deps;
12
+ this.log = deps.log ?? ((message, context) => console.warn(message, context));
13
+ }
14
+ /**
15
+ * Public since feature 072 (T126), and `attach(eventBus)` is gone with it.
16
+ * That method subscribed to the raw bus, which is how a payment-status
17
+ * e-mail went out while this module was switched off; the module now
18
+ * subscribes through `ctx.subscribe`, which stops with it.
19
+ *
20
+ * **Best-effort, and now audible.** The subscriber that calls this has no
21
+ * result to inspect, so every non-sent path is written to the log as well as
22
+ * named in the return value; a payment must not be un-recorded because the
23
+ * notification failed.
24
+ */
25
+ async notify(orderId, status, failureReason) {
26
+ const sender = this.deps.getTransactionalEmailSender();
27
+ if (!sender)
28
+ return this.notSent(orderId, 'no_sender');
29
+ try {
30
+ const em = this.deps.emFactory();
31
+ const order = await this.deps.orderRead.findById(orderId);
32
+ if (!order)
33
+ return this.notSent(orderId, 'order_not_found');
34
+ const customer = await this.deps.customerAccountRead.findById(order.placedByCustomerAccountId);
35
+ if (!customer?.email)
36
+ return this.notSent(orderId, 'no_recipient');
37
+ const channel = await em.findOne(SalesChannel, { id: order.salesChannelId });
38
+ const outcome = await sender.send({
39
+ code: 'payment_status_changed',
40
+ salesChannelId: order.salesChannelId,
41
+ language: channel?.defaultLanguage ?? 'en-US',
42
+ to: customer.email,
43
+ messageId: `payment_status_changed:${order.id}:${status}`,
44
+ variables: {
45
+ order: { businessId: order.businessId },
46
+ payment: { status, statusLabel: status === 'paid' ? 'Paid' : 'Failed', failureReason: failureReason ?? '' },
47
+ },
48
+ meta: { orderId: order.id, kind: 'payment_status_changed', status },
49
+ });
50
+ if (outcome.status !== 'sent')
51
+ return this.notSent(orderId, outcome.status);
52
+ return { sent: true };
53
+ }
54
+ catch (error) {
55
+ // A switched-off module is a presence answer about the whole operation,
56
+ // not a message that failed to render; absorbing it would report "sent
57
+ // nothing" where the truthful answer is "this capability is off".
58
+ rethrowIfModuleDisabled(error);
59
+ // Everything else is contained: the payment is recorded and must not be
60
+ // undone because the message did not go out. It is named, though.
61
+ return this.notSent(orderId, 'failed', error);
62
+ }
63
+ }
64
+ notSent(orderId, reason, error) {
65
+ this.log('[payments] the payment-status e-mail was not sent', {
66
+ orderId,
67
+ reason,
68
+ ...(error === undefined ? {} : { error: error instanceof Error ? error.message : error }),
69
+ });
70
+ return { sent: false, reason };
71
+ }
72
+ }
73
+ //# sourceMappingURL=payment-email-notifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-email-notifier.js","sourceRoot":"","sources":["../../../src/backend/services/payment-email-notifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Payment-method renderer registry for the transactional order-confirmation
3
+ * e-mail (feature 034, FR-016/FR-017). An adapter may register a custom
4
+ * e-mail renderer under a key (the adapter's `renderers.email`); when none is
5
+ * registered, the platform default text builder is used, so the payment
6
+ * section always renders.
7
+ *
8
+ * E-mail bodies are plain text (see Mailer.MailerSendInput.text), so a renderer
9
+ * is a `(ctx) => string` builder rather than a React component.
10
+ */
11
+ import type { PaymentEmailContext } from '@endora-commerce/contracts';
12
+ /**
13
+ * `PaymentEmailContext` moved to `@endora-commerce/contracts` in feature 075's Phase P —
14
+ * `orders` renders the payment line with it. Re-exported here for the length
15
+ * of Phase P, which cuts no consumer.
16
+ */
17
+ export type { PaymentEmailContext };
18
+ export type PaymentEmailRenderer = (ctx: PaymentEmailContext) => string;
19
+ export declare const defaultPaymentEmailRenderer: PaymentEmailRenderer;
20
+ export declare function registerPaymentEmailRenderer(key: string, renderer: PaymentEmailRenderer): void;
21
+ export declare function resolvePaymentEmailRenderer(rendererKey: string | null): PaymentEmailRenderer;
22
+ //# sourceMappingURL=payment-email-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-email-renderer.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-email-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;GAIG;AACH,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,mBAAmB,KAAK,MAAM,CAAC;AAExE,eAAO,MAAM,2BAA2B,EAAE,oBAIzC,CAAC;AAIF,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAE9F;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,oBAAoB,CAM5F"}
@@ -0,0 +1,17 @@
1
+ export const defaultPaymentEmailRenderer = (ctx) => {
2
+ const surcharge = ctx.additionalPrice > 0 ? ` (+${ctx.additionalPrice.toFixed(2)} ${ctx.currency})` : '';
3
+ return `${ctx.name}${surcharge}`;
4
+ };
5
+ const registry = new Map();
6
+ export function registerPaymentEmailRenderer(key, renderer) {
7
+ registry.set(key, renderer);
8
+ }
9
+ export function resolvePaymentEmailRenderer(rendererKey) {
10
+ if (rendererKey) {
11
+ const found = registry.get(rendererKey);
12
+ if (found)
13
+ return found;
14
+ }
15
+ return defaultPaymentEmailRenderer;
16
+ }
17
+ //# sourceMappingURL=payment-email-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-email-renderer.js","sourceRoot":"","sources":["../../../src/backend/services/payment-email-renderer.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,MAAM,2BAA2B,GAAyB,CAAC,GAAG,EAAE,EAAE;IACvE,MAAM,SAAS,GACb,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACzF,OAAO,GAAG,GAAG,CAAC,IAAI,GAAG,SAAS,EAAE,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAgC,CAAC;AAEzD,MAAM,UAAU,4BAA4B,CAAC,GAAW,EAAE,QAA8B;IACtF,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,WAA0B;IACpE,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACxC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,2BAA2B,CAAC;AACrC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { PaymentOpened, PaymentPlacementApplyPort } from '@endora-commerce/mod-orders/ports';
3
+ /**
4
+ * `paymentPlacementApplyPort` — the payment row an order is placed with,
5
+ * written on the caller's `EntityManager` (feature 080, T048; D-169, D-179).
6
+ *
7
+ * `orders` held this module's `Payment` class for it until T048 and did the
8
+ * `tx.create` itself — the last cross-module entity-class reach in the tree, and
9
+ * the one thing standing between `orders` and `payments` and their packaging.
10
+ * D-168 leaves a packaged `payments` no entity class for `orders` to name, so
11
+ * the reach had to go before either module moves; the transaction it runs in did
12
+ * not change, and `payments_order_fk` is untouched. The port's own doc block
13
+ * states the constraint.
14
+ *
15
+ * **Every default the row carries is this module's**, and that is the second
16
+ * thing the conversion moved. `status` (`awaiting_payment`), `attemptNo` (1) and
17
+ * `refundedAmount` (`0`) are entity defaults `orders` inherited by constructing
18
+ * the row; they are now inherited by the module that owns the vocabulary, which
19
+ * is what makes `deferred` — the one value a placement ever overrides — a named
20
+ * method below rather than a string crossing a boundary.
21
+ */
22
+ export declare class PaymentPlacementApplyService implements PaymentPlacementApplyPort {
23
+ openForOrder(em: EntityManager, input: {
24
+ orderId: string;
25
+ paymentMethodId: string;
26
+ amount: string;
27
+ currency: string;
28
+ }): Promise<PaymentOpened>;
29
+ markDeferred(em: EntityManager, input: {
30
+ paymentId: string;
31
+ }): Promise<PaymentOpened>;
32
+ }
33
+ //# sourceMappingURL=payment-placement-apply-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-placement-apply-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-placement-apply-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D,OAAO,KAAK,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAElG;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,4BAA6B,YAAW,yBAAyB;IACtE,YAAY,CAChB,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GACA,OAAO,CAAC,aAAa,CAAC;IAenB,YAAY,CAChB,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,aAAa,CAAC;CAsB1B"}
@@ -0,0 +1,68 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { Payment } from '../entities/payment.entity.js';
4
+ /**
5
+ * `paymentPlacementApplyPort` — the payment row an order is placed with,
6
+ * written on the caller's `EntityManager` (feature 080, T048; D-169, D-179).
7
+ *
8
+ * `orders` held this module's `Payment` class for it until T048 and did the
9
+ * `tx.create` itself — the last cross-module entity-class reach in the tree, and
10
+ * the one thing standing between `orders` and `payments` and their packaging.
11
+ * D-168 leaves a packaged `payments` no entity class for `orders` to name, so
12
+ * the reach had to go before either module moves; the transaction it runs in did
13
+ * not change, and `payments_order_fk` is untouched. The port's own doc block
14
+ * states the constraint.
15
+ *
16
+ * **Every default the row carries is this module's**, and that is the second
17
+ * thing the conversion moved. `status` (`awaiting_payment`), `attemptNo` (1) and
18
+ * `refundedAmount` (`0`) are entity defaults `orders` inherited by constructing
19
+ * the row; they are now inherited by the module that owns the vocabulary, which
20
+ * is what makes `deferred` — the one value a placement ever overrides — a named
21
+ * method below rather than a string crossing a boundary.
22
+ */
23
+ export class PaymentPlacementApplyService {
24
+ async openForOrder(em, input) {
25
+ // command-coverage-ignore: the payment row an audited order placement opens,
26
+ // inside that placement's own transaction. `orders` records the placement as
27
+ // one operation through its own Command; a second audit entry for the row it
28
+ // opened would record the same decision twice.
29
+ const payment = em.create(Payment, {
30
+ orderId: input.orderId,
31
+ paymentMethodId: input.paymentMethodId,
32
+ amount: input.amount,
33
+ currency: input.currency,
34
+ });
35
+ await em.persistAndFlush(payment);
36
+ return recordOf(payment);
37
+ }
38
+ async markDeferred(em, input) {
39
+ const payment = await em.findOne(Payment, { id: input.paymentId });
40
+ if (!payment) {
41
+ // Not a tolerance and not a degrade: the one caller opened this row on the
42
+ // very transaction it is handing back, so an absent row means the two
43
+ // calls are not on one unit of work — which is precisely the mistake the
44
+ // required `EntityManager` exists to make impossible. Refuse loudly rather
45
+ // than leave a credit-limit order recorded as awaiting a payment nobody
46
+ // will make.
47
+ throw new HttpError(500, ERROR_CODES.INTERNAL, 'Payment row to defer does not exist on this transaction.');
48
+ }
49
+ payment.status = 'deferred';
50
+ // command-coverage-ignore: the same placement operation as `openForOrder`
51
+ // above, one branch further in — the credit-limit order whose reservation
52
+ // this transaction has just taken. Audited once, by `orders`' own Command.
53
+ await em.flush();
54
+ return recordOf(payment);
55
+ }
56
+ }
57
+ /** A published record, never the managed entity (D-77's first narrowing). */
58
+ function recordOf(payment) {
59
+ return {
60
+ id: payment.id,
61
+ orderId: payment.orderId,
62
+ paymentMethodId: payment.paymentMethodId,
63
+ amount: payment.amount,
64
+ currency: payment.currency,
65
+ status: payment.status,
66
+ };
67
+ }
68
+ //# sourceMappingURL=payment-placement-apply-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-placement-apply-port.js","sourceRoot":"","sources":["../../../src/backend/services/payment-placement-apply-port.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAGxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,4BAA4B;IACvC,KAAK,CAAC,YAAY,CAChB,EAAiB,EACjB,KAKC;QAED,6EAA6E;QAC7E,6EAA6E;QAC7E,6EAA6E;QAC7E,+CAA+C;QAC/C,MAAM,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE;YACjC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC,CAAC;QACH,MAAM,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAClC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,EAAiB,EACjB,KAA4B;QAE5B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,2EAA2E;YAC3E,sEAAsE;YACtE,yEAAyE;YACzE,2EAA2E;YAC3E,wEAAwE;YACxE,aAAa;YACb,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,QAAQ,EACpB,0DAA0D,CAC3D,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC;QAC5B,0EAA0E;QAC1E,0EAA0E;QAC1E,2EAA2E;QAC3E,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;CACF;AAED,6EAA6E;AAC7E,SAAS,QAAQ,CAAC,OAAgB;IAChC,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { PaymentReadPort, PaymentRecord } from '@endora-commerce/contracts';
3
+ import { Payment } from '../entities/payment.entity.js';
4
+ /**
5
+ * The row-level read model `payments` publishes (feature 075, Phase P).
6
+ *
7
+ * Sixteen of this module's 35 inbound import sites are `em.findOne(Payment, …)`
8
+ * — the same four lookups written out once per gateway, with each gateway
9
+ * spelling "the latest attempt" slightly differently (`attemptNo` descending
10
+ * in two, `createdAt` descending in the others). Publishing the read fixes
11
+ * that ordering in one place: `attemptNo` first, `createdAt` as the tiebreak,
12
+ * which is the ordering the attempt counter is maintained for.
13
+ *
14
+ * The money columns stay strings: they are `decimal(14,2)`, and every consumer
15
+ * forwards the figure to a payment provider.
16
+ *
17
+ * `countByPaymentMethod` joined it for feature 075. It answers the one question
18
+ * `payment_methods` had been asking of this module's table directly, in raw SQL
19
+ * inside its delete Command — a boundary crossing that named no import
20
+ * specifier and so compiled, returned rows and went unseen until
21
+ * `check:module-boundary` learned to read a SQL statement.
22
+ */
23
+ export declare class PaymentReadService implements PaymentReadPort {
24
+ private readonly emFactory;
25
+ constructor(emFactory: () => EntityManager);
26
+ findById(id: string): Promise<PaymentRecord | null>;
27
+ listByOrderId(orderId: string): Promise<PaymentRecord[]>;
28
+ findLatestForOrder(orderId: string): Promise<PaymentRecord | null>;
29
+ findByExternalReference(externalReference: string): Promise<PaymentRecord | null>;
30
+ countByPaymentMethod(paymentMethodId: string): Promise<number>;
31
+ }
32
+ export declare function toPaymentRecord(payment: Payment): PaymentRecord;
33
+ //# sourceMappingURL=payment-read-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,kBAAmB,YAAW,eAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKnD,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IASxD,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IASlE,uBAAuB,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IASjF,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAGrE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAiB/D"}
@@ -0,0 +1,64 @@
1
+ import { Payment } from '../entities/payment.entity.js';
2
+ /**
3
+ * The row-level read model `payments` publishes (feature 075, Phase P).
4
+ *
5
+ * Sixteen of this module's 35 inbound import sites are `em.findOne(Payment, …)`
6
+ * — the same four lookups written out once per gateway, with each gateway
7
+ * spelling "the latest attempt" slightly differently (`attemptNo` descending
8
+ * in two, `createdAt` descending in the others). Publishing the read fixes
9
+ * that ordering in one place: `attemptNo` first, `createdAt` as the tiebreak,
10
+ * which is the ordering the attempt counter is maintained for.
11
+ *
12
+ * The money columns stay strings: they are `decimal(14,2)`, and every consumer
13
+ * forwards the figure to a payment provider.
14
+ *
15
+ * `countByPaymentMethod` joined it for feature 075. It answers the one question
16
+ * `payment_methods` had been asking of this module's table directly, in raw SQL
17
+ * inside its delete Command — a boundary crossing that named no import
18
+ * specifier and so compiled, returned rows and went unseen until
19
+ * `check:module-boundary` learned to read a SQL statement.
20
+ */
21
+ export class PaymentReadService {
22
+ emFactory;
23
+ constructor(emFactory) {
24
+ this.emFactory = emFactory;
25
+ }
26
+ async findById(id) {
27
+ const payment = await this.emFactory().findOne(Payment, { id });
28
+ return payment ? toPaymentRecord(payment) : null;
29
+ }
30
+ async listByOrderId(orderId) {
31
+ const payments = await this.emFactory().find(Payment, { orderId }, { orderBy: { attemptNo: 'desc', createdAt: 'desc' } });
32
+ return payments.map(toPaymentRecord);
33
+ }
34
+ async findLatestForOrder(orderId) {
35
+ const payment = await this.emFactory().findOne(Payment, { orderId }, { orderBy: { attemptNo: 'desc', createdAt: 'desc' } });
36
+ return payment ? toPaymentRecord(payment) : null;
37
+ }
38
+ async findByExternalReference(externalReference) {
39
+ const payment = await this.emFactory().findOne(Payment, { externalReference }, { orderBy: { attemptNo: 'desc', createdAt: 'desc' } });
40
+ return payment ? toPaymentRecord(payment) : null;
41
+ }
42
+ async countByPaymentMethod(paymentMethodId) {
43
+ return this.emFactory().count(Payment, { paymentMethodId });
44
+ }
45
+ }
46
+ export function toPaymentRecord(payment) {
47
+ return {
48
+ id: payment.id,
49
+ orderId: payment.orderId,
50
+ paymentMethodId: payment.paymentMethodId,
51
+ status: payment.status,
52
+ amount: payment.amount,
53
+ refundedAmount: payment.refundedAmount,
54
+ currency: payment.currency,
55
+ paidAt: payment.paidAt ?? null,
56
+ externalReference: payment.externalReference ?? null,
57
+ providerDetails: payment.providerDetails ?? null,
58
+ failureReason: payment.failureReason ?? null,
59
+ attemptNo: payment.attemptNo,
60
+ createdAt: payment.createdAt,
61
+ updatedAt: payment.updatedAt,
62
+ };
63
+ }
64
+ //# sourceMappingURL=payment-read-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-read-port.js","sourceRoot":"","sources":["../../../src/backend/services/payment-read-port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,kBAAkB;IACA;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAe;QACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAC1C,OAAO,EACP,EAAE,OAAO,EAAE,EACX,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CACtD,CAAC;QACF,OAAO,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,OAAe;QACtC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAC5C,OAAO,EACP,EAAE,OAAO,EAAE,EACX,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CACtD,CAAC;QACF,OAAO,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,iBAAyB;QACrD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAC5C,OAAO,EACP,EAAE,iBAAiB,EAAE,EACrB,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CACtD,CAAC;QACF,OAAO,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,eAAuB;QAChD,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;IAC9D,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;QAC9B,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,IAAI;QACpD,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,IAAI;QAChD,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,IAAI;QAC5C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { PaymentReferencePort } from '@endora-commerce/contracts';
3
+ /**
4
+ * The one write the four payment gateways make into this module's table
5
+ * (feature 075, Phase P) — the write side of
6
+ * `PaymentReadPort.findByExternalReference`.
7
+ *
8
+ * Each gateway opens its own provider object for an attempt (a Stripe
9
+ * PaymentIntent or Checkout Session, a TPay transaction, a PayU order, an
10
+ * Autopay transaction) and then records that object's identifier on the
11
+ * attempt, so a later provider event carrying only the provider's reference
12
+ * resolves back to a payment. Four modules were doing it with
13
+ * `em.findOne(Payment, …)` and a field assignment, which is this module's row
14
+ * being written by somebody else's `EntityManager`.
15
+ *
16
+ * It is deliberately not on the Command Bus, and the four call sites all say
17
+ * why in their own words: this is provider-integration bookkeeping, not an
18
+ * operator decision. The state change an operator is answerable for is the
19
+ * settlement, and that is audited in the payments/orders flow.
20
+ */
21
+ export declare class PaymentReferenceService implements PaymentReferencePort {
22
+ private readonly emFactory;
23
+ constructor(emFactory: () => EntityManager);
24
+ stampExternalReference(paymentId: string, externalReference: string): Promise<boolean>;
25
+ stampExternalReferenceIfAbsent(paymentId: string, externalReference: string): Promise<boolean>;
26
+ mergeProviderDetails(paymentId: string, patch: Record<string, unknown>): Promise<boolean>;
27
+ private stamp;
28
+ }
29
+ //# sourceMappingURL=payment-reference-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-reference-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/payment-reference-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGvE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,uBAAwB,YAAW,oBAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQtF,8BAA8B,CAClC,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,OAAO,CAAC;IAMb,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,OAAO,CAAC;YAYL,KAAK;CAqBpB"}