@saasicat/nest 1.0.0-rc.20 → 1.0.0-rc.21

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 (112) hide show
  1. package/dist/.build-stamp +1 -1
  2. package/dist/_entries.cjs +3570 -2574
  3. package/dist/admin/index.d.cts +5 -4
  4. package/dist/admin/index.d.ts +5 -4
  5. package/dist/admin/index.js +2 -1
  6. package/dist/{admin-resources.module-aOJ_0rur.d.cts → admin-resources.module-B4XNvdH-.d.cts} +0 -1
  7. package/dist/{admin-resources.module-BENMc0Wh.d.ts → admin-resources.module-CWqdI9BV.d.ts} +0 -1
  8. package/dist/{admin-stats.service-cwLMrkGl.d.ts → admin-stats.service-CsLNt19Z.d.ts} +10 -6
  9. package/dist/{admin-stats.service-BJdzHCtJ.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +10 -6
  10. package/dist/{aggregation-CtIjlWHT.d.ts → aggregation-ChpZDh0g.d.ts} +20 -4
  11. package/dist/{aggregation-BHo7a_In.d.cts → aggregation-OpnhwzF5.d.cts} +20 -4
  12. package/dist/billing/index.d.cts +23 -16
  13. package/dist/billing/index.d.ts +23 -16
  14. package/dist/billing/index.js +20 -15
  15. package/dist/catalog/index.d.cts +17 -8
  16. package/dist/catalog/index.d.ts +17 -8
  17. package/dist/catalog/index.js +5 -3
  18. package/dist/{catalog.module-B5E8tsl7.d.ts → catalog.module-C9O7BxRq.d.ts} +80 -7
  19. package/dist/{catalog.module-DKVvsPsl.d.cts → catalog.module-T-I5Ricz.d.cts} +80 -7
  20. package/dist/checkout-offer/index.d.cts +5 -4
  21. package/dist/checkout-offer/index.d.ts +5 -4
  22. package/dist/checkout-offer/index.js +15 -14
  23. package/dist/{checkout-offer.service-BP4CoZle.d.ts → checkout-offer.service-BeHgYikI.d.ts} +51 -8
  24. package/dist/{checkout-offer.service-Bk5mHEoR.d.cts → checkout-offer.service-CCVxG9b2.d.cts} +51 -8
  25. package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
  26. package/dist/chunk-3RLWML2S.js +896 -0
  27. package/dist/{chunk-DTIYUJH5.js → chunk-4SDSYV4A.js} +16 -6
  28. package/dist/{chunk-X4PDUYBX.js → chunk-55QEDAMX.js} +106 -302
  29. package/dist/chunk-6PJE7EWO.js +185 -0
  30. package/dist/{chunk-CRCNY3U6.js → chunk-7NJKVC5X.js} +54 -14
  31. package/dist/{chunk-RSAPMUGB.js → chunk-AOS2JPDD.js} +67 -64
  32. package/dist/{chunk-NCXGXNQK.js → chunk-CQ2ZZMTD.js} +174 -72
  33. package/dist/{chunk-NEICZJE4.js → chunk-F5X66HF5.js} +1 -1
  34. package/dist/{chunk-323FB6TD.js → chunk-FEQZCWUL.js} +67 -19
  35. package/dist/{chunk-FQS5FPH2.js → chunk-G6EZWECL.js} +42 -21
  36. package/dist/{chunk-XDRJ4FRG.js → chunk-JBCC6C3M.js} +84 -20
  37. package/dist/chunk-LF6J4YYN.js +301 -0
  38. package/dist/{chunk-OAUN2FPJ.js → chunk-LLYVYRGJ.js} +50 -21
  39. package/dist/{chunk-6ISJMNW7.js → chunk-M47BNEY2.js} +125 -108
  40. package/dist/{chunk-U465NU23.js → chunk-NDLC5GYK.js} +40 -33
  41. package/dist/{chunk-YFCTBLM5.js → chunk-QPVDCKYS.js} +237 -60
  42. package/dist/{chunk-IORLRWJZ.js → chunk-R5YCJBHY.js} +2 -2
  43. package/dist/{chunk-QBF7KLSA.js → chunk-TBBZWZQT.js} +8 -6
  44. package/dist/chunk-TOEFN7DN.js +295 -0
  45. package/dist/{chunk-ZUOCCEWB.js → chunk-WHJSYKNC.js} +2 -2
  46. package/dist/chunk-WOEJ6K7M.js +55 -0
  47. package/dist/{chunk-WYXLTSUZ.js → chunk-XSSWYPP5.js} +6 -4
  48. package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
  49. package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
  50. package/dist/{enforce-quota.interceptor-CLyCNVIm.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
  51. package/dist/{enforce-quota.interceptor-DQ_BN_jV.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
  52. package/dist/entitlement/index.d.cts +2 -1
  53. package/dist/entitlement/index.d.ts +2 -1
  54. package/dist/entitlement/index.js +6 -7
  55. package/dist/index.d.cts +27 -26
  56. package/dist/index.d.ts +27 -26
  57. package/dist/index.js +47 -33
  58. package/dist/{issuer-identity.check-B8Wzugbf.d.cts → issuer-identity.check-CBO0vcsq.d.cts} +5 -5
  59. package/dist/{issuer-identity.check-q-Uha5XY.d.ts → issuer-identity.check-Cz9CEMkZ.d.ts} +5 -5
  60. package/dist/{module-options-C5EBOxzm.d.ts → module-options-DmQ3G4sZ.d.ts} +17 -7
  61. package/dist/{module-options-Cr5FkELF.d.cts → module-options-qAOwinHR.d.cts} +17 -7
  62. package/dist/{payment-callback.service-DPUfjp-F.d.cts → payment-callback.service-CR84Xw1Z.d.cts} +31 -3
  63. package/dist/{payment-callback.service-DPUfjp-F.d.ts → payment-callback.service-CR84Xw1Z.d.ts} +31 -3
  64. package/dist/payments/index.d.cts +59 -5
  65. package/dist/payments/index.d.ts +59 -5
  66. package/dist/payments/index.js +11 -5
  67. package/dist/{payments.module-Da26YttL.d.cts → payments.module-BdUq9ot_.d.cts} +5 -2
  68. package/dist/{payments.module-C6r6dJdn.d.ts → payments.module-CQGv_VG7.d.ts} +5 -2
  69. package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
  70. package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
  71. package/dist/{plan-versions.service-BTlw-aEX.d.ts → plan-versions.service-C0N-Em2W.d.ts} +2 -48
  72. package/dist/{plan-versions.service-BM7lKLr1.d.cts → plan-versions.service-DARLh-qI.d.cts} +2 -48
  73. package/dist/platform/index.d.cts +20 -19
  74. package/dist/platform/index.d.ts +20 -19
  75. package/dist/platform/index.js +22 -21
  76. package/dist/promo/index.d.cts +9 -6
  77. package/dist/promo/index.d.ts +9 -6
  78. package/dist/promo/index.js +7 -6
  79. package/dist/{promo.module-YfF6ADDf.d.cts → promo.module-CKkECmU8.d.cts} +8 -2
  80. package/dist/{promo.module-KmIpp1Gb.d.ts → promo.module-DuIJdK3U.d.ts} +8 -2
  81. package/dist/promo.service-BYBu6dy3.d.cts +215 -0
  82. package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
  83. package/dist/registration/index.d.cts +29 -4
  84. package/dist/registration/index.d.ts +29 -4
  85. package/dist/registration/index.js +12 -5
  86. package/dist/subscriber/index.d.cts +2 -2
  87. package/dist/subscriber/index.d.ts +2 -2
  88. package/dist/subscriber/index.js +5 -4
  89. package/dist/{subscriber.service-BZsvHw1V.d.cts → subscriber.service-5W6Gi-1B.d.cts} +14 -3
  90. package/dist/{subscriber.service-BZsvHw1V.d.ts → subscriber.service-5W6Gi-1B.d.ts} +14 -3
  91. package/dist/subscription-contract/index.d.cts +3 -3
  92. package/dist/subscription-contract/index.d.ts +3 -3
  93. package/dist/subscription-contract/index.js +11 -9
  94. package/dist/{subscription-contract.service-Duv_Ns1W.d.ts → subscription-contract.service-CPZrTM9C.d.ts} +1 -1
  95. package/dist/{subscription-contract.service-D7Qb_Ns-.d.cts → subscription-contract.service-CfNApLuP.d.cts} +1 -1
  96. package/dist/{tenant-billing.controller-0HZ_2DQH.d.ts → tenant-billing.controller-BLqbsONe.d.ts} +7 -6
  97. package/dist/{tenant-billing.controller-CN9DgasV.d.cts → tenant-billing.controller-DKbAqTbM.d.cts} +7 -6
  98. package/dist/{tenant-billing.module-CPybDf7c.d.cts → tenant-billing.module-ClbjzocC.d.cts} +24 -16
  99. package/dist/{tenant-billing.module-C1sLBCW-.d.ts → tenant-billing.module-DohgJJ2m.d.ts} +24 -16
  100. package/dist/testing/index.d.cts +11 -10
  101. package/dist/testing/index.d.ts +11 -10
  102. package/dist/testing/index.js +20 -20
  103. package/package.json +3 -3
  104. package/dist/chunk-3HT3REXB.js +0 -110
  105. package/dist/chunk-5BCYJ3QY.js +0 -44
  106. package/dist/chunk-QSBMFY4I.js +0 -33
  107. package/dist/chunk-U4X2YRRO.js +0 -214
  108. package/dist/chunk-XWAZNKAB.js +0 -638
  109. package/dist/contract-line-item-money-BWWQhRul.d.cts +0 -11
  110. package/dist/contract-line-item-money-BWWQhRul.d.ts +0 -11
  111. package/dist/promo.service-CBMm9RU7.d.cts +0 -113
  112. package/dist/promo.service-CBMm9RU7.d.ts +0 -113
@@ -0,0 +1,185 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-SHUYVCID.js";
4
+
5
+ // src/promo/math.ts
6
+ function round2(n) {
7
+ return Math.round(n * 100) / 100;
8
+ }
9
+ __name(round2, "round2");
10
+ function isTaxRatePercent(rate) {
11
+ return Number.isFinite(rate) && rate >= 0 && rate <= 100 && !(rate > 0 && rate < 1);
12
+ }
13
+ __name(isTaxRatePercent, "isTaxRatePercent");
14
+ function grossFromNet(net, vatRate) {
15
+ return round2(net * (1 + vatRate / 100));
16
+ }
17
+ __name(grossFromNet, "grossFromNet");
18
+ function netFromGross(gross, vatRate) {
19
+ return round2(gross * 100 / (100 + vatRate));
20
+ }
21
+ __name(netFromGross, "netFromGross");
22
+ function computeIncludedVat(grossAmount, vatRate) {
23
+ return round2(grossAmount * vatRate / (100 + vatRate));
24
+ }
25
+ __name(computeIncludedVat, "computeIncludedVat");
26
+
27
+ // src/subscription-contract/contract-line-item-money.ts
28
+ var MONTHS_PER_YEAR = 12;
29
+ function recordContractLinesMoney(lines, money) {
30
+ const shares = grossSharesOf(lines, money.taxRate);
31
+ return lines.map((line, index) => {
32
+ const priceGross = shares[index];
33
+ return {
34
+ ...line,
35
+ priceGross,
36
+ currency: money.currency,
37
+ taxRate: money.taxRate,
38
+ taxAmount: fromCents(toCents(priceGross) - toCents(line.priceNet))
39
+ };
40
+ });
41
+ }
42
+ __name(recordContractLinesMoney, "recordContractLinesMoney");
43
+ function grossSharesOf(lines, taxRate) {
44
+ const netSoFar = /* @__PURE__ */ new Map();
45
+ return lines.map((line) => {
46
+ const before = netSoFar.get(line.billingCycle) ?? 0;
47
+ const after = before + toCents(line.priceNet);
48
+ netSoFar.set(line.billingCycle, after);
49
+ return fromCents(grossCents(after, taxRate) - grossCents(before, taxRate));
50
+ });
51
+ }
52
+ __name(grossSharesOf, "grossSharesOf");
53
+ function contractTotalsOf(lines, contractCycle) {
54
+ let subtotalNet = 0;
55
+ let discountNet = 0;
56
+ let totalGross = 0;
57
+ for (const line of lines) {
58
+ const times = timesDueInOnePeriod(contractCycle, line.billingCycle);
59
+ const net = times * toCents(line.priceNet);
60
+ if (line.kind === "discount") discountNet -= net;
61
+ else subtotalNet += net;
62
+ totalGross += times * toCents(line.priceGross);
63
+ }
64
+ return {
65
+ subtotalNet: fromCents(subtotalNet),
66
+ discountNet: fromCents(discountNet),
67
+ totalNet: fromCents(subtotalNet - discountNet),
68
+ totalGross: fromCents(totalGross)
69
+ };
70
+ }
71
+ __name(contractTotalsOf, "contractTotalsOf");
72
+ function timesDueInOnePeriod(contractCycle, lineCycle) {
73
+ return contractCycle === "yearly" && lineCycle === "monthly" ? MONTHS_PER_YEAR : 1;
74
+ }
75
+ __name(timesDueInOnePeriod, "timesDueInOnePeriod");
76
+ function grossCents(netCents, taxRate) {
77
+ return toCents(grossFromNet(fromCents(netCents), taxRate));
78
+ }
79
+ __name(grossCents, "grossCents");
80
+ function toCents(amount) {
81
+ return Math.round(amount * 100);
82
+ }
83
+ __name(toCents, "toCents");
84
+ function fromCents(cents) {
85
+ return cents === 0 ? 0 : cents / 100;
86
+ }
87
+ __name(fromCents, "fromCents");
88
+
89
+ // src/subscription-contract/contract-refusals.ts
90
+ import { UnprocessableEntityException } from "@nestjs/common";
91
+ import { CONTRACT_ERROR_CODES } from "@saasicat/core";
92
+ function assertTaxRatePercent(field, taxRate) {
93
+ if (isTaxRatePercent(taxRate)) return;
94
+ throw new UnprocessableEntityException({
95
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_TAX_RATE_NOT_PERCENT,
96
+ message: `A subscription contract states a tax rate of ${taxRate} at ${field}, which is not a percentage: a rate lies from 0 to 100, and a value between 0 and 1 is refused as a fraction.`,
97
+ params: {
98
+ field,
99
+ taxRate
100
+ }
101
+ });
102
+ }
103
+ __name(assertTaxRatePercent, "assertTaxRatePercent");
104
+ function assertContractWindow(effectiveFrom, effectiveUntil) {
105
+ if (!effectiveUntil || effectiveUntil > effectiveFrom) return;
106
+ throw new UnprocessableEntityException({
107
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_INVALID_WINDOW,
108
+ message: "effectiveUntil must be after effectiveFrom."
109
+ });
110
+ }
111
+ __name(assertContractWindow, "assertContractWindow");
112
+ function assertOnePlanLine(lineItems) {
113
+ if (lineItems.filter((item) => item.kind === "plan").length === 1) return;
114
+ throw new UnprocessableEntityException({
115
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED,
116
+ message: "A subscription contract requires exactly one plan base item."
117
+ });
118
+ }
119
+ __name(assertOnePlanLine, "assertOnePlanLine");
120
+ var ADDED_UP_TOTALS = [
121
+ "subtotalNet",
122
+ "discountNet",
123
+ "totalNet",
124
+ "totalGross"
125
+ ];
126
+ function assertLinesAddUp(lineItems, priceSnapshot) {
127
+ const lines = contractTotalsOf(lineItems, priceSnapshot.billingCycle);
128
+ const field = ADDED_UP_TOTALS.find((total) => Math.round(lines[total] * 100) !== Math.round(priceSnapshot[total] * 100));
129
+ if (!field) return;
130
+ throw new UnprocessableEntityException({
131
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_LINES_DO_NOT_ADD_UP,
132
+ message: `The line items of a subscription contract add up to ${lines[field]} for priceSnapshot.${field}, but the contract states ${priceSnapshot[field]}.`,
133
+ params: {
134
+ field: `priceSnapshot.${field}`,
135
+ lines: lines[field],
136
+ stated: priceSnapshot[field]
137
+ }
138
+ });
139
+ }
140
+ __name(assertLinesAddUp, "assertLinesAddUp");
141
+ function assertNoNegativeDiscount(data) {
142
+ const negative = (data.priceSnapshot.discountNet < 0 ? {
143
+ field: "priceSnapshot.discountNet",
144
+ amount: data.priceSnapshot.discountNet
145
+ } : null) ?? negativeResolvedAmount("promotionSnapshots", data.promotionSnapshots) ?? negativeResolvedAmount("promoCodeSnapshots", data.promoCodeSnapshots);
146
+ if (!negative) return;
147
+ throw new UnprocessableEntityException({
148
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_DISCOUNT_NEGATIVE,
149
+ message: `A subscription contract states a discount of ${negative.amount} at ${negative.field}; a discount takes money off and is never negative.`,
150
+ params: {
151
+ field: negative.field,
152
+ amount: negative.amount
153
+ }
154
+ });
155
+ }
156
+ __name(assertNoNegativeDiscount, "assertNoNegativeDiscount");
157
+ function negativeResolvedAmount(list, snapshots) {
158
+ for (const [index, snapshot] of (snapshots ?? []).entries()) {
159
+ const amount = snapshot?.resolvedAmountNet;
160
+ if (typeof amount === "number" && amount < 0) {
161
+ return {
162
+ field: `${list}[${index}].resolvedAmountNet`,
163
+ amount
164
+ };
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+ __name(negativeResolvedAmount, "negativeResolvedAmount");
170
+
171
+ export {
172
+ round2,
173
+ isTaxRatePercent,
174
+ grossFromNet,
175
+ netFromGross,
176
+ computeIncludedVat,
177
+ recordContractLinesMoney,
178
+ grossSharesOf,
179
+ contractTotalsOf,
180
+ assertTaxRatePercent,
181
+ assertContractWindow,
182
+ assertOnePlanLine,
183
+ assertLinesAddUp,
184
+ assertNoNegativeDiscount
185
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
- PLAN_CATALOG_TOKEN
3
- } from "./chunk-323FB6TD.js";
2
+ PLAN_CATALOG_SETTINGS_TOKEN
3
+ } from "./chunk-FEQZCWUL.js";
4
4
  import {
5
5
  codedError
6
6
  } from "./chunk-J7NJRK3K.js";
@@ -78,24 +78,24 @@ var PaymentGatewayRegistry = class {
78
78
  static {
79
79
  __name(this, "PaymentGatewayRegistry");
80
80
  }
81
- catalog;
81
+ settings;
82
82
  accounts = /* @__PURE__ */ new Map();
83
83
  takesNew;
84
- constructor(catalog, gateways) {
85
- this.catalog = catalog;
86
- const problems = paymentAccountProblems(catalog.payments, gateways);
84
+ constructor(settings, gateways) {
85
+ this.settings = settings;
86
+ const problems = paymentAccountProblems(settings.payments, gateways);
87
87
  if (problems.length > 0) {
88
88
  throw new Error(`Payments cannot start:
89
89
  - ${problems.join("\n- ")}`);
90
90
  }
91
- for (const [name, account] of Object.entries(catalog.payments.accounts)) {
91
+ for (const [name, account] of Object.entries(settings.payments.accounts)) {
92
92
  this.accounts.set(name, {
93
93
  name,
94
94
  provider: account.provider,
95
95
  gateway: gateways[name]
96
96
  });
97
97
  }
98
- this.takesNew = catalog.payments.newPaymentMethods;
98
+ this.takesNew = settings.payments.newPaymentMethods;
99
99
  }
100
100
  /** The account a callback names, or `null` when none is configured under it. */
101
101
  account(name) {
@@ -109,7 +109,7 @@ var PaymentGatewayRegistry = class {
109
109
  }
110
110
  /** The origins a gateway's form may send a person back to. */
111
111
  returnUrlOrigins() {
112
- return this.catalog.payments.returnUrlOrigins;
112
+ return this.settings.payments.returnUrlOrigins;
113
113
  }
114
114
  /** The account a new payment method is taken at, or `null` when none takes new ones. */
115
115
  forNewPaymentMethods() {
@@ -117,24 +117,24 @@ var PaymentGatewayRegistry = class {
117
117
  const account = this.accounts.get(this.takesNew);
118
118
  return {
119
119
  ...account,
120
- methods: this.catalog.payments.accounts[this.takesNew].methods
120
+ methods: this.settings.payments.accounts[this.takesNew].methods
121
121
  };
122
122
  }
123
123
  };
124
124
  PaymentGatewayRegistry = _ts_decorate([
125
125
  Injectable(),
126
- _ts_param(0, Inject(PLAN_CATALOG_TOKEN)),
126
+ _ts_param(0, Inject(PLAN_CATALOG_SETTINGS_TOKEN)),
127
127
  _ts_param(1, Inject(PAYMENT_GATEWAYS_TOKEN)),
128
128
  _ts_metadata("design:type", Function),
129
129
  _ts_metadata("design:paramtypes", [
130
- typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
130
+ typeof PlanCatalogSettings === "undefined" ? Object : PlanCatalogSettings,
131
131
  typeof Readonly === "undefined" ? Object : Readonly
132
132
  ])
133
133
  ], PaymentGatewayRegistry);
134
134
 
135
135
  // src/payments/payment-callback.service.ts
136
136
  import { BadRequestException, Inject as Inject2, Injectable as Injectable2, Logger, NotFoundException } from "@nestjs/common";
137
- import { PAYMENT_ERROR_CODES, isPaymentCallbackRejectedError } from "@saasicat/core";
137
+ import { PAYMENT_ERROR_CODES, isForeignPaymentMethodReferenceError, isPaymentCallbackRejectedError } from "@saasicat/core";
138
138
  function _ts_decorate2(decorators, target, key, desc) {
139
139
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
140
140
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -207,7 +207,7 @@ var PaymentCallbackService = class _PaymentCallbackService {
207
207
  afterCommit: /* @__PURE__ */ __name((step) => afterCommit.push(step), "afterCommit")
208
208
  };
209
209
  if (event.kind === "payment-method-confirmed") {
210
- const effect = await handler.confirmed(event, context);
210
+ const effect = await this.confirm(handler, event, context);
211
211
  if (effect === "nothing-to-do") {
212
212
  await this.log.releaseSession(account, event.eventId, tx);
213
213
  }
@@ -230,6 +230,42 @@ var PaymentCallbackService = class _PaymentCallbackService {
230
230
  }
231
231
  return "handled";
232
232
  }
233
+ /**
234
+ * Hands the confirmation to its handler, and names the one refusal that
235
+ * will never come out differently.
236
+ *
237
+ * A reference belongs to one subscriber for good (`SC-SEC-014`), so a
238
+ * confirmation refused for that reason is refused on every delivery. It
239
+ * still leaves by throwing, which rolls this transaction back: by the time
240
+ * it is raised a handler has written something that must not stand on its
241
+ * own — the setup marked complete on the tenant's path, the whole
242
+ * activation on the sign-up's. Answering the gateway anything else would
243
+ * commit one of those without the payment method that justifies it.
244
+ *
245
+ * The gateway therefore retries until it gives up, and the claim rolls back
246
+ * with everything else, so nothing durable is left to say why. This line is
247
+ * what says it.
248
+ *
249
+ * There is no terminal state for it, and that is worth knowing before an
250
+ * operator waits for one: the webhook route has no filter for this, so every
251
+ * redelivery is answered `500`, for as long as the provider keeps trying. A
252
+ * provider that disables an endpoint after sustained failures would take the
253
+ * account's other callbacks with it. Giving the event an end would mean
254
+ * keeping the claim while undoing what the handler wrote, which needs a
255
+ * savepoint the port does not have — so it is named here rather than
256
+ * implied. It names the account, the reference and the subject the
257
+ * event was about — never the subscriber the reference belongs to, which is
258
+ * on the other side of the boundary being refused.
259
+ */
260
+ async confirm(handler, event, context) {
261
+ try {
262
+ return await handler.confirmed(event, context);
263
+ } catch (error) {
264
+ if (!isForeignPaymentMethodReferenceError(error)) throw error;
265
+ this.logger.error(`Payment event ${event.eventId} at '${context.gatewayAccount}' confirms payment method '${error.paymentMethodRef}' for ${subjectOf(event)}, and that reference belongs to another subscriber; nothing was recorded, and every delivery of this event is refused the same way.`);
266
+ throw error;
267
+ }
268
+ }
233
269
  async read(readCallback, callback, account) {
234
270
  try {
235
271
  return await readCallback(callback);
@@ -256,6 +292,10 @@ PaymentCallbackService = _ts_decorate2([
256
292
  typeof TransactionRunner === "undefined" ? Object : TransactionRunner
257
293
  ])
258
294
  ], PaymentCallbackService);
295
+ function subjectOf(event) {
296
+ return event.subject.kind === "registration" ? `sign-up ${event.subject.pendingRegistrationId}` : `subscriber ${event.subject.subscriberId}`;
297
+ }
298
+ __name(subjectOf, "subjectOf");
259
299
  function summaryOf(event) {
260
300
  const subject = event.subject.kind === "registration" ? {
261
301
  registration: event.subject.pendingRegistrationId