@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,182 @@
1
+ import { defineModuleErrorCodes, defineModuleManifest, defineModuleSettingsManifest, } from '@endora-commerce/contracts';
2
+ /**
3
+ * The error codes this module declares (D-182,
4
+ * `specs/090-module-owned-error-codes/`).
5
+ *
6
+ * Branded, so a typo at a raise site is a compile error rather than a code that
7
+ * travels the whole path and renders raw to whoever reads it. They live in this
8
+ * file because it is the one the package publishes at its root subpath: a
9
+ * sibling module under `src/` is reachable through no subpath at all, so the
10
+ * raise sites could name it and a consumer branching on the code could not.
11
+ *
12
+ * Declaring them in `errorCodes` below is a separate act and is what routes the
13
+ * sentence to this package's own `i18n/` bundle; the reasoning for the three is
14
+ * written there.
15
+ */
16
+ export const paymentsErrorCodes = defineModuleErrorCodes([
17
+ 'PAYMENT_NOT_DUE',
18
+ 'PAYMENT_ORDER_CLOSED',
19
+ 'PAYMENT_ADAPTER_UNAVAILABLE',
20
+ ]);
21
+ export const paymentsSettingsManifest = defineModuleSettingsManifest({
22
+ moduleCode: 'payments',
23
+ groups: [{ code: 'payments', name: 'Payments' }],
24
+ settings: [
25
+ {
26
+ code: 'payments.enabled',
27
+ name: 'Payments enabled',
28
+ // The description used to describe the settlement half only, and stopped
29
+ // exactly where an operator most needs it: it never said that this module
30
+ // contributes every built-in payment adapter, so switching it off empties
31
+ // checkout's payment list and the shop stops taking orders. An operator
32
+ // cannot learn a switch's consequence from anywhere but the switch.
33
+ description: 'Switches the whole payment capability on or off. While it is off the platform offers no payment method at all: this module contributes the four built-in payment adapters (bank transfer, in-person pickup, credit limit and the gateway placeholder), so every payment method configured against one of them disappears from checkout and an order naming it is refused. Unless another installed integration supplies a payment method of its own, that means the shop stops taking orders. The settlement lifecycle stops with it: the receive_payment ingress, both retry paths (the operator\'s and the buyer\'s), the per-order payment history and the payment-status e-mail. Nothing is dropped — every payment, its status transitions and its provider references stay in the database, an order mid-settlement keeps its record, and every payment method, permission and setting comes back exactly as configured when you switch it on again. It is not an uninstall, and it is not the way to switch off a single payment provider: a gateway integration depends on this module, so the platform refuses to switch this off while such an integration is still on — switch that integration off instead.',
34
+ groupCode: 'payments',
35
+ valueType: 'boolean',
36
+ defaultValue: true,
37
+ },
38
+ ],
39
+ });
40
+ /**
41
+ * Payments module — manifest backfill (Module Lifecycle, feature 018).
42
+ *
43
+ * Predates the lifecycle system; this manifest is the static record
44
+ * required so the module participates in the registry. No install /
45
+ * uninstall hook today — the module's schema is owned by earlier
46
+ * platform-wide migrations.
47
+ */
48
+ export const manifest = defineModuleManifest({
49
+ id: 'payments',
50
+ name: 'Payments',
51
+ // What renders beside the activation control on `/platform/modules` is this
52
+ // string, not the activation Setting's — the Setting's description is served
53
+ // in the settings DTO and rendered on no screen today. So the consequence has
54
+ // to be legible here too, in one sentence: 'Payment driver abstraction and PSP
55
+ // integrations' told an operator nothing about what flipping the switch does.
56
+ description: 'The platform payment capability: the built-in payment adapters (bank transfer, in-person pickup, credit limit, gateway), the payment record every order carries, and the settlement lifecycle that PSP integrations plug into. Switched off, checkout offers no payment method and the shop takes no orders.',
57
+ version: '1.0.0',
58
+ // Feature 075 Phase C — `customer_accounts` joins the five that were already
59
+ // here: the payment-status e-mail resolves its recipient over
60
+ // `customerAccountReadPort` instead of reading the `CustomerAccount` entity.
61
+ // `orders` was already declared, which D-78 point 2 requires of the one
62
+ // co-transactional seam kept in `receive-payment-handler.ts` — the FK
63
+ // `payments_order_fk` had required it anyway.
64
+ // `organizations` joins for the buyer's retry route (issue #264): a suspended
65
+ // or blocked Organization may not pay an order any more than it may place
66
+ // one, and the guard is `organizationReadPort.assertCanTransact`. It is
67
+ // already transitively before this module through `orders`, so the entry
68
+ // changes no install or migration order — it states the edge.
69
+ dependencies: [
70
+ 'auth',
71
+ 'customer_accounts',
72
+ 'delivery_methods',
73
+ 'orders',
74
+ 'organizations',
75
+ 'payment_methods',
76
+ 'transactional_emails',
77
+ ],
78
+ // Feature 073 (Constitution XVII) — the operator's activation control. It only
79
+ // became real in T126: until this module registered its own routes there was
80
+ // no seam for a gate to sit on.
81
+ activation: { settingCode: 'payments.enabled', default: true },
82
+ // This module owns its authority. It shipped owning none, enforcing
83
+ // `catalog:read` / `catalog:write` on all three of its admin routes — which
84
+ // meant an operator who could edit a product could read every payment's
85
+ // provider payload and, through the settlement ingress, declare an arbitrary
86
+ // payment successful, moving the order's payment status with no money having
87
+ // moved. Both codes were real, declared and enforced, so the permission
88
+ // inventory's two directions were clean over it, and D-173's `foreign-gate`
89
+ // sweep passes it deliberately (`catalog` is `nonDeactivatable`, so the
90
+ // availability coupling that sweep asks about can never bite).
91
+ //
92
+ // A pair rather than one `payments:manage`: reading an order's payment
93
+ // history is support and finance work, opening a retry and declaring a
94
+ // settlement are money operations, and an operator organisation separates
95
+ // them. No third code for the ingress, though it is the most dangerous of the
96
+ // three routes: `POST /api/v1/payments/receive` is explicitly transitional
97
+ // pending a signed PSP-webhook auth path, and a code minted for it now is one
98
+ // that has to be migrated out of every role later — the situation
99
+ // `PERMISSION_CATALOGUE` records as unfixable-in-place for
100
+ // `integrations:manage` and `audit_log:read`. Revisit the split when that
101
+ // webhook path lands, which is the moment the question is answerable.
102
+ //
103
+ // No manifest `action` goes with these, and that is a decision rather than an
104
+ // omission: this module has no screen. Its only admin surface is a tab on an
105
+ // `orders` route, so a palette action would have to declare `orders:read` as
106
+ // its `requiredPermission` to agree with the gate on its own `targetRoute` —
107
+ // the `ACTION_PERMISSION_DISAGREEMENTS` shape `check:action-route-permissions`
108
+ // exists to refuse. The honest discovery entry for the payment history is
109
+ // `orders`' own.
110
+ permissions: [
111
+ { code: 'payments:read', label: 'View payments' },
112
+ { code: 'payments:write', label: 'Record and retry payments' },
113
+ ],
114
+ settings: paymentsSettingsManifest,
115
+ // Feature 090 / D-182 — the error codes this module owns, and the first three
116
+ // this package has ever declared. It is not a Phase 3 migration: the prefix
117
+ // chain routes nothing to `payments` and this module held no bundle at all,
118
+ // so these are new codes rather than a family changing hands.
119
+ //
120
+ // All three are the buyer's own payment-retry refusals, and every one of them
121
+ // wore `VALIDATION_FAILED` with a prose sentence. That is the one code
122
+ // `localizeErrorEnvelope` returns *before* translating — the code is
123
+ // overloaded and several services carry machine-readable tokens in its
124
+ // message — so a Polish buyer trying to pay a declined order was answered in
125
+ // the raise site's English, whichever language they asked for. Same defect as
126
+ // the order-cancellation route, one route over: the buyer's *retry* control
127
+ // beside their *cancel* control.
128
+ //
129
+ // **Three codes and not one**, which is the question that had to be answered
130
+ // before any of them was minted. The cancellation repair collapsed four
131
+ // branches into one code because they were one `throw` behind a conjunction
132
+ // and the buyer was told the same sentence either way. These are three
133
+ // separate raise sites behind three orthogonal predicates, and the buyer's
134
+ // next move differs in each:
135
+ //
136
+ // * `PAYMENT_NOT_DUE` — the money is not the buyer's to pay: the order is
137
+ // paid, drawn against a credit limit (`deferred`), or refunded. Nothing
138
+ // to do.
139
+ // * `PAYMENT_ORDER_CLOSED` — the order's lifecycle status is terminal, so
140
+ // somebody cancelled it and the stock is already released. The buyer who
141
+ // still wants the goods places a new order.
142
+ // * `PAYMENT_ADAPTER_UNAVAILABLE` — the order is open and the money is
143
+ // still owed; the shop can no longer *start* a session for the method it
144
+ // was placed with. The buyer contacts the shop, and an operator repairs a
145
+ // configuration.
146
+ //
147
+ // The first two are orthogonal rather than alternative: an order can be
148
+ // unpaid and cancelled, or paid and open, so neither implies the other. The
149
+ // third is reached only once both have passed.
150
+ //
151
+ // `PAYMENT_ADAPTER_UNAVAILABLE` names the adapter rather than the method on
152
+ // purpose. The registry it fails on is this module's; `PAYMENT_METHOD_*` is
153
+ // the family `payment_methods` would reach for, and two modules declaring one
154
+ // code routes it to neither of them (D-182's collision rule).
155
+ //
156
+ // No `tokens`: none of the three raise sites passes a fourth argument, so
157
+ // `refusalToken` has nothing to read, and the bundle keys are flat.
158
+ errorCodes: [
159
+ { code: paymentsErrorCodes.PAYMENT_NOT_DUE },
160
+ { code: paymentsErrorCodes.PAYMENT_ORDER_CLOSED },
161
+ { code: paymentsErrorCodes.PAYMENT_ADAPTER_UNAVAILABLE },
162
+ ],
163
+ // The bundle those sentences live in. This module shipped none until now —
164
+ // it has no admin screen of its own — so the directory and this declaration
165
+ // arrive together.
166
+ i18n: { bundlesDir: 'i18n' },
167
+ docs: { dir: 'docs' },
168
+ // Feature 047 — admin-editable transactional email owned by this module.
169
+ transactionalEmails: [
170
+ {
171
+ code: 'payment_status_changed',
172
+ name: 'Payment status changed',
173
+ group: 'payments',
174
+ variables: [
175
+ { key: 'order.businessId', label: 'Order number', sampleValue: 'ORD-1042' },
176
+ { key: 'payment.statusLabel', label: 'Payment status', sampleValue: 'Paid' },
177
+ { key: 'payment.failureReason', label: 'Failure reason', sampleValue: 'Card declined' },
178
+ ],
179
+ },
180
+ ],
181
+ });
182
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * `payments.refunded_amount` — the cumulative refunded amount the `Payment`
4
+ * entity maps (`refundedAmount`), created by the module that maps it.
5
+ *
6
+ * Until feature 134 (feature 134, research D14) the
7
+ * only migration creating this column was `stripe`'s
8
+ * `Migration20260715T103358StripePaymentRefundedAmount`: an instance without
9
+ * `stripe` could not insert a payment, and `stripe`'s hard uninstall dropped a
10
+ * column this module reads and writes. That migration keeps its class name and
11
+ * has both bodies emptied; this one replaces it.
12
+ *
13
+ * **`if not exists`**, because the column is already present on every database
14
+ * that ran `stripe`'s migration, and there this must be a no-op that keeps
15
+ * every value.
16
+ *
17
+ * **`down()` is deliberately empty.** The `payments` table is created by the
18
+ * platform's frozen `core_commerce_init`, which a hard uninstall of this module
19
+ * never reverts, so the table and its rows outlive this module. Dropping the
20
+ * column would reset every surviving payment's refunded amount to zero on the
21
+ * next install, and nothing would report it: a down that removes one column
22
+ * from rows that survive is data loss, not a revert.
23
+ */
24
+ export declare class Migration20260925T115728PaymentsRefundedAmount extends Migration {
25
+ up(): Promise<void>;
26
+ down(): Promise<void>;
27
+ }
28
+ //# sourceMappingURL=20260925T115728_payments_refunded_amount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260925T115728_payments_refunded_amount.d.ts","sourceRoot":"","sources":["../../src/migrations/20260925T115728_payments_refunded_amount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,8CAA+C,SAAQ,SAAS;IAC5D,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAMnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
@@ -0,0 +1,32 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * `payments.refunded_amount` — the cumulative refunded amount the `Payment`
4
+ * entity maps (`refundedAmount`), created by the module that maps it.
5
+ *
6
+ * Until feature 134 (feature 134, research D14) the
7
+ * only migration creating this column was `stripe`'s
8
+ * `Migration20260715T103358StripePaymentRefundedAmount`: an instance without
9
+ * `stripe` could not insert a payment, and `stripe`'s hard uninstall dropped a
10
+ * column this module reads and writes. That migration keeps its class name and
11
+ * has both bodies emptied; this one replaces it.
12
+ *
13
+ * **`if not exists`**, because the column is already present on every database
14
+ * that ran `stripe`'s migration, and there this must be a no-op that keeps
15
+ * every value.
16
+ *
17
+ * **`down()` is deliberately empty.** The `payments` table is created by the
18
+ * platform's frozen `core_commerce_init`, which a hard uninstall of this module
19
+ * never reverts, so the table and its rows outlive this module. Dropping the
20
+ * column would reset every surviving payment's refunded amount to zero on the
21
+ * next install, and nothing would report it: a down that removes one column
22
+ * from rows that survive is data loss, not a revert.
23
+ */
24
+ export class Migration20260925T115728PaymentsRefundedAmount extends Migration {
25
+ async up() {
26
+ this.addSql(`alter table "payments" add column if not exists "refunded_amount" numeric(14,2) not null default '0';`);
27
+ }
28
+ async down() {
29
+ // Intentionally empty — see the class comment.
30
+ }
31
+ }
32
+ //# sourceMappingURL=20260925T115728_payments_refunded_amount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260925T115728_payments_refunded_amount.js","sourceRoot":"","sources":["../../src/migrations/20260925T115728_payments_refunded_amount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,8CAA+C,SAAQ,SAAS;IAClE,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CACT,uGAAuG,CACxG,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,+CAA+C;IACjD,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The `./migrations` subpath — every migration class this module owns, as one
3
+ * ordered `migrations` array.
4
+ *
5
+ * The array is what the platform reads when this module is **installed**:
6
+ * `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
7
+ * the package outright when it is absent (D-168).
8
+ *
9
+ * Listed in ascending timestamp, which orders **this module's own** migrations
10
+ * and nothing else (feature 081). Where the block sits relative to every other
11
+ * module's is decided by the manifest `dependencies` graph.
12
+ *
13
+ * The **named** exports stay, and the asymmetry with `./backend` — which
14
+ * publishes an array and no entity class by name (D-168) — is deliberate.
15
+ * `db/migrations-registry.generated.ts` imports each class by name from this
16
+ * specifier, and a migration class name is contract in a way an entity class
17
+ * name is not: `mikro_orm_migrations` persists it, so it is a string every
18
+ * already-migrated database holds.
19
+ *
20
+ * A class that is in neither the array nor the barrel is a migration that does
21
+ * not run: `migration:pending` reports nothing pending and the first symptom
22
+ * is a query against a table nobody created.
23
+ */
24
+ import { Migration20260925T115728PaymentsRefundedAmount } from './20260925T115728_payments_refunded_amount.js';
25
+ export declare const migrations: (typeof Migration20260925T115728PaymentsRefundedAmount)[];
26
+ export { Migration20260925T115728PaymentsRefundedAmount, };
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,8CAA8C,EAAE,MAAM,+CAA+C,CAAC;AAE/G,eAAO,MAAM,UAAU,2DAEtB,CAAC;AAEF,OAAO,EACL,8CAA8C,GAC/C,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The `./migrations` subpath — every migration class this module owns, as one
3
+ * ordered `migrations` array.
4
+ *
5
+ * The array is what the platform reads when this module is **installed**:
6
+ * `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
7
+ * the package outright when it is absent (D-168).
8
+ *
9
+ * Listed in ascending timestamp, which orders **this module's own** migrations
10
+ * and nothing else (feature 081). Where the block sits relative to every other
11
+ * module's is decided by the manifest `dependencies` graph.
12
+ *
13
+ * The **named** exports stay, and the asymmetry with `./backend` — which
14
+ * publishes an array and no entity class by name (D-168) — is deliberate.
15
+ * `db/migrations-registry.generated.ts` imports each class by name from this
16
+ * specifier, and a migration class name is contract in a way an entity class
17
+ * name is not: `mikro_orm_migrations` persists it, so it is a string every
18
+ * already-migrated database holds.
19
+ *
20
+ * A class that is in neither the array nor the barrel is a migration that does
21
+ * not run: `migration:pending` reports nothing pending and the first symptom
22
+ * is a query against a table nobody created.
23
+ */
24
+ import { Migration20260925T115728PaymentsRefundedAmount } from './20260925T115728_payments_refunded_amount.js';
25
+ export const migrations = [
26
+ Migration20260925T115728PaymentsRefundedAmount,
27
+ ];
28
+ export { Migration20260925T115728PaymentsRefundedAmount, };
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,8CAA8C,EAAE,MAAM,+CAA+C,CAAC;AAE/G,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,8CAA8C;CAC/C,CAAC;AAEF,OAAO,EACL,8CAA8C,GAC/C,CAAC"}
@@ -0,0 +1,69 @@
1
+ ---
2
+ title: payments
3
+ description: Payment driver dispatch + settlement events
4
+ ---
5
+
6
+ # `payments`
7
+
8
+ Payment driver dispatch + settlement events. The module owns the driver
9
+ port (`gateway-adapter-port.ts`) and concrete in-tree drivers; per-vendor
10
+ adapters live in dedicated integration modules.
11
+
12
+ ## Drivers
13
+
14
+ | Driver | Behaviour |
15
+ | --- | --- |
16
+ | `bank-transfer-driver.ts` | Returns `NextAction.kind='awaiting_transfer'`; settlement happens out-of-band when the operator marks the order paid |
17
+ | `pickup-driver.ts` | `NextAction.kind='none'` for cash-on-pickup |
18
+ | `credit-limit-driver.ts` | Calls `CreditLimitService.reserve` inside the order-placement transaction |
19
+ | `gateway-adapter-port.ts` | Interface stub for external gateways; per-vendor implementations live outside the core |
20
+
21
+ ## Public surface
22
+
23
+ Three admin routes, gated on this module's own permission codes:
24
+
25
+ | Verb + Path | Permission | Purpose |
26
+ | --- | --- | --- |
27
+ | `GET /api/v1/admin/orders/:id/payments` | `payments:read` | Full payment history for one order, including each attempt's provider payload |
28
+ | `POST /api/v1/admin/orders/:id/payments/retry` | `payments:write` | Opens the next `Payment` attempt on an order |
29
+ | `POST /api/v1/payments/receive` | `payments:write` | The `receive_payment` settlement ingress: declares a payment succeeded or failed |
30
+
31
+ The pair is deliberate. Reading an order's payment history is support and
32
+ finance work; opening a retry and declaring a settlement are money operations,
33
+ and an operator organisation separates the two. There is no third code for the
34
+ settlement ingress even though it is the most dangerous of the three, because
35
+ that route is transitional pending a signed PSP-webhook auth path — see the
36
+ reasoning in `manifest.ts`.
37
+
38
+ Until `payments` declared these, all three routes were gated on `catalog:read`
39
+ and `catalog:write`, so an operator who could edit a product could read every
40
+ payment's provider payload and mark an arbitrary payment settled. **Upgrading:
41
+ a role that was reading payment data through `catalog:read` must be granted
42
+ `payments:read` explicitly on `/admin-roles`** — there is no migration, because
43
+ one granting `payments:read` to every holder of `catalog:read` would reproduce
44
+ exactly the over-grant this change removes.
45
+
46
+ The ingress body bounds its `providerDetails` to a flat map of scalars
47
+ (`operatorProviderDetailsSchema`): the column is persisted verbatim and served
48
+ back in full, so what an operator may write into it is bounded by our schema
49
+ rather than by the caller's payload. Gateway integrations build the payload in
50
+ code and are not subject to that bound.
51
+
52
+ Beyond the routes, orders consume the drivers via
53
+ `order-service.ts#placeOrder()`, and admin payment-status mutations go through
54
+ `/api/v1/admin/orders/:id/payment-status`, which `orders` owns.
55
+
56
+ The buyer's own retry (`POST /api/v1/orders/:orderId/payments/retry`) is a
57
+ customer route and authorises through `requireCustomer`, not a permission.
58
+
59
+ ## Events emitted
60
+
61
+ `payment.settled.v1`, `payment.failed.v1`, `payment.refunded.v1`.
62
+
63
+ ## Extension points
64
+
65
+ - **New gateway** — implement `gateway-adapter-port.ts`, register the
66
+ driver in the composition root, expose a configuration through the
67
+ `integrations` module.
68
+ - **Fraud / 3DS hooks** — slot in front of the driver's `reserve` call
69
+ before the order-placement transaction commits.
package/i18n/en.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "errors.PAYMENT_NOT_DUE": "This order is not awaiting payment.",
3
+ "errors.PAYMENT_ORDER_CLOSED": "This order is closed and can no longer be paid.",
4
+ "errors.PAYMENT_ADAPTER_UNAVAILABLE": "The payment method this order was placed with is no longer available."
5
+ }
package/i18n/pl.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "errors.PAYMENT_NOT_DUE": "To zamówienie nie oczekuje na płatność.",
3
+ "errors.PAYMENT_ORDER_CLOSED": "To zamówienie zostało zamknięte i nie można go już opłacić.",
4
+ "errors.PAYMENT_ADAPTER_UNAVAILABLE": "Metoda płatności wybrana przy składaniu tego zamówienia nie jest już dostępna."
5
+ }
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@endora-commerce/mod-payments",
3
+ "version": "0.100.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "description": "The platform payment capability: the built-in payment adapters (bank transfer, in-person pickup, credit limit, gateway), the payment record every order carries, and the settlement lifecycle that PSP integrations plug into. Switched off, checkout offers no payment method and the shop takes no orders.",
7
+ "license": "MIT",
8
+ "endora": {
9
+ "type": "module",
10
+ "id": "payments"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/endora-commerce/endora-commerce.git",
15
+ "directory": "packages/modules/payments"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/manifest.d.ts",
23
+ "default": "./dist/manifest.js"
24
+ },
25
+ "./backend": {
26
+ "types": "./dist/backend/index.d.ts",
27
+ "default": "./dist/backend/index.js"
28
+ },
29
+ "./migrations": {
30
+ "types": "./dist/migrations/index.d.ts",
31
+ "default": "./dist/migrations/index.js"
32
+ },
33
+ "./admin": {
34
+ "types": "./dist/admin/index.d.ts",
35
+ "default": "./dist/admin/index.js"
36
+ },
37
+ "./tailwind.css": "./tailwind.css",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "i18n",
43
+ "docs",
44
+ "tailwind.css"
45
+ ],
46
+ "engines": {
47
+ "node": ">=22.18.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@mikro-orm/core": "^6",
51
+ "@mikro-orm/migrations": "^6",
52
+ "@mikro-orm/postgresql": "^6",
53
+ "fastify": "^5",
54
+ "lucide-react": "^1",
55
+ "react": "^19",
56
+ "@endora-commerce/admin-kit": "0.100.0",
57
+ "@endora-commerce/contracts": "0.100.0",
58
+ "@endora-commerce/email-components": "0.100.0",
59
+ "@endora-commerce/mod-orders": "0.100.0",
60
+ "@endora-commerce/platform": "0.100.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@endora-commerce/admin-kit": {
64
+ "optional": true
65
+ },
66
+ "lucide-react": {
67
+ "optional": true
68
+ },
69
+ "react": {
70
+ "optional": true
71
+ }
72
+ },
73
+ "devDependencies": {
74
+ "@mikro-orm/core": "^6.6.13",
75
+ "@mikro-orm/migrations": "^6.6.13",
76
+ "@mikro-orm/postgresql": "^6.6.13",
77
+ "@types/node": "^22.9.0",
78
+ "@types/react": "^19.2.14",
79
+ "fastify": "^5.12.5",
80
+ "lucide-react": "^1.11.0",
81
+ "react": "^19.2.5",
82
+ "typescript": "^5.9.3",
83
+ "vitest": "^4.1.11",
84
+ "@endora-commerce/admin-kit": "0.100.0",
85
+ "@endora-commerce/email-components": "0.100.0",
86
+ "@endora-commerce/mod-orders": "0.100.0",
87
+ "@endora-commerce/platform": "0.100.0",
88
+ "@endora-commerce/contracts": "0.100.0"
89
+ },
90
+ "scripts": {
91
+ "build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
92
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
93
+ "lint": "eslint src",
94
+ "test": "vitest run"
95
+ }
96
+ }
package/tailwind.css ADDED
@@ -0,0 +1,14 @@
1
+ /* @endora-commerce/mod-payments — AUTO-GENERATED by `pnpm --filter backend run manifests:generate`.
2
+ *
3
+ * The `@source` directives this package asks its host to scan
4
+ * (`specs/110-instance-repository/contracts/admin-stylesheet-composition.md` R1).
5
+ * They resolve relative to **this file**, so they hold wherever the package is
6
+ * installed — a workspace link here, `node_modules` in a client's instance.
7
+ *
8
+ * The `dist` line is what a published tarball ships and is what an instance
9
+ * scans; the `src` line is inert there and is what keeps `pnpm --filter admin
10
+ * run dev` reading source in this repository. Do not edit: run
11
+ * `pnpm --filter backend run manifests:generate`.
12
+ */
13
+ @source "./dist/admin";
14
+ @source "./src/admin";